diff --git a/MeshLib/MeshEditing/ConvertToLinearMesh.cpp b/MeshLib/MeshEditing/ConvertToLinearMesh.cpp index 06cc122804a5646865ec1f301d4e15f460eba604..d06d423e26520aafd0946d158802b3f7c01344c9 100644 --- a/MeshLib/MeshEditing/ConvertToLinearMesh.cpp +++ b/MeshLib/MeshEditing/ConvertToLinearMesh.cpp @@ -10,9 +10,9 @@ #include "ConvertToLinearMesh.h" #include "MeshLib/Elements/Element.h" +#include "MeshLib/Elements/Hex.h" #include "MeshLib/Elements/Line.h" #include "MeshLib/Elements/Quad.h" -#include "MeshLib/Elements/Hex.h" #include "MeshLib/Elements/Tet.h" #include "MeshLib/Elements/Tri.h" #include "MeshLib/Elements/Utils.h" @@ -22,33 +22,43 @@ #include "MeshLib/Properties.h" #include "MeshLib/PropertyVector.h" - namespace MeshLib { - namespace { - template <typename T_ELEMENT> T_ELEMENT* createLinearElement(MeshLib::Element const* e, - std::vector<MeshLib::Node*> const& vec_new_nodes) + std::vector<MeshLib::Node*> const& vec_new_nodes) { auto const n_base_nodes = T_ELEMENT::n_base_nodes; auto** nodes = new MeshLib::Node*[n_base_nodes]; for (unsigned i = 0; i < e->getNumberOfBaseNodes(); i++) { - nodes[i] = - const_cast<MeshLib::Node*>(vec_new_nodes[e->getNode(i)->getID()]); + auto const it = find_if( + begin(vec_new_nodes), end(vec_new_nodes), + [node_i = e->getNode(i)](Node* const new_node) { + return *node_i == + *new_node; // coordinate comparison up to epsilon + }); + if (it == end(vec_new_nodes)) + { + OGS_FATAL( + "A base node %d (with original global node id %d) not found in " + "the list for element %d.", + i, e->getNode(i)->getID(), e->getID()); + } + nodes[i] = const_cast<MeshLib::Node*>(*it); } return new T_ELEMENT(nodes); } -} // unnamed namespace +} // unnamed namespace - -std::unique_ptr<MeshLib::Mesh> convertToLinearMesh(MeshLib::Mesh const& org_mesh, std::string const& new_mesh_name) +std::unique_ptr<MeshLib::Mesh> convertToLinearMesh( + MeshLib::Mesh const& org_mesh, std::string const& new_mesh_name) { - std::vector<MeshLib::Node*> vec_new_nodes = MeshLib::copyNodeVector(MeshLib::getBaseNodes(org_mesh.getElements())); + std::vector<MeshLib::Node*> vec_new_nodes = + MeshLib::copyNodeVector(MeshLib::getBaseNodes(org_mesh.getElements())); // create new elements with the quadratic nodes std::vector<MeshLib::Element*> vec_new_eles; @@ -56,32 +66,33 @@ std::unique_ptr<MeshLib::Mesh> convertToLinearMesh(MeshLib::Mesh const& org_mesh { if (e->getCellType() == MeshLib::CellType::LINE3) { - vec_new_eles.push_back(createLinearElement<MeshLib::Line>( - e, vec_new_nodes)); + vec_new_eles.push_back( + createLinearElement<MeshLib::Line>(e, vec_new_nodes)); } else if (e->getCellType() == MeshLib::CellType::QUAD8) { - vec_new_eles.push_back(createLinearElement<MeshLib::Quad>( - e, vec_new_nodes)); + vec_new_eles.push_back( + createLinearElement<MeshLib::Quad>(e, vec_new_nodes)); } else if (e->getCellType() == MeshLib::CellType::TRI6) { - vec_new_eles.push_back(createLinearElement<MeshLib::Tri>( - e, vec_new_nodes)); + vec_new_eles.push_back( + createLinearElement<MeshLib::Tri>(e, vec_new_nodes)); } else if (e->getCellType() == MeshLib::CellType::HEX20) { - vec_new_eles.push_back(createLinearElement<MeshLib::Hex>( - e, vec_new_nodes)); + vec_new_eles.push_back( + createLinearElement<MeshLib::Hex>(e, vec_new_nodes)); } else if (e->getCellType() == MeshLib::CellType::TET10) { - vec_new_eles.push_back(createLinearElement<MeshLib::Tet>( - e, vec_new_nodes)); + vec_new_eles.push_back( + createLinearElement<MeshLib::Tet>(e, vec_new_nodes)); } else { - OGS_FATAL("Mesh element type %s is not supported", MeshLib::CellType2String(e->getCellType()).c_str()); + OGS_FATAL("Mesh element type %s is not supported", + MeshLib::CellType2String(e->getCellType()).c_str()); } } @@ -112,7 +123,7 @@ std::unique_ptr<MeshLib::Mesh> convertToLinearMesh(MeshLib::Mesh const& org_mesh new_prop->resize(new_mesh->getNumberOfNodes() * n_src_comp); // copy only base node values - for (unsigned i=0; i<org_mesh.getNumberOfBaseNodes(); i++) + for (unsigned i = 0; i < org_mesh.getNumberOfBaseNodes(); i++) { for (int j = 0; j < n_src_comp; j++) { @@ -125,5 +136,4 @@ std::unique_ptr<MeshLib::Mesh> convertToLinearMesh(MeshLib::Mesh const& org_mesh return new_mesh; } -} // end namespace MeshLib - +} // end namespace MeshLib diff --git a/MeshLib/MeshEditing/ConvertToLinearMesh.h b/MeshLib/MeshEditing/ConvertToLinearMesh.h index 9093004616ca2c4e4fb46c1066d59cda2f4c6b28..92253690b5bfa3937af5398881f9f3926637cb60 100644 --- a/MeshLib/MeshEditing/ConvertToLinearMesh.h +++ b/MeshLib/MeshEditing/ConvertToLinearMesh.h @@ -16,10 +16,9 @@ namespace MeshLib { - /// Converts a non-linear mesh to a linear mesh. All the mesh properties will /// be copied except for entries for non-linear nodes. std::unique_ptr<MeshLib::Mesh> convertToLinearMesh( const MeshLib::Mesh& mesh, const std::string& new_mesh_name); -} // end namespace MeshLib +} // end namespace MeshLib diff --git a/Tests/MeshLib/ConvertToLinearMesh.cpp b/Tests/MeshLib/ConvertToLinearMesh.cpp new file mode 100644 index 0000000000000000000000000000000000000000..068bea3f6f0024b8e30cfab2b7cae4d6c5633fb2 --- /dev/null +++ b/Tests/MeshLib/ConvertToLinearMesh.cpp @@ -0,0 +1,324 @@ +/** + * \file + * + * \copyright + * Copyright (c) 2012-2019, OpenGeoSys Community (http://www.opengeosys.org) + * Distributed under a Modified BSD License. + * See accompanying file LICENSE.txt or + * http://www.opengeosys.org/LICENSE.txt + */ + +#include <gtest/gtest.h> +#include <algorithm> +#include <boost/variant.hpp> +#include <numeric> +#include <random> + +#include "MeshLib/Elements/Element.h" +#include "MeshLib/Mesh.h" +#include "MeshLib/MeshEditing/ConvertToLinearMesh.h" +#include "MeshLib/MeshGenerators/MeshGenerator.h" +#include "MeshLib/MeshGenerators/QuadraticMeshGenerator.h" +#include "MeshLib/Node.h" + +using namespace MeshLib; +using namespace std::string_literals; + +class ConvertToLinearMesh : public ::testing::Test +{ +public: + ConvertToLinearMesh() + { + linear_mesh.reset( + MeshGenerator::generateRegularHexMesh(1.0, mesh_size)); + quadratic_mesh = createQuadraticOrderMesh(*linear_mesh); + } + + static constexpr int mesh_size = 5; + + std::unique_ptr<Mesh> linear_mesh; + std::unique_ptr<Mesh> quadratic_mesh; +}; + +// Returns the (inverse) permutation vector of b_nodes to a_nodes, or an error +// message. +boost::variant<std::vector<std::size_t>, std::string> compareNodes( + std::vector<Node*> const& a_nodes, std::vector<Node*> const& b_nodes) +{ + // For each 'a' mesh node find a corresponding 'b' mesh node, not checking + // the ids, but store the id's in a map. + std::vector<std::size_t> b_node_id(a_nodes.size()); + + std::size_t const nnodes = a_nodes.size(); + for (std::size_t i = 0; i < nnodes; ++i) + { + Node* a_node = a_nodes[i]; + auto const b_it = + std::find_if(begin(b_nodes), end(b_nodes), [&](Node* const b_node) { + return *a_node == + *b_node; // coordinate comparison up to epsilon + }); + if (b_it == end(b_nodes)) + { + return "Could not find a matching node for node " + + std::to_string(a_node->getID()) + "."; + } + + b_node_id[i] = distance(begin(b_nodes), b_it); + } + + return b_node_id; +} + +// Two elements are equal if their corresponding node coordinates are equal up +// to a (shift) permutation. +// Returns a string in case of error containing a descriptive message. +boost::optional<std::string> equal(Element const& a, Element const& b) +{ + if (typeid(a) != typeid(b)) // => (a.nodes.size == b.nodes.size) + { + return "Elements have different types."s; + } + + auto const a_nodes = a.getNodes(); + auto const b_nodes = b.getNodes(); + auto const nnodes = a.getNumberOfNodes(); + + std::size_t first_b_node = 0; + while (first_b_node < nnodes) + { + if (*a_nodes[0] == *b_nodes[first_b_node]) + break; + ++first_b_node; + } + if (first_b_node == nnodes) + { + return "Did not find first node of the element " + + std::to_string(a.getID()) + " in the nodes of the element " + + std::to_string(b.getID()) + "."; + } + + for (std::size_t i = 1; i < nnodes; ++i) + { + if (!(*a_nodes[i] == *b_nodes[(first_b_node + i) % nnodes])) + { + return "node " + std::to_string(i) + " is not equal to the node " + + std::to_string((first_b_node + i) % nnodes) + "."; + } + } + + return {}; // All nodes are equal. +} + +// Returns a vector filled with numbers [0, size) randomly permuted. +std::vector<std::size_t> generateRandomPermutationVector(std::size_t const size) +{ + std::random_device rd; + std::mt19937 random_generator(rd()); + + std::vector<std::size_t> permutation(size); + std::iota(begin(permutation), end(permutation), 0); + std::shuffle(begin(permutation), end(permutation), random_generator); + return permutation; +} + +// Test the inverse permutation for being the inverse: p^-1(p(.)) = Id. +// In case of failure an error message is returned. +boost::optional<std::string> inversePermutationIdentityTest( + std::vector<std::size_t> const& permutation, + std::vector<std::size_t> const& inverse_permutation) +{ + if (permutation.size() != inverse_permutation.size()) + { + return "Inverse permutation size differs from the permutation size: " + + std::to_string(inverse_permutation.size()) + " vs. " + + std::to_string(permutation.size()) + "."; + } + + for (std::size_t i = 0; i < permutation.size(); ++i) + { + auto const p = permutation[i]; + if (inverse_permutation[p] == i) + { + continue; + } + return "Inverse permutation element " + std::to_string(p) + + " does not point to " + std::to_string(i) + + "-th element but to " + std::to_string(inverse_permutation[p]) + + "."; + } + return {}; +} + +// Create new nodes in permuted order with new ids. +std::vector<Node*> permuteNodes(std::vector<std::size_t> const& permutation, + std::vector<Node*> const& nodes) +{ + std::vector<Node*> permuted_nodes; + for (std::size_t i = 0; i < permutation.size(); ++i) + { + auto const node_p_i = nodes[permutation[i]]; + permuted_nodes.push_back(new Node{node_p_i->getCoords(), i}); + } + return permuted_nodes; +} + +// Tests the nodes of two meshes and returns a string in case of error with +// message, otherwise a permutation vector as in compareNodes(). +boost::variant<std::vector<std::size_t>, std::string> compareNodeVectors( + std::vector<Node*> const& a, std::vector<Node*> const& b) +{ + if (a.size() != b.size()) + { + return "There are different number of nodes: " + + std::to_string(a.size()) + " and " + std::to_string(b.size()) + + "."; + } + + auto const compare_nodes_result = compareNodes(a, b); + if (compare_nodes_result.type() == typeid(std::string)) + { + return "Node comparison failed: " + + boost::get<std::string>(compare_nodes_result); + } + + return boost::get<std::vector<std::size_t>>(compare_nodes_result); +} + +TEST_F(ConvertToLinearMesh, GeneratedHexMeshRandomizedNodes) +{ + ASSERT_TRUE(linear_mesh != nullptr); + ASSERT_TRUE(quadratic_mesh != nullptr); + + auto const permutation = + generateRandomPermutationVector(quadratic_mesh->getNumberOfNodes()); + + // Create new nodes in permuted order with new ids. + auto const permuted_quadratic_nodes = + permuteNodes(permutation, quadratic_mesh->getNodes()); + + // + // Test the permuted nodes and compute the inverse permutation map. + // + auto const compare_nodes_result = compareNodeVectors( + quadratic_mesh->getNodes(), permuted_quadratic_nodes); + ASSERT_FALSE(compare_nodes_result.type() == typeid(std::string)) + << "Quadratic mesh nodes comparison with permuted quadratic nodes " + "failed.\n" + << boost::get<std::string>(compare_nodes_result); + + auto const& inverse_permutation = + boost::get<std::vector<std::size_t>>(compare_nodes_result); + { + auto const result = + inversePermutationIdentityTest(permutation, inverse_permutation); + ASSERT_TRUE(result == boost::none) + << "Quadratic mesh nodes permutation test failed: " << *result; + } + + auto clone_element_using_permuted_nodes = [&](Element* const e) { + Node** nodes = new Node*[e->getNumberOfNodes()]; + for (std::size_t i = 0; i < e->getNumberOfNodes(); ++i) + { + auto const q_i = e->getNode(i)->getID(); + auto const r = inverse_permutation[q_i]; + nodes[i] = permuted_quadratic_nodes[r]; + } + return e->clone(nodes, e->getID()); + }; + + // Create new elements in the same order, but using new nodes. + std::vector<Element*> new_quadratic_elements; + std::transform(begin(quadratic_mesh->getElements()), + end(quadratic_mesh->getElements()), + back_inserter(new_quadratic_elements), + clone_element_using_permuted_nodes); + + Mesh permuted_nodes_quadratic_mesh{"permuted_quadratic_mesh", + permuted_quadratic_nodes, + new_quadratic_elements}; + // + // Test the quadratic meshes first. + // + { + ASSERT_EQ(quadratic_mesh->getNumberOfElements(), + permuted_nodes_quadratic_mesh.getNumberOfElements()); + + auto const nelements = quadratic_mesh->getNumberOfElements(); + for (std::size_t i = 0; i < nelements; ++i) + { + auto const& element_a = *quadratic_mesh->getElement(i); + auto const& element_b = + *permuted_nodes_quadratic_mesh.getElement(i); + auto const elements_are_equal = equal(element_a, element_b); + ASSERT_TRUE(elements_are_equal == boost::none) + << *elements_are_equal << " For the element " << i << "."; + } + } + + { + auto const converted_mesh = convertToLinearMesh( + permuted_nodes_quadratic_mesh, "back_to_linear_mesh"); + + // + // Two meshes are equal if all of their nodes have exactly same + // coordinates. + // + auto const compare_nodes_result = compareNodeVectors( + linear_mesh->getNodes(), converted_mesh->getNodes()); + ASSERT_FALSE(compare_nodes_result.type() == typeid(std::string)) + << "Linear mesh nodes comparison with converted linear mesh nodes " + "failed.\n" + << boost::get<std::string>(compare_nodes_result); + // TODO (naumov) check inverse permutation, but it requires the reduced + // to base nodes forward permutation vector first. + + // Two meshes are equal if their elements share same nodes. + ASSERT_EQ(linear_mesh->getNumberOfElements(), + converted_mesh->getNumberOfElements()); + + auto const linear_mesh_nelements = linear_mesh->getNumberOfElements(); + for (std::size_t i = 0; i < linear_mesh_nelements; ++i) + { + auto const& element_a = *linear_mesh->getElement(i); + auto const& element_b = *converted_mesh->getElement(i); + auto const elements_are_equal = equal(element_a, element_b); + ASSERT_TRUE(elements_are_equal == boost::none) + << *elements_are_equal << " For the element " << i << "."; + } + } +} + +TEST_F(ConvertToLinearMesh, GeneratedHexMeshBackToLinear) +{ + ASSERT_TRUE(linear_mesh != nullptr); + ASSERT_TRUE(quadratic_mesh != nullptr); + + auto const converted_mesh = + convertToLinearMesh(*quadratic_mesh, "back_to_linear_mesh"); + + // + // Two meshes are equal if all of their nodes have exactly same coordinates. + // + auto const compare_nodes_result = + compareNodeVectors(linear_mesh->getNodes(), converted_mesh->getNodes()); + ASSERT_FALSE(compare_nodes_result.type() == typeid(std::string)) + << "Linear mesh nodes comparison with converted linear mesh nodes " + "failed.\n" + << boost::get<std::string>(compare_nodes_result); + + // Two meshes are equal if their elements share same nodes. + ASSERT_EQ(linear_mesh->getNumberOfElements(), + converted_mesh->getNumberOfElements()); + + auto const linear_mesh_nelements = linear_mesh->getNumberOfElements(); + for (std::size_t i = 0; i < linear_mesh_nelements; ++i) + { + auto const& linear_mesh_element = *linear_mesh->getElement(i); + auto const& converted_mesh_element = *converted_mesh->getElement(i); + auto const elements_are_equal = + equal(linear_mesh_element, converted_mesh_element); + ASSERT_TRUE(elements_are_equal == boost::none) + << *elements_are_equal << " For the element " << i << "."; + } +}