diff --git a/co_sim_io/impl/macros.hpp b/co_sim_io/impl/macros.hpp index 32894020..e2ec81e2 100644 --- a/co_sim_io/impl/macros.hpp +++ b/co_sim_io/impl/macros.hpp @@ -33,7 +33,7 @@ the code where the CoSimIO is included #endif #ifndef CO_SIM_IO_ERROR_IF_NOT - #define CO_SIM_IO_ERROR_IF_NOT(conditional) if (!conditional) CO_SIM_IO_ERROR + #define CO_SIM_IO_ERROR_IF_NOT(conditional) if (!(conditional)) CO_SIM_IO_ERROR #endif #ifndef CO_SIM_IO_INFO diff --git a/co_sim_io/impl/model_part.hpp b/co_sim_io/impl/model_part.hpp new file mode 100644 index 00000000..0c92bdac --- /dev/null +++ b/co_sim_io/impl/model_part.hpp @@ -0,0 +1,240 @@ +// ______ _____ _ ________ +// / ____/___ / ___/(_)___ ___ / _/ __ | +// / / / __ \\__ \/ / __ `__ \ / // / / / +// / /___/ /_/ /__/ / / / / / / // // /_/ / +// \____/\____/____/_/_/ /_/ /_/___/\____/ +// Kratos CoSimulationApplication +// +// License: BSD License, see license.txt +// +// Main authors: Philipp Bucher (https://github.com/philbucher) +// + +#ifndef CO_SIM_IO_MODEL_PART_H_INCLUDED +#define CO_SIM_IO_MODEL_PART_H_INCLUDED + +/* This file contains the implementation of th CoSimIO::ModelPart +It serves as a data container when exchanging data +Also it is used in order to be consistent with Kratos to reduce compatibility problems +This is a simplified version of Kratos::ModelPart +see https://github.com/KratosMultiphysics/Kratos/blob/master/kratos/includes/model_part.h +*/ + +// System includes +#include +#include +#include +#include +#include + +// Project includes +#include "define.hpp" +#include "macros.hpp" + +namespace CoSimIO { +class Node +{ +public: + Node( + const IdType I_Id, + const double I_X, + const double I_Y, + const double I_Z) + : mId(I_Id), + mX(I_X), + mY(I_Y), + mZ(I_Z) + { + CO_SIM_IO_ERROR_IF(I_Id < 1) << "Id must be >= 1!" << std::endl; + } + + Node( + const IdType I_Id, + const CoordinatesType& I_Coordinates) + : Node(I_Id, I_Coordinates[0], I_Coordinates[1], I_Coordinates[2]) + { } + + // delete copy and assignment CTor + Node(const Node&) = delete; + Node& operator=(Node const&) = delete; + + IdType Id() const { return mId; } + double X() const { return mX; } + double Y() const { return mY; } + double Z() const { return mZ; } + CoordinatesType Coordinates() const { return {mX, mY, mZ}; } + +private: + IdType mId; + double mX; + double mY; + double mZ; +}; + +class Element +{ +public: + using ElementType = std::size_t; + using NodesContainerType = std::vector; + using ConnectivitiesType = std::vector; + + Element( + const IdType I_Id, + const ElementType I_Type, + const NodesContainerType& I_Nodes) + : mId(I_Id), + mType(I_Type), + mNodes(I_Nodes) + { + CO_SIM_IO_ERROR_IF(I_Id < 1) << "Id must be >= 1!" << std::endl; + CO_SIM_IO_ERROR_IF(NumberOfNodes() < 1) << "No nodes were passed!" << std::endl; + } + + // delete copy and assignment CTor + Element(const Element&) = delete; + Element& operator=(Element const&) = delete; + + IdType Id() const { return mId; } + ElementType Type() const { return mType; } + std::size_t NumberOfNodes() const { return mNodes.size(); } + NodesContainerType::const_iterator NodesBegin() const { return mNodes.begin(); } + NodesContainerType::const_iterator NodesEnd() const { return mNodes.end(); } + +private: + IdType mId; + ElementType mType; + NodesContainerType mNodes; +}; + +class ModelPart +{ +public: + + using NodePointerType = std::shared_ptr; // TODO switch to intrusive_ptr + using ElementPointerType = std::shared_ptr; // TODO switch to intrusive_ptr + using NodesContainerType = std::vector; + using ElementsContainerType = std::vector; + + explicit ModelPart(const std::string& I_Name) : mName(I_Name) + { + CO_SIM_IO_ERROR_IF(I_Name.empty()) << "Please don't use empty names (\"\") when creating a ModelPart" << std::endl; + CO_SIM_IO_ERROR_IF_NOT(I_Name.find(".") == std::string::npos) << "Please don't use names containing (\".\") when creating a ModelPart (used in \"" << I_Name << "\")" << std::endl; + } + + // delete copy and assignment CTor + ModelPart(const ModelPart&) = delete; + ModelPart& operator=(ModelPart const&) = delete; + + std::string Name() const { return mName; } + std::size_t NumberOfNodes() const { return mNodes.size(); } + std::size_t NumberOfElements() const { return mElements.size(); } + + Node& CreateNewNode( + const IdType I_Id, + const double I_X, + const double I_Y, + const double I_Z) + { + CO_SIM_IO_ERROR_IF(HasNode(I_Id)) << "The Node with Id " << I_Id << " exists already!" << std::endl; + + mNodes.push_back(std::make_shared(I_Id, I_X, I_Y, I_Z)); + return *(mNodes.back()); + } + + Element& CreateNewElement( + const IdType I_Id, + const Element::ElementType I_Type, + const Element::ConnectivitiesType& I_Connectivities) + { + CO_SIM_IO_ERROR_IF(HasElement(I_Id)) << "The Element with Id " << I_Id << " exists already!" << std::endl; + + Element::NodesContainerType nodes; + nodes.reserve(I_Connectivities.size()); + for (const IdType node_id : I_Connectivities) { + nodes.push_back(&GetNode(node_id)); + } + mElements.push_back(std::make_shared(I_Id, I_Type, nodes)); + return *(mElements.back()); + } + + NodesContainerType::const_iterator NodesBegin() const { return mNodes.begin(); } + ElementsContainerType::const_iterator ElementsBegin() const { return mElements.begin(); } + + NodesContainerType::const_iterator NodesEnd() const { return mNodes.end(); } + ElementsContainerType::const_iterator ElementsEnd() const { return mElements.end(); } + + Node& GetNode(const IdType I_Id) + { + auto it_node = FindNode(I_Id); + CO_SIM_IO_ERROR_IF(it_node == mNodes.end()) << "Node with Id " << I_Id << " does not exist!" << std::endl; + return **it_node; + } + + const Node& GetNode(const IdType I_Id) const + { + auto it_node = FindNode(I_Id); + CO_SIM_IO_ERROR_IF(it_node == mNodes.end()) << "Node with Id " << I_Id << " does not exist!" << std::endl; + return **it_node; + } + + Element& GetElement(const IdType I_Id) + { + auto it_elem = FindElement(I_Id); + CO_SIM_IO_ERROR_IF(it_elem == mElements.end()) << "Element with Id " << I_Id << " does not exist!" << std::endl; + return **it_elem; + } + + const Element& GetElement(const IdType I_Id) const + { + auto it_elem = FindElement(I_Id); + CO_SIM_IO_ERROR_IF(it_elem == mElements.end()) << "Element with Id " << I_Id << " does not exist!" << std::endl; + return **it_elem; + } + +private: + std::string mName; + NodesContainerType mNodes; + ElementsContainerType mElements; + + NodesContainerType::const_iterator FindNode(const IdType I_Id) const + { + return std::find_if( + mNodes.begin(), mNodes.end(), + [I_Id](const NodePointerType& rp_node) { return rp_node->Id() == I_Id;}); + } + + NodesContainerType::iterator FindNode(const IdType I_Id) + { + return std::find_if( + mNodes.begin(), mNodes.end(), + [I_Id](const NodePointerType& rp_node) { return rp_node->Id() == I_Id;}); + } + + ElementsContainerType::const_iterator FindElement(const IdType I_Id) const + { + return std::find_if( + mElements.begin(), mElements.end(), + [I_Id](const ElementPointerType& rp_elem) { return rp_elem->Id() == I_Id;}); + } + + ElementsContainerType::iterator FindElement(const IdType I_Id) + { + return std::find_if( + mElements.begin(), mElements.end(), + [I_Id](const ElementPointerType& rp_elem) { return rp_elem->Id() == I_Id;}); + } + + bool HasNode(const IdType I_Id) const + { + return FindNode(I_Id) != mNodes.end(); + } + + bool HasElement(const IdType I_Id) const + { + return FindElement(I_Id) != mElements.end(); + } +}; + +} //namespace CoSimIO + +#endif // CO_SIM_IO_MODEL_PART_H_INCLUDED diff --git a/tests/co_sim_io/impl/test_model_part.cpp b/tests/co_sim_io/impl/test_model_part.cpp new file mode 100644 index 00000000..b6359f07 --- /dev/null +++ b/tests/co_sim_io/impl/test_model_part.cpp @@ -0,0 +1,309 @@ +// ______ _____ _ ________ +// / ____/___ / ___/(_)___ ___ / _/ __ | +// / / / __ \\__ \/ / __ `__ \ / // / / / +// / /___/ /_/ /__/ / / / / / / // // /_/ / +// \____/\____/____/_/_/ /_/ /_/___/\____/ +// Kratos CoSimulationApplication +// +// License: BSD License, see license.txt +// +// Main authors: Philipp Bucher (https://github.com/philbucher) +// + +// Project includes +#include "co_sim_io_testing.hpp" +#include "impl/model_part.hpp" + + +namespace CoSimIO { + +TEST_SUITE("ModelPart") { + +TEST_CASE("node") +{ + const std::array coords = {1.0, -2.7, 9.44}; + const int id = 16; + + std::unique_ptr p_node; + + SUBCASE("from_coords") + { + p_node = CoSimIO::make_unique(id, coords[0], coords[1], coords[2]); + } + + SUBCASE("from_coords_array") + { + p_node = CoSimIO::make_unique(id, coords); + } + + CHECK_EQ(p_node->Id(), id); + + CHECK_EQ(p_node->X(), doctest::Approx(coords[0])); + CHECK_EQ(p_node->Y(), doctest::Approx(coords[1])); + CHECK_EQ(p_node->Z(), doctest::Approx(coords[2])); + + for (std::size_t i=0; i<3; ++i) { + CAPTURE(i); // log the current input data (done manually as not fully supported yet by doctest) + CHECK_EQ(p_node->Coordinates()[i], doctest::Approx(coords[i])); + } +} + +TEST_CASE("node_negative_id") +{ + const std::array coords = {1.0, -2.7, 9.44}; + const int id = -16; + + SUBCASE("from_coords") + { + CHECK_THROWS_WITH(Node(id, coords[0], coords[1], coords[2]), "Error: "); // TODO find a better way of testing this + } + + SUBCASE("from_coords_array") + { + CHECK_THROWS_WITH(Node(id, coords), "Error: "); // TODO find a better way of testing this + } +} + +TEST_CASE("element_basics") +{ + const int id = 33; + const std::size_t type = 5; + + Node node(1, 0,0,0); + + Element element(id, type, {&node}); + + CHECK_EQ(element.Id(), id); + CHECK_EQ(element.Type(), type); + CHECK_EQ(element.NumberOfNodes(), 1); +} + +TEST_CASE("element_checks") +{ + const int id = -33; + const std::size_t type = 5; + Node node(1, 0,0,0); + + SUBCASE("negative_id") + { + CHECK_THROWS_WITH(Element element(id, type, {&node}), "Error: "); // TODO find a better way of testing this + } + + SUBCASE("no_nodes") + { + CHECK_THROWS_WITH(Element element(1, type, {}), "Error: "); // TODO find a better way of testing this + } +} + +TEST_CASE("element_nodes") +{ + const int id = 33; + const std::size_t type = 5; + + const int node_ids[] = {2, 159, 61}; + + const std::array dummy_coords = {0,0,0}; + Node node_1(node_ids[0], dummy_coords); + Node node_2(node_ids[1], dummy_coords); + Node node_3(node_ids[2], dummy_coords); + + Element element(id, type, {&node_1, &node_2, &node_3}); + + CHECK_EQ(element.Id(), id); + CHECK_EQ(element.Type(), type); + CHECK_EQ(element.NumberOfNodes(), 3); + + std::size_t counter=0; + for (Element::NodesContainerType::const_iterator node_it=element.NodesBegin(); node_it!=element.NodesEnd(); ++node_it) { + CAPTURE(counter); // log the current input data (done manually as not fully supported yet by doctest) + CHECK_EQ((*node_it)->Id(), node_ids[counter]); + counter++; + } +} + +TEST_CASE("model_part_basics") +{ + ModelPart model_part("for_test"); + + CHECK_EQ(model_part.Name(), "for_test"); + CHECK_EQ(model_part.NumberOfNodes(), 0); + CHECK_EQ(model_part.NumberOfElements(), 0); +} + +TEST_CASE("model_part_invalid_names") +{ + CHECK_THROWS_WITH(ModelPart model_part(""), "Error: "); // TODO find a better way of testing this + CHECK_THROWS_WITH(ModelPart model_part("my_name.ssss"), "Error: "); // TODO find a better way of testing this +} + +TEST_CASE("model_part_create_new_node") +{ + ModelPart model_part("for_test"); + + const int node_id = 691; + const std::array node_coords = {1.0, -2.7, 9.44}; + const auto& new_node = model_part.CreateNewNode(node_id, node_coords[0], node_coords[1], node_coords[2]); + CHECK_EQ(model_part.NumberOfNodes(), 1); + + CHECK_EQ(new_node.Id(), node_id); + for (std::size_t i=0; i<3; ++i) { + CAPTURE(i); // log the current input data (done manually as not fully supported yet by doctest) + CHECK_EQ(new_node.Coordinates()[i], doctest::Approx(node_coords[i])); + } +} + +TEST_CASE("model_part_create_new_nodes") +{ + ModelPart model_part("for_test"); + + const int node_id = 691; + const std::array node_coords = {1.0, -2.7, 9.44}; + const auto& new_node = model_part.CreateNewNode(node_id, node_coords[0], node_coords[1], node_coords[2]); + CHECK_EQ(model_part.NumberOfNodes(), 1); + + model_part.CreateNewNode(node_id+1, node_coords[0], node_coords[1], node_coords[2]); + model_part.CreateNewNode(node_id+2, node_coords[0], node_coords[1], node_coords[2]); + CHECK_EQ(model_part.NumberOfNodes(), 3); + + CHECK_EQ(new_node.Id(), node_id); + for (std::size_t i=0; i<3; ++i) { + CAPTURE(i); // log the current input data (done manually as not fully supported yet by doctest) + CHECK_EQ(new_node.Coordinates()[i], doctest::Approx(node_coords[i])); + } +} + +TEST_CASE("model_part_create_new_node_twice") +{ + ModelPart model_part("for_test"); + + model_part.CreateNewNode(1, 0,0,0); + REQUIRE_EQ(model_part.NumberOfNodes(), 1); + + CHECK_THROWS_WITH(model_part.CreateNewNode(1, 0,0,0), "Error: "); // TODO find a better way of testing this +} + +TEST_CASE("model_part_get_node") +{ + ModelPart model_part("for_test"); + + const int node_id = 691; + const std::array node_coords = {1.0, -2.7, 9.44}; + + model_part.CreateNewNode(node_id, node_coords[0], node_coords[1], node_coords[2]); + + REQUIRE_EQ(model_part.NumberOfNodes(), 1); + + SUBCASE("existing") + { + const auto& r_node = model_part.GetNode(node_id); + + CHECK_EQ(r_node.Id(), node_id); + for (std::size_t i=0; i<3; ++i) { + CAPTURE(i); // log the current input data (done manually as not fully supported yet by doctest) + CHECK_EQ(r_node.Coordinates()[i], doctest::Approx(node_coords[i])); + } + } + + SUBCASE("non_existing") + { + CHECK_THROWS_WITH(model_part.GetNode(node_id+1), "Error: "); // TODO find a better way of testing this + } +} + +TEST_CASE("model_part_create_new_element") +{ + ModelPart model_part("for_test"); + + const int node_id = 691; + const std::array node_coords = {1.0, -2.7, 9.44}; + model_part.CreateNewNode(node_id, node_coords[0], node_coords[1], node_coords[2]); + CHECK_EQ(model_part.NumberOfNodes(), 1); + + const int elem_id = 47; + const std::size_t type = 5; + + const auto& new_elem = model_part.CreateNewElement(elem_id, type, {node_id}); + + CHECK_EQ(new_elem.Id(), elem_id); + CHECK_EQ(new_elem.Type(), type); + REQUIRE_EQ(new_elem.NumberOfNodes(), 1); + + for (auto node_it=new_elem.NodesBegin(); node_it!=new_elem.NodesEnd(); ++node_it) { + CHECK_EQ((*node_it)->Id(), node_id); + for (std::size_t i=0; i<3; ++i) { + CAPTURE(i); // log the current input data (done manually as not fully supported yet by doctest) + CHECK_EQ((*node_it)->Coordinates()[i], doctest::Approx(node_coords[i])); + } + } +} + +TEST_CASE("model_part_create_new_elements") +{ + ModelPart model_part("for_test"); + + const int node_ids[] = {2, 159, 61}; + const std::array node_coords = {1.0, -2.7, 9.44}; + model_part.CreateNewNode(node_ids[0], node_coords[0], node_coords[1], node_coords[2]); + model_part.CreateNewNode(node_ids[1], node_coords[1], node_coords[2], node_coords[0]); + model_part.CreateNewNode(node_ids[2], node_coords[2], node_coords[0], node_coords[1]); + + CHECK_EQ(model_part.NumberOfNodes(), 3); + + const int elem_ids[] = {21, 19, 961}; + const std::size_t elem_types[] = {5, 5, 9}; + const std::size_t elem_num_nodes[] = {1,1,2}; + + model_part.CreateNewElement(elem_ids[0], elem_types[0], {node_ids[0]}); + model_part.CreateNewElement(elem_ids[1], elem_types[1], {node_ids[1]}); + model_part.CreateNewElement(elem_ids[2], elem_types[2], {node_ids[1], node_ids[2]}); + + REQUIRE_EQ(model_part.NumberOfElements(), 3); + + std::size_t counter = 0; + for (auto elem_it=model_part.ElementsBegin(); elem_it!=model_part.ElementsEnd(); ++elem_it) { + CHECK_EQ((*elem_it)->Id(), elem_ids[counter]); + CHECK_EQ((*elem_it)->Type(), elem_types[counter]); + REQUIRE_EQ((*elem_it)->NumberOfNodes(), elem_num_nodes[counter]); + counter++; + } +} + +TEST_CASE("model_part_create_new_element_twice") +{ + ModelPart model_part("for_test"); + + model_part.CreateNewNode(1, 0,0,0); + model_part.CreateNewElement(1, 5, {1}); + REQUIRE_EQ(model_part.NumberOfElements(), 1); + + CHECK_THROWS_WITH(model_part.CreateNewElement(1, 5, {1}), "Error: "); // TODO find a better way of testing this +} + +TEST_CASE("model_part_get_element") +{ + ModelPart model_part("for_test"); + + model_part.CreateNewNode(1, 0,0,0); + + const int elem_id = 6; + model_part.CreateNewElement(elem_id, 5, {1}); + REQUIRE_EQ(model_part.NumberOfElements(), 1); + + SUBCASE("existing") + { + const auto& r_elem = model_part.GetElement(elem_id); + + CHECK_EQ(r_elem.Id(), elem_id); + CHECK_EQ(r_elem.Type(), 5); + CHECK_EQ(r_elem.NumberOfNodes(), 1); + } + + SUBCASE("non_existing") + { + CHECK_THROWS_WITH(model_part.GetElement(elem_id+1), "Error: "); // TODO find a better way of testing this + } +} + +} // TEST_SUITE("ModelPart") + +} // namespace CoSimIO