diff --git a/src/mesh/CMakeLists.txt b/src/mesh/CMakeLists.txt index afd3cb28a354332c35b0ca2757499c1e8da47d09..113e2527dbe4c3896979c3bd7f68a00369e62029 100644 --- a/src/mesh/CMakeLists.txt +++ b/src/mesh/CMakeLists.txt @@ -24,6 +24,7 @@ add_library( MeshDataManager.cpp MeshEdgeBoundary.cpp MeshFaceBoundary.cpp + MeshFlatEdgeBoundary.cpp MeshFlatFaceBoundary.cpp MeshFlatNodeBoundary.cpp MeshRelaxer.cpp diff --git a/src/mesh/MeshFlatEdgeBoundary.cpp b/src/mesh/MeshFlatEdgeBoundary.cpp new file mode 100644 index 0000000000000000000000000000000000000000..d4c1ec155a0e8c4d863facbf6280d71265944933 --- /dev/null +++ b/src/mesh/MeshFlatEdgeBoundary.cpp @@ -0,0 +1,20 @@ +#include <mesh/MeshFlatEdgeBoundary.hpp> + +#include <mesh/Connectivity.hpp> +#include <mesh/Mesh.hpp> +#include <mesh/MeshFlatNodeBoundary.hpp> + +template <size_t Dimension> +MeshFlatEdgeBoundary<Dimension> +getMeshFlatEdgeBoundary(const Mesh<Connectivity<Dimension>>& mesh, const IBoundaryDescriptor& boundary_descriptor) +{ + MeshEdgeBoundary<Dimension> mesh_edge_boundary = getMeshEdgeBoundary(mesh, boundary_descriptor); + MeshFlatNodeBoundary<Dimension> mesh_flat_node_boundary = getMeshFlatNodeBoundary(mesh, boundary_descriptor); + + return MeshFlatEdgeBoundary<Dimension>{mesh, mesh_edge_boundary.refEdgeList(), + mesh_flat_node_boundary.outgoingNormal()}; +} + +template MeshFlatEdgeBoundary<1> getMeshFlatEdgeBoundary(const Mesh<Connectivity<1>>&, const IBoundaryDescriptor&); +template MeshFlatEdgeBoundary<2> getMeshFlatEdgeBoundary(const Mesh<Connectivity<2>>&, const IBoundaryDescriptor&); +template MeshFlatEdgeBoundary<3> getMeshFlatEdgeBoundary(const Mesh<Connectivity<3>>&, const IBoundaryDescriptor&); diff --git a/src/mesh/MeshFlatEdgeBoundary.hpp b/src/mesh/MeshFlatEdgeBoundary.hpp new file mode 100644 index 0000000000000000000000000000000000000000..a104d1413a7319f08b74bf1ef103a5157b803f18 --- /dev/null +++ b/src/mesh/MeshFlatEdgeBoundary.hpp @@ -0,0 +1,46 @@ +#ifndef MESH_FLAT_EDGE_BOUNDARY_HPP +#define MESH_FLAT_EDGE_BOUNDARY_HPP + +#include <mesh/MeshEdgeBoundary.hpp> + +template <size_t Dimension> +class MeshFlatEdgeBoundary final : public MeshEdgeBoundary<Dimension> // clazy:exclude=copyable-polymorphic +{ + public: + using Rd = TinyVector<Dimension, double>; + + private: + const Rd m_outgoing_normal; + + public: + const Rd& + outgoingNormal() const + { + return m_outgoing_normal; + } + + MeshFlatEdgeBoundary& operator=(const MeshFlatEdgeBoundary&) = default; + MeshFlatEdgeBoundary& operator=(MeshFlatEdgeBoundary&&) = default; + + template <size_t MeshDimension> + friend MeshFlatEdgeBoundary<MeshDimension> getMeshFlatEdgeBoundary(const Mesh<Connectivity<MeshDimension>>& mesh, + const IBoundaryDescriptor& boundary_descriptor); + + private: + template <typename MeshType> + MeshFlatEdgeBoundary(const MeshType& mesh, const RefEdgeList& ref_edge_list, const Rd& outgoing_normal) + : MeshEdgeBoundary<Dimension>(mesh, ref_edge_list), m_outgoing_normal(outgoing_normal) + {} + + public: + MeshFlatEdgeBoundary() = default; + MeshFlatEdgeBoundary(const MeshFlatEdgeBoundary&) = default; + MeshFlatEdgeBoundary(MeshFlatEdgeBoundary&&) = default; + ~MeshFlatEdgeBoundary() = default; +}; + +template <size_t Dimension> +MeshFlatEdgeBoundary<Dimension> getMeshFlatEdgeBoundary(const Mesh<Connectivity<Dimension>>& mesh, + const IBoundaryDescriptor& boundary_descriptor); + +#endif // MESH_FLAT_EDGE_BOUNDARY_HPP diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index dfd6616c3d5f0a56b363b8e246c3e771566d5343..0b2849df2e0da1394b80800c150ded28b4cf1ada 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -177,6 +177,7 @@ add_executable (mpi_unit_tests test_ItemValueUtils.cpp test_MeshEdgeBoundary.cpp test_MeshFaceBoundary.cpp + test_MeshFlatEdgeBoundary.cpp test_MeshFlatFaceBoundary.cpp test_MeshFlatNodeBoundary.cpp test_MeshLineFaceBoundary.cpp diff --git a/tests/test_MeshFlatEdgeBoundary.cpp b/tests/test_MeshFlatEdgeBoundary.cpp new file mode 100644 index 0000000000000000000000000000000000000000..aebe2e01eafaacfa1d387ab3d8792f2edd4abde3 --- /dev/null +++ b/tests/test_MeshFlatEdgeBoundary.cpp @@ -0,0 +1,1471 @@ +#include <catch2/catch_test_macros.hpp> +#include <catch2/matchers/catch_matchers_all.hpp> + +#include <MeshDataBaseForTests.hpp> + +#include <algebra/TinyMatrix.hpp> +#include <mesh/Connectivity.hpp> +#include <mesh/Mesh.hpp> +#include <mesh/MeshFlatEdgeBoundary.hpp> +#include <mesh/NamedBoundaryDescriptor.hpp> +#include <mesh/NumberedBoundaryDescriptor.hpp> + +// clazy:excludeall=non-pod-global-static + +TEST_CASE("MeshFlatEdgeBoundary", "[mesh]") +{ + auto is_same = [](const auto& a, const auto& b) -> bool { + if (a.size() > 0 and b.size() > 0) { + return (a[0] == b[0]); + } else { + return (a.size() == b.size()); + } + }; + + auto get_edge_list_from_tag = [](const size_t tag, const auto& connectivity) -> Array<const EdgeId> { + for (size_t i = 0; i < connectivity.template numberOfRefItemList<ItemType::edge>(); ++i) { + const auto& ref_edge_list = connectivity.template refItemList<ItemType::edge>(i); + const RefId ref_id = ref_edge_list.refId(); + if (ref_id.tagNumber() == tag) { + return ref_edge_list.list(); + } + } + return {}; + }; + + auto get_edge_list_from_name = [](const std::string& name, const auto& connectivity) -> Array<const EdgeId> { + for (size_t i = 0; i < connectivity.template numberOfRefItemList<ItemType::edge>(); ++i) { + const auto& ref_edge_list = connectivity.template refItemList<ItemType::edge>(i); + const RefId ref_id = ref_edge_list.refId(); + if (ref_id.tagName() == name) { + return ref_edge_list.list(); + } + } + return {}; + }; + + SECTION("aligned axis") + { + SECTION("1D") + { + static constexpr size_t Dimension = 1; + + using ConnectivityType = Connectivity<Dimension>; + using MeshType = Mesh<ConnectivityType>; + + using R1 = TinyVector<1>; + + SECTION("cartesian 1d") + { + std::shared_ptr p_mesh = MeshDataBaseForTests::get().cartesian1DMesh(); + const MeshType& mesh = *p_mesh; + + const ConnectivityType& connectivity = mesh.connectivity(); + + { + const std::set<size_t> tag_set = {0, 1}; + + for (auto tag : tag_set) { + NumberedBoundaryDescriptor numbered_boundary_descriptor(tag); + const auto& edge_boundary = getMeshFlatEdgeBoundary(mesh, numbered_boundary_descriptor); + + auto edge_list = get_edge_list_from_tag(tag, connectivity); + REQUIRE(is_same(edge_boundary.edgeList(), edge_list)); + + R1 normal = zero; + + switch (tag) { + case 0: { + normal = R1{-1}; + break; + } + case 1: { + normal = R1{1}; + break; + } + default: { + FAIL("unexpected tag number"); + } + } + + REQUIRE(l2Norm(edge_boundary.outgoingNormal() - normal) == Catch::Approx(0).margin(1E-13)); + } + } + + { + const std::set<std::string> name_set = {"XMIN", "XMAX"}; + + for (auto name : name_set) { + NamedBoundaryDescriptor named_boundary_descriptor(name); + const auto& edge_boundary = getMeshFlatEdgeBoundary(mesh, named_boundary_descriptor); + + auto edge_list = get_edge_list_from_name(name, connectivity); + + REQUIRE(is_same(edge_boundary.edgeList(), edge_list)); + + R1 normal = zero; + + if (name == "XMIN") { + normal = R1{-1}; + } else if (name == "XMAX") { + normal = R1{1}; + } else { + FAIL("unexpected name: " + name); + } + + REQUIRE(l2Norm(edge_boundary.outgoingNormal() - normal) == Catch::Approx(0).margin(1E-13)); + } + } + } + + SECTION("unordered 1d") + { + std::shared_ptr p_mesh = MeshDataBaseForTests::get().unordered1DMesh(); + const MeshType& mesh = *p_mesh; + + const ConnectivityType& connectivity = mesh.connectivity(); + + { + const std::set<size_t> tag_set = {1, 2}; + + for (auto tag : tag_set) { + NumberedBoundaryDescriptor numbered_boundary_descriptor(tag); + const auto& edge_boundary = getMeshFlatEdgeBoundary(mesh, numbered_boundary_descriptor); + + auto edge_list = get_edge_list_from_tag(tag, connectivity); + REQUIRE(is_same(edge_boundary.edgeList(), edge_list)); + + R1 normal = zero; + + switch (tag) { + case 1: { + normal = R1{-1}; + break; + } + case 2: { + normal = R1{1}; + break; + } + default: { + FAIL("unexpected tag number"); + } + } + + REQUIRE(l2Norm(edge_boundary.outgoingNormal() - normal) == Catch::Approx(0).margin(1E-13)); + } + } + + { + const std::set<std::string> name_set = {"XMIN", "XMAX"}; + + for (auto name : name_set) { + NamedBoundaryDescriptor named_boundary_descriptor(name); + const auto& edge_boundary = getMeshFlatEdgeBoundary(mesh, named_boundary_descriptor); + + auto edge_list = get_edge_list_from_name(name, connectivity); + + REQUIRE(is_same(edge_boundary.edgeList(), edge_list)); + + R1 normal = zero; + + if (name == "XMIN") { + normal = R1{-1}; + } else if (name == "XMAX") { + normal = R1{1}; + } else { + FAIL("unexpected name: " + name); + } + + REQUIRE(l2Norm(edge_boundary.outgoingNormal() - normal) == Catch::Approx(0).margin(1E-13)); + } + } + } + } + + SECTION("2D") + { + static constexpr size_t Dimension = 2; + + using ConnectivityType = Connectivity<Dimension>; + using MeshType = Mesh<ConnectivityType>; + + using R2 = TinyVector<2>; + + SECTION("cartesian 2d") + { + std::shared_ptr p_mesh = MeshDataBaseForTests::get().cartesian2DMesh(); + const MeshType& mesh = *p_mesh; + + const ConnectivityType& connectivity = mesh.connectivity(); + + { + const std::set<size_t> tag_set = {0, 1, 2, 3}; + + for (auto tag : tag_set) { + NumberedBoundaryDescriptor numbered_boundary_descriptor(tag); + const auto& edge_boundary = getMeshFlatEdgeBoundary(mesh, numbered_boundary_descriptor); + + auto edge_list = get_edge_list_from_tag(tag, connectivity); + REQUIRE(is_same(edge_boundary.edgeList(), edge_list)); + + R2 normal = zero; + + switch (tag) { + case 0: { + normal = R2{-1, 0}; + break; + } + case 1: { + normal = R2{1, 0}; + break; + } + case 2: { + normal = R2{0, -1}; + break; + } + case 3: { + normal = R2{0, 1}; + break; + } + default: { + FAIL("unexpected tag number"); + } + } + + REQUIRE(l2Norm(edge_boundary.outgoingNormal() - normal) == Catch::Approx(0).margin(1E-13)); + } + + { + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NumberedBoundaryDescriptor(10)), + "error: cannot find edge list with name \"10\""); + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NumberedBoundaryDescriptor(11)), + "error: cannot find edge list with name \"11\""); + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NumberedBoundaryDescriptor(12)), + "error: cannot find edge list with name \"12\""); + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NumberedBoundaryDescriptor(13)), + "error: cannot find edge list with name \"13\""); + } + } + + { + const std::set<std::string> name_set = {"XMIN", "XMAX", "YMIN", "YMAX"}; + + for (auto name : name_set) { + NamedBoundaryDescriptor named_boundary_descriptor(name); + const auto& edge_boundary = getMeshFlatEdgeBoundary(mesh, named_boundary_descriptor); + + auto edge_list = get_edge_list_from_name(name, connectivity); + REQUIRE(is_same(edge_boundary.edgeList(), edge_list)); + + R2 normal = zero; + + if (name == "XMIN") { + normal = R2{-1, 0}; + } else if (name == "XMAX") { + normal = R2{1, 0}; + } else if (name == "YMIN") { + normal = R2{0, -1}; + } else if (name == "YMAX") { + normal = R2{0, 1}; + } else { + FAIL("unexpected name: " + name); + } + + REQUIRE(l2Norm(edge_boundary.outgoingNormal() - normal) == Catch::Approx(0).margin(1E-13)); + } + + { + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NamedBoundaryDescriptor("XMINYMIN")), + "error: cannot find edge list with name \"XMINYMIN\""); + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NamedBoundaryDescriptor("XMINYMAX")), + "error: cannot find edge list with name \"XMINYMAX\""); + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NamedBoundaryDescriptor("XMAXYMIN")), + "error: cannot find edge list with name \"XMAXYMIN\""); + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NamedBoundaryDescriptor("XMAXYMAX")), + "error: cannot find edge list with name \"XMAXYMAX\""); + } + } + } + + SECTION("hybrid 2d") + { + std::shared_ptr p_mesh = MeshDataBaseForTests::get().hybrid2DMesh(); + const MeshType& mesh = *p_mesh; + + const ConnectivityType& connectivity = mesh.connectivity(); + + { + const std::set<size_t> tag_set = {1, 2, 3, 4}; + + for (auto tag : tag_set) { + NumberedBoundaryDescriptor numbered_boundary_descriptor(tag); + const auto& edge_boundary = getMeshFlatEdgeBoundary(mesh, numbered_boundary_descriptor); + + auto edge_list = get_edge_list_from_tag(tag, connectivity); + REQUIRE(is_same(edge_boundary.edgeList(), edge_list)); + + R2 normal = zero; + + switch (tag) { + case 1: { + normal = R2{-1, 0}; + break; + } + case 2: { + normal = R2{1, 0}; + break; + } + case 3: { + normal = R2{0, 1}; + break; + } + case 4: { + normal = R2{0, -1}; + break; + } + default: { + FAIL("unexpected tag number"); + } + } + + REQUIRE(l2Norm(edge_boundary.outgoingNormal() - normal) == Catch::Approx(0).margin(1E-13)); + } + + { + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NumberedBoundaryDescriptor(8)), + "error: cannot find edge list with name \"8\""); + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NumberedBoundaryDescriptor(9)), + "error: cannot find edge list with name \"9\""); + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NumberedBoundaryDescriptor(10)), + "error: cannot find edge list with name \"10\""); + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NumberedBoundaryDescriptor(11)), + "error: cannot find edge list with name \"11\""); + } + } + + { + const std::set<std::string> name_set = {"XMIN", "XMAX", "YMIN", "YMAX"}; + + for (auto name : name_set) { + NamedBoundaryDescriptor named_boundary_descriptor(name); + const auto& edge_boundary = getMeshFlatEdgeBoundary(mesh, named_boundary_descriptor); + + auto edge_list = get_edge_list_from_name(name, connectivity); + + R2 normal = zero; + + REQUIRE(is_same(edge_boundary.edgeList(), edge_list)); + + if (name == "XMIN") { + normal = R2{-1, 0}; + } else if (name == "XMAX") { + normal = R2{1, 0}; + } else if (name == "YMIN") { + normal = R2{0, -1}; + } else if (name == "YMAX") { + normal = R2{0, 1}; + } else { + FAIL("unexpected name: " + name); + } + + REQUIRE(l2Norm(edge_boundary.outgoingNormal() - normal) == Catch::Approx(0).margin(1E-13)); + } + + { + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NamedBoundaryDescriptor("XMINYMIN")), + "error: cannot find edge list with name \"XMINYMIN\""); + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NamedBoundaryDescriptor("XMINYMAX")), + "error: cannot find edge list with name \"XMINYMAX\""); + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NamedBoundaryDescriptor("XMAXYMIN")), + "error: cannot find edge list with name \"XMAXYMIN\""); + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NamedBoundaryDescriptor("XMAXYMAX")), + "error: cannot find edge list with name \"XMAXYMAX\""); + } + } + } + } + + SECTION("3D") + { + static constexpr size_t Dimension = 3; + + using ConnectivityType = Connectivity<Dimension>; + using MeshType = Mesh<ConnectivityType>; + + using R3 = TinyVector<3>; + + SECTION("cartesian 3d") + { + std::shared_ptr p_mesh = MeshDataBaseForTests::get().cartesian3DMesh(); + const MeshType& mesh = *p_mesh; + + const ConnectivityType& connectivity = mesh.connectivity(); + + { + const std::set<size_t> tag_set = {0, 1, 2, 3, 4, 5}; + + for (auto tag : tag_set) { + NumberedBoundaryDescriptor numbered_boundary_descriptor(tag); + const auto& edge_boundary = getMeshFlatEdgeBoundary(mesh, numbered_boundary_descriptor); + + auto edge_list = get_edge_list_from_tag(tag, connectivity); + REQUIRE(is_same(edge_boundary.edgeList(), edge_list)); + + R3 normal = zero; + + switch (tag) { + case 0: { + normal = R3{-1, 0, 0}; + break; + } + case 1: { + normal = R3{1, 0, 0}; + break; + } + case 2: { + normal = R3{0, -1, 0}; + break; + } + case 3: { + normal = R3{0, 1, 0}; + break; + } + case 4: { + normal = R3{0, 0, -1}; + break; + } + case 5: { + normal = R3{0, 0, 1}; + break; + } + default: { + FAIL("unexpected tag number"); + } + } + + REQUIRE(l2Norm(edge_boundary.outgoingNormal() - normal) == Catch::Approx(0).margin(1E-13)); + } + + { + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NumberedBoundaryDescriptor(20)), + "error: cannot find surface with name \"20\""); + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NumberedBoundaryDescriptor(21)), + "error: cannot find surface with name \"21\""); + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NumberedBoundaryDescriptor(22)), + "error: cannot find surface with name \"22\""); + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NumberedBoundaryDescriptor(23)), + "error: cannot find surface with name \"23\""); + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NumberedBoundaryDescriptor(24)), + "error: cannot find surface with name \"24\""); + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NumberedBoundaryDescriptor(25)), + "error: cannot find surface with name \"25\""); + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NumberedBoundaryDescriptor(26)), + "error: cannot find surface with name \"26\""); + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NumberedBoundaryDescriptor(27)), + "error: cannot find surface with name \"27\""); + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NumberedBoundaryDescriptor(28)), + "error: cannot find surface with name \"28\""); + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NumberedBoundaryDescriptor(29)), + "error: cannot find surface with name \"29\""); + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NumberedBoundaryDescriptor(30)), + "error: cannot find surface with name \"30\""); + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NumberedBoundaryDescriptor(31)), + "error: cannot find surface with name \"31\""); + + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NumberedBoundaryDescriptor(10)), + "error: cannot find edge list with name \"10\""); + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NumberedBoundaryDescriptor(11)), + "error: cannot find edge list with name \"11\""); + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NumberedBoundaryDescriptor(12)), + "error: cannot find edge list with name \"12\""); + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NumberedBoundaryDescriptor(13)), + "error: cannot find edge list with name \"13\""); + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NumberedBoundaryDescriptor(14)), + "error: cannot find edge list with name \"14\""); + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NumberedBoundaryDescriptor(15)), + "error: cannot find edge list with name \"15\""); + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NumberedBoundaryDescriptor(16)), + "error: cannot find edge list with name \"16\""); + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NumberedBoundaryDescriptor(17)), + "error: cannot find edge list with name \"17\""); + } + } + + { + const std::set<std::string> name_set = {"XMIN", "XMAX", "YMIN", "YMAX", "ZMIN", "ZMAX"}; + + for (auto name : name_set) { + NamedBoundaryDescriptor named_boundary_descriptor(name); + const auto& edge_boundary = getMeshFlatEdgeBoundary(mesh, named_boundary_descriptor); + + auto edge_list = get_edge_list_from_name(name, connectivity); + + REQUIRE(is_same(edge_boundary.edgeList(), edge_list)); + + R3 normal = zero; + + if (name == "XMIN") { + normal = R3{-1, 0, 0}; + } else if (name == "XMAX") { + normal = R3{1, 0, 0}; + } else if (name == "YMIN") { + normal = R3{0, -1, 0}; + } else if (name == "YMAX") { + normal = R3{0, 1, 0}; + } else if (name == "ZMIN") { + normal = R3{0, 0, -1}; + } else if (name == "ZMAX") { + normal = R3{0, 0, 1}; + } else { + FAIL("unexpected name: " + name); + } + + REQUIRE(l2Norm(edge_boundary.outgoingNormal() - normal) == Catch::Approx(0).margin(1E-13)); + } + + { + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NamedBoundaryDescriptor("XMINYMIN")), + "error: cannot find surface with name \"XMINYMIN\""); + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NamedBoundaryDescriptor("XMINYMAX")), + "error: cannot find surface with name \"XMINYMAX\""); + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NamedBoundaryDescriptor("XMAXYMAX")), + "error: cannot find surface with name \"XMAXYMAX\""); + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NamedBoundaryDescriptor("XMINYMAX")), + "error: cannot find surface with name \"XMINYMAX\""); + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NamedBoundaryDescriptor("XMINZMIN")), + "error: cannot find surface with name \"XMINZMIN\""); + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NamedBoundaryDescriptor("XMINZMAX")), + "error: cannot find surface with name \"XMINZMAX\""); + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NamedBoundaryDescriptor("XMAXZMAX")), + "error: cannot find surface with name \"XMAXZMAX\""); + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NamedBoundaryDescriptor("XMINZMAX")), + "error: cannot find surface with name \"XMINZMAX\""); + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NamedBoundaryDescriptor("YMINZMIN")), + "error: cannot find surface with name \"YMINZMIN\""); + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NamedBoundaryDescriptor("YMINZMAX")), + "error: cannot find surface with name \"YMINZMAX\""); + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NamedBoundaryDescriptor("YMAXZMAX")), + "error: cannot find surface with name \"YMAXZMAX\""); + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NamedBoundaryDescriptor("YMINZMAX")), + "error: cannot find surface with name \"YMINZMAX\""); + + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NamedBoundaryDescriptor("XMINYMINZMIN")), + "error: cannot find edge list with name \"XMINYMINZMIN\""); + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NamedBoundaryDescriptor("XMAXYMINZMIN")), + "error: cannot find edge list with name \"XMAXYMINZMIN\""); + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NamedBoundaryDescriptor("XMAXYMAXZMIN")), + "error: cannot find edge list with name \"XMAXYMAXZMIN\""); + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NamedBoundaryDescriptor("XMINYMAXZMIN")), + "error: cannot find edge list with name \"XMINYMAXZMIN\""); + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NamedBoundaryDescriptor("XMINYMINZMAX")), + "error: cannot find edge list with name \"XMINYMINZMAX\""); + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NamedBoundaryDescriptor("XMAXYMINZMAX")), + "error: cannot find edge list with name \"XMAXYMINZMAX\""); + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NamedBoundaryDescriptor("XMAXYMAXZMAX")), + "error: cannot find edge list with name \"XMAXYMAXZMAX\""); + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NamedBoundaryDescriptor("XMINYMAXZMAX")), + "error: cannot find edge list with name \"XMINYMAXZMAX\""); + } + } + } + + SECTION("hybrid 3d") + { + std::shared_ptr p_mesh = MeshDataBaseForTests::get().hybrid3DMesh(); + const MeshType& mesh = *p_mesh; + + const ConnectivityType& connectivity = mesh.connectivity(); + + { + const std::set<size_t> tag_set = {22, 23, 24, 25, 26, 27}; + + for (auto tag : tag_set) { + NumberedBoundaryDescriptor numbered_boundary_descriptor(tag); + const auto& edge_boundary = getMeshFlatEdgeBoundary(mesh, numbered_boundary_descriptor); + + auto edge_list = get_edge_list_from_tag(tag, connectivity); + REQUIRE(is_same(edge_boundary.edgeList(), edge_list)); + + R3 normal = zero; + + switch (tag) { + case 22: { + normal = R3{-1, 0, 0}; + break; + } + case 23: { + normal = R3{1, 0, 0}; + break; + } + case 24: { + normal = R3{0, 0, 1}; + break; + } + case 25: { + normal = R3{0, 0, -1}; + break; + } + case 26: { + normal = R3{0, 1, 0}; + break; + } + case 27: { + normal = R3{0, -1, 0}; + break; + } + default: { + FAIL("unexpected tag number"); + } + } + + REQUIRE(l2Norm(edge_boundary.outgoingNormal() - normal) == Catch::Approx(0).margin(1E-13)); + } + + { + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NumberedBoundaryDescriptor(28)), + "error: cannot find surface with name \"28\""); + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NumberedBoundaryDescriptor(29)), + "error: cannot find surface with name \"29\""); + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NumberedBoundaryDescriptor(30)), + "error: cannot find surface with name \"30\""); + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NumberedBoundaryDescriptor(31)), + "error: cannot find surface with name \"31\""); + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NumberedBoundaryDescriptor(32)), + "error: cannot find surface with name \"32\""); + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NumberedBoundaryDescriptor(33)), + "error: cannot find surface with name \"33\""); + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NumberedBoundaryDescriptor(34)), + "error: cannot find surface with name \"34\""); + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NumberedBoundaryDescriptor(35)), + "error: cannot find surface with name \"35\""); + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NumberedBoundaryDescriptor(36)), + "error: cannot find surface with name \"36\""); + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NumberedBoundaryDescriptor(37)), + "error: cannot find surface with name \"37\""); + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NumberedBoundaryDescriptor(38)), + "error: cannot find surface with name \"38\""); + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NumberedBoundaryDescriptor(39)), + "error: cannot find surface with name \"39\""); + + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NumberedBoundaryDescriptor(40)), + "error: cannot find edge list with name \"40\""); + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NumberedBoundaryDescriptor(41)), + "error: cannot find edge list with name \"41\""); + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NumberedBoundaryDescriptor(42)), + "error: cannot find edge list with name \"42\""); + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NumberedBoundaryDescriptor(43)), + "error: cannot find edge list with name \"43\""); + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NumberedBoundaryDescriptor(44)), + "error: cannot find edge list with name \"44\""); + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NumberedBoundaryDescriptor(45)), + "error: cannot find edge list with name \"45\""); + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NumberedBoundaryDescriptor(47)), + "error: cannot find edge list with name \"47\""); + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NumberedBoundaryDescriptor(51)), + "error: cannot find edge list with name \"51\""); + } + } + + { + const std::set<std::string> name_set = {"XMIN", "XMAX", "YMIN", "YMAX", "ZMIN", "ZMAX"}; + + for (auto name : name_set) { + NamedBoundaryDescriptor named_boundary_descriptor(name); + const auto& edge_boundary = getMeshFlatEdgeBoundary(mesh, named_boundary_descriptor); + + auto edge_list = get_edge_list_from_name(name, connectivity); + + REQUIRE(is_same(edge_boundary.edgeList(), edge_list)); + + R3 normal = zero; + + if (name == "XMIN") { + normal = R3{-1, 0, 0}; + } else if (name == "XMAX") { + normal = R3{1, 0, 0}; + } else if (name == "YMIN") { + normal = R3{0, -1, 0}; + } else if (name == "YMAX") { + normal = R3{0, 1, 0}; + } else if (name == "ZMIN") { + normal = R3{0, 0, -1}; + } else if (name == "ZMAX") { + normal = R3{0, 0, 1}; + } else { + FAIL("unexpected name: " + name); + } + + REQUIRE(l2Norm(edge_boundary.outgoingNormal() - normal) == Catch::Approx(0).margin(1E-13)); + } + + { + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NamedBoundaryDescriptor("XMINYMIN")), + "error: cannot find surface with name \"XMINYMIN\""); + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NamedBoundaryDescriptor("XMINYMAX")), + "error: cannot find surface with name \"XMINYMAX\""); + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NamedBoundaryDescriptor("XMAXYMAX")), + "error: cannot find surface with name \"XMAXYMAX\""); + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NamedBoundaryDescriptor("XMINYMAX")), + "error: cannot find surface with name \"XMINYMAX\""); + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NamedBoundaryDescriptor("XMINZMIN")), + "error: cannot find surface with name \"XMINZMIN\""); + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NamedBoundaryDescriptor("XMINZMAX")), + "error: cannot find surface with name \"XMINZMAX\""); + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NamedBoundaryDescriptor("XMAXZMAX")), + "error: cannot find surface with name \"XMAXZMAX\""); + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NamedBoundaryDescriptor("XMINZMAX")), + "error: cannot find surface with name \"XMINZMAX\""); + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NamedBoundaryDescriptor("YMINZMIN")), + "error: cannot find surface with name \"YMINZMIN\""); + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NamedBoundaryDescriptor("YMINZMAX")), + "error: cannot find surface with name \"YMINZMAX\""); + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NamedBoundaryDescriptor("YMAXZMAX")), + "error: cannot find surface with name \"YMAXZMAX\""); + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NamedBoundaryDescriptor("YMINZMAX")), + "error: cannot find surface with name \"YMINZMAX\""); + + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NamedBoundaryDescriptor("XMINYMINZMIN")), + "error: cannot find edge list with name \"XMINYMINZMIN\""); + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NamedBoundaryDescriptor("XMAXYMINZMIN")), + "error: cannot find edge list with name \"XMAXYMINZMIN\""); + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NamedBoundaryDescriptor("XMAXYMAXZMIN")), + "error: cannot find edge list with name \"XMAXYMAXZMIN\""); + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NamedBoundaryDescriptor("XMINYMAXZMIN")), + "error: cannot find edge list with name \"XMINYMAXZMIN\""); + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NamedBoundaryDescriptor("XMINYMINZMAX")), + "error: cannot find edge list with name \"XMINYMINZMAX\""); + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NamedBoundaryDescriptor("XMAXYMINZMAX")), + "error: cannot find edge list with name \"XMAXYMINZMAX\""); + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NamedBoundaryDescriptor("XMAXYMAXZMAX")), + "error: cannot find edge list with name \"XMAXYMAXZMAX\""); + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, NamedBoundaryDescriptor("XMINYMAXZMAX")), + "error: cannot find edge list with name \"XMINYMAXZMAX\""); + } + } + } + } + } + + SECTION("rotated axis") + { + SECTION("2D") + { + static constexpr size_t Dimension = 2; + + using ConnectivityType = Connectivity<Dimension>; + using MeshType = Mesh<ConnectivityType>; + + using R2 = TinyVector<2>; + + const double theta = 0.3; + const TinyMatrix<2> R{std::cos(theta), -std::sin(theta), // + std::sin(theta), std::cos(theta)}; + + SECTION("cartesian 2d") + { + std::shared_ptr p_mesh = MeshDataBaseForTests::get().cartesian2DMesh(); + + const ConnectivityType& connectivity = p_mesh->connectivity(); + + auto xr = p_mesh->xr(); + + NodeValue<R2> rotated_xr{connectivity}; + + parallel_for( + connectivity.numberOfNodes(), PUGS_LAMBDA(const NodeId node_id) { rotated_xr[node_id] = R * xr[node_id]; }); + + MeshType mesh{p_mesh->shared_connectivity(), rotated_xr}; + + { + const std::set<size_t> tag_set = {0, 1, 2, 3}; + + for (auto tag : tag_set) { + NumberedBoundaryDescriptor numbered_boundary_descriptor(tag); + const auto& edge_boundary = getMeshFlatEdgeBoundary(mesh, numbered_boundary_descriptor); + + auto edge_list = get_edge_list_from_tag(tag, connectivity); + REQUIRE(is_same(edge_boundary.edgeList(), edge_list)); + + R2 normal = zero; + + switch (tag) { + case 0: { + normal = R * R2{-1, 0}; + break; + } + case 1: { + normal = R * R2{1, 0}; + break; + } + case 2: { + normal = R * R2{0, -1}; + break; + } + case 3: { + normal = R * R2{0, 1}; + break; + } + default: { + FAIL("unexpected tag number"); + } + } + + REQUIRE(l2Norm(edge_boundary.outgoingNormal() - normal) == Catch::Approx(0).margin(1E-13)); + } + } + + { + const std::set<std::string> name_set = {"XMIN", "XMAX", "YMIN", "YMAX"}; + + for (auto name : name_set) { + NamedBoundaryDescriptor named_boundary_descriptor(name); + const auto& edge_boundary = getMeshFlatEdgeBoundary(mesh, named_boundary_descriptor); + + auto edge_list = get_edge_list_from_name(name, connectivity); + REQUIRE(is_same(edge_boundary.edgeList(), edge_list)); + + R2 normal = zero; + + if (name == "XMIN") { + normal = R * R2{-1, 0}; + } else if (name == "XMAX") { + normal = R * R2{1, 0}; + } else if (name == "YMIN") { + normal = R * R2{0, -1}; + } else if (name == "YMAX") { + normal = R * R2{0, 1}; + } else { + FAIL("unexpected name: " + name); + } + + REQUIRE(l2Norm(edge_boundary.outgoingNormal() - normal) == Catch::Approx(0).margin(1E-13)); + } + } + } + + SECTION("hybrid 2d") + { + std::shared_ptr p_mesh = MeshDataBaseForTests::get().hybrid2DMesh(); + + const ConnectivityType& connectivity = p_mesh->connectivity(); + + auto xr = p_mesh->xr(); + + NodeValue<R2> rotated_xr{connectivity}; + parallel_for( + connectivity.numberOfNodes(), PUGS_LAMBDA(const NodeId node_id) { rotated_xr[node_id] = R * xr[node_id]; }); + + MeshType mesh{p_mesh->shared_connectivity(), rotated_xr}; + + { + const std::set<size_t> tag_set = {1, 2, 3, 4}; + + for (auto tag : tag_set) { + NumberedBoundaryDescriptor numbered_boundary_descriptor(tag); + const auto& edge_boundary = getMeshFlatEdgeBoundary(mesh, numbered_boundary_descriptor); + + auto edge_list = get_edge_list_from_tag(tag, connectivity); + REQUIRE(is_same(edge_boundary.edgeList(), edge_list)); + + R2 normal = zero; + + switch (tag) { + case 1: { + normal = R * R2{-1, 0}; + break; + } + case 2: { + normal = R * R2{1, 0}; + break; + } + case 3: { + normal = R * R2{0, 1}; + break; + } + case 4: { + normal = R * R2{0, -1}; + break; + } + default: { + FAIL("unexpected tag number"); + } + } + + REQUIRE(l2Norm(edge_boundary.outgoingNormal() - normal) == Catch::Approx(0).margin(1E-13)); + } + } + + { + const std::set<std::string> name_set = {"XMIN", "XMAX", "YMIN", "YMAX"}; + + for (auto name : name_set) { + NamedBoundaryDescriptor named_boundary_descriptor(name); + const auto& edge_boundary = getMeshFlatEdgeBoundary(mesh, named_boundary_descriptor); + + auto edge_list = get_edge_list_from_name(name, connectivity); + + R2 normal = zero; + + REQUIRE(is_same(edge_boundary.edgeList(), edge_list)); + + if (name == "XMIN") { + normal = R * R2{-1, 0}; + } else if (name == "XMAX") { + normal = R * R2{1, 0}; + } else if (name == "YMIN") { + normal = R * R2{0, -1}; + } else if (name == "YMAX") { + normal = R * R2{0, 1}; + } else { + FAIL("unexpected name: " + name); + } + + REQUIRE(l2Norm(edge_boundary.outgoingNormal() - normal) == Catch::Approx(0).margin(1E-13)); + } + } + } + } + + SECTION("3D") + { + static constexpr size_t Dimension = 3; + + using ConnectivityType = Connectivity<Dimension>; + using MeshType = Mesh<ConnectivityType>; + + using R3 = TinyVector<3>; + + const double theta = 0.3; + const double phi = 0.4; + const TinyMatrix<3> R = + TinyMatrix<3>{std::cos(theta), -std::sin(theta), 0, std::sin(theta), std::cos(theta), 0, 0, 0, 1} * + TinyMatrix<3>{0, std::cos(phi), -std::sin(phi), 0, std::sin(phi), std::cos(phi), 1, 0, 0}; + + SECTION("cartesian 3d") + { + std::shared_ptr p_mesh = MeshDataBaseForTests::get().cartesian3DMesh(); + + const ConnectivityType& connectivity = p_mesh->connectivity(); + + auto xr = p_mesh->xr(); + + NodeValue<R3> rotated_xr{connectivity}; + + parallel_for( + connectivity.numberOfNodes(), PUGS_LAMBDA(const NodeId node_id) { rotated_xr[node_id] = R * xr[node_id]; }); + + MeshType mesh{p_mesh->shared_connectivity(), rotated_xr}; + + { + const std::set<size_t> tag_set = {0, 1, 2, 3, 4, 5}; + + for (auto tag : tag_set) { + NumberedBoundaryDescriptor numbered_boundary_descriptor(tag); + const auto& edge_boundary = getMeshFlatEdgeBoundary(mesh, numbered_boundary_descriptor); + + auto edge_list = get_edge_list_from_tag(tag, connectivity); + REQUIRE(is_same(edge_boundary.edgeList(), edge_list)); + + R3 normal = zero; + + switch (tag) { + case 0: { + normal = R * R3{-1, 0, 0}; + break; + } + case 1: { + normal = R * R3{1, 0, 0}; + break; + } + case 2: { + normal = R * R3{0, -1, 0}; + break; + } + case 3: { + normal = R * R3{0, 1, 0}; + break; + } + case 4: { + normal = R * R3{0, 0, -1}; + break; + } + case 5: { + normal = R * R3{0, 0, 1}; + break; + } + default: { + FAIL("unexpected tag number"); + } + } + + REQUIRE(l2Norm(edge_boundary.outgoingNormal() - normal) == Catch::Approx(0).margin(1E-13)); + } + } + + { + const std::set<std::string> name_set = {"XMIN", "XMAX", "YMIN", "YMAX", "ZMIN", "ZMAX"}; + + for (auto name : name_set) { + NamedBoundaryDescriptor named_boundary_descriptor(name); + const auto& edge_boundary = getMeshFlatEdgeBoundary(mesh, named_boundary_descriptor); + + auto edge_list = get_edge_list_from_name(name, connectivity); + + REQUIRE(is_same(edge_boundary.edgeList(), edge_list)); + + R3 normal = zero; + + if (name == "XMIN") { + normal = R * R3{-1, 0, 0}; + } else if (name == "XMAX") { + normal = R * R3{1, 0, 0}; + } else if (name == "YMIN") { + normal = R * R3{0, -1, 0}; + } else if (name == "YMAX") { + normal = R * R3{0, 1, 0}; + } else if (name == "ZMIN") { + normal = R * R3{0, 0, -1}; + } else if (name == "ZMAX") { + normal = R * R3{0, 0, 1}; + } else { + FAIL("unexpected name: " + name); + } + + REQUIRE(l2Norm(edge_boundary.outgoingNormal() - normal) == Catch::Approx(0).margin(1E-13)); + } + } + } + + SECTION("hybrid 3d") + { + std::shared_ptr p_mesh = MeshDataBaseForTests::get().hybrid3DMesh(); + + const ConnectivityType& connectivity = p_mesh->connectivity(); + + auto xr = p_mesh->xr(); + + NodeValue<R3> rotated_xr{connectivity}; + + parallel_for( + connectivity.numberOfNodes(), PUGS_LAMBDA(const NodeId node_id) { rotated_xr[node_id] = R * xr[node_id]; }); + + MeshType mesh{p_mesh->shared_connectivity(), rotated_xr}; + + { + const std::set<size_t> tag_set = {22, 23, 24, 25, 26, 27}; + + for (auto tag : tag_set) { + NumberedBoundaryDescriptor numbered_boundary_descriptor(tag); + const auto& edge_boundary = getMeshFlatEdgeBoundary(mesh, numbered_boundary_descriptor); + + auto edge_list = get_edge_list_from_tag(tag, connectivity); + REQUIRE(is_same(edge_boundary.edgeList(), edge_list)); + + R3 normal = zero; + + switch (tag) { + case 22: { + normal = R * R3{-1, 0, 0}; + break; + } + case 23: { + normal = R * R3{1, 0, 0}; + break; + } + case 24: { + normal = R * R3{0, 0, 1}; + break; + } + case 25: { + normal = R * R3{0, 0, -1}; + break; + } + case 26: { + normal = R * R3{0, 1, 0}; + break; + } + case 27: { + normal = R * R3{0, -1, 0}; + break; + } + default: { + FAIL("unexpected tag number"); + } + } + + REQUIRE(l2Norm(edge_boundary.outgoingNormal() - normal) == Catch::Approx(0).margin(1E-13)); + } + } + + { + const std::set<std::string> name_set = {"XMIN", "XMAX", "YMIN", "YMAX", "ZMIN", "ZMAX"}; + + for (auto name : name_set) { + NamedBoundaryDescriptor named_boundary_descriptor(name); + const auto& edge_boundary = getMeshFlatEdgeBoundary(mesh, named_boundary_descriptor); + + auto edge_list = get_edge_list_from_name(name, connectivity); + + REQUIRE(is_same(edge_boundary.edgeList(), edge_list)); + + R3 normal = zero; + + if (name == "XMIN") { + normal = R * R3{-1, 0, 0}; + } else if (name == "XMAX") { + normal = R * R3{1, 0, 0}; + } else if (name == "YMIN") { + normal = R * R3{0, -1, 0}; + } else if (name == "YMAX") { + normal = R * R3{0, 1, 0}; + } else if (name == "ZMIN") { + normal = R * R3{0, 0, -1}; + } else if (name == "ZMAX") { + normal = R * R3{0, 0, 1}; + } else { + FAIL("unexpected name: " + name); + } + + REQUIRE(l2Norm(edge_boundary.outgoingNormal() - normal) == Catch::Approx(0).margin(1E-13)); + } + } + } + } + } + + SECTION("curved mesh") + { + SECTION("2D") + { + static constexpr size_t Dimension = 2; + + using ConnectivityType = Connectivity<Dimension>; + using MeshType = Mesh<ConnectivityType>; + + using R2 = TinyVector<2>; + + auto curve = [](const R2& X) -> R2 { return R2{X[0], (1 + X[0] * X[0]) * X[1]}; }; + + SECTION("hybrid 2d") + { + std::shared_ptr p_mesh = MeshDataBaseForTests::get().hybrid2DMesh(); + + const ConnectivityType& connectivity = p_mesh->connectivity(); + + auto xr = p_mesh->xr(); + + NodeValue<TinyVector<2>> curved_xr{connectivity}; + parallel_for( + connectivity.numberOfNodes(), PUGS_LAMBDA(const NodeId node_id) { curved_xr[node_id] = curve(xr[node_id]); }); + + MeshType mesh{p_mesh->shared_connectivity(), curved_xr}; + + { + const std::set<size_t> tag_set = {1, 2, 4}; + + for (auto tag : tag_set) { + NumberedBoundaryDescriptor numbered_boundary_descriptor(tag); + const auto& edge_boundary = getMeshFlatEdgeBoundary(mesh, numbered_boundary_descriptor); + + auto edge_list = get_edge_list_from_tag(tag, connectivity); + REQUIRE(is_same(edge_boundary.edgeList(), edge_list)); + + R2 normal = zero; + + switch (tag) { + case 1: { + normal = R2{-1, 0}; + break; + } + case 2: { + normal = R2{1, 0}; + break; + } + case 4: { + normal = R2{0, -1}; + break; + } + default: { + FAIL("unexpected tag number"); + } + } + + REQUIRE(l2Norm(edge_boundary.outgoingNormal() - normal) == Catch::Approx(0).margin(1E-13)); + } + + { + NumberedBoundaryDescriptor numbered_boundary_descriptor(3); + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, numbered_boundary_descriptor), + "error: invalid boundary \"YMAX\": boundary is not flat!"); + } + } + + { + const std::set<std::string> name_set = {"XMIN", "XMAX", "YMIN"}; + + for (auto name : name_set) { + NamedBoundaryDescriptor named_boundary_descriptor(name); + const auto& edge_boundary = getMeshFlatEdgeBoundary(mesh, named_boundary_descriptor); + + auto edge_list = get_edge_list_from_name(name, connectivity); + + R2 normal = zero; + + REQUIRE(is_same(edge_boundary.edgeList(), edge_list)); + + if (name == "XMIN") { + normal = R2{-1, 0}; + } else if (name == "XMAX") { + normal = R2{1, 0}; + } else if (name == "YMIN") { + normal = R2{0, -1}; + } else { + FAIL("unexpected name: " + name); + } + + REQUIRE(l2Norm(edge_boundary.outgoingNormal() - normal) == Catch::Approx(0).margin(1E-13)); + } + + { + NamedBoundaryDescriptor named_boundary_descriptor("YMAX"); + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, named_boundary_descriptor), + "error: invalid boundary \"YMAX\": boundary is not flat!"); + } + } + } + } + + SECTION("3D") + { + static constexpr size_t Dimension = 3; + + using ConnectivityType = Connectivity<Dimension>; + using MeshType = Mesh<ConnectivityType>; + + using R3 = TinyVector<3>; + + SECTION("cartesian 3d") + { + std::shared_ptr p_mesh = MeshDataBaseForTests::get().cartesian3DMesh(); + + const ConnectivityType& connectivity = p_mesh->connectivity(); + + auto xr = p_mesh->xr(); + + auto curve = [](const R3& X) -> R3 { + return R3{X[0], (1 + X[0] * X[0]) * (X[1] + 1), (1 - 0.2 * X[0] * X[0]) * X[2]}; + }; + + NodeValue<R3> curved_xr{connectivity}; + + parallel_for( + connectivity.numberOfNodes(), PUGS_LAMBDA(const NodeId node_id) { curved_xr[node_id] = curve(xr[node_id]); }); + + MeshType mesh{p_mesh->shared_connectivity(), curved_xr}; + + { + const std::set<size_t> tag_set = {0, 1, 2, 4}; + + for (auto tag : tag_set) { + auto edge_list = get_edge_list_from_tag(tag, connectivity); + + NumberedBoundaryDescriptor numbered_boundary_descriptor(tag); + const auto& edge_boundary = getMeshFlatEdgeBoundary(mesh, numbered_boundary_descriptor); + + REQUIRE(is_same(edge_boundary.edgeList(), edge_list)); + + R3 normal = zero; + + switch (tag) { + case 0: { + normal = R3{-1, 0, 0}; + break; + } + case 1: { + normal = R3{1, 0, 0}; + break; + } + case 2: { + normal = R3{0, -1, 0}; + break; + } + case 4: { + normal = R3{0, 0, -1}; + break; + } + default: { + FAIL("unexpected tag number"); + } + } + + REQUIRE(l2Norm(edge_boundary.outgoingNormal() - normal) == Catch::Approx(0).margin(1E-13)); + } + + { + NumberedBoundaryDescriptor numbered_boundary_descriptor(3); + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, numbered_boundary_descriptor), + "error: invalid boundary \"YMAX\": boundary is not flat!"); + } + + { + NumberedBoundaryDescriptor numbered_boundary_descriptor(5); + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, numbered_boundary_descriptor), + "error: invalid boundary \"ZMAX\": boundary is not flat!"); + } + } + + { + const std::set<std::string> name_set = {"XMIN", "XMAX", "YMIN", "ZMIN"}; + + for (auto name : name_set) { + NamedBoundaryDescriptor named_boundary_descriptor(name); + const auto& edge_boundary = getMeshFlatEdgeBoundary(mesh, named_boundary_descriptor); + + auto edge_list = get_edge_list_from_name(name, connectivity); + + REQUIRE(is_same(edge_boundary.edgeList(), edge_list)); + + R3 normal = zero; + + if (name == "XMIN") { + normal = R3{-1, 0, 0}; + } else if (name == "XMAX") { + normal = R3{1, 0, 0}; + } else if (name == "YMIN") { + normal = R3{0, -1, 0}; + } else if (name == "ZMIN") { + normal = R3{0, 0, -1}; + } else { + FAIL("unexpected name: " + name); + } + + REQUIRE(l2Norm(edge_boundary.outgoingNormal() - normal) == Catch::Approx(0).margin(1E-13)); + } + + { + NamedBoundaryDescriptor named_boundary_descriptor("YMAX"); + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, named_boundary_descriptor), + "error: invalid boundary \"YMAX\": boundary is not flat!"); + } + + { + NamedBoundaryDescriptor named_boundary_descriptor("ZMAX"); + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, named_boundary_descriptor), + "error: invalid boundary \"ZMAX\": boundary is not flat!"); + } + } + } + + SECTION("hybrid 3d") + { + std::shared_ptr p_mesh = MeshDataBaseForTests::get().hybrid3DMesh(); + + const ConnectivityType& connectivity = p_mesh->connectivity(); + + auto xr = p_mesh->xr(); + + auto curve = [](const R3& X) -> R3 { + return R3{X[0], (1 + X[0] * X[0]) * X[1], (1 - 0.2 * X[0] * X[0]) * X[2]}; + }; + + NodeValue<R3> curved_xr{connectivity}; + + parallel_for( + connectivity.numberOfNodes(), PUGS_LAMBDA(const NodeId node_id) { curved_xr[node_id] = curve(xr[node_id]); }); + + MeshType mesh{p_mesh->shared_connectivity(), curved_xr}; + + { + const std::set<size_t> tag_set = {22, 23, 25, 27}; + + for (auto tag : tag_set) { + NumberedBoundaryDescriptor numbered_boundary_descriptor(tag); + const auto& edge_boundary = getMeshFlatEdgeBoundary(mesh, numbered_boundary_descriptor); + + auto edge_list = get_edge_list_from_tag(tag, connectivity); + REQUIRE(is_same(edge_boundary.edgeList(), edge_list)); + + R3 normal = zero; + + switch (tag) { + case 22: { + normal = R3{-1, 0, 0}; + break; + } + case 23: { + normal = R3{1, 0, 0}; + break; + } + case 25: { + normal = R3{0, 0, -1}; + break; + } + case 27: { + normal = R3{0, -1, 0}; + break; + } + default: { + FAIL("unexpected tag number"); + } + } + + REQUIRE(l2Norm(edge_boundary.outgoingNormal() - normal) == Catch::Approx(0).margin(1E-13)); + } + + { + NumberedBoundaryDescriptor numbered_boundary_descriptor(24); + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, numbered_boundary_descriptor), + "error: invalid boundary \"ZMAX\": boundary is not flat!"); + } + + { + NumberedBoundaryDescriptor numbered_boundary_descriptor(26); + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, numbered_boundary_descriptor), + "error: invalid boundary \"YMAX\": boundary is not flat!"); + } + } + + { + const std::set<std::string> name_set = {"XMIN", "XMAX", "YMIN", "ZMIN"}; + + for (auto name : name_set) { + NamedBoundaryDescriptor named_boundary_descriptor(name); + const auto& edge_boundary = getMeshFlatEdgeBoundary(mesh, named_boundary_descriptor); + + auto edge_list = get_edge_list_from_name(name, connectivity); + + REQUIRE(is_same(edge_boundary.edgeList(), edge_list)); + + R3 normal = zero; + + if (name == "XMIN") { + normal = R3{-1, 0, 0}; + } else if (name == "XMAX") { + normal = R3{1, 0, 0}; + } else if (name == "YMIN") { + normal = R3{0, -1, 0}; + } else if (name == "ZMIN") { + normal = R3{0, 0, -1}; + } else { + FAIL("unexpected name: " + name); + } + + REQUIRE(l2Norm(edge_boundary.outgoingNormal() - normal) == Catch::Approx(0).margin(1E-13)); + } + + { + NamedBoundaryDescriptor named_boundary_descriptor("YMAX"); + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, named_boundary_descriptor), + "error: invalid boundary \"YMAX\": boundary is not flat!"); + } + + { + NamedBoundaryDescriptor named_boundary_descriptor("ZMAX"); + REQUIRE_THROWS_WITH(getMeshFlatEdgeBoundary(mesh, named_boundary_descriptor), + "error: invalid boundary \"ZMAX\": boundary is not flat!"); + } + } + } + } + } +}