From c0eb1d1ea2458ee84f34f274cd7acf1e34fb4ea5 Mon Sep 17 00:00:00 2001 From: merback Date: Tue, 20 Feb 2024 17:02:38 +0100 Subject: [PATCH 01/13] ReadMe change try --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 49b1bf0d..6f60bf34 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # IgANets: Physics-informed isogeometric analysis networks +check + IgANets is a novel approach to combine the concept of deep operator learning with the mathematical framework of isogeometric analysis. From 9982d894e6c34686ae919551b9dd6e7f780818e8 Mon Sep 17 00:00:00 2001 From: merback Date: Wed, 22 May 2024 15:24:12 +0200 Subject: [PATCH 02/13] Unittests for NURBS --- .../unittest_nonuniform_rational_bspline.cxx | 2283 +++++++++++++++++ ...ttest_nonuniform_rational_bspline_eval.cxx | 1128 ++++++++ 2 files changed, 3411 insertions(+) create mode 100644 unittests/unittest_nonuniform_rational_bspline.cxx create mode 100644 unittests/unittest_nonuniform_rational_bspline_eval.cxx diff --git a/unittests/unittest_nonuniform_rational_bspline.cxx b/unittests/unittest_nonuniform_rational_bspline.cxx new file mode 100644 index 00000000..b2f28e02 --- /dev/null +++ b/unittests/unittest_nonuniform_rational_bspline.cxx @@ -0,0 +1,2283 @@ +/** + @file unittests/unittest_nonuniform_rational_bspline.cxx + + @brief B-Spline unittests + + @author Merle Backmeyer + + @copyright This file is part of the IgANet project + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#include +#include +#include + +#include +#include + +using namespace iganet::unittests::literals; + +class BSplineTest : public ::testing::Test { +public: + BSplineTest() { std::srand(std::time(nullptr)); } + +protected: + using real_t = iganet::unittests::real_t; + iganet::Options options; +}; + +TEST_F(BSplineTest, NonUniformRationalBSpline_parDim1_geoDim1_degrees1) { + EXPECT_THROW( + (iganet::NonUniformRationalBSpline({{{0.0_r, 0.0_r, 1.0_r}}})), + std::runtime_error); + iganet::NonUniformRationalBSpline bspline( + {{{0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r}}}); + EXPECT_EQ(bspline.parDim(), 1); + EXPECT_EQ(bspline.geoDim(), 2); + EXPECT_EQ(bspline.degree(0), 1); + EXPECT_EQ(bspline.nknots(0), 5); + EXPECT_EQ(bspline.ncoeffs(0), 3); + EXPECT_EQ(bspline.ncumcoeffs(), 3); + EXPECT_TRUE(bspline.is_nonuniform()); + EXPECT_FALSE(bspline.is_uniform()); +} + +TEST_F(BSplineTest, NonUniformRationalBSpline_parDim1_geoDim2_degrees1) { + EXPECT_THROW( + (iganet::NonUniformRationalBSpline({{{0.0_r, 0.0_r, 1.0_r}}})), + std::runtime_error); + iganet::NonUniformRationalBSpline bspline( + {{{0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r}}}); + EXPECT_EQ(bspline.parDim(), 1); + EXPECT_EQ(bspline.geoDim(), 3); + EXPECT_EQ(bspline.degree(0), 1); + EXPECT_EQ(bspline.nknots(0), 5); + EXPECT_EQ(bspline.ncoeffs(0), 3); + EXPECT_EQ(bspline.ncumcoeffs(), 3); +} + +TEST_F(BSplineTest, NonUniformRationalBSpline_parDim1_geoDim3_degrees1) { + EXPECT_THROW( + (iganet::NonUniformRationalBSpline({{{0.0_r, 0.0_r, 1.0_r}}})), + std::runtime_error); + iganet::NonUniformRationalBSpline bspline( + {{{0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r}}}); + EXPECT_EQ(bspline.parDim(), 1); + EXPECT_EQ(bspline.geoDim(), 4); + EXPECT_EQ(bspline.degree(0), 1); + EXPECT_EQ(bspline.nknots(0), 5); + EXPECT_EQ(bspline.ncoeffs(0), 3); + EXPECT_EQ(bspline.ncumcoeffs(), 3); +} + +TEST_F(BSplineTest, NonUniformRationalBSpline_parDim1_geoDim4_degrees1) { + EXPECT_THROW( + (iganet::NonUniformRationalBSpline({{{0.0_r, 0.0_r, 1.0_r}}})), + std::runtime_error); + iganet::NonUniformRationalBSpline bspline( + {{{0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r}}}); + EXPECT_EQ(bspline.parDim(), 1); + EXPECT_EQ(bspline.geoDim(), 4); + EXPECT_EQ(bspline.degree(0), 1); + EXPECT_EQ(bspline.nknots(0), 5); + EXPECT_EQ(bspline.ncoeffs(0), 3); + EXPECT_EQ(bspline.ncumcoeffs(), 3); +} + +TEST_F(BSplineTest, NonUniformRationalBSpline_parDim2_geoDim1_degrees12) { + iganet::NonUniformRationalBSpline bspline( + {{{0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 1.0_r, 1.0_r, 1.0_r}}}); + EXPECT_EQ(bspline.parDim(), 2); + EXPECT_EQ(bspline.geoDim(), 2); + EXPECT_EQ(bspline.degree(0), 1); + EXPECT_EQ(bspline.degree(1), 2); + EXPECT_EQ(bspline.nknots(0), 5); + EXPECT_EQ(bspline.nknots(1), 6); + EXPECT_EQ(bspline.ncoeffs(0), 3); + EXPECT_EQ(bspline.ncoeffs(1), 3); + EXPECT_EQ(bspline.ncumcoeffs(), 9); +} + +TEST_F(BSplineTest, NonUniformRationalBSpline_parDim2_geoDim2_degrees12) { + iganet::NonUniformRationalBSpline bspline( + {{{0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 1.0_r, 1.0_r, 1.0_r}}}); + EXPECT_EQ(bspline.parDim(), 2); + EXPECT_EQ(bspline.geoDim(), 3); + EXPECT_EQ(bspline.degree(0), 1); + EXPECT_EQ(bspline.degree(1), 2); + EXPECT_EQ(bspline.nknots(0), 5); + EXPECT_EQ(bspline.nknots(1), 6); + EXPECT_EQ(bspline.ncoeffs(0), 3); + EXPECT_EQ(bspline.ncoeffs(1), 3); + EXPECT_EQ(bspline.ncumcoeffs(), 9); +} + +TEST_F(BSplineTest, NonUniformRationalBSpline_parDim2_geoDim3_degrees12) { + iganet::NonUniformRationalBSpline bspline( + {{{0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 1.0_r, 1.0_r, 1.0_r}}}); + EXPECT_EQ(bspline.parDim(), 2); + EXPECT_EQ(bspline.geoDim(), 4); + EXPECT_EQ(bspline.degree(0), 1); + EXPECT_EQ(bspline.degree(1), 2); + EXPECT_EQ(bspline.nknots(0), 5); + EXPECT_EQ(bspline.nknots(1), 6); + EXPECT_EQ(bspline.ncoeffs(0), 3); + EXPECT_EQ(bspline.ncoeffs(1), 3); + EXPECT_EQ(bspline.ncumcoeffs(), 9); +} + +TEST_F(BSplineTest, NonUniformRationalBSpline_parDim2_geoDim4_degrees12) { + iganet::NonUniformRationalBSpline bspline( + {{{0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 1.0_r, 1.0_r, 1.0_r}}}); + EXPECT_EQ(bspline.parDim(), 2); + EXPECT_EQ(bspline.geoDim(), 4); + EXPECT_EQ(bspline.degree(0), 1); + EXPECT_EQ(bspline.degree(1), 2); + EXPECT_EQ(bspline.nknots(0), 5); + EXPECT_EQ(bspline.nknots(1), 6); + EXPECT_EQ(bspline.ncoeffs(0), 3); + EXPECT_EQ(bspline.ncoeffs(1), 3); + EXPECT_EQ(bspline.ncumcoeffs(), 9); +} + +TEST_F(BSplineTest, NonUniformRationalBSpline_parDim3_geoDim1_degrees123) { + iganet::NonUniformRationalBSpline bspline( + {{{0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}); + EXPECT_EQ(bspline.parDim(), 3); + EXPECT_EQ(bspline.geoDim(), 2); + EXPECT_EQ(bspline.degree(0), 1); + EXPECT_EQ(bspline.degree(1), 2); + EXPECT_EQ(bspline.degree(2), 3); + EXPECT_EQ(bspline.nknots(0), 5); + EXPECT_EQ(bspline.nknots(1), 6); + EXPECT_EQ(bspline.nknots(2), 9); + EXPECT_EQ(bspline.ncoeffs(0), 3); + EXPECT_EQ(bspline.ncoeffs(1), 3); + EXPECT_EQ(bspline.ncoeffs(2), 5); + EXPECT_EQ(bspline.ncumcoeffs(), 45); +} + +TEST_F(BSplineTest, NonUniformRationalBSpline_parDim3_geoDim2_degrees123) { + iganet::NonUniformRationalBSpline bspline( + {{{0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}); + EXPECT_EQ(bspline.parDim(), 3); + EXPECT_EQ(bspline.geoDim(), 3); + EXPECT_EQ(bspline.degree(0), 1); + EXPECT_EQ(bspline.degree(1), 2); + EXPECT_EQ(bspline.degree(2), 3); + EXPECT_EQ(bspline.nknots(0), 5); + EXPECT_EQ(bspline.nknots(1), 6); + EXPECT_EQ(bspline.nknots(2), 9); + EXPECT_EQ(bspline.ncoeffs(0), 3); + EXPECT_EQ(bspline.ncoeffs(1), 3); + EXPECT_EQ(bspline.ncoeffs(2), 5); + EXPECT_EQ(bspline.ncumcoeffs(), 45); +} + +TEST_F(BSplineTest, NonUniformRationalBSpline_parDim3_geoDim3_degrees123) { + iganet::NonUniformRationalBSpline bspline( + {{{0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}); + EXPECT_EQ(bspline.parDim(), 3); + EXPECT_EQ(bspline.geoDim(), 4); + EXPECT_EQ(bspline.degree(0), 1); + EXPECT_EQ(bspline.degree(1), 2); + EXPECT_EQ(bspline.degree(2), 3); + EXPECT_EQ(bspline.nknots(0), 5); + EXPECT_EQ(bspline.nknots(1), 6); + EXPECT_EQ(bspline.nknots(2), 9); + EXPECT_EQ(bspline.ncoeffs(0), 3); + EXPECT_EQ(bspline.ncoeffs(1), 3); + EXPECT_EQ(bspline.ncoeffs(2), 5); + EXPECT_EQ(bspline.ncumcoeffs(), 45); +} + +TEST_F(BSplineTest, NonUniformRationalBSpline_parDim3_geoDim4_degrees123) { + iganet::NonUniformRationalBSpline bspline( + {{{0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}); + EXPECT_EQ(bspline.parDim(), 3); + EXPECT_EQ(bspline.geoDim(), 4); + EXPECT_EQ(bspline.degree(0), 1); + EXPECT_EQ(bspline.degree(1), 2); + EXPECT_EQ(bspline.degree(2), 3); + EXPECT_EQ(bspline.nknots(0), 5); + EXPECT_EQ(bspline.nknots(1), 6); + EXPECT_EQ(bspline.nknots(2), 9); + EXPECT_EQ(bspline.ncoeffs(0), 3); + EXPECT_EQ(bspline.ncoeffs(1), 3); + EXPECT_EQ(bspline.ncoeffs(2), 5); + EXPECT_EQ(bspline.ncumcoeffs(), 45); +} + +TEST_F(BSplineTest, NonUniformRationalBSpline_parDim4_geoDim1_degrees1234) { + iganet::NonUniformRationalBSpline bspline( + {{{0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}}}); + EXPECT_EQ(bspline.parDim(), 4); + EXPECT_EQ(bspline.geoDim(), 2); + EXPECT_EQ(bspline.degree(0), 1); + EXPECT_EQ(bspline.degree(1), 2); + EXPECT_EQ(bspline.degree(2), 3); + EXPECT_EQ(bspline.degree(3), 4); + EXPECT_EQ(bspline.nknots(0), 5); + EXPECT_EQ(bspline.nknots(1), 6); + EXPECT_EQ(bspline.nknots(2), 9); + EXPECT_EQ(bspline.nknots(3), 11); + EXPECT_EQ(bspline.ncoeffs(0), 3); + EXPECT_EQ(bspline.ncoeffs(1), 3); + EXPECT_EQ(bspline.ncoeffs(2), 5); + EXPECT_EQ(bspline.ncoeffs(3), 6); + EXPECT_EQ(bspline.ncumcoeffs(), 270); +} + +TEST_F(BSplineTest, NonUniformRationalBSpline_parDim4_geoDim2_degrees1234) { + iganet::NonUniformRationalBSpline bspline( + {{{0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}}}); + EXPECT_EQ(bspline.parDim(), 4); + EXPECT_EQ(bspline.geoDim(), 3); + EXPECT_EQ(bspline.degree(0), 1); + EXPECT_EQ(bspline.degree(1), 2); + EXPECT_EQ(bspline.degree(2), 3); + EXPECT_EQ(bspline.degree(3), 4); + EXPECT_EQ(bspline.nknots(0), 5); + EXPECT_EQ(bspline.nknots(1), 6); + EXPECT_EQ(bspline.nknots(2), 9); + EXPECT_EQ(bspline.nknots(3), 11); + EXPECT_EQ(bspline.ncoeffs(0), 3); + EXPECT_EQ(bspline.ncoeffs(1), 3); + EXPECT_EQ(bspline.ncoeffs(2), 5); + EXPECT_EQ(bspline.ncoeffs(3), 6); + EXPECT_EQ(bspline.ncumcoeffs(), 270); +} + +TEST_F(BSplineTest, NonUniformRationalBSpline_parDim4_geoDim3_degrees1234) { + iganet::NonUniformRationalBSpline bspline( + {{{0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}}}); + EXPECT_EQ(bspline.parDim(), 4); + EXPECT_EQ(bspline.geoDim(), 4); + EXPECT_EQ(bspline.degree(0), 1); + EXPECT_EQ(bspline.degree(1), 2); + EXPECT_EQ(bspline.degree(2), 3); + EXPECT_EQ(bspline.degree(3), 4); + EXPECT_EQ(bspline.nknots(0), 5); + EXPECT_EQ(bspline.nknots(1), 6); + EXPECT_EQ(bspline.nknots(2), 9); + EXPECT_EQ(bspline.nknots(3), 11); + EXPECT_EQ(bspline.ncoeffs(0), 3); + EXPECT_EQ(bspline.ncoeffs(1), 3); + EXPECT_EQ(bspline.ncoeffs(2), 5); + EXPECT_EQ(bspline.ncoeffs(3), 6); + EXPECT_EQ(bspline.ncumcoeffs(), 270); +} + +TEST_F(BSplineTest, NonUniformRationalBSpline_parDim4_geoDim4_degrees1234) { + iganet::NonUniformRationalBSpline bspline( + {{{0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}}}); + EXPECT_EQ(bspline.parDim(), 4); + EXPECT_EQ(bspline.geoDim(), 4); + EXPECT_EQ(bspline.degree(0), 1); + EXPECT_EQ(bspline.degree(1), 2); + EXPECT_EQ(bspline.degree(2), 3); + EXPECT_EQ(bspline.degree(3), 4); + EXPECT_EQ(bspline.nknots(0), 5); + EXPECT_EQ(bspline.nknots(1), 6); + EXPECT_EQ(bspline.nknots(2), 9); + EXPECT_EQ(bspline.nknots(3), 11); + EXPECT_EQ(bspline.ncoeffs(0), 3); + EXPECT_EQ(bspline.ncoeffs(1), 3); + EXPECT_EQ(bspline.ncoeffs(2), 5); + EXPECT_EQ(bspline.ncoeffs(3), 6); + EXPECT_EQ(bspline.ncumcoeffs(), 270); +} + +TEST_F(BSplineTest, NonUniformRationalBSpline_init) { + { + iganet::NonUniformRationalBSpline bspline( + {{{0.0_r, 0.0_r, 0.25_r, 0.5_r, 0.75_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + EXPECT_TRUE(torch::equal(bspline.coeffs(0), torch::zeros(5, options))); + EXPECT_TRUE(torch::equal(bspline.coeffs(1), torch::zeros(5, options))); + } + + { + iganet::NonUniformRationalBSpline bspline( + {{{0.0_r, 0.0_r, 0.25_r, 0.5_r, 0.75_r, 1.0_r, 1.0_r}}}, + iganet::init::ones, options); + EXPECT_TRUE(torch::equal(bspline.coeffs(0), torch::ones(5, options))); + EXPECT_TRUE(torch::equal(bspline.coeffs(1), torch::ones(5, options))); + } + + { + iganet::NonUniformRationalBSpline bspline( + {{{0.0_r, 0.0_r, 0.25_r, 0.5_r, 0.75_r, 1.0_r, 1.0_r}}}, + iganet::init::linear, options); + EXPECT_TRUE( + torch::equal(bspline.coeffs(0), torch::linspace(0, 1, 5, options))); + EXPECT_TRUE( + torch::equal(bspline.coeffs(1), torch::ones(5, options))); + + } + + { + iganet::NonUniformRationalBSpline bspline( + {{{0.0_r, 0.0_r, 0.25_r, 0.5_r, 0.75_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + EXPECT_TRUE( + torch::equal(bspline.coeffs(0), torch::linspace(0, 1, 5, options))); + EXPECT_TRUE( + torch::equal(bspline.coeffs(1), torch::ones(5, options))); + } + + { + iganet::NonUniformRationalBSpline bspline( + {{{0.0_r, 0.0_r, 0.25_r, 0.5_r, 0.75_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + EXPECT_TRUE(torch::equal(bspline.coeffs(0), torch::zeros(5, options))); + EXPECT_TRUE(torch::equal(bspline.coeffs(1), torch::zeros(5, options))); + EXPECT_TRUE(torch::equal(bspline.coeffs(2), torch::zeros(5, options))); + } + + { + iganet::NonUniformRationalBSpline bspline( + {{{0.0_r, 0.0_r, 0.25_r, 0.5_r, 0.75_r, 1.0_r, 1.0_r}}}, + iganet::init::ones, options); + EXPECT_TRUE(torch::equal(bspline.coeffs(0), torch::ones(5, options))); + EXPECT_TRUE(torch::equal(bspline.coeffs(1), torch::ones(5, options))); + EXPECT_TRUE(torch::equal(bspline.coeffs(2), torch::ones(5, options))); + } + + { + iganet::NonUniformRationalBSpline bspline( + {{{0.0_r, 0.0_r, 0.25_r, 0.5_r, 0.75_r, 1.0_r, 1.0_r}}}, + iganet::init::linear, options); + EXPECT_TRUE( + torch::equal(bspline.coeffs(0), torch::linspace(0, 1, 5, options))); + EXPECT_TRUE(torch::equal(bspline.coeffs(1), torch::ones(5, options))); + EXPECT_TRUE(torch::equal(bspline.coeffs(2), torch::ones(5, options))); + } + + { + iganet::NonUniformRationalBSpline bspline( + {{{0.0_r, 0.0_r, 0.25_r, 0.5_r, 0.75_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + EXPECT_TRUE( + torch::equal(bspline.coeffs(0), torch::linspace(0, 1, 5, options))); + EXPECT_TRUE(torch::equal(bspline.coeffs(1), torch::ones(5, options))); + EXPECT_TRUE(torch::equal(bspline.coeffs(2), torch::ones(5, options))); + } + + { + iganet::NonUniformRationalBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.2_r, 0.4_r, 0.6_r, 0.8_r, 1.0_r, 1.0_r, + 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + EXPECT_TRUE(torch::equal(bspline.coeffs(0), torch::zeros(28, options))); + EXPECT_TRUE(torch::equal(bspline.coeffs(1), torch::zeros(28, options))); + EXPECT_TRUE(torch::equal(bspline.coeffs(2), torch::zeros(28, options))); + } + + { + iganet::NonUniformRationalBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.2_r, 0.4_r, 0.6_r, 0.8_r, 1.0_r, 1.0_r, + 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::ones, options); + EXPECT_TRUE(torch::equal(bspline.coeffs(0), torch::ones(28, options))); + EXPECT_TRUE(torch::equal(bspline.coeffs(1), torch::ones(28, options))); + EXPECT_TRUE(torch::equal(bspline.coeffs(2), torch::ones(28, options))); + } + + { + iganet::NonUniformRationalBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.2_r, 0.4_r, 0.6_r, 0.8_r, 1.0_r, 1.0_r, + 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::linear, options); + EXPECT_TRUE(torch::equal(bspline.coeffs(0), + torch::linspace(0, 1, 7, options).repeat(4))); + EXPECT_TRUE( + torch::equal(bspline.coeffs(1), + torch::linspace(0, 1, 4, options).repeat_interleave(7))); + EXPECT_TRUE( + torch::equal(bspline.coeffs(2), torch::ones(28, options))); + } + + { + iganet::NonUniformRationalBSpline bspline( + {{{0.0_r, 0.0_r, 0.25_r, 0.5_r, 0.75_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.2_r, 0.4_r, 0.6_r, 0.8_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + EXPECT_TRUE(torch::equal(bspline.coeffs(0), + torch::linspace(0, 1, 5, options).repeat(6))); + EXPECT_TRUE( + torch::equal(bspline.coeffs(1), + torch::linspace(0, 1, 6, options).repeat_interleave(5))); + EXPECT_TRUE( + torch::equal(bspline.coeffs(2), torch::ones(30, options))); + } + + { + iganet::NonUniformRationalBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.2_r, 0.4_r, 0.6_r, 0.8_r, 1.0_r, 1.0_r, + 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + EXPECT_TRUE(torch::equal(bspline.coeffs(0), torch::zeros(28, options))); + EXPECT_TRUE(torch::equal(bspline.coeffs(1), torch::zeros(28, options))); + EXPECT_TRUE(torch::equal(bspline.coeffs(2), torch::zeros(28, options))); + EXPECT_TRUE(torch::equal(bspline.coeffs(3), torch::zeros(28, options))); + } + + { + iganet::NonUniformRationalBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.2_r, 0.4_r, 0.6_r, 0.8_r, 1.0_r, 1.0_r, + 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::ones, options); + EXPECT_TRUE(torch::equal(bspline.coeffs(0), torch::ones(28, options))); + EXPECT_TRUE(torch::equal(bspline.coeffs(1), torch::ones(28, options))); + EXPECT_TRUE(torch::equal(bspline.coeffs(2), torch::ones(28, options))); + EXPECT_TRUE(torch::equal(bspline.coeffs(3), torch::ones(28, options))); + } + + { + iganet::NonUniformRationalBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.2_r, 0.4_r, 0.6_r, 0.8_r, 1.0_r, 1.0_r, + 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::linear, options); + EXPECT_TRUE(torch::equal(bspline.coeffs(0), + torch::linspace(0, 1, 7, options).repeat(4))); + EXPECT_TRUE( + torch::equal(bspline.coeffs(1), + torch::linspace(0, 1, 4, options).repeat_interleave(7))); + EXPECT_TRUE(torch::equal(bspline.coeffs(2), torch::ones(28, options))); + EXPECT_TRUE(torch::equal(bspline.coeffs(3), torch::ones(28, options))); + } + + { + iganet::NonUniformRationalBSpline bspline( + {{{0.0_r, 0.0_r, 0.25_r, 0.5_r, 0.75_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.2_r, 0.4_r, 0.6_r, 0.8_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + EXPECT_TRUE(torch::equal(bspline.coeffs(0), + torch::linspace(0, 1, 5, options).repeat(6))); + EXPECT_TRUE( + torch::equal(bspline.coeffs(1), + torch::linspace(0, 1, 6, options).repeat_interleave(5))); + EXPECT_TRUE(torch::equal(bspline.coeffs(2), torch::ones(30, options))); + EXPECT_TRUE(torch::equal(bspline.coeffs(3), torch::ones(30, options))); + } + + { + iganet::NonUniformRationalBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.2_r, 0.4_r, 0.6_r, 0.8_r, 1.0_r, 1.0_r, + 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + EXPECT_TRUE(torch::equal(bspline.coeffs(0), torch::zeros(28, options))); + EXPECT_TRUE(torch::equal(bspline.coeffs(1), torch::zeros(28, options))); + EXPECT_TRUE(torch::equal(bspline.coeffs(2), torch::zeros(28, options))); + EXPECT_TRUE(torch::equal(bspline.coeffs(3), torch::zeros(28, options))); + } + + { + iganet::NonUniformRationalBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.2_r, 0.4_r, 0.6_r, 0.8_r, 1.0_r, 1.0_r, + 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::ones, options); + EXPECT_TRUE(torch::equal(bspline.coeffs(0), torch::ones(28, options))); + EXPECT_TRUE(torch::equal(bspline.coeffs(1), torch::ones(28, options))); + EXPECT_TRUE(torch::equal(bspline.coeffs(2), torch::ones(28, options))); + EXPECT_TRUE(torch::equal(bspline.coeffs(3), torch::ones(28, options))); + } + + { + iganet::NonUniformRationalBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.2_r, 0.4_r, 0.6_r, 0.8_r, 1.0_r, 1.0_r, + 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::linear, options); + EXPECT_TRUE(torch::equal(bspline.coeffs(0), + torch::linspace(0, 1, 7, options).repeat(4))); + EXPECT_TRUE( + torch::equal(bspline.coeffs(1), + torch::linspace(0, 1, 4, options).repeat_interleave(7))); + EXPECT_TRUE(torch::equal(bspline.coeffs(2), torch::ones(28, options))); + EXPECT_TRUE(torch::equal(bspline.coeffs(3), torch::ones(28, options))); + } + + { + iganet::NonUniformRationalBSpline bspline( + {{{0.0_r, 0.0_r, 0.25_r, 0.5_r, 0.75_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.2_r, 0.4_r, 0.6_r, 0.8_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + EXPECT_TRUE(torch::equal(bspline.coeffs(0), + torch::linspace(0, 1, 5, options).repeat(6))); + EXPECT_TRUE( + torch::equal(bspline.coeffs(1), + torch::linspace(0, 1, 6, options).repeat_interleave(5))); + EXPECT_TRUE(torch::equal(bspline.coeffs(2), torch::ones(30, options))); + EXPECT_TRUE(torch::equal(bspline.coeffs(3), torch::ones(30, options))); + } +} + +TEST_F(BSplineTest, NonUniformRationalBSpline_uniform_refine) { + { + iganet::NonUniformRationalBSpline bspline({4, 5}); + iganet::NonUniformRationalBSpline bspline_ref({5, 6}); + bspline.uniform_refine(); + + EXPECT_TRUE(bspline.isclose(bspline_ref)); + } + + { + iganet::NonUniformRationalBSpline bspline({4, 5}); + iganet::NonUniformRationalBSpline bspline_ref({7, 8}); + bspline.uniform_refine(2); + + EXPECT_TRUE(bspline.isclose(bspline_ref)); + } + + { + iganet::NonUniformRationalBSpline bspline({4, 5}); + iganet::NonUniformRationalBSpline bspline_ref({5, 5}); + bspline.uniform_refine(1, 0); + + EXPECT_TRUE(bspline.isclose(bspline_ref)); + } + + { + iganet::NonUniformRationalBSpline bspline({4, 5}); + iganet::NonUniformRationalBSpline bspline_ref({5, 8}); + bspline.uniform_refine(1, 0).uniform_refine(2, 1); + + EXPECT_TRUE(bspline.isclose(bspline_ref)); + } +} + +TEST_F(BSplineTest, NonUniformRationalBSpline_copy_constructor) { + iganet::NonUniformRationalBSpline bspline_orig( + {4, 5}, iganet::init::greville, options); + iganet::NonUniformRationalBSpline bspline_copy(bspline_orig); + + bspline_orig.transform([](const std::array xi) { + return std::array{0.0_r, 1.0_r, 2.0_r, 3.0_r}; + }); + + EXPECT_TRUE(bspline_orig == bspline_copy); +} + +TEST_F(BSplineTest, NonUniformRationalBSpline_clone_constructor) { + iganet::NonUniformRationalBSpline bspline_ref( + {4, 5}, iganet::init::greville, options); + iganet::NonUniformRationalBSpline bspline_orig( + {4, 5}, iganet::init::greville, options); + iganet::NonUniformRationalBSpline bspline_clone(bspline_orig, true); + + bspline_orig.transform([](const std::array xi) { + return std::array{0.0_r, 1.0_r, 2.0_r, 3.0_r}; + }); + + EXPECT_TRUE(bspline_ref == bspline_clone); +} + +TEST_F(BSplineTest, NonUniformRationalBSpline_move_constructor) { + iganet::NonUniformRationalBSpline bspline_ref( + {7, 8}, iganet::init::greville, options); + auto bspline(iganet::NonUniformRationalBSpline( + {4, 5}, iganet::init::greville, options) + .uniform_refine(2)); + + EXPECT_TRUE(bspline.isclose(bspline_ref)); +} + +TEST_F(BSplineTest, NonUniformRationalBSpline_copy_coeffs_constructor) { + iganet::NonUniformRationalBSpline bspline_orig( + {4, 5}, iganet::init::greville, options); + iganet::NonUniformRationalBSpline bspline_copy( + bspline_orig, bspline_orig.coeffs()); + + bspline_orig.transform([](const std::array xi) { + return std::array{0.0_r, 1.0_r, 2.0_r, 3.0_r}; + }); + + EXPECT_TRUE(bspline_orig == bspline_copy); +} + +TEST_F(BSplineTest, NonUniformRationalBSpline_clone_coeffs_constructor) { + iganet::NonUniformRationalBSpline bspline_ref( + {4, 5}, iganet::init::greville, options); + iganet::NonUniformRationalBSpline bspline_orig( + {4, 5}, iganet::init::greville, options); + iganet::NonUniformRationalBSpline bspline_clone( + bspline_orig, bspline_orig.coeffs(), true); + + bspline_orig.transform([](const std::array xi) { + return std::array{0.0_r, 1.0_r, 2.0_r, 3.0_r}; + }); + + EXPECT_TRUE(bspline_ref == bspline_clone); +} + + +TEST_F(BSplineTest, NonUniformRationalBSpline_to_from_xml) { + { + iganet::NonUniformRationalBSpline bspline_out( + {4}, iganet::init::zeros, options); + + bspline_out.transform([](const std::array xi) { + return std::array{static_cast(std::rand()), 1.0}; + }); + + pugi::xml_document doc = bspline_out.to_xml(); + + iganet::NonUniformRationalBSpline bspline_in(options); + bspline_in.from_xml(doc); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW((iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW((iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW((iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW((iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching id + EXPECT_THROW((iganet::NonUniformRationalBSpline{}.from_xml(doc, 1)), + std::runtime_error); + } + + { + iganet::NonUniformRationalBSpline bspline_out( + {4}, iganet::init::zeros, options); + + bspline_out.transform([](const std::array xi) { + return std::array{static_cast(std::rand()), + static_cast(std::rand()), 1.0}; + }); + + pugi::xml_document doc = bspline_out.to_xml(); + + iganet::NonUniformRationalBSpline bspline_in(options); + bspline_in.from_xml(doc); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW((iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW((iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW((iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW((iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching id + EXPECT_THROW((iganet::NonUniformRationalBSpline{}.from_xml(doc, 1)), + std::runtime_error); + } + + { + iganet::NonUniformRationalBSpline bspline_out( + {4}, iganet::init::zeros, options); + + bspline_out.transform([](const std::array xi) { + return std::array{static_cast(std::rand()), + static_cast(std::rand()), + static_cast(std::rand()), 1.0}; + }); + + pugi::xml_document doc = bspline_out.to_xml(); + + iganet::NonUniformRationalBSpline bspline_in(options); + bspline_in.from_xml(doc); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW((iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW((iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW((iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW((iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching id + EXPECT_THROW((iganet::NonUniformRationalBSpline{}.from_xml(doc, 1)), + std::runtime_error); + } + + { + iganet::NonUniformRationalBSpline bspline_out( + {4}, iganet::init::zeros, options); + + bspline_out.transform([](const std::array xi) { + return std::array{ + static_cast(std::rand()), static_cast(std::rand()), + static_cast(std::rand()), static_cast(std::rand()), 1.0}; + }); + + pugi::xml_document doc = bspline_out.to_xml(); + + iganet::NonUniformRationalBSpline bspline_in(options); + bspline_in.from_xml(doc); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW((iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW((iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW((iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW((iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching id + EXPECT_THROW((iganet::NonUniformRationalBSpline{}.from_xml(doc, 1)), + std::runtime_error); + } + + { + iganet::NonUniformRationalBSpline bspline_out( + {4, 5}, iganet::init::zeros, options); + + bspline_out.transform([](const std::array xi) { + return std::array{static_cast(std::rand()), 1.0}; + }); + + pugi::xml_document doc = bspline_out.to_xml(); + + iganet::NonUniformRationalBSpline bspline_in(options); + bspline_in.from_xml(doc); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW((iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching id + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 1)), + std::runtime_error); + } + + { + iganet::NonUniformRationalBSpline bspline_out( + {4, 5}, iganet::init::zeros, options); + + bspline_out.transform([](const std::array xi) { + return std::array{static_cast(std::rand()), + static_cast(std::rand()), 1.0}; + }); + + pugi::xml_document doc = bspline_out.to_xml(); + + iganet::NonUniformRationalBSpline bspline_in(options); + bspline_in.from_xml(doc); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW((iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching id + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 1)), + std::runtime_error); + } + + { + iganet::NonUniformRationalBSpline bspline_out( + {4, 5}, iganet::init::zeros, options); + + bspline_out.transform([](const std::array xi) { + return std::array{static_cast(std::rand()), + static_cast(std::rand()), + static_cast(std::rand()), 1.0}; + }); + + pugi::xml_document doc = bspline_out.to_xml(); + + iganet::NonUniformRationalBSpline bspline_in(options); + bspline_in.from_xml(doc); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW((iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching id + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 1)), + std::runtime_error); + } + + { + iganet::NonUniformRationalBSpline bspline_out( + {4, 5}, iganet::init::zeros, options); + + bspline_out.transform([](const std::array xi) { + return std::array{ + static_cast(std::rand()), static_cast(std::rand()), + static_cast(std::rand()), static_cast(std::rand())}; + }); + + pugi::xml_document doc = bspline_out.to_xml(); + + iganet::NonUniformRationalBSpline bspline_in(options); + bspline_in.from_xml(doc); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW((iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching id + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 1)), + std::runtime_error); + } + + { + iganet::NonUniformRationalBSpline bspline_out( + {4, 5, 6}, iganet::init::zeros, options); + + bspline_out.transform([](const std::array xi) { + return std::array{static_cast(std::rand()), 1.0}; + }); + + pugi::xml_document doc = bspline_out.to_xml(); + + iganet::NonUniformRationalBSpline bspline_in(options); + bspline_in.from_xml(doc); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW((iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching id + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 1)), + std::runtime_error); + } + + { + iganet::NonUniformRationalBSpline bspline_out( + {4, 5, 6}, iganet::init::zeros, options); + + bspline_out.transform([](const std::array xi) { + return std::array{static_cast(std::rand()), + static_cast(std::rand()), 1.0}; + }); + + pugi::xml_document doc = bspline_out.to_xml(); + + iganet::NonUniformRationalBSpline bspline_in(options); + bspline_in.from_xml(doc); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW((iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching id + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 1)), + std::runtime_error); + } + + { + iganet::NonUniformRationalBSpline bspline_out( + {4, 5, 6}, iganet::init::zeros, options); + + bspline_out.transform([](const std::array xi) { + return std::array{static_cast(std::rand()), + static_cast(std::rand()), + static_cast(std::rand()), 1.0}; + }); + + pugi::xml_document doc = bspline_out.to_xml(); + + iganet::NonUniformRationalBSpline bspline_in(options); + bspline_in.from_xml(doc); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW((iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching id + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 1)), + std::runtime_error); + } + + { + iganet::NonUniformRationalBSpline bspline_out( + {4, 5, 6}, iganet::init::zeros, options); + + bspline_out.transform([](const std::array xi) { + return std::array{ + static_cast(std::rand()), static_cast(std::rand()), + static_cast(std::rand()), static_cast(std::rand())}; + }); + + pugi::xml_document doc = bspline_out.to_xml(); + + iganet::NonUniformRationalBSpline bspline_in(options); + bspline_in.from_xml(doc); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW((iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching id + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 1)), + std::runtime_error); + } + + { + iganet::NonUniformRationalBSpline bspline_out( + {4, 5, 6, 2}, iganet::init::zeros, options); + + bspline_out.transform([](const std::array xi) { + return std::array{static_cast(std::rand()), 1.0}; + }); + + pugi::xml_document doc = bspline_out.to_xml(); + + iganet::NonUniformRationalBSpline bspline_in(options); + bspline_in.from_xml(doc); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW((iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching id + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 1)), + std::runtime_error); + } + + { + iganet::NonUniformRationalBSpline bspline_out( + {4, 5, 6, 2}, iganet::init::zeros, options); + + bspline_out.transform([](const std::array xi) { + return std::array{static_cast(std::rand()), + static_cast(std::rand()), 1.0}; + }); + + pugi::xml_document doc = bspline_out.to_xml(); + + iganet::NonUniformRationalBSpline bspline_in(options); + bspline_in.from_xml(doc); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW((iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching id + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 1)), + std::runtime_error); + } + + { + iganet::NonUniformRationalBSpline bspline_out( + {4, 5, 6, 2}, iganet::init::zeros, options); + + bspline_out.transform([](const std::array xi) { + return std::array{static_cast(std::rand()), + static_cast(std::rand()), + static_cast(std::rand()), 1.0}; + }); + + pugi::xml_document doc = bspline_out.to_xml(); + + iganet::NonUniformRationalBSpline bspline_in(options); + bspline_in.from_xml(doc); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW((iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching id + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 1)), + std::runtime_error); + } + + { + iganet::NonUniformRationalBSpline bspline_out( + {4, 5, 6, 2}, iganet::init::zeros, options); + + bspline_out.transform([](const std::array xi) { + return std::array{ + static_cast(std::rand()), static_cast(std::rand()), + static_cast(std::rand()), static_cast(std::rand())}; + }); + + pugi::xml_document doc = bspline_out.to_xml(); + + iganet::NonUniformRationalBSpline bspline_in(options); + bspline_in.from_xml(doc); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW((iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching id + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_xml(doc, 1)), + std::runtime_error); + } +} + +TEST_F(BSplineTest, NonUniformRationalBSpline_to_from_json) { + { + iganet::NonUniformRationalBSpline bspline_out( + {4}, iganet::init::zeros, options); + + bspline_out.transform([](const std::array xi) { + return std::array{static_cast(std::rand()), 1.0}; + }); + + nlohmann::json json = bspline_out.to_json(); + + iganet::NonUniformRationalBSpline bspline_in(options); + bspline_in.from_json(json); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW((iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW((iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW((iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW((iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW((iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + } + + { + iganet::NonUniformRationalBSpline bspline_out( + {4}, iganet::init::zeros, options); + + bspline_out.transform([](const std::array xi) { + return std::array{static_cast(std::rand()), + static_cast(std::rand()), 1.0}; + }); + + nlohmann::json json = bspline_out.to_json(); + + iganet::NonUniformRationalBSpline bspline_in(options); + bspline_in.from_json(json); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW((iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW((iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW((iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW((iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW((iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + } + + { + iganet::NonUniformRationalBSpline bspline_out( + {4}, iganet::init::zeros, options); + + bspline_out.transform([](const std::array xi) { + return std::array{static_cast(std::rand()), + static_cast(std::rand()), + static_cast(std::rand()), 1.0}; + }); + + nlohmann::json json = bspline_out.to_json(); + + iganet::NonUniformRationalBSpline bspline_in(options); + bspline_in.from_json(json); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW((iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW((iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW((iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW((iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW((iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + } + + { + iganet::NonUniformRationalBSpline bspline_out( + {4}, iganet::init::zeros, options); + + bspline_out.transform([](const std::array xi) { + return std::array{ + static_cast(std::rand()), static_cast(std::rand()), + static_cast(std::rand()), static_cast(std::rand()),1.0}; + }); + + nlohmann::json json = bspline_out.to_json(); + + iganet::NonUniformRationalBSpline bspline_in(options); + bspline_in.from_json(json); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW((iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW((iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW((iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW((iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW((iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + } + + { + iganet::NonUniformRationalBSpline bspline_out( + {4, 5}, iganet::init::zeros, options); + + bspline_out.transform([](const std::array xi) { + return std::array{static_cast(std::rand()),1.0}; + }); + + nlohmann::json json = bspline_out.to_json(); + + iganet::NonUniformRationalBSpline bspline_in(options); + bspline_in.from_json(json); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW((iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW((iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW((iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW((iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW((iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + } + + { + iganet::NonUniformRationalBSpline bspline_out( + {4, 5}, iganet::init::zeros, options); + + bspline_out.transform([](const std::array xi) { + return std::array{static_cast(std::rand()), + static_cast(std::rand()),1.0}; + }); + + nlohmann::json json = bspline_out.to_json(); + + iganet::NonUniformRationalBSpline bspline_in(options); + bspline_in.from_json(json); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW((iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW((iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW((iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW((iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW((iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + } + + { + iganet::NonUniformRationalBSpline bspline_out( + {4, 5}, iganet::init::zeros, options); + + bspline_out.transform([](const std::array xi) { + return std::array{static_cast(std::rand()), + static_cast(std::rand()), + static_cast(std::rand()),1.0}; + }); + + nlohmann::json json = bspline_out.to_json(); + + iganet::NonUniformRationalBSpline bspline_in(options); + bspline_in.from_json(json); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW((iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW((iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW((iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW((iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW((iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + } + + { + iganet::NonUniformRationalBSpline bspline_out( + {4, 5}, iganet::init::zeros, options); + + bspline_out.transform([](const std::array xi) { + return std::array{ + static_cast(std::rand()), static_cast(std::rand()), + static_cast(std::rand()), static_cast(std::rand())}; + }); + + nlohmann::json json = bspline_out.to_json(); + + iganet::NonUniformRationalBSpline bspline_in(options); + bspline_in.from_json(json); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW((iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW((iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW((iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW((iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW((iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + } + + { + iganet::NonUniformRationalBSpline bspline_out( + {4, 5, 6}, iganet::init::zeros, options); + + bspline_out.transform([](const std::array xi) { + return std::array{static_cast(std::rand()), 1.0}; + }); + + nlohmann::json json = bspline_out.to_json(); + + iganet::NonUniformRationalBSpline bspline_in(options); + bspline_in.from_json(json); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW((iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW((iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + } + + { + iganet::NonUniformRationalBSpline bspline_out( + {4, 5, 6}, iganet::init::zeros, options); + + bspline_out.transform([](const std::array xi) { + return std::array{static_cast(std::rand()), + static_cast(std::rand()), 1.0}; + }); + + nlohmann::json json = bspline_out.to_json(); + + iganet::NonUniformRationalBSpline bspline_in(options); + bspline_in.from_json(json); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW((iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW((iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + } + + { + iganet::NonUniformRationalBSpline bspline_out( + {4, 5, 6}, iganet::init::zeros, options); + + bspline_out.transform([](const std::array xi) { + return std::array{static_cast(std::rand()), + static_cast(std::rand()), + static_cast(std::rand()), 1.0}; + }); + + nlohmann::json json = bspline_out.to_json(); + + iganet::NonUniformRationalBSpline bspline_in(options); + bspline_in.from_json(json); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW((iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW((iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + } + + { + iganet::NonUniformRationalBSpline bspline_out( + {4, 5, 6}, iganet::init::zeros, options); + + bspline_out.transform([](const std::array xi) { + return std::array{ + static_cast(std::rand()), static_cast(std::rand()), + static_cast(std::rand()), static_cast(std::rand())}; + }); + + nlohmann::json json = bspline_out.to_json(); + + iganet::NonUniformRationalBSpline bspline_in(options); + bspline_in.from_json(json); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW((iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW((iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + } + + { + iganet::NonUniformRationalBSpline bspline_out( + {4, 5, 6, 2}, iganet::init::zeros, options); + + bspline_out.transform([](const std::array xi) { + return std::array{static_cast(std::rand()), 1.0}; + }); + + nlohmann::json json = bspline_out.to_json(); + + iganet::NonUniformRationalBSpline bspline_in(options); + bspline_in.from_json(json); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW((iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW((iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + } + + { + iganet::NonUniformRationalBSpline bspline_out( + {4, 5, 6, 2}, iganet::init::zeros, options); + + bspline_out.transform([](const std::array xi) { + return std::array{static_cast(std::rand()), + static_cast(std::rand()), 1.0}; + }); + + nlohmann::json json = bspline_out.to_json(); + + iganet::NonUniformRationalBSpline bspline_in(options); + bspline_in.from_json(json); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW((iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW((iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + } + + { + iganet::NonUniformRationalBSpline bspline_out( + {4, 5, 6, 2}, iganet::init::zeros, options); + + bspline_out.transform([](const std::array xi) { + return std::array{static_cast(std::rand()), + static_cast(std::rand()), + static_cast(std::rand()), 1.0}; + }); + + nlohmann::json json = bspline_out.to_json(); + + iganet::NonUniformRationalBSpline bspline_in(options); + bspline_in.from_json(json); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW((iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW((iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + } + + { + iganet::NonUniformRationalBSpline bspline_out( + {4, 5, 6, 2}, iganet::init::zeros, options); + + bspline_out.transform([](const std::array xi) { + return std::array{ + static_cast(std::rand()), static_cast(std::rand()), + static_cast(std::rand()), static_cast(std::rand())}; + }); + + nlohmann::json json = bspline_out.to_json(); + + iganet::NonUniformRationalBSpline bspline_in(options); + bspline_in.from_json(json); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW((iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW((iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformRationalBSpline{}.from_json(json)), + std::runtime_error); + } +} + +TEST_F(BSplineTest, NonUniformRationalBSpline_query_property) { + iganet::NonUniformRationalBSpline bspline( + {4, 5}, iganet::init::greville, options); + + EXPECT_FALSE(bspline.is_uniform()); + EXPECT_TRUE(bspline.is_nonuniform()); + + EXPECT_EQ(bspline.device(), options.device()); + EXPECT_EQ(bspline.device_index(), options.device_index()); + EXPECT_EQ(bspline.dtype(), options.dtype()); + EXPECT_EQ(bspline.is_sparse(), options.is_sparse()); + EXPECT_EQ(bspline.layout(), options.layout()); + EXPECT_EQ(bspline.pinned_memory(), options.pinned_memory()); +} + +TEST_F(BSplineTest, NonUniformRationalBSpline_requires_grad) { + { + iganet::NonUniformRationalBSpline bspline( + {4, 5}, iganet::init::greville, options); + + EXPECT_EQ(bspline.requires_grad(), false); + + for (iganet::short_t i = 0; i < bspline.parDim(); ++i) + EXPECT_EQ(bspline.knots(i).requires_grad(), false); + + for (iganet::short_t i = 0; i < bspline.geoDim(); ++i) + EXPECT_EQ(bspline.coeffs(i).requires_grad(), false); + + auto xi = iganet::utils::to_tensorArray({0.5_r}, {0.5_r}, options); + auto values = bspline.eval(xi); + + // We expect an error when calling backward() because no tensor + // has requires_grad = true + EXPECT_THROW(values[0]->backward(), c10::Error); + + xi = iganet::utils::to_tensorArray({0.5_r}, {0.5_r}, + options.requires_grad(true)); + values = bspline.eval(xi); + values[0]->backward(); + EXPECT_TRUE(torch::allclose( + xi[0].grad(), iganet::utils::to_tensor({1.0_r}, options))); + } + + { + iganet::NonUniformRationalBSpline bspline( + {4, 5}, iganet::init::linear, options.requires_grad(true)); + + EXPECT_TRUE(bspline.requires_grad()); + + for (iganet::short_t i = 0; i < bspline.parDim(); ++i) + EXPECT_TRUE(bspline.knots(i).requires_grad()); + + for (iganet::short_t i = 0; i < bspline.geoDim(); ++i) + EXPECT_TRUE(bspline.coeffs(i).requires_grad()); + + auto xi = iganet::utils::to_tensorArray({0.5_r}, {0.5_r}, options); + auto values = bspline.eval(xi); + values[0]->backward( + {}, true); // otherwise we cannot run backward() a second time + + // We expect an error because xi[0].grad() is an undefined tensor + EXPECT_THROW(torch::allclose(xi[0].grad(), torch::empty({})), c10::Error); + + xi = iganet::utils::to_tensorArray({0.5_r}, {0.5_r}, + options.requires_grad(true)); + values = bspline.eval(xi); + values[0]->backward(); + EXPECT_TRUE(torch::allclose( + xi[0].grad(), iganet::utils::to_tensor({1.0_r}, options))); + + EXPECT_TRUE(torch::allclose( + bspline.coeffs(0).grad(), + iganet::utils::to_tensor( + {0.015625_r, 0.046875_r, 0.046875_r, 0.015625_r, 0.0625_r, + 0.1875_r, 0.1875_r, 0.0625_r, 0.09375_r, 0.28125_r, + 0.28125_r, 0.09375_r, 0.0625_r, 0.1875_r, 0.1875_r, + 0.0625_r, 0.015625_r, 0.046875_r, 0.046875_r, 0.015625_r}, + options))); + } +} + +TEST_F(BSplineTest, NonUniformRationalBSpline_to_dtype) { + { + iganet::NonUniformRationalBSpline bspline( + {4, 5}, iganet::init::greville, options); + + auto bspline_double = bspline.to(); + auto bspline_float = bspline.to(); + + if constexpr (std::is_same::value) + EXPECT_TRUE(bspline == bspline_double); + else + EXPECT_TRUE(bspline != bspline_double); + + if constexpr (std::is_same::value) + EXPECT_TRUE(bspline == bspline_float); + else + EXPECT_TRUE(bspline != bspline_float); + } + + { + iganet::NonUniformRationalBSpline bspline( + {4, 5}, iganet::init::greville, options); + + auto bspline_double = bspline.to(iganet::Options{}); + auto bspline_float = bspline.to(iganet::Options{}); + + if constexpr (std::is_same::value) + EXPECT_TRUE(bspline == bspline_double); + else + EXPECT_TRUE(bspline != bspline_double); + + if constexpr (std::is_same::value) + EXPECT_TRUE(bspline == bspline_float); + else + EXPECT_TRUE(bspline != bspline_float); + } +} + +TEST_F(BSplineTest, NonUniformRationalBSpline_to_device) { + { + iganet::Options options = + iganet::Options{}.device(torch::kCPU); + iganet::NonUniformRationalBSpline bspline( + {4, 5}, iganet::init::greville, options); + + auto bspline_cpu = bspline.to(torch::kCPU); + EXPECT_TRUE(bspline == bspline_cpu); + + if (torch::cuda::is_available()) { + auto bspline_cuda = bspline.to(torch::kCUDA); + EXPECT_THROW((void)(bspline == bspline_cuda), c10::Error); + } else + EXPECT_THROW(bspline.to(torch::kCUDA), c10::Error); + + if (at::hasHIP()) { + auto bspline_hip = bspline.to(torch::kHIP); + EXPECT_THROW((void)(bspline == bspline_hip), c10::Error); + } else + EXPECT_THROW(bspline.to(torch::kHIP), c10::Error); + + if (at::hasMPS() && // will become torch::mps::is_available() + (options.dtype() != iganet::dtype())) { + auto bspline_mps = bspline.to(torch::kMPS); + EXPECT_THROW((void)(bspline == bspline_mps), c10::Error); + } else + EXPECT_THROW(bspline.to(torch::kMPS), c10::Error); + } +} + +TEST_F(BSplineTest, NonUniformRationalBSpline_reduce_continuity) { + { + iganet::NonUniformRationalBSpline bspline({5, 6}); + iganet::NonUniformRationalBSpline bspline_ref( + iganet::utils::to_array( + iganet::utils::to_vector(0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 0.5_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r), + iganet::utils::to_vector(0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, + 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r))); + bspline.reduce_continuity(); + + EXPECT_TRUE(bspline.isclose(bspline_ref)); + } + + { + iganet::NonUniformRationalBSpline bspline({5, 6}); + iganet::NonUniformRationalBSpline bspline_ref( + iganet::utils::to_array( + iganet::utils::to_vector(0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 0.5_r, + 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r), + iganet::utils::to_vector(0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, + 0.5_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r))); + bspline.reduce_continuity(2); + + EXPECT_TRUE(bspline.isclose(bspline_ref)); + } + + { + iganet::NonUniformRationalBSpline bspline({5, 6}); + iganet::NonUniformRationalBSpline bspline_ref( + iganet::utils::to_array( + iganet::utils::to_vector(0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 0.5_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r), + iganet::utils::to_vector(0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, + 0.5_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r))); + bspline.reduce_continuity(1, 0).reduce_continuity(2, 1); + + EXPECT_TRUE(bspline.isclose(bspline_ref)); + } +} + +TEST_F(BSplineTest, NonUniformRationalBSpline_insert_knots) { + { + iganet::NonUniformRationalBSpline bspline({5, 6}); + iganet::NonUniformRationalBSpline bspline_ref( + iganet::utils::to_array( + iganet::utils::to_vector(0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.1_r, 0.3_r, + 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r), + iganet::utils::to_vector(0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.2_r, + 0.4_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r))); + bspline.insert_knots( + iganet::utils::to_tensorArray({0.1_r, 0.3_r}, {0.2_r, 0.4_r})); + + EXPECT_TRUE(bspline.isclose(bspline_ref)); + } +} + +int main(int argc, char **argv) { + ::testing::InitGoogleTest(&argc, argv); + iganet::init(); + return RUN_ALL_TESTS(); +} diff --git a/unittests/unittest_nonuniform_rational_bspline_eval.cxx b/unittests/unittest_nonuniform_rational_bspline_eval.cxx new file mode 100644 index 00000000..8237c720 --- /dev/null +++ b/unittests/unittest_nonuniform_rational_bspline_eval.cxx @@ -0,0 +1,1128 @@ +/** + @file unittests/unittest_nonuniform_rational_bspline_eval.cxx + + @brief B-Spline unittests + + @author Merle Backmeyer + + @copyright This file is part of the IgANet project + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +//Comment: parDim == 1 and parDim == 4 not yet supported but tests prepared. + +#include +#include +#include + +#include +#include +#include + +using namespace iganet::unittests::literals; + +class BSplineTest : public ::testing::Test { +protected: + using real_t = iganet::unittests::real_t; + iganet::Options options; + /* + static constexpr auto trafo_parDim1_geoDim1 = + [](const std::array xi) { + return std::array{xi[0] * xi[0], sin(static_cast(M_PI)* xi[0]) + 0.3}; + }; + static constexpr auto trafo_parDim1_geoDim2 = + [](const std::array xi) { + return std::array{xi[0] * xi[0], + sin(static_cast(M_PI) * xi[0]), sin(static_cast(M_PI)* xi[0]) + 0.3}; + }; + static constexpr auto trafo_parDim1_geoDim3 = + [](const std::array xi) { + return std::array{ + xi[0] * xi[0], sin(static_cast(M_PI) * xi[0]), xi[0], sin(static_cast(M_PI)* xi[0])+0.3}; + }; + static constexpr auto trafo_parDim1_geoDim4 = + [](const std::array xi) { + return std::array{ + xi[0] * xi[0], sin(static_cast(M_PI) * xi[0]), xi[0], + cos(static_cast(M_PI) * xi[0])}; + }; + */ + static constexpr auto trafo_parDim2_geoDim1 = + [](const std::array xi) { + return std::array{xi[0] * xi[1], sin(static_cast(M_PI)* xi[0]) + 0.3}; + }; + static constexpr auto trafo_parDim2_geoDim2 = + [](const std::array xi) { + return std::array{xi[0] * xi[1], + sin(static_cast(M_PI) * xi[0]), sin(static_cast(M_PI)* xi[0]) + 0.3}; + }; + static constexpr auto trafo_parDim2_geoDim3 = + [](const std::array xi) { + return std::array{ + xi[0] * xi[1], sin(static_cast(M_PI) * xi[0]), xi[1], sin(static_cast(M_PI)* xi[0])+0.3}; + }; + static constexpr auto trafo_parDim2_geoDim4 = + [](const std::array xi) { + return std::array{ + xi[0] * xi[1], sin(static_cast(M_PI) * xi[0]), xi[1], + cos(static_cast(M_PI) * xi[1]), sin(static_cast(M_PI)* xi[0]) + 0.3}; + }; + + static constexpr auto trafo_parDim3_geoDim1 = + [](const std::array xi) { + return std::array{xi[0] * xi[1] * xi[2], sin(static_cast(M_PI)* xi[0]) + 0.3}; + }; + static constexpr auto trafo_parDim3_geoDim2 = + [](const std::array xi) { + return std::array{xi[0] * xi[1] * xi[2], + sin(static_cast(M_PI) * xi[0]), sin(static_cast(M_PI)* xi[0]) + 0.3}; + }; + static constexpr auto trafo_parDim3_geoDim3 = + [](const std::array xi) { + return std::array{xi[0] * xi[1] * xi[2], + sin(static_cast(M_PI) * xi[0]),xi[1] * xi[2], sin(static_cast(M_PI)* xi[0])+0.3}; + }; + static constexpr auto trafo_parDim3_geoDim4 = + [](const std::array xi) { + return std::array{ + xi[0] * xi[1] * xi[2], sin(static_cast(M_PI) * xi[0]), + xi[1] * xi[2], cos(static_cast(M_PI) * xi[1]), sin(static_cast(M_PI)* xi[0])+0.3}; + }; + /* + static constexpr auto trafo_parDim4_geoDim1 = + [](const std::array xi) { + return std::array{xi[0] * xi[1] * xi[2] * xi[3], sin(static_cast(M_PI)* xi[0])}; + }; + static constexpr auto trafo_parDim4_geoDim2 = + [](const std::array xi) { + return std::array{xi[0] * xi[1] * xi[2] * xi[3], + sin(static_cast(M_PI) * xi[0]), sin(static_cast(M_PI)* xi[0])}; + }; + static constexpr auto trafo_parDim4_geoDim3 = + [](const std::array xi) { + return std::array{xi[0] * xi[1] * xi[2] * xi[3], + sin(static_cast(M_PI) * xi[0]), + xi[1] * xi[2] * xi[3], sin(static_cast(M_PI)* xi[0])}; + }; + static constexpr auto trafo_parDim4_geoDim4 = + [](const std::array xi) { + return std::array{xi[0] * xi[1] * xi[2] * xi[3], + sin(static_cast(M_PI) * xi[0]), + xi[1] * xi[2] * xi[3], + cos(static_cast(M_PI) * xi[1])}; + };*/ +}; +/* +TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim1_geoDim1_degrees1) { + iganet::NonUniformRationalBSpline geo( + {{{0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r}}}, iganet::init::greville, options); + iganet::NonUniformRationalBSpline bspline( + {{{0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r}}}, iganet::init::zeros, options); + bspline.transform(trafo_parDim1_geoDim1); + auto xi = iganet::utils::to_tensorArray( + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, options); + std::cout << "start" << std::endl; + test_nurbs_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, NonUniformRationalnurbs_eval_parDim1_geoDim1_degrees2) { + iganet::NonUniformRationalBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformRationalBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim1_geoDim1); + auto xi = iganet::utils::to_tensorArray( + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, options); + test_nurbs_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim1_geoDim1_degrees3) { + iganet::NonUniformRationalBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformRationalBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim1_geoDim1); + auto xi = iganet::utils::to_tensorArray( + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, options); + test_nurbs_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim1_geoDim1_degrees4) { + iganet::NonUniformRationalBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformRationalBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim1_geoDim1); + auto xi = iganet::utils::to_tensorArray( + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, options); + test_nurbs_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim1_geoDim1_degrees5) { + iganet::NonUniformRationalBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformRationalBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim1_geoDim1); + auto xi = iganet::utils::to_tensorArray( + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, options); + test_nurbs_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim1_geoDim1_degrees6) { + iganet::NonUniformRationalBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformRationalBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim1_geoDim1); + auto xi = iganet::utils::to_tensorArray( + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, options); + test_nurbs_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim1_geoDim2_degrees1) { + iganet::NonUniformRationalBSpline geo( + {{{0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r}}}, iganet::init::greville, options); + iganet::NonUniformRationalBSpline bspline( + {{{0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r}}}, iganet::init::zeros, options); + bspline.transform(trafo_parDim1_geoDim2); + auto xi = iganet::utils::to_tensorArray( + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, options); + test_nurbs_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim1_geoDim2_degrees2) { + iganet::NonUniformRationalBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformRationalBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim1_geoDim2); + auto xi = iganet::utils::to_tensorArray( + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, options); + test_nurbs_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim1_geoDim2_degrees3) { + iganet::NonUniformRationalBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformRationalBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim1_geoDim2); + auto xi = iganet::utils::to_tensorArray( + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, options); + test_nurbs_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim1_geoDim2_degrees4) { + iganet::NonUniformRationalBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformRationalBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim1_geoDim2); + auto xi = iganet::utils::to_tensorArray( + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, options); + test_nurbs_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim1_geoDim2_degrees5) { + iganet::NonUniformRationalBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformRationalBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim1_geoDim2); + auto xi = iganet::utils::to_tensorArray( + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, options); + test_nurbs_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim1_geoDim2_degrees6) { + iganet::NonUniformRationalBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformRationalBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim1_geoDim2); + auto xi = iganet::utils::to_tensorArray( + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, options); + test_nurbs_eval(geo, bspline, xi, 1e-11); +} +*//* +TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim1_geoDim3_degrees1) { + iganet::NonUniformRationalBSpline geo( + {{{0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r}}}, iganet::init::greville, options); + iganet::NonUniformRationalBSpline bspline( + {{{0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r}}}, iganet::init::zeros, options); + bspline.transform(trafo_parDim1_geoDim3); + auto xi = iganet::utils::to_tensorArray( + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, options); + test_nurbs_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim1_geoDim3_degrees2) { + iganet::NonUniformRationalBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformRationalBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim1_geoDim3); + auto xi = iganet::utils::to_tensorArray( + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, options); + test_nurbs_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim1_geoDim3_degrees3) { + iganet::NonUniformRationalBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformRationalBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim1_geoDim3); + auto xi = iganet::utils::to_tensorArray( + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, options); + test_nurbs_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim1_geoDim3_degrees4) { + iganet::NonUniformRationalBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformRationalBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim1_geoDim3); + auto xi = iganet::utils::to_tensorArray( + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, options); + test_nurbs_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim1_geoDim3_degrees5) { + iganet::NonUniformRationalBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformRationalBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim1_geoDim3); + auto xi = iganet::utils::to_tensorArray( + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, options); + test_nurbs_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim1_geoDim3_degrees6) { + iganet::NonUniformRationalBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformRationalBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim1_geoDim3); + auto xi = iganet::utils::to_tensorArray( + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, options); + test_nurbs_eval(geo, bspline, xi, 1e-11); +}*/ +/* +TEST_F(BSplineTest, NonUniformBSpline_eval_parDim1_geoDim4_degrees1) { + iganet::NonUniformBSpline geo( + {{{0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r}}}, iganet::init::greville, options); + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r}}}, iganet::init::zeros, options); + bspline.transform(trafo_parDim1_geoDim4); + auto xi = iganet::utils::to_tensorArray( + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, options); + test_nurbs_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, NonUniformBSpline_eval_parDim1_geoDim4_degrees2) { + iganet::NonUniformBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim1_geoDim4); + auto xi = iganet::utils::to_tensorArray( + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, options); + test_nurbs_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, NonUniformBSpline_eval_parDim1_geoDim4_degrees3) { + iganet::NonUniformBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim1_geoDim4); + auto xi = iganet::utils::to_tensorArray( + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, options); + test_nurbs_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, NonUniformBSpline_eval_parDim1_geoDim4_degrees4) { + iganet::NonUniformBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim1_geoDim4); + auto xi = iganet::utils::to_tensorArray( + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, options); + test_nurbs_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, NonUniformBSpline_eval_parDim1_geoDim4_degrees5) { + iganet::NonUniformBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim1_geoDim4); + auto xi = iganet::utils::to_tensorArray( + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, options); + test_nurbs_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, NonUniformBSpline_eval_parDim1_geoDim4_degrees6) { + iganet::NonUniformBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim1_geoDim4); + auto xi = iganet::utils::to_tensorArray( + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, options); + test_nurbs_eval(geo, bspline, xi, 1e-10); +} +*/ +TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim2_geoDim1_degrees22) { + iganet::NonUniformRationalBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformRationalBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim2_geoDim1); + auto xi = iganet::utils::to_tensorArray( + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, options); + test_nurbs_eval(geo, bspline, xi, 1e-6); +} + +TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim2_geoDim1_degrees46) { + iganet::NonUniformRationalBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformRationalBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim2_geoDim1); + auto xi = iganet::utils::to_tensorArray( + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, options); + test_nurbs_eval(geo, bspline, xi, 1e-6); +} + +TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim2_geoDim1_degrees64) { + iganet::NonUniformRationalBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformRationalBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim2_geoDim1); + auto xi = iganet::utils::to_tensorArray( + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, options); + test_nurbs_eval(geo, bspline, xi, 1e-5); +} + +TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim2_geoDim2_degrees22) { + iganet::NonUniformRationalBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformRationalBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim2_geoDim2); + auto xi = iganet::utils::to_tensorArray( + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, options); + test_nurbs_eval(geo, bspline, xi, 1e-6); +} + +TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim2_geoDim2_degrees46) { + iganet::NonUniformRationalBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformRationalBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim2_geoDim2); + auto xi = iganet::utils::to_tensorArray( + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, options); + test_nurbs_eval(geo, bspline, xi, 1e-5); +} + +TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim2_geoDim2_degrees64) { + iganet::NonUniformRationalBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformRationalBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim2_geoDim2); + auto xi = iganet::utils::to_tensorArray( + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, options); + test_nurbs_eval(geo, bspline, xi, 1e-5); +} + +TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim2_geoDim3_degrees22) { + iganet::NonUniformRationalBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformRationalBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim2_geoDim3); + auto xi = iganet::utils::to_tensorArray( + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, options); + test_nurbs_eval(geo, bspline, xi, 1e-6); +} + +TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim2_geoDim3_degrees46) { + iganet::NonUniformRationalBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformRationalBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim2_geoDim3); + auto xi = iganet::utils::to_tensorArray( + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, options); + test_nurbs_eval(geo, bspline, xi, 1e-5); +} + +TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim2_geoDim3_degrees64) { + iganet::NonUniformRationalBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformRationalBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim2_geoDim3); + auto xi = iganet::utils::to_tensorArray( + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, options); + test_nurbs_eval(geo, bspline, xi, 1e-5); +} + +TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim2_geoDim4_degrees22) { + iganet::NonUniformRationalBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformRationalBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim2_geoDim4); + auto xi = iganet::utils::to_tensorArray( + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, options); + test_nurbs_eval(geo, bspline, xi, 1e-6); +} + +TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim2_geoDim4_degrees46) { + iganet::NonUniformRationalBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformRationalBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim2_geoDim4); + auto xi = iganet::utils::to_tensorArray( + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, options); + test_nurbs_eval(geo, bspline, xi, 1e-6); +} + +TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim2_geoDim4_degrees64) { + iganet::NonUniformRationalBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformRationalBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim2_geoDim4); + auto xi = iganet::utils::to_tensorArray( + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, options); + test_nurbs_eval(geo, bspline, xi, 1e-5); +} + +TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim3_geoDim1_degrees222) { + iganet::NonUniformRationalBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformRationalBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim3_geoDim1); + auto xi = iganet::utils::to_tensorArray( + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, options); + test_nurbs_eval(geo, bspline, xi, 1e-6); +} + +TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim3_geoDim1_degrees462) { + iganet::NonUniformRationalBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformRationalBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim3_geoDim1); + auto xi = iganet::utils::to_tensorArray( + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, options); + test_nurbs_eval(geo, bspline, xi, 1e-5); +} + +TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim3_geoDim1_degrees642) { + iganet::NonUniformRationalBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformRationalBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim3_geoDim1); + auto xi = iganet::utils::to_tensorArray( + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, options); + test_nurbs_eval(geo, bspline, xi, 1e-6); +} + +TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim3_geoDim2_degrees222) { + iganet::NonUniformRationalBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformRationalBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim3_geoDim2); + auto xi = iganet::utils::to_tensorArray( + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, options); + test_nurbs_eval(geo, bspline, xi, 1e-6); +} + +TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim3_geoDim2_degrees462) { + iganet::NonUniformRationalBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformRationalBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim3_geoDim2); + auto xi = iganet::utils::to_tensorArray( + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, options); + test_nurbs_eval(geo, bspline, xi, 1e-5); +} + +TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim3_geoDim2_degrees642) { + iganet::NonUniformRationalBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformRationalBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim3_geoDim2); + auto xi = iganet::utils::to_tensorArray( + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, options); + test_nurbs_eval(geo, bspline, xi, 1e-6); +} + +TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim3_geoDim3_degrees222) { + iganet::NonUniformRationalBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformRationalBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim3_geoDim3); + auto xi = iganet::utils::to_tensorArray( + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, options); + test_nurbs_eval(geo, bspline, xi, 1e-6); +} + +TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim3_geoDim3_degrees462) { + iganet::NonUniformRationalBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformRationalBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim3_geoDim3); + auto xi = iganet::utils::to_tensorArray( + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, options); + test_nurbs_eval(geo, bspline, xi, 1e-6); +} + +TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim3_geoDim3_degrees642) { + iganet::NonUniformRationalBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformRationalBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim3_geoDim3); + auto xi = iganet::utils::to_tensorArray( + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, options); + test_nurbs_eval(geo, bspline, xi, 1e-6); +} + +TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim3_geoDim4_degrees222) { + iganet::NonUniformRationalBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformRationalBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim3_geoDim4); + auto xi = iganet::utils::to_tensorArray( + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, options); + test_nurbs_eval(geo, bspline, xi, 1e-6); +} + +TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim3_geoDim4_degrees462) { + iganet::NonUniformRationalBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformRationalBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim3_geoDim4); + auto xi = iganet::utils::to_tensorArray( + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, options); + test_nurbs_eval(geo, bspline, xi, 1e-6); +} + +TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim3_geoDim4_degrees642) { + iganet::NonUniformRationalBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformRationalBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim3_geoDim4); + auto xi = iganet::utils::to_tensorArray( + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, options); + test_nurbs_eval(geo, bspline, xi, 1e-6); +} +/* +TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim4_geoDim1_degrees2222) { + iganet::NonUniformRationalBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformRationalBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim4_geoDim1); + auto xi = iganet::utils::to_tensorArray( + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, options); + test_nurbs_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim4_geoDim1_degrees2463) { + iganet::NonUniformRationalBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformRationalBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim4_geoDim1); + auto xi = iganet::utils::to_tensorArray( + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, options); + test_nurbs_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim4_geoDim2_degrees2222) { + iganet::NonUniformRationalBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformRationalBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim4_geoDim2); + auto xi = iganet::utils::to_tensorArray( + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, options); + test_nurbs_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim4_geoDim2_degrees2463) { + iganet::NonUniformRationalBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformRationalBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim4_geoDim2); + auto xi = iganet::utils::to_tensorArray( + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, options); + test_nurbs_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim4_geoDim3_degrees2222) { + iganet::NonUniformRationalBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformRationalBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim4_geoDim3); + auto xi = iganet::utils::to_tensorArray( + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, options); + test_nurbs_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim4_geoDim3_degrees2463) { + iganet::NonUniformRationalBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformRationalBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim4_geoDim3); + auto xi = iganet::utils::to_tensorArray( + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, options); + test_nurbs_eval(geo, bspline, xi, 1e-12); +} +/* +TEST_F(BSplineTest, NonUniformBSpline_eval_parDim4_geoDim4_degrees2222) { + iganet::NonUniformBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim4_geoDim4); + auto xi = iganet::utils::to_tensorArray( + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, options); + test_nurbs_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, NonUniformBSpline_eval_parDim4_geoDim4_degrees2463) { + iganet::NonUniformBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim4_geoDim4); + auto xi = iganet::utils::to_tensorArray( + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, options); + test_nurbs_eval(geo, bspline, xi, 1e-12); +} +*/ +int main(int argc, char **argv) { + ::testing::InitGoogleTest(&argc, argv); + iganet::init(); + return RUN_ALL_TESTS(); +} From 3604ecc9156a7cd2b1b676db82687a6967bec99d Mon Sep 17 00:00:00 2001 From: merback Date: Wed, 22 May 2024 15:39:06 +0200 Subject: [PATCH 03/13] Unittest BsplineLib extended for NURBS --- unittests/unittest_bsplinelib.hpp | 636 ++++++++++++++++++++++++++++++ 1 file changed, 636 insertions(+) diff --git a/unittests/unittest_bsplinelib.hpp b/unittests/unittest_bsplinelib.hpp index 8216c1f2..a7e4d63f 100644 --- a/unittests/unittest_bsplinelib.hpp +++ b/unittests/unittest_bsplinelib.hpp @@ -21,6 +21,7 @@ #endif #include +#include #if __clang_major__ > 14 #pragma clang diagnostic pop @@ -790,3 +791,638 @@ void test_bspline_eval(const Geometry_t &geometry, const Spline &bspline, test_bspline_ihess(geometry, bspline, xi, tol); } } +template auto to_bsplinelib_nurbs(const Spline& bspline) { + static_assert(Spline::geoDim() < 6, "Unsupported geometric dimension"); + + // NURBS construction + using Nurbs = bsplinelib::splines::Nurbs; + using ParameterSpace = typename Nurbs::ParameterSpace_; + using WeightedVectorSpace = typename Nurbs::WeightedVectorSpace_; + using Coordinates = typename WeightedVectorSpace::Coordinates_; + using Degrees = typename ParameterSpace::Degrees_; + using Degree = typename Degrees::value_type; + using KnotVectors = typename ParameterSpace::KnotVectors_; + using KnotVector = typename KnotVectors::value_type::element_type; + using Knots = typename KnotVector::Knots_; + using Knot = typename Knots::value_type; + + // NURBS evaluation + using ParametricCoordinate = typename Nurbs::ParametricCoordinate_; + using ScalarParametricCoordinate = typename ParametricCoordinate::value_type; + using Derivative = typename ParameterSpace::Derivative_; + using ScalarDerivative = typename Derivative::value_type; + + // Create degress structure + Degrees degrees; + for (iganet::short_t k = 0; k < bspline.parDim(); ++k) + degrees[k] = Degree{ bspline.degree(k) }; + + // Create knot vectors + KnotVectors knot_vectors; + for (iganet::short_t k = 0; k < bspline.parDim(); ++k) { + Knots knots_; + for (int64_t i = 0; i < bspline.nknots(k); ++i) + knots_.emplace_back(Knot{ + bspline.knots(k)[i].template item() }); + bsplinelib::SharedPointer knot_vector{ + std::make_shared(knots_) }; + knot_vectors[k] = std::move(knot_vector); + } + + // Create parametric space + bsplinelib::SharedPointer parameter_space{ + std::make_shared(knot_vectors, degrees) }; + + // Create coordinate vector(s) + Coordinates coordinates(bspline.ncumcoeffs(), Spline::geoDim()); + for (int64_t i = 0; i < bspline.ncumcoeffs(); ++i) { + for (int64_t j = 0; j < Spline::geoDim(); ++j) { + coordinates(i, j) = + bspline.coeffs(j)[i].template item(); + } + } + + // Create vector space + bsplinelib::SharedPointer vector_space{ + std::make_shared(std::move(coordinates)) }; + + // Create B-Spline + Nurbs bsplinelib_nurbs{ parameter_space, vector_space }; + + return bsplinelib_nurbs; +} + +// Helper functions for NURBS unittests + +template auto to_bsplinelib_nurbs(const Spline& bspline) { + static_assert(Spline::geoDim() < 6, "Unsupported geometric dimension"); + + // NURBS construction + using Nurbs = bsplinelib::splines::Nurbs; + using ParameterSpace = typename Nurbs::ParameterSpace_; + using WeightedVectorSpace = typename Nurbs::WeightedVectorSpace_; + using Coordinates = typename WeightedVectorSpace::Coordinates_; + using Degrees = typename ParameterSpace::Degrees_; + using Degree = typename Degrees::value_type; + using KnotVectors = typename ParameterSpace::KnotVectors_; + using KnotVector = typename KnotVectors::value_type::element_type; + using Knots = typename KnotVector::Knots_; + using Knot = typename Knots::value_type; + + // NURBS evaluation + using ParametricCoordinate = typename Nurbs::ParametricCoordinate_; + using ScalarParametricCoordinate = typename ParametricCoordinate::value_type; + using Derivative = typename ParameterSpace::Derivative_; + using ScalarDerivative = typename Derivative::value_type; + + // Create degress structure + Degrees degrees; + for (iganet::short_t k = 0; k < bspline.parDim(); ++k) + degrees[k] = Degree{ bspline.degree(k) }; + + // Create knot vectors + KnotVectors knot_vectors; + for (iganet::short_t k = 0; k < bspline.parDim(); ++k) { + Knots knots_; + for (int64_t i = 0; i < bspline.nknots(k); ++i) + knots_.emplace_back(Knot{ + bspline.knots(k)[i].template item() }); + bsplinelib::SharedPointer knot_vector{ + std::make_shared(knots_) }; + knot_vectors[k] = std::move(knot_vector); + } + + // Create parametric space + bsplinelib::SharedPointer parameter_space{ + std::make_shared(knot_vectors, degrees) }; + + // Create coordinate vector(s) + Coordinates coordinates(bspline.ncumcoeffs(), Spline::geoDim()); + for (int64_t i = 0; i < bspline.ncumcoeffs(); ++i) { + for (int64_t j = 0; j < Spline::geoDim(); ++j) { + coordinates(i, j) = + bspline.coeffs(j)[i].template item(); + } + } + + // Create vector space + bsplinelib::SharedPointer vector_space{ + std::make_shared(std::move(coordinates)) }; + + // Create B-Spline + Nurbs bsplinelib_nurbs{ parameter_space, vector_space }; + + return bsplinelib_nurbs; +} + + + +template + void test_nurbs_eval(const Spline& bspline, + BSplineLibNurbs& bsplinelib_bspline, + const TensorArray_t& xi, + typename Spline::value_type tol = 1e-12) { + + static_assert(Spline::parDim() < 5, "Unsupported parametric dimension"); + + // B-spline evaluation + using Derivative = typename BSplineLibNurbs::ParameterSpace_::Derivative_; + using ScalarDerivative = typename Derivative::value_type; + using Coordinate = typename BSplineLibNurbs::Coordinate_; + using ScalarCoordinate = typename Coordinate::value_type; + + using BSplineValue_type = + iganet::utils::BlockTensor; + + BSplineValue_type bspline_val; + if constexpr (precompute) { + auto knot_indices = bspline.find_knot_indices(xi); + auto basfunc = bspline.template eval_basfunc(xi, knot_indices); + auto coeff_indices = bspline.find_coeff_indices(knot_indices); + bspline_val = bspline.eval_from_precomputed(basfunc, coeff_indices, + xi[0].numel(), xi[0].sizes()); + } + else + bspline_val = bspline.template eval(xi); + + Coordinate bsplinelib_query(Spline::parDim()); + auto query = [&](const int64_t& i) -> ScalarCoordinate* { + for (iganet::short_t j = 0; j < Spline::parDim(); ++j) { + bsplinelib_query[j] = + (xi[j])[i].template item(); + } + return bsplinelib_query.data(); + }; + + auto ten_pow = [](const iganet::short_t& p) { + iganet::short_t value{ 1 }; + for (iganet::short_t i = 0; i < p; ++i) { + value *= 10; + } + return value; + }; + + const Derivative bsplinelib_deriv = [&ten_pow]() { + Derivative d_query; + for (iganet::short_t i = 0; i < Spline::parDim(); ++i) { + d_query[i] = ((iganet::short_t)deriv / ((i == 0) ? 1 : ten_pow(i))) % 10; + } + return d_query; + }(); + + Coordinate bsplinelib_val(Spline::geoDim()); + for (int64_t i = 0; i < xi[0].size(0); ++i) { + bsplinelib_bspline.EvaluateDerivative(query(i), bsplinelib_deriv.data(), + bsplinelib_val.data()); + for (iganet::short_t j = 0; j < Spline::geoDim_proj(); ++j) { + EXPECT_NEAR( + bspline_val(j)[i].template item(), + bsplinelib_val[j], tol); + } + } +} + +template + void test_nurbs_grad(const Spline& bspline, const TensorArray_t& xi, + typename Spline::value_type tol = 1e-12) { + iganet::utils::BlockTensor + bspline_grad_val; + + if constexpr (precompute) { + auto knot_indices = bspline.find_knot_indices(xi); + auto coeff_indices = bspline.find_coeff_indices(knot_indices); + bspline_grad_val = bspline.grad(xi, knot_indices, coeff_indices); + } + else + bspline_grad_val = bspline.grad(xi); + + if constexpr (Spline::parDim() == 1) { + EXPECT_TRUE(torch::allclose( + bspline_grad_val(0, 0), + bspline.template eval(xi)(0))); + } + else if constexpr (Spline::parDim() == 2) { + EXPECT_TRUE(torch::allclose( + bspline_grad_val(0, 0), + bspline.template eval(xi)(0))); + EXPECT_TRUE(torch::allclose( + bspline_grad_val(0, 1), + bspline.template eval(xi)(0))); + } + else if constexpr (Spline::parDim() == 3) { + EXPECT_TRUE(torch::allclose( + bspline_grad_val(0, 0), + bspline.template eval(xi)(0))); + EXPECT_TRUE(torch::allclose( + bspline_grad_val(0, 1), + bspline.template eval(xi)(0))); + EXPECT_TRUE(torch::allclose( + bspline_grad_val(0, 2), + bspline.template eval(xi)(0))); + } + else if constexpr (Spline::parDim() == 4) { + EXPECT_TRUE(torch::allclose( + bspline_grad_val(0, 0), + bspline.template eval(xi)(0))); + EXPECT_TRUE(torch::allclose( + bspline_grad_val(0, 1), + bspline.template eval(xi)(0))); + EXPECT_TRUE(torch::allclose( + bspline_grad_val(0, 2), + bspline.template eval(xi)(0))); + EXPECT_TRUE(torch::allclose( + bspline_grad_val(0, 3), + bspline.template eval(xi)(0))); + } +} + +template + void test_nurbs_igrad(const Geometry_t& geometry, const Spline& bspline, + const TensorArray_t& xi, + typename Spline::value_type tol = 1e-12) { + iganet::utils::BlockTensor + bspline_igrad_val; + + if constexpr (precompute) { + auto knot_indices = bspline.find_knot_indices(xi); + auto coeff_indices = bspline.find_coeff_indices(knot_indices); + auto knot_indices_G = geometry.find_knot_indices(xi); + auto coeff_indices_G = geometry.find_coeff_indices(knot_indices_G); + bspline_igrad_val = bspline.igrad(geometry, xi, knot_indices, coeff_indices, + knot_indices_G, coeff_indices_G); + } + else + bspline_igrad_val = bspline.igrad(geometry, xi); + + if constexpr (Spline::parDim() == 1) { + EXPECT_TRUE(torch::allclose( + bspline_igrad_val(0, 0), + bspline.template eval(xi)(0))); + } + else if constexpr (Spline::parDim() == 2) { + EXPECT_TRUE(torch::allclose( + bspline_igrad_val(0, 0), + bspline.template eval(xi)(0))); + EXPECT_TRUE(torch::allclose( + bspline_igrad_val(0, 1), + bspline.template eval(xi)(0))); + } + else if constexpr (Spline::parDim() == 3) { + EXPECT_TRUE(torch::allclose( + bspline_igrad_val(0, 0), + bspline.template eval(xi)(0))); + EXPECT_TRUE(torch::allclose( + bspline_igrad_val(0, 1), + bspline.template eval(xi)(0))); + EXPECT_TRUE(torch::allclose( + bspline_igrad_val(0, 2), + bspline.template eval(xi)(0))); + } + else if constexpr (Spline::parDim() == 4) { + EXPECT_TRUE(torch::allclose( + bspline_igrad_val(0, 0), + bspline.template eval(xi)(0))); + EXPECT_TRUE(torch::allclose( + bspline_igrad_val(0, 1), + bspline.template eval(xi)(0))); + EXPECT_TRUE(torch::allclose( + bspline_igrad_val(0, 2), + bspline.template eval(xi)(0))); + EXPECT_TRUE(torch::allclose( + bspline_igrad_val(0, 3), + bspline.template eval(xi)(0))); + } +} + +template + void test_nurbs_jac(const Spline& bspline, const TensorArray_t& xi, + typename Spline::value_type tol = 1e-12) { + iganet::utils::BlockTensor + bspline_jac_val; + if constexpr (precompute) { + auto knot_indices = bspline.find_knot_indices(xi); + auto coeff_indices = bspline.find_coeff_indices(knot_indices); + bspline_jac_val = bspline.jac(xi, knot_indices, coeff_indices); + } + else + bspline_jac_val = bspline.jac(xi); + + if constexpr (Spline::parDim() >= 1) { + for (iganet::short_t k = 0; k < Spline::geoDim() - 1; ++k) + EXPECT_TRUE(torch::allclose( + bspline_jac_val(k, 0), + bspline.template eval(xi)(k))); + } + + if constexpr (Spline::parDim() >= 2) { + for (iganet::short_t k = 0; k < Spline::geoDim() - 1; ++k) + EXPECT_TRUE(torch::allclose( + bspline_jac_val(k, 1), + bspline.template eval(xi)(k))); + } + + if constexpr (Spline::parDim() >= 3) { + for (iganet::short_t k = 0; k < Spline::geoDim() - 1; ++k) + EXPECT_TRUE(torch::allclose( + bspline_jac_val(k, 2), + bspline.template eval(xi)(k))); + } + + if constexpr (Spline::parDim() >= 4) { + for (iganet::short_t k = 0; k < Spline::geoDim() - 1; ++k) + EXPECT_TRUE(torch::allclose( + bspline_jac_val(k, 3), + bspline.template eval(xi)(k))); + } +} + +template + void test_nurbs_ijac(const Geometry_t& geometry, const Spline& bspline, + const TensorArray_t& xi, + typename Spline::value_type tol = 1e-12) { + iganet::utils::BlockTensor + bspline_ijac_val; + if constexpr (precompute) { + auto knot_indices = bspline.find_knot_indices(xi); + auto coeff_indices = bspline.find_coeff_indices(knot_indices); + auto knot_indices_G = geometry.find_knot_indices(xi); + auto coeff_indices_G = geometry.find_coeff_indices(knot_indices_G); + bspline_ijac_val = bspline.ijac(geometry, xi, knot_indices, coeff_indices, + knot_indices_G, coeff_indices_G); + } + else + bspline_ijac_val = bspline.ijac(geometry, xi); + + if constexpr (Spline::parDim() >= 1) { + for (iganet::short_t k = 0; k < Spline::geoDim() - 1; ++k) + EXPECT_TRUE(torch::allclose( + bspline_ijac_val(k, 0), + bspline.template eval(xi)(k))); + } + + if constexpr (Spline::parDim() >= 2) { + for (iganet::short_t k = 0; k < Spline::geoDim() - 1; ++k) + EXPECT_TRUE(torch::allclose( + bspline_ijac_val(k, 1), + bspline.template eval(xi)(k))); + } + + if constexpr (Spline::parDim() >= 3) { + for (iganet::short_t k = 0; k < Spline::geoDim() - 1; ++k) + EXPECT_TRUE(torch::allclose( + bspline_ijac_val(k, 2), + bspline.template eval(xi)(k))); + } + + if constexpr (Spline::parDim() >= 4) { + for (iganet::short_t k = 0; k < Spline::geoDim() - 1; ++k) + EXPECT_TRUE(torch::allclose( + bspline_ijac_val(k, 3), + bspline.template eval(xi)(k))); + } +} + +// first: only first derivatives, not memory optimized and not precomputed. + +template +void test_nurbs_eval(const Geometry_t& geometry, const Spline& bspline, + const TensorArray_t& xi, + typename Spline::value_type tol = 1e-12) { + // Create B-Spline + bsplinelib::splines::Nurbs bsplinelib_bspline = to_bsplinelib_nurbs(bspline); + // Evaluate function and derivatives (non-memory optimized) + test_nurbs_eval( + bspline, bsplinelib_bspline, xi, tol); + // Evaluate only first deriv + if constexpr (Spline::parDim() == 1) { + test_nurbs_eval( + bspline, bsplinelib_bspline, xi, tol); + /* test_nurbs_eval( //TODO: Check if it throws assertation error if different one. + bspline, bsplinelib_bspline, xi, tol); + test_nurbs_eval( + bspline, bsplinelib_bspline, xi, tol); + test_nurbs_eval( + bspline, bsplinelib_bspline, xi, tol); */ //TODO: Check what it does if degree to low?? + } + if constexpr (Spline::parDim() == 2) { + test_nurbs_eval( + bspline, bsplinelib_bspline, xi, tol); + test_nurbs_eval( + bspline, bsplinelib_bspline, xi, tol); + /* test_nurbs_eval( + bspline, bsplinelib_bspline, xi, tol); + test_nurbs_eval( + bspline, bsplinelib_bspline, xi, tol); + test_nurbs_eval( + bspline, bsplinelib_bspline, xi, tol);*/ + } + + if constexpr (Spline::parDim() == 3) { + test_nurbs_eval( + bspline, bsplinelib_bspline, xi, tol); + /* test_nurbs_eval( + bspline, bsplinelib_bspline, xi, tol); + test_nurbs_eval( + bspline, bsplinelib_bspline, xi, tol); + test_nurbs_eval( + bspline, bsplinelib_bspline, xi, tol);*/ + } + // parDim == 4 has not been implemented yet + /* if constexpr (Spline::parDim() == 4) { + test_nurbs_eval( + bspline, bsplinelib_bspline, xi, tol); + test_nurbs_eval( + bspline, bsplinelib_bspline, xi, tol); + test_nurbs_eval( + bspline, bsplinelib_bspline, xi, tol); + test_nurbs_eval( + bspline, bsplinelib_bspline, xi, tol); + } */ + std::cout << "memory not optimized finished" << std::endl; + // Evaluate function and derivatives (memory optimized) + // test_nurbs_eval( + // bspline, bsplinelib_bspline, xi, tol); + /* + if constexpr (Spline::parDim() == 1) { + test_nurbs_eval( + bspline, bsplinelib_bspline, xi, tol); + /* test_nurbs_eval( + bspline, bsplinelib_bspline, xi, tol); + test_nurbs_eval( + bspline, bsplinelib_bspline, xi, tol); + test_nurbs_eval( + bspline, bsplinelib_bspline, xi, tol); + } + + if constexpr (Spline::parDim() == 2) { + test_nurbs_eval( + bspline, bsplinelib_bspline, xi, tol); + /* test_nurbs_eval( + bspline, bsplinelib_bspline, xi, tol); + test_nurbs_eval( + bspline, bsplinelib_bspline, xi, tol); + test_nurbs_eval( + bspline, bsplinelib_bspline, xi, tol); + } + + if constexpr (Spline::parDim() == 3) { + test_nurbs_eval( + bspline, bsplinelib_bspline, xi, tol); + /* test_nurbs_eval( + bspline, bsplinelib_bspline, xi, tol); + test_nurbs_eval( + bspline, bsplinelib_bspline, xi, tol); + test_nurbs_eval( + bspline, bsplinelib_bspline, xi, tol); + } + + /* if constexpr (Spline::parDim() == 4) { + test_nurbs_eval( + bspline, bsplinelib_bspline, xi, tol); + test_nurbs_eval( + bspline, bsplinelib_bspline, xi, tol); + test_nurbs_eval( + bspline, bsplinelib_bspline, xi, tol); + test_nurbs_eval( + bspline, bsplinelib_bspline, xi, tol); + } + std::cout << "memory optimized finished" << std::endl; + // Evaluate function and derivatives from precomputed data (non-memory + // optimized) + test_nurbs_eval( + bspline, bsplinelib_bspline, xi, tol); + + if constexpr (Spline::parDim() == 1) { + test_nurbs_eval( + bspline, bsplinelib_bspline, xi, tol); + /* test_nurbs_eval( + bspline, bsplinelib_bspline, xi, tol); + test_nurbs_eval( + bspline, bsplinelib_bspline, xi, tol); + test_nurbs_eval( + bspline, bsplinelib_bspline, xi, tol); + } + + if constexpr (Spline::parDim() == 2) { + test_nurbs_eval( + bspline, bsplinelib_bspline, xi, tol); + /* test_nurbs_eval( + bspline, bsplinelib_bspline, xi, tol); + test_nurbs_eval( + bspline, bsplinelib_bspline, xi, tol); + test_nurbs_eval( + bspline, bsplinelib_bspline, xi, tol); + } + + if constexpr (Spline::parDim() == 3) { + test_nurbs_eval( + bspline, bsplinelib_bspline, xi, tol); + /* test_nurbs_eval( + bspline, bsplinelib_bspline, xi, tol); + test_nurbs_eval( + bspline, bsplinelib_bspline, xi, tol); + test_nurbs_eval( + bspline, bsplinelib_bspline, xi, tol); + } + + /* if constexpr (Spline::parDim() == 4) { + test_nurbs_eval( + bspline, bsplinelib_bspline, xi, tol); + test_nurbs_eval( + bspline, bsplinelib_bspline, xi, tol); + test_nurbs_eval( + bspline, bsplinelib_bspline, xi, tol); + test_nurbs_eval( + bspline, bsplinelib_bspline, xi, tol); + } + std::cout << "precomputed memory not optimized finished" << std::endl; + // Evaluate function and derivatives from precomputed data (memory optimized) + test_nurbs_eval( + bspline, bsplinelib_bspline, xi, tol); + + if constexpr (Spline::parDim() == 1) { + test_nurbs_eval( + bspline, bsplinelib_bspline, xi, tol); + /* test_nurbs_eval( + bspline, bsplinelib_bspline, xi, tol); + test_nurbs_eval( + bspline, bsplinelib_bspline, xi, tol); + test_nurbs_eval( + bspline, bsplinelib_bspline, xi, tol); + } + + if constexpr (Spline::parDim() == 2) { + test_nurbs_eval( + bspline, bsplinelib_bspline, xi, tol); + /* test_nurbs_eval( + bspline, bsplinelib_bspline, xi, tol); + test_nurbs_eval( + bspline, bsplinelib_bspline, xi, tol); + test_nurbs_eval( + bspline, bsplinelib_bspline, xi, tol); + } + + if constexpr (Spline::parDim() == 3) { + test_nurbs_eval( + bspline, bsplinelib_bspline, xi, tol); + /* test_nurbs_eval( + bspline, bsplinelib_bspline, xi, tol); + test_nurbs_eval( + bspline, bsplinelib_bspline, xi, tol); + test_nurbs_eval( + bspline, bsplinelib_bspline, xi, tol); + } + + /* if constexpr (Spline::parDim() == 4) { + test_nurbs_eval( + bspline, bsplinelib_bspline, xi, tol); + test_nurbs_eval( + bspline, bsplinelib_bspline, xi, tol); + test_nurbs_eval( + bspline, bsplinelib_bspline, xi, tol); + test_nurbs_eval( + bspline, bsplinelib_bspline, xi, tol); + } + std::cout << "precomp memory optimized finished" << std::endl;*/ + // Evaluate gradients + if constexpr (Spline::geoDim() == 1) { + test_nurbs_grad(bspline, xi, tol); + test_nurbs_grad(bspline, xi, tol); + test_nurbs_grad(bspline, xi, tol); + test_nurbs_grad(bspline, xi, tol); + + test_nurbs_igrad(geometry, bspline, xi, tol); + test_nurbs_igrad(geometry, bspline, xi, tol); + test_nurbs_igrad(geometry, bspline, xi, tol); + test_nurbs_igrad(geometry, bspline, xi, tol); + } + + /// Evaluate Jacobian + test_nurbs_jac(bspline, xi, tol); + //test_nurbs_jac(bspline, xi, tol); + //test_nurbs_jac(bspline, xi, tol); + //test_nurbs_jac(bspline, xi, tol); + + test_nurbs_ijac(geometry, bspline, xi, tol); + // test_nurbs_ijac(geometry, bspline, xi, tol); + // test_nurbs_ijac(geometry, bspline, xi, tol); + // test_nurbs_ijac(geometry, bspline, xi, tol); + + /// Evaluate Hessian - second order derivs not yet implemented + /* if constexpr (Spline::geoDim() == 1) { + test_nurbs_hess(bspline, xi, tol); + test_nurbs_hess(bspline, xi, tol); + test_nurbs_hess(bspline, xi, tol); + test_nurbs_hess(bspline, xi, tol); + + test_nurbs_ihess(geometry, bspline, xi, tol); + test_nurbs_ihess(geometry, bspline, xi, tol); + test_nurbs_ihess(geometry, bspline, xi, tol); + test_nurbs_ihess(geometry, bspline, xi, tol); + }*/ +} \ No newline at end of file From 3255515e76a94dc790413d4c1966ca8093a23b8d Mon Sep 17 00:00:00 2001 From: merback Date: Wed, 22 May 2024 22:46:42 +0200 Subject: [PATCH 04/13] Nurbs up to 1st deriv, not memory optimized, not precomputed --- include/bspline.hpp | 221 +++++++++++++++++- unittests/unittest_bsplinelib.hpp | 125 +++------- .../unittest_nonuniform_rational_bspline.cxx | 98 +++++--- ...ttest_nonuniform_rational_bspline_eval.cxx | 41 ++-- 4 files changed, 333 insertions(+), 152 deletions(-) diff --git a/include/bspline.hpp b/include/bspline.hpp index ac50e024..50c7030f 100644 --- a/include/bspline.hpp +++ b/include/bspline.hpp @@ -470,6 +470,11 @@ class UniformBSplineCore : public utils::Serializable { /// @result Number of geometric dimensions inline static constexpr short_t geoDim() { return geoDim_; } + /// @brief Returns the geometric dimension without weighted space (for non-rational BSplines just the same as geoDim()) + /// + /// @result Number of geometric dimensions + inline static constexpr short_t geoDim_proj() { return geoDim_; } + /// @brief Returns a constant reference to the array of degrees /// /// @result Array of degrees for all parametric dimensions @@ -4195,13 +4200,13 @@ class NonUniformBSplineCore Base::coeffs_[i] = coeffs[i] .clone() .to(options.requires_grad(false)) - .requires_grad_(Base::options.requires_grad()); + .requires_grad_(options.requires_grad()); else for (short_t i = 0; i < Base::geoDim_; ++i) Base::coeffs_[i] = coeffs[i]; } -private: +protected: /// @brief Initializes the B-spline knots inline void init_knots( std::array, Base::parDim_> kv) { @@ -4810,8 +4815,206 @@ class NonUniformBSplineCore } #endif // IGANET_WITH_GISMO + }; + + +/// @brief Tensor-product non-uniform rational B-spline (core functionality) +/// +/// This class extends the base class NonUniformBSplineCore to +/// non-uniform rational B-splines. Like its base class it only implements +/// the core functionality of non-uniform rational B-splines (NURBS) +/// Warning: if load/write functions are just, the 4th dimension is interpreted as weights +/// Limitation: This class has only been validated for at max first-order derivatives and parDim = [1,2,3,4]; moreover it does not work for precomputed nor memory optimized +template +class NonUniformRationalBSplineCore + : public NonUniformBSplineCore { + /// @brief Enable access to private members + friend class NonUniformBSplineCore; + + +private: + /// @brief Base type + using Base = NonUniformBSplineCore; //GeoDim is higher by 1 (projection to (GeoDim+1)-space by weights) + +public: + /// @brief Deduces the self-type possibly degrees (de-)elevated by + /// the additive constant `degree_elevate` + template ::type degree_elevate = 0> + using self_type = typename Base::template derived_type; + + /// @brief Deduces the derived self-type when exposed to different + /// class template parameters `real_t` and `GeoDim`, and the + /// `Degrees` parameter pack + template + using derived_self_type = + NonUniformRationalBSplineCore; + + /// @brief Deduces the derived self-type when exposed to a + /// different class template parameter `real_t` + template + using real_derived_self_type = + NonUniformRationalBSplineCore; + + /// @brief Returns true if the B-spline is uniform + static bool is_uniform() { return false; } + + /// @brief Returns true if the B-spline is non-uniform + static bool is_nonuniform() { return true; } + + + /// @brief Constructor for equidistant knot vectors + using NonUniformBSplineCore::NonUniformBSplineCore; + + /// @brief Constructor for non-equidistant knot vectors + /// + /// @param[in] kv Knot vectors + /// + /// @param[in] init Type of initialization + /// + /// @param[in] options Options configuration + NonUniformRationalBSplineCore( + std::array, Base::parDim_> kv, + enum init init = init::greville, + Options options = Options{}) + : Base(options) { + + Base::init_knots(kv); + Base::init_coeffs(init); + } + + /// @brief Constructor for non-equidistant knot vectors + /// + /// @param[in] kv Knot vectors + /// + /// @param[in] coeffs Vectors of coefficients per parametric dimension (later converted to homogenous coord) + /// + /// @param[in] clone If true, coefficients will be cloned. Otherwise, + /// coefficients will be aliased + /// + /// @param[in] options Options configuration + /// + /// @note It is not checked whether vectors of coefficients are + /// compatible with the given Options object if clone is false. + NonUniformRationalBSplineCore( + std::array, Base::parDim_> kv, + const std::array& coeffs, // here: coeffs have 1dim higher than NURBSClass::geoDim (weights); coeffs are given as normal coord and in eval converted to homogenous coordinates + bool clone = false, Options options = Options{}) + : Base(options) { + assert(GeoDim < 4); + + Base::init_knots(kv); + + // Copy/clone coefficients + if (clone) { + for (short_t i = 0; i < GeoDim; ++i) + Base::coeffs_[i] = (coeffs[i].clone() * coeffs[GeoDim].clone()) + .to(options.requires_grad(false)).requires_grad_(options.requires_grad()); + Base::coeffs_[GeoDim] = coeffs[GeoDim].clone().to(options.requires_grad(false)).requires_grad_(options.requires_grad()); + } + else { + for (short_t i = 0; i < GeoDim; ++i) + Base::coeffs_[i] = coeffs[i] * coeffs[GeoDim]; + Base::coeffs_[Base::geoDim_ - 1] = coeffs[Base::geoDim_ - 1]; + } + + } + + /// @brief Returns the geometric dimension without weighted space (for rational BSplines geoDim()+1) + /// + /// @result Number of geometric dimensions + inline static constexpr short_t geoDim_proj() { return GeoDim; } + //private: + +public: + + /// @brief Returns the value of the multivariate B-spline object in the point + /// `xi` + /// @{ + template + inline auto eval(const torch::Tensor& xi) const { + if constexpr (Base::parDim_ == 1) + return eval(utils::TensorArray1({ xi })); + else + throw std::runtime_error("Invalid parametric dimension"); + } + + template + inline auto eval(const std::array& xi) const { + if constexpr (Base::parDim_ == 0) { + utils::BlockTensor result; + for (short_t i = 0; i < Base::geoDim_ - 1; ++i) + if constexpr (deriv == deriv::func) + result.set(i, Base::coeffs_[i]); //TODO: Check if this is correct for NURBs + else + result.set(i, torch::zeros_like(Base::coeffs_[i])); + return result; + } + else + return eval(xi, Base::find_knot_indices(xi)); + } + + template + inline auto + eval(const std::array& xi, + const std::array& knot_indices) const { + if constexpr (Base::parDim_ == 0) { + utils::BlockTensor result; + for (short_t i = 0; i < Base::geoDim_ - 1; ++i) + if constexpr (deriv == deriv::func) + result.set(i, Base::coeffs_[i]); //TODO: Check if this is correct for NURBs + else + result.set(i, torch::zeros_like(Base::coeffs_[i])); + return result; + } + else { + return eval(xi, knot_indices, Base::find_coeff_indices(knot_indices)); + } + + + } + + template + inline auto eval(const std::array& xi, + const std::array& knot_indices, + const torch::Tensor& coeff_indices) const { + if constexpr (Base::parDim_ == 0) { + utils::BlockTensor result; + for (short_t i = 0; i < Base::geoDim_ - 1; ++i) + if constexpr (deriv == deriv::func) + result.set(i, Base::coeffs_[i]); //TODO: Check if that holds true for NURBs + else + result.set(i, torch::zeros_like(Base::coeffs_[i])); + return result; + } + else { + utils::BlockTensor result; + utils::BlockTensor pnts; + utils::BlockTensor Cw = Base::template eval(xi, knot_indices, coeff_indices); + //Divide by weight + for (short_t i = 0; i < Base::geoDim_ - 1; ++i) + pnts.set(i, torch::div(*Cw[i], *Cw[Base::geoDim_ - 1])); // TODO: Catch division by zero error! + if constexpr (deriv == deriv::func) { + return pnts; + } + else { + short_t deriv_t = static_cast(deriv); + // Ensure that only first order derivates + assert(deriv_t == 1 || deriv_t == 10 || deriv_t == 100 || deriv_t == 1000); + auto dCw = Base::template eval(xi, knot_indices, coeff_indices); + for (short_t i = 0; i < Base::geoDim_ - 1; ++i) + result.set(i, torch::div(torch::sub(*dCw[i], torch::mul(*dCw[Base::geoDim_ - 1], *pnts[i])), *Cw[Base::geoDim_ - 1])); + return result; + } + } + } + /// @} + +}; + + /// @brief B-spline (common high-level functionality) /// /// This class implements some high-level common functionality of @@ -6636,7 +6839,7 @@ class BSplineCommon : public BSplineCore, protected utils::FullQualifiedName { if constexpr (BSplineCore::parDim_ == 1) - return utils::BlockTensor( + return utils::BlockTensor( BSplineCore::template eval( xi, knot_indices, coeff_indices)) .tr(); @@ -6655,7 +6858,7 @@ class BSplineCommon : public BSplineCore, protected utils::FullQualifiedName { if constexpr (BSplineCore::parDim_ == 2) - return utils::BlockTensor( + return utils::BlockTensor( BSplineCore::template eval( xi, knot_indices, coeff_indices), BSplineCore::template eval( @@ -6677,7 +6880,7 @@ class BSplineCommon : public BSplineCore, protected utils::FullQualifiedName { if constexpr (BSplineCore::parDim_ == 3) - return utils::BlockTensor( + return utils::BlockTensor( BSplineCore::template eval( xi, knot_indices, coeff_indices), BSplineCore::template eval( @@ -6703,7 +6906,7 @@ class BSplineCommon : public BSplineCore, protected utils::FullQualifiedName { if constexpr (BSplineCore::parDim_ == 4) - return utils::BlockTensor( + return utils::BlockTensor( BSplineCore::template eval( xi, knot_indices, coeff_indices), BSplineCore::template eval( @@ -8284,6 +8487,12 @@ template using NonUniformBSpline = BSplineCommon>; +/// @brief Tensor-product non-uniform rational B-spline +template +using NonUniformRationalBSpline = +BSplineCommon>; + + /// @brief Print (as string) a UniformBSpline object template inline std::ostream & diff --git a/unittests/unittest_bsplinelib.hpp b/unittests/unittest_bsplinelib.hpp index a7e4d63f..b3726d2b 100644 --- a/unittests/unittest_bsplinelib.hpp +++ b/unittests/unittest_bsplinelib.hpp @@ -791,66 +791,7 @@ void test_bspline_eval(const Geometry_t &geometry, const Spline &bspline, test_bspline_ihess(geometry, bspline, xi, tol); } } -template auto to_bsplinelib_nurbs(const Spline& bspline) { - static_assert(Spline::geoDim() < 6, "Unsupported geometric dimension"); - - // NURBS construction - using Nurbs = bsplinelib::splines::Nurbs; - using ParameterSpace = typename Nurbs::ParameterSpace_; - using WeightedVectorSpace = typename Nurbs::WeightedVectorSpace_; - using Coordinates = typename WeightedVectorSpace::Coordinates_; - using Degrees = typename ParameterSpace::Degrees_; - using Degree = typename Degrees::value_type; - using KnotVectors = typename ParameterSpace::KnotVectors_; - using KnotVector = typename KnotVectors::value_type::element_type; - using Knots = typename KnotVector::Knots_; - using Knot = typename Knots::value_type; - - // NURBS evaluation - using ParametricCoordinate = typename Nurbs::ParametricCoordinate_; - using ScalarParametricCoordinate = typename ParametricCoordinate::value_type; - using Derivative = typename ParameterSpace::Derivative_; - using ScalarDerivative = typename Derivative::value_type; - - // Create degress structure - Degrees degrees; - for (iganet::short_t k = 0; k < bspline.parDim(); ++k) - degrees[k] = Degree{ bspline.degree(k) }; - - // Create knot vectors - KnotVectors knot_vectors; - for (iganet::short_t k = 0; k < bspline.parDim(); ++k) { - Knots knots_; - for (int64_t i = 0; i < bspline.nknots(k); ++i) - knots_.emplace_back(Knot{ - bspline.knots(k)[i].template item() }); - bsplinelib::SharedPointer knot_vector{ - std::make_shared(knots_) }; - knot_vectors[k] = std::move(knot_vector); - } - - // Create parametric space - bsplinelib::SharedPointer parameter_space{ - std::make_shared(knot_vectors, degrees) }; - - // Create coordinate vector(s) - Coordinates coordinates(bspline.ncumcoeffs(), Spline::geoDim()); - for (int64_t i = 0; i < bspline.ncumcoeffs(); ++i) { - for (int64_t j = 0; j < Spline::geoDim(); ++j) { - coordinates(i, j) = - bspline.coeffs(j)[i].template item(); - } - } - - // Create vector space - bsplinelib::SharedPointer vector_space{ - std::make_shared(std::move(coordinates)) }; - // Create B-Spline - Nurbs bsplinelib_nurbs{ parameter_space, vector_space }; - - return bsplinelib_nurbs; -} // Helper functions for NURBS unittests @@ -1187,7 +1128,7 @@ template void test_nurbs_eval(const Geometry_t& geometry, const Spline& bspline, @@ -1232,22 +1173,22 @@ void test_nurbs_eval(const Geometry_t& geometry, const Spline& bspline, test_nurbs_eval( bspline, bsplinelib_bspline, xi, tol);*/ } - // parDim == 4 has not been implemented yet - /* if constexpr (Spline::parDim() == 4) { + + if constexpr (Spline::parDim() == 4) { test_nurbs_eval( bspline, bsplinelib_bspline, xi, tol); - test_nurbs_eval( + /* test_nurbs_eval( bspline, bsplinelib_bspline, xi, tol); test_nurbs_eval( bspline, bsplinelib_bspline, xi, tol); test_nurbs_eval( - bspline, bsplinelib_bspline, xi, tol); - } */ + bspline, bsplinelib_bspline, xi, tol);*/ + } std::cout << "memory not optimized finished" << std::endl; // Evaluate function and derivatives (memory optimized) - // test_nurbs_eval( - // bspline, bsplinelib_bspline, xi, tol); - /* + /* test_nurbs_eval( + bspline, bsplinelib_bspline, xi, tol); + if constexpr (Spline::parDim() == 1) { test_nurbs_eval( bspline, bsplinelib_bspline, xi, tol); @@ -1281,37 +1222,37 @@ void test_nurbs_eval(const Geometry_t& geometry, const Spline& bspline, bspline, bsplinelib_bspline, xi, tol); } - /* if constexpr (Spline::parDim() == 4) { + if constexpr (Spline::parDim() == 4) { test_nurbs_eval( bspline, bsplinelib_bspline, xi, tol); - test_nurbs_eval( - bspline, bsplinelib_bspline, xi, tol); - test_nurbs_eval( - bspline, bsplinelib_bspline, xi, tol); - test_nurbs_eval( - bspline, bsplinelib_bspline, xi, tol); + /* test_nurbs_eval( + bspline, bsplinelib_bspline, xi, tol); + test_nurbs_eval( + bspline, bsplinelib_bspline, xi, tol); + test_nurbs_eval( + bspline, bsplinelib_bspline, xi, tol); } - std::cout << "memory optimized finished" << std::endl; + std::cout << "memory optimized finished" << std::endl;*/ // Evaluate function and derivatives from precomputed data (non-memory // optimized) - test_nurbs_eval( + /* test_nurbs_eval( bspline, bsplinelib_bspline, xi, tol); if constexpr (Spline::parDim() == 1) { test_nurbs_eval( bspline, bsplinelib_bspline, xi, tol); - /* test_nurbs_eval( + test_nurbs_eval( bspline, bsplinelib_bspline, xi, tol); test_nurbs_eval( bspline, bsplinelib_bspline, xi, tol); test_nurbs_eval( bspline, bsplinelib_bspline, xi, tol); } - + if constexpr (Spline::parDim() == 2) { test_nurbs_eval( bspline, bsplinelib_bspline, xi, tol); - /* test_nurbs_eval( + test_nurbs_eval( bspline, bsplinelib_bspline, xi, tol); test_nurbs_eval( bspline, bsplinelib_bspline, xi, tol); @@ -1322,7 +1263,7 @@ void test_nurbs_eval(const Geometry_t& geometry, const Spline& bspline, if constexpr (Spline::parDim() == 3) { test_nurbs_eval( bspline, bsplinelib_bspline, xi, tol); - /* test_nurbs_eval( + test_nurbs_eval( bspline, bsplinelib_bspline, xi, tol); test_nurbs_eval( bspline, bsplinelib_bspline, xi, tol); @@ -1330,7 +1271,7 @@ void test_nurbs_eval(const Geometry_t& geometry, const Spline& bspline, bspline, bsplinelib_bspline, xi, tol); } - /* if constexpr (Spline::parDim() == 4) { + if constexpr (Spline::parDim() == 4) { test_nurbs_eval( bspline, bsplinelib_bspline, xi, tol); test_nurbs_eval( @@ -1340,7 +1281,7 @@ void test_nurbs_eval(const Geometry_t& geometry, const Spline& bspline, test_nurbs_eval( bspline, bsplinelib_bspline, xi, tol); } - std::cout << "precomputed memory not optimized finished" << std::endl; + std::cout << "precomputed memory not optimized finished" << std::endl; // Evaluate function and derivatives from precomputed data (memory optimized) test_nurbs_eval( bspline, bsplinelib_bspline, xi, tol); @@ -1348,7 +1289,7 @@ void test_nurbs_eval(const Geometry_t& geometry, const Spline& bspline, if constexpr (Spline::parDim() == 1) { test_nurbs_eval( bspline, bsplinelib_bspline, xi, tol); - /* test_nurbs_eval( + test_nurbs_eval( bspline, bsplinelib_bspline, xi, tol); test_nurbs_eval( bspline, bsplinelib_bspline, xi, tol); @@ -1359,7 +1300,7 @@ void test_nurbs_eval(const Geometry_t& geometry, const Spline& bspline, if constexpr (Spline::parDim() == 2) { test_nurbs_eval( bspline, bsplinelib_bspline, xi, tol); - /* test_nurbs_eval( + test_nurbs_eval( bspline, bsplinelib_bspline, xi, tol); test_nurbs_eval( bspline, bsplinelib_bspline, xi, tol); @@ -1370,7 +1311,7 @@ void test_nurbs_eval(const Geometry_t& geometry, const Spline& bspline, if constexpr (Spline::parDim() == 3) { test_nurbs_eval( bspline, bsplinelib_bspline, xi, tol); - /* test_nurbs_eval( + test_nurbs_eval( bspline, bsplinelib_bspline, xi, tol); test_nurbs_eval( bspline, bsplinelib_bspline, xi, tol); @@ -1378,7 +1319,7 @@ void test_nurbs_eval(const Geometry_t& geometry, const Spline& bspline, bspline, bsplinelib_bspline, xi, tol); } - /* if constexpr (Spline::parDim() == 4) { + if constexpr (Spline::parDim() == 4) { test_nurbs_eval( bspline, bsplinelib_bspline, xi, tol); test_nurbs_eval( @@ -1388,7 +1329,7 @@ void test_nurbs_eval(const Geometry_t& geometry, const Spline& bspline, test_nurbs_eval( bspline, bsplinelib_bspline, xi, tol); } - std::cout << "precomp memory optimized finished" << std::endl;*/ + std::cout << "precomp memory optimized finished" << std::endl; */ // Evaluate gradients if constexpr (Spline::geoDim() == 1) { test_nurbs_grad(bspline, xi, tol); @@ -1405,12 +1346,12 @@ void test_nurbs_eval(const Geometry_t& geometry, const Spline& bspline, /// Evaluate Jacobian test_nurbs_jac(bspline, xi, tol); //test_nurbs_jac(bspline, xi, tol); - //test_nurbs_jac(bspline, xi, tol); - //test_nurbs_jac(bspline, xi, tol); + // test_nurbs_jac(bspline, xi, tol); + // test_nurbs_jac(bspline, xi, tol); test_nurbs_ijac(geometry, bspline, xi, tol); - // test_nurbs_ijac(geometry, bspline, xi, tol); - // test_nurbs_ijac(geometry, bspline, xi, tol); + // test_nurbs_ijac(geometry, bspline, xi, tol); + // test_nurbs_ijac(geometry, bspline, xi, tol); // test_nurbs_ijac(geometry, bspline, xi, tol); /// Evaluate Hessian - second order derivs not yet implemented diff --git a/unittests/unittest_nonuniform_rational_bspline.cxx b/unittests/unittest_nonuniform_rational_bspline.cxx index b2f28e02..861a6b57 100644 --- a/unittests/unittest_nonuniform_rational_bspline.cxx +++ b/unittests/unittest_nonuniform_rational_bspline.cxx @@ -38,6 +38,7 @@ TEST_F(BSplineTest, NonUniformRationalBSpline_parDim1_geoDim1_degrees1) { {{{0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r}}}); EXPECT_EQ(bspline.parDim(), 1); EXPECT_EQ(bspline.geoDim(), 2); + EXPECT_EQ(bspline.geoDim_proj(), 1); EXPECT_EQ(bspline.degree(0), 1); EXPECT_EQ(bspline.nknots(0), 5); EXPECT_EQ(bspline.ncoeffs(0), 3); @@ -54,6 +55,7 @@ TEST_F(BSplineTest, NonUniformRationalBSpline_parDim1_geoDim2_degrees1) { {{{0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r}}}); EXPECT_EQ(bspline.parDim(), 1); EXPECT_EQ(bspline.geoDim(), 3); + EXPECT_EQ(bspline.geoDim_proj(), 2); EXPECT_EQ(bspline.degree(0), 1); EXPECT_EQ(bspline.nknots(0), 5); EXPECT_EQ(bspline.ncoeffs(0), 3); @@ -68,6 +70,7 @@ TEST_F(BSplineTest, NonUniformRationalBSpline_parDim1_geoDim3_degrees1) { {{{0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r}}}); EXPECT_EQ(bspline.parDim(), 1); EXPECT_EQ(bspline.geoDim(), 4); + EXPECT_EQ(bspline.geoDim_proj(), 3); EXPECT_EQ(bspline.degree(0), 1); EXPECT_EQ(bspline.nknots(0), 5); EXPECT_EQ(bspline.ncoeffs(0), 3); @@ -81,7 +84,8 @@ TEST_F(BSplineTest, NonUniformRationalBSpline_parDim1_geoDim4_degrees1) { iganet::NonUniformRationalBSpline bspline( {{{0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r}}}); EXPECT_EQ(bspline.parDim(), 1); - EXPECT_EQ(bspline.geoDim(), 4); + EXPECT_EQ(bspline.geoDim(), 5); + EXPECT_EQ(bspline.geoDim_proj(), 4); EXPECT_EQ(bspline.degree(0), 1); EXPECT_EQ(bspline.nknots(0), 5); EXPECT_EQ(bspline.ncoeffs(0), 3); @@ -94,6 +98,7 @@ TEST_F(BSplineTest, NonUniformRationalBSpline_parDim2_geoDim1_degrees12) { {0.0_r, 0.0_r, 0.0_r, 1.0_r, 1.0_r, 1.0_r}}}); EXPECT_EQ(bspline.parDim(), 2); EXPECT_EQ(bspline.geoDim(), 2); + EXPECT_EQ(bspline.geoDim_proj(), 1); EXPECT_EQ(bspline.degree(0), 1); EXPECT_EQ(bspline.degree(1), 2); EXPECT_EQ(bspline.nknots(0), 5); @@ -109,6 +114,7 @@ TEST_F(BSplineTest, NonUniformRationalBSpline_parDim2_geoDim2_degrees12) { {0.0_r, 0.0_r, 0.0_r, 1.0_r, 1.0_r, 1.0_r}}}); EXPECT_EQ(bspline.parDim(), 2); EXPECT_EQ(bspline.geoDim(), 3); + EXPECT_EQ(bspline.geoDim_proj(), 2); EXPECT_EQ(bspline.degree(0), 1); EXPECT_EQ(bspline.degree(1), 2); EXPECT_EQ(bspline.nknots(0), 5); @@ -124,6 +130,7 @@ TEST_F(BSplineTest, NonUniformRationalBSpline_parDim2_geoDim3_degrees12) { {0.0_r, 0.0_r, 0.0_r, 1.0_r, 1.0_r, 1.0_r}}}); EXPECT_EQ(bspline.parDim(), 2); EXPECT_EQ(bspline.geoDim(), 4); + EXPECT_EQ(bspline.geoDim_proj(), 3); EXPECT_EQ(bspline.degree(0), 1); EXPECT_EQ(bspline.degree(1), 2); EXPECT_EQ(bspline.nknots(0), 5); @@ -138,7 +145,8 @@ TEST_F(BSplineTest, NonUniformRationalBSpline_parDim2_geoDim4_degrees12) { {{{0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r}, {0.0_r, 0.0_r, 0.0_r, 1.0_r, 1.0_r, 1.0_r}}}); EXPECT_EQ(bspline.parDim(), 2); - EXPECT_EQ(bspline.geoDim(), 4); + EXPECT_EQ(bspline.geoDim(), 5); + EXPECT_EQ(bspline.geoDim_proj(), 4); EXPECT_EQ(bspline.degree(0), 1); EXPECT_EQ(bspline.degree(1), 2); EXPECT_EQ(bspline.nknots(0), 5); @@ -155,6 +163,7 @@ TEST_F(BSplineTest, NonUniformRationalBSpline_parDim3_geoDim1_degrees123) { {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}); EXPECT_EQ(bspline.parDim(), 3); EXPECT_EQ(bspline.geoDim(), 2); + EXPECT_EQ(bspline.geoDim_proj(), 1); EXPECT_EQ(bspline.degree(0), 1); EXPECT_EQ(bspline.degree(1), 2); EXPECT_EQ(bspline.degree(2), 3); @@ -174,6 +183,7 @@ TEST_F(BSplineTest, NonUniformRationalBSpline_parDim3_geoDim2_degrees123) { {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}); EXPECT_EQ(bspline.parDim(), 3); EXPECT_EQ(bspline.geoDim(), 3); + EXPECT_EQ(bspline.geoDim_proj(), 2); EXPECT_EQ(bspline.degree(0), 1); EXPECT_EQ(bspline.degree(1), 2); EXPECT_EQ(bspline.degree(2), 3); @@ -193,6 +203,7 @@ TEST_F(BSplineTest, NonUniformRationalBSpline_parDim3_geoDim3_degrees123) { {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}); EXPECT_EQ(bspline.parDim(), 3); EXPECT_EQ(bspline.geoDim(), 4); + EXPECT_EQ(bspline.geoDim_proj(), 3); EXPECT_EQ(bspline.degree(0), 1); EXPECT_EQ(bspline.degree(1), 2); EXPECT_EQ(bspline.degree(2), 3); @@ -211,7 +222,8 @@ TEST_F(BSplineTest, NonUniformRationalBSpline_parDim3_geoDim4_degrees123) { {0.0_r, 0.0_r, 0.0_r, 1.0_r, 1.0_r, 1.0_r}, {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}); EXPECT_EQ(bspline.parDim(), 3); - EXPECT_EQ(bspline.geoDim(), 4); + EXPECT_EQ(bspline.geoDim(), 5); + EXPECT_EQ(bspline.geoDim_proj(), 4); EXPECT_EQ(bspline.degree(0), 1); EXPECT_EQ(bspline.degree(1), 2); EXPECT_EQ(bspline.degree(2), 3); @@ -233,6 +245,7 @@ TEST_F(BSplineTest, NonUniformRationalBSpline_parDim4_geoDim1_degrees1234) { 1.0_r}}}); EXPECT_EQ(bspline.parDim(), 4); EXPECT_EQ(bspline.geoDim(), 2); + EXPECT_EQ(bspline.geoDim_proj(), 1); EXPECT_EQ(bspline.degree(0), 1); EXPECT_EQ(bspline.degree(1), 2); EXPECT_EQ(bspline.degree(2), 3); @@ -257,6 +270,7 @@ TEST_F(BSplineTest, NonUniformRationalBSpline_parDim4_geoDim2_degrees1234) { 1.0_r}}}); EXPECT_EQ(bspline.parDim(), 4); EXPECT_EQ(bspline.geoDim(), 3); + EXPECT_EQ(bspline.geoDim_proj(), 2); EXPECT_EQ(bspline.degree(0), 1); EXPECT_EQ(bspline.degree(1), 2); EXPECT_EQ(bspline.degree(2), 3); @@ -281,6 +295,7 @@ TEST_F(BSplineTest, NonUniformRationalBSpline_parDim4_geoDim3_degrees1234) { 1.0_r}}}); EXPECT_EQ(bspline.parDim(), 4); EXPECT_EQ(bspline.geoDim(), 4); + EXPECT_EQ(bspline.geoDim_proj(), 3); EXPECT_EQ(bspline.degree(0), 1); EXPECT_EQ(bspline.degree(1), 2); EXPECT_EQ(bspline.degree(2), 3); @@ -304,7 +319,8 @@ TEST_F(BSplineTest, NonUniformRationalBSpline_parDim4_geoDim4_degrees1234) { {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}); EXPECT_EQ(bspline.parDim(), 4); - EXPECT_EQ(bspline.geoDim(), 4); + EXPECT_EQ(bspline.geoDim(), 5); + EXPECT_EQ(bspline.geoDim_proj(), 4); EXPECT_EQ(bspline.degree(0), 1); EXPECT_EQ(bspline.degree(1), 2); EXPECT_EQ(bspline.degree(2), 3); @@ -659,7 +675,7 @@ TEST_F(BSplineTest, NonUniformRationalBSpline_to_from_xml) { {4}, iganet::init::zeros, options); bspline_out.transform([](const std::array xi) { - return std::array{static_cast(std::rand()), 1.0}; + return std::array{static_cast(std::rand()), static_cast(std::rand())}; }); pugi::xml_document doc = bspline_out.to_xml(); @@ -703,7 +719,7 @@ TEST_F(BSplineTest, NonUniformRationalBSpline_to_from_xml) { bspline_out.transform([](const std::array xi) { return std::array{static_cast(std::rand()), - static_cast(std::rand()), 1.0}; + static_cast(std::rand()), static_cast(std::rand())}; }); pugi::xml_document doc = bspline_out.to_xml(); @@ -748,7 +764,8 @@ TEST_F(BSplineTest, NonUniformRationalBSpline_to_from_xml) { bspline_out.transform([](const std::array xi) { return std::array{static_cast(std::rand()), static_cast(std::rand()), - static_cast(std::rand()), 1.0}; + static_cast(std::rand()), + static_cast(std::rand())}; }); pugi::xml_document doc = bspline_out.to_xml(); @@ -791,9 +808,10 @@ TEST_F(BSplineTest, NonUniformRationalBSpline_to_from_xml) { {4}, iganet::init::zeros, options); bspline_out.transform([](const std::array xi) { - return std::array{ + return std::array{ static_cast(std::rand()), static_cast(std::rand()), - static_cast(std::rand()), static_cast(std::rand()), 1.0}; + static_cast(std::rand()), static_cast(std::rand()), + static_cast(std::rand())}; }); pugi::xml_document doc = bspline_out.to_xml(); @@ -980,9 +998,10 @@ TEST_F(BSplineTest, NonUniformRationalBSpline_to_from_xml) { {4, 5}, iganet::init::zeros, options); bspline_out.transform([](const std::array xi) { - return std::array{ + return std::array{ + static_cast(std::rand()), static_cast(std::rand()), static_cast(std::rand()), static_cast(std::rand()), - static_cast(std::rand()), static_cast(std::rand())}; + static_cast(std::rand())}; }); pugi::xml_document doc = bspline_out.to_xml(); @@ -1173,9 +1192,10 @@ TEST_F(BSplineTest, NonUniformRationalBSpline_to_from_xml) { {4, 5, 6}, iganet::init::zeros, options); bspline_out.transform([](const std::array xi) { - return std::array{ + return std::array{ + static_cast(std::rand()), static_cast(std::rand()), static_cast(std::rand()), static_cast(std::rand()), - static_cast(std::rand()), static_cast(std::rand())}; + static_cast(std::rand())}; }); pugi::xml_document doc = bspline_out.to_xml(); @@ -1366,9 +1386,10 @@ TEST_F(BSplineTest, NonUniformRationalBSpline_to_from_xml) { {4, 5, 6, 2}, iganet::init::zeros, options); bspline_out.transform([](const std::array xi) { - return std::array{ + return std::array{ static_cast(std::rand()), static_cast(std::rand()), - static_cast(std::rand()), static_cast(std::rand())}; + static_cast(std::rand()), static_cast(std::rand()), + static_cast(std::rand()), }; }); pugi::xml_document doc = bspline_out.to_xml(); @@ -1496,7 +1517,8 @@ TEST_F(BSplineTest, NonUniformRationalBSpline_to_from_json) { bspline_out.transform([](const std::array xi) { return std::array{static_cast(std::rand()), static_cast(std::rand()), - static_cast(std::rand()), 1.0}; + static_cast(std::rand()), + static_cast(std::rand()), }; }); nlohmann::json json = bspline_out.to_json(); @@ -1534,9 +1556,10 @@ TEST_F(BSplineTest, NonUniformRationalBSpline_to_from_json) { {4}, iganet::init::zeros, options); bspline_out.transform([](const std::array xi) { - return std::array{ + return std::array{ + static_cast(std::rand()), static_cast(std::rand()), static_cast(std::rand()), static_cast(std::rand()), - static_cast(std::rand()), static_cast(std::rand()),1.0}; + static_cast(std::rand()), }; }); nlohmann::json json = bspline_out.to_json(); @@ -1574,7 +1597,7 @@ TEST_F(BSplineTest, NonUniformRationalBSpline_to_from_json) { {4, 5}, iganet::init::zeros, options); bspline_out.transform([](const std::array xi) { - return std::array{static_cast(std::rand()),1.0}; + return std::array{static_cast(std::rand()), static_cast(std::rand()), }; }); nlohmann::json json = bspline_out.to_json(); @@ -1613,7 +1636,8 @@ TEST_F(BSplineTest, NonUniformRationalBSpline_to_from_json) { bspline_out.transform([](const std::array xi) { return std::array{static_cast(std::rand()), - static_cast(std::rand()),1.0}; + static_cast(std::rand()), + static_cast(std::rand()), }; }); nlohmann::json json = bspline_out.to_json(); @@ -1653,7 +1677,8 @@ TEST_F(BSplineTest, NonUniformRationalBSpline_to_from_json) { bspline_out.transform([](const std::array xi) { return std::array{static_cast(std::rand()), static_cast(std::rand()), - static_cast(std::rand()),1.0}; + static_cast(std::rand()), + static_cast(std::rand()), }; }); nlohmann::json json = bspline_out.to_json(); @@ -1691,9 +1716,10 @@ TEST_F(BSplineTest, NonUniformRationalBSpline_to_from_json) { {4, 5}, iganet::init::zeros, options); bspline_out.transform([](const std::array xi) { - return std::array{ + return std::array{ + static_cast(std::rand()), static_cast(std::rand()), static_cast(std::rand()), static_cast(std::rand()), - static_cast(std::rand()), static_cast(std::rand())}; + static_cast(std::rand()), }; }); nlohmann::json json = bspline_out.to_json(); @@ -1731,7 +1757,7 @@ TEST_F(BSplineTest, NonUniformRationalBSpline_to_from_json) { {4, 5, 6}, iganet::init::zeros, options); bspline_out.transform([](const std::array xi) { - return std::array{static_cast(std::rand()), 1.0}; + return std::array{static_cast(std::rand()), static_cast(std::rand()), }; }); nlohmann::json json = bspline_out.to_json(); @@ -1773,7 +1799,8 @@ TEST_F(BSplineTest, NonUniformRationalBSpline_to_from_json) { bspline_out.transform([](const std::array xi) { return std::array{static_cast(std::rand()), - static_cast(std::rand()), 1.0}; + static_cast(std::rand()), + static_cast(std::rand()), }; }); nlohmann::json json = bspline_out.to_json(); @@ -1816,7 +1843,8 @@ TEST_F(BSplineTest, NonUniformRationalBSpline_to_from_json) { bspline_out.transform([](const std::array xi) { return std::array{static_cast(std::rand()), static_cast(std::rand()), - static_cast(std::rand()), 1.0}; + static_cast(std::rand()), + static_cast(std::rand()), }; }); nlohmann::json json = bspline_out.to_json(); @@ -1857,9 +1885,10 @@ TEST_F(BSplineTest, NonUniformRationalBSpline_to_from_json) { {4, 5, 6}, iganet::init::zeros, options); bspline_out.transform([](const std::array xi) { - return std::array{ + return std::array{ + static_cast(std::rand()), static_cast(std::rand()), static_cast(std::rand()), static_cast(std::rand()), - static_cast(std::rand()), static_cast(std::rand())}; + static_cast(std::rand()), }; }); nlohmann::json json = bspline_out.to_json(); @@ -1900,7 +1929,7 @@ TEST_F(BSplineTest, NonUniformRationalBSpline_to_from_json) { {4, 5, 6, 2}, iganet::init::zeros, options); bspline_out.transform([](const std::array xi) { - return std::array{static_cast(std::rand()), 1.0}; + return std::array{static_cast(std::rand()), static_cast(std::rand()), }; }); nlohmann::json json = bspline_out.to_json(); @@ -1942,7 +1971,8 @@ TEST_F(BSplineTest, NonUniformRationalBSpline_to_from_json) { bspline_out.transform([](const std::array xi) { return std::array{static_cast(std::rand()), - static_cast(std::rand()), 1.0}; + static_cast(std::rand()), + static_cast(std::rand())}; }); nlohmann::json json = bspline_out.to_json(); @@ -1985,7 +2015,8 @@ TEST_F(BSplineTest, NonUniformRationalBSpline_to_from_json) { bspline_out.transform([](const std::array xi) { return std::array{static_cast(std::rand()), static_cast(std::rand()), - static_cast(std::rand()), 1.0}; + static_cast(std::rand()), + static_cast(std::rand())}; }); nlohmann::json json = bspline_out.to_json(); @@ -2026,9 +2057,10 @@ TEST_F(BSplineTest, NonUniformRationalBSpline_to_from_json) { {4, 5, 6, 2}, iganet::init::zeros, options); bspline_out.transform([](const std::array xi) { - return std::array{ + return std::array{ static_cast(std::rand()), static_cast(std::rand()), - static_cast(std::rand()), static_cast(std::rand())}; + static_cast(std::rand()), static_cast(std::rand()), + static_cast(std::rand())}; }); nlohmann::json json = bspline_out.to_json(); diff --git a/unittests/unittest_nonuniform_rational_bspline_eval.cxx b/unittests/unittest_nonuniform_rational_bspline_eval.cxx index 8237c720..ff579bb7 100644 --- a/unittests/unittest_nonuniform_rational_bspline_eval.cxx +++ b/unittests/unittest_nonuniform_rational_bspline_eval.cxx @@ -12,7 +12,6 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -//Comment: parDim == 1 and parDim == 4 not yet supported but tests prepared. #include #include @@ -28,7 +27,7 @@ class BSplineTest : public ::testing::Test { protected: using real_t = iganet::unittests::real_t; iganet::Options options; - /* + static constexpr auto trafo_parDim1_geoDim1 = [](const std::array xi) { return std::array{xi[0] * xi[0], sin(static_cast(M_PI)* xi[0]) + 0.3}; @@ -49,7 +48,7 @@ class BSplineTest : public ::testing::Test { xi[0] * xi[0], sin(static_cast(M_PI) * xi[0]), xi[0], cos(static_cast(M_PI) * xi[0])}; }; - */ + static constexpr auto trafo_parDim2_geoDim1 = [](const std::array xi) { return std::array{xi[0] * xi[1], sin(static_cast(M_PI)* xi[0]) + 0.3}; @@ -91,7 +90,7 @@ class BSplineTest : public ::testing::Test { xi[0] * xi[1] * xi[2], sin(static_cast(M_PI) * xi[0]), xi[1] * xi[2], cos(static_cast(M_PI) * xi[1]), sin(static_cast(M_PI)* xi[0])+0.3}; }; - /* + static constexpr auto trafo_parDim4_geoDim1 = [](const std::array xi) { return std::array{xi[0] * xi[1] * xi[2] * xi[3], sin(static_cast(M_PI)* xi[0])}; @@ -113,9 +112,9 @@ class BSplineTest : public ::testing::Test { sin(static_cast(M_PI) * xi[0]), xi[1] * xi[2] * xi[3], cos(static_cast(M_PI) * xi[1])}; - };*/ + }; }; -/* + TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim1_geoDim1_degrees1) { iganet::NonUniformRationalBSpline geo( {{{0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r}}}, iganet::init::greville, options); @@ -280,7 +279,7 @@ TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim1_geoDim2_degrees6) { {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, options); test_nurbs_eval(geo, bspline, xi, 1e-11); } -*//* + TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim1_geoDim3_degrees1) { iganet::NonUniformRationalBSpline geo( {{{0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r}}}, iganet::init::greville, options); @@ -361,9 +360,9 @@ TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim1_geoDim3_degrees6) { auto xi = iganet::utils::to_tensorArray( {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, options); test_nurbs_eval(geo, bspline, xi, 1e-11); -}*/ -/* -TEST_F(BSplineTest, NonUniformBSpline_eval_parDim1_geoDim4_degrees1) { +} + +/*TEST_F(BSplineTest, NonUniformBSpline_eval_parDim1_geoDim4_degrees1) { iganet::NonUniformBSpline geo( {{{0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r}}}, iganet::init::greville, options); iganet::NonUniformBSpline bspline( @@ -443,8 +442,8 @@ TEST_F(BSplineTest, NonUniformBSpline_eval_parDim1_geoDim4_degrees6) { auto xi = iganet::utils::to_tensorArray( {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, options); test_nurbs_eval(geo, bspline, xi, 1e-10); -} -*/ +}*/ + TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim2_geoDim1_degrees22) { iganet::NonUniformRationalBSpline geo( {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, @@ -613,7 +612,7 @@ TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim2_geoDim3_degrees64) { test_nurbs_eval(geo, bspline, xi, 1e-5); } -TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim2_geoDim4_degrees22) { +/*TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim2_geoDim4_degrees22) { iganet::NonUniformRationalBSpline geo( {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, @@ -667,7 +666,7 @@ TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim2_geoDim4_degrees64) { {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, options); test_nurbs_eval(geo, bspline, xi, 1e-5); -} +}*/ TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim3_geoDim1_degrees222) { iganet::NonUniformRationalBSpline geo( @@ -864,7 +863,7 @@ TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim3_geoDim3_degrees642) { test_nurbs_eval(geo, bspline, xi, 1e-6); } -TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim3_geoDim4_degrees222) { +/*TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim3_geoDim4_degrees222) { iganet::NonUniformRationalBSpline geo( {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, @@ -927,8 +926,8 @@ TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim3_geoDim4_degrees642) { {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, options); test_nurbs_eval(geo, bspline, xi, 1e-6); -} -/* +}*/ + TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim4_geoDim1_degrees2222) { iganet::NonUniformRationalBSpline geo( {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, @@ -1072,8 +1071,8 @@ TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim4_geoDim3_degrees2463) {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, options); test_nurbs_eval(geo, bspline, xi, 1e-12); } -/* -TEST_F(BSplineTest, NonUniformBSpline_eval_parDim4_geoDim4_degrees2222) { + +/*TEST_F(BSplineTest, NonUniformBSpline_eval_parDim4_geoDim4_degrees2222) { iganet::NonUniformBSpline geo( {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, @@ -1119,8 +1118,8 @@ TEST_F(BSplineTest, NonUniformBSpline_eval_parDim4_geoDim4_degrees2463) { {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, options); test_nurbs_eval(geo, bspline, xi, 1e-12); -} -*/ +}*/ + int main(int argc, char **argv) { ::testing::InitGoogleTest(&argc, argv); iganet::init(); From ac09a4b256ae46e52b3683ff84a90c6d97cea2a7 Mon Sep 17 00:00:00 2001 From: merback Date: Thu, 23 May 2024 09:11:58 +0200 Subject: [PATCH 05/13] geoDim == 4 and stricter tolerances in NURBS unittests --- include/bspline.hpp | 3 +- unittests/unittest_bsplinelib.hpp | 2 +- ...ttest_nonuniform_rational_bspline_eval.cxx | 124 +++++++++--------- 3 files changed, 65 insertions(+), 64 deletions(-) diff --git a/include/bspline.hpp b/include/bspline.hpp index 50c7030f..03f57f02 100644 --- a/include/bspline.hpp +++ b/include/bspline.hpp @@ -4994,8 +4994,9 @@ class NonUniformRationalBSplineCore utils::BlockTensor pnts; utils::BlockTensor Cw = Base::template eval(xi, knot_indices, coeff_indices); //Divide by weight + assert(std::abs(*Cw[Base::geoDim_ - 1]) > 0); // Avoid division by zero for (short_t i = 0; i < Base::geoDim_ - 1; ++i) - pnts.set(i, torch::div(*Cw[i], *Cw[Base::geoDim_ - 1])); // TODO: Catch division by zero error! + pnts.set(i, torch::div(*Cw[i], *Cw[Base::geoDim_ - 1])); if constexpr (deriv == deriv::func) { return pnts; } diff --git a/unittests/unittest_bsplinelib.hpp b/unittests/unittest_bsplinelib.hpp index b3726d2b..d609d511 100644 --- a/unittests/unittest_bsplinelib.hpp +++ b/unittests/unittest_bsplinelib.hpp @@ -1186,7 +1186,7 @@ void test_nurbs_eval(const Geometry_t& geometry, const Spline& bspline, } std::cout << "memory not optimized finished" << std::endl; // Evaluate function and derivatives (memory optimized) - /* test_nurbs_eval( + /* test_nurbs_eval( bspline, bsplinelib_bspline, xi, tol); if constexpr (Spline::parDim() == 1) { diff --git a/unittests/unittest_nonuniform_rational_bspline_eval.cxx b/unittests/unittest_nonuniform_rational_bspline_eval.cxx index ff579bb7..fff00dbd 100644 --- a/unittests/unittest_nonuniform_rational_bspline_eval.cxx +++ b/unittests/unittest_nonuniform_rational_bspline_eval.cxx @@ -44,9 +44,9 @@ class BSplineTest : public ::testing::Test { }; static constexpr auto trafo_parDim1_geoDim4 = [](const std::array xi) { - return std::array{ - xi[0] * xi[0], sin(static_cast(M_PI) * xi[0]), xi[0], - cos(static_cast(M_PI) * xi[0])}; + return std::array{ + xi[0] * xi[0], sin(static_cast(M_PI)* xi[0]), xi[0], + cos(static_cast(M_PI)* xi[0]), sin(static_cast(M_PI)* xi[0]) + 0.3}; }; static constexpr auto trafo_parDim2_geoDim1 = @@ -93,25 +93,25 @@ class BSplineTest : public ::testing::Test { static constexpr auto trafo_parDim4_geoDim1 = [](const std::array xi) { - return std::array{xi[0] * xi[1] * xi[2] * xi[3], sin(static_cast(M_PI)* xi[0])}; + return std::array{xi[0] * xi[1] * xi[2] * xi[3], sin(static_cast(M_PI)* xi[0])+0.3}; }; static constexpr auto trafo_parDim4_geoDim2 = [](const std::array xi) { return std::array{xi[0] * xi[1] * xi[2] * xi[3], - sin(static_cast(M_PI) * xi[0]), sin(static_cast(M_PI)* xi[0])}; + sin(static_cast(M_PI) * xi[0]), sin(static_cast(M_PI)* xi[0])+0.3}; }; static constexpr auto trafo_parDim4_geoDim3 = [](const std::array xi) { return std::array{xi[0] * xi[1] * xi[2] * xi[3], sin(static_cast(M_PI) * xi[0]), - xi[1] * xi[2] * xi[3], sin(static_cast(M_PI)* xi[0])}; + xi[1] * xi[2] * xi[3], sin(static_cast(M_PI)* xi[0])+0.3}; }; static constexpr auto trafo_parDim4_geoDim4 = [](const std::array xi) { - return std::array{xi[0] * xi[1] * xi[2] * xi[3], + return std::array{xi[0] * xi[1] * xi[2] * xi[3], sin(static_cast(M_PI) * xi[0]), xi[1] * xi[2] * xi[3], - cos(static_cast(M_PI) * xi[1])}; + cos(static_cast(M_PI) * xi[1]), sin(static_cast(M_PI)* xi[0])+0.3}; }; }; @@ -362,10 +362,10 @@ TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim1_geoDim3_degrees6) { test_nurbs_eval(geo, bspline, xi, 1e-11); } -/*TEST_F(BSplineTest, NonUniformBSpline_eval_parDim1_geoDim4_degrees1) { - iganet::NonUniformBSpline geo( +TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim1_geoDim4_degrees1) { + iganet::NonUniformRationalBSpline geo( {{{0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r}}}, iganet::init::greville, options); - iganet::NonUniformBSpline bspline( + iganet::NonUniformRationalBSpline bspline( {{{0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r}}}, iganet::init::zeros, options); bspline.transform(trafo_parDim1_geoDim4); auto xi = iganet::utils::to_tensorArray( @@ -373,11 +373,11 @@ TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim1_geoDim3_degrees6) { test_nurbs_eval(geo, bspline, xi, 1e-12); } -TEST_F(BSplineTest, NonUniformBSpline_eval_parDim1_geoDim4_degrees2) { - iganet::NonUniformBSpline geo( +TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim1_geoDim4_degrees2) { + iganet::NonUniformRationalBSpline geo( {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, iganet::init::greville, options); - iganet::NonUniformBSpline bspline( + iganet::NonUniformRationalBSpline bspline( {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, iganet::init::zeros, options); bspline.transform(trafo_parDim1_geoDim4); @@ -386,11 +386,11 @@ TEST_F(BSplineTest, NonUniformBSpline_eval_parDim1_geoDim4_degrees2) { test_nurbs_eval(geo, bspline, xi, 1e-12); } -TEST_F(BSplineTest, NonUniformBSpline_eval_parDim1_geoDim4_degrees3) { - iganet::NonUniformBSpline geo( +TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim1_geoDim4_degrees3) { + iganet::NonUniformRationalBSpline geo( {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, iganet::init::greville, options); - iganet::NonUniformBSpline bspline( + iganet::NonUniformRationalBSpline bspline( {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, iganet::init::zeros, options); bspline.transform(trafo_parDim1_geoDim4); @@ -399,12 +399,12 @@ TEST_F(BSplineTest, NonUniformBSpline_eval_parDim1_geoDim4_degrees3) { test_nurbs_eval(geo, bspline, xi, 1e-12); } -TEST_F(BSplineTest, NonUniformBSpline_eval_parDim1_geoDim4_degrees4) { - iganet::NonUniformBSpline geo( +TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim1_geoDim4_degrees4) { + iganet::NonUniformRationalBSpline geo( {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, iganet::init::greville, options); - iganet::NonUniformBSpline bspline( + iganet::NonUniformRationalBSpline bspline( {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, iganet::init::zeros, options); @@ -414,12 +414,12 @@ TEST_F(BSplineTest, NonUniformBSpline_eval_parDim1_geoDim4_degrees4) { test_nurbs_eval(geo, bspline, xi, 1e-12); } -TEST_F(BSplineTest, NonUniformBSpline_eval_parDim1_geoDim4_degrees5) { - iganet::NonUniformBSpline geo( +TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim1_geoDim4_degrees5) { + iganet::NonUniformRationalBSpline geo( {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, iganet::init::greville, options); - iganet::NonUniformBSpline bspline( + iganet::NonUniformRationalBSpline bspline( {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, iganet::init::zeros, options); @@ -429,12 +429,12 @@ TEST_F(BSplineTest, NonUniformBSpline_eval_parDim1_geoDim4_degrees5) { test_nurbs_eval(geo, bspline, xi, 1e-12); } -TEST_F(BSplineTest, NonUniformBSpline_eval_parDim1_geoDim4_degrees6) { - iganet::NonUniformBSpline geo( +TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim1_geoDim4_degrees6) { + iganet::NonUniformRationalBSpline geo( {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, iganet::init::greville, options); - iganet::NonUniformBSpline bspline( + iganet::NonUniformRationalBSpline bspline( {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, iganet::init::zeros, options); @@ -442,7 +442,7 @@ TEST_F(BSplineTest, NonUniformBSpline_eval_parDim1_geoDim4_degrees6) { auto xi = iganet::utils::to_tensorArray( {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, options); test_nurbs_eval(geo, bspline, xi, 1e-10); -}*/ +} TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim2_geoDim1_degrees22) { iganet::NonUniformRationalBSpline geo( @@ -457,7 +457,7 @@ TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim2_geoDim1_degrees22) { auto xi = iganet::utils::to_tensorArray( {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, options); - test_nurbs_eval(geo, bspline, xi, 1e-6); + test_nurbs_eval(geo, bspline, xi, 1e-12); } TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim2_geoDim1_degrees46) { @@ -477,7 +477,7 @@ TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim2_geoDim1_degrees46) { auto xi = iganet::utils::to_tensorArray( {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, options); - test_nurbs_eval(geo, bspline, xi, 1e-6); + test_nurbs_eval(geo, bspline, xi, 1e-12); } TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim2_geoDim1_degrees64) { @@ -497,7 +497,7 @@ TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim2_geoDim1_degrees64) { auto xi = iganet::utils::to_tensorArray( {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, options); - test_nurbs_eval(geo, bspline, xi, 1e-5); + test_nurbs_eval(geo, bspline, xi, 1e-12); } TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim2_geoDim2_degrees22) { @@ -513,7 +513,7 @@ TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim2_geoDim2_degrees22) { auto xi = iganet::utils::to_tensorArray( {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, options); - test_nurbs_eval(geo, bspline, xi, 1e-6); + test_nurbs_eval(geo, bspline, xi, 1e-12); } TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim2_geoDim2_degrees46) { @@ -533,7 +533,7 @@ TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim2_geoDim2_degrees46) { auto xi = iganet::utils::to_tensorArray( {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, options); - test_nurbs_eval(geo, bspline, xi, 1e-5); + test_nurbs_eval(geo, bspline, xi, 1e-12); } TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim2_geoDim2_degrees64) { @@ -553,7 +553,7 @@ TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim2_geoDim2_degrees64) { auto xi = iganet::utils::to_tensorArray( {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, options); - test_nurbs_eval(geo, bspline, xi, 1e-5); + test_nurbs_eval(geo, bspline, xi, 1e-12); } TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim2_geoDim3_degrees22) { @@ -569,7 +569,7 @@ TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim2_geoDim3_degrees22) { auto xi = iganet::utils::to_tensorArray( {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, options); - test_nurbs_eval(geo, bspline, xi, 1e-6); + test_nurbs_eval(geo, bspline, xi, 1e-12); } TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim2_geoDim3_degrees46) { @@ -589,7 +589,7 @@ TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim2_geoDim3_degrees46) { auto xi = iganet::utils::to_tensorArray( {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, options); - test_nurbs_eval(geo, bspline, xi, 1e-5); + test_nurbs_eval(geo, bspline, xi, 1e-12); } TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim2_geoDim3_degrees64) { @@ -609,10 +609,10 @@ TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim2_geoDim3_degrees64) { auto xi = iganet::utils::to_tensorArray( {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, options); - test_nurbs_eval(geo, bspline, xi, 1e-5); + test_nurbs_eval(geo, bspline, xi, 1e-12); } -/*TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim2_geoDim4_degrees22) { +TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim2_geoDim4_degrees22) { iganet::NonUniformRationalBSpline geo( {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, @@ -625,7 +625,7 @@ TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim2_geoDim3_degrees64) { auto xi = iganet::utils::to_tensorArray( {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, options); - test_nurbs_eval(geo, bspline, xi, 1e-6); + test_nurbs_eval(geo, bspline, xi, 1e-12); } TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim2_geoDim4_degrees46) { @@ -645,7 +645,7 @@ TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim2_geoDim4_degrees46) { auto xi = iganet::utils::to_tensorArray( {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, options); - test_nurbs_eval(geo, bspline, xi, 1e-6); + test_nurbs_eval(geo, bspline, xi, 1e-12); } TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim2_geoDim4_degrees64) { @@ -665,8 +665,8 @@ TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim2_geoDim4_degrees64) { auto xi = iganet::utils::to_tensorArray( {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, options); - test_nurbs_eval(geo, bspline, xi, 1e-5); -}*/ + test_nurbs_eval(geo, bspline, xi, 1e-12); +} TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim3_geoDim1_degrees222) { iganet::NonUniformRationalBSpline geo( @@ -684,7 +684,7 @@ TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim3_geoDim1_degrees222) { {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, options); - test_nurbs_eval(geo, bspline, xi, 1e-6); + test_nurbs_eval(geo, bspline, xi, 1e-12); } TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim3_geoDim1_degrees462) { @@ -707,7 +707,7 @@ TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim3_geoDim1_degrees462) { {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, options); - test_nurbs_eval(geo, bspline, xi, 1e-5); + test_nurbs_eval(geo, bspline, xi, 1e-12); } TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim3_geoDim1_degrees642) { @@ -730,7 +730,7 @@ TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim3_geoDim1_degrees642) { {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, options); - test_nurbs_eval(geo, bspline, xi, 1e-6); + test_nurbs_eval(geo, bspline, xi, 1e-12); } TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim3_geoDim2_degrees222) { @@ -749,7 +749,7 @@ TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim3_geoDim2_degrees222) { {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, options); - test_nurbs_eval(geo, bspline, xi, 1e-6); + test_nurbs_eval(geo, bspline, xi, 1e-12); } TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim3_geoDim2_degrees462) { @@ -772,7 +772,7 @@ TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim3_geoDim2_degrees462) { {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, options); - test_nurbs_eval(geo, bspline, xi, 1e-5); + test_nurbs_eval(geo, bspline, xi, 1e-12); } TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim3_geoDim2_degrees642) { @@ -795,7 +795,7 @@ TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim3_geoDim2_degrees642) { {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, options); - test_nurbs_eval(geo, bspline, xi, 1e-6); + test_nurbs_eval(geo, bspline, xi, 1e-12); } TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim3_geoDim3_degrees222) { @@ -814,7 +814,7 @@ TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim3_geoDim3_degrees222) { {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, options); - test_nurbs_eval(geo, bspline, xi, 1e-6); + test_nurbs_eval(geo, bspline, xi, 1e-12); } TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim3_geoDim3_degrees462) { @@ -837,7 +837,7 @@ TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim3_geoDim3_degrees462) { {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, options); - test_nurbs_eval(geo, bspline, xi, 1e-6); + test_nurbs_eval(geo, bspline, xi, 1e-12); } TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim3_geoDim3_degrees642) { @@ -860,10 +860,10 @@ TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim3_geoDim3_degrees642) { {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, options); - test_nurbs_eval(geo, bspline, xi, 1e-6); + test_nurbs_eval(geo, bspline, xi, 1e-12); } -/*TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim3_geoDim4_degrees222) { +TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim3_geoDim4_degrees222) { iganet::NonUniformRationalBSpline geo( {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, @@ -879,7 +879,7 @@ TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim3_geoDim3_degrees642) { {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, options); - test_nurbs_eval(geo, bspline, xi, 1e-6); + test_nurbs_eval(geo, bspline, xi, 1e-12); } TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim3_geoDim4_degrees462) { @@ -902,7 +902,7 @@ TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim3_geoDim4_degrees462) { {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, options); - test_nurbs_eval(geo, bspline, xi, 1e-6); + test_nurbs_eval(geo, bspline, xi, 1e-12); } TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim3_geoDim4_degrees642) { @@ -925,8 +925,8 @@ TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim3_geoDim4_degrees642) { {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, options); - test_nurbs_eval(geo, bspline, xi, 1e-6); -}*/ + test_nurbs_eval(geo, bspline, xi, 1e-12); +} TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim4_geoDim1_degrees2222) { iganet::NonUniformRationalBSpline geo( @@ -1072,14 +1072,14 @@ TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim4_geoDim3_degrees2463) test_nurbs_eval(geo, bspline, xi, 1e-12); } -/*TEST_F(BSplineTest, NonUniformBSpline_eval_parDim4_geoDim4_degrees2222) { - iganet::NonUniformBSpline geo( +TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim4_geoDim4_degrees2222) { + iganet::NonUniformRationalBSpline geo( {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, iganet::init::greville, options); - iganet::NonUniformBSpline bspline( + iganet::NonUniformRationalBSpline bspline( {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, @@ -1094,8 +1094,8 @@ TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim4_geoDim3_degrees2463) test_nurbs_eval(geo, bspline, xi, 1e-12); } -TEST_F(BSplineTest, NonUniformBSpline_eval_parDim4_geoDim4_degrees2463) { - iganet::NonUniformBSpline geo( +TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim4_geoDim4_degrees2463) { + iganet::NonUniformRationalBSpline geo( {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, @@ -1103,7 +1103,7 @@ TEST_F(BSplineTest, NonUniformBSpline_eval_parDim4_geoDim4_degrees2463) { 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, iganet::init::greville, options); - iganet::NonUniformBSpline bspline( + iganet::NonUniformRationalBSpline bspline( {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, @@ -1118,7 +1118,7 @@ TEST_F(BSplineTest, NonUniformBSpline_eval_parDim4_geoDim4_degrees2463) { {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, options); test_nurbs_eval(geo, bspline, xi, 1e-12); -}*/ +} int main(int argc, char **argv) { ::testing::InitGoogleTest(&argc, argv); From 05f8051d0762c0e19b38447c48539a71c39e8a24 Mon Sep 17 00:00:00 2001 From: merback Date: Fri, 24 May 2024 08:58:40 +0200 Subject: [PATCH 06/13] Load from .dat-file added --- include/bspline.hpp | 94 ++++++++++++++++++- unittests/unittest_bsplinelib.hpp | 14 +-- ...ttest_nonuniform_rational_bspline_eval.cxx | 1 - 3 files changed, 99 insertions(+), 10 deletions(-) diff --git a/include/bspline.hpp b/include/bspline.hpp index 03f57f02..1793a93b 100644 --- a/include/bspline.hpp +++ b/include/bspline.hpp @@ -4994,7 +4994,7 @@ class NonUniformRationalBSplineCore utils::BlockTensor pnts; utils::BlockTensor Cw = Base::template eval(xi, knot_indices, coeff_indices); //Divide by weight - assert(std::abs(*Cw[Base::geoDim_ - 1]) > 0); // Avoid division by zero + assert(torch::abs(torch::prod(*Cw[Base::geoDim_ - 1])).item().toDouble() > 1e-12); // Avoid division by zero for (short_t i = 0; i < Base::geoDim_ - 1; ++i) pnts.set(i, torch::div(*Cw[i], *Cw[Base::geoDim_ - 1])); if constexpr (deriv == deriv::func) { @@ -5010,9 +5010,99 @@ class NonUniformRationalBSplineCore return result; } } - } + } /// @} + /// @brief Updates the B-spline object from .DAT file (compatible with geo_pdes and BEMBEL) + inline NonUniformRationalBSplineCore& from_dat(const std::string& file_name) { + // Assume one patch geometry + std::stringstream iss; + std::string word; + std::string row; + int infoInt[5]; + std::ifstream file; + file.open(file_name); + if (!file) { + std::cerr << "File " << file_name << " doesn't exist!"; + exit(1); + } + // first 4 rows are irrelevant + for (int i = 0; i < 5; i++) { + getline(file, row); + } + // main information (not necessary for one patch geometry) + iss.str(row); + for (int i = 0; i < 5; i++) { + iss >> word; + + infoInt[i] = stoi(word); + } + + std::vector info; // p and ncp / 0,1-p 2,3-ncp //TODO: DO i need this? + getline(file, row); // file_name + + // get info + getline(file, row); // first line contains degree per parDim + iss.str(row); + while (iss >> word) { + info.push_back(stoi(word)); + } + // Assert that degrees match + for (int64_t i = 0; i < Base::parDim_; ++i) + assert(Base::degrees_[i] == info[i]); + + getline(file, row); // second line contains number of controlpoints per parDim + + word = ""; + iss.clear(); + iss.str(row); + while (iss >> word) { + info.push_back(stoi(word)); + } + + word = ""; + iss.clear(); + + // In textfiles are only [parDim] knotVectors and [Base::geoDim] Matrices + + // Load knotVectors + for (int64_t i = 0; i < Base::parDim_; ++i) { + std::vector temp; + getline(file, row); + iss.str(row); + word = ""; + + for (int k = 0; k < info[i] + info[i + Base::parDim_] + 1; k++) { + iss >> word; + temp.push_back(atof(word.c_str())); + } + iss.clear(); + Base::knots_[i] = utils::to_tensor(temp, Base::options_); + Base::nknots_[i] = temp.size(); + Base::ncoeffs_[i] = Base::nknots_[i] - Base::degrees_[i] - 1; + } + //update ncoeffs_reverse + Base::ncoeffs_reverse_ = Base::ncoeffs_; + std::reverse(Base::ncoeffs_reverse_.begin(), Base::ncoeffs_reverse_.end()); + + for (int64_t i = 0; i < Base::geoDim_; i++) { + std::vector temp; + getline(file, row); + iss.str(row); + // for all in iss. + for (int64_t i = 0; i < Base::ncumcoeffs(); i++) { + iss >> word; + temp.push_back(atof(word.c_str())); + } + Base::coeffs_[i] = utils::to_tensor(temp, Base::options_); + iss.clear(); + } + file.close(); + return *this; + } + + + }; diff --git a/unittests/unittest_bsplinelib.hpp b/unittests/unittest_bsplinelib.hpp index d609d511..6c99c546 100644 --- a/unittests/unittest_bsplinelib.hpp +++ b/unittests/unittest_bsplinelib.hpp @@ -1184,7 +1184,7 @@ void test_nurbs_eval(const Geometry_t& geometry, const Spline& bspline, test_nurbs_eval( bspline, bsplinelib_bspline, xi, tol);*/ } - std::cout << "memory not optimized finished" << std::endl; + // Evaluate function and derivatives (memory optimized) /* test_nurbs_eval( bspline, bsplinelib_bspline, xi, tol); @@ -1232,8 +1232,8 @@ void test_nurbs_eval(const Geometry_t& geometry, const Spline& bspline, test_nurbs_eval( bspline, bsplinelib_bspline, xi, tol); } - std::cout << "memory optimized finished" << std::endl;*/ - // Evaluate function and derivatives from precomputed data (non-memory + + // Evaluate function and derivatives from precomputed data (non-memory // optimized) /* test_nurbs_eval( bspline, bsplinelib_bspline, xi, tol); @@ -1281,8 +1281,8 @@ void test_nurbs_eval(const Geometry_t& geometry, const Spline& bspline, test_nurbs_eval( bspline, bsplinelib_bspline, xi, tol); } - std::cout << "precomputed memory not optimized finished" << std::endl; - // Evaluate function and derivatives from precomputed data (memory optimized) + + // Evaluate function and derivatives from precomputed data (memory optimized) test_nurbs_eval( bspline, bsplinelib_bspline, xi, tol); @@ -1329,8 +1329,8 @@ void test_nurbs_eval(const Geometry_t& geometry, const Spline& bspline, test_nurbs_eval( bspline, bsplinelib_bspline, xi, tol); } - std::cout << "precomp memory optimized finished" << std::endl; */ - // Evaluate gradients + + // Evaluate gradients if constexpr (Spline::geoDim() == 1) { test_nurbs_grad(bspline, xi, tol); test_nurbs_grad(bspline, xi, tol); diff --git a/unittests/unittest_nonuniform_rational_bspline_eval.cxx b/unittests/unittest_nonuniform_rational_bspline_eval.cxx index fff00dbd..3a9a83b2 100644 --- a/unittests/unittest_nonuniform_rational_bspline_eval.cxx +++ b/unittests/unittest_nonuniform_rational_bspline_eval.cxx @@ -123,7 +123,6 @@ TEST_F(BSplineTest, NonUniformRationalBSpline_eval_parDim1_geoDim1_degrees1) { bspline.transform(trafo_parDim1_geoDim1); auto xi = iganet::utils::to_tensorArray( {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, options); - std::cout << "start" << std::endl; test_nurbs_eval(geo, bspline, xi, 1e-12); } From d4a75dc525ad1668610686eeb105b8bfdafa0b8e Mon Sep 17 00:00:00 2001 From: merback Date: Tue, 28 May 2024 18:12:23 +0200 Subject: [PATCH 07/13] Merge branch 'master' into nurbs (2/2) --- .github/workflows/cmake-multi-platform.yml | 300 +- .github/workflows/gitlab-sync.yml | 44 +- docs/Doxygen.bib | 44 +- docs/README.md | 80 +- examples/iganet_fitting.cxx | 386 +- examples/iganet_fitting_geometry.cxx | 430 +- examples/iganet_fitting_geometry_autotune.cxx | 364 +- ...t_fitting_geometry_dataloader_autotune.cxx | 492 +- examples/iganet_fitting_geometry_simple.cxx | 390 +- examples/iganet_fitting_simple.cxx | 350 +- examples/iganet_poisson.cxx | 462 +- examples/iganet_stokes.cxx | 472 +- examples/nonuniform_bsplines.cxx | 434 +- examples/uniform_bsplines.cxx | 410 +- include/core.hpp | 524 +- include/iganet.hpp | 2754 ++--- include/options.hpp | 414 +- include/sysinfo.hpp | 1338 +-- include/utils.hpp | 58 +- include/utils/container.hpp | 618 +- include/utils/index_sequence.hpp | 82 +- include/utils/linalg.hpp | 418 +- include/utils/serialize.hpp | 1318 +-- include/utils/tensorarray.hpp | 374 +- include/utils/vslice.hpp | 502 +- pyiganet/src/CMakeLists.txt | 380 +- pyiganet/src/pyoptions.cxx | 244 +- unittests/unittest_boundary.cxx | 4310 ++++---- unittests/unittest_functionspace.cxx | 9564 ++++++++--------- unittests/unittest_iganet.cxx | 1398 +-- unittests/unittest_nonuniform_bspline.cxx | 4676 ++++---- .../unittest_nonuniform_bspline_eval.cxx | 2252 ++-- unittests/unittest_uniform_bspline.cxx | 4472 ++++---- unittests/unittest_uniform_bspline_eval.cxx | 1492 +-- webapps/models/BSplineModel.hpp | 1466 +-- webapps/server.cxx | 3686 +++---- 36 files changed, 23499 insertions(+), 23499 deletions(-) diff --git a/.github/workflows/cmake-multi-platform.yml b/.github/workflows/cmake-multi-platform.yml index ac08ab60..15e74a7b 100644 --- a/.github/workflows/cmake-multi-platform.yml +++ b/.github/workflows/cmake-multi-platform.yml @@ -1,150 +1,150 @@ -# This starter workflow is for a CMake project running on multiple platforms. There is a different starter workflow if you just want a single platform. -# See: https://github.com/actions/starter-workflows/blob/main/ci/cmake-single-platform.yml -name: CMake on multiple platforms - -on: - push: - branches: [ "master" ] - pull_request: - branches: [ "master" ] - -jobs: - build: - runs-on: ${{ matrix.os }} - - strategy: - # Set fail-fast to false to ensure that feedback is delivered for all matrix combinations. Consider changing this to true when your workflow is stable. - fail-fast: false - - # Set up a matrix to run the following 3 configurations: - # 1. - # 2. - # 3. - # - # To add more build types (Release, Debug, RelWithDebInfo, etc.) customize the build_type list. - matrix: - os: [macos-latest, ubuntu-latest] # windows-latest does not compile - build_type: [Release] - c_compiler: [gcc, clang, cl] - include: - #- os: windows-latest - # c_compiler: cl - # cpp_compiler: cl - - os: ubuntu-latest - c_compiler: gcc - cpp_compiler: g++ - - os: ubuntu-latest - c_compiler: clang - cpp_compiler: clang++ - - os: macos-latest - c_compiler: clang - cpp_compiler: clang++ - exclude: - #- os: windows-latest - # c_compiler: gcc - #- os: windows-latest - # c_compiler: clang - - os: ubuntu-latest - c_compiler: cl - - os: macos-latest - c_compiler: cl - - os: macos-latest - c_compiler: gcc - - steps: - - uses: actions/checkout@v4 - - - name: Set reusable strings - # Turn repeated input strings (such as the build output directory) into step outputs. These step outputs can be used throughout the workflow file. - id: strings - shell: bash - run: | - echo "build-output-dir=${{ github.workspace }}/build" >> "$GITHUB_OUTPUT" - - - name: Install PyTorch - Linux - if: matrix.os == 'ubuntu-latest' - run: > - wget https://download.pytorch.org/libtorch/cpu/libtorch-cxx11-abi-shared-with-deps-2.3.0%2Bcpu.zip -O libtorch.zip && - unzip libtorch.zip -d $HOME && - rm -f libtorch.zip - - - name: Install PyTorch - MacOS - if: matrix.os == 'macos-latest' - run: > - wget https://download.pytorch.org/libtorch/cpu/libtorch-macos-arm64-2.3.0.zip -O libtorch.zip && - unzip libtorch.zip -d $HOME && - rm -f libtorch.zip - - - name: Install PyTorch - Windows - if: matrix.os == 'windows-latest' - run: > - Invoke-WebRequest -Uri "https://download.pytorch.org/libtorch/cpu/libtorch-win-shared-with-deps-2.3.0%2Bcpu.zip" -OutFile libtorch.zip; - Expand-Archive -Path libtorch.zip -DestinationPath C:\; - Remove-Item -Path "libtorch.zip" -Force - - - name: Install Clang 17 - Linux - if: matrix.os == 'ubuntu-latest' - run: | - wget https://apt.llvm.org/llvm.sh - chmod +x ./llvm.sh - sudo ./llvm.sh 17 - - - name: Configure CMake - Linux / GCC - # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. - # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type - if: ${{ matrix.os == 'ubuntu-latest' && matrix.c_compiler == 'gcc' }} - run: > - cmake -B ${{ steps.strings.outputs.build-output-dir }} - -DCMAKE_CXX_COMPILER=${{ matrix.cpp_compiler }} - -DCMAKE_C_COMPILER=${{ matrix.c_compiler }} - -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} - -DTorch_DIR=$HOME/libtorch/share/cmake/Torch - -S ${{ github.workspace }} - - - name: Configure CMake - Linux / Clang - # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. - # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type - if: ${{ matrix.os == 'ubuntu-latest' && matrix.c_compiler == 'clang' }} - run: > - cmake -B ${{ steps.strings.outputs.build-output-dir }} - -DCMAKE_CXX_COMPILER=clang++-17 - -DCMAKE_C_COMPILER=clang-17 - -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} - -DTorch_DIR=$HOME/libtorch/share/cmake/Torch - -DIGANET_WITH_OPENMP=OFF - -S ${{ github.workspace }} - - - name: Configure CMake - MacOS / Clang - # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. - # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type - if: ${{ matrix.os == 'macos-latest' && matrix.c_compiler == 'clang' }} - run: > - cmake -B ${{ steps.strings.outputs.build-output-dir }} - -DCMAKE_CXX_COMPILER=${{ matrix.cpp_compiler }} - -DCMAKE_C_COMPILER=${{ matrix.c_compiler }} - -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} - -DTorch_DIR=$HOME/libtorch/share/cmake/Torch - -DIGANET_WITH_OPENMP=OFF - -S ${{ github.workspace }} - - - name: Configure CMake - Windows / Cl - if: matrix.os == 'windows-latest' - run: > - cmake -B ${{ steps.strings.outputs.build-output-dir }} - -DCMAKE_CXX_COMPILER=${{ matrix.cpp_compiler }} - -DCMAKE_C_COMPILER=${{ matrix.c_compiler }} - -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} - -DIGANET_BUILD_UNITTESTS=OFF - -DTorch_DIR=C:\libtorch\share\cmake\Torch - -DIGANET_WITH_OPENMP=OFF - -S ${{ github.workspace }} - - - name: Build - # Build your program with the given configuration. Note that --config is needed because the default Windows generator is a multi-config generator (Visual Studio generator). - run: cmake --build ${{ steps.strings.outputs.build-output-dir }} --config ${{ matrix.build_type }} - - - name: Test - working-directory: ${{ steps.strings.outputs.build-output-dir }} - # Execute tests defined by the CMake configuration. Note that --build-config is needed because the default Windows generator is a multi-config generator (Visual Studio generator). - # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail - run: ctest --build-config ${{ matrix.build_type }} +# This starter workflow is for a CMake project running on multiple platforms. There is a different starter workflow if you just want a single platform. +# See: https://github.com/actions/starter-workflows/blob/main/ci/cmake-single-platform.yml +name: CMake on multiple platforms + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +jobs: + build: + runs-on: ${{ matrix.os }} + + strategy: + # Set fail-fast to false to ensure that feedback is delivered for all matrix combinations. Consider changing this to true when your workflow is stable. + fail-fast: false + + # Set up a matrix to run the following 3 configurations: + # 1. + # 2. + # 3. + # + # To add more build types (Release, Debug, RelWithDebInfo, etc.) customize the build_type list. + matrix: + os: [macos-latest, ubuntu-latest] # windows-latest does not compile + build_type: [Release] + c_compiler: [gcc, clang, cl] + include: + #- os: windows-latest + # c_compiler: cl + # cpp_compiler: cl + - os: ubuntu-latest + c_compiler: gcc + cpp_compiler: g++ + - os: ubuntu-latest + c_compiler: clang + cpp_compiler: clang++ + - os: macos-latest + c_compiler: clang + cpp_compiler: clang++ + exclude: + #- os: windows-latest + # c_compiler: gcc + #- os: windows-latest + # c_compiler: clang + - os: ubuntu-latest + c_compiler: cl + - os: macos-latest + c_compiler: cl + - os: macos-latest + c_compiler: gcc + + steps: + - uses: actions/checkout@v4 + + - name: Set reusable strings + # Turn repeated input strings (such as the build output directory) into step outputs. These step outputs can be used throughout the workflow file. + id: strings + shell: bash + run: | + echo "build-output-dir=${{ github.workspace }}/build" >> "$GITHUB_OUTPUT" + + - name: Install PyTorch - Linux + if: matrix.os == 'ubuntu-latest' + run: > + wget https://download.pytorch.org/libtorch/cpu/libtorch-cxx11-abi-shared-with-deps-2.3.0%2Bcpu.zip -O libtorch.zip && + unzip libtorch.zip -d $HOME && + rm -f libtorch.zip + + - name: Install PyTorch - MacOS + if: matrix.os == 'macos-latest' + run: > + wget https://download.pytorch.org/libtorch/cpu/libtorch-macos-arm64-2.3.0.zip -O libtorch.zip && + unzip libtorch.zip -d $HOME && + rm -f libtorch.zip + + - name: Install PyTorch - Windows + if: matrix.os == 'windows-latest' + run: > + Invoke-WebRequest -Uri "https://download.pytorch.org/libtorch/cpu/libtorch-win-shared-with-deps-2.3.0%2Bcpu.zip" -OutFile libtorch.zip; + Expand-Archive -Path libtorch.zip -DestinationPath C:\; + Remove-Item -Path "libtorch.zip" -Force + + - name: Install Clang 17 - Linux + if: matrix.os == 'ubuntu-latest' + run: | + wget https://apt.llvm.org/llvm.sh + chmod +x ./llvm.sh + sudo ./llvm.sh 17 + + - name: Configure CMake - Linux / GCC + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + if: ${{ matrix.os == 'ubuntu-latest' && matrix.c_compiler == 'gcc' }} + run: > + cmake -B ${{ steps.strings.outputs.build-output-dir }} + -DCMAKE_CXX_COMPILER=${{ matrix.cpp_compiler }} + -DCMAKE_C_COMPILER=${{ matrix.c_compiler }} + -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} + -DTorch_DIR=$HOME/libtorch/share/cmake/Torch + -S ${{ github.workspace }} + + - name: Configure CMake - Linux / Clang + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + if: ${{ matrix.os == 'ubuntu-latest' && matrix.c_compiler == 'clang' }} + run: > + cmake -B ${{ steps.strings.outputs.build-output-dir }} + -DCMAKE_CXX_COMPILER=clang++-17 + -DCMAKE_C_COMPILER=clang-17 + -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} + -DTorch_DIR=$HOME/libtorch/share/cmake/Torch + -DIGANET_WITH_OPENMP=OFF + -S ${{ github.workspace }} + + - name: Configure CMake - MacOS / Clang + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + if: ${{ matrix.os == 'macos-latest' && matrix.c_compiler == 'clang' }} + run: > + cmake -B ${{ steps.strings.outputs.build-output-dir }} + -DCMAKE_CXX_COMPILER=${{ matrix.cpp_compiler }} + -DCMAKE_C_COMPILER=${{ matrix.c_compiler }} + -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} + -DTorch_DIR=$HOME/libtorch/share/cmake/Torch + -DIGANET_WITH_OPENMP=OFF + -S ${{ github.workspace }} + + - name: Configure CMake - Windows / Cl + if: matrix.os == 'windows-latest' + run: > + cmake -B ${{ steps.strings.outputs.build-output-dir }} + -DCMAKE_CXX_COMPILER=${{ matrix.cpp_compiler }} + -DCMAKE_C_COMPILER=${{ matrix.c_compiler }} + -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} + -DIGANET_BUILD_UNITTESTS=OFF + -DTorch_DIR=C:\libtorch\share\cmake\Torch + -DIGANET_WITH_OPENMP=OFF + -S ${{ github.workspace }} + + - name: Build + # Build your program with the given configuration. Note that --config is needed because the default Windows generator is a multi-config generator (Visual Studio generator). + run: cmake --build ${{ steps.strings.outputs.build-output-dir }} --config ${{ matrix.build_type }} + + - name: Test + working-directory: ${{ steps.strings.outputs.build-output-dir }} + # Execute tests defined by the CMake configuration. Note that --build-config is needed because the default Windows generator is a multi-config generator (Visual Studio generator). + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest --build-config ${{ matrix.build_type }} diff --git a/.github/workflows/gitlab-sync.yml b/.github/workflows/gitlab-sync.yml index 6b9fd931..18c33ec2 100644 --- a/.github/workflows/gitlab-sync.yml +++ b/.github/workflows/gitlab-sync.yml @@ -1,22 +1,22 @@ -name: GitlabSync - -on: - - push - - delete - -jobs: - sync: - runs-on: ubuntu-latest - name: Git Repo Sync - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - uses: wangchucheng/git-repo-sync@v0.1.0 - with: - # Such as https://github.com/wangchucheng/git-repo-sync.git - target-url: ${{ secrets.GITLAB_REPO_URL }} - # Such as wangchucheng - target-username: ${{ secrets.GITLAB_REPO_USERNAME }} - # You can store token in your project's 'Setting > Secrets' and reference the name here. Such as ${{ secrets.ACCESS\_TOKEN }} - target-token: ${{ secrets.GITLAB_REPO_TOKEN }} +name: GitlabSync + +on: + - push + - delete + +jobs: + sync: + runs-on: ubuntu-latest + name: Git Repo Sync + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: wangchucheng/git-repo-sync@v0.1.0 + with: + # Such as https://github.com/wangchucheng/git-repo-sync.git + target-url: ${{ secrets.GITLAB_REPO_URL }} + # Such as wangchucheng + target-username: ${{ secrets.GITLAB_REPO_USERNAME }} + # You can store token in your project's 'Setting > Secrets' and reference the name here. Such as ${{ secrets.ACCESS\_TOKEN }} + target-token: ${{ secrets.GITLAB_REPO_TOKEN }} diff --git a/docs/Doxygen.bib b/docs/Doxygen.bib index f80acf8b..bf3cbaf4 100644 --- a/docs/Doxygen.bib +++ b/docs/Doxygen.bib @@ -1,22 +1,22 @@ -@misc{Lyche:2011, - author = {Tom Lyche and Knut Morken}, - title = {Spline Methods}, - year = {2011}, - school = {Department of Informatics, Centre of Mathematics for Applications, University of Oslo}, - url = {https://www.uio.no/studier/emner/matnat/ifi/nedlagte-emner/INF-MAT5340/v13/undervisningsmateriale/book.pdf}, -} - -@article{Buffa:2011, - title = {IsoGeometric Analysis: Stable elements for the 2D Stokes equation}, - volume = {65}, - ISSN = {1097-0363}, - url = {http://dx.doi.org/10.1002/fld.2337}, - DOI = {10.1002/fld.2337}, - number = {11–12}, - journal = {International Journal for Numerical Methods in Fluids}, - publisher = {Wiley}, - author = {Buffa, A. and de Falco, C. and Sangalli, G.}, - year = {2011}, - month = mar, - pages = {1407–1422} -} +@misc{Lyche:2011, + author = {Tom Lyche and Knut Morken}, + title = {Spline Methods}, + year = {2011}, + school = {Department of Informatics, Centre of Mathematics for Applications, University of Oslo}, + url = {https://www.uio.no/studier/emner/matnat/ifi/nedlagte-emner/INF-MAT5340/v13/undervisningsmateriale/book.pdf}, +} + +@article{Buffa:2011, + title = {IsoGeometric Analysis: Stable elements for the 2D Stokes equation}, + volume = {65}, + ISSN = {1097-0363}, + url = {http://dx.doi.org/10.1002/fld.2337}, + DOI = {10.1002/fld.2337}, + number = {11–12}, + journal = {International Journal for Numerical Methods in Fluids}, + publisher = {Wiley}, + author = {Buffa, A. and de Falco, C. and Sangalli, G.}, + year = {2011}, + month = mar, + pages = {1407–1422} +} diff --git a/docs/README.md b/docs/README.md index 8b6e1320..fe1ed7e7 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,40 +1,40 @@ -# IgANets - Isogeometric Analysis Networks - -**IgANets** (Isogeometric Analysis Networks) is a C++ library that -combines Isogeometric Analysis with deep operator learning. It builds -upon the C++ API of the Torch library and is written in C++20. The -library aims to provide an easy to use, user-friendly and yet -computationally efficient framework for implementing IgANet -applications. - -## Mathematical notation - -### B-Spline basis functions - -- \f$ \xi_d \f$ is the value of the parametric coordinate in the \f$ d \f$-th parametric dimension -- \f$ \boldsymbol{\xi} = \left( \xi_1, \dots, \xi_{d_\text{par}} \right)^\top \f$ is the vector of parametric coordinates in all parametric dimension -- \f$ B_{i_d,p_d}(\xi_d) \f$ is the \f$ i_d \f$-th univariate B-spline basis function in the \f$ d \f$-th parametric dimension evaluated at \f$ \xi_d \f$ -- \f$ B_I(\boldsymbol{\xi}) = \bigotimes_{d=1}^{d_\text{par}} B_{i_d,p_d}(\xi_d) \f$ is the \f$ I \f$-th multivariate B-spline basis function -- \f$ d_\text{geo} \f$ is the total number of geometric dimension -- \f$ d_\text{par} \f$ is the total number of parametric dimension -- \f$ i_d \f$ is local index refering to the \f$ d \f$-th dimension -- \f$ \mathbf{i} = \left(i_1, \dots, i_d \right) \f$ is a local multi-index -- \f$ I \f$ is a global index -- \f$ n_d \f$ is the number of univariate B-spline basis functions in the \f$ d \f$-th dimension -- \f$ N = n_1\cdot \dots \cdot n_{d_\text{par}} \f$ is the total number of multivariable B-splines basis functions - -### B-Spline function spaces - -- \f$ S^{p}_{\boldsymbol{\alpha}} = \text{span} \left\{ B_{i,p} \right\}_{i=1}^n \f$ is the function space that is spanned by a univariate B-spline basis of degree \f$ p \f$ and regularity vector \f$ \boldsymbol{\alpha} = \left(\alpha_1, \dots, \alpha_n\right) \f$. By default, we assume maximal regularity, i.e. \f$ \alpha_i = p-1 \f$ for all \f$ i = 1, \dots, n \f$ -- \f$ S^{p_1,\dots,p_{d_\text{par}}}_{\boldsymbol{\alpha}_1,\dots,\boldsymbol{\alpha}_{d_\text{par}}} \f$ is the function space that is spanned by a multivariate B-spline basis of degree \f$ \mathbf{p} = \left(p_1, \dots, p_{d_\text{par}}\right) \f$ with regularity vectors \f$ \boldsymbol{\alpha}_d \f$ for each parametric dimension \f$ d = 1, \dots d_\text{par} \f$. By default, we assume maximal regularity (see above) - -## Documentation - -- The list of [Examples](../examples/README.md) -- The list of [PerfTests](../perftests/README.md) -- The list of [UnitTests](../unittests/README.md) -- The list of [WebApps](../webapps/README.md) - -## Copyright - -Copyright (c) 2021-2024 Matthias Möller (m.moller@tudelft.nl). +# IgANets - Isogeometric Analysis Networks + +**IgANets** (Isogeometric Analysis Networks) is a C++ library that +combines Isogeometric Analysis with deep operator learning. It builds +upon the C++ API of the Torch library and is written in C++20. The +library aims to provide an easy to use, user-friendly and yet +computationally efficient framework for implementing IgANet +applications. + +## Mathematical notation + +### B-Spline basis functions + +- \f$ \xi_d \f$ is the value of the parametric coordinate in the \f$ d \f$-th parametric dimension +- \f$ \boldsymbol{\xi} = \left( \xi_1, \dots, \xi_{d_\text{par}} \right)^\top \f$ is the vector of parametric coordinates in all parametric dimension +- \f$ B_{i_d,p_d}(\xi_d) \f$ is the \f$ i_d \f$-th univariate B-spline basis function in the \f$ d \f$-th parametric dimension evaluated at \f$ \xi_d \f$ +- \f$ B_I(\boldsymbol{\xi}) = \bigotimes_{d=1}^{d_\text{par}} B_{i_d,p_d}(\xi_d) \f$ is the \f$ I \f$-th multivariate B-spline basis function +- \f$ d_\text{geo} \f$ is the total number of geometric dimension +- \f$ d_\text{par} \f$ is the total number of parametric dimension +- \f$ i_d \f$ is local index refering to the \f$ d \f$-th dimension +- \f$ \mathbf{i} = \left(i_1, \dots, i_d \right) \f$ is a local multi-index +- \f$ I \f$ is a global index +- \f$ n_d \f$ is the number of univariate B-spline basis functions in the \f$ d \f$-th dimension +- \f$ N = n_1\cdot \dots \cdot n_{d_\text{par}} \f$ is the total number of multivariable B-splines basis functions + +### B-Spline function spaces + +- \f$ S^{p}_{\boldsymbol{\alpha}} = \text{span} \left\{ B_{i,p} \right\}_{i=1}^n \f$ is the function space that is spanned by a univariate B-spline basis of degree \f$ p \f$ and regularity vector \f$ \boldsymbol{\alpha} = \left(\alpha_1, \dots, \alpha_n\right) \f$. By default, we assume maximal regularity, i.e. \f$ \alpha_i = p-1 \f$ for all \f$ i = 1, \dots, n \f$ +- \f$ S^{p_1,\dots,p_{d_\text{par}}}_{\boldsymbol{\alpha}_1,\dots,\boldsymbol{\alpha}_{d_\text{par}}} \f$ is the function space that is spanned by a multivariate B-spline basis of degree \f$ \mathbf{p} = \left(p_1, \dots, p_{d_\text{par}}\right) \f$ with regularity vectors \f$ \boldsymbol{\alpha}_d \f$ for each parametric dimension \f$ d = 1, \dots d_\text{par} \f$. By default, we assume maximal regularity (see above) + +## Documentation + +- The list of [Examples](../examples/README.md) +- The list of [PerfTests](../perftests/README.md) +- The list of [UnitTests](../unittests/README.md) +- The list of [WebApps](../webapps/README.md) + +## Copyright + +Copyright (c) 2021-2024 Matthias Möller (m.moller@tudelft.nl). diff --git a/examples/iganet_fitting.cxx b/examples/iganet_fitting.cxx index 2b25234e..a2fe89e8 100644 --- a/examples/iganet_fitting.cxx +++ b/examples/iganet_fitting.cxx @@ -1,193 +1,193 @@ -/** - @file examples/iganet_fitting.cxx - - @brief Demonstration of IgANet function fitting - - This example demonstrates how to implement an IgANet to fit a - given function on a square geometry. In contrast to the example - iganet_fitting_simple.cxx this examples makes use of pre-computed - indices and coefficients and should therefore be faster. - - @author Matthias Moller - - @copyright This file is part of the IgANet project - - This Source Code Form is subject to the terms of the Mozilla Public - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at http://mozilla.org/MPL/2.0/. -*/ - -#include -#include -#include - -/// @brief Specialization of the abstract IgANet class for function fitting -template -class fitting : public iganet::IgANet, - public iganet::IgANetCustomizable { - -private: - /// @brief Type of the base class - using Base = iganet::IgANet; - - /// @brief Collocation points - typename Base::variable_collPts_type collPts_; - - /// @brief Type of the customizable class - using Customizable = iganet::IgANetCustomizable; - - /// @brief Knot indices - typename Customizable::variable_interior_knot_indices_type knot_indices_; - - /// @brief Coefficient indices - typename Customizable::variable_interior_coeff_indices_type coeff_indices_; - -public: - /// @brief Constructors from the base class - using iganet::IgANet::IgANet; - - /// @brief Returns a constant reference to the collocation points - auto const &collPts() const { return collPts_; } - - /// @brief Initializes the epoch - /// - /// @param[in] epoch Epoch number - bool epoch(int64_t epoch) override { - // In the very first epoch we need to generate the sampling points - // for the inputs and the sampling points in the function space of - // the variables since otherwise the respective tensors would be - // empty. In all further epochs no updates are needed since we do - // not change the inputs nor the variable function space. - if (epoch == 0) { - Base::inputs(epoch); - collPts_ = Base::variable_collPts(iganet::collPts::greville); - - knot_indices_ = - Base::f_.template find_knot_indices( - collPts_.first); - coeff_indices_ = - Base::f_.template find_coeff_indices( - knot_indices_); - - return true; - } else - return false; - } - - /// @brief Computes the loss function - /// - /// @param[in] outputs Output of the network - /// - /// @param[in] epoch Epoch number - torch::Tensor loss(const torch::Tensor &outputs, int64_t epoch) override { - - // Cast the network output (a raw tensor) into the proper - // function-space format, i.e. B-spline objects for the interior - // and boundary parts that can be evaluated. - Base::u_.from_tensor(outputs); - - // Evaluate the loss function - return torch::mse_loss( - *Base::u_.eval(collPts_.first, knot_indices_, coeff_indices_)[0], - *Base::f_.eval(collPts_.first, knot_indices_, coeff_indices_)[0]); - } -}; - -int main() { - iganet::init(); - iganet::verbose(std::cout); - - nlohmann::json json; - json["res0"] = 50; - json["res1"] = 50; - - using namespace iganet::literals; - using optimizer_t = torch::optim::Adam; - using real_t = double; - - // Geometry: Bi-linear B-spline function space S (geoDim = 2, p = q = 1) - using geometry_t = iganet::S>; - - // Variable: Bi-quadratic B-spline function space S (geoDim = 1, p = q = 2) - using variable_t = iganet::S>; - - fitting - net( // Number of neurons per layers - {50, 50}, - // Activation functions - {{iganet::activation::sigmoid}, - {iganet::activation::sigmoid}, - {iganet::activation::none}}, - // Number of B-spline coefficients of the geometry, just [0,1] x [0,1] - std::tuple(iganet::utils::to_array(2_i64, 2_i64)), - // Number of B-spline coefficients of the variable - std::tuple(iganet::utils::to_array(20_i64, 20_i64))); - - // Impose solution value for supervised training (not right-hand side) - net.f().transform([](const std::array xi) { - return std::array{ - static_cast(sin(M_PI * xi[0]) * sin(M_PI * xi[1]))}; - }); - - // Set maximum number of epochs - net.options().max_epoch(1000); - - // Set tolerance for the loss functions - net.options().min_loss(1e-8); - - // Start time measurement - auto t1 = std::chrono::high_resolution_clock::now(); - - // Train network - net.train(); - - // Stop time measurement - auto t2 = std::chrono::high_resolution_clock::now(); - iganet::Log(iganet::log::info) - << "Training took " - << std::chrono::duration_cast>(t2 - t1) - .count() - << " seconds\n"; - -#ifdef IGANET_WITH_MATPLOT - // Plot the solution - net.G().plot(net.u(), net.collPts().first, json)->show(); - - // Plot the difference between the solution and the reference data - net.G().plot(net.u().abs_diff(net.f()), net.collPts().first, json)->show(); -#endif - -#ifdef IGANET_WITH_GISMO - // Convert B-spline objects to G+Smo - auto G_gismo = net.G().to_gismo(); - auto u_gismo = net.u().to_gismo(); - auto f_gismo = net.f().to_gismo(); - - // Set up expression assembler - gsExprAssembler A(1, 1); - gsMultiBasis basis(u_gismo, true); - - A.setIntegrationElements(basis); - - auto G = A.getMap(G_gismo); - auto u = A.getCoeff(u_gismo, G); - auto f = A.getCoeff(f_gismo, G); - - // Compute L2- and H2-error - gsExprEvaluator ev(A); - - iganet::Log(iganet::log::info) - << "L2-error : " - << gismo::math::sqrt(ev.integral((u - f).sqNorm() * meas(G))) - << std::endl; - - iganet::Log(iganet::log::info) - << "H1-error : " - << gismo::math::sqrt(ev.integral( - (gismo::expr::igrad(u, G) - gismo::expr::igrad(f, G)).sqNorm() * - meas(G))) - << std::endl; -#endif - - return 0; -} +/** + @file examples/iganet_fitting.cxx + + @brief Demonstration of IgANet function fitting + + This example demonstrates how to implement an IgANet to fit a + given function on a square geometry. In contrast to the example + iganet_fitting_simple.cxx this examples makes use of pre-computed + indices and coefficients and should therefore be faster. + + @author Matthias Moller + + @copyright This file is part of the IgANet project + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#include +#include +#include + +/// @brief Specialization of the abstract IgANet class for function fitting +template +class fitting : public iganet::IgANet, + public iganet::IgANetCustomizable { + +private: + /// @brief Type of the base class + using Base = iganet::IgANet; + + /// @brief Collocation points + typename Base::variable_collPts_type collPts_; + + /// @brief Type of the customizable class + using Customizable = iganet::IgANetCustomizable; + + /// @brief Knot indices + typename Customizable::variable_interior_knot_indices_type knot_indices_; + + /// @brief Coefficient indices + typename Customizable::variable_interior_coeff_indices_type coeff_indices_; + +public: + /// @brief Constructors from the base class + using iganet::IgANet::IgANet; + + /// @brief Returns a constant reference to the collocation points + auto const &collPts() const { return collPts_; } + + /// @brief Initializes the epoch + /// + /// @param[in] epoch Epoch number + bool epoch(int64_t epoch) override { + // In the very first epoch we need to generate the sampling points + // for the inputs and the sampling points in the function space of + // the variables since otherwise the respective tensors would be + // empty. In all further epochs no updates are needed since we do + // not change the inputs nor the variable function space. + if (epoch == 0) { + Base::inputs(epoch); + collPts_ = Base::variable_collPts(iganet::collPts::greville); + + knot_indices_ = + Base::f_.template find_knot_indices( + collPts_.first); + coeff_indices_ = + Base::f_.template find_coeff_indices( + knot_indices_); + + return true; + } else + return false; + } + + /// @brief Computes the loss function + /// + /// @param[in] outputs Output of the network + /// + /// @param[in] epoch Epoch number + torch::Tensor loss(const torch::Tensor &outputs, int64_t epoch) override { + + // Cast the network output (a raw tensor) into the proper + // function-space format, i.e. B-spline objects for the interior + // and boundary parts that can be evaluated. + Base::u_.from_tensor(outputs); + + // Evaluate the loss function + return torch::mse_loss( + *Base::u_.eval(collPts_.first, knot_indices_, coeff_indices_)[0], + *Base::f_.eval(collPts_.first, knot_indices_, coeff_indices_)[0]); + } +}; + +int main() { + iganet::init(); + iganet::verbose(std::cout); + + nlohmann::json json; + json["res0"] = 50; + json["res1"] = 50; + + using namespace iganet::literals; + using optimizer_t = torch::optim::Adam; + using real_t = double; + + // Geometry: Bi-linear B-spline function space S (geoDim = 2, p = q = 1) + using geometry_t = iganet::S>; + + // Variable: Bi-quadratic B-spline function space S (geoDim = 1, p = q = 2) + using variable_t = iganet::S>; + + fitting + net( // Number of neurons per layers + {50, 50}, + // Activation functions + {{iganet::activation::sigmoid}, + {iganet::activation::sigmoid}, + {iganet::activation::none}}, + // Number of B-spline coefficients of the geometry, just [0,1] x [0,1] + std::tuple(iganet::utils::to_array(2_i64, 2_i64)), + // Number of B-spline coefficients of the variable + std::tuple(iganet::utils::to_array(20_i64, 20_i64))); + + // Impose solution value for supervised training (not right-hand side) + net.f().transform([](const std::array xi) { + return std::array{ + static_cast(sin(M_PI * xi[0]) * sin(M_PI * xi[1]))}; + }); + + // Set maximum number of epochs + net.options().max_epoch(1000); + + // Set tolerance for the loss functions + net.options().min_loss(1e-8); + + // Start time measurement + auto t1 = std::chrono::high_resolution_clock::now(); + + // Train network + net.train(); + + // Stop time measurement + auto t2 = std::chrono::high_resolution_clock::now(); + iganet::Log(iganet::log::info) + << "Training took " + << std::chrono::duration_cast>(t2 - t1) + .count() + << " seconds\n"; + +#ifdef IGANET_WITH_MATPLOT + // Plot the solution + net.G().plot(net.u(), net.collPts().first, json)->show(); + + // Plot the difference between the solution and the reference data + net.G().plot(net.u().abs_diff(net.f()), net.collPts().first, json)->show(); +#endif + +#ifdef IGANET_WITH_GISMO + // Convert B-spline objects to G+Smo + auto G_gismo = net.G().to_gismo(); + auto u_gismo = net.u().to_gismo(); + auto f_gismo = net.f().to_gismo(); + + // Set up expression assembler + gsExprAssembler A(1, 1); + gsMultiBasis basis(u_gismo, true); + + A.setIntegrationElements(basis); + + auto G = A.getMap(G_gismo); + auto u = A.getCoeff(u_gismo, G); + auto f = A.getCoeff(f_gismo, G); + + // Compute L2- and H2-error + gsExprEvaluator ev(A); + + iganet::Log(iganet::log::info) + << "L2-error : " + << gismo::math::sqrt(ev.integral((u - f).sqNorm() * meas(G))) + << std::endl; + + iganet::Log(iganet::log::info) + << "H1-error : " + << gismo::math::sqrt(ev.integral( + (gismo::expr::igrad(u, G) - gismo::expr::igrad(f, G)).sqNorm() * + meas(G))) + << std::endl; +#endif + + return 0; +} diff --git a/examples/iganet_fitting_geometry.cxx b/examples/iganet_fitting_geometry.cxx index 8d6fc11d..b33cd1e7 100644 --- a/examples/iganet_fitting_geometry.cxx +++ b/examples/iganet_fitting_geometry.cxx @@ -1,215 +1,215 @@ -/** - @file examples/iganet_fitting_geometry.cxx - - @brief Demonstration of IgANet function fitting on a geometry loaded from a - file - - @author Veronika Travnikova - - This example demonstrates how to implement an IgANet to fit a given - function on a geometry loaded from a file. In contrast to the - example iganet_fitting_geometry_simple.cxx this examples makes use - of pre-computed indices and coefficients and should therefore be - faster. - - @author Matthias Moller - - @copyright This file is part of the IgANet project - - This Source Code Form is subject to the terms of the Mozilla Public - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at http://mozilla.org/MPL/2.0/. -*/ - -#include -#include -#include - -/// @brief Specialization of the abstract IgANet class for function fitting -template -class fitting : public iganet::IgANet, - public iganet::IgANetCustomizable { - -private: - /// @brief Type of the base class - using Base = iganet::IgANet; - - /// @brief Collocation points - typename Base::variable_collPts_type collPts_; - - /// @brief Type of the customizable class - using Customizable = iganet::IgANetCustomizable; - - /// @brief Knot indices - typename Customizable::variable_interior_knot_indices_type knot_indices_; - - /// @brief Coefficient indices - typename Customizable::variable_interior_coeff_indices_type coeff_indices_; - -public: - /// @brief Constructors from the base class - using iganet::IgANet::IgANet; - - /// @brief Returns a constant reference to the collocation points - auto const &collPts() const { return collPts_; } - - /// @brief Initializes the epoch - /// - /// @param[in] epoch Epoch number - bool epoch(int64_t epoch) override { - // In the very first epoch we need to generate the sampling points - // for the inputs and the sampling points in the function space of - // the variables since otherwise the respective tensors would be - // empty. In all further epochs no updates are needed since we do - // not change the inputs nor the variable function space. - if (epoch == 0) { - Base::inputs(epoch); - collPts_ = Base::variable_collPts(iganet::collPts::greville); - - knot_indices_ = - Base::f_.template find_knot_indices( - collPts_.first); - coeff_indices_ = - Base::f_.template find_coeff_indices( - knot_indices_); - - return true; - } else - return false; - } - - /// @brief Computes the loss function - /// - /// @param[in] outputs Output of the network - /// - /// @param[in] epoch Epoch number - torch::Tensor loss(const torch::Tensor &outputs, int64_t epoch) override { - - // Cast the network output (a raw tensor) into the proper - // function-space format, i.e. B-spline objects for the interior - // and boundary parts that can be evaluated. - Base::u_.from_tensor(outputs); - - // Evaluate the loss function - return torch::mse_loss( - *Base::u_.eval(collPts_.first, knot_indices_, coeff_indices_)[0], - *Base::f_.eval(collPts_.first, knot_indices_, coeff_indices_)[0]); - } -}; - -int main() { - iganet::init(); - iganet::verbose(std::cout); - - nlohmann::json json; - json["res0"] = 50; - json["res1"] = 50; - - using namespace iganet::literals; - using optimizer_t = torch::optim::Adam; - using real_t = double; - - // Load XML file - pugi::xml_document xml; - xml.load_file(IGANET_DATA_DIR "surfaces/2d/geo02.xml"); - - // Bivariate uniform B-spline of degree 2 in both directions - // the type has to correspond to the respective geometry parameterization in - // the input file - using geometry_t = iganet::S>; - - // Variable: Bi-quadratic B-spline function space S (geoDim = 1, p = q = 2) - using variable_t = iganet::S>; - - fitting - net( // Number of neurons per layers - {50, 50}, - // Activation functions - {{iganet::activation::sigmoid}, - {iganet::activation::sigmoid}, - {iganet::activation::none}}, - // Number of B-spline coefficients of the geometry, has to correspond - // to number of coefficients in input file - std::tuple(iganet::utils::to_array(25_i64, 25_i64)), - // Number of B-spline coefficients of the variable - std::tuple(iganet::utils::to_array(30_i64, 30_i64))); - - // Load geometry parameterization from XML - net.G().from_xml(xml); - - // Impose solution value for supervised training (not right-hand side) - net.f().transform([](const std::array xi) { - return std::array{ - static_cast(sin(M_PI * xi[0]) * sin(M_PI * xi[1]))}; - }); - - // Set maximum number of epochs - net.options().max_epoch(1000); - - // Set tolerance for the loss functions - net.options().min_loss(1e-8); - - // Start time measurement - auto t1 = std::chrono::high_resolution_clock::now(); - - // Train network - net.train(); - - // Stop time measurement - auto t2 = std::chrono::high_resolution_clock::now(); - iganet::Log(iganet::log::info) - << "Training took " - << std::chrono::duration_cast>(t2 - t1) - .count() - << " seconds\n"; - -#ifdef IGANET_WITH_MATPLOT - // Evaluate position of collocation points in physical domain - auto colPts = net.G().eval(net.collPts().first); - - // Plot the solution - net.G() - .plot(net.u(), std::array{*colPts[0], *colPts[1]}, json) - ->show(); - - // Plot the difference between the solution and the reference data - net.G() - .plot(net.u().abs_diff(net.f()), - std::array{*colPts[0], *colPts[1]}, json) - ->show(); -#endif - -#ifdef IGANET_WITH_GISMO - // Convert B-spline objects to G+Smo - auto G_gismo = net.G().to_gismo(); - auto u_gismo = net.u().to_gismo(); - auto f_gismo = net.f().to_gismo(); - - // Set up expression assembler - gsExprAssembler A(1, 1); - gsMultiBasis basis(u_gismo, true); - - A.setIntegrationElements(basis); - - auto G = A.getMap(G_gismo); - auto u = A.getCoeff(u_gismo, G); - auto f = A.getCoeff(f_gismo, G); - - // Compute L2- and H2-error - gsExprEvaluator ev(A); - - iganet::Log(iganet::log::info) - << "L2-error : " - << gismo::math::sqrt(ev.integral((u - f).sqNorm() * meas(G))) - << std::endl; - - iganet::Log(iganet::log::info) - << "H1-error : " - << gismo::math::sqrt(ev.integral( - (gismo::expr::igrad(u, G) - gismo::expr::igrad(f, G)).sqNorm() * - meas(G))) - << std::endl; -#endif - - return 0; -} +/** + @file examples/iganet_fitting_geometry.cxx + + @brief Demonstration of IgANet function fitting on a geometry loaded from a + file + + @author Veronika Travnikova + + This example demonstrates how to implement an IgANet to fit a given + function on a geometry loaded from a file. In contrast to the + example iganet_fitting_geometry_simple.cxx this examples makes use + of pre-computed indices and coefficients and should therefore be + faster. + + @author Matthias Moller + + @copyright This file is part of the IgANet project + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#include +#include +#include + +/// @brief Specialization of the abstract IgANet class for function fitting +template +class fitting : public iganet::IgANet, + public iganet::IgANetCustomizable { + +private: + /// @brief Type of the base class + using Base = iganet::IgANet; + + /// @brief Collocation points + typename Base::variable_collPts_type collPts_; + + /// @brief Type of the customizable class + using Customizable = iganet::IgANetCustomizable; + + /// @brief Knot indices + typename Customizable::variable_interior_knot_indices_type knot_indices_; + + /// @brief Coefficient indices + typename Customizable::variable_interior_coeff_indices_type coeff_indices_; + +public: + /// @brief Constructors from the base class + using iganet::IgANet::IgANet; + + /// @brief Returns a constant reference to the collocation points + auto const &collPts() const { return collPts_; } + + /// @brief Initializes the epoch + /// + /// @param[in] epoch Epoch number + bool epoch(int64_t epoch) override { + // In the very first epoch we need to generate the sampling points + // for the inputs and the sampling points in the function space of + // the variables since otherwise the respective tensors would be + // empty. In all further epochs no updates are needed since we do + // not change the inputs nor the variable function space. + if (epoch == 0) { + Base::inputs(epoch); + collPts_ = Base::variable_collPts(iganet::collPts::greville); + + knot_indices_ = + Base::f_.template find_knot_indices( + collPts_.first); + coeff_indices_ = + Base::f_.template find_coeff_indices( + knot_indices_); + + return true; + } else + return false; + } + + /// @brief Computes the loss function + /// + /// @param[in] outputs Output of the network + /// + /// @param[in] epoch Epoch number + torch::Tensor loss(const torch::Tensor &outputs, int64_t epoch) override { + + // Cast the network output (a raw tensor) into the proper + // function-space format, i.e. B-spline objects for the interior + // and boundary parts that can be evaluated. + Base::u_.from_tensor(outputs); + + // Evaluate the loss function + return torch::mse_loss( + *Base::u_.eval(collPts_.first, knot_indices_, coeff_indices_)[0], + *Base::f_.eval(collPts_.first, knot_indices_, coeff_indices_)[0]); + } +}; + +int main() { + iganet::init(); + iganet::verbose(std::cout); + + nlohmann::json json; + json["res0"] = 50; + json["res1"] = 50; + + using namespace iganet::literals; + using optimizer_t = torch::optim::Adam; + using real_t = double; + + // Load XML file + pugi::xml_document xml; + xml.load_file(IGANET_DATA_DIR "surfaces/2d/geo02.xml"); + + // Bivariate uniform B-spline of degree 2 in both directions + // the type has to correspond to the respective geometry parameterization in + // the input file + using geometry_t = iganet::S>; + + // Variable: Bi-quadratic B-spline function space S (geoDim = 1, p = q = 2) + using variable_t = iganet::S>; + + fitting + net( // Number of neurons per layers + {50, 50}, + // Activation functions + {{iganet::activation::sigmoid}, + {iganet::activation::sigmoid}, + {iganet::activation::none}}, + // Number of B-spline coefficients of the geometry, has to correspond + // to number of coefficients in input file + std::tuple(iganet::utils::to_array(25_i64, 25_i64)), + // Number of B-spline coefficients of the variable + std::tuple(iganet::utils::to_array(30_i64, 30_i64))); + + // Load geometry parameterization from XML + net.G().from_xml(xml); + + // Impose solution value for supervised training (not right-hand side) + net.f().transform([](const std::array xi) { + return std::array{ + static_cast(sin(M_PI * xi[0]) * sin(M_PI * xi[1]))}; + }); + + // Set maximum number of epochs + net.options().max_epoch(1000); + + // Set tolerance for the loss functions + net.options().min_loss(1e-8); + + // Start time measurement + auto t1 = std::chrono::high_resolution_clock::now(); + + // Train network + net.train(); + + // Stop time measurement + auto t2 = std::chrono::high_resolution_clock::now(); + iganet::Log(iganet::log::info) + << "Training took " + << std::chrono::duration_cast>(t2 - t1) + .count() + << " seconds\n"; + +#ifdef IGANET_WITH_MATPLOT + // Evaluate position of collocation points in physical domain + auto colPts = net.G().eval(net.collPts().first); + + // Plot the solution + net.G() + .plot(net.u(), std::array{*colPts[0], *colPts[1]}, json) + ->show(); + + // Plot the difference between the solution and the reference data + net.G() + .plot(net.u().abs_diff(net.f()), + std::array{*colPts[0], *colPts[1]}, json) + ->show(); +#endif + +#ifdef IGANET_WITH_GISMO + // Convert B-spline objects to G+Smo + auto G_gismo = net.G().to_gismo(); + auto u_gismo = net.u().to_gismo(); + auto f_gismo = net.f().to_gismo(); + + // Set up expression assembler + gsExprAssembler A(1, 1); + gsMultiBasis basis(u_gismo, true); + + A.setIntegrationElements(basis); + + auto G = A.getMap(G_gismo); + auto u = A.getCoeff(u_gismo, G); + auto f = A.getCoeff(f_gismo, G); + + // Compute L2- and H2-error + gsExprEvaluator ev(A); + + iganet::Log(iganet::log::info) + << "L2-error : " + << gismo::math::sqrt(ev.integral((u - f).sqNorm() * meas(G))) + << std::endl; + + iganet::Log(iganet::log::info) + << "H1-error : " + << gismo::math::sqrt(ev.integral( + (gismo::expr::igrad(u, G) - gismo::expr::igrad(f, G)).sqNorm() * + meas(G))) + << std::endl; +#endif + + return 0; +} diff --git a/examples/iganet_fitting_geometry_autotune.cxx b/examples/iganet_fitting_geometry_autotune.cxx index f3973994..1af18931 100644 --- a/examples/iganet_fitting_geometry_autotune.cxx +++ b/examples/iganet_fitting_geometry_autotune.cxx @@ -1,182 +1,182 @@ -/** - @file examples/iganet_fitting_geometry_autotune.cxx - - @brief Demonstration of IgANet function fitting on a geometry loaded from a - file with automatic hyper-parameter tuning - - This example demonstrates how to auto-tune the hyper-parameters of - an IgANet for fitting a given function on a geometry loaded from a file. In - contrast to the example iganet_fitting_geometry_dataloader_autotone this - example does not make use of the dataloader and fits only on a single - geometry. - - @author Matthias Moller - - @copyright This file is part of the IgANet project - - This Source Code Form is subject to the terms of the Mozilla Public - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at http://mozilla.org/MPL/2.0/. -*/ - -#include -#include -#include - -/// @brief Specialization of the abstract IgANet class for function fitting -template -class fitting : public iganet::IgANet, - public iganet::IgANetCustomizable { - -private: - /// @brief Type of the base class - using Base = iganet::IgANet; - - /// @brief Collocation points - typename Base::variable_collPts_type collPts_; - - /// @brief Type of the customizable class - using Customizable = iganet::IgANetCustomizable; - - /// @brief Knot indices - typename Customizable::variable_interior_knot_indices_type knot_indices_; - - /// @brief Coefficient indices - typename Customizable::variable_interior_coeff_indices_type coeff_indices_; - -public: - /// @brief Constructors from the base class - using iganet::IgANet::IgANet; - - /// @brief Returns a constant reference to the collocation points - auto const &collPts() const { return collPts_; } - - /// @brief Initializes the epoch - /// - /// @param[in] epoch Epoch number - bool epoch(int64_t epoch) override { - // In the very first epoch we need to generate the sampling points - // for the inputs and the sampling points in the function space of - // the variables since otherwise the respective tensors would be - // empty. In all further epochs no updates are needed since we do - // not change the inputs nor the variable function space. - if (epoch == 0) { - collPts_ = Base::variable_collPts(iganet::collPts::greville); - - knot_indices_ = - Base::f_.template find_knot_indices( - collPts_.first); - coeff_indices_ = - Base::f_.template find_coeff_indices( - knot_indices_); - - return true; - } else - return false; - } - - /// @brief Computes the loss function - /// - /// @param[in] outputs Output of the network - /// - /// @param[in] epoch Epoch number - torch::Tensor loss(const torch::Tensor &outputs, int64_t epoch) override { - // Cast the network output (a raw tensor) into the proper - // function-space format, i.e. B-spline objects for the interior - // and boundary parts that can be evaluated. - Base::u_.from_tensor(outputs); - - // Evaluate the loss function - return torch::mse_loss( - *Base::u_.eval(collPts_.first, knot_indices_, coeff_indices_)[0], - *Base::f_.eval(collPts_.first, knot_indices_, coeff_indices_)[0]); - } -}; - -int main() { - iganet::init(); - iganet::verbose(std::cout); - - nlohmann::json json; - json["res0"] = 50; - json["res1"] = 50; - - using namespace iganet::literals; - using optimizer_t = torch::optim::Adam; - using real_t = double; - - // Load XML file - pugi::xml_document xml; - xml.load_file(IGANET_DATA_DIR "surfaces/2d/geo02.xml"); - - // Bivariate uniform B-spline of degree 2 in both directions - // the type has to correspond to the respective geometry parameterization in - // the input file - using geometry_t = iganet::S>; - - // Variable: Bi-quadratic B-spline function space S (geoDim = 1, p = q = 2) - using variable_t = iganet::S>; - - for (std::vector activation : - {std::vector{iganet::activation::relu}, - std::vector{iganet::activation::sigmoid}, - std::vector{iganet::activation::tanh}}) { - for (int64_t nlayers : - iganet::utils::getenv("IGANET_NLAYERS", {5, 6, 7, 8, 9})) { - for (int64_t nneurons : iganet::utils::getenv( - "IGANET_NNEURONS", {60, 80, 100, 120, 140, 160, 180, 200})) { - - iganet::Log(iganet::log::info) - << "#layers: " << nlayers << ", #neurons: " << nneurons - << std::endl; - - std::vector layers(nlayers, nneurons); - std::vector> activations(nlayers, activation); - activations.emplace_back( - std::vector{iganet::activation::none}); - - fitting - net( // Number of neurons per layers - layers, - // Activation functions - activations, - // Number of B-spline coefficients of the geometry - std::tuple(iganet::utils::to_array(25_i64, 25_i64)), - // Number of B-spline coefficients of the variable - std::tuple(iganet::utils::to_array(30_i64, 30_i64))); - - // Load geometry parameterization from XML - net.G().from_xml(xml); - - // Impose solution value for supervised training (not right-hand side) - net.f().transform([](const std::array xi) { - return std::array{ - static_cast(sin(M_PI * xi[0]) * sin(M_PI * xi[1]))}; - }); - - // Set maximum number of epochs - net.options().max_epoch(1000); - - // Set tolerance for the loss functions - net.options().min_loss(1e-8); - - // Start time measurement - auto t1 = std::chrono::high_resolution_clock::now(); - - // Train network - net.train(); - - // Stop time measurement - auto t2 = std::chrono::high_resolution_clock::now(); - iganet::Log(iganet::log::info) - << "Training took " - << std::chrono::duration_cast>(t2 - - t1) - .count() - << " seconds\n"; - } - } - } - - return 0; -} +/** + @file examples/iganet_fitting_geometry_autotune.cxx + + @brief Demonstration of IgANet function fitting on a geometry loaded from a + file with automatic hyper-parameter tuning + + This example demonstrates how to auto-tune the hyper-parameters of + an IgANet for fitting a given function on a geometry loaded from a file. In + contrast to the example iganet_fitting_geometry_dataloader_autotone this + example does not make use of the dataloader and fits only on a single + geometry. + + @author Matthias Moller + + @copyright This file is part of the IgANet project + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#include +#include +#include + +/// @brief Specialization of the abstract IgANet class for function fitting +template +class fitting : public iganet::IgANet, + public iganet::IgANetCustomizable { + +private: + /// @brief Type of the base class + using Base = iganet::IgANet; + + /// @brief Collocation points + typename Base::variable_collPts_type collPts_; + + /// @brief Type of the customizable class + using Customizable = iganet::IgANetCustomizable; + + /// @brief Knot indices + typename Customizable::variable_interior_knot_indices_type knot_indices_; + + /// @brief Coefficient indices + typename Customizable::variable_interior_coeff_indices_type coeff_indices_; + +public: + /// @brief Constructors from the base class + using iganet::IgANet::IgANet; + + /// @brief Returns a constant reference to the collocation points + auto const &collPts() const { return collPts_; } + + /// @brief Initializes the epoch + /// + /// @param[in] epoch Epoch number + bool epoch(int64_t epoch) override { + // In the very first epoch we need to generate the sampling points + // for the inputs and the sampling points in the function space of + // the variables since otherwise the respective tensors would be + // empty. In all further epochs no updates are needed since we do + // not change the inputs nor the variable function space. + if (epoch == 0) { + collPts_ = Base::variable_collPts(iganet::collPts::greville); + + knot_indices_ = + Base::f_.template find_knot_indices( + collPts_.first); + coeff_indices_ = + Base::f_.template find_coeff_indices( + knot_indices_); + + return true; + } else + return false; + } + + /// @brief Computes the loss function + /// + /// @param[in] outputs Output of the network + /// + /// @param[in] epoch Epoch number + torch::Tensor loss(const torch::Tensor &outputs, int64_t epoch) override { + // Cast the network output (a raw tensor) into the proper + // function-space format, i.e. B-spline objects for the interior + // and boundary parts that can be evaluated. + Base::u_.from_tensor(outputs); + + // Evaluate the loss function + return torch::mse_loss( + *Base::u_.eval(collPts_.first, knot_indices_, coeff_indices_)[0], + *Base::f_.eval(collPts_.first, knot_indices_, coeff_indices_)[0]); + } +}; + +int main() { + iganet::init(); + iganet::verbose(std::cout); + + nlohmann::json json; + json["res0"] = 50; + json["res1"] = 50; + + using namespace iganet::literals; + using optimizer_t = torch::optim::Adam; + using real_t = double; + + // Load XML file + pugi::xml_document xml; + xml.load_file(IGANET_DATA_DIR "surfaces/2d/geo02.xml"); + + // Bivariate uniform B-spline of degree 2 in both directions + // the type has to correspond to the respective geometry parameterization in + // the input file + using geometry_t = iganet::S>; + + // Variable: Bi-quadratic B-spline function space S (geoDim = 1, p = q = 2) + using variable_t = iganet::S>; + + for (std::vector activation : + {std::vector{iganet::activation::relu}, + std::vector{iganet::activation::sigmoid}, + std::vector{iganet::activation::tanh}}) { + for (int64_t nlayers : + iganet::utils::getenv("IGANET_NLAYERS", {5, 6, 7, 8, 9})) { + for (int64_t nneurons : iganet::utils::getenv( + "IGANET_NNEURONS", {60, 80, 100, 120, 140, 160, 180, 200})) { + + iganet::Log(iganet::log::info) + << "#layers: " << nlayers << ", #neurons: " << nneurons + << std::endl; + + std::vector layers(nlayers, nneurons); + std::vector> activations(nlayers, activation); + activations.emplace_back( + std::vector{iganet::activation::none}); + + fitting + net( // Number of neurons per layers + layers, + // Activation functions + activations, + // Number of B-spline coefficients of the geometry + std::tuple(iganet::utils::to_array(25_i64, 25_i64)), + // Number of B-spline coefficients of the variable + std::tuple(iganet::utils::to_array(30_i64, 30_i64))); + + // Load geometry parameterization from XML + net.G().from_xml(xml); + + // Impose solution value for supervised training (not right-hand side) + net.f().transform([](const std::array xi) { + return std::array{ + static_cast(sin(M_PI * xi[0]) * sin(M_PI * xi[1]))}; + }); + + // Set maximum number of epochs + net.options().max_epoch(1000); + + // Set tolerance for the loss functions + net.options().min_loss(1e-8); + + // Start time measurement + auto t1 = std::chrono::high_resolution_clock::now(); + + // Train network + net.train(); + + // Stop time measurement + auto t2 = std::chrono::high_resolution_clock::now(); + iganet::Log(iganet::log::info) + << "Training took " + << std::chrono::duration_cast>(t2 - + t1) + .count() + << " seconds\n"; + } + } + } + + return 0; +} diff --git a/examples/iganet_fitting_geometry_dataloader_autotune.cxx b/examples/iganet_fitting_geometry_dataloader_autotune.cxx index b2d97664..4fd861dc 100644 --- a/examples/iganet_fitting_geometry_dataloader_autotune.cxx +++ b/examples/iganet_fitting_geometry_dataloader_autotune.cxx @@ -1,246 +1,246 @@ -/** - @file examples/iganet_fitting_geometry_dataloader_autotune.cxx - - @brief Demonstration of IgANet function fitting with automatic - hyper-parameter tuning and use of the data loader for the geometry - - This example demonstrates how to auto-tune the hyper-parameters of - an IgANet for fitting a given function on a set of geometries that - are loaded with the custom data loader - - @author Matthias Moller - - @copyright This file is part of the IgANet project - - This Source Code Form is subject to the terms of the Mozilla Public - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at http://mozilla.org/MPL/2.0/. -*/ - -#include -#include -#include - -/// @brief Specialization of the abstract IgANet class for function fitting -template -class fitting : public iganet::IgANet, - public iganet::IgANetCustomizable { - -private: - /// @brief Type of the base class - using Base = iganet::IgANet; - - /// @brief Collocation points - typename Base::variable_collPts_type collPts_; - - /// @brief Type of the customizable class - using Customizable = iganet::IgANetCustomizable; - - /// @brief Knot indices - typename Customizable::variable_interior_knot_indices_type knot_indices_; - - /// @brief Coefficient indices - typename Customizable::variable_interior_coeff_indices_type coeff_indices_; - -public: - /// @brief Constructors from the base class - using iganet::IgANet::IgANet; - - /// @brief Returns a constant reference to the collocation points - auto const &collPts() const { return collPts_; } - - /// @brief Initializes the epoch - /// - /// @param[in] epoch Epoch number - bool epoch(int64_t epoch) override { - // In the very first epoch we need to generate the sampling points - // for the inputs and the sampling points in the function space of - // the variables since otherwise the respective tensors would be - // empty. In all further epochs no updates are needed since we do - // not change the inputs nor the variable function space. - collPts_ = Base::variable_collPts(iganet::collPts::greville); - - knot_indices_ = - Base::f_.template find_knot_indices( - collPts_.first); - coeff_indices_ = - Base::f_.template find_coeff_indices( - knot_indices_); - - return true; - } - - /// @brief Computes the loss function - /// - /// @param[in] outputs Output of the network - /// - /// @param[in] epoch Epoch number - torch::Tensor loss(const torch::Tensor &outputs, int64_t epoch) override { - // Cast the network output (a raw tensor) into the proper - // function-space format, i.e. B-spline objects for the interior - // and boundary parts that can be evaluated. - - if (outputs.dim() > 1) - Base::u_.from_tensor(outputs.t()); - else - Base::u_.from_tensor(outputs.flatten()); - - // Evaluate the loss function - return torch::mse_loss( - *Base::u_.eval(collPts_.first, knot_indices_, coeff_indices_)[0], - *Base::f_.eval(collPts_.first, knot_indices_, coeff_indices_)[0]); - } -}; - -int main() { - -#ifdef IGANET_WITH_MPI - // Creating MPI Process Group - auto pg = c10d::ProcessGroupMPI::createProcessGroupMPI(); - - // Retrieving MPI environment variables - auto size = pg->getSize(); - auto rank = pg->getRank(); -#endif - - iganet::init(); - iganet::verbose(std::cout); - - nlohmann::json json; - json["res0"] = 50; - json["res1"] = 50; - - using namespace iganet::literals; - using optimizer_t = torch::optim::Adam; - using real_t = double; - - // Geometry: Bi-linear B-spline function space S (geoDim = 2, p = q = 1) - using geometry_t = iganet::S>; - - // Variable: Bi-quadratic B-spline function space S (geoDim = 1, p = q = 2) - using variable_t = iganet::S>; - - // Create geometry data set for training - iganet::IgADataset<> dataset; - dataset.add_geometryMap(geometry_t{iganet::utils::to_array(25_i64, 25_i64)}, - IGANET_DATA_DIR "surfaces/2d"); - - // Impose solution value for supervised training (not right-hand side) - dataset.add_referenceData(variable_t{iganet::utils::to_array(30_i64, 30_i64)}, - [](const std::array xi) { - return std::array{static_cast( - sin(M_PI * xi[0]) * sin(M_PI * xi[1]))}; - }); - - // Create data set - auto train_dataset = dataset.map( - torch::data::transforms::Stack::example_type>()); - auto train_size = train_dataset.size().value(); - -#ifdef IGANET_WITH_MPI - // Create distributed data loader - auto data_sampler = torch::data::samplers::DistributedRandomSampler( - train_size, size, rank, false); - - auto train_loader = torch::data::make_data_loader( - std::move(train_dataset), - iganet::utils::getenv("IGANET_BATCHSIZE", 8) / size); - -#else - // Create sequential data loader - auto data_sampler = torch::data::samplers::SequentialSampler(train_size); - - auto train_loader = torch::data::make_data_loader( - std::move(train_dataset), data_sampler, - iganet::utils::getenv("IGANET_BATCHSIZE", 8)); -#endif - - // Auto-tuning loop - for (std::vector activation : - {std::vector{iganet::activation::sigmoid}}) { - for (int64_t nlayers : iganet::utils::getenv("IGANET_NLAYERS", {8, 9})) { - for (int64_t nneurons : iganet::utils::getenv( - "IGANET_NNEURONS", {60, 80, 100, 120, 140, 160, 180, 200})) { - - iganet::Log(iganet::log::info) - << "#layers: " << nlayers << ", #neurons: " << nneurons - << std::endl; - - std::vector layers(nlayers, nneurons); - std::vector> activations(nlayers, activation); - activations.emplace_back( - std::vector{iganet::activation::none}); - - fitting - net( // Number of neurons per layers - layers, - // Activation functions - activations, - // Number of B-spline coefficients of the geometry - std::tuple(iganet::utils::to_array(25_i64, 25_i64)), - // Number of B-spline coefficients of the variable - std::tuple(iganet::utils::to_array(30_i64, 30_i64))); - - // Set maximum number of epochs - net.options().max_epoch(500); - - // Set tolerance for the loss functions - net.options().min_loss(1e-8); - - // Start time measurement - auto t1 = std::chrono::high_resolution_clock::now(); - - // Train network -#ifdef IGANET_WITH_MPI - net.train(*train_loader, pg); -#else - net.train(*train_loader); -#endif - - // Stop time measurement - auto t2 = std::chrono::high_resolution_clock::now(); - iganet::Log(iganet::log::info) - << "Training took " - << std::chrono::duration_cast>(t2 - - t1) - .count() - << " seconds\n"; - -#ifdef IGANET_WITH_GISMO - // Convert B-spline objects to G+Smo - auto G_gismo = net.G().to_gismo(); - auto u_gismo = net.u().to_gismo(); - auto f_gismo = net.f().to_gismo(); - - // Set up expression assembler - gsExprAssembler A(1, 1); - gsMultiBasis basis(u_gismo, true); - - A.setIntegrationElements(basis); - - auto G = A.getMap(G_gismo); - auto u = A.getCoeff(u_gismo, G); - auto f = A.getCoeff(f_gismo, G); - - // Compute L2- and H2-error - gsExprEvaluator ev(A); - - iganet::Log(iganet::log::info) - << "L2-error : " - << gismo::math::sqrt(ev.integral((u - f).sqNorm() * meas(G))) - << std::endl; - - iganet::Log(iganet::log::info) - << "H1-error : " - << gismo::math::sqrt(ev.integral( - (gismo::expr::igrad(u, G) - gismo::expr::igrad(f, G)) - .sqNorm() * - meas(G))) - << std::endl; -#endif - } - } - } - - return 0; -} +/** + @file examples/iganet_fitting_geometry_dataloader_autotune.cxx + + @brief Demonstration of IgANet function fitting with automatic + hyper-parameter tuning and use of the data loader for the geometry + + This example demonstrates how to auto-tune the hyper-parameters of + an IgANet for fitting a given function on a set of geometries that + are loaded with the custom data loader + + @author Matthias Moller + + @copyright This file is part of the IgANet project + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#include +#include +#include + +/// @brief Specialization of the abstract IgANet class for function fitting +template +class fitting : public iganet::IgANet, + public iganet::IgANetCustomizable { + +private: + /// @brief Type of the base class + using Base = iganet::IgANet; + + /// @brief Collocation points + typename Base::variable_collPts_type collPts_; + + /// @brief Type of the customizable class + using Customizable = iganet::IgANetCustomizable; + + /// @brief Knot indices + typename Customizable::variable_interior_knot_indices_type knot_indices_; + + /// @brief Coefficient indices + typename Customizable::variable_interior_coeff_indices_type coeff_indices_; + +public: + /// @brief Constructors from the base class + using iganet::IgANet::IgANet; + + /// @brief Returns a constant reference to the collocation points + auto const &collPts() const { return collPts_; } + + /// @brief Initializes the epoch + /// + /// @param[in] epoch Epoch number + bool epoch(int64_t epoch) override { + // In the very first epoch we need to generate the sampling points + // for the inputs and the sampling points in the function space of + // the variables since otherwise the respective tensors would be + // empty. In all further epochs no updates are needed since we do + // not change the inputs nor the variable function space. + collPts_ = Base::variable_collPts(iganet::collPts::greville); + + knot_indices_ = + Base::f_.template find_knot_indices( + collPts_.first); + coeff_indices_ = + Base::f_.template find_coeff_indices( + knot_indices_); + + return true; + } + + /// @brief Computes the loss function + /// + /// @param[in] outputs Output of the network + /// + /// @param[in] epoch Epoch number + torch::Tensor loss(const torch::Tensor &outputs, int64_t epoch) override { + // Cast the network output (a raw tensor) into the proper + // function-space format, i.e. B-spline objects for the interior + // and boundary parts that can be evaluated. + + if (outputs.dim() > 1) + Base::u_.from_tensor(outputs.t()); + else + Base::u_.from_tensor(outputs.flatten()); + + // Evaluate the loss function + return torch::mse_loss( + *Base::u_.eval(collPts_.first, knot_indices_, coeff_indices_)[0], + *Base::f_.eval(collPts_.first, knot_indices_, coeff_indices_)[0]); + } +}; + +int main() { + +#ifdef IGANET_WITH_MPI + // Creating MPI Process Group + auto pg = c10d::ProcessGroupMPI::createProcessGroupMPI(); + + // Retrieving MPI environment variables + auto size = pg->getSize(); + auto rank = pg->getRank(); +#endif + + iganet::init(); + iganet::verbose(std::cout); + + nlohmann::json json; + json["res0"] = 50; + json["res1"] = 50; + + using namespace iganet::literals; + using optimizer_t = torch::optim::Adam; + using real_t = double; + + // Geometry: Bi-linear B-spline function space S (geoDim = 2, p = q = 1) + using geometry_t = iganet::S>; + + // Variable: Bi-quadratic B-spline function space S (geoDim = 1, p = q = 2) + using variable_t = iganet::S>; + + // Create geometry data set for training + iganet::IgADataset<> dataset; + dataset.add_geometryMap(geometry_t{iganet::utils::to_array(25_i64, 25_i64)}, + IGANET_DATA_DIR "surfaces/2d"); + + // Impose solution value for supervised training (not right-hand side) + dataset.add_referenceData(variable_t{iganet::utils::to_array(30_i64, 30_i64)}, + [](const std::array xi) { + return std::array{static_cast( + sin(M_PI * xi[0]) * sin(M_PI * xi[1]))}; + }); + + // Create data set + auto train_dataset = dataset.map( + torch::data::transforms::Stack::example_type>()); + auto train_size = train_dataset.size().value(); + +#ifdef IGANET_WITH_MPI + // Create distributed data loader + auto data_sampler = torch::data::samplers::DistributedRandomSampler( + train_size, size, rank, false); + + auto train_loader = torch::data::make_data_loader( + std::move(train_dataset), + iganet::utils::getenv("IGANET_BATCHSIZE", 8) / size); + +#else + // Create sequential data loader + auto data_sampler = torch::data::samplers::SequentialSampler(train_size); + + auto train_loader = torch::data::make_data_loader( + std::move(train_dataset), data_sampler, + iganet::utils::getenv("IGANET_BATCHSIZE", 8)); +#endif + + // Auto-tuning loop + for (std::vector activation : + {std::vector{iganet::activation::sigmoid}}) { + for (int64_t nlayers : iganet::utils::getenv("IGANET_NLAYERS", {8, 9})) { + for (int64_t nneurons : iganet::utils::getenv( + "IGANET_NNEURONS", {60, 80, 100, 120, 140, 160, 180, 200})) { + + iganet::Log(iganet::log::info) + << "#layers: " << nlayers << ", #neurons: " << nneurons + << std::endl; + + std::vector layers(nlayers, nneurons); + std::vector> activations(nlayers, activation); + activations.emplace_back( + std::vector{iganet::activation::none}); + + fitting + net( // Number of neurons per layers + layers, + // Activation functions + activations, + // Number of B-spline coefficients of the geometry + std::tuple(iganet::utils::to_array(25_i64, 25_i64)), + // Number of B-spline coefficients of the variable + std::tuple(iganet::utils::to_array(30_i64, 30_i64))); + + // Set maximum number of epochs + net.options().max_epoch(500); + + // Set tolerance for the loss functions + net.options().min_loss(1e-8); + + // Start time measurement + auto t1 = std::chrono::high_resolution_clock::now(); + + // Train network +#ifdef IGANET_WITH_MPI + net.train(*train_loader, pg); +#else + net.train(*train_loader); +#endif + + // Stop time measurement + auto t2 = std::chrono::high_resolution_clock::now(); + iganet::Log(iganet::log::info) + << "Training took " + << std::chrono::duration_cast>(t2 - + t1) + .count() + << " seconds\n"; + +#ifdef IGANET_WITH_GISMO + // Convert B-spline objects to G+Smo + auto G_gismo = net.G().to_gismo(); + auto u_gismo = net.u().to_gismo(); + auto f_gismo = net.f().to_gismo(); + + // Set up expression assembler + gsExprAssembler A(1, 1); + gsMultiBasis basis(u_gismo, true); + + A.setIntegrationElements(basis); + + auto G = A.getMap(G_gismo); + auto u = A.getCoeff(u_gismo, G); + auto f = A.getCoeff(f_gismo, G); + + // Compute L2- and H2-error + gsExprEvaluator ev(A); + + iganet::Log(iganet::log::info) + << "L2-error : " + << gismo::math::sqrt(ev.integral((u - f).sqNorm() * meas(G))) + << std::endl; + + iganet::Log(iganet::log::info) + << "H1-error : " + << gismo::math::sqrt(ev.integral( + (gismo::expr::igrad(u, G) - gismo::expr::igrad(f, G)) + .sqNorm() * + meas(G))) + << std::endl; +#endif + } + } + } + + return 0; +} diff --git a/examples/iganet_fitting_geometry_simple.cxx b/examples/iganet_fitting_geometry_simple.cxx index 193ab729..23b5965b 100644 --- a/examples/iganet_fitting_geometry_simple.cxx +++ b/examples/iganet_fitting_geometry_simple.cxx @@ -1,195 +1,195 @@ -/** - @file examples/iganet_fitting_geometry_simple.cxx - - @brief Demonstration of IgANet function fitting on a geometry loaded from a - file - - This example demonstrates how to implement a simple IgANet to fit a - given function on a geometry loaded from a file. In contrast to the - example iganet_fitting_geometry.cxx this examples does not - make use of pre-computed indices and coefficients and might - therefore be slower. - - @author Veronika Travnikova - - @copyright This file is part of the IgANet project - - This Source Code Form is subject to the terms of the Mozilla Public - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at http://mozilla.org/MPL/2.0/. -*/ - -#include -#include -#include - -/// @brief Specialization of the abstract IgANet class for function fitting -template -class fitting : public iganet::IgANet { - -private: - /// @brief Type of the base class - using Base = iganet::IgANet; - - /// @brief Collocation points - typename Base::variable_collPts_type collPts_; - -public: - /// @brief Constructors from the base class - using iganet::IgANet::IgANet; - - /// @brief Returns a constant reference to the collocation points - auto const &collPts() const { return collPts_; } - - /// @brief Initializes the epoch - /// - /// @param[in] epoch Epoch number - bool epoch(int64_t epoch) override { - // In the very first epoch we need to generate the sampling points - // for the inputs and the sampling points in the function space of - // the variables since otherwise the respective tensors would be - // empty. In all further epochs no updates are needed since we do - // not change the inputs nor the variable function space. - if (epoch == 0) { - Base::inputs(epoch); - collPts_ = Base::variable_collPts(iganet::collPts::greville); - - return true; - } else - return false; - } - - /// @brief Computes the loss function - /// - /// @param[in] outputs Output of the network - /// - /// @param[in] epoch Epoch number - torch::Tensor loss(const torch::Tensor &outputs, int64_t epoch) override { - - // Cast the network output (a raw tensor) into the proper - // function-space format, i.e. B-spline objects for the interior - // and boundary parts that can be evaluated. - Base::u_.from_tensor(outputs); - - // Evaluate the loss function - return torch::mse_loss(*Base::u_.eval(collPts_.first)[0], - *Base::f_.eval(collPts_.first)[0]); - } -}; - -int main() { - iganet::init(); - iganet::verbose(std::cout); - - nlohmann::json json; - json["res0"] = 50; - json["res1"] = 50; - - using namespace iganet::literals; - using optimizer_t = torch::optim::Adam; - using real_t = double; - - // Load XML file - pugi::xml_document xml; - xml.load_file(IGANET_DATA_DIR "surfaces/2d/geo02.xml"); - - // Bivariate uniform B-spline of degree 2 in both directions - // the type has to correspond to the respective geometry parameterization in - // the input file - using geometry_t = iganet::S>; - - // Variable: Bi-quadratic B-spline function space S (geoDim = 1, p = q = 2) - using variable_t = iganet::S>; - - fitting - net( // Number of neurons per layers - {50, 50}, - // Activation functions - {{iganet::activation::sigmoid}, - {iganet::activation::sigmoid}, - {iganet::activation::none}}, - // Number of B-spline coefficients of the geometry, has to correspond - // to number of coefficients in input file - std::tuple(iganet::utils::to_array(25_i64, 25_i64)), - // Number of B-spline coefficients of the variable - std::tuple(iganet::utils::to_array(30_i64, 30_i64))); - - // Load geometry parameterization from XML - net.G().from_xml(xml); - - // Impose solution value for supervised training (not right-hand side) - net.f().transform([](const std::array xi) { - return std::array{ - static_cast(sin(M_PI * xi[0]) * sin(M_PI * xi[1]))}; - }); - - // Set maximum number of epochs - net.options().max_epoch(1000); - - // Set tolerance for the loss functions - net.options().min_loss(1e-8); - - // Start time measurement - auto t1 = std::chrono::high_resolution_clock::now(); - - // Train network - net.train(); - - // Stop time measurement - auto t2 = std::chrono::high_resolution_clock::now(); - iganet::Log(iganet::log::info) - << "Training took " - << std::chrono::duration_cast>(t2 - t1) - .count() - << " seconds\n"; - -#ifdef IGANET_WITH_MATPLOT - // Evaluate position of collocation points in physical domain - auto colPts = net.G().eval(net.collPts().first); - - // Plot the solution - net.G() - .plot(net.u(), std::array{*colPts[0], *colPts[1]}, json) - ->show(); - - // Plot the difference between the solution and the reference data - net.G() - .plot(net.u().abs_diff(net.f()), - std::array{*colPts[0], *colPts[1]}, json) - ->show(); -#endif - -#ifdef IGANET_WITH_GISMO - // Convert B-spline objects to G+Smo - auto G_gismo = net.G().to_gismo(); - auto u_gismo = net.u().to_gismo(); - auto f_gismo = net.f().to_gismo(); - - // Set up expression assembler - gsExprAssembler A(1, 1); - gsMultiBasis basis(u_gismo, true); - - A.setIntegrationElements(basis); - - auto G = A.getMap(G_gismo); - auto u = A.getCoeff(u_gismo, G); - auto f = A.getCoeff(f_gismo, G); - - // Compute L2- and H2-error - gsExprEvaluator ev(A); - - iganet::Log(iganet::log::info) - << "L2-error : " - << gismo::math::sqrt(ev.integral((u - f).sqNorm() * meas(G))) - << std::endl; - - iganet::Log(iganet::log::info) - << "H1-error : " - << gismo::math::sqrt(ev.integral( - (gismo::expr::igrad(u, G) - gismo::expr::igrad(f, G)).sqNorm() * - meas(G))) - << std::endl; -#endif - - return 0; -} +/** + @file examples/iganet_fitting_geometry_simple.cxx + + @brief Demonstration of IgANet function fitting on a geometry loaded from a + file + + This example demonstrates how to implement a simple IgANet to fit a + given function on a geometry loaded from a file. In contrast to the + example iganet_fitting_geometry.cxx this examples does not + make use of pre-computed indices and coefficients and might + therefore be slower. + + @author Veronika Travnikova + + @copyright This file is part of the IgANet project + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#include +#include +#include + +/// @brief Specialization of the abstract IgANet class for function fitting +template +class fitting : public iganet::IgANet { + +private: + /// @brief Type of the base class + using Base = iganet::IgANet; + + /// @brief Collocation points + typename Base::variable_collPts_type collPts_; + +public: + /// @brief Constructors from the base class + using iganet::IgANet::IgANet; + + /// @brief Returns a constant reference to the collocation points + auto const &collPts() const { return collPts_; } + + /// @brief Initializes the epoch + /// + /// @param[in] epoch Epoch number + bool epoch(int64_t epoch) override { + // In the very first epoch we need to generate the sampling points + // for the inputs and the sampling points in the function space of + // the variables since otherwise the respective tensors would be + // empty. In all further epochs no updates are needed since we do + // not change the inputs nor the variable function space. + if (epoch == 0) { + Base::inputs(epoch); + collPts_ = Base::variable_collPts(iganet::collPts::greville); + + return true; + } else + return false; + } + + /// @brief Computes the loss function + /// + /// @param[in] outputs Output of the network + /// + /// @param[in] epoch Epoch number + torch::Tensor loss(const torch::Tensor &outputs, int64_t epoch) override { + + // Cast the network output (a raw tensor) into the proper + // function-space format, i.e. B-spline objects for the interior + // and boundary parts that can be evaluated. + Base::u_.from_tensor(outputs); + + // Evaluate the loss function + return torch::mse_loss(*Base::u_.eval(collPts_.first)[0], + *Base::f_.eval(collPts_.first)[0]); + } +}; + +int main() { + iganet::init(); + iganet::verbose(std::cout); + + nlohmann::json json; + json["res0"] = 50; + json["res1"] = 50; + + using namespace iganet::literals; + using optimizer_t = torch::optim::Adam; + using real_t = double; + + // Load XML file + pugi::xml_document xml; + xml.load_file(IGANET_DATA_DIR "surfaces/2d/geo02.xml"); + + // Bivariate uniform B-spline of degree 2 in both directions + // the type has to correspond to the respective geometry parameterization in + // the input file + using geometry_t = iganet::S>; + + // Variable: Bi-quadratic B-spline function space S (geoDim = 1, p = q = 2) + using variable_t = iganet::S>; + + fitting + net( // Number of neurons per layers + {50, 50}, + // Activation functions + {{iganet::activation::sigmoid}, + {iganet::activation::sigmoid}, + {iganet::activation::none}}, + // Number of B-spline coefficients of the geometry, has to correspond + // to number of coefficients in input file + std::tuple(iganet::utils::to_array(25_i64, 25_i64)), + // Number of B-spline coefficients of the variable + std::tuple(iganet::utils::to_array(30_i64, 30_i64))); + + // Load geometry parameterization from XML + net.G().from_xml(xml); + + // Impose solution value for supervised training (not right-hand side) + net.f().transform([](const std::array xi) { + return std::array{ + static_cast(sin(M_PI * xi[0]) * sin(M_PI * xi[1]))}; + }); + + // Set maximum number of epochs + net.options().max_epoch(1000); + + // Set tolerance for the loss functions + net.options().min_loss(1e-8); + + // Start time measurement + auto t1 = std::chrono::high_resolution_clock::now(); + + // Train network + net.train(); + + // Stop time measurement + auto t2 = std::chrono::high_resolution_clock::now(); + iganet::Log(iganet::log::info) + << "Training took " + << std::chrono::duration_cast>(t2 - t1) + .count() + << " seconds\n"; + +#ifdef IGANET_WITH_MATPLOT + // Evaluate position of collocation points in physical domain + auto colPts = net.G().eval(net.collPts().first); + + // Plot the solution + net.G() + .plot(net.u(), std::array{*colPts[0], *colPts[1]}, json) + ->show(); + + // Plot the difference between the solution and the reference data + net.G() + .plot(net.u().abs_diff(net.f()), + std::array{*colPts[0], *colPts[1]}, json) + ->show(); +#endif + +#ifdef IGANET_WITH_GISMO + // Convert B-spline objects to G+Smo + auto G_gismo = net.G().to_gismo(); + auto u_gismo = net.u().to_gismo(); + auto f_gismo = net.f().to_gismo(); + + // Set up expression assembler + gsExprAssembler A(1, 1); + gsMultiBasis basis(u_gismo, true); + + A.setIntegrationElements(basis); + + auto G = A.getMap(G_gismo); + auto u = A.getCoeff(u_gismo, G); + auto f = A.getCoeff(f_gismo, G); + + // Compute L2- and H2-error + gsExprEvaluator ev(A); + + iganet::Log(iganet::log::info) + << "L2-error : " + << gismo::math::sqrt(ev.integral((u - f).sqNorm() * meas(G))) + << std::endl; + + iganet::Log(iganet::log::info) + << "H1-error : " + << gismo::math::sqrt(ev.integral( + (gismo::expr::igrad(u, G) - gismo::expr::igrad(f, G)).sqNorm() * + meas(G))) + << std::endl; +#endif + + return 0; +} diff --git a/examples/iganet_fitting_simple.cxx b/examples/iganet_fitting_simple.cxx index f61f827a..c0abc2b5 100644 --- a/examples/iganet_fitting_simple.cxx +++ b/examples/iganet_fitting_simple.cxx @@ -1,175 +1,175 @@ -/** - @file examples/iganet_fitting_simple.cxx - - @brief Demonstration of IgANet function fitting - - This example demonstrates how to implement a simple IgANet to fit a - given function on a square geometry. In contrast to the example - iganet_fitting.cxx this examples does not make use of pre-computed - indices and coefficients and might therefore be slower. - - @author Matthias Moller - - @copyright This file is part of the IgANet project - - This Source Code Form is subject to the terms of the Mozilla Public - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at http://mozilla.org/MPL/2.0/. -*/ - -#include -#include -#include - -/// @brief Specialization of the abstract IgANet class for function fitting -template -class fitting : public iganet::IgANet { - -private: - /// @brief Type of the base class - using Base = iganet::IgANet; - - /// @brief Collocation points - typename Base::variable_collPts_type collPts_; - -public: - /// @brief Constructors from the base class - using iganet::IgANet::IgANet; - - /// @brief Returns a constant reference to the collocation points - auto const &collPts() const { return collPts_; } - - /// @brief Initializes the epoch - /// - /// @param[in] epoch Epoch number - bool epoch(int64_t epoch) override { - // In the very first epoch we need to generate the sampling points - // for the inputs and the sampling points in the function space of - // the variables since otherwise the respective tensors would be - // empty. In all further epochs no updates are needed since we do - // not change the inputs nor the variable function space. - if (epoch == 0) { - Base::inputs(epoch); - collPts_ = Base::variable_collPts(iganet::collPts::greville); - - return true; - } else - return false; - } - - /// @brief Computes the loss function - /// - /// @param[in] outputs Output of the network - /// - /// @param[in] epoch Epoch number - torch::Tensor loss(const torch::Tensor &outputs, int64_t epoch) override { - - // Cast the network output (a raw tensor) into the proper - // function-space format, i.e. B-spline objects for the interior - // and boundary parts that can be evaluated. - Base::u_.from_tensor(outputs); - - // Evaluate the loss function - return torch::mse_loss(*Base::u_.eval(collPts_.first)[0], - *Base::f_.eval(collPts_.first)[0]); - } -}; - -int main() { - iganet::init(); - iganet::verbose(std::cout); - - nlohmann::json json; - json["res0"] = 50; - json["res1"] = 50; - - using namespace iganet::literals; - using optimizer_t = torch::optim::Adam; - using real_t = double; - - // Geometry: Bi-linear B-spline function space S (geoDim = 2, p = q = 1) - using geometry_t = iganet::S>; - - // Variable: Bi-quadratic B-spline function space S (geoDim = 1, p = q = 2) - using variable_t = iganet::S>; - - fitting - net( // Number of neurons per layers - {50, 50}, - // Activation functions - {{iganet::activation::sigmoid}, - {iganet::activation::sigmoid}, - {iganet::activation::none}}, - // Number of B-spline coefficients of the geometry, just [0,1] x [0,1] - std::tuple(iganet::utils::to_array(2_i64, 2_i64)), - // Number of B-spline coefficients of the variable - std::tuple(iganet::utils::to_array(20_i64, 20_i64))); - - // Impose solution value for supervised training (not right-hand side) - net.f().transform([](const std::array xi) { - return std::array{ - static_cast(sin(M_PI * xi[0]) * sin(M_PI * xi[1]))}; - }); - - // Set maximum number of epochs - net.options().max_epoch(1000); - - // Set tolerance for the loss functions - net.options().min_loss(1e-8); - - // Start time measurement - auto t1 = std::chrono::high_resolution_clock::now(); - - // Train network - net.train(); - - // Stop time measurement - auto t2 = std::chrono::high_resolution_clock::now(); - iganet::Log(iganet::log::info) - << "Training took " - << std::chrono::duration_cast>(t2 - t1) - .count() - << " seconds\n"; - -#ifdef IGANET_WITH_MATPLOT - // Plot the solution - net.G().plot(net.u(), net.collPts().first, json)->show(); - - // Plot the difference between the solution and the reference data - net.G().plot(net.u().abs_diff(net.f()), net.collPts().first, json)->show(); -#endif - -#ifdef IGANET_WITH_GISMO - // Convert B-spline objects to G+Smo - auto G_gismo = net.G().to_gismo(); - auto u_gismo = net.u().to_gismo(); - auto f_gismo = net.f().to_gismo(); - - // Set up expression assembler - gsExprAssembler A(1, 1); - gsMultiBasis basis(u_gismo, true); - - A.setIntegrationElements(basis); - - auto G = A.getMap(G_gismo); - auto u = A.getCoeff(u_gismo, G); - auto f = A.getCoeff(f_gismo, G); - - // Compute L2- and H2-error - gsExprEvaluator ev(A); - - iganet::Log(iganet::log::info) - << "L2-error : " - << gismo::math::sqrt(ev.integral((u - f).sqNorm() * meas(G))) - << std::endl; - - iganet::Log(iganet::log::info) - << "H1-error : " - << gismo::math::sqrt(ev.integral( - (gismo::expr::igrad(u, G) - gismo::expr::igrad(f, G)).sqNorm() * - meas(G))) - << std::endl; -#endif - - return 0; -} +/** + @file examples/iganet_fitting_simple.cxx + + @brief Demonstration of IgANet function fitting + + This example demonstrates how to implement a simple IgANet to fit a + given function on a square geometry. In contrast to the example + iganet_fitting.cxx this examples does not make use of pre-computed + indices and coefficients and might therefore be slower. + + @author Matthias Moller + + @copyright This file is part of the IgANet project + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#include +#include +#include + +/// @brief Specialization of the abstract IgANet class for function fitting +template +class fitting : public iganet::IgANet { + +private: + /// @brief Type of the base class + using Base = iganet::IgANet; + + /// @brief Collocation points + typename Base::variable_collPts_type collPts_; + +public: + /// @brief Constructors from the base class + using iganet::IgANet::IgANet; + + /// @brief Returns a constant reference to the collocation points + auto const &collPts() const { return collPts_; } + + /// @brief Initializes the epoch + /// + /// @param[in] epoch Epoch number + bool epoch(int64_t epoch) override { + // In the very first epoch we need to generate the sampling points + // for the inputs and the sampling points in the function space of + // the variables since otherwise the respective tensors would be + // empty. In all further epochs no updates are needed since we do + // not change the inputs nor the variable function space. + if (epoch == 0) { + Base::inputs(epoch); + collPts_ = Base::variable_collPts(iganet::collPts::greville); + + return true; + } else + return false; + } + + /// @brief Computes the loss function + /// + /// @param[in] outputs Output of the network + /// + /// @param[in] epoch Epoch number + torch::Tensor loss(const torch::Tensor &outputs, int64_t epoch) override { + + // Cast the network output (a raw tensor) into the proper + // function-space format, i.e. B-spline objects for the interior + // and boundary parts that can be evaluated. + Base::u_.from_tensor(outputs); + + // Evaluate the loss function + return torch::mse_loss(*Base::u_.eval(collPts_.first)[0], + *Base::f_.eval(collPts_.first)[0]); + } +}; + +int main() { + iganet::init(); + iganet::verbose(std::cout); + + nlohmann::json json; + json["res0"] = 50; + json["res1"] = 50; + + using namespace iganet::literals; + using optimizer_t = torch::optim::Adam; + using real_t = double; + + // Geometry: Bi-linear B-spline function space S (geoDim = 2, p = q = 1) + using geometry_t = iganet::S>; + + // Variable: Bi-quadratic B-spline function space S (geoDim = 1, p = q = 2) + using variable_t = iganet::S>; + + fitting + net( // Number of neurons per layers + {50, 50}, + // Activation functions + {{iganet::activation::sigmoid}, + {iganet::activation::sigmoid}, + {iganet::activation::none}}, + // Number of B-spline coefficients of the geometry, just [0,1] x [0,1] + std::tuple(iganet::utils::to_array(2_i64, 2_i64)), + // Number of B-spline coefficients of the variable + std::tuple(iganet::utils::to_array(20_i64, 20_i64))); + + // Impose solution value for supervised training (not right-hand side) + net.f().transform([](const std::array xi) { + return std::array{ + static_cast(sin(M_PI * xi[0]) * sin(M_PI * xi[1]))}; + }); + + // Set maximum number of epochs + net.options().max_epoch(1000); + + // Set tolerance for the loss functions + net.options().min_loss(1e-8); + + // Start time measurement + auto t1 = std::chrono::high_resolution_clock::now(); + + // Train network + net.train(); + + // Stop time measurement + auto t2 = std::chrono::high_resolution_clock::now(); + iganet::Log(iganet::log::info) + << "Training took " + << std::chrono::duration_cast>(t2 - t1) + .count() + << " seconds\n"; + +#ifdef IGANET_WITH_MATPLOT + // Plot the solution + net.G().plot(net.u(), net.collPts().first, json)->show(); + + // Plot the difference between the solution and the reference data + net.G().plot(net.u().abs_diff(net.f()), net.collPts().first, json)->show(); +#endif + +#ifdef IGANET_WITH_GISMO + // Convert B-spline objects to G+Smo + auto G_gismo = net.G().to_gismo(); + auto u_gismo = net.u().to_gismo(); + auto f_gismo = net.f().to_gismo(); + + // Set up expression assembler + gsExprAssembler A(1, 1); + gsMultiBasis basis(u_gismo, true); + + A.setIntegrationElements(basis); + + auto G = A.getMap(G_gismo); + auto u = A.getCoeff(u_gismo, G); + auto f = A.getCoeff(f_gismo, G); + + // Compute L2- and H2-error + gsExprEvaluator ev(A); + + iganet::Log(iganet::log::info) + << "L2-error : " + << gismo::math::sqrt(ev.integral((u - f).sqNorm() * meas(G))) + << std::endl; + + iganet::Log(iganet::log::info) + << "H1-error : " + << gismo::math::sqrt(ev.integral( + (gismo::expr::igrad(u, G) - gismo::expr::igrad(f, G)).sqNorm() * + meas(G))) + << std::endl; +#endif + + return 0; +} diff --git a/examples/iganet_poisson.cxx b/examples/iganet_poisson.cxx index 3136f4c4..a9a82dc1 100644 --- a/examples/iganet_poisson.cxx +++ b/examples/iganet_poisson.cxx @@ -1,231 +1,231 @@ -/** - @file examples/iganet_poisson.cxx - - @brief Demonstration of IgANet Poisson solver - - This example demonstrates how to implement a simple IgANet for - learning the Poisson equation with (non-)homogeneous Dirichlet - boundary conditions on a square geometry. - - @author Matthias Moller - - @copyright This file is part of the IgANet project - - This Source Code Form is subject to the terms of the Mozilla Public - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at http://mozilla.org/MPL/2.0/. -*/ - -#include -#include - -using namespace iganet::literals; - -/// @brief Specialization of the abstract IgANet class for Poisson's equation -template -class poisson : public iganet::IgANet, - public iganet::IgANetCustomizable { - -private: - /// @brief Type of the base class - using Base = iganet::IgANet; - - /// @brief Collocation points - typename Base::variable_collPts_type collPts_; - - /// @brief Reference solution - Variable ref_; - - /// @brief Type of the customizable class - using Customizable = iganet::IgANetCustomizable; - - /// @brief Knot indices of variables - typename Customizable::variable_interior_knot_indices_type var_knot_indices_; - - /// @brief Coefficient indices of variables - typename Customizable::variable_interior_coeff_indices_type - var_coeff_indices_; - - /// @brief Knot indices of the geometry map - typename Customizable::geometryMap_interior_knot_indices_type G_knot_indices_; - - /// @brief Coefficient indices of the geometry map - typename Customizable::geometryMap_interior_coeff_indices_type - G_coeff_indices_; - -public: - /// @brief Constructor - template - poisson(std::vector &&layers, - std::vector> &&activations, Args &&...args) - : Base(std::forward>(layers), - std::forward>>(activations), - std::forward(args)...), - ref_(iganet::utils::to_array(10_i64, 10_i64)) {} - - /// @brief Returns a constant reference to the collocation points - auto const &collPts() const { return collPts_; } - - /// @brief Returns a constant reference to the reference solution - auto const &ref() const { return ref_; } - - /// @brief Returns a non-constant reference to the reference solution - auto &ref() { return ref_; } - - /// @brief Initializes the epoch - /// - /// @param[in] epoch Epoch number - bool epoch(int64_t epoch) override { - // In the very first epoch we need to generate the sampling points - // for the inputs and the sampling points in the function space of - // the variables since otherwise the respective tensors would be - // empty. In all further epochs no updates are needed since we do - // not change the inputs nor the variable function space. - if (epoch == 0) { - Base::inputs(epoch); - collPts_ = Base::variable_collPts(iganet::collPts::greville_ref1); - - var_knot_indices_ = - Base::f_.template find_knot_indices( - collPts_.first); - var_coeff_indices_ = - Base::f_.template find_coeff_indices( - var_knot_indices_); - - G_knot_indices_ = - Base::G_.template find_knot_indices( - collPts_.first); - G_coeff_indices_ = - Base::G_.template find_coeff_indices( - G_knot_indices_); - - return true; - } else - return false; - } - - /// @brief Computes the loss function - /// - /// @param[in] outputs Output of the network - /// - /// @param[in] epoch Epoch number - torch::Tensor loss(const torch::Tensor &outputs, int64_t epoch) override { - - // Cast the network output (a raw tensor) into the proper - // function-space format, i.e. B-spline objects for the interior - // and boundary parts that can be evaluated. - Base::u_.from_tensor(outputs); - - // Evaluate the Laplacian operator - auto u_ilapl = - Base::u_.ilapl(Base::G_, collPts_.first, var_knot_indices_, - var_coeff_indices_, G_knot_indices_, G_coeff_indices_); - - auto f = - Base::f_.eval(collPts_.first, var_knot_indices_, var_coeff_indices_); - - auto u_bdr = Base::u_.template eval( - collPts_.second); - - auto bdr = - ref_.template eval(collPts_.second); - - // Evaluate the loss function - return torch::mse_loss(*u_ilapl[0], *f[0]) + - 1e1 * torch::mse_loss(*std::get<0>(u_bdr)[0], *std::get<0>(bdr)[0]) + - 1e1 * torch::mse_loss(*std::get<1>(u_bdr)[0], *std::get<1>(bdr)[0]) + - 1e1 * torch::mse_loss(*std::get<2>(u_bdr)[0], *std::get<2>(bdr)[0]) + - 1e1 * torch::mse_loss(*std::get<3>(u_bdr)[0], *std::get<3>(bdr)[0]); - } -}; - -int main() { - iganet::init(); - iganet::verbose(std::cout); - - nlohmann::json json; - json["res0"] = 50; - json["res1"] = 50; - - using namespace iganet::literals; - using optimizer_t = torch::optim::LBFGS; - using real_t = double; - - using geometry_t = iganet::S>; - using variable_t = iganet::S>; - - poisson - net( // Number of neurons per layers - {120, 120}, - // Activation functions - {{iganet::activation::sigmoid}, - {iganet::activation::sigmoid}, - {iganet::activation::none}}, - // Number of B-spline coefficients of the geometry, just [0,1] x [0,1] - std::tuple(iganet::utils::to_array(2_i64, 2_i64)), - // Number of B-spline coefficients of the variable - std::tuple(iganet::utils::to_array(10_i64, 10_i64))); - - // Impose the negative of the second derivative of sin(M_PI*x) * - // sin(M_PI*y) as right-hand side vector (manufactured solution) - net.f().transform([](const std::array xi) { - return std::array{-2.0 * M_PI * M_PI * sin(M_PI * xi[0]) * - sin(M_PI * xi[1])}; - }); - - // Impose reference solution - net.ref().transform([](const std::array xi) { - return std::array{sin(M_PI * xi[0]) * sin(M_PI * xi[1])}; - }); - - // Impose boundary conditions - net.ref().boundary().template side<1>().transform( - [](const std::array xi) { - return std::array{0.0}; - }); - - net.ref().boundary().template side<2>().transform( - [](const std::array xi) { - return std::array{0.0}; - }); - - net.ref().boundary().template side<3>().transform( - [](const std::array xi) { - return std::array{0.0}; - }); - - net.ref().boundary().template side<4>().transform( - [](const std::array xi) { - return std::array{0.0}; - }); - - // Set maximum number of epochs - net.options().max_epoch(200); - - // Set tolerance for the loss functions - net.options().min_loss(1e-8); - - // Start time measurement - auto t1 = std::chrono::high_resolution_clock::now(); - - // Train network - net.train(); - - // Stop time measurement - auto t2 = std::chrono::high_resolution_clock::now(); - iganet::Log(iganet::log::info) - << "Training took " - << std::chrono::duration_cast>(t2 - t1) - .count() - << " seconds\n"; - -#ifdef IGANET_WITH_MATPLOT - // Plot the solution - net.G().plot(net.u(), net.collPts().first, json)->show(); - - // Plot the difference between the exact and predicted solutions - net.G().plot(net.ref().abs_diff(net.u()), net.collPts().first, json)->show(); -#endif - - return 0; -} +/** + @file examples/iganet_poisson.cxx + + @brief Demonstration of IgANet Poisson solver + + This example demonstrates how to implement a simple IgANet for + learning the Poisson equation with (non-)homogeneous Dirichlet + boundary conditions on a square geometry. + + @author Matthias Moller + + @copyright This file is part of the IgANet project + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#include +#include + +using namespace iganet::literals; + +/// @brief Specialization of the abstract IgANet class for Poisson's equation +template +class poisson : public iganet::IgANet, + public iganet::IgANetCustomizable { + +private: + /// @brief Type of the base class + using Base = iganet::IgANet; + + /// @brief Collocation points + typename Base::variable_collPts_type collPts_; + + /// @brief Reference solution + Variable ref_; + + /// @brief Type of the customizable class + using Customizable = iganet::IgANetCustomizable; + + /// @brief Knot indices of variables + typename Customizable::variable_interior_knot_indices_type var_knot_indices_; + + /// @brief Coefficient indices of variables + typename Customizable::variable_interior_coeff_indices_type + var_coeff_indices_; + + /// @brief Knot indices of the geometry map + typename Customizable::geometryMap_interior_knot_indices_type G_knot_indices_; + + /// @brief Coefficient indices of the geometry map + typename Customizable::geometryMap_interior_coeff_indices_type + G_coeff_indices_; + +public: + /// @brief Constructor + template + poisson(std::vector &&layers, + std::vector> &&activations, Args &&...args) + : Base(std::forward>(layers), + std::forward>>(activations), + std::forward(args)...), + ref_(iganet::utils::to_array(10_i64, 10_i64)) {} + + /// @brief Returns a constant reference to the collocation points + auto const &collPts() const { return collPts_; } + + /// @brief Returns a constant reference to the reference solution + auto const &ref() const { return ref_; } + + /// @brief Returns a non-constant reference to the reference solution + auto &ref() { return ref_; } + + /// @brief Initializes the epoch + /// + /// @param[in] epoch Epoch number + bool epoch(int64_t epoch) override { + // In the very first epoch we need to generate the sampling points + // for the inputs and the sampling points in the function space of + // the variables since otherwise the respective tensors would be + // empty. In all further epochs no updates are needed since we do + // not change the inputs nor the variable function space. + if (epoch == 0) { + Base::inputs(epoch); + collPts_ = Base::variable_collPts(iganet::collPts::greville_ref1); + + var_knot_indices_ = + Base::f_.template find_knot_indices( + collPts_.first); + var_coeff_indices_ = + Base::f_.template find_coeff_indices( + var_knot_indices_); + + G_knot_indices_ = + Base::G_.template find_knot_indices( + collPts_.first); + G_coeff_indices_ = + Base::G_.template find_coeff_indices( + G_knot_indices_); + + return true; + } else + return false; + } + + /// @brief Computes the loss function + /// + /// @param[in] outputs Output of the network + /// + /// @param[in] epoch Epoch number + torch::Tensor loss(const torch::Tensor &outputs, int64_t epoch) override { + + // Cast the network output (a raw tensor) into the proper + // function-space format, i.e. B-spline objects for the interior + // and boundary parts that can be evaluated. + Base::u_.from_tensor(outputs); + + // Evaluate the Laplacian operator + auto u_ilapl = + Base::u_.ilapl(Base::G_, collPts_.first, var_knot_indices_, + var_coeff_indices_, G_knot_indices_, G_coeff_indices_); + + auto f = + Base::f_.eval(collPts_.first, var_knot_indices_, var_coeff_indices_); + + auto u_bdr = Base::u_.template eval( + collPts_.second); + + auto bdr = + ref_.template eval(collPts_.second); + + // Evaluate the loss function + return torch::mse_loss(*u_ilapl[0], *f[0]) + + 1e1 * torch::mse_loss(*std::get<0>(u_bdr)[0], *std::get<0>(bdr)[0]) + + 1e1 * torch::mse_loss(*std::get<1>(u_bdr)[0], *std::get<1>(bdr)[0]) + + 1e1 * torch::mse_loss(*std::get<2>(u_bdr)[0], *std::get<2>(bdr)[0]) + + 1e1 * torch::mse_loss(*std::get<3>(u_bdr)[0], *std::get<3>(bdr)[0]); + } +}; + +int main() { + iganet::init(); + iganet::verbose(std::cout); + + nlohmann::json json; + json["res0"] = 50; + json["res1"] = 50; + + using namespace iganet::literals; + using optimizer_t = torch::optim::LBFGS; + using real_t = double; + + using geometry_t = iganet::S>; + using variable_t = iganet::S>; + + poisson + net( // Number of neurons per layers + {120, 120}, + // Activation functions + {{iganet::activation::sigmoid}, + {iganet::activation::sigmoid}, + {iganet::activation::none}}, + // Number of B-spline coefficients of the geometry, just [0,1] x [0,1] + std::tuple(iganet::utils::to_array(2_i64, 2_i64)), + // Number of B-spline coefficients of the variable + std::tuple(iganet::utils::to_array(10_i64, 10_i64))); + + // Impose the negative of the second derivative of sin(M_PI*x) * + // sin(M_PI*y) as right-hand side vector (manufactured solution) + net.f().transform([](const std::array xi) { + return std::array{-2.0 * M_PI * M_PI * sin(M_PI * xi[0]) * + sin(M_PI * xi[1])}; + }); + + // Impose reference solution + net.ref().transform([](const std::array xi) { + return std::array{sin(M_PI * xi[0]) * sin(M_PI * xi[1])}; + }); + + // Impose boundary conditions + net.ref().boundary().template side<1>().transform( + [](const std::array xi) { + return std::array{0.0}; + }); + + net.ref().boundary().template side<2>().transform( + [](const std::array xi) { + return std::array{0.0}; + }); + + net.ref().boundary().template side<3>().transform( + [](const std::array xi) { + return std::array{0.0}; + }); + + net.ref().boundary().template side<4>().transform( + [](const std::array xi) { + return std::array{0.0}; + }); + + // Set maximum number of epochs + net.options().max_epoch(200); + + // Set tolerance for the loss functions + net.options().min_loss(1e-8); + + // Start time measurement + auto t1 = std::chrono::high_resolution_clock::now(); + + // Train network + net.train(); + + // Stop time measurement + auto t2 = std::chrono::high_resolution_clock::now(); + iganet::Log(iganet::log::info) + << "Training took " + << std::chrono::duration_cast>(t2 - t1) + .count() + << " seconds\n"; + +#ifdef IGANET_WITH_MATPLOT + // Plot the solution + net.G().plot(net.u(), net.collPts().first, json)->show(); + + // Plot the difference between the exact and predicted solutions + net.G().plot(net.ref().abs_diff(net.u()), net.collPts().first, json)->show(); +#endif + + return 0; +} diff --git a/examples/iganet_stokes.cxx b/examples/iganet_stokes.cxx index 827d33c1..5d718c1f 100644 --- a/examples/iganet_stokes.cxx +++ b/examples/iganet_stokes.cxx @@ -1,236 +1,236 @@ -/** - @file examples/iganet_stokes.cxx - - @brief Demonstration of IgANet Stokes solver - - This example demonstrates how to implement a simple IgANet for - learning the Stokes equation with (non-)homogeneous Dirichlet - boundary conditions on a square geometry. - - @author Matthias Moller - - @copyright This file is part of the IgANet project - - This Source Code Form is subject to the terms of the Mozilla Public - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at http://mozilla.org/MPL/2.0/. -*/ - -#include -#include - -using namespace iganet::literals; - -/// @brief Specialization of the abstract IgANet class for Stokes's equation -template -class stokes : public iganet::IgANet, - public iganet::IgANetCustomizable { - -private: - /// @brief Type of the base class - using Base = iganet::IgANet; - - /// @brief Collocation points - typename Base::variable_collPts_type collPts_; - - /// @brief Reference solution - Variable ref_; - - /// @brief Type of the customizable class - using Customizable = iganet::IgANetCustomizable; - - /// @brief Knot indices of variables - typename Customizable::variable_interior_knot_indices_type var_knot_indices_; - - /// @brief Coefficient indices of variables - typename Customizable::variable_interior_coeff_indices_type - var_coeff_indices_; - - /// @brief Knot indices of the geometry map - typename Customizable::variable_interior_knot_indices_type G_knot_indices_; - - /// @brief Coefficient indices of the geometry map - typename Customizable::variable_interior_coeff_indices_type G_coeff_indices_; - -public: - /// @brief Constructor - template - stokes(std::vector &&layers, - std::vector> &&activations, Args &&...args) - : Base(std::forward>(layers), - std::forward>>(activations), - std::forward(args)...), - ref_(iganet::utils::to_array(10_i64, 10_i64)) {} - - /// @brief Returns a constant reference to the collocation points - auto const &collPts() const { return collPts_; } - - /// @brief Returns a constant reference to the reference solution - auto const &ref() const { return ref_; } - - /// @brief Returns a non-constant reference to the reference solution - auto &ref() { return ref_; } - - /// @brief Initializes the epoch - /// - /// @param[in] epoch Epoch number - bool epoch(int64_t epoch) override { - // In the very first epoch we need to generate the sampling points - // for the inputs and the sampling points in the function space of - // the variables since otherwise the respective tensors would be - // empty. In all further epochs no updates are needed since we do - // not change the inputs nor the variable function space. - if (epoch == 0) { - Base::inputs(epoch); - collPts_ = Base::variable_collPts(iganet::collPts::greville_ref1); - - var_knot_indices_ = - Base::u_.template find_knot_indices( - collPts_.first); - var_coeff_indices_ = - Base::u_.template find_coeff_indices( - var_knot_indices_); - - G_knot_indices_ = - Base::G_.template find_knot_indices( - collPts_.first); - G_coeff_indices_ = - Base::G_.template find_coeff_indices( - G_knot_indices_); - - return true; - } else - return false; - } - - /// @brief Computes the loss function - /// - /// @param[in] outputs Output of the network - /// - /// @param[in] epoch Epoch number - torch::Tensor loss(const torch::Tensor &outputs, int64_t epoch) override { - - // Cast the network output (a raw tensor) into the proper - // function-space format, i.e. B-spline objects for the interior - // and boundary parts that can be evaluated. - Base::u_.from_tensor(outputs); - - // Evaluate - auto u_ilapl = Base::u_.template space<0, 1>().div(std::get<0>( - collPts_.first)); //, var_knot_indices_, var_coeff_indices_); - // auto u_ilapl = - // Base::u_.ilapl(Base::G_, collPts_.first, var_knot_indices_, - // var_coeff_indices_, G_knot_indices_, - // G_coeff_indices_); - - exit(0); - - auto u_bdr = Base::u_.template eval( - collPts_.second); - - auto bdr = - ref_.template eval(collPts_.second); - - // Define the MSE loss function with zero target - auto mse_loss = [](const torch::Tensor &input) { - return torch::mean(torch::square(input)); - }; - - // Evaluate the loss function - return outputs; // mse_loss(*std::get<0>(u_ilapl)[0]); - } -}; - -int main() { - iganet::init(); - iganet::verbose(std::cout); - - nlohmann::json json; - json["res0"] = 50; - json["res1"] = 50; - - using namespace iganet::literals; - using optimizer_t = torch::optim::LBFGS; - using real_t = double; - - using geometry_t = iganet::S>; - using variable_t = iganet::RT>; - - stokes - net( // Number of neurons per layers - {120, 120}, - // Activation functions - {{iganet::activation::sigmoid}, - {iganet::activation::sigmoid}, - {iganet::activation::none}}, - // Number of B-spline coefficients of the geometry, just [0,1] x [0,1] - std::tuple(iganet::utils::to_array(4_i64, 25_i64)), - // Number of B-spline coefficients of the variable - std::tuple(iganet::utils::to_array(10_i64, 10_i64))); - - // // Impose the negative of the second derivative of sin(M_PI*x) * - // // sin(M_PI*y) as right-hand side vector (manufactured solution) - // net.f().transform([](const std::array xi) { - // return std::array{-2.0 * M_PI * M_PI * sin(M_PI * xi[0]) * - // sin(M_PI * xi[1])}; - // }); - - // // Impose reference solution - // net.ref().transform([](const std::array xi) { - // return std::array{sin(M_PI * xi[0]) * sin(M_PI * xi[1])}; - // }); - - // // Impose boundary conditions - // net.ref().boundary().template side<1>().transform( - // [](const std::array xi) { - // return std::array{0.0}; - // }); - - // net.ref().boundary().template side<2>().transform( - // [](const std::array xi) { - // return std::array{0.0}; - // }); - - // net.ref().boundary().template side<3>().transform( - // [](const std::array xi) { - // return std::array{0.0}; - // }); - - // net.ref().boundary().template side<4>().transform( - // [](const std::array xi) { - // return std::array{0.0}; - // }); - - // Set maximum number of epochs - net.options().max_epoch(200); - - // Set tolerance for the loss functions - net.options().min_loss(1e-8); - - // Start time measurement - auto t1 = std::chrono::high_resolution_clock::now(); - - // Train network - net.train(); - - // Stop time measurement - auto t2 = std::chrono::high_resolution_clock::now(); - iganet::Log(iganet::log::info) - << "Training took " - << std::chrono::duration_cast>(t2 - t1) - .count() - << " seconds\n"; - -#ifdef IGANET_WITH_MATPLOT - // Plot the solution - // net.G().plot(net.u(), net.collPts().first, json)->show(); - - // Plot the difference between the exact and predicted solutions - // net.G().plot(net.ref().abs_diff(net.u()), net.collPts().first, - // json)->show(); -#endif - - return 0; -} +/** + @file examples/iganet_stokes.cxx + + @brief Demonstration of IgANet Stokes solver + + This example demonstrates how to implement a simple IgANet for + learning the Stokes equation with (non-)homogeneous Dirichlet + boundary conditions on a square geometry. + + @author Matthias Moller + + @copyright This file is part of the IgANet project + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#include +#include + +using namespace iganet::literals; + +/// @brief Specialization of the abstract IgANet class for Stokes's equation +template +class stokes : public iganet::IgANet, + public iganet::IgANetCustomizable { + +private: + /// @brief Type of the base class + using Base = iganet::IgANet; + + /// @brief Collocation points + typename Base::variable_collPts_type collPts_; + + /// @brief Reference solution + Variable ref_; + + /// @brief Type of the customizable class + using Customizable = iganet::IgANetCustomizable; + + /// @brief Knot indices of variables + typename Customizable::variable_interior_knot_indices_type var_knot_indices_; + + /// @brief Coefficient indices of variables + typename Customizable::variable_interior_coeff_indices_type + var_coeff_indices_; + + /// @brief Knot indices of the geometry map + typename Customizable::variable_interior_knot_indices_type G_knot_indices_; + + /// @brief Coefficient indices of the geometry map + typename Customizable::variable_interior_coeff_indices_type G_coeff_indices_; + +public: + /// @brief Constructor + template + stokes(std::vector &&layers, + std::vector> &&activations, Args &&...args) + : Base(std::forward>(layers), + std::forward>>(activations), + std::forward(args)...), + ref_(iganet::utils::to_array(10_i64, 10_i64)) {} + + /// @brief Returns a constant reference to the collocation points + auto const &collPts() const { return collPts_; } + + /// @brief Returns a constant reference to the reference solution + auto const &ref() const { return ref_; } + + /// @brief Returns a non-constant reference to the reference solution + auto &ref() { return ref_; } + + /// @brief Initializes the epoch + /// + /// @param[in] epoch Epoch number + bool epoch(int64_t epoch) override { + // In the very first epoch we need to generate the sampling points + // for the inputs and the sampling points in the function space of + // the variables since otherwise the respective tensors would be + // empty. In all further epochs no updates are needed since we do + // not change the inputs nor the variable function space. + if (epoch == 0) { + Base::inputs(epoch); + collPts_ = Base::variable_collPts(iganet::collPts::greville_ref1); + + var_knot_indices_ = + Base::u_.template find_knot_indices( + collPts_.first); + var_coeff_indices_ = + Base::u_.template find_coeff_indices( + var_knot_indices_); + + G_knot_indices_ = + Base::G_.template find_knot_indices( + collPts_.first); + G_coeff_indices_ = + Base::G_.template find_coeff_indices( + G_knot_indices_); + + return true; + } else + return false; + } + + /// @brief Computes the loss function + /// + /// @param[in] outputs Output of the network + /// + /// @param[in] epoch Epoch number + torch::Tensor loss(const torch::Tensor &outputs, int64_t epoch) override { + + // Cast the network output (a raw tensor) into the proper + // function-space format, i.e. B-spline objects for the interior + // and boundary parts that can be evaluated. + Base::u_.from_tensor(outputs); + + // Evaluate + auto u_ilapl = Base::u_.template space<0, 1>().div(std::get<0>( + collPts_.first)); //, var_knot_indices_, var_coeff_indices_); + // auto u_ilapl = + // Base::u_.ilapl(Base::G_, collPts_.first, var_knot_indices_, + // var_coeff_indices_, G_knot_indices_, + // G_coeff_indices_); + + exit(0); + + auto u_bdr = Base::u_.template eval( + collPts_.second); + + auto bdr = + ref_.template eval(collPts_.second); + + // Define the MSE loss function with zero target + auto mse_loss = [](const torch::Tensor &input) { + return torch::mean(torch::square(input)); + }; + + // Evaluate the loss function + return outputs; // mse_loss(*std::get<0>(u_ilapl)[0]); + } +}; + +int main() { + iganet::init(); + iganet::verbose(std::cout); + + nlohmann::json json; + json["res0"] = 50; + json["res1"] = 50; + + using namespace iganet::literals; + using optimizer_t = torch::optim::LBFGS; + using real_t = double; + + using geometry_t = iganet::S>; + using variable_t = iganet::RT>; + + stokes + net( // Number of neurons per layers + {120, 120}, + // Activation functions + {{iganet::activation::sigmoid}, + {iganet::activation::sigmoid}, + {iganet::activation::none}}, + // Number of B-spline coefficients of the geometry, just [0,1] x [0,1] + std::tuple(iganet::utils::to_array(4_i64, 25_i64)), + // Number of B-spline coefficients of the variable + std::tuple(iganet::utils::to_array(10_i64, 10_i64))); + + // // Impose the negative of the second derivative of sin(M_PI*x) * + // // sin(M_PI*y) as right-hand side vector (manufactured solution) + // net.f().transform([](const std::array xi) { + // return std::array{-2.0 * M_PI * M_PI * sin(M_PI * xi[0]) * + // sin(M_PI * xi[1])}; + // }); + + // // Impose reference solution + // net.ref().transform([](const std::array xi) { + // return std::array{sin(M_PI * xi[0]) * sin(M_PI * xi[1])}; + // }); + + // // Impose boundary conditions + // net.ref().boundary().template side<1>().transform( + // [](const std::array xi) { + // return std::array{0.0}; + // }); + + // net.ref().boundary().template side<2>().transform( + // [](const std::array xi) { + // return std::array{0.0}; + // }); + + // net.ref().boundary().template side<3>().transform( + // [](const std::array xi) { + // return std::array{0.0}; + // }); + + // net.ref().boundary().template side<4>().transform( + // [](const std::array xi) { + // return std::array{0.0}; + // }); + + // Set maximum number of epochs + net.options().max_epoch(200); + + // Set tolerance for the loss functions + net.options().min_loss(1e-8); + + // Start time measurement + auto t1 = std::chrono::high_resolution_clock::now(); + + // Train network + net.train(); + + // Stop time measurement + auto t2 = std::chrono::high_resolution_clock::now(); + iganet::Log(iganet::log::info) + << "Training took " + << std::chrono::duration_cast>(t2 - t1) + .count() + << " seconds\n"; + +#ifdef IGANET_WITH_MATPLOT + // Plot the solution + // net.G().plot(net.u(), net.collPts().first, json)->show(); + + // Plot the difference between the exact and predicted solutions + // net.G().plot(net.ref().abs_diff(net.u()), net.collPts().first, + // json)->show(); +#endif + + return 0; +} diff --git a/examples/nonuniform_bsplines.cxx b/examples/nonuniform_bsplines.cxx index 0d0c56f1..97964179 100644 --- a/examples/nonuniform_bsplines.cxx +++ b/examples/nonuniform_bsplines.cxx @@ -1,217 +1,217 @@ -/** - @file examples/nonuniform_bsplines.cxx - - @brief Demonstration of the non-uniform B-spline class - - @author Matthias Moller - - @copyright This file is part of the IgANet project - - This Source Code Form is subject to the terms of the Mozilla Public - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at http://mozilla.org/MPL/2.0/. -*/ - -#include -#include - -int main() { - iganet::init(); - iganet::verbose(std::cout); - using real_t = double; - - nlohmann::json json; - json["res0"] = 50; - json["res1"] = 50; - - { - // Univariate non-uniform B-spline of degree 2 with 6 control points in R^1 - iganet::NonUniformBSpline bspline( - {{{0.0, 0.0, 0.0, 0.25, 0.5, 0.75, 1.0, 1.0, 1.0}}}); - iganet::NonUniformBSpline color( - {{{0.0, 0.0, 0.0, 0.25, 0.5, 0.75, 1.0, 1.0, 1.0}}}); - - // Print information - iganet::Log(iganet::log::info) << bspline << std::endl; - - // Map control points to phyiscal coordinates - bspline.transform([](const std::array xi) { - return std::array{xi[0] * xi[0]}; - }); - - // Map colors - color.transform([](const std::array xi) { - return std::array{xi[0]}; - }); - - // Evaluate B-spline at xi=0, xi=0.5, and xi=1 - iganet::Log(iganet::log::info) - << bspline.eval(iganet::utils::to_tensorArray({0.0, 0.5, 1.0})) - << std::endl; - -#ifdef IGANET_WITH_MATPLOT - // Plot B-spline - bspline.plot(json); - bspline.plot(color, json); -#endif - - // Export B-spline to XML - bspline.to_xml().print(iganet::Log(iganet::log::info)); - } - - { - // Univariate non-uniform B-spline of degree 2 with 6 control points in R^2 - iganet::NonUniformBSpline bspline( - {{{0.0, 0.0, 0.0, 0.25, 0.5, 0.75, 1.0, 1.0, 1.0}}}); - iganet::NonUniformBSpline color( - {{{0.0, 0.0, 0.0, 0.25, 0.5, 0.75, 1.0, 1.0, 1.0}}}); - - // Print information - iganet::Log(iganet::log::info) << bspline << std::endl; - - // Map control points to phyiscal coordinates - bspline.transform([](const std::array xi) { - return std::array{xi[0] * xi[0], - sin(static_cast(M_PI) * xi[0])}; - }); - - // Map colors - color.transform([](const std::array xi) { - return std::array{xi[0]}; - }); - - // Evaluate B-spline at xi=0, xi=0.5, and xi=1 - iganet::Log(iganet::log::info) - << bspline.eval(iganet::utils::to_tensorArray({0.0, 0.5, 1.0})) - << std::endl; - -#ifdef IGANET_WITH_MATPLOT - // Plot B-spline - bspline.plot(json); - bspline.plot(color, json); -#endif - - // Export B-spline to XML - bspline.to_xml().print(iganet::Log(iganet::log::info)); - } - - { - // Univariate non-uniform B-spline of degree 2 with 6 control points in R^3 - iganet::NonUniformBSpline bspline( - {{{0.0, 0.0, 0.0, 0.25, 0.5, 0.75, 1.0, 1.0, 1.0}}}); - iganet::NonUniformBSpline color( - {{{0.0, 0.0, 0.0, 0.25, 0.5, 0.75, 1.0, 1.0, 1.0}}}); - - // Print information - iganet::Log(iganet::log::info) << bspline << std::endl; - - // Map control points to phyiscal coordinates - bspline.transform([](const std::array xi) { - return std::array{ - xi[0] * xi[0], sin(static_cast(M_PI) * xi[0]), xi[0]}; - }); - - // Map colors - color.transform([](const std::array xi) { - return std::array{xi[0]}; - }); - - // Evaluate B-spline at xi=0, xi=0.5, and xi=1 - iganet::Log(iganet::log::info) - << bspline.eval(iganet::utils::to_tensorArray({0.0, 0.5, 1.0})) - << std::endl; - -#ifdef IGANET_WITH_MATPLOT - // Plot B-spline - bspline.plot(json); - bspline.plot(color, json); -#endif - - // Export B-spline to XML - bspline.to_xml().print(iganet::Log(iganet::log::info)); - } - - { - // Bivariate non-uniform B-spline of degree 3 in xi-direction and 4 - // in eta-direction with 5 x 6 control points in R^2 - iganet::NonUniformBSpline bspline( - {{{0.0, 0.0, 0.0, 0.0, 0.5, 1.0, 1.0, 1.0, 1.0}, - {0.0, 0.0, 0.0, 0.0, 0.0, 0.5, 1.0, 1.0, 1.0, 1.0, 1.0}}}); - iganet::NonUniformBSpline color( - {{{0.0, 0.0, 0.0, 0.0, 0.5, 1.0, 1.0, 1.0, 1.0}, - {0.0, 0.0, 0.0, 0.0, 0.0, 0.5, 1.0, 1.0, 1.0, 1.0, 1.0}}}); - - // Print information - iganet::Log(iganet::log::info) << bspline << std::endl; - - // Map control points to phyiscal coordinates - bspline.transform([](const std::array xi) { - return std::array{ - (xi[0] + 1) * cos(static_cast(M_PI) * xi[1]), - (xi[0] + 1) * sin(static_cast(M_PI) * xi[1])}; - }); - - // Map colors - color.transform([](const std::array xi) { - return std::array{xi[0] * xi[1]}; - }); - - // Evaluate B-spline at xi=0, xi=0.5, and xi=1 - iganet::Log(iganet::log::info) - << bspline.eval( - iganet::utils::to_tensorArray({0.0, 0.5, 1.0}, {0.0, 0.5, 0.5})) - << std::endl; - -#ifdef IGANET_WITH_MATPLOT - // Plot B-spline - bspline.plot(json); - bspline.plot(color, json); -#endif - - // Export B-spline to XML - bspline.to_xml().print(iganet::Log(iganet::log::info)); - } - - { - // Bivariate non-uniform B-spline of degree 3 in xi-direction and 4 - // in eta-direction with 5 x 6 control points in R^3 - iganet::NonUniformBSpline bspline( - {{{0.0, 0.0, 0.0, 0.0, 0.5, 1.0, 1.0, 1.0, 1.0}, - {0.0, 0.0, 0.0, 0.0, 0.0, 0.5, 1.0, 1.0, 1.0, 1.0, 1.0}}}); - iganet::NonUniformBSpline color( - {{{0.0, 0.0, 0.0, 0.0, 0.5, 1.0, 1.0, 1.0, 1.0}, - {0.0, 0.0, 0.0, 0.0, 0.0, 0.5, 1.0, 1.0, 1.0, 1.0, 1.0}}}); - - // Print information - iganet::Log(iganet::log::info) << bspline << std::endl; - - // Map control points to phyiscal coordinates - bspline.transform([](const std::array xi) { - return std::array{ - (xi[0] + 1) * cos(static_cast(M_PI) * xi[1]), - (xi[0] + 1) * sin(static_cast(M_PI) * xi[1]), xi[0]}; - }); - - // Map colors - color.transform([](const std::array xi) { - return std::array{xi[0] * xi[1]}; - }); - - // Evaluate B-spline at (xi=0,eta=0), (xi=0.5,eta=0.5), and (xi=1,eta=0.5) - iganet::Log(iganet::log::info) - << bspline.eval( - iganet::utils::to_tensorArray({0.0, 0.5, 1.0}, {0.0, 0.5, 0.5})) - << std::endl; - -#ifdef IGANET_WITH_MATPLOT - // Plot B-spline - bspline.plot(json); - bspline.plot(color, json); -#endif - - // Export B-spline to XML - bspline.to_xml().print(iganet::Log(iganet::log::info)); - } - - return 0; -} +/** + @file examples/nonuniform_bsplines.cxx + + @brief Demonstration of the non-uniform B-spline class + + @author Matthias Moller + + @copyright This file is part of the IgANet project + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#include +#include + +int main() { + iganet::init(); + iganet::verbose(std::cout); + using real_t = double; + + nlohmann::json json; + json["res0"] = 50; + json["res1"] = 50; + + { + // Univariate non-uniform B-spline of degree 2 with 6 control points in R^1 + iganet::NonUniformBSpline bspline( + {{{0.0, 0.0, 0.0, 0.25, 0.5, 0.75, 1.0, 1.0, 1.0}}}); + iganet::NonUniformBSpline color( + {{{0.0, 0.0, 0.0, 0.25, 0.5, 0.75, 1.0, 1.0, 1.0}}}); + + // Print information + iganet::Log(iganet::log::info) << bspline << std::endl; + + // Map control points to phyiscal coordinates + bspline.transform([](const std::array xi) { + return std::array{xi[0] * xi[0]}; + }); + + // Map colors + color.transform([](const std::array xi) { + return std::array{xi[0]}; + }); + + // Evaluate B-spline at xi=0, xi=0.5, and xi=1 + iganet::Log(iganet::log::info) + << bspline.eval(iganet::utils::to_tensorArray({0.0, 0.5, 1.0})) + << std::endl; + +#ifdef IGANET_WITH_MATPLOT + // Plot B-spline + bspline.plot(json); + bspline.plot(color, json); +#endif + + // Export B-spline to XML + bspline.to_xml().print(iganet::Log(iganet::log::info)); + } + + { + // Univariate non-uniform B-spline of degree 2 with 6 control points in R^2 + iganet::NonUniformBSpline bspline( + {{{0.0, 0.0, 0.0, 0.25, 0.5, 0.75, 1.0, 1.0, 1.0}}}); + iganet::NonUniformBSpline color( + {{{0.0, 0.0, 0.0, 0.25, 0.5, 0.75, 1.0, 1.0, 1.0}}}); + + // Print information + iganet::Log(iganet::log::info) << bspline << std::endl; + + // Map control points to phyiscal coordinates + bspline.transform([](const std::array xi) { + return std::array{xi[0] * xi[0], + sin(static_cast(M_PI) * xi[0])}; + }); + + // Map colors + color.transform([](const std::array xi) { + return std::array{xi[0]}; + }); + + // Evaluate B-spline at xi=0, xi=0.5, and xi=1 + iganet::Log(iganet::log::info) + << bspline.eval(iganet::utils::to_tensorArray({0.0, 0.5, 1.0})) + << std::endl; + +#ifdef IGANET_WITH_MATPLOT + // Plot B-spline + bspline.plot(json); + bspline.plot(color, json); +#endif + + // Export B-spline to XML + bspline.to_xml().print(iganet::Log(iganet::log::info)); + } + + { + // Univariate non-uniform B-spline of degree 2 with 6 control points in R^3 + iganet::NonUniformBSpline bspline( + {{{0.0, 0.0, 0.0, 0.25, 0.5, 0.75, 1.0, 1.0, 1.0}}}); + iganet::NonUniformBSpline color( + {{{0.0, 0.0, 0.0, 0.25, 0.5, 0.75, 1.0, 1.0, 1.0}}}); + + // Print information + iganet::Log(iganet::log::info) << bspline << std::endl; + + // Map control points to phyiscal coordinates + bspline.transform([](const std::array xi) { + return std::array{ + xi[0] * xi[0], sin(static_cast(M_PI) * xi[0]), xi[0]}; + }); + + // Map colors + color.transform([](const std::array xi) { + return std::array{xi[0]}; + }); + + // Evaluate B-spline at xi=0, xi=0.5, and xi=1 + iganet::Log(iganet::log::info) + << bspline.eval(iganet::utils::to_tensorArray({0.0, 0.5, 1.0})) + << std::endl; + +#ifdef IGANET_WITH_MATPLOT + // Plot B-spline + bspline.plot(json); + bspline.plot(color, json); +#endif + + // Export B-spline to XML + bspline.to_xml().print(iganet::Log(iganet::log::info)); + } + + { + // Bivariate non-uniform B-spline of degree 3 in xi-direction and 4 + // in eta-direction with 5 x 6 control points in R^2 + iganet::NonUniformBSpline bspline( + {{{0.0, 0.0, 0.0, 0.0, 0.5, 1.0, 1.0, 1.0, 1.0}, + {0.0, 0.0, 0.0, 0.0, 0.0, 0.5, 1.0, 1.0, 1.0, 1.0, 1.0}}}); + iganet::NonUniformBSpline color( + {{{0.0, 0.0, 0.0, 0.0, 0.5, 1.0, 1.0, 1.0, 1.0}, + {0.0, 0.0, 0.0, 0.0, 0.0, 0.5, 1.0, 1.0, 1.0, 1.0, 1.0}}}); + + // Print information + iganet::Log(iganet::log::info) << bspline << std::endl; + + // Map control points to phyiscal coordinates + bspline.transform([](const std::array xi) { + return std::array{ + (xi[0] + 1) * cos(static_cast(M_PI) * xi[1]), + (xi[0] + 1) * sin(static_cast(M_PI) * xi[1])}; + }); + + // Map colors + color.transform([](const std::array xi) { + return std::array{xi[0] * xi[1]}; + }); + + // Evaluate B-spline at xi=0, xi=0.5, and xi=1 + iganet::Log(iganet::log::info) + << bspline.eval( + iganet::utils::to_tensorArray({0.0, 0.5, 1.0}, {0.0, 0.5, 0.5})) + << std::endl; + +#ifdef IGANET_WITH_MATPLOT + // Plot B-spline + bspline.plot(json); + bspline.plot(color, json); +#endif + + // Export B-spline to XML + bspline.to_xml().print(iganet::Log(iganet::log::info)); + } + + { + // Bivariate non-uniform B-spline of degree 3 in xi-direction and 4 + // in eta-direction with 5 x 6 control points in R^3 + iganet::NonUniformBSpline bspline( + {{{0.0, 0.0, 0.0, 0.0, 0.5, 1.0, 1.0, 1.0, 1.0}, + {0.0, 0.0, 0.0, 0.0, 0.0, 0.5, 1.0, 1.0, 1.0, 1.0, 1.0}}}); + iganet::NonUniformBSpline color( + {{{0.0, 0.0, 0.0, 0.0, 0.5, 1.0, 1.0, 1.0, 1.0}, + {0.0, 0.0, 0.0, 0.0, 0.0, 0.5, 1.0, 1.0, 1.0, 1.0, 1.0}}}); + + // Print information + iganet::Log(iganet::log::info) << bspline << std::endl; + + // Map control points to phyiscal coordinates + bspline.transform([](const std::array xi) { + return std::array{ + (xi[0] + 1) * cos(static_cast(M_PI) * xi[1]), + (xi[0] + 1) * sin(static_cast(M_PI) * xi[1]), xi[0]}; + }); + + // Map colors + color.transform([](const std::array xi) { + return std::array{xi[0] * xi[1]}; + }); + + // Evaluate B-spline at (xi=0,eta=0), (xi=0.5,eta=0.5), and (xi=1,eta=0.5) + iganet::Log(iganet::log::info) + << bspline.eval( + iganet::utils::to_tensorArray({0.0, 0.5, 1.0}, {0.0, 0.5, 0.5})) + << std::endl; + +#ifdef IGANET_WITH_MATPLOT + // Plot B-spline + bspline.plot(json); + bspline.plot(color, json); +#endif + + // Export B-spline to XML + bspline.to_xml().print(iganet::Log(iganet::log::info)); + } + + return 0; +} diff --git a/examples/uniform_bsplines.cxx b/examples/uniform_bsplines.cxx index ea56f44b..200aefba 100644 --- a/examples/uniform_bsplines.cxx +++ b/examples/uniform_bsplines.cxx @@ -1,205 +1,205 @@ -/** - @file examples/uniform_bsplines.cxx - - @brief Demonstration of the uniform B-spline class - - @author Matthias Moller - - @copyright This file is part of the IgANet project - - This Source Code Form is subject to the terms of the Mozilla Public - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at http://mozilla.org/MPL/2.0/. -*/ - -#include -#include - -int main() { - iganet::init(); - iganet::verbose(std::cout); - using real_t = double; - - nlohmann::json json; - json["res0"] = 50; - json["res1"] = 50; - json["cnet"] = true; - - { - // Univariate uniform B-spline of degree 2 with 6 control points in R^1 - iganet::UniformBSpline bspline({6}), color({6}); - - // Print information - iganet::Log(iganet::log::info) << bspline << std::endl; - - // Map control points to phyiscal coordinates - bspline.transform([](const std::array xi) { - return std::array{xi[0] * xi[0]}; - }); - - // Map colors - color.transform([](const std::array xi) { - return std::array{xi[0]}; - }); - - // Evaluate B-spline at xi=0, xi=0.5, and xi=1 - iganet::Log(iganet::log::info) - << bspline.eval(iganet::utils::to_tensorArray({0.0, 0.5, 1.0})) - << std::endl; - -#ifdef IGANET_WITH_MATPLOT - // Plot B-spline - bspline.plot(json)->show(); - bspline.plot(color, json)->show(); -#endif - - // Export B-spline to XML - bspline.to_xml().print(iganet::Log(iganet::log::info)); - } - - { - // Univariate uniform B-spline of degree 2 with 6 control points in R^2 - iganet::UniformBSpline bspline({6}); - iganet::UniformBSpline color({6}); - - // Print information - iganet::Log(iganet::log::info) << bspline << std::endl; - - // Map control points to phyiscal coordinates - bspline.transform([](const std::array xi) { - return std::array{xi[0] * xi[0], - sin(static_cast(M_PI) * xi[0])}; - }); - - // Map colors - color.transform([](const std::array xi) { - return std::array{xi[0]}; - }); - - // Evaluate B-spline at xi=0, xi=0.5, and xi=1 - iganet::Log(iganet::log::info) - << bspline.eval(iganet::utils::to_tensorArray({0.0, 0.5, 1.0})) - << std::endl; - -#ifdef IGANET_WITH_MATPLOT - // Plot B-spline - bspline.plot(json)->show(); - bspline.plot(color, json)->show(); -#endif - - // Export B-spline to XML - bspline.to_xml().print(iganet::Log(iganet::log::info)); - } - - { - // Univariate uniform B-spline of degree 2 with 6 control points in R^3 - iganet::UniformBSpline bspline({6}); - iganet::UniformBSpline color({6}); - - // Print information - iganet::Log(iganet::log::info) << bspline << std::endl; - - // Map control points to phyiscal coordinates - bspline.transform([](const std::array xi) { - return std::array{ - xi[0] * xi[0], sin(static_cast(M_PI) * xi[0]), xi[0]}; - }); - - // Map colors - color.transform([](const std::array xi) { - return std::array{xi[0]}; - }); - - // Evaluate B-spline at xi=0, xi=0.5, and xi=1 - iganet::Log(iganet::log::info) - << bspline.eval(iganet::utils::to_tensorArray({0.0, 0.5, 1.0})) - << std::endl; - -#ifdef IGANET_WITH_MATPLOT - // Plot B-spline - bspline.plot(json)->show(); - bspline.plot(color, json)->show(); -#endif - - // Export B-spline to XML - bspline.to_xml().print(iganet::Log(iganet::log::info)); - } - - { - // Bivariate uniform B-spline of degree 3 in xi-direction and 4 - // in eta-direction with 5 x 6 control points in R^2 - iganet::UniformBSpline bspline({5, 6}); - iganet::UniformBSpline color({5, 6}); - - // Print information - iganet::Log(iganet::log::info) << bspline << std::endl; - - // Map control points to phyiscal coordinates - bspline.transform([](const std::array xi) { - return std::array{ - (xi[0] + 1) * cos(static_cast(M_PI) * xi[1]), - (xi[0] + 1) * sin(static_cast(M_PI) * xi[1])}; - }); - - // Map colors - color.transform([](const std::array xi) { - return std::array{xi[0] * xi[1]}; - }); - - // Evaluate B-spline at xi=0, xi=0.5, and xi=1 - iganet::Log(iganet::log::info) - << bspline.eval( - iganet::utils::to_tensorArray({0.0, 0.5, 1.0}, {0.0, 0.5, 0.5})) - << std::endl; - -#ifdef IGANET_WITH_MATPLOT - // Plot B-spline - bspline.plot(json)->show(); - bspline.plot(color, json)->show(); -#endif - - // Export B-spline to XML - bspline.to_xml().print(iganet::Log(iganet::log::info)); - } - - { - // Bivariate uniform B-spline of degree 3 in xi-direction and 4 - // in eta-direction with 5 x 6 control points in R^3 - iganet::UniformBSpline bspline({5, 6}); - iganet::UniformBSpline color({5, 6}); - - // Print information - iganet::Log(iganet::log::info) << bspline << std::endl; - - // Map control points to phyiscal coordinates - bspline.transform([](const std::array xi) { - return std::array{ - (xi[0] + 1) * cos(static_cast(M_PI) * xi[1]), - (xi[0] + 1) * sin(static_cast(M_PI) * xi[1]), xi[0]}; - }); - - // Map colors - color.transform([](const std::array xi) { - return std::array{xi[0] * xi[1]}; - }); - - // Evaluate B-spline at (xi=0,eta=0), (xi=0.5,eta=0.5), and (xi=1,eta=0.5) - iganet::Log(iganet::log::info) - << bspline.eval( - iganet::utils::to_tensorArray({0.0, 0.5, 1.0}, {0.0, 0.5, 0.5})) - << std::endl; - -#ifdef IGANET_WITH_MATPLOT - // Plot B-spline - bspline.plot(json)->show(); - bspline.plot(color, json)->show(); -#endif - - // Export B-spline to XML - bspline.to_xml().print(iganet::Log(iganet::log::info)); - - auto xi = iganet::utils::to_tensorArray({0.0, 0.5, 1.0}, {0.0, 0.5, 0.5}); - } - - return 0; -} +/** + @file examples/uniform_bsplines.cxx + + @brief Demonstration of the uniform B-spline class + + @author Matthias Moller + + @copyright This file is part of the IgANet project + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#include +#include + +int main() { + iganet::init(); + iganet::verbose(std::cout); + using real_t = double; + + nlohmann::json json; + json["res0"] = 50; + json["res1"] = 50; + json["cnet"] = true; + + { + // Univariate uniform B-spline of degree 2 with 6 control points in R^1 + iganet::UniformBSpline bspline({6}), color({6}); + + // Print information + iganet::Log(iganet::log::info) << bspline << std::endl; + + // Map control points to phyiscal coordinates + bspline.transform([](const std::array xi) { + return std::array{xi[0] * xi[0]}; + }); + + // Map colors + color.transform([](const std::array xi) { + return std::array{xi[0]}; + }); + + // Evaluate B-spline at xi=0, xi=0.5, and xi=1 + iganet::Log(iganet::log::info) + << bspline.eval(iganet::utils::to_tensorArray({0.0, 0.5, 1.0})) + << std::endl; + +#ifdef IGANET_WITH_MATPLOT + // Plot B-spline + bspline.plot(json)->show(); + bspline.plot(color, json)->show(); +#endif + + // Export B-spline to XML + bspline.to_xml().print(iganet::Log(iganet::log::info)); + } + + { + // Univariate uniform B-spline of degree 2 with 6 control points in R^2 + iganet::UniformBSpline bspline({6}); + iganet::UniformBSpline color({6}); + + // Print information + iganet::Log(iganet::log::info) << bspline << std::endl; + + // Map control points to phyiscal coordinates + bspline.transform([](const std::array xi) { + return std::array{xi[0] * xi[0], + sin(static_cast(M_PI) * xi[0])}; + }); + + // Map colors + color.transform([](const std::array xi) { + return std::array{xi[0]}; + }); + + // Evaluate B-spline at xi=0, xi=0.5, and xi=1 + iganet::Log(iganet::log::info) + << bspline.eval(iganet::utils::to_tensorArray({0.0, 0.5, 1.0})) + << std::endl; + +#ifdef IGANET_WITH_MATPLOT + // Plot B-spline + bspline.plot(json)->show(); + bspline.plot(color, json)->show(); +#endif + + // Export B-spline to XML + bspline.to_xml().print(iganet::Log(iganet::log::info)); + } + + { + // Univariate uniform B-spline of degree 2 with 6 control points in R^3 + iganet::UniformBSpline bspline({6}); + iganet::UniformBSpline color({6}); + + // Print information + iganet::Log(iganet::log::info) << bspline << std::endl; + + // Map control points to phyiscal coordinates + bspline.transform([](const std::array xi) { + return std::array{ + xi[0] * xi[0], sin(static_cast(M_PI) * xi[0]), xi[0]}; + }); + + // Map colors + color.transform([](const std::array xi) { + return std::array{xi[0]}; + }); + + // Evaluate B-spline at xi=0, xi=0.5, and xi=1 + iganet::Log(iganet::log::info) + << bspline.eval(iganet::utils::to_tensorArray({0.0, 0.5, 1.0})) + << std::endl; + +#ifdef IGANET_WITH_MATPLOT + // Plot B-spline + bspline.plot(json)->show(); + bspline.plot(color, json)->show(); +#endif + + // Export B-spline to XML + bspline.to_xml().print(iganet::Log(iganet::log::info)); + } + + { + // Bivariate uniform B-spline of degree 3 in xi-direction and 4 + // in eta-direction with 5 x 6 control points in R^2 + iganet::UniformBSpline bspline({5, 6}); + iganet::UniformBSpline color({5, 6}); + + // Print information + iganet::Log(iganet::log::info) << bspline << std::endl; + + // Map control points to phyiscal coordinates + bspline.transform([](const std::array xi) { + return std::array{ + (xi[0] + 1) * cos(static_cast(M_PI) * xi[1]), + (xi[0] + 1) * sin(static_cast(M_PI) * xi[1])}; + }); + + // Map colors + color.transform([](const std::array xi) { + return std::array{xi[0] * xi[1]}; + }); + + // Evaluate B-spline at xi=0, xi=0.5, and xi=1 + iganet::Log(iganet::log::info) + << bspline.eval( + iganet::utils::to_tensorArray({0.0, 0.5, 1.0}, {0.0, 0.5, 0.5})) + << std::endl; + +#ifdef IGANET_WITH_MATPLOT + // Plot B-spline + bspline.plot(json)->show(); + bspline.plot(color, json)->show(); +#endif + + // Export B-spline to XML + bspline.to_xml().print(iganet::Log(iganet::log::info)); + } + + { + // Bivariate uniform B-spline of degree 3 in xi-direction and 4 + // in eta-direction with 5 x 6 control points in R^3 + iganet::UniformBSpline bspline({5, 6}); + iganet::UniformBSpline color({5, 6}); + + // Print information + iganet::Log(iganet::log::info) << bspline << std::endl; + + // Map control points to phyiscal coordinates + bspline.transform([](const std::array xi) { + return std::array{ + (xi[0] + 1) * cos(static_cast(M_PI) * xi[1]), + (xi[0] + 1) * sin(static_cast(M_PI) * xi[1]), xi[0]}; + }); + + // Map colors + color.transform([](const std::array xi) { + return std::array{xi[0] * xi[1]}; + }); + + // Evaluate B-spline at (xi=0,eta=0), (xi=0.5,eta=0.5), and (xi=1,eta=0.5) + iganet::Log(iganet::log::info) + << bspline.eval( + iganet::utils::to_tensorArray({0.0, 0.5, 1.0}, {0.0, 0.5, 0.5})) + << std::endl; + +#ifdef IGANET_WITH_MATPLOT + // Plot B-spline + bspline.plot(json)->show(); + bspline.plot(color, json)->show(); +#endif + + // Export B-spline to XML + bspline.to_xml().print(iganet::Log(iganet::log::info)); + + auto xi = iganet::utils::to_tensorArray({0.0, 0.5, 1.0}, {0.0, 0.5, 0.5}); + } + + return 0; +} diff --git a/include/core.hpp b/include/core.hpp index 1040102f..91de5f7c 100644 --- a/include/core.hpp +++ b/include/core.hpp @@ -1,262 +1,262 @@ -/** - @file include/core.hpp - - @brief Core components - - @author Matthias Moller - - @copyright This file is part of the IgANet project - - This Source Code Form is subject to the terms of the Mozilla Public - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at http://mozilla.org/MPL/2.0/. -*/ - -#pragma once - -#include - -#include -#include -#include -#include -#include - -#include - -#ifdef IGANET_WITH_OPENMP -#include -#endif - -#ifdef IGANET_WITH_MPI -#ifndef USE_C10D_MPI -#error "Torch must be compiled with USE_DISTRIBUTED=1, USE_MPI=1, USE_C10_MPI=1" -#endif -#include -#endif - -#include -#include - -#ifdef IGANET_WITH_GISMO -#include -#endif - -#undef real_t -#undef index_t -#undef short_t - -#ifdef IGANET_WITH_MATPLOT -#ifdef __CUDACC__ -#pragma nv_diag_suppress 611 -#endif -#include -#ifdef __CUDACC__ -#pragma nv_diag_default 611 -#endif -#endif - -#include - -namespace iganet { - -using short_t = short int; - -namespace literals { - -/// @brief User-defined literals for integer values -/// @{ -inline short_t operator""_s(unsigned long long value) { return value; }; -inline int8_t operator""_i8(unsigned long long value) { return value; }; -inline int16_t operator""_i16(unsigned long long value) { return value; }; -inline int32_t operator""_i32(unsigned long long value) { return value; }; -inline int64_t operator""_i64(unsigned long long value) { return value; }; -/// @} -} // namespace literals - -// clang-format off -/// @brief Enumerator for specifying the initialization of B-spline coefficients -enum class log : short_t { - none = 0, /*!< no logging */ - fatal = 1, /*!< log fatal errors */ - error = 2, /*!< log errors */ - warning = 3, /*!< log warnings */ - info = 4, /*!< log information */ - debug = 5, /*!< log debug information */ - verbose = 6 /*!< log everything */ -}; -// clang-format on - -namespace logging { -/// @brief Dummy stream buffer -class NullStreamBuffer : public std::streambuf { -public: - /// @brief Dummy output - int overflow(int c) override { return traits_type::not_eof(c); } -}; - -/// @brief Dummy output stream -class NullOStream : public std::ostream { -public: - /// @brief Constructor - NullOStream() : std::ostream(&nullStreamBuffer) {} - -private: - NullStreamBuffer nullStreamBuffer; -}; -} // namespace logging - -/// @brief Logger -struct { -private: - /// @brief Output stream - std::ostream &outputStream = std::cout; - - /// @brief Dummy output stream - logging::NullOStream nullStream; - - /// @brief Output file - std::ofstream outputFile; - - /// @brief Log level - enum log level = log::info; - -public: - /// @brief Sets the log level - void setLogLevel(enum log level) { this->level = level; } - - /// @brief Sets the log file - void setLogFile(std::string filename) { - outputFile = std::ofstream(filename); - outputStream.rdbuf(outputFile.rdbuf()); - } - - /// @brief Returns the output stream - std::ostream &operator()(enum log level = log::info) { - if (this->level >= level) - switch (level) { - case (log::fatal): - return outputStream << "[FATAL ERROR] "; - case (log::error): - return outputStream << "[ERROR] "; - case (log::warning): - return outputStream << "[WARNING] "; - case (log::info): - return outputStream << "[INFO] "; - case (log::debug): - return outputStream << "[DEBUG] "; - case (log::verbose): - return outputStream << "[VERBOSE] "; - default: - return nullStream; - } - else - return nullStream; - } -} Log; - -/// @brief Initializes the library -inline void init(std::ostream &os = Log(log::info)) { - torch::manual_seed(1); - - // Set number of intraop thread pool threads -#ifdef IGANET_WITH_OPENMP - at::set_num_threads( - utils::getenv("IGANET_INTRAOP_NUM_THREADS", omp_get_max_threads())); -#else - at::set_num_threads(utils::getenv("IGANET_INTRAOP_NUM_THREADS", 1)); -#endif - - // Set number of interop thread pool threads - at::set_num_interop_threads(utils::getenv("IGANET_INTEROP_NUM_THREADS", 1)); - -#ifdef IGANET_WITH_MPI - int rank; - MPI_Comm_rank(MPI_COMM_WORLD, &rank); - if (rank == 0) -#endif - // Output version information - os << getVersion(); -} - -/// Stream manipulator -/// @{ -inline int get_iomanip() { - static int i = std::ios_base::xalloc(); - return i; -} - -inline std::ostream &verbose(std::ostream &os) { - os.iword(get_iomanip()) = 1; - return os; -} -inline std::ostream ®ular(std::ostream &os) { - os.iword(get_iomanip()) = 0; - return os; -} - -inline bool is_verbose(std::ostream &os) { - return os.iword(get_iomanip()) != 0; -} -/// @} - -} // namespace iganet - -namespace std { - -/// Print (as string) an std::array of generic objects -template -inline std::ostream &operator<<(std::ostream &os, const std::array &obj) { - at::optional name_ = c10::demangle(typeid(obj).name()); - -#if defined(_WIN32) - // Windows adds "struct" or "class" as a prefix. - if (name_->find("struct ") == 0) { - name_->erase(name_->begin(), name_->begin() + 7); - } else if (name_->find("class ") == 0) { - name_->erase(name_->begin(), name_->begin() + 6); - } -#endif // defined(_WIN32) - - os << *name_ << "("; - for (const auto &i : obj) - os << i << (&i == &(*obj.rbegin()) ? "" : ","); - os << ")"; - - return os; -} - -namespace detail { -template -inline std::ostream &output_tuple(std::ostream &os, - const std::tuple &obj, - std::index_sequence) { - (..., (os << std::get(obj) << "\n")); - return os; -} - -} // namespace detail - -/// Print (as string) an std::tuple of generic objects -template -inline std::ostream &operator<<(std::ostream &os, - const std::tuple &obj) { - at::optional name_ = c10::demangle(typeid(obj).name()); - -#if defined(_WIN32) - // Windows adds "struct" or "class" as a prefix. - if (name_->find("struct ") == 0) { - name_->erase(name_->begin(), name_->begin() + 7); - } else if (name_->find("class ") == 0) { - name_->erase(name_->begin(), name_->begin() + 6); - } -#endif // defined(_WIN32) - - os << *name_ << "(\n"; - detail::output_tuple(os, obj, std::make_index_sequence()); - os << "\n)"; - - return os; -} - -} // namespace std +/** + @file include/core.hpp + + @brief Core components + + @author Matthias Moller + + @copyright This file is part of the IgANet project + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#pragma once + +#include + +#include +#include +#include +#include +#include + +#include + +#ifdef IGANET_WITH_OPENMP +#include +#endif + +#ifdef IGANET_WITH_MPI +#ifndef USE_C10D_MPI +#error "Torch must be compiled with USE_DISTRIBUTED=1, USE_MPI=1, USE_C10_MPI=1" +#endif +#include +#endif + +#include +#include + +#ifdef IGANET_WITH_GISMO +#include +#endif + +#undef real_t +#undef index_t +#undef short_t + +#ifdef IGANET_WITH_MATPLOT +#ifdef __CUDACC__ +#pragma nv_diag_suppress 611 +#endif +#include +#ifdef __CUDACC__ +#pragma nv_diag_default 611 +#endif +#endif + +#include + +namespace iganet { + +using short_t = short int; + +namespace literals { + +/// @brief User-defined literals for integer values +/// @{ +inline short_t operator""_s(unsigned long long value) { return value; }; +inline int8_t operator""_i8(unsigned long long value) { return value; }; +inline int16_t operator""_i16(unsigned long long value) { return value; }; +inline int32_t operator""_i32(unsigned long long value) { return value; }; +inline int64_t operator""_i64(unsigned long long value) { return value; }; +/// @} +} // namespace literals + +// clang-format off +/// @brief Enumerator for specifying the initialization of B-spline coefficients +enum class log : short_t { + none = 0, /*!< no logging */ + fatal = 1, /*!< log fatal errors */ + error = 2, /*!< log errors */ + warning = 3, /*!< log warnings */ + info = 4, /*!< log information */ + debug = 5, /*!< log debug information */ + verbose = 6 /*!< log everything */ +}; +// clang-format on + +namespace logging { +/// @brief Dummy stream buffer +class NullStreamBuffer : public std::streambuf { +public: + /// @brief Dummy output + int overflow(int c) override { return traits_type::not_eof(c); } +}; + +/// @brief Dummy output stream +class NullOStream : public std::ostream { +public: + /// @brief Constructor + NullOStream() : std::ostream(&nullStreamBuffer) {} + +private: + NullStreamBuffer nullStreamBuffer; +}; +} // namespace logging + +/// @brief Logger +struct { +private: + /// @brief Output stream + std::ostream &outputStream = std::cout; + + /// @brief Dummy output stream + logging::NullOStream nullStream; + + /// @brief Output file + std::ofstream outputFile; + + /// @brief Log level + enum log level = log::info; + +public: + /// @brief Sets the log level + void setLogLevel(enum log level) { this->level = level; } + + /// @brief Sets the log file + void setLogFile(std::string filename) { + outputFile = std::ofstream(filename); + outputStream.rdbuf(outputFile.rdbuf()); + } + + /// @brief Returns the output stream + std::ostream &operator()(enum log level = log::info) { + if (this->level >= level) + switch (level) { + case (log::fatal): + return outputStream << "[FATAL ERROR] "; + case (log::error): + return outputStream << "[ERROR] "; + case (log::warning): + return outputStream << "[WARNING] "; + case (log::info): + return outputStream << "[INFO] "; + case (log::debug): + return outputStream << "[DEBUG] "; + case (log::verbose): + return outputStream << "[VERBOSE] "; + default: + return nullStream; + } + else + return nullStream; + } +} Log; + +/// @brief Initializes the library +inline void init(std::ostream &os = Log(log::info)) { + torch::manual_seed(1); + + // Set number of intraop thread pool threads +#ifdef IGANET_WITH_OPENMP + at::set_num_threads( + utils::getenv("IGANET_INTRAOP_NUM_THREADS", omp_get_max_threads())); +#else + at::set_num_threads(utils::getenv("IGANET_INTRAOP_NUM_THREADS", 1)); +#endif + + // Set number of interop thread pool threads + at::set_num_interop_threads(utils::getenv("IGANET_INTEROP_NUM_THREADS", 1)); + +#ifdef IGANET_WITH_MPI + int rank; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + if (rank == 0) +#endif + // Output version information + os << getVersion(); +} + +/// Stream manipulator +/// @{ +inline int get_iomanip() { + static int i = std::ios_base::xalloc(); + return i; +} + +inline std::ostream &verbose(std::ostream &os) { + os.iword(get_iomanip()) = 1; + return os; +} +inline std::ostream ®ular(std::ostream &os) { + os.iword(get_iomanip()) = 0; + return os; +} + +inline bool is_verbose(std::ostream &os) { + return os.iword(get_iomanip()) != 0; +} +/// @} + +} // namespace iganet + +namespace std { + +/// Print (as string) an std::array of generic objects +template +inline std::ostream &operator<<(std::ostream &os, const std::array &obj) { + at::optional name_ = c10::demangle(typeid(obj).name()); + +#if defined(_WIN32) + // Windows adds "struct" or "class" as a prefix. + if (name_->find("struct ") == 0) { + name_->erase(name_->begin(), name_->begin() + 7); + } else if (name_->find("class ") == 0) { + name_->erase(name_->begin(), name_->begin() + 6); + } +#endif // defined(_WIN32) + + os << *name_ << "("; + for (const auto &i : obj) + os << i << (&i == &(*obj.rbegin()) ? "" : ","); + os << ")"; + + return os; +} + +namespace detail { +template +inline std::ostream &output_tuple(std::ostream &os, + const std::tuple &obj, + std::index_sequence) { + (..., (os << std::get(obj) << "\n")); + return os; +} + +} // namespace detail + +/// Print (as string) an std::tuple of generic objects +template +inline std::ostream &operator<<(std::ostream &os, + const std::tuple &obj) { + at::optional name_ = c10::demangle(typeid(obj).name()); + +#if defined(_WIN32) + // Windows adds "struct" or "class" as a prefix. + if (name_->find("struct ") == 0) { + name_->erase(name_->begin(), name_->begin() + 7); + } else if (name_->find("class ") == 0) { + name_->erase(name_->begin(), name_->begin() + 6); + } +#endif // defined(_WIN32) + + os << *name_ << "(\n"; + detail::output_tuple(os, obj, std::make_index_sequence()); + os << "\n)"; + + return os; +} + +} // namespace std diff --git a/include/iganet.hpp b/include/iganet.hpp index 0b6e60e1..9f1b13ef 100644 --- a/include/iganet.hpp +++ b/include/iganet.hpp @@ -1,1377 +1,1377 @@ -/** - @file include/iganet.hpp - - @brief Isogeometric analysis network - - @author Matthias Moller - - @copyright This file is part of the IgANet project - - This Source Code Form is subject to the terms of the Mozilla Public - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at http://mozilla.org/MPL/2.0/. -*/ - -#pragma once - -#include - -#include -#include -#include -#include -#include -#include -#include - -namespace iganet { - -/// @brief IgANetOptions -struct IgANetOptions { - TORCH_ARG(int64_t, max_epoch) = 100; - TORCH_ARG(int64_t, batch_size) = 1000; - TORCH_ARG(double, min_loss) = 1e-4; -}; - -/// @brief IgANetGeneratorImpl -/// -/// @note Following the discussion of module overship here -/// -/// https://pytorch.org/tutorials/advanced/cpp_frontend.html#module-ownership -/// -/// we implement a generator implementation class following -/// -/// https://pytorch.org/tutorials/advanced/cpp_frontend.html#the-generator-module -template -class IgANetGeneratorImpl : public torch::nn::Module { -public: - /// @brief Default constructor - IgANetGeneratorImpl() = default; - - /// @brief Constructor - explicit IgANetGeneratorImpl( - const std::vector &layers, - const std::vector> &activations, - Options options = Options{}) { - assert(layers.size() == activations.size() + 1); - - // Generate vector of linear layers and register them as layer[i] - for (auto i = 0; i < layers.size() - 1; ++i) { - layers_.emplace_back( - register_module("layer[" + std::to_string(i) + "]", - torch::nn::Linear(layers[i], layers[i + 1]))); - layers_.back()->to(options.device(), options.dtype(), true); - - torch::nn::init::xavier_uniform_(layers_.back()->weight); - torch::nn::init::constant_(layers_.back()->bias, 0.0); - } - - // Generate vector of activation functions - for (const auto &a : activations) - switch (std::any_cast(a[0])) { - // No activation function - case activation::none: - switch (a.size()) { - case 1: - activations_.emplace_back(new None{}); - break; - default: - throw std::runtime_error("Invalid number of parameters"); - } - break; - - // Batch Normalization - case activation::batch_norm: - switch (a.size()) { - case 8: - activations_.emplace_back(new BatchNorm{ - std::any_cast(a[1]), - std::any_cast(a[2]), - std::any_cast(a[3]), - std::any_cast(a[4]), std::any_cast(a[5]), - std::any_cast(a[6]), std::any_cast(a[7])}); - break; - case 7: - activations_.emplace_back(new BatchNorm{ - std::any_cast(a[1]), - std::any_cast(a[2]), - std::any_cast(a[3]), - std::any_cast(a[4]), std::any_cast(a[5]), - std::any_cast(a[6])}); - break; - case 4: - activations_.emplace_back(new BatchNorm{ - std::any_cast(a[1]), - std::any_cast(a[2]), - std::any_cast( - a[3])}); - break; - case 3: - activations_.emplace_back( - new BatchNorm{std::any_cast(a[1]), - std::any_cast(a[2])}); - break; - default: - throw std::runtime_error("Invalid number of parameters"); - } - break; - - // CELU - case activation::celu: - switch (a.size()) { - case 3: - activations_.emplace_back( - new CELU{std::any_cast(a[1]), std::any_cast(a[2])}); - break; - case 2: - try { - activations_.emplace_back(new CELU{ - std::any_cast(a[1])}); - } catch (...) { - activations_.emplace_back(new CELU{std::any_cast(a[1])}); - } - break; - case 1: - activations_.emplace_back(new CELU{}); - break; - default: - throw std::runtime_error("Invalid number of parameters"); - } - break; - - // ELU - case activation::elu: - switch (a.size()) { - case 3: - activations_.emplace_back( - new ELU{std::any_cast(a[1]), std::any_cast(a[2])}); - break; - case 2: - try { - activations_.emplace_back(new ELU{ - std::any_cast(a[1])}); - } catch (...) { - activations_.emplace_back(new ELU{std::any_cast(a[1])}); - } - break; - case 1: - activations_.emplace_back(new ELU{}); - break; - default: - throw std::runtime_error("Invalid number of parameters"); - } - break; - - // GELU - case activation::gelu: - switch (a.size()) { - case 1: - activations_.emplace_back(new GELU{}); - break; - default: - throw std::runtime_error("Invalid number of parameters"); - } - break; - - // GLU - case activation::glu: - switch (a.size()) { - case 2: - try { - activations_.emplace_back(new GLU{ - std::any_cast(a[1])}); - } catch (...) { - activations_.emplace_back(new GLU{std::any_cast(a[1])}); - } - break; - case 1: - activations_.emplace_back(new GLU{}); - break; - default: - throw std::runtime_error("Invalid number of parameters"); - } - break; - - // Group Normalization - case activation::group_norm: - switch (a.size()) { - case 5: - activations_.emplace_back(new GroupNorm{ - std::any_cast(a[1]), std::any_cast(a[2]), - std::any_cast(a[3]), std::any_cast(a[4])}); - break; - case 2: - try { - activations_.emplace_back(new GroupNorm{ - std::any_cast( - a[1])}); - } catch (...) { - activations_.emplace_back( - new GroupNorm{std::any_cast(a[1])}); - } - break; - default: - throw std::runtime_error("Invalid number of parameters"); - } - break; - - // Gumbel-Softmax - case activation::gumbel_softmax: - switch (a.size()) { - case 4: - activations_.emplace_back(new GumbelSoftmax{ - std::any_cast(a[1]), std::any_cast(a[2]), - std::any_cast(a[3])}); - break; - case 2: - activations_.emplace_back(new GumbelSoftmax{ - std::any_cast( - a[1])}); - break; - case 1: - activations_.emplace_back(new GumbelSoftmax{}); - break; - default: - throw std::runtime_error("Invalid number of parameters"); - } - break; - - // Hard shrinkish - case activation::hardshrink: - switch (a.size()) { - case 2: - try { - activations_.emplace_back(new Hardshrink{ - std::any_cast( - a[1])}); - } catch (...) { - activations_.emplace_back( - new Hardshrink{std::any_cast(a[1])}); - } - break; - case 1: - activations_.emplace_back(new Hardshrink{}); - break; - default: - throw std::runtime_error("Invalid number of parameters"); - } - break; - - // Hardsigmoid - case activation::hardsigmoid: - switch (a.size()) { - case 1: - activations_.emplace_back(new Hardsigmoid{}); - break; - default: - throw std::runtime_error("Invalid number of parameters"); - } - break; - - // Hardswish - case activation::hardswish: - switch (a.size()) { - case 1: - activations_.emplace_back(new Hardswish{}); - break; - default: - throw std::runtime_error("Invalid number of parameters"); - } - break; - - // Hardtanh - case activation::hardtanh: - switch (a.size()) { - case 4: - activations_.emplace_back(new Hardtanh{std::any_cast(a[1]), - std::any_cast(a[2]), - std::any_cast(a[3])}); - break; - case 3: - activations_.emplace_back(new Hardtanh{std::any_cast(a[1]), - std::any_cast(a[2])}); - break; - case 2: - activations_.emplace_back(new Hardtanh{ - std::any_cast(a[1])}); - break; - case 1: - activations_.emplace_back(new Hardtanh{}); - break; - default: - throw std::runtime_error("Invalid number of parameters"); - } - break; - - // Instance Normalization - case activation::instance_norm: - switch (a.size()) { - case 8: - activations_.emplace_back(new InstanceNorm{ - std::any_cast(a[1]), - std::any_cast(a[2]), - std::any_cast(a[3]), - std::any_cast(a[4]), std::any_cast(a[5]), - std::any_cast(a[6]), std::any_cast(a[7])}); - break; - case 7: - activations_.emplace_back(new InstanceNorm{ - std::any_cast(a[1]), - std::any_cast(a[2]), - std::any_cast(a[3]), - std::any_cast(a[4]), std::any_cast(a[5]), - std::any_cast(a[6])}); - break; - case 2: - activations_.emplace_back(new InstanceNorm{ - std::any_cast( - a[1])}); - break; - case 1: - activations_.emplace_back(new InstanceNorm{}); - break; - default: - throw std::runtime_error("Invalid number of parameters"); - } - break; - - // Layer Normalization - case activation::layer_norm: - switch (a.size()) { - case 5: - activations_.emplace_back(new LayerNorm{ - std::any_cast>(a[1]), - std::any_cast(a[2]), - std::any_cast(a[3]), std::any_cast(a[4])}); - break; - case 2: - try { - activations_.emplace_back(new LayerNorm{ - std::any_cast( - a[1])}); - } catch (...) { - activations_.emplace_back( - new LayerNorm{std::any_cast>(a[1])}); - } - break; - default: - throw std::runtime_error("Invalid number of parameters"); - } - break; - - // Leaky ReLU - case activation::leaky_relu: - switch (a.size()) { - case 3: - activations_.emplace_back(new LeakyReLU{std::any_cast(a[1]), - std::any_cast(a[2])}); - break; - case 2: - try { - activations_.emplace_back(new LeakyReLU{ - std::any_cast( - a[1])}); - } catch (...) { - activations_.emplace_back( - new LeakyReLU{std::any_cast(a[1])}); - } - break; - case 1: - activations_.emplace_back(new LeakyReLU{}); - break; - default: - throw std::runtime_error("Invalid number of parameters"); - } - break; - - // Local response Normalization - case activation::local_response_norm: - switch (a.size()) { - case 5: - activations_.emplace_back(new LocalResponseNorm{ - std::any_cast(a[1]), std::any_cast(a[2]), - std::any_cast(a[3]), std::any_cast(a[4])}); - break; - case 2: - try { - activations_.emplace_back(new LocalResponseNorm{std::any_cast< - torch::nn::functional::LocalResponseNormFuncOptions>(a[1])}); - } catch (...) { - activations_.emplace_back( - new LocalResponseNorm{std::any_cast(a[1])}); - } - break; - default: - throw std::runtime_error("Invalid number of parameters"); - } - break; - - // LogSigmoid - case activation::logsigmoid: - switch (a.size()) { - case 1: - activations_.emplace_back(new LogSigmoid{}); - break; - default: - throw std::runtime_error("Invalid number of parameters"); - } - break; - - // LogSoftmax - case activation::logsoftmax: - switch (a.size()) { - case 2: - try { - activations_.emplace_back(new LogSoftmax{ - std::any_cast( - a[1])}); - } catch (...) { - activations_.emplace_back( - new LogSoftmax{std::any_cast(a[1])}); - } - break; - default: - throw std::runtime_error("Invalid number of parameters"); - } - break; - - // Mish - case activation::mish: - switch (a.size()) { - case 1: - activations_.emplace_back(new Mish{}); - break; - default: - throw std::runtime_error("Invalid number of parameters"); - } - break; - - // Lp Normalization - case activation::normalize: - switch (a.size()) { - case 4: - activations_.emplace_back(new Normalize{ - std::any_cast(a[1]), std::any_cast(a[2]), - std::any_cast(a[3])}); - break; - case 2: - activations_.emplace_back(new Normalize{ - std::any_cast( - a[1])}); - break; - case 1: - activations_.emplace_back(new Normalize{}); - break; - default: - throw std::runtime_error("Invalid number of parameters"); - } - break; - - // PReLU - case activation::prelu: - switch (a.size()) { - case 2: - activations_.emplace_back( - new PReLU{std::any_cast(a[1])}); - break; - default: - throw std::runtime_error("Invalid number of parameters"); - } - break; - - // ReLU - case activation::relu: - switch (a.size()) { - case 2: - try { - activations_.emplace_back(new ReLU{ - std::any_cast(a[1])}); - } catch (...) { - activations_.emplace_back(new ReLU{std::any_cast(a[1])}); - } - break; - case 1: - activations_.emplace_back(new ReLU{}); - break; - default: - throw std::runtime_error("Invalid number of parameters"); - } - break; - - // Relu6 - case activation::relu6: - switch (a.size()) { - case 2: - try { - activations_.emplace_back(new ReLU6{ - std::any_cast(a[1])}); - } catch (...) { - activations_.emplace_back(new ReLU6{std::any_cast(a[1])}); - } - break; - case 1: - activations_.emplace_back(new ReLU6{}); - break; - default: - throw std::runtime_error("Invalid number of parameters"); - } - break; - - // Randomized ReLU - case activation::rrelu: - switch (a.size()) { - case 4: - activations_.emplace_back(new RReLU{std::any_cast(a[1]), - std::any_cast(a[2]), - std::any_cast(a[3])}); - break; - case 3: - activations_.emplace_back(new RReLU{std::any_cast(a[1]), - std::any_cast(a[2])}); - break; - case 2: - activations_.emplace_back(new RReLU{ - std::any_cast(a[1])}); - break; - case 1: - activations_.emplace_back(new RReLU{}); - break; - default: - throw std::runtime_error("Invalid number of parameters"); - } - break; - - // SELU - case activation::selu: - switch (a.size()) { - case 2: - try { - activations_.emplace_back(new SELU{ - std::any_cast(a[1])}); - } catch (...) { - activations_.emplace_back(new SELU{std::any_cast(a[1])}); - } - break; - case 1: - activations_.emplace_back(new SELU{}); - break; - default: - throw std::runtime_error("Invalid number of parameters"); - } - break; - - // Sigmoid - case activation::sigmoid: - switch (a.size()) { - case 1: - activations_.emplace_back(new Sigmoid{}); - break; - default: - throw std::runtime_error("Invalid number of parameters"); - } - break; - - // SiLU - case activation::silu: - switch (a.size()) { - case 1: - activations_.emplace_back(new SiLU{}); - break; - default: - throw std::runtime_error("Invalid number of parameters"); - } - break; - - // Softmax - case activation::softmax: - switch (a.size()) { - case 2: - try { - activations_.emplace_back(new Softmax{ - std::any_cast( - a[1])}); - } catch (...) { - activations_.emplace_back( - new Softmax{std::any_cast(a[1])}); - } - break; - default: - throw std::runtime_error("Invalid number of parameters"); - } - break; - - // Softmin - case activation::softmin: - switch (a.size()) { - case 2: - try { - activations_.emplace_back(new Softmin{ - std::any_cast( - a[1])}); - } catch (...) { - activations_.emplace_back( - new Softmin{std::any_cast(a[1])}); - } - break; - default: - throw std::runtime_error("Invalid number of parameters"); - } - break; - - // Softplus - case activation::softplus: - switch (a.size()) { - case 3: - activations_.emplace_back(new Softplus{std::any_cast(a[1]), - std::any_cast(a[2])}); - break; - case 2: - activations_.emplace_back(new Softplus{ - std::any_cast(a[1])}); - break; - case 1: - activations_.emplace_back(new Softplus{}); - break; - default: - throw std::runtime_error("Invalid number of parameters"); - } - break; - - // Softshrink - case activation::softshrink: - switch (a.size()) { - case 2: - try { - activations_.emplace_back(new Softshrink{ - std::any_cast( - a[1])}); - } catch (...) { - activations_.emplace_back( - new Softshrink{std::any_cast(a[1])}); - } - break; - case 1: - activations_.emplace_back(new Softshrink{}); - break; - default: - throw std::runtime_error("Invalid number of parameters"); - } - break; - - // Softsign - case activation::softsign: - switch (a.size()) { - case 1: - activations_.emplace_back(new Softsign{}); - break; - default: - throw std::runtime_error("Invalid number of parameters"); - } - break; - - // Tanh - case activation::tanh: - switch (a.size()) { - case 1: - activations_.emplace_back(new Tanh{}); - break; - default: - throw std::runtime_error("Invalid number of parameters"); - } - break; - - // Tanhshrink - case activation::tanhshrink: - switch (a.size()) { - case 1: - activations_.emplace_back(new Tanhshrink{}); - break; - default: - throw std::runtime_error("Invalid number of parameters"); - } - break; - - // Threshold - case activation::threshold: - switch (a.size()) { - case 4: - activations_.emplace_back(new Threshold{std::any_cast(a[1]), - std::any_cast(a[2]), - std::any_cast(a[3])}); - break; - case 3: - activations_.emplace_back(new Threshold{std::any_cast(a[1]), - std::any_cast(a[2])}); - break; - case 2: - activations_.emplace_back(new Threshold{ - std::any_cast( - a[1])}); - break; - default: - throw std::runtime_error("Invalid number of parameters"); - } - break; - - default: - throw std::runtime_error("Invalid activation function"); - } - } - - /// @brief Forward evaluation - torch::Tensor forward(torch::Tensor x) { - torch::Tensor x_in = x.clone(); - - // Standard feed-forward neural network - for (auto [layer, activation] : utils::zip(layers_, activations_)) - x = activation->apply(layer->forward(x)); - - return x; - } - - /// @brief Writes the IgANet into a torch::serialize::OutputArchive object - inline torch::serialize::OutputArchive & - write(torch::serialize::OutputArchive &archive, - const std::string &key = "iganet") const { - assert(layers_.size() == activations_.size()); - - archive.write(key + ".layers", torch::full({1}, (int64_t)layers_.size())); - for (std::size_t i = 0; i < layers_.size(); ++i) { - archive.write( - key + ".layer[" + std::to_string(i) + "].in_features", - torch::full({1}, (int64_t)layers_[i]->options.in_features())); - archive.write( - key + ".layer[" + std::to_string(i) + "].outputs_features", - torch::full({1}, (int64_t)layers_[i]->options.out_features())); - archive.write(key + ".layer[" + std::to_string(i) + "].bias", - torch::full({1}, (int64_t)layers_[i]->options.bias())); - - activations_[i]->write(archive, key + ".layer[" + std::to_string(i) + - "].activation"); - } - - return archive; - } - - /// @brief Reads the IgANet from a torch::serialize::InputArchive object - inline torch::serialize::InputArchive & - read(torch::serialize::InputArchive &archive, - const std::string &key = "iganet") { - torch::Tensor layers, in_features, outputs_features, bias, activation; - - archive.read(key + ".layers", layers); - for (int64_t i = 0; i < layers.item(); ++i) { - archive.read(key + ".layer[" + std::to_string(i) + "].in_features", - in_features); - archive.read(key + ".layer[" + std::to_string(i) + "].outputs_features", - outputs_features); - archive.read(key + ".layer[" + std::to_string(i) + "].bias", bias); - layers_.emplace_back(register_module( - "layer[" + std::to_string(i) + "]", - torch::nn::Linear( - torch::nn::LinearOptions(in_features.item(), - outputs_features.item()) - .bias(bias.item())))); - - archive.read(key + ".layer[" + std::to_string(i) + "].activation.type", - activation); - switch (static_cast(activation.item())) { - case activation::none: - activations_.emplace_back(new None{}); - break; - case activation::batch_norm: - activations_.emplace_back( - new BatchNorm{torch::Tensor{}, torch::Tensor{}}); - break; - case activation::celu: - activations_.emplace_back(new CELU{}); - break; - case activation::elu: - activations_.emplace_back(new ELU{}); - break; - case activation::gelu: - activations_.emplace_back(new GELU{}); - break; - case activation::glu: - activations_.emplace_back(new GLU{}); - break; - case activation::group_norm: - activations_.emplace_back(new GroupNorm{0}); - break; - case activation::gumbel_softmax: - activations_.emplace_back(new GumbelSoftmax{}); - break; - case activation::hardshrink: - activations_.emplace_back(new Hardshrink{}); - break; - case activation::hardsigmoid: - activations_.emplace_back(new Hardsigmoid{}); - break; - case activation::hardswish: - activations_.emplace_back(new Hardswish{}); - break; - case activation::hardtanh: - activations_.emplace_back(new Hardtanh{}); - break; - case activation::instance_norm: - activations_.emplace_back(new InstanceNorm{}); - break; - case activation::layer_norm: - activations_.emplace_back(new LayerNorm{{}}); - break; - case activation::leaky_relu: - activations_.emplace_back(new LeakyReLU{}); - break; - case activation::local_response_norm: - activations_.emplace_back(new LocalResponseNorm{0}); - break; - case activation::logsigmoid: - activations_.emplace_back(new LogSigmoid{}); - break; - case activation::logsoftmax: - activations_.emplace_back(new LogSoftmax{0}); - break; - case activation::mish: - activations_.emplace_back(new Mish{}); - break; - case activation::normalize: - activations_.emplace_back(new Normalize{0, 0, 0}); - break; - case activation::prelu: - activations_.emplace_back(new PReLU{torch::Tensor{}}); - break; - case activation::relu: - activations_.emplace_back(new ReLU{}); - break; - case activation::relu6: - activations_.emplace_back(new ReLU6{}); - break; - case activation::rrelu: - activations_.emplace_back(new RReLU{}); - break; - case activation::selu: - activations_.emplace_back(new SELU{}); - break; - case activation::sigmoid: - activations_.emplace_back(new Sigmoid{}); - break; - case activation::silu: - activations_.emplace_back(new SiLU{}); - break; - case activation::softmax: - activations_.emplace_back(new Softmax{0}); - break; - case activation::softmin: - activations_.emplace_back(new Softmin{0}); - break; - case activation::softplus: - activations_.emplace_back(new Softplus{}); - break; - case activation::softshrink: - activations_.emplace_back(new Softshrink{}); - break; - case activation::softsign: - activations_.emplace_back(new Softsign{}); - break; - case activation::tanh: - activations_.emplace_back(new Tanh{}); - break; - case activation::tanhshrink: - activations_.emplace_back(new Tanhshrink{}); - break; - case activation::threshold: - activations_.emplace_back(new Threshold{0, 0}); - break; - default: - throw std::runtime_error("Invalid activation function"); - } - activations_.back()->read(archive, key + ".layer[" + std::to_string(i) + - "].activation"); - } - return archive; - } - - inline virtual void - pretty_print(std::ostream &os = Log(log::info)) const noexcept override { - os << "(\n"; - - int i = 0; - for (const auto &activation : activations_) - os << "activation[" << i++ << "]: " << *activation << "\n"; - os << ")\n"; - } - -private: - /// @brief Vector of linear layers - std::vector layers_; - - /// @brief Vector of activation functions - std::vector> activations_; -}; - -/// @brief IgANetGenerator -/// -/// @note: This class is normally generated by the TORCH_MODULE -/// macro. Since the latter cannot handle templated classes -/// correctly, we give the implementation explicitly -template -class IgANetGenerator - : public torch::nn::ModuleHolder> { - -public: - using torch::nn::ModuleHolder>::ModuleHolder; - using Impl = IgANetGeneratorImpl; -}; - -/// @brief IgANet -/// -/// This class implements the core functionality of IgANets -template typename IgABase = ::iganet::IgABase> -class IgANet : public IgABase, - utils::Serializable, - private utils::FullQualifiedName { -public: - /// @brief Base type - using Base = IgABase; - - /// @brief Type of the optimizer - using optimizer_type = Optimizer; - -protected: - /// @brief IgANet generator - IgANetGenerator net_; - - /// @brief Optimizer - Optimizer opt_; - - /// @brief Options - IgANetOptions options_; - -public: - /// @brief Default constructor - explicit IgANet(IgANetOptions defaults = {}, - iganet::Options options = - iganet::Options{}) - : Base(), opt_(net_->parameters()), options_(defaults) {} - - /// @brief Constructor: number of layers, activation functions, and - /// number of spline coefficients (same for geometry map and - /// variables) - template - IgANet(const std::vector &layers, - const std::vector> &activations, - std::tuple splines, IgANetOptions defaults = {}, - iganet::Options options = - iganet::Options{}) - : IgANet(layers, activations, splines, splines, defaults, options) {} - - /// @brief Constructor: number of layers, activation functions, and - /// number of spline coefficients (different for geometry map and - /// variables) - template - IgANet(const std::vector &layers, - const std::vector> &activations, - std::tuple geometryMap_splines, - std::tuple variable_splines, - IgANetOptions defaults = {}, - iganet::Options options = - iganet::Options{}) - : Base(geometryMap_splines, variable_splines, options), - // Construct the deep neural network - net_(utils::concat(std::vector{inputs(/* epoch */ 0).size(0)}, - layers, - std::vector{Base::u_.as_tensor_size()}), - activations, options), - - // Construct the optimizer - opt_(net_->parameters()), - - // Set options - options_(defaults) {} - - /// @brief Returns a constant reference to the IgANet generator - inline const IgANetGenerator &net() const { - return net_; - } - - /// @brief Returns a non-constant reference to the IgANet generator - inline IgANetGenerator &net() { return net_; } - - /// @brief Returns a constant reference to the optimizer - inline const Optimizer &opt() const { return opt_; } - - /// @brief Returns a non-constant reference to the optimizer - inline Optimizer &opt() { return opt_; } - - /// @brief Returns a constant reference to the options structure - inline const auto &options() const { return options_; } - - /// @brief Returns a non-constant reference to the options structure - inline auto &options() { return options_; } - - /// @brief Returns the network inputs - /// - /// In the default implementation the inputs are the controll - /// points of the geometry and the reference spline objects. This - /// behavior can be changed by overriding this virtual function in - /// a derived class. - virtual torch::Tensor inputs(int64_t epoch) const { - if constexpr (Base::has_GeometryMap && Base::has_RefData) - return torch::cat({Base::G_.as_tensor(), Base::f_.as_tensor()}); - else if constexpr (Base::has_GeometryMap) - return Base::G_.as_tensor(); - else if constexpr (Base::has_RefData) - return Base::f_.as_tensor(); - else - return torch::empty({0}); - } - - /// @brief Initializes epoch - virtual bool epoch(int64_t) = 0; - - /// @brief Computes the loss function - virtual torch::Tensor loss(const torch::Tensor &, int64_t) = 0; - - /// @brief Trains the IgANet - virtual void train( -#ifdef IGANET_WITH_MPI - c10::intrusive_ptr pg -#endif - ) { - torch::Tensor inputs, outputs, loss; - - // Loop over epochs - for (int64_t epoch = 0; epoch != options_.max_epoch(); ++epoch) { - - // Update epoch and inputs - if (this->epoch(epoch)) - inputs = this->inputs(epoch); - - auto closure = [&]() { - // Reset gradients - net_->zero_grad(); - - // Execute the model on the inputs - outputs = net_->forward(inputs); - - // Compute the loss value - loss = this->loss(outputs, epoch); - - // Compute gradients of the loss w.r.t. the model parameters - loss.backward({}, true, false); - - return loss; - }; - -#ifdef IGANET_WITH_MPI - // Averaging the gradients of the parameters in all the processors - // Note: This may lag behind DistributedDataParallel (DDP) in performance - // since this synchronizes parameters after backward pass while DDP - // overlaps synchronizing parameters and computing gradients in backward - // pass - std::vector> works; - for (auto ¶m : net_->named_parameters()) { - std::vector tmp = {param.value().grad()}; - works.emplace_back(pg->allreduce(tmp)); - } - - waitWork(pg, works); - - for (auto ¶m : net_->named_parameters()) { - param.value().grad().data() = - param.value().grad().data() / pg->getSize(); - } -#endif - - // Update the parameters based on the calculated gradients - opt_.step(closure); - - Log(log::verbose) << "Epoch " << std::to_string(epoch) << ": " - << loss.template item() - << std::endl; - - if (loss.template item() < - options_.min_loss()) { - Log(log::info) << "Total epochs: " << epoch << ", loss: " - << loss.template item() - << std::endl; - break; - } - } - } - - /// @brief Trains the IgANet - template - void train(DataLoader &loader -#ifdef IGANET_WITH_MPI - , - c10::intrusive_ptr pg -#endif - ) { - torch::Tensor inputs, outputs, loss; - - // Loop over epochs - for (int64_t epoch = 0; epoch != options_.max_epoch(); ++epoch) { - - typename Base::value_type Loss(0); - - for (auto &batch : loader) { - inputs = batch.data; - - if (inputs.dim() > 0) { - if constexpr (Base::has_GeometryMap) - Base::G_.from_tensor( - inputs.slice(1, 0, Base::G_.ncumcoeffs() * Base::G_.geoDim()) - .t()); - if constexpr (Base::has_RefData && Base::has_GeometryMap) - Base::f_.from_tensor( - inputs - .slice(1, Base::G_.ncumcoeffs() * Base::G_.geoDim(), - Base::G_.ncumcoeffs() * Base::G_.geoDim() + - Base::f_.ncumcoeffs() * Base::f_.geoDim()) - .t()); - else if constexpr (Base::has_RefData) - Base::f_.from_tensor( - inputs.slice(1, 0, Base::f_.ncumcoeffs() * Base::f_.geoDim()) - .t()); - } else { - if constexpr (Base::has_GeometryMap) - Base::G_.from_tensor( - inputs.slice(1, 0, Base::G_.ncumcoeffs() * Base::G_.geoDim()) - .flatten()); - if constexpr (Base::has_RefData && Base::has_GeometryMap) - Base::f_.from_tensor( - inputs - .slice(1, Base::G_.ncumcoeffs() * Base::G_.geoDim(), - Base::G_.ncumcoeffs() * Base::G_.geoDim() + - Base::f_.ncumcoeffs() * Base::f_.geoDim()) - .flatten()); - else if constexpr (Base::has_RefData) - Base::f_.from_tensor( - inputs.slice(1, 0, Base::f_.ncumcoeffs() * Base::f_.geoDim()) - .flatten()); - } - - this->epoch(epoch); - - auto closure = [&]() { - // Reset gradients - net_->zero_grad(); - - // Execute the model on the inputs - outputs = net_->forward(inputs); - - // Compute the loss value - loss = this->loss(outputs, epoch); - - // Compute gradients of the loss w.r.t. the model parameters - loss.backward({}, true, false); - - return loss; - }; - - // Update the parameters based on the calculated gradients - opt_.step(closure); - - Loss += loss.template item(); - } - - Log(log::verbose) << "Epoch " << std::to_string(epoch) << ": " << Loss - << std::endl; - - if (Loss < options_.min_loss()) { - Log(log::info) << "Total epochs: " << epoch << ", loss: " << Loss - << std::endl; - break; - } - - if (epoch == options_.max_epoch() - 1) - Log(log::warning) << "Total epochs: " << epoch << ", loss: " << Loss - << std::endl; - } - } - - /// @brief Returns the IgANet object as JSON object - inline virtual nlohmann::json to_json() const override { - return "Not implemented yet"; - } - - /// @brief Returns a string representation of the IgANet object - inline virtual void - pretty_print(std::ostream &os = Log(log::info)) const noexcept override { - os << name() << "(\n" - << "net = " << net_ << "\n"; - if constexpr (Base::has_GeometryMap) - os << "G = " << Base::G_ << "\n"; - if constexpr (Base::has_RefData) - os << "f = " << Base::f_ << "\n"; - if constexpr (Base::has_Solution) - os << "u = " << Base::u_ << "\n)"; - } - - /// @brief Saves the IgANet to file - inline void save(const std::string &filename, - const std::string &key = "iganet") const { - torch::serialize::OutputArchive archive; - write(archive, key).save_to(filename); - } - - /// @brief Loads the IgANet from file - inline void load(const std::string &filename, - const std::string &key = "iganet") { - torch::serialize::InputArchive archive; - archive.load_from(filename); - read(archive, key); - } - - /// @brief Writes the IgANet into a torch::serialize::OutputArchive object - inline torch::serialize::OutputArchive & - write(torch::serialize::OutputArchive &archive, - const std::string &key = "iganet") const { - if constexpr (Base::has_GeometryMap) - Base::G_.write(archive, key + ".geo"); - if constexpr (Base::has_RefData) - Base::f_.write(archive, key + ".ref"); - if constexpr (Base::has_Solution) - Base::u_.write(archive, key + ".out"); - - net_->write(archive, key + ".net"); - torch::serialize::OutputArchive archive_net; - net_->save(archive_net); - archive.write(key + ".net.data", archive_net); - - torch::serialize::OutputArchive archive_opt; - opt_.save(archive_opt); - archive.write(key + ".opt", archive_opt); - - return archive; - } - - /// @brief Loads the IgANet from a torch::serialize::InputArchive object - inline torch::serialize::InputArchive & - read(torch::serialize::InputArchive &archive, - const std::string &key = "iganet") { - if constexpr (Base::has_GeometryMap) - Base::G_.read(archive, key + ".geo"); - if constexpr (Base::has_RefData) - Base::f_.read(archive, key + ".ref"); - if constexpr (Base::has_Solution) - Base::u_.read(archive, key + ".out"); - - net_->read(archive, key + ".net"); - torch::serialize::InputArchive archive_net; - archive.read(key + ".net.data", archive_net); - net_->load(archive_net); - - opt_.add_parameters(net_->parameters()); - torch::serialize::InputArchive archive_opt; - archive.read(key + ".opt", archive_opt); - opt_.load(archive_opt); - - return archive; - } - - /// @brief Returns true if both IgANet objects are the same - bool operator==(const IgANet &other) const { - bool result(true); - - if constexpr (Base::has_GeometryMap) - result *= (Base::G_ == other.G()); - if constexpr (Base::has_RefData) - result *= (Base::f_ == other.f()); - if constexpr (Base::has_Solution) - result *= (Base::u_ == other.u()); - - return result; - } - - /// @brief Returns true if both IgANet objects are different - bool operator!=(const IgANet &other) const { return *this != other; } - -#ifdef IGANET_WITH_MPI -private: - /// @brief Waits for all work processes - static void waitWork(c10::intrusive_ptr pg, - std::vector> works) { - for (auto &work : works) { - try { - work->wait(); - } catch (const std::exception &ex) { - Log(log::error) << "Exception received during waitWork: " << ex.what() - << std::endl; - pg->abort(); - } - } - } -#endif -}; - -/// @brief Print (as string) a IgANet object -template -inline std::ostream & -operator<<(std::ostream &os, - const IgANet &obj) { - obj.pretty_print(os); - return os; -} - -/// @brief IgANetCustomizable -/// -/// This class implements a customizable variant of IgANets that -/// provides types and attributes for precomputing indices and basis -/// functions -template class IgANetCustomizable { -public: - /// @brief Type of the knot indices of the geometry map in the interior - using geometryMap_interior_knot_indices_type = - decltype(std::declval() - .template find_knot_indices( - std::declval())); - - /// @brief Type of the knot indices of the geometry map at the boundary - using geometryMap_boundary_knot_indices_type = - decltype(std::declval() - .template find_knot_indices( - std::declval< - typename GeometryMap::boundary_eval_type>())); - - /// @brief Type of the knot indices of the variables in the interior - using variable_interior_knot_indices_type = - decltype(std::declval() - .template find_knot_indices( - std::declval())); - - /// @brief Type of the knot indices of boundary_eval_type type at the boundary - using variable_boundary_knot_indices_type = - decltype(std::declval() - .template find_knot_indices( - std::declval())); - - /// @brief Type of the coefficient indices of geometry type in the interior - using geometryMap_interior_coeff_indices_type = - decltype(std::declval() - .template find_coeff_indices( - std::declval())); - - /// @brief Type of the coefficient indices of geometry type at the boundary - using geometryMap_boundary_coeff_indices_type = - decltype(std::declval() - .template find_coeff_indices( - std::declval< - typename GeometryMap::boundary_eval_type>())); - - /// @brief Type of the coefficient indices of variable type in the interior - using variable_interior_coeff_indices_type = - decltype(std::declval() - .template find_coeff_indices( - std::declval())); - - /// @brief Type of the coefficient indices of variable type at the boundary - using variable_boundary_coeff_indices_type = - decltype(std::declval() - .template find_coeff_indices( - std::declval())); -}; - -} // namespace iganet +/** + @file include/iganet.hpp + + @brief Isogeometric analysis network + + @author Matthias Moller + + @copyright This file is part of the IgANet project + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#pragma once + +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace iganet { + +/// @brief IgANetOptions +struct IgANetOptions { + TORCH_ARG(int64_t, max_epoch) = 100; + TORCH_ARG(int64_t, batch_size) = 1000; + TORCH_ARG(double, min_loss) = 1e-4; +}; + +/// @brief IgANetGeneratorImpl +/// +/// @note Following the discussion of module overship here +/// +/// https://pytorch.org/tutorials/advanced/cpp_frontend.html#module-ownership +/// +/// we implement a generator implementation class following +/// +/// https://pytorch.org/tutorials/advanced/cpp_frontend.html#the-generator-module +template +class IgANetGeneratorImpl : public torch::nn::Module { +public: + /// @brief Default constructor + IgANetGeneratorImpl() = default; + + /// @brief Constructor + explicit IgANetGeneratorImpl( + const std::vector &layers, + const std::vector> &activations, + Options options = Options{}) { + assert(layers.size() == activations.size() + 1); + + // Generate vector of linear layers and register them as layer[i] + for (auto i = 0; i < layers.size() - 1; ++i) { + layers_.emplace_back( + register_module("layer[" + std::to_string(i) + "]", + torch::nn::Linear(layers[i], layers[i + 1]))); + layers_.back()->to(options.device(), options.dtype(), true); + + torch::nn::init::xavier_uniform_(layers_.back()->weight); + torch::nn::init::constant_(layers_.back()->bias, 0.0); + } + + // Generate vector of activation functions + for (const auto &a : activations) + switch (std::any_cast(a[0])) { + // No activation function + case activation::none: + switch (a.size()) { + case 1: + activations_.emplace_back(new None{}); + break; + default: + throw std::runtime_error("Invalid number of parameters"); + } + break; + + // Batch Normalization + case activation::batch_norm: + switch (a.size()) { + case 8: + activations_.emplace_back(new BatchNorm{ + std::any_cast(a[1]), + std::any_cast(a[2]), + std::any_cast(a[3]), + std::any_cast(a[4]), std::any_cast(a[5]), + std::any_cast(a[6]), std::any_cast(a[7])}); + break; + case 7: + activations_.emplace_back(new BatchNorm{ + std::any_cast(a[1]), + std::any_cast(a[2]), + std::any_cast(a[3]), + std::any_cast(a[4]), std::any_cast(a[5]), + std::any_cast(a[6])}); + break; + case 4: + activations_.emplace_back(new BatchNorm{ + std::any_cast(a[1]), + std::any_cast(a[2]), + std::any_cast( + a[3])}); + break; + case 3: + activations_.emplace_back( + new BatchNorm{std::any_cast(a[1]), + std::any_cast(a[2])}); + break; + default: + throw std::runtime_error("Invalid number of parameters"); + } + break; + + // CELU + case activation::celu: + switch (a.size()) { + case 3: + activations_.emplace_back( + new CELU{std::any_cast(a[1]), std::any_cast(a[2])}); + break; + case 2: + try { + activations_.emplace_back(new CELU{ + std::any_cast(a[1])}); + } catch (...) { + activations_.emplace_back(new CELU{std::any_cast(a[1])}); + } + break; + case 1: + activations_.emplace_back(new CELU{}); + break; + default: + throw std::runtime_error("Invalid number of parameters"); + } + break; + + // ELU + case activation::elu: + switch (a.size()) { + case 3: + activations_.emplace_back( + new ELU{std::any_cast(a[1]), std::any_cast(a[2])}); + break; + case 2: + try { + activations_.emplace_back(new ELU{ + std::any_cast(a[1])}); + } catch (...) { + activations_.emplace_back(new ELU{std::any_cast(a[1])}); + } + break; + case 1: + activations_.emplace_back(new ELU{}); + break; + default: + throw std::runtime_error("Invalid number of parameters"); + } + break; + + // GELU + case activation::gelu: + switch (a.size()) { + case 1: + activations_.emplace_back(new GELU{}); + break; + default: + throw std::runtime_error("Invalid number of parameters"); + } + break; + + // GLU + case activation::glu: + switch (a.size()) { + case 2: + try { + activations_.emplace_back(new GLU{ + std::any_cast(a[1])}); + } catch (...) { + activations_.emplace_back(new GLU{std::any_cast(a[1])}); + } + break; + case 1: + activations_.emplace_back(new GLU{}); + break; + default: + throw std::runtime_error("Invalid number of parameters"); + } + break; + + // Group Normalization + case activation::group_norm: + switch (a.size()) { + case 5: + activations_.emplace_back(new GroupNorm{ + std::any_cast(a[1]), std::any_cast(a[2]), + std::any_cast(a[3]), std::any_cast(a[4])}); + break; + case 2: + try { + activations_.emplace_back(new GroupNorm{ + std::any_cast( + a[1])}); + } catch (...) { + activations_.emplace_back( + new GroupNorm{std::any_cast(a[1])}); + } + break; + default: + throw std::runtime_error("Invalid number of parameters"); + } + break; + + // Gumbel-Softmax + case activation::gumbel_softmax: + switch (a.size()) { + case 4: + activations_.emplace_back(new GumbelSoftmax{ + std::any_cast(a[1]), std::any_cast(a[2]), + std::any_cast(a[3])}); + break; + case 2: + activations_.emplace_back(new GumbelSoftmax{ + std::any_cast( + a[1])}); + break; + case 1: + activations_.emplace_back(new GumbelSoftmax{}); + break; + default: + throw std::runtime_error("Invalid number of parameters"); + } + break; + + // Hard shrinkish + case activation::hardshrink: + switch (a.size()) { + case 2: + try { + activations_.emplace_back(new Hardshrink{ + std::any_cast( + a[1])}); + } catch (...) { + activations_.emplace_back( + new Hardshrink{std::any_cast(a[1])}); + } + break; + case 1: + activations_.emplace_back(new Hardshrink{}); + break; + default: + throw std::runtime_error("Invalid number of parameters"); + } + break; + + // Hardsigmoid + case activation::hardsigmoid: + switch (a.size()) { + case 1: + activations_.emplace_back(new Hardsigmoid{}); + break; + default: + throw std::runtime_error("Invalid number of parameters"); + } + break; + + // Hardswish + case activation::hardswish: + switch (a.size()) { + case 1: + activations_.emplace_back(new Hardswish{}); + break; + default: + throw std::runtime_error("Invalid number of parameters"); + } + break; + + // Hardtanh + case activation::hardtanh: + switch (a.size()) { + case 4: + activations_.emplace_back(new Hardtanh{std::any_cast(a[1]), + std::any_cast(a[2]), + std::any_cast(a[3])}); + break; + case 3: + activations_.emplace_back(new Hardtanh{std::any_cast(a[1]), + std::any_cast(a[2])}); + break; + case 2: + activations_.emplace_back(new Hardtanh{ + std::any_cast(a[1])}); + break; + case 1: + activations_.emplace_back(new Hardtanh{}); + break; + default: + throw std::runtime_error("Invalid number of parameters"); + } + break; + + // Instance Normalization + case activation::instance_norm: + switch (a.size()) { + case 8: + activations_.emplace_back(new InstanceNorm{ + std::any_cast(a[1]), + std::any_cast(a[2]), + std::any_cast(a[3]), + std::any_cast(a[4]), std::any_cast(a[5]), + std::any_cast(a[6]), std::any_cast(a[7])}); + break; + case 7: + activations_.emplace_back(new InstanceNorm{ + std::any_cast(a[1]), + std::any_cast(a[2]), + std::any_cast(a[3]), + std::any_cast(a[4]), std::any_cast(a[5]), + std::any_cast(a[6])}); + break; + case 2: + activations_.emplace_back(new InstanceNorm{ + std::any_cast( + a[1])}); + break; + case 1: + activations_.emplace_back(new InstanceNorm{}); + break; + default: + throw std::runtime_error("Invalid number of parameters"); + } + break; + + // Layer Normalization + case activation::layer_norm: + switch (a.size()) { + case 5: + activations_.emplace_back(new LayerNorm{ + std::any_cast>(a[1]), + std::any_cast(a[2]), + std::any_cast(a[3]), std::any_cast(a[4])}); + break; + case 2: + try { + activations_.emplace_back(new LayerNorm{ + std::any_cast( + a[1])}); + } catch (...) { + activations_.emplace_back( + new LayerNorm{std::any_cast>(a[1])}); + } + break; + default: + throw std::runtime_error("Invalid number of parameters"); + } + break; + + // Leaky ReLU + case activation::leaky_relu: + switch (a.size()) { + case 3: + activations_.emplace_back(new LeakyReLU{std::any_cast(a[1]), + std::any_cast(a[2])}); + break; + case 2: + try { + activations_.emplace_back(new LeakyReLU{ + std::any_cast( + a[1])}); + } catch (...) { + activations_.emplace_back( + new LeakyReLU{std::any_cast(a[1])}); + } + break; + case 1: + activations_.emplace_back(new LeakyReLU{}); + break; + default: + throw std::runtime_error("Invalid number of parameters"); + } + break; + + // Local response Normalization + case activation::local_response_norm: + switch (a.size()) { + case 5: + activations_.emplace_back(new LocalResponseNorm{ + std::any_cast(a[1]), std::any_cast(a[2]), + std::any_cast(a[3]), std::any_cast(a[4])}); + break; + case 2: + try { + activations_.emplace_back(new LocalResponseNorm{std::any_cast< + torch::nn::functional::LocalResponseNormFuncOptions>(a[1])}); + } catch (...) { + activations_.emplace_back( + new LocalResponseNorm{std::any_cast(a[1])}); + } + break; + default: + throw std::runtime_error("Invalid number of parameters"); + } + break; + + // LogSigmoid + case activation::logsigmoid: + switch (a.size()) { + case 1: + activations_.emplace_back(new LogSigmoid{}); + break; + default: + throw std::runtime_error("Invalid number of parameters"); + } + break; + + // LogSoftmax + case activation::logsoftmax: + switch (a.size()) { + case 2: + try { + activations_.emplace_back(new LogSoftmax{ + std::any_cast( + a[1])}); + } catch (...) { + activations_.emplace_back( + new LogSoftmax{std::any_cast(a[1])}); + } + break; + default: + throw std::runtime_error("Invalid number of parameters"); + } + break; + + // Mish + case activation::mish: + switch (a.size()) { + case 1: + activations_.emplace_back(new Mish{}); + break; + default: + throw std::runtime_error("Invalid number of parameters"); + } + break; + + // Lp Normalization + case activation::normalize: + switch (a.size()) { + case 4: + activations_.emplace_back(new Normalize{ + std::any_cast(a[1]), std::any_cast(a[2]), + std::any_cast(a[3])}); + break; + case 2: + activations_.emplace_back(new Normalize{ + std::any_cast( + a[1])}); + break; + case 1: + activations_.emplace_back(new Normalize{}); + break; + default: + throw std::runtime_error("Invalid number of parameters"); + } + break; + + // PReLU + case activation::prelu: + switch (a.size()) { + case 2: + activations_.emplace_back( + new PReLU{std::any_cast(a[1])}); + break; + default: + throw std::runtime_error("Invalid number of parameters"); + } + break; + + // ReLU + case activation::relu: + switch (a.size()) { + case 2: + try { + activations_.emplace_back(new ReLU{ + std::any_cast(a[1])}); + } catch (...) { + activations_.emplace_back(new ReLU{std::any_cast(a[1])}); + } + break; + case 1: + activations_.emplace_back(new ReLU{}); + break; + default: + throw std::runtime_error("Invalid number of parameters"); + } + break; + + // Relu6 + case activation::relu6: + switch (a.size()) { + case 2: + try { + activations_.emplace_back(new ReLU6{ + std::any_cast(a[1])}); + } catch (...) { + activations_.emplace_back(new ReLU6{std::any_cast(a[1])}); + } + break; + case 1: + activations_.emplace_back(new ReLU6{}); + break; + default: + throw std::runtime_error("Invalid number of parameters"); + } + break; + + // Randomized ReLU + case activation::rrelu: + switch (a.size()) { + case 4: + activations_.emplace_back(new RReLU{std::any_cast(a[1]), + std::any_cast(a[2]), + std::any_cast(a[3])}); + break; + case 3: + activations_.emplace_back(new RReLU{std::any_cast(a[1]), + std::any_cast(a[2])}); + break; + case 2: + activations_.emplace_back(new RReLU{ + std::any_cast(a[1])}); + break; + case 1: + activations_.emplace_back(new RReLU{}); + break; + default: + throw std::runtime_error("Invalid number of parameters"); + } + break; + + // SELU + case activation::selu: + switch (a.size()) { + case 2: + try { + activations_.emplace_back(new SELU{ + std::any_cast(a[1])}); + } catch (...) { + activations_.emplace_back(new SELU{std::any_cast(a[1])}); + } + break; + case 1: + activations_.emplace_back(new SELU{}); + break; + default: + throw std::runtime_error("Invalid number of parameters"); + } + break; + + // Sigmoid + case activation::sigmoid: + switch (a.size()) { + case 1: + activations_.emplace_back(new Sigmoid{}); + break; + default: + throw std::runtime_error("Invalid number of parameters"); + } + break; + + // SiLU + case activation::silu: + switch (a.size()) { + case 1: + activations_.emplace_back(new SiLU{}); + break; + default: + throw std::runtime_error("Invalid number of parameters"); + } + break; + + // Softmax + case activation::softmax: + switch (a.size()) { + case 2: + try { + activations_.emplace_back(new Softmax{ + std::any_cast( + a[1])}); + } catch (...) { + activations_.emplace_back( + new Softmax{std::any_cast(a[1])}); + } + break; + default: + throw std::runtime_error("Invalid number of parameters"); + } + break; + + // Softmin + case activation::softmin: + switch (a.size()) { + case 2: + try { + activations_.emplace_back(new Softmin{ + std::any_cast( + a[1])}); + } catch (...) { + activations_.emplace_back( + new Softmin{std::any_cast(a[1])}); + } + break; + default: + throw std::runtime_error("Invalid number of parameters"); + } + break; + + // Softplus + case activation::softplus: + switch (a.size()) { + case 3: + activations_.emplace_back(new Softplus{std::any_cast(a[1]), + std::any_cast(a[2])}); + break; + case 2: + activations_.emplace_back(new Softplus{ + std::any_cast(a[1])}); + break; + case 1: + activations_.emplace_back(new Softplus{}); + break; + default: + throw std::runtime_error("Invalid number of parameters"); + } + break; + + // Softshrink + case activation::softshrink: + switch (a.size()) { + case 2: + try { + activations_.emplace_back(new Softshrink{ + std::any_cast( + a[1])}); + } catch (...) { + activations_.emplace_back( + new Softshrink{std::any_cast(a[1])}); + } + break; + case 1: + activations_.emplace_back(new Softshrink{}); + break; + default: + throw std::runtime_error("Invalid number of parameters"); + } + break; + + // Softsign + case activation::softsign: + switch (a.size()) { + case 1: + activations_.emplace_back(new Softsign{}); + break; + default: + throw std::runtime_error("Invalid number of parameters"); + } + break; + + // Tanh + case activation::tanh: + switch (a.size()) { + case 1: + activations_.emplace_back(new Tanh{}); + break; + default: + throw std::runtime_error("Invalid number of parameters"); + } + break; + + // Tanhshrink + case activation::tanhshrink: + switch (a.size()) { + case 1: + activations_.emplace_back(new Tanhshrink{}); + break; + default: + throw std::runtime_error("Invalid number of parameters"); + } + break; + + // Threshold + case activation::threshold: + switch (a.size()) { + case 4: + activations_.emplace_back(new Threshold{std::any_cast(a[1]), + std::any_cast(a[2]), + std::any_cast(a[3])}); + break; + case 3: + activations_.emplace_back(new Threshold{std::any_cast(a[1]), + std::any_cast(a[2])}); + break; + case 2: + activations_.emplace_back(new Threshold{ + std::any_cast( + a[1])}); + break; + default: + throw std::runtime_error("Invalid number of parameters"); + } + break; + + default: + throw std::runtime_error("Invalid activation function"); + } + } + + /// @brief Forward evaluation + torch::Tensor forward(torch::Tensor x) { + torch::Tensor x_in = x.clone(); + + // Standard feed-forward neural network + for (auto [layer, activation] : utils::zip(layers_, activations_)) + x = activation->apply(layer->forward(x)); + + return x; + } + + /// @brief Writes the IgANet into a torch::serialize::OutputArchive object + inline torch::serialize::OutputArchive & + write(torch::serialize::OutputArchive &archive, + const std::string &key = "iganet") const { + assert(layers_.size() == activations_.size()); + + archive.write(key + ".layers", torch::full({1}, (int64_t)layers_.size())); + for (std::size_t i = 0; i < layers_.size(); ++i) { + archive.write( + key + ".layer[" + std::to_string(i) + "].in_features", + torch::full({1}, (int64_t)layers_[i]->options.in_features())); + archive.write( + key + ".layer[" + std::to_string(i) + "].outputs_features", + torch::full({1}, (int64_t)layers_[i]->options.out_features())); + archive.write(key + ".layer[" + std::to_string(i) + "].bias", + torch::full({1}, (int64_t)layers_[i]->options.bias())); + + activations_[i]->write(archive, key + ".layer[" + std::to_string(i) + + "].activation"); + } + + return archive; + } + + /// @brief Reads the IgANet from a torch::serialize::InputArchive object + inline torch::serialize::InputArchive & + read(torch::serialize::InputArchive &archive, + const std::string &key = "iganet") { + torch::Tensor layers, in_features, outputs_features, bias, activation; + + archive.read(key + ".layers", layers); + for (int64_t i = 0; i < layers.item(); ++i) { + archive.read(key + ".layer[" + std::to_string(i) + "].in_features", + in_features); + archive.read(key + ".layer[" + std::to_string(i) + "].outputs_features", + outputs_features); + archive.read(key + ".layer[" + std::to_string(i) + "].bias", bias); + layers_.emplace_back(register_module( + "layer[" + std::to_string(i) + "]", + torch::nn::Linear( + torch::nn::LinearOptions(in_features.item(), + outputs_features.item()) + .bias(bias.item())))); + + archive.read(key + ".layer[" + std::to_string(i) + "].activation.type", + activation); + switch (static_cast(activation.item())) { + case activation::none: + activations_.emplace_back(new None{}); + break; + case activation::batch_norm: + activations_.emplace_back( + new BatchNorm{torch::Tensor{}, torch::Tensor{}}); + break; + case activation::celu: + activations_.emplace_back(new CELU{}); + break; + case activation::elu: + activations_.emplace_back(new ELU{}); + break; + case activation::gelu: + activations_.emplace_back(new GELU{}); + break; + case activation::glu: + activations_.emplace_back(new GLU{}); + break; + case activation::group_norm: + activations_.emplace_back(new GroupNorm{0}); + break; + case activation::gumbel_softmax: + activations_.emplace_back(new GumbelSoftmax{}); + break; + case activation::hardshrink: + activations_.emplace_back(new Hardshrink{}); + break; + case activation::hardsigmoid: + activations_.emplace_back(new Hardsigmoid{}); + break; + case activation::hardswish: + activations_.emplace_back(new Hardswish{}); + break; + case activation::hardtanh: + activations_.emplace_back(new Hardtanh{}); + break; + case activation::instance_norm: + activations_.emplace_back(new InstanceNorm{}); + break; + case activation::layer_norm: + activations_.emplace_back(new LayerNorm{{}}); + break; + case activation::leaky_relu: + activations_.emplace_back(new LeakyReLU{}); + break; + case activation::local_response_norm: + activations_.emplace_back(new LocalResponseNorm{0}); + break; + case activation::logsigmoid: + activations_.emplace_back(new LogSigmoid{}); + break; + case activation::logsoftmax: + activations_.emplace_back(new LogSoftmax{0}); + break; + case activation::mish: + activations_.emplace_back(new Mish{}); + break; + case activation::normalize: + activations_.emplace_back(new Normalize{0, 0, 0}); + break; + case activation::prelu: + activations_.emplace_back(new PReLU{torch::Tensor{}}); + break; + case activation::relu: + activations_.emplace_back(new ReLU{}); + break; + case activation::relu6: + activations_.emplace_back(new ReLU6{}); + break; + case activation::rrelu: + activations_.emplace_back(new RReLU{}); + break; + case activation::selu: + activations_.emplace_back(new SELU{}); + break; + case activation::sigmoid: + activations_.emplace_back(new Sigmoid{}); + break; + case activation::silu: + activations_.emplace_back(new SiLU{}); + break; + case activation::softmax: + activations_.emplace_back(new Softmax{0}); + break; + case activation::softmin: + activations_.emplace_back(new Softmin{0}); + break; + case activation::softplus: + activations_.emplace_back(new Softplus{}); + break; + case activation::softshrink: + activations_.emplace_back(new Softshrink{}); + break; + case activation::softsign: + activations_.emplace_back(new Softsign{}); + break; + case activation::tanh: + activations_.emplace_back(new Tanh{}); + break; + case activation::tanhshrink: + activations_.emplace_back(new Tanhshrink{}); + break; + case activation::threshold: + activations_.emplace_back(new Threshold{0, 0}); + break; + default: + throw std::runtime_error("Invalid activation function"); + } + activations_.back()->read(archive, key + ".layer[" + std::to_string(i) + + "].activation"); + } + return archive; + } + + inline virtual void + pretty_print(std::ostream &os = Log(log::info)) const noexcept override { + os << "(\n"; + + int i = 0; + for (const auto &activation : activations_) + os << "activation[" << i++ << "]: " << *activation << "\n"; + os << ")\n"; + } + +private: + /// @brief Vector of linear layers + std::vector layers_; + + /// @brief Vector of activation functions + std::vector> activations_; +}; + +/// @brief IgANetGenerator +/// +/// @note: This class is normally generated by the TORCH_MODULE +/// macro. Since the latter cannot handle templated classes +/// correctly, we give the implementation explicitly +template +class IgANetGenerator + : public torch::nn::ModuleHolder> { + +public: + using torch::nn::ModuleHolder>::ModuleHolder; + using Impl = IgANetGeneratorImpl; +}; + +/// @brief IgANet +/// +/// This class implements the core functionality of IgANets +template typename IgABase = ::iganet::IgABase> +class IgANet : public IgABase, + utils::Serializable, + private utils::FullQualifiedName { +public: + /// @brief Base type + using Base = IgABase; + + /// @brief Type of the optimizer + using optimizer_type = Optimizer; + +protected: + /// @brief IgANet generator + IgANetGenerator net_; + + /// @brief Optimizer + Optimizer opt_; + + /// @brief Options + IgANetOptions options_; + +public: + /// @brief Default constructor + explicit IgANet(IgANetOptions defaults = {}, + iganet::Options options = + iganet::Options{}) + : Base(), opt_(net_->parameters()), options_(defaults) {} + + /// @brief Constructor: number of layers, activation functions, and + /// number of spline coefficients (same for geometry map and + /// variables) + template + IgANet(const std::vector &layers, + const std::vector> &activations, + std::tuple splines, IgANetOptions defaults = {}, + iganet::Options options = + iganet::Options{}) + : IgANet(layers, activations, splines, splines, defaults, options) {} + + /// @brief Constructor: number of layers, activation functions, and + /// number of spline coefficients (different for geometry map and + /// variables) + template + IgANet(const std::vector &layers, + const std::vector> &activations, + std::tuple geometryMap_splines, + std::tuple variable_splines, + IgANetOptions defaults = {}, + iganet::Options options = + iganet::Options{}) + : Base(geometryMap_splines, variable_splines, options), + // Construct the deep neural network + net_(utils::concat(std::vector{inputs(/* epoch */ 0).size(0)}, + layers, + std::vector{Base::u_.as_tensor_size()}), + activations, options), + + // Construct the optimizer + opt_(net_->parameters()), + + // Set options + options_(defaults) {} + + /// @brief Returns a constant reference to the IgANet generator + inline const IgANetGenerator &net() const { + return net_; + } + + /// @brief Returns a non-constant reference to the IgANet generator + inline IgANetGenerator &net() { return net_; } + + /// @brief Returns a constant reference to the optimizer + inline const Optimizer &opt() const { return opt_; } + + /// @brief Returns a non-constant reference to the optimizer + inline Optimizer &opt() { return opt_; } + + /// @brief Returns a constant reference to the options structure + inline const auto &options() const { return options_; } + + /// @brief Returns a non-constant reference to the options structure + inline auto &options() { return options_; } + + /// @brief Returns the network inputs + /// + /// In the default implementation the inputs are the controll + /// points of the geometry and the reference spline objects. This + /// behavior can be changed by overriding this virtual function in + /// a derived class. + virtual torch::Tensor inputs(int64_t epoch) const { + if constexpr (Base::has_GeometryMap && Base::has_RefData) + return torch::cat({Base::G_.as_tensor(), Base::f_.as_tensor()}); + else if constexpr (Base::has_GeometryMap) + return Base::G_.as_tensor(); + else if constexpr (Base::has_RefData) + return Base::f_.as_tensor(); + else + return torch::empty({0}); + } + + /// @brief Initializes epoch + virtual bool epoch(int64_t) = 0; + + /// @brief Computes the loss function + virtual torch::Tensor loss(const torch::Tensor &, int64_t) = 0; + + /// @brief Trains the IgANet + virtual void train( +#ifdef IGANET_WITH_MPI + c10::intrusive_ptr pg +#endif + ) { + torch::Tensor inputs, outputs, loss; + + // Loop over epochs + for (int64_t epoch = 0; epoch != options_.max_epoch(); ++epoch) { + + // Update epoch and inputs + if (this->epoch(epoch)) + inputs = this->inputs(epoch); + + auto closure = [&]() { + // Reset gradients + net_->zero_grad(); + + // Execute the model on the inputs + outputs = net_->forward(inputs); + + // Compute the loss value + loss = this->loss(outputs, epoch); + + // Compute gradients of the loss w.r.t. the model parameters + loss.backward({}, true, false); + + return loss; + }; + +#ifdef IGANET_WITH_MPI + // Averaging the gradients of the parameters in all the processors + // Note: This may lag behind DistributedDataParallel (DDP) in performance + // since this synchronizes parameters after backward pass while DDP + // overlaps synchronizing parameters and computing gradients in backward + // pass + std::vector> works; + for (auto ¶m : net_->named_parameters()) { + std::vector tmp = {param.value().grad()}; + works.emplace_back(pg->allreduce(tmp)); + } + + waitWork(pg, works); + + for (auto ¶m : net_->named_parameters()) { + param.value().grad().data() = + param.value().grad().data() / pg->getSize(); + } +#endif + + // Update the parameters based on the calculated gradients + opt_.step(closure); + + Log(log::verbose) << "Epoch " << std::to_string(epoch) << ": " + << loss.template item() + << std::endl; + + if (loss.template item() < + options_.min_loss()) { + Log(log::info) << "Total epochs: " << epoch << ", loss: " + << loss.template item() + << std::endl; + break; + } + } + } + + /// @brief Trains the IgANet + template + void train(DataLoader &loader +#ifdef IGANET_WITH_MPI + , + c10::intrusive_ptr pg +#endif + ) { + torch::Tensor inputs, outputs, loss; + + // Loop over epochs + for (int64_t epoch = 0; epoch != options_.max_epoch(); ++epoch) { + + typename Base::value_type Loss(0); + + for (auto &batch : loader) { + inputs = batch.data; + + if (inputs.dim() > 0) { + if constexpr (Base::has_GeometryMap) + Base::G_.from_tensor( + inputs.slice(1, 0, Base::G_.ncumcoeffs() * Base::G_.geoDim()) + .t()); + if constexpr (Base::has_RefData && Base::has_GeometryMap) + Base::f_.from_tensor( + inputs + .slice(1, Base::G_.ncumcoeffs() * Base::G_.geoDim(), + Base::G_.ncumcoeffs() * Base::G_.geoDim() + + Base::f_.ncumcoeffs() * Base::f_.geoDim()) + .t()); + else if constexpr (Base::has_RefData) + Base::f_.from_tensor( + inputs.slice(1, 0, Base::f_.ncumcoeffs() * Base::f_.geoDim()) + .t()); + } else { + if constexpr (Base::has_GeometryMap) + Base::G_.from_tensor( + inputs.slice(1, 0, Base::G_.ncumcoeffs() * Base::G_.geoDim()) + .flatten()); + if constexpr (Base::has_RefData && Base::has_GeometryMap) + Base::f_.from_tensor( + inputs + .slice(1, Base::G_.ncumcoeffs() * Base::G_.geoDim(), + Base::G_.ncumcoeffs() * Base::G_.geoDim() + + Base::f_.ncumcoeffs() * Base::f_.geoDim()) + .flatten()); + else if constexpr (Base::has_RefData) + Base::f_.from_tensor( + inputs.slice(1, 0, Base::f_.ncumcoeffs() * Base::f_.geoDim()) + .flatten()); + } + + this->epoch(epoch); + + auto closure = [&]() { + // Reset gradients + net_->zero_grad(); + + // Execute the model on the inputs + outputs = net_->forward(inputs); + + // Compute the loss value + loss = this->loss(outputs, epoch); + + // Compute gradients of the loss w.r.t. the model parameters + loss.backward({}, true, false); + + return loss; + }; + + // Update the parameters based on the calculated gradients + opt_.step(closure); + + Loss += loss.template item(); + } + + Log(log::verbose) << "Epoch " << std::to_string(epoch) << ": " << Loss + << std::endl; + + if (Loss < options_.min_loss()) { + Log(log::info) << "Total epochs: " << epoch << ", loss: " << Loss + << std::endl; + break; + } + + if (epoch == options_.max_epoch() - 1) + Log(log::warning) << "Total epochs: " << epoch << ", loss: " << Loss + << std::endl; + } + } + + /// @brief Returns the IgANet object as JSON object + inline virtual nlohmann::json to_json() const override { + return "Not implemented yet"; + } + + /// @brief Returns a string representation of the IgANet object + inline virtual void + pretty_print(std::ostream &os = Log(log::info)) const noexcept override { + os << name() << "(\n" + << "net = " << net_ << "\n"; + if constexpr (Base::has_GeometryMap) + os << "G = " << Base::G_ << "\n"; + if constexpr (Base::has_RefData) + os << "f = " << Base::f_ << "\n"; + if constexpr (Base::has_Solution) + os << "u = " << Base::u_ << "\n)"; + } + + /// @brief Saves the IgANet to file + inline void save(const std::string &filename, + const std::string &key = "iganet") const { + torch::serialize::OutputArchive archive; + write(archive, key).save_to(filename); + } + + /// @brief Loads the IgANet from file + inline void load(const std::string &filename, + const std::string &key = "iganet") { + torch::serialize::InputArchive archive; + archive.load_from(filename); + read(archive, key); + } + + /// @brief Writes the IgANet into a torch::serialize::OutputArchive object + inline torch::serialize::OutputArchive & + write(torch::serialize::OutputArchive &archive, + const std::string &key = "iganet") const { + if constexpr (Base::has_GeometryMap) + Base::G_.write(archive, key + ".geo"); + if constexpr (Base::has_RefData) + Base::f_.write(archive, key + ".ref"); + if constexpr (Base::has_Solution) + Base::u_.write(archive, key + ".out"); + + net_->write(archive, key + ".net"); + torch::serialize::OutputArchive archive_net; + net_->save(archive_net); + archive.write(key + ".net.data", archive_net); + + torch::serialize::OutputArchive archive_opt; + opt_.save(archive_opt); + archive.write(key + ".opt", archive_opt); + + return archive; + } + + /// @brief Loads the IgANet from a torch::serialize::InputArchive object + inline torch::serialize::InputArchive & + read(torch::serialize::InputArchive &archive, + const std::string &key = "iganet") { + if constexpr (Base::has_GeometryMap) + Base::G_.read(archive, key + ".geo"); + if constexpr (Base::has_RefData) + Base::f_.read(archive, key + ".ref"); + if constexpr (Base::has_Solution) + Base::u_.read(archive, key + ".out"); + + net_->read(archive, key + ".net"); + torch::serialize::InputArchive archive_net; + archive.read(key + ".net.data", archive_net); + net_->load(archive_net); + + opt_.add_parameters(net_->parameters()); + torch::serialize::InputArchive archive_opt; + archive.read(key + ".opt", archive_opt); + opt_.load(archive_opt); + + return archive; + } + + /// @brief Returns true if both IgANet objects are the same + bool operator==(const IgANet &other) const { + bool result(true); + + if constexpr (Base::has_GeometryMap) + result *= (Base::G_ == other.G()); + if constexpr (Base::has_RefData) + result *= (Base::f_ == other.f()); + if constexpr (Base::has_Solution) + result *= (Base::u_ == other.u()); + + return result; + } + + /// @brief Returns true if both IgANet objects are different + bool operator!=(const IgANet &other) const { return *this != other; } + +#ifdef IGANET_WITH_MPI +private: + /// @brief Waits for all work processes + static void waitWork(c10::intrusive_ptr pg, + std::vector> works) { + for (auto &work : works) { + try { + work->wait(); + } catch (const std::exception &ex) { + Log(log::error) << "Exception received during waitWork: " << ex.what() + << std::endl; + pg->abort(); + } + } + } +#endif +}; + +/// @brief Print (as string) a IgANet object +template +inline std::ostream & +operator<<(std::ostream &os, + const IgANet &obj) { + obj.pretty_print(os); + return os; +} + +/// @brief IgANetCustomizable +/// +/// This class implements a customizable variant of IgANets that +/// provides types and attributes for precomputing indices and basis +/// functions +template class IgANetCustomizable { +public: + /// @brief Type of the knot indices of the geometry map in the interior + using geometryMap_interior_knot_indices_type = + decltype(std::declval() + .template find_knot_indices( + std::declval())); + + /// @brief Type of the knot indices of the geometry map at the boundary + using geometryMap_boundary_knot_indices_type = + decltype(std::declval() + .template find_knot_indices( + std::declval< + typename GeometryMap::boundary_eval_type>())); + + /// @brief Type of the knot indices of the variables in the interior + using variable_interior_knot_indices_type = + decltype(std::declval() + .template find_knot_indices( + std::declval())); + + /// @brief Type of the knot indices of boundary_eval_type type at the boundary + using variable_boundary_knot_indices_type = + decltype(std::declval() + .template find_knot_indices( + std::declval())); + + /// @brief Type of the coefficient indices of geometry type in the interior + using geometryMap_interior_coeff_indices_type = + decltype(std::declval() + .template find_coeff_indices( + std::declval())); + + /// @brief Type of the coefficient indices of geometry type at the boundary + using geometryMap_boundary_coeff_indices_type = + decltype(std::declval() + .template find_coeff_indices( + std::declval< + typename GeometryMap::boundary_eval_type>())); + + /// @brief Type of the coefficient indices of variable type in the interior + using variable_interior_coeff_indices_type = + decltype(std::declval() + .template find_coeff_indices( + std::declval())); + + /// @brief Type of the coefficient indices of variable type at the boundary + using variable_boundary_coeff_indices_type = + decltype(std::declval() + .template find_coeff_indices( + std::declval())); +}; + +} // namespace iganet diff --git a/include/options.hpp b/include/options.hpp index ff22c3f5..58fe472b 100644 --- a/include/options.hpp +++ b/include/options.hpp @@ -1,207 +1,207 @@ -/** - @file include/options.hpp - - @brief Options - - @author Matthias Moller - - @copyright This file is part of the IgANet project - - This Source Code Form is subject to the terms of the Mozilla Public - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at http://mozilla.org/MPL/2.0/. -*/ - -#pragma once - -#include -#include -#include - -#include - -namespace iganet { - -struct half {}; - -/// Determines the LibTorch dtype from template parameter -/// -/// @tparam T C++ type -/// -/// @result Torch type corresponding to the C++ type -/// @{ -template inline constexpr torch::Dtype dtype(); - -template <> inline constexpr torch::Dtype dtype() { return torch::kBool; } - -template <> inline constexpr torch::Dtype dtype() { return torch::kChar; } - -template <> inline constexpr torch::Dtype dtype() { - return torch::kShort; -} - -template <> inline constexpr torch::Dtype dtype() { return torch::kInt; } - -template <> inline constexpr torch::Dtype dtype() { return torch::kLong; } - -template <> inline constexpr torch::Dtype dtype() { return torch::kHalf; } - -template <> inline constexpr torch::Dtype dtype() { - return torch::kFloat; -} - -template <> inline constexpr torch::Dtype dtype() { - return torch::kDouble; -} - -template <> inline constexpr torch::Dtype dtype>() { - return at::kComplexHalf; -} - -template <> inline constexpr torch::Dtype dtype>() { - return at::kComplexFloat; -} - -template <> inline constexpr torch::Dtype dtype>() { - return at::kComplexDouble; -} -/// @} - -inline int guess_device_index() { -#ifdef IGANET_WITH_MPI - int rank; - MPI_Comm_rank(MPI_COMM_WORLD, &rank); - return rank % - utils::getenv("IGANET_DEVICE_COUNT", torch::cuda::is_available - ? torch::cuda::device_count() - : 1); -#else - return 0; -#endif -} - -/// @brief The Options class handles the automated determination of -/// dtype from the template argument and the selection of the device -/// -/// @tparam real_t Type of real-valued data -template -class Options : private iganet::utils::FullQualifiedName { -public: - /// Default constructor - Options() - : options_( - torch::TensorOptions() - .dtype(::iganet::dtype()) - .device_index(utils::getenv("IGANET_DEVICE_INDEX", - iganet::guess_device_index())) - .device( - (utils::getenv("IGANET_DEVICE", std::string{}) == "CPU") - ? torch::kCPU - : (utils::getenv("IGANET_DEVICE", std::string{}) == "CUDA") - ? torch::kCUDA - : (utils::getenv("IGANET_DEVICE", std::string{}) == "HIP") - ? torch::kHIP - : (utils::getenv("IGANET_DEVICE", std::string{}) == "MPS") - ? torch::kMPS - : (utils::getenv("IGANET_DEVICE", std::string{}) == "XLA") - ? torch::kXLA - : (utils::getenv("IGANET_DEVICE", std::string{}) == "XPU") - ? torch::kXPU - : (torch::cuda::is_available() ? torch::kCUDA - : torch::kCPU))) {} - - /// Constructor from torch::TensorOptions - explicit Options(torch::TensorOptions &&options) : options_(options) {} - - /// @brief Implicit conversion operator - operator torch::TensorOptions() const { return options_; } - - /// @brief Returns the `device` property - torch::Device device() const noexcept { return options_.device(); } - - /// @brief Returns the `device_index` property - int32_t device_index() const noexcept { return options_.device_index(); } - - /// @brief Returns the `dtype` property - torch::Dtype dtype() const noexcept { return ::iganet::dtype(); } - - /// @brief Returns the `layout` property - torch::Layout layout() const noexcept { return options_.layout(); } - - /// @brief Returns the `requires_grad` property - bool requires_grad() const noexcept { return options_.requires_grad(); } - - /// @brief Returns the `pinned_memory` property - bool pinned_memory() const noexcept { return options_.pinned_memory(); } - - /// @brief Returns if the layout is sparse - bool is_sparse() const noexcept { return options_.is_sparse(); } - - /// @brief Returns a new Options object with the `device` property as given - Options device(torch::Device device) const noexcept { - return Options(options_.device(device)); - } - - /// @brief Returns a new Options object with the `device_index` property as - /// given - Options device_index(int16_t device_index) const noexcept { - return Options(options_.device_index(device_index)); - } - - /// @brief Returns a new Options object with the `dtype` property as given - template Options dtype() const noexcept { - return Options(options_.dtype(::iganet::dtype())); - } - - /// @brief Returns a new Options object with the `layout` property as given - Options layout(torch::Layout layout) const noexcept { - return Options(options_.layout(layout)); - } - - /// @brief Returns a new Options object with the `requires_grad` property as - /// given - Options requires_grad(bool requires_grad) const noexcept { - return Options(options_.requires_grad(requires_grad)); - } - - /// @brief Returns a new Options object with the `pinned_memory` property as - /// given - Options pinned_memory(bool pinned_memory) const noexcept { - return Options(options_.pinned_memory(pinned_memory)); - } - - /// @brief Returns a new Options object with the `memory_format` property as - /// given - Options - memory_format(torch::MemoryFormat memory_format) const noexcept { - return Options(options_.memory_format(memory_format)); - } - - /// @brief Data type - using value_type = real_t; - - /// @brief Returns a string representation of the Options object - inline virtual void - pretty_print(std::ostream &os = Log(log::info)) const noexcept override { - os << name() << "(\noptions = " << options_ << "\n)"; - } - -private: - /// @brief Tensor options - const torch::TensorOptions options_; -}; - -/// @brief Print (as string) a Options object -template -inline std::ostream &operator<<(std::ostream &os, const Options &obj) { - obj.pretty_print(os); - return os; -} - -/// @brief Options dispatcher -template -class Options> : public Options { - using Options::Options; -}; - -} // namespace iganet +/** + @file include/options.hpp + + @brief Options + + @author Matthias Moller + + @copyright This file is part of the IgANet project + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#pragma once + +#include +#include +#include + +#include + +namespace iganet { + +struct half {}; + +/// Determines the LibTorch dtype from template parameter +/// +/// @tparam T C++ type +/// +/// @result Torch type corresponding to the C++ type +/// @{ +template inline constexpr torch::Dtype dtype(); + +template <> inline constexpr torch::Dtype dtype() { return torch::kBool; } + +template <> inline constexpr torch::Dtype dtype() { return torch::kChar; } + +template <> inline constexpr torch::Dtype dtype() { + return torch::kShort; +} + +template <> inline constexpr torch::Dtype dtype() { return torch::kInt; } + +template <> inline constexpr torch::Dtype dtype() { return torch::kLong; } + +template <> inline constexpr torch::Dtype dtype() { return torch::kHalf; } + +template <> inline constexpr torch::Dtype dtype() { + return torch::kFloat; +} + +template <> inline constexpr torch::Dtype dtype() { + return torch::kDouble; +} + +template <> inline constexpr torch::Dtype dtype>() { + return at::kComplexHalf; +} + +template <> inline constexpr torch::Dtype dtype>() { + return at::kComplexFloat; +} + +template <> inline constexpr torch::Dtype dtype>() { + return at::kComplexDouble; +} +/// @} + +inline int guess_device_index() { +#ifdef IGANET_WITH_MPI + int rank; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + return rank % + utils::getenv("IGANET_DEVICE_COUNT", torch::cuda::is_available + ? torch::cuda::device_count() + : 1); +#else + return 0; +#endif +} + +/// @brief The Options class handles the automated determination of +/// dtype from the template argument and the selection of the device +/// +/// @tparam real_t Type of real-valued data +template +class Options : private iganet::utils::FullQualifiedName { +public: + /// Default constructor + Options() + : options_( + torch::TensorOptions() + .dtype(::iganet::dtype()) + .device_index(utils::getenv("IGANET_DEVICE_INDEX", + iganet::guess_device_index())) + .device( + (utils::getenv("IGANET_DEVICE", std::string{}) == "CPU") + ? torch::kCPU + : (utils::getenv("IGANET_DEVICE", std::string{}) == "CUDA") + ? torch::kCUDA + : (utils::getenv("IGANET_DEVICE", std::string{}) == "HIP") + ? torch::kHIP + : (utils::getenv("IGANET_DEVICE", std::string{}) == "MPS") + ? torch::kMPS + : (utils::getenv("IGANET_DEVICE", std::string{}) == "XLA") + ? torch::kXLA + : (utils::getenv("IGANET_DEVICE", std::string{}) == "XPU") + ? torch::kXPU + : (torch::cuda::is_available() ? torch::kCUDA + : torch::kCPU))) {} + + /// Constructor from torch::TensorOptions + explicit Options(torch::TensorOptions &&options) : options_(options) {} + + /// @brief Implicit conversion operator + operator torch::TensorOptions() const { return options_; } + + /// @brief Returns the `device` property + torch::Device device() const noexcept { return options_.device(); } + + /// @brief Returns the `device_index` property + int32_t device_index() const noexcept { return options_.device_index(); } + + /// @brief Returns the `dtype` property + torch::Dtype dtype() const noexcept { return ::iganet::dtype(); } + + /// @brief Returns the `layout` property + torch::Layout layout() const noexcept { return options_.layout(); } + + /// @brief Returns the `requires_grad` property + bool requires_grad() const noexcept { return options_.requires_grad(); } + + /// @brief Returns the `pinned_memory` property + bool pinned_memory() const noexcept { return options_.pinned_memory(); } + + /// @brief Returns if the layout is sparse + bool is_sparse() const noexcept { return options_.is_sparse(); } + + /// @brief Returns a new Options object with the `device` property as given + Options device(torch::Device device) const noexcept { + return Options(options_.device(device)); + } + + /// @brief Returns a new Options object with the `device_index` property as + /// given + Options device_index(int16_t device_index) const noexcept { + return Options(options_.device_index(device_index)); + } + + /// @brief Returns a new Options object with the `dtype` property as given + template Options dtype() const noexcept { + return Options(options_.dtype(::iganet::dtype())); + } + + /// @brief Returns a new Options object with the `layout` property as given + Options layout(torch::Layout layout) const noexcept { + return Options(options_.layout(layout)); + } + + /// @brief Returns a new Options object with the `requires_grad` property as + /// given + Options requires_grad(bool requires_grad) const noexcept { + return Options(options_.requires_grad(requires_grad)); + } + + /// @brief Returns a new Options object with the `pinned_memory` property as + /// given + Options pinned_memory(bool pinned_memory) const noexcept { + return Options(options_.pinned_memory(pinned_memory)); + } + + /// @brief Returns a new Options object with the `memory_format` property as + /// given + Options + memory_format(torch::MemoryFormat memory_format) const noexcept { + return Options(options_.memory_format(memory_format)); + } + + /// @brief Data type + using value_type = real_t; + + /// @brief Returns a string representation of the Options object + inline virtual void + pretty_print(std::ostream &os = Log(log::info)) const noexcept override { + os << name() << "(\noptions = " << options_ << "\n)"; + } + +private: + /// @brief Tensor options + const torch::TensorOptions options_; +}; + +/// @brief Print (as string) a Options object +template +inline std::ostream &operator<<(std::ostream &os, const Options &obj) { + obj.pretty_print(os); + return os; +} + +/// @brief Options dispatcher +template +class Options> : public Options { + using Options::Options; +}; + +} // namespace iganet diff --git a/include/sysinfo.hpp b/include/sysinfo.hpp index 85aa7a34..6d12ff97 100644 --- a/include/sysinfo.hpp +++ b/include/sysinfo.hpp @@ -1,669 +1,669 @@ -/** - @file include/sysinfo.hpp - - @brief System information - - @author Matthias Moller - - @copyright This file is part of the IgANet project - - This Source Code Form is subject to the terms of the Mozilla Public - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at http://mozilla.org/MPL/2.0/. -*/ - -#pragma once - -#include - -#if defined(_WIN32) || defined(_WIN64) -#include -#elif __APPLE__ -#include -#include -#elif __linux__ || __unix__ -#include -#if defined(__x86_64__) && (defined(__GNUC__) || defined(__clang__) || \ - defined(__INTEL_COMPILER) || defined(__SUNCC_PRO)) -#include -#else -#include -#endif -#endif - -namespace iganet { - -/// @brief Returns the IgANet version -inline std::string getIgANetVersion() { return std::string(IGANET_VERSION); } - -/// @brief Returns the version of the compiler -inline std::string getCompilerVersion() { - // This code is copied from the CMakeCXXCompilerId.cpp file that was - // automatically generated with CMake 3.21.4 - - // The following two macros have been modified as we do not want to - // return the compiler version in the specific CMake format -#define DEC(n) n -#define HEX(n) n - - /* Version number components: V=Version, R=Revision, P=Patch - Version date components: YYYY=Year, MM=Month, DD=Day */ - -#if defined(__COMO__) -#define COMPILER_ID "Comeau" - /* __COMO_VERSION__ = VRR */ -#define COMPILER_VERSION_MAJOR DEC(__COMO_VERSION__ / 100) -#define COMPILER_VERSION_MINOR DEC(__COMO_VERSION__ % 100) - -#elif defined(__INTEL_COMPILER) || defined(__ICC) -#define COMPILER_ID "Intel" -#if defined(_MSC_VER) -#define SIMULATE_ID "MSVC" -#endif -#if defined(__GNUC__) -#define SIMULATE_ID "GNU" -#endif - /* __INTEL_COMPILER = VRP prior to 2021, and then VVVV for 2021 and later, - except that a few beta releases use the old format with V=2021. */ -#if __INTEL_COMPILER < 2021 || __INTEL_COMPILER == 202110 || \ - __INTEL_COMPILER == 202111 -#define COMPILER_VERSION_MAJOR DEC(__INTEL_COMPILER / 100) -#define COMPILER_VERSION_MINOR DEC(__INTEL_COMPILER / 10 % 10) -#if defined(__INTEL_COMPILER_UPDATE) -#define COMPILER_VERSION_PATCH DEC(__INTEL_COMPILER_UPDATE) -#else -#define COMPILER_VERSION_PATCH DEC(__INTEL_COMPILER % 10) -#endif -#else -#define COMPILER_VERSION_MAJOR DEC(__INTEL_COMPILER) -#define COMPILER_VERSION_MINOR DEC(__INTEL_COMPILER_UPDATE) - /* The third version component from --version is an update index, - but no macro is provided for it. */ -#define COMPILER_VERSION_PATCH DEC(0) -#endif -#if defined(__INTEL_COMPILER_BUILD_DATE) - /* __INTEL_COMPILER_BUILD_DATE = YYYYMMDD */ -#define COMPILER_VERSION_TWEAK DEC(__INTEL_COMPILER_BUILD_DATE) -#endif -#if defined(_MSC_VER) - /* _MSC_VER = VVRR */ -#define SIMULATE_VERSION_MAJOR DEC(_MSC_VER / 100) -#define SIMULATE_VERSION_MINOR DEC(_MSC_VER % 100) -#endif -#if defined(__GNUC__) -#define SIMULATE_VERSION_MAJOR DEC(__GNUC__) -#elif defined(__GNUG__) -#define SIMULATE_VERSION_MAJOR DEC(__GNUG__) -#endif -#if defined(__GNUC_MINOR__) -#define SIMULATE_VERSION_MINOR DEC(__GNUC_MINOR__) -#endif -#if defined(__GNUC_PATCHLEVEL__) -#define SIMULATE_VERSION_PATCH DEC(__GNUC_PATCHLEVEL__) -#endif - -#elif (defined(__clang__) && defined(__INTEL_CLANG_COMPILER)) || \ - defined(__INTEL_LLVM_COMPILER) -#define COMPILER_ID "IntelLLVM" -#if defined(_MSC_VER) -#define SIMULATE_ID "MSVC" -#endif -#if defined(__GNUC__) -#define SIMULATE_ID "GNU" -#endif - /* __INTEL_LLVM_COMPILER = VVVVRP prior to 2021.2.0, VVVVRRPP for 2021.2.0 and - * later. Look for 6 digit vs. 8 digit version number to decide encoding. - * VVVV is no smaller than the current year when a version is released. - */ -#if __INTEL_LLVM_COMPILER < 1000000L -#define COMPILER_VERSION_MAJOR DEC(__INTEL_LLVM_COMPILER / 100) -#define COMPILER_VERSION_MINOR DEC(__INTEL_LLVM_COMPILER / 10 % 10) -#define COMPILER_VERSION_PATCH DEC(__INTEL_LLVM_COMPILER % 10) -#else -#define COMPILER_VERSION_MAJOR DEC(__INTEL_LLVM_COMPILER / 10000) -#define COMPILER_VERSION_MINOR DEC(__INTEL_LLVM_COMPILER / 100 % 100) -#define COMPILER_VERSION_PATCH DEC(__INTEL_LLVM_COMPILER % 100) -#endif -#if defined(_MSC_VER) - /* _MSC_VER = VVRR */ -#define SIMULATE_VERSION_MAJOR DEC(_MSC_VER / 100) -#define SIMULATE_VERSION_MINOR DEC(_MSC_VER % 100) -#endif -#if defined(__GNUC__) -#define SIMULATE_VERSION_MAJOR DEC(__GNUC__) -#elif defined(__GNUG__) -#define SIMULATE_VERSION_MAJOR DEC(__GNUG__) -#endif -#if defined(__GNUC_MINOR__) -#define SIMULATE_VERSION_MINOR DEC(__GNUC_MINOR__) -#endif -#if defined(__GNUC_PATCHLEVEL__) -#define SIMULATE_VERSION_PATCH DEC(__GNUC_PATCHLEVEL__) -#endif - -#elif defined(__PATHCC__) -#define COMPILER_ID "PathScale" -#define COMPILER_VERSION_MAJOR DEC(__PATHCC__) -#define COMPILER_VERSION_MINOR DEC(__PATHCC_MINOR__) -#if defined(__PATHCC_PATCHLEVEL__) -#define COMPILER_VERSION_PATCH DEC(__PATHCC_PATCHLEVEL__) -#endif - -#elif defined(__BORLANDC__) && defined(__CODEGEARC_VERSION__) -#define COMPILER_ID "Embarcadero" -#define COMPILER_VERSION_MAJOR HEX(__CODEGEARC_VERSION__ >> 24 & 0x00FF) -#define COMPILER_VERSION_MINOR HEX(__CODEGEARC_VERSION__ >> 16 & 0x00FF) -#define COMPILER_VERSION_PATCH DEC(__CODEGEARC_VERSION__ & 0xFFFF) - -#elif defined(__BORLANDC__) -#define COMPILER_ID "Borland" - /* __BORLANDC__ = 0xVRR */ -#define COMPILER_VERSION_MAJOR HEX(__BORLANDC__ >> 8) -#define COMPILER_VERSION_MINOR HEX(__BORLANDC__ & 0xFF) - -#elif defined(__WATCOMC__) && __WATCOMC__ < 1200 -#define COMPILER_ID "Watcom" - /* __WATCOMC__ = VVRR */ -#define COMPILER_VERSION_MAJOR DEC(__WATCOMC__ / 100) -#define COMPILER_VERSION_MINOR DEC((__WATCOMC__ / 10) % 10) -#if (__WATCOMC__ % 10) > 0 -#define COMPILER_VERSION_PATCH DEC(__WATCOMC__ % 10) -#endif - -#elif defined(__WATCOMC__) -#define COMPILER_ID "OpenWatcom" - /* __WATCOMC__ = VVRP + 1100 */ -#define COMPILER_VERSION_MAJOR DEC((__WATCOMC__ - 1100) / 100) -#define COMPILER_VERSION_MINOR DEC((__WATCOMC__ / 10) % 10) -#if (__WATCOMC__ % 10) > 0 -#define COMPILER_VERSION_PATCH DEC(__WATCOMC__ % 10) -#endif - -#elif defined(__SUNPRO_CC) -#define COMPILER_ID "SunPro" -#if __SUNPRO_CC >= 0x5100 - /* __SUNPRO_CC = 0xVRRP */ -#define COMPILER_VERSION_MAJOR HEX(__SUNPRO_CC >> 12) -#define COMPILER_VERSION_MINOR HEX(__SUNPRO_CC >> 4 & 0xFF) -#define COMPILER_VERSION_PATCH HEX(__SUNPRO_CC & 0xF) -#else - /* __SUNPRO_CC = 0xVRP */ -#define COMPILER_VERSION_MAJOR HEX(__SUNPRO_CC >> 8) -#define COMPILER_VERSION_MINOR HEX(__SUNPRO_CC >> 4 & 0xF) -#define COMPILER_VERSION_PATCH HEX(__SUNPRO_CC & 0xF) -#endif - -#elif defined(__HP_aCC) -#define COMPILER_ID "HP" - /* __HP_aCC = VVRRPP */ -#define COMPILER_VERSION_MAJOR DEC(__HP_aCC / 10000) -#define COMPILER_VERSION_MINOR DEC(__HP_aCC / 100 % 100) -#define COMPILER_VERSION_PATCH DEC(__HP_aCC % 100) - -#elif defined(__DECCXX) -#define COMPILER_ID "Compaq" - /* __DECCXX_VER = VVRRTPPPP */ -#define COMPILER_VERSION_MAJOR DEC(__DECCXX_VER / 10000000) -#define COMPILER_VERSION_MINOR DEC(__DECCXX_VER / 100000 % 100) -#define COMPILER_VERSION_PATCH DEC(__DECCXX_VER % 10000) - -#elif defined(__IBMCPP__) && defined(__COMPILER_VER__) -#define COMPILER_ID "zOS" - /* __IBMCPP__ = VRP */ -#define COMPILER_VERSION_MAJOR DEC(__IBMCPP__ / 100) -#define COMPILER_VERSION_MINOR DEC(__IBMCPP__ / 10 % 10) -#define COMPILER_VERSION_PATCH DEC(__IBMCPP__ % 10) - -#elif defined(__ibmxl__) && defined(__clang__) -#define COMPILER_ID "XLClang" -#define COMPILER_VERSION_MAJOR DEC(__ibmxl_version__) -#define COMPILER_VERSION_MINOR DEC(__ibmxl_release__) -#define COMPILER_VERSION_PATCH DEC(__ibmxl_modification__) -#define COMPILER_VERSION_TWEAK DEC(__ibmxl_ptf_fix_level__) - -#elif defined(__IBMCPP__) && !defined(__COMPILER_VER__) && __IBMCPP__ >= 800 -#define COMPILER_ID "XL" - /* __IBMCPP__ = VRP */ -#define COMPILER_VERSION_MAJOR DEC(__IBMCPP__ / 100) -#define COMPILER_VERSION_MINOR DEC(__IBMCPP__ / 10 % 10) -#define COMPILER_VERSION_PATCH DEC(__IBMCPP__ % 10) - -#elif defined(__IBMCPP__) && !defined(__COMPILER_VER__) && __IBMCPP__ < 800 -#define COMPILER_ID "VisualAge" - /* __IBMCPP__ = VRP */ -#define COMPILER_VERSION_MAJOR DEC(__IBMCPP__ / 100) -#define COMPILER_VERSION_MINOR DEC(__IBMCPP__ / 10 % 10) -#define COMPILER_VERSION_PATCH DEC(__IBMCPP__ % 10) - -#elif defined(__NVCOMPILER) -#define COMPILER_ID "NVHPC" -#define COMPILER_VERSION_MAJOR DEC(__NVCOMPILER_MAJOR__) -#define COMPILER_VERSION_MINOR DEC(__NVCOMPILER_MINOR__) -#if defined(__NVCOMPILER_PATCHLEVEL__) -#define COMPILER_VERSION_PATCH DEC(__NVCOMPILER_PATCHLEVEL__) -#endif - -#elif defined(__PGI) -#define COMPILER_ID "PGI" -#define COMPILER_VERSION_MAJOR DEC(__PGIC__) -#define COMPILER_VERSION_MINOR DEC(__PGIC_MINOR__) -#if defined(__PGIC_PATCHLEVEL__) -#define COMPILER_VERSION_PATCH DEC(__PGIC_PATCHLEVEL__) -#endif - -#elif defined(_CRAYC) -#define COMPILER_ID "Cray" -#define COMPILER_VERSION_MAJOR DEC(_RELEASE_MAJOR) -#define COMPILER_VERSION_MINOR DEC(_RELEASE_MINOR) - -#elif defined(__TI_COMPILER_VERSION__) -#define COMPILER_ID "TI" - /* __TI_COMPILER_VERSION__ = VVVRRRPPP */ -#define COMPILER_VERSION_MAJOR DEC(__TI_COMPILER_VERSION__ / 1000000) -#define COMPILER_VERSION_MINOR DEC(__TI_COMPILER_VERSION__ / 1000 % 1000) -#define COMPILER_VERSION_PATCH DEC(__TI_COMPILER_VERSION__ % 1000) - -#elif defined(__CLANG_FUJITSU) -#define COMPILER_ID "FujitsuClang" -#define COMPILER_VERSION_MAJOR DEC(__FCC_major__) -#define COMPILER_VERSION_MINOR DEC(__FCC_minor__) -#define COMPILER_VERSION_PATCH DEC(__FCC_patchlevel__) -#define COMPILER_VERSION_INTERNAL_STR __clang_version__ - -#elif defined(__FUJITSU) -#define COMPILER_ID "Fujitsu" -#if defined(__FCC_version__) -#define COMPILER_VERSION __FCC_version__ -#elif defined(__FCC_major__) -#define COMPILER_VERSION_MAJOR DEC(__FCC_major__) -#define COMPILER_VERSION_MINOR DEC(__FCC_minor__) -#define COMPILER_VERSION_PATCH DEC(__FCC_patchlevel__) -#endif -#if defined(__fcc_version) -#define COMPILER_VERSION_INTERNAL DEC(__fcc_version) -#elif defined(__FCC_VERSION) -#define COMPILER_VERSION_INTERNAL DEC(__FCC_VERSION) -#endif - -#elif defined(__ghs__) -#define COMPILER_ID "GHS" - /* __GHS_VERSION_NUMBER = VVVVRP */ -#ifdef __GHS_VERSION_NUMBER -#define COMPILER_VERSION_MAJOR DEC(__GHS_VERSION_NUMBER / 100) -#define COMPILER_VERSION_MINOR DEC(__GHS_VERSION_NUMBER / 10 % 10) -#define COMPILER_VERSION_PATCH DEC(__GHS_VERSION_NUMBER % 10) -#endif - -#elif defined(__SCO_VERSION__) -#define COMPILER_ID "SCO" - -#elif defined(__ARMCC_VERSION) && !defined(__clang__) -#define COMPILER_ID "ARMCC" -#if __ARMCC_VERSION >= 1000000 - /* __ARMCC_VERSION = VRRPPPP */ -#define COMPILER_VERSION_MAJOR DEC(__ARMCC_VERSION / 1000000) -#define COMPILER_VERSION_MINOR DEC(__ARMCC_VERSION / 10000 % 100) -#define COMPILER_VERSION_PATCH DEC(__ARMCC_VERSION % 10000) -#else - /* __ARMCC_VERSION = VRPPPP */ -#define COMPILER_VERSION_MAJOR DEC(__ARMCC_VERSION / 100000) -#define COMPILER_VERSION_MINOR DEC(__ARMCC_VERSION / 10000 % 10) -#define COMPILER_VERSION_PATCH DEC(__ARMCC_VERSION % 10000) -#endif - -#elif defined(__clang__) && defined(__apple_build_version__) -#define COMPILER_ID "AppleClang" -#if defined(_MSC_VER) -#define SIMULATE_ID "MSVC" -#endif -#define COMPILER_VERSION_MAJOR DEC(__clang_major__) -#define COMPILER_VERSION_MINOR DEC(__clang_minor__) -#define COMPILER_VERSION_PATCH DEC(__clang_patchlevel__) -#if defined(_MSC_VER) - /* _MSC_VER = VVRR */ -#define SIMULATE_VERSION_MAJOR DEC(_MSC_VER / 100) -#define SIMULATE_VERSION_MINOR DEC(_MSC_VER % 100) -#endif -#define COMPILER_VERSION_TWEAK DEC(__apple_build_version__) - -#elif defined(__clang__) && defined(__ARMCOMPILER_VERSION) -#define COMPILER_ID "ARMClang" -#define COMPILER_VERSION_MAJOR DEC(__ARMCOMPILER_VERSION / 1000000) -#define COMPILER_VERSION_MINOR DEC(__ARMCOMPILER_VERSION / 10000 % 100) -#define COMPILER_VERSION_PATCH DEC(__ARMCOMPILER_VERSION % 10000) -#define COMPILER_VERSION_INTERNAL DEC(__ARMCOMPILER_VERSION) - -#elif defined(__clang__) -#define COMPILER_ID "Clang" -#if defined(_MSC_VER) -#define SIMULATE_ID "MSVC" -#endif -#define COMPILER_VERSION_MAJOR DEC(__clang_major__) -#define COMPILER_VERSION_MINOR DEC(__clang_minor__) -#define COMPILER_VERSION_PATCH DEC(__clang_patchlevel__) -#if defined(_MSC_VER) - /* _MSC_VER = VVRR */ -#define SIMULATE_VERSION_MAJOR DEC(_MSC_VER / 100) -#define SIMULATE_VERSION_MINOR DEC(_MSC_VER % 100) -#endif - -#elif defined(__GNUC__) || defined(__GNUG__) -#define COMPILER_ID "GNU" -#if defined(__GNUC__) -#define COMPILER_VERSION_MAJOR DEC(__GNUC__) -#else -#define COMPILER_VERSION_MAJOR DEC(__GNUG__) -#endif -#if defined(__GNUC_MINOR__) -#define COMPILER_VERSION_MINOR DEC(__GNUC_MINOR__) -#endif -#if defined(__GNUC_PATCHLEVEL__) -#define COMPILER_VERSION_PATCH DEC(__GNUC_PATCHLEVEL__) -#endif - -#elif defined(_MSC_VER) -#define COMPILER_ID "MSVC" - /* _MSC_VER = VVRR */ -#define COMPILER_VERSION_MAJOR DEC(_MSC_VER / 100) -#define COMPILER_VERSION_MINOR DEC(_MSC_VER % 100) -#if defined(_MSC_FULL_VER) -#if _MSC_VER >= 1400 - /* _MSC_FULL_VER = VVRRPPPPP */ -#define COMPILER_VERSION_PATCH DEC(_MSC_FULL_VER % 100000) -#else - /* _MSC_FULL_VER = VVRRPPPP */ -#define COMPILER_VERSION_PATCH DEC(_MSC_FULL_VER % 10000) -#endif -#endif -#if defined(_MSC_BUILD) -#define COMPILER_VERSION_TWEAK DEC(_MSC_BUILD) -#endif - -#elif defined(__VISUALDSPVERSION__) || defined(__ADSPBLACKFIN__) || \ - defined(__ADSPTS__) || defined(__ADSP21000__) -#define COMPILER_ID "ADSP" -#if defined(__VISUALDSPVERSION__) - /* __VISUALDSPVERSION__ = 0xVVRRPP00 */ -#define COMPILER_VERSION_MAJOR HEX(__VISUALDSPVERSION__ >> 24) -#define COMPILER_VERSION_MINOR HEX(__VISUALDSPVERSION__ >> 16 & 0xFF) -#define COMPILER_VERSION_PATCH HEX(__VISUALDSPVERSION__ >> 8 & 0xFF) -#endif - -#elif defined(__IAR_SYSTEMS_ICC__) || defined(__IAR_SYSTEMS_ICC) -#define COMPILER_ID "IAR" -#if defined(__VER__) && defined(__ICCARM__) -#define COMPILER_VERSION_MAJOR DEC((__VER__) / 1000000) -#define COMPILER_VERSION_MINOR DEC(((__VER__) / 1000) % 1000) -#define COMPILER_VERSION_PATCH DEC((__VER__) % 1000) -#define COMPILER_VERSION_INTERNAL DEC(__IAR_SYSTEMS_ICC__) -#elif defined(__VER__) && \ - (defined(__ICCAVR__) || defined(__ICCRX__) || defined(__ICCRH850__) || \ - defined(__ICCRL78__) || defined(__ICC430__) || defined(__ICCRISCV__) || \ - defined(__ICCV850__) || defined(__ICC8051__) || defined(__ICCSTM8__)) -#define COMPILER_VERSION_MAJOR DEC((__VER__) / 100) -#define COMPILER_VERSION_MINOR DEC((__VER__) - (((__VER__) / 100) * 100)) -#define COMPILER_VERSION_PATCH DEC(__SUBVERSION__) -#define COMPILER_VERSION_INTERNAL DEC(__IAR_SYSTEMS_ICC__) -#endif - - /* These compilers are either not known or too old to define an - identification macro. Try to identify the platform and guess that - it is the native compiler. */ -#elif defined(__hpux) || defined(__hpua) -#define COMPILER_ID "HP" - -#else /* unknown compiler */ -#define COMPILER_ID "Unknown-Compiler" -#endif - - return std::string(COMPILER_ID) -#ifdef COMPILER_VERSION - + " " + std::to_string(COMPILER_VERSION); -#elif defined(COMPILER_VERSION_MAJOR) - + " " + std::to_string(COMPILER_VERSION_MAJOR) -#ifdef COMPILER_VERSION_MINOR - + "." + std::to_string(COMPILER_VERSION_MINOR) -#ifdef COMPILER_VERSION_PATCH - + "." + std::to_string(COMPILER_VERSION_PATCH) -#ifdef COMPILER_VERSION_TWEAK - + "." + std::to_string(COMPILER_VERSION_TWEAK) -#endif -#endif -#endif - ; -#endif - -#undef DEC -#undef HEX -#undef COMPILER_ID -#undef COMPILER_VERSION -#undef COMPILER_VERSION_MAJOR -#undef COMPILER_VERSION_MINOR -#undef COMPILER_VERSION_PATCH -#undef COMPILER_VERSION_TWEAK -#undef SIMULATE_VERSION_MAJOR -#undef SIMULATE_VERSION_MINOR -#undef SIMULATE_VERSION_PATCH -#undef SIMULATE_VERSION_TWEAK -} - -/// @brief Returns the version of the C++ standard -inline std::string getCppVersion() { -#if defined(_MSC_VER) && _MSC_VER < 1600 - return "C++ 199711L"; -#elif _MSC_VER >= 1900 - return "C++ " + std::to_string(_MSVC_LANG); -#elif _MSC_VER >= 1600 - return "C++ 201103L"; -#else - return "C++ " + std::to_string(__cplusplus); -#endif -} - -/// @brief Returns the version of the standard library -inline std::string getStdLibVersion() { -#ifdef _LIBCPP_VERSION - return "libc++ " + std::to_string(_LIBCPP_VERSION); -#elif defined(__GLIBCXX__) - return "glibc++ " + std::to_string(__GLIBCXX__); -#elif defined(__GLIBCPP__) - return "glibc++ " + std::to_string(__GLIBCPP__); -#elif defined(__LIBCOMO__) - return "Comeau STL " + std::to_string(__LIBCOMO__); -#elif defined(__STL_CONFIG_H) - return "SGI STL"; -#elif defined(__MSL_CPP__) - return "MSL standard lib"; -#elif defined(__IBMCPP__) - return "VACPP STL"; -#elif defined(MSIPL_COMPILE_H) - return "Modena C++ STL"; -#elif (defined(_YVALS) && !defined(__IBMCPP__)) || defined(_CPPLIB_VER) - return "Dinkumware STL " + std::to_string(_CPPLIB_VER); -#elif defined(__STD_RWCOMPILER_H__) || defined(_RWSTD_VER) - return "Rogue Wave lib " + std::to_string(_RWSTD_VER); -#else - return "Unknown-STD"; -#endif -} - -/// @brief Returns the version of extra libraries -inline std::string getExtraLibsVersion() { - std::string s(""); - - // CUDA -#if defined(CUDA_VERSION) - if (!s.empty()) - s += ", "; - s += "CUDA " + std::to_string(CUDA_VERSION / 1000) + "." + - std::to_string((CUDA_VERSION % 100) / 10); -#endif - - // Intel MKL library -#if defined(INTEL_MKL_VERSION) - if (!s.empty()) - s += ", "; - s += "MKL " + std::to_string(INTEL_MKL_VERSION); -#endif - - // LibTorch -#if defined(TORCH_VERSION_MAJOR) - if (!s.empty()) - s += ", "; - s += "LibTorch " + std::to_string(TORCH_VERSION_MAJOR) + "." + - std::to_string(TORCH_VERSION_MINOR) + "." + - std::to_string(TORCH_VERSION_PATCH) + - " (#intraop threads: " + std::to_string(at::get_num_threads()) + - ", #interop threads: " + std::to_string(at::get_num_interop_threads()) + - ")"; -#endif - - // ROCm -#if defined(ROCM_VERSION_MAJOR) - if (!s.empty()) - s += ", "; - s += "ROCm " + std::to_string(ROCM_VERSION_MAJOR) + "." + - std::to_string(ROCM_VERSION_MINOR) + "." + - std::to_string(ROCM_VERSION_PATCH); -#endif - - return s; -} - -/// @brief Returns CPU information -inline std::string getCpuInfo() { -#if defined(_WIN32) || defined(_WIN64) - - int CPUInfo[4] = {-1}; - unsigned nExIds, i = 0; - char CPUBrandString[0x40]; - - __cpuid(CPUInfo, 0x80000000); - nExIds = CPUInfo[0]; - - for (i = 0x80000000; i <= nExIds; ++i) { - __cpuid(CPUInfo, i); - if (i == 0x80000002) - memcpy(CPUBrandString, CPUInfo, sizeof(CPUInfo)); - else if (i == 0x80000003) - memcpy(CPUBrandString + 16, CPUInfo, sizeof(CPUInfo)); - else if (i == 0x80000004) - memcpy(CPUBrandString + 32, CPUInfo, sizeof(CPUInfo)); - } - - return CPUBrandString; - -#elif __APPLE__ - - std::string CPUBrandString; - std::size_t size = 32; - - // Supply an oversized buffer, and avoid an extra call to sysctlbyname. - CPUBrandString.resize(size); - if (sysctlbyname("machdep.cpu.brand_string", &CPUBrandString[0], &size, NULL, - 0) == 0 && - size > 0) { - if (CPUBrandString[size - 1] == '\0') - size--; - CPUBrandString.resize(size); - return CPUBrandString; - } - -#elif __linux__ || __unix__ -#if defined(__x86_64__) && (defined(__GNUC__) || defined(__clang__) || \ - defined(__INTEL_COMPILER) || defined(__SUNCC_PRO)) - - char CPUBrandString[0x40]; - unsigned int CPUInfo[4] = {0, 0, 0, 0}; - - __cpuid(0x80000000, CPUInfo[0], CPUInfo[1], CPUInfo[2], CPUInfo[3]); - unsigned int nExIds = CPUInfo[0]; - - memset(CPUBrandString, 0, sizeof(CPUBrandString)); - - for (unsigned int i = 0x80000000; i <= nExIds; ++i) { - __cpuid(i, CPUInfo[0], CPUInfo[1], CPUInfo[2], CPUInfo[3]); - - if (i == 0x80000002) - memcpy(CPUBrandString, CPUInfo, sizeof(CPUInfo)); - else if (i == 0x80000003) - memcpy(CPUBrandString + 16, CPUInfo, sizeof(CPUInfo)); - else if (i == 0x80000004) - memcpy(CPUBrandString + 32, CPUInfo, sizeof(CPUInfo)); - } - - return CPUBrandString; - -#else - - char hostname[HOST_NAME_MAX + 1]; - gethostname(hostname, HOST_NAME_MAX + 1); - - std::string str = "Unknown-CPU ["; - str += hostname; - str += "]"; - - return str; - -#endif - -#endif - - return "Unknown-CPU"; -} - -/// @brief Returns total system memory in bytes -inline uint64_t getMemoryInBytes() { -#if defined(_WIN32) || defined(_WIN64) - - MEMORYSTATUSEX status; - status.dwLength = sizeof(status); - GlobalMemoryStatusEx(&status); - return (uint64_t)status.ullTotalPhys; - -#elif __APPLE__ - - int64_t memsize; - std::size_t size = sizeof(memsize); - - if (sysctlbyname("hw.memsize", &memsize, &size, NULL, 0) == 0) { - return (uint64_t)memsize; - } - -#elif __linux__ || __unix__ - - long pages = sysconf(_SC_PHYS_PAGES); - long page_size = sysconf(_SC_PAGE_SIZE); - return (uint64_t)(pages * page_size); - -#endif - - return 0; -} - -/// @brief Returns memory information -inline std::string getMemoryInfo() { - uint64_t memsize = getMemoryInBytes(); - if (memsize > 0) { - if (memsize < 1024) - return std::to_string(memsize) + " B"; - else if (memsize < 1024 * 1024) - return std::to_string(memsize / 1024) + " KB"; - else if (memsize < 1024 * 1024 * 1024) - return std::to_string(memsize / (1024 * 1024)) + " MB"; - else - return std::to_string(memsize / (1024 * 1024 * 1024)) + " GB"; - } else - return "Unknown-Memory"; -} - -/// @brief Returns version information -inline std::string getVersion() { - return std::string("IgANets - Isogeometric Analysis Networks") + - " (version " + getIgANetVersion() + ")\n" + "Compiled by " + - getCompilerVersion() + " (" + getCppVersion() + ", " + - getStdLibVersion() + ", " + getExtraLibsVersion() + ")\n" + - "Running on " + getCpuInfo() + " (memory " + getMemoryInfo() + ")\n"; -} - -} // namespace iganet +/** + @file include/sysinfo.hpp + + @brief System information + + @author Matthias Moller + + @copyright This file is part of the IgANet project + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#pragma once + +#include + +#if defined(_WIN32) || defined(_WIN64) +#include +#elif __APPLE__ +#include +#include +#elif __linux__ || __unix__ +#include +#if defined(__x86_64__) && (defined(__GNUC__) || defined(__clang__) || \ + defined(__INTEL_COMPILER) || defined(__SUNCC_PRO)) +#include +#else +#include +#endif +#endif + +namespace iganet { + +/// @brief Returns the IgANet version +inline std::string getIgANetVersion() { return std::string(IGANET_VERSION); } + +/// @brief Returns the version of the compiler +inline std::string getCompilerVersion() { + // This code is copied from the CMakeCXXCompilerId.cpp file that was + // automatically generated with CMake 3.21.4 + + // The following two macros have been modified as we do not want to + // return the compiler version in the specific CMake format +#define DEC(n) n +#define HEX(n) n + + /* Version number components: V=Version, R=Revision, P=Patch + Version date components: YYYY=Year, MM=Month, DD=Day */ + +#if defined(__COMO__) +#define COMPILER_ID "Comeau" + /* __COMO_VERSION__ = VRR */ +#define COMPILER_VERSION_MAJOR DEC(__COMO_VERSION__ / 100) +#define COMPILER_VERSION_MINOR DEC(__COMO_VERSION__ % 100) + +#elif defined(__INTEL_COMPILER) || defined(__ICC) +#define COMPILER_ID "Intel" +#if defined(_MSC_VER) +#define SIMULATE_ID "MSVC" +#endif +#if defined(__GNUC__) +#define SIMULATE_ID "GNU" +#endif + /* __INTEL_COMPILER = VRP prior to 2021, and then VVVV for 2021 and later, + except that a few beta releases use the old format with V=2021. */ +#if __INTEL_COMPILER < 2021 || __INTEL_COMPILER == 202110 || \ + __INTEL_COMPILER == 202111 +#define COMPILER_VERSION_MAJOR DEC(__INTEL_COMPILER / 100) +#define COMPILER_VERSION_MINOR DEC(__INTEL_COMPILER / 10 % 10) +#if defined(__INTEL_COMPILER_UPDATE) +#define COMPILER_VERSION_PATCH DEC(__INTEL_COMPILER_UPDATE) +#else +#define COMPILER_VERSION_PATCH DEC(__INTEL_COMPILER % 10) +#endif +#else +#define COMPILER_VERSION_MAJOR DEC(__INTEL_COMPILER) +#define COMPILER_VERSION_MINOR DEC(__INTEL_COMPILER_UPDATE) + /* The third version component from --version is an update index, + but no macro is provided for it. */ +#define COMPILER_VERSION_PATCH DEC(0) +#endif +#if defined(__INTEL_COMPILER_BUILD_DATE) + /* __INTEL_COMPILER_BUILD_DATE = YYYYMMDD */ +#define COMPILER_VERSION_TWEAK DEC(__INTEL_COMPILER_BUILD_DATE) +#endif +#if defined(_MSC_VER) + /* _MSC_VER = VVRR */ +#define SIMULATE_VERSION_MAJOR DEC(_MSC_VER / 100) +#define SIMULATE_VERSION_MINOR DEC(_MSC_VER % 100) +#endif +#if defined(__GNUC__) +#define SIMULATE_VERSION_MAJOR DEC(__GNUC__) +#elif defined(__GNUG__) +#define SIMULATE_VERSION_MAJOR DEC(__GNUG__) +#endif +#if defined(__GNUC_MINOR__) +#define SIMULATE_VERSION_MINOR DEC(__GNUC_MINOR__) +#endif +#if defined(__GNUC_PATCHLEVEL__) +#define SIMULATE_VERSION_PATCH DEC(__GNUC_PATCHLEVEL__) +#endif + +#elif (defined(__clang__) && defined(__INTEL_CLANG_COMPILER)) || \ + defined(__INTEL_LLVM_COMPILER) +#define COMPILER_ID "IntelLLVM" +#if defined(_MSC_VER) +#define SIMULATE_ID "MSVC" +#endif +#if defined(__GNUC__) +#define SIMULATE_ID "GNU" +#endif + /* __INTEL_LLVM_COMPILER = VVVVRP prior to 2021.2.0, VVVVRRPP for 2021.2.0 and + * later. Look for 6 digit vs. 8 digit version number to decide encoding. + * VVVV is no smaller than the current year when a version is released. + */ +#if __INTEL_LLVM_COMPILER < 1000000L +#define COMPILER_VERSION_MAJOR DEC(__INTEL_LLVM_COMPILER / 100) +#define COMPILER_VERSION_MINOR DEC(__INTEL_LLVM_COMPILER / 10 % 10) +#define COMPILER_VERSION_PATCH DEC(__INTEL_LLVM_COMPILER % 10) +#else +#define COMPILER_VERSION_MAJOR DEC(__INTEL_LLVM_COMPILER / 10000) +#define COMPILER_VERSION_MINOR DEC(__INTEL_LLVM_COMPILER / 100 % 100) +#define COMPILER_VERSION_PATCH DEC(__INTEL_LLVM_COMPILER % 100) +#endif +#if defined(_MSC_VER) + /* _MSC_VER = VVRR */ +#define SIMULATE_VERSION_MAJOR DEC(_MSC_VER / 100) +#define SIMULATE_VERSION_MINOR DEC(_MSC_VER % 100) +#endif +#if defined(__GNUC__) +#define SIMULATE_VERSION_MAJOR DEC(__GNUC__) +#elif defined(__GNUG__) +#define SIMULATE_VERSION_MAJOR DEC(__GNUG__) +#endif +#if defined(__GNUC_MINOR__) +#define SIMULATE_VERSION_MINOR DEC(__GNUC_MINOR__) +#endif +#if defined(__GNUC_PATCHLEVEL__) +#define SIMULATE_VERSION_PATCH DEC(__GNUC_PATCHLEVEL__) +#endif + +#elif defined(__PATHCC__) +#define COMPILER_ID "PathScale" +#define COMPILER_VERSION_MAJOR DEC(__PATHCC__) +#define COMPILER_VERSION_MINOR DEC(__PATHCC_MINOR__) +#if defined(__PATHCC_PATCHLEVEL__) +#define COMPILER_VERSION_PATCH DEC(__PATHCC_PATCHLEVEL__) +#endif + +#elif defined(__BORLANDC__) && defined(__CODEGEARC_VERSION__) +#define COMPILER_ID "Embarcadero" +#define COMPILER_VERSION_MAJOR HEX(__CODEGEARC_VERSION__ >> 24 & 0x00FF) +#define COMPILER_VERSION_MINOR HEX(__CODEGEARC_VERSION__ >> 16 & 0x00FF) +#define COMPILER_VERSION_PATCH DEC(__CODEGEARC_VERSION__ & 0xFFFF) + +#elif defined(__BORLANDC__) +#define COMPILER_ID "Borland" + /* __BORLANDC__ = 0xVRR */ +#define COMPILER_VERSION_MAJOR HEX(__BORLANDC__ >> 8) +#define COMPILER_VERSION_MINOR HEX(__BORLANDC__ & 0xFF) + +#elif defined(__WATCOMC__) && __WATCOMC__ < 1200 +#define COMPILER_ID "Watcom" + /* __WATCOMC__ = VVRR */ +#define COMPILER_VERSION_MAJOR DEC(__WATCOMC__ / 100) +#define COMPILER_VERSION_MINOR DEC((__WATCOMC__ / 10) % 10) +#if (__WATCOMC__ % 10) > 0 +#define COMPILER_VERSION_PATCH DEC(__WATCOMC__ % 10) +#endif + +#elif defined(__WATCOMC__) +#define COMPILER_ID "OpenWatcom" + /* __WATCOMC__ = VVRP + 1100 */ +#define COMPILER_VERSION_MAJOR DEC((__WATCOMC__ - 1100) / 100) +#define COMPILER_VERSION_MINOR DEC((__WATCOMC__ / 10) % 10) +#if (__WATCOMC__ % 10) > 0 +#define COMPILER_VERSION_PATCH DEC(__WATCOMC__ % 10) +#endif + +#elif defined(__SUNPRO_CC) +#define COMPILER_ID "SunPro" +#if __SUNPRO_CC >= 0x5100 + /* __SUNPRO_CC = 0xVRRP */ +#define COMPILER_VERSION_MAJOR HEX(__SUNPRO_CC >> 12) +#define COMPILER_VERSION_MINOR HEX(__SUNPRO_CC >> 4 & 0xFF) +#define COMPILER_VERSION_PATCH HEX(__SUNPRO_CC & 0xF) +#else + /* __SUNPRO_CC = 0xVRP */ +#define COMPILER_VERSION_MAJOR HEX(__SUNPRO_CC >> 8) +#define COMPILER_VERSION_MINOR HEX(__SUNPRO_CC >> 4 & 0xF) +#define COMPILER_VERSION_PATCH HEX(__SUNPRO_CC & 0xF) +#endif + +#elif defined(__HP_aCC) +#define COMPILER_ID "HP" + /* __HP_aCC = VVRRPP */ +#define COMPILER_VERSION_MAJOR DEC(__HP_aCC / 10000) +#define COMPILER_VERSION_MINOR DEC(__HP_aCC / 100 % 100) +#define COMPILER_VERSION_PATCH DEC(__HP_aCC % 100) + +#elif defined(__DECCXX) +#define COMPILER_ID "Compaq" + /* __DECCXX_VER = VVRRTPPPP */ +#define COMPILER_VERSION_MAJOR DEC(__DECCXX_VER / 10000000) +#define COMPILER_VERSION_MINOR DEC(__DECCXX_VER / 100000 % 100) +#define COMPILER_VERSION_PATCH DEC(__DECCXX_VER % 10000) + +#elif defined(__IBMCPP__) && defined(__COMPILER_VER__) +#define COMPILER_ID "zOS" + /* __IBMCPP__ = VRP */ +#define COMPILER_VERSION_MAJOR DEC(__IBMCPP__ / 100) +#define COMPILER_VERSION_MINOR DEC(__IBMCPP__ / 10 % 10) +#define COMPILER_VERSION_PATCH DEC(__IBMCPP__ % 10) + +#elif defined(__ibmxl__) && defined(__clang__) +#define COMPILER_ID "XLClang" +#define COMPILER_VERSION_MAJOR DEC(__ibmxl_version__) +#define COMPILER_VERSION_MINOR DEC(__ibmxl_release__) +#define COMPILER_VERSION_PATCH DEC(__ibmxl_modification__) +#define COMPILER_VERSION_TWEAK DEC(__ibmxl_ptf_fix_level__) + +#elif defined(__IBMCPP__) && !defined(__COMPILER_VER__) && __IBMCPP__ >= 800 +#define COMPILER_ID "XL" + /* __IBMCPP__ = VRP */ +#define COMPILER_VERSION_MAJOR DEC(__IBMCPP__ / 100) +#define COMPILER_VERSION_MINOR DEC(__IBMCPP__ / 10 % 10) +#define COMPILER_VERSION_PATCH DEC(__IBMCPP__ % 10) + +#elif defined(__IBMCPP__) && !defined(__COMPILER_VER__) && __IBMCPP__ < 800 +#define COMPILER_ID "VisualAge" + /* __IBMCPP__ = VRP */ +#define COMPILER_VERSION_MAJOR DEC(__IBMCPP__ / 100) +#define COMPILER_VERSION_MINOR DEC(__IBMCPP__ / 10 % 10) +#define COMPILER_VERSION_PATCH DEC(__IBMCPP__ % 10) + +#elif defined(__NVCOMPILER) +#define COMPILER_ID "NVHPC" +#define COMPILER_VERSION_MAJOR DEC(__NVCOMPILER_MAJOR__) +#define COMPILER_VERSION_MINOR DEC(__NVCOMPILER_MINOR__) +#if defined(__NVCOMPILER_PATCHLEVEL__) +#define COMPILER_VERSION_PATCH DEC(__NVCOMPILER_PATCHLEVEL__) +#endif + +#elif defined(__PGI) +#define COMPILER_ID "PGI" +#define COMPILER_VERSION_MAJOR DEC(__PGIC__) +#define COMPILER_VERSION_MINOR DEC(__PGIC_MINOR__) +#if defined(__PGIC_PATCHLEVEL__) +#define COMPILER_VERSION_PATCH DEC(__PGIC_PATCHLEVEL__) +#endif + +#elif defined(_CRAYC) +#define COMPILER_ID "Cray" +#define COMPILER_VERSION_MAJOR DEC(_RELEASE_MAJOR) +#define COMPILER_VERSION_MINOR DEC(_RELEASE_MINOR) + +#elif defined(__TI_COMPILER_VERSION__) +#define COMPILER_ID "TI" + /* __TI_COMPILER_VERSION__ = VVVRRRPPP */ +#define COMPILER_VERSION_MAJOR DEC(__TI_COMPILER_VERSION__ / 1000000) +#define COMPILER_VERSION_MINOR DEC(__TI_COMPILER_VERSION__ / 1000 % 1000) +#define COMPILER_VERSION_PATCH DEC(__TI_COMPILER_VERSION__ % 1000) + +#elif defined(__CLANG_FUJITSU) +#define COMPILER_ID "FujitsuClang" +#define COMPILER_VERSION_MAJOR DEC(__FCC_major__) +#define COMPILER_VERSION_MINOR DEC(__FCC_minor__) +#define COMPILER_VERSION_PATCH DEC(__FCC_patchlevel__) +#define COMPILER_VERSION_INTERNAL_STR __clang_version__ + +#elif defined(__FUJITSU) +#define COMPILER_ID "Fujitsu" +#if defined(__FCC_version__) +#define COMPILER_VERSION __FCC_version__ +#elif defined(__FCC_major__) +#define COMPILER_VERSION_MAJOR DEC(__FCC_major__) +#define COMPILER_VERSION_MINOR DEC(__FCC_minor__) +#define COMPILER_VERSION_PATCH DEC(__FCC_patchlevel__) +#endif +#if defined(__fcc_version) +#define COMPILER_VERSION_INTERNAL DEC(__fcc_version) +#elif defined(__FCC_VERSION) +#define COMPILER_VERSION_INTERNAL DEC(__FCC_VERSION) +#endif + +#elif defined(__ghs__) +#define COMPILER_ID "GHS" + /* __GHS_VERSION_NUMBER = VVVVRP */ +#ifdef __GHS_VERSION_NUMBER +#define COMPILER_VERSION_MAJOR DEC(__GHS_VERSION_NUMBER / 100) +#define COMPILER_VERSION_MINOR DEC(__GHS_VERSION_NUMBER / 10 % 10) +#define COMPILER_VERSION_PATCH DEC(__GHS_VERSION_NUMBER % 10) +#endif + +#elif defined(__SCO_VERSION__) +#define COMPILER_ID "SCO" + +#elif defined(__ARMCC_VERSION) && !defined(__clang__) +#define COMPILER_ID "ARMCC" +#if __ARMCC_VERSION >= 1000000 + /* __ARMCC_VERSION = VRRPPPP */ +#define COMPILER_VERSION_MAJOR DEC(__ARMCC_VERSION / 1000000) +#define COMPILER_VERSION_MINOR DEC(__ARMCC_VERSION / 10000 % 100) +#define COMPILER_VERSION_PATCH DEC(__ARMCC_VERSION % 10000) +#else + /* __ARMCC_VERSION = VRPPPP */ +#define COMPILER_VERSION_MAJOR DEC(__ARMCC_VERSION / 100000) +#define COMPILER_VERSION_MINOR DEC(__ARMCC_VERSION / 10000 % 10) +#define COMPILER_VERSION_PATCH DEC(__ARMCC_VERSION % 10000) +#endif + +#elif defined(__clang__) && defined(__apple_build_version__) +#define COMPILER_ID "AppleClang" +#if defined(_MSC_VER) +#define SIMULATE_ID "MSVC" +#endif +#define COMPILER_VERSION_MAJOR DEC(__clang_major__) +#define COMPILER_VERSION_MINOR DEC(__clang_minor__) +#define COMPILER_VERSION_PATCH DEC(__clang_patchlevel__) +#if defined(_MSC_VER) + /* _MSC_VER = VVRR */ +#define SIMULATE_VERSION_MAJOR DEC(_MSC_VER / 100) +#define SIMULATE_VERSION_MINOR DEC(_MSC_VER % 100) +#endif +#define COMPILER_VERSION_TWEAK DEC(__apple_build_version__) + +#elif defined(__clang__) && defined(__ARMCOMPILER_VERSION) +#define COMPILER_ID "ARMClang" +#define COMPILER_VERSION_MAJOR DEC(__ARMCOMPILER_VERSION / 1000000) +#define COMPILER_VERSION_MINOR DEC(__ARMCOMPILER_VERSION / 10000 % 100) +#define COMPILER_VERSION_PATCH DEC(__ARMCOMPILER_VERSION % 10000) +#define COMPILER_VERSION_INTERNAL DEC(__ARMCOMPILER_VERSION) + +#elif defined(__clang__) +#define COMPILER_ID "Clang" +#if defined(_MSC_VER) +#define SIMULATE_ID "MSVC" +#endif +#define COMPILER_VERSION_MAJOR DEC(__clang_major__) +#define COMPILER_VERSION_MINOR DEC(__clang_minor__) +#define COMPILER_VERSION_PATCH DEC(__clang_patchlevel__) +#if defined(_MSC_VER) + /* _MSC_VER = VVRR */ +#define SIMULATE_VERSION_MAJOR DEC(_MSC_VER / 100) +#define SIMULATE_VERSION_MINOR DEC(_MSC_VER % 100) +#endif + +#elif defined(__GNUC__) || defined(__GNUG__) +#define COMPILER_ID "GNU" +#if defined(__GNUC__) +#define COMPILER_VERSION_MAJOR DEC(__GNUC__) +#else +#define COMPILER_VERSION_MAJOR DEC(__GNUG__) +#endif +#if defined(__GNUC_MINOR__) +#define COMPILER_VERSION_MINOR DEC(__GNUC_MINOR__) +#endif +#if defined(__GNUC_PATCHLEVEL__) +#define COMPILER_VERSION_PATCH DEC(__GNUC_PATCHLEVEL__) +#endif + +#elif defined(_MSC_VER) +#define COMPILER_ID "MSVC" + /* _MSC_VER = VVRR */ +#define COMPILER_VERSION_MAJOR DEC(_MSC_VER / 100) +#define COMPILER_VERSION_MINOR DEC(_MSC_VER % 100) +#if defined(_MSC_FULL_VER) +#if _MSC_VER >= 1400 + /* _MSC_FULL_VER = VVRRPPPPP */ +#define COMPILER_VERSION_PATCH DEC(_MSC_FULL_VER % 100000) +#else + /* _MSC_FULL_VER = VVRRPPPP */ +#define COMPILER_VERSION_PATCH DEC(_MSC_FULL_VER % 10000) +#endif +#endif +#if defined(_MSC_BUILD) +#define COMPILER_VERSION_TWEAK DEC(_MSC_BUILD) +#endif + +#elif defined(__VISUALDSPVERSION__) || defined(__ADSPBLACKFIN__) || \ + defined(__ADSPTS__) || defined(__ADSP21000__) +#define COMPILER_ID "ADSP" +#if defined(__VISUALDSPVERSION__) + /* __VISUALDSPVERSION__ = 0xVVRRPP00 */ +#define COMPILER_VERSION_MAJOR HEX(__VISUALDSPVERSION__ >> 24) +#define COMPILER_VERSION_MINOR HEX(__VISUALDSPVERSION__ >> 16 & 0xFF) +#define COMPILER_VERSION_PATCH HEX(__VISUALDSPVERSION__ >> 8 & 0xFF) +#endif + +#elif defined(__IAR_SYSTEMS_ICC__) || defined(__IAR_SYSTEMS_ICC) +#define COMPILER_ID "IAR" +#if defined(__VER__) && defined(__ICCARM__) +#define COMPILER_VERSION_MAJOR DEC((__VER__) / 1000000) +#define COMPILER_VERSION_MINOR DEC(((__VER__) / 1000) % 1000) +#define COMPILER_VERSION_PATCH DEC((__VER__) % 1000) +#define COMPILER_VERSION_INTERNAL DEC(__IAR_SYSTEMS_ICC__) +#elif defined(__VER__) && \ + (defined(__ICCAVR__) || defined(__ICCRX__) || defined(__ICCRH850__) || \ + defined(__ICCRL78__) || defined(__ICC430__) || defined(__ICCRISCV__) || \ + defined(__ICCV850__) || defined(__ICC8051__) || defined(__ICCSTM8__)) +#define COMPILER_VERSION_MAJOR DEC((__VER__) / 100) +#define COMPILER_VERSION_MINOR DEC((__VER__) - (((__VER__) / 100) * 100)) +#define COMPILER_VERSION_PATCH DEC(__SUBVERSION__) +#define COMPILER_VERSION_INTERNAL DEC(__IAR_SYSTEMS_ICC__) +#endif + + /* These compilers are either not known or too old to define an + identification macro. Try to identify the platform and guess that + it is the native compiler. */ +#elif defined(__hpux) || defined(__hpua) +#define COMPILER_ID "HP" + +#else /* unknown compiler */ +#define COMPILER_ID "Unknown-Compiler" +#endif + + return std::string(COMPILER_ID) +#ifdef COMPILER_VERSION + + " " + std::to_string(COMPILER_VERSION); +#elif defined(COMPILER_VERSION_MAJOR) + + " " + std::to_string(COMPILER_VERSION_MAJOR) +#ifdef COMPILER_VERSION_MINOR + + "." + std::to_string(COMPILER_VERSION_MINOR) +#ifdef COMPILER_VERSION_PATCH + + "." + std::to_string(COMPILER_VERSION_PATCH) +#ifdef COMPILER_VERSION_TWEAK + + "." + std::to_string(COMPILER_VERSION_TWEAK) +#endif +#endif +#endif + ; +#endif + +#undef DEC +#undef HEX +#undef COMPILER_ID +#undef COMPILER_VERSION +#undef COMPILER_VERSION_MAJOR +#undef COMPILER_VERSION_MINOR +#undef COMPILER_VERSION_PATCH +#undef COMPILER_VERSION_TWEAK +#undef SIMULATE_VERSION_MAJOR +#undef SIMULATE_VERSION_MINOR +#undef SIMULATE_VERSION_PATCH +#undef SIMULATE_VERSION_TWEAK +} + +/// @brief Returns the version of the C++ standard +inline std::string getCppVersion() { +#if defined(_MSC_VER) && _MSC_VER < 1600 + return "C++ 199711L"; +#elif _MSC_VER >= 1900 + return "C++ " + std::to_string(_MSVC_LANG); +#elif _MSC_VER >= 1600 + return "C++ 201103L"; +#else + return "C++ " + std::to_string(__cplusplus); +#endif +} + +/// @brief Returns the version of the standard library +inline std::string getStdLibVersion() { +#ifdef _LIBCPP_VERSION + return "libc++ " + std::to_string(_LIBCPP_VERSION); +#elif defined(__GLIBCXX__) + return "glibc++ " + std::to_string(__GLIBCXX__); +#elif defined(__GLIBCPP__) + return "glibc++ " + std::to_string(__GLIBCPP__); +#elif defined(__LIBCOMO__) + return "Comeau STL " + std::to_string(__LIBCOMO__); +#elif defined(__STL_CONFIG_H) + return "SGI STL"; +#elif defined(__MSL_CPP__) + return "MSL standard lib"; +#elif defined(__IBMCPP__) + return "VACPP STL"; +#elif defined(MSIPL_COMPILE_H) + return "Modena C++ STL"; +#elif (defined(_YVALS) && !defined(__IBMCPP__)) || defined(_CPPLIB_VER) + return "Dinkumware STL " + std::to_string(_CPPLIB_VER); +#elif defined(__STD_RWCOMPILER_H__) || defined(_RWSTD_VER) + return "Rogue Wave lib " + std::to_string(_RWSTD_VER); +#else + return "Unknown-STD"; +#endif +} + +/// @brief Returns the version of extra libraries +inline std::string getExtraLibsVersion() { + std::string s(""); + + // CUDA +#if defined(CUDA_VERSION) + if (!s.empty()) + s += ", "; + s += "CUDA " + std::to_string(CUDA_VERSION / 1000) + "." + + std::to_string((CUDA_VERSION % 100) / 10); +#endif + + // Intel MKL library +#if defined(INTEL_MKL_VERSION) + if (!s.empty()) + s += ", "; + s += "MKL " + std::to_string(INTEL_MKL_VERSION); +#endif + + // LibTorch +#if defined(TORCH_VERSION_MAJOR) + if (!s.empty()) + s += ", "; + s += "LibTorch " + std::to_string(TORCH_VERSION_MAJOR) + "." + + std::to_string(TORCH_VERSION_MINOR) + "." + + std::to_string(TORCH_VERSION_PATCH) + + " (#intraop threads: " + std::to_string(at::get_num_threads()) + + ", #interop threads: " + std::to_string(at::get_num_interop_threads()) + + ")"; +#endif + + // ROCm +#if defined(ROCM_VERSION_MAJOR) + if (!s.empty()) + s += ", "; + s += "ROCm " + std::to_string(ROCM_VERSION_MAJOR) + "." + + std::to_string(ROCM_VERSION_MINOR) + "." + + std::to_string(ROCM_VERSION_PATCH); +#endif + + return s; +} + +/// @brief Returns CPU information +inline std::string getCpuInfo() { +#if defined(_WIN32) || defined(_WIN64) + + int CPUInfo[4] = {-1}; + unsigned nExIds, i = 0; + char CPUBrandString[0x40]; + + __cpuid(CPUInfo, 0x80000000); + nExIds = CPUInfo[0]; + + for (i = 0x80000000; i <= nExIds; ++i) { + __cpuid(CPUInfo, i); + if (i == 0x80000002) + memcpy(CPUBrandString, CPUInfo, sizeof(CPUInfo)); + else if (i == 0x80000003) + memcpy(CPUBrandString + 16, CPUInfo, sizeof(CPUInfo)); + else if (i == 0x80000004) + memcpy(CPUBrandString + 32, CPUInfo, sizeof(CPUInfo)); + } + + return CPUBrandString; + +#elif __APPLE__ + + std::string CPUBrandString; + std::size_t size = 32; + + // Supply an oversized buffer, and avoid an extra call to sysctlbyname. + CPUBrandString.resize(size); + if (sysctlbyname("machdep.cpu.brand_string", &CPUBrandString[0], &size, NULL, + 0) == 0 && + size > 0) { + if (CPUBrandString[size - 1] == '\0') + size--; + CPUBrandString.resize(size); + return CPUBrandString; + } + +#elif __linux__ || __unix__ +#if defined(__x86_64__) && (defined(__GNUC__) || defined(__clang__) || \ + defined(__INTEL_COMPILER) || defined(__SUNCC_PRO)) + + char CPUBrandString[0x40]; + unsigned int CPUInfo[4] = {0, 0, 0, 0}; + + __cpuid(0x80000000, CPUInfo[0], CPUInfo[1], CPUInfo[2], CPUInfo[3]); + unsigned int nExIds = CPUInfo[0]; + + memset(CPUBrandString, 0, sizeof(CPUBrandString)); + + for (unsigned int i = 0x80000000; i <= nExIds; ++i) { + __cpuid(i, CPUInfo[0], CPUInfo[1], CPUInfo[2], CPUInfo[3]); + + if (i == 0x80000002) + memcpy(CPUBrandString, CPUInfo, sizeof(CPUInfo)); + else if (i == 0x80000003) + memcpy(CPUBrandString + 16, CPUInfo, sizeof(CPUInfo)); + else if (i == 0x80000004) + memcpy(CPUBrandString + 32, CPUInfo, sizeof(CPUInfo)); + } + + return CPUBrandString; + +#else + + char hostname[HOST_NAME_MAX + 1]; + gethostname(hostname, HOST_NAME_MAX + 1); + + std::string str = "Unknown-CPU ["; + str += hostname; + str += "]"; + + return str; + +#endif + +#endif + + return "Unknown-CPU"; +} + +/// @brief Returns total system memory in bytes +inline uint64_t getMemoryInBytes() { +#if defined(_WIN32) || defined(_WIN64) + + MEMORYSTATUSEX status; + status.dwLength = sizeof(status); + GlobalMemoryStatusEx(&status); + return (uint64_t)status.ullTotalPhys; + +#elif __APPLE__ + + int64_t memsize; + std::size_t size = sizeof(memsize); + + if (sysctlbyname("hw.memsize", &memsize, &size, NULL, 0) == 0) { + return (uint64_t)memsize; + } + +#elif __linux__ || __unix__ + + long pages = sysconf(_SC_PHYS_PAGES); + long page_size = sysconf(_SC_PAGE_SIZE); + return (uint64_t)(pages * page_size); + +#endif + + return 0; +} + +/// @brief Returns memory information +inline std::string getMemoryInfo() { + uint64_t memsize = getMemoryInBytes(); + if (memsize > 0) { + if (memsize < 1024) + return std::to_string(memsize) + " B"; + else if (memsize < 1024 * 1024) + return std::to_string(memsize / 1024) + " KB"; + else if (memsize < 1024 * 1024 * 1024) + return std::to_string(memsize / (1024 * 1024)) + " MB"; + else + return std::to_string(memsize / (1024 * 1024 * 1024)) + " GB"; + } else + return "Unknown-Memory"; +} + +/// @brief Returns version information +inline std::string getVersion() { + return std::string("IgANets - Isogeometric Analysis Networks") + + " (version " + getIgANetVersion() + ")\n" + "Compiled by " + + getCompilerVersion() + " (" + getCppVersion() + ", " + + getStdLibVersion() + ", " + getExtraLibsVersion() + ")\n" + + "Running on " + getCpuInfo() + " (memory " + getMemoryInfo() + ")\n"; +} + +} // namespace iganet diff --git a/include/utils.hpp b/include/utils.hpp index 7ab3308e..fefe5dcd 100644 --- a/include/utils.hpp +++ b/include/utils.hpp @@ -1,29 +1,29 @@ -/** - @file include/utils.hpp - - @brief Utility functions - - @author Matthias Moller - - @copyright This file is part of the IgANet project - - This Source Code Form is subject to the terms of the Mozilla Public - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at http://mozilla.org/MPL/2.0/. -*/ - -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +/** + @file include/utils.hpp + + @brief Utility functions + + @author Matthias Moller + + @copyright This file is part of the IgANet project + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include diff --git a/include/utils/container.hpp b/include/utils/container.hpp index 82c07f36..c28568bf 100644 --- a/include/utils/container.hpp +++ b/include/utils/container.hpp @@ -1,309 +1,309 @@ -/** - @file include/utils/container.hpp - - @brief Container utility functions - - @author Matthias Moller - - @copyright This file is part of the IgANet project - - This Source Code Form is subject to the terms of the Mozilla Public - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at http://mozilla.org/MPL/2.0/. -*/ - -#pragma once - -#include -#include -#include - -#include - -#include - -namespace iganet { -namespace utils { - -/// @brief Converts an std::vector object into std::array -template -inline std::array to_array(std::vector &&vector) { - std::array array; - std::move(vector.begin(), vector.end(), array.begin()); - return array; -} - -/// @brief Converts an std::array object into std::vector -template -inline std::vector to_vector(std::array &&array) { - std::vector vector; - std::move(array.begin(), array.end(), vector.begin()); - return vector; -} - -/// @brief Converts a list of arguments into std::array -template inline auto to_array(Args &&...args) { - return std::array::type, sizeof...(Args)>{ - std::move(args)...}; -} - -/// @brief Converts a list of arguments into std::vector -template inline auto to_vector(Args &&...args) { - return std::vector::type>{ - std::move(args)...}; -} - -/// @brief Converts an std::array to torch::Tensor -/// @{ -template -inline auto -to_tensor(const std::array &array, - torch::IntArrayRef sizes = torch::IntArrayRef{-1}, - const iganet::Options &options = iganet::Options{}) { - if (options.device() == torch::kCPU) - return torch::from_blob(const_cast(std::data(array)), - (sizes == torch::IntArrayRef{-1}) ? array.size() - : sizes, - options) - .detach() - .clone() - .requires_grad_(options.requires_grad()); - else - return torch::from_blob(const_cast(std::data(array)), - (sizes == torch::IntArrayRef{-1}) ? array.size() - : sizes, - options.device(torch::kCPU)) - .detach() - .clone() - .to(options.device()) - .requires_grad_(options.requires_grad()); -} - -template -inline auto to_tensor(const std::array &array, - const iganet::Options &options) { - if (options.device() == torch::kCPU) - return torch::from_blob(const_cast(std::data(array)), array.size(), - options) - .detach() - .clone() - .requires_grad_(options.requires_grad()); - else - return torch::from_blob(const_cast(std::data(array)), array.size(), - options.device(torch::kCPU)) - .detach() - .clone() - .to(options.device()) - .requires_grad_(options.requires_grad()); -} -/// @} - -/// @brief Converts an std::initializer_list to torch::Tensor -/// @{ -template -inline auto -to_tensor(std::initializer_list list, - torch::IntArrayRef sizes = torch::IntArrayRef{-1}, - const iganet::Options &options = iganet::Options{}) { - if (options.device() == torch::kCPU) - return torch::from_blob( - const_cast(std::data(list)), - (sizes == torch::IntArrayRef{-1}) ? list.size() : sizes, options) - .detach() - .clone() - .requires_grad_(options.requires_grad()); - else - return torch::from_blob(const_cast(std::data(list)), - (sizes == torch::IntArrayRef{-1}) ? list.size() - : sizes, - options.device(torch::kCPU)) - .detach() - .clone() - .to(options.device()) - .requires_grad_(options.requires_grad()); -} - -template -inline auto to_tensor(std::initializer_list &list, - const iganet::Options &options) { - if (options.device() == torch::kCPU) - return torch::from_blob(const_cast(std::data(list)), list.size(), - options) - .detach() - .clone() - .requires_grad_(options.requires_grad()); - else - return torch::from_blob(const_cast(std::data(list)), list.size(), - options.device(torch::kCPU)) - .detach() - .clone() - .to(options.device()) - .requires_grad_(options.requires_grad()); -} -/// @} - -/// @brief Converts an std::vector to torch::Tensor -/// @{ -template -inline auto -to_tensor(const std::vector &vector, - torch::IntArrayRef sizes = torch::IntArrayRef{-1}, - const iganet::Options &options = iganet::Options{}) { - if (options.device() == torch::kCPU) - return torch::from_blob(const_cast(std::data(vector)), - (sizes == torch::IntArrayRef{-1}) ? vector.size() - : sizes, - options) - .detach() - .clone() - .requires_grad_(options.requires_grad()); - else - return torch::from_blob(const_cast(std::data(vector)), - (sizes == torch::IntArrayRef{-1}) ? vector.size() - : sizes, - options.device(torch::kCPU)) - .detach() - .clone() - .to(options.device()) - .requires_grad_(options.requires_grad()); -} - -template -inline auto to_tensor(const std::vector &vector, - const iganet::Options &options) { - if (options.device() == torch::kCPU) - return torch::from_blob(const_cast(std::data(vector)), vector.size(), - options) - .detach() - .clone() - .requires_grad_(options.requires_grad()); - else - return torch::from_blob(const_cast(std::data(vector)), vector.size(), - options.device(torch::kCPU)) - .detach() - .clone() - .to(options.device()) - .requires_grad_(options.requires_grad()); -} -/// @} - -/// @brief Converts an std::array to a at::IntArrayRef object -template -inline auto to_ArrayRef(const std::array &array) { - return at::ArrayRef{array}; -} - -/// @brief Concatenates multiple std::array objects -/// @{ -template -inline auto concat(const std::array &...arrays) { - std::array result; - std::size_t index{}; - - ((std::copy_n(arrays.begin(), N, result.begin() + index), index += N), ...); - - return result; -} - -template -inline auto concat(std::array &&...arrays) { - std::array result; - std::size_t index{}; - - ((std::copy_n(std::make_move_iterator(arrays.begin()), N, - result.begin() + index), - index += N), - ...); - - return result; -} -/// @} - -/// @brief Concatenates multiple std::vector objects -/// @{ -template -inline auto concat(const std::vector &...vectors) { - std::vector::type> result; - - (result.insert(result.end(), vectors.begin(), vectors.end()), ...); - - return result; -} - -template inline auto concat(std::vector &&...vectors) { - std::vector::type> result; - - (result.insert(result.end(), std::make_move_iterator(vectors.begin()), - std::make_move_iterator(vectors.end())), - ...); - - return result; -} -/// @} - -/// @brief Adds two std::arrays -template -inline constexpr std::array operator+(std::array lhs, - std::array rhs) { - std::array result; - - for (std::size_t i = 0; i < size; ++i) - result[i] = lhs[i] + rhs[i]; - - return result; -} - -/// @brief Appends data to a torch::ArrayRef object -template -inline constexpr auto operator+(torch::ArrayRef array, T data) { - std::vector result{array.vec()}; - result.push_back(data); - return result; -} - -/// @brief Appends data to a std::array object -template -inline constexpr auto operator+(std::array array, T data) { - std::array result; - for (std::size_t i = 0; i < N; ++i) - result[i] = array[i]; - result[N] = data; - return result; -} - -/// @brief Appends data to a std::vector object -template -inline constexpr auto operator+(std::vector vector, T data) { - std::vector result{vector}; - result.push_back(data); - return result; -} - -/// @brief Prepends data to a torch::ArrayRef object -template -inline constexpr auto operator+(T data, torch::ArrayRef array) { - std::vector result{array.vec()}; - result.insert(result.begin(), data); - return result; -} - -/// @brief Prepends data to a std::array object -template -inline constexpr auto operator+(T data, std::array array) { - std::array result; - result[0] = data; - for (std::size_t i = 0; i < N; ++i) - result[i + 1] = array[i]; - return result; -} - -/// @brief Prepends data to a std::vector object -template -inline constexpr auto operator+(T data, std::vector vector) { - std::vector result{vector}; - result.insert(result.begin(), data); - return result; -} - -} // namespace utils -} // namespace iganet +/** + @file include/utils/container.hpp + + @brief Container utility functions + + @author Matthias Moller + + @copyright This file is part of the IgANet project + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#pragma once + +#include +#include +#include + +#include + +#include + +namespace iganet { +namespace utils { + +/// @brief Converts an std::vector object into std::array +template +inline std::array to_array(std::vector &&vector) { + std::array array; + std::move(vector.begin(), vector.end(), array.begin()); + return array; +} + +/// @brief Converts an std::array object into std::vector +template +inline std::vector to_vector(std::array &&array) { + std::vector vector; + std::move(array.begin(), array.end(), vector.begin()); + return vector; +} + +/// @brief Converts a list of arguments into std::array +template inline auto to_array(Args &&...args) { + return std::array::type, sizeof...(Args)>{ + std::move(args)...}; +} + +/// @brief Converts a list of arguments into std::vector +template inline auto to_vector(Args &&...args) { + return std::vector::type>{ + std::move(args)...}; +} + +/// @brief Converts an std::array to torch::Tensor +/// @{ +template +inline auto +to_tensor(const std::array &array, + torch::IntArrayRef sizes = torch::IntArrayRef{-1}, + const iganet::Options &options = iganet::Options{}) { + if (options.device() == torch::kCPU) + return torch::from_blob(const_cast(std::data(array)), + (sizes == torch::IntArrayRef{-1}) ? array.size() + : sizes, + options) + .detach() + .clone() + .requires_grad_(options.requires_grad()); + else + return torch::from_blob(const_cast(std::data(array)), + (sizes == torch::IntArrayRef{-1}) ? array.size() + : sizes, + options.device(torch::kCPU)) + .detach() + .clone() + .to(options.device()) + .requires_grad_(options.requires_grad()); +} + +template +inline auto to_tensor(const std::array &array, + const iganet::Options &options) { + if (options.device() == torch::kCPU) + return torch::from_blob(const_cast(std::data(array)), array.size(), + options) + .detach() + .clone() + .requires_grad_(options.requires_grad()); + else + return torch::from_blob(const_cast(std::data(array)), array.size(), + options.device(torch::kCPU)) + .detach() + .clone() + .to(options.device()) + .requires_grad_(options.requires_grad()); +} +/// @} + +/// @brief Converts an std::initializer_list to torch::Tensor +/// @{ +template +inline auto +to_tensor(std::initializer_list list, + torch::IntArrayRef sizes = torch::IntArrayRef{-1}, + const iganet::Options &options = iganet::Options{}) { + if (options.device() == torch::kCPU) + return torch::from_blob( + const_cast(std::data(list)), + (sizes == torch::IntArrayRef{-1}) ? list.size() : sizes, options) + .detach() + .clone() + .requires_grad_(options.requires_grad()); + else + return torch::from_blob(const_cast(std::data(list)), + (sizes == torch::IntArrayRef{-1}) ? list.size() + : sizes, + options.device(torch::kCPU)) + .detach() + .clone() + .to(options.device()) + .requires_grad_(options.requires_grad()); +} + +template +inline auto to_tensor(std::initializer_list &list, + const iganet::Options &options) { + if (options.device() == torch::kCPU) + return torch::from_blob(const_cast(std::data(list)), list.size(), + options) + .detach() + .clone() + .requires_grad_(options.requires_grad()); + else + return torch::from_blob(const_cast(std::data(list)), list.size(), + options.device(torch::kCPU)) + .detach() + .clone() + .to(options.device()) + .requires_grad_(options.requires_grad()); +} +/// @} + +/// @brief Converts an std::vector to torch::Tensor +/// @{ +template +inline auto +to_tensor(const std::vector &vector, + torch::IntArrayRef sizes = torch::IntArrayRef{-1}, + const iganet::Options &options = iganet::Options{}) { + if (options.device() == torch::kCPU) + return torch::from_blob(const_cast(std::data(vector)), + (sizes == torch::IntArrayRef{-1}) ? vector.size() + : sizes, + options) + .detach() + .clone() + .requires_grad_(options.requires_grad()); + else + return torch::from_blob(const_cast(std::data(vector)), + (sizes == torch::IntArrayRef{-1}) ? vector.size() + : sizes, + options.device(torch::kCPU)) + .detach() + .clone() + .to(options.device()) + .requires_grad_(options.requires_grad()); +} + +template +inline auto to_tensor(const std::vector &vector, + const iganet::Options &options) { + if (options.device() == torch::kCPU) + return torch::from_blob(const_cast(std::data(vector)), vector.size(), + options) + .detach() + .clone() + .requires_grad_(options.requires_grad()); + else + return torch::from_blob(const_cast(std::data(vector)), vector.size(), + options.device(torch::kCPU)) + .detach() + .clone() + .to(options.device()) + .requires_grad_(options.requires_grad()); +} +/// @} + +/// @brief Converts an std::array to a at::IntArrayRef object +template +inline auto to_ArrayRef(const std::array &array) { + return at::ArrayRef{array}; +} + +/// @brief Concatenates multiple std::array objects +/// @{ +template +inline auto concat(const std::array &...arrays) { + std::array result; + std::size_t index{}; + + ((std::copy_n(arrays.begin(), N, result.begin() + index), index += N), ...); + + return result; +} + +template +inline auto concat(std::array &&...arrays) { + std::array result; + std::size_t index{}; + + ((std::copy_n(std::make_move_iterator(arrays.begin()), N, + result.begin() + index), + index += N), + ...); + + return result; +} +/// @} + +/// @brief Concatenates multiple std::vector objects +/// @{ +template +inline auto concat(const std::vector &...vectors) { + std::vector::type> result; + + (result.insert(result.end(), vectors.begin(), vectors.end()), ...); + + return result; +} + +template inline auto concat(std::vector &&...vectors) { + std::vector::type> result; + + (result.insert(result.end(), std::make_move_iterator(vectors.begin()), + std::make_move_iterator(vectors.end())), + ...); + + return result; +} +/// @} + +/// @brief Adds two std::arrays +template +inline constexpr std::array operator+(std::array lhs, + std::array rhs) { + std::array result; + + for (std::size_t i = 0; i < size; ++i) + result[i] = lhs[i] + rhs[i]; + + return result; +} + +/// @brief Appends data to a torch::ArrayRef object +template +inline constexpr auto operator+(torch::ArrayRef array, T data) { + std::vector result{array.vec()}; + result.push_back(data); + return result; +} + +/// @brief Appends data to a std::array object +template +inline constexpr auto operator+(std::array array, T data) { + std::array result; + for (std::size_t i = 0; i < N; ++i) + result[i] = array[i]; + result[N] = data; + return result; +} + +/// @brief Appends data to a std::vector object +template +inline constexpr auto operator+(std::vector vector, T data) { + std::vector result{vector}; + result.push_back(data); + return result; +} + +/// @brief Prepends data to a torch::ArrayRef object +template +inline constexpr auto operator+(T data, torch::ArrayRef array) { + std::vector result{array.vec()}; + result.insert(result.begin(), data); + return result; +} + +/// @brief Prepends data to a std::array object +template +inline constexpr auto operator+(T data, std::array array) { + std::array result; + result[0] = data; + for (std::size_t i = 0; i < N; ++i) + result[i + 1] = array[i]; + return result; +} + +/// @brief Prepends data to a std::vector object +template +inline constexpr auto operator+(T data, std::vector vector) { + std::vector result{vector}; + result.insert(result.begin(), data); + return result; +} + +} // namespace utils +} // namespace iganet diff --git a/include/utils/index_sequence.hpp b/include/utils/index_sequence.hpp index b3851bb1..d87b71b9 100644 --- a/include/utils/index_sequence.hpp +++ b/include/utils/index_sequence.hpp @@ -1,41 +1,41 @@ -/** - @file include/utils/index_sequence.hpp - - @brief Integer sequence utility function - - @author Matthias Moller - - @copyright This file is part of the IgANet project - - This Source Code Form is subject to the terms of the Mozilla Public - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at http://mozilla.org/MPL/2.0/. -*/ - -#pragma once - -#include - -namespace iganet { -namespace utils { - - namespace detail { - - /// @brief Reverse index sequence helper - template - struct make_reverse_index_sequence_helper; - - template - struct make_reverse_index_sequence_helper> - : std::index_sequence<(N - NN)...> {}; - - } // namespace detail - - /// @brief Reverse index sequence - template - struct make_reverse_index_sequence - : detail::make_reverse_index_sequence_helper{})> {}; - -} // namespace utils -} // namespace iganet +/** + @file include/utils/index_sequence.hpp + + @brief Integer sequence utility function + + @author Matthias Moller + + @copyright This file is part of the IgANet project + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#pragma once + +#include + +namespace iganet { +namespace utils { + + namespace detail { + + /// @brief Reverse index sequence helper + template + struct make_reverse_index_sequence_helper; + + template + struct make_reverse_index_sequence_helper> + : std::index_sequence<(N - NN)...> {}; + + } // namespace detail + + /// @brief Reverse index sequence + template + struct make_reverse_index_sequence + : detail::make_reverse_index_sequence_helper{})> {}; + +} // namespace utils +} // namespace iganet diff --git a/include/utils/linalg.hpp b/include/utils/linalg.hpp index 77ebd296..10c4b319 100644 --- a/include/utils/linalg.hpp +++ b/include/utils/linalg.hpp @@ -1,209 +1,209 @@ -/** - @file include/utils/linalg.hpp - - @brief Linear algebra utility functions - - @author Matthias Moller - - @copyright This file is part of the IgANet project - - This Source Code Form is subject to the terms of the Mozilla Public - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at http://mozilla.org/MPL/2.0/. -*/ - -#pragma once - -#include - -namespace iganet { -namespace utils { - -/// @brief Computes the directional dot-product between two tensors -/// with summation along the given dimension -/// -/// @tparam dim Dimension along which the sum is computed -/// -/// @tparam T0 Type of the first argument -/// -/// @tparam T1 Type of the second argument -/// -/// @param[in] t0 First argument -/// -/// @param[in] t1 Second argument -/// -/// @result Tensor containing the directional dot-product -template -inline auto dotproduct(T0 &&t0, T1 &&t1) { - return torch::sum(torch::mul(t0, t1), dim); -} - -/// @brief Computes the directional Kronecker-product between two -/// tensors along the given dimension -/// -/// @tparam dim Dimension along which the Kronecker-product is computed -/// -/// @tparam T0 Type of the first argument -/// -/// @tparam T1 Type of the second argument -/// -/// @param[in] t0 First argument -/// -/// @param[in] t1 Second argument -/// -/// @result Tensor containing the dimensional Kronecker-product -/// -/// @note This is not the regular Kronecker-product but a -/// directional variant, that is, the Kronecker-product is computed -/// along the given direction. All other directions are left -/// unchanged. For the regular Kronecker-product use `torch::kron`. -template -inline auto kronproduct(T0 &&t0, T1 &&t1) { - switch (t1.sizes().size()) { - case 1: - return torch::mul(t0.repeat_interleave(t1.size(dim), 0), - t1.repeat({t0.size(dim)})); - case 2: - if constexpr (dim == 0) - return torch::mul(t0.repeat_interleave(t1.size(dim), 0), - t1.repeat({t0.size(dim), 1})); - else if constexpr (dim == 1) - return torch::mul(t0.repeat_interleave(t1.size(dim), 1), - t1.repeat({1, t0.size(dim)})); - case 3: - if constexpr (dim == 0) - return torch::mul(t0.repeat_interleave(t1.size(dim), 0), - t1.repeat({t0.size(dim), 1, 1})); - else if constexpr (dim == 1) - return torch::mul(t0.repeat_interleave(t1.size(dim), 1), - t1.repeat({1, t0.size(dim), 1})); - else if constexpr (dim == 2) - return torch::mul(t0.repeat_interleave(t1.size(dim), 0), - t1.repeat({1, 1, t0.size(dim)})); - case 4: - if constexpr (dim == 0) - return torch::mul(t0.repeat_interleave(t1.size(dim), 0), - t1.repeat({t0.size(dim), 1, 1, 1})); - else if constexpr (dim == 1) - return torch::mul(t0.repeat_interleave(t1.size(dim), 1), - t1.repeat({1, t0.size(dim), 1, 1})); - else if constexpr (dim == 2) - return torch::mul(t0.repeat_interleave(t1.size(dim), 0), - t1.repeat({1, 1, t0.size(dim), 1})); - else if constexpr (dim == 3) - return torch::mul(t0.repeat_interleave(t1.size(dim), 0), - t1.repeat({1, 1, 1, t0.size(dim)})); - case 5: - if constexpr (dim == 0) - return torch::mul(t0.repeat_interleave(t1.size(dim), 0), - t1.repeat({t0.size(dim), 1, 1, 1, 1})); - else if constexpr (dim == 1) - return torch::mul(t0.repeat_interleave(t1.size(dim), 1), - t1.repeat({1, t0.size(dim), 1, 1, 1})); - else if constexpr (dim == 2) - return torch::mul(t0.repeat_interleave(t1.size(dim), 0), - t1.repeat({1, 1, t0.size(dim), 1, 1})); - else if constexpr (dim == 3) - return torch::mul(t0.repeat_interleave(t1.size(dim), 0), - t1.repeat({1, 1, 1, t0.size(dim), 1})); - else if constexpr (dim == 4) - return torch::mul(t0.repeat_interleave(t1.size(dim), 0), - t1.repeat({1, 1, 1, 1, t0.size(dim)})); - case 6: - if constexpr (dim == 0) - return torch::mul(t0.repeat_interleave(t1.size(dim), 0), - t1.repeat({t0.size(dim), 1, 1, 1, 1, 1})); - else if constexpr (dim == 1) - return torch::mul(t0.repeat_interleave(t1.size(dim), 1), - t1.repeat({1, t0.size(dim), 1, 1, 1, 1})); - else if constexpr (dim == 2) - return torch::mul(t0.repeat_interleave(t1.size(dim), 0), - t1.repeat({1, 1, t0.size(dim), 1, 1, 1})); - else if constexpr (dim == 3) - return torch::mul(t0.repeat_interleave(t1.size(dim), 0), - t1.repeat({1, 1, 1, t0.size(dim), 1, 1})); - else if constexpr (dim == 4) - return torch::mul(t0.repeat_interleave(t1.size(dim), 0), - t1.repeat({1, 1, 1, 1, t0.size(dim), 1})); - else if constexpr (dim == 5) - return torch::mul(t0.repeat_interleave(t1.size(dim), 0), - t1.repeat({1, 1, 1, 1, 1, t0.size(dim)})); - case 7: - if constexpr (dim == 0) - return torch::mul(t0.repeat_interleave(t1.size(dim), 0), - t1.repeat({t0.size(dim), 1, 1, 1, 1, 1, 1})); - else if constexpr (dim == 1) - return torch::mul(t0.repeat_interleave(t1.size(dim), 1), - t1.repeat({1, t0.size(dim), 1, 1, 1, 1, 1})); - else if constexpr (dim == 2) - return torch::mul(t0.repeat_interleave(t1.size(dim), 0), - t1.repeat({1, 1, t0.size(dim), 1, 1, 1, 1})); - else if constexpr (dim == 3) - return torch::mul(t0.repeat_interleave(t1.size(dim), 0), - t1.repeat({1, 1, 1, t0.size(dim), 1, 1, 1})); - else if constexpr (dim == 4) - return torch::mul(t0.repeat_interleave(t1.size(dim), 0), - t1.repeat({1, 1, 1, 1, t0.size(dim), 1, 1})); - else if constexpr (dim == 5) - return torch::mul(t0.repeat_interleave(t1.size(dim), 0), - t1.repeat({1, 1, 1, 1, 1, t0.size(dim), 1})); - else if constexpr (dim == 6) - return torch::mul(t0.repeat_interleave(t1.size(dim), 0), - t1.repeat({1, 1, 1, 1, 1, 1, t0.size(dim)})); - case 8: - if constexpr (dim == 0) - return torch::mul(t0.repeat_interleave(t1.size(dim), 0), - t1.repeat({t0.size(dim), 1, 1, 1, 1, 1, 1, 1})); - else if constexpr (dim == 1) - return torch::mul(t0.repeat_interleave(t1.size(dim), 1), - t1.repeat({1, t0.size(dim), 1, 1, 1, 1, 1, 1})); - else if constexpr (dim == 2) - return torch::mul(t0.repeat_interleave(t1.size(dim), 0), - t1.repeat({1, 1, t0.size(dim), 1, 1, 1, 1, 1})); - else if constexpr (dim == 3) - return torch::mul(t0.repeat_interleave(t1.size(dim), 0), - t1.repeat({1, 1, 1, t0.size(dim), 1, 1, 1, 1})); - else if constexpr (dim == 4) - return torch::mul(t0.repeat_interleave(t1.size(dim), 0), - t1.repeat({1, 1, 1, 1, t0.size(dim), 1, 1, 1})); - else if constexpr (dim == 5) - return torch::mul(t0.repeat_interleave(t1.size(dim), 0), - t1.repeat({1, 1, 1, 1, 1, t0.size(dim), 1, 1})); - else if constexpr (dim == 6) - return torch::mul(t0.repeat_interleave(t1.size(dim), 0), - t1.repeat({1, 1, 1, 1, 1, 1, t0.size(dim), 1})); - else if constexpr (dim == 7) - return torch::mul(t0.repeat_interleave(t1.size(dim), 0), - t1.repeat({1, 1, 1, 1, 1, 1, 1, t0.size(dim)})); - default: - throw std::runtime_error("Unsupported tensor dimension"); - } -} - -/// @brief Computes the directional Kronecker-product between two -/// tensors along the given dimension -/// -/// @tparam dim Dimension along which the Kronecker-product is computed -/// -/// @tparam T Type of the first argument -/// -/// @tparam Ts Types of the variadic arguments -/// -/// @param[in] t First argument -/// -/// @param[in] ts Variadic arguments -/// -/// @result Tensor containing the dimensional Kronecker-product -/// -/// @note This is not the regular Kronecker-product but a -/// directional variant, that is, the Kronecker-product is computed -/// along the given direction. All other directions are left -/// unchanged. For the regular Kronecker-product use `torch::kron`. -template -inline auto kronproduct(T &&t, Ts &&...ts) { - return kronproduct(std::forward(t), - kronproduct(std::forward(ts)...)); -} - -} // namespace utils -} // namespace iganet +/** + @file include/utils/linalg.hpp + + @brief Linear algebra utility functions + + @author Matthias Moller + + @copyright This file is part of the IgANet project + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#pragma once + +#include + +namespace iganet { +namespace utils { + +/// @brief Computes the directional dot-product between two tensors +/// with summation along the given dimension +/// +/// @tparam dim Dimension along which the sum is computed +/// +/// @tparam T0 Type of the first argument +/// +/// @tparam T1 Type of the second argument +/// +/// @param[in] t0 First argument +/// +/// @param[in] t1 Second argument +/// +/// @result Tensor containing the directional dot-product +template +inline auto dotproduct(T0 &&t0, T1 &&t1) { + return torch::sum(torch::mul(t0, t1), dim); +} + +/// @brief Computes the directional Kronecker-product between two +/// tensors along the given dimension +/// +/// @tparam dim Dimension along which the Kronecker-product is computed +/// +/// @tparam T0 Type of the first argument +/// +/// @tparam T1 Type of the second argument +/// +/// @param[in] t0 First argument +/// +/// @param[in] t1 Second argument +/// +/// @result Tensor containing the dimensional Kronecker-product +/// +/// @note This is not the regular Kronecker-product but a +/// directional variant, that is, the Kronecker-product is computed +/// along the given direction. All other directions are left +/// unchanged. For the regular Kronecker-product use `torch::kron`. +template +inline auto kronproduct(T0 &&t0, T1 &&t1) { + switch (t1.sizes().size()) { + case 1: + return torch::mul(t0.repeat_interleave(t1.size(dim), 0), + t1.repeat({t0.size(dim)})); + case 2: + if constexpr (dim == 0) + return torch::mul(t0.repeat_interleave(t1.size(dim), 0), + t1.repeat({t0.size(dim), 1})); + else if constexpr (dim == 1) + return torch::mul(t0.repeat_interleave(t1.size(dim), 1), + t1.repeat({1, t0.size(dim)})); + case 3: + if constexpr (dim == 0) + return torch::mul(t0.repeat_interleave(t1.size(dim), 0), + t1.repeat({t0.size(dim), 1, 1})); + else if constexpr (dim == 1) + return torch::mul(t0.repeat_interleave(t1.size(dim), 1), + t1.repeat({1, t0.size(dim), 1})); + else if constexpr (dim == 2) + return torch::mul(t0.repeat_interleave(t1.size(dim), 0), + t1.repeat({1, 1, t0.size(dim)})); + case 4: + if constexpr (dim == 0) + return torch::mul(t0.repeat_interleave(t1.size(dim), 0), + t1.repeat({t0.size(dim), 1, 1, 1})); + else if constexpr (dim == 1) + return torch::mul(t0.repeat_interleave(t1.size(dim), 1), + t1.repeat({1, t0.size(dim), 1, 1})); + else if constexpr (dim == 2) + return torch::mul(t0.repeat_interleave(t1.size(dim), 0), + t1.repeat({1, 1, t0.size(dim), 1})); + else if constexpr (dim == 3) + return torch::mul(t0.repeat_interleave(t1.size(dim), 0), + t1.repeat({1, 1, 1, t0.size(dim)})); + case 5: + if constexpr (dim == 0) + return torch::mul(t0.repeat_interleave(t1.size(dim), 0), + t1.repeat({t0.size(dim), 1, 1, 1, 1})); + else if constexpr (dim == 1) + return torch::mul(t0.repeat_interleave(t1.size(dim), 1), + t1.repeat({1, t0.size(dim), 1, 1, 1})); + else if constexpr (dim == 2) + return torch::mul(t0.repeat_interleave(t1.size(dim), 0), + t1.repeat({1, 1, t0.size(dim), 1, 1})); + else if constexpr (dim == 3) + return torch::mul(t0.repeat_interleave(t1.size(dim), 0), + t1.repeat({1, 1, 1, t0.size(dim), 1})); + else if constexpr (dim == 4) + return torch::mul(t0.repeat_interleave(t1.size(dim), 0), + t1.repeat({1, 1, 1, 1, t0.size(dim)})); + case 6: + if constexpr (dim == 0) + return torch::mul(t0.repeat_interleave(t1.size(dim), 0), + t1.repeat({t0.size(dim), 1, 1, 1, 1, 1})); + else if constexpr (dim == 1) + return torch::mul(t0.repeat_interleave(t1.size(dim), 1), + t1.repeat({1, t0.size(dim), 1, 1, 1, 1})); + else if constexpr (dim == 2) + return torch::mul(t0.repeat_interleave(t1.size(dim), 0), + t1.repeat({1, 1, t0.size(dim), 1, 1, 1})); + else if constexpr (dim == 3) + return torch::mul(t0.repeat_interleave(t1.size(dim), 0), + t1.repeat({1, 1, 1, t0.size(dim), 1, 1})); + else if constexpr (dim == 4) + return torch::mul(t0.repeat_interleave(t1.size(dim), 0), + t1.repeat({1, 1, 1, 1, t0.size(dim), 1})); + else if constexpr (dim == 5) + return torch::mul(t0.repeat_interleave(t1.size(dim), 0), + t1.repeat({1, 1, 1, 1, 1, t0.size(dim)})); + case 7: + if constexpr (dim == 0) + return torch::mul(t0.repeat_interleave(t1.size(dim), 0), + t1.repeat({t0.size(dim), 1, 1, 1, 1, 1, 1})); + else if constexpr (dim == 1) + return torch::mul(t0.repeat_interleave(t1.size(dim), 1), + t1.repeat({1, t0.size(dim), 1, 1, 1, 1, 1})); + else if constexpr (dim == 2) + return torch::mul(t0.repeat_interleave(t1.size(dim), 0), + t1.repeat({1, 1, t0.size(dim), 1, 1, 1, 1})); + else if constexpr (dim == 3) + return torch::mul(t0.repeat_interleave(t1.size(dim), 0), + t1.repeat({1, 1, 1, t0.size(dim), 1, 1, 1})); + else if constexpr (dim == 4) + return torch::mul(t0.repeat_interleave(t1.size(dim), 0), + t1.repeat({1, 1, 1, 1, t0.size(dim), 1, 1})); + else if constexpr (dim == 5) + return torch::mul(t0.repeat_interleave(t1.size(dim), 0), + t1.repeat({1, 1, 1, 1, 1, t0.size(dim), 1})); + else if constexpr (dim == 6) + return torch::mul(t0.repeat_interleave(t1.size(dim), 0), + t1.repeat({1, 1, 1, 1, 1, 1, t0.size(dim)})); + case 8: + if constexpr (dim == 0) + return torch::mul(t0.repeat_interleave(t1.size(dim), 0), + t1.repeat({t0.size(dim), 1, 1, 1, 1, 1, 1, 1})); + else if constexpr (dim == 1) + return torch::mul(t0.repeat_interleave(t1.size(dim), 1), + t1.repeat({1, t0.size(dim), 1, 1, 1, 1, 1, 1})); + else if constexpr (dim == 2) + return torch::mul(t0.repeat_interleave(t1.size(dim), 0), + t1.repeat({1, 1, t0.size(dim), 1, 1, 1, 1, 1})); + else if constexpr (dim == 3) + return torch::mul(t0.repeat_interleave(t1.size(dim), 0), + t1.repeat({1, 1, 1, t0.size(dim), 1, 1, 1, 1})); + else if constexpr (dim == 4) + return torch::mul(t0.repeat_interleave(t1.size(dim), 0), + t1.repeat({1, 1, 1, 1, t0.size(dim), 1, 1, 1})); + else if constexpr (dim == 5) + return torch::mul(t0.repeat_interleave(t1.size(dim), 0), + t1.repeat({1, 1, 1, 1, 1, t0.size(dim), 1, 1})); + else if constexpr (dim == 6) + return torch::mul(t0.repeat_interleave(t1.size(dim), 0), + t1.repeat({1, 1, 1, 1, 1, 1, t0.size(dim), 1})); + else if constexpr (dim == 7) + return torch::mul(t0.repeat_interleave(t1.size(dim), 0), + t1.repeat({1, 1, 1, 1, 1, 1, 1, t0.size(dim)})); + default: + throw std::runtime_error("Unsupported tensor dimension"); + } +} + +/// @brief Computes the directional Kronecker-product between two +/// tensors along the given dimension +/// +/// @tparam dim Dimension along which the Kronecker-product is computed +/// +/// @tparam T Type of the first argument +/// +/// @tparam Ts Types of the variadic arguments +/// +/// @param[in] t First argument +/// +/// @param[in] ts Variadic arguments +/// +/// @result Tensor containing the dimensional Kronecker-product +/// +/// @note This is not the regular Kronecker-product but a +/// directional variant, that is, the Kronecker-product is computed +/// along the given direction. All other directions are left +/// unchanged. For the regular Kronecker-product use `torch::kron`. +template +inline auto kronproduct(T &&t, Ts &&...ts) { + return kronproduct(std::forward(t), + kronproduct(std::forward(ts)...)); +} + +} // namespace utils +} // namespace iganet diff --git a/include/utils/serialize.hpp b/include/utils/serialize.hpp index 6a04ca53..1501046d 100644 --- a/include/utils/serialize.hpp +++ b/include/utils/serialize.hpp @@ -1,659 +1,659 @@ -/** - @file include/utils/serialize.hpp - - @brief Serialization utility functions - - @author Matthias Moller - - @copyright This file is part of the IgANet project - - This Source Code Form is subject to the terms of the Mozilla Public - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at http://mozilla.org/MPL/2.0/. -*/ - -#pragma once - -#include - -#include -#include - -#include - -namespace iganet { -namespace utils { - -/// @brief Serialization prototype -/// -/// This abstract class defines the functions that must be -/// implemented to serialize an object -struct Serializable { - /// @brief Returns the object as JSON object - virtual nlohmann::json to_json() const = 0; - - /// @brief Returns a string representation of the object - virtual void pretty_print(std::ostream &os = Log(log::info)) const = 0; -}; - -/// @brief Converts a torch::TensorAccessor object to a JSON object -template -inline auto to_json(const torch::TensorAccessor &accessor) { - auto json = nlohmann::json::array(); - - if constexpr (N == 1) { - for (int64_t i = 0; i < accessor.size(0); ++i) - json.push_back(accessor[i]); - } else if constexpr (N == 2) { - for (int64_t i = 0; i < accessor.size(0); ++i) - for (int64_t j = 0; j < accessor.size(1); ++j) - json.push_back(accessor[i][j]); - } else if constexpr (N == 3) { - for (int64_t i = 0; i < accessor.size(0); ++i) - for (int64_t j = 0; j < accessor.size(1); ++j) - for (int64_t k = 0; k < accessor.size(2); ++k) - json.push_back(accessor[i][j][k]); - } else if constexpr (N == 4) { - for (int64_t i = 0; i < accessor.size(0); ++i) - for (int64_t j = 0; j < accessor.size(1); ++j) - for (int64_t k = 0; k < accessor.size(2); ++k) - for (int64_t l = 0; l < accessor.size(3); ++l) - json.push_back(accessor[i][j][k][l]); - } - - return json; -} - -/// @brief Converts a torch::Tensor object to a JSON object -template -inline auto to_json(const torch::Tensor &tensor) { - if (tensor.is_cuda()) { - auto [tensor_cpu, accessor] = to_tensorAccessor(tensor, torch::kCPU); - return to_json(accessor); - } else { - auto accessor = to_tensorAccessor(tensor); - return to_json(accessor); - } -} - -/// @brief Converts an std::array of torch::Tensor objects to a JSON -/// object -template -inline auto to_json(const utils::TensorArray &tensors) { - auto json = nlohmann::json::array(); - -#ifdef __CUDACC__ -#pragma nv_diag_suppress 186 -#endif - - for (std::size_t i = 0; i < M; ++i) { - if (tensors[i].is_cuda()) { - auto [tensor_cpu, accessor] = - to_tensorAccessor(tensors[i], torch::kCPU); - json.push_back(to_json(accessor)); - } else { - auto accessor = to_tensorAccessor(tensors[i]); - json.push_back(to_json(accessor)); - } - } - -#ifdef __CUDACC__ -#pragma nv_diag_default 186 -#endif - - return json; -} - -#ifdef IGANET_WITH_GISMO -/// @brief Converts a gismo::gsMatrix object to a JSON object -template -inline auto to_json(const gismo::gsMatrix &matrix, - bool flatten = false) { - auto json = nlohmann::json::array(); - - if constexpr (Options == gismo::RowMajor) { - if (flatten) { - for (std::size_t i = 0; i < matrix.rows(); ++i) - for (std::size_t j = 0; j < matrix.cols(); ++j) - json.push_back(matrix(i, j)); - } else { - for (std::size_t i = 0; i < matrix.rows(); ++i) { - auto data = nlohmann::json::array(); - for (std::size_t j = 0; j < matrix.cols(); ++j) { - data.push_back(matrix(i, j)); - } - json.emplace_back(data); - } - } - - } else if constexpr (Options == gismo::ColMajor) { - if (flatten) { - for (std::size_t j = 0; j < matrix.cols(); ++j) - for (std::size_t i = 0; i < matrix.rows(); ++i) - json.push_back(matrix(i, j)); - } else { - for (std::size_t j = 0; j < matrix.cols(); ++j) { - auto data = nlohmann::json::array(); - for (std::size_t i = 0; i < matrix.rows(); ++i) { - data.push_back(matrix(i, j)); - } - json.emplace_back(data); - } - } - - } else - throw std::runtime_error("Invalid matrix options"); - - return json; -} - -/// @brief Converts a gismo::gsBSpline object to a JSON object -template inline auto to_json(const gismo::gsBSpline &bspline) { - auto json = nlohmann::json(); - - json["degrees"] = nlohmann::json::array(); - - for (std::size_t i = 0; i < bspline.parDim(); ++i) - json["degrees"].push_back(bspline.degree(i)); - - json["geoDim"] = bspline.geoDim(); - json["parDim"] = bspline.parDim(); - - json["ncoeffs"] = nlohmann::json::array(); - for (std::size_t i = 0; i < bspline.parDim(); ++i) - json["ncoeffs"].push_back(bspline.basis().size(i)); - - json["coeffs"] = to_json(bspline.coefs()); - - json["nknots"] = nlohmann::json::array(); - for (std::size_t i = 0; i < bspline.parDim(); ++i) - json["nknots"].push_back(bspline.knots(i).size()); - - json["knots"] = nlohmann::json::array(); - for (std::size_t i = 0; i < bspline.parDim(); ++i) - json["knots"].push_back(bspline.knots(i)); - - return json; -} - -/// @brief Converts a gismo::gsTensorBSpline object to a JSON object -template -inline auto to_json(const gismo::gsTensorBSpline &bspline) { - auto json = nlohmann::json(); - - json["degrees"] = nlohmann::json::array(); - - for (std::size_t i = 0; i < bspline.parDim(); ++i) - json["degrees"].push_back(bspline.degree(i)); - - json["geoDim"] = bspline.geoDim(); - json["parDim"] = bspline.parDim(); - - json["ncoeffs"] = nlohmann::json::array(); - for (std::size_t i = 0; i < bspline.parDim(); ++i) - json["ncoeffs"].push_back(bspline.basis().size(i)); - - json["coeffs"] = to_json(bspline.coefs()); - - json["nknots"] = nlohmann::json::array(); - for (std::size_t i = 0; i < bspline.parDim(); ++i) - json["nknots"].push_back(bspline.knots(i).size()); - - json["knots"] = nlohmann::json::array(); - for (std::size_t i = 0; i < bspline.parDim(); ++i) - json["knots"].push_back(bspline.knots(i)); - - return json; -} - -/// @brief Converts a gismo::gsMultiPatch object to a JSON object -template inline auto to_json(const gismo::gsMultiPatch &mp) { - - if (mp.nPatches() > 1) { - // This must be revised in the protocol - auto json = nlohmann::json::array(); - - for (std::size_t i = 0; i < mp.nPatches(); ++i) { - - if (auto patch = dynamic_cast *>(&mp.patch(i))) - json.push_back(to_json(*patch)); - else if (auto patch = - dynamic_cast *>(&mp.patch(i))) - json.push_back(to_json(*patch)); - else if (auto patch = - dynamic_cast *>(&mp.patch(i))) - json.push_back(to_json(*patch)); - else if (auto patch = - dynamic_cast *>(&mp.patch(i))) - json.push_back(to_json(*patch)); - else - json.push_back("{ Invalid patch type }"); - } - - return json; - - } else { - - if (auto patch = dynamic_cast *>(&mp.patch(0))) - return to_json(*patch); - else if (auto patch = - dynamic_cast *>(&mp.patch(0))) - return to_json(*patch); - else if (auto patch = - dynamic_cast *>(&mp.patch(0))) - return to_json(*patch); - else if (auto patch = - dynamic_cast *>(&mp.patch(0))) - return to_json(*patch); - else - return nlohmann::json("{ Invalid patch type }"); - } -} -#endif - -/// @brief Converts a torch::TensorAccessor object to an XML document object -template -inline pugi::xml_document to_xml(const torch::TensorAccessor &accessor, - torch::IntArrayRef sizes, - std::string tag = "Matrix", int id = 0, - std::string label = "", int index = -1) { - pugi::xml_document doc; - pugi::xml_node root = doc.append_child("xml"); - to_xml(accessor, sizes, root, id, label, index); - - return doc; -} - -/// @brief Converts a torch::TensorAccessor object to an XML object -template -inline pugi::xml_node &to_xml(const torch::TensorAccessor &accessor, - torch::IntArrayRef sizes, pugi::xml_node &root, - std::string tag = "Matrix", int id = 0, - std::string label = "", int index = -1) { - - // add node - pugi::xml_node node = root.append_child(tag.c_str()); - - if (id >= 0) - node.append_attribute("id") = id; - - if (index >= 0) - node.append_attribute("index") = index; - - if (!label.empty()) - node.append_attribute("label") = label.c_str(); - - // add rows/cols or dimensions - if (tag == "Matrix") { - if constexpr (N == 1) { - node.append_attribute("rows") = sizes[0]; - node.append_attribute("cols") = 1; - - std::stringstream ss; - for (std::size_t i = 0; i < sizes[0]; ++i) - ss << std::to_string(accessor[i]) << (i < sizes[0] - 1 ? " " : ""); - node.append_child(pugi::node_pcdata).set_value(ss.str().c_str()); - } else if constexpr (N == 2) { - node.append_attribute("rows") = sizes[0]; - node.append_attribute("cols") = sizes[1]; - - std::stringstream ss; - for (std::size_t i = 0; i < sizes[0]; ++i) - for (std::size_t j = 0; j < sizes[1]; ++j) - ss << std::to_string(accessor[i][j]) - << (j < sizes[1] - 1 ? " " : (i < sizes[0] - 1 ? " " : "")); - node.append_child(pugi::node_pcdata).set_value(ss.str().c_str()); - } else - throw std::runtime_error( - "Tag \"Matrix\" only supports 1- and 2-dimensional tensors"); - } else { - std::stringstream ss; - for (const auto &size : sizes) - ss << std::to_string(size) << " "; - - pugi::xml_node dims = node.append_child("Dimensions"); - dims.append_child(pugi::node_pcdata).set_value(ss.str().c_str()); - - ss.str(""); - if constexpr (N == 1) { - for (std::size_t i = 0; i < sizes[0]; ++i) - ss << std::to_string(accessor[i]) << " "; - } else if constexpr (N == 2) { - for (std::size_t i = 0; i < sizes[0]; ++i) - for (std::size_t j = 0; j < sizes[1]; ++j) - ss << std::to_string(accessor[i][j]) << " "; - } else if constexpr (N == 3) { - for (std::size_t i = 0; i < sizes[0]; ++i) - for (std::size_t j = 0; j < sizes[1]; ++j) - for (std::size_t k = 0; j < sizes[2]; ++k) - ss << std::to_string(accessor[i][j][k]) << " "; - } else if constexpr (N == 4) { - for (std::size_t i = 0; i < sizes[0]; ++i) - for (std::size_t j = 0; j < sizes[1]; ++j) - for (std::size_t k = 0; k < sizes[2]; ++k) - for (std::size_t l = 0; l < sizes[3]; ++l) - ss << std::to_string(accessor[i][j][k][l]) << " "; - - } else if constexpr (N == 5) { - for (std::size_t i = 0; i < sizes[0]; ++i) - for (std::size_t j = 0; j < sizes[1]; ++j) - for (std::size_t k = 0; k < sizes[2]; ++k) - for (std::size_t l = 0; l < sizes[3]; ++l) - for (std::size_t m = 0; m < sizes[4]; ++m) - ss << std::to_string(accessor[i][j][k][l][m]) << " "; - } else if constexpr (N == 6) { - for (std::size_t i = 0; i < sizes[0]; ++i) - for (std::size_t j = 0; j < sizes[1]; ++j) - for (std::size_t k = 0; k < sizes[2]; ++k) - for (std::size_t l = 0; l < sizes[3]; ++l) - for (std::size_t m = 0; m < sizes[4]; ++m) - for (std::size_t n = 0; n < sizes[5]; ++n) - ss << std::to_string(accessor[i][j][k][l][m][n]) << " "; - } else - throw std::runtime_error( - "Dimensions higher than 4 are not implemented yet"); - - pugi::xml_node data = node.append_child("Data"); - data.append_child(pugi::node_pcdata).set_value(ss.str().c_str()); - } - - return root; -} - -/// @brief Converts a torch::Tensor object to an XML document object -template -inline pugi::xml_document to_xml(const torch::Tensor &tensor, - std::string tag = "Matrix", int id = 0, - std::string label = "", int index = -1) { - pugi::xml_document doc; - pugi::xml_node root = doc.append_child("xml"); - to_xml(tensor, root, id, label, index); - - return doc; -} - -/// @brief Converts a torch::Tensor object to an XML object -template -inline pugi::xml_node &to_xml(const torch::Tensor &tensor, pugi::xml_node &root, - std::string tag = "Matrix", int id = 0, - std::string label = "", int index = -1) { - - if (tensor.is_cuda()) { - auto [tensor_cpu, accessor] = to_tensorAccessor(tensor, torch::kCPU); - return to_xml(accessor, tensor.sizes(), root, tag, id, label, index); - } else { - auto accessor = to_tensorAccessor(tensor); - return to_xml(accessor, tensor.sizes(), root, tag, id, label, index); - } -} - -/// @brief Converts an std::array of torch::Tensor objects to an XML -/// object -template -inline pugi::xml_document to_xml(const utils::TensorArray &tensors, - std::string tag = "Matrix", int id = 0, - std::string label = "", int index = -1) { - pugi::xml_document doc; - pugi::xml_node root = doc.append_child("xml"); - to_xml(tensors, root, id, label, index); - - return doc; -} - -/// @brief Converts an std::array of torch::Tensor objects to an XML -/// object -template -inline pugi::xml_node &to_xml(const utils::TensorArray &tensors, - pugi::xml_node &root, std::string tag = "Matrix", - int id = 0, std::string label = "") { - - for (std::size_t i = 0; i < M; ++i) { - if (tensors[i].is_cuda()) { - auto [tensor_cpu, accessor] = - to_tensorAccessor(tensors[i], torch::kCPU); - to_xml(accessor, tensors[i].sizes(), root, tag, id, label, i); - } else { - auto accessor = to_tensorAccessor(tensors[i]); - to_xml(accessor, tensors[i].sizes(), root, tag, id, label, i); - } - } - - return root; -} - -/// @brief Converts an XML documentobject to a torch::TensorAccessor object -template -inline torch::TensorAccessor & -from_xml(const pugi::xml_document &doc, torch::TensorAccessor &accessor, - torch::IntArrayRef sizes, std::string tag = "Matrix", int id = 0, - std::string label = "", int index = -1) { - return from_xml(doc.child("xml"), accessor, sizes, tag, id, label, index); -} - -/// @brief Converts an XML object to a torch::TensorAccessor object -template -inline torch::TensorAccessor & -from_xml(const pugi::xml_node &root, torch::TensorAccessor &accessor, - torch::IntArrayRef sizes, std::string tag = "Matrix", int id = 0, - std::string label = "", int index = -1) { - - return accessor; -} - -/// @brief Converts an XML document object to a torch::Tensor object -template -inline torch::Tensor & -from_xml(const pugi::xml_document &doc, torch::Tensor &tensor, - std::string tag = "Matrix", int id = 0, std::string label = "", - bool alloc = true, int index = -1) { - return from_xml(doc.child("xml"), tensor, tag, id, label, index); -} - -/// @brief Converts an XML object to a torch::Tensor object -template -inline torch::Tensor & -from_xml(const pugi::xml_node &root, torch::Tensor &tensor, - std::string tag = "Matrix", int id = 0, std::string label = "", - bool alloc = true, int index = -1) { - - // Loop through all nodes - for (pugi::xml_node node : root.children(tag.c_str())) { - - if ((id >= 0 ? node.attribute("id").as_int() == id : true) && - (index >= 0 ? node.attribute("index").as_int() == index : true) && - (!label.empty() ? node.attribute("label").value() == label : true)) { - - if (tag == "Matrix") { - - int64_t rows = node.attribute("rows").as_int(); - int64_t cols = node.attribute("cols").as_int(); - - if (!alloc && (tensor.size(0) != rows || tensor.size(1) != cols)) - throw std::runtime_error("Invalid matrix dimensions"); - - else if (alloc && (tensor.size(0) != rows || tensor.size(1) != cols)) - tensor = torch::zeros({rows, cols}, tensor.options()); - - std::string values = std::regex_replace( - node.text().get(), std::regex("[\t\r\n\a]+| +"), " "); - - auto [tensor_cpu, accessor] = - to_tensorAccessor(tensor, torch::kCPU); - auto value = strtok(&values[0], " "); - - for (int64_t i = 0; i < rows; ++i) - for (int64_t j = 0; j < cols; ++j) { - if (value == NULL) - throw std::runtime_error( - "XML object does not provide enough coefficients"); - - accessor[i][j] = static_cast(std::stod(value)); - value = strtok(NULL, " "); - } - - if (value != NULL) - throw std::runtime_error("XML object provides too many coefficients"); - - if (tensor.device().type() != torch::kCPU) - tensor = std::move(tensor_cpu); - - return tensor; - - } else { - - std::vector sizes; - - // Check for "Dimensions" - if (pugi::xml_node dims = node.child("Dimensions")) { - - std::string values = std::regex_replace( - dims.text().get(), std::regex("[\t\r\n\a]+| +"), " "); - for (auto value = strtok(&values[0], " "); value != NULL; - value = strtok(NULL, " ")) - sizes.push_back(static_cast(std::stoi(value))); - - if (!alloc && (tensor.sizes() != sizes)) - throw std::runtime_error("Invalid tensor dimensions"); - - else if (alloc && (tensor.sizes() != sizes)) - tensor = torch::zeros(torch::IntArrayRef{sizes}, tensor.options()); - - if (sizes.size() != N) - throw std::runtime_error("Invalid tensor dimensions"); - - // Check for "Data" - if (pugi::xml_node data = node.child("Data")) { - std::string values = std::regex_replace( - data.text().get(), std::regex("[\t\r\n\a]+| +"), " "); - - auto [tensor_cpu, accessor] = - to_tensorAccessor(tensor, torch::kCPU); - auto value = strtok(&values[0], " "); - - if constexpr (N == 1) { - for (int64_t i = 0; i < sizes[0]; ++i) { - if (value == NULL) - throw std::runtime_error( - "XML object does not provide enough coefficients"); - - accessor[i] = static_cast(std::stod(value)); - value = strtok(NULL, " "); - } - } else if constexpr (N == 2) { - for (int64_t i = 0; i < sizes[0]; ++i) - for (int64_t j = 0; j < sizes[1]; ++j) { - if (value == NULL) - throw std::runtime_error( - "XML object does not provide enough coefficients"); - - accessor[i][j] = static_cast(std::stod(value)); - value = strtok(NULL, " "); - } - } else if constexpr (N == 3) { - for (int64_t i = 0; i < sizes[0]; ++i) - for (int64_t j = 0; j < sizes[1]; ++j) - for (int64_t k = 0; k < sizes[2]; ++k) { - if (value == NULL) - throw std::runtime_error( - "XML object does not provide enough coefficients"); - - accessor[i][j][k] = static_cast(std::stod(value)); - value = strtok(NULL, " "); - } - } else if constexpr (N == 4) { - for (int64_t i = 0; i < sizes[0]; ++i) - for (int64_t j = 0; j < sizes[1]; ++j) - for (int64_t k = 0; k < sizes[2]; ++k) - for (int64_t l = 0; l < sizes[3]; ++l) { - if (value == NULL) - throw std::runtime_error( - "XML object does not provide enough coefficients"); - - accessor[i][j][k][l] = static_cast(std::stod(value)); - value = strtok(NULL, " "); - } - } else if constexpr (N == 5) { - for (int64_t i = 0; i < sizes[0]; ++i) - for (int64_t j = 0; j < sizes[1]; ++j) - for (int64_t k = 0; k < sizes[2]; ++k) - for (int64_t l = 0; l < sizes[3]; ++l) - for (int64_t m = 0; m < sizes[4]; ++m) { - if (value == NULL) - throw std::runtime_error( - "XML object does not provide enough " - "coefficients"); - - accessor[i][j][k][l][m] = - static_cast(std::stod(value)); - value = strtok(NULL, " "); - } - } else if constexpr (N == 6) { - for (int64_t i = 0; i < sizes[0]; ++i) - for (int64_t j = 0; j < sizes[1]; ++j) - for (int64_t k = 0; k < sizes[2]; ++k) - for (int64_t l = 0; l < sizes[3]; ++l) - for (int64_t m = 0; m < sizes[4]; ++m) - for (int64_t n = 0; n < sizes[5]; ++n) { - if (value == NULL) - throw std::runtime_error( - "XML object does not provide enough " - "coefficients"); - - accessor[i][j][k][l][m][n] = - static_cast(std::stod(value)); - value = strtok(NULL, " "); - } - } - - if (value != NULL) - throw std::runtime_error( - "XML object provides too many coefficients"); - - if (tensor.device().type() != torch::kCPU) - tensor = std::move(tensor_cpu); - - return tensor; - } // "Data" - } // "Dimenions" - - throw std::runtime_error( - "XML object does not provide a \"Dimensions\" tag"); - - return tensor; - } - - } // try next node - } // "tag" - - throw std::runtime_error( - "XML object does not provide tag with given id, index, and/or label"); - return tensor; -} - -/// @brief Converts an XML document object to an std::array of torch::Tensor -/// objects -template -inline utils::TensorArray & -from_xml(const pugi::xml_document &doc, utils::TensorArray &tensors, - std::string tag = "Matrix", int id = 0, bool alloc = true, - std::string label = "") { - - return from_xml(doc.child("xml"), tensors, tag, id, label, alloc); -} - -/// @brief Converts an XML object to an std::array of torch::Tensor objects -template -inline utils::TensorArray & -from_xml(const pugi::xml_node &root, utils::TensorArray &tensors, - std::string tag = "Matrix", int id = 0, bool alloc = true, - std::string label = "") { - - for (std::size_t i = 0; i < M; ++i) { - from_xml(root, tensors[i], tag, id, label, alloc, i); - } - - return tensors; -} - -} // namespace utils -} // namespace iganet +/** + @file include/utils/serialize.hpp + + @brief Serialization utility functions + + @author Matthias Moller + + @copyright This file is part of the IgANet project + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#pragma once + +#include + +#include +#include + +#include + +namespace iganet { +namespace utils { + +/// @brief Serialization prototype +/// +/// This abstract class defines the functions that must be +/// implemented to serialize an object +struct Serializable { + /// @brief Returns the object as JSON object + virtual nlohmann::json to_json() const = 0; + + /// @brief Returns a string representation of the object + virtual void pretty_print(std::ostream &os = Log(log::info)) const = 0; +}; + +/// @brief Converts a torch::TensorAccessor object to a JSON object +template +inline auto to_json(const torch::TensorAccessor &accessor) { + auto json = nlohmann::json::array(); + + if constexpr (N == 1) { + for (int64_t i = 0; i < accessor.size(0); ++i) + json.push_back(accessor[i]); + } else if constexpr (N == 2) { + for (int64_t i = 0; i < accessor.size(0); ++i) + for (int64_t j = 0; j < accessor.size(1); ++j) + json.push_back(accessor[i][j]); + } else if constexpr (N == 3) { + for (int64_t i = 0; i < accessor.size(0); ++i) + for (int64_t j = 0; j < accessor.size(1); ++j) + for (int64_t k = 0; k < accessor.size(2); ++k) + json.push_back(accessor[i][j][k]); + } else if constexpr (N == 4) { + for (int64_t i = 0; i < accessor.size(0); ++i) + for (int64_t j = 0; j < accessor.size(1); ++j) + for (int64_t k = 0; k < accessor.size(2); ++k) + for (int64_t l = 0; l < accessor.size(3); ++l) + json.push_back(accessor[i][j][k][l]); + } + + return json; +} + +/// @brief Converts a torch::Tensor object to a JSON object +template +inline auto to_json(const torch::Tensor &tensor) { + if (tensor.is_cuda()) { + auto [tensor_cpu, accessor] = to_tensorAccessor(tensor, torch::kCPU); + return to_json(accessor); + } else { + auto accessor = to_tensorAccessor(tensor); + return to_json(accessor); + } +} + +/// @brief Converts an std::array of torch::Tensor objects to a JSON +/// object +template +inline auto to_json(const utils::TensorArray &tensors) { + auto json = nlohmann::json::array(); + +#ifdef __CUDACC__ +#pragma nv_diag_suppress 186 +#endif + + for (std::size_t i = 0; i < M; ++i) { + if (tensors[i].is_cuda()) { + auto [tensor_cpu, accessor] = + to_tensorAccessor(tensors[i], torch::kCPU); + json.push_back(to_json(accessor)); + } else { + auto accessor = to_tensorAccessor(tensors[i]); + json.push_back(to_json(accessor)); + } + } + +#ifdef __CUDACC__ +#pragma nv_diag_default 186 +#endif + + return json; +} + +#ifdef IGANET_WITH_GISMO +/// @brief Converts a gismo::gsMatrix object to a JSON object +template +inline auto to_json(const gismo::gsMatrix &matrix, + bool flatten = false) { + auto json = nlohmann::json::array(); + + if constexpr (Options == gismo::RowMajor) { + if (flatten) { + for (std::size_t i = 0; i < matrix.rows(); ++i) + for (std::size_t j = 0; j < matrix.cols(); ++j) + json.push_back(matrix(i, j)); + } else { + for (std::size_t i = 0; i < matrix.rows(); ++i) { + auto data = nlohmann::json::array(); + for (std::size_t j = 0; j < matrix.cols(); ++j) { + data.push_back(matrix(i, j)); + } + json.emplace_back(data); + } + } + + } else if constexpr (Options == gismo::ColMajor) { + if (flatten) { + for (std::size_t j = 0; j < matrix.cols(); ++j) + for (std::size_t i = 0; i < matrix.rows(); ++i) + json.push_back(matrix(i, j)); + } else { + for (std::size_t j = 0; j < matrix.cols(); ++j) { + auto data = nlohmann::json::array(); + for (std::size_t i = 0; i < matrix.rows(); ++i) { + data.push_back(matrix(i, j)); + } + json.emplace_back(data); + } + } + + } else + throw std::runtime_error("Invalid matrix options"); + + return json; +} + +/// @brief Converts a gismo::gsBSpline object to a JSON object +template inline auto to_json(const gismo::gsBSpline &bspline) { + auto json = nlohmann::json(); + + json["degrees"] = nlohmann::json::array(); + + for (std::size_t i = 0; i < bspline.parDim(); ++i) + json["degrees"].push_back(bspline.degree(i)); + + json["geoDim"] = bspline.geoDim(); + json["parDim"] = bspline.parDim(); + + json["ncoeffs"] = nlohmann::json::array(); + for (std::size_t i = 0; i < bspline.parDim(); ++i) + json["ncoeffs"].push_back(bspline.basis().size(i)); + + json["coeffs"] = to_json(bspline.coefs()); + + json["nknots"] = nlohmann::json::array(); + for (std::size_t i = 0; i < bspline.parDim(); ++i) + json["nknots"].push_back(bspline.knots(i).size()); + + json["knots"] = nlohmann::json::array(); + for (std::size_t i = 0; i < bspline.parDim(); ++i) + json["knots"].push_back(bspline.knots(i)); + + return json; +} + +/// @brief Converts a gismo::gsTensorBSpline object to a JSON object +template +inline auto to_json(const gismo::gsTensorBSpline &bspline) { + auto json = nlohmann::json(); + + json["degrees"] = nlohmann::json::array(); + + for (std::size_t i = 0; i < bspline.parDim(); ++i) + json["degrees"].push_back(bspline.degree(i)); + + json["geoDim"] = bspline.geoDim(); + json["parDim"] = bspline.parDim(); + + json["ncoeffs"] = nlohmann::json::array(); + for (std::size_t i = 0; i < bspline.parDim(); ++i) + json["ncoeffs"].push_back(bspline.basis().size(i)); + + json["coeffs"] = to_json(bspline.coefs()); + + json["nknots"] = nlohmann::json::array(); + for (std::size_t i = 0; i < bspline.parDim(); ++i) + json["nknots"].push_back(bspline.knots(i).size()); + + json["knots"] = nlohmann::json::array(); + for (std::size_t i = 0; i < bspline.parDim(); ++i) + json["knots"].push_back(bspline.knots(i)); + + return json; +} + +/// @brief Converts a gismo::gsMultiPatch object to a JSON object +template inline auto to_json(const gismo::gsMultiPatch &mp) { + + if (mp.nPatches() > 1) { + // This must be revised in the protocol + auto json = nlohmann::json::array(); + + for (std::size_t i = 0; i < mp.nPatches(); ++i) { + + if (auto patch = dynamic_cast *>(&mp.patch(i))) + json.push_back(to_json(*patch)); + else if (auto patch = + dynamic_cast *>(&mp.patch(i))) + json.push_back(to_json(*patch)); + else if (auto patch = + dynamic_cast *>(&mp.patch(i))) + json.push_back(to_json(*patch)); + else if (auto patch = + dynamic_cast *>(&mp.patch(i))) + json.push_back(to_json(*patch)); + else + json.push_back("{ Invalid patch type }"); + } + + return json; + + } else { + + if (auto patch = dynamic_cast *>(&mp.patch(0))) + return to_json(*patch); + else if (auto patch = + dynamic_cast *>(&mp.patch(0))) + return to_json(*patch); + else if (auto patch = + dynamic_cast *>(&mp.patch(0))) + return to_json(*patch); + else if (auto patch = + dynamic_cast *>(&mp.patch(0))) + return to_json(*patch); + else + return nlohmann::json("{ Invalid patch type }"); + } +} +#endif + +/// @brief Converts a torch::TensorAccessor object to an XML document object +template +inline pugi::xml_document to_xml(const torch::TensorAccessor &accessor, + torch::IntArrayRef sizes, + std::string tag = "Matrix", int id = 0, + std::string label = "", int index = -1) { + pugi::xml_document doc; + pugi::xml_node root = doc.append_child("xml"); + to_xml(accessor, sizes, root, id, label, index); + + return doc; +} + +/// @brief Converts a torch::TensorAccessor object to an XML object +template +inline pugi::xml_node &to_xml(const torch::TensorAccessor &accessor, + torch::IntArrayRef sizes, pugi::xml_node &root, + std::string tag = "Matrix", int id = 0, + std::string label = "", int index = -1) { + + // add node + pugi::xml_node node = root.append_child(tag.c_str()); + + if (id >= 0) + node.append_attribute("id") = id; + + if (index >= 0) + node.append_attribute("index") = index; + + if (!label.empty()) + node.append_attribute("label") = label.c_str(); + + // add rows/cols or dimensions + if (tag == "Matrix") { + if constexpr (N == 1) { + node.append_attribute("rows") = sizes[0]; + node.append_attribute("cols") = 1; + + std::stringstream ss; + for (std::size_t i = 0; i < sizes[0]; ++i) + ss << std::to_string(accessor[i]) << (i < sizes[0] - 1 ? " " : ""); + node.append_child(pugi::node_pcdata).set_value(ss.str().c_str()); + } else if constexpr (N == 2) { + node.append_attribute("rows") = sizes[0]; + node.append_attribute("cols") = sizes[1]; + + std::stringstream ss; + for (std::size_t i = 0; i < sizes[0]; ++i) + for (std::size_t j = 0; j < sizes[1]; ++j) + ss << std::to_string(accessor[i][j]) + << (j < sizes[1] - 1 ? " " : (i < sizes[0] - 1 ? " " : "")); + node.append_child(pugi::node_pcdata).set_value(ss.str().c_str()); + } else + throw std::runtime_error( + "Tag \"Matrix\" only supports 1- and 2-dimensional tensors"); + } else { + std::stringstream ss; + for (const auto &size : sizes) + ss << std::to_string(size) << " "; + + pugi::xml_node dims = node.append_child("Dimensions"); + dims.append_child(pugi::node_pcdata).set_value(ss.str().c_str()); + + ss.str(""); + if constexpr (N == 1) { + for (std::size_t i = 0; i < sizes[0]; ++i) + ss << std::to_string(accessor[i]) << " "; + } else if constexpr (N == 2) { + for (std::size_t i = 0; i < sizes[0]; ++i) + for (std::size_t j = 0; j < sizes[1]; ++j) + ss << std::to_string(accessor[i][j]) << " "; + } else if constexpr (N == 3) { + for (std::size_t i = 0; i < sizes[0]; ++i) + for (std::size_t j = 0; j < sizes[1]; ++j) + for (std::size_t k = 0; j < sizes[2]; ++k) + ss << std::to_string(accessor[i][j][k]) << " "; + } else if constexpr (N == 4) { + for (std::size_t i = 0; i < sizes[0]; ++i) + for (std::size_t j = 0; j < sizes[1]; ++j) + for (std::size_t k = 0; k < sizes[2]; ++k) + for (std::size_t l = 0; l < sizes[3]; ++l) + ss << std::to_string(accessor[i][j][k][l]) << " "; + + } else if constexpr (N == 5) { + for (std::size_t i = 0; i < sizes[0]; ++i) + for (std::size_t j = 0; j < sizes[1]; ++j) + for (std::size_t k = 0; k < sizes[2]; ++k) + for (std::size_t l = 0; l < sizes[3]; ++l) + for (std::size_t m = 0; m < sizes[4]; ++m) + ss << std::to_string(accessor[i][j][k][l][m]) << " "; + } else if constexpr (N == 6) { + for (std::size_t i = 0; i < sizes[0]; ++i) + for (std::size_t j = 0; j < sizes[1]; ++j) + for (std::size_t k = 0; k < sizes[2]; ++k) + for (std::size_t l = 0; l < sizes[3]; ++l) + for (std::size_t m = 0; m < sizes[4]; ++m) + for (std::size_t n = 0; n < sizes[5]; ++n) + ss << std::to_string(accessor[i][j][k][l][m][n]) << " "; + } else + throw std::runtime_error( + "Dimensions higher than 4 are not implemented yet"); + + pugi::xml_node data = node.append_child("Data"); + data.append_child(pugi::node_pcdata).set_value(ss.str().c_str()); + } + + return root; +} + +/// @brief Converts a torch::Tensor object to an XML document object +template +inline pugi::xml_document to_xml(const torch::Tensor &tensor, + std::string tag = "Matrix", int id = 0, + std::string label = "", int index = -1) { + pugi::xml_document doc; + pugi::xml_node root = doc.append_child("xml"); + to_xml(tensor, root, id, label, index); + + return doc; +} + +/// @brief Converts a torch::Tensor object to an XML object +template +inline pugi::xml_node &to_xml(const torch::Tensor &tensor, pugi::xml_node &root, + std::string tag = "Matrix", int id = 0, + std::string label = "", int index = -1) { + + if (tensor.is_cuda()) { + auto [tensor_cpu, accessor] = to_tensorAccessor(tensor, torch::kCPU); + return to_xml(accessor, tensor.sizes(), root, tag, id, label, index); + } else { + auto accessor = to_tensorAccessor(tensor); + return to_xml(accessor, tensor.sizes(), root, tag, id, label, index); + } +} + +/// @brief Converts an std::array of torch::Tensor objects to an XML +/// object +template +inline pugi::xml_document to_xml(const utils::TensorArray &tensors, + std::string tag = "Matrix", int id = 0, + std::string label = "", int index = -1) { + pugi::xml_document doc; + pugi::xml_node root = doc.append_child("xml"); + to_xml(tensors, root, id, label, index); + + return doc; +} + +/// @brief Converts an std::array of torch::Tensor objects to an XML +/// object +template +inline pugi::xml_node &to_xml(const utils::TensorArray &tensors, + pugi::xml_node &root, std::string tag = "Matrix", + int id = 0, std::string label = "") { + + for (std::size_t i = 0; i < M; ++i) { + if (tensors[i].is_cuda()) { + auto [tensor_cpu, accessor] = + to_tensorAccessor(tensors[i], torch::kCPU); + to_xml(accessor, tensors[i].sizes(), root, tag, id, label, i); + } else { + auto accessor = to_tensorAccessor(tensors[i]); + to_xml(accessor, tensors[i].sizes(), root, tag, id, label, i); + } + } + + return root; +} + +/// @brief Converts an XML documentobject to a torch::TensorAccessor object +template +inline torch::TensorAccessor & +from_xml(const pugi::xml_document &doc, torch::TensorAccessor &accessor, + torch::IntArrayRef sizes, std::string tag = "Matrix", int id = 0, + std::string label = "", int index = -1) { + return from_xml(doc.child("xml"), accessor, sizes, tag, id, label, index); +} + +/// @brief Converts an XML object to a torch::TensorAccessor object +template +inline torch::TensorAccessor & +from_xml(const pugi::xml_node &root, torch::TensorAccessor &accessor, + torch::IntArrayRef sizes, std::string tag = "Matrix", int id = 0, + std::string label = "", int index = -1) { + + return accessor; +} + +/// @brief Converts an XML document object to a torch::Tensor object +template +inline torch::Tensor & +from_xml(const pugi::xml_document &doc, torch::Tensor &tensor, + std::string tag = "Matrix", int id = 0, std::string label = "", + bool alloc = true, int index = -1) { + return from_xml(doc.child("xml"), tensor, tag, id, label, index); +} + +/// @brief Converts an XML object to a torch::Tensor object +template +inline torch::Tensor & +from_xml(const pugi::xml_node &root, torch::Tensor &tensor, + std::string tag = "Matrix", int id = 0, std::string label = "", + bool alloc = true, int index = -1) { + + // Loop through all nodes + for (pugi::xml_node node : root.children(tag.c_str())) { + + if ((id >= 0 ? node.attribute("id").as_int() == id : true) && + (index >= 0 ? node.attribute("index").as_int() == index : true) && + (!label.empty() ? node.attribute("label").value() == label : true)) { + + if (tag == "Matrix") { + + int64_t rows = node.attribute("rows").as_int(); + int64_t cols = node.attribute("cols").as_int(); + + if (!alloc && (tensor.size(0) != rows || tensor.size(1) != cols)) + throw std::runtime_error("Invalid matrix dimensions"); + + else if (alloc && (tensor.size(0) != rows || tensor.size(1) != cols)) + tensor = torch::zeros({rows, cols}, tensor.options()); + + std::string values = std::regex_replace( + node.text().get(), std::regex("[\t\r\n\a]+| +"), " "); + + auto [tensor_cpu, accessor] = + to_tensorAccessor(tensor, torch::kCPU); + auto value = strtok(&values[0], " "); + + for (int64_t i = 0; i < rows; ++i) + for (int64_t j = 0; j < cols; ++j) { + if (value == NULL) + throw std::runtime_error( + "XML object does not provide enough coefficients"); + + accessor[i][j] = static_cast(std::stod(value)); + value = strtok(NULL, " "); + } + + if (value != NULL) + throw std::runtime_error("XML object provides too many coefficients"); + + if (tensor.device().type() != torch::kCPU) + tensor = std::move(tensor_cpu); + + return tensor; + + } else { + + std::vector sizes; + + // Check for "Dimensions" + if (pugi::xml_node dims = node.child("Dimensions")) { + + std::string values = std::regex_replace( + dims.text().get(), std::regex("[\t\r\n\a]+| +"), " "); + for (auto value = strtok(&values[0], " "); value != NULL; + value = strtok(NULL, " ")) + sizes.push_back(static_cast(std::stoi(value))); + + if (!alloc && (tensor.sizes() != sizes)) + throw std::runtime_error("Invalid tensor dimensions"); + + else if (alloc && (tensor.sizes() != sizes)) + tensor = torch::zeros(torch::IntArrayRef{sizes}, tensor.options()); + + if (sizes.size() != N) + throw std::runtime_error("Invalid tensor dimensions"); + + // Check for "Data" + if (pugi::xml_node data = node.child("Data")) { + std::string values = std::regex_replace( + data.text().get(), std::regex("[\t\r\n\a]+| +"), " "); + + auto [tensor_cpu, accessor] = + to_tensorAccessor(tensor, torch::kCPU); + auto value = strtok(&values[0], " "); + + if constexpr (N == 1) { + for (int64_t i = 0; i < sizes[0]; ++i) { + if (value == NULL) + throw std::runtime_error( + "XML object does not provide enough coefficients"); + + accessor[i] = static_cast(std::stod(value)); + value = strtok(NULL, " "); + } + } else if constexpr (N == 2) { + for (int64_t i = 0; i < sizes[0]; ++i) + for (int64_t j = 0; j < sizes[1]; ++j) { + if (value == NULL) + throw std::runtime_error( + "XML object does not provide enough coefficients"); + + accessor[i][j] = static_cast(std::stod(value)); + value = strtok(NULL, " "); + } + } else if constexpr (N == 3) { + for (int64_t i = 0; i < sizes[0]; ++i) + for (int64_t j = 0; j < sizes[1]; ++j) + for (int64_t k = 0; k < sizes[2]; ++k) { + if (value == NULL) + throw std::runtime_error( + "XML object does not provide enough coefficients"); + + accessor[i][j][k] = static_cast(std::stod(value)); + value = strtok(NULL, " "); + } + } else if constexpr (N == 4) { + for (int64_t i = 0; i < sizes[0]; ++i) + for (int64_t j = 0; j < sizes[1]; ++j) + for (int64_t k = 0; k < sizes[2]; ++k) + for (int64_t l = 0; l < sizes[3]; ++l) { + if (value == NULL) + throw std::runtime_error( + "XML object does not provide enough coefficients"); + + accessor[i][j][k][l] = static_cast(std::stod(value)); + value = strtok(NULL, " "); + } + } else if constexpr (N == 5) { + for (int64_t i = 0; i < sizes[0]; ++i) + for (int64_t j = 0; j < sizes[1]; ++j) + for (int64_t k = 0; k < sizes[2]; ++k) + for (int64_t l = 0; l < sizes[3]; ++l) + for (int64_t m = 0; m < sizes[4]; ++m) { + if (value == NULL) + throw std::runtime_error( + "XML object does not provide enough " + "coefficients"); + + accessor[i][j][k][l][m] = + static_cast(std::stod(value)); + value = strtok(NULL, " "); + } + } else if constexpr (N == 6) { + for (int64_t i = 0; i < sizes[0]; ++i) + for (int64_t j = 0; j < sizes[1]; ++j) + for (int64_t k = 0; k < sizes[2]; ++k) + for (int64_t l = 0; l < sizes[3]; ++l) + for (int64_t m = 0; m < sizes[4]; ++m) + for (int64_t n = 0; n < sizes[5]; ++n) { + if (value == NULL) + throw std::runtime_error( + "XML object does not provide enough " + "coefficients"); + + accessor[i][j][k][l][m][n] = + static_cast(std::stod(value)); + value = strtok(NULL, " "); + } + } + + if (value != NULL) + throw std::runtime_error( + "XML object provides too many coefficients"); + + if (tensor.device().type() != torch::kCPU) + tensor = std::move(tensor_cpu); + + return tensor; + } // "Data" + } // "Dimenions" + + throw std::runtime_error( + "XML object does not provide a \"Dimensions\" tag"); + + return tensor; + } + + } // try next node + } // "tag" + + throw std::runtime_error( + "XML object does not provide tag with given id, index, and/or label"); + return tensor; +} + +/// @brief Converts an XML document object to an std::array of torch::Tensor +/// objects +template +inline utils::TensorArray & +from_xml(const pugi::xml_document &doc, utils::TensorArray &tensors, + std::string tag = "Matrix", int id = 0, bool alloc = true, + std::string label = "") { + + return from_xml(doc.child("xml"), tensors, tag, id, label, alloc); +} + +/// @brief Converts an XML object to an std::array of torch::Tensor objects +template +inline utils::TensorArray & +from_xml(const pugi::xml_node &root, utils::TensorArray &tensors, + std::string tag = "Matrix", int id = 0, bool alloc = true, + std::string label = "") { + + for (std::size_t i = 0; i < M; ++i) { + from_xml(root, tensors[i], tag, id, label, alloc, i); + } + + return tensors; +} + +} // namespace utils +} // namespace iganet diff --git a/include/utils/tensorarray.hpp b/include/utils/tensorarray.hpp index 27af97b9..9e7ea7ef 100644 --- a/include/utils/tensorarray.hpp +++ b/include/utils/tensorarray.hpp @@ -1,187 +1,187 @@ -/** - @file include/utils/tensorarray.hpp - - @brief TensorArray utility functions - - @author Matthias Moller - - @copyright This file is part of the IgANet project - - This Source Code Form is subject to the terms of the Mozilla Public - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at http://mozilla.org/MPL/2.0/. -*/ - -#pragma once - -#include -#include - -#include -#include - -#include - -namespace iganet { -namespace utils { - -template using TensorArray = std::array; - -using TensorArray0 = TensorArray<0>; -using TensorArray1 = TensorArray<1>; -using TensorArray2 = TensorArray<2>; -using TensorArray3 = TensorArray<3>; -using TensorArray4 = TensorArray<4>; - -/// @brief Converts a set of std::initializer_list objects to a TensorArray -/// object -/// @{ -template -inline constexpr TensorArray -to_tensorArray(std::initializer_list &&...lists) { - return {to_tensor(std::forward>(lists), - torch::IntArrayRef{-1}, Options{})...}; -} - -template -inline constexpr TensorArray -to_tensorArray(torch::IntArrayRef sizes, std::initializer_list &&...lists) { - return {to_tensor(std::forward>(lists), sizes, - Options{})...}; -} - -template -inline constexpr TensorArray -to_tensorArray(const iganet::Options &options, - std::initializer_list &&...lists) { - static_assert( - (std::is_same_v && ...), - "Type mismatch between Options and std::initializer_list"); - return {to_tensor(std::forward>(lists), - torch::IntArrayRef{-1}, options)...}; -} - -template -inline constexpr TensorArray -to_tensorArray(torch::IntArrayRef sizes, const iganet::Options &options, - std::initializer_list &&...lists) { - static_assert( - (std::is_same_v && ...), - "Type mismatch between Options and std::initializer_list"); - return {to_tensor(std::forward>(lists), sizes, - options)...}; -} -/// @} - -/// @brief Converts a torch::Tensor object to a -/// torch::TensorAccessor object -/// @{ -template -auto to_tensorAccessor(const torch::Tensor &tensor) { - return tensor.template accessor(); -} - -template -auto to_tensorAccessor(const torch::Tensor &tensor, - c10::DeviceType deviceType) { - - if (deviceType != tensor.device().type()) { - auto tensor_device = tensor.to(deviceType); - auto accessor = tensor_device.template accessor(); - return std::tuple(tensor_device, accessor); - } else { - auto accessor = tensor.template accessor(); - return std::tuple(tensor, accessor); - } -} -/// @} - -namespace detail { -/// @brief Converts an std::array of torch::Tensor objects to an -/// array of torch::TensorAccessor objects -/// @{ -template -auto to_tensorAccessor(const TensorArray &tensors, - std::index_sequence) { - return std::array, sizeof...(Is)>{ - tensors[Is].template accessor()...}; -} - -template -auto to_tensorAccessor(const TensorArray &tensors, - c10::DeviceType deviceType, std::index_sequence) { - std::array tensors_device{ - tensors[Is].to(deviceType)...}; - std::array, sizeof...(Is)> accessors{ - tensors_device[Is].template accessor()...}; - return std::tuple(tensors_device, accessors); -} - -template -auto to_tensorAccessor(const BlockTensor &blocktensor, - c10::DeviceType deviceType, std::index_sequence) { - std::array tensors_device{ - blocktensor[Is]->to(deviceType)...}; - std::array, sizeof...(Is)> accessors{ - tensors_device[Is].template accessor()...}; - return std::tuple(tensors_device, accessors); -} -/// @} -} // namespace detail - -/// @brief Converts an std::array of torch::Tensor objects to an -/// array of torch::TensorAccessor objects -/// @{ -template -auto to_tensorAccessor(const TensorArray &tensors) { - return detail::to_tensorAccessor(tensors, - std::make_index_sequence()); -} - -template -auto to_tensorAccessor(const TensorArray &tensors, - c10::DeviceType deviceType) { - return detail::to_tensorAccessor(tensors, deviceType, - std::make_index_sequence()); -} - -template -auto to_tensorAccessor(const BlockTensor &blocktensor, - c10::DeviceType deviceType) { - return detail::to_tensorAccessor( - blocktensor, deviceType, std::make_index_sequence<(Dims * ...)>()); -} -/// @} - -} // namespace utils -} // namespace iganet - -namespace std { - -/// Print (as string) a TensorArray object -template -inline std::ostream &operator<<(std::ostream &os, - const std::array &obj) { - at::optional name_ = c10::demangle(typeid(obj).name()); - -#if defined(_WIN32) - // Windows adds "struct" or "class" as a prefix. - if (name_->find("struct ") == 0) { - name_->erase(name_->begin(), name_->begin() + 7); - } else if (name_->find("class ") == 0) { - name_->erase(name_->begin(), name_->begin() + 6); - } -#endif // defined(_WIN32) - - os << *name_ << "(\n"; - for (const auto &i : obj) - if (!i.numel()) - os << "{}\n"; - else - os << ((i.sizes().size() == 1) ? i.view({1, i.size(0)}) : i) << "\n"; - os << ")"; - - return os; -} - -} // namespace std +/** + @file include/utils/tensorarray.hpp + + @brief TensorArray utility functions + + @author Matthias Moller + + @copyright This file is part of the IgANet project + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#pragma once + +#include +#include + +#include +#include + +#include + +namespace iganet { +namespace utils { + +template using TensorArray = std::array; + +using TensorArray0 = TensorArray<0>; +using TensorArray1 = TensorArray<1>; +using TensorArray2 = TensorArray<2>; +using TensorArray3 = TensorArray<3>; +using TensorArray4 = TensorArray<4>; + +/// @brief Converts a set of std::initializer_list objects to a TensorArray +/// object +/// @{ +template +inline constexpr TensorArray +to_tensorArray(std::initializer_list &&...lists) { + return {to_tensor(std::forward>(lists), + torch::IntArrayRef{-1}, Options{})...}; +} + +template +inline constexpr TensorArray +to_tensorArray(torch::IntArrayRef sizes, std::initializer_list &&...lists) { + return {to_tensor(std::forward>(lists), sizes, + Options{})...}; +} + +template +inline constexpr TensorArray +to_tensorArray(const iganet::Options &options, + std::initializer_list &&...lists) { + static_assert( + (std::is_same_v && ...), + "Type mismatch between Options and std::initializer_list"); + return {to_tensor(std::forward>(lists), + torch::IntArrayRef{-1}, options)...}; +} + +template +inline constexpr TensorArray +to_tensorArray(torch::IntArrayRef sizes, const iganet::Options &options, + std::initializer_list &&...lists) { + static_assert( + (std::is_same_v && ...), + "Type mismatch between Options and std::initializer_list"); + return {to_tensor(std::forward>(lists), sizes, + options)...}; +} +/// @} + +/// @brief Converts a torch::Tensor object to a +/// torch::TensorAccessor object +/// @{ +template +auto to_tensorAccessor(const torch::Tensor &tensor) { + return tensor.template accessor(); +} + +template +auto to_tensorAccessor(const torch::Tensor &tensor, + c10::DeviceType deviceType) { + + if (deviceType != tensor.device().type()) { + auto tensor_device = tensor.to(deviceType); + auto accessor = tensor_device.template accessor(); + return std::tuple(tensor_device, accessor); + } else { + auto accessor = tensor.template accessor(); + return std::tuple(tensor, accessor); + } +} +/// @} + +namespace detail { +/// @brief Converts an std::array of torch::Tensor objects to an +/// array of torch::TensorAccessor objects +/// @{ +template +auto to_tensorAccessor(const TensorArray &tensors, + std::index_sequence) { + return std::array, sizeof...(Is)>{ + tensors[Is].template accessor()...}; +} + +template +auto to_tensorAccessor(const TensorArray &tensors, + c10::DeviceType deviceType, std::index_sequence) { + std::array tensors_device{ + tensors[Is].to(deviceType)...}; + std::array, sizeof...(Is)> accessors{ + tensors_device[Is].template accessor()...}; + return std::tuple(tensors_device, accessors); +} + +template +auto to_tensorAccessor(const BlockTensor &blocktensor, + c10::DeviceType deviceType, std::index_sequence) { + std::array tensors_device{ + blocktensor[Is]->to(deviceType)...}; + std::array, sizeof...(Is)> accessors{ + tensors_device[Is].template accessor()...}; + return std::tuple(tensors_device, accessors); +} +/// @} +} // namespace detail + +/// @brief Converts an std::array of torch::Tensor objects to an +/// array of torch::TensorAccessor objects +/// @{ +template +auto to_tensorAccessor(const TensorArray &tensors) { + return detail::to_tensorAccessor(tensors, + std::make_index_sequence()); +} + +template +auto to_tensorAccessor(const TensorArray &tensors, + c10::DeviceType deviceType) { + return detail::to_tensorAccessor(tensors, deviceType, + std::make_index_sequence()); +} + +template +auto to_tensorAccessor(const BlockTensor &blocktensor, + c10::DeviceType deviceType) { + return detail::to_tensorAccessor( + blocktensor, deviceType, std::make_index_sequence<(Dims * ...)>()); +} +/// @} + +} // namespace utils +} // namespace iganet + +namespace std { + +/// Print (as string) a TensorArray object +template +inline std::ostream &operator<<(std::ostream &os, + const std::array &obj) { + at::optional name_ = c10::demangle(typeid(obj).name()); + +#if defined(_WIN32) + // Windows adds "struct" or "class" as a prefix. + if (name_->find("struct ") == 0) { + name_->erase(name_->begin(), name_->begin() + 7); + } else if (name_->find("class ") == 0) { + name_->erase(name_->begin(), name_->begin() + 6); + } +#endif // defined(_WIN32) + + os << *name_ << "(\n"; + for (const auto &i : obj) + if (!i.numel()) + os << "{}\n"; + else + os << ((i.sizes().size() == 1) ? i.view({1, i.size(0)}) : i) << "\n"; + os << ")"; + + return os; +} + +} // namespace std diff --git a/include/utils/vslice.hpp b/include/utils/vslice.hpp index 10351ed6..89d10c75 100644 --- a/include/utils/vslice.hpp +++ b/include/utils/vslice.hpp @@ -1,251 +1,251 @@ -/** - @file include/utils/vslice.hpp - - @brief VSlice utility functions - - @author Matthias Moller - - @copyright This file is part of the IgANet project - - This Source Code Form is subject to the terms of the Mozilla Public - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at http://mozilla.org/MPL/2.0/. -*/ - -#pragma once - -#include - -#include - -#include - -namespace iganet { -namespace utils { - -/// @brief Vectorized version of `torch::indexing::Slice` (see -/// https://pytorch.org/cppdocs/notes/tensor_indexing.html) -/// -/// Creates a one-dimensional `torch::Tensor` object of -/// size `index.numel() * (stop_offset-start_offset)` with the -/// following content -/// -/// \code -/// [ index[0]+start_offset, ..., index[N-1]+start_offset, -/// index[0]+start_offset+1, ..., index[N-1]+start_offset+1, -/// ... -/// index[0]+stop_offset-1, ... index[N-1]+stop_offset-1 ] -/// \endcode -/// -/// @param[in] index Tensor of indices -/// -/// @param[in] start_offset Starting value of the offset -/// -/// @param[in] stop_offset Stopping value of the offset -template -inline auto VSlice(torch::Tensor index, int64_t start_offset, - int64_t stop_offset) { - if constexpr (transpose) - return index.repeat_interleave(stop_offset - start_offset) + - torch::linspace(start_offset, stop_offset - 1, - stop_offset - start_offset, index.options()) - .repeat(index.numel()); - else - return index.repeat(stop_offset - start_offset) + - torch::linspace(start_offset, stop_offset - 1, - stop_offset - start_offset, index.options()) - .repeat_interleave(index.numel()); -} - -/// @brief Vectorized version of `torch::indexing::Slice` (see -/// https://pytorch.org/cppdocs/notes/tensor_indexing.html) -/// -/// @param[in] index 2d array of tensors of indices -/// -/// @param[in] start_offset 2d array of starting value of the offset -/// -/// @param[in] stop_offset 2d array of stopping value of the offset -/// -/// @param[in] leading_dim Leading dimension -template -inline auto VSlice(const utils::TensorArray<2> &index, - const std::array &start_offset, - const std::array &stop_offset, - int64_t leading_dim = 1) { - assert(index[0].numel() == index[1].numel()); - - auto dist0 = stop_offset[0] - start_offset[0]; - auto dist1 = stop_offset[1] - start_offset[1]; - auto dist01 = dist0 * dist1; - - if constexpr (transpose) - return (index[1].repeat_interleave(dist01) + - torch::linspace(start_offset[1], stop_offset[1] - 1, dist1, - index[0].options()) - .repeat_interleave(dist0) - .repeat(index[0].numel())) * - leading_dim + - index[0].repeat_interleave(dist0).repeat_interleave(dist1) + - torch::linspace(start_offset[0], stop_offset[0] - 1, dist0, - index[0].options()) - .repeat(index[1].numel()) - .repeat(dist1); - else - return (index[1].repeat(dist01) + - torch::linspace(start_offset[1], stop_offset[1] - 1, dist1, - index[0].options()) - .repeat_interleave(index[0].numel() * dist0)) * - leading_dim + - (index[0].repeat(dist0) + torch::linspace(start_offset[0], - stop_offset[0] - 1, dist0, - index[0].options()) - .repeat_interleave(index[1].numel())) - .repeat(dist1); -} - -/// @brief Vectorized version of `torch::indexing::Slice` (see -/// https://pytorch.org/cppdocs/notes/tensor_indexing.html) -/// -/// @param[in] index 3d array of tensors of indices -/// -/// @param[in] start_offset 3d array of starting value of the offset -/// -/// @param[in] stop_offset 3d array of stopping value of the offset -/// -/// @param[in] leading_dim 2d array of leading dimension -template -inline auto VSlice(const utils::TensorArray<3> &index, - const std::array &start_offset, - const std::array &stop_offset, - const std::array &leading_dim = {1, 1}) { - assert(index[0].numel() == index[1].numel() && - index[1].numel() == index[2].numel()); - - auto dist0 = stop_offset[0] - start_offset[0]; - auto dist1 = stop_offset[1] - start_offset[1]; - auto dist2 = stop_offset[2] - start_offset[2]; - auto dist01 = dist0 * dist1; - auto dist12 = dist1 * dist2; - auto dist012 = dist0 * dist12; - - if constexpr (transpose) - return (index[2].repeat_interleave(dist012) + - torch::linspace(start_offset[2], stop_offset[2] - 1, dist2, - index[0].options()) - .repeat_interleave(dist01) - .repeat(index[0].numel())) * - leading_dim[0] * leading_dim[1] + - (index[1].repeat_interleave(dist01).repeat_interleave(dist2) + - torch::linspace(start_offset[1], stop_offset[1] - 1, dist1, - index[0].options()) - .repeat_interleave(dist0) - .repeat(index[0].numel()) - .repeat(dist2)) * - leading_dim[0] + - index[0].repeat_interleave(dist0).repeat_interleave(dist12) + - torch::linspace(start_offset[0], stop_offset[0] - 1, dist0, - index[0].options()) - .repeat(index[0].numel()) - .repeat(dist12); - else - return (index[2].repeat(dist012) + - torch::linspace(start_offset[2], stop_offset[2] - 1, dist2, - index[0].options()) - .repeat_interleave(index[0].numel() * dist01)) * - leading_dim[0] * leading_dim[1] + - (index[1].repeat(dist01) + - torch::linspace(start_offset[1], stop_offset[1] - 1, dist1, - index[0].options()) - .repeat_interleave(index[0].numel() * dist0)) - .repeat(dist2) * - leading_dim[0] + - (index[0].repeat(dist0) + torch::linspace(start_offset[0], - stop_offset[0] - 1, dist0, - index[0].options()) - .repeat_interleave(index[0].numel())) - .repeat(dist12); -} - -/// @brief Vectorized version of `torch::indexing::Slice` (see -/// https://pytorch.org/cppdocs/notes/tensor_indexing.html) -/// -/// @param[in] index 4d array of tensors of indices -/// -/// @param[in] start_offset 4d array of starting value of the offset -/// -/// @param[in] stop_offset 4d array of stopping value of the offset -/// -/// @param[in] leading_dim 3d array of leading dimension -template -inline auto VSlice(const utils::TensorArray<4> &index, - const std::array &start_offset, - const std::array &stop_offset, - const std::array &leading_dim = {1, 1, 1}) { - assert(index[0].numel() == index[1].numel() && - index[1].numel() == index[2].numel() && - index[2].numel() == index[3].numel()); - - auto dist0 = stop_offset[0] - start_offset[0]; - auto dist1 = stop_offset[1] - start_offset[1]; - auto dist2 = stop_offset[2] - start_offset[2]; - auto dist3 = stop_offset[3] - start_offset[3]; - auto dist01 = dist0 * dist1; - auto dist12 = dist1 * dist2; - auto dist23 = dist2 * dist3; - auto dist012 = dist0 * dist12; - auto dist123 = dist1 * dist23; - auto dist0123 = dist01 * dist23; - - if constexpr (transpose) - return (index[3].repeat_interleave(dist0123) + - torch::linspace(start_offset[3], stop_offset[3] - 1, dist3, - index[0].options()) - .repeat_interleave(dist012) - .repeat(index[0].numel())) * - leading_dim[0] * leading_dim[1] * leading_dim[2] + - (index[2].repeat_interleave(dist012).repeat_interleave(dist3) + - torch::linspace(start_offset[2], stop_offset[2] - 1, dist2, - index[0].options()) - .repeat_interleave(dist01) - .repeat(index[0].numel()) - .repeat(dist3)) * - leading_dim[0] * leading_dim[1] + - (index[1].repeat_interleave(dist01).repeat_interleave(dist23) + - torch::linspace(start_offset[1], stop_offset[1] - 1, dist1, - index[0].options()) - .repeat_interleave(dist0) - .repeat(index[0].numel()) - .repeat(dist23)) * - leading_dim[0] + - index[0].repeat_interleave(dist0).repeat_interleave(dist123) + - torch::linspace(start_offset[0], stop_offset[0] - 1, dist0, - index[0].options()) - .repeat(index[0].numel()) - .repeat(dist123); - else - return (index[3].repeat(dist0123) + - torch::linspace(start_offset[3], stop_offset[3] - 1, dist3, - index[0].options()) - .repeat_interleave(index[0].numel() * dist012)) * - leading_dim[0] * leading_dim[1] * leading_dim[2] + - (index[2].repeat(dist012) + - torch::linspace(start_offset[2], stop_offset[2] - 1, dist2, - index[0].options()) - .repeat_interleave(index[0].numel() * dist01)) - .repeat(dist3) * - leading_dim[0] * leading_dim[1] + - (index[1].repeat(dist01) + - torch::linspace(start_offset[1], stop_offset[1] - 1, dist1, - index[0].options()) - .repeat_interleave(index[0].numel() * dist0)) - .repeat(dist23) * - leading_dim[0] + - (index[0].repeat(dist0) + torch::linspace(start_offset[0], - stop_offset[0] - 1, dist0, - index[0].options()) - .repeat_interleave(index[0].numel())) - .repeat(dist123); -} - -} // namespace utils -} // namespace iganet +/** + @file include/utils/vslice.hpp + + @brief VSlice utility functions + + @author Matthias Moller + + @copyright This file is part of the IgANet project + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#pragma once + +#include + +#include + +#include + +namespace iganet { +namespace utils { + +/// @brief Vectorized version of `torch::indexing::Slice` (see +/// https://pytorch.org/cppdocs/notes/tensor_indexing.html) +/// +/// Creates a one-dimensional `torch::Tensor` object of +/// size `index.numel() * (stop_offset-start_offset)` with the +/// following content +/// +/// \code +/// [ index[0]+start_offset, ..., index[N-1]+start_offset, +/// index[0]+start_offset+1, ..., index[N-1]+start_offset+1, +/// ... +/// index[0]+stop_offset-1, ... index[N-1]+stop_offset-1 ] +/// \endcode +/// +/// @param[in] index Tensor of indices +/// +/// @param[in] start_offset Starting value of the offset +/// +/// @param[in] stop_offset Stopping value of the offset +template +inline auto VSlice(torch::Tensor index, int64_t start_offset, + int64_t stop_offset) { + if constexpr (transpose) + return index.repeat_interleave(stop_offset - start_offset) + + torch::linspace(start_offset, stop_offset - 1, + stop_offset - start_offset, index.options()) + .repeat(index.numel()); + else + return index.repeat(stop_offset - start_offset) + + torch::linspace(start_offset, stop_offset - 1, + stop_offset - start_offset, index.options()) + .repeat_interleave(index.numel()); +} + +/// @brief Vectorized version of `torch::indexing::Slice` (see +/// https://pytorch.org/cppdocs/notes/tensor_indexing.html) +/// +/// @param[in] index 2d array of tensors of indices +/// +/// @param[in] start_offset 2d array of starting value of the offset +/// +/// @param[in] stop_offset 2d array of stopping value of the offset +/// +/// @param[in] leading_dim Leading dimension +template +inline auto VSlice(const utils::TensorArray<2> &index, + const std::array &start_offset, + const std::array &stop_offset, + int64_t leading_dim = 1) { + assert(index[0].numel() == index[1].numel()); + + auto dist0 = stop_offset[0] - start_offset[0]; + auto dist1 = stop_offset[1] - start_offset[1]; + auto dist01 = dist0 * dist1; + + if constexpr (transpose) + return (index[1].repeat_interleave(dist01) + + torch::linspace(start_offset[1], stop_offset[1] - 1, dist1, + index[0].options()) + .repeat_interleave(dist0) + .repeat(index[0].numel())) * + leading_dim + + index[0].repeat_interleave(dist0).repeat_interleave(dist1) + + torch::linspace(start_offset[0], stop_offset[0] - 1, dist0, + index[0].options()) + .repeat(index[1].numel()) + .repeat(dist1); + else + return (index[1].repeat(dist01) + + torch::linspace(start_offset[1], stop_offset[1] - 1, dist1, + index[0].options()) + .repeat_interleave(index[0].numel() * dist0)) * + leading_dim + + (index[0].repeat(dist0) + torch::linspace(start_offset[0], + stop_offset[0] - 1, dist0, + index[0].options()) + .repeat_interleave(index[1].numel())) + .repeat(dist1); +} + +/// @brief Vectorized version of `torch::indexing::Slice` (see +/// https://pytorch.org/cppdocs/notes/tensor_indexing.html) +/// +/// @param[in] index 3d array of tensors of indices +/// +/// @param[in] start_offset 3d array of starting value of the offset +/// +/// @param[in] stop_offset 3d array of stopping value of the offset +/// +/// @param[in] leading_dim 2d array of leading dimension +template +inline auto VSlice(const utils::TensorArray<3> &index, + const std::array &start_offset, + const std::array &stop_offset, + const std::array &leading_dim = {1, 1}) { + assert(index[0].numel() == index[1].numel() && + index[1].numel() == index[2].numel()); + + auto dist0 = stop_offset[0] - start_offset[0]; + auto dist1 = stop_offset[1] - start_offset[1]; + auto dist2 = stop_offset[2] - start_offset[2]; + auto dist01 = dist0 * dist1; + auto dist12 = dist1 * dist2; + auto dist012 = dist0 * dist12; + + if constexpr (transpose) + return (index[2].repeat_interleave(dist012) + + torch::linspace(start_offset[2], stop_offset[2] - 1, dist2, + index[0].options()) + .repeat_interleave(dist01) + .repeat(index[0].numel())) * + leading_dim[0] * leading_dim[1] + + (index[1].repeat_interleave(dist01).repeat_interleave(dist2) + + torch::linspace(start_offset[1], stop_offset[1] - 1, dist1, + index[0].options()) + .repeat_interleave(dist0) + .repeat(index[0].numel()) + .repeat(dist2)) * + leading_dim[0] + + index[0].repeat_interleave(dist0).repeat_interleave(dist12) + + torch::linspace(start_offset[0], stop_offset[0] - 1, dist0, + index[0].options()) + .repeat(index[0].numel()) + .repeat(dist12); + else + return (index[2].repeat(dist012) + + torch::linspace(start_offset[2], stop_offset[2] - 1, dist2, + index[0].options()) + .repeat_interleave(index[0].numel() * dist01)) * + leading_dim[0] * leading_dim[1] + + (index[1].repeat(dist01) + + torch::linspace(start_offset[1], stop_offset[1] - 1, dist1, + index[0].options()) + .repeat_interleave(index[0].numel() * dist0)) + .repeat(dist2) * + leading_dim[0] + + (index[0].repeat(dist0) + torch::linspace(start_offset[0], + stop_offset[0] - 1, dist0, + index[0].options()) + .repeat_interleave(index[0].numel())) + .repeat(dist12); +} + +/// @brief Vectorized version of `torch::indexing::Slice` (see +/// https://pytorch.org/cppdocs/notes/tensor_indexing.html) +/// +/// @param[in] index 4d array of tensors of indices +/// +/// @param[in] start_offset 4d array of starting value of the offset +/// +/// @param[in] stop_offset 4d array of stopping value of the offset +/// +/// @param[in] leading_dim 3d array of leading dimension +template +inline auto VSlice(const utils::TensorArray<4> &index, + const std::array &start_offset, + const std::array &stop_offset, + const std::array &leading_dim = {1, 1, 1}) { + assert(index[0].numel() == index[1].numel() && + index[1].numel() == index[2].numel() && + index[2].numel() == index[3].numel()); + + auto dist0 = stop_offset[0] - start_offset[0]; + auto dist1 = stop_offset[1] - start_offset[1]; + auto dist2 = stop_offset[2] - start_offset[2]; + auto dist3 = stop_offset[3] - start_offset[3]; + auto dist01 = dist0 * dist1; + auto dist12 = dist1 * dist2; + auto dist23 = dist2 * dist3; + auto dist012 = dist0 * dist12; + auto dist123 = dist1 * dist23; + auto dist0123 = dist01 * dist23; + + if constexpr (transpose) + return (index[3].repeat_interleave(dist0123) + + torch::linspace(start_offset[3], stop_offset[3] - 1, dist3, + index[0].options()) + .repeat_interleave(dist012) + .repeat(index[0].numel())) * + leading_dim[0] * leading_dim[1] * leading_dim[2] + + (index[2].repeat_interleave(dist012).repeat_interleave(dist3) + + torch::linspace(start_offset[2], stop_offset[2] - 1, dist2, + index[0].options()) + .repeat_interleave(dist01) + .repeat(index[0].numel()) + .repeat(dist3)) * + leading_dim[0] * leading_dim[1] + + (index[1].repeat_interleave(dist01).repeat_interleave(dist23) + + torch::linspace(start_offset[1], stop_offset[1] - 1, dist1, + index[0].options()) + .repeat_interleave(dist0) + .repeat(index[0].numel()) + .repeat(dist23)) * + leading_dim[0] + + index[0].repeat_interleave(dist0).repeat_interleave(dist123) + + torch::linspace(start_offset[0], stop_offset[0] - 1, dist0, + index[0].options()) + .repeat(index[0].numel()) + .repeat(dist123); + else + return (index[3].repeat(dist0123) + + torch::linspace(start_offset[3], stop_offset[3] - 1, dist3, + index[0].options()) + .repeat_interleave(index[0].numel() * dist012)) * + leading_dim[0] * leading_dim[1] * leading_dim[2] + + (index[2].repeat(dist012) + + torch::linspace(start_offset[2], stop_offset[2] - 1, dist2, + index[0].options()) + .repeat_interleave(index[0].numel() * dist01)) + .repeat(dist3) * + leading_dim[0] * leading_dim[1] + + (index[1].repeat(dist01) + + torch::linspace(start_offset[1], stop_offset[1] - 1, dist1, + index[0].options()) + .repeat_interleave(index[0].numel() * dist0)) + .repeat(dist23) * + leading_dim[0] + + (index[0].repeat(dist0) + torch::linspace(start_offset[0], + stop_offset[0] - 1, dist0, + index[0].options()) + .repeat_interleave(index[0].numel())) + .repeat(dist123); +} + +} // namespace utils +} // namespace iganet diff --git a/pyiganet/src/CMakeLists.txt b/pyiganet/src/CMakeLists.txt index 57a5c2d0..572bb9ff 100644 --- a/pyiganet/src/CMakeLists.txt +++ b/pyiganet/src/CMakeLists.txt @@ -1,190 +1,190 @@ -######################################################################## -# CMakeLists.txt -# -# Author: Matthias Moller -# Copyright (C) 2021-2023 by the IgaNet authors -# -# This file is part of the IgaNet project -# -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. -# -# CMakeLists.txt accepts the following command line parameters -# -# PYIGANET_COEFF_TYPE -# -######################################################################## - -include(boost_preprocessor) -include(pybind11) - -######################################################################## -# Options -######################################################################## - -if(NOT PYIGANET_COEFF_TYPE) - set(PYIGANET_COEFF_TYPE double CACHE STRING - "Coefficient type(float, double)" FORCE) -endif() -set_property(CACHE PYIGANET_COEFF_TYPE PROPERTY STRINGS "float" "double") - -message("PyIgaNet options:") -message("PYIGANET_COEFF_TYPE................: ${PYIGANET_COEFF_TYPE}") -message("") - -######################################################################## -# Auto-generate source files -######################################################################## - -# Clean output directory -file(GLOB_RECURSE FILES_AND_DIRS "${CMAKE_CURRENT_BINARY_DIR}/*") -foreach(ITEM ${FILES_AND_DIRS}) - file(REMOVE_RECURSE ${ITEM}) -endforeach() - -# Config -configure_file(${CMAKE_CURRENT_SOURCE_DIR}/pyconfig.hpp.in - ${CMAKE_CURRENT_BINARY_DIR}/pyconfig.hpp @ONLY) - -# Create list of PyBind11 init subroutines -list(APPEND init_core "init_options") - -# Explicit instantiation of templated blocktensor classes -foreach(ROWS RANGE 1 2) - foreach(COLS RANGE 1 2) - - set(SUFFIX "_${ROWS}_${COLS}") - list(APPEND init_core "init_BlockTensor${SUFFIX}") - - configure_file(${CMAKE_CURRENT_SOURCE_DIR}/pyblocktensor.cxx.in - ${CMAKE_CURRENT_BINARY_DIR}/pyblocktensor${SUFFIX}.cxx @ONLY) - endforeach() -endforeach() - -# Create list of PyBind11 init subroutines -list(APPEND init_splines "init_bspline") - -# Explicit instantiation of templated spline classes -foreach(GEODIM RANGE 1 2) - foreach(DEGREES IN ITEMS - "1" "1,1")# "1,1,1" "1,1,1,1") -# "2" "2,2" "2,2,2" "2,2,2,2" -# "3" "3,3" "3,3,3" "3,3,3,3" -# "4" "4,4" "4,4,4" "4,4,4,4" -# "5" "5,5" "5,5,5" "5,5,5,5") - - set(SUFFIX "${GEODIM}d_${DEGREES}") - string(REPLACE "," "_" SUFFIX ${SUFFIX}) - list(APPEND init_splines "init_UniformBSpline${SUFFIX}") - - string(REPLACE "," ";" PARDIM ${DEGREES}) - list(LENGTH PARDIM PARDIM) - - configure_file(${CMAKE_CURRENT_SOURCE_DIR}/pybspline.cxx.in - ${CMAKE_CURRENT_BINARY_DIR}/pybspline${SUFFIX}.cxx @ONLY) - endforeach() -endforeach() - -# Create main PyIgANet source file -file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/pyiganet.cxx - "/** - @file pyiganet/pyiganet.cxx - - @brief PyIgANet - - @author Matthias Moller - - @copyright This file is part of the IgANet project - - This Source Code Form is subject to the terms of the Mozilla Public - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at http://mozilla.org/MPL/2.0/. -*/ - -#include - -#include - -namespace py = pybind11;\n\n") - -foreach(init IN LISTS init_core init_splines) - file(APPEND ${CMAKE_CURRENT_BINARY_DIR}/pyiganet.cxx - "void ${init}(py::module_ &);\n") -endforeach() - -file(APPEND ${CMAKE_CURRENT_BINARY_DIR}/pyiganet.cxx - "\n\n/** - @brief Creates IgANet Python module -*/ - -PYBIND11_MODULE(pyiganet_core, m) {\n - -m.attr(\"__name__\") = \"pyiganet_core\"; -m.attr(\"__version__\") = \"${IGANET_VERSION}\"; -m.doc() = \"IgANet (Physics-informed isogeometric analysis neural network)\"; - -py::module core = m.def_submodule(\"core\"); - -core.attr(\"__name__\") = \"pyiganet_core.core\"; -core.attr(\"__version__\") = \"${IGANET_VERSION}\"; -core.doc() = \"IgANet (Physics-informed isogeometric analysis neural network): Core module\"; - -") - -foreach(init IN LISTS init_core) - file(APPEND ${CMAKE_CURRENT_BINARY_DIR}/pyiganet.cxx - "${init}(core);\n") -endforeach() - -file(APPEND ${CMAKE_CURRENT_BINARY_DIR}/pyiganet.cxx - "\n - -py::module splines = m.def_submodule(\"splines\"); - -splines.attr(\"__name__\") = \"pyiganet_core.splines\"; -splines.attr(\"__version__\") = \"${IGANET_VERSION}\"; -splines.doc() = \"IgANet (Physics-informed isogeometric analysis neural network): Spline module\"; - -") - -foreach(init IN LISTS init_splines) - file(APPEND ${CMAKE_CURRENT_BINARY_DIR}/pyiganet.cxx - "${init}(splines);\n") -endforeach() - -file(APPEND ${CMAKE_CURRENT_BINARY_DIR}/pyiganet.cxx - "}\n") - -# Include directories -include_directories(${CMAKE_CURRENT_BINARY_DIR}) -include_directories(${CMAKE_CURRENT_SOURCE_DIR}) - -file(GLOB SRCS ${CMAKE_CURRENT_SOURCE_DIR}/*.cxx ${CMAKE_CURRENT_BINARY_DIR}/*.cxx) - -pybind11_add_module(pyiganet_core MODULE ${SRCS}) - -# LibTorch -target_include_directories(pyiganet_core PUBLIC ${TORCH_INCLUDE_DIRS}) -target_link_libraries(pyiganet_core PUBLIC pugixml) - -# Intel Extension for PyTorch -if (IPEX_FOUND) - target_link_libraries(${name} PUBLIC ${TORCH_IPEX_LIBRARIES}) -endif() - -if (IGANET_WITH_GISMO) - target_link_libraries(pyiganet_core PUBLIC gismo_static) -endif() - -if (IGANET_WITH_MATPLOT) - target_link_libraries(pyiganet_core PUBLIC Matplot++::matplot) -endif() - -if (IGANET_WITH_MPI) - target_link_libraries(pyiganet_core PUBLIC MPI::MPI_CXX) -endif() - -if (IGANET_WITH_OPENMP) - target_link_libraries(pyiganet_core PUBLIC OpenMP::OpenMP_CXX) -endif() +######################################################################## +# CMakeLists.txt +# +# Author: Matthias Moller +# Copyright (C) 2021-2023 by the IgaNet authors +# +# This file is part of the IgaNet project +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# CMakeLists.txt accepts the following command line parameters +# +# PYIGANET_COEFF_TYPE +# +######################################################################## + +include(boost_preprocessor) +include(pybind11) + +######################################################################## +# Options +######################################################################## + +if(NOT PYIGANET_COEFF_TYPE) + set(PYIGANET_COEFF_TYPE double CACHE STRING + "Coefficient type(float, double)" FORCE) +endif() +set_property(CACHE PYIGANET_COEFF_TYPE PROPERTY STRINGS "float" "double") + +message("PyIgaNet options:") +message("PYIGANET_COEFF_TYPE................: ${PYIGANET_COEFF_TYPE}") +message("") + +######################################################################## +# Auto-generate source files +######################################################################## + +# Clean output directory +file(GLOB_RECURSE FILES_AND_DIRS "${CMAKE_CURRENT_BINARY_DIR}/*") +foreach(ITEM ${FILES_AND_DIRS}) + file(REMOVE_RECURSE ${ITEM}) +endforeach() + +# Config +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/pyconfig.hpp.in + ${CMAKE_CURRENT_BINARY_DIR}/pyconfig.hpp @ONLY) + +# Create list of PyBind11 init subroutines +list(APPEND init_core "init_options") + +# Explicit instantiation of templated blocktensor classes +foreach(ROWS RANGE 1 2) + foreach(COLS RANGE 1 2) + + set(SUFFIX "_${ROWS}_${COLS}") + list(APPEND init_core "init_BlockTensor${SUFFIX}") + + configure_file(${CMAKE_CURRENT_SOURCE_DIR}/pyblocktensor.cxx.in + ${CMAKE_CURRENT_BINARY_DIR}/pyblocktensor${SUFFIX}.cxx @ONLY) + endforeach() +endforeach() + +# Create list of PyBind11 init subroutines +list(APPEND init_splines "init_bspline") + +# Explicit instantiation of templated spline classes +foreach(GEODIM RANGE 1 2) + foreach(DEGREES IN ITEMS + "1" "1,1")# "1,1,1" "1,1,1,1") +# "2" "2,2" "2,2,2" "2,2,2,2" +# "3" "3,3" "3,3,3" "3,3,3,3" +# "4" "4,4" "4,4,4" "4,4,4,4" +# "5" "5,5" "5,5,5" "5,5,5,5") + + set(SUFFIX "${GEODIM}d_${DEGREES}") + string(REPLACE "," "_" SUFFIX ${SUFFIX}) + list(APPEND init_splines "init_UniformBSpline${SUFFIX}") + + string(REPLACE "," ";" PARDIM ${DEGREES}) + list(LENGTH PARDIM PARDIM) + + configure_file(${CMAKE_CURRENT_SOURCE_DIR}/pybspline.cxx.in + ${CMAKE_CURRENT_BINARY_DIR}/pybspline${SUFFIX}.cxx @ONLY) + endforeach() +endforeach() + +# Create main PyIgANet source file +file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/pyiganet.cxx + "/** + @file pyiganet/pyiganet.cxx + + @brief PyIgANet + + @author Matthias Moller + + @copyright This file is part of the IgANet project + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#include + +#include + +namespace py = pybind11;\n\n") + +foreach(init IN LISTS init_core init_splines) + file(APPEND ${CMAKE_CURRENT_BINARY_DIR}/pyiganet.cxx + "void ${init}(py::module_ &);\n") +endforeach() + +file(APPEND ${CMAKE_CURRENT_BINARY_DIR}/pyiganet.cxx + "\n\n/** + @brief Creates IgANet Python module +*/ + +PYBIND11_MODULE(pyiganet_core, m) {\n + +m.attr(\"__name__\") = \"pyiganet_core\"; +m.attr(\"__version__\") = \"${IGANET_VERSION}\"; +m.doc() = \"IgANet (Physics-informed isogeometric analysis neural network)\"; + +py::module core = m.def_submodule(\"core\"); + +core.attr(\"__name__\") = \"pyiganet_core.core\"; +core.attr(\"__version__\") = \"${IGANET_VERSION}\"; +core.doc() = \"IgANet (Physics-informed isogeometric analysis neural network): Core module\"; + +") + +foreach(init IN LISTS init_core) + file(APPEND ${CMAKE_CURRENT_BINARY_DIR}/pyiganet.cxx + "${init}(core);\n") +endforeach() + +file(APPEND ${CMAKE_CURRENT_BINARY_DIR}/pyiganet.cxx + "\n + +py::module splines = m.def_submodule(\"splines\"); + +splines.attr(\"__name__\") = \"pyiganet_core.splines\"; +splines.attr(\"__version__\") = \"${IGANET_VERSION}\"; +splines.doc() = \"IgANet (Physics-informed isogeometric analysis neural network): Spline module\"; + +") + +foreach(init IN LISTS init_splines) + file(APPEND ${CMAKE_CURRENT_BINARY_DIR}/pyiganet.cxx + "${init}(splines);\n") +endforeach() + +file(APPEND ${CMAKE_CURRENT_BINARY_DIR}/pyiganet.cxx + "}\n") + +# Include directories +include_directories(${CMAKE_CURRENT_BINARY_DIR}) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}) + +file(GLOB SRCS ${CMAKE_CURRENT_SOURCE_DIR}/*.cxx ${CMAKE_CURRENT_BINARY_DIR}/*.cxx) + +pybind11_add_module(pyiganet_core MODULE ${SRCS}) + +# LibTorch +target_include_directories(pyiganet_core PUBLIC ${TORCH_INCLUDE_DIRS}) +target_link_libraries(pyiganet_core PUBLIC pugixml) + +# Intel Extension for PyTorch +if (IPEX_FOUND) + target_link_libraries(${name} PUBLIC ${TORCH_IPEX_LIBRARIES}) +endif() + +if (IGANET_WITH_GISMO) + target_link_libraries(pyiganet_core PUBLIC gismo_static) +endif() + +if (IGANET_WITH_MATPLOT) + target_link_libraries(pyiganet_core PUBLIC Matplot++::matplot) +endif() + +if (IGANET_WITH_MPI) + target_link_libraries(pyiganet_core PUBLIC MPI::MPI_CXX) +endif() + +if (IGANET_WITH_OPENMP) + target_link_libraries(pyiganet_core PUBLIC OpenMP::OpenMP_CXX) +endif() diff --git a/pyiganet/src/pyoptions.cxx b/pyiganet/src/pyoptions.cxx index 01b29f9f..000d9aa1 100644 --- a/pyiganet/src/pyoptions.cxx +++ b/pyiganet/src/pyoptions.cxx @@ -1,122 +1,122 @@ -/** - @file pyiganet/pycore.cxx - - @brief PyIgANet core components - - @author Matthias Moller - - @copyright This file is part of the IgANet project - - This Source Code Form is subject to the terms of the Mozilla Public - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at http://mozilla.org/MPL/2.0/. -*/ - -#include -#include -#include -#include - -#include - -#include - -#include - -namespace py = pybind11; - -void init_options(py::module_ &m) { - - py::enum_(m, "MemoryFormat", "Memory format types") - .value("Contiguous", c10::MemoryFormat::Contiguous) - .value("Preserve", c10::MemoryFormat::Preserve) - .value("ChannelsLast", c10::MemoryFormat::ChannelsLast) - .value("ChannelsLast3d", c10::MemoryFormat::ChannelsLast3d); - - py::enum_(m, "Layout", "Memory layout types") - .value("Strided", c10::Layout::Strided) - .value("Sparse", c10::Layout::Sparse) - .value("SparseCsr", c10::Layout::SparseCsr) - .value("Mkldnn", c10::Layout::Mkldnn) - .value("SparseCsc", c10::Layout::SparseCsc) - .value("SparseBsr", c10::Layout::SparseBsr) - .value("SparseBsc", c10::Layout::SparseBsc); - - py::class_>(m, "Options") - - // Constructors - .def(py::init<>()) - - // Member functions - .def("device", - py::overload_cast<>(&iganet::Options::device, - py::const_), - "Returns the `device` property") - - .def("device", - py::overload_cast( - &iganet::Options::device, py::const_), - "Returns a new Options object with the `device` property as given") - - .def("device_index", - py::overload_cast<>(&iganet::Options::device_index, - py::const_), - "Returns the `device_index` property") - - .def("device_index", - py::overload_cast( - &iganet::Options::device_index, py::const_), - "Returns a new Options object with the `device_index` property as " - "given") - - .def( - "dtype", - [](const iganet::Options &self) { - return self.dtype(); - }, - "Returns the `dtype` property") - - .def("layout", - py::overload_cast<>(&iganet::Options::layout, - py::const_), - "Returns the `layout` property") - - .def("layout", - py::overload_cast( - &iganet::Options::layout, py::const_), - "Returns a new Options object with the `layout` property as given") - - .def("requires_grad", - py::overload_cast<>( - &iganet::Options::requires_grad, py::const_), - "Returns the `requires_grad` property") - - .def("requires_grad", - py::overload_cast( - &iganet::Options::requires_grad, py::const_), - "Returns a new Options object with the `requires_grad` property as " - "given") - - .def("pinned_memory", - py::overload_cast<>( - &iganet::Options::pinned_memory, py::const_), - "Returns the `pinned_memory` property") - - .def("pinned_memory", - py::overload_cast( - &iganet::Options::pinned_memory, py::const_), - "Returns a new Options object with the `pinned_memory` property as " - "given") - - .def("is_sparse", - py::overload_cast<>(&iganet::Options::is_sparse, - py::const_), - "Returns if the layout is sparse") - - // Print - .def("__repr__", [](const iganet::Options &obj) { - std::stringstream ss; - obj.pretty_print(ss); - return ss.str(); - }); -} +/** + @file pyiganet/pycore.cxx + + @brief PyIgANet core components + + @author Matthias Moller + + @copyright This file is part of the IgANet project + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#include +#include +#include +#include + +#include + +#include + +#include + +namespace py = pybind11; + +void init_options(py::module_ &m) { + + py::enum_(m, "MemoryFormat", "Memory format types") + .value("Contiguous", c10::MemoryFormat::Contiguous) + .value("Preserve", c10::MemoryFormat::Preserve) + .value("ChannelsLast", c10::MemoryFormat::ChannelsLast) + .value("ChannelsLast3d", c10::MemoryFormat::ChannelsLast3d); + + py::enum_(m, "Layout", "Memory layout types") + .value("Strided", c10::Layout::Strided) + .value("Sparse", c10::Layout::Sparse) + .value("SparseCsr", c10::Layout::SparseCsr) + .value("Mkldnn", c10::Layout::Mkldnn) + .value("SparseCsc", c10::Layout::SparseCsc) + .value("SparseBsr", c10::Layout::SparseBsr) + .value("SparseBsc", c10::Layout::SparseBsc); + + py::class_>(m, "Options") + + // Constructors + .def(py::init<>()) + + // Member functions + .def("device", + py::overload_cast<>(&iganet::Options::device, + py::const_), + "Returns the `device` property") + + .def("device", + py::overload_cast( + &iganet::Options::device, py::const_), + "Returns a new Options object with the `device` property as given") + + .def("device_index", + py::overload_cast<>(&iganet::Options::device_index, + py::const_), + "Returns the `device_index` property") + + .def("device_index", + py::overload_cast( + &iganet::Options::device_index, py::const_), + "Returns a new Options object with the `device_index` property as " + "given") + + .def( + "dtype", + [](const iganet::Options &self) { + return self.dtype(); + }, + "Returns the `dtype` property") + + .def("layout", + py::overload_cast<>(&iganet::Options::layout, + py::const_), + "Returns the `layout` property") + + .def("layout", + py::overload_cast( + &iganet::Options::layout, py::const_), + "Returns a new Options object with the `layout` property as given") + + .def("requires_grad", + py::overload_cast<>( + &iganet::Options::requires_grad, py::const_), + "Returns the `requires_grad` property") + + .def("requires_grad", + py::overload_cast( + &iganet::Options::requires_grad, py::const_), + "Returns a new Options object with the `requires_grad` property as " + "given") + + .def("pinned_memory", + py::overload_cast<>( + &iganet::Options::pinned_memory, py::const_), + "Returns the `pinned_memory` property") + + .def("pinned_memory", + py::overload_cast( + &iganet::Options::pinned_memory, py::const_), + "Returns a new Options object with the `pinned_memory` property as " + "given") + + .def("is_sparse", + py::overload_cast<>(&iganet::Options::is_sparse, + py::const_), + "Returns if the layout is sparse") + + // Print + .def("__repr__", [](const iganet::Options &obj) { + std::stringstream ss; + obj.pretty_print(ss); + return ss.str(); + }); +} diff --git a/unittests/unittest_boundary.cxx b/unittests/unittest_boundary.cxx index b234d323..a1a6b0fa 100644 --- a/unittests/unittest_boundary.cxx +++ b/unittests/unittest_boundary.cxx @@ -1,2155 +1,2155 @@ -/** - @file unittests/unittest_boundary.cxx - - @brief Boundary unittests - - @author Matthias Moller - - @copyright This file is part of the IgANet project - - This Source Code Form is subject to the terms of the Mozilla Public - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at http://mozilla.org/MPL/2.0/. -*/ - -#include -#include -#include - -#include -#include - -using namespace iganet::unittests::literals; - -class BoundaryTest : public ::testing::Test { -public: - BoundaryTest() { std::srand(std::time(nullptr)); } - -protected: - using real_t = iganet::unittests::real_t; - iganet::Options options; -}; - -TEST_F(BoundaryTest, Boundary_parDim1_geoDim1_degrees2) { - using iganet::deriv; - using iganet::side; - using BSpline = iganet::UniformBSpline; - iganet::Boundary boundary({0}, iganet::init::greville, options); - - auto xi = std::tuple{std::array{}, - std::array{}}; - - // Evaluation - auto values = boundary.eval(xi); - - EXPECT_TRUE(torch::equal(*std::get(values)[0], - torch::ones(1, options))); - EXPECT_TRUE(torch::equal(*std::get(values)[0], - torch::ones(1, options))); - - values = boundary.eval(xi); - - EXPECT_TRUE(torch::equal(*std::get(values)[0], - torch::zeros(1, options))); - EXPECT_TRUE(torch::equal(*std::get(values)[0], - torch::zeros(1, options))); - - values = boundary.eval(xi); - - EXPECT_TRUE(torch::equal(*std::get(values)[0], - torch::zeros(1, options))); - EXPECT_TRUE(torch::equal(*std::get(values)[0], - torch::zeros(1, options))); - - // Evaluation from precomputed coefficients and basis functions - auto knot_indices = boundary.find_knot_indices(xi); - auto coeff_indices = boundary.find_coeff_indices(knot_indices); - - auto numel = [](const auto &xi) { - return std::tuple{1, 1}; - }; - auto sizes = [](const auto &xi) { - return std::tuple{torch::IntArrayRef{}, torch::IntArrayRef{}}; - }; - - auto basfunc = - boundary.template eval_basfunc(xi, knot_indices); - values = boundary.eval_from_precomputed(basfunc, coeff_indices, numel(xi), - sizes(xi)); - - EXPECT_TRUE(torch::equal(*std::get(values)[0], - torch::ones({}, options))); - EXPECT_TRUE(torch::equal(*std::get(values)[0], - torch::ones({}, options))); - - basfunc = boundary.template eval_basfunc(xi, knot_indices); - values = boundary.eval_from_precomputed(basfunc, coeff_indices, numel(xi), - sizes(xi)); - - EXPECT_TRUE(torch::equal(*std::get(values)[0], - torch::zeros({}, options))); - EXPECT_TRUE(torch::equal(*std::get(values)[0], - torch::zeros({}, options))); - - basfunc = - boundary.template eval_basfunc(xi, knot_indices); - values = boundary.eval_from_precomputed(basfunc, coeff_indices, numel(xi), - sizes(xi)); - - EXPECT_TRUE(torch::equal(*std::get(values)[0], - torch::zeros({}, options))); - EXPECT_TRUE(torch::equal(*std::get(values)[0], - torch::zeros({}, options))); -} - -TEST_F(BoundaryTest, Boundary_parDim2_geoDim1_degrees23) { - using iganet::deriv; - using iganet::side; - using BSpline = iganet::UniformBSpline; - iganet::Boundary boundary({5, 4}, iganet::init::greville, options); - - auto xi = std::tuple{ - iganet::utils::to_tensorArray(options, {1.0_r, 0.2_r, 0.1_r, 0.5_r, 0.9_r, - 0.75_r, 0.0_r}) /* west */, - iganet::utils::to_tensorArray(options, {1.0_r, 0.2_r, 0.1_r, 0.5_r, 0.9_r, - 0.75_r, 0.0_r}) /* east */, - iganet::utils::to_tensorArray( - options, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}) /* south */, - iganet::utils::to_tensorArray( - options, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}) /* north */}; - - // Evaluation - auto values = boundary.eval(xi); - - iganet::UniformBSpline bspline_bdrNS( - {5}, iganet::init::greville, options); - iganet::UniformBSpline bspline_bdrEW( - {4}, iganet::init::greville, options); - - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - - values = boundary.eval(xi); - - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - - values = boundary.eval(xi); - - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - - values = boundary.eval(xi); - - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - - values = boundary.eval(xi); - - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - - values = boundary.eval(xi); - - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - - // Evaluation from precomputed coefficients and basis functions - auto knot_indices = boundary.find_knot_indices(xi); - auto coeff_indices = boundary.find_coeff_indices(knot_indices); - - auto numel = [](const auto &xi) { - return std::tuple{std::get<0>(xi)[0].numel(), std::get<1>(xi)[0].numel(), - std::get<2>(xi)[0].numel(), std::get<3>(xi)[0].numel()}; - }; - auto sizes = [](const auto &xi) { - return std::tuple{std::get<0>(xi)[0].sizes(), std::get<1>(xi)[0].sizes(), - std::get<2>(xi)[0].sizes(), std::get<3>(xi)[0].sizes()}; - }; - - auto basfunc = - boundary.template eval_basfunc(xi, knot_indices); - values = boundary.eval_from_precomputed(basfunc, coeff_indices, numel(xi), - sizes(xi)); - - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - - basfunc = boundary.template eval_basfunc(xi, knot_indices); - values = boundary.eval_from_precomputed(basfunc, coeff_indices, numel(xi), - sizes(xi)); - - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - - basfunc = - boundary.template eval_basfunc(xi, knot_indices); - values = boundary.eval_from_precomputed(basfunc, coeff_indices, numel(xi), - sizes(xi)); - - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - - basfunc = boundary.template eval_basfunc(xi, knot_indices); - values = boundary.eval_from_precomputed(basfunc, coeff_indices, numel(xi), - sizes(xi)); - - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - - basfunc = - boundary.template eval_basfunc(xi, knot_indices); - values = boundary.eval_from_precomputed(basfunc, coeff_indices, numel(xi), - sizes(xi)); - - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - - basfunc = boundary.template eval_basfunc( - xi, knot_indices); - values = boundary.eval_from_precomputed(basfunc, coeff_indices, numel(xi), - sizes(xi)); - - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); -} - -TEST_F(BoundaryTest, Boundary_parDim3_geoDim1_degrees234) { - using iganet::deriv; - using iganet::side; - using BSpline = iganet::UniformBSpline; - iganet::Boundary boundary({5, 4, 7}, iganet::init::greville, - options); - - auto xi = std::tuple{ - iganet::utils::to_tensorArray( - options, {1.0_r, 0.2_r, 0.1_r, 0.5_r, 0.9_r, 0.75_r, 0.0_r} /* v */, - {0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r, 0.0_r, - 0.1_r} /* w */) /* west */, - iganet::utils::to_tensorArray( - options, {1.0_r, 0.2_r, 0.1_r, 0.5_r, 0.9_r, 0.75_r, 0.0_r} /* v */, - {0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r, 0.0_r, - 0.1_r} /* w */) /* east */, - iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r} /* u */, - {0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r, 0.0_r, - 0.1_r} /* w */) /* south */, - iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r} /* u */, - {0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r, 0.0_r, - 0.1_r} /* w */) /* north */, - iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r} /* u */, - {1.0_r, 0.2_r, 0.1_r, 0.5_r, 0.9_r, 0.75_r, - 0.0_r} /* v */) /* front */, - iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r} /* u */, - {1.0_r, 0.2_r, 0.1_r, 0.5_r, 0.9_r, 0.75_r, - 0.0_r} /* v */) /* back */}; - - // Evaluation - auto values = boundary.eval(xi); - - iganet::UniformBSpline bspline_bdrNS( - {5, 7}, iganet::init::greville, options); - iganet::UniformBSpline bspline_bdrEW( - {4, 7}, iganet::init::greville, options); - iganet::UniformBSpline bspline_bdrFB( - {5, 4}, iganet::init::greville, options); - - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - - values = boundary.eval(xi); - - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - - values = boundary.eval(xi); - - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - - values = boundary.eval(xi); - - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - - values = boundary.eval(xi); - - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - - values = boundary.eval(xi); - - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - - values = boundary.eval(xi); - - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - - values = boundary.eval(xi); - - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - - values = boundary.eval(xi); - - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - - values = boundary.eval(xi); - - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - - // Evaluation from precomputed coefficients and basis functions - auto knot_indices = boundary.find_knot_indices(xi); - auto coeff_indices = boundary.find_coeff_indices(knot_indices); - - auto numel = [](const auto &xi) { - return std::tuple{std::get<0>(xi)[0].numel(), std::get<1>(xi)[0].numel(), - std::get<2>(xi)[0].numel(), std::get<3>(xi)[0].numel(), - std::get<4>(xi)[0].numel(), std::get<5>(xi)[0].numel()}; - }; - auto sizes = [](const auto &xi) { - return std::tuple{std::get<0>(xi)[0].sizes(), std::get<1>(xi)[0].sizes(), - std::get<2>(xi)[0].sizes(), std::get<3>(xi)[0].sizes(), - std::get<4>(xi)[0].sizes(), std::get<5>(xi)[0].sizes()}; - }; - - auto basfunc = - boundary.template eval_basfunc(xi, knot_indices); - values = boundary.eval_from_precomputed(basfunc, coeff_indices, numel(xi), - sizes(xi)); - - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - - basfunc = boundary.template eval_basfunc(xi, knot_indices); - values = boundary.eval_from_precomputed(basfunc, coeff_indices, numel(xi), - sizes(xi)); - - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - - basfunc = - boundary.template eval_basfunc(xi, knot_indices); - values = boundary.eval_from_precomputed(basfunc, coeff_indices, numel(xi), - sizes(xi)); - - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - - basfunc = boundary.template eval_basfunc(xi, knot_indices); - values = boundary.eval_from_precomputed(basfunc, coeff_indices, numel(xi), - sizes(xi)); - - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - - basfunc = - boundary.template eval_basfunc(xi, knot_indices); - values = boundary.eval_from_precomputed(basfunc, coeff_indices, numel(xi), - sizes(xi)); - - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - - basfunc = boundary.template eval_basfunc(xi, knot_indices); - values = boundary.eval_from_precomputed(basfunc, coeff_indices, numel(xi), - sizes(xi)); - - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - - basfunc = - boundary.template eval_basfunc(xi, knot_indices); - values = boundary.eval_from_precomputed(basfunc, coeff_indices, numel(xi), - sizes(xi)); - - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - - basfunc = boundary.template eval_basfunc( - xi, knot_indices); - values = boundary.eval_from_precomputed(basfunc, coeff_indices, numel(xi), - sizes(xi)); - - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - - basfunc = boundary.template eval_basfunc( - xi, knot_indices); - values = boundary.eval_from_precomputed(basfunc, coeff_indices, numel(xi), - sizes(xi)); - - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - - basfunc = boundary.template eval_basfunc( - xi, knot_indices); - values = boundary.eval_from_precomputed(basfunc, coeff_indices, numel(xi), - sizes(xi)); - - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(values)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); -} - -TEST_F(BoundaryTest, Boundary_init) { - { - using iganet::side; - using BSpline = iganet::UniformBSpline; - iganet::Boundary boundary({5, 4}, iganet::init::zeros, options); - - EXPECT_TRUE(torch::equal(boundary.side().coeffs(0), - torch::zeros(4, options))); - EXPECT_TRUE(torch::equal(boundary.side().coeffs(0), - torch::zeros(4, options))); - - EXPECT_TRUE(torch::equal(boundary.side().coeffs(0), - torch::zeros(5, options))); - EXPECT_TRUE(torch::equal(boundary.side().coeffs(0), - torch::zeros(5, options))); - } - - { - using iganet::side; - using BSpline = iganet::UniformBSpline; - iganet::Boundary boundary({5, 4}, iganet::init::ones, options); - - EXPECT_TRUE(torch::equal(boundary.side().coeffs(0), - torch::ones(4, options))); - EXPECT_TRUE(torch::equal(boundary.side().coeffs(0), - torch::ones(4, options))); - - EXPECT_TRUE(torch::equal(boundary.side().coeffs(0), - torch::ones(5, options))); - EXPECT_TRUE(torch::equal(boundary.side().coeffs(0), - torch::ones(5, options))); - } - - { - using iganet::side; - using BSpline = iganet::UniformBSpline; - iganet::Boundary boundary({5, 4}, iganet::init::linear, options); - - EXPECT_TRUE(torch::equal(boundary.side().coeffs(0), - torch::linspace(0, 1, 4, options))); - EXPECT_TRUE(torch::equal(boundary.side().coeffs(0), - torch::linspace(0, 1, 4, options))); - - EXPECT_TRUE(torch::equal(boundary.side().coeffs(0), - torch::linspace(0, 1, 5, options))); - EXPECT_TRUE(torch::equal(boundary.side().coeffs(0), - torch::linspace(0, 1, 5, options))); - } - - { - using iganet::side; - using BSpline = iganet::UniformBSpline; - iganet::Boundary boundary({5, 4}, iganet::init::greville, options); - - EXPECT_TRUE(torch::allclose(boundary.side().coeffs(0), - torch::linspace(0, 1, 4, options))); - EXPECT_TRUE(torch::allclose(boundary.side().coeffs(0), - torch::linspace(0, 1, 4, options))); - - EXPECT_TRUE(torch::allclose(boundary.side().coeffs(0), - torch::linspace(0, 1, 5, options))); - EXPECT_TRUE(torch::allclose(boundary.side().coeffs(0), - torch::linspace(0, 1, 5, options))); - } -} - -TEST_F(BoundaryTest, Boundary_refine) { - - // parDim == 2 - - { - using BSpline = iganet::UniformBSpline; - iganet::Boundary boundary({5, 4}, iganet::init::greville, options); - iganet::Boundary boundary_ref({8, 5}, iganet::init::greville, - options); - boundary.uniform_refine(); - EXPECT_TRUE(boundary.isclose(boundary_ref)); - } - - { - using BSpline = iganet::UniformBSpline; - iganet::Boundary boundary({5, 4}, iganet::init::greville, options); - iganet::Boundary boundary_ref({14, 7}, iganet::init::greville, - options); - boundary.uniform_refine(2); - EXPECT_TRUE(boundary.isclose(boundary_ref)); - } - - { - using BSpline = iganet::UniformBSpline; - iganet::Boundary boundary({5, 4}, iganet::init::greville, options); - iganet::Boundary boundary_ref({8, 4}, iganet::init::greville, - options); - boundary.uniform_refine(1, 0); - EXPECT_TRUE(boundary.isclose(boundary_ref)); - } - - { - using BSpline = iganet::UniformBSpline; - iganet::Boundary boundary({5, 4}, iganet::init::greville, options); - iganet::Boundary boundary_ref({5, 5}, iganet::init::greville, - options); - boundary.uniform_refine(1, 1); - EXPECT_TRUE(boundary.isclose(boundary_ref)); - } - - { - using BSpline = iganet::UniformBSpline; - iganet::Boundary boundary({5, 4}, iganet::init::greville, options); - EXPECT_THROW((boundary.uniform_refine(1, 2)), std::runtime_error); - EXPECT_THROW((boundary.uniform_refine(1, 3)), std::runtime_error); - EXPECT_THROW((boundary.uniform_refine(1, 4)), std::runtime_error); - } - - { - using BSpline = iganet::UniformBSpline; - iganet::Boundary boundary({5, 4}, iganet::init::greville, options); - iganet::Boundary boundary_ref({14, 5}, iganet::init::greville, - options); - boundary.uniform_refine(2, 0).uniform_refine(1, 1); - EXPECT_TRUE(boundary.isclose(boundary_ref)); - } - - // parDim == 3 - - { - using BSpline = iganet::UniformBSpline; - iganet::Boundary boundary({5, 4, 7}, iganet::init::greville, - options); - iganet::Boundary boundary_ref({8, 5, 10}, iganet::init::greville, - options); - boundary.uniform_refine(); - EXPECT_TRUE(boundary.isclose(boundary_ref)); - } - - { - using BSpline = iganet::UniformBSpline; - iganet::Boundary boundary({5, 4, 7}, iganet::init::greville, - options); - iganet::Boundary boundary_ref({14, 7, 16}, iganet::init::greville, - options); - boundary.uniform_refine(2); - EXPECT_TRUE(boundary.isclose(boundary_ref)); - } - - { - using BSpline = iganet::UniformBSpline; - iganet::Boundary boundary({5, 4, 7}, iganet::init::greville, - options); - iganet::Boundary boundary_ref({8, 4, 7}, iganet::init::greville, - options); - boundary.uniform_refine(1, 0); - EXPECT_TRUE(boundary.isclose(boundary_ref)); - } - - { - using BSpline = iganet::UniformBSpline; - iganet::Boundary boundary({5, 4, 7}, iganet::init::greville, - options); - iganet::Boundary boundary_ref({5, 5, 7}, iganet::init::greville, - options); - boundary.uniform_refine(1, 1); - EXPECT_TRUE(boundary.isclose(boundary_ref)); - } - - { - using BSpline = iganet::UniformBSpline; - iganet::Boundary boundary({5, 4, 7}, iganet::init::greville, - options); - iganet::Boundary boundary_ref({5, 4, 10}, iganet::init::greville, - options); - boundary.uniform_refine(1, 2); - EXPECT_TRUE(boundary.isclose(boundary_ref)); - } - - { - using BSpline = iganet::UniformBSpline; - iganet::Boundary boundary({5, 4, 7}, iganet::init::greville, - options); - iganet::Boundary boundary_ref({5, 4, 10}, iganet::init::greville, - options); - EXPECT_THROW((boundary.uniform_refine(1, 3)), std::runtime_error); - EXPECT_THROW((boundary.uniform_refine(1, 4)), std::runtime_error); - } - - { - using BSpline = iganet::UniformBSpline; - iganet::Boundary boundary({5, 4, 7}, iganet::init::greville, - options); - iganet::Boundary boundary_ref({8, 7, 10}, iganet::init::greville, - options); - boundary.uniform_refine(1, 0).uniform_refine(2, 1).uniform_refine(1, 2); - EXPECT_TRUE(boundary.isclose(boundary_ref)); - } - - // parDim == 4 - - { - using BSpline = iganet::UniformBSpline; - iganet::Boundary boundary({5, 4, 7, 8}, iganet::init::greville, - options); - iganet::Boundary boundary_ref({8, 5, 10, 11}, - iganet::init::greville, options); - boundary.uniform_refine(); - EXPECT_TRUE(boundary.isclose(boundary_ref)); - } - - { - using BSpline = iganet::UniformBSpline; - iganet::Boundary boundary({5, 4, 7, 8}, iganet::init::greville, - options); - iganet::Boundary boundary_ref({14, 7, 16, 17}, - iganet::init::greville, options); - boundary.uniform_refine(2); - EXPECT_TRUE(boundary.isclose(boundary_ref)); - } - - { - using BSpline = iganet::UniformBSpline; - iganet::Boundary boundary({5, 4, 7, 8}, iganet::init::greville, - options); - iganet::Boundary boundary_ref({8, 4, 7, 8}, iganet::init::greville, - options); - boundary.uniform_refine(1, 0); - EXPECT_TRUE(boundary.isclose(boundary_ref)); - } - - { - using BSpline = iganet::UniformBSpline; - iganet::Boundary boundary({5, 4, 7, 8}, iganet::init::greville, - options); - iganet::Boundary boundary_ref({5, 5, 7, 8}, iganet::init::greville, - options); - boundary.uniform_refine(1, 1); - EXPECT_TRUE(boundary.isclose(boundary_ref)); - } - - { - using BSpline = iganet::UniformBSpline; - iganet::Boundary boundary({5, 4, 7, 8}, iganet::init::greville, - options); - iganet::Boundary boundary_ref({5, 4, 10, 8}, - iganet::init::greville, options); - boundary.uniform_refine(1, 2); - EXPECT_TRUE(boundary.isclose(boundary_ref)); - } - - { - using BSpline = iganet::UniformBSpline; - iganet::Boundary boundary({5, 4, 7, 8}, iganet::init::greville, - options); - iganet::Boundary boundary_ref({5, 4, 7, 11}, - iganet::init::greville, options); - boundary.uniform_refine(1, 3); - EXPECT_TRUE(boundary.isclose(boundary_ref)); - } - - { - using BSpline = iganet::UniformBSpline; - iganet::Boundary boundary({5, 4, 7, 8}, iganet::init::greville, - options); - EXPECT_THROW((boundary.uniform_refine(1, 4)), std::runtime_error); - } - - { - using BSpline = iganet::UniformBSpline; - iganet::Boundary boundary({5, 4, 7, 8}, iganet::init::greville, - options); - iganet::Boundary boundary_ref({8, 7, 10, 17}, - iganet::init::greville, options); - boundary.uniform_refine(1, 0) - .uniform_refine(2, 1) - .uniform_refine(1, 2) - .uniform_refine(2, 3); - EXPECT_TRUE(boundary.isclose(boundary_ref)); - } -} - -TEST_F(BoundaryTest, Boundary_copy_constructor) { - using BSpline = iganet::UniformBSpline; - iganet::Boundary boundary_orig({5, 4}, iganet::init::greville, - options); - iganet::Boundary boundary_copy(boundary_orig); - - boundary_orig.side().transform( - [](const std::array xi) { - return std::array{0.0_r}; - }); - - EXPECT_TRUE(boundary_orig == boundary_copy); -} - -TEST_F(BoundaryTest, Boundary_clone_constructor) { - using BSpline = iganet::UniformBSpline; - iganet::Boundary boundary_ref({5, 4}, iganet::init::greville, - options); - iganet::Boundary boundary_orig({5, 4}, iganet::init::greville, - options); - iganet::Boundary boundary_clone(boundary_orig, true); - - boundary_orig.side().transform( - [](const std::array xi) { - return std::array{0.0_r}; - }); - - EXPECT_TRUE(boundary_ref == boundary_clone); -} - -TEST_F(BoundaryTest, Boundary_move_constructor) { - using BSpline = iganet::UniformBSpline; - iganet::Boundary boundary_ref({14, 7}, iganet::init::greville, - options); - auto boundary( - iganet::Boundary({5, 4}, iganet::init::greville, options) - .uniform_refine(2)); - - EXPECT_TRUE(boundary.isclose(boundary_ref)); -} - -TEST_F(BoundaryTest, Boundary_read_write) { - std::filesystem::path filename = - std::filesystem::temp_directory_path() / std::to_string(rand()); - using BSpline = iganet::UniformBSpline; - iganet::Boundary boundary_out({5, 4}, iganet::init::greville, - options); - boundary_out.save(filename.c_str()); - - iganet::Boundary boundary_in(options); - boundary_in.load(filename.c_str()); - std::filesystem::remove(filename); - - EXPECT_TRUE(boundary_in == boundary_out); - EXPECT_FALSE(boundary_in != boundary_out); -} - -TEST_F(BoundaryTest, Boundary_to_from_xml) { - { - using BSpline = iganet::UniformBSpline; - iganet::Boundary boundary_out({5}, iganet::init::greville, - options); - - boundary_out.side().transform( - [](const std::array) { - return std::array{static_cast(std::rand()), - static_cast(std::rand()), - static_cast(std::rand()), - static_cast(std::rand())}; - }); - - boundary_out.side().transform( - [](const std::array) { - return std::array{static_cast(std::rand()), - static_cast(std::rand()), - static_cast(std::rand()), - static_cast(std::rand())}; - }); - - pugi::xml_document doc = boundary_out.to_xml(); - - iganet::Boundary boundary_in(options); - boundary_in.from_xml(doc); - - EXPECT_TRUE(boundary_in.isclose(boundary_out)); - - // non-matching degree - EXPECT_THROW( - (iganet::Boundary>{}.from_xml(doc, - 0)), - std::runtime_error); // XML object provides too many coefficients - EXPECT_THROW( - (iganet::Boundary>{}.from_xml(doc, - 0)), - std::runtime_error); // XML object provides too many coefficients - EXPECT_THROW( - (iganet::Boundary>{}.from_xml(doc, - 0)), - std::runtime_error); // XML object provides too few coefficients - - // non-matching parametric dimension - EXPECT_THROW( - (iganet::Boundary>{}.from_xml( - doc, 0)), - std::runtime_error); - EXPECT_THROW((iganet::Boundary>{} - .from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::Boundary>{} - .from_xml(doc, 0)), - std::runtime_error); - - // non-matching geometric dimension - EXPECT_THROW( - (iganet::Boundary>{}.from_xml(doc, - 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::Boundary>{}.from_xml(doc, - 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::Boundary>{}.from_xml(doc, - 0)), - std::runtime_error); - - // non-matching id - EXPECT_THROW((iganet::Boundary{}.from_xml(doc, 1)), - std::runtime_error); - } - - { - using BSpline = iganet::UniformBSpline; - iganet::Boundary boundary_out({5, 4}, iganet::init::greville, - options); - - boundary_out.side().transform( - [](const std::array xi) { - return std::array{static_cast(std::rand())}; - }); - - boundary_out.side().transform( - [](const std::array xi) { - return std::array{static_cast(std::rand())}; - }); - - boundary_out.side().transform( - [](const std::array xi) { - return std::array{static_cast(std::rand())}; - }); - - boundary_out.side().transform( - [](const std::array xi) { - return std::array{static_cast(std::rand())}; - }); - - pugi::xml_document doc = boundary_out.to_xml(); - - iganet::Boundary boundary_in(options); - boundary_in.from_xml(doc); - - EXPECT_TRUE(boundary_in.isclose(boundary_out)); - - // non-matching degree - EXPECT_THROW( - (iganet::Boundary>{}.from_xml( - doc, 0)), - std::runtime_error); // XML object provides too many coefficients - EXPECT_THROW( - (iganet::Boundary>{}.from_xml( - doc, 0)), - std::runtime_error); // XML object provides too many coefficients - EXPECT_THROW( - (iganet::Boundary>{}.from_xml( - doc, 0)), - std::runtime_error); // XML object provides too few coefficients - EXPECT_THROW( - (iganet::Boundary>{}.from_xml( - doc, 0)), - std::runtime_error); // XML object provides too few coefficients - - // non-matching parametric dimension - EXPECT_THROW( - (iganet::Boundary>{}.from_xml(doc, - 0)), - std::runtime_error); - EXPECT_THROW((iganet::Boundary>{} - .from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::Boundary>{} - .from_xml(doc, 0)), - std::runtime_error); - - // non-matching geometric dimension - EXPECT_THROW( - (iganet::Boundary>{}.from_xml( - doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::Boundary>{}.from_xml( - doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::Boundary>{}.from_xml( - doc, 0)), - std::runtime_error); - - // non-matching id - EXPECT_THROW((iganet::Boundary{}.from_xml(doc, 1)), - std::runtime_error); - } - - { - using BSpline = iganet::UniformBSpline; - iganet::Boundary boundary_out({5, 4, 5}, iganet::init::greville, - options); - - boundary_out.side().transform( - [](const std::array xi) { - return std::array{static_cast(std::rand()), - static_cast(std::rand()), - static_cast(std::rand())}; - }); - - boundary_out.side().transform( - [](const std::array xi) { - return std::array{static_cast(std::rand()), - static_cast(std::rand()), - static_cast(std::rand())}; - }); - - boundary_out.side().transform( - [](const std::array xi) { - return std::array{static_cast(std::rand()), - static_cast(std::rand()), - static_cast(std::rand())}; - }); - - boundary_out.side().transform( - [](const std::array xi) { - return std::array{static_cast(std::rand()), - static_cast(std::rand()), - static_cast(std::rand())}; - }); - - boundary_out.side().transform( - [](const std::array xi) { - return std::array{static_cast(std::rand()), - static_cast(std::rand()), - static_cast(std::rand())}; - }); - - boundary_out.side().transform( - [](const std::array xi) { - return std::array{static_cast(std::rand()), - static_cast(std::rand()), - static_cast(std::rand())}; - }); - - pugi::xml_document doc = boundary_out.to_xml(); - - iganet::Boundary boundary_in(options); - boundary_in.from_xml(doc); - - EXPECT_TRUE(boundary_in.isclose(boundary_out)); - - // non-matching degree - EXPECT_THROW((iganet::Boundary>{} - .from_xml(doc, 0)), - std::runtime_error); - - // non-matching parametric dimension - EXPECT_THROW( - (iganet::Boundary>{}.from_xml(doc, - 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::Boundary>{}.from_xml( - doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::Boundary>{} - .from_xml(doc, 0)), - std::runtime_error); - - // non-matching geometric dimension - EXPECT_THROW((iganet::Boundary>{} - .from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW((iganet::Boundary>{} - .from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW((iganet::Boundary>{} - .from_xml(doc, 0)), - std::runtime_error); - - // non-matching id - EXPECT_THROW((iganet::Boundary{}.from_xml(doc, 1)), - std::runtime_error); - } - - { - using BSpline = iganet::UniformBSpline; - iganet::Boundary boundary_out({5, 4, 5, 6}, iganet::init::greville, - options); - - boundary_out.side().transform( - [](const std::array xi) { - return std::array{static_cast(std::rand()), - static_cast(std::rand())}; - }); - - boundary_out.side().transform( - [](const std::array xi) { - return std::array{static_cast(std::rand()), - static_cast(std::rand())}; - }); - - boundary_out.side().transform( - [](const std::array xi) { - return std::array{static_cast(std::rand()), - static_cast(std::rand())}; - }); - - boundary_out.side().transform( - [](const std::array xi) { - return std::array{static_cast(std::rand()), - static_cast(std::rand())}; - }); - - boundary_out.side().transform( - [](const std::array xi) { - return std::array{static_cast(std::rand()), - static_cast(std::rand())}; - }); - - boundary_out.side().transform( - [](const std::array xi) { - return std::array{static_cast(std::rand()), - static_cast(std::rand())}; - }); - - boundary_out.side().transform( - [](const std::array xi) { - return std::array{static_cast(std::rand()), - static_cast(std::rand())}; - }); - - boundary_out.side().transform( - [](const std::array xi) { - return std::array{static_cast(std::rand()), - static_cast(std::rand())}; - }); - - pugi::xml_document doc = boundary_out.to_xml(); - - iganet::Boundary boundary_in(options); - boundary_in.from_xml(doc); - - EXPECT_TRUE(boundary_in.isclose(boundary_out)); - - // non-matching degree - EXPECT_THROW( - (iganet::Boundary>{} - .from_xml(doc, 0)), - std::runtime_error); - - // non-matching parametric dimension - EXPECT_THROW( - (iganet::Boundary>{}.from_xml(doc, - 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::Boundary>{}.from_xml( - doc, 0)), - std::runtime_error); - EXPECT_THROW((iganet::Boundary>{} - .from_xml(doc, 0)), - std::runtime_error); - - // non-matching geometric dimension - EXPECT_THROW( - (iganet::Boundary>{} - .from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::Boundary>{} - .from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::Boundary>{} - .from_xml(doc, 0)), - std::runtime_error); - - // non-matching id - EXPECT_THROW((iganet::Boundary{}.from_xml(doc, 1)), - std::runtime_error); - } -} - -TEST_F(BoundaryTest, Boundary_load_from_xml) { - { - pugi::xml_document doc; - pugi::xml_parse_result result = - doc.load_file(IGANET_DATA_DIR "domain1d/line_boundary.xml"); - - using BSpline = iganet::UniformBSpline; - iganet::Boundary boundary_in(options); - boundary_in.from_xml(doc); - - iganet::Boundary boundary_ref({5}, iganet::init::greville, - options); - - boundary_ref.side().transform( - [](const std::array) { - return std::array{ - static_cast(1.0), static_cast(2.0), - static_cast(3.0), static_cast(4.0)}; - }); - - boundary_ref.side().transform( - [](const std::array) { - return std::array{ - static_cast(-1.0), static_cast(-2.0), - static_cast(-3.0), static_cast(-4.0)}; - }); - - EXPECT_TRUE(boundary_in.isclose(boundary_ref)); - } - - { - pugi::xml_document doc; - pugi::xml_parse_result result = - doc.load_file(IGANET_DATA_DIR "domain2d/square_boundary.xml"); - - using BSpline = iganet::UniformBSpline; - iganet::Boundary boundary_in(options); - boundary_in.from_xml(doc); - - iganet::Boundary boundary_ref({5, 4}, iganet::init::greville, - options); - - boundary_ref.side().transform( - [](const std::array xi) { - return std::array{xi[0]}; - }); - - boundary_ref.side().transform( - [](const std::array xi) { - return std::array{xi[0] + 10}; - }); - - boundary_ref.side().transform( - [](const std::array xi) { - return std::array{xi[0] + 20}; - }); - - boundary_ref.side().transform( - [](const std::array xi) { - return std::array{xi[0] + 30}; - }); - - EXPECT_TRUE(boundary_in.isclose(boundary_ref)); - } - - { - pugi::xml_document doc; - pugi::xml_parse_result result = - doc.load_file(IGANET_DATA_DIR "domain3d/cube_boundary.xml"); - - using BSpline = iganet::UniformBSpline; - iganet::Boundary boundary_in(options); - boundary_in.from_xml(doc); - - iganet::Boundary boundary_ref({5, 4, 5}, iganet::init::greville, - options); - - boundary_ref.side().transform( - [](const std::array xi) { - return std::array{xi[0] + xi[1] + 1, xi[0] + xi[1] + 2, - xi[0] + xi[1] + 3}; - }); - - boundary_ref.side().transform( - [](const std::array xi) { - return std::array{xi[0] + xi[1] + 11, xi[0] + xi[1] + 12, - xi[0] + xi[1] + 13}; - }); - - boundary_ref.side().transform( - [](const std::array xi) { - return std::array{xi[0] + xi[1] + 21, xi[0] + xi[1] + 22, - xi[0] + xi[1] + 23}; - }); - - boundary_ref.side().transform( - [](const std::array xi) { - return std::array{xi[0] + xi[1] + 31, xi[0] + xi[1] + 32, - xi[0] + xi[1] + 33}; - }); - - boundary_ref.side().transform( - [](const std::array xi) { - return std::array{xi[0] + xi[1] + 41, xi[0] + xi[1] + 42, - xi[0] + xi[1] + 43}; - }); - - boundary_ref.side().transform( - [](const std::array xi) { - return std::array{xi[0] + xi[1] + 51, xi[0] + xi[1] + 52, - xi[0] + xi[1] + 53}; - }); - - EXPECT_TRUE(boundary_in.isclose(boundary_ref)); - } - - { - pugi::xml_document doc; - pugi::xml_parse_result result = - doc.load_file(IGANET_DATA_DIR "domain4d/hypercube_boundary.xml"); - - using BSpline = iganet::UniformBSpline; - iganet::Boundary boundary_in(options); - boundary_in.from_xml(doc); - - iganet::Boundary boundary_ref({5, 4, 5, 6}, iganet::init::greville, - options); - - boundary_ref.side().transform( - [](const std::array xi) { - return std::array{xi[0] + xi[1] + xi[2] + 1, - xi[0] + xi[1] + xi[2] + 2}; - }); - - boundary_ref.side().transform( - [](const std::array xi) { - return std::array{xi[0] + xi[1] + xi[2] + 11, - xi[0] + xi[1] + xi[2] + 12}; - }); - - boundary_ref.side().transform( - [](const std::array xi) { - return std::array{xi[0] + xi[1] + xi[2] + 21, - xi[0] + xi[1] + xi[2] + 22}; - }); - - boundary_ref.side().transform( - [](const std::array xi) { - return std::array{xi[0] + xi[1] + xi[2] + 31, - xi[0] + xi[1] + xi[2] + 32}; - }); - - boundary_ref.side().transform( - [](const std::array xi) { - return std::array{xi[0] + xi[1] + xi[2] + 41, - xi[0] + xi[1] + xi[2] + 42}; - }); - - boundary_ref.side().transform( - [](const std::array xi) { - return std::array{xi[0] + xi[1] + xi[2] + 51, - xi[0] + xi[1] + xi[2] + 52}; - }); - - boundary_ref.side().transform( - [](const std::array xi) { - return std::array{xi[0] + xi[1] + xi[2] + 61, - xi[0] + xi[1] + xi[2] + 62}; - }); - - boundary_ref.side().transform( - [](const std::array xi) { - return std::array{xi[0] + xi[1] + xi[2] + 71, - xi[0] + xi[1] + xi[2] + 72}; - }); - - EXPECT_TRUE(boundary_in.isclose(boundary_ref)); - } -} - -TEST_F(BoundaryTest, Boundary_to_from_json) { - { - using BSpline = iganet::UniformBSpline; - iganet::Boundary boundary_out({5}, iganet::init::greville, - options); - - boundary_out.side().transform( - [](const std::array) { - return std::array{static_cast(std::rand()), - static_cast(std::rand()), - static_cast(std::rand()), - static_cast(std::rand())}; - }); - - boundary_out.side().transform( - [](const std::array) { - return std::array{static_cast(std::rand()), - static_cast(std::rand()), - static_cast(std::rand()), - static_cast(std::rand())}; - }); - - nlohmann::json json = boundary_out.to_json(); - - iganet::Boundary boundary_in(options); - boundary_in.from_json(json); - - EXPECT_TRUE(boundary_in.isclose(boundary_out)); - } - - { - using BSpline = iganet::UniformBSpline; - iganet::Boundary boundary_out({5, 4}, iganet::init::greville, - options); - - boundary_out.side().transform( - [](const std::array xi) { - return std::array{static_cast(std::rand())}; - }); - - boundary_out.side().transform( - [](const std::array xi) { - return std::array{static_cast(std::rand())}; - }); - - boundary_out.side().transform( - [](const std::array xi) { - return std::array{static_cast(std::rand())}; - }); - - boundary_out.side().transform( - [](const std::array xi) { - return std::array{static_cast(std::rand())}; - }); - - nlohmann::json json = boundary_out.to_json(); - - iganet::Boundary boundary_in(options); - boundary_in.from_json(json); - - EXPECT_TRUE(boundary_in.isclose(boundary_out)); - } - - { - using BSpline = iganet::UniformBSpline; - iganet::Boundary boundary_out({5, 4, 5}, iganet::init::greville, - options); - - boundary_out.side().transform( - [](const std::array xi) { - return std::array{static_cast(std::rand()), - static_cast(std::rand()), - static_cast(std::rand())}; - }); - - boundary_out.side().transform( - [](const std::array xi) { - return std::array{static_cast(std::rand()), - static_cast(std::rand()), - static_cast(std::rand())}; - }); - - boundary_out.side().transform( - [](const std::array xi) { - return std::array{static_cast(std::rand()), - static_cast(std::rand()), - static_cast(std::rand())}; - }); - - boundary_out.side().transform( - [](const std::array xi) { - return std::array{static_cast(std::rand()), - static_cast(std::rand()), - static_cast(std::rand())}; - }); - - boundary_out.side().transform( - [](const std::array xi) { - return std::array{static_cast(std::rand()), - static_cast(std::rand()), - static_cast(std::rand())}; - }); - - boundary_out.side().transform( - [](const std::array xi) { - return std::array{static_cast(std::rand()), - static_cast(std::rand()), - static_cast(std::rand())}; - }); - - nlohmann::json json = boundary_out.to_json(); - - iganet::Boundary boundary_in(options); - boundary_in.from_json(json); - - EXPECT_TRUE(boundary_in.isclose(boundary_out)); - } - - { - using BSpline = iganet::UniformBSpline; - iganet::Boundary boundary_out({5, 4, 5, 6}, iganet::init::greville, - options); - - boundary_out.side().transform( - [](const std::array xi) { - return std::array{static_cast(std::rand()), - static_cast(std::rand())}; - }); - - boundary_out.side().transform( - [](const std::array xi) { - return std::array{static_cast(std::rand()), - static_cast(std::rand())}; - }); - - boundary_out.side().transform( - [](const std::array xi) { - return std::array{static_cast(std::rand()), - static_cast(std::rand())}; - }); - - boundary_out.side().transform( - [](const std::array xi) { - return std::array{static_cast(std::rand()), - static_cast(std::rand())}; - }); - - boundary_out.side().transform( - [](const std::array xi) { - return std::array{static_cast(std::rand()), - static_cast(std::rand())}; - }); - - boundary_out.side().transform( - [](const std::array xi) { - return std::array{static_cast(std::rand()), - static_cast(std::rand())}; - }); - - boundary_out.side().transform( - [](const std::array xi) { - return std::array{static_cast(std::rand()), - static_cast(std::rand())}; - }); - - boundary_out.side().transform( - [](const std::array xi) { - return std::array{static_cast(std::rand()), - static_cast(std::rand())}; - }); - - nlohmann::json json = boundary_out.to_json(); - - iganet::Boundary boundary_in(options); - boundary_in.from_json(json); - - EXPECT_TRUE(boundary_in.isclose(boundary_out)); - } -} - -TEST_F(BoundaryTest, Boundary_query_property) { - using BSpline = iganet::UniformBSpline; - iganet::Boundary boundary({5, 4, 7}, iganet::init::greville, - options); - - EXPECT_TRUE( - std::apply([this](auto... is_uniform) { return (is_uniform && ...); }, - boundary.is_uniform())); - EXPECT_FALSE(std::apply( - [this](auto... is_nonuniform) { return (is_nonuniform || ...); }, - boundary.is_nonuniform())); - - EXPECT_TRUE(std::apply( - [this](auto... device) { return ((device == options.device()) && ...); }, - boundary.device())); - EXPECT_TRUE(std::apply( - [this](auto... device_index) { - return ((device_index == options.device_index()) && ...); - }, - boundary.device_index())); - EXPECT_TRUE(std::apply( - [this](auto... dtype) { return ((dtype == options.dtype()) && ...); }, - boundary.dtype())); - EXPECT_TRUE(std::apply( - [this](auto... is_sparse) { - return ((is_sparse == options.is_sparse()) && ...); - }, - boundary.is_sparse())); - EXPECT_TRUE(std::apply( - [this](auto... layout) { return ((layout == options.layout()) && ...); }, - boundary.layout())); - EXPECT_TRUE(std::apply( - [this](auto... pinned_memory) { - return ((pinned_memory == options.pinned_memory()) && ...); - }, - boundary.pinned_memory())); -} - -template -inline void -check_requires_grad(std::index_sequence, const Values &values, - const Xi &xi, - const iganet::Options &options) { - auto check = [&options](const auto &values, const auto &xi) { - values[0]->operator[](0).backward(); - EXPECT_TRUE(torch::allclose(xi[0].grad(), - iganet::utils::to_tensor({1.0_r}, options))); - }; - - (check(std::get(values), std::get(xi)), ...); -} - -template -inline void check_requires_grad_throw( - std::index_sequence, const Values &values, const Xi &xi, - const iganet::Options &options) { - auto check = [&options](const auto &values, const auto &xi) { - values[0]->operator[](0).backward( - {}, true); // otherwise we cannot run backward() a second time - EXPECT_THROW(torch::allclose(xi[0].grad(), torch::empty({})), c10::Error); - }; - - (check(std::get(values), std::get(xi)), ...); -} - -TEST_F(BoundaryTest, Boundary_requires_grad) { - { - using iganet::side; - using BSpline = iganet::UniformBSpline; - iganet::Boundary boundary({5, 4, 7}, iganet::init::greville, - options); - - EXPECT_FALSE( - std::apply([](auto... requires_grad) { return (requires_grad || ...); }, - boundary.requires_grad())); - - auto xi = std::tuple{ - iganet::utils::to_tensorArray(options, {0.5_r} /* v */, {0.5_r} /* w */ - ) /* west */, - iganet::utils::to_tensorArray(options, {0.5_r} /* v */, {0.5_r} /* w */ - ) /* east */, - iganet::utils::to_tensorArray(options, {0.5_r} /* u */, {0.5_r} /* w */ - ) /* south */, - iganet::utils::to_tensorArray(options, {0.5_r} /* u */, {0.5_r} /* w */ - ) /* north */, - iganet::utils::to_tensorArray(options, {0.5_r} /* u */, {0.5_r} /* v */ - ) /* front */, - iganet::utils::to_tensorArray(options, {0.5_r} /* u */, {0.5_r} /* v */ - ) /* back */}; - auto values = boundary.eval(xi); - - // We expect an error when calling backward() because no tensor - // has requires_grad = true - std::apply( - [](const auto &...values) { - auto check = [](const auto &values) { - EXPECT_THROW(values[0]->backward(), c10::Error); - }; - (check(values), ...); - }, - values); - - xi = std::tuple{iganet::utils::to_tensorArray(options.requires_grad(true), - {0.5_r} /* v */, - {0.5_r} /* w */) /* west */, - iganet::utils::to_tensorArray(options.requires_grad(true), - {0.5_r} /* v */, - {0.5_r} /* w */) /* east */, - iganet::utils::to_tensorArray(options.requires_grad(true), - {0.5_r} /* u */, - {0.5_r} /* w */) /* south */, - iganet::utils::to_tensorArray(options.requires_grad(true), - {0.5_r} /* u */, - {0.5_r} /* w */) /* north */, - iganet::utils::to_tensorArray(options.requires_grad(true), - {0.5_r} /* u */, - {0.5_r} /* v */) /* front */, - iganet::utils::to_tensorArray(options.requires_grad(true), - {0.5_r} /* u */, - {0.5_r} /* v */) /* back */}; - values = boundary.eval(xi); - - // Note that this check cannot be implemented using std::apply as - // this functions only accepts one tuple as argument whereas here - // both values and xi need to be passed - check_requires_grad( - std::make_index_sequence::nsides()>{}, values, - xi, options); - } - - { - using iganet::side; - using BSpline = iganet::UniformBSpline; - iganet::Boundary boundary({5, 4, 7}, iganet::init::greville, - options.requires_grad(true)); - - EXPECT_TRUE( - std::apply([](auto... requires_grad) { return (requires_grad && ...); }, - boundary.requires_grad())); - - auto xi = - std::tuple{iganet::utils::to_tensorArray(options, {0.5_r} /* v */, - {0.5_r} /* w */) /* west */, - iganet::utils::to_tensorArray(options, {0.5_r} /* v */, - {0.5_r} /* w */) /* east */, - iganet::utils::to_tensorArray(options, {0.5_r} /* u */, - {0.5_r} /* w */) /* south */, - iganet::utils::to_tensorArray(options, {0.5_r} /* u */, - {0.5_r} /* w */) /* north */, - iganet::utils::to_tensorArray(options, {0.5_r} /* u */, - {0.5_r} /* v */) /* front */, - iganet::utils::to_tensorArray(options, {0.5_r} /* u */, - {0.5_r} /* v */) /* back */}; - auto values = boundary.eval(xi); - - // We expect an error because xi[0].grad() is an undefined tensor - // - // Note that this check cannot be implemented using std::apply as - // this functions only accepts one tuple as argument whereas here - // both values and xi need to be passed - check_requires_grad_throw( - std::make_index_sequence::nsides()>{}, values, - xi, options); - - xi = std::tuple{iganet::utils::to_tensorArray(options.requires_grad(true), - {0.5_r} /* v */, - {0.5_r} /* w */) /* west */, - iganet::utils::to_tensorArray(options.requires_grad(true), - {0.5_r} /* v */, - {0.5_r} /* w */) /* east */, - iganet::utils::to_tensorArray(options.requires_grad(true), - {0.5_r} /* u */, - {0.5_r} /* w */) /* south */, - iganet::utils::to_tensorArray(options.requires_grad(true), - {0.5_r} /* u */, - {0.5_r} /* w */) /* north */, - iganet::utils::to_tensorArray(options.requires_grad(true), - {0.5_r} /* u */, - {0.5_r} /* v */) /* front */, - iganet::utils::to_tensorArray(options.requires_grad(true), - {0.5_r} /* u */, - {0.5_r} /* v */) /* back */}; - values = boundary.eval(xi); - - // Note that this check cannot be implemented using std::apply as - // this functions only accepts one tuple as argument whereas here - // both values and xi need to be passed - check_requires_grad( - std::make_index_sequence::nsides()>{}, values, - xi, options); - } -} - -TEST_F(BoundaryTest, Boundary_to_dtype) { - { - using iganet::side; - using BSpline = iganet::UniformBSpline; - iganet::Boundary boundary({5, 4, 7}, iganet::init::greville, - options); - - auto boundary_double = boundary.to(); - auto boundary_float = boundary.to(); - - if constexpr (std::is_same::value) - EXPECT_TRUE(boundary == boundary_double); - else - EXPECT_TRUE(boundary != boundary_double); - - if constexpr (std::is_same::value) - EXPECT_TRUE(boundary == boundary_float); - else - EXPECT_TRUE(boundary != boundary_float); - } - - { - using iganet::side; - using BSpline = iganet::UniformBSpline; - iganet::Boundary boundary({5, 4, 7}, iganet::init::greville, - options); - - auto boundary_double = boundary.to(iganet::Options{}); - auto boundary_float = boundary.to(iganet::Options{}); - - if constexpr (std::is_same::value) - EXPECT_TRUE(boundary == boundary_double); - else - EXPECT_TRUE(boundary != boundary_double); - - if constexpr (std::is_same::value) - EXPECT_TRUE(boundary == boundary_float); - else - EXPECT_TRUE(boundary != boundary_float); - } -} - -TEST_F(BoundaryTest, Boundary_to_device) { - { - using iganet::side; - using BSpline = iganet::UniformBSpline; - iganet::Options options = - iganet::Options{}.device(torch::kCPU); - iganet::Boundary boundary({5, 4, 7}, iganet::init::greville, - options); - - auto boundary_cpu = boundary.to(torch::kCPU); - EXPECT_TRUE(boundary == boundary_cpu); - - if (torch::cuda::is_available()) { - auto boundary_cuda = boundary.to(torch::kCUDA); - EXPECT_THROW((void)(boundary == boundary_cuda), c10::Error); - } else - EXPECT_THROW(boundary.to(torch::kCUDA), c10::Error); - - if (at::hasHIP()) { - auto boundary_hip = boundary.to(torch::kHIP); - EXPECT_THROW((void)(boundary == boundary_hip), c10::Error); - } else - EXPECT_THROW(boundary.to(torch::kHIP), c10::Error); - - if (at::hasMPS() && // will become torch::mps::is_available() - (options.dtype() != iganet::dtype())) { - auto boundary_mps = boundary.to(torch::kMPS); - EXPECT_THROW((void)(boundary == boundary_mps), c10::Error); - } else - EXPECT_THROW(boundary.to(torch::kMPS), c10::Error); - } -} - -int main(int argc, char **argv) { - ::testing::InitGoogleTest(&argc, argv); - iganet::init(); - return RUN_ALL_TESTS(); -} +/** + @file unittests/unittest_boundary.cxx + + @brief Boundary unittests + + @author Matthias Moller + + @copyright This file is part of the IgANet project + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#include +#include +#include + +#include +#include + +using namespace iganet::unittests::literals; + +class BoundaryTest : public ::testing::Test { +public: + BoundaryTest() { std::srand(std::time(nullptr)); } + +protected: + using real_t = iganet::unittests::real_t; + iganet::Options options; +}; + +TEST_F(BoundaryTest, Boundary_parDim1_geoDim1_degrees2) { + using iganet::deriv; + using iganet::side; + using BSpline = iganet::UniformBSpline; + iganet::Boundary boundary({0}, iganet::init::greville, options); + + auto xi = std::tuple{std::array{}, + std::array{}}; + + // Evaluation + auto values = boundary.eval(xi); + + EXPECT_TRUE(torch::equal(*std::get(values)[0], + torch::ones(1, options))); + EXPECT_TRUE(torch::equal(*std::get(values)[0], + torch::ones(1, options))); + + values = boundary.eval(xi); + + EXPECT_TRUE(torch::equal(*std::get(values)[0], + torch::zeros(1, options))); + EXPECT_TRUE(torch::equal(*std::get(values)[0], + torch::zeros(1, options))); + + values = boundary.eval(xi); + + EXPECT_TRUE(torch::equal(*std::get(values)[0], + torch::zeros(1, options))); + EXPECT_TRUE(torch::equal(*std::get(values)[0], + torch::zeros(1, options))); + + // Evaluation from precomputed coefficients and basis functions + auto knot_indices = boundary.find_knot_indices(xi); + auto coeff_indices = boundary.find_coeff_indices(knot_indices); + + auto numel = [](const auto &xi) { + return std::tuple{1, 1}; + }; + auto sizes = [](const auto &xi) { + return std::tuple{torch::IntArrayRef{}, torch::IntArrayRef{}}; + }; + + auto basfunc = + boundary.template eval_basfunc(xi, knot_indices); + values = boundary.eval_from_precomputed(basfunc, coeff_indices, numel(xi), + sizes(xi)); + + EXPECT_TRUE(torch::equal(*std::get(values)[0], + torch::ones({}, options))); + EXPECT_TRUE(torch::equal(*std::get(values)[0], + torch::ones({}, options))); + + basfunc = boundary.template eval_basfunc(xi, knot_indices); + values = boundary.eval_from_precomputed(basfunc, coeff_indices, numel(xi), + sizes(xi)); + + EXPECT_TRUE(torch::equal(*std::get(values)[0], + torch::zeros({}, options))); + EXPECT_TRUE(torch::equal(*std::get(values)[0], + torch::zeros({}, options))); + + basfunc = + boundary.template eval_basfunc(xi, knot_indices); + values = boundary.eval_from_precomputed(basfunc, coeff_indices, numel(xi), + sizes(xi)); + + EXPECT_TRUE(torch::equal(*std::get(values)[0], + torch::zeros({}, options))); + EXPECT_TRUE(torch::equal(*std::get(values)[0], + torch::zeros({}, options))); +} + +TEST_F(BoundaryTest, Boundary_parDim2_geoDim1_degrees23) { + using iganet::deriv; + using iganet::side; + using BSpline = iganet::UniformBSpline; + iganet::Boundary boundary({5, 4}, iganet::init::greville, options); + + auto xi = std::tuple{ + iganet::utils::to_tensorArray(options, {1.0_r, 0.2_r, 0.1_r, 0.5_r, 0.9_r, + 0.75_r, 0.0_r}) /* west */, + iganet::utils::to_tensorArray(options, {1.0_r, 0.2_r, 0.1_r, 0.5_r, 0.9_r, + 0.75_r, 0.0_r}) /* east */, + iganet::utils::to_tensorArray( + options, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}) /* south */, + iganet::utils::to_tensorArray( + options, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}) /* north */}; + + // Evaluation + auto values = boundary.eval(xi); + + iganet::UniformBSpline bspline_bdrNS( + {5}, iganet::init::greville, options); + iganet::UniformBSpline bspline_bdrEW( + {4}, iganet::init::greville, options); + + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + + values = boundary.eval(xi); + + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + + values = boundary.eval(xi); + + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + + values = boundary.eval(xi); + + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + + values = boundary.eval(xi); + + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + + values = boundary.eval(xi); + + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + + // Evaluation from precomputed coefficients and basis functions + auto knot_indices = boundary.find_knot_indices(xi); + auto coeff_indices = boundary.find_coeff_indices(knot_indices); + + auto numel = [](const auto &xi) { + return std::tuple{std::get<0>(xi)[0].numel(), std::get<1>(xi)[0].numel(), + std::get<2>(xi)[0].numel(), std::get<3>(xi)[0].numel()}; + }; + auto sizes = [](const auto &xi) { + return std::tuple{std::get<0>(xi)[0].sizes(), std::get<1>(xi)[0].sizes(), + std::get<2>(xi)[0].sizes(), std::get<3>(xi)[0].sizes()}; + }; + + auto basfunc = + boundary.template eval_basfunc(xi, knot_indices); + values = boundary.eval_from_precomputed(basfunc, coeff_indices, numel(xi), + sizes(xi)); + + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + + basfunc = boundary.template eval_basfunc(xi, knot_indices); + values = boundary.eval_from_precomputed(basfunc, coeff_indices, numel(xi), + sizes(xi)); + + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + + basfunc = + boundary.template eval_basfunc(xi, knot_indices); + values = boundary.eval_from_precomputed(basfunc, coeff_indices, numel(xi), + sizes(xi)); + + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + + basfunc = boundary.template eval_basfunc(xi, knot_indices); + values = boundary.eval_from_precomputed(basfunc, coeff_indices, numel(xi), + sizes(xi)); + + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + + basfunc = + boundary.template eval_basfunc(xi, knot_indices); + values = boundary.eval_from_precomputed(basfunc, coeff_indices, numel(xi), + sizes(xi)); + + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + + basfunc = boundary.template eval_basfunc( + xi, knot_indices); + values = boundary.eval_from_precomputed(basfunc, coeff_indices, numel(xi), + sizes(xi)); + + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); +} + +TEST_F(BoundaryTest, Boundary_parDim3_geoDim1_degrees234) { + using iganet::deriv; + using iganet::side; + using BSpline = iganet::UniformBSpline; + iganet::Boundary boundary({5, 4, 7}, iganet::init::greville, + options); + + auto xi = std::tuple{ + iganet::utils::to_tensorArray( + options, {1.0_r, 0.2_r, 0.1_r, 0.5_r, 0.9_r, 0.75_r, 0.0_r} /* v */, + {0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r, 0.0_r, + 0.1_r} /* w */) /* west */, + iganet::utils::to_tensorArray( + options, {1.0_r, 0.2_r, 0.1_r, 0.5_r, 0.9_r, 0.75_r, 0.0_r} /* v */, + {0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r, 0.0_r, + 0.1_r} /* w */) /* east */, + iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r} /* u */, + {0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r, 0.0_r, + 0.1_r} /* w */) /* south */, + iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r} /* u */, + {0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r, 0.0_r, + 0.1_r} /* w */) /* north */, + iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r} /* u */, + {1.0_r, 0.2_r, 0.1_r, 0.5_r, 0.9_r, 0.75_r, + 0.0_r} /* v */) /* front */, + iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r} /* u */, + {1.0_r, 0.2_r, 0.1_r, 0.5_r, 0.9_r, 0.75_r, + 0.0_r} /* v */) /* back */}; + + // Evaluation + auto values = boundary.eval(xi); + + iganet::UniformBSpline bspline_bdrNS( + {5, 7}, iganet::init::greville, options); + iganet::UniformBSpline bspline_bdrEW( + {4, 7}, iganet::init::greville, options); + iganet::UniformBSpline bspline_bdrFB( + {5, 4}, iganet::init::greville, options); + + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + + values = boundary.eval(xi); + + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + + values = boundary.eval(xi); + + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + + values = boundary.eval(xi); + + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + + values = boundary.eval(xi); + + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + + values = boundary.eval(xi); + + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + + values = boundary.eval(xi); + + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + + values = boundary.eval(xi); + + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + + values = boundary.eval(xi); + + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + + values = boundary.eval(xi); + + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + + // Evaluation from precomputed coefficients and basis functions + auto knot_indices = boundary.find_knot_indices(xi); + auto coeff_indices = boundary.find_coeff_indices(knot_indices); + + auto numel = [](const auto &xi) { + return std::tuple{std::get<0>(xi)[0].numel(), std::get<1>(xi)[0].numel(), + std::get<2>(xi)[0].numel(), std::get<3>(xi)[0].numel(), + std::get<4>(xi)[0].numel(), std::get<5>(xi)[0].numel()}; + }; + auto sizes = [](const auto &xi) { + return std::tuple{std::get<0>(xi)[0].sizes(), std::get<1>(xi)[0].sizes(), + std::get<2>(xi)[0].sizes(), std::get<3>(xi)[0].sizes(), + std::get<4>(xi)[0].sizes(), std::get<5>(xi)[0].sizes()}; + }; + + auto basfunc = + boundary.template eval_basfunc(xi, knot_indices); + values = boundary.eval_from_precomputed(basfunc, coeff_indices, numel(xi), + sizes(xi)); + + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + + basfunc = boundary.template eval_basfunc(xi, knot_indices); + values = boundary.eval_from_precomputed(basfunc, coeff_indices, numel(xi), + sizes(xi)); + + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + + basfunc = + boundary.template eval_basfunc(xi, knot_indices); + values = boundary.eval_from_precomputed(basfunc, coeff_indices, numel(xi), + sizes(xi)); + + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + + basfunc = boundary.template eval_basfunc(xi, knot_indices); + values = boundary.eval_from_precomputed(basfunc, coeff_indices, numel(xi), + sizes(xi)); + + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + + basfunc = + boundary.template eval_basfunc(xi, knot_indices); + values = boundary.eval_from_precomputed(basfunc, coeff_indices, numel(xi), + sizes(xi)); + + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + + basfunc = boundary.template eval_basfunc(xi, knot_indices); + values = boundary.eval_from_precomputed(basfunc, coeff_indices, numel(xi), + sizes(xi)); + + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + + basfunc = + boundary.template eval_basfunc(xi, knot_indices); + values = boundary.eval_from_precomputed(basfunc, coeff_indices, numel(xi), + sizes(xi)); + + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + + basfunc = boundary.template eval_basfunc( + xi, knot_indices); + values = boundary.eval_from_precomputed(basfunc, coeff_indices, numel(xi), + sizes(xi)); + + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + + basfunc = boundary.template eval_basfunc( + xi, knot_indices); + values = boundary.eval_from_precomputed(basfunc, coeff_indices, numel(xi), + sizes(xi)); + + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + + basfunc = boundary.template eval_basfunc( + xi, knot_indices); + values = boundary.eval_from_precomputed(basfunc, coeff_indices, numel(xi), + sizes(xi)); + + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(values)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); +} + +TEST_F(BoundaryTest, Boundary_init) { + { + using iganet::side; + using BSpline = iganet::UniformBSpline; + iganet::Boundary boundary({5, 4}, iganet::init::zeros, options); + + EXPECT_TRUE(torch::equal(boundary.side().coeffs(0), + torch::zeros(4, options))); + EXPECT_TRUE(torch::equal(boundary.side().coeffs(0), + torch::zeros(4, options))); + + EXPECT_TRUE(torch::equal(boundary.side().coeffs(0), + torch::zeros(5, options))); + EXPECT_TRUE(torch::equal(boundary.side().coeffs(0), + torch::zeros(5, options))); + } + + { + using iganet::side; + using BSpline = iganet::UniformBSpline; + iganet::Boundary boundary({5, 4}, iganet::init::ones, options); + + EXPECT_TRUE(torch::equal(boundary.side().coeffs(0), + torch::ones(4, options))); + EXPECT_TRUE(torch::equal(boundary.side().coeffs(0), + torch::ones(4, options))); + + EXPECT_TRUE(torch::equal(boundary.side().coeffs(0), + torch::ones(5, options))); + EXPECT_TRUE(torch::equal(boundary.side().coeffs(0), + torch::ones(5, options))); + } + + { + using iganet::side; + using BSpline = iganet::UniformBSpline; + iganet::Boundary boundary({5, 4}, iganet::init::linear, options); + + EXPECT_TRUE(torch::equal(boundary.side().coeffs(0), + torch::linspace(0, 1, 4, options))); + EXPECT_TRUE(torch::equal(boundary.side().coeffs(0), + torch::linspace(0, 1, 4, options))); + + EXPECT_TRUE(torch::equal(boundary.side().coeffs(0), + torch::linspace(0, 1, 5, options))); + EXPECT_TRUE(torch::equal(boundary.side().coeffs(0), + torch::linspace(0, 1, 5, options))); + } + + { + using iganet::side; + using BSpline = iganet::UniformBSpline; + iganet::Boundary boundary({5, 4}, iganet::init::greville, options); + + EXPECT_TRUE(torch::allclose(boundary.side().coeffs(0), + torch::linspace(0, 1, 4, options))); + EXPECT_TRUE(torch::allclose(boundary.side().coeffs(0), + torch::linspace(0, 1, 4, options))); + + EXPECT_TRUE(torch::allclose(boundary.side().coeffs(0), + torch::linspace(0, 1, 5, options))); + EXPECT_TRUE(torch::allclose(boundary.side().coeffs(0), + torch::linspace(0, 1, 5, options))); + } +} + +TEST_F(BoundaryTest, Boundary_refine) { + + // parDim == 2 + + { + using BSpline = iganet::UniformBSpline; + iganet::Boundary boundary({5, 4}, iganet::init::greville, options); + iganet::Boundary boundary_ref({8, 5}, iganet::init::greville, + options); + boundary.uniform_refine(); + EXPECT_TRUE(boundary.isclose(boundary_ref)); + } + + { + using BSpline = iganet::UniformBSpline; + iganet::Boundary boundary({5, 4}, iganet::init::greville, options); + iganet::Boundary boundary_ref({14, 7}, iganet::init::greville, + options); + boundary.uniform_refine(2); + EXPECT_TRUE(boundary.isclose(boundary_ref)); + } + + { + using BSpline = iganet::UniformBSpline; + iganet::Boundary boundary({5, 4}, iganet::init::greville, options); + iganet::Boundary boundary_ref({8, 4}, iganet::init::greville, + options); + boundary.uniform_refine(1, 0); + EXPECT_TRUE(boundary.isclose(boundary_ref)); + } + + { + using BSpline = iganet::UniformBSpline; + iganet::Boundary boundary({5, 4}, iganet::init::greville, options); + iganet::Boundary boundary_ref({5, 5}, iganet::init::greville, + options); + boundary.uniform_refine(1, 1); + EXPECT_TRUE(boundary.isclose(boundary_ref)); + } + + { + using BSpline = iganet::UniformBSpline; + iganet::Boundary boundary({5, 4}, iganet::init::greville, options); + EXPECT_THROW((boundary.uniform_refine(1, 2)), std::runtime_error); + EXPECT_THROW((boundary.uniform_refine(1, 3)), std::runtime_error); + EXPECT_THROW((boundary.uniform_refine(1, 4)), std::runtime_error); + } + + { + using BSpline = iganet::UniformBSpline; + iganet::Boundary boundary({5, 4}, iganet::init::greville, options); + iganet::Boundary boundary_ref({14, 5}, iganet::init::greville, + options); + boundary.uniform_refine(2, 0).uniform_refine(1, 1); + EXPECT_TRUE(boundary.isclose(boundary_ref)); + } + + // parDim == 3 + + { + using BSpline = iganet::UniformBSpline; + iganet::Boundary boundary({5, 4, 7}, iganet::init::greville, + options); + iganet::Boundary boundary_ref({8, 5, 10}, iganet::init::greville, + options); + boundary.uniform_refine(); + EXPECT_TRUE(boundary.isclose(boundary_ref)); + } + + { + using BSpline = iganet::UniformBSpline; + iganet::Boundary boundary({5, 4, 7}, iganet::init::greville, + options); + iganet::Boundary boundary_ref({14, 7, 16}, iganet::init::greville, + options); + boundary.uniform_refine(2); + EXPECT_TRUE(boundary.isclose(boundary_ref)); + } + + { + using BSpline = iganet::UniformBSpline; + iganet::Boundary boundary({5, 4, 7}, iganet::init::greville, + options); + iganet::Boundary boundary_ref({8, 4, 7}, iganet::init::greville, + options); + boundary.uniform_refine(1, 0); + EXPECT_TRUE(boundary.isclose(boundary_ref)); + } + + { + using BSpline = iganet::UniformBSpline; + iganet::Boundary boundary({5, 4, 7}, iganet::init::greville, + options); + iganet::Boundary boundary_ref({5, 5, 7}, iganet::init::greville, + options); + boundary.uniform_refine(1, 1); + EXPECT_TRUE(boundary.isclose(boundary_ref)); + } + + { + using BSpline = iganet::UniformBSpline; + iganet::Boundary boundary({5, 4, 7}, iganet::init::greville, + options); + iganet::Boundary boundary_ref({5, 4, 10}, iganet::init::greville, + options); + boundary.uniform_refine(1, 2); + EXPECT_TRUE(boundary.isclose(boundary_ref)); + } + + { + using BSpline = iganet::UniformBSpline; + iganet::Boundary boundary({5, 4, 7}, iganet::init::greville, + options); + iganet::Boundary boundary_ref({5, 4, 10}, iganet::init::greville, + options); + EXPECT_THROW((boundary.uniform_refine(1, 3)), std::runtime_error); + EXPECT_THROW((boundary.uniform_refine(1, 4)), std::runtime_error); + } + + { + using BSpline = iganet::UniformBSpline; + iganet::Boundary boundary({5, 4, 7}, iganet::init::greville, + options); + iganet::Boundary boundary_ref({8, 7, 10}, iganet::init::greville, + options); + boundary.uniform_refine(1, 0).uniform_refine(2, 1).uniform_refine(1, 2); + EXPECT_TRUE(boundary.isclose(boundary_ref)); + } + + // parDim == 4 + + { + using BSpline = iganet::UniformBSpline; + iganet::Boundary boundary({5, 4, 7, 8}, iganet::init::greville, + options); + iganet::Boundary boundary_ref({8, 5, 10, 11}, + iganet::init::greville, options); + boundary.uniform_refine(); + EXPECT_TRUE(boundary.isclose(boundary_ref)); + } + + { + using BSpline = iganet::UniformBSpline; + iganet::Boundary boundary({5, 4, 7, 8}, iganet::init::greville, + options); + iganet::Boundary boundary_ref({14, 7, 16, 17}, + iganet::init::greville, options); + boundary.uniform_refine(2); + EXPECT_TRUE(boundary.isclose(boundary_ref)); + } + + { + using BSpline = iganet::UniformBSpline; + iganet::Boundary boundary({5, 4, 7, 8}, iganet::init::greville, + options); + iganet::Boundary boundary_ref({8, 4, 7, 8}, iganet::init::greville, + options); + boundary.uniform_refine(1, 0); + EXPECT_TRUE(boundary.isclose(boundary_ref)); + } + + { + using BSpline = iganet::UniformBSpline; + iganet::Boundary boundary({5, 4, 7, 8}, iganet::init::greville, + options); + iganet::Boundary boundary_ref({5, 5, 7, 8}, iganet::init::greville, + options); + boundary.uniform_refine(1, 1); + EXPECT_TRUE(boundary.isclose(boundary_ref)); + } + + { + using BSpline = iganet::UniformBSpline; + iganet::Boundary boundary({5, 4, 7, 8}, iganet::init::greville, + options); + iganet::Boundary boundary_ref({5, 4, 10, 8}, + iganet::init::greville, options); + boundary.uniform_refine(1, 2); + EXPECT_TRUE(boundary.isclose(boundary_ref)); + } + + { + using BSpline = iganet::UniformBSpline; + iganet::Boundary boundary({5, 4, 7, 8}, iganet::init::greville, + options); + iganet::Boundary boundary_ref({5, 4, 7, 11}, + iganet::init::greville, options); + boundary.uniform_refine(1, 3); + EXPECT_TRUE(boundary.isclose(boundary_ref)); + } + + { + using BSpline = iganet::UniformBSpline; + iganet::Boundary boundary({5, 4, 7, 8}, iganet::init::greville, + options); + EXPECT_THROW((boundary.uniform_refine(1, 4)), std::runtime_error); + } + + { + using BSpline = iganet::UniformBSpline; + iganet::Boundary boundary({5, 4, 7, 8}, iganet::init::greville, + options); + iganet::Boundary boundary_ref({8, 7, 10, 17}, + iganet::init::greville, options); + boundary.uniform_refine(1, 0) + .uniform_refine(2, 1) + .uniform_refine(1, 2) + .uniform_refine(2, 3); + EXPECT_TRUE(boundary.isclose(boundary_ref)); + } +} + +TEST_F(BoundaryTest, Boundary_copy_constructor) { + using BSpline = iganet::UniformBSpline; + iganet::Boundary boundary_orig({5, 4}, iganet::init::greville, + options); + iganet::Boundary boundary_copy(boundary_orig); + + boundary_orig.side().transform( + [](const std::array xi) { + return std::array{0.0_r}; + }); + + EXPECT_TRUE(boundary_orig == boundary_copy); +} + +TEST_F(BoundaryTest, Boundary_clone_constructor) { + using BSpline = iganet::UniformBSpline; + iganet::Boundary boundary_ref({5, 4}, iganet::init::greville, + options); + iganet::Boundary boundary_orig({5, 4}, iganet::init::greville, + options); + iganet::Boundary boundary_clone(boundary_orig, true); + + boundary_orig.side().transform( + [](const std::array xi) { + return std::array{0.0_r}; + }); + + EXPECT_TRUE(boundary_ref == boundary_clone); +} + +TEST_F(BoundaryTest, Boundary_move_constructor) { + using BSpline = iganet::UniformBSpline; + iganet::Boundary boundary_ref({14, 7}, iganet::init::greville, + options); + auto boundary( + iganet::Boundary({5, 4}, iganet::init::greville, options) + .uniform_refine(2)); + + EXPECT_TRUE(boundary.isclose(boundary_ref)); +} + +TEST_F(BoundaryTest, Boundary_read_write) { + std::filesystem::path filename = + std::filesystem::temp_directory_path() / std::to_string(rand()); + using BSpline = iganet::UniformBSpline; + iganet::Boundary boundary_out({5, 4}, iganet::init::greville, + options); + boundary_out.save(filename.c_str()); + + iganet::Boundary boundary_in(options); + boundary_in.load(filename.c_str()); + std::filesystem::remove(filename); + + EXPECT_TRUE(boundary_in == boundary_out); + EXPECT_FALSE(boundary_in != boundary_out); +} + +TEST_F(BoundaryTest, Boundary_to_from_xml) { + { + using BSpline = iganet::UniformBSpline; + iganet::Boundary boundary_out({5}, iganet::init::greville, + options); + + boundary_out.side().transform( + [](const std::array) { + return std::array{static_cast(std::rand()), + static_cast(std::rand()), + static_cast(std::rand()), + static_cast(std::rand())}; + }); + + boundary_out.side().transform( + [](const std::array) { + return std::array{static_cast(std::rand()), + static_cast(std::rand()), + static_cast(std::rand()), + static_cast(std::rand())}; + }); + + pugi::xml_document doc = boundary_out.to_xml(); + + iganet::Boundary boundary_in(options); + boundary_in.from_xml(doc); + + EXPECT_TRUE(boundary_in.isclose(boundary_out)); + + // non-matching degree + EXPECT_THROW( + (iganet::Boundary>{}.from_xml(doc, + 0)), + std::runtime_error); // XML object provides too many coefficients + EXPECT_THROW( + (iganet::Boundary>{}.from_xml(doc, + 0)), + std::runtime_error); // XML object provides too many coefficients + EXPECT_THROW( + (iganet::Boundary>{}.from_xml(doc, + 0)), + std::runtime_error); // XML object provides too few coefficients + + // non-matching parametric dimension + EXPECT_THROW( + (iganet::Boundary>{}.from_xml( + doc, 0)), + std::runtime_error); + EXPECT_THROW((iganet::Boundary>{} + .from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::Boundary>{} + .from_xml(doc, 0)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW( + (iganet::Boundary>{}.from_xml(doc, + 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::Boundary>{}.from_xml(doc, + 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::Boundary>{}.from_xml(doc, + 0)), + std::runtime_error); + + // non-matching id + EXPECT_THROW((iganet::Boundary{}.from_xml(doc, 1)), + std::runtime_error); + } + + { + using BSpline = iganet::UniformBSpline; + iganet::Boundary boundary_out({5, 4}, iganet::init::greville, + options); + + boundary_out.side().transform( + [](const std::array xi) { + return std::array{static_cast(std::rand())}; + }); + + boundary_out.side().transform( + [](const std::array xi) { + return std::array{static_cast(std::rand())}; + }); + + boundary_out.side().transform( + [](const std::array xi) { + return std::array{static_cast(std::rand())}; + }); + + boundary_out.side().transform( + [](const std::array xi) { + return std::array{static_cast(std::rand())}; + }); + + pugi::xml_document doc = boundary_out.to_xml(); + + iganet::Boundary boundary_in(options); + boundary_in.from_xml(doc); + + EXPECT_TRUE(boundary_in.isclose(boundary_out)); + + // non-matching degree + EXPECT_THROW( + (iganet::Boundary>{}.from_xml( + doc, 0)), + std::runtime_error); // XML object provides too many coefficients + EXPECT_THROW( + (iganet::Boundary>{}.from_xml( + doc, 0)), + std::runtime_error); // XML object provides too many coefficients + EXPECT_THROW( + (iganet::Boundary>{}.from_xml( + doc, 0)), + std::runtime_error); // XML object provides too few coefficients + EXPECT_THROW( + (iganet::Boundary>{}.from_xml( + doc, 0)), + std::runtime_error); // XML object provides too few coefficients + + // non-matching parametric dimension + EXPECT_THROW( + (iganet::Boundary>{}.from_xml(doc, + 0)), + std::runtime_error); + EXPECT_THROW((iganet::Boundary>{} + .from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::Boundary>{} + .from_xml(doc, 0)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW( + (iganet::Boundary>{}.from_xml( + doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::Boundary>{}.from_xml( + doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::Boundary>{}.from_xml( + doc, 0)), + std::runtime_error); + + // non-matching id + EXPECT_THROW((iganet::Boundary{}.from_xml(doc, 1)), + std::runtime_error); + } + + { + using BSpline = iganet::UniformBSpline; + iganet::Boundary boundary_out({5, 4, 5}, iganet::init::greville, + options); + + boundary_out.side().transform( + [](const std::array xi) { + return std::array{static_cast(std::rand()), + static_cast(std::rand()), + static_cast(std::rand())}; + }); + + boundary_out.side().transform( + [](const std::array xi) { + return std::array{static_cast(std::rand()), + static_cast(std::rand()), + static_cast(std::rand())}; + }); + + boundary_out.side().transform( + [](const std::array xi) { + return std::array{static_cast(std::rand()), + static_cast(std::rand()), + static_cast(std::rand())}; + }); + + boundary_out.side().transform( + [](const std::array xi) { + return std::array{static_cast(std::rand()), + static_cast(std::rand()), + static_cast(std::rand())}; + }); + + boundary_out.side().transform( + [](const std::array xi) { + return std::array{static_cast(std::rand()), + static_cast(std::rand()), + static_cast(std::rand())}; + }); + + boundary_out.side().transform( + [](const std::array xi) { + return std::array{static_cast(std::rand()), + static_cast(std::rand()), + static_cast(std::rand())}; + }); + + pugi::xml_document doc = boundary_out.to_xml(); + + iganet::Boundary boundary_in(options); + boundary_in.from_xml(doc); + + EXPECT_TRUE(boundary_in.isclose(boundary_out)); + + // non-matching degree + EXPECT_THROW((iganet::Boundary>{} + .from_xml(doc, 0)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW( + (iganet::Boundary>{}.from_xml(doc, + 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::Boundary>{}.from_xml( + doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::Boundary>{} + .from_xml(doc, 0)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW((iganet::Boundary>{} + .from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW((iganet::Boundary>{} + .from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW((iganet::Boundary>{} + .from_xml(doc, 0)), + std::runtime_error); + + // non-matching id + EXPECT_THROW((iganet::Boundary{}.from_xml(doc, 1)), + std::runtime_error); + } + + { + using BSpline = iganet::UniformBSpline; + iganet::Boundary boundary_out({5, 4, 5, 6}, iganet::init::greville, + options); + + boundary_out.side().transform( + [](const std::array xi) { + return std::array{static_cast(std::rand()), + static_cast(std::rand())}; + }); + + boundary_out.side().transform( + [](const std::array xi) { + return std::array{static_cast(std::rand()), + static_cast(std::rand())}; + }); + + boundary_out.side().transform( + [](const std::array xi) { + return std::array{static_cast(std::rand()), + static_cast(std::rand())}; + }); + + boundary_out.side().transform( + [](const std::array xi) { + return std::array{static_cast(std::rand()), + static_cast(std::rand())}; + }); + + boundary_out.side().transform( + [](const std::array xi) { + return std::array{static_cast(std::rand()), + static_cast(std::rand())}; + }); + + boundary_out.side().transform( + [](const std::array xi) { + return std::array{static_cast(std::rand()), + static_cast(std::rand())}; + }); + + boundary_out.side().transform( + [](const std::array xi) { + return std::array{static_cast(std::rand()), + static_cast(std::rand())}; + }); + + boundary_out.side().transform( + [](const std::array xi) { + return std::array{static_cast(std::rand()), + static_cast(std::rand())}; + }); + + pugi::xml_document doc = boundary_out.to_xml(); + + iganet::Boundary boundary_in(options); + boundary_in.from_xml(doc); + + EXPECT_TRUE(boundary_in.isclose(boundary_out)); + + // non-matching degree + EXPECT_THROW( + (iganet::Boundary>{} + .from_xml(doc, 0)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW( + (iganet::Boundary>{}.from_xml(doc, + 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::Boundary>{}.from_xml( + doc, 0)), + std::runtime_error); + EXPECT_THROW((iganet::Boundary>{} + .from_xml(doc, 0)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW( + (iganet::Boundary>{} + .from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::Boundary>{} + .from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::Boundary>{} + .from_xml(doc, 0)), + std::runtime_error); + + // non-matching id + EXPECT_THROW((iganet::Boundary{}.from_xml(doc, 1)), + std::runtime_error); + } +} + +TEST_F(BoundaryTest, Boundary_load_from_xml) { + { + pugi::xml_document doc; + pugi::xml_parse_result result = + doc.load_file(IGANET_DATA_DIR "domain1d/line_boundary.xml"); + + using BSpline = iganet::UniformBSpline; + iganet::Boundary boundary_in(options); + boundary_in.from_xml(doc); + + iganet::Boundary boundary_ref({5}, iganet::init::greville, + options); + + boundary_ref.side().transform( + [](const std::array) { + return std::array{ + static_cast(1.0), static_cast(2.0), + static_cast(3.0), static_cast(4.0)}; + }); + + boundary_ref.side().transform( + [](const std::array) { + return std::array{ + static_cast(-1.0), static_cast(-2.0), + static_cast(-3.0), static_cast(-4.0)}; + }); + + EXPECT_TRUE(boundary_in.isclose(boundary_ref)); + } + + { + pugi::xml_document doc; + pugi::xml_parse_result result = + doc.load_file(IGANET_DATA_DIR "domain2d/square_boundary.xml"); + + using BSpline = iganet::UniformBSpline; + iganet::Boundary boundary_in(options); + boundary_in.from_xml(doc); + + iganet::Boundary boundary_ref({5, 4}, iganet::init::greville, + options); + + boundary_ref.side().transform( + [](const std::array xi) { + return std::array{xi[0]}; + }); + + boundary_ref.side().transform( + [](const std::array xi) { + return std::array{xi[0] + 10}; + }); + + boundary_ref.side().transform( + [](const std::array xi) { + return std::array{xi[0] + 20}; + }); + + boundary_ref.side().transform( + [](const std::array xi) { + return std::array{xi[0] + 30}; + }); + + EXPECT_TRUE(boundary_in.isclose(boundary_ref)); + } + + { + pugi::xml_document doc; + pugi::xml_parse_result result = + doc.load_file(IGANET_DATA_DIR "domain3d/cube_boundary.xml"); + + using BSpline = iganet::UniformBSpline; + iganet::Boundary boundary_in(options); + boundary_in.from_xml(doc); + + iganet::Boundary boundary_ref({5, 4, 5}, iganet::init::greville, + options); + + boundary_ref.side().transform( + [](const std::array xi) { + return std::array{xi[0] + xi[1] + 1, xi[0] + xi[1] + 2, + xi[0] + xi[1] + 3}; + }); + + boundary_ref.side().transform( + [](const std::array xi) { + return std::array{xi[0] + xi[1] + 11, xi[0] + xi[1] + 12, + xi[0] + xi[1] + 13}; + }); + + boundary_ref.side().transform( + [](const std::array xi) { + return std::array{xi[0] + xi[1] + 21, xi[0] + xi[1] + 22, + xi[0] + xi[1] + 23}; + }); + + boundary_ref.side().transform( + [](const std::array xi) { + return std::array{xi[0] + xi[1] + 31, xi[0] + xi[1] + 32, + xi[0] + xi[1] + 33}; + }); + + boundary_ref.side().transform( + [](const std::array xi) { + return std::array{xi[0] + xi[1] + 41, xi[0] + xi[1] + 42, + xi[0] + xi[1] + 43}; + }); + + boundary_ref.side().transform( + [](const std::array xi) { + return std::array{xi[0] + xi[1] + 51, xi[0] + xi[1] + 52, + xi[0] + xi[1] + 53}; + }); + + EXPECT_TRUE(boundary_in.isclose(boundary_ref)); + } + + { + pugi::xml_document doc; + pugi::xml_parse_result result = + doc.load_file(IGANET_DATA_DIR "domain4d/hypercube_boundary.xml"); + + using BSpline = iganet::UniformBSpline; + iganet::Boundary boundary_in(options); + boundary_in.from_xml(doc); + + iganet::Boundary boundary_ref({5, 4, 5, 6}, iganet::init::greville, + options); + + boundary_ref.side().transform( + [](const std::array xi) { + return std::array{xi[0] + xi[1] + xi[2] + 1, + xi[0] + xi[1] + xi[2] + 2}; + }); + + boundary_ref.side().transform( + [](const std::array xi) { + return std::array{xi[0] + xi[1] + xi[2] + 11, + xi[0] + xi[1] + xi[2] + 12}; + }); + + boundary_ref.side().transform( + [](const std::array xi) { + return std::array{xi[0] + xi[1] + xi[2] + 21, + xi[0] + xi[1] + xi[2] + 22}; + }); + + boundary_ref.side().transform( + [](const std::array xi) { + return std::array{xi[0] + xi[1] + xi[2] + 31, + xi[0] + xi[1] + xi[2] + 32}; + }); + + boundary_ref.side().transform( + [](const std::array xi) { + return std::array{xi[0] + xi[1] + xi[2] + 41, + xi[0] + xi[1] + xi[2] + 42}; + }); + + boundary_ref.side().transform( + [](const std::array xi) { + return std::array{xi[0] + xi[1] + xi[2] + 51, + xi[0] + xi[1] + xi[2] + 52}; + }); + + boundary_ref.side().transform( + [](const std::array xi) { + return std::array{xi[0] + xi[1] + xi[2] + 61, + xi[0] + xi[1] + xi[2] + 62}; + }); + + boundary_ref.side().transform( + [](const std::array xi) { + return std::array{xi[0] + xi[1] + xi[2] + 71, + xi[0] + xi[1] + xi[2] + 72}; + }); + + EXPECT_TRUE(boundary_in.isclose(boundary_ref)); + } +} + +TEST_F(BoundaryTest, Boundary_to_from_json) { + { + using BSpline = iganet::UniformBSpline; + iganet::Boundary boundary_out({5}, iganet::init::greville, + options); + + boundary_out.side().transform( + [](const std::array) { + return std::array{static_cast(std::rand()), + static_cast(std::rand()), + static_cast(std::rand()), + static_cast(std::rand())}; + }); + + boundary_out.side().transform( + [](const std::array) { + return std::array{static_cast(std::rand()), + static_cast(std::rand()), + static_cast(std::rand()), + static_cast(std::rand())}; + }); + + nlohmann::json json = boundary_out.to_json(); + + iganet::Boundary boundary_in(options); + boundary_in.from_json(json); + + EXPECT_TRUE(boundary_in.isclose(boundary_out)); + } + + { + using BSpline = iganet::UniformBSpline; + iganet::Boundary boundary_out({5, 4}, iganet::init::greville, + options); + + boundary_out.side().transform( + [](const std::array xi) { + return std::array{static_cast(std::rand())}; + }); + + boundary_out.side().transform( + [](const std::array xi) { + return std::array{static_cast(std::rand())}; + }); + + boundary_out.side().transform( + [](const std::array xi) { + return std::array{static_cast(std::rand())}; + }); + + boundary_out.side().transform( + [](const std::array xi) { + return std::array{static_cast(std::rand())}; + }); + + nlohmann::json json = boundary_out.to_json(); + + iganet::Boundary boundary_in(options); + boundary_in.from_json(json); + + EXPECT_TRUE(boundary_in.isclose(boundary_out)); + } + + { + using BSpline = iganet::UniformBSpline; + iganet::Boundary boundary_out({5, 4, 5}, iganet::init::greville, + options); + + boundary_out.side().transform( + [](const std::array xi) { + return std::array{static_cast(std::rand()), + static_cast(std::rand()), + static_cast(std::rand())}; + }); + + boundary_out.side().transform( + [](const std::array xi) { + return std::array{static_cast(std::rand()), + static_cast(std::rand()), + static_cast(std::rand())}; + }); + + boundary_out.side().transform( + [](const std::array xi) { + return std::array{static_cast(std::rand()), + static_cast(std::rand()), + static_cast(std::rand())}; + }); + + boundary_out.side().transform( + [](const std::array xi) { + return std::array{static_cast(std::rand()), + static_cast(std::rand()), + static_cast(std::rand())}; + }); + + boundary_out.side().transform( + [](const std::array xi) { + return std::array{static_cast(std::rand()), + static_cast(std::rand()), + static_cast(std::rand())}; + }); + + boundary_out.side().transform( + [](const std::array xi) { + return std::array{static_cast(std::rand()), + static_cast(std::rand()), + static_cast(std::rand())}; + }); + + nlohmann::json json = boundary_out.to_json(); + + iganet::Boundary boundary_in(options); + boundary_in.from_json(json); + + EXPECT_TRUE(boundary_in.isclose(boundary_out)); + } + + { + using BSpline = iganet::UniformBSpline; + iganet::Boundary boundary_out({5, 4, 5, 6}, iganet::init::greville, + options); + + boundary_out.side().transform( + [](const std::array xi) { + return std::array{static_cast(std::rand()), + static_cast(std::rand())}; + }); + + boundary_out.side().transform( + [](const std::array xi) { + return std::array{static_cast(std::rand()), + static_cast(std::rand())}; + }); + + boundary_out.side().transform( + [](const std::array xi) { + return std::array{static_cast(std::rand()), + static_cast(std::rand())}; + }); + + boundary_out.side().transform( + [](const std::array xi) { + return std::array{static_cast(std::rand()), + static_cast(std::rand())}; + }); + + boundary_out.side().transform( + [](const std::array xi) { + return std::array{static_cast(std::rand()), + static_cast(std::rand())}; + }); + + boundary_out.side().transform( + [](const std::array xi) { + return std::array{static_cast(std::rand()), + static_cast(std::rand())}; + }); + + boundary_out.side().transform( + [](const std::array xi) { + return std::array{static_cast(std::rand()), + static_cast(std::rand())}; + }); + + boundary_out.side().transform( + [](const std::array xi) { + return std::array{static_cast(std::rand()), + static_cast(std::rand())}; + }); + + nlohmann::json json = boundary_out.to_json(); + + iganet::Boundary boundary_in(options); + boundary_in.from_json(json); + + EXPECT_TRUE(boundary_in.isclose(boundary_out)); + } +} + +TEST_F(BoundaryTest, Boundary_query_property) { + using BSpline = iganet::UniformBSpline; + iganet::Boundary boundary({5, 4, 7}, iganet::init::greville, + options); + + EXPECT_TRUE( + std::apply([this](auto... is_uniform) { return (is_uniform && ...); }, + boundary.is_uniform())); + EXPECT_FALSE(std::apply( + [this](auto... is_nonuniform) { return (is_nonuniform || ...); }, + boundary.is_nonuniform())); + + EXPECT_TRUE(std::apply( + [this](auto... device) { return ((device == options.device()) && ...); }, + boundary.device())); + EXPECT_TRUE(std::apply( + [this](auto... device_index) { + return ((device_index == options.device_index()) && ...); + }, + boundary.device_index())); + EXPECT_TRUE(std::apply( + [this](auto... dtype) { return ((dtype == options.dtype()) && ...); }, + boundary.dtype())); + EXPECT_TRUE(std::apply( + [this](auto... is_sparse) { + return ((is_sparse == options.is_sparse()) && ...); + }, + boundary.is_sparse())); + EXPECT_TRUE(std::apply( + [this](auto... layout) { return ((layout == options.layout()) && ...); }, + boundary.layout())); + EXPECT_TRUE(std::apply( + [this](auto... pinned_memory) { + return ((pinned_memory == options.pinned_memory()) && ...); + }, + boundary.pinned_memory())); +} + +template +inline void +check_requires_grad(std::index_sequence, const Values &values, + const Xi &xi, + const iganet::Options &options) { + auto check = [&options](const auto &values, const auto &xi) { + values[0]->operator[](0).backward(); + EXPECT_TRUE(torch::allclose(xi[0].grad(), + iganet::utils::to_tensor({1.0_r}, options))); + }; + + (check(std::get(values), std::get(xi)), ...); +} + +template +inline void check_requires_grad_throw( + std::index_sequence, const Values &values, const Xi &xi, + const iganet::Options &options) { + auto check = [&options](const auto &values, const auto &xi) { + values[0]->operator[](0).backward( + {}, true); // otherwise we cannot run backward() a second time + EXPECT_THROW(torch::allclose(xi[0].grad(), torch::empty({})), c10::Error); + }; + + (check(std::get(values), std::get(xi)), ...); +} + +TEST_F(BoundaryTest, Boundary_requires_grad) { + { + using iganet::side; + using BSpline = iganet::UniformBSpline; + iganet::Boundary boundary({5, 4, 7}, iganet::init::greville, + options); + + EXPECT_FALSE( + std::apply([](auto... requires_grad) { return (requires_grad || ...); }, + boundary.requires_grad())); + + auto xi = std::tuple{ + iganet::utils::to_tensorArray(options, {0.5_r} /* v */, {0.5_r} /* w */ + ) /* west */, + iganet::utils::to_tensorArray(options, {0.5_r} /* v */, {0.5_r} /* w */ + ) /* east */, + iganet::utils::to_tensorArray(options, {0.5_r} /* u */, {0.5_r} /* w */ + ) /* south */, + iganet::utils::to_tensorArray(options, {0.5_r} /* u */, {0.5_r} /* w */ + ) /* north */, + iganet::utils::to_tensorArray(options, {0.5_r} /* u */, {0.5_r} /* v */ + ) /* front */, + iganet::utils::to_tensorArray(options, {0.5_r} /* u */, {0.5_r} /* v */ + ) /* back */}; + auto values = boundary.eval(xi); + + // We expect an error when calling backward() because no tensor + // has requires_grad = true + std::apply( + [](const auto &...values) { + auto check = [](const auto &values) { + EXPECT_THROW(values[0]->backward(), c10::Error); + }; + (check(values), ...); + }, + values); + + xi = std::tuple{iganet::utils::to_tensorArray(options.requires_grad(true), + {0.5_r} /* v */, + {0.5_r} /* w */) /* west */, + iganet::utils::to_tensorArray(options.requires_grad(true), + {0.5_r} /* v */, + {0.5_r} /* w */) /* east */, + iganet::utils::to_tensorArray(options.requires_grad(true), + {0.5_r} /* u */, + {0.5_r} /* w */) /* south */, + iganet::utils::to_tensorArray(options.requires_grad(true), + {0.5_r} /* u */, + {0.5_r} /* w */) /* north */, + iganet::utils::to_tensorArray(options.requires_grad(true), + {0.5_r} /* u */, + {0.5_r} /* v */) /* front */, + iganet::utils::to_tensorArray(options.requires_grad(true), + {0.5_r} /* u */, + {0.5_r} /* v */) /* back */}; + values = boundary.eval(xi); + + // Note that this check cannot be implemented using std::apply as + // this functions only accepts one tuple as argument whereas here + // both values and xi need to be passed + check_requires_grad( + std::make_index_sequence::nsides()>{}, values, + xi, options); + } + + { + using iganet::side; + using BSpline = iganet::UniformBSpline; + iganet::Boundary boundary({5, 4, 7}, iganet::init::greville, + options.requires_grad(true)); + + EXPECT_TRUE( + std::apply([](auto... requires_grad) { return (requires_grad && ...); }, + boundary.requires_grad())); + + auto xi = + std::tuple{iganet::utils::to_tensorArray(options, {0.5_r} /* v */, + {0.5_r} /* w */) /* west */, + iganet::utils::to_tensorArray(options, {0.5_r} /* v */, + {0.5_r} /* w */) /* east */, + iganet::utils::to_tensorArray(options, {0.5_r} /* u */, + {0.5_r} /* w */) /* south */, + iganet::utils::to_tensorArray(options, {0.5_r} /* u */, + {0.5_r} /* w */) /* north */, + iganet::utils::to_tensorArray(options, {0.5_r} /* u */, + {0.5_r} /* v */) /* front */, + iganet::utils::to_tensorArray(options, {0.5_r} /* u */, + {0.5_r} /* v */) /* back */}; + auto values = boundary.eval(xi); + + // We expect an error because xi[0].grad() is an undefined tensor + // + // Note that this check cannot be implemented using std::apply as + // this functions only accepts one tuple as argument whereas here + // both values and xi need to be passed + check_requires_grad_throw( + std::make_index_sequence::nsides()>{}, values, + xi, options); + + xi = std::tuple{iganet::utils::to_tensorArray(options.requires_grad(true), + {0.5_r} /* v */, + {0.5_r} /* w */) /* west */, + iganet::utils::to_tensorArray(options.requires_grad(true), + {0.5_r} /* v */, + {0.5_r} /* w */) /* east */, + iganet::utils::to_tensorArray(options.requires_grad(true), + {0.5_r} /* u */, + {0.5_r} /* w */) /* south */, + iganet::utils::to_tensorArray(options.requires_grad(true), + {0.5_r} /* u */, + {0.5_r} /* w */) /* north */, + iganet::utils::to_tensorArray(options.requires_grad(true), + {0.5_r} /* u */, + {0.5_r} /* v */) /* front */, + iganet::utils::to_tensorArray(options.requires_grad(true), + {0.5_r} /* u */, + {0.5_r} /* v */) /* back */}; + values = boundary.eval(xi); + + // Note that this check cannot be implemented using std::apply as + // this functions only accepts one tuple as argument whereas here + // both values and xi need to be passed + check_requires_grad( + std::make_index_sequence::nsides()>{}, values, + xi, options); + } +} + +TEST_F(BoundaryTest, Boundary_to_dtype) { + { + using iganet::side; + using BSpline = iganet::UniformBSpline; + iganet::Boundary boundary({5, 4, 7}, iganet::init::greville, + options); + + auto boundary_double = boundary.to(); + auto boundary_float = boundary.to(); + + if constexpr (std::is_same::value) + EXPECT_TRUE(boundary == boundary_double); + else + EXPECT_TRUE(boundary != boundary_double); + + if constexpr (std::is_same::value) + EXPECT_TRUE(boundary == boundary_float); + else + EXPECT_TRUE(boundary != boundary_float); + } + + { + using iganet::side; + using BSpline = iganet::UniformBSpline; + iganet::Boundary boundary({5, 4, 7}, iganet::init::greville, + options); + + auto boundary_double = boundary.to(iganet::Options{}); + auto boundary_float = boundary.to(iganet::Options{}); + + if constexpr (std::is_same::value) + EXPECT_TRUE(boundary == boundary_double); + else + EXPECT_TRUE(boundary != boundary_double); + + if constexpr (std::is_same::value) + EXPECT_TRUE(boundary == boundary_float); + else + EXPECT_TRUE(boundary != boundary_float); + } +} + +TEST_F(BoundaryTest, Boundary_to_device) { + { + using iganet::side; + using BSpline = iganet::UniformBSpline; + iganet::Options options = + iganet::Options{}.device(torch::kCPU); + iganet::Boundary boundary({5, 4, 7}, iganet::init::greville, + options); + + auto boundary_cpu = boundary.to(torch::kCPU); + EXPECT_TRUE(boundary == boundary_cpu); + + if (torch::cuda::is_available()) { + auto boundary_cuda = boundary.to(torch::kCUDA); + EXPECT_THROW((void)(boundary == boundary_cuda), c10::Error); + } else + EXPECT_THROW(boundary.to(torch::kCUDA), c10::Error); + + if (at::hasHIP()) { + auto boundary_hip = boundary.to(torch::kHIP); + EXPECT_THROW((void)(boundary == boundary_hip), c10::Error); + } else + EXPECT_THROW(boundary.to(torch::kHIP), c10::Error); + + if (at::hasMPS() && // will become torch::mps::is_available() + (options.dtype() != iganet::dtype())) { + auto boundary_mps = boundary.to(torch::kMPS); + EXPECT_THROW((void)(boundary == boundary_mps), c10::Error); + } else + EXPECT_THROW(boundary.to(torch::kMPS), c10::Error); + } +} + +int main(int argc, char **argv) { + ::testing::InitGoogleTest(&argc, argv); + iganet::init(); + return RUN_ALL_TESTS(); +} diff --git a/unittests/unittest_functionspace.cxx b/unittests/unittest_functionspace.cxx index 5e61be1d..11d4c8bb 100644 --- a/unittests/unittest_functionspace.cxx +++ b/unittests/unittest_functionspace.cxx @@ -1,4782 +1,4782 @@ -/** - @file unittests/unittest_functionspace.cxx - - @brief Function space unittests - - @author Matthias Moller - - @copyright This file is part of the IgANet project - - This Source Code Form is subject to the terms of the Mozilla Public - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at http://mozilla.org/MPL/2.0/. -*/ - -#include -#include -#include - -#include -#include - -using namespace iganet::unittests::literals; - -class FunctionSpaceTest : public ::testing::Test { -protected: - using real_t = iganet::unittests::real_t; - iganet::Options options; -}; - -TEST_F(FunctionSpaceTest, S_geoDim1_degrees2) { - using iganet::deriv; - using iganet::functionspace; - using iganet::side; - using BSpline = iganet::UniformBSpline; - iganet::S functionspace({5}, iganet::init::greville, options); - BSpline bspline({5}, iganet::init::greville, options); - - { // Interior - - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - - // Evaluation - EXPECT_TRUE(torch::equal( - *(functionspace.eval( - xi)[0]), - *(bspline.eval(xi)[0]))); - - EXPECT_TRUE(torch::equal( - *(functionspace.eval(xi)[0]), - *(bspline.eval(xi)[0]))); - - EXPECT_TRUE(torch::equal( - *(functionspace.eval( - xi)[0]), - *(bspline.eval(xi)[0]))); - - // Evaluation from precomputed coefficients and basis functions - auto knot_indices = - functionspace.template find_knot_indices(xi); - auto coeff_indices = - functionspace.template find_coeff_indices( - knot_indices); - - auto basfunc = - functionspace - .template eval_basfunc( - xi, knot_indices); - - EXPECT_TRUE(torch::equal( - *(functionspace.eval_from_precomputed( - basfunc, coeff_indices, xi[0].numel(), xi[0].sizes())[0]), - *(bspline.eval(xi)[0]))); - - basfunc = - functionspace - .template eval_basfunc( - xi, knot_indices); - EXPECT_TRUE(torch::equal( - *(functionspace.eval_from_precomputed( - basfunc, coeff_indices, xi[0].numel(), xi[0].sizes())[0]), - *(bspline.eval(xi)[0]))); - - basfunc = functionspace.template eval_basfunc( - xi, knot_indices); - EXPECT_TRUE(torch::equal( - *(functionspace.eval_from_precomputed( - basfunc, coeff_indices, xi[0].numel(), xi[0].sizes())[0]), - *(bspline.eval(xi)[0]))); - - /// Evaluation of gradient (in parametric domain) - auto grad = functionspace.template grad(xi); - auto grad_ref = bspline.grad(xi); - - for (std::size_t i = 0; i < grad.entries(); ++i) - EXPECT_TRUE(torch::equal(*(grad)[i], *(grad_ref)[i])); - - /// Evaluation of gradient (in physical domain) - auto igrad = functionspace.template igrad( - functionspace, xi); - auto igrad_ref = bspline.igrad(bspline, xi); - - for (std::size_t i = 0; i < igrad.entries(); ++i) - EXPECT_TRUE(torch::equal(*(igrad)[i], *(igrad_ref)[i])); - - /// Evaluation of Jacobian (in parametric domain) - auto jac = functionspace.template jac(xi); - auto jac_ref = bspline.jac(xi); - - for (std::size_t i = 0; i < jac.entries(); ++i) - EXPECT_TRUE(torch::equal(*(jac)[i], *(jac_ref)[i])); - - /// Evaluation of Jacobian (in physical domain) - auto ijac = functionspace.template ijac( - functionspace, xi); - auto ijac_ref = bspline.ijac(bspline, xi); - - for (std::size_t i = 0; i < ijac.entries(); ++i) - EXPECT_TRUE(torch::equal(*(ijac)[i], *(ijac_ref)[i])); - - /// Evaluation of Hessian (in parametric domain) - auto hess = functionspace.template hess(xi); - auto hess_ref = bspline.hess(xi); - - for (std::size_t i = 0; i < hess.entries(); ++i) - EXPECT_TRUE(torch::equal(*(hess)[i], *(hess_ref)[i])); - - /// Evaluation of Hessian (in physical domain) - auto ihess = functionspace.template ihess( - functionspace, xi); - auto ihess_ref = bspline.ihess(bspline, xi); - - for (std::size_t i = 0; i < ihess.entries(); ++i) - EXPECT_TRUE(torch::equal(*(ihess)[i], *(ihess_ref)[i])); - } - - { // Boundary - - auto xi = std::tuple{std::array{}, - std::array{}}; - - // Evaluation - auto eval = - functionspace.eval(xi); - - EXPECT_TRUE(torch::equal(*std::get(eval)[0], - torch::zeros(1, options))); - EXPECT_TRUE(torch::equal(*std::get(eval)[0], - torch::ones(1, options))); - - eval = functionspace.eval(xi); - - EXPECT_TRUE(torch::equal(*std::get(eval)[0], - torch::zeros(1, options))); - EXPECT_TRUE(torch::equal(*std::get(eval)[0], - torch::zeros(1, options))); - - eval = - functionspace.eval(xi); - - EXPECT_TRUE(torch::equal(*std::get(eval)[0], - torch::zeros(1, options))); - EXPECT_TRUE(torch::equal(*std::get(eval)[0], - torch::zeros(1, options))); - - // Evaluation from precomputed coefficients and basis functions - auto knot_indices = - functionspace.template find_knot_indices(xi); - auto coeff_indices = - functionspace.template find_coeff_indices( - knot_indices); - - auto numel = [](const auto &xi) { - return std::tuple{1, 1}; - }; - auto sizes = [](const auto &xi) { - return std::tuple{torch::IntArrayRef{}, torch::IntArrayRef{}}; - }; - - auto basfunc = - functionspace - .template eval_basfunc( - xi, knot_indices); - eval = - functionspace.template eval_from_precomputed( - basfunc, coeff_indices, numel(xi), sizes(xi)); - - EXPECT_TRUE(torch::allclose(*std::get(eval)[0], - torch::zeros(1, options))); - EXPECT_TRUE(torch::allclose(*std::get(eval)[0], - torch::ones(1, options))); - - basfunc = - functionspace - .template eval_basfunc( - xi, knot_indices); - eval = - functionspace.template eval_from_precomputed( - basfunc, coeff_indices, numel(xi), sizes(xi)); - - EXPECT_TRUE(torch::equal(*std::get(eval)[0], - torch::zeros({}, options))); - EXPECT_TRUE(torch::equal(*std::get(eval)[0], - torch::zeros({}, options))); - - basfunc = functionspace.template eval_basfunc( - xi, knot_indices); - eval = - functionspace.template eval_from_precomputed( - basfunc, coeff_indices, numel(xi), sizes(xi)); - - EXPECT_TRUE(torch::equal(*std::get(eval)[0], - torch::zeros({}, options))); - EXPECT_TRUE(torch::equal(*std::get(eval)[0], - torch::zeros({}, options))); - - /// Evaluation of gradient (in parametric domain) - auto grad = functionspace.template grad(xi); - - for (std::size_t i = 0; i < std::get<0>(grad).entries(); ++i) { - EXPECT_TRUE(torch::equal(*std::get(grad)[i], - torch::zeros({1}, options))); - EXPECT_TRUE(torch::equal(*std::get(grad)[i], - torch::zeros({1}, options))); - } - - /// Evaluation of gradient (in physical domain) - auto igrad = functionspace.template igrad( - functionspace, xi); - - for (std::size_t i = 0; i < std::get<0>(igrad).entries(); ++i) { - EXPECT_TRUE(torch::equal(*std::get(igrad)[i], - torch::zeros({1}, options))); - EXPECT_TRUE(torch::equal(*std::get(igrad)[i], - torch::zeros({1}, options))); - } - - /// Evaluation of Jacobian (in parametric domain) - auto jac = functionspace.template jac(xi); - - for (std::size_t i = 0; i < std::get<0>(jac).entries(); ++i) { - EXPECT_TRUE(torch::equal(*std::get(jac)[i], - torch::zeros({1}, options))); - EXPECT_TRUE(torch::equal(*std::get(jac)[i], - torch::zeros({1}, options))); - } - - /// Evaluation of Jacobian (in physical domain) - auto ijac = functionspace.template ijac( - functionspace, xi); - - for (std::size_t i = 0; i < std::get<0>(ijac).entries(); ++i) { - EXPECT_TRUE(torch::equal(*std::get(ijac)[i], - torch::zeros({1}, options))); - EXPECT_TRUE(torch::equal(*std::get(ijac)[i], - torch::zeros({1}, options))); - } - - /// Evaluation of Hessian (in parametric domain) - auto hess = functionspace.template hess(xi); - - for (std::size_t i = 0; i < std::get<0>(hess).entries(); ++i) { - EXPECT_TRUE(torch::equal(*std::get(hess)[i], - torch::zeros({1}, options))); - EXPECT_TRUE(torch::equal(*std::get(hess)[i], - torch::zeros({1}, options))); - } - - /// Evaluation of Hessian (in physical domain) - auto ihess = functionspace.template ihess( - functionspace, xi); - - for (std::size_t i = 0; i < std::get<0>(ihess).entries(); ++i) { - EXPECT_TRUE(torch::equal(*std::get(ihess)[i], - torch::zeros({1}, options))); - EXPECT_TRUE(torch::equal(*std::get(ihess)[i], - torch::zeros({1}, options))); - } - } -} - -TEST_F(FunctionSpaceTest, S_geoDim1_degrees23) { - using iganet::deriv; - using iganet::functionspace; - using iganet::side; - using BSpline = iganet::UniformBSpline; - using Geometry = iganet::UniformBSpline; - iganet::S functionspace({5, 4}, iganet::init::greville, options); - iganet::S S_geometry({5, 4}, iganet::init::greville, options); - BSpline bspline({5, 4}, iganet::init::greville, options); - Geometry geometry({5, 4}, iganet::init::greville, options); - - { // Interior - - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r} /* u */, - {1.0_r, 0.2_r, 0.1_r, 0.5_r, 0.9_r, 0.75_r, 0.0_r} /* v */); - - // Evaluation - EXPECT_TRUE(torch::equal( - *(functionspace.eval( - xi)[0]), - *(bspline.eval(xi)[0]))); - - EXPECT_TRUE(torch::equal( - *(functionspace.eval(xi)[0]), - *(bspline.eval(xi)[0]))); - - EXPECT_TRUE(torch::equal( - *(functionspace.eval( - xi)[0]), - *(bspline.eval(xi)[0]))); - - EXPECT_TRUE(torch::equal( - *(functionspace.eval(xi)[0]), - *(bspline.eval(xi)[0]))); - - EXPECT_TRUE(torch::equal( - *(functionspace.eval( - xi)[0]), - *(bspline.eval(xi)[0]))); - - EXPECT_TRUE( - torch::equal(*(functionspace.eval(xi)[0]), - *(bspline.eval(xi)[0]))); - - // Evaluation from precomputed coefficients and basis functions - auto knot_indices = - functionspace.template find_knot_indices(xi); - auto coeff_indices = - functionspace.template find_coeff_indices( - knot_indices); - - auto basfunc = - functionspace - .template eval_basfunc( - xi, knot_indices); - EXPECT_TRUE(torch::equal( - *(functionspace.eval_from_precomputed( - basfunc, coeff_indices, xi[0].numel(), xi[0].sizes())[0]), - *(bspline.eval(xi)[0]))); - - basfunc = - functionspace - .template eval_basfunc( - xi, knot_indices); - EXPECT_TRUE(torch::equal( - *(functionspace.eval_from_precomputed( - basfunc, coeff_indices, xi[0].numel(), xi[0].sizes())[0]), - *(bspline.eval(xi)[0]))); - - basfunc = functionspace.template eval_basfunc( - xi, knot_indices); - EXPECT_TRUE(torch::equal( - *(functionspace.eval_from_precomputed( - basfunc, coeff_indices, xi[0].numel(), xi[0].sizes())[0]), - *(bspline.eval(xi)[0]))); - - basfunc = - functionspace - .template eval_basfunc( - xi, knot_indices); - EXPECT_TRUE(torch::equal( - *(functionspace.eval_from_precomputed( - basfunc, coeff_indices, xi[0].numel(), xi[0].sizes())[0]), - *(bspline.eval(xi)[0]))); - - basfunc = functionspace.template eval_basfunc( - xi, knot_indices); - EXPECT_TRUE(torch::equal( - *(functionspace.eval_from_precomputed( - basfunc, coeff_indices, xi[0].numel(), xi[0].sizes())[0]), - *(bspline.eval(xi)[0]))); - - basfunc = functionspace.template eval_basfunc( - xi, knot_indices); - EXPECT_TRUE(torch::equal( - *(functionspace.eval_from_precomputed( - basfunc, coeff_indices, xi[0].numel(), xi[0].sizes())[0]), - *(bspline.eval(xi)[0]))); - - /// Evaluation of gradient (in parametric domain) - auto grad = functionspace.template grad(xi); - auto grad_ref = bspline.grad(xi); - - for (std::size_t i = 0; i < grad.entries(); ++i) - EXPECT_TRUE(torch::equal(*(grad)[i], *(grad_ref)[i])); - - /// Evaluation of gradient (in physical domain) - auto igrad = functionspace.template igrad( - S_geometry, xi); - auto igrad_ref = bspline.igrad(geometry, xi); - - for (std::size_t i = 0; i < igrad.entries(); ++i) - EXPECT_TRUE(torch::equal(*(igrad)[i], *(igrad_ref)[i])); - - /// Evaluation of Jacobian (in parametric domain) - auto jac = functionspace.template jac(xi); - auto jac_ref = bspline.jac(xi); - - for (std::size_t i = 0; i < jac.entries(); ++i) - EXPECT_TRUE(torch::equal(*(jac)[i], *(jac_ref)[i])); - - /// Evaluation of Jacobian (in physical domain) - auto ijac = functionspace.template ijac( - S_geometry, xi); - auto ijac_ref = bspline.ijac(geometry, xi); - - for (std::size_t i = 0; i < ijac.entries(); ++i) - EXPECT_TRUE(torch::equal(*(ijac)[i], *(ijac_ref)[i])); - - /// Evaluation of Hessian (in parametric domain) - auto hess = functionspace.template hess(xi); - auto hess_ref = bspline.hess(xi); - - for (std::size_t i = 0; i < hess.entries(); ++i) - EXPECT_TRUE(torch::equal(*(hess)[i], *(hess_ref)[i])); - - /// Evaluation of Hessian (in physical domain) - auto ihess = functionspace.template ihess( - S_geometry, xi); - auto ihess_ref = bspline.ihess(geometry, xi); - - for (std::size_t i = 0; i < ihess.entries(); ++i) - EXPECT_TRUE(torch::equal(*(ihess)[i], *(ihess_ref)[i])); - } - - { // Boundary - - iganet::UniformBSpline bspline_bdrNS( - {5}, iganet::init::greville, options); - iganet::UniformBSpline bspline_bdrEW( - {4}, iganet::init::greville, options); - - auto xi = - std::tuple{iganet::utils::to_tensorArray(options, {1.0_r, 0.2_r, 0.1_r, - 0.5_r, 0.9_r, 0.75_r, - 0.0_r}) /* west */, - iganet::utils::to_tensorArray(options, {1.0_r, 0.2_r, 0.1_r, - 0.5_r, 0.9_r, 0.75_r, - 0.0_r}) /* east */, - iganet::utils::to_tensorArray(options, {0.0_r, 0.1_r, 0.2_r, - 0.5_r, 0.75_r, 0.9_r, - 1.0_r}) /* south */, - iganet::utils::to_tensorArray(options, {0.0_r, 0.1_r, 0.2_r, - 0.5_r, 0.75_r, 0.9_r, - 1.0_r}) /* north */}; - - // Evaluation - auto eval = - functionspace.eval(xi); - - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::allclose(*(std::get(eval)[0]), - torch::ones(7, options))); - EXPECT_TRUE(torch::allclose(*(std::get(eval)[0]), - torch::zeros(7, options))); - - eval = functionspace.eval(xi); - - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::allclose(*(std::get(eval)[0]), - torch::zeros(7, options))); - EXPECT_TRUE(torch::allclose(*(std::get(eval)[0]), - torch::zeros(7, options))); - - eval = - functionspace.eval(xi); - - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::allclose(*(std::get(eval)[0]), - torch::zeros(7, options))); - EXPECT_TRUE(torch::allclose(*(std::get(eval)[0]), - torch::zeros(7, options))); - - eval = functionspace.eval(xi); - iganet::verbose(std::cout); - std::cout << functionspace.boundary() << std::endl; - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::allclose(*(std::get(eval)[0]), - torch::zeros(7, options))); - std::cout << *(std::get(eval)[0]) << std::endl; - EXPECT_TRUE(torch::allclose(*(std::get(eval)[0]), - torch::zeros(7, options))); - - eval = - functionspace.eval(xi); - - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::allclose(*(std::get(eval)[0]), - torch::zeros(7, options))); - std::cout << *(std::get(eval)[0]) << std::endl; - EXPECT_TRUE(torch::allclose(*(std::get(eval)[0]), - torch::zeros(7, options))); - - eval = functionspace - .eval(xi); - - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::allclose(*(std::get(eval)[0]), - torch::zeros(7, options))); - EXPECT_TRUE(torch::allclose(*(std::get(eval)[0]), - torch::zeros(7, options))); - - // Evaluation from precomputed coefficients and basis functions - auto knot_indices = - functionspace.template find_knot_indices(xi); - auto coeff_indices = - functionspace.template find_coeff_indices( - knot_indices); - - auto numel = [](const auto &xi) { - return std::tuple{std::get<0>(xi)[0].numel(), std::get<1>(xi)[0].numel(), - std::get<2>(xi)[0].numel(), std::get<3>(xi)[0].numel()}; - }; - auto sizes = [](const auto &xi) { - return std::tuple{std::get<0>(xi)[0].sizes(), std::get<1>(xi)[0].sizes(), - std::get<2>(xi)[0].sizes(), std::get<3>(xi)[0].sizes()}; - }; - - auto basfunc = - functionspace - .template eval_basfunc( - xi, knot_indices); - eval = - functionspace.template eval_from_precomputed( - basfunc, coeff_indices, numel(xi), sizes(xi)); - - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - std::cout << *(std::get(eval)[0]) << std::endl; - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - std::cout << *(std::get(eval)[0]) << std::endl; - exit(0); - basfunc = - functionspace - .template eval_basfunc( - xi, knot_indices); - eval = - functionspace.template eval_from_precomputed( - basfunc, coeff_indices, numel(xi), sizes(xi)); - - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - - basfunc = functionspace.template eval_basfunc( - xi, knot_indices); - eval = - functionspace.template eval_from_precomputed( - basfunc, coeff_indices, numel(xi), sizes(xi)); - - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - - basfunc = - functionspace - .template eval_basfunc( - xi, knot_indices); - eval = - functionspace.template eval_from_precomputed( - basfunc, coeff_indices, numel(xi), sizes(xi)); - - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - - basfunc = functionspace.template eval_basfunc( - xi, knot_indices); - eval = - functionspace.template eval_from_precomputed( - basfunc, coeff_indices, numel(xi), sizes(xi)); - - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - - basfunc = functionspace.template eval_basfunc( - xi, knot_indices); - eval = - functionspace.template eval_from_precomputed( - basfunc, coeff_indices, numel(xi), sizes(xi)); - - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - - /// Evaluation of gradient (in parametric domain) - auto grad = functionspace.template grad(xi); - - for (std::size_t i = 0; i < std::get<0>(grad).entries(); ++i) { - EXPECT_TRUE(torch::equal( - *(std::get(grad)[i]), - *(bspline_bdrNS.grad(std::get(xi))[i]))); - EXPECT_TRUE(torch::equal( - *(std::get(grad)[i]), - *(bspline_bdrNS.grad(std::get(xi))[i]))); - EXPECT_TRUE(torch::equal( - *(std::get(grad)[i]), - *(bspline_bdrEW.grad(std::get(xi))[i]))); - EXPECT_TRUE(torch::equal( - *(std::get(grad)[i]), - *(bspline_bdrEW.grad(std::get(xi))[i]))); - } - - /// Evaluation of gradient (in physical domain) - auto igrad = functionspace.template igrad( - functionspace, xi); - - for (std::size_t i = 0; i < std::get<0>(igrad).entries(); ++i) { - EXPECT_TRUE( - torch::equal(*(std::get(igrad)[i]), - *(bspline_bdrNS.igrad( - bspline_bdrNS, std::get(xi))[i]))); - EXPECT_TRUE( - torch::equal(*(std::get(igrad)[i]), - *(bspline_bdrNS.igrad( - bspline_bdrNS, std::get(xi))[i]))); - EXPECT_TRUE( - torch::equal(*(std::get(igrad)[i]), - *(bspline_bdrEW.igrad( - bspline_bdrEW, std::get(xi))[i]))); - EXPECT_TRUE( - torch::equal(*(std::get(igrad)[i]), - *(bspline_bdrEW.igrad( - bspline_bdrEW, std::get(xi))[i]))); - } - - /// Evaluation of Jacobian (in parametric domain) - auto jac = functionspace.template jac(xi); - - for (std::size_t i = 0; i < std::get<0>(jac).entries(); ++i) { - EXPECT_TRUE(torch::equal( - *(std::get(jac)[i]), - *(bspline_bdrNS.jac(std::get(xi))[i]))); - EXPECT_TRUE(torch::equal( - *(std::get(jac)[i]), - *(bspline_bdrNS.jac(std::get(xi))[i]))); - EXPECT_TRUE(torch::equal( - *(std::get(jac)[i]), - *(bspline_bdrEW.jac(std::get(xi))[i]))); - EXPECT_TRUE(torch::equal( - *(std::get(jac)[i]), - *(bspline_bdrEW.jac(std::get(xi))[i]))); - } - - /// Evaluation of Jacobian (in physical domain) - auto ijac = functionspace.template ijac( - functionspace, xi); - - for (std::size_t i = 0; i < std::get<0>(ijac).entries(); ++i) { - EXPECT_TRUE( - torch::equal(*(std::get(ijac)[i]), - *(bspline_bdrNS.ijac( - bspline_bdrNS, std::get(xi))[i]))); - EXPECT_TRUE( - torch::equal(*(std::get(ijac)[i]), - *(bspline_bdrNS.ijac( - bspline_bdrNS, std::get(xi))[i]))); - EXPECT_TRUE( - torch::equal(*(std::get(ijac)[i]), - *(bspline_bdrEW.ijac( - bspline_bdrEW, std::get(xi))[i]))); - EXPECT_TRUE( - torch::equal(*(std::get(ijac)[i]), - *(bspline_bdrEW.ijac( - bspline_bdrEW, std::get(xi))[i]))); - } - - /// Evaluation of Hessian (in parametric domain) - auto hess = functionspace.template hess(xi); - - for (std::size_t i = 0; i < std::get<0>(hess).entries(); ++i) { - EXPECT_TRUE(torch::equal( - *(std::get(hess)[i]), - *(bspline_bdrNS.hess(std::get(xi))[i]))); - EXPECT_TRUE(torch::equal( - *(std::get(hess)[i]), - *(bspline_bdrNS.hess(std::get(xi))[i]))); - EXPECT_TRUE(torch::equal( - *(std::get(hess)[i]), - *(bspline_bdrEW.hess(std::get(xi))[i]))); - EXPECT_TRUE(torch::equal( - *(std::get(hess)[i]), - *(bspline_bdrEW.hess(std::get(xi))[i]))); - } - - /// Evaluation of Hessian (in physical domain) - auto ihess = functionspace.template ihess( - functionspace, xi); - - for (std::size_t i = 0; i < std::get<0>(ihess).entries(); ++i) { - EXPECT_TRUE( - torch::equal(*(std::get(ihess)[i]), - *(bspline_bdrNS.ihess( - bspline_bdrNS, std::get(xi))[i]))); - EXPECT_TRUE( - torch::equal(*(std::get(ihess)[i]), - *(bspline_bdrNS.ihess( - bspline_bdrNS, std::get(xi))[i]))); - EXPECT_TRUE( - torch::equal(*(std::get(ihess)[i]), - *(bspline_bdrEW.ihess( - bspline_bdrEW, std::get(xi))[i]))); - EXPECT_TRUE( - torch::equal(*(std::get(ihess)[i]), - *(bspline_bdrEW.ihess( - bspline_bdrEW, std::get(xi))[i]))); - } - } -} - -TEST_F(FunctionSpaceTest, S_geoDim1_degrees234) { - using iganet::deriv; - using iganet::functionspace; - using iganet::side; - using BSpline = iganet::UniformBSpline; - using Geometry = iganet::UniformBSpline; - iganet::S functionspace({5, 4, 7}, iganet::init::greville, options); - iganet::S S_geometry({5, 4, 7}, iganet::init::greville, options); - BSpline bspline({5, 4, 7}, iganet::init::greville, options); - Geometry geometry({5, 4, 7}, iganet::init::greville, options); - - { // Interior - - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r} /* u */, - {1.0_r, 0.2_r, 0.1_r, 0.5_r, 0.9_r, 0.75_r, 0.0_r} /* v */, - {0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r, 0.0_r, 0.1_r} /* w */); - - // Evaluation - EXPECT_TRUE(torch::equal( - *(functionspace.eval( - xi)[0]), - *(bspline.eval(xi)[0]))); - - EXPECT_TRUE(torch::equal( - *(functionspace.eval(xi)[0]), - *(bspline.eval(xi)[0]))); - - EXPECT_TRUE(torch::equal( - *(functionspace.eval( - xi)[0]), - *(bspline.eval(xi)[0]))); - - EXPECT_TRUE(torch::equal( - *(functionspace.eval(xi)[0]), - *(bspline.eval(xi)[0]))); - - EXPECT_TRUE(torch::equal( - *(functionspace.eval( - xi)[0]), - *(bspline.eval(xi)[0]))); - - EXPECT_TRUE(torch::equal( - *(functionspace.eval(xi)[0]), - *(bspline.eval(xi)[0]))); - - EXPECT_TRUE(torch::equal( - *(functionspace.eval( - xi)[0]), - *(bspline.eval(xi)[0]))); - - EXPECT_TRUE( - torch::equal(*(functionspace.eval(xi)[0]), - *(bspline.eval(xi)[0]))); - - EXPECT_TRUE( - torch::equal(*(functionspace.eval(xi)[0]), - *(bspline.eval(xi)[0]))); - - EXPECT_TRUE( - torch::equal(*(functionspace.eval(xi)[0]), - *(bspline.eval(xi)[0]))); - - // Evaluation from precomputed coefficients and basis functions - auto knot_indices = - functionspace.template find_knot_indices(xi); - auto coeff_indices = - functionspace.template find_coeff_indices( - knot_indices); - - auto basfunc = - functionspace - .template eval_basfunc( - xi, knot_indices); - EXPECT_TRUE(torch::equal( - *(functionspace.eval_from_precomputed( - basfunc, coeff_indices, xi[0].numel(), xi[0].sizes())[0]), - *(bspline.eval(xi)[0]))); - - basfunc = - functionspace - .template eval_basfunc( - xi, knot_indices); - EXPECT_TRUE(torch::equal( - *(functionspace.eval_from_precomputed( - basfunc, coeff_indices, xi[0].numel(), xi[0].sizes())[0]), - *(bspline.eval(xi)[0]))); - - basfunc = functionspace.template eval_basfunc( - xi, knot_indices); - EXPECT_TRUE(torch::equal( - *(functionspace.eval_from_precomputed( - basfunc, coeff_indices, xi[0].numel(), xi[0].sizes())[0]), - *(bspline.eval(xi)[0]))); - - basfunc = - functionspace - .template eval_basfunc( - xi, knot_indices); - EXPECT_TRUE(torch::equal( - *(functionspace.eval_from_precomputed( - basfunc, coeff_indices, xi[0].numel(), xi[0].sizes())[0]), - *(bspline.eval(xi)[0]))); - - basfunc = functionspace.template eval_basfunc( - xi, knot_indices); - EXPECT_TRUE(torch::equal( - *(functionspace.eval_from_precomputed( - basfunc, coeff_indices, xi[0].numel(), xi[0].sizes())[0]), - *(bspline.eval(xi)[0]))); - - basfunc = - functionspace - .template eval_basfunc( - xi, knot_indices); - EXPECT_TRUE(torch::equal( - *(functionspace.eval_from_precomputed( - basfunc, coeff_indices, xi[0].numel(), xi[0].sizes())[0]), - *(bspline.eval(xi)[0]))); - - basfunc = functionspace.template eval_basfunc( - xi, knot_indices); - EXPECT_TRUE(torch::equal( - *(functionspace.eval_from_precomputed( - basfunc, coeff_indices, xi[0].numel(), xi[0].sizes())[0]), - *(bspline.eval(xi)[0]))); - - basfunc = functionspace.template eval_basfunc( - xi, knot_indices); - EXPECT_TRUE(torch::equal( - *(functionspace.eval_from_precomputed( - basfunc, coeff_indices, xi[0].numel(), xi[0].sizes())[0]), - *(bspline.eval(xi)[0]))); - - basfunc = functionspace.template eval_basfunc( - xi, knot_indices); - EXPECT_TRUE(torch::equal( - *(functionspace.eval_from_precomputed( - basfunc, coeff_indices, xi[0].numel(), xi[0].sizes())[0]), - *(bspline.eval(xi)[0]))); - - basfunc = functionspace.template eval_basfunc( - xi, knot_indices); - EXPECT_TRUE(torch::equal( - *(functionspace.eval_from_precomputed( - basfunc, coeff_indices, xi[0].numel(), xi[0].sizes())[0]), - *(bspline.eval(xi)[0]))); - - /// Evaluation of gradient (in parametric domain) - auto grad = functionspace.template grad(xi); - auto grad_ref = bspline.grad(xi); - - for (std::size_t i = 0; i < grad.entries(); ++i) - EXPECT_TRUE(torch::equal(*(grad)[i], *(grad_ref)[i])); - - /// Evaluation of gradient (in physical domain) - auto igrad = functionspace.template igrad( - S_geometry, xi); - auto igrad_ref = bspline.igrad(geometry, xi); - - for (std::size_t i = 0; i < igrad.entries(); ++i) - EXPECT_TRUE(torch::equal(*(igrad)[i], *(igrad_ref)[i])); - - /// Evaluation of Jacobian (in parametric domain) - auto jac = functionspace.template jac(xi); - auto jac_ref = bspline.jac(xi); - - for (std::size_t i = 0; i < jac.entries(); ++i) - EXPECT_TRUE(torch::equal(*(jac)[i], *(jac_ref)[i])); - - /// Evaluation of Jacobian (in physical domain) - auto ijac = functionspace.template ijac( - S_geometry, xi); - auto ijac_ref = bspline.ijac(geometry, xi); - - for (std::size_t i = 0; i < ijac.entries(); ++i) - EXPECT_TRUE(torch::equal(*(ijac)[i], *(ijac_ref)[i])); - - /// Evaluation of Hessian (in parametric domain) - auto hess = functionspace.template hess(xi); - auto hess_ref = bspline.hess(xi); - - for (std::size_t i = 0; i < hess.entries(); ++i) - EXPECT_TRUE(torch::equal(*(hess)[i], *(hess_ref)[i])); - - /// Evaluation of Hessian (in physical domain) - auto ihess = functionspace.template ihess( - S_geometry, xi); - auto ihess_ref = bspline.ihess(geometry, xi); - - for (std::size_t i = 0; i < ihess.entries(); ++i) - EXPECT_TRUE(torch::equal(*(ihess)[i], *(ihess_ref)[i])); - } - - { // Boundary - - iganet::UniformBSpline bspline_bdrNS( - {5, 7}, iganet::init::greville, options); - iganet::UniformBSpline bspline_bdrEW( - {4, 7}, iganet::init::greville, options); - iganet::UniformBSpline bspline_bdrFB( - {5, 4}, iganet::init::greville, options); - iganet::UniformBSpline geometry_bdrNS( - {5, 7}, iganet::init::greville, options); - iganet::UniformBSpline geometry_bdrEW( - {4, 7}, iganet::init::greville, options); - iganet::UniformBSpline geometry_bdrFB( - {5, 4}, iganet::init::greville, options); - - auto xi = std::tuple{ - iganet::utils::to_tensorArray( - options, {1.0_r, 0.2_r, 0.1_r, 0.5_r, 0.9_r, 0.75_r, 0.0_r} /* v */, - {0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r, 0.0_r, - 0.1_r} /* w */) /* west */, - iganet::utils::to_tensorArray( - options, {1.0_r, 0.2_r, 0.1_r, 0.5_r, 0.9_r, 0.75_r, 0.0_r} /* v */, - {0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r, 0.0_r, - 0.1_r} /* w */) /* east */, - iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r} /* u */, - {0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r, 0.0_r, - 0.1_r} /* w */) /* south */, - iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r} /* u */, - {0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r, 0.0_r, - 0.1_r} /* w */) /* north */, - iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r} /* u */, - {1.0_r, 0.2_r, 0.1_r, 0.5_r, 0.9_r, 0.75_r, - 0.0_r} /* v */) /* front */, - iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r} /* u */, - {1.0_r, 0.2_r, 0.1_r, 0.5_r, 0.9_r, 0.75_r, - 0.0_r} /* v */) /* back */}; - - // Evaluation - auto eval = - functionspace.eval(xi); - - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - - eval = functionspace.eval(xi); - - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - - eval = - functionspace.eval(xi); - - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - - eval = functionspace.eval(xi); - - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - - eval = - functionspace.eval(xi); - - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - - eval = functionspace.eval(xi); - - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - - eval = - functionspace.eval(xi); - - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - - eval = functionspace - .eval(xi); - - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - - eval = functionspace - .eval(xi); - - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - - eval = functionspace - .eval(xi); - - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - - // Evaluation from precomputed coefficients and basis functions - auto knot_indices = - functionspace.template find_knot_indices(xi); - auto coeff_indices = - functionspace.template find_coeff_indices( - knot_indices); - - auto numel = [](const auto &xi) { - return std::tuple{std::get<0>(xi)[0].numel(), std::get<1>(xi)[0].numel(), - std::get<2>(xi)[0].numel(), std::get<3>(xi)[0].numel(), - std::get<4>(xi)[0].numel(), std::get<5>(xi)[0].numel()}; - }; - auto sizes = [](const auto &xi) { - return std::tuple{std::get<0>(xi)[0].sizes(), std::get<1>(xi)[0].sizes(), - std::get<2>(xi)[0].sizes(), std::get<3>(xi)[0].sizes(), - std::get<4>(xi)[0].sizes(), std::get<5>(xi)[0].sizes()}; - }; - - auto basfunc = - functionspace - .template eval_basfunc( - xi, knot_indices); - eval = - functionspace.template eval_from_precomputed( - basfunc, coeff_indices, numel(xi), sizes(xi)); - - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - - basfunc = - functionspace - .template eval_basfunc( - xi, knot_indices); - eval = - functionspace.template eval_from_precomputed( - basfunc, coeff_indices, numel(xi), sizes(xi)); - - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - - basfunc = functionspace.template eval_basfunc( - xi, knot_indices); - eval = - functionspace.template eval_from_precomputed( - basfunc, coeff_indices, numel(xi), sizes(xi)); - - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - - basfunc = - functionspace - .template eval_basfunc( - xi, knot_indices); - eval = - functionspace.template eval_from_precomputed( - basfunc, coeff_indices, numel(xi), sizes(xi)); - - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - - basfunc = functionspace.template eval_basfunc( - xi, knot_indices); - eval = - functionspace.template eval_from_precomputed( - basfunc, coeff_indices, numel(xi), sizes(xi)); - - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - - basfunc = - functionspace - .template eval_basfunc( - xi, knot_indices); - eval = - functionspace.template eval_from_precomputed( - basfunc, coeff_indices, numel(xi), sizes(xi)); - - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - - basfunc = functionspace.template eval_basfunc( - xi, knot_indices); - eval = - functionspace.template eval_from_precomputed( - basfunc, coeff_indices, numel(xi), sizes(xi)); - - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - - basfunc = functionspace.template eval_basfunc( - xi, knot_indices); - eval = - functionspace.template eval_from_precomputed( - basfunc, coeff_indices, numel(xi), sizes(xi)); - - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - - basfunc = functionspace.template eval_basfunc( - xi, knot_indices); - eval = - functionspace.template eval_from_precomputed( - basfunc, coeff_indices, numel(xi), sizes(xi)); - - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - - basfunc = functionspace.template eval_basfunc( - xi, knot_indices); - eval = - functionspace.template eval_from_precomputed( - basfunc, coeff_indices, numel(xi), sizes(xi)); - - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - - /// Evaluation of gradient (in parametric domain) - auto grad = functionspace.template grad(xi); - - for (std::size_t i = 0; i < std::get<0>(grad).entries(); ++i) { - EXPECT_TRUE(torch::equal( - *(std::get(grad)[i]), - *(bspline_bdrNS.grad(std::get(xi))[i]))); - EXPECT_TRUE(torch::equal( - *(std::get(grad)[i]), - *(bspline_bdrNS.grad(std::get(xi))[i]))); - EXPECT_TRUE(torch::equal( - *(std::get(grad)[i]), - *(bspline_bdrEW.grad(std::get(xi))[i]))); - EXPECT_TRUE(torch::equal( - *(std::get(grad)[i]), - *(bspline_bdrEW.grad(std::get(xi))[i]))); - EXPECT_TRUE(torch::equal( - *(std::get(grad)[i]), - *(bspline_bdrFB.grad(std::get(xi))[i]))); - EXPECT_TRUE(torch::equal( - *(std::get(grad)[i]), - *(bspline_bdrFB.grad(std::get(xi))[i]))); - } - - /// Evaluation of gradient (in physical domain) - auto igrad = functionspace.template igrad( - S_geometry, xi); - - for (std::size_t i = 0; i < std::get<0>(igrad).entries(); ++i) { - EXPECT_TRUE( - torch::equal(*(std::get(igrad)[i]), - *(bspline_bdrNS.igrad( - geometry_bdrNS, std::get(xi))[i]))); - EXPECT_TRUE( - torch::equal(*(std::get(igrad)[i]), - *(bspline_bdrNS.igrad( - geometry_bdrNS, std::get(xi))[i]))); - EXPECT_TRUE( - torch::equal(*(std::get(igrad)[i]), - *(bspline_bdrEW.igrad( - geometry_bdrEW, std::get(xi))[i]))); - EXPECT_TRUE( - torch::equal(*(std::get(igrad)[i]), - *(bspline_bdrEW.igrad( - geometry_bdrEW, std::get(xi))[i]))); - EXPECT_TRUE( - torch::equal(*(std::get(igrad)[i]), - *(bspline_bdrFB.igrad( - geometry_bdrFB, std::get(xi))[i]))); - EXPECT_TRUE( - torch::equal(*(std::get(igrad)[i]), - *(bspline_bdrFB.igrad( - geometry_bdrFB, std::get(xi))[i]))); - } - - /// Evaluation of Jacobian (in parametric domain) - auto jac = functionspace.template jac(xi); - - for (std::size_t i = 0; i < std::get<0>(jac).entries(); ++i) { - EXPECT_TRUE(torch::equal( - *(std::get(jac)[i]), - *(bspline_bdrNS.jac(std::get(xi))[i]))); - EXPECT_TRUE(torch::equal( - *(std::get(jac)[i]), - *(bspline_bdrNS.jac(std::get(xi))[i]))); - EXPECT_TRUE(torch::equal( - *(std::get(jac)[i]), - *(bspline_bdrEW.jac(std::get(xi))[i]))); - EXPECT_TRUE(torch::equal( - *(std::get(jac)[i]), - *(bspline_bdrEW.jac(std::get(xi))[i]))); - EXPECT_TRUE(torch::equal( - *(std::get(jac)[i]), - *(bspline_bdrFB.jac(std::get(xi))[i]))); - EXPECT_TRUE(torch::equal( - *(std::get(jac)[i]), - *(bspline_bdrFB.jac(std::get(xi))[i]))); - } - - /// Evaluation of Jacobian (in physical domain) - auto ijac = functionspace.template ijac( - S_geometry, xi); - - for (std::size_t i = 0; i < std::get<0>(ijac).entries(); ++i) { - EXPECT_TRUE( - torch::equal(*(std::get(ijac)[i]), - *(bspline_bdrNS.ijac( - geometry_bdrNS, std::get(xi))[i]))); - EXPECT_TRUE( - torch::equal(*(std::get(ijac)[i]), - *(bspline_bdrNS.ijac( - geometry_bdrNS, std::get(xi))[i]))); - EXPECT_TRUE( - torch::equal(*(std::get(ijac)[i]), - *(bspline_bdrEW.ijac( - geometry_bdrEW, std::get(xi))[i]))); - EXPECT_TRUE( - torch::equal(*(std::get(ijac)[i]), - *(bspline_bdrEW.ijac( - geometry_bdrEW, std::get(xi))[i]))); - EXPECT_TRUE( - torch::equal(*(std::get(ijac)[i]), - *(bspline_bdrFB.ijac( - geometry_bdrFB, std::get(xi))[i]))); - EXPECT_TRUE( - torch::equal(*(std::get(ijac)[i]), - *(bspline_bdrFB.ijac( - geometry_bdrFB, std::get(xi))[i]))); - } - - /// Evaluation of Hessian (in parametric domain) - auto hess = functionspace.template hess(xi); - - for (std::size_t i = 0; i < std::get<0>(hess).entries(); ++i) { - EXPECT_TRUE(torch::equal( - *(std::get(hess)[i]), - *(bspline_bdrNS.hess(std::get(xi))[i]))); - EXPECT_TRUE(torch::equal( - *(std::get(hess)[i]), - *(bspline_bdrNS.hess(std::get(xi))[i]))); - EXPECT_TRUE(torch::equal( - *(std::get(hess)[i]), - *(bspline_bdrEW.hess(std::get(xi))[i]))); - EXPECT_TRUE(torch::equal( - *(std::get(hess)[i]), - *(bspline_bdrEW.hess(std::get(xi))[i]))); - EXPECT_TRUE(torch::equal( - *(std::get(hess)[i]), - *(bspline_bdrFB.hess(std::get(xi))[i]))); - EXPECT_TRUE(torch::equal( - *(std::get(hess)[i]), - *(bspline_bdrFB.hess(std::get(xi))[i]))); - } - - /// Evaluation of Hessian (in physical domain) - auto ihess = - functionspace.template ihess(S_geometry, xi); - - for (std::size_t i = 0; i < std::get<0>(ihess).entries(); ++i) { - EXPECT_TRUE( - torch::equal(*(std::get(ihess)[i]), - *(bspline_bdrNS.ihess( - geometry_bdrNS, std::get(xi))[i]))); - EXPECT_TRUE( - torch::equal(*(std::get(ihess)[i]), - *(bspline_bdrNS.ihess( - geometry_bdrNS, std::get(xi))[i]))); - EXPECT_TRUE( - torch::equal(*(std::get(ihess)[i]), - *(bspline_bdrEW.ihess( - geometry_bdrEW, std::get(xi))[i]))); - EXPECT_TRUE( - torch::equal(*(std::get(ihess)[i]), - *(bspline_bdrEW.ihess( - geometry_bdrEW, std::get(xi))[i]))); - EXPECT_TRUE( - torch::equal(*(std::get(ihess)[i]), - *(bspline_bdrFB.ihess( - geometry_bdrFB, std::get(xi))[i]))); - EXPECT_TRUE( - torch::equal(*(std::get(ihess)[i]), - *(bspline_bdrFB.ihess( - geometry_bdrFB, std::get(xi))[i]))); - } - } -} - -TEST_F(FunctionSpaceTest, S_geoDim1_degrees2341) { - using iganet::deriv; - using iganet::functionspace; - using iganet::side; - using BSpline = iganet::UniformBSpline; - using Geometry = iganet::UniformBSpline; - iganet::S functionspace({5, 4, 7, 3}, iganet::init::greville, - options); - iganet::S S_geometry({5, 4, 7, 3}, iganet::init::greville, options); - BSpline bspline({5, 4, 7, 3}, iganet::init::greville, options); - Geometry geometry({5, 4, 7, 3}, iganet::init::greville, options); - - { // Interior - - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r} /* u */, - {1.0_r, 0.2_r, 0.1_r, 0.5_r, 0.9_r, 0.75_r, 0.0_r} /* v */, - {0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r, 0.0_r, 0.1_r} /* w */, - {0.1_r, 0.1_r, 0.2_r, 0.3_r, 0.3_r, 0.0_r, 0.1_r} /* t */); - - // Evaluation - EXPECT_TRUE(torch::equal( - *(functionspace.eval( - xi)[0]), - *(bspline.eval(xi)[0]))); - - EXPECT_TRUE(torch::equal( - *(functionspace.eval(xi)[0]), - *(bspline.eval(xi)[0]))); - - EXPECT_TRUE(torch::equal( - *(functionspace.eval( - xi)[0]), - *(bspline.eval(xi)[0]))); - - EXPECT_TRUE(torch::equal( - *(functionspace.eval(xi)[0]), - *(bspline.eval(xi)[0]))); - - EXPECT_TRUE(torch::equal( - *(functionspace.eval( - xi)[0]), - *(bspline.eval(xi)[0]))); - - EXPECT_TRUE(torch::equal( - *(functionspace.eval(xi)[0]), - *(bspline.eval(xi)[0]))); - - EXPECT_TRUE(torch::equal( - *(functionspace.eval( - xi)[0]), - *(bspline.eval(xi)[0]))); - - EXPECT_TRUE(torch::equal( - *(functionspace.eval(xi)[0]), - *(bspline.eval(xi)[0]))); - - EXPECT_TRUE(torch::equal( - *(functionspace.eval( - xi)[0]), - *(bspline.eval(xi)[0]))); - - EXPECT_TRUE( - torch::equal(*(functionspace.eval(xi)[0]), - *(bspline.eval(xi)[0]))); - - EXPECT_TRUE( - torch::equal(*(functionspace.eval(xi)[0]), - *(bspline.eval(xi)[0]))); - - EXPECT_TRUE( - torch::equal(*(functionspace.eval(xi)[0]), - *(bspline.eval(xi)[0]))); - - EXPECT_TRUE( - torch::equal(*(functionspace.eval(xi)[0]), - *(bspline.eval(xi)[0]))); - - EXPECT_TRUE( - torch::equal(*(functionspace.eval(xi)[0]), - *(bspline.eval(xi)[0]))); - - EXPECT_TRUE( - torch::equal(*(functionspace.eval(xi)[0]), - *(bspline.eval(xi)[0]))); - - // Evaluation from precomputed coefficients and basis functions - auto knot_indices = - functionspace.template find_knot_indices(xi); - auto coeff_indices = - functionspace.template find_coeff_indices( - knot_indices); - - auto basfunc = - functionspace - .template eval_basfunc( - xi, knot_indices); - EXPECT_TRUE(torch::equal( - *(functionspace.eval_from_precomputed( - basfunc, coeff_indices, xi[0].numel(), xi[0].sizes())[0]), - *(bspline.eval(xi)[0]))); - - basfunc = - functionspace - .template eval_basfunc( - xi, knot_indices); - EXPECT_TRUE(torch::equal( - *(functionspace.eval_from_precomputed( - basfunc, coeff_indices, xi[0].numel(), xi[0].sizes())[0]), - *(bspline.eval(xi)[0]))); - - basfunc = functionspace.template eval_basfunc( - xi, knot_indices); - EXPECT_TRUE(torch::equal( - *(functionspace.eval_from_precomputed( - basfunc, coeff_indices, xi[0].numel(), xi[0].sizes())[0]), - *(bspline.eval(xi)[0]))); - - basfunc = - functionspace - .template eval_basfunc( - xi, knot_indices); - EXPECT_TRUE(torch::equal( - *(functionspace.eval_from_precomputed( - basfunc, coeff_indices, xi[0].numel(), xi[0].sizes())[0]), - *(bspline.eval(xi)[0]))); - - basfunc = functionspace.template eval_basfunc( - xi, knot_indices); - EXPECT_TRUE(torch::equal( - *(functionspace.eval_from_precomputed( - basfunc, coeff_indices, xi[0].numel(), xi[0].sizes())[0]), - *(bspline.eval(xi)[0]))); - - basfunc = - functionspace - .template eval_basfunc( - xi, knot_indices); - EXPECT_TRUE(torch::equal( - *(functionspace.eval_from_precomputed( - basfunc, coeff_indices, xi[0].numel(), xi[0].sizes())[0]), - *(bspline.eval(xi)[0]))); - - basfunc = functionspace.template eval_basfunc( - xi, knot_indices); - EXPECT_TRUE(torch::equal( - *(functionspace.eval_from_precomputed( - basfunc, coeff_indices, xi[0].numel(), xi[0].sizes())[0]), - *(bspline.eval(xi)[0]))); - - basfunc = - functionspace - .template eval_basfunc( - xi, knot_indices); - EXPECT_TRUE(torch::equal( - *(functionspace.eval_from_precomputed( - basfunc, coeff_indices, xi[0].numel(), xi[0].sizes())[0]), - *(bspline.eval(xi)[0]))); - - basfunc = functionspace.template eval_basfunc( - xi, knot_indices); - EXPECT_TRUE(torch::equal( - *(functionspace.eval_from_precomputed( - basfunc, coeff_indices, xi[0].numel(), xi[0].sizes())[0]), - *(bspline.eval(xi)[0]))); - - basfunc = functionspace.template eval_basfunc( - xi, knot_indices); - EXPECT_TRUE(torch::equal( - *(functionspace.eval_from_precomputed( - basfunc, coeff_indices, xi[0].numel(), xi[0].sizes())[0]), - *(bspline.eval(xi)[0]))); - - basfunc = functionspace.template eval_basfunc( - xi, knot_indices); - EXPECT_TRUE(torch::equal( - *(functionspace.eval_from_precomputed( - basfunc, coeff_indices, xi[0].numel(), xi[0].sizes())[0]), - *(bspline.eval(xi)[0]))); - - basfunc = functionspace.template eval_basfunc( - xi, knot_indices); - EXPECT_TRUE(torch::equal( - *(functionspace.eval_from_precomputed( - basfunc, coeff_indices, xi[0].numel(), xi[0].sizes())[0]), - *(bspline.eval(xi)[0]))); - - basfunc = functionspace.template eval_basfunc( - xi, knot_indices); - EXPECT_TRUE(torch::equal( - *(functionspace.eval_from_precomputed( - basfunc, coeff_indices, xi[0].numel(), xi[0].sizes())[0]), - *(bspline.eval(xi)[0]))); - - basfunc = functionspace.template eval_basfunc( - xi, knot_indices); - EXPECT_TRUE(torch::equal( - *(functionspace.eval_from_precomputed( - basfunc, coeff_indices, xi[0].numel(), xi[0].sizes())[0]), - *(bspline.eval(xi)[0]))); - - basfunc = functionspace.template eval_basfunc( - xi, knot_indices); - EXPECT_TRUE(torch::equal( - *(functionspace.eval_from_precomputed( - basfunc, coeff_indices, xi[0].numel(), xi[0].sizes())[0]), - *(bspline.eval(xi)[0]))); - - /// Evaluation of gradient (in parametric domain) - auto grad = functionspace.template grad(xi); - auto grad_ref = bspline.grad(xi); - - for (std::size_t i = 0; i < grad.entries(); ++i) - EXPECT_TRUE(torch::equal(*(grad)[i], *(grad_ref)[i])); - - /// Evaluation of gradient (in physical domain) - auto igrad = functionspace.template igrad( - S_geometry, xi); - auto igrad_ref = bspline.igrad(geometry, xi); - - for (std::size_t i = 0; i < igrad.entries(); ++i) - EXPECT_TRUE(torch::equal(*(igrad)[i], *(igrad_ref)[i])); - - /// Evaluation of Jacobian (in parametric domain) - auto jac = functionspace.template jac(xi); - auto jac_ref = bspline.jac(xi); - - for (std::size_t i = 0; i < jac.entries(); ++i) - EXPECT_TRUE(torch::equal(*(jac)[i], *(jac_ref)[i])); - - /// Evaluation of Jacobian (in physical domain) - auto ijac = functionspace.template ijac( - S_geometry, xi); - auto ijac_ref = bspline.ijac(geometry, xi); - - for (std::size_t i = 0; i < ijac.entries(); ++i) - EXPECT_TRUE(torch::equal(*(ijac)[i], *(ijac_ref)[i])); - - /// Evaluation of Hessian (in parametric domain) - auto hess = functionspace.template hess(xi); - auto hess_ref = bspline.hess(xi); - - for (std::size_t i = 0; i < hess.entries(); ++i) - EXPECT_TRUE(torch::equal(*(hess)[i], *(hess_ref)[i])); - - /// Evaluation of Hessian (in physical domain) - auto ihess = functionspace.template ihess( - S_geometry, xi); - auto ihess_ref = bspline.ihess(geometry, xi); - - for (std::size_t i = 0; i < ihess.entries(); ++i) - EXPECT_TRUE(torch::equal(*(ihess)[i], *(ihess_ref)[i])); - } - - { // Boundary - - iganet::UniformBSpline bspline_bdrNS( - {5, 7, 3}, iganet::init::greville, options); - iganet::UniformBSpline bspline_bdrEW( - {4, 7, 3}, iganet::init::greville, options); - iganet::UniformBSpline bspline_bdrFB( - {5, 4, 3}, iganet::init::greville, options); - iganet::UniformBSpline bspline_bdrSE( - {5, 4, 7}, iganet::init::greville, options); - iganet::UniformBSpline geometry_bdrNS( - {5, 7, 3}, iganet::init::greville, options); - iganet::UniformBSpline geometry_bdrEW( - {4, 7, 3}, iganet::init::greville, options); - iganet::UniformBSpline geometry_bdrFB( - {5, 4, 3}, iganet::init::greville, options); - iganet::UniformBSpline geometry_bdrSE( - {5, 4, 7}, iganet::init::greville, options); - - auto xi = std::tuple{ - iganet::utils::to_tensorArray( - options, {1.0_r, 0.2_r, 0.1_r, 0.5_r, 0.9_r, 0.75_r, 0.0_r} /* v */, - {0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r, 0.0_r, 0.1_r} /* w */, - {0.1_r, 0.1_r, 0.2_r, 0.3_r, 0.3_r, 0.0_r, - 0.1_r} /* t */) /* west */, - iganet::utils::to_tensorArray( - options, {1.0_r, 0.2_r, 0.1_r, 0.5_r, 0.9_r, 0.75_r, 0.0_r} /* v */, - {0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r, 0.0_r, 0.1_r} /* w */, - {0.1_r, 0.1_r, 0.2_r, 0.3_r, 0.3_r, 0.0_r, - 0.1_r} /* t */) /* east */, - iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r} /* u */, - {0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r, 0.0_r, 0.1_r} /* w */, - {0.1_r, 0.1_r, 0.2_r, 0.3_r, 0.3_r, 0.0_r, - 0.1_r} /* t */) /* south */, - iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r} /* u */, - {0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r, 0.0_r, 0.1_r} /* w */, - {0.1_r, 0.1_r, 0.2_r, 0.3_r, 0.3_r, 0.0_r, - 0.1_r} /* t */) /* north */, - iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r} /* u */, - {1.0_r, 0.2_r, 0.1_r, 0.5_r, 0.9_r, 0.75_r, 0.0_r} /* v */, - {0.1_r, 0.1_r, 0.2_r, 0.3_r, 0.3_r, 0.0_r, - 0.1_r} /* t */) /* front */, - iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r} /* u */, - {1.0_r, 0.2_r, 0.1_r, 0.5_r, 0.9_r, 0.75_r, 0.0_r} /* v */, - {0.1_r, 0.1_r, 0.2_r, 0.3_r, 0.3_r, 0.0_r, - 0.1_r} /* t */) /* back */, - iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r} /* u */, - {1.0_r, 0.2_r, 0.1_r, 0.5_r, 0.9_r, 0.75_r, 0.0_r} /* v */, - {0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r, 0.0_r, - 0.1_r} /* w */) /* stime */, - iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r} /* u */, - {1.0_r, 0.2_r, 0.1_r, 0.5_r, 0.9_r, 0.75_r, 0.0_r} /* v */, - {0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r, 0.0_r, - 0.1_r} /* w */) /* etime */}; - - // Evaluation - auto eval = - functionspace.eval(xi); - - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrSE.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrSE.eval( - std::get(xi))[0]))); - - eval = functionspace.eval(xi); - - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrSE.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrSE.eval( - std::get(xi))[0]))); - - eval = - functionspace.eval(xi); - - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrSE.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrSE.eval( - std::get(xi))[0]))); - - eval = functionspace.eval(xi); - - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrSE.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrSE.eval( - std::get(xi))[0]))); - - eval = - functionspace.eval(xi); - - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrSE.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrSE.eval( - std::get(xi))[0]))); - - eval = functionspace.eval(xi); - - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrSE.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrSE.eval( - std::get(xi))[0]))); - - eval = - functionspace.eval(xi); - - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrSE.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrSE.eval( - std::get(xi))[0]))); - - eval = functionspace.eval(xi); - - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrSE.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrSE.eval( - std::get(xi))[0]))); - - eval = - functionspace.eval(xi); - - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrSE.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrSE.eval( - std::get(xi))[0]))); - - eval = functionspace - .eval(xi); - - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrSE.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrSE.eval( - std::get(xi))[0]))); - - eval = functionspace - .eval(xi); - - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrSE.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrSE.eval( - std::get(xi))[0]))); - - eval = functionspace - .eval(xi); - - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrSE.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrSE.eval( - std::get(xi))[0]))); - - eval = functionspace - .eval(xi); - - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrSE.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrSE.eval( - std::get(xi))[0]))); - - eval = functionspace - .eval(xi); - - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrSE.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrSE.eval( - std::get(xi))[0]))); - - eval = functionspace - .eval(xi); - - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrSE.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrSE.eval( - std::get(xi))[0]))); - - // Evaluation from precomputed coefficients and basis functions - auto knot_indices = - functionspace.template find_knot_indices(xi); - auto coeff_indices = - functionspace.template find_coeff_indices( - knot_indices); - - auto numel = [](const auto &xi) { - return std::tuple{std::get<0>(xi)[0].numel(), std::get<1>(xi)[0].numel(), - std::get<2>(xi)[0].numel(), std::get<3>(xi)[0].numel(), - std::get<4>(xi)[0].numel(), std::get<5>(xi)[0].numel(), - std::get<6>(xi)[0].numel(), std::get<7>(xi)[0].numel()}; - }; - auto sizes = [](const auto &xi) { - return std::tuple{std::get<0>(xi)[0].sizes(), std::get<1>(xi)[0].sizes(), - std::get<2>(xi)[0].sizes(), std::get<3>(xi)[0].sizes(), - std::get<4>(xi)[0].sizes(), std::get<5>(xi)[0].sizes(), - std::get<6>(xi)[0].sizes(), std::get<7>(xi)[0].sizes()}; - }; - - auto basfunc = - functionspace - .template eval_basfunc( - xi, knot_indices); - eval = - functionspace.template eval_from_precomputed( - basfunc, coeff_indices, numel(xi), sizes(xi)); - - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrSE.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrSE.eval( - std::get(xi))[0]))); - - basfunc = - functionspace - .template eval_basfunc( - xi, knot_indices); - eval = - functionspace.template eval_from_precomputed( - basfunc, coeff_indices, numel(xi), sizes(xi)); - - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrSE.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrSE.eval( - std::get(xi))[0]))); - - basfunc = functionspace.template eval_basfunc( - xi, knot_indices); - eval = - functionspace.template eval_from_precomputed( - basfunc, coeff_indices, numel(xi), sizes(xi)); - - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrSE.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrSE.eval( - std::get(xi))[0]))); - - basfunc = - functionspace - .template eval_basfunc( - xi, knot_indices); - eval = - functionspace.template eval_from_precomputed( - basfunc, coeff_indices, numel(xi), sizes(xi)); - - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrSE.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrSE.eval( - std::get(xi))[0]))); - - basfunc = functionspace.template eval_basfunc( - xi, knot_indices); - eval = - functionspace.template eval_from_precomputed( - basfunc, coeff_indices, numel(xi), sizes(xi)); - - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrSE.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrSE.eval( - std::get(xi))[0]))); - - basfunc = - functionspace - .template eval_basfunc( - xi, knot_indices); - eval = - functionspace.template eval_from_precomputed( - basfunc, coeff_indices, numel(xi), sizes(xi)); - - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrSE.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrSE.eval( - std::get(xi))[0]))); - - basfunc = functionspace.template eval_basfunc( - xi, knot_indices); - eval = - functionspace.template eval_from_precomputed( - basfunc, coeff_indices, numel(xi), sizes(xi)); - - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrSE.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrSE.eval( - std::get(xi))[0]))); - - basfunc = - functionspace - .template eval_basfunc( - xi, knot_indices); - eval = - functionspace.template eval_from_precomputed( - basfunc, coeff_indices, numel(xi), sizes(xi)); - - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrSE.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrSE.eval( - std::get(xi))[0]))); - - basfunc = functionspace.template eval_basfunc( - xi, knot_indices); - eval = - functionspace.template eval_from_precomputed( - basfunc, coeff_indices, numel(xi), sizes(xi)); - - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrSE.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrSE.eval( - std::get(xi))[0]))); - - basfunc = functionspace.template eval_basfunc( - xi, knot_indices); - eval = - functionspace.template eval_from_precomputed( - basfunc, coeff_indices, numel(xi), sizes(xi)); - - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrSE.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrSE.eval( - std::get(xi))[0]))); - - basfunc = functionspace.template eval_basfunc( - xi, knot_indices); - eval = - functionspace.template eval_from_precomputed( - basfunc, coeff_indices, numel(xi), sizes(xi)); - - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrSE.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrSE.eval( - std::get(xi))[0]))); - - basfunc = functionspace.template eval_basfunc( - xi, knot_indices); - eval = - functionspace.template eval_from_precomputed( - basfunc, coeff_indices, numel(xi), sizes(xi)); - - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrSE.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrSE.eval( - std::get(xi))[0]))); - - basfunc = functionspace.template eval_basfunc( - xi, knot_indices); - eval = - functionspace.template eval_from_precomputed( - basfunc, coeff_indices, numel(xi), sizes(xi)); - - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrSE.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrSE.eval( - std::get(xi))[0]))); - - basfunc = functionspace.template eval_basfunc( - xi, knot_indices); - eval = - functionspace.template eval_from_precomputed( - basfunc, coeff_indices, numel(xi), sizes(xi)); - - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrSE.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrSE.eval( - std::get(xi))[0]))); - - basfunc = functionspace.template eval_basfunc( - xi, knot_indices); - eval = - functionspace.template eval_from_precomputed( - basfunc, coeff_indices, numel(xi), sizes(xi)); - - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrNS.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrEW.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrFB.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrSE.eval( - std::get(xi))[0]))); - EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), - *(bspline_bdrSE.eval( - std::get(xi))[0]))); - - /// Evaluation of gradient (in parametric domain) - auto grad = functionspace.template grad(xi); - - for (std::size_t i = 0; i < std::get<0>(grad).entries(); ++i) { - EXPECT_TRUE(torch::equal( - *(std::get(grad)[i]), - *(bspline_bdrNS.grad(std::get(xi))[i]))); - EXPECT_TRUE(torch::equal( - *(std::get(grad)[i]), - *(bspline_bdrNS.grad(std::get(xi))[i]))); - EXPECT_TRUE(torch::equal( - *(std::get(grad)[i]), - *(bspline_bdrEW.grad(std::get(xi))[i]))); - EXPECT_TRUE(torch::equal( - *(std::get(grad)[i]), - *(bspline_bdrEW.grad(std::get(xi))[i]))); - EXPECT_TRUE(torch::equal( - *(std::get(grad)[i]), - *(bspline_bdrFB.grad(std::get(xi))[i]))); - EXPECT_TRUE(torch::equal( - *(std::get(grad)[i]), - *(bspline_bdrFB.grad(std::get(xi))[i]))); - EXPECT_TRUE(torch::equal( - *(std::get(grad)[i]), - *(bspline_bdrSE.grad(std::get(xi))[i]))); - EXPECT_TRUE(torch::equal( - *(std::get(grad)[i]), - *(bspline_bdrSE.grad(std::get(xi))[i]))); - } - - /// Evaluation of gradient (in physical domain) - auto igrad = functionspace.template igrad( - S_geometry, xi); - - for (std::size_t i = 0; i < std::get<0>(igrad).entries(); ++i) { - EXPECT_TRUE( - torch::equal(*(std::get(igrad)[i]), - *(bspline_bdrNS.igrad( - geometry_bdrNS, std::get(xi))[i]))); - EXPECT_TRUE( - torch::equal(*(std::get(igrad)[i]), - *(bspline_bdrNS.igrad( - geometry_bdrNS, std::get(xi))[i]))); - EXPECT_TRUE( - torch::equal(*(std::get(igrad)[i]), - *(bspline_bdrEW.igrad( - geometry_bdrEW, std::get(xi))[i]))); - EXPECT_TRUE( - torch::equal(*(std::get(igrad)[i]), - *(bspline_bdrEW.igrad( - geometry_bdrEW, std::get(xi))[i]))); - EXPECT_TRUE( - torch::equal(*(std::get(igrad)[i]), - *(bspline_bdrFB.igrad( - geometry_bdrFB, std::get(xi))[i]))); - EXPECT_TRUE( - torch::equal(*(std::get(igrad)[i]), - *(bspline_bdrFB.igrad( - geometry_bdrFB, std::get(xi))[i]))); - EXPECT_TRUE( - torch::equal(*(std::get(igrad)[i]), - *(bspline_bdrSE.igrad( - geometry_bdrSE, std::get(xi))[i]))); - EXPECT_TRUE( - torch::equal(*(std::get(igrad)[i]), - *(bspline_bdrSE.igrad( - geometry_bdrSE, std::get(xi))[i]))); - } - - /// Evaluation of Jacobian (in parametric domain) - auto jac = functionspace.template jac(xi); - - for (std::size_t i = 0; i < std::get<0>(jac).entries(); ++i) { - EXPECT_TRUE(torch::equal( - *(std::get(jac)[i]), - *(bspline_bdrNS.jac(std::get(xi))[i]))); - EXPECT_TRUE(torch::equal( - *(std::get(jac)[i]), - *(bspline_bdrNS.jac(std::get(xi))[i]))); - EXPECT_TRUE(torch::equal( - *(std::get(jac)[i]), - *(bspline_bdrEW.jac(std::get(xi))[i]))); - EXPECT_TRUE(torch::equal( - *(std::get(jac)[i]), - *(bspline_bdrEW.jac(std::get(xi))[i]))); - EXPECT_TRUE(torch::equal( - *(std::get(jac)[i]), - *(bspline_bdrFB.jac(std::get(xi))[i]))); - EXPECT_TRUE(torch::equal( - *(std::get(jac)[i]), - *(bspline_bdrFB.jac(std::get(xi))[i]))); - EXPECT_TRUE(torch::equal( - *(std::get(jac)[i]), - *(bspline_bdrSE.jac(std::get(xi))[i]))); - EXPECT_TRUE(torch::equal( - *(std::get(jac)[i]), - *(bspline_bdrSE.jac(std::get(xi))[i]))); - } - - /// Evaluation of Jacobian (in physical domain) - auto ijac = functionspace.template ijac( - S_geometry, xi); - - for (std::size_t i = 0; i < std::get<0>(ijac).entries(); ++i) { - EXPECT_TRUE( - torch::equal(*(std::get(ijac)[i]), - *(bspline_bdrNS.ijac( - geometry_bdrNS, std::get(xi))[i]))); - EXPECT_TRUE( - torch::equal(*(std::get(ijac)[i]), - *(bspline_bdrNS.ijac( - geometry_bdrNS, std::get(xi))[i]))); - EXPECT_TRUE( - torch::equal(*(std::get(ijac)[i]), - *(bspline_bdrEW.ijac( - geometry_bdrEW, std::get(xi))[i]))); - EXPECT_TRUE( - torch::equal(*(std::get(ijac)[i]), - *(bspline_bdrEW.ijac( - geometry_bdrEW, std::get(xi))[i]))); - EXPECT_TRUE( - torch::equal(*(std::get(ijac)[i]), - *(bspline_bdrFB.ijac( - geometry_bdrFB, std::get(xi))[i]))); - EXPECT_TRUE( - torch::equal(*(std::get(ijac)[i]), - *(bspline_bdrFB.ijac( - geometry_bdrFB, std::get(xi))[i]))); - EXPECT_TRUE( - torch::equal(*(std::get(ijac)[i]), - *(bspline_bdrSE.ijac( - geometry_bdrSE, std::get(xi))[i]))); - EXPECT_TRUE( - torch::equal(*(std::get(ijac)[i]), - *(bspline_bdrSE.ijac( - geometry_bdrSE, std::get(xi))[i]))); - } - - /// Evaluation of Hessian (in parametric domain) - auto hess = functionspace.template hess(xi); - - for (std::size_t i = 0; i < std::get<0>(hess).entries(); ++i) { - EXPECT_TRUE(torch::equal( - *(std::get(hess)[i]), - *(bspline_bdrNS.hess(std::get(xi))[i]))); - EXPECT_TRUE(torch::equal( - *(std::get(hess)[i]), - *(bspline_bdrNS.hess(std::get(xi))[i]))); - EXPECT_TRUE(torch::equal( - *(std::get(hess)[i]), - *(bspline_bdrEW.hess(std::get(xi))[i]))); - EXPECT_TRUE(torch::equal( - *(std::get(hess)[i]), - *(bspline_bdrEW.hess(std::get(xi))[i]))); - EXPECT_TRUE(torch::equal( - *(std::get(hess)[i]), - *(bspline_bdrFB.hess(std::get(xi))[i]))); - EXPECT_TRUE(torch::equal( - *(std::get(hess)[i]), - *(bspline_bdrFB.hess(std::get(xi))[i]))); - EXPECT_TRUE(torch::equal( - *(std::get(hess)[i]), - *(bspline_bdrSE.hess(std::get(xi))[i]))); - EXPECT_TRUE(torch::equal( - *(std::get(hess)[i]), - *(bspline_bdrSE.hess(std::get(xi))[i]))); - } - - /// Evaluation of Hessian (in physical domain) - auto ihess = - functionspace.template ihess(S_geometry, xi); - - for (std::size_t i = 0; i < std::get<0>(ihess).entries(); ++i) { - EXPECT_TRUE( - torch::equal(*(std::get(ihess)[i]), - *(bspline_bdrNS.ihess( - geometry_bdrNS, std::get(xi))[i]))); - EXPECT_TRUE( - torch::equal(*(std::get(ihess)[i]), - *(bspline_bdrNS.ihess( - geometry_bdrNS, std::get(xi))[i]))); - EXPECT_TRUE( - torch::equal(*(std::get(ihess)[i]), - *(bspline_bdrEW.ihess( - geometry_bdrEW, std::get(xi))[i]))); - EXPECT_TRUE( - torch::equal(*(std::get(ihess)[i]), - *(bspline_bdrEW.ihess( - geometry_bdrEW, std::get(xi))[i]))); - EXPECT_TRUE( - torch::equal(*(std::get(ihess)[i]), - *(bspline_bdrFB.ihess( - geometry_bdrFB, std::get(xi))[i]))); - EXPECT_TRUE( - torch::equal(*(std::get(ihess)[i]), - *(bspline_bdrFB.ihess( - geometry_bdrFB, std::get(xi))[i]))); - EXPECT_TRUE( - torch::equal(*(std::get(ihess)[i]), - *(bspline_bdrSE.ihess( - geometry_bdrSE, std::get(xi))[i]))); - EXPECT_TRUE( - torch::equal(*(std::get(ihess)[i]), - *(bspline_bdrSE.ihess( - geometry_bdrSE, std::get(xi))[i]))); - } - } -} - -TEST_F(FunctionSpaceTest, RT_geoDim1_degrees2) { - using iganet::deriv; - using iganet::functionspace; - using iganet::side; - using BSpline = iganet::NonUniformBSpline; - using Geometry = iganet::NonUniformBSpline; - iganet::RT functionspace({5}, iganet::init::greville, options); - iganet::RT RT_geometry({5}, iganet::init::greville, options); - - iganet::NonUniformBSpline bspline0( - {5 + 1}, iganet::init::greville, options); - iganet::NonUniformBSpline bspline1({5}, iganet::init::greville, - options); - iganet::NonUniformBSpline geometry0( - {5 + 1}, iganet::init::greville, options); - iganet::NonUniformBSpline geometry1({5}, iganet::init::greville, - options); - - { // Interior - - auto xi_ = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r} /* u */); - auto xi = std::tuple{xi_, xi_}; - - // Evaluation - auto eval = - functionspace.eval(xi); - - EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), - *(bspline0.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), - *(bspline1.eval(xi_)[0]))); - - eval = functionspace.eval(xi); - - EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), - *(bspline0.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), - *(bspline1.eval(xi_)[0]))); - - eval = - functionspace.eval(xi); - - EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), - *(bspline0.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), - *(bspline1.eval(xi_)[0]))); - - // Evaluation from precomputed coefficients and basis functions - auto knot_indices = - functionspace.template find_knot_indices(xi); - auto coeff_indices = - functionspace.template find_coeff_indices( - knot_indices); - - auto numel = [](const auto &xi) { - return std::tuple{std::get<0>(xi)[0].numel(), std::get<1>(xi)[0].numel()}; - }; - auto sizes = [](const auto &xi) { - return std::tuple{std::get<0>(xi)[0].sizes(), std::get<1>(xi)[0].sizes()}; - }; - - auto basfunc = - functionspace - .template eval_basfunc( - xi, knot_indices); - eval = functionspace.eval_from_precomputed( - basfunc, coeff_indices, numel(xi), sizes(xi)); - - EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), - *(bspline0.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), - *(bspline1.eval(xi_)[0]))); - - basfunc = - functionspace - .template eval_basfunc( - xi, knot_indices); - eval = functionspace.eval_from_precomputed( - basfunc, coeff_indices, numel(xi), sizes(xi)); - - EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), - *(bspline0.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), - *(bspline1.eval(xi_)[0]))); - - basfunc = functionspace.template eval_basfunc( - xi, knot_indices); - eval = functionspace.eval_from_precomputed( - basfunc, coeff_indices, numel(xi), sizes(xi)); - - EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), - *(bspline0.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), - *(bspline1.eval(xi_)[0]))); - - /// Evaluation of gradient (in parametric domain) - auto grad = - functionspace.template grad_all(xi); - auto grad_ref0 = bspline0.grad(xi_); - auto grad_ref1 = bspline1.grad(xi_); - - for (std::size_t i = 0; i < grad_ref0.entries(); ++i) { - EXPECT_TRUE(torch::equal(*(std::get<0>(grad)[i]), *(grad_ref0)[i])); - EXPECT_TRUE(torch::equal(*(std::get<1>(grad)[i]), *(grad_ref1)[i])); - } - - /// Evaluation of gradient (in physical domain) - auto igrad = - functionspace.template igrad_all( - RT_geometry, xi); - auto igrad_ref0 = bspline0.igrad(geometry0, xi_); - auto igrad_ref1 = bspline1.igrad(geometry1, xi_); - - for (std::size_t i = 0; i < igrad_ref0.entries(); ++i) { - EXPECT_TRUE(torch::equal(*(std::get<0>(igrad)[i]), *(igrad_ref0)[i])); - EXPECT_TRUE(torch::equal(*(std::get<1>(igrad)[i]), *(igrad_ref1)[i])); - } - - /// Evaluation of Jacobian (in parametric domain) - auto jac = - functionspace.template jac_all(xi); - auto jac_ref0 = bspline0.jac(xi_); - auto jac_ref1 = bspline1.jac(xi_); - - for (std::size_t i = 0; i < jac_ref0.entries(); ++i) { - EXPECT_TRUE(torch::equal(*(std::get<0>(jac)[i]), *(jac_ref0)[i])); - EXPECT_TRUE(torch::equal(*(std::get<1>(jac)[i]), *(jac_ref1)[i])); - } - - /// Evaluation of Jacobian (in physical domain) - auto ijac = functionspace.template ijac_all( - RT_geometry, xi); - auto ijac_ref0 = bspline0.ijac(geometry0, xi_); - auto ijac_ref1 = bspline1.ijac(geometry1, xi_); - - for (std::size_t i = 0; i < ijac_ref0.entries(); ++i) { - EXPECT_TRUE(torch::equal(*(std::get<0>(ijac)[i]), *(ijac_ref0)[i])); - EXPECT_TRUE(torch::equal(*(std::get<1>(ijac)[i]), *(ijac_ref1)[i])); - } - - /// Evaluation of Hessian (in parametric domain) - auto hess = - functionspace.template hess_all(xi); - auto hess_ref0 = bspline0.hess(xi_); - auto hess_ref1 = bspline1.hess(xi_); - - for (std::size_t i = 0; i < hess_ref0.entries(); ++i) { - EXPECT_TRUE(torch::equal(*(std::get<0>(hess)[i]), *(hess_ref0)[i])); - EXPECT_TRUE(torch::equal(*(std::get<1>(hess)[i]), *(hess_ref1)[i])); - } - - /// Evaluation of Hessian (in physical domain) - auto ihess = - functionspace.template ihess_all( - RT_geometry, xi); - auto ihess_ref0 = bspline0.ihess(geometry0, xi_); - auto ihess_ref1 = bspline1.ihess(geometry1, xi_); - - for (std::size_t i = 0; i < ihess_ref0.entries(); ++i) { - EXPECT_TRUE(torch::equal(*(std::get<0>(ihess)[i]), *(ihess_ref0)[i])); - EXPECT_TRUE(torch::equal(*(std::get<1>(ihess)[i]), *(ihess_ref1)[i])); - } - } -} - -TEST_F(FunctionSpaceTest, RT_geoDim1_degrees23) { - using iganet::deriv; - using iganet::functionspace; - using iganet::side; - using BSpline = iganet::NonUniformBSpline; - using Geometry = iganet::NonUniformBSpline; - iganet::RT functionspace({5, 6}, iganet::init::greville, options); - iganet::RT RT_geometry({5, 6}, iganet::init::greville, options); - - iganet::NonUniformBSpline bspline0( - {5 + 1, 6}, iganet::init::greville, options); - iganet::NonUniformBSpline bspline1( - {5, 6 + 1}, iganet::init::greville, options); - iganet::NonUniformBSpline bspline2( - {5, 6}, iganet::init::greville, options); - iganet::NonUniformBSpline geometry0( - {5 + 1, 6}, iganet::init::greville, options); - iganet::NonUniformBSpline geometry1( - {5, 6 + 1}, iganet::init::greville, options); - iganet::NonUniformBSpline geometry2( - {5, 6}, iganet::init::greville, options); - - { // Interior - - auto xi_ = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r} /* u */, - {1.0_r, 0.2_r, 0.1_r, 0.5_r, 0.9_r, 0.75_r, 0.0_r} /* v */); - auto xi = std::tuple{xi_, xi_, xi_}; - - // Evaluation - auto eval = - functionspace.eval(xi); - - EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), - *(bspline0.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), - *(bspline1.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<2>(eval)[0]), - *(bspline2.eval(xi_)[0]))); - - eval = functionspace.eval(xi); - - EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), - *(bspline0.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), - *(bspline1.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<2>(eval)[0]), - *(bspline2.eval(xi_)[0]))); - - eval = - functionspace.eval(xi); - - EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), - *(bspline0.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), - *(bspline1.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<2>(eval)[0]), - *(bspline2.eval(xi_)[0]))); - - eval = functionspace.eval(xi); - - EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), - *(bspline0.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), - *(bspline1.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<2>(eval)[0]), - *(bspline2.eval(xi_)[0]))); - - eval = - functionspace.eval(xi); - - EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), - *(bspline0.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), - *(bspline1.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<2>(eval)[0]), - *(bspline2.eval(xi_)[0]))); - - eval = functionspace - .eval(xi); - - EXPECT_TRUE( - torch::equal(*(std::get<0>(eval)[0]), - *(bspline0.eval(xi_)[0]))); - EXPECT_TRUE( - torch::equal(*(std::get<1>(eval)[0]), - *(bspline1.eval(xi_)[0]))); - EXPECT_TRUE( - torch::equal(*(std::get<2>(eval)[0]), - *(bspline2.eval(xi_)[0]))); - - // Evaluation from precomputed coefficients and basis functions - auto knot_indices = - functionspace.template find_knot_indices(xi); - auto coeff_indices = - functionspace.template find_coeff_indices( - knot_indices); - - auto numel = [](const auto &xi) { - return std::tuple{std::get<0>(xi)[0].numel(), std::get<1>(xi)[0].numel(), - std::get<2>(xi)[0].numel()}; - }; - auto sizes = [](const auto &xi) { - return std::tuple{std::get<0>(xi)[0].sizes(), std::get<1>(xi)[0].sizes(), - std::get<2>(xi)[0].sizes()}; - }; - - auto basfunc = - functionspace - .template eval_basfunc( - xi, knot_indices); - eval = functionspace.eval_from_precomputed( - basfunc, coeff_indices, numel(xi), sizes(xi)); - - EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), - *(bspline0.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), - *(bspline1.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<2>(eval)[0]), - *(bspline2.eval(xi_)[0]))); - - basfunc = - functionspace - .template eval_basfunc( - xi, knot_indices); - eval = functionspace.eval_from_precomputed( - basfunc, coeff_indices, numel(xi), sizes(xi)); - - EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), - *(bspline0.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), - *(bspline1.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<2>(eval)[0]), - *(bspline2.eval(xi_)[0]))); - - basfunc = functionspace.template eval_basfunc( - xi, knot_indices); - eval = functionspace.eval_from_precomputed( - basfunc, coeff_indices, numel(xi), sizes(xi)); - - EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), - *(bspline0.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), - *(bspline1.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<2>(eval)[0]), - *(bspline2.eval(xi_)[0]))); - - basfunc = - functionspace - .template eval_basfunc( - xi, knot_indices); - eval = functionspace.eval_from_precomputed( - basfunc, coeff_indices, numel(xi), sizes(xi)); - - EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), - *(bspline0.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), - *(bspline1.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<2>(eval)[0]), - *(bspline2.eval(xi_)[0]))); - - basfunc = functionspace.template eval_basfunc( - xi, knot_indices); - eval = functionspace.eval_from_precomputed( - basfunc, coeff_indices, numel(xi), sizes(xi)); - - EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), - *(bspline0.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), - *(bspline1.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<2>(eval)[0]), - *(bspline2.eval(xi_)[0]))); - - basfunc = functionspace.template eval_basfunc( - xi, knot_indices); - eval = functionspace.eval_from_precomputed( - basfunc, coeff_indices, numel(xi), sizes(xi)); - - EXPECT_TRUE( - torch::equal(*(std::get<0>(eval)[0]), - *(bspline0.eval(xi_)[0]))); - EXPECT_TRUE( - torch::equal(*(std::get<1>(eval)[0]), - *(bspline1.eval(xi_)[0]))); - EXPECT_TRUE( - torch::equal(*(std::get<2>(eval)[0]), - *(bspline2.eval(xi_)[0]))); - - /// Evaluation of gradient (in parametric domain) - auto grad = - functionspace.template grad_all(xi); - auto grad_ref0 = bspline0.grad(xi_); - auto grad_ref1 = bspline1.grad(xi_); - auto grad_ref2 = bspline2.grad(xi_); - - for (std::size_t i = 0; i < grad_ref0.entries(); ++i) { - EXPECT_TRUE(torch::equal(*(std::get<0>(grad)[i]), *(grad_ref0)[i])); - EXPECT_TRUE(torch::equal(*(std::get<1>(grad)[i]), *(grad_ref1)[i])); - EXPECT_TRUE(torch::equal(*(std::get<2>(grad)[i]), *(grad_ref2)[i])); - } - - /// Evaluation of gradient (in physical domain) - auto igrad = - functionspace.template igrad_all( - RT_geometry, xi); - auto igrad_ref0 = bspline0.igrad(geometry0, xi_); - auto igrad_ref1 = bspline1.igrad(geometry1, xi_); - auto igrad_ref2 = bspline2.igrad(geometry2, xi_); - - for (std::size_t i = 0; i < igrad_ref0.entries(); ++i) { - EXPECT_TRUE(torch::equal(*(std::get<0>(igrad)[i]), *(igrad_ref0)[i])); - EXPECT_TRUE(torch::equal(*(std::get<1>(igrad)[i]), *(igrad_ref1)[i])); - EXPECT_TRUE(torch::equal(*(std::get<2>(igrad)[i]), *(igrad_ref2)[i])); - } - - /// Evaluation of Jacobian (in parametric domain) - auto jac = - functionspace.template jac_all(xi); - auto jac_ref0 = bspline0.jac(xi_); - auto jac_ref1 = bspline1.jac(xi_); - auto jac_ref2 = bspline2.jac(xi_); - - for (std::size_t i = 0; i < jac_ref0.entries(); ++i) { - EXPECT_TRUE(torch::equal(*(std::get<0>(jac)[i]), *(jac_ref0)[i])); - EXPECT_TRUE(torch::equal(*(std::get<1>(jac)[i]), *(jac_ref1)[i])); - EXPECT_TRUE(torch::equal(*(std::get<2>(jac)[i]), *(jac_ref2)[i])); - } - - /// Evaluation of Jacobian (in physical domain) - auto ijac = functionspace.template ijac_all( - RT_geometry, xi); - auto ijac_ref0 = bspline0.ijac(geometry0, xi_); - auto ijac_ref1 = bspline1.ijac(geometry1, xi_); - auto ijac_ref2 = bspline2.ijac(geometry2, xi_); - - for (std::size_t i = 0; i < ijac_ref0.entries(); ++i) { - EXPECT_TRUE(torch::equal(*(std::get<0>(ijac)[i]), *(ijac_ref0)[i])); - EXPECT_TRUE(torch::equal(*(std::get<1>(ijac)[i]), *(ijac_ref1)[i])); - EXPECT_TRUE(torch::equal(*(std::get<2>(ijac)[i]), *(ijac_ref2)[i])); - } - - /// Evaluation of Hessian (in parametric domain) - auto hess = - functionspace.template hess_all(xi); - auto hess_ref0 = bspline0.hess(xi_); - auto hess_ref1 = bspline1.hess(xi_); - auto hess_ref2 = bspline2.hess(xi_); - - for (std::size_t i = 0; i < hess_ref0.entries(); ++i) { - EXPECT_TRUE(torch::equal(*(std::get<0>(hess)[i]), *(hess_ref0)[i])); - EXPECT_TRUE(torch::equal(*(std::get<1>(hess)[i]), *(hess_ref1)[i])); - EXPECT_TRUE(torch::equal(*(std::get<2>(hess)[i]), *(hess_ref2)[i])); - } - - /// Evaluation of Hessian (in physical domain) - auto ihess = - functionspace.template ihess_all( - RT_geometry, xi); - auto ihess_ref0 = bspline0.ihess(geometry0, xi_); - auto ihess_ref1 = bspline1.ihess(geometry1, xi_); - auto ihess_ref2 = bspline2.ihess(geometry2, xi_); - - for (std::size_t i = 0; i < ihess_ref0.entries(); ++i) { - EXPECT_TRUE(torch::equal(*(std::get<0>(ihess)[i]), *(ihess_ref0)[i])); - EXPECT_TRUE(torch::equal(*(std::get<1>(ihess)[i]), *(ihess_ref1)[i])); - EXPECT_TRUE(torch::equal(*(std::get<2>(ihess)[i]), *(ihess_ref2)[i])); - } - } -} - -TEST_F(FunctionSpaceTest, RT_geoDim1_degrees234) { - using iganet::deriv; - using iganet::functionspace; - using iganet::side; - using BSpline = iganet::NonUniformBSpline; - using Geometry = iganet::NonUniformBSpline; - iganet::RT functionspace({5, 6, 7}, iganet::init::greville, options); - iganet::RT RT_geometry({5, 6, 7}, iganet::init::greville, options); - - iganet::NonUniformBSpline bspline0( - {5 + 1, 6, 7}, iganet::init::greville, options); - iganet::NonUniformBSpline bspline1( - {5, 6 + 1, 7}, iganet::init::greville, options); - iganet::NonUniformBSpline bspline2( - {5, 6, 7 + 1}, iganet::init::greville, options); - iganet::NonUniformBSpline bspline3( - {5, 6, 7}, iganet::init::greville, options); - iganet::NonUniformBSpline geometry0( - {5 + 1, 6, 7}, iganet::init::greville, options); - iganet::NonUniformBSpline geometry1( - {5, 6 + 1, 7}, iganet::init::greville, options); - iganet::NonUniformBSpline geometry2( - {5, 6, 7 + 1}, iganet::init::greville, options); - iganet::NonUniformBSpline geometry3( - {5, 6, 7}, iganet::init::greville, options); - - { // Interior - - auto xi_ = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r} /* u */, - {1.0_r, 0.2_r, 0.1_r, 0.5_r, 0.9_r, 0.75_r, 0.0_r} /* v */, - {0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r, 0.0_r, 0.1_r} /* w */); - auto xi = std::tuple{xi_, xi_, xi_, xi_}; - - // Evaluation - auto eval = - functionspace.eval(xi); - - EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), - *(bspline0.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), - *(bspline1.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<2>(eval)[0]), - *(bspline2.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<3>(eval)[0]), - *(bspline3.eval(xi_)[0]))); - - eval = functionspace.eval(xi); - - EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), - *(bspline0.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), - *(bspline1.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<2>(eval)[0]), - *(bspline2.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<3>(eval)[0]), - *(bspline3.eval(xi_)[0]))); - - eval = - functionspace.eval(xi); - - EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), - *(bspline0.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), - *(bspline1.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<2>(eval)[0]), - *(bspline2.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<3>(eval)[0]), - *(bspline3.eval(xi_)[0]))); - - eval = functionspace.eval(xi); - - EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), - *(bspline0.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), - *(bspline1.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<2>(eval)[0]), - *(bspline2.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<3>(eval)[0]), - *(bspline3.eval(xi_)[0]))); - - eval = - functionspace.eval(xi); - - EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), - *(bspline0.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), - *(bspline1.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<2>(eval)[0]), - *(bspline2.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<3>(eval)[0]), - *(bspline3.eval(xi_)[0]))); - - eval = functionspace.eval(xi); - - EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), - *(bspline0.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), - *(bspline1.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<2>(eval)[0]), - *(bspline2.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<3>(eval)[0]), - *(bspline3.eval(xi_)[0]))); - - eval = - functionspace.eval(xi); - - EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), - *(bspline0.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), - *(bspline1.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<2>(eval)[0]), - *(bspline2.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<3>(eval)[0]), - *(bspline3.eval(xi_)[0]))); - - eval = functionspace - .eval(xi); - - EXPECT_TRUE( - torch::equal(*(std::get<0>(eval)[0]), - *(bspline0.eval(xi_)[0]))); - EXPECT_TRUE( - torch::equal(*(std::get<1>(eval)[0]), - *(bspline1.eval(xi_)[0]))); - EXPECT_TRUE( - torch::equal(*(std::get<2>(eval)[0]), - *(bspline2.eval(xi_)[0]))); - EXPECT_TRUE( - torch::equal(*(std::get<3>(eval)[0]), - *(bspline3.eval(xi_)[0]))); - - eval = functionspace - .eval(xi); - - EXPECT_TRUE( - torch::equal(*(std::get<0>(eval)[0]), - *(bspline0.eval(xi_)[0]))); - EXPECT_TRUE( - torch::equal(*(std::get<1>(eval)[0]), - *(bspline1.eval(xi_)[0]))); - EXPECT_TRUE( - torch::equal(*(std::get<2>(eval)[0]), - *(bspline2.eval(xi_)[0]))); - EXPECT_TRUE( - torch::equal(*(std::get<3>(eval)[0]), - *(bspline3.eval(xi_)[0]))); - - eval = functionspace - .eval(xi); - - EXPECT_TRUE( - torch::equal(*(std::get<0>(eval)[0]), - *(bspline0.eval(xi_)[0]))); - EXPECT_TRUE( - torch::equal(*(std::get<1>(eval)[0]), - *(bspline1.eval(xi_)[0]))); - EXPECT_TRUE( - torch::equal(*(std::get<2>(eval)[0]), - *(bspline2.eval(xi_)[0]))); - EXPECT_TRUE( - torch::equal(*(std::get<3>(eval)[0]), - *(bspline3.eval(xi_)[0]))); - - // Evaluation from precomputed coefficients and basis functions - auto knot_indices = - functionspace.template find_knot_indices(xi); - auto coeff_indices = - functionspace.template find_coeff_indices( - knot_indices); - - auto numel = [](const auto &xi) { - return std::tuple{std::get<0>(xi)[0].numel(), std::get<1>(xi)[0].numel(), - std::get<2>(xi)[0].numel(), std::get<3>(xi)[0].numel()}; - }; - auto sizes = [](const auto &xi) { - return std::tuple{std::get<0>(xi)[0].sizes(), std::get<1>(xi)[0].sizes(), - std::get<2>(xi)[0].sizes(), std::get<3>(xi)[0].sizes()}; - }; - - auto basfunc = - functionspace - .template eval_basfunc( - xi, knot_indices); - eval = functionspace.eval_from_precomputed( - basfunc, coeff_indices, numel(xi), sizes(xi)); - - EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), - *(bspline0.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), - *(bspline1.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<2>(eval)[0]), - *(bspline2.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<3>(eval)[0]), - *(bspline3.eval(xi_)[0]))); - - basfunc = - functionspace - .template eval_basfunc( - xi, knot_indices); - eval = functionspace.eval_from_precomputed( - basfunc, coeff_indices, numel(xi), sizes(xi)); - - EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), - *(bspline0.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), - *(bspline1.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<2>(eval)[0]), - *(bspline2.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<3>(eval)[0]), - *(bspline3.eval(xi_)[0]))); - - basfunc = functionspace.template eval_basfunc( - xi, knot_indices); - eval = functionspace.eval_from_precomputed( - basfunc, coeff_indices, numel(xi), sizes(xi)); - - EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), - *(bspline0.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), - *(bspline1.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<2>(eval)[0]), - *(bspline2.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<3>(eval)[0]), - *(bspline3.eval(xi_)[0]))); - - basfunc = - functionspace - .template eval_basfunc( - xi, knot_indices); - eval = functionspace.eval_from_precomputed( - basfunc, coeff_indices, numel(xi), sizes(xi)); - - EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), - *(bspline0.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), - *(bspline1.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<2>(eval)[0]), - *(bspline2.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<3>(eval)[0]), - *(bspline3.eval(xi_)[0]))); - - basfunc = functionspace.template eval_basfunc( - xi, knot_indices); - eval = functionspace.eval_from_precomputed( - basfunc, coeff_indices, numel(xi), sizes(xi)); - - EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), - *(bspline0.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), - *(bspline1.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<2>(eval)[0]), - *(bspline2.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<3>(eval)[0]), - *(bspline3.eval(xi_)[0]))); - - basfunc = - functionspace - .template eval_basfunc( - xi, knot_indices); - eval = functionspace.eval_from_precomputed( - basfunc, coeff_indices, numel(xi), sizes(xi)); - - EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), - *(bspline0.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), - *(bspline1.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<2>(eval)[0]), - *(bspline2.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<3>(eval)[0]), - *(bspline3.eval(xi_)[0]))); - - basfunc = functionspace.template eval_basfunc( - xi, knot_indices); - eval = functionspace.eval_from_precomputed( - basfunc, coeff_indices, numel(xi), sizes(xi)); - - EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), - *(bspline0.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), - *(bspline1.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<2>(eval)[0]), - *(bspline2.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<3>(eval)[0]), - *(bspline3.eval(xi_)[0]))); - - basfunc = functionspace.template eval_basfunc( - xi, knot_indices); - eval = functionspace.eval_from_precomputed( - basfunc, coeff_indices, numel(xi), sizes(xi)); - - EXPECT_TRUE( - torch::equal(*(std::get<0>(eval)[0]), - *(bspline0.eval(xi_)[0]))); - EXPECT_TRUE( - torch::equal(*(std::get<1>(eval)[0]), - *(bspline1.eval(xi_)[0]))); - EXPECT_TRUE( - torch::equal(*(std::get<2>(eval)[0]), - *(bspline2.eval(xi_)[0]))); - EXPECT_TRUE( - torch::equal(*(std::get<3>(eval)[0]), - *(bspline3.eval(xi_)[0]))); - - basfunc = functionspace.template eval_basfunc( - xi, knot_indices); - eval = functionspace.eval_from_precomputed( - basfunc, coeff_indices, numel(xi), sizes(xi)); - - EXPECT_TRUE( - torch::equal(*(std::get<0>(eval)[0]), - *(bspline0.eval(xi_)[0]))); - EXPECT_TRUE( - torch::equal(*(std::get<1>(eval)[0]), - *(bspline1.eval(xi_)[0]))); - EXPECT_TRUE( - torch::equal(*(std::get<2>(eval)[0]), - *(bspline2.eval(xi_)[0]))); - EXPECT_TRUE( - torch::equal(*(std::get<3>(eval)[0]), - *(bspline3.eval(xi_)[0]))); - - basfunc = functionspace.template eval_basfunc( - xi, knot_indices); - eval = functionspace.eval_from_precomputed( - basfunc, coeff_indices, numel(xi), sizes(xi)); - - EXPECT_TRUE( - torch::equal(*(std::get<0>(eval)[0]), - *(bspline0.eval(xi_)[0]))); - EXPECT_TRUE( - torch::equal(*(std::get<1>(eval)[0]), - *(bspline1.eval(xi_)[0]))); - EXPECT_TRUE( - torch::equal(*(std::get<2>(eval)[0]), - *(bspline2.eval(xi_)[0]))); - EXPECT_TRUE( - torch::equal(*(std::get<3>(eval)[0]), - *(bspline3.eval(xi_)[0]))); - - /// Evaluation of gradient (in parametric domain) - auto grad = - functionspace.template grad_all(xi); - auto grad_ref0 = bspline0.grad(xi_); - auto grad_ref1 = bspline1.grad(xi_); - auto grad_ref2 = bspline2.grad(xi_); - auto grad_ref3 = bspline3.grad(xi_); - - for (std::size_t i = 0; i < grad_ref0.entries(); ++i) { - EXPECT_TRUE(torch::equal(*(std::get<0>(grad)[i]), *(grad_ref0)[i])); - EXPECT_TRUE(torch::equal(*(std::get<1>(grad)[i]), *(grad_ref1)[i])); - EXPECT_TRUE(torch::equal(*(std::get<2>(grad)[i]), *(grad_ref2)[i])); - EXPECT_TRUE(torch::equal(*(std::get<3>(grad)[i]), *(grad_ref3)[i])); - } - - /// Evaluation of gradient (in physical domain) - auto igrad = - functionspace.template igrad_all( - RT_geometry, xi); - auto igrad_ref0 = bspline0.igrad(geometry0, xi_); - auto igrad_ref1 = bspline1.igrad(geometry1, xi_); - auto igrad_ref2 = bspline2.igrad(geometry2, xi_); - auto igrad_ref3 = bspline3.igrad(geometry3, xi_); - - for (std::size_t i = 0; i < igrad_ref0.entries(); ++i) { - EXPECT_TRUE(torch::equal(*(std::get<0>(igrad)[i]), *(igrad_ref0)[i])); - EXPECT_TRUE(torch::equal(*(std::get<1>(igrad)[i]), *(igrad_ref1)[i])); - EXPECT_TRUE(torch::equal(*(std::get<2>(igrad)[i]), *(igrad_ref2)[i])); - EXPECT_TRUE(torch::equal(*(std::get<3>(igrad)[i]), *(igrad_ref3)[i])); - } - - /// Evaluation of Jacobian (in parametric domain) - auto jac = - functionspace.template jac_all(xi); - auto jac_ref0 = bspline0.jac(xi_); - auto jac_ref1 = bspline1.jac(xi_); - auto jac_ref2 = bspline2.jac(xi_); - auto jac_ref3 = bspline3.jac(xi_); - - for (std::size_t i = 0; i < jac_ref0.entries(); ++i) { - EXPECT_TRUE(torch::equal(*(std::get<0>(jac)[i]), *(jac_ref0)[i])); - EXPECT_TRUE(torch::equal(*(std::get<1>(jac)[i]), *(jac_ref1)[i])); - EXPECT_TRUE(torch::equal(*(std::get<2>(jac)[i]), *(jac_ref2)[i])); - EXPECT_TRUE(torch::equal(*(std::get<3>(jac)[i]), *(jac_ref3)[i])); - } - - /// Evaluation of Jacobian (in physical domain) - auto ijac = functionspace.template ijac_all( - RT_geometry, xi); - auto ijac_ref0 = bspline0.ijac(geometry0, xi_); - auto ijac_ref1 = bspline1.ijac(geometry1, xi_); - auto ijac_ref2 = bspline2.ijac(geometry2, xi_); - auto ijac_ref3 = bspline3.ijac(geometry3, xi_); - - for (std::size_t i = 0; i < ijac_ref0.entries(); ++i) { - EXPECT_TRUE(torch::equal(*(std::get<0>(ijac)[i]), *(ijac_ref0)[i])); - EXPECT_TRUE(torch::equal(*(std::get<1>(ijac)[i]), *(ijac_ref1)[i])); - EXPECT_TRUE(torch::equal(*(std::get<2>(ijac)[i]), *(ijac_ref2)[i])); - EXPECT_TRUE(torch::equal(*(std::get<3>(ijac)[i]), *(ijac_ref3)[i])); - } - - /// Evaluation of Hessian (in parametric domain) - auto hess = - functionspace.template hess_all(xi); - auto hess_ref0 = bspline0.hess(xi_); - auto hess_ref1 = bspline1.hess(xi_); - auto hess_ref2 = bspline2.hess(xi_); - auto hess_ref3 = bspline3.hess(xi_); - - for (std::size_t i = 0; i < hess_ref0.entries(); ++i) { - EXPECT_TRUE(torch::equal(*(std::get<0>(hess)[i]), *(hess_ref0)[i])); - EXPECT_TRUE(torch::equal(*(std::get<1>(hess)[i]), *(hess_ref1)[i])); - EXPECT_TRUE(torch::equal(*(std::get<2>(hess)[i]), *(hess_ref2)[i])); - EXPECT_TRUE(torch::equal(*(std::get<3>(hess)[i]), *(hess_ref3)[i])); - } - - /// Evaluation of Hessian (in physical domain) - auto ihess = - functionspace.template ihess_all( - RT_geometry, xi); - auto ihess_ref0 = bspline0.ihess(geometry0, xi_); - auto ihess_ref1 = bspline1.ihess(geometry1, xi_); - auto ihess_ref2 = bspline2.ihess(geometry2, xi_); - auto ihess_ref3 = bspline3.ihess(geometry3, xi_); - - for (std::size_t i = 0; i < ihess_ref0.entries(); ++i) { - EXPECT_TRUE(torch::equal(*(std::get<0>(ihess)[i]), *(ihess_ref0)[i])); - EXPECT_TRUE(torch::equal(*(std::get<1>(ihess)[i]), *(ihess_ref1)[i])); - EXPECT_TRUE(torch::equal(*(std::get<2>(ihess)[i]), *(ihess_ref2)[i])); - EXPECT_TRUE(torch::equal(*(std::get<3>(ihess)[i]), *(ihess_ref3)[i])); - } - } -} - -TEST_F(FunctionSpaceTest, RT_geoDim1_degrees2341) { - using iganet::deriv; - using iganet::functionspace; - using iganet::side; - using BSpline = iganet::NonUniformBSpline; - using Geometry = iganet::NonUniformBSpline; - iganet::RT functionspace({5, 6, 7, 4}, iganet::init::greville, - options); - iganet::RT RT_geometry({5, 6, 7, 4}, iganet::init::greville, - options); - - iganet::NonUniformBSpline bspline0( - {5 + 1, 6, 7, 4}, iganet::init::greville, options); - iganet::NonUniformBSpline bspline1( - {5, 6 + 1, 7, 4}, iganet::init::greville, options); - iganet::NonUniformBSpline bspline2( - {5, 6, 7 + 1, 4}, iganet::init::greville, options); - iganet::NonUniformBSpline bspline3( - {5, 6, 7, 4 + 1}, iganet::init::greville, options); - iganet::NonUniformBSpline bspline4( - {5, 6, 7, 4}, iganet::init::greville, options); - iganet::NonUniformBSpline geometry0( - {5 + 1, 6, 7, 4}, iganet::init::greville, options); - iganet::NonUniformBSpline geometry1( - {5, 6 + 1, 7, 4}, iganet::init::greville, options); - iganet::NonUniformBSpline geometry2( - {5, 6, 7 + 1, 4}, iganet::init::greville, options); - iganet::NonUniformBSpline geometry3( - {5, 6, 7, 4 + 1}, iganet::init::greville, options); - iganet::NonUniformBSpline geometry4( - {5, 6, 7, 4}, iganet::init::greville, options); - - { // Interior - - auto xi_ = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r} /* u */, - {1.0_r, 0.2_r, 0.1_r, 0.5_r, 0.9_r, 0.75_r, 0.0_r} /* v */, - {0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r, 0.0_r, 0.1_r} /* w */, - {0.1_r, 0.1_r, 0.2_r, 0.3_r, 0.3_r, 0.0_r, 0.1_r} /* t */); - auto xi = std::tuple{xi_, xi_, xi_, xi_, xi_}; - - // Evaluation - auto eval = - functionspace.eval(xi); - - EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), - *(bspline0.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), - *(bspline1.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<2>(eval)[0]), - *(bspline2.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<3>(eval)[0]), - *(bspline3.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<4>(eval)[0]), - *(bspline4.eval(xi_)[0]))); - - eval = functionspace.eval(xi); - - EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), - *(bspline0.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), - *(bspline1.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<2>(eval)[0]), - *(bspline2.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<3>(eval)[0]), - *(bspline3.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<4>(eval)[0]), - *(bspline4.eval(xi_)[0]))); - - eval = - functionspace.eval(xi); - - EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), - *(bspline0.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), - *(bspline1.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<2>(eval)[0]), - *(bspline2.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<3>(eval)[0]), - *(bspline3.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<4>(eval)[0]), - *(bspline4.eval(xi_)[0]))); - - eval = functionspace.eval(xi); - - EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), - *(bspline0.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), - *(bspline1.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<2>(eval)[0]), - *(bspline2.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<3>(eval)[0]), - *(bspline3.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<4>(eval)[0]), - *(bspline4.eval(xi_)[0]))); - - eval = - functionspace.eval(xi); - - EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), - *(bspline0.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), - *(bspline1.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<2>(eval)[0]), - *(bspline2.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<3>(eval)[0]), - *(bspline3.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<4>(eval)[0]), - *(bspline4.eval(xi_)[0]))); - - eval = functionspace.eval(xi); - - EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), - *(bspline0.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), - *(bspline1.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<2>(eval)[0]), - *(bspline2.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<3>(eval)[0]), - *(bspline3.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<4>(eval)[0]), - *(bspline4.eval(xi_)[0]))); - - eval = - functionspace.eval(xi); - - EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), - *(bspline0.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), - *(bspline1.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<2>(eval)[0]), - *(bspline2.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<3>(eval)[0]), - *(bspline3.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<4>(eval)[0]), - *(bspline4.eval(xi_)[0]))); - - eval = functionspace.eval(xi); - - EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), - *(bspline0.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), - *(bspline1.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<2>(eval)[0]), - *(bspline2.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<3>(eval)[0]), - *(bspline3.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<4>(eval)[0]), - *(bspline4.eval(xi_)[0]))); - - eval = - functionspace.eval(xi); - - EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), - *(bspline0.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), - *(bspline1.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<2>(eval)[0]), - *(bspline2.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<3>(eval)[0]), - *(bspline3.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<4>(eval)[0]), - *(bspline4.eval(xi_)[0]))); - - eval = functionspace - .eval(xi); - - EXPECT_TRUE( - torch::equal(*(std::get<0>(eval)[0]), - *(bspline0.eval(xi_)[0]))); - EXPECT_TRUE( - torch::equal(*(std::get<1>(eval)[0]), - *(bspline1.eval(xi_)[0]))); - EXPECT_TRUE( - torch::equal(*(std::get<2>(eval)[0]), - *(bspline2.eval(xi_)[0]))); - EXPECT_TRUE( - torch::equal(*(std::get<3>(eval)[0]), - *(bspline3.eval(xi_)[0]))); - EXPECT_TRUE( - torch::equal(*(std::get<4>(eval)[0]), - *(bspline4.eval(xi_)[0]))); - - eval = functionspace - .eval(xi); - - EXPECT_TRUE( - torch::equal(*(std::get<0>(eval)[0]), - *(bspline0.eval(xi_)[0]))); - EXPECT_TRUE( - torch::equal(*(std::get<1>(eval)[0]), - *(bspline1.eval(xi_)[0]))); - EXPECT_TRUE( - torch::equal(*(std::get<2>(eval)[0]), - *(bspline2.eval(xi_)[0]))); - EXPECT_TRUE( - torch::equal(*(std::get<3>(eval)[0]), - *(bspline3.eval(xi_)[0]))); - EXPECT_TRUE( - torch::equal(*(std::get<4>(eval)[0]), - *(bspline4.eval(xi_)[0]))); - - eval = functionspace - .eval(xi); - - EXPECT_TRUE( - torch::equal(*(std::get<0>(eval)[0]), - *(bspline0.eval(xi_)[0]))); - EXPECT_TRUE( - torch::equal(*(std::get<1>(eval)[0]), - *(bspline1.eval(xi_)[0]))); - EXPECT_TRUE( - torch::equal(*(std::get<2>(eval)[0]), - *(bspline2.eval(xi_)[0]))); - EXPECT_TRUE( - torch::equal(*(std::get<3>(eval)[0]), - *(bspline3.eval(xi_)[0]))); - EXPECT_TRUE( - torch::equal(*(std::get<4>(eval)[0]), - *(bspline4.eval(xi_)[0]))); - - eval = functionspace - .eval(xi); - - EXPECT_TRUE( - torch::equal(*(std::get<0>(eval)[0]), - *(bspline0.eval(xi_)[0]))); - EXPECT_TRUE( - torch::equal(*(std::get<1>(eval)[0]), - *(bspline1.eval(xi_)[0]))); - EXPECT_TRUE( - torch::equal(*(std::get<2>(eval)[0]), - *(bspline2.eval(xi_)[0]))); - EXPECT_TRUE( - torch::equal(*(std::get<3>(eval)[0]), - *(bspline3.eval(xi_)[0]))); - EXPECT_TRUE( - torch::equal(*(std::get<4>(eval)[0]), - *(bspline4.eval(xi_)[0]))); - - eval = functionspace - .eval(xi); - - EXPECT_TRUE( - torch::equal(*(std::get<0>(eval)[0]), - *(bspline0.eval(xi_)[0]))); - EXPECT_TRUE( - torch::equal(*(std::get<1>(eval)[0]), - *(bspline1.eval(xi_)[0]))); - EXPECT_TRUE( - torch::equal(*(std::get<2>(eval)[0]), - *(bspline2.eval(xi_)[0]))); - EXPECT_TRUE( - torch::equal(*(std::get<3>(eval)[0]), - *(bspline3.eval(xi_)[0]))); - EXPECT_TRUE( - torch::equal(*(std::get<4>(eval)[0]), - *(bspline4.eval(xi_)[0]))); - - eval = functionspace - .eval(xi); - - EXPECT_TRUE( - torch::equal(*(std::get<0>(eval)[0]), - *(bspline0.eval(xi_)[0]))); - EXPECT_TRUE( - torch::equal(*(std::get<1>(eval)[0]), - *(bspline1.eval(xi_)[0]))); - EXPECT_TRUE( - torch::equal(*(std::get<2>(eval)[0]), - *(bspline2.eval(xi_)[0]))); - EXPECT_TRUE( - torch::equal(*(std::get<3>(eval)[0]), - *(bspline3.eval(xi_)[0]))); - EXPECT_TRUE( - torch::equal(*(std::get<4>(eval)[0]), - *(bspline4.eval(xi_)[0]))); - - // Evaluation from precomputed coefficients and basis functions - auto knot_indices = - functionspace.template find_knot_indices(xi); - auto coeff_indices = - functionspace.template find_coeff_indices( - knot_indices); - - auto numel = [](const auto &xi) { - return std::tuple{std::get<0>(xi)[0].numel(), std::get<1>(xi)[0].numel(), - std::get<2>(xi)[0].numel(), std::get<3>(xi)[0].numel(), - std::get<4>(xi)[0].numel()}; - }; - auto sizes = [](const auto &xi) { - return std::tuple{std::get<0>(xi)[0].sizes(), std::get<1>(xi)[0].sizes(), - std::get<2>(xi)[0].sizes(), std::get<3>(xi)[0].sizes(), - std::get<4>(xi)[0].sizes()}; - }; - - auto basfunc = - functionspace - .template eval_basfunc( - xi, knot_indices); - eval = functionspace.eval_from_precomputed( - basfunc, coeff_indices, numel(xi), sizes(xi)); - - EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), - *(bspline0.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), - *(bspline1.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<2>(eval)[0]), - *(bspline2.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<3>(eval)[0]), - *(bspline3.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<4>(eval)[0]), - *(bspline4.eval(xi_)[0]))); - - basfunc = - functionspace - .template eval_basfunc( - xi, knot_indices); - eval = functionspace.eval_from_precomputed( - basfunc, coeff_indices, numel(xi), sizes(xi)); - - EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), - *(bspline0.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), - *(bspline1.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<2>(eval)[0]), - *(bspline2.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<3>(eval)[0]), - *(bspline3.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<4>(eval)[0]), - *(bspline4.eval(xi_)[0]))); - - basfunc = functionspace.template eval_basfunc( - xi, knot_indices); - eval = functionspace.eval_from_precomputed( - basfunc, coeff_indices, numel(xi), sizes(xi)); - - EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), - *(bspline0.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), - *(bspline1.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<2>(eval)[0]), - *(bspline2.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<3>(eval)[0]), - *(bspline3.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<4>(eval)[0]), - *(bspline4.eval(xi_)[0]))); - - basfunc = - functionspace - .template eval_basfunc( - xi, knot_indices); - eval = functionspace.eval_from_precomputed( - basfunc, coeff_indices, numel(xi), sizes(xi)); - - EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), - *(bspline0.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), - *(bspline1.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<2>(eval)[0]), - *(bspline2.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<3>(eval)[0]), - *(bspline3.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<4>(eval)[0]), - *(bspline4.eval(xi_)[0]))); - - basfunc = functionspace.template eval_basfunc( - xi, knot_indices); - eval = functionspace.eval_from_precomputed( - basfunc, coeff_indices, numel(xi), sizes(xi)); - - EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), - *(bspline0.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), - *(bspline1.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<2>(eval)[0]), - *(bspline2.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<3>(eval)[0]), - *(bspline3.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<4>(eval)[0]), - *(bspline4.eval(xi_)[0]))); - - basfunc = - functionspace - .template eval_basfunc( - xi, knot_indices); - eval = functionspace.eval_from_precomputed( - basfunc, coeff_indices, numel(xi), sizes(xi)); - - EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), - *(bspline0.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), - *(bspline1.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<2>(eval)[0]), - *(bspline2.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<3>(eval)[0]), - *(bspline3.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<4>(eval)[0]), - *(bspline4.eval(xi_)[0]))); - - basfunc = functionspace.template eval_basfunc( - xi, knot_indices); - eval = functionspace.eval_from_precomputed( - basfunc, coeff_indices, numel(xi), sizes(xi)); - - EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), - *(bspline0.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), - *(bspline1.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<2>(eval)[0]), - *(bspline2.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<3>(eval)[0]), - *(bspline3.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<4>(eval)[0]), - *(bspline4.eval(xi_)[0]))); - - basfunc = - functionspace - .template eval_basfunc( - xi, knot_indices); - eval = functionspace.eval_from_precomputed( - basfunc, coeff_indices, numel(xi), sizes(xi)); - - EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), - *(bspline0.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), - *(bspline1.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<2>(eval)[0]), - *(bspline2.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<3>(eval)[0]), - *(bspline3.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<4>(eval)[0]), - *(bspline4.eval(xi_)[0]))); - - basfunc = functionspace.template eval_basfunc( - xi, knot_indices); - eval = functionspace.eval_from_precomputed( - basfunc, coeff_indices, numel(xi), sizes(xi)); - - EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), - *(bspline0.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), - *(bspline1.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<2>(eval)[0]), - *(bspline2.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<3>(eval)[0]), - *(bspline3.eval(xi_)[0]))); - EXPECT_TRUE(torch::equal(*(std::get<4>(eval)[0]), - *(bspline4.eval(xi_)[0]))); - - basfunc = functionspace.template eval_basfunc( - xi, knot_indices); - eval = functionspace.eval_from_precomputed( - basfunc, coeff_indices, numel(xi), sizes(xi)); - - EXPECT_TRUE( - torch::equal(*(std::get<0>(eval)[0]), - *(bspline0.eval(xi_)[0]))); - EXPECT_TRUE( - torch::equal(*(std::get<1>(eval)[0]), - *(bspline1.eval(xi_)[0]))); - EXPECT_TRUE( - torch::equal(*(std::get<2>(eval)[0]), - *(bspline2.eval(xi_)[0]))); - EXPECT_TRUE( - torch::equal(*(std::get<3>(eval)[0]), - *(bspline3.eval(xi_)[0]))); - EXPECT_TRUE( - torch::equal(*(std::get<4>(eval)[0]), - *(bspline4.eval(xi_)[0]))); - - basfunc = functionspace.template eval_basfunc( - xi, knot_indices); - eval = functionspace.eval_from_precomputed( - basfunc, coeff_indices, numel(xi), sizes(xi)); - - EXPECT_TRUE( - torch::equal(*(std::get<0>(eval)[0]), - *(bspline0.eval(xi_)[0]))); - EXPECT_TRUE( - torch::equal(*(std::get<1>(eval)[0]), - *(bspline1.eval(xi_)[0]))); - EXPECT_TRUE( - torch::equal(*(std::get<2>(eval)[0]), - *(bspline2.eval(xi_)[0]))); - EXPECT_TRUE( - torch::equal(*(std::get<3>(eval)[0]), - *(bspline3.eval(xi_)[0]))); - EXPECT_TRUE( - torch::equal(*(std::get<4>(eval)[0]), - *(bspline4.eval(xi_)[0]))); - - basfunc = functionspace.template eval_basfunc( - xi, knot_indices); - eval = functionspace.eval_from_precomputed( - basfunc, coeff_indices, numel(xi), sizes(xi)); - - EXPECT_TRUE( - torch::equal(*(std::get<0>(eval)[0]), - *(bspline0.eval(xi_)[0]))); - EXPECT_TRUE( - torch::equal(*(std::get<1>(eval)[0]), - *(bspline1.eval(xi_)[0]))); - EXPECT_TRUE( - torch::equal(*(std::get<2>(eval)[0]), - *(bspline2.eval(xi_)[0]))); - EXPECT_TRUE( - torch::equal(*(std::get<3>(eval)[0]), - *(bspline3.eval(xi_)[0]))); - EXPECT_TRUE( - torch::equal(*(std::get<4>(eval)[0]), - *(bspline4.eval(xi_)[0]))); - - basfunc = functionspace.template eval_basfunc( - xi, knot_indices); - eval = functionspace.eval_from_precomputed( - basfunc, coeff_indices, numel(xi), sizes(xi)); - - EXPECT_TRUE( - torch::equal(*(std::get<0>(eval)[0]), - *(bspline0.eval(xi_)[0]))); - EXPECT_TRUE( - torch::equal(*(std::get<1>(eval)[0]), - *(bspline1.eval(xi_)[0]))); - EXPECT_TRUE( - torch::equal(*(std::get<2>(eval)[0]), - *(bspline2.eval(xi_)[0]))); - EXPECT_TRUE( - torch::equal(*(std::get<3>(eval)[0]), - *(bspline3.eval(xi_)[0]))); - EXPECT_TRUE( - torch::equal(*(std::get<4>(eval)[0]), - *(bspline4.eval(xi_)[0]))); - - basfunc = functionspace.template eval_basfunc( - xi, knot_indices); - eval = functionspace.eval_from_precomputed( - basfunc, coeff_indices, numel(xi), sizes(xi)); - - EXPECT_TRUE( - torch::equal(*(std::get<0>(eval)[0]), - *(bspline0.eval(xi_)[0]))); - EXPECT_TRUE( - torch::equal(*(std::get<1>(eval)[0]), - *(bspline1.eval(xi_)[0]))); - EXPECT_TRUE( - torch::equal(*(std::get<2>(eval)[0]), - *(bspline2.eval(xi_)[0]))); - EXPECT_TRUE( - torch::equal(*(std::get<3>(eval)[0]), - *(bspline3.eval(xi_)[0]))); - EXPECT_TRUE( - torch::equal(*(std::get<4>(eval)[0]), - *(bspline4.eval(xi_)[0]))); - - basfunc = functionspace.template eval_basfunc( - xi, knot_indices); - eval = functionspace.eval_from_precomputed( - basfunc, coeff_indices, numel(xi), sizes(xi)); - - EXPECT_TRUE( - torch::equal(*(std::get<0>(eval)[0]), - *(bspline0.eval(xi_)[0]))); - EXPECT_TRUE( - torch::equal(*(std::get<1>(eval)[0]), - *(bspline1.eval(xi_)[0]))); - EXPECT_TRUE( - torch::equal(*(std::get<2>(eval)[0]), - *(bspline2.eval(xi_)[0]))); - EXPECT_TRUE( - torch::equal(*(std::get<3>(eval)[0]), - *(bspline3.eval(xi_)[0]))); - EXPECT_TRUE( - torch::equal(*(std::get<4>(eval)[0]), - *(bspline4.eval(xi_)[0]))); - - /// Evaluation of gradient (in parametric domain) - auto grad = - functionspace.template grad_all(xi); - auto grad_ref0 = bspline0.grad(xi_); - auto grad_ref1 = bspline1.grad(xi_); - auto grad_ref2 = bspline2.grad(xi_); - auto grad_ref3 = bspline3.grad(xi_); - auto grad_ref4 = bspline4.grad(xi_); - - for (std::size_t i = 0; i < grad_ref0.entries(); ++i) { - EXPECT_TRUE(torch::equal(*(std::get<0>(grad)[i]), *(grad_ref0)[i])); - EXPECT_TRUE(torch::equal(*(std::get<1>(grad)[i]), *(grad_ref1)[i])); - EXPECT_TRUE(torch::equal(*(std::get<2>(grad)[i]), *(grad_ref2)[i])); - EXPECT_TRUE(torch::equal(*(std::get<3>(grad)[i]), *(grad_ref3)[i])); - EXPECT_TRUE(torch::equal(*(std::get<4>(grad)[i]), *(grad_ref4)[i])); - } - - /// Evaluation of gradient (in physical domain) - auto igrad = - functionspace.template igrad_all( - RT_geometry, xi); - auto igrad_ref0 = bspline0.igrad(geometry0, xi_); - auto igrad_ref1 = bspline1.igrad(geometry1, xi_); - auto igrad_ref2 = bspline2.igrad(geometry2, xi_); - auto igrad_ref3 = bspline3.igrad(geometry3, xi_); - auto igrad_ref4 = bspline4.igrad(geometry4, xi_); - - for (std::size_t i = 0; i < igrad_ref0.entries(); ++i) { - EXPECT_TRUE(torch::equal(*(std::get<0>(igrad)[i]), *(igrad_ref0)[i])); - EXPECT_TRUE(torch::equal(*(std::get<1>(igrad)[i]), *(igrad_ref1)[i])); - EXPECT_TRUE(torch::equal(*(std::get<2>(igrad)[i]), *(igrad_ref2)[i])); - EXPECT_TRUE(torch::equal(*(std::get<3>(igrad)[i]), *(igrad_ref3)[i])); - EXPECT_TRUE(torch::equal(*(std::get<4>(igrad)[i]), *(igrad_ref4)[i])); - } - - /// Evaluation of Jacobian (in parametric domain) - auto jac = - functionspace.template jac_all(xi); - auto jac_ref0 = bspline0.jac(xi_); - auto jac_ref1 = bspline1.jac(xi_); - auto jac_ref2 = bspline2.jac(xi_); - auto jac_ref3 = bspline3.jac(xi_); - auto jac_ref4 = bspline4.jac(xi_); - - for (std::size_t i = 0; i < jac_ref0.entries(); ++i) { - EXPECT_TRUE(torch::equal(*(std::get<0>(jac)[i]), *(jac_ref0)[i])); - EXPECT_TRUE(torch::equal(*(std::get<1>(jac)[i]), *(jac_ref1)[i])); - EXPECT_TRUE(torch::equal(*(std::get<2>(jac)[i]), *(jac_ref2)[i])); - EXPECT_TRUE(torch::equal(*(std::get<3>(jac)[i]), *(jac_ref3)[i])); - EXPECT_TRUE(torch::equal(*(std::get<4>(jac)[i]), *(jac_ref4)[i])); - } - - /// Evaluation of Jacobian (in physical domain) - auto ijac = functionspace.template ijac_all( - RT_geometry, xi); - auto ijac_ref0 = bspline0.ijac(geometry0, xi_); - auto ijac_ref1 = bspline1.ijac(geometry1, xi_); - auto ijac_ref2 = bspline2.ijac(geometry2, xi_); - auto ijac_ref3 = bspline3.ijac(geometry3, xi_); - auto ijac_ref4 = bspline4.ijac(geometry4, xi_); - - for (std::size_t i = 0; i < ijac_ref0.entries(); ++i) { - EXPECT_TRUE(torch::equal(*(std::get<0>(ijac)[i]), *(ijac_ref0)[i])); - EXPECT_TRUE(torch::equal(*(std::get<1>(ijac)[i]), *(ijac_ref1)[i])); - EXPECT_TRUE(torch::equal(*(std::get<2>(ijac)[i]), *(ijac_ref2)[i])); - EXPECT_TRUE(torch::equal(*(std::get<3>(ijac)[i]), *(ijac_ref3)[i])); - EXPECT_TRUE(torch::equal(*(std::get<4>(ijac)[i]), *(ijac_ref4)[i])); - } - - /// Evaluation of Hessian (in parametric domain) - auto hess = - functionspace.template hess_all(xi); - auto hess_ref0 = bspline0.hess(xi_); - auto hess_ref1 = bspline1.hess(xi_); - auto hess_ref2 = bspline2.hess(xi_); - auto hess_ref3 = bspline3.hess(xi_); - auto hess_ref4 = bspline4.hess(xi_); - - for (std::size_t i = 0; i < hess_ref0.entries(); ++i) { - EXPECT_TRUE(torch::equal(*(std::get<0>(hess)[i]), *(hess_ref0)[i])); - EXPECT_TRUE(torch::equal(*(std::get<1>(hess)[i]), *(hess_ref1)[i])); - EXPECT_TRUE(torch::equal(*(std::get<2>(hess)[i]), *(hess_ref2)[i])); - EXPECT_TRUE(torch::equal(*(std::get<3>(hess)[i]), *(hess_ref3)[i])); - EXPECT_TRUE(torch::equal(*(std::get<4>(hess)[i]), *(hess_ref4)[i])); - } - - /// Evaluation of Hessian (in physical domain) - auto ihess = - functionspace.template ihess_all( - RT_geometry, xi); - auto ihess_ref0 = bspline0.ihess(geometry0, xi_); - auto ihess_ref1 = bspline1.ihess(geometry1, xi_); - auto ihess_ref2 = bspline2.ihess(geometry2, xi_); - auto ihess_ref3 = bspline3.ihess(geometry3, xi_); - auto ihess_ref4 = bspline4.ihess(geometry4, xi_); - - for (std::size_t i = 0; i < ihess_ref0.entries(); ++i) { - EXPECT_TRUE(torch::equal(*(std::get<0>(ihess)[i]), *(ihess_ref0)[i])); - EXPECT_TRUE(torch::equal(*(std::get<1>(ihess)[i]), *(ihess_ref1)[i])); - EXPECT_TRUE(torch::equal(*(std::get<2>(ihess)[i]), *(ihess_ref2)[i])); - EXPECT_TRUE(torch::equal(*(std::get<3>(ihess)[i]), *(ihess_ref3)[i])); - EXPECT_TRUE(torch::equal(*(std::get<4>(ihess)[i]), *(ihess_ref4)[i])); - } - } -} - -TEST_F(FunctionSpaceTest, FunctionSpace_init) {} - -TEST_F(FunctionSpaceTest, FunctionSpace_refine) {} - -TEST_F(FunctionSpaceTest, FunctionSpace_copy_constructor) {} - -TEST_F(FunctionSpaceTest, FunctionSpace_clone_constructor) {} - -TEST_F(FunctionSpaceTest, FunctionSpace_move_constructor) {} - -TEST_F(FunctionSpaceTest, FunctionSpace_read_write) {} - -TEST_F(FunctionSpaceTest, FunctionSpace_to_from_xml) {} - -TEST_F(FunctionSpaceTest, FunctionSpace_load_from_xml) {} - -TEST_F(FunctionSpaceTest, FunctionSpace_to_from_json) {} - -TEST_F(FunctionSpaceTest, FunctionSpace_query_property) {} - -TEST_F(FunctionSpaceTest, FunctionSpace_requires_grad) {} - -TEST_F(FunctionSpaceTest, FunctionSpace_to_dtype) {} - -TEST_F(FunctionSpaceTest, FunctionSpace_to_device) {} - -int main(int argc, char **argv) { - ::testing::InitGoogleTest(&argc, argv); - iganet::init(); - return RUN_ALL_TESTS(); -} +/** + @file unittests/unittest_functionspace.cxx + + @brief Function space unittests + + @author Matthias Moller + + @copyright This file is part of the IgANet project + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#include +#include +#include + +#include +#include + +using namespace iganet::unittests::literals; + +class FunctionSpaceTest : public ::testing::Test { +protected: + using real_t = iganet::unittests::real_t; + iganet::Options options; +}; + +TEST_F(FunctionSpaceTest, S_geoDim1_degrees2) { + using iganet::deriv; + using iganet::functionspace; + using iganet::side; + using BSpline = iganet::UniformBSpline; + iganet::S functionspace({5}, iganet::init::greville, options); + BSpline bspline({5}, iganet::init::greville, options); + + { // Interior + + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + + // Evaluation + EXPECT_TRUE(torch::equal( + *(functionspace.eval( + xi)[0]), + *(bspline.eval(xi)[0]))); + + EXPECT_TRUE(torch::equal( + *(functionspace.eval(xi)[0]), + *(bspline.eval(xi)[0]))); + + EXPECT_TRUE(torch::equal( + *(functionspace.eval( + xi)[0]), + *(bspline.eval(xi)[0]))); + + // Evaluation from precomputed coefficients and basis functions + auto knot_indices = + functionspace.template find_knot_indices(xi); + auto coeff_indices = + functionspace.template find_coeff_indices( + knot_indices); + + auto basfunc = + functionspace + .template eval_basfunc( + xi, knot_indices); + + EXPECT_TRUE(torch::equal( + *(functionspace.eval_from_precomputed( + basfunc, coeff_indices, xi[0].numel(), xi[0].sizes())[0]), + *(bspline.eval(xi)[0]))); + + basfunc = + functionspace + .template eval_basfunc( + xi, knot_indices); + EXPECT_TRUE(torch::equal( + *(functionspace.eval_from_precomputed( + basfunc, coeff_indices, xi[0].numel(), xi[0].sizes())[0]), + *(bspline.eval(xi)[0]))); + + basfunc = functionspace.template eval_basfunc( + xi, knot_indices); + EXPECT_TRUE(torch::equal( + *(functionspace.eval_from_precomputed( + basfunc, coeff_indices, xi[0].numel(), xi[0].sizes())[0]), + *(bspline.eval(xi)[0]))); + + /// Evaluation of gradient (in parametric domain) + auto grad = functionspace.template grad(xi); + auto grad_ref = bspline.grad(xi); + + for (std::size_t i = 0; i < grad.entries(); ++i) + EXPECT_TRUE(torch::equal(*(grad)[i], *(grad_ref)[i])); + + /// Evaluation of gradient (in physical domain) + auto igrad = functionspace.template igrad( + functionspace, xi); + auto igrad_ref = bspline.igrad(bspline, xi); + + for (std::size_t i = 0; i < igrad.entries(); ++i) + EXPECT_TRUE(torch::equal(*(igrad)[i], *(igrad_ref)[i])); + + /// Evaluation of Jacobian (in parametric domain) + auto jac = functionspace.template jac(xi); + auto jac_ref = bspline.jac(xi); + + for (std::size_t i = 0; i < jac.entries(); ++i) + EXPECT_TRUE(torch::equal(*(jac)[i], *(jac_ref)[i])); + + /// Evaluation of Jacobian (in physical domain) + auto ijac = functionspace.template ijac( + functionspace, xi); + auto ijac_ref = bspline.ijac(bspline, xi); + + for (std::size_t i = 0; i < ijac.entries(); ++i) + EXPECT_TRUE(torch::equal(*(ijac)[i], *(ijac_ref)[i])); + + /// Evaluation of Hessian (in parametric domain) + auto hess = functionspace.template hess(xi); + auto hess_ref = bspline.hess(xi); + + for (std::size_t i = 0; i < hess.entries(); ++i) + EXPECT_TRUE(torch::equal(*(hess)[i], *(hess_ref)[i])); + + /// Evaluation of Hessian (in physical domain) + auto ihess = functionspace.template ihess( + functionspace, xi); + auto ihess_ref = bspline.ihess(bspline, xi); + + for (std::size_t i = 0; i < ihess.entries(); ++i) + EXPECT_TRUE(torch::equal(*(ihess)[i], *(ihess_ref)[i])); + } + + { // Boundary + + auto xi = std::tuple{std::array{}, + std::array{}}; + + // Evaluation + auto eval = + functionspace.eval(xi); + + EXPECT_TRUE(torch::equal(*std::get(eval)[0], + torch::zeros(1, options))); + EXPECT_TRUE(torch::equal(*std::get(eval)[0], + torch::ones(1, options))); + + eval = functionspace.eval(xi); + + EXPECT_TRUE(torch::equal(*std::get(eval)[0], + torch::zeros(1, options))); + EXPECT_TRUE(torch::equal(*std::get(eval)[0], + torch::zeros(1, options))); + + eval = + functionspace.eval(xi); + + EXPECT_TRUE(torch::equal(*std::get(eval)[0], + torch::zeros(1, options))); + EXPECT_TRUE(torch::equal(*std::get(eval)[0], + torch::zeros(1, options))); + + // Evaluation from precomputed coefficients and basis functions + auto knot_indices = + functionspace.template find_knot_indices(xi); + auto coeff_indices = + functionspace.template find_coeff_indices( + knot_indices); + + auto numel = [](const auto &xi) { + return std::tuple{1, 1}; + }; + auto sizes = [](const auto &xi) { + return std::tuple{torch::IntArrayRef{}, torch::IntArrayRef{}}; + }; + + auto basfunc = + functionspace + .template eval_basfunc( + xi, knot_indices); + eval = + functionspace.template eval_from_precomputed( + basfunc, coeff_indices, numel(xi), sizes(xi)); + + EXPECT_TRUE(torch::allclose(*std::get(eval)[0], + torch::zeros(1, options))); + EXPECT_TRUE(torch::allclose(*std::get(eval)[0], + torch::ones(1, options))); + + basfunc = + functionspace + .template eval_basfunc( + xi, knot_indices); + eval = + functionspace.template eval_from_precomputed( + basfunc, coeff_indices, numel(xi), sizes(xi)); + + EXPECT_TRUE(torch::equal(*std::get(eval)[0], + torch::zeros({}, options))); + EXPECT_TRUE(torch::equal(*std::get(eval)[0], + torch::zeros({}, options))); + + basfunc = functionspace.template eval_basfunc( + xi, knot_indices); + eval = + functionspace.template eval_from_precomputed( + basfunc, coeff_indices, numel(xi), sizes(xi)); + + EXPECT_TRUE(torch::equal(*std::get(eval)[0], + torch::zeros({}, options))); + EXPECT_TRUE(torch::equal(*std::get(eval)[0], + torch::zeros({}, options))); + + /// Evaluation of gradient (in parametric domain) + auto grad = functionspace.template grad(xi); + + for (std::size_t i = 0; i < std::get<0>(grad).entries(); ++i) { + EXPECT_TRUE(torch::equal(*std::get(grad)[i], + torch::zeros({1}, options))); + EXPECT_TRUE(torch::equal(*std::get(grad)[i], + torch::zeros({1}, options))); + } + + /// Evaluation of gradient (in physical domain) + auto igrad = functionspace.template igrad( + functionspace, xi); + + for (std::size_t i = 0; i < std::get<0>(igrad).entries(); ++i) { + EXPECT_TRUE(torch::equal(*std::get(igrad)[i], + torch::zeros({1}, options))); + EXPECT_TRUE(torch::equal(*std::get(igrad)[i], + torch::zeros({1}, options))); + } + + /// Evaluation of Jacobian (in parametric domain) + auto jac = functionspace.template jac(xi); + + for (std::size_t i = 0; i < std::get<0>(jac).entries(); ++i) { + EXPECT_TRUE(torch::equal(*std::get(jac)[i], + torch::zeros({1}, options))); + EXPECT_TRUE(torch::equal(*std::get(jac)[i], + torch::zeros({1}, options))); + } + + /// Evaluation of Jacobian (in physical domain) + auto ijac = functionspace.template ijac( + functionspace, xi); + + for (std::size_t i = 0; i < std::get<0>(ijac).entries(); ++i) { + EXPECT_TRUE(torch::equal(*std::get(ijac)[i], + torch::zeros({1}, options))); + EXPECT_TRUE(torch::equal(*std::get(ijac)[i], + torch::zeros({1}, options))); + } + + /// Evaluation of Hessian (in parametric domain) + auto hess = functionspace.template hess(xi); + + for (std::size_t i = 0; i < std::get<0>(hess).entries(); ++i) { + EXPECT_TRUE(torch::equal(*std::get(hess)[i], + torch::zeros({1}, options))); + EXPECT_TRUE(torch::equal(*std::get(hess)[i], + torch::zeros({1}, options))); + } + + /// Evaluation of Hessian (in physical domain) + auto ihess = functionspace.template ihess( + functionspace, xi); + + for (std::size_t i = 0; i < std::get<0>(ihess).entries(); ++i) { + EXPECT_TRUE(torch::equal(*std::get(ihess)[i], + torch::zeros({1}, options))); + EXPECT_TRUE(torch::equal(*std::get(ihess)[i], + torch::zeros({1}, options))); + } + } +} + +TEST_F(FunctionSpaceTest, S_geoDim1_degrees23) { + using iganet::deriv; + using iganet::functionspace; + using iganet::side; + using BSpline = iganet::UniformBSpline; + using Geometry = iganet::UniformBSpline; + iganet::S functionspace({5, 4}, iganet::init::greville, options); + iganet::S S_geometry({5, 4}, iganet::init::greville, options); + BSpline bspline({5, 4}, iganet::init::greville, options); + Geometry geometry({5, 4}, iganet::init::greville, options); + + { // Interior + + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r} /* u */, + {1.0_r, 0.2_r, 0.1_r, 0.5_r, 0.9_r, 0.75_r, 0.0_r} /* v */); + + // Evaluation + EXPECT_TRUE(torch::equal( + *(functionspace.eval( + xi)[0]), + *(bspline.eval(xi)[0]))); + + EXPECT_TRUE(torch::equal( + *(functionspace.eval(xi)[0]), + *(bspline.eval(xi)[0]))); + + EXPECT_TRUE(torch::equal( + *(functionspace.eval( + xi)[0]), + *(bspline.eval(xi)[0]))); + + EXPECT_TRUE(torch::equal( + *(functionspace.eval(xi)[0]), + *(bspline.eval(xi)[0]))); + + EXPECT_TRUE(torch::equal( + *(functionspace.eval( + xi)[0]), + *(bspline.eval(xi)[0]))); + + EXPECT_TRUE( + torch::equal(*(functionspace.eval(xi)[0]), + *(bspline.eval(xi)[0]))); + + // Evaluation from precomputed coefficients and basis functions + auto knot_indices = + functionspace.template find_knot_indices(xi); + auto coeff_indices = + functionspace.template find_coeff_indices( + knot_indices); + + auto basfunc = + functionspace + .template eval_basfunc( + xi, knot_indices); + EXPECT_TRUE(torch::equal( + *(functionspace.eval_from_precomputed( + basfunc, coeff_indices, xi[0].numel(), xi[0].sizes())[0]), + *(bspline.eval(xi)[0]))); + + basfunc = + functionspace + .template eval_basfunc( + xi, knot_indices); + EXPECT_TRUE(torch::equal( + *(functionspace.eval_from_precomputed( + basfunc, coeff_indices, xi[0].numel(), xi[0].sizes())[0]), + *(bspline.eval(xi)[0]))); + + basfunc = functionspace.template eval_basfunc( + xi, knot_indices); + EXPECT_TRUE(torch::equal( + *(functionspace.eval_from_precomputed( + basfunc, coeff_indices, xi[0].numel(), xi[0].sizes())[0]), + *(bspline.eval(xi)[0]))); + + basfunc = + functionspace + .template eval_basfunc( + xi, knot_indices); + EXPECT_TRUE(torch::equal( + *(functionspace.eval_from_precomputed( + basfunc, coeff_indices, xi[0].numel(), xi[0].sizes())[0]), + *(bspline.eval(xi)[0]))); + + basfunc = functionspace.template eval_basfunc( + xi, knot_indices); + EXPECT_TRUE(torch::equal( + *(functionspace.eval_from_precomputed( + basfunc, coeff_indices, xi[0].numel(), xi[0].sizes())[0]), + *(bspline.eval(xi)[0]))); + + basfunc = functionspace.template eval_basfunc( + xi, knot_indices); + EXPECT_TRUE(torch::equal( + *(functionspace.eval_from_precomputed( + basfunc, coeff_indices, xi[0].numel(), xi[0].sizes())[0]), + *(bspline.eval(xi)[0]))); + + /// Evaluation of gradient (in parametric domain) + auto grad = functionspace.template grad(xi); + auto grad_ref = bspline.grad(xi); + + for (std::size_t i = 0; i < grad.entries(); ++i) + EXPECT_TRUE(torch::equal(*(grad)[i], *(grad_ref)[i])); + + /// Evaluation of gradient (in physical domain) + auto igrad = functionspace.template igrad( + S_geometry, xi); + auto igrad_ref = bspline.igrad(geometry, xi); + + for (std::size_t i = 0; i < igrad.entries(); ++i) + EXPECT_TRUE(torch::equal(*(igrad)[i], *(igrad_ref)[i])); + + /// Evaluation of Jacobian (in parametric domain) + auto jac = functionspace.template jac(xi); + auto jac_ref = bspline.jac(xi); + + for (std::size_t i = 0; i < jac.entries(); ++i) + EXPECT_TRUE(torch::equal(*(jac)[i], *(jac_ref)[i])); + + /// Evaluation of Jacobian (in physical domain) + auto ijac = functionspace.template ijac( + S_geometry, xi); + auto ijac_ref = bspline.ijac(geometry, xi); + + for (std::size_t i = 0; i < ijac.entries(); ++i) + EXPECT_TRUE(torch::equal(*(ijac)[i], *(ijac_ref)[i])); + + /// Evaluation of Hessian (in parametric domain) + auto hess = functionspace.template hess(xi); + auto hess_ref = bspline.hess(xi); + + for (std::size_t i = 0; i < hess.entries(); ++i) + EXPECT_TRUE(torch::equal(*(hess)[i], *(hess_ref)[i])); + + /// Evaluation of Hessian (in physical domain) + auto ihess = functionspace.template ihess( + S_geometry, xi); + auto ihess_ref = bspline.ihess(geometry, xi); + + for (std::size_t i = 0; i < ihess.entries(); ++i) + EXPECT_TRUE(torch::equal(*(ihess)[i], *(ihess_ref)[i])); + } + + { // Boundary + + iganet::UniformBSpline bspline_bdrNS( + {5}, iganet::init::greville, options); + iganet::UniformBSpline bspline_bdrEW( + {4}, iganet::init::greville, options); + + auto xi = + std::tuple{iganet::utils::to_tensorArray(options, {1.0_r, 0.2_r, 0.1_r, + 0.5_r, 0.9_r, 0.75_r, + 0.0_r}) /* west */, + iganet::utils::to_tensorArray(options, {1.0_r, 0.2_r, 0.1_r, + 0.5_r, 0.9_r, 0.75_r, + 0.0_r}) /* east */, + iganet::utils::to_tensorArray(options, {0.0_r, 0.1_r, 0.2_r, + 0.5_r, 0.75_r, 0.9_r, + 1.0_r}) /* south */, + iganet::utils::to_tensorArray(options, {0.0_r, 0.1_r, 0.2_r, + 0.5_r, 0.75_r, 0.9_r, + 1.0_r}) /* north */}; + + // Evaluation + auto eval = + functionspace.eval(xi); + + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::allclose(*(std::get(eval)[0]), + torch::ones(7, options))); + EXPECT_TRUE(torch::allclose(*(std::get(eval)[0]), + torch::zeros(7, options))); + + eval = functionspace.eval(xi); + + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::allclose(*(std::get(eval)[0]), + torch::zeros(7, options))); + EXPECT_TRUE(torch::allclose(*(std::get(eval)[0]), + torch::zeros(7, options))); + + eval = + functionspace.eval(xi); + + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::allclose(*(std::get(eval)[0]), + torch::zeros(7, options))); + EXPECT_TRUE(torch::allclose(*(std::get(eval)[0]), + torch::zeros(7, options))); + + eval = functionspace.eval(xi); + iganet::verbose(std::cout); + std::cout << functionspace.boundary() << std::endl; + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::allclose(*(std::get(eval)[0]), + torch::zeros(7, options))); + std::cout << *(std::get(eval)[0]) << std::endl; + EXPECT_TRUE(torch::allclose(*(std::get(eval)[0]), + torch::zeros(7, options))); + + eval = + functionspace.eval(xi); + + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::allclose(*(std::get(eval)[0]), + torch::zeros(7, options))); + std::cout << *(std::get(eval)[0]) << std::endl; + EXPECT_TRUE(torch::allclose(*(std::get(eval)[0]), + torch::zeros(7, options))); + + eval = functionspace + .eval(xi); + + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::allclose(*(std::get(eval)[0]), + torch::zeros(7, options))); + EXPECT_TRUE(torch::allclose(*(std::get(eval)[0]), + torch::zeros(7, options))); + + // Evaluation from precomputed coefficients and basis functions + auto knot_indices = + functionspace.template find_knot_indices(xi); + auto coeff_indices = + functionspace.template find_coeff_indices( + knot_indices); + + auto numel = [](const auto &xi) { + return std::tuple{std::get<0>(xi)[0].numel(), std::get<1>(xi)[0].numel(), + std::get<2>(xi)[0].numel(), std::get<3>(xi)[0].numel()}; + }; + auto sizes = [](const auto &xi) { + return std::tuple{std::get<0>(xi)[0].sizes(), std::get<1>(xi)[0].sizes(), + std::get<2>(xi)[0].sizes(), std::get<3>(xi)[0].sizes()}; + }; + + auto basfunc = + functionspace + .template eval_basfunc( + xi, knot_indices); + eval = + functionspace.template eval_from_precomputed( + basfunc, coeff_indices, numel(xi), sizes(xi)); + + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + std::cout << *(std::get(eval)[0]) << std::endl; + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + std::cout << *(std::get(eval)[0]) << std::endl; + exit(0); + basfunc = + functionspace + .template eval_basfunc( + xi, knot_indices); + eval = + functionspace.template eval_from_precomputed( + basfunc, coeff_indices, numel(xi), sizes(xi)); + + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + + basfunc = functionspace.template eval_basfunc( + xi, knot_indices); + eval = + functionspace.template eval_from_precomputed( + basfunc, coeff_indices, numel(xi), sizes(xi)); + + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + + basfunc = + functionspace + .template eval_basfunc( + xi, knot_indices); + eval = + functionspace.template eval_from_precomputed( + basfunc, coeff_indices, numel(xi), sizes(xi)); + + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + + basfunc = functionspace.template eval_basfunc( + xi, knot_indices); + eval = + functionspace.template eval_from_precomputed( + basfunc, coeff_indices, numel(xi), sizes(xi)); + + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + + basfunc = functionspace.template eval_basfunc( + xi, knot_indices); + eval = + functionspace.template eval_from_precomputed( + basfunc, coeff_indices, numel(xi), sizes(xi)); + + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + + /// Evaluation of gradient (in parametric domain) + auto grad = functionspace.template grad(xi); + + for (std::size_t i = 0; i < std::get<0>(grad).entries(); ++i) { + EXPECT_TRUE(torch::equal( + *(std::get(grad)[i]), + *(bspline_bdrNS.grad(std::get(xi))[i]))); + EXPECT_TRUE(torch::equal( + *(std::get(grad)[i]), + *(bspline_bdrNS.grad(std::get(xi))[i]))); + EXPECT_TRUE(torch::equal( + *(std::get(grad)[i]), + *(bspline_bdrEW.grad(std::get(xi))[i]))); + EXPECT_TRUE(torch::equal( + *(std::get(grad)[i]), + *(bspline_bdrEW.grad(std::get(xi))[i]))); + } + + /// Evaluation of gradient (in physical domain) + auto igrad = functionspace.template igrad( + functionspace, xi); + + for (std::size_t i = 0; i < std::get<0>(igrad).entries(); ++i) { + EXPECT_TRUE( + torch::equal(*(std::get(igrad)[i]), + *(bspline_bdrNS.igrad( + bspline_bdrNS, std::get(xi))[i]))); + EXPECT_TRUE( + torch::equal(*(std::get(igrad)[i]), + *(bspline_bdrNS.igrad( + bspline_bdrNS, std::get(xi))[i]))); + EXPECT_TRUE( + torch::equal(*(std::get(igrad)[i]), + *(bspline_bdrEW.igrad( + bspline_bdrEW, std::get(xi))[i]))); + EXPECT_TRUE( + torch::equal(*(std::get(igrad)[i]), + *(bspline_bdrEW.igrad( + bspline_bdrEW, std::get(xi))[i]))); + } + + /// Evaluation of Jacobian (in parametric domain) + auto jac = functionspace.template jac(xi); + + for (std::size_t i = 0; i < std::get<0>(jac).entries(); ++i) { + EXPECT_TRUE(torch::equal( + *(std::get(jac)[i]), + *(bspline_bdrNS.jac(std::get(xi))[i]))); + EXPECT_TRUE(torch::equal( + *(std::get(jac)[i]), + *(bspline_bdrNS.jac(std::get(xi))[i]))); + EXPECT_TRUE(torch::equal( + *(std::get(jac)[i]), + *(bspline_bdrEW.jac(std::get(xi))[i]))); + EXPECT_TRUE(torch::equal( + *(std::get(jac)[i]), + *(bspline_bdrEW.jac(std::get(xi))[i]))); + } + + /// Evaluation of Jacobian (in physical domain) + auto ijac = functionspace.template ijac( + functionspace, xi); + + for (std::size_t i = 0; i < std::get<0>(ijac).entries(); ++i) { + EXPECT_TRUE( + torch::equal(*(std::get(ijac)[i]), + *(bspline_bdrNS.ijac( + bspline_bdrNS, std::get(xi))[i]))); + EXPECT_TRUE( + torch::equal(*(std::get(ijac)[i]), + *(bspline_bdrNS.ijac( + bspline_bdrNS, std::get(xi))[i]))); + EXPECT_TRUE( + torch::equal(*(std::get(ijac)[i]), + *(bspline_bdrEW.ijac( + bspline_bdrEW, std::get(xi))[i]))); + EXPECT_TRUE( + torch::equal(*(std::get(ijac)[i]), + *(bspline_bdrEW.ijac( + bspline_bdrEW, std::get(xi))[i]))); + } + + /// Evaluation of Hessian (in parametric domain) + auto hess = functionspace.template hess(xi); + + for (std::size_t i = 0; i < std::get<0>(hess).entries(); ++i) { + EXPECT_TRUE(torch::equal( + *(std::get(hess)[i]), + *(bspline_bdrNS.hess(std::get(xi))[i]))); + EXPECT_TRUE(torch::equal( + *(std::get(hess)[i]), + *(bspline_bdrNS.hess(std::get(xi))[i]))); + EXPECT_TRUE(torch::equal( + *(std::get(hess)[i]), + *(bspline_bdrEW.hess(std::get(xi))[i]))); + EXPECT_TRUE(torch::equal( + *(std::get(hess)[i]), + *(bspline_bdrEW.hess(std::get(xi))[i]))); + } + + /// Evaluation of Hessian (in physical domain) + auto ihess = functionspace.template ihess( + functionspace, xi); + + for (std::size_t i = 0; i < std::get<0>(ihess).entries(); ++i) { + EXPECT_TRUE( + torch::equal(*(std::get(ihess)[i]), + *(bspline_bdrNS.ihess( + bspline_bdrNS, std::get(xi))[i]))); + EXPECT_TRUE( + torch::equal(*(std::get(ihess)[i]), + *(bspline_bdrNS.ihess( + bspline_bdrNS, std::get(xi))[i]))); + EXPECT_TRUE( + torch::equal(*(std::get(ihess)[i]), + *(bspline_bdrEW.ihess( + bspline_bdrEW, std::get(xi))[i]))); + EXPECT_TRUE( + torch::equal(*(std::get(ihess)[i]), + *(bspline_bdrEW.ihess( + bspline_bdrEW, std::get(xi))[i]))); + } + } +} + +TEST_F(FunctionSpaceTest, S_geoDim1_degrees234) { + using iganet::deriv; + using iganet::functionspace; + using iganet::side; + using BSpline = iganet::UniformBSpline; + using Geometry = iganet::UniformBSpline; + iganet::S functionspace({5, 4, 7}, iganet::init::greville, options); + iganet::S S_geometry({5, 4, 7}, iganet::init::greville, options); + BSpline bspline({5, 4, 7}, iganet::init::greville, options); + Geometry geometry({5, 4, 7}, iganet::init::greville, options); + + { // Interior + + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r} /* u */, + {1.0_r, 0.2_r, 0.1_r, 0.5_r, 0.9_r, 0.75_r, 0.0_r} /* v */, + {0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r, 0.0_r, 0.1_r} /* w */); + + // Evaluation + EXPECT_TRUE(torch::equal( + *(functionspace.eval( + xi)[0]), + *(bspline.eval(xi)[0]))); + + EXPECT_TRUE(torch::equal( + *(functionspace.eval(xi)[0]), + *(bspline.eval(xi)[0]))); + + EXPECT_TRUE(torch::equal( + *(functionspace.eval( + xi)[0]), + *(bspline.eval(xi)[0]))); + + EXPECT_TRUE(torch::equal( + *(functionspace.eval(xi)[0]), + *(bspline.eval(xi)[0]))); + + EXPECT_TRUE(torch::equal( + *(functionspace.eval( + xi)[0]), + *(bspline.eval(xi)[0]))); + + EXPECT_TRUE(torch::equal( + *(functionspace.eval(xi)[0]), + *(bspline.eval(xi)[0]))); + + EXPECT_TRUE(torch::equal( + *(functionspace.eval( + xi)[0]), + *(bspline.eval(xi)[0]))); + + EXPECT_TRUE( + torch::equal(*(functionspace.eval(xi)[0]), + *(bspline.eval(xi)[0]))); + + EXPECT_TRUE( + torch::equal(*(functionspace.eval(xi)[0]), + *(bspline.eval(xi)[0]))); + + EXPECT_TRUE( + torch::equal(*(functionspace.eval(xi)[0]), + *(bspline.eval(xi)[0]))); + + // Evaluation from precomputed coefficients and basis functions + auto knot_indices = + functionspace.template find_knot_indices(xi); + auto coeff_indices = + functionspace.template find_coeff_indices( + knot_indices); + + auto basfunc = + functionspace + .template eval_basfunc( + xi, knot_indices); + EXPECT_TRUE(torch::equal( + *(functionspace.eval_from_precomputed( + basfunc, coeff_indices, xi[0].numel(), xi[0].sizes())[0]), + *(bspline.eval(xi)[0]))); + + basfunc = + functionspace + .template eval_basfunc( + xi, knot_indices); + EXPECT_TRUE(torch::equal( + *(functionspace.eval_from_precomputed( + basfunc, coeff_indices, xi[0].numel(), xi[0].sizes())[0]), + *(bspline.eval(xi)[0]))); + + basfunc = functionspace.template eval_basfunc( + xi, knot_indices); + EXPECT_TRUE(torch::equal( + *(functionspace.eval_from_precomputed( + basfunc, coeff_indices, xi[0].numel(), xi[0].sizes())[0]), + *(bspline.eval(xi)[0]))); + + basfunc = + functionspace + .template eval_basfunc( + xi, knot_indices); + EXPECT_TRUE(torch::equal( + *(functionspace.eval_from_precomputed( + basfunc, coeff_indices, xi[0].numel(), xi[0].sizes())[0]), + *(bspline.eval(xi)[0]))); + + basfunc = functionspace.template eval_basfunc( + xi, knot_indices); + EXPECT_TRUE(torch::equal( + *(functionspace.eval_from_precomputed( + basfunc, coeff_indices, xi[0].numel(), xi[0].sizes())[0]), + *(bspline.eval(xi)[0]))); + + basfunc = + functionspace + .template eval_basfunc( + xi, knot_indices); + EXPECT_TRUE(torch::equal( + *(functionspace.eval_from_precomputed( + basfunc, coeff_indices, xi[0].numel(), xi[0].sizes())[0]), + *(bspline.eval(xi)[0]))); + + basfunc = functionspace.template eval_basfunc( + xi, knot_indices); + EXPECT_TRUE(torch::equal( + *(functionspace.eval_from_precomputed( + basfunc, coeff_indices, xi[0].numel(), xi[0].sizes())[0]), + *(bspline.eval(xi)[0]))); + + basfunc = functionspace.template eval_basfunc( + xi, knot_indices); + EXPECT_TRUE(torch::equal( + *(functionspace.eval_from_precomputed( + basfunc, coeff_indices, xi[0].numel(), xi[0].sizes())[0]), + *(bspline.eval(xi)[0]))); + + basfunc = functionspace.template eval_basfunc( + xi, knot_indices); + EXPECT_TRUE(torch::equal( + *(functionspace.eval_from_precomputed( + basfunc, coeff_indices, xi[0].numel(), xi[0].sizes())[0]), + *(bspline.eval(xi)[0]))); + + basfunc = functionspace.template eval_basfunc( + xi, knot_indices); + EXPECT_TRUE(torch::equal( + *(functionspace.eval_from_precomputed( + basfunc, coeff_indices, xi[0].numel(), xi[0].sizes())[0]), + *(bspline.eval(xi)[0]))); + + /// Evaluation of gradient (in parametric domain) + auto grad = functionspace.template grad(xi); + auto grad_ref = bspline.grad(xi); + + for (std::size_t i = 0; i < grad.entries(); ++i) + EXPECT_TRUE(torch::equal(*(grad)[i], *(grad_ref)[i])); + + /// Evaluation of gradient (in physical domain) + auto igrad = functionspace.template igrad( + S_geometry, xi); + auto igrad_ref = bspline.igrad(geometry, xi); + + for (std::size_t i = 0; i < igrad.entries(); ++i) + EXPECT_TRUE(torch::equal(*(igrad)[i], *(igrad_ref)[i])); + + /// Evaluation of Jacobian (in parametric domain) + auto jac = functionspace.template jac(xi); + auto jac_ref = bspline.jac(xi); + + for (std::size_t i = 0; i < jac.entries(); ++i) + EXPECT_TRUE(torch::equal(*(jac)[i], *(jac_ref)[i])); + + /// Evaluation of Jacobian (in physical domain) + auto ijac = functionspace.template ijac( + S_geometry, xi); + auto ijac_ref = bspline.ijac(geometry, xi); + + for (std::size_t i = 0; i < ijac.entries(); ++i) + EXPECT_TRUE(torch::equal(*(ijac)[i], *(ijac_ref)[i])); + + /// Evaluation of Hessian (in parametric domain) + auto hess = functionspace.template hess(xi); + auto hess_ref = bspline.hess(xi); + + for (std::size_t i = 0; i < hess.entries(); ++i) + EXPECT_TRUE(torch::equal(*(hess)[i], *(hess_ref)[i])); + + /// Evaluation of Hessian (in physical domain) + auto ihess = functionspace.template ihess( + S_geometry, xi); + auto ihess_ref = bspline.ihess(geometry, xi); + + for (std::size_t i = 0; i < ihess.entries(); ++i) + EXPECT_TRUE(torch::equal(*(ihess)[i], *(ihess_ref)[i])); + } + + { // Boundary + + iganet::UniformBSpline bspline_bdrNS( + {5, 7}, iganet::init::greville, options); + iganet::UniformBSpline bspline_bdrEW( + {4, 7}, iganet::init::greville, options); + iganet::UniformBSpline bspline_bdrFB( + {5, 4}, iganet::init::greville, options); + iganet::UniformBSpline geometry_bdrNS( + {5, 7}, iganet::init::greville, options); + iganet::UniformBSpline geometry_bdrEW( + {4, 7}, iganet::init::greville, options); + iganet::UniformBSpline geometry_bdrFB( + {5, 4}, iganet::init::greville, options); + + auto xi = std::tuple{ + iganet::utils::to_tensorArray( + options, {1.0_r, 0.2_r, 0.1_r, 0.5_r, 0.9_r, 0.75_r, 0.0_r} /* v */, + {0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r, 0.0_r, + 0.1_r} /* w */) /* west */, + iganet::utils::to_tensorArray( + options, {1.0_r, 0.2_r, 0.1_r, 0.5_r, 0.9_r, 0.75_r, 0.0_r} /* v */, + {0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r, 0.0_r, + 0.1_r} /* w */) /* east */, + iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r} /* u */, + {0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r, 0.0_r, + 0.1_r} /* w */) /* south */, + iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r} /* u */, + {0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r, 0.0_r, + 0.1_r} /* w */) /* north */, + iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r} /* u */, + {1.0_r, 0.2_r, 0.1_r, 0.5_r, 0.9_r, 0.75_r, + 0.0_r} /* v */) /* front */, + iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r} /* u */, + {1.0_r, 0.2_r, 0.1_r, 0.5_r, 0.9_r, 0.75_r, + 0.0_r} /* v */) /* back */}; + + // Evaluation + auto eval = + functionspace.eval(xi); + + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + + eval = functionspace.eval(xi); + + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + + eval = + functionspace.eval(xi); + + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + + eval = functionspace.eval(xi); + + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + + eval = + functionspace.eval(xi); + + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + + eval = functionspace.eval(xi); + + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + + eval = + functionspace.eval(xi); + + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + + eval = functionspace + .eval(xi); + + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + + eval = functionspace + .eval(xi); + + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + + eval = functionspace + .eval(xi); + + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + + // Evaluation from precomputed coefficients and basis functions + auto knot_indices = + functionspace.template find_knot_indices(xi); + auto coeff_indices = + functionspace.template find_coeff_indices( + knot_indices); + + auto numel = [](const auto &xi) { + return std::tuple{std::get<0>(xi)[0].numel(), std::get<1>(xi)[0].numel(), + std::get<2>(xi)[0].numel(), std::get<3>(xi)[0].numel(), + std::get<4>(xi)[0].numel(), std::get<5>(xi)[0].numel()}; + }; + auto sizes = [](const auto &xi) { + return std::tuple{std::get<0>(xi)[0].sizes(), std::get<1>(xi)[0].sizes(), + std::get<2>(xi)[0].sizes(), std::get<3>(xi)[0].sizes(), + std::get<4>(xi)[0].sizes(), std::get<5>(xi)[0].sizes()}; + }; + + auto basfunc = + functionspace + .template eval_basfunc( + xi, knot_indices); + eval = + functionspace.template eval_from_precomputed( + basfunc, coeff_indices, numel(xi), sizes(xi)); + + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + + basfunc = + functionspace + .template eval_basfunc( + xi, knot_indices); + eval = + functionspace.template eval_from_precomputed( + basfunc, coeff_indices, numel(xi), sizes(xi)); + + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + + basfunc = functionspace.template eval_basfunc( + xi, knot_indices); + eval = + functionspace.template eval_from_precomputed( + basfunc, coeff_indices, numel(xi), sizes(xi)); + + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + + basfunc = + functionspace + .template eval_basfunc( + xi, knot_indices); + eval = + functionspace.template eval_from_precomputed( + basfunc, coeff_indices, numel(xi), sizes(xi)); + + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + + basfunc = functionspace.template eval_basfunc( + xi, knot_indices); + eval = + functionspace.template eval_from_precomputed( + basfunc, coeff_indices, numel(xi), sizes(xi)); + + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + + basfunc = + functionspace + .template eval_basfunc( + xi, knot_indices); + eval = + functionspace.template eval_from_precomputed( + basfunc, coeff_indices, numel(xi), sizes(xi)); + + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + + basfunc = functionspace.template eval_basfunc( + xi, knot_indices); + eval = + functionspace.template eval_from_precomputed( + basfunc, coeff_indices, numel(xi), sizes(xi)); + + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + + basfunc = functionspace.template eval_basfunc( + xi, knot_indices); + eval = + functionspace.template eval_from_precomputed( + basfunc, coeff_indices, numel(xi), sizes(xi)); + + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + + basfunc = functionspace.template eval_basfunc( + xi, knot_indices); + eval = + functionspace.template eval_from_precomputed( + basfunc, coeff_indices, numel(xi), sizes(xi)); + + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + + basfunc = functionspace.template eval_basfunc( + xi, knot_indices); + eval = + functionspace.template eval_from_precomputed( + basfunc, coeff_indices, numel(xi), sizes(xi)); + + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + + /// Evaluation of gradient (in parametric domain) + auto grad = functionspace.template grad(xi); + + for (std::size_t i = 0; i < std::get<0>(grad).entries(); ++i) { + EXPECT_TRUE(torch::equal( + *(std::get(grad)[i]), + *(bspline_bdrNS.grad(std::get(xi))[i]))); + EXPECT_TRUE(torch::equal( + *(std::get(grad)[i]), + *(bspline_bdrNS.grad(std::get(xi))[i]))); + EXPECT_TRUE(torch::equal( + *(std::get(grad)[i]), + *(bspline_bdrEW.grad(std::get(xi))[i]))); + EXPECT_TRUE(torch::equal( + *(std::get(grad)[i]), + *(bspline_bdrEW.grad(std::get(xi))[i]))); + EXPECT_TRUE(torch::equal( + *(std::get(grad)[i]), + *(bspline_bdrFB.grad(std::get(xi))[i]))); + EXPECT_TRUE(torch::equal( + *(std::get(grad)[i]), + *(bspline_bdrFB.grad(std::get(xi))[i]))); + } + + /// Evaluation of gradient (in physical domain) + auto igrad = functionspace.template igrad( + S_geometry, xi); + + for (std::size_t i = 0; i < std::get<0>(igrad).entries(); ++i) { + EXPECT_TRUE( + torch::equal(*(std::get(igrad)[i]), + *(bspline_bdrNS.igrad( + geometry_bdrNS, std::get(xi))[i]))); + EXPECT_TRUE( + torch::equal(*(std::get(igrad)[i]), + *(bspline_bdrNS.igrad( + geometry_bdrNS, std::get(xi))[i]))); + EXPECT_TRUE( + torch::equal(*(std::get(igrad)[i]), + *(bspline_bdrEW.igrad( + geometry_bdrEW, std::get(xi))[i]))); + EXPECT_TRUE( + torch::equal(*(std::get(igrad)[i]), + *(bspline_bdrEW.igrad( + geometry_bdrEW, std::get(xi))[i]))); + EXPECT_TRUE( + torch::equal(*(std::get(igrad)[i]), + *(bspline_bdrFB.igrad( + geometry_bdrFB, std::get(xi))[i]))); + EXPECT_TRUE( + torch::equal(*(std::get(igrad)[i]), + *(bspline_bdrFB.igrad( + geometry_bdrFB, std::get(xi))[i]))); + } + + /// Evaluation of Jacobian (in parametric domain) + auto jac = functionspace.template jac(xi); + + for (std::size_t i = 0; i < std::get<0>(jac).entries(); ++i) { + EXPECT_TRUE(torch::equal( + *(std::get(jac)[i]), + *(bspline_bdrNS.jac(std::get(xi))[i]))); + EXPECT_TRUE(torch::equal( + *(std::get(jac)[i]), + *(bspline_bdrNS.jac(std::get(xi))[i]))); + EXPECT_TRUE(torch::equal( + *(std::get(jac)[i]), + *(bspline_bdrEW.jac(std::get(xi))[i]))); + EXPECT_TRUE(torch::equal( + *(std::get(jac)[i]), + *(bspline_bdrEW.jac(std::get(xi))[i]))); + EXPECT_TRUE(torch::equal( + *(std::get(jac)[i]), + *(bspline_bdrFB.jac(std::get(xi))[i]))); + EXPECT_TRUE(torch::equal( + *(std::get(jac)[i]), + *(bspline_bdrFB.jac(std::get(xi))[i]))); + } + + /// Evaluation of Jacobian (in physical domain) + auto ijac = functionspace.template ijac( + S_geometry, xi); + + for (std::size_t i = 0; i < std::get<0>(ijac).entries(); ++i) { + EXPECT_TRUE( + torch::equal(*(std::get(ijac)[i]), + *(bspline_bdrNS.ijac( + geometry_bdrNS, std::get(xi))[i]))); + EXPECT_TRUE( + torch::equal(*(std::get(ijac)[i]), + *(bspline_bdrNS.ijac( + geometry_bdrNS, std::get(xi))[i]))); + EXPECT_TRUE( + torch::equal(*(std::get(ijac)[i]), + *(bspline_bdrEW.ijac( + geometry_bdrEW, std::get(xi))[i]))); + EXPECT_TRUE( + torch::equal(*(std::get(ijac)[i]), + *(bspline_bdrEW.ijac( + geometry_bdrEW, std::get(xi))[i]))); + EXPECT_TRUE( + torch::equal(*(std::get(ijac)[i]), + *(bspline_bdrFB.ijac( + geometry_bdrFB, std::get(xi))[i]))); + EXPECT_TRUE( + torch::equal(*(std::get(ijac)[i]), + *(bspline_bdrFB.ijac( + geometry_bdrFB, std::get(xi))[i]))); + } + + /// Evaluation of Hessian (in parametric domain) + auto hess = functionspace.template hess(xi); + + for (std::size_t i = 0; i < std::get<0>(hess).entries(); ++i) { + EXPECT_TRUE(torch::equal( + *(std::get(hess)[i]), + *(bspline_bdrNS.hess(std::get(xi))[i]))); + EXPECT_TRUE(torch::equal( + *(std::get(hess)[i]), + *(bspline_bdrNS.hess(std::get(xi))[i]))); + EXPECT_TRUE(torch::equal( + *(std::get(hess)[i]), + *(bspline_bdrEW.hess(std::get(xi))[i]))); + EXPECT_TRUE(torch::equal( + *(std::get(hess)[i]), + *(bspline_bdrEW.hess(std::get(xi))[i]))); + EXPECT_TRUE(torch::equal( + *(std::get(hess)[i]), + *(bspline_bdrFB.hess(std::get(xi))[i]))); + EXPECT_TRUE(torch::equal( + *(std::get(hess)[i]), + *(bspline_bdrFB.hess(std::get(xi))[i]))); + } + + /// Evaluation of Hessian (in physical domain) + auto ihess = + functionspace.template ihess(S_geometry, xi); + + for (std::size_t i = 0; i < std::get<0>(ihess).entries(); ++i) { + EXPECT_TRUE( + torch::equal(*(std::get(ihess)[i]), + *(bspline_bdrNS.ihess( + geometry_bdrNS, std::get(xi))[i]))); + EXPECT_TRUE( + torch::equal(*(std::get(ihess)[i]), + *(bspline_bdrNS.ihess( + geometry_bdrNS, std::get(xi))[i]))); + EXPECT_TRUE( + torch::equal(*(std::get(ihess)[i]), + *(bspline_bdrEW.ihess( + geometry_bdrEW, std::get(xi))[i]))); + EXPECT_TRUE( + torch::equal(*(std::get(ihess)[i]), + *(bspline_bdrEW.ihess( + geometry_bdrEW, std::get(xi))[i]))); + EXPECT_TRUE( + torch::equal(*(std::get(ihess)[i]), + *(bspline_bdrFB.ihess( + geometry_bdrFB, std::get(xi))[i]))); + EXPECT_TRUE( + torch::equal(*(std::get(ihess)[i]), + *(bspline_bdrFB.ihess( + geometry_bdrFB, std::get(xi))[i]))); + } + } +} + +TEST_F(FunctionSpaceTest, S_geoDim1_degrees2341) { + using iganet::deriv; + using iganet::functionspace; + using iganet::side; + using BSpline = iganet::UniformBSpline; + using Geometry = iganet::UniformBSpline; + iganet::S functionspace({5, 4, 7, 3}, iganet::init::greville, + options); + iganet::S S_geometry({5, 4, 7, 3}, iganet::init::greville, options); + BSpline bspline({5, 4, 7, 3}, iganet::init::greville, options); + Geometry geometry({5, 4, 7, 3}, iganet::init::greville, options); + + { // Interior + + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r} /* u */, + {1.0_r, 0.2_r, 0.1_r, 0.5_r, 0.9_r, 0.75_r, 0.0_r} /* v */, + {0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r, 0.0_r, 0.1_r} /* w */, + {0.1_r, 0.1_r, 0.2_r, 0.3_r, 0.3_r, 0.0_r, 0.1_r} /* t */); + + // Evaluation + EXPECT_TRUE(torch::equal( + *(functionspace.eval( + xi)[0]), + *(bspline.eval(xi)[0]))); + + EXPECT_TRUE(torch::equal( + *(functionspace.eval(xi)[0]), + *(bspline.eval(xi)[0]))); + + EXPECT_TRUE(torch::equal( + *(functionspace.eval( + xi)[0]), + *(bspline.eval(xi)[0]))); + + EXPECT_TRUE(torch::equal( + *(functionspace.eval(xi)[0]), + *(bspline.eval(xi)[0]))); + + EXPECT_TRUE(torch::equal( + *(functionspace.eval( + xi)[0]), + *(bspline.eval(xi)[0]))); + + EXPECT_TRUE(torch::equal( + *(functionspace.eval(xi)[0]), + *(bspline.eval(xi)[0]))); + + EXPECT_TRUE(torch::equal( + *(functionspace.eval( + xi)[0]), + *(bspline.eval(xi)[0]))); + + EXPECT_TRUE(torch::equal( + *(functionspace.eval(xi)[0]), + *(bspline.eval(xi)[0]))); + + EXPECT_TRUE(torch::equal( + *(functionspace.eval( + xi)[0]), + *(bspline.eval(xi)[0]))); + + EXPECT_TRUE( + torch::equal(*(functionspace.eval(xi)[0]), + *(bspline.eval(xi)[0]))); + + EXPECT_TRUE( + torch::equal(*(functionspace.eval(xi)[0]), + *(bspline.eval(xi)[0]))); + + EXPECT_TRUE( + torch::equal(*(functionspace.eval(xi)[0]), + *(bspline.eval(xi)[0]))); + + EXPECT_TRUE( + torch::equal(*(functionspace.eval(xi)[0]), + *(bspline.eval(xi)[0]))); + + EXPECT_TRUE( + torch::equal(*(functionspace.eval(xi)[0]), + *(bspline.eval(xi)[0]))); + + EXPECT_TRUE( + torch::equal(*(functionspace.eval(xi)[0]), + *(bspline.eval(xi)[0]))); + + // Evaluation from precomputed coefficients and basis functions + auto knot_indices = + functionspace.template find_knot_indices(xi); + auto coeff_indices = + functionspace.template find_coeff_indices( + knot_indices); + + auto basfunc = + functionspace + .template eval_basfunc( + xi, knot_indices); + EXPECT_TRUE(torch::equal( + *(functionspace.eval_from_precomputed( + basfunc, coeff_indices, xi[0].numel(), xi[0].sizes())[0]), + *(bspline.eval(xi)[0]))); + + basfunc = + functionspace + .template eval_basfunc( + xi, knot_indices); + EXPECT_TRUE(torch::equal( + *(functionspace.eval_from_precomputed( + basfunc, coeff_indices, xi[0].numel(), xi[0].sizes())[0]), + *(bspline.eval(xi)[0]))); + + basfunc = functionspace.template eval_basfunc( + xi, knot_indices); + EXPECT_TRUE(torch::equal( + *(functionspace.eval_from_precomputed( + basfunc, coeff_indices, xi[0].numel(), xi[0].sizes())[0]), + *(bspline.eval(xi)[0]))); + + basfunc = + functionspace + .template eval_basfunc( + xi, knot_indices); + EXPECT_TRUE(torch::equal( + *(functionspace.eval_from_precomputed( + basfunc, coeff_indices, xi[0].numel(), xi[0].sizes())[0]), + *(bspline.eval(xi)[0]))); + + basfunc = functionspace.template eval_basfunc( + xi, knot_indices); + EXPECT_TRUE(torch::equal( + *(functionspace.eval_from_precomputed( + basfunc, coeff_indices, xi[0].numel(), xi[0].sizes())[0]), + *(bspline.eval(xi)[0]))); + + basfunc = + functionspace + .template eval_basfunc( + xi, knot_indices); + EXPECT_TRUE(torch::equal( + *(functionspace.eval_from_precomputed( + basfunc, coeff_indices, xi[0].numel(), xi[0].sizes())[0]), + *(bspline.eval(xi)[0]))); + + basfunc = functionspace.template eval_basfunc( + xi, knot_indices); + EXPECT_TRUE(torch::equal( + *(functionspace.eval_from_precomputed( + basfunc, coeff_indices, xi[0].numel(), xi[0].sizes())[0]), + *(bspline.eval(xi)[0]))); + + basfunc = + functionspace + .template eval_basfunc( + xi, knot_indices); + EXPECT_TRUE(torch::equal( + *(functionspace.eval_from_precomputed( + basfunc, coeff_indices, xi[0].numel(), xi[0].sizes())[0]), + *(bspline.eval(xi)[0]))); + + basfunc = functionspace.template eval_basfunc( + xi, knot_indices); + EXPECT_TRUE(torch::equal( + *(functionspace.eval_from_precomputed( + basfunc, coeff_indices, xi[0].numel(), xi[0].sizes())[0]), + *(bspline.eval(xi)[0]))); + + basfunc = functionspace.template eval_basfunc( + xi, knot_indices); + EXPECT_TRUE(torch::equal( + *(functionspace.eval_from_precomputed( + basfunc, coeff_indices, xi[0].numel(), xi[0].sizes())[0]), + *(bspline.eval(xi)[0]))); + + basfunc = functionspace.template eval_basfunc( + xi, knot_indices); + EXPECT_TRUE(torch::equal( + *(functionspace.eval_from_precomputed( + basfunc, coeff_indices, xi[0].numel(), xi[0].sizes())[0]), + *(bspline.eval(xi)[0]))); + + basfunc = functionspace.template eval_basfunc( + xi, knot_indices); + EXPECT_TRUE(torch::equal( + *(functionspace.eval_from_precomputed( + basfunc, coeff_indices, xi[0].numel(), xi[0].sizes())[0]), + *(bspline.eval(xi)[0]))); + + basfunc = functionspace.template eval_basfunc( + xi, knot_indices); + EXPECT_TRUE(torch::equal( + *(functionspace.eval_from_precomputed( + basfunc, coeff_indices, xi[0].numel(), xi[0].sizes())[0]), + *(bspline.eval(xi)[0]))); + + basfunc = functionspace.template eval_basfunc( + xi, knot_indices); + EXPECT_TRUE(torch::equal( + *(functionspace.eval_from_precomputed( + basfunc, coeff_indices, xi[0].numel(), xi[0].sizes())[0]), + *(bspline.eval(xi)[0]))); + + basfunc = functionspace.template eval_basfunc( + xi, knot_indices); + EXPECT_TRUE(torch::equal( + *(functionspace.eval_from_precomputed( + basfunc, coeff_indices, xi[0].numel(), xi[0].sizes())[0]), + *(bspline.eval(xi)[0]))); + + /// Evaluation of gradient (in parametric domain) + auto grad = functionspace.template grad(xi); + auto grad_ref = bspline.grad(xi); + + for (std::size_t i = 0; i < grad.entries(); ++i) + EXPECT_TRUE(torch::equal(*(grad)[i], *(grad_ref)[i])); + + /// Evaluation of gradient (in physical domain) + auto igrad = functionspace.template igrad( + S_geometry, xi); + auto igrad_ref = bspline.igrad(geometry, xi); + + for (std::size_t i = 0; i < igrad.entries(); ++i) + EXPECT_TRUE(torch::equal(*(igrad)[i], *(igrad_ref)[i])); + + /// Evaluation of Jacobian (in parametric domain) + auto jac = functionspace.template jac(xi); + auto jac_ref = bspline.jac(xi); + + for (std::size_t i = 0; i < jac.entries(); ++i) + EXPECT_TRUE(torch::equal(*(jac)[i], *(jac_ref)[i])); + + /// Evaluation of Jacobian (in physical domain) + auto ijac = functionspace.template ijac( + S_geometry, xi); + auto ijac_ref = bspline.ijac(geometry, xi); + + for (std::size_t i = 0; i < ijac.entries(); ++i) + EXPECT_TRUE(torch::equal(*(ijac)[i], *(ijac_ref)[i])); + + /// Evaluation of Hessian (in parametric domain) + auto hess = functionspace.template hess(xi); + auto hess_ref = bspline.hess(xi); + + for (std::size_t i = 0; i < hess.entries(); ++i) + EXPECT_TRUE(torch::equal(*(hess)[i], *(hess_ref)[i])); + + /// Evaluation of Hessian (in physical domain) + auto ihess = functionspace.template ihess( + S_geometry, xi); + auto ihess_ref = bspline.ihess(geometry, xi); + + for (std::size_t i = 0; i < ihess.entries(); ++i) + EXPECT_TRUE(torch::equal(*(ihess)[i], *(ihess_ref)[i])); + } + + { // Boundary + + iganet::UniformBSpline bspline_bdrNS( + {5, 7, 3}, iganet::init::greville, options); + iganet::UniformBSpline bspline_bdrEW( + {4, 7, 3}, iganet::init::greville, options); + iganet::UniformBSpline bspline_bdrFB( + {5, 4, 3}, iganet::init::greville, options); + iganet::UniformBSpline bspline_bdrSE( + {5, 4, 7}, iganet::init::greville, options); + iganet::UniformBSpline geometry_bdrNS( + {5, 7, 3}, iganet::init::greville, options); + iganet::UniformBSpline geometry_bdrEW( + {4, 7, 3}, iganet::init::greville, options); + iganet::UniformBSpline geometry_bdrFB( + {5, 4, 3}, iganet::init::greville, options); + iganet::UniformBSpline geometry_bdrSE( + {5, 4, 7}, iganet::init::greville, options); + + auto xi = std::tuple{ + iganet::utils::to_tensorArray( + options, {1.0_r, 0.2_r, 0.1_r, 0.5_r, 0.9_r, 0.75_r, 0.0_r} /* v */, + {0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r, 0.0_r, 0.1_r} /* w */, + {0.1_r, 0.1_r, 0.2_r, 0.3_r, 0.3_r, 0.0_r, + 0.1_r} /* t */) /* west */, + iganet::utils::to_tensorArray( + options, {1.0_r, 0.2_r, 0.1_r, 0.5_r, 0.9_r, 0.75_r, 0.0_r} /* v */, + {0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r, 0.0_r, 0.1_r} /* w */, + {0.1_r, 0.1_r, 0.2_r, 0.3_r, 0.3_r, 0.0_r, + 0.1_r} /* t */) /* east */, + iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r} /* u */, + {0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r, 0.0_r, 0.1_r} /* w */, + {0.1_r, 0.1_r, 0.2_r, 0.3_r, 0.3_r, 0.0_r, + 0.1_r} /* t */) /* south */, + iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r} /* u */, + {0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r, 0.0_r, 0.1_r} /* w */, + {0.1_r, 0.1_r, 0.2_r, 0.3_r, 0.3_r, 0.0_r, + 0.1_r} /* t */) /* north */, + iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r} /* u */, + {1.0_r, 0.2_r, 0.1_r, 0.5_r, 0.9_r, 0.75_r, 0.0_r} /* v */, + {0.1_r, 0.1_r, 0.2_r, 0.3_r, 0.3_r, 0.0_r, + 0.1_r} /* t */) /* front */, + iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r} /* u */, + {1.0_r, 0.2_r, 0.1_r, 0.5_r, 0.9_r, 0.75_r, 0.0_r} /* v */, + {0.1_r, 0.1_r, 0.2_r, 0.3_r, 0.3_r, 0.0_r, + 0.1_r} /* t */) /* back */, + iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r} /* u */, + {1.0_r, 0.2_r, 0.1_r, 0.5_r, 0.9_r, 0.75_r, 0.0_r} /* v */, + {0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r, 0.0_r, + 0.1_r} /* w */) /* stime */, + iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r} /* u */, + {1.0_r, 0.2_r, 0.1_r, 0.5_r, 0.9_r, 0.75_r, 0.0_r} /* v */, + {0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r, 0.0_r, + 0.1_r} /* w */) /* etime */}; + + // Evaluation + auto eval = + functionspace.eval(xi); + + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrSE.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrSE.eval( + std::get(xi))[0]))); + + eval = functionspace.eval(xi); + + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrSE.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrSE.eval( + std::get(xi))[0]))); + + eval = + functionspace.eval(xi); + + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrSE.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrSE.eval( + std::get(xi))[0]))); + + eval = functionspace.eval(xi); + + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrSE.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrSE.eval( + std::get(xi))[0]))); + + eval = + functionspace.eval(xi); + + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrSE.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrSE.eval( + std::get(xi))[0]))); + + eval = functionspace.eval(xi); + + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrSE.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrSE.eval( + std::get(xi))[0]))); + + eval = + functionspace.eval(xi); + + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrSE.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrSE.eval( + std::get(xi))[0]))); + + eval = functionspace.eval(xi); + + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrSE.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrSE.eval( + std::get(xi))[0]))); + + eval = + functionspace.eval(xi); + + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrSE.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrSE.eval( + std::get(xi))[0]))); + + eval = functionspace + .eval(xi); + + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrSE.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrSE.eval( + std::get(xi))[0]))); + + eval = functionspace + .eval(xi); + + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrSE.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrSE.eval( + std::get(xi))[0]))); + + eval = functionspace + .eval(xi); + + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrSE.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrSE.eval( + std::get(xi))[0]))); + + eval = functionspace + .eval(xi); + + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrSE.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrSE.eval( + std::get(xi))[0]))); + + eval = functionspace + .eval(xi); + + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrSE.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrSE.eval( + std::get(xi))[0]))); + + eval = functionspace + .eval(xi); + + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrSE.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrSE.eval( + std::get(xi))[0]))); + + // Evaluation from precomputed coefficients and basis functions + auto knot_indices = + functionspace.template find_knot_indices(xi); + auto coeff_indices = + functionspace.template find_coeff_indices( + knot_indices); + + auto numel = [](const auto &xi) { + return std::tuple{std::get<0>(xi)[0].numel(), std::get<1>(xi)[0].numel(), + std::get<2>(xi)[0].numel(), std::get<3>(xi)[0].numel(), + std::get<4>(xi)[0].numel(), std::get<5>(xi)[0].numel(), + std::get<6>(xi)[0].numel(), std::get<7>(xi)[0].numel()}; + }; + auto sizes = [](const auto &xi) { + return std::tuple{std::get<0>(xi)[0].sizes(), std::get<1>(xi)[0].sizes(), + std::get<2>(xi)[0].sizes(), std::get<3>(xi)[0].sizes(), + std::get<4>(xi)[0].sizes(), std::get<5>(xi)[0].sizes(), + std::get<6>(xi)[0].sizes(), std::get<7>(xi)[0].sizes()}; + }; + + auto basfunc = + functionspace + .template eval_basfunc( + xi, knot_indices); + eval = + functionspace.template eval_from_precomputed( + basfunc, coeff_indices, numel(xi), sizes(xi)); + + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrSE.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrSE.eval( + std::get(xi))[0]))); + + basfunc = + functionspace + .template eval_basfunc( + xi, knot_indices); + eval = + functionspace.template eval_from_precomputed( + basfunc, coeff_indices, numel(xi), sizes(xi)); + + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrSE.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrSE.eval( + std::get(xi))[0]))); + + basfunc = functionspace.template eval_basfunc( + xi, knot_indices); + eval = + functionspace.template eval_from_precomputed( + basfunc, coeff_indices, numel(xi), sizes(xi)); + + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrSE.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrSE.eval( + std::get(xi))[0]))); + + basfunc = + functionspace + .template eval_basfunc( + xi, knot_indices); + eval = + functionspace.template eval_from_precomputed( + basfunc, coeff_indices, numel(xi), sizes(xi)); + + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrSE.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrSE.eval( + std::get(xi))[0]))); + + basfunc = functionspace.template eval_basfunc( + xi, knot_indices); + eval = + functionspace.template eval_from_precomputed( + basfunc, coeff_indices, numel(xi), sizes(xi)); + + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrSE.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrSE.eval( + std::get(xi))[0]))); + + basfunc = + functionspace + .template eval_basfunc( + xi, knot_indices); + eval = + functionspace.template eval_from_precomputed( + basfunc, coeff_indices, numel(xi), sizes(xi)); + + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrSE.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrSE.eval( + std::get(xi))[0]))); + + basfunc = functionspace.template eval_basfunc( + xi, knot_indices); + eval = + functionspace.template eval_from_precomputed( + basfunc, coeff_indices, numel(xi), sizes(xi)); + + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrSE.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrSE.eval( + std::get(xi))[0]))); + + basfunc = + functionspace + .template eval_basfunc( + xi, knot_indices); + eval = + functionspace.template eval_from_precomputed( + basfunc, coeff_indices, numel(xi), sizes(xi)); + + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrSE.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrSE.eval( + std::get(xi))[0]))); + + basfunc = functionspace.template eval_basfunc( + xi, knot_indices); + eval = + functionspace.template eval_from_precomputed( + basfunc, coeff_indices, numel(xi), sizes(xi)); + + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrSE.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrSE.eval( + std::get(xi))[0]))); + + basfunc = functionspace.template eval_basfunc( + xi, knot_indices); + eval = + functionspace.template eval_from_precomputed( + basfunc, coeff_indices, numel(xi), sizes(xi)); + + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrSE.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrSE.eval( + std::get(xi))[0]))); + + basfunc = functionspace.template eval_basfunc( + xi, knot_indices); + eval = + functionspace.template eval_from_precomputed( + basfunc, coeff_indices, numel(xi), sizes(xi)); + + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrSE.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrSE.eval( + std::get(xi))[0]))); + + basfunc = functionspace.template eval_basfunc( + xi, knot_indices); + eval = + functionspace.template eval_from_precomputed( + basfunc, coeff_indices, numel(xi), sizes(xi)); + + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrSE.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrSE.eval( + std::get(xi))[0]))); + + basfunc = functionspace.template eval_basfunc( + xi, knot_indices); + eval = + functionspace.template eval_from_precomputed( + basfunc, coeff_indices, numel(xi), sizes(xi)); + + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrSE.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrSE.eval( + std::get(xi))[0]))); + + basfunc = functionspace.template eval_basfunc( + xi, knot_indices); + eval = + functionspace.template eval_from_precomputed( + basfunc, coeff_indices, numel(xi), sizes(xi)); + + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrSE.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrSE.eval( + std::get(xi))[0]))); + + basfunc = functionspace.template eval_basfunc( + xi, knot_indices); + eval = + functionspace.template eval_from_precomputed( + basfunc, coeff_indices, numel(xi), sizes(xi)); + + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrNS.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrEW.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrFB.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrSE.eval( + std::get(xi))[0]))); + EXPECT_TRUE(torch::equal(*(std::get(eval)[0]), + *(bspline_bdrSE.eval( + std::get(xi))[0]))); + + /// Evaluation of gradient (in parametric domain) + auto grad = functionspace.template grad(xi); + + for (std::size_t i = 0; i < std::get<0>(grad).entries(); ++i) { + EXPECT_TRUE(torch::equal( + *(std::get(grad)[i]), + *(bspline_bdrNS.grad(std::get(xi))[i]))); + EXPECT_TRUE(torch::equal( + *(std::get(grad)[i]), + *(bspline_bdrNS.grad(std::get(xi))[i]))); + EXPECT_TRUE(torch::equal( + *(std::get(grad)[i]), + *(bspline_bdrEW.grad(std::get(xi))[i]))); + EXPECT_TRUE(torch::equal( + *(std::get(grad)[i]), + *(bspline_bdrEW.grad(std::get(xi))[i]))); + EXPECT_TRUE(torch::equal( + *(std::get(grad)[i]), + *(bspline_bdrFB.grad(std::get(xi))[i]))); + EXPECT_TRUE(torch::equal( + *(std::get(grad)[i]), + *(bspline_bdrFB.grad(std::get(xi))[i]))); + EXPECT_TRUE(torch::equal( + *(std::get(grad)[i]), + *(bspline_bdrSE.grad(std::get(xi))[i]))); + EXPECT_TRUE(torch::equal( + *(std::get(grad)[i]), + *(bspline_bdrSE.grad(std::get(xi))[i]))); + } + + /// Evaluation of gradient (in physical domain) + auto igrad = functionspace.template igrad( + S_geometry, xi); + + for (std::size_t i = 0; i < std::get<0>(igrad).entries(); ++i) { + EXPECT_TRUE( + torch::equal(*(std::get(igrad)[i]), + *(bspline_bdrNS.igrad( + geometry_bdrNS, std::get(xi))[i]))); + EXPECT_TRUE( + torch::equal(*(std::get(igrad)[i]), + *(bspline_bdrNS.igrad( + geometry_bdrNS, std::get(xi))[i]))); + EXPECT_TRUE( + torch::equal(*(std::get(igrad)[i]), + *(bspline_bdrEW.igrad( + geometry_bdrEW, std::get(xi))[i]))); + EXPECT_TRUE( + torch::equal(*(std::get(igrad)[i]), + *(bspline_bdrEW.igrad( + geometry_bdrEW, std::get(xi))[i]))); + EXPECT_TRUE( + torch::equal(*(std::get(igrad)[i]), + *(bspline_bdrFB.igrad( + geometry_bdrFB, std::get(xi))[i]))); + EXPECT_TRUE( + torch::equal(*(std::get(igrad)[i]), + *(bspline_bdrFB.igrad( + geometry_bdrFB, std::get(xi))[i]))); + EXPECT_TRUE( + torch::equal(*(std::get(igrad)[i]), + *(bspline_bdrSE.igrad( + geometry_bdrSE, std::get(xi))[i]))); + EXPECT_TRUE( + torch::equal(*(std::get(igrad)[i]), + *(bspline_bdrSE.igrad( + geometry_bdrSE, std::get(xi))[i]))); + } + + /// Evaluation of Jacobian (in parametric domain) + auto jac = functionspace.template jac(xi); + + for (std::size_t i = 0; i < std::get<0>(jac).entries(); ++i) { + EXPECT_TRUE(torch::equal( + *(std::get(jac)[i]), + *(bspline_bdrNS.jac(std::get(xi))[i]))); + EXPECT_TRUE(torch::equal( + *(std::get(jac)[i]), + *(bspline_bdrNS.jac(std::get(xi))[i]))); + EXPECT_TRUE(torch::equal( + *(std::get(jac)[i]), + *(bspline_bdrEW.jac(std::get(xi))[i]))); + EXPECT_TRUE(torch::equal( + *(std::get(jac)[i]), + *(bspline_bdrEW.jac(std::get(xi))[i]))); + EXPECT_TRUE(torch::equal( + *(std::get(jac)[i]), + *(bspline_bdrFB.jac(std::get(xi))[i]))); + EXPECT_TRUE(torch::equal( + *(std::get(jac)[i]), + *(bspline_bdrFB.jac(std::get(xi))[i]))); + EXPECT_TRUE(torch::equal( + *(std::get(jac)[i]), + *(bspline_bdrSE.jac(std::get(xi))[i]))); + EXPECT_TRUE(torch::equal( + *(std::get(jac)[i]), + *(bspline_bdrSE.jac(std::get(xi))[i]))); + } + + /// Evaluation of Jacobian (in physical domain) + auto ijac = functionspace.template ijac( + S_geometry, xi); + + for (std::size_t i = 0; i < std::get<0>(ijac).entries(); ++i) { + EXPECT_TRUE( + torch::equal(*(std::get(ijac)[i]), + *(bspline_bdrNS.ijac( + geometry_bdrNS, std::get(xi))[i]))); + EXPECT_TRUE( + torch::equal(*(std::get(ijac)[i]), + *(bspline_bdrNS.ijac( + geometry_bdrNS, std::get(xi))[i]))); + EXPECT_TRUE( + torch::equal(*(std::get(ijac)[i]), + *(bspline_bdrEW.ijac( + geometry_bdrEW, std::get(xi))[i]))); + EXPECT_TRUE( + torch::equal(*(std::get(ijac)[i]), + *(bspline_bdrEW.ijac( + geometry_bdrEW, std::get(xi))[i]))); + EXPECT_TRUE( + torch::equal(*(std::get(ijac)[i]), + *(bspline_bdrFB.ijac( + geometry_bdrFB, std::get(xi))[i]))); + EXPECT_TRUE( + torch::equal(*(std::get(ijac)[i]), + *(bspline_bdrFB.ijac( + geometry_bdrFB, std::get(xi))[i]))); + EXPECT_TRUE( + torch::equal(*(std::get(ijac)[i]), + *(bspline_bdrSE.ijac( + geometry_bdrSE, std::get(xi))[i]))); + EXPECT_TRUE( + torch::equal(*(std::get(ijac)[i]), + *(bspline_bdrSE.ijac( + geometry_bdrSE, std::get(xi))[i]))); + } + + /// Evaluation of Hessian (in parametric domain) + auto hess = functionspace.template hess(xi); + + for (std::size_t i = 0; i < std::get<0>(hess).entries(); ++i) { + EXPECT_TRUE(torch::equal( + *(std::get(hess)[i]), + *(bspline_bdrNS.hess(std::get(xi))[i]))); + EXPECT_TRUE(torch::equal( + *(std::get(hess)[i]), + *(bspline_bdrNS.hess(std::get(xi))[i]))); + EXPECT_TRUE(torch::equal( + *(std::get(hess)[i]), + *(bspline_bdrEW.hess(std::get(xi))[i]))); + EXPECT_TRUE(torch::equal( + *(std::get(hess)[i]), + *(bspline_bdrEW.hess(std::get(xi))[i]))); + EXPECT_TRUE(torch::equal( + *(std::get(hess)[i]), + *(bspline_bdrFB.hess(std::get(xi))[i]))); + EXPECT_TRUE(torch::equal( + *(std::get(hess)[i]), + *(bspline_bdrFB.hess(std::get(xi))[i]))); + EXPECT_TRUE(torch::equal( + *(std::get(hess)[i]), + *(bspline_bdrSE.hess(std::get(xi))[i]))); + EXPECT_TRUE(torch::equal( + *(std::get(hess)[i]), + *(bspline_bdrSE.hess(std::get(xi))[i]))); + } + + /// Evaluation of Hessian (in physical domain) + auto ihess = + functionspace.template ihess(S_geometry, xi); + + for (std::size_t i = 0; i < std::get<0>(ihess).entries(); ++i) { + EXPECT_TRUE( + torch::equal(*(std::get(ihess)[i]), + *(bspline_bdrNS.ihess( + geometry_bdrNS, std::get(xi))[i]))); + EXPECT_TRUE( + torch::equal(*(std::get(ihess)[i]), + *(bspline_bdrNS.ihess( + geometry_bdrNS, std::get(xi))[i]))); + EXPECT_TRUE( + torch::equal(*(std::get(ihess)[i]), + *(bspline_bdrEW.ihess( + geometry_bdrEW, std::get(xi))[i]))); + EXPECT_TRUE( + torch::equal(*(std::get(ihess)[i]), + *(bspline_bdrEW.ihess( + geometry_bdrEW, std::get(xi))[i]))); + EXPECT_TRUE( + torch::equal(*(std::get(ihess)[i]), + *(bspline_bdrFB.ihess( + geometry_bdrFB, std::get(xi))[i]))); + EXPECT_TRUE( + torch::equal(*(std::get(ihess)[i]), + *(bspline_bdrFB.ihess( + geometry_bdrFB, std::get(xi))[i]))); + EXPECT_TRUE( + torch::equal(*(std::get(ihess)[i]), + *(bspline_bdrSE.ihess( + geometry_bdrSE, std::get(xi))[i]))); + EXPECT_TRUE( + torch::equal(*(std::get(ihess)[i]), + *(bspline_bdrSE.ihess( + geometry_bdrSE, std::get(xi))[i]))); + } + } +} + +TEST_F(FunctionSpaceTest, RT_geoDim1_degrees2) { + using iganet::deriv; + using iganet::functionspace; + using iganet::side; + using BSpline = iganet::NonUniformBSpline; + using Geometry = iganet::NonUniformBSpline; + iganet::RT functionspace({5}, iganet::init::greville, options); + iganet::RT RT_geometry({5}, iganet::init::greville, options); + + iganet::NonUniformBSpline bspline0( + {5 + 1}, iganet::init::greville, options); + iganet::NonUniformBSpline bspline1({5}, iganet::init::greville, + options); + iganet::NonUniformBSpline geometry0( + {5 + 1}, iganet::init::greville, options); + iganet::NonUniformBSpline geometry1({5}, iganet::init::greville, + options); + + { // Interior + + auto xi_ = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r} /* u */); + auto xi = std::tuple{xi_, xi_}; + + // Evaluation + auto eval = + functionspace.eval(xi); + + EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), + *(bspline0.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), + *(bspline1.eval(xi_)[0]))); + + eval = functionspace.eval(xi); + + EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), + *(bspline0.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), + *(bspline1.eval(xi_)[0]))); + + eval = + functionspace.eval(xi); + + EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), + *(bspline0.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), + *(bspline1.eval(xi_)[0]))); + + // Evaluation from precomputed coefficients and basis functions + auto knot_indices = + functionspace.template find_knot_indices(xi); + auto coeff_indices = + functionspace.template find_coeff_indices( + knot_indices); + + auto numel = [](const auto &xi) { + return std::tuple{std::get<0>(xi)[0].numel(), std::get<1>(xi)[0].numel()}; + }; + auto sizes = [](const auto &xi) { + return std::tuple{std::get<0>(xi)[0].sizes(), std::get<1>(xi)[0].sizes()}; + }; + + auto basfunc = + functionspace + .template eval_basfunc( + xi, knot_indices); + eval = functionspace.eval_from_precomputed( + basfunc, coeff_indices, numel(xi), sizes(xi)); + + EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), + *(bspline0.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), + *(bspline1.eval(xi_)[0]))); + + basfunc = + functionspace + .template eval_basfunc( + xi, knot_indices); + eval = functionspace.eval_from_precomputed( + basfunc, coeff_indices, numel(xi), sizes(xi)); + + EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), + *(bspline0.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), + *(bspline1.eval(xi_)[0]))); + + basfunc = functionspace.template eval_basfunc( + xi, knot_indices); + eval = functionspace.eval_from_precomputed( + basfunc, coeff_indices, numel(xi), sizes(xi)); + + EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), + *(bspline0.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), + *(bspline1.eval(xi_)[0]))); + + /// Evaluation of gradient (in parametric domain) + auto grad = + functionspace.template grad_all(xi); + auto grad_ref0 = bspline0.grad(xi_); + auto grad_ref1 = bspline1.grad(xi_); + + for (std::size_t i = 0; i < grad_ref0.entries(); ++i) { + EXPECT_TRUE(torch::equal(*(std::get<0>(grad)[i]), *(grad_ref0)[i])); + EXPECT_TRUE(torch::equal(*(std::get<1>(grad)[i]), *(grad_ref1)[i])); + } + + /// Evaluation of gradient (in physical domain) + auto igrad = + functionspace.template igrad_all( + RT_geometry, xi); + auto igrad_ref0 = bspline0.igrad(geometry0, xi_); + auto igrad_ref1 = bspline1.igrad(geometry1, xi_); + + for (std::size_t i = 0; i < igrad_ref0.entries(); ++i) { + EXPECT_TRUE(torch::equal(*(std::get<0>(igrad)[i]), *(igrad_ref0)[i])); + EXPECT_TRUE(torch::equal(*(std::get<1>(igrad)[i]), *(igrad_ref1)[i])); + } + + /// Evaluation of Jacobian (in parametric domain) + auto jac = + functionspace.template jac_all(xi); + auto jac_ref0 = bspline0.jac(xi_); + auto jac_ref1 = bspline1.jac(xi_); + + for (std::size_t i = 0; i < jac_ref0.entries(); ++i) { + EXPECT_TRUE(torch::equal(*(std::get<0>(jac)[i]), *(jac_ref0)[i])); + EXPECT_TRUE(torch::equal(*(std::get<1>(jac)[i]), *(jac_ref1)[i])); + } + + /// Evaluation of Jacobian (in physical domain) + auto ijac = functionspace.template ijac_all( + RT_geometry, xi); + auto ijac_ref0 = bspline0.ijac(geometry0, xi_); + auto ijac_ref1 = bspline1.ijac(geometry1, xi_); + + for (std::size_t i = 0; i < ijac_ref0.entries(); ++i) { + EXPECT_TRUE(torch::equal(*(std::get<0>(ijac)[i]), *(ijac_ref0)[i])); + EXPECT_TRUE(torch::equal(*(std::get<1>(ijac)[i]), *(ijac_ref1)[i])); + } + + /// Evaluation of Hessian (in parametric domain) + auto hess = + functionspace.template hess_all(xi); + auto hess_ref0 = bspline0.hess(xi_); + auto hess_ref1 = bspline1.hess(xi_); + + for (std::size_t i = 0; i < hess_ref0.entries(); ++i) { + EXPECT_TRUE(torch::equal(*(std::get<0>(hess)[i]), *(hess_ref0)[i])); + EXPECT_TRUE(torch::equal(*(std::get<1>(hess)[i]), *(hess_ref1)[i])); + } + + /// Evaluation of Hessian (in physical domain) + auto ihess = + functionspace.template ihess_all( + RT_geometry, xi); + auto ihess_ref0 = bspline0.ihess(geometry0, xi_); + auto ihess_ref1 = bspline1.ihess(geometry1, xi_); + + for (std::size_t i = 0; i < ihess_ref0.entries(); ++i) { + EXPECT_TRUE(torch::equal(*(std::get<0>(ihess)[i]), *(ihess_ref0)[i])); + EXPECT_TRUE(torch::equal(*(std::get<1>(ihess)[i]), *(ihess_ref1)[i])); + } + } +} + +TEST_F(FunctionSpaceTest, RT_geoDim1_degrees23) { + using iganet::deriv; + using iganet::functionspace; + using iganet::side; + using BSpline = iganet::NonUniformBSpline; + using Geometry = iganet::NonUniformBSpline; + iganet::RT functionspace({5, 6}, iganet::init::greville, options); + iganet::RT RT_geometry({5, 6}, iganet::init::greville, options); + + iganet::NonUniformBSpline bspline0( + {5 + 1, 6}, iganet::init::greville, options); + iganet::NonUniformBSpline bspline1( + {5, 6 + 1}, iganet::init::greville, options); + iganet::NonUniformBSpline bspline2( + {5, 6}, iganet::init::greville, options); + iganet::NonUniformBSpline geometry0( + {5 + 1, 6}, iganet::init::greville, options); + iganet::NonUniformBSpline geometry1( + {5, 6 + 1}, iganet::init::greville, options); + iganet::NonUniformBSpline geometry2( + {5, 6}, iganet::init::greville, options); + + { // Interior + + auto xi_ = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r} /* u */, + {1.0_r, 0.2_r, 0.1_r, 0.5_r, 0.9_r, 0.75_r, 0.0_r} /* v */); + auto xi = std::tuple{xi_, xi_, xi_}; + + // Evaluation + auto eval = + functionspace.eval(xi); + + EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), + *(bspline0.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), + *(bspline1.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<2>(eval)[0]), + *(bspline2.eval(xi_)[0]))); + + eval = functionspace.eval(xi); + + EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), + *(bspline0.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), + *(bspline1.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<2>(eval)[0]), + *(bspline2.eval(xi_)[0]))); + + eval = + functionspace.eval(xi); + + EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), + *(bspline0.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), + *(bspline1.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<2>(eval)[0]), + *(bspline2.eval(xi_)[0]))); + + eval = functionspace.eval(xi); + + EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), + *(bspline0.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), + *(bspline1.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<2>(eval)[0]), + *(bspline2.eval(xi_)[0]))); + + eval = + functionspace.eval(xi); + + EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), + *(bspline0.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), + *(bspline1.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<2>(eval)[0]), + *(bspline2.eval(xi_)[0]))); + + eval = functionspace + .eval(xi); + + EXPECT_TRUE( + torch::equal(*(std::get<0>(eval)[0]), + *(bspline0.eval(xi_)[0]))); + EXPECT_TRUE( + torch::equal(*(std::get<1>(eval)[0]), + *(bspline1.eval(xi_)[0]))); + EXPECT_TRUE( + torch::equal(*(std::get<2>(eval)[0]), + *(bspline2.eval(xi_)[0]))); + + // Evaluation from precomputed coefficients and basis functions + auto knot_indices = + functionspace.template find_knot_indices(xi); + auto coeff_indices = + functionspace.template find_coeff_indices( + knot_indices); + + auto numel = [](const auto &xi) { + return std::tuple{std::get<0>(xi)[0].numel(), std::get<1>(xi)[0].numel(), + std::get<2>(xi)[0].numel()}; + }; + auto sizes = [](const auto &xi) { + return std::tuple{std::get<0>(xi)[0].sizes(), std::get<1>(xi)[0].sizes(), + std::get<2>(xi)[0].sizes()}; + }; + + auto basfunc = + functionspace + .template eval_basfunc( + xi, knot_indices); + eval = functionspace.eval_from_precomputed( + basfunc, coeff_indices, numel(xi), sizes(xi)); + + EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), + *(bspline0.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), + *(bspline1.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<2>(eval)[0]), + *(bspline2.eval(xi_)[0]))); + + basfunc = + functionspace + .template eval_basfunc( + xi, knot_indices); + eval = functionspace.eval_from_precomputed( + basfunc, coeff_indices, numel(xi), sizes(xi)); + + EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), + *(bspline0.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), + *(bspline1.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<2>(eval)[0]), + *(bspline2.eval(xi_)[0]))); + + basfunc = functionspace.template eval_basfunc( + xi, knot_indices); + eval = functionspace.eval_from_precomputed( + basfunc, coeff_indices, numel(xi), sizes(xi)); + + EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), + *(bspline0.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), + *(bspline1.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<2>(eval)[0]), + *(bspline2.eval(xi_)[0]))); + + basfunc = + functionspace + .template eval_basfunc( + xi, knot_indices); + eval = functionspace.eval_from_precomputed( + basfunc, coeff_indices, numel(xi), sizes(xi)); + + EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), + *(bspline0.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), + *(bspline1.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<2>(eval)[0]), + *(bspline2.eval(xi_)[0]))); + + basfunc = functionspace.template eval_basfunc( + xi, knot_indices); + eval = functionspace.eval_from_precomputed( + basfunc, coeff_indices, numel(xi), sizes(xi)); + + EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), + *(bspline0.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), + *(bspline1.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<2>(eval)[0]), + *(bspline2.eval(xi_)[0]))); + + basfunc = functionspace.template eval_basfunc( + xi, knot_indices); + eval = functionspace.eval_from_precomputed( + basfunc, coeff_indices, numel(xi), sizes(xi)); + + EXPECT_TRUE( + torch::equal(*(std::get<0>(eval)[0]), + *(bspline0.eval(xi_)[0]))); + EXPECT_TRUE( + torch::equal(*(std::get<1>(eval)[0]), + *(bspline1.eval(xi_)[0]))); + EXPECT_TRUE( + torch::equal(*(std::get<2>(eval)[0]), + *(bspline2.eval(xi_)[0]))); + + /// Evaluation of gradient (in parametric domain) + auto grad = + functionspace.template grad_all(xi); + auto grad_ref0 = bspline0.grad(xi_); + auto grad_ref1 = bspline1.grad(xi_); + auto grad_ref2 = bspline2.grad(xi_); + + for (std::size_t i = 0; i < grad_ref0.entries(); ++i) { + EXPECT_TRUE(torch::equal(*(std::get<0>(grad)[i]), *(grad_ref0)[i])); + EXPECT_TRUE(torch::equal(*(std::get<1>(grad)[i]), *(grad_ref1)[i])); + EXPECT_TRUE(torch::equal(*(std::get<2>(grad)[i]), *(grad_ref2)[i])); + } + + /// Evaluation of gradient (in physical domain) + auto igrad = + functionspace.template igrad_all( + RT_geometry, xi); + auto igrad_ref0 = bspline0.igrad(geometry0, xi_); + auto igrad_ref1 = bspline1.igrad(geometry1, xi_); + auto igrad_ref2 = bspline2.igrad(geometry2, xi_); + + for (std::size_t i = 0; i < igrad_ref0.entries(); ++i) { + EXPECT_TRUE(torch::equal(*(std::get<0>(igrad)[i]), *(igrad_ref0)[i])); + EXPECT_TRUE(torch::equal(*(std::get<1>(igrad)[i]), *(igrad_ref1)[i])); + EXPECT_TRUE(torch::equal(*(std::get<2>(igrad)[i]), *(igrad_ref2)[i])); + } + + /// Evaluation of Jacobian (in parametric domain) + auto jac = + functionspace.template jac_all(xi); + auto jac_ref0 = bspline0.jac(xi_); + auto jac_ref1 = bspline1.jac(xi_); + auto jac_ref2 = bspline2.jac(xi_); + + for (std::size_t i = 0; i < jac_ref0.entries(); ++i) { + EXPECT_TRUE(torch::equal(*(std::get<0>(jac)[i]), *(jac_ref0)[i])); + EXPECT_TRUE(torch::equal(*(std::get<1>(jac)[i]), *(jac_ref1)[i])); + EXPECT_TRUE(torch::equal(*(std::get<2>(jac)[i]), *(jac_ref2)[i])); + } + + /// Evaluation of Jacobian (in physical domain) + auto ijac = functionspace.template ijac_all( + RT_geometry, xi); + auto ijac_ref0 = bspline0.ijac(geometry0, xi_); + auto ijac_ref1 = bspline1.ijac(geometry1, xi_); + auto ijac_ref2 = bspline2.ijac(geometry2, xi_); + + for (std::size_t i = 0; i < ijac_ref0.entries(); ++i) { + EXPECT_TRUE(torch::equal(*(std::get<0>(ijac)[i]), *(ijac_ref0)[i])); + EXPECT_TRUE(torch::equal(*(std::get<1>(ijac)[i]), *(ijac_ref1)[i])); + EXPECT_TRUE(torch::equal(*(std::get<2>(ijac)[i]), *(ijac_ref2)[i])); + } + + /// Evaluation of Hessian (in parametric domain) + auto hess = + functionspace.template hess_all(xi); + auto hess_ref0 = bspline0.hess(xi_); + auto hess_ref1 = bspline1.hess(xi_); + auto hess_ref2 = bspline2.hess(xi_); + + for (std::size_t i = 0; i < hess_ref0.entries(); ++i) { + EXPECT_TRUE(torch::equal(*(std::get<0>(hess)[i]), *(hess_ref0)[i])); + EXPECT_TRUE(torch::equal(*(std::get<1>(hess)[i]), *(hess_ref1)[i])); + EXPECT_TRUE(torch::equal(*(std::get<2>(hess)[i]), *(hess_ref2)[i])); + } + + /// Evaluation of Hessian (in physical domain) + auto ihess = + functionspace.template ihess_all( + RT_geometry, xi); + auto ihess_ref0 = bspline0.ihess(geometry0, xi_); + auto ihess_ref1 = bspline1.ihess(geometry1, xi_); + auto ihess_ref2 = bspline2.ihess(geometry2, xi_); + + for (std::size_t i = 0; i < ihess_ref0.entries(); ++i) { + EXPECT_TRUE(torch::equal(*(std::get<0>(ihess)[i]), *(ihess_ref0)[i])); + EXPECT_TRUE(torch::equal(*(std::get<1>(ihess)[i]), *(ihess_ref1)[i])); + EXPECT_TRUE(torch::equal(*(std::get<2>(ihess)[i]), *(ihess_ref2)[i])); + } + } +} + +TEST_F(FunctionSpaceTest, RT_geoDim1_degrees234) { + using iganet::deriv; + using iganet::functionspace; + using iganet::side; + using BSpline = iganet::NonUniformBSpline; + using Geometry = iganet::NonUniformBSpline; + iganet::RT functionspace({5, 6, 7}, iganet::init::greville, options); + iganet::RT RT_geometry({5, 6, 7}, iganet::init::greville, options); + + iganet::NonUniformBSpline bspline0( + {5 + 1, 6, 7}, iganet::init::greville, options); + iganet::NonUniformBSpline bspline1( + {5, 6 + 1, 7}, iganet::init::greville, options); + iganet::NonUniformBSpline bspline2( + {5, 6, 7 + 1}, iganet::init::greville, options); + iganet::NonUniformBSpline bspline3( + {5, 6, 7}, iganet::init::greville, options); + iganet::NonUniformBSpline geometry0( + {5 + 1, 6, 7}, iganet::init::greville, options); + iganet::NonUniformBSpline geometry1( + {5, 6 + 1, 7}, iganet::init::greville, options); + iganet::NonUniformBSpline geometry2( + {5, 6, 7 + 1}, iganet::init::greville, options); + iganet::NonUniformBSpline geometry3( + {5, 6, 7}, iganet::init::greville, options); + + { // Interior + + auto xi_ = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r} /* u */, + {1.0_r, 0.2_r, 0.1_r, 0.5_r, 0.9_r, 0.75_r, 0.0_r} /* v */, + {0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r, 0.0_r, 0.1_r} /* w */); + auto xi = std::tuple{xi_, xi_, xi_, xi_}; + + // Evaluation + auto eval = + functionspace.eval(xi); + + EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), + *(bspline0.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), + *(bspline1.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<2>(eval)[0]), + *(bspline2.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<3>(eval)[0]), + *(bspline3.eval(xi_)[0]))); + + eval = functionspace.eval(xi); + + EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), + *(bspline0.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), + *(bspline1.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<2>(eval)[0]), + *(bspline2.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<3>(eval)[0]), + *(bspline3.eval(xi_)[0]))); + + eval = + functionspace.eval(xi); + + EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), + *(bspline0.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), + *(bspline1.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<2>(eval)[0]), + *(bspline2.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<3>(eval)[0]), + *(bspline3.eval(xi_)[0]))); + + eval = functionspace.eval(xi); + + EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), + *(bspline0.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), + *(bspline1.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<2>(eval)[0]), + *(bspline2.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<3>(eval)[0]), + *(bspline3.eval(xi_)[0]))); + + eval = + functionspace.eval(xi); + + EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), + *(bspline0.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), + *(bspline1.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<2>(eval)[0]), + *(bspline2.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<3>(eval)[0]), + *(bspline3.eval(xi_)[0]))); + + eval = functionspace.eval(xi); + + EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), + *(bspline0.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), + *(bspline1.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<2>(eval)[0]), + *(bspline2.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<3>(eval)[0]), + *(bspline3.eval(xi_)[0]))); + + eval = + functionspace.eval(xi); + + EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), + *(bspline0.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), + *(bspline1.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<2>(eval)[0]), + *(bspline2.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<3>(eval)[0]), + *(bspline3.eval(xi_)[0]))); + + eval = functionspace + .eval(xi); + + EXPECT_TRUE( + torch::equal(*(std::get<0>(eval)[0]), + *(bspline0.eval(xi_)[0]))); + EXPECT_TRUE( + torch::equal(*(std::get<1>(eval)[0]), + *(bspline1.eval(xi_)[0]))); + EXPECT_TRUE( + torch::equal(*(std::get<2>(eval)[0]), + *(bspline2.eval(xi_)[0]))); + EXPECT_TRUE( + torch::equal(*(std::get<3>(eval)[0]), + *(bspline3.eval(xi_)[0]))); + + eval = functionspace + .eval(xi); + + EXPECT_TRUE( + torch::equal(*(std::get<0>(eval)[0]), + *(bspline0.eval(xi_)[0]))); + EXPECT_TRUE( + torch::equal(*(std::get<1>(eval)[0]), + *(bspline1.eval(xi_)[0]))); + EXPECT_TRUE( + torch::equal(*(std::get<2>(eval)[0]), + *(bspline2.eval(xi_)[0]))); + EXPECT_TRUE( + torch::equal(*(std::get<3>(eval)[0]), + *(bspline3.eval(xi_)[0]))); + + eval = functionspace + .eval(xi); + + EXPECT_TRUE( + torch::equal(*(std::get<0>(eval)[0]), + *(bspline0.eval(xi_)[0]))); + EXPECT_TRUE( + torch::equal(*(std::get<1>(eval)[0]), + *(bspline1.eval(xi_)[0]))); + EXPECT_TRUE( + torch::equal(*(std::get<2>(eval)[0]), + *(bspline2.eval(xi_)[0]))); + EXPECT_TRUE( + torch::equal(*(std::get<3>(eval)[0]), + *(bspline3.eval(xi_)[0]))); + + // Evaluation from precomputed coefficients and basis functions + auto knot_indices = + functionspace.template find_knot_indices(xi); + auto coeff_indices = + functionspace.template find_coeff_indices( + knot_indices); + + auto numel = [](const auto &xi) { + return std::tuple{std::get<0>(xi)[0].numel(), std::get<1>(xi)[0].numel(), + std::get<2>(xi)[0].numel(), std::get<3>(xi)[0].numel()}; + }; + auto sizes = [](const auto &xi) { + return std::tuple{std::get<0>(xi)[0].sizes(), std::get<1>(xi)[0].sizes(), + std::get<2>(xi)[0].sizes(), std::get<3>(xi)[0].sizes()}; + }; + + auto basfunc = + functionspace + .template eval_basfunc( + xi, knot_indices); + eval = functionspace.eval_from_precomputed( + basfunc, coeff_indices, numel(xi), sizes(xi)); + + EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), + *(bspline0.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), + *(bspline1.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<2>(eval)[0]), + *(bspline2.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<3>(eval)[0]), + *(bspline3.eval(xi_)[0]))); + + basfunc = + functionspace + .template eval_basfunc( + xi, knot_indices); + eval = functionspace.eval_from_precomputed( + basfunc, coeff_indices, numel(xi), sizes(xi)); + + EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), + *(bspline0.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), + *(bspline1.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<2>(eval)[0]), + *(bspline2.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<3>(eval)[0]), + *(bspline3.eval(xi_)[0]))); + + basfunc = functionspace.template eval_basfunc( + xi, knot_indices); + eval = functionspace.eval_from_precomputed( + basfunc, coeff_indices, numel(xi), sizes(xi)); + + EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), + *(bspline0.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), + *(bspline1.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<2>(eval)[0]), + *(bspline2.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<3>(eval)[0]), + *(bspline3.eval(xi_)[0]))); + + basfunc = + functionspace + .template eval_basfunc( + xi, knot_indices); + eval = functionspace.eval_from_precomputed( + basfunc, coeff_indices, numel(xi), sizes(xi)); + + EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), + *(bspline0.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), + *(bspline1.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<2>(eval)[0]), + *(bspline2.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<3>(eval)[0]), + *(bspline3.eval(xi_)[0]))); + + basfunc = functionspace.template eval_basfunc( + xi, knot_indices); + eval = functionspace.eval_from_precomputed( + basfunc, coeff_indices, numel(xi), sizes(xi)); + + EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), + *(bspline0.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), + *(bspline1.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<2>(eval)[0]), + *(bspline2.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<3>(eval)[0]), + *(bspline3.eval(xi_)[0]))); + + basfunc = + functionspace + .template eval_basfunc( + xi, knot_indices); + eval = functionspace.eval_from_precomputed( + basfunc, coeff_indices, numel(xi), sizes(xi)); + + EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), + *(bspline0.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), + *(bspline1.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<2>(eval)[0]), + *(bspline2.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<3>(eval)[0]), + *(bspline3.eval(xi_)[0]))); + + basfunc = functionspace.template eval_basfunc( + xi, knot_indices); + eval = functionspace.eval_from_precomputed( + basfunc, coeff_indices, numel(xi), sizes(xi)); + + EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), + *(bspline0.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), + *(bspline1.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<2>(eval)[0]), + *(bspline2.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<3>(eval)[0]), + *(bspline3.eval(xi_)[0]))); + + basfunc = functionspace.template eval_basfunc( + xi, knot_indices); + eval = functionspace.eval_from_precomputed( + basfunc, coeff_indices, numel(xi), sizes(xi)); + + EXPECT_TRUE( + torch::equal(*(std::get<0>(eval)[0]), + *(bspline0.eval(xi_)[0]))); + EXPECT_TRUE( + torch::equal(*(std::get<1>(eval)[0]), + *(bspline1.eval(xi_)[0]))); + EXPECT_TRUE( + torch::equal(*(std::get<2>(eval)[0]), + *(bspline2.eval(xi_)[0]))); + EXPECT_TRUE( + torch::equal(*(std::get<3>(eval)[0]), + *(bspline3.eval(xi_)[0]))); + + basfunc = functionspace.template eval_basfunc( + xi, knot_indices); + eval = functionspace.eval_from_precomputed( + basfunc, coeff_indices, numel(xi), sizes(xi)); + + EXPECT_TRUE( + torch::equal(*(std::get<0>(eval)[0]), + *(bspline0.eval(xi_)[0]))); + EXPECT_TRUE( + torch::equal(*(std::get<1>(eval)[0]), + *(bspline1.eval(xi_)[0]))); + EXPECT_TRUE( + torch::equal(*(std::get<2>(eval)[0]), + *(bspline2.eval(xi_)[0]))); + EXPECT_TRUE( + torch::equal(*(std::get<3>(eval)[0]), + *(bspline3.eval(xi_)[0]))); + + basfunc = functionspace.template eval_basfunc( + xi, knot_indices); + eval = functionspace.eval_from_precomputed( + basfunc, coeff_indices, numel(xi), sizes(xi)); + + EXPECT_TRUE( + torch::equal(*(std::get<0>(eval)[0]), + *(bspline0.eval(xi_)[0]))); + EXPECT_TRUE( + torch::equal(*(std::get<1>(eval)[0]), + *(bspline1.eval(xi_)[0]))); + EXPECT_TRUE( + torch::equal(*(std::get<2>(eval)[0]), + *(bspline2.eval(xi_)[0]))); + EXPECT_TRUE( + torch::equal(*(std::get<3>(eval)[0]), + *(bspline3.eval(xi_)[0]))); + + /// Evaluation of gradient (in parametric domain) + auto grad = + functionspace.template grad_all(xi); + auto grad_ref0 = bspline0.grad(xi_); + auto grad_ref1 = bspline1.grad(xi_); + auto grad_ref2 = bspline2.grad(xi_); + auto grad_ref3 = bspline3.grad(xi_); + + for (std::size_t i = 0; i < grad_ref0.entries(); ++i) { + EXPECT_TRUE(torch::equal(*(std::get<0>(grad)[i]), *(grad_ref0)[i])); + EXPECT_TRUE(torch::equal(*(std::get<1>(grad)[i]), *(grad_ref1)[i])); + EXPECT_TRUE(torch::equal(*(std::get<2>(grad)[i]), *(grad_ref2)[i])); + EXPECT_TRUE(torch::equal(*(std::get<3>(grad)[i]), *(grad_ref3)[i])); + } + + /// Evaluation of gradient (in physical domain) + auto igrad = + functionspace.template igrad_all( + RT_geometry, xi); + auto igrad_ref0 = bspline0.igrad(geometry0, xi_); + auto igrad_ref1 = bspline1.igrad(geometry1, xi_); + auto igrad_ref2 = bspline2.igrad(geometry2, xi_); + auto igrad_ref3 = bspline3.igrad(geometry3, xi_); + + for (std::size_t i = 0; i < igrad_ref0.entries(); ++i) { + EXPECT_TRUE(torch::equal(*(std::get<0>(igrad)[i]), *(igrad_ref0)[i])); + EXPECT_TRUE(torch::equal(*(std::get<1>(igrad)[i]), *(igrad_ref1)[i])); + EXPECT_TRUE(torch::equal(*(std::get<2>(igrad)[i]), *(igrad_ref2)[i])); + EXPECT_TRUE(torch::equal(*(std::get<3>(igrad)[i]), *(igrad_ref3)[i])); + } + + /// Evaluation of Jacobian (in parametric domain) + auto jac = + functionspace.template jac_all(xi); + auto jac_ref0 = bspline0.jac(xi_); + auto jac_ref1 = bspline1.jac(xi_); + auto jac_ref2 = bspline2.jac(xi_); + auto jac_ref3 = bspline3.jac(xi_); + + for (std::size_t i = 0; i < jac_ref0.entries(); ++i) { + EXPECT_TRUE(torch::equal(*(std::get<0>(jac)[i]), *(jac_ref0)[i])); + EXPECT_TRUE(torch::equal(*(std::get<1>(jac)[i]), *(jac_ref1)[i])); + EXPECT_TRUE(torch::equal(*(std::get<2>(jac)[i]), *(jac_ref2)[i])); + EXPECT_TRUE(torch::equal(*(std::get<3>(jac)[i]), *(jac_ref3)[i])); + } + + /// Evaluation of Jacobian (in physical domain) + auto ijac = functionspace.template ijac_all( + RT_geometry, xi); + auto ijac_ref0 = bspline0.ijac(geometry0, xi_); + auto ijac_ref1 = bspline1.ijac(geometry1, xi_); + auto ijac_ref2 = bspline2.ijac(geometry2, xi_); + auto ijac_ref3 = bspline3.ijac(geometry3, xi_); + + for (std::size_t i = 0; i < ijac_ref0.entries(); ++i) { + EXPECT_TRUE(torch::equal(*(std::get<0>(ijac)[i]), *(ijac_ref0)[i])); + EXPECT_TRUE(torch::equal(*(std::get<1>(ijac)[i]), *(ijac_ref1)[i])); + EXPECT_TRUE(torch::equal(*(std::get<2>(ijac)[i]), *(ijac_ref2)[i])); + EXPECT_TRUE(torch::equal(*(std::get<3>(ijac)[i]), *(ijac_ref3)[i])); + } + + /// Evaluation of Hessian (in parametric domain) + auto hess = + functionspace.template hess_all(xi); + auto hess_ref0 = bspline0.hess(xi_); + auto hess_ref1 = bspline1.hess(xi_); + auto hess_ref2 = bspline2.hess(xi_); + auto hess_ref3 = bspline3.hess(xi_); + + for (std::size_t i = 0; i < hess_ref0.entries(); ++i) { + EXPECT_TRUE(torch::equal(*(std::get<0>(hess)[i]), *(hess_ref0)[i])); + EXPECT_TRUE(torch::equal(*(std::get<1>(hess)[i]), *(hess_ref1)[i])); + EXPECT_TRUE(torch::equal(*(std::get<2>(hess)[i]), *(hess_ref2)[i])); + EXPECT_TRUE(torch::equal(*(std::get<3>(hess)[i]), *(hess_ref3)[i])); + } + + /// Evaluation of Hessian (in physical domain) + auto ihess = + functionspace.template ihess_all( + RT_geometry, xi); + auto ihess_ref0 = bspline0.ihess(geometry0, xi_); + auto ihess_ref1 = bspline1.ihess(geometry1, xi_); + auto ihess_ref2 = bspline2.ihess(geometry2, xi_); + auto ihess_ref3 = bspline3.ihess(geometry3, xi_); + + for (std::size_t i = 0; i < ihess_ref0.entries(); ++i) { + EXPECT_TRUE(torch::equal(*(std::get<0>(ihess)[i]), *(ihess_ref0)[i])); + EXPECT_TRUE(torch::equal(*(std::get<1>(ihess)[i]), *(ihess_ref1)[i])); + EXPECT_TRUE(torch::equal(*(std::get<2>(ihess)[i]), *(ihess_ref2)[i])); + EXPECT_TRUE(torch::equal(*(std::get<3>(ihess)[i]), *(ihess_ref3)[i])); + } + } +} + +TEST_F(FunctionSpaceTest, RT_geoDim1_degrees2341) { + using iganet::deriv; + using iganet::functionspace; + using iganet::side; + using BSpline = iganet::NonUniformBSpline; + using Geometry = iganet::NonUniformBSpline; + iganet::RT functionspace({5, 6, 7, 4}, iganet::init::greville, + options); + iganet::RT RT_geometry({5, 6, 7, 4}, iganet::init::greville, + options); + + iganet::NonUniformBSpline bspline0( + {5 + 1, 6, 7, 4}, iganet::init::greville, options); + iganet::NonUniformBSpline bspline1( + {5, 6 + 1, 7, 4}, iganet::init::greville, options); + iganet::NonUniformBSpline bspline2( + {5, 6, 7 + 1, 4}, iganet::init::greville, options); + iganet::NonUniformBSpline bspline3( + {5, 6, 7, 4 + 1}, iganet::init::greville, options); + iganet::NonUniformBSpline bspline4( + {5, 6, 7, 4}, iganet::init::greville, options); + iganet::NonUniformBSpline geometry0( + {5 + 1, 6, 7, 4}, iganet::init::greville, options); + iganet::NonUniformBSpline geometry1( + {5, 6 + 1, 7, 4}, iganet::init::greville, options); + iganet::NonUniformBSpline geometry2( + {5, 6, 7 + 1, 4}, iganet::init::greville, options); + iganet::NonUniformBSpline geometry3( + {5, 6, 7, 4 + 1}, iganet::init::greville, options); + iganet::NonUniformBSpline geometry4( + {5, 6, 7, 4}, iganet::init::greville, options); + + { // Interior + + auto xi_ = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r} /* u */, + {1.0_r, 0.2_r, 0.1_r, 0.5_r, 0.9_r, 0.75_r, 0.0_r} /* v */, + {0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r, 0.0_r, 0.1_r} /* w */, + {0.1_r, 0.1_r, 0.2_r, 0.3_r, 0.3_r, 0.0_r, 0.1_r} /* t */); + auto xi = std::tuple{xi_, xi_, xi_, xi_, xi_}; + + // Evaluation + auto eval = + functionspace.eval(xi); + + EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), + *(bspline0.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), + *(bspline1.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<2>(eval)[0]), + *(bspline2.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<3>(eval)[0]), + *(bspline3.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<4>(eval)[0]), + *(bspline4.eval(xi_)[0]))); + + eval = functionspace.eval(xi); + + EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), + *(bspline0.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), + *(bspline1.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<2>(eval)[0]), + *(bspline2.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<3>(eval)[0]), + *(bspline3.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<4>(eval)[0]), + *(bspline4.eval(xi_)[0]))); + + eval = + functionspace.eval(xi); + + EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), + *(bspline0.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), + *(bspline1.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<2>(eval)[0]), + *(bspline2.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<3>(eval)[0]), + *(bspline3.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<4>(eval)[0]), + *(bspline4.eval(xi_)[0]))); + + eval = functionspace.eval(xi); + + EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), + *(bspline0.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), + *(bspline1.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<2>(eval)[0]), + *(bspline2.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<3>(eval)[0]), + *(bspline3.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<4>(eval)[0]), + *(bspline4.eval(xi_)[0]))); + + eval = + functionspace.eval(xi); + + EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), + *(bspline0.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), + *(bspline1.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<2>(eval)[0]), + *(bspline2.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<3>(eval)[0]), + *(bspline3.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<4>(eval)[0]), + *(bspline4.eval(xi_)[0]))); + + eval = functionspace.eval(xi); + + EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), + *(bspline0.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), + *(bspline1.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<2>(eval)[0]), + *(bspline2.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<3>(eval)[0]), + *(bspline3.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<4>(eval)[0]), + *(bspline4.eval(xi_)[0]))); + + eval = + functionspace.eval(xi); + + EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), + *(bspline0.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), + *(bspline1.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<2>(eval)[0]), + *(bspline2.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<3>(eval)[0]), + *(bspline3.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<4>(eval)[0]), + *(bspline4.eval(xi_)[0]))); + + eval = functionspace.eval(xi); + + EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), + *(bspline0.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), + *(bspline1.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<2>(eval)[0]), + *(bspline2.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<3>(eval)[0]), + *(bspline3.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<4>(eval)[0]), + *(bspline4.eval(xi_)[0]))); + + eval = + functionspace.eval(xi); + + EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), + *(bspline0.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), + *(bspline1.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<2>(eval)[0]), + *(bspline2.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<3>(eval)[0]), + *(bspline3.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<4>(eval)[0]), + *(bspline4.eval(xi_)[0]))); + + eval = functionspace + .eval(xi); + + EXPECT_TRUE( + torch::equal(*(std::get<0>(eval)[0]), + *(bspline0.eval(xi_)[0]))); + EXPECT_TRUE( + torch::equal(*(std::get<1>(eval)[0]), + *(bspline1.eval(xi_)[0]))); + EXPECT_TRUE( + torch::equal(*(std::get<2>(eval)[0]), + *(bspline2.eval(xi_)[0]))); + EXPECT_TRUE( + torch::equal(*(std::get<3>(eval)[0]), + *(bspline3.eval(xi_)[0]))); + EXPECT_TRUE( + torch::equal(*(std::get<4>(eval)[0]), + *(bspline4.eval(xi_)[0]))); + + eval = functionspace + .eval(xi); + + EXPECT_TRUE( + torch::equal(*(std::get<0>(eval)[0]), + *(bspline0.eval(xi_)[0]))); + EXPECT_TRUE( + torch::equal(*(std::get<1>(eval)[0]), + *(bspline1.eval(xi_)[0]))); + EXPECT_TRUE( + torch::equal(*(std::get<2>(eval)[0]), + *(bspline2.eval(xi_)[0]))); + EXPECT_TRUE( + torch::equal(*(std::get<3>(eval)[0]), + *(bspline3.eval(xi_)[0]))); + EXPECT_TRUE( + torch::equal(*(std::get<4>(eval)[0]), + *(bspline4.eval(xi_)[0]))); + + eval = functionspace + .eval(xi); + + EXPECT_TRUE( + torch::equal(*(std::get<0>(eval)[0]), + *(bspline0.eval(xi_)[0]))); + EXPECT_TRUE( + torch::equal(*(std::get<1>(eval)[0]), + *(bspline1.eval(xi_)[0]))); + EXPECT_TRUE( + torch::equal(*(std::get<2>(eval)[0]), + *(bspline2.eval(xi_)[0]))); + EXPECT_TRUE( + torch::equal(*(std::get<3>(eval)[0]), + *(bspline3.eval(xi_)[0]))); + EXPECT_TRUE( + torch::equal(*(std::get<4>(eval)[0]), + *(bspline4.eval(xi_)[0]))); + + eval = functionspace + .eval(xi); + + EXPECT_TRUE( + torch::equal(*(std::get<0>(eval)[0]), + *(bspline0.eval(xi_)[0]))); + EXPECT_TRUE( + torch::equal(*(std::get<1>(eval)[0]), + *(bspline1.eval(xi_)[0]))); + EXPECT_TRUE( + torch::equal(*(std::get<2>(eval)[0]), + *(bspline2.eval(xi_)[0]))); + EXPECT_TRUE( + torch::equal(*(std::get<3>(eval)[0]), + *(bspline3.eval(xi_)[0]))); + EXPECT_TRUE( + torch::equal(*(std::get<4>(eval)[0]), + *(bspline4.eval(xi_)[0]))); + + eval = functionspace + .eval(xi); + + EXPECT_TRUE( + torch::equal(*(std::get<0>(eval)[0]), + *(bspline0.eval(xi_)[0]))); + EXPECT_TRUE( + torch::equal(*(std::get<1>(eval)[0]), + *(bspline1.eval(xi_)[0]))); + EXPECT_TRUE( + torch::equal(*(std::get<2>(eval)[0]), + *(bspline2.eval(xi_)[0]))); + EXPECT_TRUE( + torch::equal(*(std::get<3>(eval)[0]), + *(bspline3.eval(xi_)[0]))); + EXPECT_TRUE( + torch::equal(*(std::get<4>(eval)[0]), + *(bspline4.eval(xi_)[0]))); + + eval = functionspace + .eval(xi); + + EXPECT_TRUE( + torch::equal(*(std::get<0>(eval)[0]), + *(bspline0.eval(xi_)[0]))); + EXPECT_TRUE( + torch::equal(*(std::get<1>(eval)[0]), + *(bspline1.eval(xi_)[0]))); + EXPECT_TRUE( + torch::equal(*(std::get<2>(eval)[0]), + *(bspline2.eval(xi_)[0]))); + EXPECT_TRUE( + torch::equal(*(std::get<3>(eval)[0]), + *(bspline3.eval(xi_)[0]))); + EXPECT_TRUE( + torch::equal(*(std::get<4>(eval)[0]), + *(bspline4.eval(xi_)[0]))); + + // Evaluation from precomputed coefficients and basis functions + auto knot_indices = + functionspace.template find_knot_indices(xi); + auto coeff_indices = + functionspace.template find_coeff_indices( + knot_indices); + + auto numel = [](const auto &xi) { + return std::tuple{std::get<0>(xi)[0].numel(), std::get<1>(xi)[0].numel(), + std::get<2>(xi)[0].numel(), std::get<3>(xi)[0].numel(), + std::get<4>(xi)[0].numel()}; + }; + auto sizes = [](const auto &xi) { + return std::tuple{std::get<0>(xi)[0].sizes(), std::get<1>(xi)[0].sizes(), + std::get<2>(xi)[0].sizes(), std::get<3>(xi)[0].sizes(), + std::get<4>(xi)[0].sizes()}; + }; + + auto basfunc = + functionspace + .template eval_basfunc( + xi, knot_indices); + eval = functionspace.eval_from_precomputed( + basfunc, coeff_indices, numel(xi), sizes(xi)); + + EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), + *(bspline0.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), + *(bspline1.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<2>(eval)[0]), + *(bspline2.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<3>(eval)[0]), + *(bspline3.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<4>(eval)[0]), + *(bspline4.eval(xi_)[0]))); + + basfunc = + functionspace + .template eval_basfunc( + xi, knot_indices); + eval = functionspace.eval_from_precomputed( + basfunc, coeff_indices, numel(xi), sizes(xi)); + + EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), + *(bspline0.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), + *(bspline1.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<2>(eval)[0]), + *(bspline2.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<3>(eval)[0]), + *(bspline3.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<4>(eval)[0]), + *(bspline4.eval(xi_)[0]))); + + basfunc = functionspace.template eval_basfunc( + xi, knot_indices); + eval = functionspace.eval_from_precomputed( + basfunc, coeff_indices, numel(xi), sizes(xi)); + + EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), + *(bspline0.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), + *(bspline1.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<2>(eval)[0]), + *(bspline2.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<3>(eval)[0]), + *(bspline3.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<4>(eval)[0]), + *(bspline4.eval(xi_)[0]))); + + basfunc = + functionspace + .template eval_basfunc( + xi, knot_indices); + eval = functionspace.eval_from_precomputed( + basfunc, coeff_indices, numel(xi), sizes(xi)); + + EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), + *(bspline0.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), + *(bspline1.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<2>(eval)[0]), + *(bspline2.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<3>(eval)[0]), + *(bspline3.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<4>(eval)[0]), + *(bspline4.eval(xi_)[0]))); + + basfunc = functionspace.template eval_basfunc( + xi, knot_indices); + eval = functionspace.eval_from_precomputed( + basfunc, coeff_indices, numel(xi), sizes(xi)); + + EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), + *(bspline0.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), + *(bspline1.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<2>(eval)[0]), + *(bspline2.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<3>(eval)[0]), + *(bspline3.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<4>(eval)[0]), + *(bspline4.eval(xi_)[0]))); + + basfunc = + functionspace + .template eval_basfunc( + xi, knot_indices); + eval = functionspace.eval_from_precomputed( + basfunc, coeff_indices, numel(xi), sizes(xi)); + + EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), + *(bspline0.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), + *(bspline1.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<2>(eval)[0]), + *(bspline2.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<3>(eval)[0]), + *(bspline3.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<4>(eval)[0]), + *(bspline4.eval(xi_)[0]))); + + basfunc = functionspace.template eval_basfunc( + xi, knot_indices); + eval = functionspace.eval_from_precomputed( + basfunc, coeff_indices, numel(xi), sizes(xi)); + + EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), + *(bspline0.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), + *(bspline1.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<2>(eval)[0]), + *(bspline2.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<3>(eval)[0]), + *(bspline3.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<4>(eval)[0]), + *(bspline4.eval(xi_)[0]))); + + basfunc = + functionspace + .template eval_basfunc( + xi, knot_indices); + eval = functionspace.eval_from_precomputed( + basfunc, coeff_indices, numel(xi), sizes(xi)); + + EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), + *(bspline0.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), + *(bspline1.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<2>(eval)[0]), + *(bspline2.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<3>(eval)[0]), + *(bspline3.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<4>(eval)[0]), + *(bspline4.eval(xi_)[0]))); + + basfunc = functionspace.template eval_basfunc( + xi, knot_indices); + eval = functionspace.eval_from_precomputed( + basfunc, coeff_indices, numel(xi), sizes(xi)); + + EXPECT_TRUE(torch::equal(*(std::get<0>(eval)[0]), + *(bspline0.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<1>(eval)[0]), + *(bspline1.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<2>(eval)[0]), + *(bspline2.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<3>(eval)[0]), + *(bspline3.eval(xi_)[0]))); + EXPECT_TRUE(torch::equal(*(std::get<4>(eval)[0]), + *(bspline4.eval(xi_)[0]))); + + basfunc = functionspace.template eval_basfunc( + xi, knot_indices); + eval = functionspace.eval_from_precomputed( + basfunc, coeff_indices, numel(xi), sizes(xi)); + + EXPECT_TRUE( + torch::equal(*(std::get<0>(eval)[0]), + *(bspline0.eval(xi_)[0]))); + EXPECT_TRUE( + torch::equal(*(std::get<1>(eval)[0]), + *(bspline1.eval(xi_)[0]))); + EXPECT_TRUE( + torch::equal(*(std::get<2>(eval)[0]), + *(bspline2.eval(xi_)[0]))); + EXPECT_TRUE( + torch::equal(*(std::get<3>(eval)[0]), + *(bspline3.eval(xi_)[0]))); + EXPECT_TRUE( + torch::equal(*(std::get<4>(eval)[0]), + *(bspline4.eval(xi_)[0]))); + + basfunc = functionspace.template eval_basfunc( + xi, knot_indices); + eval = functionspace.eval_from_precomputed( + basfunc, coeff_indices, numel(xi), sizes(xi)); + + EXPECT_TRUE( + torch::equal(*(std::get<0>(eval)[0]), + *(bspline0.eval(xi_)[0]))); + EXPECT_TRUE( + torch::equal(*(std::get<1>(eval)[0]), + *(bspline1.eval(xi_)[0]))); + EXPECT_TRUE( + torch::equal(*(std::get<2>(eval)[0]), + *(bspline2.eval(xi_)[0]))); + EXPECT_TRUE( + torch::equal(*(std::get<3>(eval)[0]), + *(bspline3.eval(xi_)[0]))); + EXPECT_TRUE( + torch::equal(*(std::get<4>(eval)[0]), + *(bspline4.eval(xi_)[0]))); + + basfunc = functionspace.template eval_basfunc( + xi, knot_indices); + eval = functionspace.eval_from_precomputed( + basfunc, coeff_indices, numel(xi), sizes(xi)); + + EXPECT_TRUE( + torch::equal(*(std::get<0>(eval)[0]), + *(bspline0.eval(xi_)[0]))); + EXPECT_TRUE( + torch::equal(*(std::get<1>(eval)[0]), + *(bspline1.eval(xi_)[0]))); + EXPECT_TRUE( + torch::equal(*(std::get<2>(eval)[0]), + *(bspline2.eval(xi_)[0]))); + EXPECT_TRUE( + torch::equal(*(std::get<3>(eval)[0]), + *(bspline3.eval(xi_)[0]))); + EXPECT_TRUE( + torch::equal(*(std::get<4>(eval)[0]), + *(bspline4.eval(xi_)[0]))); + + basfunc = functionspace.template eval_basfunc( + xi, knot_indices); + eval = functionspace.eval_from_precomputed( + basfunc, coeff_indices, numel(xi), sizes(xi)); + + EXPECT_TRUE( + torch::equal(*(std::get<0>(eval)[0]), + *(bspline0.eval(xi_)[0]))); + EXPECT_TRUE( + torch::equal(*(std::get<1>(eval)[0]), + *(bspline1.eval(xi_)[0]))); + EXPECT_TRUE( + torch::equal(*(std::get<2>(eval)[0]), + *(bspline2.eval(xi_)[0]))); + EXPECT_TRUE( + torch::equal(*(std::get<3>(eval)[0]), + *(bspline3.eval(xi_)[0]))); + EXPECT_TRUE( + torch::equal(*(std::get<4>(eval)[0]), + *(bspline4.eval(xi_)[0]))); + + basfunc = functionspace.template eval_basfunc( + xi, knot_indices); + eval = functionspace.eval_from_precomputed( + basfunc, coeff_indices, numel(xi), sizes(xi)); + + EXPECT_TRUE( + torch::equal(*(std::get<0>(eval)[0]), + *(bspline0.eval(xi_)[0]))); + EXPECT_TRUE( + torch::equal(*(std::get<1>(eval)[0]), + *(bspline1.eval(xi_)[0]))); + EXPECT_TRUE( + torch::equal(*(std::get<2>(eval)[0]), + *(bspline2.eval(xi_)[0]))); + EXPECT_TRUE( + torch::equal(*(std::get<3>(eval)[0]), + *(bspline3.eval(xi_)[0]))); + EXPECT_TRUE( + torch::equal(*(std::get<4>(eval)[0]), + *(bspline4.eval(xi_)[0]))); + + basfunc = functionspace.template eval_basfunc( + xi, knot_indices); + eval = functionspace.eval_from_precomputed( + basfunc, coeff_indices, numel(xi), sizes(xi)); + + EXPECT_TRUE( + torch::equal(*(std::get<0>(eval)[0]), + *(bspline0.eval(xi_)[0]))); + EXPECT_TRUE( + torch::equal(*(std::get<1>(eval)[0]), + *(bspline1.eval(xi_)[0]))); + EXPECT_TRUE( + torch::equal(*(std::get<2>(eval)[0]), + *(bspline2.eval(xi_)[0]))); + EXPECT_TRUE( + torch::equal(*(std::get<3>(eval)[0]), + *(bspline3.eval(xi_)[0]))); + EXPECT_TRUE( + torch::equal(*(std::get<4>(eval)[0]), + *(bspline4.eval(xi_)[0]))); + + /// Evaluation of gradient (in parametric domain) + auto grad = + functionspace.template grad_all(xi); + auto grad_ref0 = bspline0.grad(xi_); + auto grad_ref1 = bspline1.grad(xi_); + auto grad_ref2 = bspline2.grad(xi_); + auto grad_ref3 = bspline3.grad(xi_); + auto grad_ref4 = bspline4.grad(xi_); + + for (std::size_t i = 0; i < grad_ref0.entries(); ++i) { + EXPECT_TRUE(torch::equal(*(std::get<0>(grad)[i]), *(grad_ref0)[i])); + EXPECT_TRUE(torch::equal(*(std::get<1>(grad)[i]), *(grad_ref1)[i])); + EXPECT_TRUE(torch::equal(*(std::get<2>(grad)[i]), *(grad_ref2)[i])); + EXPECT_TRUE(torch::equal(*(std::get<3>(grad)[i]), *(grad_ref3)[i])); + EXPECT_TRUE(torch::equal(*(std::get<4>(grad)[i]), *(grad_ref4)[i])); + } + + /// Evaluation of gradient (in physical domain) + auto igrad = + functionspace.template igrad_all( + RT_geometry, xi); + auto igrad_ref0 = bspline0.igrad(geometry0, xi_); + auto igrad_ref1 = bspline1.igrad(geometry1, xi_); + auto igrad_ref2 = bspline2.igrad(geometry2, xi_); + auto igrad_ref3 = bspline3.igrad(geometry3, xi_); + auto igrad_ref4 = bspline4.igrad(geometry4, xi_); + + for (std::size_t i = 0; i < igrad_ref0.entries(); ++i) { + EXPECT_TRUE(torch::equal(*(std::get<0>(igrad)[i]), *(igrad_ref0)[i])); + EXPECT_TRUE(torch::equal(*(std::get<1>(igrad)[i]), *(igrad_ref1)[i])); + EXPECT_TRUE(torch::equal(*(std::get<2>(igrad)[i]), *(igrad_ref2)[i])); + EXPECT_TRUE(torch::equal(*(std::get<3>(igrad)[i]), *(igrad_ref3)[i])); + EXPECT_TRUE(torch::equal(*(std::get<4>(igrad)[i]), *(igrad_ref4)[i])); + } + + /// Evaluation of Jacobian (in parametric domain) + auto jac = + functionspace.template jac_all(xi); + auto jac_ref0 = bspline0.jac(xi_); + auto jac_ref1 = bspline1.jac(xi_); + auto jac_ref2 = bspline2.jac(xi_); + auto jac_ref3 = bspline3.jac(xi_); + auto jac_ref4 = bspline4.jac(xi_); + + for (std::size_t i = 0; i < jac_ref0.entries(); ++i) { + EXPECT_TRUE(torch::equal(*(std::get<0>(jac)[i]), *(jac_ref0)[i])); + EXPECT_TRUE(torch::equal(*(std::get<1>(jac)[i]), *(jac_ref1)[i])); + EXPECT_TRUE(torch::equal(*(std::get<2>(jac)[i]), *(jac_ref2)[i])); + EXPECT_TRUE(torch::equal(*(std::get<3>(jac)[i]), *(jac_ref3)[i])); + EXPECT_TRUE(torch::equal(*(std::get<4>(jac)[i]), *(jac_ref4)[i])); + } + + /// Evaluation of Jacobian (in physical domain) + auto ijac = functionspace.template ijac_all( + RT_geometry, xi); + auto ijac_ref0 = bspline0.ijac(geometry0, xi_); + auto ijac_ref1 = bspline1.ijac(geometry1, xi_); + auto ijac_ref2 = bspline2.ijac(geometry2, xi_); + auto ijac_ref3 = bspline3.ijac(geometry3, xi_); + auto ijac_ref4 = bspline4.ijac(geometry4, xi_); + + for (std::size_t i = 0; i < ijac_ref0.entries(); ++i) { + EXPECT_TRUE(torch::equal(*(std::get<0>(ijac)[i]), *(ijac_ref0)[i])); + EXPECT_TRUE(torch::equal(*(std::get<1>(ijac)[i]), *(ijac_ref1)[i])); + EXPECT_TRUE(torch::equal(*(std::get<2>(ijac)[i]), *(ijac_ref2)[i])); + EXPECT_TRUE(torch::equal(*(std::get<3>(ijac)[i]), *(ijac_ref3)[i])); + EXPECT_TRUE(torch::equal(*(std::get<4>(ijac)[i]), *(ijac_ref4)[i])); + } + + /// Evaluation of Hessian (in parametric domain) + auto hess = + functionspace.template hess_all(xi); + auto hess_ref0 = bspline0.hess(xi_); + auto hess_ref1 = bspline1.hess(xi_); + auto hess_ref2 = bspline2.hess(xi_); + auto hess_ref3 = bspline3.hess(xi_); + auto hess_ref4 = bspline4.hess(xi_); + + for (std::size_t i = 0; i < hess_ref0.entries(); ++i) { + EXPECT_TRUE(torch::equal(*(std::get<0>(hess)[i]), *(hess_ref0)[i])); + EXPECT_TRUE(torch::equal(*(std::get<1>(hess)[i]), *(hess_ref1)[i])); + EXPECT_TRUE(torch::equal(*(std::get<2>(hess)[i]), *(hess_ref2)[i])); + EXPECT_TRUE(torch::equal(*(std::get<3>(hess)[i]), *(hess_ref3)[i])); + EXPECT_TRUE(torch::equal(*(std::get<4>(hess)[i]), *(hess_ref4)[i])); + } + + /// Evaluation of Hessian (in physical domain) + auto ihess = + functionspace.template ihess_all( + RT_geometry, xi); + auto ihess_ref0 = bspline0.ihess(geometry0, xi_); + auto ihess_ref1 = bspline1.ihess(geometry1, xi_); + auto ihess_ref2 = bspline2.ihess(geometry2, xi_); + auto ihess_ref3 = bspline3.ihess(geometry3, xi_); + auto ihess_ref4 = bspline4.ihess(geometry4, xi_); + + for (std::size_t i = 0; i < ihess_ref0.entries(); ++i) { + EXPECT_TRUE(torch::equal(*(std::get<0>(ihess)[i]), *(ihess_ref0)[i])); + EXPECT_TRUE(torch::equal(*(std::get<1>(ihess)[i]), *(ihess_ref1)[i])); + EXPECT_TRUE(torch::equal(*(std::get<2>(ihess)[i]), *(ihess_ref2)[i])); + EXPECT_TRUE(torch::equal(*(std::get<3>(ihess)[i]), *(ihess_ref3)[i])); + EXPECT_TRUE(torch::equal(*(std::get<4>(ihess)[i]), *(ihess_ref4)[i])); + } + } +} + +TEST_F(FunctionSpaceTest, FunctionSpace_init) {} + +TEST_F(FunctionSpaceTest, FunctionSpace_refine) {} + +TEST_F(FunctionSpaceTest, FunctionSpace_copy_constructor) {} + +TEST_F(FunctionSpaceTest, FunctionSpace_clone_constructor) {} + +TEST_F(FunctionSpaceTest, FunctionSpace_move_constructor) {} + +TEST_F(FunctionSpaceTest, FunctionSpace_read_write) {} + +TEST_F(FunctionSpaceTest, FunctionSpace_to_from_xml) {} + +TEST_F(FunctionSpaceTest, FunctionSpace_load_from_xml) {} + +TEST_F(FunctionSpaceTest, FunctionSpace_to_from_json) {} + +TEST_F(FunctionSpaceTest, FunctionSpace_query_property) {} + +TEST_F(FunctionSpaceTest, FunctionSpace_requires_grad) {} + +TEST_F(FunctionSpaceTest, FunctionSpace_to_dtype) {} + +TEST_F(FunctionSpaceTest, FunctionSpace_to_device) {} + +int main(int argc, char **argv) { + ::testing::InitGoogleTest(&argc, argv); + iganet::init(); + return RUN_ALL_TESTS(); +} diff --git a/unittests/unittest_iganet.cxx b/unittests/unittest_iganet.cxx index 92879857..cb97eaec 100644 --- a/unittests/unittest_iganet.cxx +++ b/unittests/unittest_iganet.cxx @@ -1,699 +1,699 @@ -/** - @file unittests/unittest_iganet.cxx - - @brief IgANet unittests - - @author Matthias Moller - - @copyright This file is part of the IgANet project - - This Source Code Form is subject to the terms of the Mozilla Public - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at http://mozilla.org/MPL/2.0/. -*/ - -#include -#include - -#include -#include - -template -class IgANet : public iganet::IgANet { -private: - using Base = iganet::IgANet; - - typename Base::variable_collPts_type collPts_; - -public: - using iganet::IgANet::IgANet; - - bool epoch(int64_t epoch) override { return false; } - - torch::Tensor loss(const torch::Tensor &outputs, int64_t epoch) override { - return torch::zeros({1}); - } -}; - -TEST(BSpline, IgANet_UniformBSpline_1d) { - using namespace iganet::literals; - using real_t = iganet::unittests::real_t; - using Optimizer = torch::optim::Adam; - - using GeometryMap = iganet::S>; - using Variable = iganet::S>; - - IgANet net( // Number of neurons per layers - {50, 30, 70}, - // Activation functions - {{iganet::activation::tanh}, - {iganet::activation::relu}, - {iganet::activation::sigmoid}, - {iganet::activation::none}}, - // Number of B-spline coefficients - std::tuple(iganet::utils::to_array(6_i64))); - - EXPECT_EQ(net.G().parDim(), 1); - EXPECT_EQ(net.f().parDim(), 1); - EXPECT_EQ(net.u().parDim(), 1); - - EXPECT_EQ(net.f().boundary().side().parDim(), 0); - EXPECT_EQ(net.f().boundary().side().parDim(), 0); - - EXPECT_EQ(net.G().geoDim(), 1); - EXPECT_EQ(net.f().geoDim(), 1); - EXPECT_EQ(net.u().geoDim(), 1); - - EXPECT_EQ(net.f().boundary().side().geoDim(), 1); - EXPECT_EQ(net.f().boundary().side().geoDim(), 1); - - EXPECT_EQ(net.G().degree(0), 5); - EXPECT_EQ(net.f().degree(0), 5); - EXPECT_EQ(net.u().degree(0), 5); - - EXPECT_EQ(net.G().ncoeffs(0), 6); - EXPECT_EQ(net.f().ncoeffs(0), 6); - EXPECT_EQ(net.u().ncoeffs(0), 6); -} - -TEST(BSpline, IgANet_UniformBSpline_2d) { - using namespace iganet::literals; - using real_t = iganet::unittests::real_t; - using Optimizer = torch::optim::Adam; - - using GeometryMap = iganet::S>; - using Variable = iganet::S>; - - IgANet net( // Number of neurons per layers - {50, 30, 70}, - // Activation functions - {{iganet::activation::tanh}, - {iganet::activation::relu}, - {iganet::activation::sigmoid}, - {iganet::activation::none}}, - // Number of B-spline coefficients - std::tuple(iganet::utils::to_array(4_i64, 6_i64))); - - EXPECT_EQ(net.G().parDim(), 2); - EXPECT_EQ(net.f().parDim(), 2); - EXPECT_EQ(net.u().parDim(), 2); - - EXPECT_EQ(net.f().boundary().side().parDim(), 1); - EXPECT_EQ(net.f().boundary().side().parDim(), 1); - EXPECT_EQ(net.f().boundary().side().parDim(), 1); - EXPECT_EQ(net.f().boundary().side().parDim(), 1); - - EXPECT_EQ(net.G().geoDim(), 2); - EXPECT_EQ(net.f().geoDim(), 1); - EXPECT_EQ(net.u().geoDim(), 1); - - EXPECT_EQ(net.f().boundary().side().geoDim(), 1); - EXPECT_EQ(net.f().boundary().side().geoDim(), 1); - EXPECT_EQ(net.f().boundary().side().geoDim(), 1); - EXPECT_EQ(net.f().boundary().side().geoDim(), 1); - - EXPECT_EQ(net.G().degree(0), 3); - EXPECT_EQ(net.f().degree(0), 3); - EXPECT_EQ(net.u().degree(0), 3); - - EXPECT_EQ(net.G().degree(1), 5); - EXPECT_EQ(net.f().degree(1), 5); - EXPECT_EQ(net.u().degree(1), 5); - - EXPECT_EQ(net.f().boundary().side().degree(0), 5); - EXPECT_EQ(net.f().boundary().side().degree(0), 5); - EXPECT_EQ(net.f().boundary().side().degree(0), 3); - EXPECT_EQ(net.f().boundary().side().degree(0), 3); - - EXPECT_EQ(net.G().ncoeffs(0), 4); - EXPECT_EQ(net.f().ncoeffs(0), 4); - EXPECT_EQ(net.u().ncoeffs(0), 4); - - EXPECT_EQ(net.G().ncoeffs(1), 6); - EXPECT_EQ(net.f().ncoeffs(1), 6); - EXPECT_EQ(net.u().ncoeffs(1), 6); - - EXPECT_EQ(net.f().boundary().side().ncoeffs(0), 6); - EXPECT_EQ(net.f().boundary().side().ncoeffs(0), 6); - EXPECT_EQ(net.f().boundary().side().ncoeffs(0), 4); - EXPECT_EQ(net.f().boundary().side().ncoeffs(0), 4); -} - -TEST(BSpline, IgANet_UniformBSpline_3d) { - using namespace iganet::literals; - using real_t = iganet::unittests::real_t; - using Optimizer = torch::optim::Adam; - - using GeometryMap = iganet::S>; - using Variable = iganet::S>; - - IgANet net( // Number of neurons per layers - {50, 30, 70}, - // Activation functions - {{iganet::activation::tanh}, - {iganet::activation::relu}, - {iganet::activation::sigmoid}, - {iganet::activation::none}}, - // Number of B-spline coefficients - std::tuple(iganet::utils::to_array(4_i64, 6_i64, 3_i64))); - - EXPECT_EQ(net.G().parDim(), 3); - EXPECT_EQ(net.f().parDim(), 3); - EXPECT_EQ(net.u().parDim(), 3); - - EXPECT_EQ(net.f().boundary().side().parDim(), 2); - EXPECT_EQ(net.f().boundary().side().parDim(), 2); - EXPECT_EQ(net.f().boundary().side().parDim(), 2); - EXPECT_EQ(net.f().boundary().side().parDim(), 2); - EXPECT_EQ(net.f().boundary().side().parDim(), 2); - EXPECT_EQ(net.f().boundary().side().parDim(), 2); - - EXPECT_EQ(net.G().geoDim(), 3); - EXPECT_EQ(net.f().geoDim(), 1); - EXPECT_EQ(net.u().geoDim(), 1); - - EXPECT_EQ(net.f().boundary().side().geoDim(), 1); - EXPECT_EQ(net.f().boundary().side().geoDim(), 1); - EXPECT_EQ(net.f().boundary().side().geoDim(), 1); - EXPECT_EQ(net.f().boundary().side().geoDim(), 1); - EXPECT_EQ(net.f().boundary().side().geoDim(), 1); - EXPECT_EQ(net.f().boundary().side().geoDim(), 1); - - EXPECT_EQ(net.G().degree(0), 3); - EXPECT_EQ(net.f().degree(0), 3); - EXPECT_EQ(net.u().degree(0), 3); - - EXPECT_EQ(net.G().degree(1), 5); - EXPECT_EQ(net.f().degree(1), 5); - EXPECT_EQ(net.u().degree(1), 5); - - EXPECT_EQ(net.G().degree(2), 1); - EXPECT_EQ(net.f().degree(2), 1); - EXPECT_EQ(net.u().degree(2), 1); - - EXPECT_EQ(net.f().boundary().side().degree(0), 5); - EXPECT_EQ(net.f().boundary().side().degree(0), 5); - EXPECT_EQ(net.f().boundary().side().degree(0), 3); - EXPECT_EQ(net.f().boundary().side().degree(0), 3); - EXPECT_EQ(net.f().boundary().side().degree(0), 3); - EXPECT_EQ(net.f().boundary().side().degree(0), 3); - - EXPECT_EQ(net.f().boundary().side().degree(1), 1); - EXPECT_EQ(net.f().boundary().side().degree(1), 1); - EXPECT_EQ(net.f().boundary().side().degree(1), 1); - EXPECT_EQ(net.f().boundary().side().degree(1), 1); - EXPECT_EQ(net.f().boundary().side().degree(1), 5); - EXPECT_EQ(net.f().boundary().side().degree(1), 5); - - EXPECT_EQ(net.G().ncoeffs(0), 4); - EXPECT_EQ(net.f().ncoeffs(0), 4); - EXPECT_EQ(net.u().ncoeffs(0), 4); - - EXPECT_EQ(net.G().ncoeffs(1), 6); - EXPECT_EQ(net.f().ncoeffs(1), 6); - EXPECT_EQ(net.u().ncoeffs(1), 6); - - EXPECT_EQ(net.G().ncoeffs(2), 3); - EXPECT_EQ(net.f().ncoeffs(2), 3); - EXPECT_EQ(net.u().ncoeffs(2), 3); - - EXPECT_EQ(net.f().boundary().side().ncoeffs(0), 6); - EXPECT_EQ(net.f().boundary().side().ncoeffs(0), 6); - EXPECT_EQ(net.f().boundary().side().ncoeffs(0), 4); - EXPECT_EQ(net.f().boundary().side().ncoeffs(0), 4); - EXPECT_EQ(net.f().boundary().side().ncoeffs(0), 4); - EXPECT_EQ(net.f().boundary().side().ncoeffs(0), 4); - - EXPECT_EQ(net.f().boundary().side().ncoeffs(1), 3); - EXPECT_EQ(net.f().boundary().side().ncoeffs(1), 3); - EXPECT_EQ(net.f().boundary().side().ncoeffs(1), 3); - EXPECT_EQ(net.f().boundary().side().ncoeffs(1), 3); - EXPECT_EQ(net.f().boundary().side().ncoeffs(1), 6); - EXPECT_EQ(net.f().boundary().side().ncoeffs(1), 6); -} - -TEST(BSpline, IgANet_UniformBSpline_4d) { - using namespace iganet::literals; - using real_t = iganet::unittests::real_t; - using Optimizer = torch::optim::Adam; - - using GeometryMap = iganet::S>; - using Variable = iganet::S>; - - IgANet net( // Number of neurons per layers - {50, 30, 70}, - // Activation functions - {{iganet::activation::tanh}, - {iganet::activation::relu}, - {iganet::activation::sigmoid}, - {iganet::activation::none}}, - // Number of B-spline coefficients - std::tuple(iganet::utils::to_array(4_i64, 6_i64, 3_i64, 5_i64))); - - EXPECT_EQ(net.G().parDim(), 4); - EXPECT_EQ(net.f().parDim(), 4); - EXPECT_EQ(net.u().parDim(), 4); - - EXPECT_EQ(net.f().boundary().side().parDim(), 3); - EXPECT_EQ(net.f().boundary().side().parDim(), 3); - EXPECT_EQ(net.f().boundary().side().parDim(), 3); - EXPECT_EQ(net.f().boundary().side().parDim(), 3); - EXPECT_EQ(net.f().boundary().side().parDim(), 3); - EXPECT_EQ(net.f().boundary().side().parDim(), 3); - EXPECT_EQ(net.f().boundary().side().parDim(), 3); - EXPECT_EQ(net.f().boundary().side().parDim(), 3); - - EXPECT_EQ(net.G().geoDim(), 4); - EXPECT_EQ(net.f().geoDim(), 1); - EXPECT_EQ(net.u().geoDim(), 1); - - EXPECT_EQ(net.f().boundary().side().geoDim(), 1); - EXPECT_EQ(net.f().boundary().side().geoDim(), 1); - EXPECT_EQ(net.f().boundary().side().geoDim(), 1); - EXPECT_EQ(net.f().boundary().side().geoDim(), 1); - EXPECT_EQ(net.f().boundary().side().geoDim(), 1); - EXPECT_EQ(net.f().boundary().side().geoDim(), 1); - EXPECT_EQ(net.f().boundary().side().geoDim(), 1); - EXPECT_EQ(net.f().boundary().side().geoDim(), 1); - - EXPECT_EQ(net.G().degree(0), 3); - EXPECT_EQ(net.f().degree(0), 3); - EXPECT_EQ(net.u().degree(0), 3); - - EXPECT_EQ(net.G().degree(1), 5); - EXPECT_EQ(net.f().degree(1), 5); - EXPECT_EQ(net.u().degree(1), 5); - - EXPECT_EQ(net.G().degree(2), 1); - EXPECT_EQ(net.f().degree(2), 1); - EXPECT_EQ(net.u().degree(2), 1); - - EXPECT_EQ(net.G().degree(3), 4); - EXPECT_EQ(net.f().degree(3), 4); - EXPECT_EQ(net.u().degree(3), 4); - - EXPECT_EQ(net.f().boundary().side().degree(0), 5); - EXPECT_EQ(net.f().boundary().side().degree(0), 5); - EXPECT_EQ(net.f().boundary().side().degree(0), 3); - EXPECT_EQ(net.f().boundary().side().degree(0), 3); - EXPECT_EQ(net.f().boundary().side().degree(0), 3); - EXPECT_EQ(net.f().boundary().side().degree(0), 3); - EXPECT_EQ(net.f().boundary().side().degree(0), 3); - EXPECT_EQ(net.f().boundary().side().degree(0), 3); - - EXPECT_EQ(net.f().boundary().side().degree(1), 1); - EXPECT_EQ(net.f().boundary().side().degree(1), 1); - EXPECT_EQ(net.f().boundary().side().degree(1), 1); - EXPECT_EQ(net.f().boundary().side().degree(1), 1); - EXPECT_EQ(net.f().boundary().side().degree(1), 5); - EXPECT_EQ(net.f().boundary().side().degree(1), 5); - EXPECT_EQ(net.f().boundary().side().degree(1), 5); - EXPECT_EQ(net.f().boundary().side().degree(1), 5); - - EXPECT_EQ(net.f().boundary().side().degree(2), 4); - EXPECT_EQ(net.f().boundary().side().degree(2), 4); - EXPECT_EQ(net.f().boundary().side().degree(2), 4); - EXPECT_EQ(net.f().boundary().side().degree(2), 4); - EXPECT_EQ(net.f().boundary().side().degree(2), 4); - EXPECT_EQ(net.f().boundary().side().degree(2), 4); - EXPECT_EQ(net.f().boundary().side().degree(2), 1); - EXPECT_EQ(net.f().boundary().side().degree(2), 1); - - EXPECT_EQ(net.G().ncoeffs(0), 4); - EXPECT_EQ(net.f().ncoeffs(0), 4); - EXPECT_EQ(net.u().ncoeffs(0), 4); - - EXPECT_EQ(net.G().ncoeffs(1), 6); - EXPECT_EQ(net.f().ncoeffs(1), 6); - EXPECT_EQ(net.u().ncoeffs(1), 6); - - EXPECT_EQ(net.G().ncoeffs(2), 3); - EXPECT_EQ(net.f().ncoeffs(2), 3); - EXPECT_EQ(net.u().ncoeffs(2), 3); - - EXPECT_EQ(net.G().ncoeffs(3), 5); - EXPECT_EQ(net.f().ncoeffs(3), 5); - EXPECT_EQ(net.u().ncoeffs(3), 5); - - EXPECT_EQ(net.f().boundary().side().ncoeffs(0), 6); - EXPECT_EQ(net.f().boundary().side().ncoeffs(0), 6); - EXPECT_EQ(net.f().boundary().side().ncoeffs(0), 4); - EXPECT_EQ(net.f().boundary().side().ncoeffs(0), 4); - EXPECT_EQ(net.f().boundary().side().ncoeffs(0), 4); - EXPECT_EQ(net.f().boundary().side().ncoeffs(0), 4); - EXPECT_EQ(net.f().boundary().side().ncoeffs(0), 4); - EXPECT_EQ(net.f().boundary().side().ncoeffs(0), 4); - - EXPECT_EQ(net.f().boundary().side().ncoeffs(1), 3); - EXPECT_EQ(net.f().boundary().side().ncoeffs(1), 3); - EXPECT_EQ(net.f().boundary().side().ncoeffs(1), 3); - EXPECT_EQ(net.f().boundary().side().ncoeffs(1), 3); - EXPECT_EQ(net.f().boundary().side().ncoeffs(1), 6); - EXPECT_EQ(net.f().boundary().side().ncoeffs(1), 6); - EXPECT_EQ(net.f().boundary().side().ncoeffs(1), 6); - EXPECT_EQ(net.f().boundary().side().ncoeffs(1), 6); - - EXPECT_EQ(net.f().boundary().side().ncoeffs(2), 5); - EXPECT_EQ(net.f().boundary().side().ncoeffs(2), 5); - EXPECT_EQ(net.f().boundary().side().ncoeffs(2), 5); - EXPECT_EQ(net.f().boundary().side().ncoeffs(2), 5); - EXPECT_EQ(net.f().boundary().side().ncoeffs(2), 5); - EXPECT_EQ(net.f().boundary().side().ncoeffs(2), 5); - EXPECT_EQ(net.f().boundary().side().ncoeffs(2), 3); - EXPECT_EQ(net.f().boundary().side().ncoeffs(2), 3); -} - -TEST(BSpline, IgANet_NonUniformBSpline_1d) { - using namespace iganet::literals; - using real_t = iganet::unittests::real_t; - using Optimizer = torch::optim::Adam; - - using GeometryMap = iganet::S>; - using Variable = iganet::S>; - - IgANet net( // Number of neurons per layers - {50, 30, 70}, - // Activation functions - {{iganet::activation::tanh}, - {iganet::activation::relu}, - {iganet::activation::sigmoid}, - {iganet::activation::none}}, - // Number of B-spline coefficients - std::tuple(iganet::utils::to_array(6_i64))); - - EXPECT_EQ(net.G().parDim(), 1); - EXPECT_EQ(net.f().parDim(), 1); - EXPECT_EQ(net.u().parDim(), 1); - - EXPECT_EQ(net.f().boundary().side().parDim(), 0); - EXPECT_EQ(net.f().boundary().side().parDim(), 0); - - EXPECT_EQ(net.G().geoDim(), 1); - EXPECT_EQ(net.f().geoDim(), 1); - EXPECT_EQ(net.u().geoDim(), 1); - - EXPECT_EQ(net.f().boundary().side().geoDim(), 1); - EXPECT_EQ(net.f().boundary().side().geoDim(), 1); - - EXPECT_EQ(net.G().degree(0), 5); - EXPECT_EQ(net.f().degree(0), 5); - EXPECT_EQ(net.u().degree(0), 5); - - EXPECT_EQ(net.G().ncoeffs(0), 6); - EXPECT_EQ(net.f().ncoeffs(0), 6); - EXPECT_EQ(net.u().ncoeffs(0), 6); -} - -TEST(BSpline, IgANet_NonUniformBSpline_2d) { - using namespace iganet::literals; - using real_t = iganet::unittests::real_t; - using Optimizer = torch::optim::Adam; - - using GeometryMap = iganet::S>; - using Variable = iganet::S>; - - IgANet net( // Number of neurons per layers - {50, 30, 70}, - // Activation functions - {{iganet::activation::tanh}, - {iganet::activation::relu}, - {iganet::activation::sigmoid}, - {iganet::activation::none}}, - // Number of B-spline coefficients - std::tuple(iganet::utils::to_array(4_i64, 6_i64))); - - EXPECT_EQ(net.G().parDim(), 2); - EXPECT_EQ(net.f().parDim(), 2); - EXPECT_EQ(net.u().parDim(), 2); - - EXPECT_EQ(net.f().boundary().side().parDim(), 1); - EXPECT_EQ(net.f().boundary().side().parDim(), 1); - EXPECT_EQ(net.f().boundary().side().parDim(), 1); - EXPECT_EQ(net.f().boundary().side().parDim(), 1); - - EXPECT_EQ(net.G().geoDim(), 2); - EXPECT_EQ(net.f().geoDim(), 1); - EXPECT_EQ(net.u().geoDim(), 1); - - EXPECT_EQ(net.f().boundary().side().geoDim(), 1); - EXPECT_EQ(net.f().boundary().side().geoDim(), 1); - EXPECT_EQ(net.f().boundary().side().geoDim(), 1); - EXPECT_EQ(net.f().boundary().side().geoDim(), 1); - - EXPECT_EQ(net.G().degree(0), 3); - EXPECT_EQ(net.f().degree(0), 3); - EXPECT_EQ(net.u().degree(0), 3); - - EXPECT_EQ(net.G().degree(1), 5); - EXPECT_EQ(net.f().degree(1), 5); - EXPECT_EQ(net.u().degree(1), 5); - - EXPECT_EQ(net.f().boundary().side().degree(0), 5); - EXPECT_EQ(net.f().boundary().side().degree(0), 5); - EXPECT_EQ(net.f().boundary().side().degree(0), 3); - EXPECT_EQ(net.f().boundary().side().degree(0), 3); - - EXPECT_EQ(net.G().ncoeffs(0), 4); - EXPECT_EQ(net.f().ncoeffs(0), 4); - EXPECT_EQ(net.u().ncoeffs(0), 4); - - EXPECT_EQ(net.G().ncoeffs(1), 6); - EXPECT_EQ(net.f().ncoeffs(1), 6); - EXPECT_EQ(net.u().ncoeffs(1), 6); - - EXPECT_EQ(net.f().boundary().side().ncoeffs(0), 6); - EXPECT_EQ(net.f().boundary().side().ncoeffs(0), 6); - EXPECT_EQ(net.f().boundary().side().ncoeffs(0), 4); - EXPECT_EQ(net.f().boundary().side().ncoeffs(0), 4); -} - -TEST(BSpline, IgANet_NonUniformBSpline_3d) { - using namespace iganet::literals; - using real_t = iganet::unittests::real_t; - using Optimizer = torch::optim::Adam; - - using GeometryMap = iganet::S>; - using Variable = iganet::S>; - - IgANet net( // Number of neurons per layers - {50, 30, 70}, - // Activation functions - {{iganet::activation::tanh}, - {iganet::activation::relu}, - {iganet::activation::sigmoid}, - {iganet::activation::none}}, - // Number of B-spline coefficients - std::tuple(iganet::utils::to_array(4_i64, 6_i64, 3_i64))); - - EXPECT_EQ(net.G().parDim(), 3); - EXPECT_EQ(net.f().parDim(), 3); - EXPECT_EQ(net.u().parDim(), 3); - - EXPECT_EQ(net.f().boundary().side().parDim(), 2); - EXPECT_EQ(net.f().boundary().side().parDim(), 2); - EXPECT_EQ(net.f().boundary().side().parDim(), 2); - EXPECT_EQ(net.f().boundary().side().parDim(), 2); - EXPECT_EQ(net.f().boundary().side().parDim(), 2); - EXPECT_EQ(net.f().boundary().side().parDim(), 2); - - EXPECT_EQ(net.G().geoDim(), 3); - EXPECT_EQ(net.f().geoDim(), 1); - EXPECT_EQ(net.u().geoDim(), 1); - - EXPECT_EQ(net.f().boundary().side().geoDim(), 1); - EXPECT_EQ(net.f().boundary().side().geoDim(), 1); - EXPECT_EQ(net.f().boundary().side().geoDim(), 1); - EXPECT_EQ(net.f().boundary().side().geoDim(), 1); - EXPECT_EQ(net.f().boundary().side().geoDim(), 1); - EXPECT_EQ(net.f().boundary().side().geoDim(), 1); - - EXPECT_EQ(net.G().degree(0), 3); - EXPECT_EQ(net.f().degree(0), 3); - EXPECT_EQ(net.u().degree(0), 3); - - EXPECT_EQ(net.G().degree(1), 5); - EXPECT_EQ(net.f().degree(1), 5); - EXPECT_EQ(net.u().degree(1), 5); - - EXPECT_EQ(net.G().degree(2), 1); - EXPECT_EQ(net.f().degree(2), 1); - EXPECT_EQ(net.u().degree(2), 1); - - EXPECT_EQ(net.f().boundary().side().degree(0), 5); - EXPECT_EQ(net.f().boundary().side().degree(0), 5); - EXPECT_EQ(net.f().boundary().side().degree(0), 3); - EXPECT_EQ(net.f().boundary().side().degree(0), 3); - EXPECT_EQ(net.f().boundary().side().degree(0), 3); - EXPECT_EQ(net.f().boundary().side().degree(0), 3); - - EXPECT_EQ(net.f().boundary().side().degree(1), 1); - EXPECT_EQ(net.f().boundary().side().degree(1), 1); - EXPECT_EQ(net.f().boundary().side().degree(1), 1); - EXPECT_EQ(net.f().boundary().side().degree(1), 1); - EXPECT_EQ(net.f().boundary().side().degree(1), 5); - EXPECT_EQ(net.f().boundary().side().degree(1), 5); - - EXPECT_EQ(net.G().ncoeffs(0), 4); - EXPECT_EQ(net.f().ncoeffs(0), 4); - EXPECT_EQ(net.u().ncoeffs(0), 4); - - EXPECT_EQ(net.G().ncoeffs(1), 6); - EXPECT_EQ(net.f().ncoeffs(1), 6); - EXPECT_EQ(net.u().ncoeffs(1), 6); - - EXPECT_EQ(net.G().ncoeffs(2), 3); - EXPECT_EQ(net.f().ncoeffs(2), 3); - EXPECT_EQ(net.u().ncoeffs(2), 3); - - EXPECT_EQ(net.f().boundary().side().ncoeffs(0), 6); - EXPECT_EQ(net.f().boundary().side().ncoeffs(0), 6); - EXPECT_EQ(net.f().boundary().side().ncoeffs(0), 4); - EXPECT_EQ(net.f().boundary().side().ncoeffs(0), 4); - EXPECT_EQ(net.f().boundary().side().ncoeffs(0), 4); - EXPECT_EQ(net.f().boundary().side().ncoeffs(0), 4); - - EXPECT_EQ(net.f().boundary().side().ncoeffs(1), 3); - EXPECT_EQ(net.f().boundary().side().ncoeffs(1), 3); - EXPECT_EQ(net.f().boundary().side().ncoeffs(1), 3); - EXPECT_EQ(net.f().boundary().side().ncoeffs(1), 3); - EXPECT_EQ(net.f().boundary().side().ncoeffs(1), 6); - EXPECT_EQ(net.f().boundary().side().ncoeffs(1), 6); -} - -TEST(BSpline, IgANet_NonUniformBSpline_4d) { - using namespace iganet::literals; - using real_t = iganet::unittests::real_t; - using Optimizer = torch::optim::Adam; - - using GeometryMap = - iganet::S>; - using Variable = iganet::S>; - - IgANet net( // Number of neurons per layers - {50, 30, 70}, - // Activation functions - {{iganet::activation::tanh}, - {iganet::activation::relu}, - {iganet::activation::sigmoid}, - {iganet::activation::none}}, - // Number of B-spline coefficients - std::tuple(iganet::utils::to_array(4_i64, 6_i64, 3_i64, 5_i64))); - - EXPECT_EQ(net.G().parDim(), 4); - EXPECT_EQ(net.f().parDim(), 4); - EXPECT_EQ(net.u().parDim(), 4); - - EXPECT_EQ(net.f().boundary().side().parDim(), 3); - EXPECT_EQ(net.f().boundary().side().parDim(), 3); - EXPECT_EQ(net.f().boundary().side().parDim(), 3); - EXPECT_EQ(net.f().boundary().side().parDim(), 3); - EXPECT_EQ(net.f().boundary().side().parDim(), 3); - EXPECT_EQ(net.f().boundary().side().parDim(), 3); - EXPECT_EQ(net.f().boundary().side().parDim(), 3); - EXPECT_EQ(net.f().boundary().side().parDim(), 3); - - EXPECT_EQ(net.G().geoDim(), 4); - EXPECT_EQ(net.f().geoDim(), 1); - EXPECT_EQ(net.u().geoDim(), 1); - - EXPECT_EQ(net.f().boundary().side().geoDim(), 1); - EXPECT_EQ(net.f().boundary().side().geoDim(), 1); - EXPECT_EQ(net.f().boundary().side().geoDim(), 1); - EXPECT_EQ(net.f().boundary().side().geoDim(), 1); - EXPECT_EQ(net.f().boundary().side().geoDim(), 1); - EXPECT_EQ(net.f().boundary().side().geoDim(), 1); - EXPECT_EQ(net.f().boundary().side().geoDim(), 1); - EXPECT_EQ(net.f().boundary().side().geoDim(), 1); - - EXPECT_EQ(net.G().degree(0), 3); - EXPECT_EQ(net.f().degree(0), 3); - EXPECT_EQ(net.u().degree(0), 3); - - EXPECT_EQ(net.G().degree(1), 5); - EXPECT_EQ(net.f().degree(1), 5); - EXPECT_EQ(net.u().degree(1), 5); - - EXPECT_EQ(net.G().degree(2), 1); - EXPECT_EQ(net.f().degree(2), 1); - EXPECT_EQ(net.u().degree(2), 1); - - EXPECT_EQ(net.G().degree(3), 4); - EXPECT_EQ(net.f().degree(3), 4); - EXPECT_EQ(net.u().degree(3), 4); - - EXPECT_EQ(net.f().boundary().side().degree(0), 5); - EXPECT_EQ(net.f().boundary().side().degree(0), 5); - EXPECT_EQ(net.f().boundary().side().degree(0), 3); - EXPECT_EQ(net.f().boundary().side().degree(0), 3); - EXPECT_EQ(net.f().boundary().side().degree(0), 3); - EXPECT_EQ(net.f().boundary().side().degree(0), 3); - EXPECT_EQ(net.f().boundary().side().degree(0), 3); - EXPECT_EQ(net.f().boundary().side().degree(0), 3); - - EXPECT_EQ(net.f().boundary().side().degree(1), 1); - EXPECT_EQ(net.f().boundary().side().degree(1), 1); - EXPECT_EQ(net.f().boundary().side().degree(1), 1); - EXPECT_EQ(net.f().boundary().side().degree(1), 1); - EXPECT_EQ(net.f().boundary().side().degree(1), 5); - EXPECT_EQ(net.f().boundary().side().degree(1), 5); - EXPECT_EQ(net.f().boundary().side().degree(1), 5); - EXPECT_EQ(net.f().boundary().side().degree(1), 5); - - EXPECT_EQ(net.f().boundary().side().degree(2), 4); - EXPECT_EQ(net.f().boundary().side().degree(2), 4); - EXPECT_EQ(net.f().boundary().side().degree(2), 4); - EXPECT_EQ(net.f().boundary().side().degree(2), 4); - EXPECT_EQ(net.f().boundary().side().degree(2), 4); - EXPECT_EQ(net.f().boundary().side().degree(2), 4); - EXPECT_EQ(net.f().boundary().side().degree(2), 1); - EXPECT_EQ(net.f().boundary().side().degree(2), 1); - - EXPECT_EQ(net.G().ncoeffs(0), 4); - EXPECT_EQ(net.f().ncoeffs(0), 4); - EXPECT_EQ(net.u().ncoeffs(0), 4); - - EXPECT_EQ(net.G().ncoeffs(1), 6); - EXPECT_EQ(net.f().ncoeffs(1), 6); - EXPECT_EQ(net.u().ncoeffs(1), 6); - - EXPECT_EQ(net.G().ncoeffs(2), 3); - EXPECT_EQ(net.f().ncoeffs(2), 3); - EXPECT_EQ(net.u().ncoeffs(2), 3); - - EXPECT_EQ(net.G().ncoeffs(3), 5); - EXPECT_EQ(net.f().ncoeffs(3), 5); - EXPECT_EQ(net.u().ncoeffs(3), 5); - - EXPECT_EQ(net.f().boundary().side().ncoeffs(0), 6); - EXPECT_EQ(net.f().boundary().side().ncoeffs(0), 6); - EXPECT_EQ(net.f().boundary().side().ncoeffs(0), 4); - EXPECT_EQ(net.f().boundary().side().ncoeffs(0), 4); - EXPECT_EQ(net.f().boundary().side().ncoeffs(0), 4); - EXPECT_EQ(net.f().boundary().side().ncoeffs(0), 4); - EXPECT_EQ(net.f().boundary().side().ncoeffs(0), 4); - EXPECT_EQ(net.f().boundary().side().ncoeffs(0), 4); - - EXPECT_EQ(net.f().boundary().side().ncoeffs(1), 3); - EXPECT_EQ(net.f().boundary().side().ncoeffs(1), 3); - EXPECT_EQ(net.f().boundary().side().ncoeffs(1), 3); - EXPECT_EQ(net.f().boundary().side().ncoeffs(1), 3); - EXPECT_EQ(net.f().boundary().side().ncoeffs(1), 6); - EXPECT_EQ(net.f().boundary().side().ncoeffs(1), 6); - EXPECT_EQ(net.f().boundary().side().ncoeffs(1), 6); - EXPECT_EQ(net.f().boundary().side().ncoeffs(1), 6); - - EXPECT_EQ(net.f().boundary().side().ncoeffs(2), 5); - EXPECT_EQ(net.f().boundary().side().ncoeffs(2), 5); - EXPECT_EQ(net.f().boundary().side().ncoeffs(2), 5); - EXPECT_EQ(net.f().boundary().side().ncoeffs(2), 5); - EXPECT_EQ(net.f().boundary().side().ncoeffs(2), 5); - EXPECT_EQ(net.f().boundary().side().ncoeffs(2), 5); - EXPECT_EQ(net.f().boundary().side().ncoeffs(2), 3); - EXPECT_EQ(net.f().boundary().side().ncoeffs(2), 3); -} - -int main(int argc, char **argv) { - ::testing::InitGoogleTest(&argc, argv); - iganet::init(); - return RUN_ALL_TESTS(); -} +/** + @file unittests/unittest_iganet.cxx + + @brief IgANet unittests + + @author Matthias Moller + + @copyright This file is part of the IgANet project + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#include +#include + +#include +#include + +template +class IgANet : public iganet::IgANet { +private: + using Base = iganet::IgANet; + + typename Base::variable_collPts_type collPts_; + +public: + using iganet::IgANet::IgANet; + + bool epoch(int64_t epoch) override { return false; } + + torch::Tensor loss(const torch::Tensor &outputs, int64_t epoch) override { + return torch::zeros({1}); + } +}; + +TEST(BSpline, IgANet_UniformBSpline_1d) { + using namespace iganet::literals; + using real_t = iganet::unittests::real_t; + using Optimizer = torch::optim::Adam; + + using GeometryMap = iganet::S>; + using Variable = iganet::S>; + + IgANet net( // Number of neurons per layers + {50, 30, 70}, + // Activation functions + {{iganet::activation::tanh}, + {iganet::activation::relu}, + {iganet::activation::sigmoid}, + {iganet::activation::none}}, + // Number of B-spline coefficients + std::tuple(iganet::utils::to_array(6_i64))); + + EXPECT_EQ(net.G().parDim(), 1); + EXPECT_EQ(net.f().parDim(), 1); + EXPECT_EQ(net.u().parDim(), 1); + + EXPECT_EQ(net.f().boundary().side().parDim(), 0); + EXPECT_EQ(net.f().boundary().side().parDim(), 0); + + EXPECT_EQ(net.G().geoDim(), 1); + EXPECT_EQ(net.f().geoDim(), 1); + EXPECT_EQ(net.u().geoDim(), 1); + + EXPECT_EQ(net.f().boundary().side().geoDim(), 1); + EXPECT_EQ(net.f().boundary().side().geoDim(), 1); + + EXPECT_EQ(net.G().degree(0), 5); + EXPECT_EQ(net.f().degree(0), 5); + EXPECT_EQ(net.u().degree(0), 5); + + EXPECT_EQ(net.G().ncoeffs(0), 6); + EXPECT_EQ(net.f().ncoeffs(0), 6); + EXPECT_EQ(net.u().ncoeffs(0), 6); +} + +TEST(BSpline, IgANet_UniformBSpline_2d) { + using namespace iganet::literals; + using real_t = iganet::unittests::real_t; + using Optimizer = torch::optim::Adam; + + using GeometryMap = iganet::S>; + using Variable = iganet::S>; + + IgANet net( // Number of neurons per layers + {50, 30, 70}, + // Activation functions + {{iganet::activation::tanh}, + {iganet::activation::relu}, + {iganet::activation::sigmoid}, + {iganet::activation::none}}, + // Number of B-spline coefficients + std::tuple(iganet::utils::to_array(4_i64, 6_i64))); + + EXPECT_EQ(net.G().parDim(), 2); + EXPECT_EQ(net.f().parDim(), 2); + EXPECT_EQ(net.u().parDim(), 2); + + EXPECT_EQ(net.f().boundary().side().parDim(), 1); + EXPECT_EQ(net.f().boundary().side().parDim(), 1); + EXPECT_EQ(net.f().boundary().side().parDim(), 1); + EXPECT_EQ(net.f().boundary().side().parDim(), 1); + + EXPECT_EQ(net.G().geoDim(), 2); + EXPECT_EQ(net.f().geoDim(), 1); + EXPECT_EQ(net.u().geoDim(), 1); + + EXPECT_EQ(net.f().boundary().side().geoDim(), 1); + EXPECT_EQ(net.f().boundary().side().geoDim(), 1); + EXPECT_EQ(net.f().boundary().side().geoDim(), 1); + EXPECT_EQ(net.f().boundary().side().geoDim(), 1); + + EXPECT_EQ(net.G().degree(0), 3); + EXPECT_EQ(net.f().degree(0), 3); + EXPECT_EQ(net.u().degree(0), 3); + + EXPECT_EQ(net.G().degree(1), 5); + EXPECT_EQ(net.f().degree(1), 5); + EXPECT_EQ(net.u().degree(1), 5); + + EXPECT_EQ(net.f().boundary().side().degree(0), 5); + EXPECT_EQ(net.f().boundary().side().degree(0), 5); + EXPECT_EQ(net.f().boundary().side().degree(0), 3); + EXPECT_EQ(net.f().boundary().side().degree(0), 3); + + EXPECT_EQ(net.G().ncoeffs(0), 4); + EXPECT_EQ(net.f().ncoeffs(0), 4); + EXPECT_EQ(net.u().ncoeffs(0), 4); + + EXPECT_EQ(net.G().ncoeffs(1), 6); + EXPECT_EQ(net.f().ncoeffs(1), 6); + EXPECT_EQ(net.u().ncoeffs(1), 6); + + EXPECT_EQ(net.f().boundary().side().ncoeffs(0), 6); + EXPECT_EQ(net.f().boundary().side().ncoeffs(0), 6); + EXPECT_EQ(net.f().boundary().side().ncoeffs(0), 4); + EXPECT_EQ(net.f().boundary().side().ncoeffs(0), 4); +} + +TEST(BSpline, IgANet_UniformBSpline_3d) { + using namespace iganet::literals; + using real_t = iganet::unittests::real_t; + using Optimizer = torch::optim::Adam; + + using GeometryMap = iganet::S>; + using Variable = iganet::S>; + + IgANet net( // Number of neurons per layers + {50, 30, 70}, + // Activation functions + {{iganet::activation::tanh}, + {iganet::activation::relu}, + {iganet::activation::sigmoid}, + {iganet::activation::none}}, + // Number of B-spline coefficients + std::tuple(iganet::utils::to_array(4_i64, 6_i64, 3_i64))); + + EXPECT_EQ(net.G().parDim(), 3); + EXPECT_EQ(net.f().parDim(), 3); + EXPECT_EQ(net.u().parDim(), 3); + + EXPECT_EQ(net.f().boundary().side().parDim(), 2); + EXPECT_EQ(net.f().boundary().side().parDim(), 2); + EXPECT_EQ(net.f().boundary().side().parDim(), 2); + EXPECT_EQ(net.f().boundary().side().parDim(), 2); + EXPECT_EQ(net.f().boundary().side().parDim(), 2); + EXPECT_EQ(net.f().boundary().side().parDim(), 2); + + EXPECT_EQ(net.G().geoDim(), 3); + EXPECT_EQ(net.f().geoDim(), 1); + EXPECT_EQ(net.u().geoDim(), 1); + + EXPECT_EQ(net.f().boundary().side().geoDim(), 1); + EXPECT_EQ(net.f().boundary().side().geoDim(), 1); + EXPECT_EQ(net.f().boundary().side().geoDim(), 1); + EXPECT_EQ(net.f().boundary().side().geoDim(), 1); + EXPECT_EQ(net.f().boundary().side().geoDim(), 1); + EXPECT_EQ(net.f().boundary().side().geoDim(), 1); + + EXPECT_EQ(net.G().degree(0), 3); + EXPECT_EQ(net.f().degree(0), 3); + EXPECT_EQ(net.u().degree(0), 3); + + EXPECT_EQ(net.G().degree(1), 5); + EXPECT_EQ(net.f().degree(1), 5); + EXPECT_EQ(net.u().degree(1), 5); + + EXPECT_EQ(net.G().degree(2), 1); + EXPECT_EQ(net.f().degree(2), 1); + EXPECT_EQ(net.u().degree(2), 1); + + EXPECT_EQ(net.f().boundary().side().degree(0), 5); + EXPECT_EQ(net.f().boundary().side().degree(0), 5); + EXPECT_EQ(net.f().boundary().side().degree(0), 3); + EXPECT_EQ(net.f().boundary().side().degree(0), 3); + EXPECT_EQ(net.f().boundary().side().degree(0), 3); + EXPECT_EQ(net.f().boundary().side().degree(0), 3); + + EXPECT_EQ(net.f().boundary().side().degree(1), 1); + EXPECT_EQ(net.f().boundary().side().degree(1), 1); + EXPECT_EQ(net.f().boundary().side().degree(1), 1); + EXPECT_EQ(net.f().boundary().side().degree(1), 1); + EXPECT_EQ(net.f().boundary().side().degree(1), 5); + EXPECT_EQ(net.f().boundary().side().degree(1), 5); + + EXPECT_EQ(net.G().ncoeffs(0), 4); + EXPECT_EQ(net.f().ncoeffs(0), 4); + EXPECT_EQ(net.u().ncoeffs(0), 4); + + EXPECT_EQ(net.G().ncoeffs(1), 6); + EXPECT_EQ(net.f().ncoeffs(1), 6); + EXPECT_EQ(net.u().ncoeffs(1), 6); + + EXPECT_EQ(net.G().ncoeffs(2), 3); + EXPECT_EQ(net.f().ncoeffs(2), 3); + EXPECT_EQ(net.u().ncoeffs(2), 3); + + EXPECT_EQ(net.f().boundary().side().ncoeffs(0), 6); + EXPECT_EQ(net.f().boundary().side().ncoeffs(0), 6); + EXPECT_EQ(net.f().boundary().side().ncoeffs(0), 4); + EXPECT_EQ(net.f().boundary().side().ncoeffs(0), 4); + EXPECT_EQ(net.f().boundary().side().ncoeffs(0), 4); + EXPECT_EQ(net.f().boundary().side().ncoeffs(0), 4); + + EXPECT_EQ(net.f().boundary().side().ncoeffs(1), 3); + EXPECT_EQ(net.f().boundary().side().ncoeffs(1), 3); + EXPECT_EQ(net.f().boundary().side().ncoeffs(1), 3); + EXPECT_EQ(net.f().boundary().side().ncoeffs(1), 3); + EXPECT_EQ(net.f().boundary().side().ncoeffs(1), 6); + EXPECT_EQ(net.f().boundary().side().ncoeffs(1), 6); +} + +TEST(BSpline, IgANet_UniformBSpline_4d) { + using namespace iganet::literals; + using real_t = iganet::unittests::real_t; + using Optimizer = torch::optim::Adam; + + using GeometryMap = iganet::S>; + using Variable = iganet::S>; + + IgANet net( // Number of neurons per layers + {50, 30, 70}, + // Activation functions + {{iganet::activation::tanh}, + {iganet::activation::relu}, + {iganet::activation::sigmoid}, + {iganet::activation::none}}, + // Number of B-spline coefficients + std::tuple(iganet::utils::to_array(4_i64, 6_i64, 3_i64, 5_i64))); + + EXPECT_EQ(net.G().parDim(), 4); + EXPECT_EQ(net.f().parDim(), 4); + EXPECT_EQ(net.u().parDim(), 4); + + EXPECT_EQ(net.f().boundary().side().parDim(), 3); + EXPECT_EQ(net.f().boundary().side().parDim(), 3); + EXPECT_EQ(net.f().boundary().side().parDim(), 3); + EXPECT_EQ(net.f().boundary().side().parDim(), 3); + EXPECT_EQ(net.f().boundary().side().parDim(), 3); + EXPECT_EQ(net.f().boundary().side().parDim(), 3); + EXPECT_EQ(net.f().boundary().side().parDim(), 3); + EXPECT_EQ(net.f().boundary().side().parDim(), 3); + + EXPECT_EQ(net.G().geoDim(), 4); + EXPECT_EQ(net.f().geoDim(), 1); + EXPECT_EQ(net.u().geoDim(), 1); + + EXPECT_EQ(net.f().boundary().side().geoDim(), 1); + EXPECT_EQ(net.f().boundary().side().geoDim(), 1); + EXPECT_EQ(net.f().boundary().side().geoDim(), 1); + EXPECT_EQ(net.f().boundary().side().geoDim(), 1); + EXPECT_EQ(net.f().boundary().side().geoDim(), 1); + EXPECT_EQ(net.f().boundary().side().geoDim(), 1); + EXPECT_EQ(net.f().boundary().side().geoDim(), 1); + EXPECT_EQ(net.f().boundary().side().geoDim(), 1); + + EXPECT_EQ(net.G().degree(0), 3); + EXPECT_EQ(net.f().degree(0), 3); + EXPECT_EQ(net.u().degree(0), 3); + + EXPECT_EQ(net.G().degree(1), 5); + EXPECT_EQ(net.f().degree(1), 5); + EXPECT_EQ(net.u().degree(1), 5); + + EXPECT_EQ(net.G().degree(2), 1); + EXPECT_EQ(net.f().degree(2), 1); + EXPECT_EQ(net.u().degree(2), 1); + + EXPECT_EQ(net.G().degree(3), 4); + EXPECT_EQ(net.f().degree(3), 4); + EXPECT_EQ(net.u().degree(3), 4); + + EXPECT_EQ(net.f().boundary().side().degree(0), 5); + EXPECT_EQ(net.f().boundary().side().degree(0), 5); + EXPECT_EQ(net.f().boundary().side().degree(0), 3); + EXPECT_EQ(net.f().boundary().side().degree(0), 3); + EXPECT_EQ(net.f().boundary().side().degree(0), 3); + EXPECT_EQ(net.f().boundary().side().degree(0), 3); + EXPECT_EQ(net.f().boundary().side().degree(0), 3); + EXPECT_EQ(net.f().boundary().side().degree(0), 3); + + EXPECT_EQ(net.f().boundary().side().degree(1), 1); + EXPECT_EQ(net.f().boundary().side().degree(1), 1); + EXPECT_EQ(net.f().boundary().side().degree(1), 1); + EXPECT_EQ(net.f().boundary().side().degree(1), 1); + EXPECT_EQ(net.f().boundary().side().degree(1), 5); + EXPECT_EQ(net.f().boundary().side().degree(1), 5); + EXPECT_EQ(net.f().boundary().side().degree(1), 5); + EXPECT_EQ(net.f().boundary().side().degree(1), 5); + + EXPECT_EQ(net.f().boundary().side().degree(2), 4); + EXPECT_EQ(net.f().boundary().side().degree(2), 4); + EXPECT_EQ(net.f().boundary().side().degree(2), 4); + EXPECT_EQ(net.f().boundary().side().degree(2), 4); + EXPECT_EQ(net.f().boundary().side().degree(2), 4); + EXPECT_EQ(net.f().boundary().side().degree(2), 4); + EXPECT_EQ(net.f().boundary().side().degree(2), 1); + EXPECT_EQ(net.f().boundary().side().degree(2), 1); + + EXPECT_EQ(net.G().ncoeffs(0), 4); + EXPECT_EQ(net.f().ncoeffs(0), 4); + EXPECT_EQ(net.u().ncoeffs(0), 4); + + EXPECT_EQ(net.G().ncoeffs(1), 6); + EXPECT_EQ(net.f().ncoeffs(1), 6); + EXPECT_EQ(net.u().ncoeffs(1), 6); + + EXPECT_EQ(net.G().ncoeffs(2), 3); + EXPECT_EQ(net.f().ncoeffs(2), 3); + EXPECT_EQ(net.u().ncoeffs(2), 3); + + EXPECT_EQ(net.G().ncoeffs(3), 5); + EXPECT_EQ(net.f().ncoeffs(3), 5); + EXPECT_EQ(net.u().ncoeffs(3), 5); + + EXPECT_EQ(net.f().boundary().side().ncoeffs(0), 6); + EXPECT_EQ(net.f().boundary().side().ncoeffs(0), 6); + EXPECT_EQ(net.f().boundary().side().ncoeffs(0), 4); + EXPECT_EQ(net.f().boundary().side().ncoeffs(0), 4); + EXPECT_EQ(net.f().boundary().side().ncoeffs(0), 4); + EXPECT_EQ(net.f().boundary().side().ncoeffs(0), 4); + EXPECT_EQ(net.f().boundary().side().ncoeffs(0), 4); + EXPECT_EQ(net.f().boundary().side().ncoeffs(0), 4); + + EXPECT_EQ(net.f().boundary().side().ncoeffs(1), 3); + EXPECT_EQ(net.f().boundary().side().ncoeffs(1), 3); + EXPECT_EQ(net.f().boundary().side().ncoeffs(1), 3); + EXPECT_EQ(net.f().boundary().side().ncoeffs(1), 3); + EXPECT_EQ(net.f().boundary().side().ncoeffs(1), 6); + EXPECT_EQ(net.f().boundary().side().ncoeffs(1), 6); + EXPECT_EQ(net.f().boundary().side().ncoeffs(1), 6); + EXPECT_EQ(net.f().boundary().side().ncoeffs(1), 6); + + EXPECT_EQ(net.f().boundary().side().ncoeffs(2), 5); + EXPECT_EQ(net.f().boundary().side().ncoeffs(2), 5); + EXPECT_EQ(net.f().boundary().side().ncoeffs(2), 5); + EXPECT_EQ(net.f().boundary().side().ncoeffs(2), 5); + EXPECT_EQ(net.f().boundary().side().ncoeffs(2), 5); + EXPECT_EQ(net.f().boundary().side().ncoeffs(2), 5); + EXPECT_EQ(net.f().boundary().side().ncoeffs(2), 3); + EXPECT_EQ(net.f().boundary().side().ncoeffs(2), 3); +} + +TEST(BSpline, IgANet_NonUniformBSpline_1d) { + using namespace iganet::literals; + using real_t = iganet::unittests::real_t; + using Optimizer = torch::optim::Adam; + + using GeometryMap = iganet::S>; + using Variable = iganet::S>; + + IgANet net( // Number of neurons per layers + {50, 30, 70}, + // Activation functions + {{iganet::activation::tanh}, + {iganet::activation::relu}, + {iganet::activation::sigmoid}, + {iganet::activation::none}}, + // Number of B-spline coefficients + std::tuple(iganet::utils::to_array(6_i64))); + + EXPECT_EQ(net.G().parDim(), 1); + EXPECT_EQ(net.f().parDim(), 1); + EXPECT_EQ(net.u().parDim(), 1); + + EXPECT_EQ(net.f().boundary().side().parDim(), 0); + EXPECT_EQ(net.f().boundary().side().parDim(), 0); + + EXPECT_EQ(net.G().geoDim(), 1); + EXPECT_EQ(net.f().geoDim(), 1); + EXPECT_EQ(net.u().geoDim(), 1); + + EXPECT_EQ(net.f().boundary().side().geoDim(), 1); + EXPECT_EQ(net.f().boundary().side().geoDim(), 1); + + EXPECT_EQ(net.G().degree(0), 5); + EXPECT_EQ(net.f().degree(0), 5); + EXPECT_EQ(net.u().degree(0), 5); + + EXPECT_EQ(net.G().ncoeffs(0), 6); + EXPECT_EQ(net.f().ncoeffs(0), 6); + EXPECT_EQ(net.u().ncoeffs(0), 6); +} + +TEST(BSpline, IgANet_NonUniformBSpline_2d) { + using namespace iganet::literals; + using real_t = iganet::unittests::real_t; + using Optimizer = torch::optim::Adam; + + using GeometryMap = iganet::S>; + using Variable = iganet::S>; + + IgANet net( // Number of neurons per layers + {50, 30, 70}, + // Activation functions + {{iganet::activation::tanh}, + {iganet::activation::relu}, + {iganet::activation::sigmoid}, + {iganet::activation::none}}, + // Number of B-spline coefficients + std::tuple(iganet::utils::to_array(4_i64, 6_i64))); + + EXPECT_EQ(net.G().parDim(), 2); + EXPECT_EQ(net.f().parDim(), 2); + EXPECT_EQ(net.u().parDim(), 2); + + EXPECT_EQ(net.f().boundary().side().parDim(), 1); + EXPECT_EQ(net.f().boundary().side().parDim(), 1); + EXPECT_EQ(net.f().boundary().side().parDim(), 1); + EXPECT_EQ(net.f().boundary().side().parDim(), 1); + + EXPECT_EQ(net.G().geoDim(), 2); + EXPECT_EQ(net.f().geoDim(), 1); + EXPECT_EQ(net.u().geoDim(), 1); + + EXPECT_EQ(net.f().boundary().side().geoDim(), 1); + EXPECT_EQ(net.f().boundary().side().geoDim(), 1); + EXPECT_EQ(net.f().boundary().side().geoDim(), 1); + EXPECT_EQ(net.f().boundary().side().geoDim(), 1); + + EXPECT_EQ(net.G().degree(0), 3); + EXPECT_EQ(net.f().degree(0), 3); + EXPECT_EQ(net.u().degree(0), 3); + + EXPECT_EQ(net.G().degree(1), 5); + EXPECT_EQ(net.f().degree(1), 5); + EXPECT_EQ(net.u().degree(1), 5); + + EXPECT_EQ(net.f().boundary().side().degree(0), 5); + EXPECT_EQ(net.f().boundary().side().degree(0), 5); + EXPECT_EQ(net.f().boundary().side().degree(0), 3); + EXPECT_EQ(net.f().boundary().side().degree(0), 3); + + EXPECT_EQ(net.G().ncoeffs(0), 4); + EXPECT_EQ(net.f().ncoeffs(0), 4); + EXPECT_EQ(net.u().ncoeffs(0), 4); + + EXPECT_EQ(net.G().ncoeffs(1), 6); + EXPECT_EQ(net.f().ncoeffs(1), 6); + EXPECT_EQ(net.u().ncoeffs(1), 6); + + EXPECT_EQ(net.f().boundary().side().ncoeffs(0), 6); + EXPECT_EQ(net.f().boundary().side().ncoeffs(0), 6); + EXPECT_EQ(net.f().boundary().side().ncoeffs(0), 4); + EXPECT_EQ(net.f().boundary().side().ncoeffs(0), 4); +} + +TEST(BSpline, IgANet_NonUniformBSpline_3d) { + using namespace iganet::literals; + using real_t = iganet::unittests::real_t; + using Optimizer = torch::optim::Adam; + + using GeometryMap = iganet::S>; + using Variable = iganet::S>; + + IgANet net( // Number of neurons per layers + {50, 30, 70}, + // Activation functions + {{iganet::activation::tanh}, + {iganet::activation::relu}, + {iganet::activation::sigmoid}, + {iganet::activation::none}}, + // Number of B-spline coefficients + std::tuple(iganet::utils::to_array(4_i64, 6_i64, 3_i64))); + + EXPECT_EQ(net.G().parDim(), 3); + EXPECT_EQ(net.f().parDim(), 3); + EXPECT_EQ(net.u().parDim(), 3); + + EXPECT_EQ(net.f().boundary().side().parDim(), 2); + EXPECT_EQ(net.f().boundary().side().parDim(), 2); + EXPECT_EQ(net.f().boundary().side().parDim(), 2); + EXPECT_EQ(net.f().boundary().side().parDim(), 2); + EXPECT_EQ(net.f().boundary().side().parDim(), 2); + EXPECT_EQ(net.f().boundary().side().parDim(), 2); + + EXPECT_EQ(net.G().geoDim(), 3); + EXPECT_EQ(net.f().geoDim(), 1); + EXPECT_EQ(net.u().geoDim(), 1); + + EXPECT_EQ(net.f().boundary().side().geoDim(), 1); + EXPECT_EQ(net.f().boundary().side().geoDim(), 1); + EXPECT_EQ(net.f().boundary().side().geoDim(), 1); + EXPECT_EQ(net.f().boundary().side().geoDim(), 1); + EXPECT_EQ(net.f().boundary().side().geoDim(), 1); + EXPECT_EQ(net.f().boundary().side().geoDim(), 1); + + EXPECT_EQ(net.G().degree(0), 3); + EXPECT_EQ(net.f().degree(0), 3); + EXPECT_EQ(net.u().degree(0), 3); + + EXPECT_EQ(net.G().degree(1), 5); + EXPECT_EQ(net.f().degree(1), 5); + EXPECT_EQ(net.u().degree(1), 5); + + EXPECT_EQ(net.G().degree(2), 1); + EXPECT_EQ(net.f().degree(2), 1); + EXPECT_EQ(net.u().degree(2), 1); + + EXPECT_EQ(net.f().boundary().side().degree(0), 5); + EXPECT_EQ(net.f().boundary().side().degree(0), 5); + EXPECT_EQ(net.f().boundary().side().degree(0), 3); + EXPECT_EQ(net.f().boundary().side().degree(0), 3); + EXPECT_EQ(net.f().boundary().side().degree(0), 3); + EXPECT_EQ(net.f().boundary().side().degree(0), 3); + + EXPECT_EQ(net.f().boundary().side().degree(1), 1); + EXPECT_EQ(net.f().boundary().side().degree(1), 1); + EXPECT_EQ(net.f().boundary().side().degree(1), 1); + EXPECT_EQ(net.f().boundary().side().degree(1), 1); + EXPECT_EQ(net.f().boundary().side().degree(1), 5); + EXPECT_EQ(net.f().boundary().side().degree(1), 5); + + EXPECT_EQ(net.G().ncoeffs(0), 4); + EXPECT_EQ(net.f().ncoeffs(0), 4); + EXPECT_EQ(net.u().ncoeffs(0), 4); + + EXPECT_EQ(net.G().ncoeffs(1), 6); + EXPECT_EQ(net.f().ncoeffs(1), 6); + EXPECT_EQ(net.u().ncoeffs(1), 6); + + EXPECT_EQ(net.G().ncoeffs(2), 3); + EXPECT_EQ(net.f().ncoeffs(2), 3); + EXPECT_EQ(net.u().ncoeffs(2), 3); + + EXPECT_EQ(net.f().boundary().side().ncoeffs(0), 6); + EXPECT_EQ(net.f().boundary().side().ncoeffs(0), 6); + EXPECT_EQ(net.f().boundary().side().ncoeffs(0), 4); + EXPECT_EQ(net.f().boundary().side().ncoeffs(0), 4); + EXPECT_EQ(net.f().boundary().side().ncoeffs(0), 4); + EXPECT_EQ(net.f().boundary().side().ncoeffs(0), 4); + + EXPECT_EQ(net.f().boundary().side().ncoeffs(1), 3); + EXPECT_EQ(net.f().boundary().side().ncoeffs(1), 3); + EXPECT_EQ(net.f().boundary().side().ncoeffs(1), 3); + EXPECT_EQ(net.f().boundary().side().ncoeffs(1), 3); + EXPECT_EQ(net.f().boundary().side().ncoeffs(1), 6); + EXPECT_EQ(net.f().boundary().side().ncoeffs(1), 6); +} + +TEST(BSpline, IgANet_NonUniformBSpline_4d) { + using namespace iganet::literals; + using real_t = iganet::unittests::real_t; + using Optimizer = torch::optim::Adam; + + using GeometryMap = + iganet::S>; + using Variable = iganet::S>; + + IgANet net( // Number of neurons per layers + {50, 30, 70}, + // Activation functions + {{iganet::activation::tanh}, + {iganet::activation::relu}, + {iganet::activation::sigmoid}, + {iganet::activation::none}}, + // Number of B-spline coefficients + std::tuple(iganet::utils::to_array(4_i64, 6_i64, 3_i64, 5_i64))); + + EXPECT_EQ(net.G().parDim(), 4); + EXPECT_EQ(net.f().parDim(), 4); + EXPECT_EQ(net.u().parDim(), 4); + + EXPECT_EQ(net.f().boundary().side().parDim(), 3); + EXPECT_EQ(net.f().boundary().side().parDim(), 3); + EXPECT_EQ(net.f().boundary().side().parDim(), 3); + EXPECT_EQ(net.f().boundary().side().parDim(), 3); + EXPECT_EQ(net.f().boundary().side().parDim(), 3); + EXPECT_EQ(net.f().boundary().side().parDim(), 3); + EXPECT_EQ(net.f().boundary().side().parDim(), 3); + EXPECT_EQ(net.f().boundary().side().parDim(), 3); + + EXPECT_EQ(net.G().geoDim(), 4); + EXPECT_EQ(net.f().geoDim(), 1); + EXPECT_EQ(net.u().geoDim(), 1); + + EXPECT_EQ(net.f().boundary().side().geoDim(), 1); + EXPECT_EQ(net.f().boundary().side().geoDim(), 1); + EXPECT_EQ(net.f().boundary().side().geoDim(), 1); + EXPECT_EQ(net.f().boundary().side().geoDim(), 1); + EXPECT_EQ(net.f().boundary().side().geoDim(), 1); + EXPECT_EQ(net.f().boundary().side().geoDim(), 1); + EXPECT_EQ(net.f().boundary().side().geoDim(), 1); + EXPECT_EQ(net.f().boundary().side().geoDim(), 1); + + EXPECT_EQ(net.G().degree(0), 3); + EXPECT_EQ(net.f().degree(0), 3); + EXPECT_EQ(net.u().degree(0), 3); + + EXPECT_EQ(net.G().degree(1), 5); + EXPECT_EQ(net.f().degree(1), 5); + EXPECT_EQ(net.u().degree(1), 5); + + EXPECT_EQ(net.G().degree(2), 1); + EXPECT_EQ(net.f().degree(2), 1); + EXPECT_EQ(net.u().degree(2), 1); + + EXPECT_EQ(net.G().degree(3), 4); + EXPECT_EQ(net.f().degree(3), 4); + EXPECT_EQ(net.u().degree(3), 4); + + EXPECT_EQ(net.f().boundary().side().degree(0), 5); + EXPECT_EQ(net.f().boundary().side().degree(0), 5); + EXPECT_EQ(net.f().boundary().side().degree(0), 3); + EXPECT_EQ(net.f().boundary().side().degree(0), 3); + EXPECT_EQ(net.f().boundary().side().degree(0), 3); + EXPECT_EQ(net.f().boundary().side().degree(0), 3); + EXPECT_EQ(net.f().boundary().side().degree(0), 3); + EXPECT_EQ(net.f().boundary().side().degree(0), 3); + + EXPECT_EQ(net.f().boundary().side().degree(1), 1); + EXPECT_EQ(net.f().boundary().side().degree(1), 1); + EXPECT_EQ(net.f().boundary().side().degree(1), 1); + EXPECT_EQ(net.f().boundary().side().degree(1), 1); + EXPECT_EQ(net.f().boundary().side().degree(1), 5); + EXPECT_EQ(net.f().boundary().side().degree(1), 5); + EXPECT_EQ(net.f().boundary().side().degree(1), 5); + EXPECT_EQ(net.f().boundary().side().degree(1), 5); + + EXPECT_EQ(net.f().boundary().side().degree(2), 4); + EXPECT_EQ(net.f().boundary().side().degree(2), 4); + EXPECT_EQ(net.f().boundary().side().degree(2), 4); + EXPECT_EQ(net.f().boundary().side().degree(2), 4); + EXPECT_EQ(net.f().boundary().side().degree(2), 4); + EXPECT_EQ(net.f().boundary().side().degree(2), 4); + EXPECT_EQ(net.f().boundary().side().degree(2), 1); + EXPECT_EQ(net.f().boundary().side().degree(2), 1); + + EXPECT_EQ(net.G().ncoeffs(0), 4); + EXPECT_EQ(net.f().ncoeffs(0), 4); + EXPECT_EQ(net.u().ncoeffs(0), 4); + + EXPECT_EQ(net.G().ncoeffs(1), 6); + EXPECT_EQ(net.f().ncoeffs(1), 6); + EXPECT_EQ(net.u().ncoeffs(1), 6); + + EXPECT_EQ(net.G().ncoeffs(2), 3); + EXPECT_EQ(net.f().ncoeffs(2), 3); + EXPECT_EQ(net.u().ncoeffs(2), 3); + + EXPECT_EQ(net.G().ncoeffs(3), 5); + EXPECT_EQ(net.f().ncoeffs(3), 5); + EXPECT_EQ(net.u().ncoeffs(3), 5); + + EXPECT_EQ(net.f().boundary().side().ncoeffs(0), 6); + EXPECT_EQ(net.f().boundary().side().ncoeffs(0), 6); + EXPECT_EQ(net.f().boundary().side().ncoeffs(0), 4); + EXPECT_EQ(net.f().boundary().side().ncoeffs(0), 4); + EXPECT_EQ(net.f().boundary().side().ncoeffs(0), 4); + EXPECT_EQ(net.f().boundary().side().ncoeffs(0), 4); + EXPECT_EQ(net.f().boundary().side().ncoeffs(0), 4); + EXPECT_EQ(net.f().boundary().side().ncoeffs(0), 4); + + EXPECT_EQ(net.f().boundary().side().ncoeffs(1), 3); + EXPECT_EQ(net.f().boundary().side().ncoeffs(1), 3); + EXPECT_EQ(net.f().boundary().side().ncoeffs(1), 3); + EXPECT_EQ(net.f().boundary().side().ncoeffs(1), 3); + EXPECT_EQ(net.f().boundary().side().ncoeffs(1), 6); + EXPECT_EQ(net.f().boundary().side().ncoeffs(1), 6); + EXPECT_EQ(net.f().boundary().side().ncoeffs(1), 6); + EXPECT_EQ(net.f().boundary().side().ncoeffs(1), 6); + + EXPECT_EQ(net.f().boundary().side().ncoeffs(2), 5); + EXPECT_EQ(net.f().boundary().side().ncoeffs(2), 5); + EXPECT_EQ(net.f().boundary().side().ncoeffs(2), 5); + EXPECT_EQ(net.f().boundary().side().ncoeffs(2), 5); + EXPECT_EQ(net.f().boundary().side().ncoeffs(2), 5); + EXPECT_EQ(net.f().boundary().side().ncoeffs(2), 5); + EXPECT_EQ(net.f().boundary().side().ncoeffs(2), 3); + EXPECT_EQ(net.f().boundary().side().ncoeffs(2), 3); +} + +int main(int argc, char **argv) { + ::testing::InitGoogleTest(&argc, argv); + iganet::init(); + return RUN_ALL_TESTS(); +} diff --git a/unittests/unittest_nonuniform_bspline.cxx b/unittests/unittest_nonuniform_bspline.cxx index 6e763d8a..cdd1f3e2 100644 --- a/unittests/unittest_nonuniform_bspline.cxx +++ b/unittests/unittest_nonuniform_bspline.cxx @@ -1,2338 +1,2338 @@ -/** - @file unittests/unittest_nonuniform_bspline.cxx - - @brief B-Spline unittests - - @author Matthias Moller - - @copyright This file is part of the IgANet project - - This Source Code Form is subject to the terms of the Mozilla Public - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at http://mozilla.org/MPL/2.0/. -*/ - -#include -#include -#include - -#include -#include - -using namespace iganet::unittests::literals; - -class BSplineTest : public ::testing::Test { -public: - BSplineTest() { std::srand(std::time(nullptr)); } - -protected: - using real_t = iganet::unittests::real_t; - iganet::Options options; -}; - -TEST_F(BSplineTest, NonUniformBSpline_parDim1_geoDim1_degrees1) { - EXPECT_THROW( - (iganet::NonUniformBSpline({{{0.0_r, 0.0_r, 1.0_r}}})), - std::runtime_error); - iganet::NonUniformBSpline bspline( - {{{0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r}}}); - EXPECT_EQ(bspline.parDim(), 1); - EXPECT_EQ(bspline.geoDim(), 1); - EXPECT_EQ(bspline.degree(0), 1); - EXPECT_EQ(bspline.nknots(0), 5); - EXPECT_EQ(bspline.ncoeffs(0), 3); - EXPECT_EQ(bspline.ncumcoeffs(), 3); - EXPECT_TRUE(bspline.is_nonuniform()); - EXPECT_FALSE(bspline.is_uniform()); -} - -TEST_F(BSplineTest, NonUniformBSpline_parDim1_geoDim2_degrees1) { - EXPECT_THROW( - (iganet::NonUniformBSpline({{{0.0_r, 0.0_r, 1.0_r}}})), - std::runtime_error); - iganet::NonUniformBSpline bspline( - {{{0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r}}}); - EXPECT_EQ(bspline.parDim(), 1); - EXPECT_EQ(bspline.geoDim(), 2); - EXPECT_EQ(bspline.degree(0), 1); - EXPECT_EQ(bspline.nknots(0), 5); - EXPECT_EQ(bspline.ncoeffs(0), 3); - EXPECT_EQ(bspline.ncumcoeffs(), 3); -} - -TEST_F(BSplineTest, NonUniformBSpline_parDim1_geoDim3_degrees1) { - EXPECT_THROW( - (iganet::NonUniformBSpline({{{0.0_r, 0.0_r, 1.0_r}}})), - std::runtime_error); - iganet::NonUniformBSpline bspline( - {{{0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r}}}); - EXPECT_EQ(bspline.parDim(), 1); - EXPECT_EQ(bspline.geoDim(), 3); - EXPECT_EQ(bspline.degree(0), 1); - EXPECT_EQ(bspline.nknots(0), 5); - EXPECT_EQ(bspline.ncoeffs(0), 3); - EXPECT_EQ(bspline.ncumcoeffs(), 3); -} - -TEST_F(BSplineTest, NonUniformBSpline_parDim1_geoDim4_degrees1) { - EXPECT_THROW( - (iganet::NonUniformBSpline({{{0.0_r, 0.0_r, 1.0_r}}})), - std::runtime_error); - iganet::NonUniformBSpline bspline( - {{{0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r}}}); - EXPECT_EQ(bspline.parDim(), 1); - EXPECT_EQ(bspline.geoDim(), 4); - EXPECT_EQ(bspline.degree(0), 1); - EXPECT_EQ(bspline.nknots(0), 5); - EXPECT_EQ(bspline.ncoeffs(0), 3); - EXPECT_EQ(bspline.ncumcoeffs(), 3); -} - -TEST_F(BSplineTest, NonUniformBSpline_parDim2_geoDim1_degrees12) { - iganet::NonUniformBSpline bspline( - {{{0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 1.0_r, 1.0_r, 1.0_r}}}); - EXPECT_EQ(bspline.parDim(), 2); - EXPECT_EQ(bspline.geoDim(), 1); - EXPECT_EQ(bspline.degree(0), 1); - EXPECT_EQ(bspline.degree(1), 2); - EXPECT_EQ(bspline.nknots(0), 5); - EXPECT_EQ(bspline.nknots(1), 6); - EXPECT_EQ(bspline.ncoeffs(0), 3); - EXPECT_EQ(bspline.ncoeffs(1), 3); - EXPECT_EQ(bspline.ncumcoeffs(), 9); -} - -TEST_F(BSplineTest, NonUniformBSpline_parDim2_geoDim2_degrees12) { - iganet::NonUniformBSpline bspline( - {{{0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 1.0_r, 1.0_r, 1.0_r}}}); - EXPECT_EQ(bspline.parDim(), 2); - EXPECT_EQ(bspline.geoDim(), 2); - EXPECT_EQ(bspline.degree(0), 1); - EXPECT_EQ(bspline.degree(1), 2); - EXPECT_EQ(bspline.nknots(0), 5); - EXPECT_EQ(bspline.nknots(1), 6); - EXPECT_EQ(bspline.ncoeffs(0), 3); - EXPECT_EQ(bspline.ncoeffs(1), 3); - EXPECT_EQ(bspline.ncumcoeffs(), 9); -} - -TEST_F(BSplineTest, NonUniformBSpline_parDim2_geoDim3_degrees12) { - iganet::NonUniformBSpline bspline( - {{{0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 1.0_r, 1.0_r, 1.0_r}}}); - EXPECT_EQ(bspline.parDim(), 2); - EXPECT_EQ(bspline.geoDim(), 3); - EXPECT_EQ(bspline.degree(0), 1); - EXPECT_EQ(bspline.degree(1), 2); - EXPECT_EQ(bspline.nknots(0), 5); - EXPECT_EQ(bspline.nknots(1), 6); - EXPECT_EQ(bspline.ncoeffs(0), 3); - EXPECT_EQ(bspline.ncoeffs(1), 3); - EXPECT_EQ(bspline.ncumcoeffs(), 9); -} - -TEST_F(BSplineTest, NonUniformBSpline_parDim2_geoDim4_degrees12) { - iganet::NonUniformBSpline bspline( - {{{0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 1.0_r, 1.0_r, 1.0_r}}}); - EXPECT_EQ(bspline.parDim(), 2); - EXPECT_EQ(bspline.geoDim(), 4); - EXPECT_EQ(bspline.degree(0), 1); - EXPECT_EQ(bspline.degree(1), 2); - EXPECT_EQ(bspline.nknots(0), 5); - EXPECT_EQ(bspline.nknots(1), 6); - EXPECT_EQ(bspline.ncoeffs(0), 3); - EXPECT_EQ(bspline.ncoeffs(1), 3); - EXPECT_EQ(bspline.ncumcoeffs(), 9); -} - -TEST_F(BSplineTest, NonUniformBSpline_parDim3_geoDim1_degrees123) { - iganet::NonUniformBSpline bspline( - {{{0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}); - EXPECT_EQ(bspline.parDim(), 3); - EXPECT_EQ(bspline.geoDim(), 1); - EXPECT_EQ(bspline.degree(0), 1); - EXPECT_EQ(bspline.degree(1), 2); - EXPECT_EQ(bspline.degree(2), 3); - EXPECT_EQ(bspline.nknots(0), 5); - EXPECT_EQ(bspline.nknots(1), 6); - EXPECT_EQ(bspline.nknots(2), 9); - EXPECT_EQ(bspline.ncoeffs(0), 3); - EXPECT_EQ(bspline.ncoeffs(1), 3); - EXPECT_EQ(bspline.ncoeffs(2), 5); - EXPECT_EQ(bspline.ncumcoeffs(), 45); -} - -TEST_F(BSplineTest, NonUniformBSpline_parDim3_geoDim2_degrees123) { - iganet::NonUniformBSpline bspline( - {{{0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}); - EXPECT_EQ(bspline.parDim(), 3); - EXPECT_EQ(bspline.geoDim(), 2); - EXPECT_EQ(bspline.degree(0), 1); - EXPECT_EQ(bspline.degree(1), 2); - EXPECT_EQ(bspline.degree(2), 3); - EXPECT_EQ(bspline.nknots(0), 5); - EXPECT_EQ(bspline.nknots(1), 6); - EXPECT_EQ(bspline.nknots(2), 9); - EXPECT_EQ(bspline.ncoeffs(0), 3); - EXPECT_EQ(bspline.ncoeffs(1), 3); - EXPECT_EQ(bspline.ncoeffs(2), 5); - EXPECT_EQ(bspline.ncumcoeffs(), 45); -} - -TEST_F(BSplineTest, NonUniformBSpline_parDim3_geoDim3_degrees123) { - iganet::NonUniformBSpline bspline( - {{{0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}); - EXPECT_EQ(bspline.parDim(), 3); - EXPECT_EQ(bspline.geoDim(), 3); - EXPECT_EQ(bspline.degree(0), 1); - EXPECT_EQ(bspline.degree(1), 2); - EXPECT_EQ(bspline.degree(2), 3); - EXPECT_EQ(bspline.nknots(0), 5); - EXPECT_EQ(bspline.nknots(1), 6); - EXPECT_EQ(bspline.nknots(2), 9); - EXPECT_EQ(bspline.ncoeffs(0), 3); - EXPECT_EQ(bspline.ncoeffs(1), 3); - EXPECT_EQ(bspline.ncoeffs(2), 5); - EXPECT_EQ(bspline.ncumcoeffs(), 45); -} - -TEST_F(BSplineTest, NonUniformBSpline_parDim3_geoDim4_degrees123) { - iganet::NonUniformBSpline bspline( - {{{0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}); - EXPECT_EQ(bspline.parDim(), 3); - EXPECT_EQ(bspline.geoDim(), 4); - EXPECT_EQ(bspline.degree(0), 1); - EXPECT_EQ(bspline.degree(1), 2); - EXPECT_EQ(bspline.degree(2), 3); - EXPECT_EQ(bspline.nknots(0), 5); - EXPECT_EQ(bspline.nknots(1), 6); - EXPECT_EQ(bspline.nknots(2), 9); - EXPECT_EQ(bspline.ncoeffs(0), 3); - EXPECT_EQ(bspline.ncoeffs(1), 3); - EXPECT_EQ(bspline.ncoeffs(2), 5); - EXPECT_EQ(bspline.ncumcoeffs(), 45); -} - -TEST_F(BSplineTest, NonUniformBSpline_parDim4_geoDim1_degrees1234) { - iganet::NonUniformBSpline bspline( - {{{0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, - 1.0_r}}}); - EXPECT_EQ(bspline.parDim(), 4); - EXPECT_EQ(bspline.geoDim(), 1); - EXPECT_EQ(bspline.degree(0), 1); - EXPECT_EQ(bspline.degree(1), 2); - EXPECT_EQ(bspline.degree(2), 3); - EXPECT_EQ(bspline.degree(3), 4); - EXPECT_EQ(bspline.nknots(0), 5); - EXPECT_EQ(bspline.nknots(1), 6); - EXPECT_EQ(bspline.nknots(2), 9); - EXPECT_EQ(bspline.nknots(3), 11); - EXPECT_EQ(bspline.ncoeffs(0), 3); - EXPECT_EQ(bspline.ncoeffs(1), 3); - EXPECT_EQ(bspline.ncoeffs(2), 5); - EXPECT_EQ(bspline.ncoeffs(3), 6); - EXPECT_EQ(bspline.ncumcoeffs(), 270); -} - -TEST_F(BSplineTest, NonUniformBSpline_parDim4_geoDim2_degrees1234) { - iganet::NonUniformBSpline bspline( - {{{0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, - 1.0_r}}}); - EXPECT_EQ(bspline.parDim(), 4); - EXPECT_EQ(bspline.geoDim(), 2); - EXPECT_EQ(bspline.degree(0), 1); - EXPECT_EQ(bspline.degree(1), 2); - EXPECT_EQ(bspline.degree(2), 3); - EXPECT_EQ(bspline.degree(3), 4); - EXPECT_EQ(bspline.nknots(0), 5); - EXPECT_EQ(bspline.nknots(1), 6); - EXPECT_EQ(bspline.nknots(2), 9); - EXPECT_EQ(bspline.nknots(3), 11); - EXPECT_EQ(bspline.ncoeffs(0), 3); - EXPECT_EQ(bspline.ncoeffs(1), 3); - EXPECT_EQ(bspline.ncoeffs(2), 5); - EXPECT_EQ(bspline.ncoeffs(3), 6); - EXPECT_EQ(bspline.ncumcoeffs(), 270); -} - -TEST_F(BSplineTest, NonUniformBSpline_parDim4_geoDim3_degrees1234) { - iganet::NonUniformBSpline bspline( - {{{0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, - 1.0_r}}}); - EXPECT_EQ(bspline.parDim(), 4); - EXPECT_EQ(bspline.geoDim(), 3); - EXPECT_EQ(bspline.degree(0), 1); - EXPECT_EQ(bspline.degree(1), 2); - EXPECT_EQ(bspline.degree(2), 3); - EXPECT_EQ(bspline.degree(3), 4); - EXPECT_EQ(bspline.nknots(0), 5); - EXPECT_EQ(bspline.nknots(1), 6); - EXPECT_EQ(bspline.nknots(2), 9); - EXPECT_EQ(bspline.nknots(3), 11); - EXPECT_EQ(bspline.ncoeffs(0), 3); - EXPECT_EQ(bspline.ncoeffs(1), 3); - EXPECT_EQ(bspline.ncoeffs(2), 5); - EXPECT_EQ(bspline.ncoeffs(3), 6); - EXPECT_EQ(bspline.ncumcoeffs(), 270); -} - -TEST_F(BSplineTest, NonUniformBSpline_parDim4_geoDim4_degrees1234) { - iganet::NonUniformBSpline bspline( - {{{0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, - 1.0_r}}}); - EXPECT_EQ(bspline.parDim(), 4); - EXPECT_EQ(bspline.geoDim(), 4); - EXPECT_EQ(bspline.degree(0), 1); - EXPECT_EQ(bspline.degree(1), 2); - EXPECT_EQ(bspline.degree(2), 3); - EXPECT_EQ(bspline.degree(3), 4); - EXPECT_EQ(bspline.nknots(0), 5); - EXPECT_EQ(bspline.nknots(1), 6); - EXPECT_EQ(bspline.nknots(2), 9); - EXPECT_EQ(bspline.nknots(3), 11); - EXPECT_EQ(bspline.ncoeffs(0), 3); - EXPECT_EQ(bspline.ncoeffs(1), 3); - EXPECT_EQ(bspline.ncoeffs(2), 5); - EXPECT_EQ(bspline.ncoeffs(3), 6); - EXPECT_EQ(bspline.ncumcoeffs(), 270); -} - -TEST_F(BSplineTest, NonUniformBSpline_init) { - { - iganet::NonUniformBSpline bspline( - {{{0.0_r, 0.0_r, 0.25_r, 0.5_r, 0.75_r, 1.0_r, 1.0_r}}}, - iganet::init::zeros, options); - EXPECT_TRUE(torch::equal(bspline.coeffs(0), torch::zeros(5, options))); - } - - { - iganet::NonUniformBSpline bspline( - {{{0.0_r, 0.0_r, 0.25_r, 0.5_r, 0.75_r, 1.0_r, 1.0_r}}}, - iganet::init::ones, options); - EXPECT_TRUE(torch::equal(bspline.coeffs(0), torch::ones(5, options))); - } - - { - iganet::NonUniformBSpline bspline( - {{{0.0_r, 0.0_r, 0.25_r, 0.5_r, 0.75_r, 1.0_r, 1.0_r}}}, - iganet::init::linear, options); - EXPECT_TRUE( - torch::equal(bspline.coeffs(0), torch::linspace(0, 1, 5, options))); - } - - { - iganet::NonUniformBSpline bspline( - {{{0.0_r, 0.0_r, 0.25_r, 0.5_r, 0.75_r, 1.0_r, 1.0_r}}}, - iganet::init::greville, options); - EXPECT_TRUE( - torch::equal(bspline.coeffs(0), torch::linspace(0, 1, 5, options))); - } - - { - iganet::NonUniformBSpline bspline( - {{{0.0_r, 0.0_r, 0.25_r, 0.5_r, 0.75_r, 1.0_r, 1.0_r}}}, - iganet::init::zeros, options); - EXPECT_TRUE(torch::equal(bspline.coeffs(0), torch::zeros(5, options))); - EXPECT_TRUE(torch::equal(bspline.coeffs(1), torch::zeros(5, options))); - } - - { - iganet::NonUniformBSpline bspline( - {{{0.0_r, 0.0_r, 0.25_r, 0.5_r, 0.75_r, 1.0_r, 1.0_r}}}, - iganet::init::ones, options); - EXPECT_TRUE(torch::equal(bspline.coeffs(0), torch::ones(5, options))); - EXPECT_TRUE(torch::equal(bspline.coeffs(1), torch::ones(5, options))); - } - - { - iganet::NonUniformBSpline bspline( - {{{0.0_r, 0.0_r, 0.25_r, 0.5_r, 0.75_r, 1.0_r, 1.0_r}}}, - iganet::init::linear, options); - EXPECT_TRUE( - torch::equal(bspline.coeffs(0), torch::linspace(0, 1, 5, options))); - EXPECT_TRUE(torch::equal(bspline.coeffs(1), torch::ones(5, options))); - } - - { - iganet::NonUniformBSpline bspline( - {{{0.0_r, 0.0_r, 0.25_r, 0.5_r, 0.75_r, 1.0_r, 1.0_r}}}, - iganet::init::greville, options); - EXPECT_TRUE( - torch::equal(bspline.coeffs(0), torch::linspace(0, 1, 5, options))); - EXPECT_TRUE(torch::equal(bspline.coeffs(1), torch::ones(5, options))); - } - - { - iganet::NonUniformBSpline bspline( - {{{0.0_r, 0.0_r, 0.0_r, 0.2_r, 0.4_r, 0.6_r, 0.8_r, 1.0_r, 1.0_r, - 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::zeros, options); - EXPECT_TRUE(torch::equal(bspline.coeffs(0), torch::zeros(28, options))); - EXPECT_TRUE(torch::equal(bspline.coeffs(1), torch::zeros(28, options))); - } - - { - iganet::NonUniformBSpline bspline( - {{{0.0_r, 0.0_r, 0.0_r, 0.2_r, 0.4_r, 0.6_r, 0.8_r, 1.0_r, 1.0_r, - 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::ones, options); - EXPECT_TRUE(torch::equal(bspline.coeffs(0), torch::ones(28, options))); - EXPECT_TRUE(torch::equal(bspline.coeffs(1), torch::ones(28, options))); - } - - { - iganet::NonUniformBSpline bspline( - {{{0.0_r, 0.0_r, 0.0_r, 0.2_r, 0.4_r, 0.6_r, 0.8_r, 1.0_r, 1.0_r, - 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::linear, options); - EXPECT_TRUE(torch::equal(bspline.coeffs(0), - torch::linspace(0, 1, 7, options).repeat(4))); - EXPECT_TRUE( - torch::equal(bspline.coeffs(1), - torch::linspace(0, 1, 4, options).repeat_interleave(7))); - } - - { - iganet::NonUniformBSpline bspline( - {{{0.0_r, 0.0_r, 0.25_r, 0.5_r, 0.75_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.2_r, 0.4_r, 0.6_r, 0.8_r, 1.0_r, 1.0_r}}}, - iganet::init::greville, options); - EXPECT_TRUE(torch::equal(bspline.coeffs(0), - torch::linspace(0, 1, 5, options).repeat(6))); - EXPECT_TRUE( - torch::equal(bspline.coeffs(1), - torch::linspace(0, 1, 6, options).repeat_interleave(5))); - } - - { - iganet::NonUniformBSpline bspline( - {{{0.0_r, 0.0_r, 0.0_r, 0.2_r, 0.4_r, 0.6_r, 0.8_r, 1.0_r, 1.0_r, - 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::zeros, options); - EXPECT_TRUE(torch::equal(bspline.coeffs(0), torch::zeros(28, options))); - EXPECT_TRUE(torch::equal(bspline.coeffs(1), torch::zeros(28, options))); - EXPECT_TRUE(torch::equal(bspline.coeffs(2), torch::zeros(28, options))); - } - - { - iganet::NonUniformBSpline bspline( - {{{0.0_r, 0.0_r, 0.0_r, 0.2_r, 0.4_r, 0.6_r, 0.8_r, 1.0_r, 1.0_r, - 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::ones, options); - EXPECT_TRUE(torch::equal(bspline.coeffs(0), torch::ones(28, options))); - EXPECT_TRUE(torch::equal(bspline.coeffs(1), torch::ones(28, options))); - EXPECT_TRUE(torch::equal(bspline.coeffs(2), torch::ones(28, options))); - } - - { - iganet::NonUniformBSpline bspline( - {{{0.0_r, 0.0_r, 0.0_r, 0.2_r, 0.4_r, 0.6_r, 0.8_r, 1.0_r, 1.0_r, - 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::linear, options); - EXPECT_TRUE(torch::equal(bspline.coeffs(0), - torch::linspace(0, 1, 7, options).repeat(4))); - EXPECT_TRUE( - torch::equal(bspline.coeffs(1), - torch::linspace(0, 1, 4, options).repeat_interleave(7))); - EXPECT_TRUE(torch::equal(bspline.coeffs(2), torch::ones(28, options))); - } - - { - iganet::NonUniformBSpline bspline( - {{{0.0_r, 0.0_r, 0.25_r, 0.5_r, 0.75_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.2_r, 0.4_r, 0.6_r, 0.8_r, 1.0_r, 1.0_r}}}, - iganet::init::greville, options); - EXPECT_TRUE(torch::equal(bspline.coeffs(0), - torch::linspace(0, 1, 5, options).repeat(6))); - EXPECT_TRUE( - torch::equal(bspline.coeffs(1), - torch::linspace(0, 1, 6, options).repeat_interleave(5))); - EXPECT_TRUE(torch::equal(bspline.coeffs(2), torch::ones(30, options))); - } - - { - iganet::NonUniformBSpline bspline( - {{{0.0_r, 0.0_r, 0.0_r, 0.2_r, 0.4_r, 0.6_r, 0.8_r, 1.0_r, 1.0_r, - 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::zeros, options); - EXPECT_TRUE(torch::equal(bspline.coeffs(0), torch::zeros(28, options))); - EXPECT_TRUE(torch::equal(bspline.coeffs(1), torch::zeros(28, options))); - EXPECT_TRUE(torch::equal(bspline.coeffs(2), torch::zeros(28, options))); - EXPECT_TRUE(torch::equal(bspline.coeffs(3), torch::zeros(28, options))); - } - - { - iganet::NonUniformBSpline bspline( - {{{0.0_r, 0.0_r, 0.0_r, 0.2_r, 0.4_r, 0.6_r, 0.8_r, 1.0_r, 1.0_r, - 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::ones, options); - EXPECT_TRUE(torch::equal(bspline.coeffs(0), torch::ones(28, options))); - EXPECT_TRUE(torch::equal(bspline.coeffs(1), torch::ones(28, options))); - EXPECT_TRUE(torch::equal(bspline.coeffs(2), torch::ones(28, options))); - EXPECT_TRUE(torch::equal(bspline.coeffs(3), torch::ones(28, options))); - } - - { - iganet::NonUniformBSpline bspline( - {{{0.0_r, 0.0_r, 0.0_r, 0.2_r, 0.4_r, 0.6_r, 0.8_r, 1.0_r, 1.0_r, - 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::linear, options); - EXPECT_TRUE(torch::equal(bspline.coeffs(0), - torch::linspace(0, 1, 7, options).repeat(4))); - EXPECT_TRUE( - torch::equal(bspline.coeffs(1), - torch::linspace(0, 1, 4, options).repeat_interleave(7))); - EXPECT_TRUE(torch::equal(bspline.coeffs(2), torch::ones(28, options))); - EXPECT_TRUE(torch::equal(bspline.coeffs(3), torch::ones(28, options))); - } - - { - iganet::NonUniformBSpline bspline( - {{{0.0_r, 0.0_r, 0.25_r, 0.5_r, 0.75_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.2_r, 0.4_r, 0.6_r, 0.8_r, 1.0_r, 1.0_r}}}, - iganet::init::greville, options); - EXPECT_TRUE(torch::equal(bspline.coeffs(0), - torch::linspace(0, 1, 5, options).repeat(6))); - EXPECT_TRUE( - torch::equal(bspline.coeffs(1), - torch::linspace(0, 1, 6, options).repeat_interleave(5))); - EXPECT_TRUE(torch::equal(bspline.coeffs(2), torch::ones(30, options))); - EXPECT_TRUE(torch::equal(bspline.coeffs(3), torch::ones(30, options))); - } -} - -TEST_F(BSplineTest, NonUniformBSpline_uniform_refine) { - { - iganet::NonUniformBSpline bspline({4, 5}); - iganet::NonUniformBSpline bspline_ref({5, 6}); - bspline.uniform_refine(); - - EXPECT_TRUE(bspline.isclose(bspline_ref)); - } - - { - iganet::NonUniformBSpline bspline({4, 5}); - iganet::NonUniformBSpline bspline_ref({7, 8}); - bspline.uniform_refine(2); - - EXPECT_TRUE(bspline.isclose(bspline_ref)); - } - - { - iganet::NonUniformBSpline bspline({4, 5}); - iganet::NonUniformBSpline bspline_ref({5, 5}); - bspline.uniform_refine(1, 0); - - EXPECT_TRUE(bspline.isclose(bspline_ref)); - } - - { - iganet::NonUniformBSpline bspline({4, 5}); - iganet::NonUniformBSpline bspline_ref({5, 8}); - bspline.uniform_refine(1, 0).uniform_refine(2, 1); - - EXPECT_TRUE(bspline.isclose(bspline_ref)); - } -} - -TEST_F(BSplineTest, NonUniformBSpline_copy_constructor) { - iganet::NonUniformBSpline bspline_orig( - {4, 5}, iganet::init::greville, options); - iganet::NonUniformBSpline bspline_copy(bspline_orig); - - bspline_orig.transform([](const std::array xi) { - return std::array{0.0_r, 1.0_r, 2.0_r}; - }); - - EXPECT_TRUE(bspline_orig == bspline_copy); -} - -TEST_F(BSplineTest, NonUniformBSpline_clone_constructor) { - iganet::NonUniformBSpline bspline_ref( - {4, 5}, iganet::init::greville, options); - iganet::NonUniformBSpline bspline_orig( - {4, 5}, iganet::init::greville, options); - iganet::NonUniformBSpline bspline_clone(bspline_orig, true); - - bspline_orig.transform([](const std::array xi) { - return std::array{0.0_r, 1.0_r, 2.0_r}; - }); - - EXPECT_TRUE(bspline_ref == bspline_clone); -} - -TEST_F(BSplineTest, NonUniformBSpline_move_constructor) { - iganet::NonUniformBSpline bspline_ref( - {7, 8}, iganet::init::greville, options); - auto bspline(iganet::NonUniformBSpline( - {4, 5}, iganet::init::greville, options) - .uniform_refine(2)); - - EXPECT_TRUE(bspline.isclose(bspline_ref)); -} - -TEST_F(BSplineTest, NonUniformBSpline_copy_coeffs_constructor) { - iganet::NonUniformBSpline bspline_orig( - {4, 5}, iganet::init::greville, options); - iganet::NonUniformBSpline bspline_copy( - bspline_orig, bspline_orig.coeffs()); - - bspline_orig.transform([](const std::array xi) { - return std::array{0.0_r, 1.0_r, 2.0_r}; - }); - - EXPECT_TRUE(bspline_orig == bspline_copy); -} - -TEST_F(BSplineTest, NonUniformBSpline_clone_coeffs_constructor) { - iganet::NonUniformBSpline bspline_ref( - {4, 5}, iganet::init::greville, options); - iganet::NonUniformBSpline bspline_orig( - {4, 5}, iganet::init::greville, options); - iganet::NonUniformBSpline bspline_clone( - bspline_orig, bspline_orig.coeffs(), true); - - bspline_orig.transform([](const std::array xi) { - return std::array{0.0_r, 1.0_r, 2.0_r}; - }); - - EXPECT_TRUE(bspline_ref == bspline_clone); -} - -TEST_F(BSplineTest, NonUniformBSpline_read_write) { - std::filesystem::path filename = - std::filesystem::temp_directory_path() / std::to_string(rand()); - iganet::NonUniformBSpline bspline_out( - {{{0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::greville, options); - bspline_out.save(filename.c_str()); - iganet::NonUniformBSpline bspline_in(options); - bspline_in.load(filename.c_str()); - std::filesystem::remove(filename); - - EXPECT_TRUE(bspline_in == bspline_out); - EXPECT_FALSE(bspline_in != bspline_out); -} - -TEST_F(BSplineTest, NonUniformBSpline_to_from_xml) { - { - iganet::NonUniformBSpline bspline_out( - {4}, iganet::init::zeros, options); - - bspline_out.transform([](const std::array xi) { - return std::array{static_cast(std::rand())}; - }); - - pugi::xml_document doc = bspline_out.to_xml(); - - iganet::NonUniformBSpline bspline_in(options); - bspline_in.from_xml(doc); - - EXPECT_TRUE(bspline_in == bspline_out); - - // non-matching degree - EXPECT_THROW((iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching parametric dimension - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching geometric dimension - EXPECT_THROW((iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW((iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW((iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching id - EXPECT_THROW((iganet::NonUniformBSpline{}.from_xml(doc, 1)), - std::runtime_error); - } - - { - iganet::NonUniformBSpline bspline_out( - {4}, iganet::init::zeros, options); - - bspline_out.transform([](const std::array xi) { - return std::array{static_cast(std::rand()), - static_cast(std::rand())}; - }); - - pugi::xml_document doc = bspline_out.to_xml(); - - iganet::NonUniformBSpline bspline_in(options); - bspline_in.from_xml(doc); - - EXPECT_TRUE(bspline_in == bspline_out); - - // non-matching degree - EXPECT_THROW((iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching parametric dimension - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching geometric dimension - EXPECT_THROW((iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW((iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW((iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching id - EXPECT_THROW((iganet::NonUniformBSpline{}.from_xml(doc, 1)), - std::runtime_error); - } - - { - iganet::NonUniformBSpline bspline_out( - {4}, iganet::init::zeros, options); - - bspline_out.transform([](const std::array xi) { - return std::array{static_cast(std::rand()), - static_cast(std::rand()), - static_cast(std::rand())}; - }); - - pugi::xml_document doc = bspline_out.to_xml(); - - iganet::NonUniformBSpline bspline_in(options); - bspline_in.from_xml(doc); - - EXPECT_TRUE(bspline_in == bspline_out); - - // non-matching degree - EXPECT_THROW((iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching parametric dimension - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching geometric dimension - EXPECT_THROW((iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW((iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW((iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching id - EXPECT_THROW((iganet::NonUniformBSpline{}.from_xml(doc, 1)), - std::runtime_error); - } - - { - iganet::NonUniformBSpline bspline_out( - {4}, iganet::init::zeros, options); - - bspline_out.transform([](const std::array xi) { - return std::array{ - static_cast(std::rand()), static_cast(std::rand()), - static_cast(std::rand()), static_cast(std::rand())}; - }); - - pugi::xml_document doc = bspline_out.to_xml(); - - iganet::NonUniformBSpline bspline_in(options); - bspline_in.from_xml(doc); - - EXPECT_TRUE(bspline_in == bspline_out); - - // non-matching degree - EXPECT_THROW((iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching parametric dimension - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching geometric dimension - EXPECT_THROW((iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW((iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW((iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching id - EXPECT_THROW((iganet::NonUniformBSpline{}.from_xml(doc, 1)), - std::runtime_error); - } - - { - iganet::NonUniformBSpline bspline_out( - {4, 5}, iganet::init::zeros, options); - - bspline_out.transform([](const std::array xi) { - return std::array{static_cast(std::rand())}; - }); - - pugi::xml_document doc = bspline_out.to_xml(); - - iganet::NonUniformBSpline bspline_in(options); - bspline_in.from_xml(doc); - - EXPECT_TRUE(bspline_in == bspline_out); - - // non-matching degree - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching parametric dimension - EXPECT_THROW((iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching geometric dimension - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching id - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 1)), - std::runtime_error); - } - - { - iganet::NonUniformBSpline bspline_out( - {4, 5}, iganet::init::zeros, options); - - bspline_out.transform([](const std::array xi) { - return std::array{static_cast(std::rand()), - static_cast(std::rand())}; - }); - - pugi::xml_document doc = bspline_out.to_xml(); - - iganet::NonUniformBSpline bspline_in(options); - bspline_in.from_xml(doc); - - EXPECT_TRUE(bspline_in == bspline_out); - - // non-matching degree - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching parametric dimension - EXPECT_THROW((iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching geometric dimension - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching id - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 1)), - std::runtime_error); - } - - { - iganet::NonUniformBSpline bspline_out( - {4, 5}, iganet::init::zeros, options); - - bspline_out.transform([](const std::array xi) { - return std::array{static_cast(std::rand()), - static_cast(std::rand()), - static_cast(std::rand())}; - }); - - pugi::xml_document doc = bspline_out.to_xml(); - - iganet::NonUniformBSpline bspline_in(options); - bspline_in.from_xml(doc); - - EXPECT_TRUE(bspline_in == bspline_out); - - // non-matching degree - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching parametric dimension - EXPECT_THROW((iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching geometric dimension - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching id - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 1)), - std::runtime_error); - } - - { - iganet::NonUniformBSpline bspline_out( - {4, 5}, iganet::init::zeros, options); - - bspline_out.transform([](const std::array xi) { - return std::array{ - static_cast(std::rand()), static_cast(std::rand()), - static_cast(std::rand()), static_cast(std::rand())}; - }); - - pugi::xml_document doc = bspline_out.to_xml(); - - iganet::NonUniformBSpline bspline_in(options); - bspline_in.from_xml(doc); - - EXPECT_TRUE(bspline_in == bspline_out); - - // non-matching degree - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching parametric dimension - EXPECT_THROW((iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching geometric dimension - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching id - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 1)), - std::runtime_error); - } - - { - iganet::NonUniformBSpline bspline_out( - {4, 5, 6}, iganet::init::zeros, options); - - bspline_out.transform([](const std::array xi) { - return std::array{static_cast(std::rand())}; - }); - - pugi::xml_document doc = bspline_out.to_xml(); - - iganet::NonUniformBSpline bspline_in(options); - bspline_in.from_xml(doc); - - EXPECT_TRUE(bspline_in == bspline_out); - - // non-matching degree - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching parametric dimension - EXPECT_THROW((iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching geometric dimension - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching id - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 1)), - std::runtime_error); - } - - { - iganet::NonUniformBSpline bspline_out( - {4, 5, 6}, iganet::init::zeros, options); - - bspline_out.transform([](const std::array xi) { - return std::array{static_cast(std::rand()), - static_cast(std::rand())}; - }); - - pugi::xml_document doc = bspline_out.to_xml(); - - iganet::NonUniformBSpline bspline_in(options); - bspline_in.from_xml(doc); - - EXPECT_TRUE(bspline_in == bspline_out); - - // non-matching degree - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching parametric dimension - EXPECT_THROW((iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching geometric dimension - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching id - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 1)), - std::runtime_error); - } - - { - iganet::NonUniformBSpline bspline_out( - {4, 5, 6}, iganet::init::zeros, options); - - bspline_out.transform([](const std::array xi) { - return std::array{static_cast(std::rand()), - static_cast(std::rand()), - static_cast(std::rand())}; - }); - - pugi::xml_document doc = bspline_out.to_xml(); - - iganet::NonUniformBSpline bspline_in(options); - bspline_in.from_xml(doc); - - EXPECT_TRUE(bspline_in == bspline_out); - - // non-matching degree - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching parametric dimension - EXPECT_THROW((iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching geometric dimension - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching id - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 1)), - std::runtime_error); - } - - { - iganet::NonUniformBSpline bspline_out( - {4, 5, 6}, iganet::init::zeros, options); - - bspline_out.transform([](const std::array xi) { - return std::array{ - static_cast(std::rand()), static_cast(std::rand()), - static_cast(std::rand()), static_cast(std::rand())}; - }); - - pugi::xml_document doc = bspline_out.to_xml(); - - iganet::NonUniformBSpline bspline_in(options); - bspline_in.from_xml(doc); - - EXPECT_TRUE(bspline_in == bspline_out); - - // non-matching degree - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching parametric dimension - EXPECT_THROW((iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching geometric dimension - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching id - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 1)), - std::runtime_error); - } - - { - iganet::NonUniformBSpline bspline_out( - {4, 5, 6, 2}, iganet::init::zeros, options); - - bspline_out.transform([](const std::array xi) { - return std::array{static_cast(std::rand())}; - }); - - pugi::xml_document doc = bspline_out.to_xml(); - - iganet::NonUniformBSpline bspline_in(options); - bspline_in.from_xml(doc); - - EXPECT_TRUE(bspline_in == bspline_out); - - // non-matching degree - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching parametric dimension - EXPECT_THROW((iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching geometric dimension - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching id - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 1)), - std::runtime_error); - } - - { - iganet::NonUniformBSpline bspline_out( - {4, 5, 6, 2}, iganet::init::zeros, options); - - bspline_out.transform([](const std::array xi) { - return std::array{static_cast(std::rand()), - static_cast(std::rand())}; - }); - - pugi::xml_document doc = bspline_out.to_xml(); - - iganet::NonUniformBSpline bspline_in(options); - bspline_in.from_xml(doc); - - EXPECT_TRUE(bspline_in == bspline_out); - - // non-matching degree - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching parametric dimension - EXPECT_THROW((iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching geometric dimension - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching id - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 1)), - std::runtime_error); - } - - { - iganet::NonUniformBSpline bspline_out( - {4, 5, 6, 2}, iganet::init::zeros, options); - - bspline_out.transform([](const std::array xi) { - return std::array{static_cast(std::rand()), - static_cast(std::rand()), - static_cast(std::rand())}; - }); - - pugi::xml_document doc = bspline_out.to_xml(); - - iganet::NonUniformBSpline bspline_in(options); - bspline_in.from_xml(doc); - - EXPECT_TRUE(bspline_in == bspline_out); - - // non-matching degree - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching parametric dimension - EXPECT_THROW((iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching geometric dimension - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching id - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 1)), - std::runtime_error); - } - - { - iganet::NonUniformBSpline bspline_out( - {4, 5, 6, 2}, iganet::init::zeros, options); - - bspline_out.transform([](const std::array xi) { - return std::array{ - static_cast(std::rand()), static_cast(std::rand()), - static_cast(std::rand()), static_cast(std::rand())}; - }); - - pugi::xml_document doc = bspline_out.to_xml(); - - iganet::NonUniformBSpline bspline_in(options); - bspline_in.from_xml(doc); - - EXPECT_TRUE(bspline_in == bspline_out); - - // non-matching degree - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching parametric dimension - EXPECT_THROW((iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching geometric dimension - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching id - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_xml(doc, 1)), - std::runtime_error); - } -} - -TEST_F(BSplineTest, NonUniformBSpline_load_from_xml) { - { - pugi::xml_document doc; - pugi::xml_parse_result result = - doc.load_file(IGANET_DATA_DIR "domain1d/line.xml"); - - iganet::NonUniformBSpline bspline_in(options); - bspline_in.from_xml(doc); - - iganet::NonUniformBSpline bspline_ref( - {3}, iganet::init::zeros, options); - - bspline_ref.transform([](const std::array xi) { - return std::array{xi[0], 0.0_r, 0.0_r}; - }); - - EXPECT_TRUE(bspline_in == bspline_ref); - } - - { - pugi::xml_document doc; - pugi::xml_parse_result result = - doc.load_file(IGANET_DATA_DIR "domain2d/square.xml"); - - iganet::NonUniformBSpline bspline_in(options); - bspline_in.from_xml(doc, 1); - - iganet::NonUniformBSpline bspline_ref( - {2, 2}, iganet::init::greville, options); - - EXPECT_TRUE(bspline_in == bspline_ref); - } - - { - pugi::xml_document doc; - pugi::xml_parse_result result = - doc.load_file(IGANET_DATA_DIR "domain3d/GshapedVolume.xml"); - - iganet::NonUniformBSpline bspline_in(options); - bspline_in.from_xml(doc); - } - - { - pugi::xml_document doc; - pugi::xml_parse_result result = - doc.load_file(IGANET_DATA_DIR "surfaces/g_plus_s_surf.xml"); - - iganet::NonUniformBSpline bspline_in0(options); - iganet::NonUniformBSpline bspline_in1(options); - - for (int i = 0; i < 126; ++i) { - try { - bspline_in0.from_xml(doc, i); - } catch (...) { - bspline_in1.from_xml(doc, i); - } - } - } -} - -TEST_F(BSplineTest, NonUniformBSpline_to_from_json) { - { - iganet::NonUniformBSpline bspline_out( - {4}, iganet::init::zeros, options); - - bspline_out.transform([](const std::array xi) { - return std::array{static_cast(std::rand())}; - }); - - nlohmann::json json = bspline_out.to_json(); - - iganet::NonUniformBSpline bspline_in(options); - bspline_in.from_json(json); - - EXPECT_TRUE(bspline_in == bspline_out); - - // non-matching degree - EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - - // non-matching parametric dimension - EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - - // non-matching geometric dimension - EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - } - - { - iganet::NonUniformBSpline bspline_out( - {4}, iganet::init::zeros, options); - - bspline_out.transform([](const std::array xi) { - return std::array{static_cast(std::rand()), - static_cast(std::rand())}; - }); - - nlohmann::json json = bspline_out.to_json(); - - iganet::NonUniformBSpline bspline_in(options); - bspline_in.from_json(json); - - EXPECT_TRUE(bspline_in == bspline_out); - - // non-matching degree - EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - - // non-matching parametric dimension - EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - - // non-matching geometric dimension - EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - } - - { - iganet::NonUniformBSpline bspline_out( - {4}, iganet::init::zeros, options); - - bspline_out.transform([](const std::array xi) { - return std::array{static_cast(std::rand()), - static_cast(std::rand()), - static_cast(std::rand())}; - }); - - nlohmann::json json = bspline_out.to_json(); - - iganet::NonUniformBSpline bspline_in(options); - bspline_in.from_json(json); - - EXPECT_TRUE(bspline_in == bspline_out); - - // non-matching degree - EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - - // non-matching parametric dimension - EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - - // non-matching geometric dimension - EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - } - - { - iganet::NonUniformBSpline bspline_out( - {4}, iganet::init::zeros, options); - - bspline_out.transform([](const std::array xi) { - return std::array{ - static_cast(std::rand()), static_cast(std::rand()), - static_cast(std::rand()), static_cast(std::rand())}; - }); - - nlohmann::json json = bspline_out.to_json(); - - iganet::NonUniformBSpline bspline_in(options); - bspline_in.from_json(json); - - EXPECT_TRUE(bspline_in == bspline_out); - - // non-matching degree - EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - - // non-matching parametric dimension - EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - - // non-matching geometric dimension - EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - } - - { - iganet::NonUniformBSpline bspline_out( - {4, 5}, iganet::init::zeros, options); - - bspline_out.transform([](const std::array xi) { - return std::array{static_cast(std::rand())}; - }); - - nlohmann::json json = bspline_out.to_json(); - - iganet::NonUniformBSpline bspline_in(options); - bspline_in.from_json(json); - - EXPECT_TRUE(bspline_in == bspline_out); - - // non-matching degree - EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - - // non-matching parametric dimension - EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - - // non-matching geometric dimension - EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - } - - { - iganet::NonUniformBSpline bspline_out( - {4, 5}, iganet::init::zeros, options); - - bspline_out.transform([](const std::array xi) { - return std::array{static_cast(std::rand()), - static_cast(std::rand())}; - }); - - nlohmann::json json = bspline_out.to_json(); - - iganet::NonUniformBSpline bspline_in(options); - bspline_in.from_json(json); - - EXPECT_TRUE(bspline_in == bspline_out); - - // non-matching degree - EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - - // non-matching parametric dimension - EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - - // non-matching geometric dimension - EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - } - - { - iganet::NonUniformBSpline bspline_out( - {4, 5}, iganet::init::zeros, options); - - bspline_out.transform([](const std::array xi) { - return std::array{static_cast(std::rand()), - static_cast(std::rand()), - static_cast(std::rand())}; - }); - - nlohmann::json json = bspline_out.to_json(); - - iganet::NonUniformBSpline bspline_in(options); - bspline_in.from_json(json); - - EXPECT_TRUE(bspline_in == bspline_out); - - // non-matching degree - EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - - // non-matching parametric dimension - EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - - // non-matching geometric dimension - EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - } - - { - iganet::NonUniformBSpline bspline_out( - {4, 5}, iganet::init::zeros, options); - - bspline_out.transform([](const std::array xi) { - return std::array{ - static_cast(std::rand()), static_cast(std::rand()), - static_cast(std::rand()), static_cast(std::rand())}; - }); - - nlohmann::json json = bspline_out.to_json(); - - iganet::NonUniformBSpline bspline_in(options); - bspline_in.from_json(json); - - EXPECT_TRUE(bspline_in == bspline_out); - - // non-matching degree - EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - - // non-matching parametric dimension - EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - - // non-matching geometric dimension - EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - } - - { - iganet::NonUniformBSpline bspline_out( - {4, 5, 6}, iganet::init::zeros, options); - - bspline_out.transform([](const std::array xi) { - return std::array{static_cast(std::rand())}; - }); - - nlohmann::json json = bspline_out.to_json(); - - iganet::NonUniformBSpline bspline_in(options); - bspline_in.from_json(json); - - EXPECT_TRUE(bspline_in == bspline_out); - - // non-matching degree - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - - // non-matching parametric dimension - EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - - // non-matching geometric dimension - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - } - - { - iganet::NonUniformBSpline bspline_out( - {4, 5, 6}, iganet::init::zeros, options); - - bspline_out.transform([](const std::array xi) { - return std::array{static_cast(std::rand()), - static_cast(std::rand())}; - }); - - nlohmann::json json = bspline_out.to_json(); - - iganet::NonUniformBSpline bspline_in(options); - bspline_in.from_json(json); - - EXPECT_TRUE(bspline_in == bspline_out); - - // non-matching degree - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - - // non-matching parametric dimension - EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - - // non-matching geometric dimension - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - } - - { - iganet::NonUniformBSpline bspline_out( - {4, 5, 6}, iganet::init::zeros, options); - - bspline_out.transform([](const std::array xi) { - return std::array{static_cast(std::rand()), - static_cast(std::rand()), - static_cast(std::rand())}; - }); - - nlohmann::json json = bspline_out.to_json(); - - iganet::NonUniformBSpline bspline_in(options); - bspline_in.from_json(json); - - EXPECT_TRUE(bspline_in == bspline_out); - - // non-matching degree - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - - // non-matching parametric dimension - EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - - // non-matching geometric dimension - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - } - - { - iganet::NonUniformBSpline bspline_out( - {4, 5, 6}, iganet::init::zeros, options); - - bspline_out.transform([](const std::array xi) { - return std::array{ - static_cast(std::rand()), static_cast(std::rand()), - static_cast(std::rand()), static_cast(std::rand())}; - }); - - nlohmann::json json = bspline_out.to_json(); - - iganet::NonUniformBSpline bspline_in(options); - bspline_in.from_json(json); - - EXPECT_TRUE(bspline_in == bspline_out); - - // non-matching degree - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - - // non-matching parametric dimension - EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - - // non-matching geometric dimension - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - } - - { - iganet::NonUniformBSpline bspline_out( - {4, 5, 6, 2}, iganet::init::zeros, options); - - bspline_out.transform([](const std::array xi) { - return std::array{static_cast(std::rand())}; - }); - - nlohmann::json json = bspline_out.to_json(); - - iganet::NonUniformBSpline bspline_in(options); - bspline_in.from_json(json); - - EXPECT_TRUE(bspline_in == bspline_out); - - // non-matching degree - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - - // non-matching parametric dimension - EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - - // non-matching geometric dimension - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - } - - { - iganet::NonUniformBSpline bspline_out( - {4, 5, 6, 2}, iganet::init::zeros, options); - - bspline_out.transform([](const std::array xi) { - return std::array{static_cast(std::rand()), - static_cast(std::rand())}; - }); - - nlohmann::json json = bspline_out.to_json(); - - iganet::NonUniformBSpline bspline_in(options); - bspline_in.from_json(json); - - EXPECT_TRUE(bspline_in == bspline_out); - - // non-matching degree - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - - // non-matching parametric dimension - EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - - // non-matching geometric dimension - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - } - - { - iganet::NonUniformBSpline bspline_out( - {4, 5, 6, 2}, iganet::init::zeros, options); - - bspline_out.transform([](const std::array xi) { - return std::array{static_cast(std::rand()), - static_cast(std::rand()), - static_cast(std::rand())}; - }); - - nlohmann::json json = bspline_out.to_json(); - - iganet::NonUniformBSpline bspline_in(options); - bspline_in.from_json(json); - - EXPECT_TRUE(bspline_in == bspline_out); - - // non-matching degree - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - - // non-matching parametric dimension - EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - - // non-matching geometric dimension - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - } - - { - iganet::NonUniformBSpline bspline_out( - {4, 5, 6, 2}, iganet::init::zeros, options); - - bspline_out.transform([](const std::array xi) { - return std::array{ - static_cast(std::rand()), static_cast(std::rand()), - static_cast(std::rand()), static_cast(std::rand())}; - }); - - nlohmann::json json = bspline_out.to_json(); - - iganet::NonUniformBSpline bspline_in(options); - bspline_in.from_json(json); - - EXPECT_TRUE(bspline_in == bspline_out); - - // non-matching degree - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - - // non-matching parametric dimension - EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - - // non-matching geometric dimension - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW( - (iganet::NonUniformBSpline{}.from_json(json)), - std::runtime_error); - } -} - -TEST_F(BSplineTest, NonUniformBSpline_query_property) { - iganet::NonUniformBSpline bspline( - {4, 5}, iganet::init::greville, options); - - EXPECT_FALSE(bspline.is_uniform()); - EXPECT_TRUE(bspline.is_nonuniform()); - - EXPECT_EQ(bspline.device(), options.device()); - EXPECT_EQ(bspline.device_index(), options.device_index()); - EXPECT_EQ(bspline.dtype(), options.dtype()); - EXPECT_EQ(bspline.is_sparse(), options.is_sparse()); - EXPECT_EQ(bspline.layout(), options.layout()); - EXPECT_EQ(bspline.pinned_memory(), options.pinned_memory()); -} - -TEST_F(BSplineTest, NonUniformBSpline_requires_grad) { - { - iganet::NonUniformBSpline bspline( - {4, 5}, iganet::init::greville, options); - - EXPECT_EQ(bspline.requires_grad(), false); - - for (iganet::short_t i = 0; i < bspline.parDim(); ++i) - EXPECT_EQ(bspline.knots(i).requires_grad(), false); - - for (iganet::short_t i = 0; i < bspline.geoDim(); ++i) - EXPECT_EQ(bspline.coeffs(i).requires_grad(), false); - - auto xi = iganet::utils::to_tensorArray(options, {0.5_r}, {0.5_r}); - auto values = bspline.eval(xi); - - // We expect an error when calling backward() because no tensor - // has requires_grad = true - EXPECT_THROW(values[0]->backward(), c10::Error); - - xi = iganet::utils::to_tensorArray(options.requires_grad(true), {0.5_r}, - {0.5_r}); - values = bspline.eval(xi); - values[0]->backward(); - EXPECT_TRUE(torch::allclose(xi[0].grad(), - iganet::utils::to_tensor({1.0_r}, options))); - } - - { - iganet::NonUniformBSpline bspline( - {4, 5}, iganet::init::linear, options.requires_grad(true)); - - EXPECT_TRUE(bspline.requires_grad()); - - for (iganet::short_t i = 0; i < bspline.parDim(); ++i) - EXPECT_TRUE(bspline.knots(i).requires_grad()); - - for (iganet::short_t i = 0; i < bspline.geoDim(); ++i) - EXPECT_TRUE(bspline.coeffs(i).requires_grad()); - - auto xi = iganet::utils::to_tensorArray(options, {0.5_r}, {0.5_r}); - auto values = bspline.eval(xi); - values[0]->backward( - {}, true); // otherwise we cannot run backward() a second time - - // We expect an error because xi[0].grad() is an undefined tensor - EXPECT_THROW(torch::allclose(xi[0].grad(), torch::empty({})), c10::Error); - - xi = iganet::utils::to_tensorArray(options.requires_grad(true), {0.5_r}, - {0.5_r}); - values = bspline.eval(xi); - values[0]->backward(); - EXPECT_TRUE(torch::allclose(xi[0].grad(), - iganet::utils::to_tensor({1.0_r}, options))); - - EXPECT_TRUE(torch::allclose( - bspline.coeffs(0).grad(), - iganet::utils::to_tensor( - {0.015625_r, 0.046875_r, 0.046875_r, 0.015625_r, 0.0625_r, - 0.1875_r, 0.1875_r, 0.0625_r, 0.09375_r, 0.28125_r, - 0.28125_r, 0.09375_r, 0.0625_r, 0.1875_r, 0.1875_r, - 0.0625_r, 0.015625_r, 0.046875_r, 0.046875_r, 0.015625_r}, - options))); - } -} - -TEST_F(BSplineTest, NonUniformBSpline_to_dtype) { - { - iganet::NonUniformBSpline bspline( - {4, 5}, iganet::init::greville, options); - - auto bspline_double = bspline.to(); - auto bspline_float = bspline.to(); - - if constexpr (std::is_same::value) - EXPECT_TRUE(bspline == bspline_double); - else - EXPECT_TRUE(bspline != bspline_double); - - if constexpr (std::is_same::value) - EXPECT_TRUE(bspline == bspline_float); - else - EXPECT_TRUE(bspline != bspline_float); - } - - { - iganet::NonUniformBSpline bspline( - {4, 5}, iganet::init::greville, options); - - auto bspline_double = bspline.to(iganet::Options{}); - auto bspline_float = bspline.to(iganet::Options{}); - - if constexpr (std::is_same::value) - EXPECT_TRUE(bspline == bspline_double); - else - EXPECT_TRUE(bspline != bspline_double); - - if constexpr (std::is_same::value) - EXPECT_TRUE(bspline == bspline_float); - else - EXPECT_TRUE(bspline != bspline_float); - } -} - -TEST_F(BSplineTest, NonUniformBSpline_to_device) { - { - iganet::Options options = - iganet::Options{}.device(torch::kCPU); - iganet::NonUniformBSpline bspline( - {4, 5}, iganet::init::greville, options); - - auto bspline_cpu = bspline.to(torch::kCPU); - EXPECT_TRUE(bspline == bspline_cpu); - - if (torch::cuda::is_available()) { - auto bspline_cuda = bspline.to(torch::kCUDA); - EXPECT_THROW((void)(bspline == bspline_cuda), c10::Error); - } else - EXPECT_THROW(bspline.to(torch::kCUDA), c10::Error); - - if (at::hasHIP()) { - auto bspline_hip = bspline.to(torch::kHIP); - EXPECT_THROW((void)(bspline == bspline_hip), c10::Error); - } else - EXPECT_THROW(bspline.to(torch::kHIP), c10::Error); - - if (at::hasMPS() && // will become torch::mps::is_available() - (options.dtype() != iganet::dtype())) { - auto bspline_mps = bspline.to(torch::kMPS); - EXPECT_THROW((void)(bspline == bspline_mps), c10::Error); - } else - EXPECT_THROW(bspline.to(torch::kMPS), c10::Error); - } -} - -TEST_F(BSplineTest, NonUniformBSpline_reduce_continuity) { - { - iganet::NonUniformBSpline bspline({5, 6}); - iganet::NonUniformBSpline bspline_ref( - iganet::utils::to_array( - iganet::utils::to_vector(0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 0.5_r, - 1.0_r, 1.0_r, 1.0_r, 1.0_r), - iganet::utils::to_vector(0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, - 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, - 1.0_r))); - bspline.reduce_continuity(); - - EXPECT_TRUE(bspline.isclose(bspline_ref)); - } - - { - iganet::NonUniformBSpline bspline({5, 6}); - iganet::NonUniformBSpline bspline_ref( - iganet::utils::to_array( - iganet::utils::to_vector(0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 0.5_r, - 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r), - iganet::utils::to_vector(0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, - 0.5_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, - 1.0_r))); - bspline.reduce_continuity(2); - - EXPECT_TRUE(bspline.isclose(bspline_ref)); - } - - { - iganet::NonUniformBSpline bspline({5, 6}); - iganet::NonUniformBSpline bspline_ref( - iganet::utils::to_array( - iganet::utils::to_vector(0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 0.5_r, - 1.0_r, 1.0_r, 1.0_r, 1.0_r), - iganet::utils::to_vector(0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, - 0.5_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, - 1.0_r))); - bspline.reduce_continuity(1, 0).reduce_continuity(2, 1); - - EXPECT_TRUE(bspline.isclose(bspline_ref)); - } -} - -TEST_F(BSplineTest, NonUniformBSpline_insert_knots) { - { - iganet::NonUniformBSpline bspline({5, 6}); - iganet::NonUniformBSpline bspline_ref( - iganet::utils::to_array( - iganet::utils::to_vector(0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.1_r, 0.3_r, - 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r), - iganet::utils::to_vector(0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.2_r, - 0.4_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, - 1.0_r))); - bspline.insert_knots( - iganet::utils::to_tensorArray({0.1_r, 0.3_r}, {0.2_r, 0.4_r})); - - EXPECT_TRUE(bspline.isclose(bspline_ref)); - } -} - -int main(int argc, char **argv) { - ::testing::InitGoogleTest(&argc, argv); - iganet::init(); - return RUN_ALL_TESTS(); -} +/** + @file unittests/unittest_nonuniform_bspline.cxx + + @brief B-Spline unittests + + @author Matthias Moller + + @copyright This file is part of the IgANet project + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#include +#include +#include + +#include +#include + +using namespace iganet::unittests::literals; + +class BSplineTest : public ::testing::Test { +public: + BSplineTest() { std::srand(std::time(nullptr)); } + +protected: + using real_t = iganet::unittests::real_t; + iganet::Options options; +}; + +TEST_F(BSplineTest, NonUniformBSpline_parDim1_geoDim1_degrees1) { + EXPECT_THROW( + (iganet::NonUniformBSpline({{{0.0_r, 0.0_r, 1.0_r}}})), + std::runtime_error); + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r}}}); + EXPECT_EQ(bspline.parDim(), 1); + EXPECT_EQ(bspline.geoDim(), 1); + EXPECT_EQ(bspline.degree(0), 1); + EXPECT_EQ(bspline.nknots(0), 5); + EXPECT_EQ(bspline.ncoeffs(0), 3); + EXPECT_EQ(bspline.ncumcoeffs(), 3); + EXPECT_TRUE(bspline.is_nonuniform()); + EXPECT_FALSE(bspline.is_uniform()); +} + +TEST_F(BSplineTest, NonUniformBSpline_parDim1_geoDim2_degrees1) { + EXPECT_THROW( + (iganet::NonUniformBSpline({{{0.0_r, 0.0_r, 1.0_r}}})), + std::runtime_error); + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r}}}); + EXPECT_EQ(bspline.parDim(), 1); + EXPECT_EQ(bspline.geoDim(), 2); + EXPECT_EQ(bspline.degree(0), 1); + EXPECT_EQ(bspline.nknots(0), 5); + EXPECT_EQ(bspline.ncoeffs(0), 3); + EXPECT_EQ(bspline.ncumcoeffs(), 3); +} + +TEST_F(BSplineTest, NonUniformBSpline_parDim1_geoDim3_degrees1) { + EXPECT_THROW( + (iganet::NonUniformBSpline({{{0.0_r, 0.0_r, 1.0_r}}})), + std::runtime_error); + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r}}}); + EXPECT_EQ(bspline.parDim(), 1); + EXPECT_EQ(bspline.geoDim(), 3); + EXPECT_EQ(bspline.degree(0), 1); + EXPECT_EQ(bspline.nknots(0), 5); + EXPECT_EQ(bspline.ncoeffs(0), 3); + EXPECT_EQ(bspline.ncumcoeffs(), 3); +} + +TEST_F(BSplineTest, NonUniformBSpline_parDim1_geoDim4_degrees1) { + EXPECT_THROW( + (iganet::NonUniformBSpline({{{0.0_r, 0.0_r, 1.0_r}}})), + std::runtime_error); + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r}}}); + EXPECT_EQ(bspline.parDim(), 1); + EXPECT_EQ(bspline.geoDim(), 4); + EXPECT_EQ(bspline.degree(0), 1); + EXPECT_EQ(bspline.nknots(0), 5); + EXPECT_EQ(bspline.ncoeffs(0), 3); + EXPECT_EQ(bspline.ncumcoeffs(), 3); +} + +TEST_F(BSplineTest, NonUniformBSpline_parDim2_geoDim1_degrees12) { + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 1.0_r, 1.0_r, 1.0_r}}}); + EXPECT_EQ(bspline.parDim(), 2); + EXPECT_EQ(bspline.geoDim(), 1); + EXPECT_EQ(bspline.degree(0), 1); + EXPECT_EQ(bspline.degree(1), 2); + EXPECT_EQ(bspline.nknots(0), 5); + EXPECT_EQ(bspline.nknots(1), 6); + EXPECT_EQ(bspline.ncoeffs(0), 3); + EXPECT_EQ(bspline.ncoeffs(1), 3); + EXPECT_EQ(bspline.ncumcoeffs(), 9); +} + +TEST_F(BSplineTest, NonUniformBSpline_parDim2_geoDim2_degrees12) { + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 1.0_r, 1.0_r, 1.0_r}}}); + EXPECT_EQ(bspline.parDim(), 2); + EXPECT_EQ(bspline.geoDim(), 2); + EXPECT_EQ(bspline.degree(0), 1); + EXPECT_EQ(bspline.degree(1), 2); + EXPECT_EQ(bspline.nknots(0), 5); + EXPECT_EQ(bspline.nknots(1), 6); + EXPECT_EQ(bspline.ncoeffs(0), 3); + EXPECT_EQ(bspline.ncoeffs(1), 3); + EXPECT_EQ(bspline.ncumcoeffs(), 9); +} + +TEST_F(BSplineTest, NonUniformBSpline_parDim2_geoDim3_degrees12) { + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 1.0_r, 1.0_r, 1.0_r}}}); + EXPECT_EQ(bspline.parDim(), 2); + EXPECT_EQ(bspline.geoDim(), 3); + EXPECT_EQ(bspline.degree(0), 1); + EXPECT_EQ(bspline.degree(1), 2); + EXPECT_EQ(bspline.nknots(0), 5); + EXPECT_EQ(bspline.nknots(1), 6); + EXPECT_EQ(bspline.ncoeffs(0), 3); + EXPECT_EQ(bspline.ncoeffs(1), 3); + EXPECT_EQ(bspline.ncumcoeffs(), 9); +} + +TEST_F(BSplineTest, NonUniformBSpline_parDim2_geoDim4_degrees12) { + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 1.0_r, 1.0_r, 1.0_r}}}); + EXPECT_EQ(bspline.parDim(), 2); + EXPECT_EQ(bspline.geoDim(), 4); + EXPECT_EQ(bspline.degree(0), 1); + EXPECT_EQ(bspline.degree(1), 2); + EXPECT_EQ(bspline.nknots(0), 5); + EXPECT_EQ(bspline.nknots(1), 6); + EXPECT_EQ(bspline.ncoeffs(0), 3); + EXPECT_EQ(bspline.ncoeffs(1), 3); + EXPECT_EQ(bspline.ncumcoeffs(), 9); +} + +TEST_F(BSplineTest, NonUniformBSpline_parDim3_geoDim1_degrees123) { + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}); + EXPECT_EQ(bspline.parDim(), 3); + EXPECT_EQ(bspline.geoDim(), 1); + EXPECT_EQ(bspline.degree(0), 1); + EXPECT_EQ(bspline.degree(1), 2); + EXPECT_EQ(bspline.degree(2), 3); + EXPECT_EQ(bspline.nknots(0), 5); + EXPECT_EQ(bspline.nknots(1), 6); + EXPECT_EQ(bspline.nknots(2), 9); + EXPECT_EQ(bspline.ncoeffs(0), 3); + EXPECT_EQ(bspline.ncoeffs(1), 3); + EXPECT_EQ(bspline.ncoeffs(2), 5); + EXPECT_EQ(bspline.ncumcoeffs(), 45); +} + +TEST_F(BSplineTest, NonUniformBSpline_parDim3_geoDim2_degrees123) { + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}); + EXPECT_EQ(bspline.parDim(), 3); + EXPECT_EQ(bspline.geoDim(), 2); + EXPECT_EQ(bspline.degree(0), 1); + EXPECT_EQ(bspline.degree(1), 2); + EXPECT_EQ(bspline.degree(2), 3); + EXPECT_EQ(bspline.nknots(0), 5); + EXPECT_EQ(bspline.nknots(1), 6); + EXPECT_EQ(bspline.nknots(2), 9); + EXPECT_EQ(bspline.ncoeffs(0), 3); + EXPECT_EQ(bspline.ncoeffs(1), 3); + EXPECT_EQ(bspline.ncoeffs(2), 5); + EXPECT_EQ(bspline.ncumcoeffs(), 45); +} + +TEST_F(BSplineTest, NonUniformBSpline_parDim3_geoDim3_degrees123) { + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}); + EXPECT_EQ(bspline.parDim(), 3); + EXPECT_EQ(bspline.geoDim(), 3); + EXPECT_EQ(bspline.degree(0), 1); + EXPECT_EQ(bspline.degree(1), 2); + EXPECT_EQ(bspline.degree(2), 3); + EXPECT_EQ(bspline.nknots(0), 5); + EXPECT_EQ(bspline.nknots(1), 6); + EXPECT_EQ(bspline.nknots(2), 9); + EXPECT_EQ(bspline.ncoeffs(0), 3); + EXPECT_EQ(bspline.ncoeffs(1), 3); + EXPECT_EQ(bspline.ncoeffs(2), 5); + EXPECT_EQ(bspline.ncumcoeffs(), 45); +} + +TEST_F(BSplineTest, NonUniformBSpline_parDim3_geoDim4_degrees123) { + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}); + EXPECT_EQ(bspline.parDim(), 3); + EXPECT_EQ(bspline.geoDim(), 4); + EXPECT_EQ(bspline.degree(0), 1); + EXPECT_EQ(bspline.degree(1), 2); + EXPECT_EQ(bspline.degree(2), 3); + EXPECT_EQ(bspline.nknots(0), 5); + EXPECT_EQ(bspline.nknots(1), 6); + EXPECT_EQ(bspline.nknots(2), 9); + EXPECT_EQ(bspline.ncoeffs(0), 3); + EXPECT_EQ(bspline.ncoeffs(1), 3); + EXPECT_EQ(bspline.ncoeffs(2), 5); + EXPECT_EQ(bspline.ncumcoeffs(), 45); +} + +TEST_F(BSplineTest, NonUniformBSpline_parDim4_geoDim1_degrees1234) { + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}}}); + EXPECT_EQ(bspline.parDim(), 4); + EXPECT_EQ(bspline.geoDim(), 1); + EXPECT_EQ(bspline.degree(0), 1); + EXPECT_EQ(bspline.degree(1), 2); + EXPECT_EQ(bspline.degree(2), 3); + EXPECT_EQ(bspline.degree(3), 4); + EXPECT_EQ(bspline.nknots(0), 5); + EXPECT_EQ(bspline.nknots(1), 6); + EXPECT_EQ(bspline.nknots(2), 9); + EXPECT_EQ(bspline.nknots(3), 11); + EXPECT_EQ(bspline.ncoeffs(0), 3); + EXPECT_EQ(bspline.ncoeffs(1), 3); + EXPECT_EQ(bspline.ncoeffs(2), 5); + EXPECT_EQ(bspline.ncoeffs(3), 6); + EXPECT_EQ(bspline.ncumcoeffs(), 270); +} + +TEST_F(BSplineTest, NonUniformBSpline_parDim4_geoDim2_degrees1234) { + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}}}); + EXPECT_EQ(bspline.parDim(), 4); + EXPECT_EQ(bspline.geoDim(), 2); + EXPECT_EQ(bspline.degree(0), 1); + EXPECT_EQ(bspline.degree(1), 2); + EXPECT_EQ(bspline.degree(2), 3); + EXPECT_EQ(bspline.degree(3), 4); + EXPECT_EQ(bspline.nknots(0), 5); + EXPECT_EQ(bspline.nknots(1), 6); + EXPECT_EQ(bspline.nknots(2), 9); + EXPECT_EQ(bspline.nknots(3), 11); + EXPECT_EQ(bspline.ncoeffs(0), 3); + EXPECT_EQ(bspline.ncoeffs(1), 3); + EXPECT_EQ(bspline.ncoeffs(2), 5); + EXPECT_EQ(bspline.ncoeffs(3), 6); + EXPECT_EQ(bspline.ncumcoeffs(), 270); +} + +TEST_F(BSplineTest, NonUniformBSpline_parDim4_geoDim3_degrees1234) { + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}}}); + EXPECT_EQ(bspline.parDim(), 4); + EXPECT_EQ(bspline.geoDim(), 3); + EXPECT_EQ(bspline.degree(0), 1); + EXPECT_EQ(bspline.degree(1), 2); + EXPECT_EQ(bspline.degree(2), 3); + EXPECT_EQ(bspline.degree(3), 4); + EXPECT_EQ(bspline.nknots(0), 5); + EXPECT_EQ(bspline.nknots(1), 6); + EXPECT_EQ(bspline.nknots(2), 9); + EXPECT_EQ(bspline.nknots(3), 11); + EXPECT_EQ(bspline.ncoeffs(0), 3); + EXPECT_EQ(bspline.ncoeffs(1), 3); + EXPECT_EQ(bspline.ncoeffs(2), 5); + EXPECT_EQ(bspline.ncoeffs(3), 6); + EXPECT_EQ(bspline.ncumcoeffs(), 270); +} + +TEST_F(BSplineTest, NonUniformBSpline_parDim4_geoDim4_degrees1234) { + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}}}); + EXPECT_EQ(bspline.parDim(), 4); + EXPECT_EQ(bspline.geoDim(), 4); + EXPECT_EQ(bspline.degree(0), 1); + EXPECT_EQ(bspline.degree(1), 2); + EXPECT_EQ(bspline.degree(2), 3); + EXPECT_EQ(bspline.degree(3), 4); + EXPECT_EQ(bspline.nknots(0), 5); + EXPECT_EQ(bspline.nknots(1), 6); + EXPECT_EQ(bspline.nknots(2), 9); + EXPECT_EQ(bspline.nknots(3), 11); + EXPECT_EQ(bspline.ncoeffs(0), 3); + EXPECT_EQ(bspline.ncoeffs(1), 3); + EXPECT_EQ(bspline.ncoeffs(2), 5); + EXPECT_EQ(bspline.ncoeffs(3), 6); + EXPECT_EQ(bspline.ncumcoeffs(), 270); +} + +TEST_F(BSplineTest, NonUniformBSpline_init) { + { + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.25_r, 0.5_r, 0.75_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + EXPECT_TRUE(torch::equal(bspline.coeffs(0), torch::zeros(5, options))); + } + + { + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.25_r, 0.5_r, 0.75_r, 1.0_r, 1.0_r}}}, + iganet::init::ones, options); + EXPECT_TRUE(torch::equal(bspline.coeffs(0), torch::ones(5, options))); + } + + { + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.25_r, 0.5_r, 0.75_r, 1.0_r, 1.0_r}}}, + iganet::init::linear, options); + EXPECT_TRUE( + torch::equal(bspline.coeffs(0), torch::linspace(0, 1, 5, options))); + } + + { + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.25_r, 0.5_r, 0.75_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + EXPECT_TRUE( + torch::equal(bspline.coeffs(0), torch::linspace(0, 1, 5, options))); + } + + { + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.25_r, 0.5_r, 0.75_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + EXPECT_TRUE(torch::equal(bspline.coeffs(0), torch::zeros(5, options))); + EXPECT_TRUE(torch::equal(bspline.coeffs(1), torch::zeros(5, options))); + } + + { + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.25_r, 0.5_r, 0.75_r, 1.0_r, 1.0_r}}}, + iganet::init::ones, options); + EXPECT_TRUE(torch::equal(bspline.coeffs(0), torch::ones(5, options))); + EXPECT_TRUE(torch::equal(bspline.coeffs(1), torch::ones(5, options))); + } + + { + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.25_r, 0.5_r, 0.75_r, 1.0_r, 1.0_r}}}, + iganet::init::linear, options); + EXPECT_TRUE( + torch::equal(bspline.coeffs(0), torch::linspace(0, 1, 5, options))); + EXPECT_TRUE(torch::equal(bspline.coeffs(1), torch::ones(5, options))); + } + + { + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.25_r, 0.5_r, 0.75_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + EXPECT_TRUE( + torch::equal(bspline.coeffs(0), torch::linspace(0, 1, 5, options))); + EXPECT_TRUE(torch::equal(bspline.coeffs(1), torch::ones(5, options))); + } + + { + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.2_r, 0.4_r, 0.6_r, 0.8_r, 1.0_r, 1.0_r, + 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + EXPECT_TRUE(torch::equal(bspline.coeffs(0), torch::zeros(28, options))); + EXPECT_TRUE(torch::equal(bspline.coeffs(1), torch::zeros(28, options))); + } + + { + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.2_r, 0.4_r, 0.6_r, 0.8_r, 1.0_r, 1.0_r, + 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::ones, options); + EXPECT_TRUE(torch::equal(bspline.coeffs(0), torch::ones(28, options))); + EXPECT_TRUE(torch::equal(bspline.coeffs(1), torch::ones(28, options))); + } + + { + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.2_r, 0.4_r, 0.6_r, 0.8_r, 1.0_r, 1.0_r, + 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::linear, options); + EXPECT_TRUE(torch::equal(bspline.coeffs(0), + torch::linspace(0, 1, 7, options).repeat(4))); + EXPECT_TRUE( + torch::equal(bspline.coeffs(1), + torch::linspace(0, 1, 4, options).repeat_interleave(7))); + } + + { + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.25_r, 0.5_r, 0.75_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.2_r, 0.4_r, 0.6_r, 0.8_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + EXPECT_TRUE(torch::equal(bspline.coeffs(0), + torch::linspace(0, 1, 5, options).repeat(6))); + EXPECT_TRUE( + torch::equal(bspline.coeffs(1), + torch::linspace(0, 1, 6, options).repeat_interleave(5))); + } + + { + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.2_r, 0.4_r, 0.6_r, 0.8_r, 1.0_r, 1.0_r, + 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + EXPECT_TRUE(torch::equal(bspline.coeffs(0), torch::zeros(28, options))); + EXPECT_TRUE(torch::equal(bspline.coeffs(1), torch::zeros(28, options))); + EXPECT_TRUE(torch::equal(bspline.coeffs(2), torch::zeros(28, options))); + } + + { + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.2_r, 0.4_r, 0.6_r, 0.8_r, 1.0_r, 1.0_r, + 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::ones, options); + EXPECT_TRUE(torch::equal(bspline.coeffs(0), torch::ones(28, options))); + EXPECT_TRUE(torch::equal(bspline.coeffs(1), torch::ones(28, options))); + EXPECT_TRUE(torch::equal(bspline.coeffs(2), torch::ones(28, options))); + } + + { + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.2_r, 0.4_r, 0.6_r, 0.8_r, 1.0_r, 1.0_r, + 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::linear, options); + EXPECT_TRUE(torch::equal(bspline.coeffs(0), + torch::linspace(0, 1, 7, options).repeat(4))); + EXPECT_TRUE( + torch::equal(bspline.coeffs(1), + torch::linspace(0, 1, 4, options).repeat_interleave(7))); + EXPECT_TRUE(torch::equal(bspline.coeffs(2), torch::ones(28, options))); + } + + { + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.25_r, 0.5_r, 0.75_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.2_r, 0.4_r, 0.6_r, 0.8_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + EXPECT_TRUE(torch::equal(bspline.coeffs(0), + torch::linspace(0, 1, 5, options).repeat(6))); + EXPECT_TRUE( + torch::equal(bspline.coeffs(1), + torch::linspace(0, 1, 6, options).repeat_interleave(5))); + EXPECT_TRUE(torch::equal(bspline.coeffs(2), torch::ones(30, options))); + } + + { + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.2_r, 0.4_r, 0.6_r, 0.8_r, 1.0_r, 1.0_r, + 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + EXPECT_TRUE(torch::equal(bspline.coeffs(0), torch::zeros(28, options))); + EXPECT_TRUE(torch::equal(bspline.coeffs(1), torch::zeros(28, options))); + EXPECT_TRUE(torch::equal(bspline.coeffs(2), torch::zeros(28, options))); + EXPECT_TRUE(torch::equal(bspline.coeffs(3), torch::zeros(28, options))); + } + + { + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.2_r, 0.4_r, 0.6_r, 0.8_r, 1.0_r, 1.0_r, + 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::ones, options); + EXPECT_TRUE(torch::equal(bspline.coeffs(0), torch::ones(28, options))); + EXPECT_TRUE(torch::equal(bspline.coeffs(1), torch::ones(28, options))); + EXPECT_TRUE(torch::equal(bspline.coeffs(2), torch::ones(28, options))); + EXPECT_TRUE(torch::equal(bspline.coeffs(3), torch::ones(28, options))); + } + + { + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.2_r, 0.4_r, 0.6_r, 0.8_r, 1.0_r, 1.0_r, + 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::linear, options); + EXPECT_TRUE(torch::equal(bspline.coeffs(0), + torch::linspace(0, 1, 7, options).repeat(4))); + EXPECT_TRUE( + torch::equal(bspline.coeffs(1), + torch::linspace(0, 1, 4, options).repeat_interleave(7))); + EXPECT_TRUE(torch::equal(bspline.coeffs(2), torch::ones(28, options))); + EXPECT_TRUE(torch::equal(bspline.coeffs(3), torch::ones(28, options))); + } + + { + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.25_r, 0.5_r, 0.75_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.2_r, 0.4_r, 0.6_r, 0.8_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + EXPECT_TRUE(torch::equal(bspline.coeffs(0), + torch::linspace(0, 1, 5, options).repeat(6))); + EXPECT_TRUE( + torch::equal(bspline.coeffs(1), + torch::linspace(0, 1, 6, options).repeat_interleave(5))); + EXPECT_TRUE(torch::equal(bspline.coeffs(2), torch::ones(30, options))); + EXPECT_TRUE(torch::equal(bspline.coeffs(3), torch::ones(30, options))); + } +} + +TEST_F(BSplineTest, NonUniformBSpline_uniform_refine) { + { + iganet::NonUniformBSpline bspline({4, 5}); + iganet::NonUniformBSpline bspline_ref({5, 6}); + bspline.uniform_refine(); + + EXPECT_TRUE(bspline.isclose(bspline_ref)); + } + + { + iganet::NonUniformBSpline bspline({4, 5}); + iganet::NonUniformBSpline bspline_ref({7, 8}); + bspline.uniform_refine(2); + + EXPECT_TRUE(bspline.isclose(bspline_ref)); + } + + { + iganet::NonUniformBSpline bspline({4, 5}); + iganet::NonUniformBSpline bspline_ref({5, 5}); + bspline.uniform_refine(1, 0); + + EXPECT_TRUE(bspline.isclose(bspline_ref)); + } + + { + iganet::NonUniformBSpline bspline({4, 5}); + iganet::NonUniformBSpline bspline_ref({5, 8}); + bspline.uniform_refine(1, 0).uniform_refine(2, 1); + + EXPECT_TRUE(bspline.isclose(bspline_ref)); + } +} + +TEST_F(BSplineTest, NonUniformBSpline_copy_constructor) { + iganet::NonUniformBSpline bspline_orig( + {4, 5}, iganet::init::greville, options); + iganet::NonUniformBSpline bspline_copy(bspline_orig); + + bspline_orig.transform([](const std::array xi) { + return std::array{0.0_r, 1.0_r, 2.0_r}; + }); + + EXPECT_TRUE(bspline_orig == bspline_copy); +} + +TEST_F(BSplineTest, NonUniformBSpline_clone_constructor) { + iganet::NonUniformBSpline bspline_ref( + {4, 5}, iganet::init::greville, options); + iganet::NonUniformBSpline bspline_orig( + {4, 5}, iganet::init::greville, options); + iganet::NonUniformBSpline bspline_clone(bspline_orig, true); + + bspline_orig.transform([](const std::array xi) { + return std::array{0.0_r, 1.0_r, 2.0_r}; + }); + + EXPECT_TRUE(bspline_ref == bspline_clone); +} + +TEST_F(BSplineTest, NonUniformBSpline_move_constructor) { + iganet::NonUniformBSpline bspline_ref( + {7, 8}, iganet::init::greville, options); + auto bspline(iganet::NonUniformBSpline( + {4, 5}, iganet::init::greville, options) + .uniform_refine(2)); + + EXPECT_TRUE(bspline.isclose(bspline_ref)); +} + +TEST_F(BSplineTest, NonUniformBSpline_copy_coeffs_constructor) { + iganet::NonUniformBSpline bspline_orig( + {4, 5}, iganet::init::greville, options); + iganet::NonUniformBSpline bspline_copy( + bspline_orig, bspline_orig.coeffs()); + + bspline_orig.transform([](const std::array xi) { + return std::array{0.0_r, 1.0_r, 2.0_r}; + }); + + EXPECT_TRUE(bspline_orig == bspline_copy); +} + +TEST_F(BSplineTest, NonUniformBSpline_clone_coeffs_constructor) { + iganet::NonUniformBSpline bspline_ref( + {4, 5}, iganet::init::greville, options); + iganet::NonUniformBSpline bspline_orig( + {4, 5}, iganet::init::greville, options); + iganet::NonUniformBSpline bspline_clone( + bspline_orig, bspline_orig.coeffs(), true); + + bspline_orig.transform([](const std::array xi) { + return std::array{0.0_r, 1.0_r, 2.0_r}; + }); + + EXPECT_TRUE(bspline_ref == bspline_clone); +} + +TEST_F(BSplineTest, NonUniformBSpline_read_write) { + std::filesystem::path filename = + std::filesystem::temp_directory_path() / std::to_string(rand()); + iganet::NonUniformBSpline bspline_out( + {{{0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + bspline_out.save(filename.c_str()); + iganet::NonUniformBSpline bspline_in(options); + bspline_in.load(filename.c_str()); + std::filesystem::remove(filename); + + EXPECT_TRUE(bspline_in == bspline_out); + EXPECT_FALSE(bspline_in != bspline_out); +} + +TEST_F(BSplineTest, NonUniformBSpline_to_from_xml) { + { + iganet::NonUniformBSpline bspline_out( + {4}, iganet::init::zeros, options); + + bspline_out.transform([](const std::array xi) { + return std::array{static_cast(std::rand())}; + }); + + pugi::xml_document doc = bspline_out.to_xml(); + + iganet::NonUniformBSpline bspline_in(options); + bspline_in.from_xml(doc); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW((iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW((iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW((iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW((iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching id + EXPECT_THROW((iganet::NonUniformBSpline{}.from_xml(doc, 1)), + std::runtime_error); + } + + { + iganet::NonUniformBSpline bspline_out( + {4}, iganet::init::zeros, options); + + bspline_out.transform([](const std::array xi) { + return std::array{static_cast(std::rand()), + static_cast(std::rand())}; + }); + + pugi::xml_document doc = bspline_out.to_xml(); + + iganet::NonUniformBSpline bspline_in(options); + bspline_in.from_xml(doc); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW((iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW((iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW((iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW((iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching id + EXPECT_THROW((iganet::NonUniformBSpline{}.from_xml(doc, 1)), + std::runtime_error); + } + + { + iganet::NonUniformBSpline bspline_out( + {4}, iganet::init::zeros, options); + + bspline_out.transform([](const std::array xi) { + return std::array{static_cast(std::rand()), + static_cast(std::rand()), + static_cast(std::rand())}; + }); + + pugi::xml_document doc = bspline_out.to_xml(); + + iganet::NonUniformBSpline bspline_in(options); + bspline_in.from_xml(doc); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW((iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW((iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW((iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW((iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching id + EXPECT_THROW((iganet::NonUniformBSpline{}.from_xml(doc, 1)), + std::runtime_error); + } + + { + iganet::NonUniformBSpline bspline_out( + {4}, iganet::init::zeros, options); + + bspline_out.transform([](const std::array xi) { + return std::array{ + static_cast(std::rand()), static_cast(std::rand()), + static_cast(std::rand()), static_cast(std::rand())}; + }); + + pugi::xml_document doc = bspline_out.to_xml(); + + iganet::NonUniformBSpline bspline_in(options); + bspline_in.from_xml(doc); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW((iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW((iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW((iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW((iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching id + EXPECT_THROW((iganet::NonUniformBSpline{}.from_xml(doc, 1)), + std::runtime_error); + } + + { + iganet::NonUniformBSpline bspline_out( + {4, 5}, iganet::init::zeros, options); + + bspline_out.transform([](const std::array xi) { + return std::array{static_cast(std::rand())}; + }); + + pugi::xml_document doc = bspline_out.to_xml(); + + iganet::NonUniformBSpline bspline_in(options); + bspline_in.from_xml(doc); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW((iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching id + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 1)), + std::runtime_error); + } + + { + iganet::NonUniformBSpline bspline_out( + {4, 5}, iganet::init::zeros, options); + + bspline_out.transform([](const std::array xi) { + return std::array{static_cast(std::rand()), + static_cast(std::rand())}; + }); + + pugi::xml_document doc = bspline_out.to_xml(); + + iganet::NonUniformBSpline bspline_in(options); + bspline_in.from_xml(doc); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW((iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching id + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 1)), + std::runtime_error); + } + + { + iganet::NonUniformBSpline bspline_out( + {4, 5}, iganet::init::zeros, options); + + bspline_out.transform([](const std::array xi) { + return std::array{static_cast(std::rand()), + static_cast(std::rand()), + static_cast(std::rand())}; + }); + + pugi::xml_document doc = bspline_out.to_xml(); + + iganet::NonUniformBSpline bspline_in(options); + bspline_in.from_xml(doc); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW((iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching id + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 1)), + std::runtime_error); + } + + { + iganet::NonUniformBSpline bspline_out( + {4, 5}, iganet::init::zeros, options); + + bspline_out.transform([](const std::array xi) { + return std::array{ + static_cast(std::rand()), static_cast(std::rand()), + static_cast(std::rand()), static_cast(std::rand())}; + }); + + pugi::xml_document doc = bspline_out.to_xml(); + + iganet::NonUniformBSpline bspline_in(options); + bspline_in.from_xml(doc); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW((iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching id + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 1)), + std::runtime_error); + } + + { + iganet::NonUniformBSpline bspline_out( + {4, 5, 6}, iganet::init::zeros, options); + + bspline_out.transform([](const std::array xi) { + return std::array{static_cast(std::rand())}; + }); + + pugi::xml_document doc = bspline_out.to_xml(); + + iganet::NonUniformBSpline bspline_in(options); + bspline_in.from_xml(doc); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW((iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching id + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 1)), + std::runtime_error); + } + + { + iganet::NonUniformBSpline bspline_out( + {4, 5, 6}, iganet::init::zeros, options); + + bspline_out.transform([](const std::array xi) { + return std::array{static_cast(std::rand()), + static_cast(std::rand())}; + }); + + pugi::xml_document doc = bspline_out.to_xml(); + + iganet::NonUniformBSpline bspline_in(options); + bspline_in.from_xml(doc); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW((iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching id + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 1)), + std::runtime_error); + } + + { + iganet::NonUniformBSpline bspline_out( + {4, 5, 6}, iganet::init::zeros, options); + + bspline_out.transform([](const std::array xi) { + return std::array{static_cast(std::rand()), + static_cast(std::rand()), + static_cast(std::rand())}; + }); + + pugi::xml_document doc = bspline_out.to_xml(); + + iganet::NonUniformBSpline bspline_in(options); + bspline_in.from_xml(doc); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW((iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching id + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 1)), + std::runtime_error); + } + + { + iganet::NonUniformBSpline bspline_out( + {4, 5, 6}, iganet::init::zeros, options); + + bspline_out.transform([](const std::array xi) { + return std::array{ + static_cast(std::rand()), static_cast(std::rand()), + static_cast(std::rand()), static_cast(std::rand())}; + }); + + pugi::xml_document doc = bspline_out.to_xml(); + + iganet::NonUniformBSpline bspline_in(options); + bspline_in.from_xml(doc); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW((iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching id + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 1)), + std::runtime_error); + } + + { + iganet::NonUniformBSpline bspline_out( + {4, 5, 6, 2}, iganet::init::zeros, options); + + bspline_out.transform([](const std::array xi) { + return std::array{static_cast(std::rand())}; + }); + + pugi::xml_document doc = bspline_out.to_xml(); + + iganet::NonUniformBSpline bspline_in(options); + bspline_in.from_xml(doc); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW((iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching id + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 1)), + std::runtime_error); + } + + { + iganet::NonUniformBSpline bspline_out( + {4, 5, 6, 2}, iganet::init::zeros, options); + + bspline_out.transform([](const std::array xi) { + return std::array{static_cast(std::rand()), + static_cast(std::rand())}; + }); + + pugi::xml_document doc = bspline_out.to_xml(); + + iganet::NonUniformBSpline bspline_in(options); + bspline_in.from_xml(doc); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW((iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching id + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 1)), + std::runtime_error); + } + + { + iganet::NonUniformBSpline bspline_out( + {4, 5, 6, 2}, iganet::init::zeros, options); + + bspline_out.transform([](const std::array xi) { + return std::array{static_cast(std::rand()), + static_cast(std::rand()), + static_cast(std::rand())}; + }); + + pugi::xml_document doc = bspline_out.to_xml(); + + iganet::NonUniformBSpline bspline_in(options); + bspline_in.from_xml(doc); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW((iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching id + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 1)), + std::runtime_error); + } + + { + iganet::NonUniformBSpline bspline_out( + {4, 5, 6, 2}, iganet::init::zeros, options); + + bspline_out.transform([](const std::array xi) { + return std::array{ + static_cast(std::rand()), static_cast(std::rand()), + static_cast(std::rand()), static_cast(std::rand())}; + }); + + pugi::xml_document doc = bspline_out.to_xml(); + + iganet::NonUniformBSpline bspline_in(options); + bspline_in.from_xml(doc); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW((iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching id + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_xml(doc, 1)), + std::runtime_error); + } +} + +TEST_F(BSplineTest, NonUniformBSpline_load_from_xml) { + { + pugi::xml_document doc; + pugi::xml_parse_result result = + doc.load_file(IGANET_DATA_DIR "domain1d/line.xml"); + + iganet::NonUniformBSpline bspline_in(options); + bspline_in.from_xml(doc); + + iganet::NonUniformBSpline bspline_ref( + {3}, iganet::init::zeros, options); + + bspline_ref.transform([](const std::array xi) { + return std::array{xi[0], 0.0_r, 0.0_r}; + }); + + EXPECT_TRUE(bspline_in == bspline_ref); + } + + { + pugi::xml_document doc; + pugi::xml_parse_result result = + doc.load_file(IGANET_DATA_DIR "domain2d/square.xml"); + + iganet::NonUniformBSpline bspline_in(options); + bspline_in.from_xml(doc, 1); + + iganet::NonUniformBSpline bspline_ref( + {2, 2}, iganet::init::greville, options); + + EXPECT_TRUE(bspline_in == bspline_ref); + } + + { + pugi::xml_document doc; + pugi::xml_parse_result result = + doc.load_file(IGANET_DATA_DIR "domain3d/GshapedVolume.xml"); + + iganet::NonUniformBSpline bspline_in(options); + bspline_in.from_xml(doc); + } + + { + pugi::xml_document doc; + pugi::xml_parse_result result = + doc.load_file(IGANET_DATA_DIR "surfaces/g_plus_s_surf.xml"); + + iganet::NonUniformBSpline bspline_in0(options); + iganet::NonUniformBSpline bspline_in1(options); + + for (int i = 0; i < 126; ++i) { + try { + bspline_in0.from_xml(doc, i); + } catch (...) { + bspline_in1.from_xml(doc, i); + } + } + } +} + +TEST_F(BSplineTest, NonUniformBSpline_to_from_json) { + { + iganet::NonUniformBSpline bspline_out( + {4}, iganet::init::zeros, options); + + bspline_out.transform([](const std::array xi) { + return std::array{static_cast(std::rand())}; + }); + + nlohmann::json json = bspline_out.to_json(); + + iganet::NonUniformBSpline bspline_in(options); + bspline_in.from_json(json); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + } + + { + iganet::NonUniformBSpline bspline_out( + {4}, iganet::init::zeros, options); + + bspline_out.transform([](const std::array xi) { + return std::array{static_cast(std::rand()), + static_cast(std::rand())}; + }); + + nlohmann::json json = bspline_out.to_json(); + + iganet::NonUniformBSpline bspline_in(options); + bspline_in.from_json(json); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + } + + { + iganet::NonUniformBSpline bspline_out( + {4}, iganet::init::zeros, options); + + bspline_out.transform([](const std::array xi) { + return std::array{static_cast(std::rand()), + static_cast(std::rand()), + static_cast(std::rand())}; + }); + + nlohmann::json json = bspline_out.to_json(); + + iganet::NonUniformBSpline bspline_in(options); + bspline_in.from_json(json); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + } + + { + iganet::NonUniformBSpline bspline_out( + {4}, iganet::init::zeros, options); + + bspline_out.transform([](const std::array xi) { + return std::array{ + static_cast(std::rand()), static_cast(std::rand()), + static_cast(std::rand()), static_cast(std::rand())}; + }); + + nlohmann::json json = bspline_out.to_json(); + + iganet::NonUniformBSpline bspline_in(options); + bspline_in.from_json(json); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + } + + { + iganet::NonUniformBSpline bspline_out( + {4, 5}, iganet::init::zeros, options); + + bspline_out.transform([](const std::array xi) { + return std::array{static_cast(std::rand())}; + }); + + nlohmann::json json = bspline_out.to_json(); + + iganet::NonUniformBSpline bspline_in(options); + bspline_in.from_json(json); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + } + + { + iganet::NonUniformBSpline bspline_out( + {4, 5}, iganet::init::zeros, options); + + bspline_out.transform([](const std::array xi) { + return std::array{static_cast(std::rand()), + static_cast(std::rand())}; + }); + + nlohmann::json json = bspline_out.to_json(); + + iganet::NonUniformBSpline bspline_in(options); + bspline_in.from_json(json); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + } + + { + iganet::NonUniformBSpline bspline_out( + {4, 5}, iganet::init::zeros, options); + + bspline_out.transform([](const std::array xi) { + return std::array{static_cast(std::rand()), + static_cast(std::rand()), + static_cast(std::rand())}; + }); + + nlohmann::json json = bspline_out.to_json(); + + iganet::NonUniformBSpline bspline_in(options); + bspline_in.from_json(json); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + } + + { + iganet::NonUniformBSpline bspline_out( + {4, 5}, iganet::init::zeros, options); + + bspline_out.transform([](const std::array xi) { + return std::array{ + static_cast(std::rand()), static_cast(std::rand()), + static_cast(std::rand()), static_cast(std::rand())}; + }); + + nlohmann::json json = bspline_out.to_json(); + + iganet::NonUniformBSpline bspline_in(options); + bspline_in.from_json(json); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + } + + { + iganet::NonUniformBSpline bspline_out( + {4, 5, 6}, iganet::init::zeros, options); + + bspline_out.transform([](const std::array xi) { + return std::array{static_cast(std::rand())}; + }); + + nlohmann::json json = bspline_out.to_json(); + + iganet::NonUniformBSpline bspline_in(options); + bspline_in.from_json(json); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + } + + { + iganet::NonUniformBSpline bspline_out( + {4, 5, 6}, iganet::init::zeros, options); + + bspline_out.transform([](const std::array xi) { + return std::array{static_cast(std::rand()), + static_cast(std::rand())}; + }); + + nlohmann::json json = bspline_out.to_json(); + + iganet::NonUniformBSpline bspline_in(options); + bspline_in.from_json(json); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + } + + { + iganet::NonUniformBSpline bspline_out( + {4, 5, 6}, iganet::init::zeros, options); + + bspline_out.transform([](const std::array xi) { + return std::array{static_cast(std::rand()), + static_cast(std::rand()), + static_cast(std::rand())}; + }); + + nlohmann::json json = bspline_out.to_json(); + + iganet::NonUniformBSpline bspline_in(options); + bspline_in.from_json(json); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + } + + { + iganet::NonUniformBSpline bspline_out( + {4, 5, 6}, iganet::init::zeros, options); + + bspline_out.transform([](const std::array xi) { + return std::array{ + static_cast(std::rand()), static_cast(std::rand()), + static_cast(std::rand()), static_cast(std::rand())}; + }); + + nlohmann::json json = bspline_out.to_json(); + + iganet::NonUniformBSpline bspline_in(options); + bspline_in.from_json(json); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + } + + { + iganet::NonUniformBSpline bspline_out( + {4, 5, 6, 2}, iganet::init::zeros, options); + + bspline_out.transform([](const std::array xi) { + return std::array{static_cast(std::rand())}; + }); + + nlohmann::json json = bspline_out.to_json(); + + iganet::NonUniformBSpline bspline_in(options); + bspline_in.from_json(json); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + } + + { + iganet::NonUniformBSpline bspline_out( + {4, 5, 6, 2}, iganet::init::zeros, options); + + bspline_out.transform([](const std::array xi) { + return std::array{static_cast(std::rand()), + static_cast(std::rand())}; + }); + + nlohmann::json json = bspline_out.to_json(); + + iganet::NonUniformBSpline bspline_in(options); + bspline_in.from_json(json); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + } + + { + iganet::NonUniformBSpline bspline_out( + {4, 5, 6, 2}, iganet::init::zeros, options); + + bspline_out.transform([](const std::array xi) { + return std::array{static_cast(std::rand()), + static_cast(std::rand()), + static_cast(std::rand())}; + }); + + nlohmann::json json = bspline_out.to_json(); + + iganet::NonUniformBSpline bspline_in(options); + bspline_in.from_json(json); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + } + + { + iganet::NonUniformBSpline bspline_out( + {4, 5, 6, 2}, iganet::init::zeros, options); + + bspline_out.transform([](const std::array xi) { + return std::array{ + static_cast(std::rand()), static_cast(std::rand()), + static_cast(std::rand()), static_cast(std::rand())}; + }); + + nlohmann::json json = bspline_out.to_json(); + + iganet::NonUniformBSpline bspline_in(options); + bspline_in.from_json(json); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW((iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::NonUniformBSpline{}.from_json(json)), + std::runtime_error); + } +} + +TEST_F(BSplineTest, NonUniformBSpline_query_property) { + iganet::NonUniformBSpline bspline( + {4, 5}, iganet::init::greville, options); + + EXPECT_FALSE(bspline.is_uniform()); + EXPECT_TRUE(bspline.is_nonuniform()); + + EXPECT_EQ(bspline.device(), options.device()); + EXPECT_EQ(bspline.device_index(), options.device_index()); + EXPECT_EQ(bspline.dtype(), options.dtype()); + EXPECT_EQ(bspline.is_sparse(), options.is_sparse()); + EXPECT_EQ(bspline.layout(), options.layout()); + EXPECT_EQ(bspline.pinned_memory(), options.pinned_memory()); +} + +TEST_F(BSplineTest, NonUniformBSpline_requires_grad) { + { + iganet::NonUniformBSpline bspline( + {4, 5}, iganet::init::greville, options); + + EXPECT_EQ(bspline.requires_grad(), false); + + for (iganet::short_t i = 0; i < bspline.parDim(); ++i) + EXPECT_EQ(bspline.knots(i).requires_grad(), false); + + for (iganet::short_t i = 0; i < bspline.geoDim(); ++i) + EXPECT_EQ(bspline.coeffs(i).requires_grad(), false); + + auto xi = iganet::utils::to_tensorArray(options, {0.5_r}, {0.5_r}); + auto values = bspline.eval(xi); + + // We expect an error when calling backward() because no tensor + // has requires_grad = true + EXPECT_THROW(values[0]->backward(), c10::Error); + + xi = iganet::utils::to_tensorArray(options.requires_grad(true), {0.5_r}, + {0.5_r}); + values = bspline.eval(xi); + values[0]->backward(); + EXPECT_TRUE(torch::allclose(xi[0].grad(), + iganet::utils::to_tensor({1.0_r}, options))); + } + + { + iganet::NonUniformBSpline bspline( + {4, 5}, iganet::init::linear, options.requires_grad(true)); + + EXPECT_TRUE(bspline.requires_grad()); + + for (iganet::short_t i = 0; i < bspline.parDim(); ++i) + EXPECT_TRUE(bspline.knots(i).requires_grad()); + + for (iganet::short_t i = 0; i < bspline.geoDim(); ++i) + EXPECT_TRUE(bspline.coeffs(i).requires_grad()); + + auto xi = iganet::utils::to_tensorArray(options, {0.5_r}, {0.5_r}); + auto values = bspline.eval(xi); + values[0]->backward( + {}, true); // otherwise we cannot run backward() a second time + + // We expect an error because xi[0].grad() is an undefined tensor + EXPECT_THROW(torch::allclose(xi[0].grad(), torch::empty({})), c10::Error); + + xi = iganet::utils::to_tensorArray(options.requires_grad(true), {0.5_r}, + {0.5_r}); + values = bspline.eval(xi); + values[0]->backward(); + EXPECT_TRUE(torch::allclose(xi[0].grad(), + iganet::utils::to_tensor({1.0_r}, options))); + + EXPECT_TRUE(torch::allclose( + bspline.coeffs(0).grad(), + iganet::utils::to_tensor( + {0.015625_r, 0.046875_r, 0.046875_r, 0.015625_r, 0.0625_r, + 0.1875_r, 0.1875_r, 0.0625_r, 0.09375_r, 0.28125_r, + 0.28125_r, 0.09375_r, 0.0625_r, 0.1875_r, 0.1875_r, + 0.0625_r, 0.015625_r, 0.046875_r, 0.046875_r, 0.015625_r}, + options))); + } +} + +TEST_F(BSplineTest, NonUniformBSpline_to_dtype) { + { + iganet::NonUniformBSpline bspline( + {4, 5}, iganet::init::greville, options); + + auto bspline_double = bspline.to(); + auto bspline_float = bspline.to(); + + if constexpr (std::is_same::value) + EXPECT_TRUE(bspline == bspline_double); + else + EXPECT_TRUE(bspline != bspline_double); + + if constexpr (std::is_same::value) + EXPECT_TRUE(bspline == bspline_float); + else + EXPECT_TRUE(bspline != bspline_float); + } + + { + iganet::NonUniformBSpline bspline( + {4, 5}, iganet::init::greville, options); + + auto bspline_double = bspline.to(iganet::Options{}); + auto bspline_float = bspline.to(iganet::Options{}); + + if constexpr (std::is_same::value) + EXPECT_TRUE(bspline == bspline_double); + else + EXPECT_TRUE(bspline != bspline_double); + + if constexpr (std::is_same::value) + EXPECT_TRUE(bspline == bspline_float); + else + EXPECT_TRUE(bspline != bspline_float); + } +} + +TEST_F(BSplineTest, NonUniformBSpline_to_device) { + { + iganet::Options options = + iganet::Options{}.device(torch::kCPU); + iganet::NonUniformBSpline bspline( + {4, 5}, iganet::init::greville, options); + + auto bspline_cpu = bspline.to(torch::kCPU); + EXPECT_TRUE(bspline == bspline_cpu); + + if (torch::cuda::is_available()) { + auto bspline_cuda = bspline.to(torch::kCUDA); + EXPECT_THROW((void)(bspline == bspline_cuda), c10::Error); + } else + EXPECT_THROW(bspline.to(torch::kCUDA), c10::Error); + + if (at::hasHIP()) { + auto bspline_hip = bspline.to(torch::kHIP); + EXPECT_THROW((void)(bspline == bspline_hip), c10::Error); + } else + EXPECT_THROW(bspline.to(torch::kHIP), c10::Error); + + if (at::hasMPS() && // will become torch::mps::is_available() + (options.dtype() != iganet::dtype())) { + auto bspline_mps = bspline.to(torch::kMPS); + EXPECT_THROW((void)(bspline == bspline_mps), c10::Error); + } else + EXPECT_THROW(bspline.to(torch::kMPS), c10::Error); + } +} + +TEST_F(BSplineTest, NonUniformBSpline_reduce_continuity) { + { + iganet::NonUniformBSpline bspline({5, 6}); + iganet::NonUniformBSpline bspline_ref( + iganet::utils::to_array( + iganet::utils::to_vector(0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 0.5_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r), + iganet::utils::to_vector(0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, + 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r))); + bspline.reduce_continuity(); + + EXPECT_TRUE(bspline.isclose(bspline_ref)); + } + + { + iganet::NonUniformBSpline bspline({5, 6}); + iganet::NonUniformBSpline bspline_ref( + iganet::utils::to_array( + iganet::utils::to_vector(0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 0.5_r, + 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r), + iganet::utils::to_vector(0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, + 0.5_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r))); + bspline.reduce_continuity(2); + + EXPECT_TRUE(bspline.isclose(bspline_ref)); + } + + { + iganet::NonUniformBSpline bspline({5, 6}); + iganet::NonUniformBSpline bspline_ref( + iganet::utils::to_array( + iganet::utils::to_vector(0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 0.5_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r), + iganet::utils::to_vector(0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, + 0.5_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r))); + bspline.reduce_continuity(1, 0).reduce_continuity(2, 1); + + EXPECT_TRUE(bspline.isclose(bspline_ref)); + } +} + +TEST_F(BSplineTest, NonUniformBSpline_insert_knots) { + { + iganet::NonUniformBSpline bspline({5, 6}); + iganet::NonUniformBSpline bspline_ref( + iganet::utils::to_array( + iganet::utils::to_vector(0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.1_r, 0.3_r, + 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r), + iganet::utils::to_vector(0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.2_r, + 0.4_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r))); + bspline.insert_knots( + iganet::utils::to_tensorArray({0.1_r, 0.3_r}, {0.2_r, 0.4_r})); + + EXPECT_TRUE(bspline.isclose(bspline_ref)); + } +} + +int main(int argc, char **argv) { + ::testing::InitGoogleTest(&argc, argv); + iganet::init(); + return RUN_ALL_TESTS(); +} diff --git a/unittests/unittest_nonuniform_bspline_eval.cxx b/unittests/unittest_nonuniform_bspline_eval.cxx index 784aacec..bbd36739 100644 --- a/unittests/unittest_nonuniform_bspline_eval.cxx +++ b/unittests/unittest_nonuniform_bspline_eval.cxx @@ -1,1126 +1,1126 @@ -/** - @file unittests/unittest_nonuniform_bspline_eval.cxx - - @brief B-Spline unittests - - @author Matthias Moller - - @copyright This file is part of the IgANet project - - This Source Code Form is subject to the terms of the Mozilla Public - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at http://mozilla.org/MPL/2.0/. -*/ - -#include -#include -#include - -#include -#include -#include - -using namespace iganet::unittests::literals; - -class BSplineTest : public ::testing::Test { -protected: - using real_t = iganet::unittests::real_t; - iganet::Options options; - - static constexpr auto trafo_parDim1_geoDim1 = - [](const std::array xi) { - return std::array{xi[0] * xi[0]}; - }; - static constexpr auto trafo_parDim1_geoDim2 = - [](const std::array xi) { - return std::array{xi[0] * xi[0], - sin(static_cast(M_PI) * xi[0])}; - }; - static constexpr auto trafo_parDim1_geoDim3 = - [](const std::array xi) { - return std::array{ - xi[0] * xi[0], sin(static_cast(M_PI) * xi[0]), xi[0]}; - }; - static constexpr auto trafo_parDim1_geoDim4 = - [](const std::array xi) { - return std::array{ - xi[0] * xi[0], sin(static_cast(M_PI) * xi[0]), xi[0], - cos(static_cast(M_PI) * xi[0])}; - }; - - static constexpr auto trafo_parDim2_geoDim1 = - [](const std::array xi) { - return std::array{xi[0] * xi[1]}; - }; - static constexpr auto trafo_parDim2_geoDim2 = - [](const std::array xi) { - return std::array{xi[0] * xi[1], - sin(static_cast(M_PI) * xi[0])}; - }; - static constexpr auto trafo_parDim2_geoDim3 = - [](const std::array xi) { - return std::array{ - xi[0] * xi[1], sin(static_cast(M_PI) * xi[0]), xi[1]}; - }; - static constexpr auto trafo_parDim2_geoDim4 = - [](const std::array xi) { - return std::array{ - xi[0] * xi[1], sin(static_cast(M_PI) * xi[0]), xi[1], - cos(static_cast(M_PI) * xi[1])}; - }; - - static constexpr auto trafo_parDim3_geoDim1 = - [](const std::array xi) { - return std::array{xi[0] * xi[1] * xi[2]}; - }; - static constexpr auto trafo_parDim3_geoDim2 = - [](const std::array xi) { - return std::array{xi[0] * xi[1] * xi[2], - sin(static_cast(M_PI) * xi[0])}; - }; - static constexpr auto trafo_parDim3_geoDim3 = - [](const std::array xi) { - return std::array{xi[0] * xi[1] * xi[2], - sin(static_cast(M_PI) * xi[0]), - xi[1] * xi[2]}; - }; - static constexpr auto trafo_parDim3_geoDim4 = - [](const std::array xi) { - return std::array{ - xi[0] * xi[1] * xi[2], sin(static_cast(M_PI) * xi[0]), - xi[1] * xi[2], cos(static_cast(M_PI) * xi[1])}; - }; - - static constexpr auto trafo_parDim4_geoDim1 = - [](const std::array xi) { - return std::array{xi[0] * xi[1] * xi[2] * xi[3]}; - }; - static constexpr auto trafo_parDim4_geoDim2 = - [](const std::array xi) { - return std::array{xi[0] * xi[1] * xi[2] * xi[3], - sin(static_cast(M_PI) * xi[0])}; - }; - static constexpr auto trafo_parDim4_geoDim3 = - [](const std::array xi) { - return std::array{xi[0] * xi[1] * xi[2] * xi[3], - sin(static_cast(M_PI) * xi[0]), - xi[1] * xi[2] * xi[3]}; - }; - static constexpr auto trafo_parDim4_geoDim4 = - [](const std::array xi) { - return std::array{xi[0] * xi[1] * xi[2] * xi[3], - sin(static_cast(M_PI) * xi[0]), - xi[1] * xi[2] * xi[3], - cos(static_cast(M_PI) * xi[1])}; - }; -}; - -TEST_F(BSplineTest, NonUniformBSpline_eval_parDim1_geoDim1_degrees1) { - iganet::NonUniformBSpline geo( - {{{0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r}}}, iganet::init::greville, options); - iganet::NonUniformBSpline bspline( - {{{0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r}}}, iganet::init::zeros, options); - bspline.transform(trafo_parDim1_geoDim1); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-12); -} - -TEST_F(BSplineTest, NonUniformBSpline_eval_parDim1_geoDim1_degrees2) { - iganet::NonUniformBSpline geo( - {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::greville, options); - iganet::NonUniformBSpline bspline( - {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::zeros, options); - bspline.transform(trafo_parDim1_geoDim1); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-12); -} - -TEST_F(BSplineTest, NonUniformBSpline_eval_parDim1_geoDim1_degrees3) { - iganet::NonUniformBSpline geo( - {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::greville, options); - iganet::NonUniformBSpline bspline( - {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::zeros, options); - bspline.transform(trafo_parDim1_geoDim1); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-12); -} - -TEST_F(BSplineTest, NonUniformBSpline_eval_parDim1_geoDim1_degrees4) { - iganet::NonUniformBSpline geo( - {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, - 1.0_r}}}, - iganet::init::greville, options); - iganet::NonUniformBSpline bspline( - {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, - 1.0_r}}}, - iganet::init::zeros, options); - bspline.transform(trafo_parDim1_geoDim1); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-12); -} - -TEST_F(BSplineTest, NonUniformBSpline_eval_parDim1_geoDim1_degrees5) { - iganet::NonUniformBSpline geo( - {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, - 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::greville, options); - iganet::NonUniformBSpline bspline( - {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, - 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::zeros, options); - bspline.transform(trafo_parDim1_geoDim1); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-12); -} - -TEST_F(BSplineTest, NonUniformBSpline_eval_parDim1_geoDim1_degrees6) { - iganet::NonUniformBSpline geo( - {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, - 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::greville, options); - iganet::NonUniformBSpline bspline( - {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, - 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::zeros, options); - bspline.transform(trafo_parDim1_geoDim1); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-12); -} - -TEST_F(BSplineTest, NonUniformBSpline_eval_parDim1_geoDim2_degrees1) { - iganet::NonUniformBSpline geo( - {{{0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r}}}, iganet::init::greville, options); - iganet::NonUniformBSpline bspline( - {{{0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r}}}, iganet::init::zeros, options); - bspline.transform(trafo_parDim1_geoDim2); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-12); -} - -TEST_F(BSplineTest, NonUniformBSpline_eval_parDim1_geoDim2_degrees2) { - iganet::NonUniformBSpline geo( - {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::greville, options); - iganet::NonUniformBSpline bspline( - {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::zeros, options); - bspline.transform(trafo_parDim1_geoDim2); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-12); -} - -TEST_F(BSplineTest, NonUniformBSpline_eval_parDim1_geoDim2_degrees3) { - iganet::NonUniformBSpline geo( - {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::greville, options); - iganet::NonUniformBSpline bspline( - {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::zeros, options); - bspline.transform(trafo_parDim1_geoDim2); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-12); -} - -TEST_F(BSplineTest, NonUniformBSpline_eval_parDim1_geoDim2_degrees4) { - iganet::NonUniformBSpline geo( - {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, - 1.0_r}}}, - iganet::init::greville, options); - iganet::NonUniformBSpline bspline( - {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, - 1.0_r}}}, - iganet::init::zeros, options); - bspline.transform(trafo_parDim1_geoDim2); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-12); -} - -TEST_F(BSplineTest, NonUniformBSpline_eval_parDim1_geoDim2_degrees5) { - iganet::NonUniformBSpline geo( - {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, - 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::greville, options); - iganet::NonUniformBSpline bspline( - {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, - 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::zeros, options); - bspline.transform(trafo_parDim1_geoDim2); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-12); -} - -TEST_F(BSplineTest, NonUniformBSpline_eval_parDim1_geoDim2_degrees6) { - iganet::NonUniformBSpline geo( - {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, - 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::greville, options); - iganet::NonUniformBSpline bspline( - {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, - 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::zeros, options); - bspline.transform(trafo_parDim1_geoDim2); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-11); -} - -TEST_F(BSplineTest, NonUniformBSpline_eval_parDim1_geoDim3_degrees1) { - iganet::NonUniformBSpline geo( - {{{0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r}}}, iganet::init::greville, options); - iganet::NonUniformBSpline bspline( - {{{0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r}}}, iganet::init::zeros, options); - bspline.transform(trafo_parDim1_geoDim3); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-12); -} - -TEST_F(BSplineTest, NonUniformBSpline_eval_parDim1_geoDim3_degrees2) { - iganet::NonUniformBSpline geo( - {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::greville, options); - iganet::NonUniformBSpline bspline( - {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::zeros, options); - bspline.transform(trafo_parDim1_geoDim3); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-12); -} - -TEST_F(BSplineTest, NonUniformBSpline_eval_parDim1_geoDim3_degrees3) { - iganet::NonUniformBSpline geo( - {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::greville, options); - iganet::NonUniformBSpline bspline( - {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::zeros, options); - bspline.transform(trafo_parDim1_geoDim3); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-12); -} - -TEST_F(BSplineTest, NonUniformBSpline_eval_parDim1_geoDim3_degrees4) { - iganet::NonUniformBSpline geo( - {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, - 1.0_r}}}, - iganet::init::greville, options); - iganet::NonUniformBSpline bspline( - {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, - 1.0_r}}}, - iganet::init::zeros, options); - bspline.transform(trafo_parDim1_geoDim3); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-12); -} - -TEST_F(BSplineTest, NonUniformBSpline_eval_parDim1_geoDim3_degrees5) { - iganet::NonUniformBSpline geo( - {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, - 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::greville, options); - iganet::NonUniformBSpline bspline( - {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, - 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::zeros, options); - bspline.transform(trafo_parDim1_geoDim3); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-12); -} - -TEST_F(BSplineTest, NonUniformBSpline_eval_parDim1_geoDim3_degrees6) { - iganet::NonUniformBSpline geo( - {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, - 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::greville, options); - iganet::NonUniformBSpline bspline( - {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, - 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::zeros, options); - bspline.transform(trafo_parDim1_geoDim3); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-11); -} - -TEST_F(BSplineTest, NonUniformBSpline_eval_parDim1_geoDim4_degrees1) { - iganet::NonUniformBSpline geo( - {{{0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r}}}, iganet::init::greville, options); - iganet::NonUniformBSpline bspline( - {{{0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r}}}, iganet::init::zeros, options); - bspline.transform(trafo_parDim1_geoDim4); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-12); -} - -TEST_F(BSplineTest, NonUniformBSpline_eval_parDim1_geoDim4_degrees2) { - iganet::NonUniformBSpline geo( - {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::greville, options); - iganet::NonUniformBSpline bspline( - {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::zeros, options); - bspline.transform(trafo_parDim1_geoDim4); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-12); -} - -TEST_F(BSplineTest, NonUniformBSpline_eval_parDim1_geoDim4_degrees3) { - iganet::NonUniformBSpline geo( - {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::greville, options); - iganet::NonUniformBSpline bspline( - {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::zeros, options); - bspline.transform(trafo_parDim1_geoDim4); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-12); -} - -TEST_F(BSplineTest, NonUniformBSpline_eval_parDim1_geoDim4_degrees4) { - iganet::NonUniformBSpline geo( - {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, - 1.0_r}}}, - iganet::init::greville, options); - iganet::NonUniformBSpline bspline( - {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, - 1.0_r}}}, - iganet::init::zeros, options); - bspline.transform(trafo_parDim1_geoDim4); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-12); -} - -TEST_F(BSplineTest, NonUniformBSpline_eval_parDim1_geoDim4_degrees5) { - iganet::NonUniformBSpline geo( - {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, - 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::greville, options); - iganet::NonUniformBSpline bspline( - {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, - 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::zeros, options); - bspline.transform(trafo_parDim1_geoDim4); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-12); -} - -TEST_F(BSplineTest, NonUniformBSpline_eval_parDim1_geoDim4_degrees6) { - iganet::NonUniformBSpline geo( - {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, - 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::greville, options); - iganet::NonUniformBSpline bspline( - {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, - 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::zeros, options); - bspline.transform(trafo_parDim1_geoDim4); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-10); -} - -TEST_F(BSplineTest, NonUniformBSpline_eval_parDim2_geoDim1_degrees22) { - iganet::NonUniformBSpline geo( - {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::greville, options); - iganet::NonUniformBSpline bspline( - {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::zeros, options); - bspline.transform(trafo_parDim2_geoDim1); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-12); -} - -TEST_F(BSplineTest, NonUniformBSpline_eval_parDim2_geoDim1_degrees46) { - iganet::NonUniformBSpline geo( - {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, - 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, - 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::greville, options); - iganet::NonUniformBSpline bspline( - {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, - 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, - 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::zeros, options); - bspline.transform(trafo_parDim2_geoDim1); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-12); -} - -TEST_F(BSplineTest, NonUniformBSpline_eval_parDim2_geoDim1_degrees64) { - iganet::NonUniformBSpline geo( - {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, - 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, - 1.0_r}}}, - iganet::init::greville, options); - iganet::NonUniformBSpline bspline( - {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, - 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, - 1.0_r}}}, - iganet::init::zeros, options); - bspline.transform(trafo_parDim2_geoDim1); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-12); -} - -TEST_F(BSplineTest, NonUniformBSpline_eval_parDim2_geoDim2_degrees22) { - iganet::NonUniformBSpline geo( - {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::greville, options); - iganet::NonUniformBSpline bspline( - {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::zeros, options); - bspline.transform(trafo_parDim2_geoDim2); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-12); -} - -TEST_F(BSplineTest, NonUniformBSpline_eval_parDim2_geoDim2_degrees46) { - iganet::NonUniformBSpline geo( - {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, - 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, - 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::greville, options); - iganet::NonUniformBSpline bspline( - {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, - 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, - 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::zeros, options); - bspline.transform(trafo_parDim2_geoDim2); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-12); -} - -TEST_F(BSplineTest, NonUniformBSpline_eval_parDim2_geoDim2_degrees64) { - iganet::NonUniformBSpline geo( - {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, - 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, - 1.0_r}}}, - iganet::init::greville, options); - iganet::NonUniformBSpline bspline( - {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, - 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, - 1.0_r}}}, - iganet::init::zeros, options); - bspline.transform(trafo_parDim2_geoDim2); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-12); -} - -TEST_F(BSplineTest, NonUniformBSpline_eval_parDim2_geoDim3_degrees22) { - iganet::NonUniformBSpline geo( - {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::greville, options); - iganet::NonUniformBSpline bspline( - {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::zeros, options); - bspline.transform(trafo_parDim2_geoDim3); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-12); -} - -TEST_F(BSplineTest, NonUniformBSpline_eval_parDim2_geoDim3_degrees46) { - iganet::NonUniformBSpline geo( - {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, - 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, - 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::greville, options); - iganet::NonUniformBSpline bspline( - {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, - 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, - 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::zeros, options); - bspline.transform(trafo_parDim2_geoDim3); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-11); -} - -TEST_F(BSplineTest, NonUniformBSpline_eval_parDim2_geoDim3_degrees64) { - iganet::NonUniformBSpline geo( - {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, - 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, - 1.0_r}}}, - iganet::init::greville, options); - iganet::NonUniformBSpline bspline( - {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, - 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, - 1.0_r}}}, - iganet::init::zeros, options); - bspline.transform(trafo_parDim2_geoDim3); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-12); -} - -TEST_F(BSplineTest, NonUniformBSpline_eval_parDim2_geoDim4_degrees22) { - iganet::NonUniformBSpline geo( - {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::greville, options); - iganet::NonUniformBSpline bspline( - {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::zeros, options); - bspline.transform(trafo_parDim2_geoDim4); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-12); -} - -TEST_F(BSplineTest, NonUniformBSpline_eval_parDim2_geoDim4_degrees46) { - iganet::NonUniformBSpline geo( - {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, - 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, - 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::greville, options); - iganet::NonUniformBSpline bspline( - {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, - 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, - 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::zeros, options); - bspline.transform(trafo_parDim2_geoDim4); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-11); -} - -TEST_F(BSplineTest, NonUniformBSpline_eval_parDim2_geoDim4_degrees64) { - iganet::NonUniformBSpline geo( - {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, - 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, - 1.0_r}}}, - iganet::init::greville, options); - iganet::NonUniformBSpline bspline( - {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, - 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, - 1.0_r}}}, - iganet::init::zeros, options); - bspline.transform(trafo_parDim2_geoDim4); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-10); -} - -TEST_F(BSplineTest, NonUniformBSpline_eval_parDim3_geoDim1_degrees222) { - iganet::NonUniformBSpline geo( - {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::greville, options); - iganet::NonUniformBSpline bspline( - {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::zeros, options); - bspline.transform(trafo_parDim3_geoDim1); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-12); -} - -TEST_F(BSplineTest, NonUniformBSpline_eval_parDim3_geoDim1_degrees462) { - iganet::NonUniformBSpline geo( - {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, - 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, - 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::greville, options); - iganet::NonUniformBSpline bspline( - {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, - 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, - 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::zeros, options); - bspline.transform(trafo_parDim3_geoDim1); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-12); -} - -TEST_F(BSplineTest, NonUniformBSpline_eval_parDim3_geoDim1_degrees642) { - iganet::NonUniformBSpline geo( - {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, - 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, - 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::greville, options); - iganet::NonUniformBSpline bspline( - {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, - 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, - 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::zeros, options); - bspline.transform(trafo_parDim3_geoDim1); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-12); -} - -TEST_F(BSplineTest, NonUniformBSpline_eval_parDim3_geoDim2_degrees222) { - iganet::NonUniformBSpline geo( - {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::greville, options); - iganet::NonUniformBSpline bspline( - {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::zeros, options); - bspline.transform(trafo_parDim3_geoDim2); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-12); -} - -TEST_F(BSplineTest, NonUniformBSpline_eval_parDim3_geoDim2_degrees462) { - iganet::NonUniformBSpline geo( - {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, - 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, - 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::greville, options); - iganet::NonUniformBSpline bspline( - {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, - 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, - 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::zeros, options); - bspline.transform(trafo_parDim3_geoDim2); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-12); -} - -TEST_F(BSplineTest, NonUniformBSpline_eval_parDim3_geoDim2_degrees642) { - iganet::NonUniformBSpline geo( - {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, - 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, - 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::greville, options); - iganet::NonUniformBSpline bspline( - {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, - 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, - 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::zeros, options); - bspline.transform(trafo_parDim3_geoDim2); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-12); -} - -TEST_F(BSplineTest, NonUniformBSpline_eval_parDim3_geoDim3_degrees222) { - iganet::NonUniformBSpline geo( - {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::greville, options); - iganet::NonUniformBSpline bspline( - {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::zeros, options); - bspline.transform(trafo_parDim3_geoDim3); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-12); -} - -TEST_F(BSplineTest, NonUniformBSpline_eval_parDim3_geoDim3_degrees462) { - iganet::NonUniformBSpline geo( - {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, - 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, - 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::greville, options); - iganet::NonUniformBSpline bspline( - {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, - 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, - 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::zeros, options); - bspline.transform(trafo_parDim3_geoDim3); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-12); -} - -TEST_F(BSplineTest, NonUniformBSpline_eval_parDim3_geoDim3_degrees642) { - iganet::NonUniformBSpline geo( - {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, - 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, - 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::greville, options); - iganet::NonUniformBSpline bspline( - {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, - 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, - 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::zeros, options); - bspline.transform(trafo_parDim3_geoDim3); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-12); -} - -TEST_F(BSplineTest, NonUniformBSpline_eval_parDim3_geoDim4_degrees222) { - iganet::NonUniformBSpline geo( - {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::greville, options); - iganet::NonUniformBSpline bspline( - {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::zeros, options); - bspline.transform(trafo_parDim3_geoDim4); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-12); -} - -TEST_F(BSplineTest, NonUniformBSpline_eval_parDim3_geoDim4_degrees462) { - iganet::NonUniformBSpline geo( - {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, - 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, - 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::greville, options); - iganet::NonUniformBSpline bspline( - {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, - 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, - 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::zeros, options); - bspline.transform(trafo_parDim3_geoDim4); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-12); -} - -TEST_F(BSplineTest, NonUniformBSpline_eval_parDim3_geoDim4_degrees642) { - iganet::NonUniformBSpline geo( - {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, - 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, - 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::greville, options); - iganet::NonUniformBSpline bspline( - {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, - 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, - 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::zeros, options); - bspline.transform(trafo_parDim3_geoDim4); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-10); -} - -TEST_F(BSplineTest, NonUniformBSpline_eval_parDim4_geoDim1_degrees2222) { - iganet::NonUniformBSpline geo( - {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::greville, options); - iganet::NonUniformBSpline bspline( - {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::zeros, options); - bspline.transform(trafo_parDim4_geoDim1); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-12); -} - -TEST_F(BSplineTest, NonUniformBSpline_eval_parDim4_geoDim1_degrees2463) { - iganet::NonUniformBSpline geo( - {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, - 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, - 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::greville, options); - iganet::NonUniformBSpline bspline( - {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, - 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, - 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::zeros, options); - bspline.transform(trafo_parDim4_geoDim1); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-12); -} - -TEST_F(BSplineTest, NonUniformBSpline_eval_parDim4_geoDim2_degrees2222) { - iganet::NonUniformBSpline geo( - {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::greville, options); - iganet::NonUniformBSpline bspline( - {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::zeros, options); - bspline.transform(trafo_parDim4_geoDim2); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-12); -} - -TEST_F(BSplineTest, NonUniformBSpline_eval_parDim4_geoDim2_degrees2463) { - iganet::NonUniformBSpline geo( - {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, - 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, - 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::greville, options); - iganet::NonUniformBSpline bspline( - {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, - 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, - 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::zeros, options); - bspline.transform(trafo_parDim4_geoDim2); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-12); -} - -TEST_F(BSplineTest, NonUniformBSpline_eval_parDim4_geoDim3_degrees2222) { - iganet::NonUniformBSpline geo( - {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::greville, options); - iganet::NonUniformBSpline bspline( - {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::zeros, options); - bspline.transform(trafo_parDim4_geoDim3); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-12); -} - -TEST_F(BSplineTest, NonUniformBSpline_eval_parDim4_geoDim3_degrees2463) { - iganet::NonUniformBSpline geo( - {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, - 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, - 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::greville, options); - iganet::NonUniformBSpline bspline( - {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, - 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, - 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::zeros, options); - bspline.transform(trafo_parDim4_geoDim3); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-12); -} - -TEST_F(BSplineTest, NonUniformBSpline_eval_parDim4_geoDim4_degrees2222) { - iganet::NonUniformBSpline geo( - {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::greville, options); - iganet::NonUniformBSpline bspline( - {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::zeros, options); - bspline.transform(trafo_parDim4_geoDim4); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-12); -} - -TEST_F(BSplineTest, NonUniformBSpline_eval_parDim4_geoDim4_degrees2463) { - iganet::NonUniformBSpline geo( - {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, - 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, - 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::greville, options); - iganet::NonUniformBSpline bspline( - {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, - 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, - 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, - {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, - iganet::init::zeros, options); - bspline.transform(trafo_parDim4_geoDim4); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-12); -} - -int main(int argc, char **argv) { - ::testing::InitGoogleTest(&argc, argv); - iganet::init(); - return RUN_ALL_TESTS(); -} +/** + @file unittests/unittest_nonuniform_bspline_eval.cxx + + @brief B-Spline unittests + + @author Matthias Moller + + @copyright This file is part of the IgANet project + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#include +#include +#include + +#include +#include +#include + +using namespace iganet::unittests::literals; + +class BSplineTest : public ::testing::Test { +protected: + using real_t = iganet::unittests::real_t; + iganet::Options options; + + static constexpr auto trafo_parDim1_geoDim1 = + [](const std::array xi) { + return std::array{xi[0] * xi[0]}; + }; + static constexpr auto trafo_parDim1_geoDim2 = + [](const std::array xi) { + return std::array{xi[0] * xi[0], + sin(static_cast(M_PI) * xi[0])}; + }; + static constexpr auto trafo_parDim1_geoDim3 = + [](const std::array xi) { + return std::array{ + xi[0] * xi[0], sin(static_cast(M_PI) * xi[0]), xi[0]}; + }; + static constexpr auto trafo_parDim1_geoDim4 = + [](const std::array xi) { + return std::array{ + xi[0] * xi[0], sin(static_cast(M_PI) * xi[0]), xi[0], + cos(static_cast(M_PI) * xi[0])}; + }; + + static constexpr auto trafo_parDim2_geoDim1 = + [](const std::array xi) { + return std::array{xi[0] * xi[1]}; + }; + static constexpr auto trafo_parDim2_geoDim2 = + [](const std::array xi) { + return std::array{xi[0] * xi[1], + sin(static_cast(M_PI) * xi[0])}; + }; + static constexpr auto trafo_parDim2_geoDim3 = + [](const std::array xi) { + return std::array{ + xi[0] * xi[1], sin(static_cast(M_PI) * xi[0]), xi[1]}; + }; + static constexpr auto trafo_parDim2_geoDim4 = + [](const std::array xi) { + return std::array{ + xi[0] * xi[1], sin(static_cast(M_PI) * xi[0]), xi[1], + cos(static_cast(M_PI) * xi[1])}; + }; + + static constexpr auto trafo_parDim3_geoDim1 = + [](const std::array xi) { + return std::array{xi[0] * xi[1] * xi[2]}; + }; + static constexpr auto trafo_parDim3_geoDim2 = + [](const std::array xi) { + return std::array{xi[0] * xi[1] * xi[2], + sin(static_cast(M_PI) * xi[0])}; + }; + static constexpr auto trafo_parDim3_geoDim3 = + [](const std::array xi) { + return std::array{xi[0] * xi[1] * xi[2], + sin(static_cast(M_PI) * xi[0]), + xi[1] * xi[2]}; + }; + static constexpr auto trafo_parDim3_geoDim4 = + [](const std::array xi) { + return std::array{ + xi[0] * xi[1] * xi[2], sin(static_cast(M_PI) * xi[0]), + xi[1] * xi[2], cos(static_cast(M_PI) * xi[1])}; + }; + + static constexpr auto trafo_parDim4_geoDim1 = + [](const std::array xi) { + return std::array{xi[0] * xi[1] * xi[2] * xi[3]}; + }; + static constexpr auto trafo_parDim4_geoDim2 = + [](const std::array xi) { + return std::array{xi[0] * xi[1] * xi[2] * xi[3], + sin(static_cast(M_PI) * xi[0])}; + }; + static constexpr auto trafo_parDim4_geoDim3 = + [](const std::array xi) { + return std::array{xi[0] * xi[1] * xi[2] * xi[3], + sin(static_cast(M_PI) * xi[0]), + xi[1] * xi[2] * xi[3]}; + }; + static constexpr auto trafo_parDim4_geoDim4 = + [](const std::array xi) { + return std::array{xi[0] * xi[1] * xi[2] * xi[3], + sin(static_cast(M_PI) * xi[0]), + xi[1] * xi[2] * xi[3], + cos(static_cast(M_PI) * xi[1])}; + }; +}; + +TEST_F(BSplineTest, NonUniformBSpline_eval_parDim1_geoDim1_degrees1) { + iganet::NonUniformBSpline geo( + {{{0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r}}}, iganet::init::greville, options); + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r}}}, iganet::init::zeros, options); + bspline.transform(trafo_parDim1_geoDim1); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, NonUniformBSpline_eval_parDim1_geoDim1_degrees2) { + iganet::NonUniformBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim1_geoDim1); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, NonUniformBSpline_eval_parDim1_geoDim1_degrees3) { + iganet::NonUniformBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim1_geoDim1); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, NonUniformBSpline_eval_parDim1_geoDim1_degrees4) { + iganet::NonUniformBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim1_geoDim1); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, NonUniformBSpline_eval_parDim1_geoDim1_degrees5) { + iganet::NonUniformBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim1_geoDim1); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, NonUniformBSpline_eval_parDim1_geoDim1_degrees6) { + iganet::NonUniformBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim1_geoDim1); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, NonUniformBSpline_eval_parDim1_geoDim2_degrees1) { + iganet::NonUniformBSpline geo( + {{{0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r}}}, iganet::init::greville, options); + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r}}}, iganet::init::zeros, options); + bspline.transform(trafo_parDim1_geoDim2); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, NonUniformBSpline_eval_parDim1_geoDim2_degrees2) { + iganet::NonUniformBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim1_geoDim2); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, NonUniformBSpline_eval_parDim1_geoDim2_degrees3) { + iganet::NonUniformBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim1_geoDim2); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, NonUniformBSpline_eval_parDim1_geoDim2_degrees4) { + iganet::NonUniformBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim1_geoDim2); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, NonUniformBSpline_eval_parDim1_geoDim2_degrees5) { + iganet::NonUniformBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim1_geoDim2); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, NonUniformBSpline_eval_parDim1_geoDim2_degrees6) { + iganet::NonUniformBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim1_geoDim2); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-11); +} + +TEST_F(BSplineTest, NonUniformBSpline_eval_parDim1_geoDim3_degrees1) { + iganet::NonUniformBSpline geo( + {{{0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r}}}, iganet::init::greville, options); + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r}}}, iganet::init::zeros, options); + bspline.transform(trafo_parDim1_geoDim3); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, NonUniformBSpline_eval_parDim1_geoDim3_degrees2) { + iganet::NonUniformBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim1_geoDim3); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, NonUniformBSpline_eval_parDim1_geoDim3_degrees3) { + iganet::NonUniformBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim1_geoDim3); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, NonUniformBSpline_eval_parDim1_geoDim3_degrees4) { + iganet::NonUniformBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim1_geoDim3); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, NonUniformBSpline_eval_parDim1_geoDim3_degrees5) { + iganet::NonUniformBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim1_geoDim3); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, NonUniformBSpline_eval_parDim1_geoDim3_degrees6) { + iganet::NonUniformBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim1_geoDim3); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-11); +} + +TEST_F(BSplineTest, NonUniformBSpline_eval_parDim1_geoDim4_degrees1) { + iganet::NonUniformBSpline geo( + {{{0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r}}}, iganet::init::greville, options); + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r}}}, iganet::init::zeros, options); + bspline.transform(trafo_parDim1_geoDim4); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, NonUniformBSpline_eval_parDim1_geoDim4_degrees2) { + iganet::NonUniformBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim1_geoDim4); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, NonUniformBSpline_eval_parDim1_geoDim4_degrees3) { + iganet::NonUniformBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim1_geoDim4); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, NonUniformBSpline_eval_parDim1_geoDim4_degrees4) { + iganet::NonUniformBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim1_geoDim4); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, NonUniformBSpline_eval_parDim1_geoDim4_degrees5) { + iganet::NonUniformBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim1_geoDim4); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, NonUniformBSpline_eval_parDim1_geoDim4_degrees6) { + iganet::NonUniformBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim1_geoDim4); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-10); +} + +TEST_F(BSplineTest, NonUniformBSpline_eval_parDim2_geoDim1_degrees22) { + iganet::NonUniformBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim2_geoDim1); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, NonUniformBSpline_eval_parDim2_geoDim1_degrees46) { + iganet::NonUniformBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim2_geoDim1); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, NonUniformBSpline_eval_parDim2_geoDim1_degrees64) { + iganet::NonUniformBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim2_geoDim1); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, NonUniformBSpline_eval_parDim2_geoDim2_degrees22) { + iganet::NonUniformBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim2_geoDim2); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, NonUniformBSpline_eval_parDim2_geoDim2_degrees46) { + iganet::NonUniformBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim2_geoDim2); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, NonUniformBSpline_eval_parDim2_geoDim2_degrees64) { + iganet::NonUniformBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim2_geoDim2); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, NonUniformBSpline_eval_parDim2_geoDim3_degrees22) { + iganet::NonUniformBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim2_geoDim3); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, NonUniformBSpline_eval_parDim2_geoDim3_degrees46) { + iganet::NonUniformBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim2_geoDim3); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-11); +} + +TEST_F(BSplineTest, NonUniformBSpline_eval_parDim2_geoDim3_degrees64) { + iganet::NonUniformBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim2_geoDim3); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, NonUniformBSpline_eval_parDim2_geoDim4_degrees22) { + iganet::NonUniformBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim2_geoDim4); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, NonUniformBSpline_eval_parDim2_geoDim4_degrees46) { + iganet::NonUniformBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim2_geoDim4); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-11); +} + +TEST_F(BSplineTest, NonUniformBSpline_eval_parDim2_geoDim4_degrees64) { + iganet::NonUniformBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim2_geoDim4); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-10); +} + +TEST_F(BSplineTest, NonUniformBSpline_eval_parDim3_geoDim1_degrees222) { + iganet::NonUniformBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim3_geoDim1); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, NonUniformBSpline_eval_parDim3_geoDim1_degrees462) { + iganet::NonUniformBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim3_geoDim1); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, NonUniformBSpline_eval_parDim3_geoDim1_degrees642) { + iganet::NonUniformBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim3_geoDim1); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, NonUniformBSpline_eval_parDim3_geoDim2_degrees222) { + iganet::NonUniformBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim3_geoDim2); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, NonUniformBSpline_eval_parDim3_geoDim2_degrees462) { + iganet::NonUniformBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim3_geoDim2); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, NonUniformBSpline_eval_parDim3_geoDim2_degrees642) { + iganet::NonUniformBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim3_geoDim2); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, NonUniformBSpline_eval_parDim3_geoDim3_degrees222) { + iganet::NonUniformBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim3_geoDim3); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, NonUniformBSpline_eval_parDim3_geoDim3_degrees462) { + iganet::NonUniformBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim3_geoDim3); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, NonUniformBSpline_eval_parDim3_geoDim3_degrees642) { + iganet::NonUniformBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim3_geoDim3); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, NonUniformBSpline_eval_parDim3_geoDim4_degrees222) { + iganet::NonUniformBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim3_geoDim4); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, NonUniformBSpline_eval_parDim3_geoDim4_degrees462) { + iganet::NonUniformBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim3_geoDim4); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, NonUniformBSpline_eval_parDim3_geoDim4_degrees642) { + iganet::NonUniformBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim3_geoDim4); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-10); +} + +TEST_F(BSplineTest, NonUniformBSpline_eval_parDim4_geoDim1_degrees2222) { + iganet::NonUniformBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim4_geoDim1); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, NonUniformBSpline_eval_parDim4_geoDim1_degrees2463) { + iganet::NonUniformBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim4_geoDim1); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, NonUniformBSpline_eval_parDim4_geoDim2_degrees2222) { + iganet::NonUniformBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim4_geoDim2); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, NonUniformBSpline_eval_parDim4_geoDim2_degrees2463) { + iganet::NonUniformBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim4_geoDim2); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, NonUniformBSpline_eval_parDim4_geoDim3_degrees2222) { + iganet::NonUniformBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim4_geoDim3); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, NonUniformBSpline_eval_parDim4_geoDim3_degrees2463) { + iganet::NonUniformBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim4_geoDim3); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, NonUniformBSpline_eval_parDim4_geoDim4_degrees2222) { + iganet::NonUniformBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim4_geoDim4); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, NonUniformBSpline_eval_parDim4_geoDim4_degrees2463) { + iganet::NonUniformBSpline geo( + {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::greville, options); + iganet::NonUniformBSpline bspline( + {{{0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r, + 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, + 1.0_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}, + {0.0_r, 0.0_r, 0.0_r, 0.0_r, 0.5_r, 1.0_r, 1.0_r, 1.0_r, 1.0_r}}}, + iganet::init::zeros, options); + bspline.transform(trafo_parDim4_geoDim4); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-12); +} + +int main(int argc, char **argv) { + ::testing::InitGoogleTest(&argc, argv); + iganet::init(); + return RUN_ALL_TESTS(); +} diff --git a/unittests/unittest_uniform_bspline.cxx b/unittests/unittest_uniform_bspline.cxx index 41341649..7bf9f4f5 100644 --- a/unittests/unittest_uniform_bspline.cxx +++ b/unittests/unittest_uniform_bspline.cxx @@ -1,2236 +1,2236 @@ -/** - @file unittests/unittest_uniform_bspline.cxx - - @brief B-Spline unittests - - @author Matthias Moller - - @copyright This file is part of the IgANet project - - This Source Code Form is subject to the terms of the Mozilla Public - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at http://mozilla.org/MPL/2.0/. -*/ - -#include -#include -#include - -#include -#include - -using namespace iganet::unittests::literals; - -class BSplineTest : public ::testing::Test { -public: - BSplineTest() { std::srand(std::time(nullptr)); } - -protected: - using real_t = iganet::unittests::real_t; - iganet::Options options; -}; - -TEST_F(BSplineTest, UniformBSpline_parDim1_geoDim1_degrees1) { - for (iganet::short_t n0 = 0; n0 < 2; n0++) - EXPECT_THROW((iganet::UniformBSpline({n0})), - std::runtime_error); - - iganet::UniformBSpline bspline({2}); - EXPECT_EQ(bspline.parDim(), 1); - EXPECT_EQ(bspline.geoDim(), 1); - EXPECT_EQ(bspline.degree(0), 1); - EXPECT_EQ(bspline.nknots(0), 4); - EXPECT_EQ(bspline.ncoeffs(0), 2); - EXPECT_EQ(bspline.ncumcoeffs(), 2); -} - -TEST_F(BSplineTest, UniformBSpline_parDim1_geoDim1_degrees2) { - for (iganet::short_t n0 = 0; n0 < 3; n0++) - EXPECT_THROW((iganet::UniformBSpline({n0})), - std::runtime_error); - - iganet::UniformBSpline bspline({3}); - EXPECT_EQ(bspline.parDim(), 1); - EXPECT_EQ(bspline.geoDim(), 1); - EXPECT_EQ(bspline.degree(0), 2); - EXPECT_EQ(bspline.nknots(0), 6); - EXPECT_EQ(bspline.ncoeffs(0), 3); - EXPECT_EQ(bspline.ncumcoeffs(), 3); -} - -TEST_F(BSplineTest, UniformBSpline_parDim1_geoDim1_degrees3) { - for (iganet::short_t n0 = 0; n0 < 4; n0++) - EXPECT_THROW((iganet::UniformBSpline({n0})), - std::runtime_error); - - iganet::UniformBSpline bspline({4}); - EXPECT_EQ(bspline.parDim(), 1); - EXPECT_EQ(bspline.geoDim(), 1); - EXPECT_EQ(bspline.degree(0), 3); - EXPECT_EQ(bspline.nknots(0), 8); - EXPECT_EQ(bspline.ncoeffs(0), 4); - EXPECT_EQ(bspline.ncumcoeffs(), 4); -} - -TEST_F(BSplineTest, UniformBSpline_parDim1_geoDim2_degrees4) { - for (iganet::short_t n0 = 0; n0 < 5; n0++) - EXPECT_THROW((iganet::UniformBSpline({n0})), - std::runtime_error); - - iganet::UniformBSpline bspline({5}); - EXPECT_EQ(bspline.parDim(), 1); - EXPECT_EQ(bspline.geoDim(), 2); - EXPECT_EQ(bspline.degree(0), 4); - EXPECT_EQ(bspline.nknots(0), 10); - EXPECT_EQ(bspline.ncoeffs(0), 5); - EXPECT_EQ(bspline.ncumcoeffs(), 5); -} - -TEST_F(BSplineTest, UniformBSpline_parDim1_geoDim3_degrees5) { - for (iganet::short_t n0 = 0; n0 < 6; n0++) - EXPECT_THROW((iganet::UniformBSpline({n0})), - std::runtime_error); - - iganet::UniformBSpline bspline({6}); - EXPECT_EQ(bspline.parDim(), 1); - EXPECT_EQ(bspline.geoDim(), 3); - EXPECT_EQ(bspline.degree(0), 5); - EXPECT_EQ(bspline.nknots(0), 12); - EXPECT_EQ(bspline.ncoeffs(0), 6); - EXPECT_EQ(bspline.ncumcoeffs(), 6); -} - -TEST_F(BSplineTest, UniformBSpline_parDim1_geoDim4_degrees6) { - for (iganet::short_t n0 = 0; n0 < 7; n0++) - EXPECT_THROW((iganet::UniformBSpline({n0})), - std::runtime_error); - - iganet::UniformBSpline bspline({7}); - EXPECT_EQ(bspline.parDim(), 1); - EXPECT_EQ(bspline.geoDim(), 4); - EXPECT_EQ(bspline.degree(0), 6); - EXPECT_EQ(bspline.nknots(0), 14); - EXPECT_EQ(bspline.ncoeffs(0), 7); - EXPECT_EQ(bspline.ncumcoeffs(), 7); -} - -TEST_F(BSplineTest, UniformBSpline_parDim2_geoDim1_degrees34) { - for (iganet::short_t n0 = 0; n0 < 4; n0++) - for (iganet::short_t n1 = 0; n1 < 5; n1++) - EXPECT_THROW((iganet::UniformBSpline({n0, n1})), - std::runtime_error); - - iganet::UniformBSpline bspline({4, 5}); - EXPECT_EQ(bspline.parDim(), 2); - EXPECT_EQ(bspline.geoDim(), 1); - EXPECT_EQ(bspline.degree(0), 3); - EXPECT_EQ(bspline.degree(1), 4); - EXPECT_EQ(bspline.nknots(0), 8); - EXPECT_EQ(bspline.nknots(1), 10); - EXPECT_EQ(bspline.ncoeffs(0), 4); - EXPECT_EQ(bspline.ncoeffs(1), 5); - EXPECT_EQ(bspline.ncumcoeffs(), 20); -} - -TEST_F(BSplineTest, UniformBSpline_parDim2_geoDim2_degrees34) { - for (iganet::short_t n0 = 0; n0 < 4; n0++) - for (iganet::short_t n1 = 0; n1 < 5; n1++) - EXPECT_THROW((iganet::UniformBSpline({n0, n1})), - std::runtime_error); - - iganet::UniformBSpline bspline({4, 5}); - EXPECT_EQ(bspline.parDim(), 2); - EXPECT_EQ(bspline.geoDim(), 2); - EXPECT_EQ(bspline.degree(0), 3); - EXPECT_EQ(bspline.degree(1), 4); - EXPECT_EQ(bspline.nknots(0), 8); - EXPECT_EQ(bspline.nknots(1), 10); - EXPECT_EQ(bspline.ncoeffs(0), 4); - EXPECT_EQ(bspline.ncoeffs(1), 5); - EXPECT_EQ(bspline.ncumcoeffs(), 20); -} - -TEST_F(BSplineTest, UniformBSpline_parDim2_geoDim3_degrees34) { - for (iganet::short_t n0 = 0; n0 < 4; n0++) - for (iganet::short_t n1 = 0; n1 < 5; n1++) - EXPECT_THROW((iganet::UniformBSpline({n0, n1})), - std::runtime_error); - - iganet::UniformBSpline bspline({4, 5}); - EXPECT_EQ(bspline.parDim(), 2); - EXPECT_EQ(bspline.geoDim(), 3); - EXPECT_EQ(bspline.degree(0), 3); - EXPECT_EQ(bspline.degree(1), 4); - EXPECT_EQ(bspline.nknots(0), 8); - EXPECT_EQ(bspline.nknots(1), 10); - EXPECT_EQ(bspline.ncoeffs(0), 4); - EXPECT_EQ(bspline.ncoeffs(1), 5); - EXPECT_EQ(bspline.ncumcoeffs(), 20); -} - -TEST_F(BSplineTest, UniformBSpline_parDim2_geoDim4_degrees34) { - for (iganet::short_t n0 = 0; n0 < 4; n0++) - for (iganet::short_t n1 = 0; n1 < 5; n1++) - EXPECT_THROW((iganet::UniformBSpline({n0, n1})), - std::runtime_error); - - iganet::UniformBSpline bspline({4, 5}); - EXPECT_EQ(bspline.parDim(), 2); - EXPECT_EQ(bspline.geoDim(), 4); - EXPECT_EQ(bspline.degree(0), 3); - EXPECT_EQ(bspline.degree(1), 4); - EXPECT_EQ(bspline.nknots(0), 8); - EXPECT_EQ(bspline.nknots(1), 10); - EXPECT_EQ(bspline.ncoeffs(0), 4); - EXPECT_EQ(bspline.ncoeffs(1), 5); - EXPECT_EQ(bspline.ncumcoeffs(), 20); -} - -TEST_F(BSplineTest, UniformBSpline_parDim3_geoDim1_degrees342) { - for (iganet::short_t n0 = 0; n0 < 4; n0++) - for (iganet::short_t n1 = 0; n1 < 5; n1++) - for (iganet::short_t n2 = 0; n2 < 3; n2++) - EXPECT_THROW((iganet::UniformBSpline({n0, n1, n2})), - std::runtime_error); - - iganet::UniformBSpline bspline({4, 5, 3}); - EXPECT_EQ(bspline.parDim(), 3); - EXPECT_EQ(bspline.geoDim(), 1); - EXPECT_EQ(bspline.degree(0), 3); - EXPECT_EQ(bspline.degree(1), 4); - EXPECT_EQ(bspline.degree(2), 2); - EXPECT_EQ(bspline.nknots(0), 8); - EXPECT_EQ(bspline.nknots(1), 10); - EXPECT_EQ(bspline.nknots(2), 6); - EXPECT_EQ(bspline.ncoeffs(0), 4); - EXPECT_EQ(bspline.ncoeffs(1), 5); - EXPECT_EQ(bspline.ncoeffs(2), 3); - EXPECT_EQ(bspline.ncumcoeffs(), 60); -} - -TEST_F(BSplineTest, UniformBSpline_parDim2_geoDim3_degrees342) { - for (iganet::short_t n0 = 0; n0 < 4; n0++) - for (iganet::short_t n1 = 0; n1 < 5; n1++) - for (iganet::short_t n2 = 0; n2 < 3; n2++) - EXPECT_THROW((iganet::UniformBSpline({n0, n1, n2})), - std::runtime_error); - - iganet::UniformBSpline bspline({4, 5, 3}); - EXPECT_EQ(bspline.parDim(), 3); - EXPECT_EQ(bspline.geoDim(), 2); - EXPECT_EQ(bspline.degree(0), 3); - EXPECT_EQ(bspline.degree(1), 4); - EXPECT_EQ(bspline.degree(2), 2); - EXPECT_EQ(bspline.nknots(0), 8); - EXPECT_EQ(bspline.nknots(1), 10); - EXPECT_EQ(bspline.nknots(2), 6); - EXPECT_EQ(bspline.ncoeffs(0), 4); - EXPECT_EQ(bspline.ncoeffs(1), 5); - EXPECT_EQ(bspline.ncoeffs(2), 3); - EXPECT_EQ(bspline.ncumcoeffs(), 60); -} - -TEST_F(BSplineTest, UniformBSpline_parDim3_geoDim3_degrees342) { - for (iganet::short_t n0 = 0; n0 < 4; n0++) - for (iganet::short_t n1 = 0; n1 < 5; n1++) - for (iganet::short_t n2 = 0; n2 < 3; n2++) - EXPECT_THROW((iganet::UniformBSpline({n0, n1, n2})), - std::runtime_error); - - iganet::UniformBSpline bspline({4, 5, 3}); - EXPECT_EQ(bspline.parDim(), 3); - EXPECT_EQ(bspline.geoDim(), 3); - EXPECT_EQ(bspline.degree(0), 3); - EXPECT_EQ(bspline.degree(1), 4); - EXPECT_EQ(bspline.degree(2), 2); - EXPECT_EQ(bspline.nknots(0), 8); - EXPECT_EQ(bspline.nknots(1), 10); - EXPECT_EQ(bspline.nknots(2), 6); - EXPECT_EQ(bspline.ncoeffs(0), 4); - EXPECT_EQ(bspline.ncoeffs(1), 5); - EXPECT_EQ(bspline.ncoeffs(2), 3); - EXPECT_EQ(bspline.ncumcoeffs(), 60); -} - -TEST_F(BSplineTest, UniformBSpline_parDim3_geoDim4_degrees342) { - for (iganet::short_t n0 = 0; n0 < 4; n0++) - for (iganet::short_t n1 = 0; n1 < 5; n1++) - for (iganet::short_t n2 = 0; n2 < 3; n2++) - EXPECT_THROW((iganet::UniformBSpline({n0, n1, n2})), - std::runtime_error); - - iganet::UniformBSpline bspline({4, 5, 3}); - EXPECT_EQ(bspline.parDim(), 3); - EXPECT_EQ(bspline.geoDim(), 4); - EXPECT_EQ(bspline.degree(0), 3); - EXPECT_EQ(bspline.degree(1), 4); - EXPECT_EQ(bspline.degree(2), 2); - EXPECT_EQ(bspline.nknots(0), 8); - EXPECT_EQ(bspline.nknots(1), 10); - EXPECT_EQ(bspline.nknots(2), 6); - EXPECT_EQ(bspline.ncoeffs(0), 4); - EXPECT_EQ(bspline.ncoeffs(1), 5); - EXPECT_EQ(bspline.ncoeffs(2), 3); - EXPECT_EQ(bspline.ncumcoeffs(), 60); -} - -TEST_F(BSplineTest, UniformBSpline_parDim4_geoDim1_degrees3421) { - for (iganet::short_t n0 = 0; n0 < 4; n0++) - for (iganet::short_t n1 = 0; n1 < 5; n1++) - for (iganet::short_t n2 = 0; n2 < 3; n2++) - for (iganet::short_t n3 = 0; n3 < 2; n3++) - EXPECT_THROW( - (iganet::UniformBSpline({n0, n1, n2, n3})), - std::runtime_error); - - iganet::UniformBSpline bspline({4, 5, 3, 2}); - EXPECT_EQ(bspline.parDim(), 4); - EXPECT_EQ(bspline.geoDim(), 1); - EXPECT_EQ(bspline.degree(0), 3); - EXPECT_EQ(bspline.degree(1), 4); - EXPECT_EQ(bspline.degree(2), 2); - EXPECT_EQ(bspline.degree(3), 1); - EXPECT_EQ(bspline.nknots(0), 8); - EXPECT_EQ(bspline.nknots(1), 10); - EXPECT_EQ(bspline.nknots(2), 6); - EXPECT_EQ(bspline.nknots(3), 4); - EXPECT_EQ(bspline.ncoeffs(0), 4); - EXPECT_EQ(bspline.ncoeffs(1), 5); - EXPECT_EQ(bspline.ncoeffs(2), 3); - EXPECT_EQ(bspline.ncoeffs(3), 2); - EXPECT_EQ(bspline.ncumcoeffs(), 120); -} - -TEST_F(BSplineTest, UniformBSpline_parDim4_geoDim2_degrees3421) { - for (iganet::short_t n0 = 0; n0 < 4; n0++) - for (iganet::short_t n1 = 0; n1 < 5; n1++) - for (iganet::short_t n2 = 0; n2 < 3; n2++) - for (iganet::short_t n3 = 0; n3 < 2; n3++) - EXPECT_THROW( - (iganet::UniformBSpline({n0, n1, n2, n3})), - std::runtime_error); - - iganet::UniformBSpline bspline({4, 5, 3, 2}); - EXPECT_EQ(bspline.parDim(), 4); - EXPECT_EQ(bspline.geoDim(), 2); - EXPECT_EQ(bspline.degree(0), 3); - EXPECT_EQ(bspline.degree(1), 4); - EXPECT_EQ(bspline.degree(2), 2); - EXPECT_EQ(bspline.degree(3), 1); - EXPECT_EQ(bspline.nknots(0), 8); - EXPECT_EQ(bspline.nknots(1), 10); - EXPECT_EQ(bspline.nknots(2), 6); - EXPECT_EQ(bspline.nknots(3), 4); - EXPECT_EQ(bspline.ncoeffs(0), 4); - EXPECT_EQ(bspline.ncoeffs(1), 5); - EXPECT_EQ(bspline.ncoeffs(2), 3); - EXPECT_EQ(bspline.ncoeffs(3), 2); - EXPECT_EQ(bspline.ncumcoeffs(), 120); -} - -TEST_F(BSplineTest, UniformBSpline_parDim4_geoDim3_degrees3421) { - for (iganet::short_t n0 = 0; n0 < 4; n0++) - for (iganet::short_t n1 = 0; n1 < 5; n1++) - for (iganet::short_t n2 = 0; n2 < 3; n2++) - for (iganet::short_t n3 = 0; n3 < 2; n3++) - EXPECT_THROW( - (iganet::UniformBSpline({n0, n1, n2, n3})), - std::runtime_error); - - iganet::UniformBSpline bspline({4, 5, 3, 2}); - EXPECT_EQ(bspline.parDim(), 4); - EXPECT_EQ(bspline.geoDim(), 3); - EXPECT_EQ(bspline.degree(0), 3); - EXPECT_EQ(bspline.degree(1), 4); - EXPECT_EQ(bspline.degree(2), 2); - EXPECT_EQ(bspline.degree(3), 1); - EXPECT_EQ(bspline.nknots(0), 8); - EXPECT_EQ(bspline.nknots(1), 10); - EXPECT_EQ(bspline.nknots(2), 6); - EXPECT_EQ(bspline.nknots(3), 4); - EXPECT_EQ(bspline.ncoeffs(0), 4); - EXPECT_EQ(bspline.ncoeffs(1), 5); - EXPECT_EQ(bspline.ncoeffs(2), 3); - EXPECT_EQ(bspline.ncoeffs(3), 2); - EXPECT_EQ(bspline.ncumcoeffs(), 120); -} - -TEST_F(BSplineTest, UniformBSpline_parDim4_geoDim4_degrees3421) { - for (iganet::short_t n0 = 0; n0 < 4; n0++) - for (iganet::short_t n1 = 0; n1 < 5; n1++) - for (iganet::short_t n2 = 0; n2 < 3; n2++) - for (iganet::short_t n3 = 0; n3 < 2; n3++) - EXPECT_THROW( - (iganet::UniformBSpline({n0, n1, n2, n3})), - std::runtime_error); - - iganet::UniformBSpline bspline({4, 5, 3, 2}); - EXPECT_EQ(bspline.parDim(), 4); - EXPECT_EQ(bspline.geoDim(), 4); - EXPECT_EQ(bspline.degree(0), 3); - EXPECT_EQ(bspline.degree(1), 4); - EXPECT_EQ(bspline.degree(2), 2); - EXPECT_EQ(bspline.degree(3), 1); - EXPECT_EQ(bspline.nknots(0), 8); - EXPECT_EQ(bspline.nknots(1), 10); - EXPECT_EQ(bspline.nknots(2), 6); - EXPECT_EQ(bspline.nknots(3), 4); - EXPECT_EQ(bspline.ncoeffs(0), 4); - EXPECT_EQ(bspline.ncoeffs(1), 5); - EXPECT_EQ(bspline.ncoeffs(2), 3); - EXPECT_EQ(bspline.ncoeffs(3), 2); - EXPECT_EQ(bspline.ncumcoeffs(), 120); -} - -TEST_F(BSplineTest, UniformBSpline_init) { - { - iganet::UniformBSpline bspline({5}, iganet::init::zeros, - options); - EXPECT_TRUE(torch::equal(bspline.coeffs(0), torch::zeros(5, options))); - } - - { - iganet::UniformBSpline bspline({5}, iganet::init::ones, - options); - EXPECT_TRUE(torch::equal(bspline.coeffs(0), torch::ones(5, options))); - } - - { - iganet::UniformBSpline bspline({5}, iganet::init::linear, - options); - EXPECT_TRUE( - torch::equal(bspline.coeffs(0), torch::linspace(0, 1, 5, options))); - } - - { - iganet::UniformBSpline bspline({5}, iganet::init::greville, - options); - EXPECT_TRUE( - torch::equal(bspline.coeffs(0), torch::linspace(0, 1, 5, options))); - } - - { - iganet::UniformBSpline bspline({5}, iganet::init::zeros, - options); - EXPECT_TRUE(torch::equal(bspline.coeffs(0), torch::zeros(5, options))); - EXPECT_TRUE(torch::equal(bspline.coeffs(1), torch::zeros(5, options))); - } - - { - iganet::UniformBSpline bspline({5}, iganet::init::ones, - options); - EXPECT_TRUE(torch::equal(bspline.coeffs(0), torch::ones(5, options))); - EXPECT_TRUE(torch::equal(bspline.coeffs(1), torch::ones(5, options))); - } - - { - iganet::UniformBSpline bspline({5}, iganet::init::linear, - options); - EXPECT_TRUE( - torch::equal(bspline.coeffs(0), torch::linspace(0, 1, 5, options))); - EXPECT_TRUE(torch::equal(bspline.coeffs(1), torch::ones(5, options))); - } - - { - iganet::UniformBSpline bspline({5}, iganet::init::greville, - options); - EXPECT_TRUE( - torch::equal(bspline.coeffs(0), torch::linspace(0, 1, 5, options))); - EXPECT_TRUE(torch::equal(bspline.coeffs(1), torch::ones(5, options))); - } - - { - iganet::UniformBSpline bspline({5, 8}, iganet::init::zeros, - options); - EXPECT_TRUE(torch::equal(bspline.coeffs(0), torch::zeros(40, options))); - EXPECT_TRUE(torch::equal(bspline.coeffs(1), torch::zeros(40, options))); - } - - { - iganet::UniformBSpline bspline({5, 8}, iganet::init::ones, - options); - EXPECT_TRUE(torch::equal(bspline.coeffs(0), torch::ones(40, options))); - EXPECT_TRUE(torch::equal(bspline.coeffs(1), torch::ones(40, options))); - } - - { - iganet::UniformBSpline bspline( - {5, 8}, iganet::init::linear, options); - EXPECT_TRUE(torch::equal(bspline.coeffs(0), - torch::linspace(0, 1, 5, options).repeat(8))); - EXPECT_TRUE( - torch::equal(bspline.coeffs(1), - torch::linspace(0, 1, 8, options).repeat_interleave(5))); - } - - { - iganet::UniformBSpline bspline( - {5, 8}, iganet::init::greville, options); - EXPECT_TRUE(torch::equal(bspline.coeffs(0), - torch::linspace(0, 1, 5, options).repeat(8))); - EXPECT_TRUE(torch::allclose( - bspline.coeffs(1), - torch::linspace(0, 1, 8, options).repeat_interleave(5))); - } - - { - iganet::UniformBSpline bspline({5, 8}, iganet::init::zeros, - options); - EXPECT_TRUE(torch::equal(bspline.coeffs(0), torch::zeros(40, options))); - EXPECT_TRUE(torch::equal(bspline.coeffs(1), torch::zeros(40, options))); - EXPECT_TRUE(torch::equal(bspline.coeffs(2), torch::zeros(40, options))); - } - - { - iganet::UniformBSpline bspline({5, 8}, iganet::init::ones, - options); - EXPECT_TRUE(torch::equal(bspline.coeffs(0), torch::ones(40, options))); - EXPECT_TRUE(torch::equal(bspline.coeffs(1), torch::ones(40, options))); - EXPECT_TRUE(torch::equal(bspline.coeffs(2), torch::ones(40, options))); - } - - { - iganet::UniformBSpline bspline( - {5, 8}, iganet::init::linear, options); - EXPECT_TRUE(torch::equal(bspline.coeffs(0), - torch::linspace(0, 1, 5, options).repeat(8))); - EXPECT_TRUE( - torch::equal(bspline.coeffs(1), - torch::linspace(0, 1, 8, options).repeat_interleave(5))); - EXPECT_TRUE(torch::equal(bspline.coeffs(2), torch::ones(40, options))); - } - - { - iganet::UniformBSpline bspline( - {5, 8}, iganet::init::greville, options); - EXPECT_TRUE(torch::equal(bspline.coeffs(0), - torch::linspace(0, 1, 5, options).repeat(8))); - EXPECT_TRUE(torch::allclose( - bspline.coeffs(1), - torch::linspace(0, 1, 8, options).repeat_interleave(5))); - EXPECT_TRUE(torch::equal(bspline.coeffs(2), torch::ones(40, options))); - } - - { - iganet::UniformBSpline bspline({5, 8}, iganet::init::zeros, - options); - EXPECT_TRUE(torch::equal(bspline.coeffs(0), torch::zeros(40, options))); - EXPECT_TRUE(torch::equal(bspline.coeffs(1), torch::zeros(40, options))); - EXPECT_TRUE(torch::equal(bspline.coeffs(2), torch::zeros(40, options))); - EXPECT_TRUE(torch::equal(bspline.coeffs(3), torch::zeros(40, options))); - } - - { - iganet::UniformBSpline bspline({5, 8}, iganet::init::ones, - options); - EXPECT_TRUE(torch::equal(bspline.coeffs(0), torch::ones(40, options))); - EXPECT_TRUE(torch::equal(bspline.coeffs(1), torch::ones(40, options))); - EXPECT_TRUE(torch::equal(bspline.coeffs(2), torch::ones(40, options))); - EXPECT_TRUE(torch::equal(bspline.coeffs(3), torch::ones(40, options))); - } - - { - iganet::UniformBSpline bspline( - {5, 8}, iganet::init::linear, options); - EXPECT_TRUE(torch::equal(bspline.coeffs(0), - torch::linspace(0, 1, 5, options).repeat(8))); - EXPECT_TRUE( - torch::equal(bspline.coeffs(1), - torch::linspace(0, 1, 8, options).repeat_interleave(5))); - EXPECT_TRUE(torch::equal(bspline.coeffs(2), torch::ones(40, options))); - EXPECT_TRUE(torch::equal(bspline.coeffs(3), torch::ones(40, options))); - } - - { - iganet::UniformBSpline bspline( - {5, 8}, iganet::init::greville, options); - EXPECT_TRUE(torch::equal(bspline.coeffs(0), - torch::linspace(0, 1, 5, options).repeat(8))); - EXPECT_TRUE(torch::allclose( - bspline.coeffs(1), - torch::linspace(0, 1, 8, options).repeat_interleave(5))); - EXPECT_TRUE(torch::equal(bspline.coeffs(2), torch::ones(40, options))); - EXPECT_TRUE(torch::equal(bspline.coeffs(3), torch::ones(40, options))); - } -} - -TEST_F(BSplineTest, UniformBSpline_uniform_refine) { - { - iganet::UniformBSpline bspline({4, 5}); - iganet::UniformBSpline bspline_ref({5, 6}); - bspline.uniform_refine(); - - EXPECT_TRUE(bspline.isclose(bspline_ref)); - } - - { - iganet::UniformBSpline bspline({4, 5}); - iganet::UniformBSpline bspline_ref({7, 8}); - bspline.uniform_refine(2); - - EXPECT_TRUE(bspline.isclose(bspline_ref)); - } - - { - iganet::UniformBSpline bspline({4, 5}); - iganet::UniformBSpline bspline_ref({5, 5}); - bspline.uniform_refine(1, 0); - - EXPECT_TRUE(bspline.isclose(bspline_ref)); - } - - { - iganet::UniformBSpline bspline({4, 5}); - iganet::UniformBSpline bspline_ref({5, 8}); - bspline.uniform_refine(1, 0).uniform_refine(2, 1); - - EXPECT_TRUE(bspline.isclose(bspline_ref)); - } -} - -TEST_F(BSplineTest, UniformBSpline_copy_constructor) { - iganet::UniformBSpline bspline_orig( - {4, 5}, iganet::init::greville, options); - iganet::UniformBSpline bspline_copy(bspline_orig); - - bspline_orig.transform([](const std::array xi) { - return std::array{0.0_r, 1.0_r, 2.0_r}; - }); - - EXPECT_TRUE(bspline_orig == bspline_copy); -} - -TEST_F(BSplineTest, UniformBSpline_clone_constructor) { - iganet::UniformBSpline bspline_ref( - {4, 5}, iganet::init::greville, options); - iganet::UniformBSpline bspline_orig( - {4, 5}, iganet::init::greville, options); - iganet::UniformBSpline bspline_clone(bspline_orig, true); - - bspline_orig.transform([](const std::array xi) { - return std::array{0.0_r, 1.0_r, 2.0_r}; - }); - - EXPECT_TRUE(bspline_ref == bspline_clone); -} - -TEST_F(BSplineTest, UniformBSpline_move_constructor) { - iganet::UniformBSpline bspline_ref( - {7, 8}, iganet::init::greville, options); - auto bspline(iganet::UniformBSpline( - {4, 5}, iganet::init::greville, options) - .uniform_refine(2)); - - EXPECT_TRUE(bspline.isclose(bspline_ref)); -} - -TEST_F(BSplineTest, UniformBSpline_copy_coeffs_constructor) { - iganet::UniformBSpline bspline_orig( - {4, 5}, iganet::init::greville, options); - iganet::UniformBSpline bspline_copy(bspline_orig, - bspline_orig.coeffs()); - - bspline_orig.transform([](const std::array xi) { - return std::array{0.0_r, 1.0_r, 2.0_r}; - }); - - EXPECT_TRUE(bspline_orig == bspline_copy); -} - -TEST_F(BSplineTest, UniformBSpline_clone_coeffs_constructor) { - iganet::UniformBSpline bspline_ref( - {4, 5}, iganet::init::greville, options); - iganet::UniformBSpline bspline_orig( - {4, 5}, iganet::init::greville, options); - iganet::UniformBSpline bspline_clone( - bspline_orig, bspline_orig.coeffs(), true); - - bspline_orig.transform([](const std::array xi) { - return std::array{0.0_r, 1.0_r, 2.0_r}; - }); - - EXPECT_TRUE(bspline_ref == bspline_clone); -} - -TEST_F(BSplineTest, UniformBSpline_read_write) { - std::filesystem::path filename = - std::filesystem::temp_directory_path() / std::to_string(rand()); - iganet::UniformBSpline bspline_out( - {4, 5}, iganet::init::greville, options); - bspline_out.save(filename.c_str()); - - iganet::UniformBSpline bspline_in(options); - bspline_in.load(filename.c_str()); - std::filesystem::remove(filename); - - EXPECT_TRUE(bspline_in == bspline_out); - EXPECT_FALSE(bspline_in != bspline_out); -} - -TEST_F(BSplineTest, UniformBSpline_to_from_xml) { - { - iganet::UniformBSpline bspline_out({4}, iganet::init::zeros, - options); - - bspline_out.transform([](const std::array xi) { - return std::array{static_cast(std::rand())}; - }); - - pugi::xml_document doc = bspline_out.to_xml(); - - iganet::UniformBSpline bspline_in(options); - bspline_in.from_xml(doc); - - EXPECT_TRUE(bspline_in == bspline_out); - - // non-matching degree - EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching parametric dimension - EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching geometric dimension - EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching id - EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 1)), - std::runtime_error); - } - - { - iganet::UniformBSpline bspline_out({4}, iganet::init::zeros, - options); - - bspline_out.transform([](const std::array xi) { - return std::array{static_cast(std::rand()), - static_cast(std::rand())}; - }); - - pugi::xml_document doc = bspline_out.to_xml(); - - iganet::UniformBSpline bspline_in(options); - bspline_in.from_xml(doc); - - EXPECT_TRUE(bspline_in == bspline_out); - - // non-matching degree - EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching parametric dimension - EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching geometric dimension - EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching id - EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 1)), - std::runtime_error); - } - - { - iganet::UniformBSpline bspline_out({4}, iganet::init::zeros, - options); - - bspline_out.transform([](const std::array xi) { - return std::array{static_cast(std::rand()), - static_cast(std::rand()), - static_cast(std::rand())}; - }); - - pugi::xml_document doc = bspline_out.to_xml(); - - iganet::UniformBSpline bspline_in(options); - bspline_in.from_xml(doc); - - EXPECT_TRUE(bspline_in == bspline_out); - - // non-matching degree - EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching parametric dimension - EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching geometric dimension - EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching id - EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 1)), - std::runtime_error); - } - - { - iganet::UniformBSpline bspline_out({4}, iganet::init::zeros, - options); - - bspline_out.transform([](const std::array xi) { - return std::array{ - static_cast(std::rand()), static_cast(std::rand()), - static_cast(std::rand()), static_cast(std::rand())}; - }); - - pugi::xml_document doc = bspline_out.to_xml(); - - iganet::UniformBSpline bspline_in(options); - bspline_in.from_xml(doc); - - EXPECT_TRUE(bspline_in == bspline_out); - - // non-matching degree - EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching parametric dimension - EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching geometric dimension - EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching id - EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 1)), - std::runtime_error); - } - - { - iganet::UniformBSpline bspline_out( - {4, 5}, iganet::init::zeros, options); - - bspline_out.transform([](const std::array xi) { - return std::array{static_cast(std::rand())}; - }); - - pugi::xml_document doc = bspline_out.to_xml(); - - iganet::UniformBSpline bspline_in(options); - bspline_in.from_xml(doc); - - EXPECT_TRUE(bspline_in == bspline_out); - - // non-matching degree - EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching parametric dimension - EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching geometric dimension - EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching id - EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 1)), - std::runtime_error); - } - - { - iganet::UniformBSpline bspline_out( - {4, 5}, iganet::init::zeros, options); - - bspline_out.transform([](const std::array xi) { - return std::array{static_cast(std::rand()), - static_cast(std::rand())}; - }); - - pugi::xml_document doc = bspline_out.to_xml(); - - iganet::UniformBSpline bspline_in(options); - bspline_in.from_xml(doc); - - EXPECT_TRUE(bspline_in == bspline_out); - - // non-matching degree - EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching parametric dimension - EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching geometric dimension - EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching id - EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 1)), - std::runtime_error); - } - - { - iganet::UniformBSpline bspline_out( - {4, 5}, iganet::init::zeros, options); - - bspline_out.transform([](const std::array xi) { - return std::array{static_cast(std::rand()), - static_cast(std::rand()), - static_cast(std::rand())}; - }); - - pugi::xml_document doc = bspline_out.to_xml(); - - iganet::UniformBSpline bspline_in(options); - bspline_in.from_xml(doc); - - EXPECT_TRUE(bspline_in == bspline_out); - - // non-matching degree - EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching parametric dimension - EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching geometric dimension - EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching id - EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 1)), - std::runtime_error); - } - - { - iganet::UniformBSpline bspline_out( - {4, 5}, iganet::init::zeros, options); - - bspline_out.transform([](const std::array xi) { - return std::array{ - static_cast(std::rand()), static_cast(std::rand()), - static_cast(std::rand()), static_cast(std::rand())}; - }); - - pugi::xml_document doc = bspline_out.to_xml(); - - iganet::UniformBSpline bspline_in(options); - bspline_in.from_xml(doc); - - EXPECT_TRUE(bspline_in == bspline_out); - - // non-matching degree - EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching parametric dimension - EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching geometric dimension - EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching id - EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 1)), - std::runtime_error); - } - - { - iganet::UniformBSpline bspline_out( - {4, 5, 6}, iganet::init::zeros, options); - - bspline_out.transform([](const std::array xi) { - return std::array{static_cast(std::rand())}; - }); - - pugi::xml_document doc = bspline_out.to_xml(); - - iganet::UniformBSpline bspline_in(options); - bspline_in.from_xml(doc); - - EXPECT_TRUE(bspline_in == bspline_out); - - // non-matching degree - EXPECT_THROW( - (iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching parametric dimension - EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching geometric dimension - EXPECT_THROW( - (iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching id - EXPECT_THROW( - (iganet::UniformBSpline{}.from_xml(doc, 1)), - std::runtime_error); - } - - { - iganet::UniformBSpline bspline_out( - {4, 5, 6}, iganet::init::zeros, options); - - bspline_out.transform([](const std::array xi) { - return std::array{static_cast(std::rand()), - static_cast(std::rand())}; - }); - - pugi::xml_document doc = bspline_out.to_xml(); - - iganet::UniformBSpline bspline_in(options); - bspline_in.from_xml(doc); - - EXPECT_TRUE(bspline_in == bspline_out); - - // non-matching degree - EXPECT_THROW( - (iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching parametric dimension - EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching geometric dimension - EXPECT_THROW( - (iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching id - EXPECT_THROW( - (iganet::UniformBSpline{}.from_xml(doc, 1)), - std::runtime_error); - } - - { - iganet::UniformBSpline bspline_out( - {4, 5, 6}, iganet::init::zeros, options); - - bspline_out.transform([](const std::array xi) { - return std::array{static_cast(std::rand()), - static_cast(std::rand()), - static_cast(std::rand())}; - }); - - pugi::xml_document doc = bspline_out.to_xml(); - - iganet::UniformBSpline bspline_in(options); - bspline_in.from_xml(doc); - - EXPECT_TRUE(bspline_in == bspline_out); - - // non-matching degree - EXPECT_THROW( - (iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching parametric dimension - EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching geometric dimension - EXPECT_THROW( - (iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching id - EXPECT_THROW( - (iganet::UniformBSpline{}.from_xml(doc, 1)), - std::runtime_error); - } - - { - iganet::UniformBSpline bspline_out( - {4, 5, 6}, iganet::init::zeros, options); - - bspline_out.transform([](const std::array xi) { - return std::array{ - static_cast(std::rand()), static_cast(std::rand()), - static_cast(std::rand()), static_cast(std::rand())}; - }); - - pugi::xml_document doc = bspline_out.to_xml(); - - iganet::UniformBSpline bspline_in(options); - bspline_in.from_xml(doc); - - EXPECT_TRUE(bspline_in == bspline_out); - - // non-matching degree - EXPECT_THROW( - (iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching parametric dimension - EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching geometric dimension - EXPECT_THROW( - (iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching id - EXPECT_THROW( - (iganet::UniformBSpline{}.from_xml(doc, 1)), - std::runtime_error); - } - - { - iganet::UniformBSpline bspline_out( - {4, 5, 6, 2}, iganet::init::zeros, options); - - bspline_out.transform([](const std::array xi) { - return std::array{static_cast(std::rand())}; - }); - - pugi::xml_document doc = bspline_out.to_xml(); - - iganet::UniformBSpline bspline_in(options); - bspline_in.from_xml(doc); - - EXPECT_TRUE(bspline_in == bspline_out); - - // non-matching degree - EXPECT_THROW( - (iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching parametric dimension - EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching geometric dimension - EXPECT_THROW( - (iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching id - EXPECT_THROW( - (iganet::UniformBSpline{}.from_xml(doc, 1)), - std::runtime_error); - } - - { - iganet::UniformBSpline bspline_out( - {4, 5, 6, 2}, iganet::init::zeros, options); - - bspline_out.transform([](const std::array xi) { - return std::array{static_cast(std::rand()), - static_cast(std::rand())}; - }); - - pugi::xml_document doc = bspline_out.to_xml(); - - iganet::UniformBSpline bspline_in(options); - bspline_in.from_xml(doc); - - EXPECT_TRUE(bspline_in == bspline_out); - - // non-matching degree - EXPECT_THROW( - (iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching parametric dimension - EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching geometric dimension - EXPECT_THROW( - (iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching id - EXPECT_THROW( - (iganet::UniformBSpline{}.from_xml(doc, 1)), - std::runtime_error); - } - - { - iganet::UniformBSpline bspline_out( - {4, 5, 6, 2}, iganet::init::zeros, options); - - bspline_out.transform([](const std::array xi) { - return std::array{static_cast(std::rand()), - static_cast(std::rand()), - static_cast(std::rand())}; - }); - - pugi::xml_document doc = bspline_out.to_xml(); - - iganet::UniformBSpline bspline_in(options); - bspline_in.from_xml(doc); - - EXPECT_TRUE(bspline_in == bspline_out); - - // non-matching degree - EXPECT_THROW( - (iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching parametric dimension - EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching geometric dimension - EXPECT_THROW( - (iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching id - EXPECT_THROW( - (iganet::UniformBSpline{}.from_xml(doc, 1)), - std::runtime_error); - } - - { - iganet::UniformBSpline bspline_out( - {4, 5, 6, 2}, iganet::init::zeros, options); - - bspline_out.transform([](const std::array xi) { - return std::array{ - static_cast(std::rand()), static_cast(std::rand()), - static_cast(std::rand()), static_cast(std::rand())}; - }); - - pugi::xml_document doc = bspline_out.to_xml(); - - iganet::UniformBSpline bspline_in(options); - bspline_in.from_xml(doc); - - EXPECT_TRUE(bspline_in == bspline_out); - - // non-matching degree - EXPECT_THROW( - (iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching parametric dimension - EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching geometric dimension - EXPECT_THROW( - (iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - EXPECT_THROW( - (iganet::UniformBSpline{}.from_xml(doc, 0)), - std::runtime_error); - - // non-matching id - EXPECT_THROW( - (iganet::UniformBSpline{}.from_xml(doc, 1)), - std::runtime_error); - } -} - -TEST_F(BSplineTest, UniformBSpline_load_from_xml) { - { - pugi::xml_document doc; - pugi::xml_parse_result result = - doc.load_file(IGANET_DATA_DIR "domain1d/line.xml"); - - iganet::UniformBSpline bspline_in(options); - bspline_in.from_xml(doc); - - iganet::UniformBSpline bspline_ref({3}, iganet::init::zeros, - options); - - bspline_ref.transform([](const std::array xi) { - return std::array{xi[0], 0.0_r, 0.0_r}; - }); - - EXPECT_TRUE(bspline_in == bspline_ref); - } - - { - pugi::xml_document doc; - pugi::xml_parse_result result = - doc.load_file(IGANET_DATA_DIR "domain2d/square.xml"); - - iganet::UniformBSpline bspline_in(options); - bspline_in.from_xml(doc, 1); - - iganet::UniformBSpline bspline_ref( - {2, 2}, iganet::init::greville, options); - - EXPECT_TRUE(bspline_in == bspline_ref); - } - - { - pugi::xml_document doc; - pugi::xml_parse_result result = - doc.load_file(IGANET_DATA_DIR "domain3d/GshapedVolume.xml"); - - iganet::UniformBSpline bspline_in(options); - bspline_in.from_xml(doc); - } - - { - pugi::xml_document doc; - pugi::xml_parse_result result = - doc.load_file(IGANET_DATA_DIR "surfaces/g_plus_s_surf.xml"); - - iganet::UniformBSpline bspline_in0(options); - iganet::UniformBSpline bspline_in1(options); - - for (int i = 0; i < 126; ++i) { - try { - bspline_in0.from_xml(doc, i); - } catch (...) { - bspline_in1.from_xml(doc, i); - } - } - } -} - -TEST_F(BSplineTest, UniformBSpline_to_from_json) { - { - iganet::UniformBSpline bspline_out({4}, iganet::init::zeros, - options); - - bspline_out.transform([](const std::array xi) { - return std::array{static_cast(std::rand())}; - }); - - nlohmann::json json = bspline_out.to_json(); - - iganet::UniformBSpline bspline_in(options); - bspline_in.from_json(json); - - EXPECT_TRUE(bspline_in == bspline_out); - - // non-matching degree - EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - - // non-matching parametric dimension - EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW( - (iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - - // non-matching geometric dimension - EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - } - - { - iganet::UniformBSpline bspline_out({4}, iganet::init::zeros, - options); - - bspline_out.transform([](const std::array xi) { - return std::array{static_cast(std::rand()), - static_cast(std::rand())}; - }); - - nlohmann::json json = bspline_out.to_json(); - - iganet::UniformBSpline bspline_in(options); - bspline_in.from_json(json); - - EXPECT_TRUE(bspline_in == bspline_out); - - // non-matching degree - EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - - // non-matching parametric dimension - EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW( - (iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - - // non-matching geometric dimension - EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - } - - { - iganet::UniformBSpline bspline_out({4}, iganet::init::zeros, - options); - - bspline_out.transform([](const std::array xi) { - return std::array{static_cast(std::rand()), - static_cast(std::rand()), - static_cast(std::rand())}; - }); - - nlohmann::json json = bspline_out.to_json(); - - iganet::UniformBSpline bspline_in(options); - bspline_in.from_json(json); - - EXPECT_TRUE(bspline_in == bspline_out); - - // non-matching degree - EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - - // non-matching parametric dimension - EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW( - (iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - - // non-matching geometric dimension - EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - } - - { - iganet::UniformBSpline bspline_out({4}, iganet::init::zeros, - options); - - bspline_out.transform([](const std::array xi) { - return std::array{ - static_cast(std::rand()), static_cast(std::rand()), - static_cast(std::rand()), static_cast(std::rand())}; - }); - - nlohmann::json json = bspline_out.to_json(); - - iganet::UniformBSpline bspline_in(options); - bspline_in.from_json(json); - - EXPECT_TRUE(bspline_in == bspline_out); - - // non-matching degree - EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - - // non-matching parametric dimension - EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW( - (iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - - // non-matching geometric dimension - EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - } - - { - iganet::UniformBSpline bspline_out( - {4, 5}, iganet::init::zeros, options); - - bspline_out.transform([](const std::array xi) { - return std::array{static_cast(std::rand())}; - }); - - nlohmann::json json = bspline_out.to_json(); - - iganet::UniformBSpline bspline_in(options); - bspline_in.from_json(json); - - EXPECT_TRUE(bspline_in == bspline_out); - - // non-matching degree - EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - - // non-matching parametric dimension - EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW( - (iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - - // non-matching geometric dimension - EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - } - - { - iganet::UniformBSpline bspline_out( - {4, 5}, iganet::init::zeros, options); - - bspline_out.transform([](const std::array xi) { - return std::array{static_cast(std::rand()), - static_cast(std::rand())}; - }); - - nlohmann::json json = bspline_out.to_json(); - - iganet::UniformBSpline bspline_in(options); - bspline_in.from_json(json); - - EXPECT_TRUE(bspline_in == bspline_out); - - // non-matching degree - EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - - // non-matching parametric dimension - EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW( - (iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - - // non-matching geometric dimension - EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - } - - { - iganet::UniformBSpline bspline_out( - {4, 5}, iganet::init::zeros, options); - - bspline_out.transform([](const std::array xi) { - return std::array{static_cast(std::rand()), - static_cast(std::rand()), - static_cast(std::rand())}; - }); - - nlohmann::json json = bspline_out.to_json(); - - iganet::UniformBSpline bspline_in(options); - bspline_in.from_json(json); - - EXPECT_TRUE(bspline_in == bspline_out); - - // non-matching degree - EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - - // non-matching parametric dimension - EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW( - (iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - - // non-matching geometric dimension - EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - } - - { - iganet::UniformBSpline bspline_out( - {4, 5}, iganet::init::zeros, options); - - bspline_out.transform([](const std::array xi) { - return std::array{ - static_cast(std::rand()), static_cast(std::rand()), - static_cast(std::rand()), static_cast(std::rand())}; - }); - - nlohmann::json json = bspline_out.to_json(); - - iganet::UniformBSpline bspline_in(options); - bspline_in.from_json(json); - - EXPECT_TRUE(bspline_in == bspline_out); - - // non-matching degree - EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - - // non-matching parametric dimension - EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW( - (iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - - // non-matching geometric dimension - EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - } - - { - iganet::UniformBSpline bspline_out( - {4, 5, 6}, iganet::init::zeros, options); - - bspline_out.transform([](const std::array xi) { - return std::array{static_cast(std::rand())}; - }); - - nlohmann::json json = bspline_out.to_json(); - - iganet::UniformBSpline bspline_in(options); - bspline_in.from_json(json); - - EXPECT_TRUE(bspline_in == bspline_out); - - // non-matching degree - EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - - // non-matching parametric dimension - EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW( - (iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - - // non-matching geometric dimension - EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - } - - { - iganet::UniformBSpline bspline_out( - {4, 5, 6}, iganet::init::zeros, options); - - bspline_out.transform([](const std::array xi) { - return std::array{static_cast(std::rand()), - static_cast(std::rand())}; - }); - - nlohmann::json json = bspline_out.to_json(); - - iganet::UniformBSpline bspline_in(options); - bspline_in.from_json(json); - - EXPECT_TRUE(bspline_in == bspline_out); - - // non-matching degree - EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - - // non-matching parametric dimension - EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW( - (iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - - // non-matching geometric dimension - EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - } - - { - iganet::UniformBSpline bspline_out( - {4, 5, 6}, iganet::init::zeros, options); - - bspline_out.transform([](const std::array xi) { - return std::array{static_cast(std::rand()), - static_cast(std::rand()), - static_cast(std::rand())}; - }); - - nlohmann::json json = bspline_out.to_json(); - - iganet::UniformBSpline bspline_in(options); - bspline_in.from_json(json); - - EXPECT_TRUE(bspline_in == bspline_out); - - // non-matching degree - EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - - // non-matching parametric dimension - EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW( - (iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - - // non-matching geometric dimension - EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - } - - { - iganet::UniformBSpline bspline_out( - {4, 5, 6}, iganet::init::zeros, options); - - bspline_out.transform([](const std::array xi) { - return std::array{ - static_cast(std::rand()), static_cast(std::rand()), - static_cast(std::rand()), static_cast(std::rand())}; - }); - - nlohmann::json json = bspline_out.to_json(); - - iganet::UniformBSpline bspline_in(options); - bspline_in.from_json(json); - - EXPECT_TRUE(bspline_in == bspline_out); - - // non-matching degree - EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - - // non-matching parametric dimension - EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW( - (iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - - // non-matching geometric dimension - EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - } - - { - iganet::UniformBSpline bspline_out( - {4, 5, 6, 2}, iganet::init::zeros, options); - - bspline_out.transform([](const std::array xi) { - return std::array{static_cast(std::rand())}; - }); - - nlohmann::json json = bspline_out.to_json(); - - iganet::UniformBSpline bspline_in(options); - bspline_in.from_json(json); - - EXPECT_TRUE(bspline_in == bspline_out); - - // non-matching degree - EXPECT_THROW( - (iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - - // non-matching parametric dimension - EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - - // non-matching geometric dimension - EXPECT_THROW( - (iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW( - (iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW( - (iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - } - - { - iganet::UniformBSpline bspline_out( - {4, 5, 6, 2}, iganet::init::zeros, options); - - bspline_out.transform([](const std::array xi) { - return std::array{static_cast(std::rand()), - static_cast(std::rand())}; - }); - - nlohmann::json json = bspline_out.to_json(); - - iganet::UniformBSpline bspline_in(options); - bspline_in.from_json(json); - - EXPECT_TRUE(bspline_in == bspline_out); - - // non-matching degree - EXPECT_THROW( - (iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - - // non-matching parametric dimension - EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - - // non-matching geometric dimension - EXPECT_THROW( - (iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW( - (iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW( - (iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - } - - { - iganet::UniformBSpline bspline_out( - {4, 5, 6, 2}, iganet::init::zeros, options); - - bspline_out.transform([](const std::array xi) { - return std::array{static_cast(std::rand()), - static_cast(std::rand()), - static_cast(std::rand())}; - }); - - nlohmann::json json = bspline_out.to_json(); - - iganet::UniformBSpline bspline_in(options); - bspline_in.from_json(json); - - EXPECT_TRUE(bspline_in == bspline_out); - - // non-matching degree - EXPECT_THROW( - (iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - - // non-matching parametric dimension - EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - - // non-matching geometric dimension - EXPECT_THROW( - (iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW( - (iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW( - (iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - } - - { - iganet::UniformBSpline bspline_out( - {4, 5, 6, 2}, iganet::init::zeros, options); - - bspline_out.transform([](const std::array xi) { - return std::array{ - static_cast(std::rand()), static_cast(std::rand()), - static_cast(std::rand()), static_cast(std::rand())}; - }); - - nlohmann::json json = bspline_out.to_json(); - - iganet::UniformBSpline bspline_in(options); - bspline_in.from_json(json); - - EXPECT_TRUE(bspline_in == bspline_out); - - // non-matching degree - EXPECT_THROW( - (iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - - // non-matching parametric dimension - EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - - // non-matching geometric dimension - EXPECT_THROW( - (iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW( - (iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - EXPECT_THROW( - (iganet::UniformBSpline{}.from_json(json)), - std::runtime_error); - } -} - -TEST_F(BSplineTest, UniformBSpline_query_property) { - iganet::UniformBSpline bspline( - {4, 5}, iganet::init::greville, options); - - EXPECT_TRUE(bspline.is_uniform()); - EXPECT_FALSE(bspline.is_nonuniform()); - - EXPECT_EQ(bspline.device(), options.device()); - EXPECT_EQ(bspline.device_index(), options.device_index()); - EXPECT_EQ(bspline.dtype(), options.dtype()); - EXPECT_EQ(bspline.is_sparse(), options.is_sparse()); - EXPECT_EQ(bspline.layout(), options.layout()); - EXPECT_EQ(bspline.pinned_memory(), options.pinned_memory()); -} - -TEST_F(BSplineTest, UniformBSpline_requires_grad) { - { - iganet::UniformBSpline bspline( - {4, 5}, iganet::init::greville, options); - - EXPECT_FALSE(bspline.requires_grad()); - - for (iganet::short_t i = 0; i < bspline.parDim(); ++i) - EXPECT_FALSE(bspline.knots(i).requires_grad()); - - for (iganet::short_t i = 0; i < bspline.geoDim(); ++i) - EXPECT_FALSE(bspline.coeffs(i).requires_grad()); - - auto xi = iganet::utils::to_tensorArray(options, {0.5_r}, {0.5_r}); - auto values = bspline.eval(xi); - - // We expect an error when calling backward() because no tensor - // has requires_grad = true - EXPECT_THROW(values[0]->backward(), c10::Error); - - xi = iganet::utils::to_tensorArray(options.requires_grad(true), {0.5_r}, - {0.5_r}); - values = bspline.eval(xi); - values[0]->backward(); - EXPECT_TRUE(torch::allclose(xi[0].grad(), - iganet::utils::to_tensor({1.0_r}, options))); - } - - { - iganet::UniformBSpline bspline( - {4, 5}, iganet::init::linear, options.requires_grad(true)); - - EXPECT_TRUE(bspline.requires_grad()); - - for (iganet::short_t i = 0; i < bspline.parDim(); ++i) - EXPECT_TRUE(bspline.knots(i).requires_grad()); - - for (iganet::short_t i = 0; i < bspline.geoDim(); ++i) - EXPECT_TRUE(bspline.coeffs(i).requires_grad()); - - auto xi = iganet::utils::to_tensorArray(options, {0.5_r}, {0.5_r}); - auto values = bspline.eval(xi); - values[0]->backward( - {}, true); // otherwise we cannot run backward() a second time - - // We expect an error because xi[0].grad() is an undefined tensor - EXPECT_THROW(torch::allclose(xi[0].grad(), torch::empty({})), c10::Error); - - xi = iganet::utils::to_tensorArray(options.requires_grad(true), {0.5_r}, - {0.5_r}); - values = bspline.eval(xi); - values[0]->backward(); - EXPECT_TRUE(torch::allclose(xi[0].grad(), - iganet::utils::to_tensor({1.0_r}, options))); - - EXPECT_TRUE(torch::allclose( - bspline.coeffs(0).grad(), - iganet::utils::to_tensor( - {0.015625_r, 0.046875_r, 0.046875_r, 0.015625_r, 0.0625_r, - 0.1875_r, 0.1875_r, 0.0625_r, 0.09375_r, 0.28125_r, - 0.28125_r, 0.09375_r, 0.0625_r, 0.1875_r, 0.1875_r, - 0.0625_r, 0.015625_r, 0.046875_r, 0.046875_r, 0.015625_r}, - options))); - } -} - -TEST_F(BSplineTest, UniformBSpline_to_dtype) { - { - iganet::UniformBSpline bspline( - {4, 5}, iganet::init::greville, options); - - auto bspline_double = bspline.to(); - auto bspline_float = bspline.to(); - - if constexpr (std::is_same::value) - EXPECT_TRUE(bspline == bspline_double); - else - EXPECT_TRUE(bspline != bspline_double); - - if constexpr (std::is_same::value) - EXPECT_TRUE(bspline == bspline_float); - else - EXPECT_TRUE(bspline != bspline_float); - } - - { - iganet::UniformBSpline bspline( - {4, 5}, iganet::init::greville, options); - - auto bspline_double = bspline.to(iganet::Options{}); - auto bspline_float = bspline.to(iganet::Options{}); - - if constexpr (std::is_same::value) - EXPECT_TRUE(bspline == bspline_double); - else - EXPECT_TRUE(bspline != bspline_double); - - if constexpr (std::is_same::value) - EXPECT_TRUE(bspline == bspline_float); - else - EXPECT_TRUE(bspline != bspline_float); - } -} - -TEST_F(BSplineTest, UniformBSpline_to_device) { - { - iganet::Options options = - iganet::Options{}.device(torch::kCPU); - iganet::UniformBSpline bspline( - {4, 5}, iganet::init::greville, options); - - auto bspline_cpu = bspline.to(torch::kCPU); - EXPECT_TRUE(bspline == bspline_cpu); - - if (torch::cuda::is_available()) { - auto bspline_cuda = bspline.to(torch::kCUDA); - EXPECT_THROW((void)(bspline == bspline_cuda), c10::Error); - } else - EXPECT_THROW(bspline.to(torch::kCUDA), c10::Error); - - if (at::hasHIP()) { - auto bspline_hip = bspline.to(torch::kHIP); - EXPECT_THROW((void)(bspline == bspline_hip), c10::Error); - } else - EXPECT_THROW(bspline.to(torch::kHIP), c10::Error); - - if (at::hasMPS() && // will become torch::mps::is_available() - (options.dtype() != iganet::dtype())) { - auto bspline_mps = bspline.to(torch::kMPS); - EXPECT_THROW((void)(bspline == bspline_mps), c10::Error); - } else - EXPECT_THROW(bspline.to(torch::kMPS), c10::Error); - } -} - -int main(int argc, char **argv) { - ::testing::InitGoogleTest(&argc, argv); - iganet::init(); - return RUN_ALL_TESTS(); -} +/** + @file unittests/unittest_uniform_bspline.cxx + + @brief B-Spline unittests + + @author Matthias Moller + + @copyright This file is part of the IgANet project + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#include +#include +#include + +#include +#include + +using namespace iganet::unittests::literals; + +class BSplineTest : public ::testing::Test { +public: + BSplineTest() { std::srand(std::time(nullptr)); } + +protected: + using real_t = iganet::unittests::real_t; + iganet::Options options; +}; + +TEST_F(BSplineTest, UniformBSpline_parDim1_geoDim1_degrees1) { + for (iganet::short_t n0 = 0; n0 < 2; n0++) + EXPECT_THROW((iganet::UniformBSpline({n0})), + std::runtime_error); + + iganet::UniformBSpline bspline({2}); + EXPECT_EQ(bspline.parDim(), 1); + EXPECT_EQ(bspline.geoDim(), 1); + EXPECT_EQ(bspline.degree(0), 1); + EXPECT_EQ(bspline.nknots(0), 4); + EXPECT_EQ(bspline.ncoeffs(0), 2); + EXPECT_EQ(bspline.ncumcoeffs(), 2); +} + +TEST_F(BSplineTest, UniformBSpline_parDim1_geoDim1_degrees2) { + for (iganet::short_t n0 = 0; n0 < 3; n0++) + EXPECT_THROW((iganet::UniformBSpline({n0})), + std::runtime_error); + + iganet::UniformBSpline bspline({3}); + EXPECT_EQ(bspline.parDim(), 1); + EXPECT_EQ(bspline.geoDim(), 1); + EXPECT_EQ(bspline.degree(0), 2); + EXPECT_EQ(bspline.nknots(0), 6); + EXPECT_EQ(bspline.ncoeffs(0), 3); + EXPECT_EQ(bspline.ncumcoeffs(), 3); +} + +TEST_F(BSplineTest, UniformBSpline_parDim1_geoDim1_degrees3) { + for (iganet::short_t n0 = 0; n0 < 4; n0++) + EXPECT_THROW((iganet::UniformBSpline({n0})), + std::runtime_error); + + iganet::UniformBSpline bspline({4}); + EXPECT_EQ(bspline.parDim(), 1); + EXPECT_EQ(bspline.geoDim(), 1); + EXPECT_EQ(bspline.degree(0), 3); + EXPECT_EQ(bspline.nknots(0), 8); + EXPECT_EQ(bspline.ncoeffs(0), 4); + EXPECT_EQ(bspline.ncumcoeffs(), 4); +} + +TEST_F(BSplineTest, UniformBSpline_parDim1_geoDim2_degrees4) { + for (iganet::short_t n0 = 0; n0 < 5; n0++) + EXPECT_THROW((iganet::UniformBSpline({n0})), + std::runtime_error); + + iganet::UniformBSpline bspline({5}); + EXPECT_EQ(bspline.parDim(), 1); + EXPECT_EQ(bspline.geoDim(), 2); + EXPECT_EQ(bspline.degree(0), 4); + EXPECT_EQ(bspline.nknots(0), 10); + EXPECT_EQ(bspline.ncoeffs(0), 5); + EXPECT_EQ(bspline.ncumcoeffs(), 5); +} + +TEST_F(BSplineTest, UniformBSpline_parDim1_geoDim3_degrees5) { + for (iganet::short_t n0 = 0; n0 < 6; n0++) + EXPECT_THROW((iganet::UniformBSpline({n0})), + std::runtime_error); + + iganet::UniformBSpline bspline({6}); + EXPECT_EQ(bspline.parDim(), 1); + EXPECT_EQ(bspline.geoDim(), 3); + EXPECT_EQ(bspline.degree(0), 5); + EXPECT_EQ(bspline.nknots(0), 12); + EXPECT_EQ(bspline.ncoeffs(0), 6); + EXPECT_EQ(bspline.ncumcoeffs(), 6); +} + +TEST_F(BSplineTest, UniformBSpline_parDim1_geoDim4_degrees6) { + for (iganet::short_t n0 = 0; n0 < 7; n0++) + EXPECT_THROW((iganet::UniformBSpline({n0})), + std::runtime_error); + + iganet::UniformBSpline bspline({7}); + EXPECT_EQ(bspline.parDim(), 1); + EXPECT_EQ(bspline.geoDim(), 4); + EXPECT_EQ(bspline.degree(0), 6); + EXPECT_EQ(bspline.nknots(0), 14); + EXPECT_EQ(bspline.ncoeffs(0), 7); + EXPECT_EQ(bspline.ncumcoeffs(), 7); +} + +TEST_F(BSplineTest, UniformBSpline_parDim2_geoDim1_degrees34) { + for (iganet::short_t n0 = 0; n0 < 4; n0++) + for (iganet::short_t n1 = 0; n1 < 5; n1++) + EXPECT_THROW((iganet::UniformBSpline({n0, n1})), + std::runtime_error); + + iganet::UniformBSpline bspline({4, 5}); + EXPECT_EQ(bspline.parDim(), 2); + EXPECT_EQ(bspline.geoDim(), 1); + EXPECT_EQ(bspline.degree(0), 3); + EXPECT_EQ(bspline.degree(1), 4); + EXPECT_EQ(bspline.nknots(0), 8); + EXPECT_EQ(bspline.nknots(1), 10); + EXPECT_EQ(bspline.ncoeffs(0), 4); + EXPECT_EQ(bspline.ncoeffs(1), 5); + EXPECT_EQ(bspline.ncumcoeffs(), 20); +} + +TEST_F(BSplineTest, UniformBSpline_parDim2_geoDim2_degrees34) { + for (iganet::short_t n0 = 0; n0 < 4; n0++) + for (iganet::short_t n1 = 0; n1 < 5; n1++) + EXPECT_THROW((iganet::UniformBSpline({n0, n1})), + std::runtime_error); + + iganet::UniformBSpline bspline({4, 5}); + EXPECT_EQ(bspline.parDim(), 2); + EXPECT_EQ(bspline.geoDim(), 2); + EXPECT_EQ(bspline.degree(0), 3); + EXPECT_EQ(bspline.degree(1), 4); + EXPECT_EQ(bspline.nknots(0), 8); + EXPECT_EQ(bspline.nknots(1), 10); + EXPECT_EQ(bspline.ncoeffs(0), 4); + EXPECT_EQ(bspline.ncoeffs(1), 5); + EXPECT_EQ(bspline.ncumcoeffs(), 20); +} + +TEST_F(BSplineTest, UniformBSpline_parDim2_geoDim3_degrees34) { + for (iganet::short_t n0 = 0; n0 < 4; n0++) + for (iganet::short_t n1 = 0; n1 < 5; n1++) + EXPECT_THROW((iganet::UniformBSpline({n0, n1})), + std::runtime_error); + + iganet::UniformBSpline bspline({4, 5}); + EXPECT_EQ(bspline.parDim(), 2); + EXPECT_EQ(bspline.geoDim(), 3); + EXPECT_EQ(bspline.degree(0), 3); + EXPECT_EQ(bspline.degree(1), 4); + EXPECT_EQ(bspline.nknots(0), 8); + EXPECT_EQ(bspline.nknots(1), 10); + EXPECT_EQ(bspline.ncoeffs(0), 4); + EXPECT_EQ(bspline.ncoeffs(1), 5); + EXPECT_EQ(bspline.ncumcoeffs(), 20); +} + +TEST_F(BSplineTest, UniformBSpline_parDim2_geoDim4_degrees34) { + for (iganet::short_t n0 = 0; n0 < 4; n0++) + for (iganet::short_t n1 = 0; n1 < 5; n1++) + EXPECT_THROW((iganet::UniformBSpline({n0, n1})), + std::runtime_error); + + iganet::UniformBSpline bspline({4, 5}); + EXPECT_EQ(bspline.parDim(), 2); + EXPECT_EQ(bspline.geoDim(), 4); + EXPECT_EQ(bspline.degree(0), 3); + EXPECT_EQ(bspline.degree(1), 4); + EXPECT_EQ(bspline.nknots(0), 8); + EXPECT_EQ(bspline.nknots(1), 10); + EXPECT_EQ(bspline.ncoeffs(0), 4); + EXPECT_EQ(bspline.ncoeffs(1), 5); + EXPECT_EQ(bspline.ncumcoeffs(), 20); +} + +TEST_F(BSplineTest, UniformBSpline_parDim3_geoDim1_degrees342) { + for (iganet::short_t n0 = 0; n0 < 4; n0++) + for (iganet::short_t n1 = 0; n1 < 5; n1++) + for (iganet::short_t n2 = 0; n2 < 3; n2++) + EXPECT_THROW((iganet::UniformBSpline({n0, n1, n2})), + std::runtime_error); + + iganet::UniformBSpline bspline({4, 5, 3}); + EXPECT_EQ(bspline.parDim(), 3); + EXPECT_EQ(bspline.geoDim(), 1); + EXPECT_EQ(bspline.degree(0), 3); + EXPECT_EQ(bspline.degree(1), 4); + EXPECT_EQ(bspline.degree(2), 2); + EXPECT_EQ(bspline.nknots(0), 8); + EXPECT_EQ(bspline.nknots(1), 10); + EXPECT_EQ(bspline.nknots(2), 6); + EXPECT_EQ(bspline.ncoeffs(0), 4); + EXPECT_EQ(bspline.ncoeffs(1), 5); + EXPECT_EQ(bspline.ncoeffs(2), 3); + EXPECT_EQ(bspline.ncumcoeffs(), 60); +} + +TEST_F(BSplineTest, UniformBSpline_parDim2_geoDim3_degrees342) { + for (iganet::short_t n0 = 0; n0 < 4; n0++) + for (iganet::short_t n1 = 0; n1 < 5; n1++) + for (iganet::short_t n2 = 0; n2 < 3; n2++) + EXPECT_THROW((iganet::UniformBSpline({n0, n1, n2})), + std::runtime_error); + + iganet::UniformBSpline bspline({4, 5, 3}); + EXPECT_EQ(bspline.parDim(), 3); + EXPECT_EQ(bspline.geoDim(), 2); + EXPECT_EQ(bspline.degree(0), 3); + EXPECT_EQ(bspline.degree(1), 4); + EXPECT_EQ(bspline.degree(2), 2); + EXPECT_EQ(bspline.nknots(0), 8); + EXPECT_EQ(bspline.nknots(1), 10); + EXPECT_EQ(bspline.nknots(2), 6); + EXPECT_EQ(bspline.ncoeffs(0), 4); + EXPECT_EQ(bspline.ncoeffs(1), 5); + EXPECT_EQ(bspline.ncoeffs(2), 3); + EXPECT_EQ(bspline.ncumcoeffs(), 60); +} + +TEST_F(BSplineTest, UniformBSpline_parDim3_geoDim3_degrees342) { + for (iganet::short_t n0 = 0; n0 < 4; n0++) + for (iganet::short_t n1 = 0; n1 < 5; n1++) + for (iganet::short_t n2 = 0; n2 < 3; n2++) + EXPECT_THROW((iganet::UniformBSpline({n0, n1, n2})), + std::runtime_error); + + iganet::UniformBSpline bspline({4, 5, 3}); + EXPECT_EQ(bspline.parDim(), 3); + EXPECT_EQ(bspline.geoDim(), 3); + EXPECT_EQ(bspline.degree(0), 3); + EXPECT_EQ(bspline.degree(1), 4); + EXPECT_EQ(bspline.degree(2), 2); + EXPECT_EQ(bspline.nknots(0), 8); + EXPECT_EQ(bspline.nknots(1), 10); + EXPECT_EQ(bspline.nknots(2), 6); + EXPECT_EQ(bspline.ncoeffs(0), 4); + EXPECT_EQ(bspline.ncoeffs(1), 5); + EXPECT_EQ(bspline.ncoeffs(2), 3); + EXPECT_EQ(bspline.ncumcoeffs(), 60); +} + +TEST_F(BSplineTest, UniformBSpline_parDim3_geoDim4_degrees342) { + for (iganet::short_t n0 = 0; n0 < 4; n0++) + for (iganet::short_t n1 = 0; n1 < 5; n1++) + for (iganet::short_t n2 = 0; n2 < 3; n2++) + EXPECT_THROW((iganet::UniformBSpline({n0, n1, n2})), + std::runtime_error); + + iganet::UniformBSpline bspline({4, 5, 3}); + EXPECT_EQ(bspline.parDim(), 3); + EXPECT_EQ(bspline.geoDim(), 4); + EXPECT_EQ(bspline.degree(0), 3); + EXPECT_EQ(bspline.degree(1), 4); + EXPECT_EQ(bspline.degree(2), 2); + EXPECT_EQ(bspline.nknots(0), 8); + EXPECT_EQ(bspline.nknots(1), 10); + EXPECT_EQ(bspline.nknots(2), 6); + EXPECT_EQ(bspline.ncoeffs(0), 4); + EXPECT_EQ(bspline.ncoeffs(1), 5); + EXPECT_EQ(bspline.ncoeffs(2), 3); + EXPECT_EQ(bspline.ncumcoeffs(), 60); +} + +TEST_F(BSplineTest, UniformBSpline_parDim4_geoDim1_degrees3421) { + for (iganet::short_t n0 = 0; n0 < 4; n0++) + for (iganet::short_t n1 = 0; n1 < 5; n1++) + for (iganet::short_t n2 = 0; n2 < 3; n2++) + for (iganet::short_t n3 = 0; n3 < 2; n3++) + EXPECT_THROW( + (iganet::UniformBSpline({n0, n1, n2, n3})), + std::runtime_error); + + iganet::UniformBSpline bspline({4, 5, 3, 2}); + EXPECT_EQ(bspline.parDim(), 4); + EXPECT_EQ(bspline.geoDim(), 1); + EXPECT_EQ(bspline.degree(0), 3); + EXPECT_EQ(bspline.degree(1), 4); + EXPECT_EQ(bspline.degree(2), 2); + EXPECT_EQ(bspline.degree(3), 1); + EXPECT_EQ(bspline.nknots(0), 8); + EXPECT_EQ(bspline.nknots(1), 10); + EXPECT_EQ(bspline.nknots(2), 6); + EXPECT_EQ(bspline.nknots(3), 4); + EXPECT_EQ(bspline.ncoeffs(0), 4); + EXPECT_EQ(bspline.ncoeffs(1), 5); + EXPECT_EQ(bspline.ncoeffs(2), 3); + EXPECT_EQ(bspline.ncoeffs(3), 2); + EXPECT_EQ(bspline.ncumcoeffs(), 120); +} + +TEST_F(BSplineTest, UniformBSpline_parDim4_geoDim2_degrees3421) { + for (iganet::short_t n0 = 0; n0 < 4; n0++) + for (iganet::short_t n1 = 0; n1 < 5; n1++) + for (iganet::short_t n2 = 0; n2 < 3; n2++) + for (iganet::short_t n3 = 0; n3 < 2; n3++) + EXPECT_THROW( + (iganet::UniformBSpline({n0, n1, n2, n3})), + std::runtime_error); + + iganet::UniformBSpline bspline({4, 5, 3, 2}); + EXPECT_EQ(bspline.parDim(), 4); + EXPECT_EQ(bspline.geoDim(), 2); + EXPECT_EQ(bspline.degree(0), 3); + EXPECT_EQ(bspline.degree(1), 4); + EXPECT_EQ(bspline.degree(2), 2); + EXPECT_EQ(bspline.degree(3), 1); + EXPECT_EQ(bspline.nknots(0), 8); + EXPECT_EQ(bspline.nknots(1), 10); + EXPECT_EQ(bspline.nknots(2), 6); + EXPECT_EQ(bspline.nknots(3), 4); + EXPECT_EQ(bspline.ncoeffs(0), 4); + EXPECT_EQ(bspline.ncoeffs(1), 5); + EXPECT_EQ(bspline.ncoeffs(2), 3); + EXPECT_EQ(bspline.ncoeffs(3), 2); + EXPECT_EQ(bspline.ncumcoeffs(), 120); +} + +TEST_F(BSplineTest, UniformBSpline_parDim4_geoDim3_degrees3421) { + for (iganet::short_t n0 = 0; n0 < 4; n0++) + for (iganet::short_t n1 = 0; n1 < 5; n1++) + for (iganet::short_t n2 = 0; n2 < 3; n2++) + for (iganet::short_t n3 = 0; n3 < 2; n3++) + EXPECT_THROW( + (iganet::UniformBSpline({n0, n1, n2, n3})), + std::runtime_error); + + iganet::UniformBSpline bspline({4, 5, 3, 2}); + EXPECT_EQ(bspline.parDim(), 4); + EXPECT_EQ(bspline.geoDim(), 3); + EXPECT_EQ(bspline.degree(0), 3); + EXPECT_EQ(bspline.degree(1), 4); + EXPECT_EQ(bspline.degree(2), 2); + EXPECT_EQ(bspline.degree(3), 1); + EXPECT_EQ(bspline.nknots(0), 8); + EXPECT_EQ(bspline.nknots(1), 10); + EXPECT_EQ(bspline.nknots(2), 6); + EXPECT_EQ(bspline.nknots(3), 4); + EXPECT_EQ(bspline.ncoeffs(0), 4); + EXPECT_EQ(bspline.ncoeffs(1), 5); + EXPECT_EQ(bspline.ncoeffs(2), 3); + EXPECT_EQ(bspline.ncoeffs(3), 2); + EXPECT_EQ(bspline.ncumcoeffs(), 120); +} + +TEST_F(BSplineTest, UniformBSpline_parDim4_geoDim4_degrees3421) { + for (iganet::short_t n0 = 0; n0 < 4; n0++) + for (iganet::short_t n1 = 0; n1 < 5; n1++) + for (iganet::short_t n2 = 0; n2 < 3; n2++) + for (iganet::short_t n3 = 0; n3 < 2; n3++) + EXPECT_THROW( + (iganet::UniformBSpline({n0, n1, n2, n3})), + std::runtime_error); + + iganet::UniformBSpline bspline({4, 5, 3, 2}); + EXPECT_EQ(bspline.parDim(), 4); + EXPECT_EQ(bspline.geoDim(), 4); + EXPECT_EQ(bspline.degree(0), 3); + EXPECT_EQ(bspline.degree(1), 4); + EXPECT_EQ(bspline.degree(2), 2); + EXPECT_EQ(bspline.degree(3), 1); + EXPECT_EQ(bspline.nknots(0), 8); + EXPECT_EQ(bspline.nknots(1), 10); + EXPECT_EQ(bspline.nknots(2), 6); + EXPECT_EQ(bspline.nknots(3), 4); + EXPECT_EQ(bspline.ncoeffs(0), 4); + EXPECT_EQ(bspline.ncoeffs(1), 5); + EXPECT_EQ(bspline.ncoeffs(2), 3); + EXPECT_EQ(bspline.ncoeffs(3), 2); + EXPECT_EQ(bspline.ncumcoeffs(), 120); +} + +TEST_F(BSplineTest, UniformBSpline_init) { + { + iganet::UniformBSpline bspline({5}, iganet::init::zeros, + options); + EXPECT_TRUE(torch::equal(bspline.coeffs(0), torch::zeros(5, options))); + } + + { + iganet::UniformBSpline bspline({5}, iganet::init::ones, + options); + EXPECT_TRUE(torch::equal(bspline.coeffs(0), torch::ones(5, options))); + } + + { + iganet::UniformBSpline bspline({5}, iganet::init::linear, + options); + EXPECT_TRUE( + torch::equal(bspline.coeffs(0), torch::linspace(0, 1, 5, options))); + } + + { + iganet::UniformBSpline bspline({5}, iganet::init::greville, + options); + EXPECT_TRUE( + torch::equal(bspline.coeffs(0), torch::linspace(0, 1, 5, options))); + } + + { + iganet::UniformBSpline bspline({5}, iganet::init::zeros, + options); + EXPECT_TRUE(torch::equal(bspline.coeffs(0), torch::zeros(5, options))); + EXPECT_TRUE(torch::equal(bspline.coeffs(1), torch::zeros(5, options))); + } + + { + iganet::UniformBSpline bspline({5}, iganet::init::ones, + options); + EXPECT_TRUE(torch::equal(bspline.coeffs(0), torch::ones(5, options))); + EXPECT_TRUE(torch::equal(bspline.coeffs(1), torch::ones(5, options))); + } + + { + iganet::UniformBSpline bspline({5}, iganet::init::linear, + options); + EXPECT_TRUE( + torch::equal(bspline.coeffs(0), torch::linspace(0, 1, 5, options))); + EXPECT_TRUE(torch::equal(bspline.coeffs(1), torch::ones(5, options))); + } + + { + iganet::UniformBSpline bspline({5}, iganet::init::greville, + options); + EXPECT_TRUE( + torch::equal(bspline.coeffs(0), torch::linspace(0, 1, 5, options))); + EXPECT_TRUE(torch::equal(bspline.coeffs(1), torch::ones(5, options))); + } + + { + iganet::UniformBSpline bspline({5, 8}, iganet::init::zeros, + options); + EXPECT_TRUE(torch::equal(bspline.coeffs(0), torch::zeros(40, options))); + EXPECT_TRUE(torch::equal(bspline.coeffs(1), torch::zeros(40, options))); + } + + { + iganet::UniformBSpline bspline({5, 8}, iganet::init::ones, + options); + EXPECT_TRUE(torch::equal(bspline.coeffs(0), torch::ones(40, options))); + EXPECT_TRUE(torch::equal(bspline.coeffs(1), torch::ones(40, options))); + } + + { + iganet::UniformBSpline bspline( + {5, 8}, iganet::init::linear, options); + EXPECT_TRUE(torch::equal(bspline.coeffs(0), + torch::linspace(0, 1, 5, options).repeat(8))); + EXPECT_TRUE( + torch::equal(bspline.coeffs(1), + torch::linspace(0, 1, 8, options).repeat_interleave(5))); + } + + { + iganet::UniformBSpline bspline( + {5, 8}, iganet::init::greville, options); + EXPECT_TRUE(torch::equal(bspline.coeffs(0), + torch::linspace(0, 1, 5, options).repeat(8))); + EXPECT_TRUE(torch::allclose( + bspline.coeffs(1), + torch::linspace(0, 1, 8, options).repeat_interleave(5))); + } + + { + iganet::UniformBSpline bspline({5, 8}, iganet::init::zeros, + options); + EXPECT_TRUE(torch::equal(bspline.coeffs(0), torch::zeros(40, options))); + EXPECT_TRUE(torch::equal(bspline.coeffs(1), torch::zeros(40, options))); + EXPECT_TRUE(torch::equal(bspline.coeffs(2), torch::zeros(40, options))); + } + + { + iganet::UniformBSpline bspline({5, 8}, iganet::init::ones, + options); + EXPECT_TRUE(torch::equal(bspline.coeffs(0), torch::ones(40, options))); + EXPECT_TRUE(torch::equal(bspline.coeffs(1), torch::ones(40, options))); + EXPECT_TRUE(torch::equal(bspline.coeffs(2), torch::ones(40, options))); + } + + { + iganet::UniformBSpline bspline( + {5, 8}, iganet::init::linear, options); + EXPECT_TRUE(torch::equal(bspline.coeffs(0), + torch::linspace(0, 1, 5, options).repeat(8))); + EXPECT_TRUE( + torch::equal(bspline.coeffs(1), + torch::linspace(0, 1, 8, options).repeat_interleave(5))); + EXPECT_TRUE(torch::equal(bspline.coeffs(2), torch::ones(40, options))); + } + + { + iganet::UniformBSpline bspline( + {5, 8}, iganet::init::greville, options); + EXPECT_TRUE(torch::equal(bspline.coeffs(0), + torch::linspace(0, 1, 5, options).repeat(8))); + EXPECT_TRUE(torch::allclose( + bspline.coeffs(1), + torch::linspace(0, 1, 8, options).repeat_interleave(5))); + EXPECT_TRUE(torch::equal(bspline.coeffs(2), torch::ones(40, options))); + } + + { + iganet::UniformBSpline bspline({5, 8}, iganet::init::zeros, + options); + EXPECT_TRUE(torch::equal(bspline.coeffs(0), torch::zeros(40, options))); + EXPECT_TRUE(torch::equal(bspline.coeffs(1), torch::zeros(40, options))); + EXPECT_TRUE(torch::equal(bspline.coeffs(2), torch::zeros(40, options))); + EXPECT_TRUE(torch::equal(bspline.coeffs(3), torch::zeros(40, options))); + } + + { + iganet::UniformBSpline bspline({5, 8}, iganet::init::ones, + options); + EXPECT_TRUE(torch::equal(bspline.coeffs(0), torch::ones(40, options))); + EXPECT_TRUE(torch::equal(bspline.coeffs(1), torch::ones(40, options))); + EXPECT_TRUE(torch::equal(bspline.coeffs(2), torch::ones(40, options))); + EXPECT_TRUE(torch::equal(bspline.coeffs(3), torch::ones(40, options))); + } + + { + iganet::UniformBSpline bspline( + {5, 8}, iganet::init::linear, options); + EXPECT_TRUE(torch::equal(bspline.coeffs(0), + torch::linspace(0, 1, 5, options).repeat(8))); + EXPECT_TRUE( + torch::equal(bspline.coeffs(1), + torch::linspace(0, 1, 8, options).repeat_interleave(5))); + EXPECT_TRUE(torch::equal(bspline.coeffs(2), torch::ones(40, options))); + EXPECT_TRUE(torch::equal(bspline.coeffs(3), torch::ones(40, options))); + } + + { + iganet::UniformBSpline bspline( + {5, 8}, iganet::init::greville, options); + EXPECT_TRUE(torch::equal(bspline.coeffs(0), + torch::linspace(0, 1, 5, options).repeat(8))); + EXPECT_TRUE(torch::allclose( + bspline.coeffs(1), + torch::linspace(0, 1, 8, options).repeat_interleave(5))); + EXPECT_TRUE(torch::equal(bspline.coeffs(2), torch::ones(40, options))); + EXPECT_TRUE(torch::equal(bspline.coeffs(3), torch::ones(40, options))); + } +} + +TEST_F(BSplineTest, UniformBSpline_uniform_refine) { + { + iganet::UniformBSpline bspline({4, 5}); + iganet::UniformBSpline bspline_ref({5, 6}); + bspline.uniform_refine(); + + EXPECT_TRUE(bspline.isclose(bspline_ref)); + } + + { + iganet::UniformBSpline bspline({4, 5}); + iganet::UniformBSpline bspline_ref({7, 8}); + bspline.uniform_refine(2); + + EXPECT_TRUE(bspline.isclose(bspline_ref)); + } + + { + iganet::UniformBSpline bspline({4, 5}); + iganet::UniformBSpline bspline_ref({5, 5}); + bspline.uniform_refine(1, 0); + + EXPECT_TRUE(bspline.isclose(bspline_ref)); + } + + { + iganet::UniformBSpline bspline({4, 5}); + iganet::UniformBSpline bspline_ref({5, 8}); + bspline.uniform_refine(1, 0).uniform_refine(2, 1); + + EXPECT_TRUE(bspline.isclose(bspline_ref)); + } +} + +TEST_F(BSplineTest, UniformBSpline_copy_constructor) { + iganet::UniformBSpline bspline_orig( + {4, 5}, iganet::init::greville, options); + iganet::UniformBSpline bspline_copy(bspline_orig); + + bspline_orig.transform([](const std::array xi) { + return std::array{0.0_r, 1.0_r, 2.0_r}; + }); + + EXPECT_TRUE(bspline_orig == bspline_copy); +} + +TEST_F(BSplineTest, UniformBSpline_clone_constructor) { + iganet::UniformBSpline bspline_ref( + {4, 5}, iganet::init::greville, options); + iganet::UniformBSpline bspline_orig( + {4, 5}, iganet::init::greville, options); + iganet::UniformBSpline bspline_clone(bspline_orig, true); + + bspline_orig.transform([](const std::array xi) { + return std::array{0.0_r, 1.0_r, 2.0_r}; + }); + + EXPECT_TRUE(bspline_ref == bspline_clone); +} + +TEST_F(BSplineTest, UniformBSpline_move_constructor) { + iganet::UniformBSpline bspline_ref( + {7, 8}, iganet::init::greville, options); + auto bspline(iganet::UniformBSpline( + {4, 5}, iganet::init::greville, options) + .uniform_refine(2)); + + EXPECT_TRUE(bspline.isclose(bspline_ref)); +} + +TEST_F(BSplineTest, UniformBSpline_copy_coeffs_constructor) { + iganet::UniformBSpline bspline_orig( + {4, 5}, iganet::init::greville, options); + iganet::UniformBSpline bspline_copy(bspline_orig, + bspline_orig.coeffs()); + + bspline_orig.transform([](const std::array xi) { + return std::array{0.0_r, 1.0_r, 2.0_r}; + }); + + EXPECT_TRUE(bspline_orig == bspline_copy); +} + +TEST_F(BSplineTest, UniformBSpline_clone_coeffs_constructor) { + iganet::UniformBSpline bspline_ref( + {4, 5}, iganet::init::greville, options); + iganet::UniformBSpline bspline_orig( + {4, 5}, iganet::init::greville, options); + iganet::UniformBSpline bspline_clone( + bspline_orig, bspline_orig.coeffs(), true); + + bspline_orig.transform([](const std::array xi) { + return std::array{0.0_r, 1.0_r, 2.0_r}; + }); + + EXPECT_TRUE(bspline_ref == bspline_clone); +} + +TEST_F(BSplineTest, UniformBSpline_read_write) { + std::filesystem::path filename = + std::filesystem::temp_directory_path() / std::to_string(rand()); + iganet::UniformBSpline bspline_out( + {4, 5}, iganet::init::greville, options); + bspline_out.save(filename.c_str()); + + iganet::UniformBSpline bspline_in(options); + bspline_in.load(filename.c_str()); + std::filesystem::remove(filename); + + EXPECT_TRUE(bspline_in == bspline_out); + EXPECT_FALSE(bspline_in != bspline_out); +} + +TEST_F(BSplineTest, UniformBSpline_to_from_xml) { + { + iganet::UniformBSpline bspline_out({4}, iganet::init::zeros, + options); + + bspline_out.transform([](const std::array xi) { + return std::array{static_cast(std::rand())}; + }); + + pugi::xml_document doc = bspline_out.to_xml(); + + iganet::UniformBSpline bspline_in(options); + bspline_in.from_xml(doc); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching id + EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 1)), + std::runtime_error); + } + + { + iganet::UniformBSpline bspline_out({4}, iganet::init::zeros, + options); + + bspline_out.transform([](const std::array xi) { + return std::array{static_cast(std::rand()), + static_cast(std::rand())}; + }); + + pugi::xml_document doc = bspline_out.to_xml(); + + iganet::UniformBSpline bspline_in(options); + bspline_in.from_xml(doc); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching id + EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 1)), + std::runtime_error); + } + + { + iganet::UniformBSpline bspline_out({4}, iganet::init::zeros, + options); + + bspline_out.transform([](const std::array xi) { + return std::array{static_cast(std::rand()), + static_cast(std::rand()), + static_cast(std::rand())}; + }); + + pugi::xml_document doc = bspline_out.to_xml(); + + iganet::UniformBSpline bspline_in(options); + bspline_in.from_xml(doc); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching id + EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 1)), + std::runtime_error); + } + + { + iganet::UniformBSpline bspline_out({4}, iganet::init::zeros, + options); + + bspline_out.transform([](const std::array xi) { + return std::array{ + static_cast(std::rand()), static_cast(std::rand()), + static_cast(std::rand()), static_cast(std::rand())}; + }); + + pugi::xml_document doc = bspline_out.to_xml(); + + iganet::UniformBSpline bspline_in(options); + bspline_in.from_xml(doc); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching id + EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 1)), + std::runtime_error); + } + + { + iganet::UniformBSpline bspline_out( + {4, 5}, iganet::init::zeros, options); + + bspline_out.transform([](const std::array xi) { + return std::array{static_cast(std::rand())}; + }); + + pugi::xml_document doc = bspline_out.to_xml(); + + iganet::UniformBSpline bspline_in(options); + bspline_in.from_xml(doc); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching id + EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 1)), + std::runtime_error); + } + + { + iganet::UniformBSpline bspline_out( + {4, 5}, iganet::init::zeros, options); + + bspline_out.transform([](const std::array xi) { + return std::array{static_cast(std::rand()), + static_cast(std::rand())}; + }); + + pugi::xml_document doc = bspline_out.to_xml(); + + iganet::UniformBSpline bspline_in(options); + bspline_in.from_xml(doc); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching id + EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 1)), + std::runtime_error); + } + + { + iganet::UniformBSpline bspline_out( + {4, 5}, iganet::init::zeros, options); + + bspline_out.transform([](const std::array xi) { + return std::array{static_cast(std::rand()), + static_cast(std::rand()), + static_cast(std::rand())}; + }); + + pugi::xml_document doc = bspline_out.to_xml(); + + iganet::UniformBSpline bspline_in(options); + bspline_in.from_xml(doc); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching id + EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 1)), + std::runtime_error); + } + + { + iganet::UniformBSpline bspline_out( + {4, 5}, iganet::init::zeros, options); + + bspline_out.transform([](const std::array xi) { + return std::array{ + static_cast(std::rand()), static_cast(std::rand()), + static_cast(std::rand()), static_cast(std::rand())}; + }); + + pugi::xml_document doc = bspline_out.to_xml(); + + iganet::UniformBSpline bspline_in(options); + bspline_in.from_xml(doc); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching id + EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 1)), + std::runtime_error); + } + + { + iganet::UniformBSpline bspline_out( + {4, 5, 6}, iganet::init::zeros, options); + + bspline_out.transform([](const std::array xi) { + return std::array{static_cast(std::rand())}; + }); + + pugi::xml_document doc = bspline_out.to_xml(); + + iganet::UniformBSpline bspline_in(options); + bspline_in.from_xml(doc); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW( + (iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW( + (iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching id + EXPECT_THROW( + (iganet::UniformBSpline{}.from_xml(doc, 1)), + std::runtime_error); + } + + { + iganet::UniformBSpline bspline_out( + {4, 5, 6}, iganet::init::zeros, options); + + bspline_out.transform([](const std::array xi) { + return std::array{static_cast(std::rand()), + static_cast(std::rand())}; + }); + + pugi::xml_document doc = bspline_out.to_xml(); + + iganet::UniformBSpline bspline_in(options); + bspline_in.from_xml(doc); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW( + (iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW( + (iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching id + EXPECT_THROW( + (iganet::UniformBSpline{}.from_xml(doc, 1)), + std::runtime_error); + } + + { + iganet::UniformBSpline bspline_out( + {4, 5, 6}, iganet::init::zeros, options); + + bspline_out.transform([](const std::array xi) { + return std::array{static_cast(std::rand()), + static_cast(std::rand()), + static_cast(std::rand())}; + }); + + pugi::xml_document doc = bspline_out.to_xml(); + + iganet::UniformBSpline bspline_in(options); + bspline_in.from_xml(doc); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW( + (iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW( + (iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching id + EXPECT_THROW( + (iganet::UniformBSpline{}.from_xml(doc, 1)), + std::runtime_error); + } + + { + iganet::UniformBSpline bspline_out( + {4, 5, 6}, iganet::init::zeros, options); + + bspline_out.transform([](const std::array xi) { + return std::array{ + static_cast(std::rand()), static_cast(std::rand()), + static_cast(std::rand()), static_cast(std::rand())}; + }); + + pugi::xml_document doc = bspline_out.to_xml(); + + iganet::UniformBSpline bspline_in(options); + bspline_in.from_xml(doc); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW( + (iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW( + (iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching id + EXPECT_THROW( + (iganet::UniformBSpline{}.from_xml(doc, 1)), + std::runtime_error); + } + + { + iganet::UniformBSpline bspline_out( + {4, 5, 6, 2}, iganet::init::zeros, options); + + bspline_out.transform([](const std::array xi) { + return std::array{static_cast(std::rand())}; + }); + + pugi::xml_document doc = bspline_out.to_xml(); + + iganet::UniformBSpline bspline_in(options); + bspline_in.from_xml(doc); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW( + (iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW( + (iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching id + EXPECT_THROW( + (iganet::UniformBSpline{}.from_xml(doc, 1)), + std::runtime_error); + } + + { + iganet::UniformBSpline bspline_out( + {4, 5, 6, 2}, iganet::init::zeros, options); + + bspline_out.transform([](const std::array xi) { + return std::array{static_cast(std::rand()), + static_cast(std::rand())}; + }); + + pugi::xml_document doc = bspline_out.to_xml(); + + iganet::UniformBSpline bspline_in(options); + bspline_in.from_xml(doc); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW( + (iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW( + (iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching id + EXPECT_THROW( + (iganet::UniformBSpline{}.from_xml(doc, 1)), + std::runtime_error); + } + + { + iganet::UniformBSpline bspline_out( + {4, 5, 6, 2}, iganet::init::zeros, options); + + bspline_out.transform([](const std::array xi) { + return std::array{static_cast(std::rand()), + static_cast(std::rand()), + static_cast(std::rand())}; + }); + + pugi::xml_document doc = bspline_out.to_xml(); + + iganet::UniformBSpline bspline_in(options); + bspline_in.from_xml(doc); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW( + (iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW( + (iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching id + EXPECT_THROW( + (iganet::UniformBSpline{}.from_xml(doc, 1)), + std::runtime_error); + } + + { + iganet::UniformBSpline bspline_out( + {4, 5, 6, 2}, iganet::init::zeros, options); + + bspline_out.transform([](const std::array xi) { + return std::array{ + static_cast(std::rand()), static_cast(std::rand()), + static_cast(std::rand()), static_cast(std::rand())}; + }); + + pugi::xml_document doc = bspline_out.to_xml(); + + iganet::UniformBSpline bspline_in(options); + bspline_in.from_xml(doc); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW( + (iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW((iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW( + (iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + EXPECT_THROW( + (iganet::UniformBSpline{}.from_xml(doc, 0)), + std::runtime_error); + + // non-matching id + EXPECT_THROW( + (iganet::UniformBSpline{}.from_xml(doc, 1)), + std::runtime_error); + } +} + +TEST_F(BSplineTest, UniformBSpline_load_from_xml) { + { + pugi::xml_document doc; + pugi::xml_parse_result result = + doc.load_file(IGANET_DATA_DIR "domain1d/line.xml"); + + iganet::UniformBSpline bspline_in(options); + bspline_in.from_xml(doc); + + iganet::UniformBSpline bspline_ref({3}, iganet::init::zeros, + options); + + bspline_ref.transform([](const std::array xi) { + return std::array{xi[0], 0.0_r, 0.0_r}; + }); + + EXPECT_TRUE(bspline_in == bspline_ref); + } + + { + pugi::xml_document doc; + pugi::xml_parse_result result = + doc.load_file(IGANET_DATA_DIR "domain2d/square.xml"); + + iganet::UniformBSpline bspline_in(options); + bspline_in.from_xml(doc, 1); + + iganet::UniformBSpline bspline_ref( + {2, 2}, iganet::init::greville, options); + + EXPECT_TRUE(bspline_in == bspline_ref); + } + + { + pugi::xml_document doc; + pugi::xml_parse_result result = + doc.load_file(IGANET_DATA_DIR "domain3d/GshapedVolume.xml"); + + iganet::UniformBSpline bspline_in(options); + bspline_in.from_xml(doc); + } + + { + pugi::xml_document doc; + pugi::xml_parse_result result = + doc.load_file(IGANET_DATA_DIR "surfaces/g_plus_s_surf.xml"); + + iganet::UniformBSpline bspline_in0(options); + iganet::UniformBSpline bspline_in1(options); + + for (int i = 0; i < 126; ++i) { + try { + bspline_in0.from_xml(doc, i); + } catch (...) { + bspline_in1.from_xml(doc, i); + } + } + } +} + +TEST_F(BSplineTest, UniformBSpline_to_from_json) { + { + iganet::UniformBSpline bspline_out({4}, iganet::init::zeros, + options); + + bspline_out.transform([](const std::array xi) { + return std::array{static_cast(std::rand())}; + }); + + nlohmann::json json = bspline_out.to_json(); + + iganet::UniformBSpline bspline_in(options); + bspline_in.from_json(json); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + } + + { + iganet::UniformBSpline bspline_out({4}, iganet::init::zeros, + options); + + bspline_out.transform([](const std::array xi) { + return std::array{static_cast(std::rand()), + static_cast(std::rand())}; + }); + + nlohmann::json json = bspline_out.to_json(); + + iganet::UniformBSpline bspline_in(options); + bspline_in.from_json(json); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + } + + { + iganet::UniformBSpline bspline_out({4}, iganet::init::zeros, + options); + + bspline_out.transform([](const std::array xi) { + return std::array{static_cast(std::rand()), + static_cast(std::rand()), + static_cast(std::rand())}; + }); + + nlohmann::json json = bspline_out.to_json(); + + iganet::UniformBSpline bspline_in(options); + bspline_in.from_json(json); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + } + + { + iganet::UniformBSpline bspline_out({4}, iganet::init::zeros, + options); + + bspline_out.transform([](const std::array xi) { + return std::array{ + static_cast(std::rand()), static_cast(std::rand()), + static_cast(std::rand()), static_cast(std::rand())}; + }); + + nlohmann::json json = bspline_out.to_json(); + + iganet::UniformBSpline bspline_in(options); + bspline_in.from_json(json); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + } + + { + iganet::UniformBSpline bspline_out( + {4, 5}, iganet::init::zeros, options); + + bspline_out.transform([](const std::array xi) { + return std::array{static_cast(std::rand())}; + }); + + nlohmann::json json = bspline_out.to_json(); + + iganet::UniformBSpline bspline_in(options); + bspline_in.from_json(json); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + } + + { + iganet::UniformBSpline bspline_out( + {4, 5}, iganet::init::zeros, options); + + bspline_out.transform([](const std::array xi) { + return std::array{static_cast(std::rand()), + static_cast(std::rand())}; + }); + + nlohmann::json json = bspline_out.to_json(); + + iganet::UniformBSpline bspline_in(options); + bspline_in.from_json(json); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + } + + { + iganet::UniformBSpline bspline_out( + {4, 5}, iganet::init::zeros, options); + + bspline_out.transform([](const std::array xi) { + return std::array{static_cast(std::rand()), + static_cast(std::rand()), + static_cast(std::rand())}; + }); + + nlohmann::json json = bspline_out.to_json(); + + iganet::UniformBSpline bspline_in(options); + bspline_in.from_json(json); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + } + + { + iganet::UniformBSpline bspline_out( + {4, 5}, iganet::init::zeros, options); + + bspline_out.transform([](const std::array xi) { + return std::array{ + static_cast(std::rand()), static_cast(std::rand()), + static_cast(std::rand()), static_cast(std::rand())}; + }); + + nlohmann::json json = bspline_out.to_json(); + + iganet::UniformBSpline bspline_in(options); + bspline_in.from_json(json); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + } + + { + iganet::UniformBSpline bspline_out( + {4, 5, 6}, iganet::init::zeros, options); + + bspline_out.transform([](const std::array xi) { + return std::array{static_cast(std::rand())}; + }); + + nlohmann::json json = bspline_out.to_json(); + + iganet::UniformBSpline bspline_in(options); + bspline_in.from_json(json); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + } + + { + iganet::UniformBSpline bspline_out( + {4, 5, 6}, iganet::init::zeros, options); + + bspline_out.transform([](const std::array xi) { + return std::array{static_cast(std::rand()), + static_cast(std::rand())}; + }); + + nlohmann::json json = bspline_out.to_json(); + + iganet::UniformBSpline bspline_in(options); + bspline_in.from_json(json); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + } + + { + iganet::UniformBSpline bspline_out( + {4, 5, 6}, iganet::init::zeros, options); + + bspline_out.transform([](const std::array xi) { + return std::array{static_cast(std::rand()), + static_cast(std::rand()), + static_cast(std::rand())}; + }); + + nlohmann::json json = bspline_out.to_json(); + + iganet::UniformBSpline bspline_in(options); + bspline_in.from_json(json); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + } + + { + iganet::UniformBSpline bspline_out( + {4, 5, 6}, iganet::init::zeros, options); + + bspline_out.transform([](const std::array xi) { + return std::array{ + static_cast(std::rand()), static_cast(std::rand()), + static_cast(std::rand()), static_cast(std::rand())}; + }); + + nlohmann::json json = bspline_out.to_json(); + + iganet::UniformBSpline bspline_in(options); + bspline_in.from_json(json); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + } + + { + iganet::UniformBSpline bspline_out( + {4, 5, 6, 2}, iganet::init::zeros, options); + + bspline_out.transform([](const std::array xi) { + return std::array{static_cast(std::rand())}; + }); + + nlohmann::json json = bspline_out.to_json(); + + iganet::UniformBSpline bspline_in(options); + bspline_in.from_json(json); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW( + (iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW( + (iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + } + + { + iganet::UniformBSpline bspline_out( + {4, 5, 6, 2}, iganet::init::zeros, options); + + bspline_out.transform([](const std::array xi) { + return std::array{static_cast(std::rand()), + static_cast(std::rand())}; + }); + + nlohmann::json json = bspline_out.to_json(); + + iganet::UniformBSpline bspline_in(options); + bspline_in.from_json(json); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW( + (iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW( + (iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + } + + { + iganet::UniformBSpline bspline_out( + {4, 5, 6, 2}, iganet::init::zeros, options); + + bspline_out.transform([](const std::array xi) { + return std::array{static_cast(std::rand()), + static_cast(std::rand()), + static_cast(std::rand())}; + }); + + nlohmann::json json = bspline_out.to_json(); + + iganet::UniformBSpline bspline_in(options); + bspline_in.from_json(json); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW( + (iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW( + (iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + } + + { + iganet::UniformBSpline bspline_out( + {4, 5, 6, 2}, iganet::init::zeros, options); + + bspline_out.transform([](const std::array xi) { + return std::array{ + static_cast(std::rand()), static_cast(std::rand()), + static_cast(std::rand()), static_cast(std::rand())}; + }); + + nlohmann::json json = bspline_out.to_json(); + + iganet::UniformBSpline bspline_in(options); + bspline_in.from_json(json); + + EXPECT_TRUE(bspline_in == bspline_out); + + // non-matching degree + EXPECT_THROW( + (iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching parametric dimension + EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW((iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + + // non-matching geometric dimension + EXPECT_THROW( + (iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + EXPECT_THROW( + (iganet::UniformBSpline{}.from_json(json)), + std::runtime_error); + } +} + +TEST_F(BSplineTest, UniformBSpline_query_property) { + iganet::UniformBSpline bspline( + {4, 5}, iganet::init::greville, options); + + EXPECT_TRUE(bspline.is_uniform()); + EXPECT_FALSE(bspline.is_nonuniform()); + + EXPECT_EQ(bspline.device(), options.device()); + EXPECT_EQ(bspline.device_index(), options.device_index()); + EXPECT_EQ(bspline.dtype(), options.dtype()); + EXPECT_EQ(bspline.is_sparse(), options.is_sparse()); + EXPECT_EQ(bspline.layout(), options.layout()); + EXPECT_EQ(bspline.pinned_memory(), options.pinned_memory()); +} + +TEST_F(BSplineTest, UniformBSpline_requires_grad) { + { + iganet::UniformBSpline bspline( + {4, 5}, iganet::init::greville, options); + + EXPECT_FALSE(bspline.requires_grad()); + + for (iganet::short_t i = 0; i < bspline.parDim(); ++i) + EXPECT_FALSE(bspline.knots(i).requires_grad()); + + for (iganet::short_t i = 0; i < bspline.geoDim(); ++i) + EXPECT_FALSE(bspline.coeffs(i).requires_grad()); + + auto xi = iganet::utils::to_tensorArray(options, {0.5_r}, {0.5_r}); + auto values = bspline.eval(xi); + + // We expect an error when calling backward() because no tensor + // has requires_grad = true + EXPECT_THROW(values[0]->backward(), c10::Error); + + xi = iganet::utils::to_tensorArray(options.requires_grad(true), {0.5_r}, + {0.5_r}); + values = bspline.eval(xi); + values[0]->backward(); + EXPECT_TRUE(torch::allclose(xi[0].grad(), + iganet::utils::to_tensor({1.0_r}, options))); + } + + { + iganet::UniformBSpline bspline( + {4, 5}, iganet::init::linear, options.requires_grad(true)); + + EXPECT_TRUE(bspline.requires_grad()); + + for (iganet::short_t i = 0; i < bspline.parDim(); ++i) + EXPECT_TRUE(bspline.knots(i).requires_grad()); + + for (iganet::short_t i = 0; i < bspline.geoDim(); ++i) + EXPECT_TRUE(bspline.coeffs(i).requires_grad()); + + auto xi = iganet::utils::to_tensorArray(options, {0.5_r}, {0.5_r}); + auto values = bspline.eval(xi); + values[0]->backward( + {}, true); // otherwise we cannot run backward() a second time + + // We expect an error because xi[0].grad() is an undefined tensor + EXPECT_THROW(torch::allclose(xi[0].grad(), torch::empty({})), c10::Error); + + xi = iganet::utils::to_tensorArray(options.requires_grad(true), {0.5_r}, + {0.5_r}); + values = bspline.eval(xi); + values[0]->backward(); + EXPECT_TRUE(torch::allclose(xi[0].grad(), + iganet::utils::to_tensor({1.0_r}, options))); + + EXPECT_TRUE(torch::allclose( + bspline.coeffs(0).grad(), + iganet::utils::to_tensor( + {0.015625_r, 0.046875_r, 0.046875_r, 0.015625_r, 0.0625_r, + 0.1875_r, 0.1875_r, 0.0625_r, 0.09375_r, 0.28125_r, + 0.28125_r, 0.09375_r, 0.0625_r, 0.1875_r, 0.1875_r, + 0.0625_r, 0.015625_r, 0.046875_r, 0.046875_r, 0.015625_r}, + options))); + } +} + +TEST_F(BSplineTest, UniformBSpline_to_dtype) { + { + iganet::UniformBSpline bspline( + {4, 5}, iganet::init::greville, options); + + auto bspline_double = bspline.to(); + auto bspline_float = bspline.to(); + + if constexpr (std::is_same::value) + EXPECT_TRUE(bspline == bspline_double); + else + EXPECT_TRUE(bspline != bspline_double); + + if constexpr (std::is_same::value) + EXPECT_TRUE(bspline == bspline_float); + else + EXPECT_TRUE(bspline != bspline_float); + } + + { + iganet::UniformBSpline bspline( + {4, 5}, iganet::init::greville, options); + + auto bspline_double = bspline.to(iganet::Options{}); + auto bspline_float = bspline.to(iganet::Options{}); + + if constexpr (std::is_same::value) + EXPECT_TRUE(bspline == bspline_double); + else + EXPECT_TRUE(bspline != bspline_double); + + if constexpr (std::is_same::value) + EXPECT_TRUE(bspline == bspline_float); + else + EXPECT_TRUE(bspline != bspline_float); + } +} + +TEST_F(BSplineTest, UniformBSpline_to_device) { + { + iganet::Options options = + iganet::Options{}.device(torch::kCPU); + iganet::UniformBSpline bspline( + {4, 5}, iganet::init::greville, options); + + auto bspline_cpu = bspline.to(torch::kCPU); + EXPECT_TRUE(bspline == bspline_cpu); + + if (torch::cuda::is_available()) { + auto bspline_cuda = bspline.to(torch::kCUDA); + EXPECT_THROW((void)(bspline == bspline_cuda), c10::Error); + } else + EXPECT_THROW(bspline.to(torch::kCUDA), c10::Error); + + if (at::hasHIP()) { + auto bspline_hip = bspline.to(torch::kHIP); + EXPECT_THROW((void)(bspline == bspline_hip), c10::Error); + } else + EXPECT_THROW(bspline.to(torch::kHIP), c10::Error); + + if (at::hasMPS() && // will become torch::mps::is_available() + (options.dtype() != iganet::dtype())) { + auto bspline_mps = bspline.to(torch::kMPS); + EXPECT_THROW((void)(bspline == bspline_mps), c10::Error); + } else + EXPECT_THROW(bspline.to(torch::kMPS), c10::Error); + } +} + +int main(int argc, char **argv) { + ::testing::InitGoogleTest(&argc, argv); + iganet::init(); + return RUN_ALL_TESTS(); +} diff --git a/unittests/unittest_uniform_bspline_eval.cxx b/unittests/unittest_uniform_bspline_eval.cxx index 0ab7250a..9d325760 100644 --- a/unittests/unittest_uniform_bspline_eval.cxx +++ b/unittests/unittest_uniform_bspline_eval.cxx @@ -1,746 +1,746 @@ -/** - @file unittests/unittest_uniform_bspline_eval.cxx - - @brief B-Spline unittests - - @author Matthias Moller - - @copyright This file is part of the IgANet project - - This Source Code Form is subject to the terms of the Mozilla Public - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at http://mozilla.org/MPL/2.0/. -*/ - -#include -#include -#include - -#include -#include -#include - -using namespace iganet::unittests::literals; - -class BSplineTest : public ::testing::Test { -protected: - using real_t = iganet::unittests::real_t; - iganet::Options options; - - static constexpr auto trafo_parDim1_geoDim1 = - [](const std::array xi) { - return std::array{xi[0] * xi[0]}; - }; - static constexpr auto trafo_parDim1_geoDim2 = - [](const std::array xi) { - return std::array{xi[0] * xi[0], - sin(static_cast(M_PI) * xi[0])}; - }; - static constexpr auto trafo_parDim1_geoDim3 = - [](const std::array xi) { - return std::array{ - xi[0] * xi[0], sin(static_cast(M_PI) * xi[0]), xi[0]}; - }; - static constexpr auto trafo_parDim1_geoDim4 = - [](const std::array xi) { - return std::array{ - xi[0] * xi[0], sin(static_cast(M_PI) * xi[0]), xi[0], - cos(static_cast(M_PI) * xi[0])}; - }; - - static constexpr auto trafo_parDim2_geoDim1 = - [](const std::array xi) { - return std::array{xi[0] * xi[1]}; - }; - static constexpr auto trafo_parDim2_geoDim2 = - [](const std::array xi) { - return std::array{xi[0] * xi[1], - sin(static_cast(M_PI) * xi[0])}; - }; - static constexpr auto trafo_parDim2_geoDim3 = - [](const std::array xi) { - return std::array{ - xi[0] * xi[1], sin(static_cast(M_PI) * xi[0]), xi[1]}; - }; - static constexpr auto trafo_parDim2_geoDim4 = - [](const std::array xi) { - return std::array{ - xi[0] * xi[1], sin(static_cast(M_PI) * xi[0]), xi[1], - cos(static_cast(M_PI) * xi[1])}; - }; - - static constexpr auto trafo_parDim3_geoDim1 = - [](const std::array xi) { - return std::array{xi[0] * xi[1] * xi[2]}; - }; - static constexpr auto trafo_parDim3_geoDim2 = - [](const std::array xi) { - return std::array{xi[0] * xi[1] * xi[2], - sin(static_cast(M_PI) * xi[0])}; - }; - static constexpr auto trafo_parDim3_geoDim3 = - [](const std::array xi) { - return std::array{xi[0] * xi[1] * xi[2], - sin(static_cast(M_PI) * xi[0]), - xi[1] * xi[2]}; - }; - static constexpr auto trafo_parDim3_geoDim4 = - [](const std::array xi) { - return std::array{ - xi[0] * xi[1] * xi[2], sin(static_cast(M_PI) * xi[0]), - xi[1] * xi[2], cos(static_cast(M_PI) * xi[1])}; - }; - - static constexpr auto trafo_parDim4_geoDim1 = - [](const std::array xi) { - return std::array{xi[0] * xi[1] * xi[2] * xi[3]}; - }; - static constexpr auto trafo_parDim4_geoDim2 = - [](const std::array xi) { - return std::array{xi[0] * xi[1] * xi[2] * xi[3], - sin(static_cast(M_PI) * xi[0])}; - }; - static constexpr auto trafo_parDim4_geoDim3 = - [](const std::array xi) { - return std::array{xi[0] * xi[1] * xi[2] * xi[3], - sin(static_cast(M_PI) * xi[0]), - xi[1] * xi[2] * xi[3]}; - }; - static constexpr auto trafo_parDim4_geoDim4 = - [](const std::array xi) { - return std::array{xi[0] * xi[1] * xi[2] * xi[3], - sin(static_cast(M_PI) * xi[0]), - xi[1] * xi[2] * xi[3], - cos(static_cast(M_PI) * xi[1])}; - }; -}; - -TEST_F(BSplineTest, UniformBSpline_eval_parDim1_geoDim1_degrees1) { - iganet::UniformBSpline geo({11}, iganet::init::greville, - options); - iganet::UniformBSpline bspline({11}, iganet::init::zeros, - options); - bspline.transform(trafo_parDim1_geoDim1); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-12); -} - -TEST_F(BSplineTest, UniformBSpline_eval_parDim1_geoDim1_degrees2) { - iganet::UniformBSpline geo({11}, iganet::init::greville, - options); - iganet::UniformBSpline bspline({11}, iganet::init::zeros, - options); - bspline.transform(trafo_parDim1_geoDim1); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-12); -} - -TEST_F(BSplineTest, UniformBSpline_eval_parDim1_geoDim1_degrees3) { - iganet::UniformBSpline geo({11}, iganet::init::greville, - options); - iganet::UniformBSpline bspline({11}, iganet::init::zeros, - options); - bspline.transform(trafo_parDim1_geoDim1); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-12); -} - -TEST_F(BSplineTest, UniformBSpline_eval_parDim1_geoDim1_degrees4) { - iganet::UniformBSpline geo({11}, iganet::init::greville, - options); - iganet::UniformBSpline bspline({11}, iganet::init::zeros, - options); - bspline.transform(trafo_parDim1_geoDim1); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-10); -} - -TEST_F(BSplineTest, UniformBSpline_eval_parDim1_geoDim1_degrees5) { - iganet::UniformBSpline geo({11}, iganet::init::greville, - options); - iganet::UniformBSpline bspline({11}, iganet::init::zeros, - options); - bspline.transform(trafo_parDim1_geoDim1); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-10); -} - -TEST_F(BSplineTest, UniformBSpline_eval_parDim1_geoDim1_degrees6) { - iganet::UniformBSpline geo({11}, iganet::init::greville, - options); - iganet::UniformBSpline bspline({11}, iganet::init::zeros, - options); - bspline.transform(trafo_parDim1_geoDim1); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-10); -} - -TEST_F(BSplineTest, UniformBSpline_eval_parDim1_geoDim2_degrees1) { - iganet::UniformBSpline geo({11}, iganet::init::greville, - options); - iganet::UniformBSpline bspline({11}, iganet::init::zeros, - options); - bspline.transform(trafo_parDim1_geoDim2); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-12); -} - -TEST_F(BSplineTest, UniformBSpline_eval_parDim1_geoDim2_degrees2) { - iganet::UniformBSpline geo({11}, iganet::init::greville, - options); - iganet::UniformBSpline bspline({11}, iganet::init::zeros, - options); - bspline.transform(trafo_parDim1_geoDim2); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-12); -} - -TEST_F(BSplineTest, UniformBSpline_eval_parDim1_geoDim2_degrees3) { - iganet::UniformBSpline geo({11}, iganet::init::greville, - options); - iganet::UniformBSpline bspline({11}, iganet::init::zeros, - options); - bspline.transform(trafo_parDim1_geoDim2); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-12); -} - -TEST_F(BSplineTest, UniformBSpline_eval_parDim1_geoDim2_degrees4) { - iganet::UniformBSpline geo({11}, iganet::init::greville, - options); - iganet::UniformBSpline bspline({11}, iganet::init::zeros, - options); - bspline.transform(trafo_parDim1_geoDim2); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-10); -} - -TEST_F(BSplineTest, UniformBSpline_eval_parDim1_geoDim2_degrees5) { - iganet::UniformBSpline geo({11}, iganet::init::greville, - options); - iganet::UniformBSpline bspline({11}, iganet::init::zeros, - options); - bspline.transform(trafo_parDim1_geoDim2); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-10); -} - -TEST_F(BSplineTest, UniformBSpline_eval_parDim1_geoDim2_degrees6) { - iganet::UniformBSpline geo({11}, iganet::init::greville, - options); - iganet::UniformBSpline bspline({11}, iganet::init::zeros, - options); - bspline.transform(trafo_parDim1_geoDim2); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-10); -} - -TEST_F(BSplineTest, UniformBSpline_eval_parDim1_geoDim3_degrees1) { - iganet::UniformBSpline geo({11}, iganet::init::greville, - options); - iganet::UniformBSpline bspline({11}, iganet::init::zeros, - options); - bspline.transform(trafo_parDim1_geoDim3); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-12); -} - -TEST_F(BSplineTest, UniformBSpline_eval_parDim1_geoDim3_degrees2) { - iganet::UniformBSpline geo({11}, iganet::init::greville, - options); - iganet::UniformBSpline bspline({11}, iganet::init::zeros, - options); - bspline.transform(trafo_parDim1_geoDim3); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-12); -} - -TEST_F(BSplineTest, UniformBSpline_eval_parDim1_geoDim3_degrees3) { - iganet::UniformBSpline geo({11}, iganet::init::greville, - options); - iganet::UniformBSpline bspline({11}, iganet::init::zeros, - options); - bspline.transform(trafo_parDim1_geoDim3); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-12); -} - -TEST_F(BSplineTest, UniformBSpline_eval_parDim1_geoDim3_degrees4) { - iganet::UniformBSpline geo({11}, iganet::init::greville, - options); - iganet::UniformBSpline bspline({11}, iganet::init::zeros, - options); - bspline.transform(trafo_parDim1_geoDim3); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-10); -} - -TEST_F(BSplineTest, UniformBSpline_eval_parDim1_geoDim3_degrees5) { - iganet::UniformBSpline geo({11}, iganet::init::greville, - options); - iganet::UniformBSpline bspline({11}, iganet::init::zeros, - options); - bspline.transform(trafo_parDim1_geoDim3); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-10); -} - -TEST_F(BSplineTest, UniformBSpline_eval_parDim1_geoDim3_degrees6) { - iganet::UniformBSpline geo({11}, iganet::init::greville, - options); - iganet::UniformBSpline bspline({11}, iganet::init::zeros, - options); - bspline.transform(trafo_parDim1_geoDim3); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-10); -} - -TEST_F(BSplineTest, UniformBSpline_eval_parDim1_geoDim4_degrees1) { - iganet::UniformBSpline geo({11}, iganet::init::greville, - options); - iganet::UniformBSpline bspline({11}, iganet::init::zeros, - options); - bspline.transform(trafo_parDim1_geoDim4); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-12); -} - -TEST_F(BSplineTest, UniformBSpline_eval_parDim1_geoDim4_degrees2) { - iganet::UniformBSpline geo({11}, iganet::init::greville, - options); - iganet::UniformBSpline bspline({11}, iganet::init::zeros, - options); - bspline.transform(trafo_parDim1_geoDim4); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-12); -} - -TEST_F(BSplineTest, UniformBSpline_eval_parDim1_geoDim4_degrees3) { - iganet::UniformBSpline geo({11}, iganet::init::greville, - options); - iganet::UniformBSpline bspline({11}, iganet::init::zeros, - options); - bspline.transform(trafo_parDim1_geoDim4); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-12); -} - -TEST_F(BSplineTest, UniformBSpline_eval_parDim1_geoDim4_degrees4) { - iganet::UniformBSpline geo({11}, iganet::init::greville, - options); - iganet::UniformBSpline bspline({11}, iganet::init::zeros, - options); - bspline.transform(trafo_parDim1_geoDim4); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-10); -} - -TEST_F(BSplineTest, UniformBSpline_eval_parDim1_geoDim4_degrees5) { - iganet::UniformBSpline geo({11}, iganet::init::greville, - options); - iganet::UniformBSpline bspline({11}, iganet::init::zeros, - options); - bspline.transform(trafo_parDim1_geoDim4); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-10); -} - -TEST_F(BSplineTest, UniformBSpline_eval_parDim1_geoDim4_degrees6) { - iganet::UniformBSpline geo({11}, iganet::init::greville, - options); - iganet::UniformBSpline bspline({11}, iganet::init::zeros, - options); - bspline.transform(trafo_parDim1_geoDim4); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-10); -} - -TEST_F(BSplineTest, UniformBSpline_eval_parDim2_geoDim1_degrees22) { - iganet::UniformBSpline geo({6, 5}, iganet::init::greville, - options); - iganet::UniformBSpline bspline({6, 5}, iganet::init::zeros, - options); - bspline.transform(trafo_parDim2_geoDim1); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-12); -} - -TEST_F(BSplineTest, UniformBSpline_eval_parDim2_geoDim1_degrees46) { - iganet::UniformBSpline geo({5, 11}, iganet::init::greville, - options); - iganet::UniformBSpline bspline({5, 11}, iganet::init::zeros, - options); - bspline.transform(trafo_parDim2_geoDim1); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-10); -} - -TEST_F(BSplineTest, UniformBSpline_eval_parDim2_geoDim1_degrees64) { - iganet::UniformBSpline geo({11, 5}, iganet::init::greville, - options); - iganet::UniformBSpline bspline({11, 5}, iganet::init::zeros, - options); - bspline.transform(trafo_parDim2_geoDim1); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-10); -} - -TEST_F(BSplineTest, UniformBSpline_eval_parDim2_geoDim2_degrees22) { - iganet::UniformBSpline geo({6, 5}, iganet::init::greville, - options); - iganet::UniformBSpline bspline({6, 5}, iganet::init::zeros, - options); - bspline.transform(trafo_parDim2_geoDim2); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-12); -} - -TEST_F(BSplineTest, UniformBSpline_eval_parDim2_geoDim2_degrees46) { - iganet::UniformBSpline geo({5, 11}, iganet::init::greville, - options); - iganet::UniformBSpline bspline({5, 11}, iganet::init::zeros, - options); - bspline.transform(trafo_parDim2_geoDim2); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-10); -} - -TEST_F(BSplineTest, UniformBSpline_eval_parDim2_geoDim2_degrees64) { - iganet::UniformBSpline geo({11, 5}, iganet::init::greville, - options); - iganet::UniformBSpline bspline({11, 5}, iganet::init::zeros, - options); - bspline.transform(trafo_parDim2_geoDim2); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-10); -} - -TEST_F(BSplineTest, UniformBSpline_eval_parDim2_geoDim3_degrees22) { - iganet::UniformBSpline geo({6, 5}, iganet::init::greville, - options); - iganet::UniformBSpline bspline({6, 5}, iganet::init::zeros, - options); - bspline.transform(trafo_parDim2_geoDim3); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-12); -} - -TEST_F(BSplineTest, UniformBSpline_eval_parDim2_geoDim3_degrees46) { - iganet::UniformBSpline geo({5, 11}, iganet::init::greville, - options); - iganet::UniformBSpline bspline({5, 11}, iganet::init::zeros, - options); - bspline.transform(trafo_parDim2_geoDim3); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-10); -} - -TEST_F(BSplineTest, UniformBSpline_eval_parDim2_geoDim3_degrees64) { - iganet::UniformBSpline geo({11, 5}, iganet::init::greville, - options); - iganet::UniformBSpline bspline({11, 5}, iganet::init::zeros, - options); - bspline.transform(trafo_parDim2_geoDim3); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-10); -} - -TEST_F(BSplineTest, UniformBSpline_eval_parDim2_geoDim4_degrees22) { - iganet::UniformBSpline geo({6, 5}, iganet::init::greville, - options); - iganet::UniformBSpline bspline({6, 5}, iganet::init::zeros, - options); - bspline.transform(trafo_parDim2_geoDim4); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-12); -} - -TEST_F(BSplineTest, UniformBSpline_eval_parDim2_geoDim4_degrees46) { - iganet::UniformBSpline geo({5, 11}, iganet::init::greville, - options); - iganet::UniformBSpline bspline({5, 11}, iganet::init::zeros, - options); - bspline.transform(trafo_parDim2_geoDim4); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-10); -} - -TEST_F(BSplineTest, UniformBSpline_eval_parDim2_geoDim4_degrees64) { - iganet::UniformBSpline geo({11, 5}, iganet::init::greville, - options); - iganet::UniformBSpline bspline({11, 5}, iganet::init::zeros, - options); - bspline.transform(trafo_parDim2_geoDim4); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-10); -} - -TEST_F(BSplineTest, UniformBSpline_eval_parDim3_geoDim1_degrees222) { - iganet::UniformBSpline geo( - {11, 5, 3}, iganet::init::greville, options); - iganet::UniformBSpline bspline( - {11, 5, 3}, iganet::init::zeros, options); - bspline.transform(trafo_parDim3_geoDim1); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-12); -} - -TEST_F(BSplineTest, UniformBSpline_eval_parDim3_geoDim1_degrees264) { - iganet::UniformBSpline geo( - {3, 11, 5}, iganet::init::greville, options); - iganet::UniformBSpline bspline( - {3, 11, 5}, iganet::init::zeros, options); - bspline.transform(trafo_parDim3_geoDim1); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-10); -} - -TEST_F(BSplineTest, UniformBSpline_eval_parDim3_geoDim2_degrees222) { - iganet::UniformBSpline geo( - {11, 5, 3}, iganet::init::greville, options); - iganet::UniformBSpline bspline( - {11, 5, 3}, iganet::init::zeros, options); - bspline.transform(trafo_parDim3_geoDim2); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-12); -} - -TEST_F(BSplineTest, UniformBSpline_eval_parDim3_geoDim2_degrees264) { - iganet::UniformBSpline geo( - {3, 11, 5}, iganet::init::greville, options); - iganet::UniformBSpline bspline( - {3, 11, 5}, iganet::init::zeros, options); - bspline.transform(trafo_parDim3_geoDim2); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-10); -} - -TEST_F(BSplineTest, UniformBSpline_eval_parDim3_geoDim3_degrees222) { - iganet::UniformBSpline geo( - {11, 5, 3}, iganet::init::greville, options); - iganet::UniformBSpline bspline( - {11, 5, 3}, iganet::init::zeros, options); - bspline.transform(trafo_parDim3_geoDim3); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-12); -} - -TEST_F(BSplineTest, UniformBSpline_eval_parDim3_geoDim3_degrees264) { - iganet::UniformBSpline geo( - {3, 11, 5}, iganet::init::greville, options); - iganet::UniformBSpline bspline( - {3, 11, 5}, iganet::init::zeros, options); - bspline.transform(trafo_parDim3_geoDim3); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-10); -} - -TEST_F(BSplineTest, UniformBSpline_eval_parDim3_geoDim4_degrees222) { - iganet::UniformBSpline geo( - {11, 5, 3}, iganet::init::greville, options); - iganet::UniformBSpline bspline( - {11, 5, 3}, iganet::init::zeros, options); - bspline.transform(trafo_parDim3_geoDim4); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-12); -} - -TEST_F(BSplineTest, UniformBSpline_eval_parDim3_geoDim4_degrees264) { - iganet::UniformBSpline geo( - {3, 11, 5}, iganet::init::greville, options); - iganet::UniformBSpline bspline( - {3, 11, 5}, iganet::init::zeros, options); - bspline.transform(trafo_parDim3_geoDim4); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-10); -} - -TEST_F(BSplineTest, UniformBSpline_eval_parDim4_geoDim1_degrees2222) { - iganet::UniformBSpline geo( - {11, 5, 3, 8}, iganet::init::greville, options); - iganet::UniformBSpline bspline( - {11, 5, 3, 8}, iganet::init::zeros, options); - bspline.transform(trafo_parDim4_geoDim1); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-12); -} - -TEST_F(BSplineTest, UniformBSpline_eval_parDim4_geoDim1_degrees2643) { - iganet::UniformBSpline geo( - {3, 11, 5, 8}, iganet::init::greville, options); - iganet::UniformBSpline bspline( - {3, 11, 5, 8}, iganet::init::zeros, options); - bspline.transform(trafo_parDim4_geoDim1); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-12); -} - -TEST_F(BSplineTest, UniformBSpline_eval_parDim4_geoDim2_degrees2222) { - iganet::UniformBSpline geo( - {11, 5, 3, 8}, iganet::init::greville, options); - iganet::UniformBSpline bspline( - {11, 5, 3, 8}, iganet::init::zeros, options); - bspline.transform(trafo_parDim4_geoDim2); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-12); -} - -TEST_F(BSplineTest, UniformBSpline_eval_parDim4_geoDim2_degrees2643) { - iganet::UniformBSpline geo( - {3, 11, 5, 8}, iganet::init::greville, options); - iganet::UniformBSpline bspline( - {3, 11, 5, 8}, iganet::init::zeros, options); - bspline.transform(trafo_parDim4_geoDim2); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-12); -} - -TEST_F(BSplineTest, UniformBSpline_eval_parDim4_geoDim3_degrees2222) { - iganet::UniformBSpline geo( - {11, 5, 3, 8}, iganet::init::greville, options); - iganet::UniformBSpline bspline( - {11, 5, 3, 8}, iganet::init::zeros, options); - bspline.transform(trafo_parDim4_geoDim3); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-12); -} - -TEST_F(BSplineTest, UniformBSpline_eval_parDim4_geoDim3_degrees2643) { - iganet::UniformBSpline geo( - {3, 11, 5, 8}, iganet::init::greville, options); - iganet::UniformBSpline bspline( - {3, 11, 5, 8}, iganet::init::zeros, options); - bspline.transform(trafo_parDim4_geoDim3); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-12); -} - -TEST_F(BSplineTest, UniformBSpline_eval_parDim4_geoDim4_degrees2222) { - iganet::UniformBSpline geo( - {11, 5, 3, 8}, iganet::init::greville, options); - iganet::UniformBSpline bspline( - {11, 5, 3, 8}, iganet::init::zeros, options); - bspline.transform(trafo_parDim4_geoDim4); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-12); -} - -TEST_F(BSplineTest, UniformBSpline_eval_parDim4_geoDim4_degrees2643) { - iganet::UniformBSpline geo( - {3, 11, 5, 8}, iganet::init::greville, options); - iganet::UniformBSpline bspline( - {3, 11, 5, 8}, iganet::init::zeros, options); - bspline.transform(trafo_parDim4_geoDim4); - auto xi = iganet::utils::to_tensorArray( - options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, - {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); - test_bspline_eval(geo, bspline, xi, 1e-12); -} - -int main(int argc, char **argv) { - ::testing::InitGoogleTest(&argc, argv); - iganet::init(); - return RUN_ALL_TESTS(); -} +/** + @file unittests/unittest_uniform_bspline_eval.cxx + + @brief B-Spline unittests + + @author Matthias Moller + + @copyright This file is part of the IgANet project + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#include +#include +#include + +#include +#include +#include + +using namespace iganet::unittests::literals; + +class BSplineTest : public ::testing::Test { +protected: + using real_t = iganet::unittests::real_t; + iganet::Options options; + + static constexpr auto trafo_parDim1_geoDim1 = + [](const std::array xi) { + return std::array{xi[0] * xi[0]}; + }; + static constexpr auto trafo_parDim1_geoDim2 = + [](const std::array xi) { + return std::array{xi[0] * xi[0], + sin(static_cast(M_PI) * xi[0])}; + }; + static constexpr auto trafo_parDim1_geoDim3 = + [](const std::array xi) { + return std::array{ + xi[0] * xi[0], sin(static_cast(M_PI) * xi[0]), xi[0]}; + }; + static constexpr auto trafo_parDim1_geoDim4 = + [](const std::array xi) { + return std::array{ + xi[0] * xi[0], sin(static_cast(M_PI) * xi[0]), xi[0], + cos(static_cast(M_PI) * xi[0])}; + }; + + static constexpr auto trafo_parDim2_geoDim1 = + [](const std::array xi) { + return std::array{xi[0] * xi[1]}; + }; + static constexpr auto trafo_parDim2_geoDim2 = + [](const std::array xi) { + return std::array{xi[0] * xi[1], + sin(static_cast(M_PI) * xi[0])}; + }; + static constexpr auto trafo_parDim2_geoDim3 = + [](const std::array xi) { + return std::array{ + xi[0] * xi[1], sin(static_cast(M_PI) * xi[0]), xi[1]}; + }; + static constexpr auto trafo_parDim2_geoDim4 = + [](const std::array xi) { + return std::array{ + xi[0] * xi[1], sin(static_cast(M_PI) * xi[0]), xi[1], + cos(static_cast(M_PI) * xi[1])}; + }; + + static constexpr auto trafo_parDim3_geoDim1 = + [](const std::array xi) { + return std::array{xi[0] * xi[1] * xi[2]}; + }; + static constexpr auto trafo_parDim3_geoDim2 = + [](const std::array xi) { + return std::array{xi[0] * xi[1] * xi[2], + sin(static_cast(M_PI) * xi[0])}; + }; + static constexpr auto trafo_parDim3_geoDim3 = + [](const std::array xi) { + return std::array{xi[0] * xi[1] * xi[2], + sin(static_cast(M_PI) * xi[0]), + xi[1] * xi[2]}; + }; + static constexpr auto trafo_parDim3_geoDim4 = + [](const std::array xi) { + return std::array{ + xi[0] * xi[1] * xi[2], sin(static_cast(M_PI) * xi[0]), + xi[1] * xi[2], cos(static_cast(M_PI) * xi[1])}; + }; + + static constexpr auto trafo_parDim4_geoDim1 = + [](const std::array xi) { + return std::array{xi[0] * xi[1] * xi[2] * xi[3]}; + }; + static constexpr auto trafo_parDim4_geoDim2 = + [](const std::array xi) { + return std::array{xi[0] * xi[1] * xi[2] * xi[3], + sin(static_cast(M_PI) * xi[0])}; + }; + static constexpr auto trafo_parDim4_geoDim3 = + [](const std::array xi) { + return std::array{xi[0] * xi[1] * xi[2] * xi[3], + sin(static_cast(M_PI) * xi[0]), + xi[1] * xi[2] * xi[3]}; + }; + static constexpr auto trafo_parDim4_geoDim4 = + [](const std::array xi) { + return std::array{xi[0] * xi[1] * xi[2] * xi[3], + sin(static_cast(M_PI) * xi[0]), + xi[1] * xi[2] * xi[3], + cos(static_cast(M_PI) * xi[1])}; + }; +}; + +TEST_F(BSplineTest, UniformBSpline_eval_parDim1_geoDim1_degrees1) { + iganet::UniformBSpline geo({11}, iganet::init::greville, + options); + iganet::UniformBSpline bspline({11}, iganet::init::zeros, + options); + bspline.transform(trafo_parDim1_geoDim1); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, UniformBSpline_eval_parDim1_geoDim1_degrees2) { + iganet::UniformBSpline geo({11}, iganet::init::greville, + options); + iganet::UniformBSpline bspline({11}, iganet::init::zeros, + options); + bspline.transform(trafo_parDim1_geoDim1); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, UniformBSpline_eval_parDim1_geoDim1_degrees3) { + iganet::UniformBSpline geo({11}, iganet::init::greville, + options); + iganet::UniformBSpline bspline({11}, iganet::init::zeros, + options); + bspline.transform(trafo_parDim1_geoDim1); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, UniformBSpline_eval_parDim1_geoDim1_degrees4) { + iganet::UniformBSpline geo({11}, iganet::init::greville, + options); + iganet::UniformBSpline bspline({11}, iganet::init::zeros, + options); + bspline.transform(trafo_parDim1_geoDim1); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-10); +} + +TEST_F(BSplineTest, UniformBSpline_eval_parDim1_geoDim1_degrees5) { + iganet::UniformBSpline geo({11}, iganet::init::greville, + options); + iganet::UniformBSpline bspline({11}, iganet::init::zeros, + options); + bspline.transform(trafo_parDim1_geoDim1); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-10); +} + +TEST_F(BSplineTest, UniformBSpline_eval_parDim1_geoDim1_degrees6) { + iganet::UniformBSpline geo({11}, iganet::init::greville, + options); + iganet::UniformBSpline bspline({11}, iganet::init::zeros, + options); + bspline.transform(trafo_parDim1_geoDim1); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-10); +} + +TEST_F(BSplineTest, UniformBSpline_eval_parDim1_geoDim2_degrees1) { + iganet::UniformBSpline geo({11}, iganet::init::greville, + options); + iganet::UniformBSpline bspline({11}, iganet::init::zeros, + options); + bspline.transform(trafo_parDim1_geoDim2); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, UniformBSpline_eval_parDim1_geoDim2_degrees2) { + iganet::UniformBSpline geo({11}, iganet::init::greville, + options); + iganet::UniformBSpline bspline({11}, iganet::init::zeros, + options); + bspline.transform(trafo_parDim1_geoDim2); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, UniformBSpline_eval_parDim1_geoDim2_degrees3) { + iganet::UniformBSpline geo({11}, iganet::init::greville, + options); + iganet::UniformBSpline bspline({11}, iganet::init::zeros, + options); + bspline.transform(trafo_parDim1_geoDim2); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, UniformBSpline_eval_parDim1_geoDim2_degrees4) { + iganet::UniformBSpline geo({11}, iganet::init::greville, + options); + iganet::UniformBSpline bspline({11}, iganet::init::zeros, + options); + bspline.transform(trafo_parDim1_geoDim2); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-10); +} + +TEST_F(BSplineTest, UniformBSpline_eval_parDim1_geoDim2_degrees5) { + iganet::UniformBSpline geo({11}, iganet::init::greville, + options); + iganet::UniformBSpline bspline({11}, iganet::init::zeros, + options); + bspline.transform(trafo_parDim1_geoDim2); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-10); +} + +TEST_F(BSplineTest, UniformBSpline_eval_parDim1_geoDim2_degrees6) { + iganet::UniformBSpline geo({11}, iganet::init::greville, + options); + iganet::UniformBSpline bspline({11}, iganet::init::zeros, + options); + bspline.transform(trafo_parDim1_geoDim2); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-10); +} + +TEST_F(BSplineTest, UniformBSpline_eval_parDim1_geoDim3_degrees1) { + iganet::UniformBSpline geo({11}, iganet::init::greville, + options); + iganet::UniformBSpline bspline({11}, iganet::init::zeros, + options); + bspline.transform(trafo_parDim1_geoDim3); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, UniformBSpline_eval_parDim1_geoDim3_degrees2) { + iganet::UniformBSpline geo({11}, iganet::init::greville, + options); + iganet::UniformBSpline bspline({11}, iganet::init::zeros, + options); + bspline.transform(trafo_parDim1_geoDim3); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, UniformBSpline_eval_parDim1_geoDim3_degrees3) { + iganet::UniformBSpline geo({11}, iganet::init::greville, + options); + iganet::UniformBSpline bspline({11}, iganet::init::zeros, + options); + bspline.transform(trafo_parDim1_geoDim3); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, UniformBSpline_eval_parDim1_geoDim3_degrees4) { + iganet::UniformBSpline geo({11}, iganet::init::greville, + options); + iganet::UniformBSpline bspline({11}, iganet::init::zeros, + options); + bspline.transform(trafo_parDim1_geoDim3); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-10); +} + +TEST_F(BSplineTest, UniformBSpline_eval_parDim1_geoDim3_degrees5) { + iganet::UniformBSpline geo({11}, iganet::init::greville, + options); + iganet::UniformBSpline bspline({11}, iganet::init::zeros, + options); + bspline.transform(trafo_parDim1_geoDim3); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-10); +} + +TEST_F(BSplineTest, UniformBSpline_eval_parDim1_geoDim3_degrees6) { + iganet::UniformBSpline geo({11}, iganet::init::greville, + options); + iganet::UniformBSpline bspline({11}, iganet::init::zeros, + options); + bspline.transform(trafo_parDim1_geoDim3); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-10); +} + +TEST_F(BSplineTest, UniformBSpline_eval_parDim1_geoDim4_degrees1) { + iganet::UniformBSpline geo({11}, iganet::init::greville, + options); + iganet::UniformBSpline bspline({11}, iganet::init::zeros, + options); + bspline.transform(trafo_parDim1_geoDim4); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, UniformBSpline_eval_parDim1_geoDim4_degrees2) { + iganet::UniformBSpline geo({11}, iganet::init::greville, + options); + iganet::UniformBSpline bspline({11}, iganet::init::zeros, + options); + bspline.transform(trafo_parDim1_geoDim4); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, UniformBSpline_eval_parDim1_geoDim4_degrees3) { + iganet::UniformBSpline geo({11}, iganet::init::greville, + options); + iganet::UniformBSpline bspline({11}, iganet::init::zeros, + options); + bspline.transform(trafo_parDim1_geoDim4); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, UniformBSpline_eval_parDim1_geoDim4_degrees4) { + iganet::UniformBSpline geo({11}, iganet::init::greville, + options); + iganet::UniformBSpline bspline({11}, iganet::init::zeros, + options); + bspline.transform(trafo_parDim1_geoDim4); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-10); +} + +TEST_F(BSplineTest, UniformBSpline_eval_parDim1_geoDim4_degrees5) { + iganet::UniformBSpline geo({11}, iganet::init::greville, + options); + iganet::UniformBSpline bspline({11}, iganet::init::zeros, + options); + bspline.transform(trafo_parDim1_geoDim4); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-10); +} + +TEST_F(BSplineTest, UniformBSpline_eval_parDim1_geoDim4_degrees6) { + iganet::UniformBSpline geo({11}, iganet::init::greville, + options); + iganet::UniformBSpline bspline({11}, iganet::init::zeros, + options); + bspline.transform(trafo_parDim1_geoDim4); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-10); +} + +TEST_F(BSplineTest, UniformBSpline_eval_parDim2_geoDim1_degrees22) { + iganet::UniformBSpline geo({6, 5}, iganet::init::greville, + options); + iganet::UniformBSpline bspline({6, 5}, iganet::init::zeros, + options); + bspline.transform(trafo_parDim2_geoDim1); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, UniformBSpline_eval_parDim2_geoDim1_degrees46) { + iganet::UniformBSpline geo({5, 11}, iganet::init::greville, + options); + iganet::UniformBSpline bspline({5, 11}, iganet::init::zeros, + options); + bspline.transform(trafo_parDim2_geoDim1); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-10); +} + +TEST_F(BSplineTest, UniformBSpline_eval_parDim2_geoDim1_degrees64) { + iganet::UniformBSpline geo({11, 5}, iganet::init::greville, + options); + iganet::UniformBSpline bspline({11, 5}, iganet::init::zeros, + options); + bspline.transform(trafo_parDim2_geoDim1); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-10); +} + +TEST_F(BSplineTest, UniformBSpline_eval_parDim2_geoDim2_degrees22) { + iganet::UniformBSpline geo({6, 5}, iganet::init::greville, + options); + iganet::UniformBSpline bspline({6, 5}, iganet::init::zeros, + options); + bspline.transform(trafo_parDim2_geoDim2); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, UniformBSpline_eval_parDim2_geoDim2_degrees46) { + iganet::UniformBSpline geo({5, 11}, iganet::init::greville, + options); + iganet::UniformBSpline bspline({5, 11}, iganet::init::zeros, + options); + bspline.transform(trafo_parDim2_geoDim2); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-10); +} + +TEST_F(BSplineTest, UniformBSpline_eval_parDim2_geoDim2_degrees64) { + iganet::UniformBSpline geo({11, 5}, iganet::init::greville, + options); + iganet::UniformBSpline bspline({11, 5}, iganet::init::zeros, + options); + bspline.transform(trafo_parDim2_geoDim2); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-10); +} + +TEST_F(BSplineTest, UniformBSpline_eval_parDim2_geoDim3_degrees22) { + iganet::UniformBSpline geo({6, 5}, iganet::init::greville, + options); + iganet::UniformBSpline bspline({6, 5}, iganet::init::zeros, + options); + bspline.transform(trafo_parDim2_geoDim3); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, UniformBSpline_eval_parDim2_geoDim3_degrees46) { + iganet::UniformBSpline geo({5, 11}, iganet::init::greville, + options); + iganet::UniformBSpline bspline({5, 11}, iganet::init::zeros, + options); + bspline.transform(trafo_parDim2_geoDim3); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-10); +} + +TEST_F(BSplineTest, UniformBSpline_eval_parDim2_geoDim3_degrees64) { + iganet::UniformBSpline geo({11, 5}, iganet::init::greville, + options); + iganet::UniformBSpline bspline({11, 5}, iganet::init::zeros, + options); + bspline.transform(trafo_parDim2_geoDim3); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-10); +} + +TEST_F(BSplineTest, UniformBSpline_eval_parDim2_geoDim4_degrees22) { + iganet::UniformBSpline geo({6, 5}, iganet::init::greville, + options); + iganet::UniformBSpline bspline({6, 5}, iganet::init::zeros, + options); + bspline.transform(trafo_parDim2_geoDim4); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, UniformBSpline_eval_parDim2_geoDim4_degrees46) { + iganet::UniformBSpline geo({5, 11}, iganet::init::greville, + options); + iganet::UniformBSpline bspline({5, 11}, iganet::init::zeros, + options); + bspline.transform(trafo_parDim2_geoDim4); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-10); +} + +TEST_F(BSplineTest, UniformBSpline_eval_parDim2_geoDim4_degrees64) { + iganet::UniformBSpline geo({11, 5}, iganet::init::greville, + options); + iganet::UniformBSpline bspline({11, 5}, iganet::init::zeros, + options); + bspline.transform(trafo_parDim2_geoDim4); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-10); +} + +TEST_F(BSplineTest, UniformBSpline_eval_parDim3_geoDim1_degrees222) { + iganet::UniformBSpline geo( + {11, 5, 3}, iganet::init::greville, options); + iganet::UniformBSpline bspline( + {11, 5, 3}, iganet::init::zeros, options); + bspline.transform(trafo_parDim3_geoDim1); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, UniformBSpline_eval_parDim3_geoDim1_degrees264) { + iganet::UniformBSpline geo( + {3, 11, 5}, iganet::init::greville, options); + iganet::UniformBSpline bspline( + {3, 11, 5}, iganet::init::zeros, options); + bspline.transform(trafo_parDim3_geoDim1); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-10); +} + +TEST_F(BSplineTest, UniformBSpline_eval_parDim3_geoDim2_degrees222) { + iganet::UniformBSpline geo( + {11, 5, 3}, iganet::init::greville, options); + iganet::UniformBSpline bspline( + {11, 5, 3}, iganet::init::zeros, options); + bspline.transform(trafo_parDim3_geoDim2); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, UniformBSpline_eval_parDim3_geoDim2_degrees264) { + iganet::UniformBSpline geo( + {3, 11, 5}, iganet::init::greville, options); + iganet::UniformBSpline bspline( + {3, 11, 5}, iganet::init::zeros, options); + bspline.transform(trafo_parDim3_geoDim2); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-10); +} + +TEST_F(BSplineTest, UniformBSpline_eval_parDim3_geoDim3_degrees222) { + iganet::UniformBSpline geo( + {11, 5, 3}, iganet::init::greville, options); + iganet::UniformBSpline bspline( + {11, 5, 3}, iganet::init::zeros, options); + bspline.transform(trafo_parDim3_geoDim3); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, UniformBSpline_eval_parDim3_geoDim3_degrees264) { + iganet::UniformBSpline geo( + {3, 11, 5}, iganet::init::greville, options); + iganet::UniformBSpline bspline( + {3, 11, 5}, iganet::init::zeros, options); + bspline.transform(trafo_parDim3_geoDim3); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-10); +} + +TEST_F(BSplineTest, UniformBSpline_eval_parDim3_geoDim4_degrees222) { + iganet::UniformBSpline geo( + {11, 5, 3}, iganet::init::greville, options); + iganet::UniformBSpline bspline( + {11, 5, 3}, iganet::init::zeros, options); + bspline.transform(trafo_parDim3_geoDim4); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, UniformBSpline_eval_parDim3_geoDim4_degrees264) { + iganet::UniformBSpline geo( + {3, 11, 5}, iganet::init::greville, options); + iganet::UniformBSpline bspline( + {3, 11, 5}, iganet::init::zeros, options); + bspline.transform(trafo_parDim3_geoDim4); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-10); +} + +TEST_F(BSplineTest, UniformBSpline_eval_parDim4_geoDim1_degrees2222) { + iganet::UniformBSpline geo( + {11, 5, 3, 8}, iganet::init::greville, options); + iganet::UniformBSpline bspline( + {11, 5, 3, 8}, iganet::init::zeros, options); + bspline.transform(trafo_parDim4_geoDim1); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, UniformBSpline_eval_parDim4_geoDim1_degrees2643) { + iganet::UniformBSpline geo( + {3, 11, 5, 8}, iganet::init::greville, options); + iganet::UniformBSpline bspline( + {3, 11, 5, 8}, iganet::init::zeros, options); + bspline.transform(trafo_parDim4_geoDim1); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, UniformBSpline_eval_parDim4_geoDim2_degrees2222) { + iganet::UniformBSpline geo( + {11, 5, 3, 8}, iganet::init::greville, options); + iganet::UniformBSpline bspline( + {11, 5, 3, 8}, iganet::init::zeros, options); + bspline.transform(trafo_parDim4_geoDim2); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, UniformBSpline_eval_parDim4_geoDim2_degrees2643) { + iganet::UniformBSpline geo( + {3, 11, 5, 8}, iganet::init::greville, options); + iganet::UniformBSpline bspline( + {3, 11, 5, 8}, iganet::init::zeros, options); + bspline.transform(trafo_parDim4_geoDim2); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, UniformBSpline_eval_parDim4_geoDim3_degrees2222) { + iganet::UniformBSpline geo( + {11, 5, 3, 8}, iganet::init::greville, options); + iganet::UniformBSpline bspline( + {11, 5, 3, 8}, iganet::init::zeros, options); + bspline.transform(trafo_parDim4_geoDim3); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, UniformBSpline_eval_parDim4_geoDim3_degrees2643) { + iganet::UniformBSpline geo( + {3, 11, 5, 8}, iganet::init::greville, options); + iganet::UniformBSpline bspline( + {3, 11, 5, 8}, iganet::init::zeros, options); + bspline.transform(trafo_parDim4_geoDim3); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, UniformBSpline_eval_parDim4_geoDim4_degrees2222) { + iganet::UniformBSpline geo( + {11, 5, 3, 8}, iganet::init::greville, options); + iganet::UniformBSpline bspline( + {11, 5, 3, 8}, iganet::init::zeros, options); + bspline.transform(trafo_parDim4_geoDim4); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-12); +} + +TEST_F(BSplineTest, UniformBSpline_eval_parDim4_geoDim4_degrees2643) { + iganet::UniformBSpline geo( + {3, 11, 5, 8}, iganet::init::greville, options); + iganet::UniformBSpline bspline( + {3, 11, 5, 8}, iganet::init::zeros, options); + bspline.transform(trafo_parDim4_geoDim4); + auto xi = iganet::utils::to_tensorArray( + options, {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}, + {0.0_r, 0.1_r, 0.2_r, 0.5_r, 0.75_r, 0.9_r, 1.0_r}); + test_bspline_eval(geo, bspline, xi, 1e-12); +} + +int main(int argc, char **argv) { + ::testing::InitGoogleTest(&argc, argv); + iganet::init(); + return RUN_ALL_TESTS(); +} diff --git a/webapps/models/BSplineModel.hpp b/webapps/models/BSplineModel.hpp index 1ad0ea9e..c7abc7a0 100644 --- a/webapps/models/BSplineModel.hpp +++ b/webapps/models/BSplineModel.hpp @@ -1,733 +1,733 @@ -/** - @file webapps/models/BSplineModel.hpp - - @brief BSpline model - - @author Matthias Moller - - @copyright This file is part of the IgANet project - - This Source Code Form is subject to the terms of the Mozilla Public - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at http://mozilla.org/MPL/2.0/. -*/ - -#include -#include - -namespace iganet { - -namespace webapp { - -/// @brief Enumerator for specifying the degree of B-splines -enum class degree { - constant = 0, /*!< constant B-Spline basis functions */ - linear = 1, /*!< linear B-Spline basis functions */ - quadratic = 2, /*!< quadratic B-Spline basis functions */ - cubic = 3, /*!< cubic B-Spline basis functions */ - quartic = 4, /*!< quartic B-Spline basis functions */ - quintic = 5 /*!< quintic B-Spline basis functions */ -}; - -/// @brief B-spline model -template -class BSplineModel : public Model, - public ModelEval, - public ModelReparameterize, - public ModelRefine, - public ModelSerialize, - public ModelXML, - public BSpline_t { -private: - /// @brief Global offset vector - torch::Tensor offset_; - - /// @brief Global rotation vector - torch::Tensor rotation_; - - /// @brief "fake" solution vector - BSpline_t solution_; - -public: - /// @brief Default constructor - BSplineModel() - : offset_(torch::zeros({3}, Options{})), - rotation_( - torch::zeros({3}, Options{})) {} - - /// @brief Constructor for equidistant knot vectors - BSplineModel(const std::array ncoeffs, - enum iganet::init init = iganet::init::zeros) - : BSpline_t(ncoeffs, init), solution_(ncoeffs, init), - offset_(torch::zeros({3}, Options{})), - rotation_( - torch::zeros({3}, Options{})) { - if constexpr (BSpline_t::parDim() == 1) - solution_.transform([](const std::array - xi) { - return std::array{ - static_cast(std::sin(M_PI * xi[0])), 0.0, 0.0}; - }); - - else if constexpr (BSpline_t::parDim() == 2) - solution_.transform([](const std::array - xi) { - return std::array{ - static_cast(std::sin(M_PI * xi[0]) * - std::sin(M_PI * xi[1])), - 0.0, 0.0}; - }); - - else if constexpr (BSpline_t::parDim() == 3) - solution_.transform([](const std::array - xi) { - return std::array{ - static_cast(std::sin(M_PI * xi[0]) * - std::sin(M_PI * xi[1]) * - std::sin(M_PI * xi[2])), - 0.0, 0.0}; - }); - } - - /// @brief Destructor - ~BSplineModel() {} - - /// @brief Returns the model's name - std::string getName() const override { - if constexpr (BSpline_t::parDim() == 1) - return "BSplineCurve"; - else if constexpr (BSpline_t::parDim() == 2) - return "BSplineSurface"; - else if constexpr (BSpline_t::parDim() == 3) - return "BSplineVolume"; - else if constexpr (BSpline_t::parDim() == 4) - return "BSplineHyperVolume"; - else - return "invalidName"; - } - - /// @brief Returns the model's description - std::string getDescription() const override { - if constexpr (BSpline_t::parDim() == 1) - return "B-spline curve"; - else if constexpr (BSpline_t::parDim() == 2) - return "B-spline surface"; - else if constexpr (BSpline_t::parDim() == 3) - return "B-spline volume"; - else if constexpr (BSpline_t::parDim() == 4) - return "B-spline hypervolume"; - else - return "invalidDescription"; - } - - /// @brief Returns the model's options - nlohmann::json getOptions() const override { - if constexpr (BSpline_t::parDim() == 1) - return R"([{ - "name" : "degree", - "description" : "Polynomial degree of the B-spline", - "type" : "select", - "value" : ["constant", "linear", "quadratic", "cubic", "quartic", "quintic"], - "default" : 2, - "uiid" : 0},{ - "name" : "ncoeffs", - "description" : "Number of coefficients per parametric dimension", - "type" : ["int"], - "value" : [3], - "default" : [3], - "uiid" : 1},{ - "name" : "init", - "description" : "Initialization of the coefficients", - "type" : "select", - "value" : ["zeros", "ones", "linear", "random", "greville"], - "default" : 4, - "uiid" : 2},{ - "name" : "nonuniform", - "description" : "Create non-uniform B-spline", - "type" : "select", - "value" : ["false", "true"], - "default" : 0, - "uiid" : 3}])"_json; - - else if constexpr (BSpline_t::parDim() == 2) - return R"([{ - "name" : "degree", - "description" : "Polynomial degree of the B-spline", - "type" : "select", - "value" : ["constant", "linear", "quadratic", "cubic", "quartic", "quintic"], - "default" : 2, - "uiid" : 0},{ - "name" : "ncoeffs", - "description" : "Number of coefficients per parametric dimension", - "type" : ["int","int"], - "value" : [3,3], - "default" : [3,3], - "uiid" : 1},{ - "name" : "init", - "description" : "Initialization of the coefficients", - "type" : "select", - "value" : ["zeros", "ones", "linear", "random", "greville"], - "default" : 4, - "uiid" : 2},{ - "name" : "nonuniform", - "description" : "Create non-uniform B-spline", - "type" : "select", - "value" : ["false", "true"], - "default" : 0, - "uiid" : 3}])"_json; - - else if constexpr (BSpline_t::parDim() == 3) - return R"([{ - "name" : "degree", - "description" : "Polynomial degree of the B-spline", - "type" : "select", - "value" : ["constant", "linear", "quadratic", "cubic", "quartic", "quintic"], - "default" : 2, - "uiid" : 0},{ - "name" : "ncoeffs", - "description" : "Number of coefficients per parametric dimension", - "type" : ["int","int","int"], - "value" : [3,3,3], - "default" : [3,3,3], - "uiid" : 1},{ - "name" : "init", - "description" : "Initialization of the coefficients", - "type" : "select", - "value" : ["zeros", "ones", "linear", "random", "greville"], - "default" : 4, - "uiid" : 2},{ - "name" : "nonuniform", - "description" : "Create non-uniform B-spline", - "type" : "select", - "value" : ["false", "true"], - "default" : 0, - "uiid" : 3}])"_json; - - else if constexpr (BSpline_t::parDim() == 4) - return R"([{ - "name" : "degree", - "description" : "Polynomial degree of the B-spline", - "type" : "select", - "value" : ["constant", "linear", "quadratic", "cubic", "quartic", "quintic"], - "default" : 2, - "uiid" : 0},{ - "name" : "ncoeffs", - "description" : "Number of coefficients per parametric dimension", - "type" : [int,int,int,int], - "value" : [3,3,3,3], - "default" : [3,3,3,3], - "uiid" : 1},{ - "name" : "init", - "description" : "Initialization of the coefficients", - "type" : "select", - "value" : ["zeros", "ones", "linear", "random", "greville"], - "default" : 4, - "uiid" : 2},{ - "name" : "nonuniform", - "description" : "Create non-uniform B-spline", - "type" : "select", - "value" : ["false", "true"], - "default" : 0, - "uiid" : 3}])"_json; - - else - return R"({ INVALID REQUEST })"_json; - } - - /// @brief Returns the model's inputs - nlohmann::json getInputs() const override { - return R"([{ - "name" : "geometry", - "description" : "Geometry", - "type" : 2}])"_json; - } - - /// @brief Returns the model's outputs - nlohmann::json getOutputs() const override { - if constexpr (BSpline_t::geoDim() == 1) - return R"([{ - "name" : "ValueFieldMagnitude", - "description" : "Magnitude of the B-spline values", - "type" : 1}])"_json; - else - return R"([{ - "name" : "ValueFieldMagnitude", - "description" : "Magnitude of the B-spline values", - "type" : 1},{ - "name" : "ValueField", - "description" : "B-spline values", - "type" : 2}])"_json; - } - - /// @brief Serializes the model to JSON - nlohmann::json to_json(const std::string &component, - const std::string &attribute) const override { - - if (component == "geometry" || - component == "" /* might be removed in the future */) { - if (attribute != "") { - nlohmann::json json; - if (attribute == "degrees") - json["degrees"] = this->degrees(); - else if (attribute == "geoDim") - json["geoDim"] = this->geoDim(); - else if (attribute == "parDim") - json["parDim"] = this->parDim(); - else if (attribute == "ncoeffs") - json["ncoeffs"] = this->ncoeffs(); - else if (attribute == "nknots") - json["nknots"] = this->nknots(); - else if (attribute == "coeffs") - json["coeffs"] = this->coeffs_to_json(); - else if (attribute == "knots") - json["knots"] = this->knots_to_json(); - - return json; - - } else { - auto json = BSpline_t::to_json(); - json.update(Model::to_json("transform", ""), true); - - return json; - } - } - - else if (component == "solution") { - if (attribute != "") { - nlohmann::json json; - if (attribute == "degrees") - json["degrees"] = solution_.degrees(); - else if (attribute == "geoDim") - json["geoDim"] = solution_.geoDim(); - else if (attribute == "parDim") - json["parDim"] = solution_.parDim(); - else if (attribute == "ncoeffs") - json["ncoeffs"] = solution_.ncoeffs(); - else if (attribute == "nknots") - json["nknots"] = solution_.nknots(); - else if (attribute == "coeffs") - json["coeffs"] = solution_.coeffs_to_json(); - else if (attribute == "knots") - json["knots"] = solution_.knots_to_json(); - - return json; - } else - - return solution_.to_json(); - } - - else - return Model::to_json(component, attribute); - } - - /// @brief Updates the attributes of the model - nlohmann::json updateAttribute(const std::string &component, - const std::string &attribute, - const nlohmann::json &json) override { - if (attribute == "coeffs") { - if (!json.contains("data")) - throw InvalidModelAttributeException(); - if (!json["data"].contains("indices") || !json["data"].contains("coeffs")) - throw InvalidModelAttributeException(); - - auto indices = json["data"]["indices"].get>(); - auto coeffs_cpu = - utils::to_tensorAccessor( - BSpline_t::coeffs(), torch::kCPU); - - switch (BSpline_t::geoDim()) { - case (1): { - auto coords = - json["data"]["coeffs"] - .get>>(); - auto xAccessor = std::get<1>(coeffs_cpu)[0]; - - for (const auto &[index, coord] : iganet::utils::zip(indices, coords)) { - if (index < 0 || index >= BSpline_t::ncumcoeffs()) - throw IndexOutOfBoundsException(); - xAccessor[index] = std::get<0>(coord); - } - break; - } - case (2): { - auto coords = - json["data"]["coeffs"] - .get>>(); - auto xAccessor = std::get<1>(coeffs_cpu)[0]; - auto yAccessor = std::get<1>(coeffs_cpu)[1]; - - for (const auto &[index, coord] : iganet::utils::zip(indices, coords)) { - if (index < 0 || index >= BSpline_t::ncumcoeffs()) - throw IndexOutOfBoundsException(); - - xAccessor[index] = std::get<0>(coord); - yAccessor[index] = std::get<1>(coord); - } - break; - } - case (3): { - auto coords = - json["data"]["coeffs"] - .get>>(); - auto xAccessor = std::get<1>(coeffs_cpu)[0]; - auto yAccessor = std::get<1>(coeffs_cpu)[1]; - auto zAccessor = std::get<1>(coeffs_cpu)[2]; - - for (const auto &[index, coord] : iganet::utils::zip(indices, coords)) { - if (index < 0 || index >= BSpline_t::ncumcoeffs()) - throw IndexOutOfBoundsException(); - - xAccessor[index] = std::get<0>(coord); - yAccessor[index] = std::get<1>(coord); - zAccessor[index] = std::get<2>(coord); - } - break; - } - case (4): { - auto coords = - json["data"]["coeffs"] - .get>>(); - auto xAccessor = std::get<1>(coeffs_cpu)[0]; - auto yAccessor = std::get<1>(coeffs_cpu)[1]; - auto zAccessor = std::get<1>(coeffs_cpu)[2]; - auto tAccessor = std::get<1>(coeffs_cpu)[3]; - - for (const auto &[index, coord] : iganet::utils::zip(indices, coords)) { - if (index < 0 || index >= BSpline_t::ncumcoeffs()) - throw IndexOutOfBoundsException(); - - xAccessor[index] = std::get<0>(coord); - yAccessor[index] = std::get<1>(coord); - zAccessor[index] = std::get<2>(coord); - tAccessor[index] = std::get<3>(coord); - } - break; - } - default: - throw InvalidModelAttributeException(); - } - return "{}"; - } else - return Model::updateAttribute(component, attribute, json); - } - - /// @brief Evaluates the model - nlohmann::json eval(const std::string &component, - const nlohmann::json &json) const override { - - if constexpr (BSpline_t::parDim() == 1) { - - std::array res({25}); - if (json.contains("data")) - if (json["data"].contains("resolution")) - res = json["data"]["resolution"].get>(); - - utils::TensorArray1 xi = {torch::linspace(0, 1, res[0])}; - - if (component == "ValueFieldMagnitude") { - return nlohmann::json::array().emplace_back( - utils::to_json(*(solution_.eval(xi)[0]))); - } else if (component == "ValueField") { - auto values = BSpline_t::eval(xi); - auto result = nlohmann::json::array(); - for (short_t dim = 0; dim < BSpline_t::geoDim(); ++dim) - result.emplace_back( - utils::to_json(*(values[dim]))); - return result; - } else - return R"({ INVALID REQUEST })"_json; - } - - else if constexpr (BSpline_t::parDim() == 2) { - - std::array res({25, 25}); - if (json.contains("data")) - if (json["data"].contains("resolution")) - res = json["data"]["resolution"].get>(); - - utils::TensorArray2 xi = utils::to_array<2>(torch::meshgrid( - {torch::linspace(0, 1, res[0], - Options{}), - torch::linspace(0, 1, res[1], - Options{})}, - "xy")); - - if (component == "ValueFieldMagnitude") { - return nlohmann::json::array().emplace_back( - utils::to_json(*(solution_.eval(xi)[0]))); - } else if (component == "ValueField") { - auto values = BSpline_t::eval(xi); - auto result = nlohmann::json::array(); - for (short_t dim = 0; dim < BSpline_t::geoDim(); ++dim) - result.emplace_back( - utils::to_json(*(values[dim]))); - return result; - } else - return R"({ INVALID REQUEST })"_json; - } - - else if constexpr (BSpline_t::parDim() == 3) { - - std::array res({25, 25, 25}); - if (json.contains("data")) - if (json["data"].contains("resolution")) - res = json["data"]["resolution"].get>(); - - utils::TensorArray3 xi = utils::to_array<3>(torch::meshgrid( - {torch::linspace(0, 1, res[0], - Options{}), - torch::linspace(0, 1, res[1], - Options{}), - torch::linspace(0, 1, res[2], - Options{})}, - "xy")); - - if (component == "ValueFieldMagnitude") { - return nlohmann::json::array().emplace_back( - utils::to_json(*(solution_.eval(xi)[0]))); - } else if (component == "ValueField") { - auto values = BSpline_t::eval(xi); - auto result = nlohmann::json::array(); - for (short_t dim = 0; dim < BSpline_t::geoDim(); ++dim) - result.emplace_back( - utils::to_json(*(values[dim]))); - return result; - } else - return R"({ INVALID REQUEST })"_json; - } - - else if constexpr (BSpline_t::parDim() == 4) { - - std::array res({25, 25, 25, 25}); - if (json.contains("data")) - if (json["data"].contains("resolution")) - res = json["data"]["resolution"].get>(); - - utils::TensorArray4 xi = utils::to_array<4>(torch::meshgrid( - {torch::linspace(0, 1, res[0], - Options{}), - torch::linspace(0, 1, res[1], - Options{}), - torch::linspace(0, 1, res[2], - Options{}), - torch::linspace(0, 1, res[3], - Options{})}, - "xy")); - - if (component == "ValueFieldMagnitude") { - return nlohmann::json::array().emplace_back( - utils::to_json(*(solution_.eval(xi)[0]))); - } else if (component == "ValueField") { - auto values = BSpline_t::eval(xi); - auto result = nlohmann::json::array(); - for (short_t dim = 0; dim < BSpline_t::geoDim(); ++dim) - result.emplace_back( - utils::to_json(*(values[dim]))); - return result; - } else - return R"({ INVALID REQUEST })"_json; - } - } - - /// @brief Refines the model - void refine(const nlohmann::json &json = NULL) override { - int num = 1, dim = -1; - - if (json.contains("data")) { - if (json["data"].contains("num")) - num = json["data"]["num"].get(); - - if (json["data"].contains("dim")) - dim = json["data"]["dim"].get(); - } - - BSpline_t::uniform_refine(num, dim); - - solution_.uniform_refine(num, dim); - if constexpr (BSpline_t::parDim() == 1) - solution_.transform([](const std::array - xi) { - return std::array{ - static_cast(std::sin(M_PI * xi[0])), 0.0, 0.0}; - }); - - else if constexpr (BSpline_t::parDim() == 2) - solution_.transform([](const std::array - xi) { - return std::array{ - static_cast(std::sin(M_PI * xi[0]) * - std::sin(M_PI * xi[1])), - 0.0, 0.0}; - }); - - else if constexpr (BSpline_t::parDim() == 3) - solution_.transform([](const std::array - xi) { - return std::array{ - static_cast(std::sin(M_PI * xi[0]) * - std::sin(M_PI * xi[1]) * - std::sin(M_PI * xi[2])), - 0.0, 0.0}; - }); - } - - /// @brief Reparameterize the model - void reparameterize(const nlohmann::json &json = NULL) override { - - // gismo::gsBarrierPatch opt(geo_, false); - // opt.options().setInt("ParamMethod", 1); - // opt.compute(); - - // geo_ = opt.result(); - } - - /// @brief Loads model from LibTorch file - void load(const nlohmann::json &json) override { - - if (json.contains("data")) { - if (json["data"].contains("binary")) { - - // get binary vector from JSON object - std::vector binary = json["data"]["binary"]; - - // recover input archive from binary vector - torch::serialize::InputArchive archive; - archive.load_from(reinterpret_cast(binary.data()), - binary.size()); - - archive.read("transform", transform_); - BSpline_t::read(archive, "geometry"); - solution_.read(archive, "solution"); - - return; - } - } - - throw InvalidModelException(); - } - - /// @brief Saves model to LibTorch file - nlohmann::json save() const override { - - // serialize model to output archive - torch::serialize::OutputArchive archive; - archive.write("model", - static_cast(std::hash{}(getName()))); - archive.write("nonuniform", static_cast(BSpline_t::is_nonuniform())); - archive.write("transform", transform_); - - BSpline_t::write(archive, "geometry"); - solution_.write(archive, "solution"); - - // store output archive in binary vector - std::vector binary; - - archive.save_to( - [&binary](const void *data, size_t size) mutable -> std::size_t { - auto data_ = reinterpret_cast(data); - - for (std::size_t i = 0; i < size; ++i) - binary.push_back(data_[i]); - - return size; - }); - - // attach binary vector to JSON object - nlohmann::json json; - json["binary"] = binary; - - return json; - } - - /// @brief Imports the model from XML (as JSON object) - void importXML(const nlohmann::json &json, const std::string &component, - int id) override { - - if (json.contains("data")) { - if (json["data"].contains("xml")) { - - std::string xml = json["data"]["xml"].get(); - - pugi::xml_document doc; - pugi::xml_parse_result result = - doc.load_buffer(xml.c_str(), xml.size()); - - if (pugi::xml_node root = doc.child("xml")) - importXML(root, component, id); - else - throw std::runtime_error("No \"xml\" node in XML object"); - - return; - } - } - - throw std::runtime_error("No XML node in JSON object"); - } - - /// @brief Imports the model from XML (as XML object) - void importXML(const pugi::xml_node &xml, const std::string &component, - int id) override { - - if (component.empty()) { - BSpline_t::from_xml(xml, id, "geometry"); - solution_.from_xml(xml, id, "solution"); - iganet::utils::from_xml(xml, transform_, "Matrix", id, - "transform", false); - } else { - if (component == "geometry") { - BSpline_t::from_xml(xml, id, "geometry"); - iganet::utils::from_xml(xml, transform_, "Matrix", - id, "transform", false); - } else if (component == "solution") - solution_.from_xml(xml, id, "solution"); - else - throw std::runtime_error("Unsupported component"); - } - } - - /// @brief Exports the model to XML (as JSON object) - nlohmann::json exportXML(const std::string &component, int id) override { - - // serialize to XML - pugi::xml_document doc; - pugi::xml_node xml = doc.append_child("xml"); - xml = exportXML(xml, component, id); - - // serialize to JSON - std::ostringstream oss; - doc.save(oss); - - return oss.str(); - } - - /// @brief Exports the model to XML (as XML object) - pugi::xml_node &exportXML(pugi::xml_node &xml, const std::string &component, - int id) override { - - if (component.empty()) { - BSpline_t::to_xml(xml, id, "geometry"); - solution_.to_xml(xml, id, "solution"); - iganet::utils::to_xml(transform_, xml, "Matrix", id, - "transform"); - } else { - if (component == "geometry") { - BSpline_t::to_xml(xml, id, "geometry"); - iganet::utils::to_xml(transform_, xml, "Matrix", id, - "transform"); - } else if (component == "solution") - solution_.to_xml(xml, id, "solution"); - else - throw std::runtime_error("Unsupported component"); - } - return xml; - } -}; - -} // namespace webapp -} // namespace iganet +/** + @file webapps/models/BSplineModel.hpp + + @brief BSpline model + + @author Matthias Moller + + @copyright This file is part of the IgANet project + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#include +#include + +namespace iganet { + +namespace webapp { + +/// @brief Enumerator for specifying the degree of B-splines +enum class degree { + constant = 0, /*!< constant B-Spline basis functions */ + linear = 1, /*!< linear B-Spline basis functions */ + quadratic = 2, /*!< quadratic B-Spline basis functions */ + cubic = 3, /*!< cubic B-Spline basis functions */ + quartic = 4, /*!< quartic B-Spline basis functions */ + quintic = 5 /*!< quintic B-Spline basis functions */ +}; + +/// @brief B-spline model +template +class BSplineModel : public Model, + public ModelEval, + public ModelReparameterize, + public ModelRefine, + public ModelSerialize, + public ModelXML, + public BSpline_t { +private: + /// @brief Global offset vector + torch::Tensor offset_; + + /// @brief Global rotation vector + torch::Tensor rotation_; + + /// @brief "fake" solution vector + BSpline_t solution_; + +public: + /// @brief Default constructor + BSplineModel() + : offset_(torch::zeros({3}, Options{})), + rotation_( + torch::zeros({3}, Options{})) {} + + /// @brief Constructor for equidistant knot vectors + BSplineModel(const std::array ncoeffs, + enum iganet::init init = iganet::init::zeros) + : BSpline_t(ncoeffs, init), solution_(ncoeffs, init), + offset_(torch::zeros({3}, Options{})), + rotation_( + torch::zeros({3}, Options{})) { + if constexpr (BSpline_t::parDim() == 1) + solution_.transform([](const std::array + xi) { + return std::array{ + static_cast(std::sin(M_PI * xi[0])), 0.0, 0.0}; + }); + + else if constexpr (BSpline_t::parDim() == 2) + solution_.transform([](const std::array + xi) { + return std::array{ + static_cast(std::sin(M_PI * xi[0]) * + std::sin(M_PI * xi[1])), + 0.0, 0.0}; + }); + + else if constexpr (BSpline_t::parDim() == 3) + solution_.transform([](const std::array + xi) { + return std::array{ + static_cast(std::sin(M_PI * xi[0]) * + std::sin(M_PI * xi[1]) * + std::sin(M_PI * xi[2])), + 0.0, 0.0}; + }); + } + + /// @brief Destructor + ~BSplineModel() {} + + /// @brief Returns the model's name + std::string getName() const override { + if constexpr (BSpline_t::parDim() == 1) + return "BSplineCurve"; + else if constexpr (BSpline_t::parDim() == 2) + return "BSplineSurface"; + else if constexpr (BSpline_t::parDim() == 3) + return "BSplineVolume"; + else if constexpr (BSpline_t::parDim() == 4) + return "BSplineHyperVolume"; + else + return "invalidName"; + } + + /// @brief Returns the model's description + std::string getDescription() const override { + if constexpr (BSpline_t::parDim() == 1) + return "B-spline curve"; + else if constexpr (BSpline_t::parDim() == 2) + return "B-spline surface"; + else if constexpr (BSpline_t::parDim() == 3) + return "B-spline volume"; + else if constexpr (BSpline_t::parDim() == 4) + return "B-spline hypervolume"; + else + return "invalidDescription"; + } + + /// @brief Returns the model's options + nlohmann::json getOptions() const override { + if constexpr (BSpline_t::parDim() == 1) + return R"([{ + "name" : "degree", + "description" : "Polynomial degree of the B-spline", + "type" : "select", + "value" : ["constant", "linear", "quadratic", "cubic", "quartic", "quintic"], + "default" : 2, + "uiid" : 0},{ + "name" : "ncoeffs", + "description" : "Number of coefficients per parametric dimension", + "type" : ["int"], + "value" : [3], + "default" : [3], + "uiid" : 1},{ + "name" : "init", + "description" : "Initialization of the coefficients", + "type" : "select", + "value" : ["zeros", "ones", "linear", "random", "greville"], + "default" : 4, + "uiid" : 2},{ + "name" : "nonuniform", + "description" : "Create non-uniform B-spline", + "type" : "select", + "value" : ["false", "true"], + "default" : 0, + "uiid" : 3}])"_json; + + else if constexpr (BSpline_t::parDim() == 2) + return R"([{ + "name" : "degree", + "description" : "Polynomial degree of the B-spline", + "type" : "select", + "value" : ["constant", "linear", "quadratic", "cubic", "quartic", "quintic"], + "default" : 2, + "uiid" : 0},{ + "name" : "ncoeffs", + "description" : "Number of coefficients per parametric dimension", + "type" : ["int","int"], + "value" : [3,3], + "default" : [3,3], + "uiid" : 1},{ + "name" : "init", + "description" : "Initialization of the coefficients", + "type" : "select", + "value" : ["zeros", "ones", "linear", "random", "greville"], + "default" : 4, + "uiid" : 2},{ + "name" : "nonuniform", + "description" : "Create non-uniform B-spline", + "type" : "select", + "value" : ["false", "true"], + "default" : 0, + "uiid" : 3}])"_json; + + else if constexpr (BSpline_t::parDim() == 3) + return R"([{ + "name" : "degree", + "description" : "Polynomial degree of the B-spline", + "type" : "select", + "value" : ["constant", "linear", "quadratic", "cubic", "quartic", "quintic"], + "default" : 2, + "uiid" : 0},{ + "name" : "ncoeffs", + "description" : "Number of coefficients per parametric dimension", + "type" : ["int","int","int"], + "value" : [3,3,3], + "default" : [3,3,3], + "uiid" : 1},{ + "name" : "init", + "description" : "Initialization of the coefficients", + "type" : "select", + "value" : ["zeros", "ones", "linear", "random", "greville"], + "default" : 4, + "uiid" : 2},{ + "name" : "nonuniform", + "description" : "Create non-uniform B-spline", + "type" : "select", + "value" : ["false", "true"], + "default" : 0, + "uiid" : 3}])"_json; + + else if constexpr (BSpline_t::parDim() == 4) + return R"([{ + "name" : "degree", + "description" : "Polynomial degree of the B-spline", + "type" : "select", + "value" : ["constant", "linear", "quadratic", "cubic", "quartic", "quintic"], + "default" : 2, + "uiid" : 0},{ + "name" : "ncoeffs", + "description" : "Number of coefficients per parametric dimension", + "type" : [int,int,int,int], + "value" : [3,3,3,3], + "default" : [3,3,3,3], + "uiid" : 1},{ + "name" : "init", + "description" : "Initialization of the coefficients", + "type" : "select", + "value" : ["zeros", "ones", "linear", "random", "greville"], + "default" : 4, + "uiid" : 2},{ + "name" : "nonuniform", + "description" : "Create non-uniform B-spline", + "type" : "select", + "value" : ["false", "true"], + "default" : 0, + "uiid" : 3}])"_json; + + else + return R"({ INVALID REQUEST })"_json; + } + + /// @brief Returns the model's inputs + nlohmann::json getInputs() const override { + return R"([{ + "name" : "geometry", + "description" : "Geometry", + "type" : 2}])"_json; + } + + /// @brief Returns the model's outputs + nlohmann::json getOutputs() const override { + if constexpr (BSpline_t::geoDim() == 1) + return R"([{ + "name" : "ValueFieldMagnitude", + "description" : "Magnitude of the B-spline values", + "type" : 1}])"_json; + else + return R"([{ + "name" : "ValueFieldMagnitude", + "description" : "Magnitude of the B-spline values", + "type" : 1},{ + "name" : "ValueField", + "description" : "B-spline values", + "type" : 2}])"_json; + } + + /// @brief Serializes the model to JSON + nlohmann::json to_json(const std::string &component, + const std::string &attribute) const override { + + if (component == "geometry" || + component == "" /* might be removed in the future */) { + if (attribute != "") { + nlohmann::json json; + if (attribute == "degrees") + json["degrees"] = this->degrees(); + else if (attribute == "geoDim") + json["geoDim"] = this->geoDim(); + else if (attribute == "parDim") + json["parDim"] = this->parDim(); + else if (attribute == "ncoeffs") + json["ncoeffs"] = this->ncoeffs(); + else if (attribute == "nknots") + json["nknots"] = this->nknots(); + else if (attribute == "coeffs") + json["coeffs"] = this->coeffs_to_json(); + else if (attribute == "knots") + json["knots"] = this->knots_to_json(); + + return json; + + } else { + auto json = BSpline_t::to_json(); + json.update(Model::to_json("transform", ""), true); + + return json; + } + } + + else if (component == "solution") { + if (attribute != "") { + nlohmann::json json; + if (attribute == "degrees") + json["degrees"] = solution_.degrees(); + else if (attribute == "geoDim") + json["geoDim"] = solution_.geoDim(); + else if (attribute == "parDim") + json["parDim"] = solution_.parDim(); + else if (attribute == "ncoeffs") + json["ncoeffs"] = solution_.ncoeffs(); + else if (attribute == "nknots") + json["nknots"] = solution_.nknots(); + else if (attribute == "coeffs") + json["coeffs"] = solution_.coeffs_to_json(); + else if (attribute == "knots") + json["knots"] = solution_.knots_to_json(); + + return json; + } else + + return solution_.to_json(); + } + + else + return Model::to_json(component, attribute); + } + + /// @brief Updates the attributes of the model + nlohmann::json updateAttribute(const std::string &component, + const std::string &attribute, + const nlohmann::json &json) override { + if (attribute == "coeffs") { + if (!json.contains("data")) + throw InvalidModelAttributeException(); + if (!json["data"].contains("indices") || !json["data"].contains("coeffs")) + throw InvalidModelAttributeException(); + + auto indices = json["data"]["indices"].get>(); + auto coeffs_cpu = + utils::to_tensorAccessor( + BSpline_t::coeffs(), torch::kCPU); + + switch (BSpline_t::geoDim()) { + case (1): { + auto coords = + json["data"]["coeffs"] + .get>>(); + auto xAccessor = std::get<1>(coeffs_cpu)[0]; + + for (const auto &[index, coord] : iganet::utils::zip(indices, coords)) { + if (index < 0 || index >= BSpline_t::ncumcoeffs()) + throw IndexOutOfBoundsException(); + xAccessor[index] = std::get<0>(coord); + } + break; + } + case (2): { + auto coords = + json["data"]["coeffs"] + .get>>(); + auto xAccessor = std::get<1>(coeffs_cpu)[0]; + auto yAccessor = std::get<1>(coeffs_cpu)[1]; + + for (const auto &[index, coord] : iganet::utils::zip(indices, coords)) { + if (index < 0 || index >= BSpline_t::ncumcoeffs()) + throw IndexOutOfBoundsException(); + + xAccessor[index] = std::get<0>(coord); + yAccessor[index] = std::get<1>(coord); + } + break; + } + case (3): { + auto coords = + json["data"]["coeffs"] + .get>>(); + auto xAccessor = std::get<1>(coeffs_cpu)[0]; + auto yAccessor = std::get<1>(coeffs_cpu)[1]; + auto zAccessor = std::get<1>(coeffs_cpu)[2]; + + for (const auto &[index, coord] : iganet::utils::zip(indices, coords)) { + if (index < 0 || index >= BSpline_t::ncumcoeffs()) + throw IndexOutOfBoundsException(); + + xAccessor[index] = std::get<0>(coord); + yAccessor[index] = std::get<1>(coord); + zAccessor[index] = std::get<2>(coord); + } + break; + } + case (4): { + auto coords = + json["data"]["coeffs"] + .get>>(); + auto xAccessor = std::get<1>(coeffs_cpu)[0]; + auto yAccessor = std::get<1>(coeffs_cpu)[1]; + auto zAccessor = std::get<1>(coeffs_cpu)[2]; + auto tAccessor = std::get<1>(coeffs_cpu)[3]; + + for (const auto &[index, coord] : iganet::utils::zip(indices, coords)) { + if (index < 0 || index >= BSpline_t::ncumcoeffs()) + throw IndexOutOfBoundsException(); + + xAccessor[index] = std::get<0>(coord); + yAccessor[index] = std::get<1>(coord); + zAccessor[index] = std::get<2>(coord); + tAccessor[index] = std::get<3>(coord); + } + break; + } + default: + throw InvalidModelAttributeException(); + } + return "{}"; + } else + return Model::updateAttribute(component, attribute, json); + } + + /// @brief Evaluates the model + nlohmann::json eval(const std::string &component, + const nlohmann::json &json) const override { + + if constexpr (BSpline_t::parDim() == 1) { + + std::array res({25}); + if (json.contains("data")) + if (json["data"].contains("resolution")) + res = json["data"]["resolution"].get>(); + + utils::TensorArray1 xi = {torch::linspace(0, 1, res[0])}; + + if (component == "ValueFieldMagnitude") { + return nlohmann::json::array().emplace_back( + utils::to_json(*(solution_.eval(xi)[0]))); + } else if (component == "ValueField") { + auto values = BSpline_t::eval(xi); + auto result = nlohmann::json::array(); + for (short_t dim = 0; dim < BSpline_t::geoDim(); ++dim) + result.emplace_back( + utils::to_json(*(values[dim]))); + return result; + } else + return R"({ INVALID REQUEST })"_json; + } + + else if constexpr (BSpline_t::parDim() == 2) { + + std::array res({25, 25}); + if (json.contains("data")) + if (json["data"].contains("resolution")) + res = json["data"]["resolution"].get>(); + + utils::TensorArray2 xi = utils::to_array<2>(torch::meshgrid( + {torch::linspace(0, 1, res[0], + Options{}), + torch::linspace(0, 1, res[1], + Options{})}, + "xy")); + + if (component == "ValueFieldMagnitude") { + return nlohmann::json::array().emplace_back( + utils::to_json(*(solution_.eval(xi)[0]))); + } else if (component == "ValueField") { + auto values = BSpline_t::eval(xi); + auto result = nlohmann::json::array(); + for (short_t dim = 0; dim < BSpline_t::geoDim(); ++dim) + result.emplace_back( + utils::to_json(*(values[dim]))); + return result; + } else + return R"({ INVALID REQUEST })"_json; + } + + else if constexpr (BSpline_t::parDim() == 3) { + + std::array res({25, 25, 25}); + if (json.contains("data")) + if (json["data"].contains("resolution")) + res = json["data"]["resolution"].get>(); + + utils::TensorArray3 xi = utils::to_array<3>(torch::meshgrid( + {torch::linspace(0, 1, res[0], + Options{}), + torch::linspace(0, 1, res[1], + Options{}), + torch::linspace(0, 1, res[2], + Options{})}, + "xy")); + + if (component == "ValueFieldMagnitude") { + return nlohmann::json::array().emplace_back( + utils::to_json(*(solution_.eval(xi)[0]))); + } else if (component == "ValueField") { + auto values = BSpline_t::eval(xi); + auto result = nlohmann::json::array(); + for (short_t dim = 0; dim < BSpline_t::geoDim(); ++dim) + result.emplace_back( + utils::to_json(*(values[dim]))); + return result; + } else + return R"({ INVALID REQUEST })"_json; + } + + else if constexpr (BSpline_t::parDim() == 4) { + + std::array res({25, 25, 25, 25}); + if (json.contains("data")) + if (json["data"].contains("resolution")) + res = json["data"]["resolution"].get>(); + + utils::TensorArray4 xi = utils::to_array<4>(torch::meshgrid( + {torch::linspace(0, 1, res[0], + Options{}), + torch::linspace(0, 1, res[1], + Options{}), + torch::linspace(0, 1, res[2], + Options{}), + torch::linspace(0, 1, res[3], + Options{})}, + "xy")); + + if (component == "ValueFieldMagnitude") { + return nlohmann::json::array().emplace_back( + utils::to_json(*(solution_.eval(xi)[0]))); + } else if (component == "ValueField") { + auto values = BSpline_t::eval(xi); + auto result = nlohmann::json::array(); + for (short_t dim = 0; dim < BSpline_t::geoDim(); ++dim) + result.emplace_back( + utils::to_json(*(values[dim]))); + return result; + } else + return R"({ INVALID REQUEST })"_json; + } + } + + /// @brief Refines the model + void refine(const nlohmann::json &json = NULL) override { + int num = 1, dim = -1; + + if (json.contains("data")) { + if (json["data"].contains("num")) + num = json["data"]["num"].get(); + + if (json["data"].contains("dim")) + dim = json["data"]["dim"].get(); + } + + BSpline_t::uniform_refine(num, dim); + + solution_.uniform_refine(num, dim); + if constexpr (BSpline_t::parDim() == 1) + solution_.transform([](const std::array + xi) { + return std::array{ + static_cast(std::sin(M_PI * xi[0])), 0.0, 0.0}; + }); + + else if constexpr (BSpline_t::parDim() == 2) + solution_.transform([](const std::array + xi) { + return std::array{ + static_cast(std::sin(M_PI * xi[0]) * + std::sin(M_PI * xi[1])), + 0.0, 0.0}; + }); + + else if constexpr (BSpline_t::parDim() == 3) + solution_.transform([](const std::array + xi) { + return std::array{ + static_cast(std::sin(M_PI * xi[0]) * + std::sin(M_PI * xi[1]) * + std::sin(M_PI * xi[2])), + 0.0, 0.0}; + }); + } + + /// @brief Reparameterize the model + void reparameterize(const nlohmann::json &json = NULL) override { + + // gismo::gsBarrierPatch opt(geo_, false); + // opt.options().setInt("ParamMethod", 1); + // opt.compute(); + + // geo_ = opt.result(); + } + + /// @brief Loads model from LibTorch file + void load(const nlohmann::json &json) override { + + if (json.contains("data")) { + if (json["data"].contains("binary")) { + + // get binary vector from JSON object + std::vector binary = json["data"]["binary"]; + + // recover input archive from binary vector + torch::serialize::InputArchive archive; + archive.load_from(reinterpret_cast(binary.data()), + binary.size()); + + archive.read("transform", transform_); + BSpline_t::read(archive, "geometry"); + solution_.read(archive, "solution"); + + return; + } + } + + throw InvalidModelException(); + } + + /// @brief Saves model to LibTorch file + nlohmann::json save() const override { + + // serialize model to output archive + torch::serialize::OutputArchive archive; + archive.write("model", + static_cast(std::hash{}(getName()))); + archive.write("nonuniform", static_cast(BSpline_t::is_nonuniform())); + archive.write("transform", transform_); + + BSpline_t::write(archive, "geometry"); + solution_.write(archive, "solution"); + + // store output archive in binary vector + std::vector binary; + + archive.save_to( + [&binary](const void *data, size_t size) mutable -> std::size_t { + auto data_ = reinterpret_cast(data); + + for (std::size_t i = 0; i < size; ++i) + binary.push_back(data_[i]); + + return size; + }); + + // attach binary vector to JSON object + nlohmann::json json; + json["binary"] = binary; + + return json; + } + + /// @brief Imports the model from XML (as JSON object) + void importXML(const nlohmann::json &json, const std::string &component, + int id) override { + + if (json.contains("data")) { + if (json["data"].contains("xml")) { + + std::string xml = json["data"]["xml"].get(); + + pugi::xml_document doc; + pugi::xml_parse_result result = + doc.load_buffer(xml.c_str(), xml.size()); + + if (pugi::xml_node root = doc.child("xml")) + importXML(root, component, id); + else + throw std::runtime_error("No \"xml\" node in XML object"); + + return; + } + } + + throw std::runtime_error("No XML node in JSON object"); + } + + /// @brief Imports the model from XML (as XML object) + void importXML(const pugi::xml_node &xml, const std::string &component, + int id) override { + + if (component.empty()) { + BSpline_t::from_xml(xml, id, "geometry"); + solution_.from_xml(xml, id, "solution"); + iganet::utils::from_xml(xml, transform_, "Matrix", id, + "transform", false); + } else { + if (component == "geometry") { + BSpline_t::from_xml(xml, id, "geometry"); + iganet::utils::from_xml(xml, transform_, "Matrix", + id, "transform", false); + } else if (component == "solution") + solution_.from_xml(xml, id, "solution"); + else + throw std::runtime_error("Unsupported component"); + } + } + + /// @brief Exports the model to XML (as JSON object) + nlohmann::json exportXML(const std::string &component, int id) override { + + // serialize to XML + pugi::xml_document doc; + pugi::xml_node xml = doc.append_child("xml"); + xml = exportXML(xml, component, id); + + // serialize to JSON + std::ostringstream oss; + doc.save(oss); + + return oss.str(); + } + + /// @brief Exports the model to XML (as XML object) + pugi::xml_node &exportXML(pugi::xml_node &xml, const std::string &component, + int id) override { + + if (component.empty()) { + BSpline_t::to_xml(xml, id, "geometry"); + solution_.to_xml(xml, id, "solution"); + iganet::utils::to_xml(transform_, xml, "Matrix", id, + "transform"); + } else { + if (component == "geometry") { + BSpline_t::to_xml(xml, id, "geometry"); + iganet::utils::to_xml(transform_, xml, "Matrix", id, + "transform"); + } else if (component == "solution") + solution_.to_xml(xml, id, "solution"); + else + throw std::runtime_error("Unsupported component"); + } + return xml; + } +}; + +} // namespace webapp +} // namespace iganet diff --git a/webapps/server.cxx b/webapps/server.cxx index f3090754..7a90ee21 100644 --- a/webapps/server.cxx +++ b/webapps/server.cxx @@ -1,1843 +1,1843 @@ -/** - @file webapps/server.cxx - - @brief Demonstration of a server application - - @author Matthias Moller - - @copyright This file is part of the IgANet project - - This Source Code Form is subject to the terms of the Mozilla Public - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at http://mozilla.org/MPL/2.0/. -*/ - -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -namespace iganet { -namespace webapp { - -/// @brief Enumerator for specifying the status -enum class status : short_t { - success = 0, /*!< request was handled successfully */ - invalidRequest = 1, /*!< invalid request */ - invalidCreateRequest = 2, /*!< invalid create request */ - invalidRemoveRequest = 3, /*!< invalid remove request */ - invalidConnectRequest = 4, /*!< invalid connect request */ - invalidDisconnectRequest = 5, /*!< invalid disconnect request */ - invalidGetRequest = 6, /*!< invalid get request */ - invalidPutRequest = 7, /*!< invalid put request */ - invalidEvalRequest = 8, /*!< invalid eval request */ - invalidRefineRequest = 9, /*!< invalid refine request */ - invalidElevateRequest = 10, /*!< invalid degree elevate request */ - invalidIncreaseRequest = 11, /*!< invalid degree increase request */ - invalidReparameterizeRequest = 12, /*!< invalid reparameterize request */ - invalidLoadRequest = 13, /*!< invalid load request */ - invalidSaveRequest = 14, /*!< invalid save request */ - invalidImportRequest = 15, /*!< invalid import request */ - invalidExportRequest = 16, /*!< invalid export request */ - invalidComputeErrorRequest = 17 /*!< invalid compute error request */ -}; - -/// @brief InvalidSessionId exception -struct InvalidSessionIdException : public std::exception { - const char *what() const throw() { return "Invalid session id"; } -}; - -/// @brief InvalidModelId exception -struct InvalidModelIdException : public std::exception { - const char *what() const throw() { return "Invalid model id"; } -}; - -/// @brief InvalidModelType exception -struct InvalidModelTypeException : public std::exception { - const char *what() const throw() { return "Invalid model type"; } -}; - -/// @brief Tokenize the input string -std::vector tokenize(std::string str, - std::string separator = "/") { - std::vector tokens; - for (auto i = strtok(&str[0], &separator[0]); i != NULL; - i = strtok(NULL, &separator[0])) - tokens.push_back(i); - return tokens; -} - -/// @brief Session -template struct Session { -private: - /// @brief Session UUID - const std::string uuid_; - - /// @brief Hashed password - const std::string hash_; - - /// @brief Creation time stamp - std::chrono::system_clock::time_point creation_time_; - - /// @brief Access time stamp - std::chrono::system_clock::time_point access_time_; - -public: - /// @brief Default constructor - Session(std::string hash) - : uuid_(iganet::utils::uuid::create()), hash_(hash), - creation_time_(std::chrono::system_clock::now()), - access_time_(std::chrono::system_clock::now()) {} - - /// @brief Returns the UUID - inline const std::string &getUUID() const { return uuid_; } - - /// @brief Returns a constant reference to the list of models - inline const auto &getModels() const { return models; } - - /// @brief Returns a non-constant reference to the list of models - inline auto &getModels() { return models; } - - /// @brief Returns the requested model or throws an exception - inline std::shared_ptr getModel(int64_t id) { - auto it = models.find(id); - if (it == models.end()) - throw InvalidModelIdException(); - else - return it->second; - } - - /// @brief Returns the model and removes it from the list of models - inline std::shared_ptr removeModel(int64_t id) { - auto it = models.find(id); - if (it == models.end()) - throw InvalidModelIdException(); - else { - auto model = it->second; - models.erase(it); - return model; - } - } - - /// @brief Returns true if the session has a non-zero hash - inline bool hasHash() const { return (hash_ != ""); } - - /// @brief Returns true if the provided hash coincides with the session's hash - inline bool checkHash(std::string hash) const { return (hash_ == hash); } - - /// @brief Updates the access time stamp - inline void access() { access_time_ = std::chrono::system_clock::now(); } - - /// @brief Returns the creation time - inline std::chrono::system_clock::time_point getCreationTime() const { - return creation_time_; - } - - /// @brief Returns the access time - inline std::chrono::system_clock::time_point getAccessTime() const { - return access_time_; - } - - /// @brief List of models - std::map> models; -}; - -/// @brief Sessions structure -template struct Sessions { -public: - /// @brief Returns the requested session or throws an exception - inline std::shared_ptr> getSession(std::string uuid) { - auto it = sessions_.find(uuid); - if (it == sessions_.end()) - throw InvalidSessionIdException(); - else { - it->second->access(); - return it->second; - } - } - - /// @brief Returns a new session - inline std::shared_ptr> createSession(std::string hash) { - auto session = - std::make_shared>(hash); - sessions_[session->getUUID()] = session; - return session; - } - - /// @brief Returns the session and removes it from the list of sessions - inline std::shared_ptr> removeSession(std::string uuid) { - auto it = sessions_.find(uuid); - if (it == sessions_.end()) - throw InvalidSessionIdException(); - else { - auto session = it->second; - sessions_.erase(it); - return session; - } - } - - /// @brief Add path to model path - inline static void addModelPath(const std::string &path) { - models_.addModelPath(path); - } - - /// @brief Add list of paths to model path - inline static void addModelPath(const std::vector &path) { - models_.addModelPath(path); - } - - /// @brief Returns a non-constant reference to the list of sessions - inline static auto &getSessions() { return sessions_; } - - /// @brief Returns a non-constant reference to the list of models - inline static auto &getModels() { return models_; } - -private: - /// @brief List of sessions shared between all sockets - inline static std::map>> sessions_; - - /// @brief List of models - inline static ModelManager models_ = - ModelManager(iganet::webapp::tokenize("webapps/models,models", ",")); -}; - -} // namespace webapp -} // namespace iganet - -int main(int argc, char const *argv[]) { - using PerSocketData = iganet::webapp::Sessions; - - popl::OptionParser op("Allowed options"); - auto help_option = op.add("h", "help", "print help message"); - auto port_option = - op.add>("p", "port", "TCP port of the server", 9001); - auto config_option = op.add>( - "f", "configfile", "configuration file", ""); - auto keyfile_option = op.add>( - "k", "keyfile", "key file for SSL encryption", ""); - auto certfile_option = op.add>( - "c", "certfile", "certificate file for SSL encryption", ""); - auto modelpath_option = op.add>( - "m", "modelpath", "path to model files", ""); - auto passphrase_option = op.add>( - "a", "passphrase", "passphrase for SSL encryption", ""); - auto threads_option = - op.add>("t", "threads", "number of server threads", 1); - - op.parse(argc, argv); - - // Print auto-generated help message - if (help_option->count() == 1) { - std::cout << op << std::endl; - return 0; - } else if (help_option->count() == 2) { - std::cout << op.help(popl::Attribute::advanced) << std::endl; - return 0; - } else if (help_option->count() > 2) { - std::cout << op.help(popl::Attribute::expert) << std::endl; - return 0; - } - - // Initialize backend - iganet::init(); - - // Load configuration from file - nlohmann::json config; - if (!config_option->value().empty()) { - std::ifstream file(config_option->value()); - if (file) { - try { - config = nlohmann::json::parse(file); - } catch (std::exception &e) { - std::cerr << e.what(); - return -1; - } - } else { - std::ifstream file(std::filesystem::path(__FILE__).replace_filename( - config_option->value())); - if (file) { - try { - config = nlohmann::json::parse(file); - } catch (std::exception &e) { - std::cerr << e.what(); - return -1; - } - } - } - } - - // Override commandline arguments - if (port_option->is_set()) - config["port"] = port_option->value(); - - if (keyfile_option->is_set()) - config["keyFile"] = keyfile_option->value(); - - if (certfile_option->is_set()) - config["certFile"] = certfile_option->value(); - - if (passphrase_option->is_set()) - config["passphrase"] = passphrase_option->value(); - - if (modelpath_option->is_set()) - config["modelPath"] = modelpath_option->value(); - - if (threads_option->is_set()) - config["numThreads"] = threads_option->value(); - - // Check if key file is available - if (config.contains("keyFile")) { - if (!std::filesystem::exists( - std::filesystem::path(config["keyFile"].get()))) { - if (std::filesystem::exists( - std::filesystem::path(__FILE__).replace_filename( - config["keyFile"].get()))) { - config["keyFile"] = std::filesystem::path(__FILE__).replace_filename( - config["keyFile"].get()); - } else { - throw std::runtime_error("Unable to open key file " + - config["keyFile"].get()); - } - } - } - - // Check if cert file is available - if (config.contains("certFile")) { - if (!std::filesystem::exists( - std::filesystem::path(config["certFile"].get()))) { - if (std::filesystem::exists( - std::filesystem::path(__FILE__).replace_filename( - config["certFile"].get()))) { - config["certFile"] = std::filesystem::path(__FILE__).replace_filename( - config["certFile"].get()); - } else { - throw std::runtime_error("Unable to open cert file " + - config["certFile"].get()); - } - } - } - - // Add paths to model search path - if (config.contains("modelPath")) - PerSocketData::addModelPath( - iganet::webapp::tokenize(config["modelPath"].get(), ",")); - - // Multi-threaded websocket application - std::vector threads(config.contains("numThreads") - ? config["numThreads"].get() - : std::thread::hardware_concurrency()); - - std::transform( - threads.begin(), threads.end(), threads.begin(), - [&config, &port_option](std::thread *) { - return new std::thread([&config, &port_option]() { - // Create WebSocket application - try { - uWS::SSLApp( - {.key_file_name = - (config.contains("keyFile") - ? config["keyFile"].get().c_str() - : std::filesystem::path(__FILE__) - .replace_filename("key.pem") - .c_str()), - .cert_file_name = - (config.contains("certFile") - ? config["certFile"].get().c_str() - : std::filesystem::path(__FILE__) - .replace_filename("cert.pem") - .c_str()), - .passphrase = - (config.contains("passphrase") - ? config["passphrase"].get().c_str() - : "")}) - .ws( - "/*", - {/* Settings */ - .compression = - uWS::CompressOptions(uWS::DEDICATED_COMPRESSOR_4KB | - uWS::DEDICATED_DECOMPRESSOR), - .maxPayloadLength = - (config.contains("maxPayloadLength") - ? config["maxPayloadLength"].get() - : 100 * 1024 * 1024), - .idleTimeout = - (config.contains("idleTimeout") - ? config["idleTimeout"].get() - : static_cast(16)), - .maxBackpressure = - (config.contains("maxBackpressure") - ? config["maxBackpressure"].get() - : 100 * 1024 * 1024), - .closeOnBackpressureLimit = - (config.contains("closeOnBackpressureLimit") - ? config["closeOnBackpressureLimit"].get() - : false), - .resetIdleTimeoutOnSend = - (config.contains("resetIdleTimeoutOnSend") - ? config["resetIdleTimeoutOnSend"].get() - : false), - .sendPingsAutomatically = - (config.contains("sendPingsAutomatically") - ? config["sendPingsAutomatically"].get() - : true), - /* Handlers */ - .upgrade = nullptr, - .open = - [](auto *ws) { -#ifndef NDEBUG - std::stringstream msg; - msg << "[Thread " << std::this_thread::get_id() - << "] Connection has been opened\n"; - std::clog << msg.str(); -#endif - }, - .message = - [](auto *ws, std::string_view message, - uWS::OpCode opCode) { - try { - // Tokenize request - auto request = nlohmann::json::parse(message); - auto tokens = iganet::webapp::tokenize( - request["request"].get()); - - // Prepare response - nlohmann::json response; - response["request"] = request["id"]; - response["status"] = - iganet::webapp::status::success; - -#ifndef NDEBUG - std::stringstream msg; - for (auto const &token : tokens) - msg << "[Thread " << std::this_thread::get_id() - << "] " << token << "/"; - msg << std::endl; - std::clog << msg.str(); -#endif - - // Dispatch request - if (tokens[0] == "get") { - // - // request: get/* - // - - try { - - if (tokens.size() == 1) { - // - // request: get - // - - // Get list of all active sessions - std::vector ids; - for (const auto &session : - ws->getUserData()->getSessions()) - ids.push_back(session.first); - response["data"]["ids"] = ids; - ws->send(response.dump(), uWS::OpCode::TEXT, - true); - } - - else if (tokens.size() == 2) { - // - // request: get/ - // - - // Get session - auto session = - ws->getUserData()->getSession(tokens[1]); - - // Get list of all active models in session - std::vector ids; - auto models = nlohmann::json::array(); - for (const auto &model : - session->getModels()) { - ids.push_back(model.first); - models.push_back(model.second->getModel()); - } - response["data"]["ids"] = ids; - response["data"]["models"] = models; - ws->send(response.dump(), uWS::OpCode::TEXT, - true); - } - - else if (tokens.size() == 3) { - // - // request: get// - // - - // Get session - auto session = - ws->getUserData()->getSession(tokens[1]); - - // Get model - auto model = - session->getModel(stoi(tokens[2])); - - // Serialize model to JSON - response["data"] = model->to_json("", ""); - response["data"]["model"] = - model->getModel(); - ws->send(response.dump(), uWS::OpCode::TEXT, - true); - } - - else if (tokens.size() == 4) { - // - // request: - // get/// - // - // or - // - // request: - // get/// - // - - // Get session - auto session = - ws->getUserData()->getSession(tokens[1]); - - // Get model - auto model = - session->getModel(stoi(tokens[2])); - - // Serialize model component to JSON - response["data"] = - model->to_json(tokens[3], ""); - ws->send(response.dump(), uWS::OpCode::TEXT, - true); - } - - else if (tokens.size() == 5) { - // - // request: - // get//// - // - - // Get session - auto session = - ws->getUserData()->getSession(tokens[1]); - - // Get model - auto model = - session->getModel(stoi(tokens[2])); - - // Serialize attribute of model component to - // JSON - response["data"] = - model->to_json(tokens[3], tokens[4]); - ws->send(response.dump(), uWS::OpCode::TEXT, - true); - } - - else - throw std::runtime_error( - "Invalid GET request"); - - } catch (...) { - response["status"] = - iganet::webapp::status::invalidGetRequest; - response["reason"] = - "Invalid GET request. Valid GET requests " - "are " - "\"get\", \"get/\", " - "\"get//\", " - "and " - "\"get///" - "\", and " - "\"get///" - "/\""; - ws->send(response.dump(), uWS::OpCode::TEXT, - true); - } - - } // GET - - else if (tokens[0] == "put") { - // - // request: put/* - // - - try { - - if (tokens.size() == 4) { - // - // request: - // put/// - // - - // Get session - auto session = - ws->getUserData()->getSession(tokens[1]); - - // Get model - auto model = - session->getModel(stoi(tokens[2])); - - // Update model attribute - response["data"] = model->updateAttribute( - "", tokens[3], request); - ws->send(response.dump(), uWS::OpCode::TEXT, - true); - - // Broadcast update of model instance - nlohmann::json broadcast; - broadcast["id"] = session->getUUID(); - broadcast["request"] = "update/instance"; - broadcast["data"]["id"] = stoi(tokens[2]); - broadcast["data"]["component"] = ""; - broadcast["data"]["attribute"] = tokens[3]; - ws->publish(session->getUUID(), - broadcast.dump(), - uWS::OpCode::TEXT); - } - - else if (tokens.size() == 5) { - // - // request: - // put//// - // - - // Get session - auto session = - ws->getUserData()->getSession(tokens[1]); - - // Get model - auto model = - session->getModel(stoi(tokens[2])); - - // Update model attribute - response["data"] = model->updateAttribute( - tokens[3], tokens[4], request); - ws->send(response.dump(), uWS::OpCode::TEXT, - true); - - // Broadcast update of model instance - nlohmann::json broadcast; - broadcast["id"] = session->getUUID(); - broadcast["request"] = "update/instance"; - broadcast["data"]["id"] = stoi(tokens[2]); - broadcast["data"]["component"] = tokens[3]; - broadcast["data"]["attribute"] = tokens[4]; - ws->publish(session->getUUID(), - broadcast.dump(), - uWS::OpCode::TEXT); - } - - else - throw std::runtime_error( - "Invalid PUT request"); - - } catch (...) { - response["status"] = - iganet::webapp::status::invalidPutRequest; - response["reason"] = - "Invalid PUT request. Valid PUT requests " - "are " - "\"put///" - "\", and " - "\"put///" - "/\""; - ws->send(response.dump(), uWS::OpCode::TEXT, - true); - } - - } // PUT - - else if (tokens[0] == "create") { - // - // request: create/* - // - - try { - - if (tokens.size() == 2 && - tokens[1] == "session") { - // - // request: create/session - // - - // Get password hash - std::string hash(""); - if (request.contains("data")) - if (request["data"].contains("hash")) - hash = request["data"]["hash"] - .get(); - - // Create a new session - auto session = - ws->getUserData()->createSession(hash); - std::string uuid = session->getUUID(); - - response["data"]["id"] = uuid; - response["data"]["models"] = - ws->getUserData() - ->getModels() - .getModels(); - ws->send(response.dump(), uWS::OpCode::TEXT, - true); - - // Subscribe to new session - ws->subscribe(uuid); - - // Broadcast creation of a new session - nlohmann::json broadcast; - broadcast["id"] = uuid; - broadcast["request"] = "create/session"; - broadcast["data"]["id"] = uuid; - ws->publish(uuid, broadcast.dump(), - uWS::OpCode::TEXT); - } - - else if (tokens.size() == 3) { - // - // request: create// - // - - // Get session - auto session = - ws->getUserData()->getSession(tokens[1]); - - // Get new model's id - int64_t id = (session->getModels().size() > 0 - ? session->getModels() - .crbegin() - ->first + - 1 - : 0); - - // Create a new model instance - session->models[id] = - ws->getUserData()->getModels().create( - tokens[2], request); - response["data"]["id"] = std::to_string(id); - response["data"]["model"] = - session->models[id]->getModel(); - ws->send(response.dump(), uWS::OpCode::TEXT, - true); - - // Broadcast creation of a new model instance - nlohmann::json broadcast; - broadcast["id"] = session->getUUID(); - broadcast["request"] = "create/instance"; - broadcast["data"]["id"] = id; - broadcast["data"]["model"] = - session->models[id]->getModel(); - ws->publish(session->getUUID(), - broadcast.dump(), - uWS::OpCode::TEXT); - } - - else - throw std::runtime_error( - "Invalid CREATE request"); - - } catch (...) { - response["status"] = iganet::webapp::status:: - invalidCreateRequest; - response["reason"] = - "Invalid CREATE request. Valid CREATE " - "requests " - "are \"create/session\" and " - "\"create//\""; - ws->send(response.dump(), uWS::OpCode::TEXT, - true); - } - - } // CREATE - - else if (tokens[0] == "remove") { - // - // request: remove/* - // - - try { - - if (tokens.size() == 2) { - // - // request: remove/ - // - - // Remove session - auto session = - ws->getUserData()->removeSession( - tokens[1]); - ws->send(response.dump(), uWS::OpCode::TEXT, - true); - - // Broadcast removal of session - nlohmann::json broadcast; - broadcast["id"] = session->getUUID(); - broadcast["request"] = "remove/session"; - broadcast["data"]["id"] = session->getUUID(); - ws->publish(session->getUUID(), - broadcast.dump(), - uWS::OpCode::TEXT); - } - - else if (tokens.size() == 3) { - // - // request: - // remove// - // - - // Get session - auto session = - ws->getUserData()->getSession(tokens[1]); - - // Remove model - auto model = - session->removeModel(stoi(tokens[2])); - ws->send(response.dump(), uWS::OpCode::TEXT, - true); - - // Broadcast removal of model instance - nlohmann::json broadcast; - broadcast["id"] = session->getUUID(); - broadcast["request"] = "remove/instance"; - broadcast["data"]["id"] = stoi(tokens[2]); - ws->publish(session->getUUID(), - broadcast.dump(), - uWS::OpCode::TEXT); - } - - else - throw std::runtime_error( - "Invalid REMOVE request"); - - } catch (...) { - response["status"] = iganet::webapp::status:: - invalidRemoveRequest; - response["reason"] = - "Invalid REMOVE request. Valid REMOVE " - "requests " - "are " - "\"remove/\" and " - "\"remove//\""; - ws->send(response.dump(), uWS::OpCode::TEXT, - true); - } - - } // REMOVE - - else if (tokens[0] == "connect") { - // - // request: connect/* - // - - try { - - if (tokens.size() == 2) { - // - // request: connect/ - // - - // Get session - auto session = - ws->getUserData()->getSession(tokens[1]); - - // Get password hash - std::string hash(""); - if (request.contains("data")) - if (request["data"].contains("hash")) - hash = request["data"]["hash"] - .get(); - - if (!session->checkHash(hash)) - throw std::runtime_error( - "Invalid CONNECT request. Invalid " - "ession password."); - - // Connect to an existing session - response["data"]["id"] = session->getUUID(); - response["data"]["models"] = - ws->getUserData() - ->getModels() - .getModels(); - ws->send(response.dump(), uWS::OpCode::TEXT, - true); - - // Subscribe to existing session - ws->subscribe(session->getUUID()); - } - - else - throw std::runtime_error( - "Invalid CONNECT request"); - - } catch (...) { - response["status"] = iganet::webapp::status:: - invalidConnectRequest; - response["reason"] = - "Invalid CONNECT request. Valid CONNECT " - "requests " - "are \"connect/\""; - ws->send(response.dump(), uWS::OpCode::TEXT, - true); - } - - } // CONNECT - - else if (tokens[0] == "disconnect") { - // - // request: diconnect/* - // - - try { - - if (tokens.size() == 2) { - // - // request: diconnect/ - // - - // Get session - auto session = - ws->getUserData()->getSession(tokens[1]); - - // Disconnect from an existing session - response["data"]["id"] = session->getUUID(); - ws->send(response.dump(), uWS::OpCode::TEXT, - true); - - // Unsubscribe from existing session - ws->unsubscribe(session->getUUID()); - } - - else - throw std::runtime_error( - "Invalid DISCONNECT request"); - - } catch (...) { - response["status"] = iganet::webapp::status:: - invalidDisconnectRequest; - response["reason"] = - "Invalid DISCONNECT request. Valid " - "DISCONNECT " - "requests are " - "\"diconnect/\""; - ws->send(response.dump(), uWS::OpCode::TEXT, - true); - } - - } // DISCONNECT - - else if (tokens[0] == "eval") { - // - // request: eval/* - // - - try { - - if (tokens.size() == 4) { - // - // request: - // eval/// - // - - // Get session - auto session = - ws->getUserData()->getSession(tokens[1]); - - // Get model - auto model = - session->getModel(stoi(tokens[2])); - - // Evaluate an existing model - if (auto m = std::dynamic_pointer_cast< - iganet::ModelEval>(model)) - response["data"] = - m->eval(tokens[3], request); - else { - response["status"] = iganet::webapp:: - status::invalidEvalRequest; - response["reason"] = - "Invalid EVAL request. Valid EVAL " - "requests " - "are " - "\"eval///" - "\""; - } - ws->send(response.dump(), uWS::OpCode::TEXT, - true); - } - - else - throw std::runtime_error( - "Invalid EVAL request"); - - } catch (...) { - response["status"] = - iganet::webapp::status::invalidEvalRequest; - response["reason"] = - "Invalid EVAL request. Valid EVAL " - "requests are " - "\"eval///" - "\""; - ws->send(response.dump(), uWS::OpCode::TEXT, - true); - } - - } // EVAL - - else if (tokens[0] == "load") { - // - // request: load/* - // - - try { - - if (tokens.size() == 2) { - // - // request: load/ - // - - // Get session - auto session = - ws->getUserData()->getSession(tokens[1]); - - // Get binary data - auto instances = - request["data"]["instances"]; - - // Create vector if ids and array of models - std::vector ids; - auto models = nlohmann::json::array(); - - // Loop over all instances - for (const auto &instance : instances) { - - // Get new model's id - int64_t id = - (session->getModels().size() > 0 - ? session->getModels() - .crbegin() - ->first + - 1 - : 0); - - nlohmann::json request; - request["data"]["binary"] = instance; - - // Create a new model instance from binary - // data stream - session->models[id] = - ws->getUserData()->getModels().load( - request); - ids.push_back(id); - models.push_back( - session->models[id]->getModel()); - - // Broadcast creation of a new model - // instance - nlohmann::json broadcast; - broadcast["id"] = session->getUUID(); - broadcast["request"] = "create/instance"; - broadcast["data"]["id"] = id; - broadcast["data"]["model"] = - session->models[id]->getModel(); - ws->publish(session->getUUID(), - broadcast.dump(), - uWS::OpCode::TEXT); - } - - response["data"]["ids"] = ids; - response["data"]["models"] = models; - ws->send(response.dump(), uWS::OpCode::TEXT, - true); - } - - else - throw std::runtime_error( - "Invalid LOAD request"); - - } catch (...) { - response["status"] = - iganet::webapp::status::invalidLoadRequest; - response["reason"] = "Invalid LOAD request. " - "Valid LOAD requests are " - "\"load/session\" and " - "\"load/\""; - ws->send(response.dump(), uWS::OpCode::TEXT, - true); - } - - } // LOAD - - else if (tokens[0] == "save") { - // - // request: save/* - // - - try { - - if (tokens.size() == 2) { - // - // request: save/ - // - - // Get session - auto session = - ws->getUserData()->getSession(tokens[1]); - - // Save all active models in session - auto models = nlohmann::json::array(); - for (const auto &model : - session->getModels()) { - if (auto m = std::dynamic_pointer_cast< - iganet::ModelSerialize>( - model.second)) { - models.push_back(m->save()); - } - } - response["data"] = models; - ws->send(response.dump(), uWS::OpCode::TEXT, - true); - } - - else if (tokens.size() == 3) { - // - // request: - // save// - // - - // Get session - auto session = - ws->getUserData()->getSession(tokens[1]); - - // Get model - auto model = - session->getModel(stoi(tokens[2])); - - // Save model - if (auto m = std::dynamic_pointer_cast< - iganet::ModelSerialize>(model)) - response["data"] = m->save(); - else { - response["status"] = iganet::webapp:: - status::invalidSaveRequest; - response["reason"] = - "Invalid SAVE request. Valid SAVE " - "requests " - "are " - "\"save/\" and " - "\"save//" - "\""; - } - ws->send(response.dump(), uWS::OpCode::TEXT, - true); - } - - else - throw std::runtime_error( - "Invalid SAVE request"); - - } catch (...) { - response["status"] = - iganet::webapp::status::invalidSaveRequest; - response["reason"] = - "Invalid SAVE request. Valid SAVE " - "requests are " - "\"save/\" and " - "\"save//\""; - ws->send(response.dump(), uWS::OpCode::TEXT, - true); - } - - } // SAVE - - else if (tokens[0] == "importxml") { - // - // request: importxml/* - // - - try { - - if (tokens.size() == 2) { - // - // request: importxml/ - // - - // Get session - auto session = - ws->getUserData()->getSession(tokens[1]); - - // Load all existing models from XML - for (const auto &model : - session->getModels()) { - if (auto m = std::dynamic_pointer_cast< - iganet::ModelXML>(model.second)) { - m->importXML(request, "", model.first); - } else { - response["status"] = iganet::webapp:: - status::invalidImportRequest; - response["reason"] = - "Invalid IMPORTXML request. Valid " - "IMPORTXML " - "requests are " - "\"importxml/\", " - "\"importxml//" - "\" and " - "\"importxml//" - "/" - "\""; - ws->send(response.dump(), - uWS::OpCode::TEXT, true); - break; - } - } - ws->send(response.dump(), uWS::OpCode::TEXT, - true); - - // Broadcast update of model instances - std::vector ids; - for (const auto &model : - session->getModels()) - ids.push_back(model.first); - - // Broadcast update of model instance - nlohmann::json broadcast; - broadcast["id"] = session->getUUID(); - broadcast["request"] = "update/instance"; - broadcast["data"]["ids"] = ids; - ws->publish(session->getUUID(), - broadcast.dump(), - uWS::OpCode::TEXT); - } - - else if (tokens.size() == 3) { - // - // request: - // importxml// - // - - // Get session - auto session = - ws->getUserData()->getSession(tokens[1]); - - // Get model - auto model = - session->getModel(stoi(tokens[2])); - - // Import an existing model from XML - if (auto m = std::dynamic_pointer_cast< - iganet::ModelXML>(model)) - m->importXML(request, "", -1); - else { - response["status"] = iganet::webapp:: - status::invalidImportRequest; - response["reason"] = - "Invalid IMPORTXML request. Valid " - "IMPORTXML " - "requests are " - "\"importxml/\", " - "\"importxml//" - "\" " - "and " - "\"importxml//" - "/" - "\""; - } - ws->send(response.dump(), uWS::OpCode::TEXT, - true); - - // Broadcast update of model instance - nlohmann::json broadcast; - broadcast["id"] = session->getUUID(); - broadcast["request"] = "update/instance"; - broadcast["data"]["id"] = stoi(tokens[2]); - ws->publish(session->getUUID(), - broadcast.dump(), - uWS::OpCode::TEXT); - } - - else if (tokens.size() == 4) { - // - // request: - // importxml/// - // - - // Get session - auto session = - ws->getUserData()->getSession(tokens[1]); - - // Get model - auto model = - session->getModel(stoi(tokens[2])); - - // Import an existing model component from - // XML - if (auto m = std::dynamic_pointer_cast< - iganet::ModelXML>(model)) - m->importXML(request, tokens[3], -1); - else { - response["status"] = iganet::webapp:: - status::invalidImportRequest; - response["reason"] = - "Invalid IMPORTXML request. Valid " - "IMPORTXML " - "requests are " - "\"importxml/\", " - "\"importxml//" - "\" " - "and " - "\"importxml//" - "/" - "\""; - } - ws->send(response.dump(), uWS::OpCode::TEXT, - true); - - // Broadcast update of model instance - nlohmann::json broadcast; - broadcast["id"] = session->getUUID(); - broadcast["request"] = "update/instance"; - broadcast["data"]["id"] = stoi(tokens[2]); - ws->publish(session->getUUID(), - broadcast.dump(), - uWS::OpCode::TEXT); - } - - else - throw std::runtime_error( - "Invalid IMPORTXML request"); - - } catch (...) { - response["status"] = iganet::webapp::status:: - invalidImportRequest; - response["reason"] = - "Invalid IMPORTXML request. Valid " - "IMPORTXML " - "IMPORTXML " - "requests are \"importxml/\", " - "\"importxml//" - "\" and " - "\"importxml//" - "/" - "\""; - ws->send(response.dump(), uWS::OpCode::TEXT, - true); - } - - } // IMPORTXML - - else if (tokens[0] == "exportxml") { - // - // request: exportxml/* - // - - try { - - if (tokens.size() == 2) { - // - // request: exportxml/ - // - - // Get session - auto session = - ws->getUserData()->getSession(tokens[1]); - - // Export all existing models to XML - pugi::xml_document doc; - pugi::xml_node xml = doc.append_child("xml"); - - for (const auto &model : - session->getModels()) { - if (auto m = std::dynamic_pointer_cast< - iganet::ModelXML>(model.second)) - xml = m->exportXML(xml, "", model.first); - else - throw std::runtime_error( - "Invalid EXPORTXML request"); - } - std::ostringstream oss; - doc.save(oss); - - response["data"]["xml"] = oss.str(); - ws->send(response.dump(), uWS::OpCode::TEXT, - true); - } - - else if (tokens.size() == 3) { - // - // request: - // exportxml// - // - - // Get session - auto session = - ws->getUserData()->getSession(tokens[1]); - - // Get model - auto model = - session->getModel(stoi(tokens[2])); - - // Export an existing model to XML - if (auto m = std::dynamic_pointer_cast< - iganet::ModelXML>(model)) - response["data"]["xml"] = - m->exportXML("", stoi(tokens[2])); - else - throw std::runtime_error( - "Invalid EXPORTXML request"); - - ws->send(response.dump(), uWS::OpCode::TEXT, - true); - } - - else if (tokens.size() == 4) { - // - // request: - // exportxml/// - // - - // Get session - auto session = - ws->getUserData()->getSession(tokens[1]); - - // Get model - auto model = - session->getModel(stoi(tokens[2])); - - // Export an existing model to XML - if (auto m = std::dynamic_pointer_cast< - iganet::ModelXML>(model)) - response["data"]["xml"] = m->exportXML( - tokens[3], stoi(tokens[2])); - else - throw std::runtime_error( - "Invalid EXPORTXML request"); - - ws->send(response.dump(), uWS::OpCode::TEXT, - true); - } - - else - throw std::runtime_error( - "Invalid EXPORTXML request"); - - } catch (...) { - response["status"] = iganet::webapp::status:: - invalidExportRequest; - response["reason"] = - "Invalid EXPORTXML request. Valid " - "EXPORTXML " - "EXPORTXML " - "requests are \"exportxml/\", " - "\"exportxml//" - "\" and " - "and " - "\"exportxml//" - "/" - "\""; - ws->send(response.dump(), uWS::OpCode::TEXT, - true); - } - - } // EXPORTXML - - else if (tokens[0] == "refine") { - // - // request: refine/* - // - - try { - - if (tokens.size() == 3) { - // - // request: - // refine// - // - - // Get session - auto session = - ws->getUserData()->getSession(tokens[1]); - - // Get model - auto model = - session->getModel(stoi(tokens[2])); - - // Refine an existing model - if (auto m = std::dynamic_pointer_cast< - iganet::ModelRefine>(model)) - m->refine(request); - else { - response["status"] = iganet::webapp:: - status::invalidRefineRequest; - response["reason"] = - "Invalid REFINE request. Valid REFINE " - "requests are " - "\"refine//" - "\""; - } - ws->send(response.dump(), uWS::OpCode::TEXT, - true); - - // Broadcast refinement of model instance - nlohmann::json broadcast; - broadcast["id"] = session->getUUID(); - broadcast["request"] = "refine/instance"; - broadcast["data"]["id"] = stoi(tokens[2]); - ws->publish(session->getUUID(), - broadcast.dump(), - uWS::OpCode::TEXT); - } - - else - throw std::runtime_error( - "Invalid REFINE request"); - - } catch (...) { - response["status"] = iganet::webapp::status:: - invalidRefineRequest; - response["reason"] = - "Invalid REFINE request. Valid REFINE " - "requests " - "are " - "\"refine//\""; - ws->send(response.dump(), uWS::OpCode::TEXT, - true); - } - - } // REFINE - - else if (tokens[0] == "elevate") { - // - // request: elevate/* - // - - try { - - if (tokens.size() == 3) { - // - // request: - // elevate// - // - - // Get session - auto session = - ws->getUserData()->getSession(tokens[1]); - - // Get model - auto model = - session->getModel(stoi(tokens[2])); - - // Degree elevate an existing model - if (auto m = std::dynamic_pointer_cast< - iganet::ModelElevate>(model)) - m->elevate(request); - else { - response["status"] = iganet::webapp:: - status::invalidElevateRequest; - response["reason"] = - "Invalid ELEVATE request. Valid " - "ELEVATE " - "requests are " - "\"elevate//" - "\""; - } - ws->send(response.dump(), uWS::OpCode::TEXT, - true); - - // Broadcast degree elevation of model - // instance - nlohmann::json broadcast; - broadcast["id"] = session->getUUID(); - broadcast["request"] = "elevate/instance"; - broadcast["data"]["id"] = stoi(tokens[2]); - ws->publish(session->getUUID(), - broadcast.dump(), - uWS::OpCode::TEXT); - } - - else - throw std::runtime_error( - "Invalid ELEVATE request"); - - } catch (...) { - response["status"] = iganet::webapp::status:: - invalidElevateRequest; - response["reason"] = - "Invalid ELEVATE request. Valid ELEVATE " - "requests " - "are " - "\"elevate//" - "\""; - ws->send(response.dump(), uWS::OpCode::TEXT, - true); - } - - } // ELEVATE - - else if (tokens[0] == "increase") { - // - // request: increase/* - // - - try { - - if (tokens.size() == 3) { - // - // request: - // increase// - // - - // Get session - auto session = - ws->getUserData()->getSession(tokens[1]); - - // Get model - auto model = - session->getModel(stoi(tokens[2])); - - // Degree increase an existing model - if (auto m = std::dynamic_pointer_cast< - iganet::ModelIncrease>(model)) - m->increase(request); - else { - response["status"] = iganet::webapp:: - status::invalidIncreaseRequest; - response["reason"] = - "Invalid INCREASE request. Valid " - "INCREASE " - "requests are " - "\"increase//" - "\""; - } - ws->send(response.dump(), uWS::OpCode::TEXT, - true); - - // Broadcast degree increase of model - // instance - nlohmann::json broadcast; - broadcast["id"] = session->getUUID(); - broadcast["request"] = "increase/instance"; - broadcast["data"]["id"] = stoi(tokens[2]); - ws->publish(session->getUUID(), - broadcast.dump(), - uWS::OpCode::TEXT); - } - - else - throw std::runtime_error( - "Invalid INCREASE request"); - - } catch (...) { - response["status"] = iganet::webapp::status:: - invalidElevateRequest; - response["reason"] = - "Invalid INCREASE request. Valid INCREASE " - "requests " - "are " - "\"increase//" - "\""; - ws->send(response.dump(), uWS::OpCode::TEXT, - true); - } - - } // INCREASE - - else if (tokens[0] == "reparameterize") { - // - // request: reparameterize/* - // - - try { - - if (tokens.size() == 3) { - // - // request: - // reparameterize// - // - - // Get session - auto session = - ws->getUserData()->getSession(tokens[1]); - - // Get model - auto model = - session->getModel(stoi(tokens[2])); - - // Reparameterize an existing model - if (auto m = std::dynamic_pointer_cast< - iganet::ModelReparameterize>(model)) - m->reparameterize(request); - else { - response["status"] = iganet::webapp:: - status::invalidReparameterizeRequest; - response["reason"] = - "Invalid REPARAMETERIZE request. " - "Valid REPARAMETERIZE " - "requests are " - "\"reparameterize//" - "\""; - } - ws->send(response.dump(), uWS::OpCode::TEXT, - true); - - // Broadcast reparameterization of model - // instance - nlohmann::json broadcast; - broadcast["id"] = session->getUUID(); - broadcast["request"] = - "reparameterize/instance"; - broadcast["data"]["id"] = stoi(tokens[2]); - ws->publish(session->getUUID(), - broadcast.dump(), - uWS::OpCode::TEXT); - } - - else - throw std::runtime_error( - "Invalid REPARAMETERIZE request"); - - } catch (...) { - response["status"] = iganet::webapp::status:: - invalidReparameterizeRequest; - response["reason"] = - "Invalid REPARAMETERIZE request. Valid " - "REPARAMETERIZE " - "requests " - "are " - "\"reparameterize//" - "\""; - ws->send(response.dump(), uWS::OpCode::TEXT, - true); - } - - } // REPARAMETERIZE - - else if (tokens[0] == "info") { - // - // request: info/* - // - - try { - - if (tokens.size() == 1) { - // - // request: info - // - - // Get list of all active sessions - auto sessions = nlohmann::json::array(); - for (const auto &session : - ws->getUserData()->getSessions()) { - auto json = nlohmann::json(); - auto creation_time = std::chrono::system_clock::to_time_t(session.second->getCreationTime()); - auto access_time = std::chrono::system_clock::to_time_t(session.second->getAccessTime()); - json["id"] = session.first; - json["creation_time"] = std::ctime(&creation_time); - json["access_time"] = std::ctime(&access_time); - json["hasHash"] = - session.second->hasHash(); - - json["models"] = - session.second->getModels().size(); - - sessions.push_back(json); - } - response["data"]["sessions"] = sessions; - ws->send(response.dump(), uWS::OpCode::TEXT, - true); - } - - else - throw std::runtime_error( - "Invalid INFO request"); - - } catch (...) { - response["status"] = - iganet::webapp::status::invalidGetRequest; - response["reason"] = "Invalid INFO request. " - "Valid INFO requests " - "are " - "\"info\""; - ws->send(response.dump(), uWS::OpCode::TEXT, - true); - } - - } // INFO - - else { - response["status"] = - iganet::webapp::status::invalidRequest; - response["reason"] = "Invalid request"; - ws->send(response.dump(), uWS::OpCode::TEXT, - true); - } - } catch (std::exception &e) { - nlohmann::json response; - try { - auto request = nlohmann::json::parse(message); - response["request"] = request["id"]; - response["status"] = - iganet::webapp::status::invalidRequest; - response["reason"] = e.what(); - ws->send(response.dump(), uWS::OpCode::TEXT, - true); - } catch (...) { - response["request"] = "unknown"; - response["status"] = - iganet::webapp::status::invalidRequest; - response["reason"] = "Invalid request"; - ws->send(response.dump(), uWS::OpCode::TEXT, - true); - } - } - }, - .drain = - [](auto *ws) { - /* Check ws->getBufferedAmount() here */ - }, - .ping = - [](auto *ws, std::string_view) { - /* Not implemented yet */ - }, - .pong = - [](auto *ws, std::string_view) { - /* Not implemented yet */ - }, - .close = - [](auto *ws, int code, std::string_view message) { - /* You may access ws->getUserData() here */ -#ifndef NDEBUG - std::stringstream msg; - msg << "[Thread " << std::this_thread::get_id() - << "] Connection has been closed\n"; - std::clog << msg.str(); -#endif - }}) - .listen(port_option->value(), - [&port_option](auto *listen_socket) { - if (listen_socket) { - std::stringstream msg; - msg << "[Thread " << std::this_thread::get_id() - << "] Listening on port " - << port_option->value() << std::endl; - std::clog << msg.str(); - } else { - std::stringstream msg; - msg << "[Thread " << std::this_thread::get_id() - << "] Failed to listen on port " - << port_option->value() << std::endl; - std::clog << msg.str(); - } - }) - .run(); - } catch (std::exception &e) { - std::cerr << e.what(); - } - }); - }); - - std::for_each(threads.begin(), threads.end(), - [](std::thread *t) { t->join(); }); - - return 0; -} +/** + @file webapps/server.cxx + + @brief Demonstration of a server application + + @author Matthias Moller + + @copyright This file is part of the IgANet project + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace iganet { +namespace webapp { + +/// @brief Enumerator for specifying the status +enum class status : short_t { + success = 0, /*!< request was handled successfully */ + invalidRequest = 1, /*!< invalid request */ + invalidCreateRequest = 2, /*!< invalid create request */ + invalidRemoveRequest = 3, /*!< invalid remove request */ + invalidConnectRequest = 4, /*!< invalid connect request */ + invalidDisconnectRequest = 5, /*!< invalid disconnect request */ + invalidGetRequest = 6, /*!< invalid get request */ + invalidPutRequest = 7, /*!< invalid put request */ + invalidEvalRequest = 8, /*!< invalid eval request */ + invalidRefineRequest = 9, /*!< invalid refine request */ + invalidElevateRequest = 10, /*!< invalid degree elevate request */ + invalidIncreaseRequest = 11, /*!< invalid degree increase request */ + invalidReparameterizeRequest = 12, /*!< invalid reparameterize request */ + invalidLoadRequest = 13, /*!< invalid load request */ + invalidSaveRequest = 14, /*!< invalid save request */ + invalidImportRequest = 15, /*!< invalid import request */ + invalidExportRequest = 16, /*!< invalid export request */ + invalidComputeErrorRequest = 17 /*!< invalid compute error request */ +}; + +/// @brief InvalidSessionId exception +struct InvalidSessionIdException : public std::exception { + const char *what() const throw() { return "Invalid session id"; } +}; + +/// @brief InvalidModelId exception +struct InvalidModelIdException : public std::exception { + const char *what() const throw() { return "Invalid model id"; } +}; + +/// @brief InvalidModelType exception +struct InvalidModelTypeException : public std::exception { + const char *what() const throw() { return "Invalid model type"; } +}; + +/// @brief Tokenize the input string +std::vector tokenize(std::string str, + std::string separator = "/") { + std::vector tokens; + for (auto i = strtok(&str[0], &separator[0]); i != NULL; + i = strtok(NULL, &separator[0])) + tokens.push_back(i); + return tokens; +} + +/// @brief Session +template struct Session { +private: + /// @brief Session UUID + const std::string uuid_; + + /// @brief Hashed password + const std::string hash_; + + /// @brief Creation time stamp + std::chrono::system_clock::time_point creation_time_; + + /// @brief Access time stamp + std::chrono::system_clock::time_point access_time_; + +public: + /// @brief Default constructor + Session(std::string hash) + : uuid_(iganet::utils::uuid::create()), hash_(hash), + creation_time_(std::chrono::system_clock::now()), + access_time_(std::chrono::system_clock::now()) {} + + /// @brief Returns the UUID + inline const std::string &getUUID() const { return uuid_; } + + /// @brief Returns a constant reference to the list of models + inline const auto &getModels() const { return models; } + + /// @brief Returns a non-constant reference to the list of models + inline auto &getModels() { return models; } + + /// @brief Returns the requested model or throws an exception + inline std::shared_ptr getModel(int64_t id) { + auto it = models.find(id); + if (it == models.end()) + throw InvalidModelIdException(); + else + return it->second; + } + + /// @brief Returns the model and removes it from the list of models + inline std::shared_ptr removeModel(int64_t id) { + auto it = models.find(id); + if (it == models.end()) + throw InvalidModelIdException(); + else { + auto model = it->second; + models.erase(it); + return model; + } + } + + /// @brief Returns true if the session has a non-zero hash + inline bool hasHash() const { return (hash_ != ""); } + + /// @brief Returns true if the provided hash coincides with the session's hash + inline bool checkHash(std::string hash) const { return (hash_ == hash); } + + /// @brief Updates the access time stamp + inline void access() { access_time_ = std::chrono::system_clock::now(); } + + /// @brief Returns the creation time + inline std::chrono::system_clock::time_point getCreationTime() const { + return creation_time_; + } + + /// @brief Returns the access time + inline std::chrono::system_clock::time_point getAccessTime() const { + return access_time_; + } + + /// @brief List of models + std::map> models; +}; + +/// @brief Sessions structure +template struct Sessions { +public: + /// @brief Returns the requested session or throws an exception + inline std::shared_ptr> getSession(std::string uuid) { + auto it = sessions_.find(uuid); + if (it == sessions_.end()) + throw InvalidSessionIdException(); + else { + it->second->access(); + return it->second; + } + } + + /// @brief Returns a new session + inline std::shared_ptr> createSession(std::string hash) { + auto session = + std::make_shared>(hash); + sessions_[session->getUUID()] = session; + return session; + } + + /// @brief Returns the session and removes it from the list of sessions + inline std::shared_ptr> removeSession(std::string uuid) { + auto it = sessions_.find(uuid); + if (it == sessions_.end()) + throw InvalidSessionIdException(); + else { + auto session = it->second; + sessions_.erase(it); + return session; + } + } + + /// @brief Add path to model path + inline static void addModelPath(const std::string &path) { + models_.addModelPath(path); + } + + /// @brief Add list of paths to model path + inline static void addModelPath(const std::vector &path) { + models_.addModelPath(path); + } + + /// @brief Returns a non-constant reference to the list of sessions + inline static auto &getSessions() { return sessions_; } + + /// @brief Returns a non-constant reference to the list of models + inline static auto &getModels() { return models_; } + +private: + /// @brief List of sessions shared between all sockets + inline static std::map>> sessions_; + + /// @brief List of models + inline static ModelManager models_ = + ModelManager(iganet::webapp::tokenize("webapps/models,models", ",")); +}; + +} // namespace webapp +} // namespace iganet + +int main(int argc, char const *argv[]) { + using PerSocketData = iganet::webapp::Sessions; + + popl::OptionParser op("Allowed options"); + auto help_option = op.add("h", "help", "print help message"); + auto port_option = + op.add>("p", "port", "TCP port of the server", 9001); + auto config_option = op.add>( + "f", "configfile", "configuration file", ""); + auto keyfile_option = op.add>( + "k", "keyfile", "key file for SSL encryption", ""); + auto certfile_option = op.add>( + "c", "certfile", "certificate file for SSL encryption", ""); + auto modelpath_option = op.add>( + "m", "modelpath", "path to model files", ""); + auto passphrase_option = op.add>( + "a", "passphrase", "passphrase for SSL encryption", ""); + auto threads_option = + op.add>("t", "threads", "number of server threads", 1); + + op.parse(argc, argv); + + // Print auto-generated help message + if (help_option->count() == 1) { + std::cout << op << std::endl; + return 0; + } else if (help_option->count() == 2) { + std::cout << op.help(popl::Attribute::advanced) << std::endl; + return 0; + } else if (help_option->count() > 2) { + std::cout << op.help(popl::Attribute::expert) << std::endl; + return 0; + } + + // Initialize backend + iganet::init(); + + // Load configuration from file + nlohmann::json config; + if (!config_option->value().empty()) { + std::ifstream file(config_option->value()); + if (file) { + try { + config = nlohmann::json::parse(file); + } catch (std::exception &e) { + std::cerr << e.what(); + return -1; + } + } else { + std::ifstream file(std::filesystem::path(__FILE__).replace_filename( + config_option->value())); + if (file) { + try { + config = nlohmann::json::parse(file); + } catch (std::exception &e) { + std::cerr << e.what(); + return -1; + } + } + } + } + + // Override commandline arguments + if (port_option->is_set()) + config["port"] = port_option->value(); + + if (keyfile_option->is_set()) + config["keyFile"] = keyfile_option->value(); + + if (certfile_option->is_set()) + config["certFile"] = certfile_option->value(); + + if (passphrase_option->is_set()) + config["passphrase"] = passphrase_option->value(); + + if (modelpath_option->is_set()) + config["modelPath"] = modelpath_option->value(); + + if (threads_option->is_set()) + config["numThreads"] = threads_option->value(); + + // Check if key file is available + if (config.contains("keyFile")) { + if (!std::filesystem::exists( + std::filesystem::path(config["keyFile"].get()))) { + if (std::filesystem::exists( + std::filesystem::path(__FILE__).replace_filename( + config["keyFile"].get()))) { + config["keyFile"] = std::filesystem::path(__FILE__).replace_filename( + config["keyFile"].get()); + } else { + throw std::runtime_error("Unable to open key file " + + config["keyFile"].get()); + } + } + } + + // Check if cert file is available + if (config.contains("certFile")) { + if (!std::filesystem::exists( + std::filesystem::path(config["certFile"].get()))) { + if (std::filesystem::exists( + std::filesystem::path(__FILE__).replace_filename( + config["certFile"].get()))) { + config["certFile"] = std::filesystem::path(__FILE__).replace_filename( + config["certFile"].get()); + } else { + throw std::runtime_error("Unable to open cert file " + + config["certFile"].get()); + } + } + } + + // Add paths to model search path + if (config.contains("modelPath")) + PerSocketData::addModelPath( + iganet::webapp::tokenize(config["modelPath"].get(), ",")); + + // Multi-threaded websocket application + std::vector threads(config.contains("numThreads") + ? config["numThreads"].get() + : std::thread::hardware_concurrency()); + + std::transform( + threads.begin(), threads.end(), threads.begin(), + [&config, &port_option](std::thread *) { + return new std::thread([&config, &port_option]() { + // Create WebSocket application + try { + uWS::SSLApp( + {.key_file_name = + (config.contains("keyFile") + ? config["keyFile"].get().c_str() + : std::filesystem::path(__FILE__) + .replace_filename("key.pem") + .c_str()), + .cert_file_name = + (config.contains("certFile") + ? config["certFile"].get().c_str() + : std::filesystem::path(__FILE__) + .replace_filename("cert.pem") + .c_str()), + .passphrase = + (config.contains("passphrase") + ? config["passphrase"].get().c_str() + : "")}) + .ws( + "/*", + {/* Settings */ + .compression = + uWS::CompressOptions(uWS::DEDICATED_COMPRESSOR_4KB | + uWS::DEDICATED_DECOMPRESSOR), + .maxPayloadLength = + (config.contains("maxPayloadLength") + ? config["maxPayloadLength"].get() + : 100 * 1024 * 1024), + .idleTimeout = + (config.contains("idleTimeout") + ? config["idleTimeout"].get() + : static_cast(16)), + .maxBackpressure = + (config.contains("maxBackpressure") + ? config["maxBackpressure"].get() + : 100 * 1024 * 1024), + .closeOnBackpressureLimit = + (config.contains("closeOnBackpressureLimit") + ? config["closeOnBackpressureLimit"].get() + : false), + .resetIdleTimeoutOnSend = + (config.contains("resetIdleTimeoutOnSend") + ? config["resetIdleTimeoutOnSend"].get() + : false), + .sendPingsAutomatically = + (config.contains("sendPingsAutomatically") + ? config["sendPingsAutomatically"].get() + : true), + /* Handlers */ + .upgrade = nullptr, + .open = + [](auto *ws) { +#ifndef NDEBUG + std::stringstream msg; + msg << "[Thread " << std::this_thread::get_id() + << "] Connection has been opened\n"; + std::clog << msg.str(); +#endif + }, + .message = + [](auto *ws, std::string_view message, + uWS::OpCode opCode) { + try { + // Tokenize request + auto request = nlohmann::json::parse(message); + auto tokens = iganet::webapp::tokenize( + request["request"].get()); + + // Prepare response + nlohmann::json response; + response["request"] = request["id"]; + response["status"] = + iganet::webapp::status::success; + +#ifndef NDEBUG + std::stringstream msg; + for (auto const &token : tokens) + msg << "[Thread " << std::this_thread::get_id() + << "] " << token << "/"; + msg << std::endl; + std::clog << msg.str(); +#endif + + // Dispatch request + if (tokens[0] == "get") { + // + // request: get/* + // + + try { + + if (tokens.size() == 1) { + // + // request: get + // + + // Get list of all active sessions + std::vector ids; + for (const auto &session : + ws->getUserData()->getSessions()) + ids.push_back(session.first); + response["data"]["ids"] = ids; + ws->send(response.dump(), uWS::OpCode::TEXT, + true); + } + + else if (tokens.size() == 2) { + // + // request: get/ + // + + // Get session + auto session = + ws->getUserData()->getSession(tokens[1]); + + // Get list of all active models in session + std::vector ids; + auto models = nlohmann::json::array(); + for (const auto &model : + session->getModels()) { + ids.push_back(model.first); + models.push_back(model.second->getModel()); + } + response["data"]["ids"] = ids; + response["data"]["models"] = models; + ws->send(response.dump(), uWS::OpCode::TEXT, + true); + } + + else if (tokens.size() == 3) { + // + // request: get// + // + + // Get session + auto session = + ws->getUserData()->getSession(tokens[1]); + + // Get model + auto model = + session->getModel(stoi(tokens[2])); + + // Serialize model to JSON + response["data"] = model->to_json("", ""); + response["data"]["model"] = + model->getModel(); + ws->send(response.dump(), uWS::OpCode::TEXT, + true); + } + + else if (tokens.size() == 4) { + // + // request: + // get/// + // + // or + // + // request: + // get/// + // + + // Get session + auto session = + ws->getUserData()->getSession(tokens[1]); + + // Get model + auto model = + session->getModel(stoi(tokens[2])); + + // Serialize model component to JSON + response["data"] = + model->to_json(tokens[3], ""); + ws->send(response.dump(), uWS::OpCode::TEXT, + true); + } + + else if (tokens.size() == 5) { + // + // request: + // get//// + // + + // Get session + auto session = + ws->getUserData()->getSession(tokens[1]); + + // Get model + auto model = + session->getModel(stoi(tokens[2])); + + // Serialize attribute of model component to + // JSON + response["data"] = + model->to_json(tokens[3], tokens[4]); + ws->send(response.dump(), uWS::OpCode::TEXT, + true); + } + + else + throw std::runtime_error( + "Invalid GET request"); + + } catch (...) { + response["status"] = + iganet::webapp::status::invalidGetRequest; + response["reason"] = + "Invalid GET request. Valid GET requests " + "are " + "\"get\", \"get/\", " + "\"get//\", " + "and " + "\"get///" + "\", and " + "\"get///" + "/\""; + ws->send(response.dump(), uWS::OpCode::TEXT, + true); + } + + } // GET + + else if (tokens[0] == "put") { + // + // request: put/* + // + + try { + + if (tokens.size() == 4) { + // + // request: + // put/// + // + + // Get session + auto session = + ws->getUserData()->getSession(tokens[1]); + + // Get model + auto model = + session->getModel(stoi(tokens[2])); + + // Update model attribute + response["data"] = model->updateAttribute( + "", tokens[3], request); + ws->send(response.dump(), uWS::OpCode::TEXT, + true); + + // Broadcast update of model instance + nlohmann::json broadcast; + broadcast["id"] = session->getUUID(); + broadcast["request"] = "update/instance"; + broadcast["data"]["id"] = stoi(tokens[2]); + broadcast["data"]["component"] = ""; + broadcast["data"]["attribute"] = tokens[3]; + ws->publish(session->getUUID(), + broadcast.dump(), + uWS::OpCode::TEXT); + } + + else if (tokens.size() == 5) { + // + // request: + // put//// + // + + // Get session + auto session = + ws->getUserData()->getSession(tokens[1]); + + // Get model + auto model = + session->getModel(stoi(tokens[2])); + + // Update model attribute + response["data"] = model->updateAttribute( + tokens[3], tokens[4], request); + ws->send(response.dump(), uWS::OpCode::TEXT, + true); + + // Broadcast update of model instance + nlohmann::json broadcast; + broadcast["id"] = session->getUUID(); + broadcast["request"] = "update/instance"; + broadcast["data"]["id"] = stoi(tokens[2]); + broadcast["data"]["component"] = tokens[3]; + broadcast["data"]["attribute"] = tokens[4]; + ws->publish(session->getUUID(), + broadcast.dump(), + uWS::OpCode::TEXT); + } + + else + throw std::runtime_error( + "Invalid PUT request"); + + } catch (...) { + response["status"] = + iganet::webapp::status::invalidPutRequest; + response["reason"] = + "Invalid PUT request. Valid PUT requests " + "are " + "\"put///" + "\", and " + "\"put///" + "/\""; + ws->send(response.dump(), uWS::OpCode::TEXT, + true); + } + + } // PUT + + else if (tokens[0] == "create") { + // + // request: create/* + // + + try { + + if (tokens.size() == 2 && + tokens[1] == "session") { + // + // request: create/session + // + + // Get password hash + std::string hash(""); + if (request.contains("data")) + if (request["data"].contains("hash")) + hash = request["data"]["hash"] + .get(); + + // Create a new session + auto session = + ws->getUserData()->createSession(hash); + std::string uuid = session->getUUID(); + + response["data"]["id"] = uuid; + response["data"]["models"] = + ws->getUserData() + ->getModels() + .getModels(); + ws->send(response.dump(), uWS::OpCode::TEXT, + true); + + // Subscribe to new session + ws->subscribe(uuid); + + // Broadcast creation of a new session + nlohmann::json broadcast; + broadcast["id"] = uuid; + broadcast["request"] = "create/session"; + broadcast["data"]["id"] = uuid; + ws->publish(uuid, broadcast.dump(), + uWS::OpCode::TEXT); + } + + else if (tokens.size() == 3) { + // + // request: create// + // + + // Get session + auto session = + ws->getUserData()->getSession(tokens[1]); + + // Get new model's id + int64_t id = (session->getModels().size() > 0 + ? session->getModels() + .crbegin() + ->first + + 1 + : 0); + + // Create a new model instance + session->models[id] = + ws->getUserData()->getModels().create( + tokens[2], request); + response["data"]["id"] = std::to_string(id); + response["data"]["model"] = + session->models[id]->getModel(); + ws->send(response.dump(), uWS::OpCode::TEXT, + true); + + // Broadcast creation of a new model instance + nlohmann::json broadcast; + broadcast["id"] = session->getUUID(); + broadcast["request"] = "create/instance"; + broadcast["data"]["id"] = id; + broadcast["data"]["model"] = + session->models[id]->getModel(); + ws->publish(session->getUUID(), + broadcast.dump(), + uWS::OpCode::TEXT); + } + + else + throw std::runtime_error( + "Invalid CREATE request"); + + } catch (...) { + response["status"] = iganet::webapp::status:: + invalidCreateRequest; + response["reason"] = + "Invalid CREATE request. Valid CREATE " + "requests " + "are \"create/session\" and " + "\"create//\""; + ws->send(response.dump(), uWS::OpCode::TEXT, + true); + } + + } // CREATE + + else if (tokens[0] == "remove") { + // + // request: remove/* + // + + try { + + if (tokens.size() == 2) { + // + // request: remove/ + // + + // Remove session + auto session = + ws->getUserData()->removeSession( + tokens[1]); + ws->send(response.dump(), uWS::OpCode::TEXT, + true); + + // Broadcast removal of session + nlohmann::json broadcast; + broadcast["id"] = session->getUUID(); + broadcast["request"] = "remove/session"; + broadcast["data"]["id"] = session->getUUID(); + ws->publish(session->getUUID(), + broadcast.dump(), + uWS::OpCode::TEXT); + } + + else if (tokens.size() == 3) { + // + // request: + // remove// + // + + // Get session + auto session = + ws->getUserData()->getSession(tokens[1]); + + // Remove model + auto model = + session->removeModel(stoi(tokens[2])); + ws->send(response.dump(), uWS::OpCode::TEXT, + true); + + // Broadcast removal of model instance + nlohmann::json broadcast; + broadcast["id"] = session->getUUID(); + broadcast["request"] = "remove/instance"; + broadcast["data"]["id"] = stoi(tokens[2]); + ws->publish(session->getUUID(), + broadcast.dump(), + uWS::OpCode::TEXT); + } + + else + throw std::runtime_error( + "Invalid REMOVE request"); + + } catch (...) { + response["status"] = iganet::webapp::status:: + invalidRemoveRequest; + response["reason"] = + "Invalid REMOVE request. Valid REMOVE " + "requests " + "are " + "\"remove/\" and " + "\"remove//\""; + ws->send(response.dump(), uWS::OpCode::TEXT, + true); + } + + } // REMOVE + + else if (tokens[0] == "connect") { + // + // request: connect/* + // + + try { + + if (tokens.size() == 2) { + // + // request: connect/ + // + + // Get session + auto session = + ws->getUserData()->getSession(tokens[1]); + + // Get password hash + std::string hash(""); + if (request.contains("data")) + if (request["data"].contains("hash")) + hash = request["data"]["hash"] + .get(); + + if (!session->checkHash(hash)) + throw std::runtime_error( + "Invalid CONNECT request. Invalid " + "ession password."); + + // Connect to an existing session + response["data"]["id"] = session->getUUID(); + response["data"]["models"] = + ws->getUserData() + ->getModels() + .getModels(); + ws->send(response.dump(), uWS::OpCode::TEXT, + true); + + // Subscribe to existing session + ws->subscribe(session->getUUID()); + } + + else + throw std::runtime_error( + "Invalid CONNECT request"); + + } catch (...) { + response["status"] = iganet::webapp::status:: + invalidConnectRequest; + response["reason"] = + "Invalid CONNECT request. Valid CONNECT " + "requests " + "are \"connect/\""; + ws->send(response.dump(), uWS::OpCode::TEXT, + true); + } + + } // CONNECT + + else if (tokens[0] == "disconnect") { + // + // request: diconnect/* + // + + try { + + if (tokens.size() == 2) { + // + // request: diconnect/ + // + + // Get session + auto session = + ws->getUserData()->getSession(tokens[1]); + + // Disconnect from an existing session + response["data"]["id"] = session->getUUID(); + ws->send(response.dump(), uWS::OpCode::TEXT, + true); + + // Unsubscribe from existing session + ws->unsubscribe(session->getUUID()); + } + + else + throw std::runtime_error( + "Invalid DISCONNECT request"); + + } catch (...) { + response["status"] = iganet::webapp::status:: + invalidDisconnectRequest; + response["reason"] = + "Invalid DISCONNECT request. Valid " + "DISCONNECT " + "requests are " + "\"diconnect/\""; + ws->send(response.dump(), uWS::OpCode::TEXT, + true); + } + + } // DISCONNECT + + else if (tokens[0] == "eval") { + // + // request: eval/* + // + + try { + + if (tokens.size() == 4) { + // + // request: + // eval/// + // + + // Get session + auto session = + ws->getUserData()->getSession(tokens[1]); + + // Get model + auto model = + session->getModel(stoi(tokens[2])); + + // Evaluate an existing model + if (auto m = std::dynamic_pointer_cast< + iganet::ModelEval>(model)) + response["data"] = + m->eval(tokens[3], request); + else { + response["status"] = iganet::webapp:: + status::invalidEvalRequest; + response["reason"] = + "Invalid EVAL request. Valid EVAL " + "requests " + "are " + "\"eval///" + "\""; + } + ws->send(response.dump(), uWS::OpCode::TEXT, + true); + } + + else + throw std::runtime_error( + "Invalid EVAL request"); + + } catch (...) { + response["status"] = + iganet::webapp::status::invalidEvalRequest; + response["reason"] = + "Invalid EVAL request. Valid EVAL " + "requests are " + "\"eval///" + "\""; + ws->send(response.dump(), uWS::OpCode::TEXT, + true); + } + + } // EVAL + + else if (tokens[0] == "load") { + // + // request: load/* + // + + try { + + if (tokens.size() == 2) { + // + // request: load/ + // + + // Get session + auto session = + ws->getUserData()->getSession(tokens[1]); + + // Get binary data + auto instances = + request["data"]["instances"]; + + // Create vector if ids and array of models + std::vector ids; + auto models = nlohmann::json::array(); + + // Loop over all instances + for (const auto &instance : instances) { + + // Get new model's id + int64_t id = + (session->getModels().size() > 0 + ? session->getModels() + .crbegin() + ->first + + 1 + : 0); + + nlohmann::json request; + request["data"]["binary"] = instance; + + // Create a new model instance from binary + // data stream + session->models[id] = + ws->getUserData()->getModels().load( + request); + ids.push_back(id); + models.push_back( + session->models[id]->getModel()); + + // Broadcast creation of a new model + // instance + nlohmann::json broadcast; + broadcast["id"] = session->getUUID(); + broadcast["request"] = "create/instance"; + broadcast["data"]["id"] = id; + broadcast["data"]["model"] = + session->models[id]->getModel(); + ws->publish(session->getUUID(), + broadcast.dump(), + uWS::OpCode::TEXT); + } + + response["data"]["ids"] = ids; + response["data"]["models"] = models; + ws->send(response.dump(), uWS::OpCode::TEXT, + true); + } + + else + throw std::runtime_error( + "Invalid LOAD request"); + + } catch (...) { + response["status"] = + iganet::webapp::status::invalidLoadRequest; + response["reason"] = "Invalid LOAD request. " + "Valid LOAD requests are " + "\"load/session\" and " + "\"load/\""; + ws->send(response.dump(), uWS::OpCode::TEXT, + true); + } + + } // LOAD + + else if (tokens[0] == "save") { + // + // request: save/* + // + + try { + + if (tokens.size() == 2) { + // + // request: save/ + // + + // Get session + auto session = + ws->getUserData()->getSession(tokens[1]); + + // Save all active models in session + auto models = nlohmann::json::array(); + for (const auto &model : + session->getModels()) { + if (auto m = std::dynamic_pointer_cast< + iganet::ModelSerialize>( + model.second)) { + models.push_back(m->save()); + } + } + response["data"] = models; + ws->send(response.dump(), uWS::OpCode::TEXT, + true); + } + + else if (tokens.size() == 3) { + // + // request: + // save// + // + + // Get session + auto session = + ws->getUserData()->getSession(tokens[1]); + + // Get model + auto model = + session->getModel(stoi(tokens[2])); + + // Save model + if (auto m = std::dynamic_pointer_cast< + iganet::ModelSerialize>(model)) + response["data"] = m->save(); + else { + response["status"] = iganet::webapp:: + status::invalidSaveRequest; + response["reason"] = + "Invalid SAVE request. Valid SAVE " + "requests " + "are " + "\"save/\" and " + "\"save//" + "\""; + } + ws->send(response.dump(), uWS::OpCode::TEXT, + true); + } + + else + throw std::runtime_error( + "Invalid SAVE request"); + + } catch (...) { + response["status"] = + iganet::webapp::status::invalidSaveRequest; + response["reason"] = + "Invalid SAVE request. Valid SAVE " + "requests are " + "\"save/\" and " + "\"save//\""; + ws->send(response.dump(), uWS::OpCode::TEXT, + true); + } + + } // SAVE + + else if (tokens[0] == "importxml") { + // + // request: importxml/* + // + + try { + + if (tokens.size() == 2) { + // + // request: importxml/ + // + + // Get session + auto session = + ws->getUserData()->getSession(tokens[1]); + + // Load all existing models from XML + for (const auto &model : + session->getModels()) { + if (auto m = std::dynamic_pointer_cast< + iganet::ModelXML>(model.second)) { + m->importXML(request, "", model.first); + } else { + response["status"] = iganet::webapp:: + status::invalidImportRequest; + response["reason"] = + "Invalid IMPORTXML request. Valid " + "IMPORTXML " + "requests are " + "\"importxml/\", " + "\"importxml//" + "\" and " + "\"importxml//" + "/" + "\""; + ws->send(response.dump(), + uWS::OpCode::TEXT, true); + break; + } + } + ws->send(response.dump(), uWS::OpCode::TEXT, + true); + + // Broadcast update of model instances + std::vector ids; + for (const auto &model : + session->getModels()) + ids.push_back(model.first); + + // Broadcast update of model instance + nlohmann::json broadcast; + broadcast["id"] = session->getUUID(); + broadcast["request"] = "update/instance"; + broadcast["data"]["ids"] = ids; + ws->publish(session->getUUID(), + broadcast.dump(), + uWS::OpCode::TEXT); + } + + else if (tokens.size() == 3) { + // + // request: + // importxml// + // + + // Get session + auto session = + ws->getUserData()->getSession(tokens[1]); + + // Get model + auto model = + session->getModel(stoi(tokens[2])); + + // Import an existing model from XML + if (auto m = std::dynamic_pointer_cast< + iganet::ModelXML>(model)) + m->importXML(request, "", -1); + else { + response["status"] = iganet::webapp:: + status::invalidImportRequest; + response["reason"] = + "Invalid IMPORTXML request. Valid " + "IMPORTXML " + "requests are " + "\"importxml/\", " + "\"importxml//" + "\" " + "and " + "\"importxml//" + "/" + "\""; + } + ws->send(response.dump(), uWS::OpCode::TEXT, + true); + + // Broadcast update of model instance + nlohmann::json broadcast; + broadcast["id"] = session->getUUID(); + broadcast["request"] = "update/instance"; + broadcast["data"]["id"] = stoi(tokens[2]); + ws->publish(session->getUUID(), + broadcast.dump(), + uWS::OpCode::TEXT); + } + + else if (tokens.size() == 4) { + // + // request: + // importxml/// + // + + // Get session + auto session = + ws->getUserData()->getSession(tokens[1]); + + // Get model + auto model = + session->getModel(stoi(tokens[2])); + + // Import an existing model component from + // XML + if (auto m = std::dynamic_pointer_cast< + iganet::ModelXML>(model)) + m->importXML(request, tokens[3], -1); + else { + response["status"] = iganet::webapp:: + status::invalidImportRequest; + response["reason"] = + "Invalid IMPORTXML request. Valid " + "IMPORTXML " + "requests are " + "\"importxml/\", " + "\"importxml//" + "\" " + "and " + "\"importxml//" + "/" + "\""; + } + ws->send(response.dump(), uWS::OpCode::TEXT, + true); + + // Broadcast update of model instance + nlohmann::json broadcast; + broadcast["id"] = session->getUUID(); + broadcast["request"] = "update/instance"; + broadcast["data"]["id"] = stoi(tokens[2]); + ws->publish(session->getUUID(), + broadcast.dump(), + uWS::OpCode::TEXT); + } + + else + throw std::runtime_error( + "Invalid IMPORTXML request"); + + } catch (...) { + response["status"] = iganet::webapp::status:: + invalidImportRequest; + response["reason"] = + "Invalid IMPORTXML request. Valid " + "IMPORTXML " + "IMPORTXML " + "requests are \"importxml/\", " + "\"importxml//" + "\" and " + "\"importxml//" + "/" + "\""; + ws->send(response.dump(), uWS::OpCode::TEXT, + true); + } + + } // IMPORTXML + + else if (tokens[0] == "exportxml") { + // + // request: exportxml/* + // + + try { + + if (tokens.size() == 2) { + // + // request: exportxml/ + // + + // Get session + auto session = + ws->getUserData()->getSession(tokens[1]); + + // Export all existing models to XML + pugi::xml_document doc; + pugi::xml_node xml = doc.append_child("xml"); + + for (const auto &model : + session->getModels()) { + if (auto m = std::dynamic_pointer_cast< + iganet::ModelXML>(model.second)) + xml = m->exportXML(xml, "", model.first); + else + throw std::runtime_error( + "Invalid EXPORTXML request"); + } + std::ostringstream oss; + doc.save(oss); + + response["data"]["xml"] = oss.str(); + ws->send(response.dump(), uWS::OpCode::TEXT, + true); + } + + else if (tokens.size() == 3) { + // + // request: + // exportxml// + // + + // Get session + auto session = + ws->getUserData()->getSession(tokens[1]); + + // Get model + auto model = + session->getModel(stoi(tokens[2])); + + // Export an existing model to XML + if (auto m = std::dynamic_pointer_cast< + iganet::ModelXML>(model)) + response["data"]["xml"] = + m->exportXML("", stoi(tokens[2])); + else + throw std::runtime_error( + "Invalid EXPORTXML request"); + + ws->send(response.dump(), uWS::OpCode::TEXT, + true); + } + + else if (tokens.size() == 4) { + // + // request: + // exportxml/// + // + + // Get session + auto session = + ws->getUserData()->getSession(tokens[1]); + + // Get model + auto model = + session->getModel(stoi(tokens[2])); + + // Export an existing model to XML + if (auto m = std::dynamic_pointer_cast< + iganet::ModelXML>(model)) + response["data"]["xml"] = m->exportXML( + tokens[3], stoi(tokens[2])); + else + throw std::runtime_error( + "Invalid EXPORTXML request"); + + ws->send(response.dump(), uWS::OpCode::TEXT, + true); + } + + else + throw std::runtime_error( + "Invalid EXPORTXML request"); + + } catch (...) { + response["status"] = iganet::webapp::status:: + invalidExportRequest; + response["reason"] = + "Invalid EXPORTXML request. Valid " + "EXPORTXML " + "EXPORTXML " + "requests are \"exportxml/\", " + "\"exportxml//" + "\" and " + "and " + "\"exportxml//" + "/" + "\""; + ws->send(response.dump(), uWS::OpCode::TEXT, + true); + } + + } // EXPORTXML + + else if (tokens[0] == "refine") { + // + // request: refine/* + // + + try { + + if (tokens.size() == 3) { + // + // request: + // refine// + // + + // Get session + auto session = + ws->getUserData()->getSession(tokens[1]); + + // Get model + auto model = + session->getModel(stoi(tokens[2])); + + // Refine an existing model + if (auto m = std::dynamic_pointer_cast< + iganet::ModelRefine>(model)) + m->refine(request); + else { + response["status"] = iganet::webapp:: + status::invalidRefineRequest; + response["reason"] = + "Invalid REFINE request. Valid REFINE " + "requests are " + "\"refine//" + "\""; + } + ws->send(response.dump(), uWS::OpCode::TEXT, + true); + + // Broadcast refinement of model instance + nlohmann::json broadcast; + broadcast["id"] = session->getUUID(); + broadcast["request"] = "refine/instance"; + broadcast["data"]["id"] = stoi(tokens[2]); + ws->publish(session->getUUID(), + broadcast.dump(), + uWS::OpCode::TEXT); + } + + else + throw std::runtime_error( + "Invalid REFINE request"); + + } catch (...) { + response["status"] = iganet::webapp::status:: + invalidRefineRequest; + response["reason"] = + "Invalid REFINE request. Valid REFINE " + "requests " + "are " + "\"refine//\""; + ws->send(response.dump(), uWS::OpCode::TEXT, + true); + } + + } // REFINE + + else if (tokens[0] == "elevate") { + // + // request: elevate/* + // + + try { + + if (tokens.size() == 3) { + // + // request: + // elevate// + // + + // Get session + auto session = + ws->getUserData()->getSession(tokens[1]); + + // Get model + auto model = + session->getModel(stoi(tokens[2])); + + // Degree elevate an existing model + if (auto m = std::dynamic_pointer_cast< + iganet::ModelElevate>(model)) + m->elevate(request); + else { + response["status"] = iganet::webapp:: + status::invalidElevateRequest; + response["reason"] = + "Invalid ELEVATE request. Valid " + "ELEVATE " + "requests are " + "\"elevate//" + "\""; + } + ws->send(response.dump(), uWS::OpCode::TEXT, + true); + + // Broadcast degree elevation of model + // instance + nlohmann::json broadcast; + broadcast["id"] = session->getUUID(); + broadcast["request"] = "elevate/instance"; + broadcast["data"]["id"] = stoi(tokens[2]); + ws->publish(session->getUUID(), + broadcast.dump(), + uWS::OpCode::TEXT); + } + + else + throw std::runtime_error( + "Invalid ELEVATE request"); + + } catch (...) { + response["status"] = iganet::webapp::status:: + invalidElevateRequest; + response["reason"] = + "Invalid ELEVATE request. Valid ELEVATE " + "requests " + "are " + "\"elevate//" + "\""; + ws->send(response.dump(), uWS::OpCode::TEXT, + true); + } + + } // ELEVATE + + else if (tokens[0] == "increase") { + // + // request: increase/* + // + + try { + + if (tokens.size() == 3) { + // + // request: + // increase// + // + + // Get session + auto session = + ws->getUserData()->getSession(tokens[1]); + + // Get model + auto model = + session->getModel(stoi(tokens[2])); + + // Degree increase an existing model + if (auto m = std::dynamic_pointer_cast< + iganet::ModelIncrease>(model)) + m->increase(request); + else { + response["status"] = iganet::webapp:: + status::invalidIncreaseRequest; + response["reason"] = + "Invalid INCREASE request. Valid " + "INCREASE " + "requests are " + "\"increase//" + "\""; + } + ws->send(response.dump(), uWS::OpCode::TEXT, + true); + + // Broadcast degree increase of model + // instance + nlohmann::json broadcast; + broadcast["id"] = session->getUUID(); + broadcast["request"] = "increase/instance"; + broadcast["data"]["id"] = stoi(tokens[2]); + ws->publish(session->getUUID(), + broadcast.dump(), + uWS::OpCode::TEXT); + } + + else + throw std::runtime_error( + "Invalid INCREASE request"); + + } catch (...) { + response["status"] = iganet::webapp::status:: + invalidElevateRequest; + response["reason"] = + "Invalid INCREASE request. Valid INCREASE " + "requests " + "are " + "\"increase//" + "\""; + ws->send(response.dump(), uWS::OpCode::TEXT, + true); + } + + } // INCREASE + + else if (tokens[0] == "reparameterize") { + // + // request: reparameterize/* + // + + try { + + if (tokens.size() == 3) { + // + // request: + // reparameterize// + // + + // Get session + auto session = + ws->getUserData()->getSession(tokens[1]); + + // Get model + auto model = + session->getModel(stoi(tokens[2])); + + // Reparameterize an existing model + if (auto m = std::dynamic_pointer_cast< + iganet::ModelReparameterize>(model)) + m->reparameterize(request); + else { + response["status"] = iganet::webapp:: + status::invalidReparameterizeRequest; + response["reason"] = + "Invalid REPARAMETERIZE request. " + "Valid REPARAMETERIZE " + "requests are " + "\"reparameterize//" + "\""; + } + ws->send(response.dump(), uWS::OpCode::TEXT, + true); + + // Broadcast reparameterization of model + // instance + nlohmann::json broadcast; + broadcast["id"] = session->getUUID(); + broadcast["request"] = + "reparameterize/instance"; + broadcast["data"]["id"] = stoi(tokens[2]); + ws->publish(session->getUUID(), + broadcast.dump(), + uWS::OpCode::TEXT); + } + + else + throw std::runtime_error( + "Invalid REPARAMETERIZE request"); + + } catch (...) { + response["status"] = iganet::webapp::status:: + invalidReparameterizeRequest; + response["reason"] = + "Invalid REPARAMETERIZE request. Valid " + "REPARAMETERIZE " + "requests " + "are " + "\"reparameterize//" + "\""; + ws->send(response.dump(), uWS::OpCode::TEXT, + true); + } + + } // REPARAMETERIZE + + else if (tokens[0] == "info") { + // + // request: info/* + // + + try { + + if (tokens.size() == 1) { + // + // request: info + // + + // Get list of all active sessions + auto sessions = nlohmann::json::array(); + for (const auto &session : + ws->getUserData()->getSessions()) { + auto json = nlohmann::json(); + auto creation_time = std::chrono::system_clock::to_time_t(session.second->getCreationTime()); + auto access_time = std::chrono::system_clock::to_time_t(session.second->getAccessTime()); + json["id"] = session.first; + json["creation_time"] = std::ctime(&creation_time); + json["access_time"] = std::ctime(&access_time); + json["hasHash"] = + session.second->hasHash(); + + json["models"] = + session.second->getModels().size(); + + sessions.push_back(json); + } + response["data"]["sessions"] = sessions; + ws->send(response.dump(), uWS::OpCode::TEXT, + true); + } + + else + throw std::runtime_error( + "Invalid INFO request"); + + } catch (...) { + response["status"] = + iganet::webapp::status::invalidGetRequest; + response["reason"] = "Invalid INFO request. " + "Valid INFO requests " + "are " + "\"info\""; + ws->send(response.dump(), uWS::OpCode::TEXT, + true); + } + + } // INFO + + else { + response["status"] = + iganet::webapp::status::invalidRequest; + response["reason"] = "Invalid request"; + ws->send(response.dump(), uWS::OpCode::TEXT, + true); + } + } catch (std::exception &e) { + nlohmann::json response; + try { + auto request = nlohmann::json::parse(message); + response["request"] = request["id"]; + response["status"] = + iganet::webapp::status::invalidRequest; + response["reason"] = e.what(); + ws->send(response.dump(), uWS::OpCode::TEXT, + true); + } catch (...) { + response["request"] = "unknown"; + response["status"] = + iganet::webapp::status::invalidRequest; + response["reason"] = "Invalid request"; + ws->send(response.dump(), uWS::OpCode::TEXT, + true); + } + } + }, + .drain = + [](auto *ws) { + /* Check ws->getBufferedAmount() here */ + }, + .ping = + [](auto *ws, std::string_view) { + /* Not implemented yet */ + }, + .pong = + [](auto *ws, std::string_view) { + /* Not implemented yet */ + }, + .close = + [](auto *ws, int code, std::string_view message) { + /* You may access ws->getUserData() here */ +#ifndef NDEBUG + std::stringstream msg; + msg << "[Thread " << std::this_thread::get_id() + << "] Connection has been closed\n"; + std::clog << msg.str(); +#endif + }}) + .listen(port_option->value(), + [&port_option](auto *listen_socket) { + if (listen_socket) { + std::stringstream msg; + msg << "[Thread " << std::this_thread::get_id() + << "] Listening on port " + << port_option->value() << std::endl; + std::clog << msg.str(); + } else { + std::stringstream msg; + msg << "[Thread " << std::this_thread::get_id() + << "] Failed to listen on port " + << port_option->value() << std::endl; + std::clog << msg.str(); + } + }) + .run(); + } catch (std::exception &e) { + std::cerr << e.what(); + } + }); + }); + + std::for_each(threads.begin(), threads.end(), + [](std::thread *t) { t->join(); }); + + return 0; +} From 8ec3148a88522c43b5cc1f91706d46140dccee16 Mon Sep 17 00:00:00 2001 From: merback Date: Tue, 28 May 2024 18:14:37 +0200 Subject: [PATCH 08/13] NURBS adapted for dimension independent implementation --- include/boundary.hpp | 3904 ++-- include/bspline.hpp | 16173 ++++++++-------- include/functionspace.hpp | 7526 +++---- unittests/unittest_bsplinelib.hpp | 2737 +-- .../unittest_nonuniform_rational_bspline.cxx | 4630 ++--- ...ttest_nonuniform_rational_bspline_eval.cxx | 2252 +-- 6 files changed, 18621 insertions(+), 18601 deletions(-) diff --git a/include/boundary.hpp b/include/boundary.hpp index a24f8912..91d7a508 100644 --- a/include/boundary.hpp +++ b/include/boundary.hpp @@ -1,1952 +1,1952 @@ -/** - @file include/boundary.hpp - - @brief Boundary treatment - - @author Matthias Moller - - @copyright This file is part of the IgANet project - - This Source Code Form is subject to the terms of the Mozilla Public - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at http://mozilla.org/MPL/2.0/. -*/ - -#pragma once - -#include -#include - -#include - -namespace iganet { - -/// @brief Identifiers for topological sides -enum side { - west = 1, - east = 2, - south = 3, - north = 4, - front = 5, - back = 6, - stime = 7, - etime = 8, - left = 1, - right = 2, - down = 3, - up = 4, - none = 0 -}; - -/// @brief BoundaryCore -template class BoundaryCore; - -/// @brief BoundaryCore (1d specialization) -/// -/// This specialization has 2 sides -/// - west (u=0) -/// - east (u=1) -template -class BoundaryCore : public utils::Serializable, - private utils::FullQualifiedName { - /// @brief Enable access to private members - template friend class BoundaryCommon; - -protected: - /// @brief Spline type - using spline_type = Spline; - - /// @brief Boundary spline type - using boundary_spline_type = - typename Spline::template derived_self_type; - - /// @brief Deduces the derived boundary spline type when exposed - /// to a different class template parameter `real_t` - template - using real_derived_boundary_spline_type = - typename Spline::template derived_self_type; - - /// @brief Tuple of splines - std::tuple bdr_; - -public: - /// @brief Boundary type - using boundary_type = decltype(bdr_); - - /// @brief Evaluation type - using eval_type = std::tuple; - - /// @brief Default constructor - BoundaryCore(Options options = - Options{}) - : bdr_({boundary_spline_type(options), boundary_spline_type(options)}) {} - - /// @brief Copy constructor - BoundaryCore(const boundary_type &bdr_) : bdr_(bdr_) {} - - /// @brief Move constructor - BoundaryCore(boundary_type &&bdr_) : bdr_(bdr_) {} - - /// @brief Copy/clone constructor - BoundaryCore(const BoundaryCore &other, bool clone) - : bdr_(clone ? std::apply( - [](const auto &...bspline) { - return std::make_tuple(bspline.clone()...); - }, - other.coeffs()) - : other.coeffs()) {} - - /// @brief Constructor - BoundaryCore(const std::array &, enum init init = init::zeros, - Options options = - Options{}) - : bdr_({boundary_spline_type(std::array{}, init, options), - boundary_spline_type(std::array{}, init, options)}) {} - - /// @brief Constructor - BoundaryCore(const std::array, 1> &, - enum init init = init::zeros, - Options options = - Options{}) - : bdr_({boundary_spline_type(std::array{}, init, options), - boundary_spline_type(std::array{}, init, options)}) {} - - /// @brief Sets the coefficients of all spline objects from a - /// single tensor that holds both boundary and inner coefficients - /// - /// @param[in] tensor Tensor from which to extract the coefficients - /// - /// @result Updates spline object - inline auto &from_full_tensor(const torch::Tensor &tensor) { - - if (tensor.dim() > 1) { - auto tensor_view = tensor.view({Spline::geoDim(), -1, tensor.size(-1)}); - - side().from_tensor(tensor_view.index({torch::indexing::Slice(), 0}) - .reshape({-1, tensor.size(-1)})); - side().from_tensor(tensor_view.index({torch::indexing::Slice(), -1}) - .reshape({-1, tensor.size(-1)})); - } else { - auto tensor_view = tensor.view({Spline::geoDim(), -1}); - - side().from_tensor( - tensor_view.index({torch::indexing::Slice(), 0}).flatten()); - side().from_tensor( - tensor_view.index({torch::indexing::Slice(), -1}).flatten()); - } - return *this; - } - - /// @brief Returns the number of sides - inline static constexpr short_t nsides() { return side::east; } - - /// @brief Returns constant reference to side-th Spline - template inline constexpr auto &side() const { - static_assert(s > none && s <= nsides()); - return std::get(bdr_); - } - - /// @brief Returns non-constant reference to side-th Spline - template inline constexpr auto &side() { - static_assert(s > none && s <= nsides()); - return std::get(bdr_); - } - - /// @brief Returns a constant reference to the array of - /// coefficients for all boundary segments. - inline constexpr auto &coeffs() const { return bdr_; } - - /// @brief Returns a non-constant reference to the array of - /// coefficients for all boundary segments. - inline constexpr auto &coeffs() { return bdr_; } - - /// @brief Returns the total number of coefficients - inline int64_t ncumcoeffs() const { - int64_t s = 0; - s += side().ncumcoeffs(); - s += side().ncumcoeffs(); - - return s; - } - - /// @brief Returns a string representation of the Boundary object - inline virtual void - pretty_print(std::ostream &os = Log(log::info)) const noexcept override { - os << name() << "(\n" - << "west = " << side() << "\n" - << "east = " << side() << "\n)"; - } - - /// @brief Returns the boundary object as JSON object - inline nlohmann::json to_json() const override { - nlohmann::json json; - json["west"] = side().to_json(); - json["east"] = side().to_json(); - - return json; - } - - /// @brief Updates the boundary object from JSON object - inline BoundaryCore &from_json(const nlohmann::json &json) { - side().from_json(json["west"]); - side().from_json(json["east"]); - - return *this; - } - - /// @brief Returns the Greville abscissae - inline eval_type greville() const { - return eval_type{side().greville(), side().greville()}; - } -}; - -/// @brief BoundaryCore (2d specialization) -/// -/// This specialization has 4 sides -/// - west (u=0, v ) -/// - east (u=1, v ) -/// - south (u, v=0) -/// - north (u, v=1) -template -class BoundaryCore : public utils::Serializable, - private utils::FullQualifiedName { - /// @brief Enable access to private members - template friend class BoundaryCommon; - -protected: - /// @brief Spline type - using spline_type = Spline; - - /// @brief Boundary spline type - using boundary_spline_type = std::tuple< - typename Spline::template derived_self_type< - typename Spline::value_type, Spline::geoDim(), Spline::degree(1)>, - typename Spline::template derived_self_type< - typename Spline::value_type, Spline::geoDim(), Spline::degree(0)>>; - - /// @brief Deduces the derived boundary spline type when exposed - /// to a different class template parameter `real_t` - template - using real_derived_boundary_spline_type = - std::tuple, - typename Spline::template derived_self_type< - real_t, Spline::geoDim(), Spline::degree(0)>>; - - /// @brief Tuple of splines - std::tuple, - typename std::tuple_element_t<0, boundary_spline_type>, - typename std::tuple_element_t<1, boundary_spline_type>, - typename std::tuple_element_t<1, boundary_spline_type>> - bdr_; - -public: - /// @brief Boundary type - using boundary_type = decltype(bdr_); - - /// @brief Evaluation type - using eval_type = std::tuple, utils::TensorArray<1>, - utils::TensorArray<1>, utils::TensorArray<1>>; - - /// @brief Default constructor - BoundaryCore(Options options = - Options{}) - : bdr_({std::tuple_element_t<0, boundary_spline_type>(options), - std::tuple_element_t<0, boundary_spline_type>(options), - std::tuple_element_t<1, boundary_spline_type>(options), - std::tuple_element_t<1, boundary_spline_type>(options)}) {} - - /// @brief Copy constructor - BoundaryCore(const boundary_type &bdr_) : bdr_(bdr_) {} - - /// @brief Move constructor - BoundaryCore(boundary_type &&bdr_) : bdr_(bdr_) {} - - /// @brief Copy/clone constructor - BoundaryCore(const BoundaryCore &other, bool clone) - : bdr_(clone ? std::apply( - [](const auto &...bspline) { - return std::make_tuple(bspline.clone()...); - }, - other.coeffs()) - : other.coeffs()) {} - - /// @brief Constructor - BoundaryCore(const std::array &ncoeffs, - enum init init = init::zeros, - Options options = - Options{}) - : bdr_({std::tuple_element_t<0, boundary_spline_type>( - std::array({ncoeffs[1]}), init, options), - std::tuple_element_t<0, boundary_spline_type>( - std::array({ncoeffs[1]}), init, options), - std::tuple_element_t<1, boundary_spline_type>( - std::array({ncoeffs[0]}), init, options), - std::tuple_element_t<1, boundary_spline_type>( - std::array({ncoeffs[0]}), init, options)}) {} - - /// @brief Constructor - BoundaryCore( - const std::array, 2> &kv, - enum init init = init::zeros, - Options options = - Options{}) - : bdr_({std::tuple_element_t<0, boundary_spline_type>( - std::array, 1>( - {kv[1]}), - init, options), - std::tuple_element_t<0, boundary_spline_type>( - std::array, 1>( - {kv[1]}), - init, options), - std::tuple_element_t<1, boundary_spline_type>( - std::array, 1>( - {kv[0]}), - init, options), - std::tuple_element_t<1, boundary_spline_type>( - std::array, 1>( - {kv[0]}), - init, options)}) {} - - /// @brief Sets the coefficients of all spline objects from a - /// single tensor that holds both boundary and inner coefficients - /// - /// @param[in] tensor Tensor from which to extract the coefficients - /// - /// @result Updates spline object - inline auto &from_full_tensor(const torch::Tensor &tensor) { - - if (tensor.dim() > 1) { - auto tensor_view = - tensor.view({Spline::geoDim(), side().ncoeffs(0), - side().ncoeffs(0), tensor.size(-1)}); - - side().from_tensor( - tensor_view - .index({torch::indexing::Slice(), torch::indexing::Slice(), 0}) - .reshape({-1, tensor.size(-1)})); - side().from_tensor( - tensor_view - .index({torch::indexing::Slice(), torch::indexing::Slice(), -1}) - .reshape({-1, tensor.size(-1)})); - side().from_tensor( - tensor_view - .index({torch::indexing::Slice(), 0, torch::indexing::Slice()}) - .reshape({-1, tensor.size(-1)})); - side().from_tensor( - tensor_view - .index({torch::indexing::Slice(), -1, torch::indexing::Slice()}) - .reshape({-1, tensor.size(-1)})); - } else { - auto tensor_view = tensor.view({Spline::geoDim(), side().ncoeffs(0), - side().ncoeffs(0)}); - - side().from_tensor( - tensor_view - .index({torch::indexing::Slice(), torch::indexing::Slice(), 0}) - .flatten()); - side().from_tensor( - tensor_view - .index({torch::indexing::Slice(), torch::indexing::Slice(), -1}) - .flatten()); - side().from_tensor( - tensor_view - .index({torch::indexing::Slice(), 0, torch::indexing::Slice()}) - .flatten()); - side().from_tensor( - tensor_view - .index({torch::indexing::Slice(), -1, torch::indexing::Slice()}) - .flatten()); - } - return *this; - } - - /// @brief Returns the number of sides - inline static constexpr short_t nsides() { return side::north; } - - /// @brief Returns constant reference to the s-th side's spline - template inline constexpr auto &side() const { - static_assert(s > none && s <= nsides()); - return std::get(bdr_); - } - - /// @brief Returns non-constant reference to the s-th side's spline - template inline constexpr auto &side() { - static_assert(s > none && s <= nsides()); - return std::get(bdr_); - } - - /// @brief Returns a constant reference to the array of - /// coefficients for all boundary segments. - inline constexpr auto &coeffs() const { return bdr_; } - - /// @brief Returns a non-constant reference to the array of - /// coefficients for all boundary segments. - inline constexpr auto &coeffs() { return bdr_; } - - /// @brief Returns the total number of coefficients - inline int64_t ncumcoeffs() const { - int64_t s = 0; - s += side().ncumcoeffs(); - s += side().ncumcoeffs(); - s += side().ncumcoeffs(); - s += side().ncumcoeffs(); - - return s; - } - - /// @brief Returns a string representation of the Boundary object - inline virtual void - pretty_print(std::ostream &os = Log(log::info)) const noexcept override { - os << name() << "(\n" - << "west = " << side() << "\n" - << "east = " << side() << "\n" - << "south = " << side() << "\n" - << "north = " << side() << "\n)"; - } - - /// @brief Returns the boundary object as JSON object - inline nlohmann::json to_json() const override { - nlohmann::json json; - json["west"] = side().to_json(); - json["east"] = side().to_json(); - json["south"] = side().to_json(); - json["north"] = side().to_json(); - - return json; - } - - /// @brief Updates the boundary object from JSON object - inline BoundaryCore &from_json(const nlohmann::json &json) { - side().from_json(json["west"]); - side().from_json(json["east"]); - side().from_json(json["south"]); - side().from_json(json["north"]); - - return *this; - } - - /// @brief Returns the Greville abscissae - inline eval_type greville() const { - return eval_type{side().greville(), side().greville(), - side().greville(), side().greville()}; - } -}; - -/// @brief BoundaryCore (3d specialization) -/// -/// This specialization has 6 sides -/// - west (u=0, v, w) -/// - east (u=1, v, w) -/// - south (u, v=0, w) -/// - north (u, v=1, w) -/// - front (u, v, w=0) -/// - back (u, v, w=1) -template -class BoundaryCore : public utils::Serializable, - private utils::FullQualifiedName { - /// @brief Enable access to private members - template friend class BoundaryCommon; - -protected: - /// @brief Spline type - using spline_type = Spline; - - /// @brief Boundary spline type - using boundary_spline_type = - std::tuple, - typename Spline::template derived_self_type< - typename Spline::value_type, Spline::geoDim(), - Spline::degree(0), Spline::degree(2)>, - typename Spline::template derived_self_type< - typename Spline::value_type, Spline::geoDim(), - Spline::degree(0), Spline::degree(1)>>; - - /// @brief Deduces the derived boundary spline type when exposed - /// to a different class template parameter `real_t` - template - using real_derived_boundary_spline_type = std::tuple< - typename Spline::template derived_self_type< - real_t, Spline::geoDim(), Spline::degree(1), Spline::degree(2)>, - typename Spline::template derived_self_type< - real_t, Spline::geoDim(), Spline::degree(0), Spline::degree(2)>, - typename Spline::template derived_self_type< - real_t, Spline::geoDim(), Spline::degree(0), Spline::degree(1)>>; - - /// @brief Tuple of splines - std::tuple, - typename std::tuple_element_t<0, boundary_spline_type>, - typename std::tuple_element_t<1, boundary_spline_type>, - typename std::tuple_element_t<1, boundary_spline_type>, - typename std::tuple_element_t<2, boundary_spline_type>, - typename std::tuple_element_t<2, boundary_spline_type>> - bdr_; - -public: - /// @brief Boundary type - using boundary_type = decltype(bdr_); - - /// @brief Evaluation type - using eval_type = std::tuple, utils::TensorArray<2>, - utils::TensorArray<2>, utils::TensorArray<2>, - utils::TensorArray<2>, utils::TensorArray<2>>; - - /// @brief Default constructor - BoundaryCore(Options options = - Options{}) - : bdr_({std::tuple_element_t<0, boundary_spline_type>(options), - std::tuple_element_t<0, boundary_spline_type>(options), - std::tuple_element_t<1, boundary_spline_type>(options), - std::tuple_element_t<1, boundary_spline_type>(options), - std::tuple_element_t<2, boundary_spline_type>(options), - std::tuple_element_t<2, boundary_spline_type>(options)}) {} - - /// @brief Copy constructor - BoundaryCore(const boundary_type &bdr_) : bdr_(bdr_) {} - - /// @brief Move constructor - BoundaryCore(boundary_type &&bdr_) : bdr_(bdr_) {} - - /// @brief Copy/clone constructor - BoundaryCore(const BoundaryCore &other, bool clone) - : bdr_(clone ? std::apply( - [](const auto &...bspline) { - return std::make_tuple(bspline.clone()...); - }, - other.coeffs()) - : other.coeffs()) {} - - /// @brief Constructor - BoundaryCore(const std::array &ncoeffs, - enum init init = init::zeros, - Options options = - Options{}) - : bdr_({std::tuple_element_t<0, boundary_spline_type>( - std::array({ncoeffs[1], ncoeffs[2]}), init, - options), - std::tuple_element_t<0, boundary_spline_type>( - std::array({ncoeffs[1], ncoeffs[2]}), init, - options), - std::tuple_element_t<1, boundary_spline_type>( - std::array({ncoeffs[0], ncoeffs[2]}), init, - options), - std::tuple_element_t<1, boundary_spline_type>( - std::array({ncoeffs[0], ncoeffs[2]}), init, - options), - std::tuple_element_t<2, boundary_spline_type>( - std::array({ncoeffs[0], ncoeffs[1]}), init, - options), - std::tuple_element_t<2, boundary_spline_type>( - std::array({ncoeffs[0], ncoeffs[1]}), init, - options)}) {} - - /// @brief Constructor - BoundaryCore( - const std::array, 3> &kv, - enum init init = init::zeros, - Options options = - Options{}) - : bdr_({std::tuple_element_t<0, boundary_spline_type>( - std::array, 2>( - {kv[1], kv[2]}), - init, options), - std::tuple_element_t<0, boundary_spline_type>( - std::array, 2>( - {kv[1], kv[2]}), - init, options), - std::tuple_element_t<1, boundary_spline_type>( - std::array, 2>( - {kv[0], kv[2]}), - init, options), - std::tuple_element_t<1, boundary_spline_type>( - std::array, 2>( - {kv[0], kv[2]}), - init, options), - std::tuple_element_t<2, boundary_spline_type>( - std::array, 2>( - {kv[0], kv[1]}), - init, options), - std::tuple_element_t<2, boundary_spline_type>( - std::array, 2>( - {kv[0], kv[1]}), - init, options)}) {} - - /// @brief Sets the coefficients of all spline objects from a - /// single tensor that holds both boundary and inner coefficients - /// - /// @param[in] tensor Tensor from which to extract the coefficients - /// - /// @result Updates spline object - inline auto &from_full_tensor(const torch::Tensor &tensor) { - - if (tensor.dim() > 1) { - auto tensor_view = tensor.view( - {Spline::geoDim(), side().ncoeffs(1), side().ncoeffs(0), - side().ncoeffs(0), tensor.size(-1)}); - - side().from_tensor( - tensor_view - .index({torch::indexing::Slice(), torch::indexing::Slice(), - torch::indexing::Slice(), 0}) - .reshape({-1, tensor.size(-1)})); - side().from_tensor( - tensor_view - .index({torch::indexing::Slice(), torch::indexing::Slice(), - torch::indexing::Slice(), -1}) - .reshape({-1, tensor.size(-1)})); - side().from_tensor( - tensor_view - .index({torch::indexing::Slice(), torch::indexing::Slice(), 0, - torch::indexing::Slice()}) - .reshape({-1, tensor.size(-1)})); - side().from_tensor( - tensor_view - .index({torch::indexing::Slice(), torch::indexing::Slice(), -1, - torch::indexing::Slice()}) - .reshape({-1, tensor.size(-1)})); - side().from_tensor( - tensor_view - .index({torch::indexing::Slice(), 0, torch::indexing::Slice(), - torch::indexing::Slice()}) - .reshape({-1, tensor.size(-1)})); - side().from_tensor( - tensor_view - .index({torch::indexing::Slice(), -1, torch::indexing::Slice(), - torch::indexing::Slice()}) - .reshape({-1, tensor.size(-1)})); - } else { - auto tensor_view = - tensor.view({Spline::geoDim(), side().ncoeffs(1), - side().ncoeffs(0), side().ncoeffs(0)}); - - side().from_tensor( - tensor_view - .index({torch::indexing::Slice(), torch::indexing::Slice(), - torch::indexing::Slice(), 0}) - .flatten()); - side().from_tensor( - tensor_view - .index({torch::indexing::Slice(), torch::indexing::Slice(), - torch::indexing::Slice(), -1}) - .flatten()); - - side().from_tensor( - tensor_view - .index({torch::indexing::Slice(), torch::indexing::Slice(), 0, - torch::indexing::Slice()}) - .flatten()); - side().from_tensor( - tensor_view - .index({torch::indexing::Slice(), torch::indexing::Slice(), -1, - torch::indexing::Slice()}) - .flatten()); - - side().from_tensor( - tensor_view - .index({torch::indexing::Slice(), 0, torch::indexing::Slice(), - torch::indexing::Slice()}) - .flatten()); - side().from_tensor( - tensor_view - .index({torch::indexing::Slice(), -1, torch::indexing::Slice(), - torch::indexing::Slice()}) - .flatten()); - } - return *this; - } - - /// @brief Returns the number of sides - inline static constexpr short_t nsides() { return side::back; } - - /// @brief Returns constant reference to side-th spline - template inline constexpr auto &side() const { - static_assert(s > none && s <= nsides()); - return std::get(bdr_); - } - - /// @brief Returns non-constant reference to side-th spline - template inline constexpr auto &side() { - static_assert(s > none && s <= nsides()); - return std::get(bdr_); - } - - /// @brief Returns a constant reference to the array of - /// coefficients for all boundary segments. - inline constexpr auto &coeffs() const { return bdr_; } - - /// @brief Returns a non-constant reference to the array of - /// coefficients for all boundary segments. - inline constexpr auto &coeffs() { return bdr_; } - - /// @brief Returns the total number of coefficients - inline int64_t ncumcoeffs() const { - int64_t s = 0; - s += side().ncumcoeffs(); - s += side().ncumcoeffs(); - s += side().ncumcoeffs(); - s += side().ncumcoeffs(); - s += side().ncumcoeffs(); - s += side().ncumcoeffs(); - - return s; - } - - /// @brief Returns a string representation of the Boundary object - inline virtual void - pretty_print(std::ostream &os = Log(log::info)) const noexcept override { - os << name() << "(\n" - << "west = " << side() << "\n" - << "east = " << side() << "\n" - << "south = " << side() << "\n" - << "north = " << side() << "\n" - << "front = " << side() << "\n" - << "back = " << side() << "\n)"; - } - - /// @brief Returns the boundary object as JSON object - inline nlohmann::json to_json() const override { - nlohmann::json json; - json["west"] = side().to_json(); - json["east"] = side().to_json(); - json["south"] = side().to_json(); - json["north"] = side().to_json(); - json["front"] = side().to_json(); - json["back"] = side().to_json(); - - return json; - } - - /// @brief Updates the boundary object from JSON object - inline BoundaryCore &from_json(const nlohmann::json &json) { - side().from_json(json["west"]); - side().from_json(json["east"]); - side().from_json(json["south"]); - side().from_json(json["north"]); - side().from_json(json["front"]); - side().from_json(json["back"]); - - return *this; - } - - /// @brief Returns the Greville abscissae - inline eval_type greville() const { - return eval_type{side().greville(), side().greville(), - side().greville(), side().greville(), - side().greville(), side().greville()}; - } -}; - -/// @brief BoundaryCore (4d specialization) -/// -/// This specialization has 8 sides -/// - west (u=0, v, w, t) -/// - east (u=1, v, w, t) -/// - south (u, v=0, w, t) -/// - north (u, v=1, w, t) -/// - front (u, v, w=0, t) -/// - back (u, v, w=1, t) -/// - stime (u, v, w, t=0) -/// - etime (u, v, w, t=1) -template -class BoundaryCore : public utils::Serializable, - private utils::FullQualifiedName { - /// @brief Enable access to private members - template friend class BoundaryCommon; - -protected: - /// @brief Spline type - using spline_type = Spline; - - /// @brief Array storing the degrees - using boundary_spline_type = - std::tuple, - typename Spline::template derived_self_type< - typename Spline::value_type, Spline::geoDim(), - Spline::degree(0), Spline::degree(2), Spline::degree(3)>, - typename Spline::template derived_self_type< - typename Spline::value_type, Spline::geoDim(), - Spline::degree(0), Spline::degree(1), Spline::degree(3)>, - typename Spline::template derived_self_type< - typename Spline::value_type, Spline::geoDim(), - Spline::degree(0), Spline::degree(1), Spline::degree(2)>>; - - /// @brief Deduces the derived boundary spline type when exposed - /// to a different class template parameter `real_t` - template - using real_derived_boundary_spline_type = - std::tuple, - typename Spline::template derived_self_type< - real_t, Spline::geoDim(), Spline::degree(0), - Spline::degree(2), Spline::degree(3)>, - typename Spline::template derived_self_type< - real_t, Spline::geoDim(), Spline::degree(0), - Spline::degree(1), Spline::degree(3)>, - typename Spline::template derived_self_type< - real_t, Spline::geoDim(), Spline::degree(0), - Spline::degree(1), Spline::degree(2)>>; - - /// @brief Tuple of splines - std::tuple, - typename std::tuple_element_t<0, boundary_spline_type>, - typename std::tuple_element_t<1, boundary_spline_type>, - typename std::tuple_element_t<1, boundary_spline_type>, - typename std::tuple_element_t<2, boundary_spline_type>, - typename std::tuple_element_t<2, boundary_spline_type>, - typename std::tuple_element_t<3, boundary_spline_type>, - typename std::tuple_element_t<3, boundary_spline_type>> - bdr_; - -public: - /// @brief Boundary type - using boundary_type = decltype(bdr_); - - /// @brief Evaluation type - using eval_type = std::tuple, utils::TensorArray<3>, - utils::TensorArray<3>, utils::TensorArray<3>, - utils::TensorArray<3>, utils::TensorArray<3>, - utils::TensorArray<3>, utils::TensorArray<3>>; - - /// @brief Default constructor - BoundaryCore(Options options = - Options{}) - : bdr_({std::tuple_element_t<0, boundary_spline_type>(options), - std::tuple_element_t<0, boundary_spline_type>(options), - std::tuple_element_t<1, boundary_spline_type>(options), - std::tuple_element_t<1, boundary_spline_type>(options), - std::tuple_element_t<2, boundary_spline_type>(options), - std::tuple_element_t<2, boundary_spline_type>(options), - std::tuple_element_t<3, boundary_spline_type>(options), - std::tuple_element_t<3, boundary_spline_type>(options)}) {} - - /// @brief Copy constructor - BoundaryCore(const boundary_type &bdr_) : bdr_(bdr_) {} - - /// @brief Move constructor - BoundaryCore(boundary_type &&bdr_) : bdr_(bdr_) {} - - /// @brief Copy/clone constructor - BoundaryCore(const BoundaryCore &other, bool clone) - : bdr_(clone ? std::apply( - [](const auto &...bspline) { - return std::make_tuple(bspline.clone()...); - }, - other.coeffs()) - : other.coeffs()) {} - - /// @brief Constructor - BoundaryCore(const std::array &ncoeffs, - enum init init = init::zeros, - Options options = - Options{}) - : bdr_({std::tuple_element_t<0, boundary_spline_type>( - std::array({ncoeffs[1], ncoeffs[2], ncoeffs[3]}), - init, options), - std::tuple_element_t<0, boundary_spline_type>( - std::array({ncoeffs[1], ncoeffs[2], ncoeffs[3]}), - init, options), - std::tuple_element_t<1, boundary_spline_type>( - std::array({ncoeffs[0], ncoeffs[2], ncoeffs[3]}), - init, options), - std::tuple_element_t<1, boundary_spline_type>( - std::array({ncoeffs[0], ncoeffs[2], ncoeffs[3]}), - init, options), - std::tuple_element_t<2, boundary_spline_type>( - std::array({ncoeffs[0], ncoeffs[1], ncoeffs[3]}), - init, options), - std::tuple_element_t<2, boundary_spline_type>( - std::array({ncoeffs[0], ncoeffs[1], ncoeffs[3]}), - init, options), - std::tuple_element_t<3, boundary_spline_type>( - std::array({ncoeffs[0], ncoeffs[1], ncoeffs[2]}), - init, options), - std::tuple_element_t<3, boundary_spline_type>( - std::array({ncoeffs[0], ncoeffs[1], ncoeffs[2]}), - init, options)}) {} - - /// @brief Constructor - BoundaryCore( - const std::array, 4> &kv, - enum init init = init::zeros, - Options options = - Options{}) - : bdr_({std::tuple_element_t<0, boundary_spline_type>( - std::array, 3>( - {kv[1], kv[2], kv[3]}), - init, options), - std::tuple_element_t<0, boundary_spline_type>( - std::array, 3>( - {kv[1], kv[2], kv[3]}), - init, options), - std::tuple_element_t<1, boundary_spline_type>( - std::array, 3>( - {kv[0], kv[2], kv[3]}), - init, options), - std::tuple_element_t<1, boundary_spline_type>( - std::array, 3>( - {kv[0], kv[2], kv[3]}), - init, options), - std::tuple_element_t<2, boundary_spline_type>( - std::array, 3>( - {kv[0], kv[1], kv[3]}), - init, options), - std::tuple_element_t<2, boundary_spline_type>( - std::array, 3>( - {kv[0], kv[1], kv[3]}), - init, options), - std::tuple_element_t<3, boundary_spline_type>( - std::array, 3>( - {kv[0], kv[1], kv[2]}), - init, options), - std::tuple_element_t<3, boundary_spline_type>( - std::array, 3>( - {kv[0], kv[1], kv[2]}), - init, options)}) {} - - /// @brief Sets the coefficients of all spline objects from a - /// single tensor that holds both boundary and inner coefficients - /// - /// @param[in] tensor Tensor from which to extract the coefficients - /// - /// @result Updates spline object - inline auto &from_full_tensor(const torch::Tensor &tensor) { - - if (tensor.dim() > 1) { - auto tensor_view = tensor.view( - {Spline::geoDim(), side().ncoeffs(2), side().ncoeffs(1), - side().ncoeffs(0), side().ncoeffs(0), tensor.size(-1)}); - - side().from_tensor( - tensor_view - .index({torch::indexing::Slice(), torch::indexing::Slice(), - torch::indexing::Slice(), torch::indexing::Slice(), 0}) - .reshape({-1, tensor.size(-1)})); - side().from_tensor( - tensor_view - .index({torch::indexing::Slice(), torch::indexing::Slice(), - torch::indexing::Slice(), torch::indexing::Slice(), -1}) - .reshape({-1, tensor.size(-1)})); - side().from_tensor( - tensor_view - .index({torch::indexing::Slice(), torch::indexing::Slice(), - torch::indexing::Slice(), 0, torch::indexing::Slice()}) - .reshape({-1, tensor.size(-1)})); - side().from_tensor( - tensor_view - .index({torch::indexing::Slice(), torch::indexing::Slice(), - torch::indexing::Slice(), -1, torch::indexing::Slice()}) - .reshape({-1, tensor.size(-1)})); - side().from_tensor( - tensor_view - .index({torch::indexing::Slice(), torch::indexing::Slice(), 0, - torch::indexing::Slice(), torch::indexing::Slice()}) - .reshape({-1, tensor.size(-1)})); - side().from_tensor( - tensor_view - .index({torch::indexing::Slice(), torch::indexing::Slice(), -1, - torch::indexing::Slice(), torch::indexing::Slice()}) - .reshape({-1, tensor.size(-1)})); - side().from_tensor( - tensor_view - .index({torch::indexing::Slice(), 0, torch::indexing::Slice(), - torch::indexing::Slice(), torch::indexing::Slice()}) - .reshape({-1, tensor.size(-1)})); - side().from_tensor( - tensor_view - .index({torch::indexing::Slice(), -1, torch::indexing::Slice(), - torch::indexing::Slice(), torch::indexing::Slice()}) - .reshape({-1, tensor.size(-1)})); - } else { - auto tensor_view = tensor.view( - {Spline::geoDim(), side().ncoeffs(2), side().ncoeffs(1), - side().ncoeffs(0), side().ncoeffs(0)}); - - side().from_tensor( - tensor_view - .index({torch::indexing::Slice(), torch::indexing::Slice(), - torch::indexing::Slice(), torch::indexing::Slice(), 0}) - .flatten()); - side().from_tensor( - tensor_view - .index({torch::indexing::Slice(), torch::indexing::Slice(), - torch::indexing::Slice(), torch::indexing::Slice(), -1}) - .flatten()); - - side().from_tensor( - tensor_view - .index({torch::indexing::Slice(), torch::indexing::Slice(), - torch::indexing::Slice(), 0, torch::indexing::Slice()}) - .flatten()); - side().from_tensor( - tensor_view - .index({torch::indexing::Slice(), torch::indexing::Slice(), - torch::indexing::Slice(), -1, torch::indexing::Slice()}) - .flatten()); - - side().from_tensor( - tensor_view - .index({torch::indexing::Slice(), torch::indexing::Slice(), 0, - torch::indexing::Slice(), torch::indexing::Slice()}) - .flatten()); - side().from_tensor( - tensor_view - .index({torch::indexing::Slice(), torch::indexing::Slice(), -1, - torch::indexing::Slice(), torch::indexing::Slice()}) - .flatten()); - - side().from_tensor( - tensor_view - .index({torch::indexing::Slice(), 0, torch::indexing::Slice(), - torch::indexing::Slice(), torch::indexing::Slice()}) - .flatten()); - side().from_tensor( - tensor_view - .index({torch::indexing::Slice(), -1, torch::indexing::Slice(), - torch::indexing::Slice(), torch::indexing::Slice()}) - .flatten()); - } - return *this; - } - - /// @brief Returns the number of sides - inline static constexpr short_t nsides() { return side::etime; } - - /// @brief Returns constant reference to side-th spline - template inline constexpr auto &side() const { - static_assert(s > none && s <= nsides()); - return std::get(bdr_); - } - - /// @brief Returns non-constant reference to side-th spline - template inline constexpr auto &side() { - static_assert(s > none && s <= nsides()); - return std::get(bdr_); - } - - /// @brief Returns a constant reference to the array of - /// coefficients for all boundary segments. - inline constexpr auto &coeffs() const { return bdr_; } - - /// @brief Returns a non-constant reference to the array of - /// coefficients for all boundary segments. - inline constexpr auto &coeffs() { return bdr_; } - - /// @brief Returns the total number of coefficients - inline int64_t ncumcoeffs() const { - int64_t s = 0; - s += side().ncumcoeffs(); - s += side().ncumcoeffs(); - s += side().ncumcoeffs(); - s += side().ncumcoeffs(); - s += side().ncumcoeffs(); - s += side().ncumcoeffs(); - s += side().ncumcoeffs(); - s += side().ncumcoeffs(); - - return s; - } - - /// @brief Returns a string representation of the Boundary object - inline virtual void - pretty_print(std::ostream &os = Log(log::info)) const noexcept override { - os << name() << "(\n" - << "west = " << side() << "\n" - << "east = " << side() << "\n" - << "south = " << side() << "\n" - << "north = " << side() << "\n" - << "front = " << side() << "\n" - << "back = " << side() << "\n" - << "stime = " << side() << "\n" - << "etime = " << side() << "\n)"; - } - - /// @brief Returns the boundary object as JSON object - inline nlohmann::json to_json() const override { - nlohmann::json json; - json["west"] = side().to_json(); - json["east"] = side().to_json(); - json["south"] = side().to_json(); - json["north"] = side().to_json(); - json["front"] = side().to_json(); - json["back"] = side().to_json(); - json["stime"] = side().to_json(); - json["etime"] = side().to_json(); - - return json; - } - - /// @brief Updates the boundary object from JSON object - inline BoundaryCore &from_json(const nlohmann::json &json) { - side().from_json(json["west"]); - side().from_json(json["east"]); - side().from_json(json["south"]); - side().from_json(json["north"]); - side().from_json(json["front"]); - side().from_json(json["back"]); - side().from_json(json["stime"]); - side().from_json(json["etime"]); - - return *this; - } - - /// @brief Returns the Greville abscissae - inline eval_type greville() const { - return eval_type{side().greville(), side().greville(), - side().greville(), side().greville(), - side().greville(), side().greville(), - side().greville(), side().greville()}; - } -}; - -/// @brief - -/// @brief Boundary (common high-level functionality) -template class BoundaryCommon : public BoundaryCore { -public: - /// @brief Constructors from the base class - using BoundaryCore::BoundaryCore; - - /// @brief Returns a clone of the boundary object - BoundaryCommon clone() const { return BoundaryCommon(*this); } - -private: - /// @brief Returns all coefficients of all spline objects as a - /// single tensor - /// - /// @result Tensor of coefficients - template - inline torch::Tensor as_tensor_(std::index_sequence) const { - return torch::cat({std::get(BoundaryCore::bdr_).as_tensor()...}); - } - -public: - /// @brief Returns all coefficients of all spline objects as a - /// single tensor - /// - /// @result Tensor of coefficients - inline torch::Tensor as_tensor() const { - return as_tensor_(std::make_index_sequence{}); - } - -private: - /// @brief Returns the size of the single tensor representation of - /// all spline objects - /// - /// @result Size of the tensor - template - inline int64_t as_tensor_size_(std::index_sequence) const { - return std::apply( - [](auto... size) { return (size + ...); }, - std::make_tuple(std::get(BoundaryCore::bdr_).as_tensor_size()...)); - } - -public: - /// @brief Returns the size of the single tensor representation of - /// all spline objects - // - /// @result Size of the tensor - inline int64_t as_tensor_size() const { - return as_tensor_size_(std::make_index_sequence{}); - } - -private: - /// @brief Sets the coefficients of all spline objects from a - /// single tensor - /// - /// @param[in] tensor Tensor from which to extract the coefficients - /// - /// @result Updates spline object - template - inline auto &from_tensor_(std::index_sequence, - const torch::Tensor &tensor) { - - std::size_t start(0); - auto end = [&start](std::size_t inc) { return start += inc; }; - - (std::get(BoundaryCore::bdr_) - .from_tensor(tensor.index({torch::indexing::Slice( - start, end(std::get(BoundaryCore::bdr_).ncumcoeffs() * - std::get(BoundaryCore::bdr_).geoDim()))})), - ...); - - return *this; - } - -public: - /// @brief Sets the coefficients of all spline objects from a - /// single tensor - /// - /// @param[in] tensor Tensor from which to extract the coefficients - /// - /// @result Updated spline objects - inline auto &from_tensor(const torch::Tensor &tensor) { - return from_tensor_(std::make_index_sequence{}, - tensor); - } - -private: - /// @brief Returns the values of the boundary spline objects in - /// the points `xi` @{ - template - inline auto eval_(std::index_sequence, - const std::tuple &xi) const { - return std::tuple( - std::get(BoundaryCore::bdr_) - .template eval(std::get(xi))...); - } - - template - inline auto eval_(std::index_sequence, const std::tuple &xi, - const std::tuple &indices) const { - return std::tuple(std::get(BoundaryCore::bdr_) - .template eval( - std::get(xi), std::get(indices))...); - } - - template - inline auto eval_(std::index_sequence, const std::tuple &xi, - const std::tuple &indices, - const std::tuple &coeff_indices) const { - return std::tuple(std::get(BoundaryCore::bdr_) - .template eval( - std::get(xi), std::get(indices), - std::get(coeff_indices))...); - } - /// @} - -public: - /// @brief Returns the values of the spline objects in the points `xi` - /// @{ - template - inline auto eval(const std::tuple &xi) const { - return eval_( - std::make_index_sequence{}, xi); - } - - template - inline auto eval(const std::tuple &xi, - const std::tuple &indices) const { - static_assert(sizeof...(Xi) == sizeof...(Indices)); - return eval_( - std::make_index_sequence{}, xi, indices); - } - - template - inline auto eval(const std::tuple &xi, - const std::tuple &indices, - const std::tuple &coeff_indices) const { - static_assert(sizeof...(Xi) == sizeof...(Indices) && - sizeof...(Xi) == sizeof...(Coeff_Indices)); - return eval_( - std::make_index_sequence{}, xi, indices, - coeff_indices); - } - /// @} - -private: - /// @brief Returns the value of the boundary spline objects from - /// precomputed basis function @{ - template - inline auto - eval_from_precomputed_(std::index_sequence, - const std::tuple &basfunc, - const std::tuple &coeff_indices, - const std::tuple &numeval, - const std::tuple &sizes) const { - return std::tuple(std::get(BoundaryCore::bdr_) - .eval_from_precomputed(std::get(basfunc), - std::get(coeff_indices), - std::get(numeval), - std::get(sizes))...); - } - - template - inline auto - eval_from_precomputed_(std::index_sequence, - const std::tuple &basfunc, - const std::tuple &coeff_indices, - const std::tuple &xi) const { - return std::tuple( - std::get(BoundaryCore::bdr_) - .eval_from_precomputed( - std::get(basfunc), std::get(coeff_indices), - std::get(xi)[0].numel(), std::get(xi)[0].sizes())...); - } - /// @} - -public: - /// @brief Returns the value of the spline objects from - /// precomputed basis function @{ - template - inline auto - eval_from_precomputed(const std::tuple &basfunc, - const std::tuple &coeff_indices, - const std::tuple &numeval, - const std::tuple &sizes) const { - static_assert(sizeof...(Basfunc) == sizeof...(Coeff_Indices) && - sizeof...(Basfunc) == sizeof...(Numeval) && - sizeof...(Basfunc) == sizeof...(Sizes)); - return eval_from_precomputed_( - std::make_index_sequence{}, basfunc, - coeff_indices, numeval, sizes); - } - - template - inline auto - eval_from_precomputed(const std::tuple &basfunc, - const std::tuple &coeff_indices, - const std::tuple &xi) const { - static_assert(sizeof...(Basfunc) == sizeof...(Coeff_Indices) && - sizeof...(Basfunc) == sizeof...(Xi)); - return eval_from_precomputed_( - std::make_index_sequence{}, basfunc, - coeff_indices, xi); - } - /// @} - -private: - /// @brief Returns the knot indicies of boundary spline object's - /// knot spans containing `xi` - template - inline auto find_knot_indices_(std::index_sequence, - const std::tuple &xi) const { - return std::tuple(std::get(BoundaryCore::bdr_) - .find_knot_indices(std::get(xi))...); - } - -public: - /// @brief Returns the knot indicies of knot spans containing `xi` - template - inline auto find_knot_indices(const std::tuple &xi) const { - return find_knot_indices_( - std::make_index_sequence{}, xi); - } - -private: - /// @brief Returns the values of the boundary spline spline - /// object's basis functions in the points `xi` - /// @{ - template - inline auto eval_basfunc_(std::index_sequence, - const std::tuple &xi) const { - return std::tuple(std::get(BoundaryCore::bdr_) - .template eval_basfunc( - std::get(xi))...); - } - - template - inline auto eval_basfunc_(std::index_sequence, - const std::tuple &xi, - const std::tuple &indices) const { - return std::tuple(std::get(BoundaryCore::bdr_) - .template eval_basfunc( - std::get(xi), std::get(indices))...); - } - /// @} - -public: - /// @brief Returns the values of the spline objects' basis - /// functions in the points `xi` @{ - template - inline auto eval_basfunc(const std::tuple &xi) const { - return eval_basfunc_( - std::make_index_sequence{}, xi); - } - - template - inline auto eval_basfunc(const std::tuple &xi, - const std::tuple &indices) const { - static_assert(sizeof...(Xi) == sizeof...(Indices)); - return eval_basfunc_( - std::make_index_sequence{}, xi, indices); - } - /// @} - -private: - /// @brief Returns the indices of the boundary spline object's - /// coefficients corresponding to the knot indices `indices` - template - inline auto find_coeff_indices_(std::index_sequence, - const std::tuple &indices) const { - return std::tuple(std::get(BoundaryCore::bdr_) - .template find_coeff_indices( - std::get(indices))...); - } - -public: - /// @brief Returns the indices of the spline objects' - /// coefficients corresponding to the knot indices `indices` - template - inline auto find_coeff_indices(const std::tuple &indices) const { - return find_coeff_indices_( - std::make_index_sequence{}, indices); - } - -private: - /// @brief Returns the boundary spline object with uniformly - /// refined knot and coefficient vectors - template - inline auto &uniform_refine_(std::index_sequence, int numRefine = 1, - int dim = -1) { - (std::get(BoundaryCore::bdr_).uniform_refine(numRefine, dim), ...); - return *this; - } - -public: - /// @brief Returns the spline objects with uniformly refined - /// knot and coefficient vectors - inline auto &uniform_refine(int numRefine = 1, int dim = -1) { - if (dim == -1) { - if constexpr (BoundaryCore::spline_type::parDim() > 1) - uniform_refine_(std::make_index_sequence{}, - numRefine, dim); - } else if (dim == 0) { - if constexpr (BoundaryCore::nsides() == 2) { - // We do not refine the boundary of a curve - } else if constexpr (BoundaryCore::nsides() == 4) { - std::get(BoundaryCore::bdr_) - .uniform_refine(numRefine, 0); - std::get(BoundaryCore::bdr_) - .uniform_refine(numRefine, 0); - } else if constexpr (BoundaryCore::nsides() == 6) { - std::get(BoundaryCore::bdr_) - .uniform_refine(numRefine, 0); - std::get(BoundaryCore::bdr_) - .uniform_refine(numRefine, 0); - std::get(BoundaryCore::bdr_) - .uniform_refine(numRefine, 0); - std::get(BoundaryCore::bdr_) - .uniform_refine(numRefine, 0); - } else if constexpr (BoundaryCore::nsides() == 8) { - std::get(BoundaryCore::bdr_) - .uniform_refine(numRefine, 0); - std::get(BoundaryCore::bdr_) - .uniform_refine(numRefine, 0); - std::get(BoundaryCore::bdr_) - .uniform_refine(numRefine, 0); - std::get(BoundaryCore::bdr_) - .uniform_refine(numRefine, 0); - std::get(BoundaryCore::bdr_) - .uniform_refine(numRefine, 0); - std::get(BoundaryCore::bdr_) - .uniform_refine(numRefine, 0); - } else - throw std::runtime_error("Invalid dimension"); - } else if (dim == 1) { - if constexpr (BoundaryCore::nsides() == 4) { - std::get(BoundaryCore::bdr_) - .uniform_refine(numRefine, 0); - std::get(BoundaryCore::bdr_) - .uniform_refine(numRefine, 0); - } else if constexpr (BoundaryCore::nsides() == 6) { - std::get(BoundaryCore::bdr_) - .uniform_refine(numRefine, 0); - std::get(BoundaryCore::bdr_) - .uniform_refine(numRefine, 0); - std::get(BoundaryCore::bdr_) - .uniform_refine(numRefine, 1); - std::get(BoundaryCore::bdr_) - .uniform_refine(numRefine, 1); - - } else if constexpr (BoundaryCore::nsides() == 8) { - std::get(BoundaryCore::bdr_) - .uniform_refine(numRefine, 0); - std::get(BoundaryCore::bdr_) - .uniform_refine(numRefine, 0); - std::get(BoundaryCore::bdr_) - .uniform_refine(numRefine, 1); - std::get(BoundaryCore::bdr_) - .uniform_refine(numRefine, 1); - std::get(BoundaryCore::bdr_) - .uniform_refine(numRefine, 1); - std::get(BoundaryCore::bdr_) - .uniform_refine(numRefine, 1); - } else - throw std::runtime_error("Invalid dimension"); - } else if (dim == 2) { - if constexpr (BoundaryCore::nsides() == 6) { - std::get(BoundaryCore::bdr_) - .uniform_refine(numRefine, 1); - std::get(BoundaryCore::bdr_) - .uniform_refine(numRefine, 1); - std::get(BoundaryCore::bdr_) - .uniform_refine(numRefine, 1); - std::get(BoundaryCore::bdr_) - .uniform_refine(numRefine, 1); - } else if constexpr (BoundaryCore::nsides() == 8) { - std::get(BoundaryCore::bdr_) - .uniform_refine(numRefine, 1); - std::get(BoundaryCore::bdr_) - .uniform_refine(numRefine, 1); - std::get(BoundaryCore::bdr_) - .uniform_refine(numRefine, 1); - std::get(BoundaryCore::bdr_) - .uniform_refine(numRefine, 1); - std::get(BoundaryCore::bdr_) - .uniform_refine(numRefine, 2); - std::get(BoundaryCore::bdr_) - .uniform_refine(numRefine, 2); - } else - throw std::runtime_error("Invalid dimension"); - } else if (dim == 3) { - if constexpr (BoundaryCore::nsides() == 8) { - std::get(BoundaryCore::bdr_) - .uniform_refine(numRefine, 2); - std::get(BoundaryCore::bdr_) - .uniform_refine(numRefine, 2); - std::get(BoundaryCore::bdr_) - .uniform_refine(numRefine, 2); - std::get(BoundaryCore::bdr_) - .uniform_refine(numRefine, 2); - std::get(BoundaryCore::bdr_) - .uniform_refine(numRefine, 2); - std::get(BoundaryCore::bdr_) - .uniform_refine(numRefine, 2); - } else - throw std::runtime_error("Invalid dimension"); - } else - throw std::runtime_error("Invalid dimension"); - return *this; - } - -private: - /// @brief Writes the boundary spline object into a - /// torch::serialize::OutputArchive object - template - inline torch::serialize::OutputArchive & - write_(std::index_sequence, torch::serialize::OutputArchive &archive, - const std::string &key = "boundary") const { - (std::get(BoundaryCore::bdr_) - .write(archive, key + ".bdr[" + std::to_string(Is) + "]"), - ...); - return archive; - } - -public: - /// @brief Saves the boundary spline to file - inline void save(const std::string &filename, - const std::string &key = "boundary") const { - torch::serialize::OutputArchive archive; - write(archive, key).save_to(filename); - } - - /// @brief Writes the boundary spline object into a - /// torch::serialize::OutputArchive object - inline torch::serialize::OutputArchive & - write(torch::serialize::OutputArchive &archive, - const std::string &key = "boundary") const { - write_(std::make_index_sequence{}, archive, key); - return archive; - } - -private: - /// @brief Loads the function space object from a - /// torch::serialize::InputArchive object - template - inline torch::serialize::InputArchive & - read_(std::index_sequence, torch::serialize::InputArchive &archive, - const std::string &key = "boundary") { - (std::get(BoundaryCore::bdr_) - .read(archive, key + ".bdr[" + std::to_string(Is) + "]"), - ...); - return archive; - } - -public: - /// @brief Loads the boundary spline object from file - inline void load(const std::string &filename, - const std::string &key = "boundary") { - torch::serialize::InputArchive archive; - archive.load_from(filename); - read(archive, key); - } - - /// @brief Loads the boundary spline object from a - /// torch::serialize::InputArchive object - inline torch::serialize::InputArchive & - read(torch::serialize::InputArchive &archive, - const std::string &key = "boundary") { - read_(std::make_index_sequence{}, archive, key); - return archive; - } - - /// @brief Returns the boundary object as XML object - inline pugi::xml_document to_xml(int id = 0, std::string label = "", - int index = -1) const { - pugi::xml_document doc; - pugi::xml_node root = doc.append_child("xml"); - to_xml(root, id, label, index); - - return doc; - } - - /// @brief Returns the boundary object as XML node - inline pugi::xml_node &to_xml(pugi::xml_node &root, int id = 0, - std::string label = "", int index = -1) const { - // add Boundary node - pugi::xml_node bdr = root.append_child("Boundary"); - - if (id >= 0) - bdr.append_attribute("id") = id; - - if (index >= 0) - bdr.append_attribute("index") = index; - - if (!label.empty()) - bdr.append_attribute("label") = label.c_str(); - - int index_ = 0; - std::apply( - [&bdr, &index_](const auto &...bspline) { - (bspline.to_xml(bdr, -1, "", index_++), ...); - }, - BoundaryCore::bdr_); - - return root; - } - - /// @brief Updates the boundary object from XML object - inline BoundaryCommon &from_xml(const pugi::xml_document &doc, int id = 0, - std::string label = "", int index = -1) { - return from_xml(doc.child("xml"), id, label, index); - } - - /// @brief Updates the boundary object from XML node - inline BoundaryCommon &from_xml(const pugi::xml_node &root, int id = 0, - std::string label = "", int index = -1) { - - // Loop through all boundary nodes - for (pugi::xml_node bdr : root.children("Boundary")) { - - // Check for "Boundary" with given id, index, label - if ((id >= 0 ? bdr.attribute("id").as_int() == id : true) && - (index >= 0 ? bdr.attribute("index").as_int() == index : true) && - (!label.empty() ? bdr.attribute("label").value() == label : true)) { - - int index_ = 0; - std::apply( - [&bdr, &index_](auto &...bspline) { - (bspline.from_xml(bdr, -1, "", index_++), ...); - }, - BoundaryCore::bdr_); - - return *this; - } else - continue; // try next "Boundary" - } - - throw std::runtime_error("XML object does not provide geometry with given " - "id, index, and/or label"); - return *this; - } - -private: - /// @brief Returns true if both boundary spline objects are the - /// same - template - inline bool isequal_(std::index_sequence, - const BoundaryCommon &other) const { - return ( - (std::get(BoundaryCore::bdr_) == std::get(other.coeffs())) && - ...); - } - -public: - /// @brief Returns true if both boundary objects are the same - template - inline bool operator==(const BoundaryCommon &other) const { - return isequal_(std::make_index_sequence{}, other); - } - - /// @brief Returns true if both boundary objects are different - template - inline bool operator!=(const BoundaryCommon &other) const { - return !( - *this == - other); // Do not change this to (*this != other) is it does not work - } - -private: - /// @brief Returns true if both boundary spline objects are close up to the - /// given tolerances - template - inline bool - isclose_(std::index_sequence, - const BoundaryCommon &other, - typename BoundaryCore::spline_type::value_type rtol, - typename BoundaryCore::spline_type::value_type atol) const { - return ((std::get(BoundaryCore::bdr_) - .isclose(std::get(other.coeffs()))) && - ...); - } - -public: - /// @brief Returns true if both boundary objects are close up to the given - /// tolerances - template - inline bool - isclose(const BoundaryCommon &other, - typename BoundaryCore::spline_type::value_type rtol = - typename BoundaryCore::spline_type::value_type{1e-5}, - typename BoundaryCore::spline_type::value_type atol = - typename BoundaryCore::spline_type::value_type{1e-8}) const { - return isclose_(std::make_index_sequence{}, other, - rtol, atol); - } - -#define GENERATE_EXPR_MACRO(r, data, name) \ -private: \ - template \ - inline auto BOOST_PP_CAT(name, _)(std::index_sequence, \ - const std::tuple &xi) const { \ - return std::tuple( \ - std::get(BoundaryCore::bdr_) \ - .template name(std::get(xi))...); \ - } \ - \ - template \ - inline auto BOOST_PP_CAT(name, _)(std::index_sequence, \ - const std::tuple &xi, \ - const std::tuple &indices) \ - const { \ - return std::tuple(std::get(BoundaryCore::bdr_) \ - .template name( \ - std::get(xi), std::get(indices))...); \ - } \ - \ - template \ - inline auto BOOST_PP_CAT(name, _)( \ - std::index_sequence, const std::tuple &xi, \ - const std::tuple &indices, \ - const std::tuple &coeff_indices) const { \ - return std::tuple(std::get(BoundaryCore::bdr_) \ - .template name( \ - std::get(xi), std::get(indices), \ - std::get(coeff_indices))...); \ - } \ - \ -public: \ - template \ - inline auto name(const Args &...args) const { \ - return BOOST_PP_CAT(name, _)( \ - std::make_index_sequence{}, args...); \ - } - - /// @brief Auto-generated functions - /// @{ - BOOST_PP_SEQ_FOR_EACH(GENERATE_EXPR_MACRO, _, GENERATE_EXPR_SEQ) - /// @} -#undef GENERATE_EXPR_MACRO - -#define GENERATE_IEXPR_MACRO(r, data, name) \ -private: \ - template \ - inline auto BOOST_PP_CAT(name, _)(std::index_sequence, \ - const std::tuple &G, \ - const std::tuple &xi) const { \ - return std::tuple(std::get(BoundaryCore::bdr_) \ - .template name( \ - std::get(G), std::get(xi))...); \ - } \ - \ - template \ - inline auto BOOST_PP_CAT(name, _)( \ - std::index_sequence, const std::tuple &G, \ - const std::tuple &xi, const std::tuple &indices) \ - const { \ - return std::tuple( \ - std::get(BoundaryCore::bdr_) \ - .template name( \ - std::get(G), std::get(xi), std::get(indices))...); \ - } \ - \ - template \ - inline auto BOOST_PP_CAT(name, _)( \ - std::index_sequence, const std::tuple &G, \ - const std::tuple &xi, const std::tuple &indices, \ - const std::tuple &coeff_indices) const { \ - return std::tuple(std::get(BoundaryCore::bdr_) \ - .template name( \ - std::get(G), std::get(xi), \ - std::get(indices), \ - std::get(coeff_indices))...); \ - } \ - \ -public: \ - template \ - inline auto name(const Args &...args) const { \ - return BOOST_PP_CAT(name, _)( \ - std::make_index_sequence{}, args...); \ - } - - /// @brief Auto-generated functions - /// @{ - BOOST_PP_SEQ_FOR_EACH(GENERATE_IEXPR_MACRO, _, GENERATE_IEXPR_SEQ) - /// @} -#undef GENERATE_IEXPR_MACRO - - /// @brief Returns the `device` property of all splines - auto device() const noexcept { - return std::apply( - [](const auto &...bspline) { - return std::make_tuple(bspline.device()...); - }, - BoundaryCore::bdr_); - } - - /// @brief Returns the `device_index` property of all splines - auto device_index() const noexcept { - return std::apply( - [](const auto &...bspline) { - return std::make_tuple(bspline.device_index()...); - }, - BoundaryCore::bdr_); - } - - /// @brief Returns the `dtype` property of all splines - auto dtype() const noexcept { - return std::apply( - [](const auto &...bspline) { - return std::make_tuple(bspline.dtype()...); - }, - BoundaryCore::bdr_); - } - - /// @brief Returns the `layout` property of all splines - auto layout() const noexcept { - return std::apply( - [](const auto &...bspline) { - return std::make_tuple(bspline.layout()...); - }, - BoundaryCore::bdr_); - } - - /// @brief Returns the `requires_grad` property of all splines - auto requires_grad() const noexcept { - return std::apply( - [](const auto &...bspline) { - return std::make_tuple(bspline.requires_grad()...); - }, - BoundaryCore::bdr_); - } - - /// @brief Returns the `pinned_memory` property of all splines - auto pinned_memory() const noexcept { - return std::apply( - [](const auto &...bspline) { - return std::make_tuple(bspline.pinned_memory()...); - }, - BoundaryCore::bdr_); - } - - /// @brief Returns if the layout is sparse of all splines - auto is_sparse() const noexcept { - return std::apply( - [](const auto &...bspline) { - return std::make_tuple(bspline.is_sparse()...); - }, - BoundaryCore::bdr_); - } - - /// @brief Returns true if the B-spline is uniform of all splines - auto is_uniform() const noexcept { - return std::apply( - [](const auto &...bspline) { - return std::make_tuple(bspline.is_uniform()...); - }, - BoundaryCore::bdr_); - } - - /// @brief Returns true if the B-spline is non-uniform if all splines - auto is_nonuniform() const noexcept { - return std::apply( - [](const auto &...bspline) { - return std::make_tuple(bspline.is_nonuniform()...); - }, - BoundaryCore::bdr_); - } - - /// @brief Sets the boundary object's `requires_grad` property - BoundaryCommon &set_requires_grad(bool requires_grad) { - std::apply( - [requires_grad](const auto &...bspline) { - (bspline.set_requires_grad(requires_grad), ...); - }, - BoundaryCore::bdr_); - - return *this; - } - - /// @brief Returns a copy of the boundary object with settings from options - template inline auto to(Options options) const { - using boundary_type = BoundaryCommon>; - - return boundary_type(std::apply( - [&options](const auto &...bspline) { - return std::make_tuple(bspline.template to(options)...); - }, - BoundaryCore::bdr_)); - } - - /// @brief Returns a copy of the boundary object with settings from device - inline auto to(torch::Device device) const { - return BoundaryCommon(std::apply( - [&device](const auto &...bspline) { - return std::make_tuple(bspline.to(device)...); - }, - BoundaryCore::bdr_)); - } - - /// @brief Returns a copy of the boundary object with real_t type - template inline auto to() const { - using boundary_type = BoundaryCommon()), - BoundaryCore::spline_type::parDim()>>; - - return boundary_type(std::apply( - [](const auto &...bspline) { - return std::make_tuple(bspline.template to()...); - }, - BoundaryCore::bdr_)); - } -}; - -/// @brief Boundary -template -using Boundary = BoundaryCommon>; - -/// @brief Print (as string) a Boundary object -template -inline std::ostream &operator<<(std::ostream &os, const Boundary &obj) { - obj.pretty_print(os); - return os; -} - -} // namespace iganet +/** + @file include/boundary.hpp + + @brief Boundary treatment + + @author Matthias Moller + + @copyright This file is part of the IgANet project + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#pragma once + +#include +#include + +#include + +namespace iganet { + +/// @brief Identifiers for topological sides +enum side { + west = 1, + east = 2, + south = 3, + north = 4, + front = 5, + back = 6, + stime = 7, + etime = 8, + left = 1, + right = 2, + down = 3, + up = 4, + none = 0 +}; + +/// @brief BoundaryCore +template class BoundaryCore; + +/// @brief BoundaryCore (1d specialization) +/// +/// This specialization has 2 sides +/// - west (u=0) +/// - east (u=1) +template +class BoundaryCore : public utils::Serializable, + private utils::FullQualifiedName { + /// @brief Enable access to private members + template friend class BoundaryCommon; + +protected: + /// @brief Spline type + using spline_type = Spline; + + /// @brief Boundary spline type + using boundary_spline_type = + typename Spline::template derived_self_type; + + /// @brief Deduces the derived boundary spline type when exposed + /// to a different class template parameter `real_t` + template + using real_derived_boundary_spline_type = + typename Spline::template derived_self_type; + + /// @brief Tuple of splines + std::tuple bdr_; + +public: + /// @brief Boundary type + using boundary_type = decltype(bdr_); + + /// @brief Evaluation type + using eval_type = std::tuple; + + /// @brief Default constructor + BoundaryCore(Options options = + Options{}) + : bdr_({boundary_spline_type(options), boundary_spline_type(options)}) {} + + /// @brief Copy constructor + BoundaryCore(const boundary_type &bdr_) : bdr_(bdr_) {} + + /// @brief Move constructor + BoundaryCore(boundary_type &&bdr_) : bdr_(bdr_) {} + + /// @brief Copy/clone constructor + BoundaryCore(const BoundaryCore &other, bool clone) + : bdr_(clone ? std::apply( + [](const auto &...bspline) { + return std::make_tuple(bspline.clone()...); + }, + other.coeffs()) + : other.coeffs()) {} + + /// @brief Constructor + BoundaryCore(const std::array &, enum init init = init::zeros, + Options options = + Options{}) + : bdr_({boundary_spline_type(std::array{}, init, options), + boundary_spline_type(std::array{}, init, options)}) {} + + /// @brief Constructor + BoundaryCore(const std::array, 1> &, + enum init init = init::zeros, + Options options = + Options{}) + : bdr_({boundary_spline_type(std::array{}, init, options), + boundary_spline_type(std::array{}, init, options)}) {} + + /// @brief Sets the coefficients of all spline objects from a + /// single tensor that holds both boundary and inner coefficients + /// + /// @param[in] tensor Tensor from which to extract the coefficients + /// + /// @result Updates spline object + inline auto &from_full_tensor(const torch::Tensor &tensor) { + + if (tensor.dim() > 1) { + auto tensor_view = tensor.view({Spline::controlPointDim(), -1, tensor.size(-1)}); + + side().from_tensor(tensor_view.index({torch::indexing::Slice(), 0}) + .reshape({-1, tensor.size(-1)})); + side().from_tensor(tensor_view.index({torch::indexing::Slice(), -1}) + .reshape({-1, tensor.size(-1)})); + } else { + auto tensor_view = tensor.view({Spline::controlPointDim(), -1}); + + side().from_tensor( + tensor_view.index({torch::indexing::Slice(), 0}).flatten()); + side().from_tensor( + tensor_view.index({torch::indexing::Slice(), -1}).flatten()); + } + return *this; + } + + /// @brief Returns the number of sides + inline static constexpr short_t nsides() { return side::east; } + + /// @brief Returns constant reference to side-th Spline + template inline constexpr auto &side() const { + static_assert(s > none && s <= nsides()); + return std::get(bdr_); + } + + /// @brief Returns non-constant reference to side-th Spline + template inline constexpr auto &side() { + static_assert(s > none && s <= nsides()); + return std::get(bdr_); + } + + /// @brief Returns a constant reference to the array of + /// coefficients for all boundary segments. + inline constexpr auto &coeffs() const { return bdr_; } + + /// @brief Returns a non-constant reference to the array of + /// coefficients for all boundary segments. + inline constexpr auto &coeffs() { return bdr_; } + + /// @brief Returns the total number of coefficients + inline int64_t ncumcoeffs() const { + int64_t s = 0; + s += side().ncumcoeffs(); + s += side().ncumcoeffs(); + + return s; + } + + /// @brief Returns a string representation of the Boundary object + inline virtual void + pretty_print(std::ostream &os = Log(log::info)) const noexcept override { + os << name() << "(\n" + << "west = " << side() << "\n" + << "east = " << side() << "\n)"; + } + + /// @brief Returns the boundary object as JSON object + inline nlohmann::json to_json() const override { + nlohmann::json json; + json["west"] = side().to_json(); + json["east"] = side().to_json(); + + return json; + } + + /// @brief Updates the boundary object from JSON object + inline BoundaryCore &from_json(const nlohmann::json &json) { + side().from_json(json["west"]); + side().from_json(json["east"]); + + return *this; + } + + /// @brief Returns the Greville abscissae + inline eval_type greville() const { + return eval_type{side().greville(), side().greville()}; + } +}; + +/// @brief BoundaryCore (2d specialization) +/// +/// This specialization has 4 sides +/// - west (u=0, v ) +/// - east (u=1, v ) +/// - south (u, v=0) +/// - north (u, v=1) +template +class BoundaryCore : public utils::Serializable, + private utils::FullQualifiedName { + /// @brief Enable access to private members + template friend class BoundaryCommon; + +protected: + /// @brief Spline type + using spline_type = Spline; + + /// @brief Boundary spline type + using boundary_spline_type = std::tuple< + typename Spline::template derived_self_type< + typename Spline::value_type, Spline::geoDim(), Spline::degree(1)>, + typename Spline::template derived_self_type< + typename Spline::value_type, Spline::geoDim(), Spline::degree(0)>>; + + /// @brief Deduces the derived boundary spline type when exposed + /// to a different class template parameter `real_t` + template + using real_derived_boundary_spline_type = + std::tuple, + typename Spline::template derived_self_type< + real_t, Spline::geoDim(), Spline::degree(0)>>; + + /// @brief Tuple of splines + std::tuple, + typename std::tuple_element_t<0, boundary_spline_type>, + typename std::tuple_element_t<1, boundary_spline_type>, + typename std::tuple_element_t<1, boundary_spline_type>> + bdr_; + +public: + /// @brief Boundary type + using boundary_type = decltype(bdr_); + + /// @brief Evaluation type + using eval_type = std::tuple, utils::TensorArray<1>, + utils::TensorArray<1>, utils::TensorArray<1>>; + + /// @brief Default constructor + BoundaryCore(Options options = + Options{}) + : bdr_({std::tuple_element_t<0, boundary_spline_type>(options), + std::tuple_element_t<0, boundary_spline_type>(options), + std::tuple_element_t<1, boundary_spline_type>(options), + std::tuple_element_t<1, boundary_spline_type>(options)}) {} + + /// @brief Copy constructor + BoundaryCore(const boundary_type &bdr_) : bdr_(bdr_) {} + + /// @brief Move constructor + BoundaryCore(boundary_type &&bdr_) : bdr_(bdr_) {} + + /// @brief Copy/clone constructor + BoundaryCore(const BoundaryCore &other, bool clone) + : bdr_(clone ? std::apply( + [](const auto &...bspline) { + return std::make_tuple(bspline.clone()...); + }, + other.coeffs()) + : other.coeffs()) {} + + /// @brief Constructor + BoundaryCore(const std::array &ncoeffs, + enum init init = init::zeros, + Options options = + Options{}) + : bdr_({std::tuple_element_t<0, boundary_spline_type>( + std::array({ncoeffs[1]}), init, options), + std::tuple_element_t<0, boundary_spline_type>( + std::array({ncoeffs[1]}), init, options), + std::tuple_element_t<1, boundary_spline_type>( + std::array({ncoeffs[0]}), init, options), + std::tuple_element_t<1, boundary_spline_type>( + std::array({ncoeffs[0]}), init, options)}) {} + + /// @brief Constructor + BoundaryCore( + const std::array, 2> &kv, + enum init init = init::zeros, + Options options = + Options{}) + : bdr_({std::tuple_element_t<0, boundary_spline_type>( + std::array, 1>( + {kv[1]}), + init, options), + std::tuple_element_t<0, boundary_spline_type>( + std::array, 1>( + {kv[1]}), + init, options), + std::tuple_element_t<1, boundary_spline_type>( + std::array, 1>( + {kv[0]}), + init, options), + std::tuple_element_t<1, boundary_spline_type>( + std::array, 1>( + {kv[0]}), + init, options)}) {} + + /// @brief Sets the coefficients of all spline objects from a + /// single tensor that holds both boundary and inner coefficients + /// + /// @param[in] tensor Tensor from which to extract the coefficients + /// + /// @result Updates spline object + inline auto &from_full_tensor(const torch::Tensor &tensor) { + + if (tensor.dim() > 1) { + auto tensor_view = + tensor.view({Spline::controlPointDim(), side().ncoeffs(0), + side().ncoeffs(0), tensor.size(-1)}); + + side().from_tensor( + tensor_view + .index({torch::indexing::Slice(), torch::indexing::Slice(), 0}) + .reshape({-1, tensor.size(-1)})); + side().from_tensor( + tensor_view + .index({torch::indexing::Slice(), torch::indexing::Slice(), -1}) + .reshape({-1, tensor.size(-1)})); + side().from_tensor( + tensor_view + .index({torch::indexing::Slice(), 0, torch::indexing::Slice()}) + .reshape({-1, tensor.size(-1)})); + side().from_tensor( + tensor_view + .index({torch::indexing::Slice(), -1, torch::indexing::Slice()}) + .reshape({-1, tensor.size(-1)})); + } else { + auto tensor_view = tensor.view({Spline::controlPointDim(), side().ncoeffs(0), + side().ncoeffs(0)}); + + side().from_tensor( + tensor_view + .index({torch::indexing::Slice(), torch::indexing::Slice(), 0}) + .flatten()); + side().from_tensor( + tensor_view + .index({torch::indexing::Slice(), torch::indexing::Slice(), -1}) + .flatten()); + side().from_tensor( + tensor_view + .index({torch::indexing::Slice(), 0, torch::indexing::Slice()}) + .flatten()); + side().from_tensor( + tensor_view + .index({torch::indexing::Slice(), -1, torch::indexing::Slice()}) + .flatten()); + } + return *this; + } + + /// @brief Returns the number of sides + inline static constexpr short_t nsides() { return side::north; } + + /// @brief Returns constant reference to the s-th side's spline + template inline constexpr auto &side() const { + static_assert(s > none && s <= nsides()); + return std::get(bdr_); + } + + /// @brief Returns non-constant reference to the s-th side's spline + template inline constexpr auto &side() { + static_assert(s > none && s <= nsides()); + return std::get(bdr_); + } + + /// @brief Returns a constant reference to the array of + /// coefficients for all boundary segments. + inline constexpr auto &coeffs() const { return bdr_; } + + /// @brief Returns a non-constant reference to the array of + /// coefficients for all boundary segments. + inline constexpr auto &coeffs() { return bdr_; } + + /// @brief Returns the total number of coefficients + inline int64_t ncumcoeffs() const { + int64_t s = 0; + s += side().ncumcoeffs(); + s += side().ncumcoeffs(); + s += side().ncumcoeffs(); + s += side().ncumcoeffs(); + + return s; + } + + /// @brief Returns a string representation of the Boundary object + inline virtual void + pretty_print(std::ostream &os = Log(log::info)) const noexcept override { + os << name() << "(\n" + << "west = " << side() << "\n" + << "east = " << side() << "\n" + << "south = " << side() << "\n" + << "north = " << side() << "\n)"; + } + + /// @brief Returns the boundary object as JSON object + inline nlohmann::json to_json() const override { + nlohmann::json json; + json["west"] = side().to_json(); + json["east"] = side().to_json(); + json["south"] = side().to_json(); + json["north"] = side().to_json(); + + return json; + } + + /// @brief Updates the boundary object from JSON object + inline BoundaryCore &from_json(const nlohmann::json &json) { + side().from_json(json["west"]); + side().from_json(json["east"]); + side().from_json(json["south"]); + side().from_json(json["north"]); + + return *this; + } + + /// @brief Returns the Greville abscissae + inline eval_type greville() const { + return eval_type{side().greville(), side().greville(), + side().greville(), side().greville()}; + } +}; + +/// @brief BoundaryCore (3d specialization) +/// +/// This specialization has 6 sides +/// - west (u=0, v, w) +/// - east (u=1, v, w) +/// - south (u, v=0, w) +/// - north (u, v=1, w) +/// - front (u, v, w=0) +/// - back (u, v, w=1) +template +class BoundaryCore : public utils::Serializable, + private utils::FullQualifiedName { + /// @brief Enable access to private members + template friend class BoundaryCommon; + +protected: + /// @brief Spline type + using spline_type = Spline; + + /// @brief Boundary spline type + using boundary_spline_type = + std::tuple, + typename Spline::template derived_self_type< + typename Spline::value_type, Spline::geoDim(), + Spline::degree(0), Spline::degree(2)>, + typename Spline::template derived_self_type< + typename Spline::value_type, Spline::geoDim(), + Spline::degree(0), Spline::degree(1)>>; + + /// @brief Deduces the derived boundary spline type when exposed + /// to a different class template parameter `real_t` + template + using real_derived_boundary_spline_type = std::tuple< + typename Spline::template derived_self_type< + real_t, Spline::geoDim(), Spline::degree(1), Spline::degree(2)>, + typename Spline::template derived_self_type< + real_t, Spline::geoDim(), Spline::degree(0), Spline::degree(2)>, + typename Spline::template derived_self_type< + real_t, Spline::geoDim(), Spline::degree(0), Spline::degree(1)>>; + + /// @brief Tuple of splines + std::tuple, + typename std::tuple_element_t<0, boundary_spline_type>, + typename std::tuple_element_t<1, boundary_spline_type>, + typename std::tuple_element_t<1, boundary_spline_type>, + typename std::tuple_element_t<2, boundary_spline_type>, + typename std::tuple_element_t<2, boundary_spline_type>> + bdr_; + +public: + /// @brief Boundary type + using boundary_type = decltype(bdr_); + + /// @brief Evaluation type + using eval_type = std::tuple, utils::TensorArray<2>, + utils::TensorArray<2>, utils::TensorArray<2>, + utils::TensorArray<2>, utils::TensorArray<2>>; + + /// @brief Default constructor + BoundaryCore(Options options = + Options{}) + : bdr_({std::tuple_element_t<0, boundary_spline_type>(options), + std::tuple_element_t<0, boundary_spline_type>(options), + std::tuple_element_t<1, boundary_spline_type>(options), + std::tuple_element_t<1, boundary_spline_type>(options), + std::tuple_element_t<2, boundary_spline_type>(options), + std::tuple_element_t<2, boundary_spline_type>(options)}) {} + + /// @brief Copy constructor + BoundaryCore(const boundary_type &bdr_) : bdr_(bdr_) {} + + /// @brief Move constructor + BoundaryCore(boundary_type &&bdr_) : bdr_(bdr_) {} + + /// @brief Copy/clone constructor + BoundaryCore(const BoundaryCore &other, bool clone) + : bdr_(clone ? std::apply( + [](const auto &...bspline) { + return std::make_tuple(bspline.clone()...); + }, + other.coeffs()) + : other.coeffs()) {} + + /// @brief Constructor + BoundaryCore(const std::array &ncoeffs, + enum init init = init::zeros, + Options options = + Options{}) + : bdr_({std::tuple_element_t<0, boundary_spline_type>( + std::array({ncoeffs[1], ncoeffs[2]}), init, + options), + std::tuple_element_t<0, boundary_spline_type>( + std::array({ncoeffs[1], ncoeffs[2]}), init, + options), + std::tuple_element_t<1, boundary_spline_type>( + std::array({ncoeffs[0], ncoeffs[2]}), init, + options), + std::tuple_element_t<1, boundary_spline_type>( + std::array({ncoeffs[0], ncoeffs[2]}), init, + options), + std::tuple_element_t<2, boundary_spline_type>( + std::array({ncoeffs[0], ncoeffs[1]}), init, + options), + std::tuple_element_t<2, boundary_spline_type>( + std::array({ncoeffs[0], ncoeffs[1]}), init, + options)}) {} + + /// @brief Constructor + BoundaryCore( + const std::array, 3> &kv, + enum init init = init::zeros, + Options options = + Options{}) + : bdr_({std::tuple_element_t<0, boundary_spline_type>( + std::array, 2>( + {kv[1], kv[2]}), + init, options), + std::tuple_element_t<0, boundary_spline_type>( + std::array, 2>( + {kv[1], kv[2]}), + init, options), + std::tuple_element_t<1, boundary_spline_type>( + std::array, 2>( + {kv[0], kv[2]}), + init, options), + std::tuple_element_t<1, boundary_spline_type>( + std::array, 2>( + {kv[0], kv[2]}), + init, options), + std::tuple_element_t<2, boundary_spline_type>( + std::array, 2>( + {kv[0], kv[1]}), + init, options), + std::tuple_element_t<2, boundary_spline_type>( + std::array, 2>( + {kv[0], kv[1]}), + init, options)}) {} + + /// @brief Sets the coefficients of all spline objects from a + /// single tensor that holds both boundary and inner coefficients + /// + /// @param[in] tensor Tensor from which to extract the coefficients + /// + /// @result Updates spline object + inline auto &from_full_tensor(const torch::Tensor &tensor) { + + if (tensor.dim() > 1) { + auto tensor_view = tensor.view( + {Spline::controlPointDim(), side().ncoeffs(1), side().ncoeffs(0), + side().ncoeffs(0), tensor.size(-1)}); + + side().from_tensor( + tensor_view + .index({torch::indexing::Slice(), torch::indexing::Slice(), + torch::indexing::Slice(), 0}) + .reshape({-1, tensor.size(-1)})); + side().from_tensor( + tensor_view + .index({torch::indexing::Slice(), torch::indexing::Slice(), + torch::indexing::Slice(), -1}) + .reshape({-1, tensor.size(-1)})); + side().from_tensor( + tensor_view + .index({torch::indexing::Slice(), torch::indexing::Slice(), 0, + torch::indexing::Slice()}) + .reshape({-1, tensor.size(-1)})); + side().from_tensor( + tensor_view + .index({torch::indexing::Slice(), torch::indexing::Slice(), -1, + torch::indexing::Slice()}) + .reshape({-1, tensor.size(-1)})); + side().from_tensor( + tensor_view + .index({torch::indexing::Slice(), 0, torch::indexing::Slice(), + torch::indexing::Slice()}) + .reshape({-1, tensor.size(-1)})); + side().from_tensor( + tensor_view + .index({torch::indexing::Slice(), -1, torch::indexing::Slice(), + torch::indexing::Slice()}) + .reshape({-1, tensor.size(-1)})); + } else { + auto tensor_view = + tensor.view({Spline::controlPointDim(), side().ncoeffs(1), + side().ncoeffs(0), side().ncoeffs(0)}); + + side().from_tensor( + tensor_view + .index({torch::indexing::Slice(), torch::indexing::Slice(), + torch::indexing::Slice(), 0}) + .flatten()); + side().from_tensor( + tensor_view + .index({torch::indexing::Slice(), torch::indexing::Slice(), + torch::indexing::Slice(), -1}) + .flatten()); + + side().from_tensor( + tensor_view + .index({torch::indexing::Slice(), torch::indexing::Slice(), 0, + torch::indexing::Slice()}) + .flatten()); + side().from_tensor( + tensor_view + .index({torch::indexing::Slice(), torch::indexing::Slice(), -1, + torch::indexing::Slice()}) + .flatten()); + + side().from_tensor( + tensor_view + .index({torch::indexing::Slice(), 0, torch::indexing::Slice(), + torch::indexing::Slice()}) + .flatten()); + side().from_tensor( + tensor_view + .index({torch::indexing::Slice(), -1, torch::indexing::Slice(), + torch::indexing::Slice()}) + .flatten()); + } + return *this; + } + + /// @brief Returns the number of sides + inline static constexpr short_t nsides() { return side::back; } + + /// @brief Returns constant reference to side-th spline + template inline constexpr auto &side() const { + static_assert(s > none && s <= nsides()); + return std::get(bdr_); + } + + /// @brief Returns non-constant reference to side-th spline + template inline constexpr auto &side() { + static_assert(s > none && s <= nsides()); + return std::get(bdr_); + } + + /// @brief Returns a constant reference to the array of + /// coefficients for all boundary segments. + inline constexpr auto &coeffs() const { return bdr_; } + + /// @brief Returns a non-constant reference to the array of + /// coefficients for all boundary segments. + inline constexpr auto &coeffs() { return bdr_; } + + /// @brief Returns the total number of coefficients + inline int64_t ncumcoeffs() const { + int64_t s = 0; + s += side().ncumcoeffs(); + s += side().ncumcoeffs(); + s += side().ncumcoeffs(); + s += side().ncumcoeffs(); + s += side().ncumcoeffs(); + s += side().ncumcoeffs(); + + return s; + } + + /// @brief Returns a string representation of the Boundary object + inline virtual void + pretty_print(std::ostream &os = Log(log::info)) const noexcept override { + os << name() << "(\n" + << "west = " << side() << "\n" + << "east = " << side() << "\n" + << "south = " << side() << "\n" + << "north = " << side() << "\n" + << "front = " << side() << "\n" + << "back = " << side() << "\n)"; + } + + /// @brief Returns the boundary object as JSON object + inline nlohmann::json to_json() const override { + nlohmann::json json; + json["west"] = side().to_json(); + json["east"] = side().to_json(); + json["south"] = side().to_json(); + json["north"] = side().to_json(); + json["front"] = side().to_json(); + json["back"] = side().to_json(); + + return json; + } + + /// @brief Updates the boundary object from JSON object + inline BoundaryCore &from_json(const nlohmann::json &json) { + side().from_json(json["west"]); + side().from_json(json["east"]); + side().from_json(json["south"]); + side().from_json(json["north"]); + side().from_json(json["front"]); + side().from_json(json["back"]); + + return *this; + } + + /// @brief Returns the Greville abscissae + inline eval_type greville() const { + return eval_type{side().greville(), side().greville(), + side().greville(), side().greville(), + side().greville(), side().greville()}; + } +}; + +/// @brief BoundaryCore (4d specialization) +/// +/// This specialization has 8 sides +/// - west (u=0, v, w, t) +/// - east (u=1, v, w, t) +/// - south (u, v=0, w, t) +/// - north (u, v=1, w, t) +/// - front (u, v, w=0, t) +/// - back (u, v, w=1, t) +/// - stime (u, v, w, t=0) +/// - etime (u, v, w, t=1) +template +class BoundaryCore : public utils::Serializable, + private utils::FullQualifiedName { + /// @brief Enable access to private members + template friend class BoundaryCommon; + +protected: + /// @brief Spline type + using spline_type = Spline; + + /// @brief Array storing the degrees + using boundary_spline_type = + std::tuple, + typename Spline::template derived_self_type< + typename Spline::value_type, Spline::geoDim(), + Spline::degree(0), Spline::degree(2), Spline::degree(3)>, + typename Spline::template derived_self_type< + typename Spline::value_type, Spline::geoDim(), + Spline::degree(0), Spline::degree(1), Spline::degree(3)>, + typename Spline::template derived_self_type< + typename Spline::value_type, Spline::geoDim(), + Spline::degree(0), Spline::degree(1), Spline::degree(2)>>; + + /// @brief Deduces the derived boundary spline type when exposed + /// to a different class template parameter `real_t` + template + using real_derived_boundary_spline_type = + std::tuple, + typename Spline::template derived_self_type< + real_t, Spline::geoDim(), Spline::degree(0), + Spline::degree(2), Spline::degree(3)>, + typename Spline::template derived_self_type< + real_t, Spline::geoDim(), Spline::degree(0), + Spline::degree(1), Spline::degree(3)>, + typename Spline::template derived_self_type< + real_t, Spline::geoDim(), Spline::degree(0), + Spline::degree(1), Spline::degree(2)>>; + + /// @brief Tuple of splines + std::tuple, + typename std::tuple_element_t<0, boundary_spline_type>, + typename std::tuple_element_t<1, boundary_spline_type>, + typename std::tuple_element_t<1, boundary_spline_type>, + typename std::tuple_element_t<2, boundary_spline_type>, + typename std::tuple_element_t<2, boundary_spline_type>, + typename std::tuple_element_t<3, boundary_spline_type>, + typename std::tuple_element_t<3, boundary_spline_type>> + bdr_; + +public: + /// @brief Boundary type + using boundary_type = decltype(bdr_); + + /// @brief Evaluation type + using eval_type = std::tuple, utils::TensorArray<3>, + utils::TensorArray<3>, utils::TensorArray<3>, + utils::TensorArray<3>, utils::TensorArray<3>, + utils::TensorArray<3>, utils::TensorArray<3>>; + + /// @brief Default constructor + BoundaryCore(Options options = + Options{}) + : bdr_({std::tuple_element_t<0, boundary_spline_type>(options), + std::tuple_element_t<0, boundary_spline_type>(options), + std::tuple_element_t<1, boundary_spline_type>(options), + std::tuple_element_t<1, boundary_spline_type>(options), + std::tuple_element_t<2, boundary_spline_type>(options), + std::tuple_element_t<2, boundary_spline_type>(options), + std::tuple_element_t<3, boundary_spline_type>(options), + std::tuple_element_t<3, boundary_spline_type>(options)}) {} + + /// @brief Copy constructor + BoundaryCore(const boundary_type &bdr_) : bdr_(bdr_) {} + + /// @brief Move constructor + BoundaryCore(boundary_type &&bdr_) : bdr_(bdr_) {} + + /// @brief Copy/clone constructor + BoundaryCore(const BoundaryCore &other, bool clone) + : bdr_(clone ? std::apply( + [](const auto &...bspline) { + return std::make_tuple(bspline.clone()...); + }, + other.coeffs()) + : other.coeffs()) {} + + /// @brief Constructor + BoundaryCore(const std::array &ncoeffs, + enum init init = init::zeros, + Options options = + Options{}) + : bdr_({std::tuple_element_t<0, boundary_spline_type>( + std::array({ncoeffs[1], ncoeffs[2], ncoeffs[3]}), + init, options), + std::tuple_element_t<0, boundary_spline_type>( + std::array({ncoeffs[1], ncoeffs[2], ncoeffs[3]}), + init, options), + std::tuple_element_t<1, boundary_spline_type>( + std::array({ncoeffs[0], ncoeffs[2], ncoeffs[3]}), + init, options), + std::tuple_element_t<1, boundary_spline_type>( + std::array({ncoeffs[0], ncoeffs[2], ncoeffs[3]}), + init, options), + std::tuple_element_t<2, boundary_spline_type>( + std::array({ncoeffs[0], ncoeffs[1], ncoeffs[3]}), + init, options), + std::tuple_element_t<2, boundary_spline_type>( + std::array({ncoeffs[0], ncoeffs[1], ncoeffs[3]}), + init, options), + std::tuple_element_t<3, boundary_spline_type>( + std::array({ncoeffs[0], ncoeffs[1], ncoeffs[2]}), + init, options), + std::tuple_element_t<3, boundary_spline_type>( + std::array({ncoeffs[0], ncoeffs[1], ncoeffs[2]}), + init, options)}) {} + + /// @brief Constructor + BoundaryCore( + const std::array, 4> &kv, + enum init init = init::zeros, + Options options = + Options{}) + : bdr_({std::tuple_element_t<0, boundary_spline_type>( + std::array, 3>( + {kv[1], kv[2], kv[3]}), + init, options), + std::tuple_element_t<0, boundary_spline_type>( + std::array, 3>( + {kv[1], kv[2], kv[3]}), + init, options), + std::tuple_element_t<1, boundary_spline_type>( + std::array, 3>( + {kv[0], kv[2], kv[3]}), + init, options), + std::tuple_element_t<1, boundary_spline_type>( + std::array, 3>( + {kv[0], kv[2], kv[3]}), + init, options), + std::tuple_element_t<2, boundary_spline_type>( + std::array, 3>( + {kv[0], kv[1], kv[3]}), + init, options), + std::tuple_element_t<2, boundary_spline_type>( + std::array, 3>( + {kv[0], kv[1], kv[3]}), + init, options), + std::tuple_element_t<3, boundary_spline_type>( + std::array, 3>( + {kv[0], kv[1], kv[2]}), + init, options), + std::tuple_element_t<3, boundary_spline_type>( + std::array, 3>( + {kv[0], kv[1], kv[2]}), + init, options)}) {} + + /// @brief Sets the coefficients of all spline objects from a + /// single tensor that holds both boundary and inner coefficients + /// + /// @param[in] tensor Tensor from which to extract the coefficients + /// + /// @result Updates spline object + inline auto &from_full_tensor(const torch::Tensor &tensor) { + + if (tensor.dim() > 1) { + auto tensor_view = tensor.view( + {Spline::controlPointDim(), side().ncoeffs(2), side().ncoeffs(1), + side().ncoeffs(0), side().ncoeffs(0), tensor.size(-1)}); + + side().from_tensor( + tensor_view + .index({torch::indexing::Slice(), torch::indexing::Slice(), + torch::indexing::Slice(), torch::indexing::Slice(), 0}) + .reshape({-1, tensor.size(-1)})); + side().from_tensor( + tensor_view + .index({torch::indexing::Slice(), torch::indexing::Slice(), + torch::indexing::Slice(), torch::indexing::Slice(), -1}) + .reshape({-1, tensor.size(-1)})); + side().from_tensor( + tensor_view + .index({torch::indexing::Slice(), torch::indexing::Slice(), + torch::indexing::Slice(), 0, torch::indexing::Slice()}) + .reshape({-1, tensor.size(-1)})); + side().from_tensor( + tensor_view + .index({torch::indexing::Slice(), torch::indexing::Slice(), + torch::indexing::Slice(), -1, torch::indexing::Slice()}) + .reshape({-1, tensor.size(-1)})); + side().from_tensor( + tensor_view + .index({torch::indexing::Slice(), torch::indexing::Slice(), 0, + torch::indexing::Slice(), torch::indexing::Slice()}) + .reshape({-1, tensor.size(-1)})); + side().from_tensor( + tensor_view + .index({torch::indexing::Slice(), torch::indexing::Slice(), -1, + torch::indexing::Slice(), torch::indexing::Slice()}) + .reshape({-1, tensor.size(-1)})); + side().from_tensor( + tensor_view + .index({torch::indexing::Slice(), 0, torch::indexing::Slice(), + torch::indexing::Slice(), torch::indexing::Slice()}) + .reshape({-1, tensor.size(-1)})); + side().from_tensor( + tensor_view + .index({torch::indexing::Slice(), -1, torch::indexing::Slice(), + torch::indexing::Slice(), torch::indexing::Slice()}) + .reshape({-1, tensor.size(-1)})); + } else { + auto tensor_view = tensor.view( + {Spline::controlPointDim(), side().ncoeffs(2), side().ncoeffs(1), + side().ncoeffs(0), side().ncoeffs(0)}); + + side().from_tensor( + tensor_view + .index({torch::indexing::Slice(), torch::indexing::Slice(), + torch::indexing::Slice(), torch::indexing::Slice(), 0}) + .flatten()); + side().from_tensor( + tensor_view + .index({torch::indexing::Slice(), torch::indexing::Slice(), + torch::indexing::Slice(), torch::indexing::Slice(), -1}) + .flatten()); + + side().from_tensor( + tensor_view + .index({torch::indexing::Slice(), torch::indexing::Slice(), + torch::indexing::Slice(), 0, torch::indexing::Slice()}) + .flatten()); + side().from_tensor( + tensor_view + .index({torch::indexing::Slice(), torch::indexing::Slice(), + torch::indexing::Slice(), -1, torch::indexing::Slice()}) + .flatten()); + + side().from_tensor( + tensor_view + .index({torch::indexing::Slice(), torch::indexing::Slice(), 0, + torch::indexing::Slice(), torch::indexing::Slice()}) + .flatten()); + side().from_tensor( + tensor_view + .index({torch::indexing::Slice(), torch::indexing::Slice(), -1, + torch::indexing::Slice(), torch::indexing::Slice()}) + .flatten()); + + side().from_tensor( + tensor_view + .index({torch::indexing::Slice(), 0, torch::indexing::Slice(), + torch::indexing::Slice(), torch::indexing::Slice()}) + .flatten()); + side().from_tensor( + tensor_view + .index({torch::indexing::Slice(), -1, torch::indexing::Slice(), + torch::indexing::Slice(), torch::indexing::Slice()}) + .flatten()); + } + return *this; + } + + /// @brief Returns the number of sides + inline static constexpr short_t nsides() { return side::etime; } + + /// @brief Returns constant reference to side-th spline + template inline constexpr auto &side() const { + static_assert(s > none && s <= nsides()); + return std::get(bdr_); + } + + /// @brief Returns non-constant reference to side-th spline + template inline constexpr auto &side() { + static_assert(s > none && s <= nsides()); + return std::get(bdr_); + } + + /// @brief Returns a constant reference to the array of + /// coefficients for all boundary segments. + inline constexpr auto &coeffs() const { return bdr_; } + + /// @brief Returns a non-constant reference to the array of + /// coefficients for all boundary segments. + inline constexpr auto &coeffs() { return bdr_; } + + /// @brief Returns the total number of coefficients + inline int64_t ncumcoeffs() const { + int64_t s = 0; + s += side().ncumcoeffs(); + s += side().ncumcoeffs(); + s += side().ncumcoeffs(); + s += side().ncumcoeffs(); + s += side().ncumcoeffs(); + s += side().ncumcoeffs(); + s += side().ncumcoeffs(); + s += side().ncumcoeffs(); + + return s; + } + + /// @brief Returns a string representation of the Boundary object + inline virtual void + pretty_print(std::ostream &os = Log(log::info)) const noexcept override { + os << name() << "(\n" + << "west = " << side() << "\n" + << "east = " << side() << "\n" + << "south = " << side() << "\n" + << "north = " << side() << "\n" + << "front = " << side() << "\n" + << "back = " << side() << "\n" + << "stime = " << side() << "\n" + << "etime = " << side() << "\n)"; + } + + /// @brief Returns the boundary object as JSON object + inline nlohmann::json to_json() const override { + nlohmann::json json; + json["west"] = side().to_json(); + json["east"] = side().to_json(); + json["south"] = side().to_json(); + json["north"] = side().to_json(); + json["front"] = side().to_json(); + json["back"] = side().to_json(); + json["stime"] = side().to_json(); + json["etime"] = side().to_json(); + + return json; + } + + /// @brief Updates the boundary object from JSON object + inline BoundaryCore &from_json(const nlohmann::json &json) { + side().from_json(json["west"]); + side().from_json(json["east"]); + side().from_json(json["south"]); + side().from_json(json["north"]); + side().from_json(json["front"]); + side().from_json(json["back"]); + side().from_json(json["stime"]); + side().from_json(json["etime"]); + + return *this; + } + + /// @brief Returns the Greville abscissae + inline eval_type greville() const { + return eval_type{side().greville(), side().greville(), + side().greville(), side().greville(), + side().greville(), side().greville(), + side().greville(), side().greville()}; + } +}; + +/// @brief + +/// @brief Boundary (common high-level functionality) +template class BoundaryCommon : public BoundaryCore { +public: + /// @brief Constructors from the base class + using BoundaryCore::BoundaryCore; + + /// @brief Returns a clone of the boundary object + BoundaryCommon clone() const { return BoundaryCommon(*this); } + +private: + /// @brief Returns all coefficients of all spline objects as a + /// single tensor + /// + /// @result Tensor of coefficients + template + inline torch::Tensor as_tensor_(std::index_sequence) const { + return torch::cat({std::get(BoundaryCore::bdr_).as_tensor()...}); + } + +public: + /// @brief Returns all coefficients of all spline objects as a + /// single tensor + /// + /// @result Tensor of coefficients + inline torch::Tensor as_tensor() const { + return as_tensor_(std::make_index_sequence{}); + } + +private: + /// @brief Returns the size of the single tensor representation of + /// all spline objects + /// + /// @result Size of the tensor + template + inline int64_t as_tensor_size_(std::index_sequence) const { + return std::apply( + [](auto... size) { return (size + ...); }, + std::make_tuple(std::get(BoundaryCore::bdr_).as_tensor_size()...)); + } + +public: + /// @brief Returns the size of the single tensor representation of + /// all spline objects + // + /// @result Size of the tensor + inline int64_t as_tensor_size() const { + return as_tensor_size_(std::make_index_sequence{}); + } + +private: + /// @brief Sets the coefficients of all spline objects from a + /// single tensor + /// + /// @param[in] tensor Tensor from which to extract the coefficients + /// + /// @result Updates spline object + template + inline auto &from_tensor_(std::index_sequence, + const torch::Tensor &tensor) { + + std::size_t start(0); + auto end = [&start](std::size_t inc) { return start += inc; }; + + (std::get(BoundaryCore::bdr_) + .from_tensor(tensor.index({torch::indexing::Slice( + start, end(std::get(BoundaryCore::bdr_).ncumcoeffs() * + std::get(BoundaryCore::bdr_).controlPointDim()))})), + ...); + + return *this; + } + +public: + /// @brief Sets the coefficients of all spline objects from a + /// single tensor + /// + /// @param[in] tensor Tensor from which to extract the coefficients + /// + /// @result Updated spline objects + inline auto &from_tensor(const torch::Tensor &tensor) { + return from_tensor_(std::make_index_sequence{}, + tensor); + } + +private: + /// @brief Returns the values of the boundary spline objects in + /// the points `xi` @{ + template + inline auto eval_(std::index_sequence, + const std::tuple &xi) const { + return std::tuple( + std::get(BoundaryCore::bdr_) + .template eval(std::get(xi))...); + } + + template + inline auto eval_(std::index_sequence, const std::tuple &xi, + const std::tuple &indices) const { + return std::tuple(std::get(BoundaryCore::bdr_) + .template eval( + std::get(xi), std::get(indices))...); + } + + template + inline auto eval_(std::index_sequence, const std::tuple &xi, + const std::tuple &indices, + const std::tuple &coeff_indices) const { + return std::tuple(std::get(BoundaryCore::bdr_) + .template eval( + std::get(xi), std::get(indices), + std::get(coeff_indices))...); + } + /// @} + +public: + /// @brief Returns the values of the spline objects in the points `xi` + /// @{ + template + inline auto eval(const std::tuple &xi) const { + return eval_( + std::make_index_sequence{}, xi); + } + + template + inline auto eval(const std::tuple &xi, + const std::tuple &indices) const { + static_assert(sizeof...(Xi) == sizeof...(Indices)); + return eval_( + std::make_index_sequence{}, xi, indices); + } + + template + inline auto eval(const std::tuple &xi, + const std::tuple &indices, + const std::tuple &coeff_indices) const { + static_assert(sizeof...(Xi) == sizeof...(Indices) && + sizeof...(Xi) == sizeof...(Coeff_Indices)); + return eval_( + std::make_index_sequence{}, xi, indices, + coeff_indices); + } + /// @} + +private: + /// @brief Returns the value of the boundary spline objects from + /// precomputed basis function @{ + template + inline auto + eval_from_precomputed_(std::index_sequence, + const std::tuple &basfunc, + const std::tuple &coeff_indices, + const std::tuple &numeval, + const std::tuple &sizes) const { + return std::tuple(std::get(BoundaryCore::bdr_) + .eval_from_precomputed(std::get(basfunc), + std::get(coeff_indices), + std::get(numeval), + std::get(sizes))...); + } + + template + inline auto + eval_from_precomputed_(std::index_sequence, + const std::tuple &basfunc, + const std::tuple &coeff_indices, + const std::tuple &xi) const { + return std::tuple( + std::get(BoundaryCore::bdr_) + .eval_from_precomputed( + std::get(basfunc), std::get(coeff_indices), + std::get(xi)[0].numel(), std::get(xi)[0].sizes())...); + } + /// @} + +public: + /// @brief Returns the value of the spline objects from + /// precomputed basis function @{ + template + inline auto + eval_from_precomputed(const std::tuple &basfunc, + const std::tuple &coeff_indices, + const std::tuple &numeval, + const std::tuple &sizes) const { + static_assert(sizeof...(Basfunc) == sizeof...(Coeff_Indices) && + sizeof...(Basfunc) == sizeof...(Numeval) && + sizeof...(Basfunc) == sizeof...(Sizes)); + return eval_from_precomputed_( + std::make_index_sequence{}, basfunc, + coeff_indices, numeval, sizes); + } + + template + inline auto + eval_from_precomputed(const std::tuple &basfunc, + const std::tuple &coeff_indices, + const std::tuple &xi) const { + static_assert(sizeof...(Basfunc) == sizeof...(Coeff_Indices) && + sizeof...(Basfunc) == sizeof...(Xi)); + return eval_from_precomputed_( + std::make_index_sequence{}, basfunc, + coeff_indices, xi); + } + /// @} + +private: + /// @brief Returns the knot indicies of boundary spline object's + /// knot spans containing `xi` + template + inline auto find_knot_indices_(std::index_sequence, + const std::tuple &xi) const { + return std::tuple(std::get(BoundaryCore::bdr_) + .find_knot_indices(std::get(xi))...); + } + +public: + /// @brief Returns the knot indicies of knot spans containing `xi` + template + inline auto find_knot_indices(const std::tuple &xi) const { + return find_knot_indices_( + std::make_index_sequence{}, xi); + } + +private: + /// @brief Returns the values of the boundary spline spline + /// object's basis functions in the points `xi` + /// @{ + template + inline auto eval_basfunc_(std::index_sequence, + const std::tuple &xi) const { + return std::tuple(std::get(BoundaryCore::bdr_) + .template eval_basfunc( + std::get(xi))...); + } + + template + inline auto eval_basfunc_(std::index_sequence, + const std::tuple &xi, + const std::tuple &indices) const { + return std::tuple(std::get(BoundaryCore::bdr_) + .template eval_basfunc( + std::get(xi), std::get(indices))...); + } + /// @} + +public: + /// @brief Returns the values of the spline objects' basis + /// functions in the points `xi` @{ + template + inline auto eval_basfunc(const std::tuple &xi) const { + return eval_basfunc_( + std::make_index_sequence{}, xi); + } + + template + inline auto eval_basfunc(const std::tuple &xi, + const std::tuple &indices) const { + static_assert(sizeof...(Xi) == sizeof...(Indices)); + return eval_basfunc_( + std::make_index_sequence{}, xi, indices); + } + /// @} + +private: + /// @brief Returns the indices of the boundary spline object's + /// coefficients corresponding to the knot indices `indices` + template + inline auto find_coeff_indices_(std::index_sequence, + const std::tuple &indices) const { + return std::tuple(std::get(BoundaryCore::bdr_) + .template find_coeff_indices( + std::get(indices))...); + } + +public: + /// @brief Returns the indices of the spline objects' + /// coefficients corresponding to the knot indices `indices` + template + inline auto find_coeff_indices(const std::tuple &indices) const { + return find_coeff_indices_( + std::make_index_sequence{}, indices); + } + +private: + /// @brief Returns the boundary spline object with uniformly + /// refined knot and coefficient vectors + template + inline auto &uniform_refine_(std::index_sequence, int numRefine = 1, + int dim = -1) { + (std::get(BoundaryCore::bdr_).uniform_refine(numRefine, dim), ...); + return *this; + } + +public: + /// @brief Returns the spline objects with uniformly refined + /// knot and coefficient vectors + inline auto &uniform_refine(int numRefine = 1, int dim = -1) { + if (dim == -1) { + if constexpr (BoundaryCore::spline_type::parDim() > 1) + uniform_refine_(std::make_index_sequence{}, + numRefine, dim); + } else if (dim == 0) { + if constexpr (BoundaryCore::nsides() == 2) { + // We do not refine the boundary of a curve + } else if constexpr (BoundaryCore::nsides() == 4) { + std::get(BoundaryCore::bdr_) + .uniform_refine(numRefine, 0); + std::get(BoundaryCore::bdr_) + .uniform_refine(numRefine, 0); + } else if constexpr (BoundaryCore::nsides() == 6) { + std::get(BoundaryCore::bdr_) + .uniform_refine(numRefine, 0); + std::get(BoundaryCore::bdr_) + .uniform_refine(numRefine, 0); + std::get(BoundaryCore::bdr_) + .uniform_refine(numRefine, 0); + std::get(BoundaryCore::bdr_) + .uniform_refine(numRefine, 0); + } else if constexpr (BoundaryCore::nsides() == 8) { + std::get(BoundaryCore::bdr_) + .uniform_refine(numRefine, 0); + std::get(BoundaryCore::bdr_) + .uniform_refine(numRefine, 0); + std::get(BoundaryCore::bdr_) + .uniform_refine(numRefine, 0); + std::get(BoundaryCore::bdr_) + .uniform_refine(numRefine, 0); + std::get(BoundaryCore::bdr_) + .uniform_refine(numRefine, 0); + std::get(BoundaryCore::bdr_) + .uniform_refine(numRefine, 0); + } else + throw std::runtime_error("Invalid dimension"); + } else if (dim == 1) { + if constexpr (BoundaryCore::nsides() == 4) { + std::get(BoundaryCore::bdr_) + .uniform_refine(numRefine, 0); + std::get(BoundaryCore::bdr_) + .uniform_refine(numRefine, 0); + } else if constexpr (BoundaryCore::nsides() == 6) { + std::get(BoundaryCore::bdr_) + .uniform_refine(numRefine, 0); + std::get(BoundaryCore::bdr_) + .uniform_refine(numRefine, 0); + std::get(BoundaryCore::bdr_) + .uniform_refine(numRefine, 1); + std::get(BoundaryCore::bdr_) + .uniform_refine(numRefine, 1); + + } else if constexpr (BoundaryCore::nsides() == 8) { + std::get(BoundaryCore::bdr_) + .uniform_refine(numRefine, 0); + std::get(BoundaryCore::bdr_) + .uniform_refine(numRefine, 0); + std::get(BoundaryCore::bdr_) + .uniform_refine(numRefine, 1); + std::get(BoundaryCore::bdr_) + .uniform_refine(numRefine, 1); + std::get(BoundaryCore::bdr_) + .uniform_refine(numRefine, 1); + std::get(BoundaryCore::bdr_) + .uniform_refine(numRefine, 1); + } else + throw std::runtime_error("Invalid dimension"); + } else if (dim == 2) { + if constexpr (BoundaryCore::nsides() == 6) { + std::get(BoundaryCore::bdr_) + .uniform_refine(numRefine, 1); + std::get(BoundaryCore::bdr_) + .uniform_refine(numRefine, 1); + std::get(BoundaryCore::bdr_) + .uniform_refine(numRefine, 1); + std::get(BoundaryCore::bdr_) + .uniform_refine(numRefine, 1); + } else if constexpr (BoundaryCore::nsides() == 8) { + std::get(BoundaryCore::bdr_) + .uniform_refine(numRefine, 1); + std::get(BoundaryCore::bdr_) + .uniform_refine(numRefine, 1); + std::get(BoundaryCore::bdr_) + .uniform_refine(numRefine, 1); + std::get(BoundaryCore::bdr_) + .uniform_refine(numRefine, 1); + std::get(BoundaryCore::bdr_) + .uniform_refine(numRefine, 2); + std::get(BoundaryCore::bdr_) + .uniform_refine(numRefine, 2); + } else + throw std::runtime_error("Invalid dimension"); + } else if (dim == 3) { + if constexpr (BoundaryCore::nsides() == 8) { + std::get(BoundaryCore::bdr_) + .uniform_refine(numRefine, 2); + std::get(BoundaryCore::bdr_) + .uniform_refine(numRefine, 2); + std::get(BoundaryCore::bdr_) + .uniform_refine(numRefine, 2); + std::get(BoundaryCore::bdr_) + .uniform_refine(numRefine, 2); + std::get(BoundaryCore::bdr_) + .uniform_refine(numRefine, 2); + std::get(BoundaryCore::bdr_) + .uniform_refine(numRefine, 2); + } else + throw std::runtime_error("Invalid dimension"); + } else + throw std::runtime_error("Invalid dimension"); + return *this; + } + +private: + /// @brief Writes the boundary spline object into a + /// torch::serialize::OutputArchive object + template + inline torch::serialize::OutputArchive & + write_(std::index_sequence, torch::serialize::OutputArchive &archive, + const std::string &key = "boundary") const { + (std::get(BoundaryCore::bdr_) + .write(archive, key + ".bdr[" + std::to_string(Is) + "]"), + ...); + return archive; + } + +public: + /// @brief Saves the boundary spline to file + inline void save(const std::string &filename, + const std::string &key = "boundary") const { + torch::serialize::OutputArchive archive; + write(archive, key).save_to(filename); + } + + /// @brief Writes the boundary spline object into a + /// torch::serialize::OutputArchive object + inline torch::serialize::OutputArchive & + write(torch::serialize::OutputArchive &archive, + const std::string &key = "boundary") const { + write_(std::make_index_sequence{}, archive, key); + return archive; + } + +private: + /// @brief Loads the function space object from a + /// torch::serialize::InputArchive object + template + inline torch::serialize::InputArchive & + read_(std::index_sequence, torch::serialize::InputArchive &archive, + const std::string &key = "boundary") { + (std::get(BoundaryCore::bdr_) + .read(archive, key + ".bdr[" + std::to_string(Is) + "]"), + ...); + return archive; + } + +public: + /// @brief Loads the boundary spline object from file + inline void load(const std::string &filename, + const std::string &key = "boundary") { + torch::serialize::InputArchive archive; + archive.load_from(filename); + read(archive, key); + } + + /// @brief Loads the boundary spline object from a + /// torch::serialize::InputArchive object + inline torch::serialize::InputArchive & + read(torch::serialize::InputArchive &archive, + const std::string &key = "boundary") { + read_(std::make_index_sequence{}, archive, key); + return archive; + } + + /// @brief Returns the boundary object as XML object + inline pugi::xml_document to_xml(int id = 0, std::string label = "", + int index = -1) const { + pugi::xml_document doc; + pugi::xml_node root = doc.append_child("xml"); + to_xml(root, id, label, index); + + return doc; + } + + /// @brief Returns the boundary object as XML node + inline pugi::xml_node &to_xml(pugi::xml_node &root, int id = 0, + std::string label = "", int index = -1) const { + // add Boundary node + pugi::xml_node bdr = root.append_child("Boundary"); + + if (id >= 0) + bdr.append_attribute("id") = id; + + if (index >= 0) + bdr.append_attribute("index") = index; + + if (!label.empty()) + bdr.append_attribute("label") = label.c_str(); + + int index_ = 0; + std::apply( + [&bdr, &index_](const auto &...bspline) { + (bspline.to_xml(bdr, -1, "", index_++), ...); + }, + BoundaryCore::bdr_); + + return root; + } + + /// @brief Updates the boundary object from XML object + inline BoundaryCommon &from_xml(const pugi::xml_document &doc, int id = 0, + std::string label = "", int index = -1) { + return from_xml(doc.child("xml"), id, label, index); + } + + /// @brief Updates the boundary object from XML node + inline BoundaryCommon &from_xml(const pugi::xml_node &root, int id = 0, + std::string label = "", int index = -1) { + + // Loop through all boundary nodes + for (pugi::xml_node bdr : root.children("Boundary")) { + + // Check for "Boundary" with given id, index, label + if ((id >= 0 ? bdr.attribute("id").as_int() == id : true) && + (index >= 0 ? bdr.attribute("index").as_int() == index : true) && + (!label.empty() ? bdr.attribute("label").value() == label : true)) { + + int index_ = 0; + std::apply( + [&bdr, &index_](auto &...bspline) { + (bspline.from_xml(bdr, -1, "", index_++), ...); + }, + BoundaryCore::bdr_); + + return *this; + } else + continue; // try next "Boundary" + } + + throw std::runtime_error("XML object does not provide geometry with given " + "id, index, and/or label"); + return *this; + } + +private: + /// @brief Returns true if both boundary spline objects are the + /// same + template + inline bool isequal_(std::index_sequence, + const BoundaryCommon &other) const { + return ( + (std::get(BoundaryCore::bdr_) == std::get(other.coeffs())) && + ...); + } + +public: + /// @brief Returns true if both boundary objects are the same + template + inline bool operator==(const BoundaryCommon &other) const { + return isequal_(std::make_index_sequence{}, other); + } + + /// @brief Returns true if both boundary objects are different + template + inline bool operator!=(const BoundaryCommon &other) const { + return !( + *this == + other); // Do not change this to (*this != other) is it does not work + } + +private: + /// @brief Returns true if both boundary spline objects are close up to the + /// given tolerances + template + inline bool + isclose_(std::index_sequence, + const BoundaryCommon &other, + typename BoundaryCore::spline_type::value_type rtol, + typename BoundaryCore::spline_type::value_type atol) const { + return ((std::get(BoundaryCore::bdr_) + .isclose(std::get(other.coeffs()))) && + ...); + } + +public: + /// @brief Returns true if both boundary objects are close up to the given + /// tolerances + template + inline bool + isclose(const BoundaryCommon &other, + typename BoundaryCore::spline_type::value_type rtol = + typename BoundaryCore::spline_type::value_type{1e-5}, + typename BoundaryCore::spline_type::value_type atol = + typename BoundaryCore::spline_type::value_type{1e-8}) const { + return isclose_(std::make_index_sequence{}, other, + rtol, atol); + } + +#define GENERATE_EXPR_MACRO(r, data, name) \ +private: \ + template \ + inline auto BOOST_PP_CAT(name, _)(std::index_sequence, \ + const std::tuple &xi) const { \ + return std::tuple( \ + std::get(BoundaryCore::bdr_) \ + .template name(std::get(xi))...); \ + } \ + \ + template \ + inline auto BOOST_PP_CAT(name, _)(std::index_sequence, \ + const std::tuple &xi, \ + const std::tuple &indices) \ + const { \ + return std::tuple(std::get(BoundaryCore::bdr_) \ + .template name( \ + std::get(xi), std::get(indices))...); \ + } \ + \ + template \ + inline auto BOOST_PP_CAT(name, _)( \ + std::index_sequence, const std::tuple &xi, \ + const std::tuple &indices, \ + const std::tuple &coeff_indices) const { \ + return std::tuple(std::get(BoundaryCore::bdr_) \ + .template name( \ + std::get(xi), std::get(indices), \ + std::get(coeff_indices))...); \ + } \ + \ +public: \ + template \ + inline auto name(const Args &...args) const { \ + return BOOST_PP_CAT(name, _)( \ + std::make_index_sequence{}, args...); \ + } + + /// @brief Auto-generated functions + /// @{ + BOOST_PP_SEQ_FOR_EACH(GENERATE_EXPR_MACRO, _, GENERATE_EXPR_SEQ) + /// @} +#undef GENERATE_EXPR_MACRO + +#define GENERATE_IEXPR_MACRO(r, data, name) \ +private: \ + template \ + inline auto BOOST_PP_CAT(name, _)(std::index_sequence, \ + const std::tuple &G, \ + const std::tuple &xi) const { \ + return std::tuple(std::get(BoundaryCore::bdr_) \ + .template name( \ + std::get(G), std::get(xi))...); \ + } \ + \ + template \ + inline auto BOOST_PP_CAT(name, _)( \ + std::index_sequence, const std::tuple &G, \ + const std::tuple &xi, const std::tuple &indices) \ + const { \ + return std::tuple( \ + std::get(BoundaryCore::bdr_) \ + .template name( \ + std::get(G), std::get(xi), std::get(indices))...); \ + } \ + \ + template \ + inline auto BOOST_PP_CAT(name, _)( \ + std::index_sequence, const std::tuple &G, \ + const std::tuple &xi, const std::tuple &indices, \ + const std::tuple &coeff_indices) const { \ + return std::tuple(std::get(BoundaryCore::bdr_) \ + .template name( \ + std::get(G), std::get(xi), \ + std::get(indices), \ + std::get(coeff_indices))...); \ + } \ + \ +public: \ + template \ + inline auto name(const Args &...args) const { \ + return BOOST_PP_CAT(name, _)( \ + std::make_index_sequence{}, args...); \ + } + + /// @brief Auto-generated functions + /// @{ + BOOST_PP_SEQ_FOR_EACH(GENERATE_IEXPR_MACRO, _, GENERATE_IEXPR_SEQ) + /// @} +#undef GENERATE_IEXPR_MACRO + + /// @brief Returns the `device` property of all splines + auto device() const noexcept { + return std::apply( + [](const auto &...bspline) { + return std::make_tuple(bspline.device()...); + }, + BoundaryCore::bdr_); + } + + /// @brief Returns the `device_index` property of all splines + auto device_index() const noexcept { + return std::apply( + [](const auto &...bspline) { + return std::make_tuple(bspline.device_index()...); + }, + BoundaryCore::bdr_); + } + + /// @brief Returns the `dtype` property of all splines + auto dtype() const noexcept { + return std::apply( + [](const auto &...bspline) { + return std::make_tuple(bspline.dtype()...); + }, + BoundaryCore::bdr_); + } + + /// @brief Returns the `layout` property of all splines + auto layout() const noexcept { + return std::apply( + [](const auto &...bspline) { + return std::make_tuple(bspline.layout()...); + }, + BoundaryCore::bdr_); + } + + /// @brief Returns the `requires_grad` property of all splines + auto requires_grad() const noexcept { + return std::apply( + [](const auto &...bspline) { + return std::make_tuple(bspline.requires_grad()...); + }, + BoundaryCore::bdr_); + } + + /// @brief Returns the `pinned_memory` property of all splines + auto pinned_memory() const noexcept { + return std::apply( + [](const auto &...bspline) { + return std::make_tuple(bspline.pinned_memory()...); + }, + BoundaryCore::bdr_); + } + + /// @brief Returns if the layout is sparse of all splines + auto is_sparse() const noexcept { + return std::apply( + [](const auto &...bspline) { + return std::make_tuple(bspline.is_sparse()...); + }, + BoundaryCore::bdr_); + } + + /// @brief Returns true if the B-spline is uniform of all splines + auto is_uniform() const noexcept { + return std::apply( + [](const auto &...bspline) { + return std::make_tuple(bspline.is_uniform()...); + }, + BoundaryCore::bdr_); + } + + /// @brief Returns true if the B-spline is non-uniform if all splines + auto is_nonuniform() const noexcept { + return std::apply( + [](const auto &...bspline) { + return std::make_tuple(bspline.is_nonuniform()...); + }, + BoundaryCore::bdr_); + } + + /// @brief Sets the boundary object's `requires_grad` property + BoundaryCommon &set_requires_grad(bool requires_grad) { + std::apply( + [requires_grad](const auto &...bspline) { + (bspline.set_requires_grad(requires_grad), ...); + }, + BoundaryCore::bdr_); + + return *this; + } + + /// @brief Returns a copy of the boundary object with settings from options + template inline auto to(Options options) const { + using boundary_type = BoundaryCommon>; + + return boundary_type(std::apply( + [&options](const auto &...bspline) { + return std::make_tuple(bspline.template to(options)...); + }, + BoundaryCore::bdr_)); + } + + /// @brief Returns a copy of the boundary object with settings from device + inline auto to(torch::Device device) const { + return BoundaryCommon(std::apply( + [&device](const auto &...bspline) { + return std::make_tuple(bspline.to(device)...); + }, + BoundaryCore::bdr_)); + } + + /// @brief Returns a copy of the boundary object with real_t type + template inline auto to() const { + using boundary_type = BoundaryCommon()), + BoundaryCore::spline_type::parDim()>>; + + return boundary_type(std::apply( + [](const auto &...bspline) { + return std::make_tuple(bspline.template to()...); + }, + BoundaryCore::bdr_)); + } +}; + +/// @brief Boundary +template +using Boundary = BoundaryCommon>; + +/// @brief Print (as string) a Boundary object +template +inline std::ostream &operator<<(std::ostream &os, const Boundary &obj) { + obj.pretty_print(os); + return os; +} + +} // namespace iganet diff --git a/include/bspline.hpp b/include/bspline.hpp index c0d2ab82..640855e9 100644 --- a/include/bspline.hpp +++ b/include/bspline.hpp @@ -1,8077 +1,8096 @@ -/** - @file include/bspline.hpp - - @brief Multivariate B-splines - - @author Matthias Moller - - @copyright This file is part of the IgANet project - - This Source Code Form is subject to the terms of the Mozilla Public - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at http://mozilla.org/MPL/2.0/. -*/ - -#pragma once - -#include -#include -#include -#include -#include - -#include - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#if defined(__CUDACC__) -#include -#endif - -#if defined(__HIPCC__) -#include -#endif - -#if defined(__CUDACC__) || defined(__HIPCC__) -namespace iganet { -namespace cuda { -/** - @brief Compute Greville abscissae -*/ -template -__global__ void -greville_kernel(torch::PackedTensorAccessor64 greville, - const torch::PackedTensorAccessor64 knots, - int64_t ncoeffs, short_t degree, bool interior) { - for (int64_t k = blockIdx.x * blockDim.x + threadIdx.x; - k < ncoeffs - (interior ? 2 : 0); k += blockDim.x * gridDim.x) { - for (short_t l = 1; l <= degree; ++l) - greville[k] += knots[k + (interior ? 1 : 0) + l]; - greville[k] /= real_t(degree); - } -} -} // namespace cuda -} // namespace iganet -#endif - -/// @brief Sequence of expression (parametric coordinates) -/// -/// For each item in this sequence corresponding expressions will be -/// generated for function spaces, boundary spaces, etc. -#define GENERATE_EXPR_SEQ (curl)(div)(grad)(hess)(jac)(lapl) - -/// @brief Sequence of expression (physical coordinates) -/// -/// For each item in this sequence corresponding expressions will be -/// generated for function spaces, boundary spaces, etc. -#define GENERATE_IEXPR_SEQ (icurl)(idiv)(igrad)(ihess)(ijac)(ilapl) - -namespace iganet { -using namespace literals; -using utils::operator+; - -// clang-format off -/// @brief Enumerator for specifying the initialization of B-spline coefficients -enum class init : short_t { - zeros = 0, /*!< set coefficient values to zero */ - ones = 1, /*!< set coefficient values to one */ - linear = - 2, /*!< set coefficient values to \f$0,1,\dots \#\text{coeffs}-1\f$ */ - random = 3, /*!< set coefficient values to random numbers */ - greville = 4, /*!< set coefficient values to the Greville abscissae */ - linspace = 5 /*!< set coefficient values to \f$0,1,\dots\f$ pattern (mostly - for testing) */ -}; -// clang-format on - -/// @brief Enumerator for specifying the derivative of B-spline evaluation -/// -/// **Examples** -/// -/// * 3d Laplace operator `dx^2+dy^2+dz^2` -/// * 2d convection operator with time derivative dt+dx+dy` -enum class deriv : short_t { - func = 0, /*!< function value */ - - dx = 1, /*!< first derivative in x-direction */ - dy = 10, /*!< first derivative in y-direction */ - dz = 100, /*!< first derivative in z-direction */ - dt = 1000, /*!< first derivative in t-direction */ -}; - -/// @brief Adds two enumerators for specifying the derivative of B-spline -/// evaluation -/// -/// @param[in] lhs First derivative enumerator -/// -/// @param[in] rhs Second derivative enumerator -/// -/// @result Sum of the two enumerators -inline constexpr auto operator+(deriv lhs, deriv rhs) { - return deriv(static_cast(lhs) + static_cast(rhs)); -} - -/// @brief Raises an enumerator for specifying the derivative of B-spline -/// evaluation to a higher exponent -/// -/// @param[in] lhs Derivative enumerator -/// -/// @param[in] rhs Exponent -/// -/// @result Derivative enumerator raised to the exponent -inline constexpr auto operator^(deriv lhs, short_t rhs) { - return deriv(static_cast(lhs) * static_cast(rhs)); -} - -/// @brief Tensor-product uniform B-spline (core functionality) -/// -/// This class implements the core functionality of all B-spline -/// classes and serves as base class for (non-)uniform B-splines. -/// -/// Mathematically, this class defines a mapping -/// -/// \f[ -/// \mathbf{f}:\hat\Omega \mapsto \Omega -/// \f] -/// -/// from the \f$d_\text{par}\f$-dimensional *parametric space* -/// \f$\hat\Omega=[0,1]^{d_\text{par}}\f$ to the -/// \f$d_\text{geo}\f$-dimensional *geometric space* -/// \f$\Omega\subset\mathbb{R}^{d_\text{geo}}\f$. -/// -/// This mapping is defined by tensor-product B-spline basis -/// functions -/// -/// \f[ -/// B_I(\boldsymbol{\xi}) = \bigotimes_{d=1}^{d_\text{par}} B_{i_d,p_d}(\xi_d) -/// \f] -/// -/// and the control points -/// -/// \f[ -/// \mathbf{c}_I = \mathbf{c}_{i_1,i_2,\dots, i_{d_\text{par}}} \in -/// \mathbb{R}^{d_\text{geo}}. -/// \f] -/// -/// Here, \f$i_d\f$ are the local numbers of the univariate -/// B-splines \f$\left(B_{i_d,p_d}\right)_{i_d=1}^{n_d}\f$ in the -/// \f$d\f$-th parametric dimension, \f$p_d\f$ is the respective -/// *degree*, and \f$n_d\f$ is the number of univariate B-splines in -/// the \f$d\f$-th direction. Moreover, \f$0\le \xi_{i_d}\le 1\f$ is -/// the parametric value at which the B-spline is evaluated. The -/// multivariate B-spline function is defined as follows -/// -/// \f[ -/// \mathbf{f}(\boldsymbol{\xi}) = \sum_{I=1}^N B_I(\boldsymbol{\xi}) -/// \mathbf{c}_I \f] -/// -/// Here and below we adopt the vector notation \f$\boldsymbol{\xi} -/// = \left(\xi_1,\xi_2,\dots,\xi_{d_\text{par}}\right)^\top\f$ and -/// combine multiple local indices -/// \f$i_1,i_2,\dots,i_{d_\text{par}}\f$ of univariate B-spline -/// basis functions into the global index \f$1\le I \le N\f$ with -/// \f$N=n_1\cdot n_2\cdot\dots\cdot n_{d_\text{par}}\f$ denoting -/// the total number of multivariate B-splines. -/// -/// This class implements B-spline functions and their derivatives -/// for 1, 2, 3, and 4 parametric dimensions. The univariate -/// B-splines are uniquely determined by their knot vectors -/// -/// \f[ -/// \left(t_{i_d}\right)_{i_d=1}^{n_d+p_d+1} -/// \f] -/// -/// with \f$0\le t_{i_d}\le 1\f$ and \f$t_{i_d}\le t_{i_d+1}\f$ for -/// all \f$i_d\f$, that is, the knot vectors are given by a -/// non-decreasing sequence of values in the interval \f$[0,1]\f$ -/// with the possibility that knot values are repeated. -/// -/// This class implements the evaluation of B-splines and their -/// derivatives as explained in Chapters 2 and 3 from \cite Lyche:2011. -/// -/// @note C++ uses 0-based indexing so that all of the above -/// formulas need to be shifted by -1. Moreover, all vectors, -/// matrices, and tensors are implemented as `torch::Tensor` -/// objects and hence adopt Torch's local-to-global mapping. It is -/// therefore imperative to always use Torch's indexing -/// functionality to extract sub-tensors. -template -class UniformBSplineCore : public utils::Serializable { - /// @brief Enable access to private members - template friend class BSplineCommon; - -protected: - /// @brief Dimension of the parametric space - /// \f$\hat\Omega=[0,1]^{d_\text{par}}\f$ - static constexpr const short_t parDim_ = sizeof...(Degrees); - - /// @brief Dimension of the geometric space - /// \f$\Omega\subset\mathbb{R}^{d_\text{geo}}\f$ - static constexpr const short_t geoDim_ = GeoDim; - - /// @brief Array storing the degrees - /// \f$\left(p_d\right)_{d=1}^{d_\text{par}}\f$ - static constexpr const std::array degrees_ = {Degrees...}; - - /// @brief Array storing the sizes of the knot vectors - /// \f$\left(n_d+p_d+1\right)_{d=1}^{d_\text{par}}\f$ - std::array nknots_; - - /// @brief Array storing the sizes of the coefficients of the - /// control net \f$\left(n_d\right)_{d=1}^{d_\text{par}}\f$ - std::array ncoeffs_; - - /// @brief Array storing the sizes of the coefficients of the - /// control net \f$\left(n_d\right)_{d=1}^{d_\text{par}}\f$ in - /// reverse order (needed for coeffs_view) - std::array ncoeffs_reverse_; - - /// @brief Array storing the knot vectors - /// \f$\left(\left(t_{i_d}\right)_{i_d=1}^{n_d+p_d+1}\right)_{d=1}^{d_\text{par}}\f$ - utils::TensorArray knots_; - - /// @brief Array storing the coefficients of the control net - /// \f$\left(\mathbf{c}_{i_d}\right)_{i_d=1}^{n_d}\f$, - /// \f$\mathbf{c}_{i_d}\in\mathbb{R}^{d_\text{geo}}\f$ - - utils::TensorArray coeffs_; - - /// @brief Options - Options options_; - -public: - /// @brief Value type - using value_type = real_t; - - /// @brief Deduces the type of the template template parameter `T` - /// when exposed to the class template parameters `real_t` and - /// `GeoDim`, and the `Degrees` parameter pack. The optional - /// template parameter `degree_elevate` can be used to - /// (de-)elevate the degrees by an additive constant - template