diff --git a/kratos/CMakeLists.txt b/kratos/CMakeLists.txt index 1f2c030f3168..76305ac309b1 100644 --- a/kratos/CMakeLists.txt +++ b/kratos/CMakeLists.txt @@ -7,7 +7,7 @@ else(${USE_TRIANGLE_NONFREE_TPL} MATCHES ON ) endif(${USE_TRIANGLE_NONFREE_TPL} MATCHES ON ) include_directories( ${KRATOS_SOURCE_DIR}/external_libraries/tinyexpr ) -SET(LIST_FOLDER sources conditions constraints containers elements factories geometries includes integration linear_solvers mappers modeler modified_shape_functions operations processes controllers response_functions solving_strategies spaces spatial_containers utilities) +SET(LIST_FOLDER sources conditions constraints containers elements factories geometries includes integration linear_solvers mappers modeler modified_shape_functions operations processes controllers response_functions solving_strategies spaces spatial_containers utilities tensor_adaptors) SET(LIST_FUTURE future) SET(LIST_LEGACY legacy) diff --git a/kratos/python/add_tensor_adaptors_to_python.cpp b/kratos/python/add_tensor_adaptors_to_python.cpp index edcc149b7c2f..87ba1413f3b6 100644 --- a/kratos/python/add_tensor_adaptors_to_python.cpp +++ b/kratos/python/add_tensor_adaptors_to_python.cpp @@ -14,26 +14,27 @@ #include // External includes -#include -#include #include +#include +#include // Project includes #include "includes/define_python.h" #include "includes/model_part.h" +#include "python/numpy_utils.h" #include "utilities/container_io_utils.h" #include "utilities/parallel_utilities.h" -#include "python/numpy_utils.h" // Tensor adaptors -#include "tensor_adaptors/tensor_adaptor.h" #include "tensor_adaptors/combined_tensor_adaptor.h" -#include "tensor_adaptors/historical_variable_tensor_adaptor.h" -#include "tensor_adaptors/variable_tensor_adaptor.h" -#include "tensor_adaptors/flags_tensor_adaptor.h" +#include "tensor_adaptors/connectivity_ids_tensor_adaptor.h" #include "tensor_adaptors/equation_ids_tensor_adaptor.h" +#include "tensor_adaptors/flags_tensor_adaptor.h" #include "tensor_adaptors/gauss_point_variable_tensor_adaptor.h" +#include "tensor_adaptors/historical_variable_tensor_adaptor.h" #include "tensor_adaptors/node_position_tensor_adaptor.h" +#include "tensor_adaptors/tensor_adaptor.h" +#include "tensor_adaptors/variable_tensor_adaptor.h" // Include base h #include "add_tensor_adaptors_to_python.h" @@ -42,116 +43,244 @@ namespace Kratos::Python { namespace Detail { -template -void AddBaseTensorAdaptor( - pybind11::module& rModule, - const std::string& rName) -{ - // add the base tensor adaptor - using tensor_adaptor = TensorAdaptor; - pybind11::class_(rModule, (rName + "Adaptor").c_str()) - .def(pybind11::init::Pointer, const bool>(), pybind11::arg("container"), pybind11::arg("nd_data"), pybind11::arg("copy") = true) - .def(pybind11::init(), pybind11::arg("tensor_adaptor"), pybind11::arg("copy") = true) - .def("Check", &tensor_adaptor::Check) - .def("CollectData", &tensor_adaptor::CollectData) - .def("StoreData", &tensor_adaptor::StoreData) - .def("GetContainer", &tensor_adaptor::GetContainer) - .def("HasContainer", &tensor_adaptor::HasContainer) - .def("Shape", &tensor_adaptor::Shape) - .def("DataShape", &tensor_adaptor::DataShape) - .def("Size", &tensor_adaptor::Size) - .def("__str__", PrintObject) - .def("ViewData", [](tensor_adaptor& rSelf){ return GetPybindArray(*rSelf.pGetStorage()); }) - .def("SetData", [](tensor_adaptor& rSelf, const pybind11::array& rArray) { SetPybindArray(*rSelf.pGetStorage(), rArray); }, pybind11::arg("array").noconvert()) - .def_property("data", - [](tensor_adaptor& rSelf){ return GetPybindArray(*rSelf.pGetStorage()); }, - pybind11::cpp_function( - [](tensor_adaptor& rSelf, const pybind11::array& rArray) { SetPybindArray(*rSelf.pGetStorage(), rArray); }, - pybind11::arg("self"), - // no convert makes sure that the numpy arrays are - // not converted, hence nothing will be copied. numpy - // array will be passed as it is to the SetPybindArray - // method. - pybind11::arg("array").noconvert()) - ) - ; +template +void AddBaseTensorAdaptor(pybind11::module &rModule, const std::string &rName) { + // add the base tensor adaptor + using tensor_adaptor = TensorAdaptor; + pybind11::class_( + rModule, (rName + "Adaptor").c_str()) + .def(pybind11::init::Pointer, const bool>(), + pybind11::arg("container"), pybind11::arg("nd_data"), + pybind11::arg("copy") = true) + .def(pybind11::init(), + pybind11::arg("tensor_adaptor"), pybind11::arg("copy") = true) + .def("Check", &tensor_adaptor::Check) + .def("CollectData", &tensor_adaptor::CollectData) + .def("StoreData", &tensor_adaptor::StoreData) + .def("GetContainer", &tensor_adaptor::GetContainer) + .def("HasContainer", &tensor_adaptor::HasContainer) + .def("Shape", &tensor_adaptor::Shape) + .def("DataShape", &tensor_adaptor::DataShape) + .def("Size", &tensor_adaptor::Size) + .def("__str__", PrintObject) + .def("ViewData", + [](tensor_adaptor &rSelf) { + return GetPybindArray(*rSelf.pGetStorage()); + }) + .def( + "SetData", + [](tensor_adaptor &rSelf, const pybind11::array &rArray) { + SetPybindArray(*rSelf.pGetStorage(), rArray); + }, + pybind11::arg("array").noconvert()) + .def_property( + "data", + [](tensor_adaptor &rSelf) { + return GetPybindArray(*rSelf.pGetStorage()); + }, + pybind11::cpp_function( + [](tensor_adaptor &rSelf, const pybind11::array &rArray) { + SetPybindArray(*rSelf.pGetStorage(), rArray); + }, + pybind11::arg("self"), + // no convert makes sure that the numpy arrays are + // not converted, hence nothing will be copied. numpy + // array will be passed as it is to the SetPybindArray + // method. + pybind11::arg("array").noconvert())); } -template -void AddCombinedTensorAdaptor( - pybind11::module& rModule, - const std::string& rName) -{ - using combined_ta_type = CombinedTensorAdaptor; - pybind11::class_(rModule, rName.c_str()) - .def(pybind11::init(), pybind11::arg("list_of_tensor_adaptors"), pybind11::arg("perform_collect_data_recursively") = true, pybind11::arg("perform_store_data_recursively") = true) // reveling ctor - .def(pybind11::init(), pybind11::arg("list_of_tensor_adaptors"), pybind11::arg("axis"), pybind11::arg("perform_collect_data_recursively") = true, pybind11::arg("perform_store_data_recursively") = true) // axis based ctor - .def(pybind11::init(), pybind11::arg("list_of_tensor_adaptors"), pybind11::arg("perform_collect_data_recursively") = true, pybind11::arg("perform_store_data_recursively") = true, pybind11::arg("copy") = true) - .def("GetTensorAdaptors", &combined_ta_type::GetTensorAdaptors) - ; +template +void AddCombinedTensorAdaptor(pybind11::module &rModule, + const std::string &rName) { + using combined_ta_type = CombinedTensorAdaptor; + pybind11::class_(rModule, rName.c_str()) + .def(pybind11::init< + const typename combined_ta_type::TensorAdaptorVectorType &, + const bool, const bool>(), + pybind11::arg("list_of_tensor_adaptors"), + pybind11::arg("perform_collect_data_recursively") = true, + pybind11::arg("perform_store_data_recursively") = + true) // reveling ctor + .def(pybind11::init< + const typename combined_ta_type::TensorAdaptorVectorType &, + const unsigned int, const bool, const bool>(), + pybind11::arg("list_of_tensor_adaptors"), pybind11::arg("axis"), + pybind11::arg("perform_collect_data_recursively") = true, + pybind11::arg("perform_store_data_recursively") = + true) // axis based ctor + .def(pybind11::init(), + pybind11::arg("list_of_tensor_adaptors"), + pybind11::arg("perform_collect_data_recursively") = true, + pybind11::arg("perform_store_data_recursively") = true, + pybind11::arg("copy") = true) + .def("GetTensorAdaptors", &combined_ta_type::GetTensorAdaptors); } } // namespace Detail -void AddTensorAdaptorsToPython(pybind11::module& m) -{ - namespace py = pybind11; - - auto tensor_adaptor_sub_module = m.def_submodule("TensorAdaptors"); - Detail::AddBaseTensorAdaptor(tensor_adaptor_sub_module, "BoolTensor"); - Detail::AddBaseTensorAdaptor(tensor_adaptor_sub_module, "IntTensor"); - Detail::AddBaseTensorAdaptor(tensor_adaptor_sub_module, "DoubleTensor"); - - Detail::AddCombinedTensorAdaptor(tensor_adaptor_sub_module, "BoolCombinedTensorAdaptor"); - Detail::AddCombinedTensorAdaptor(tensor_adaptor_sub_module, "IntCombinedTensorAdaptor"); - Detail::AddCombinedTensorAdaptor(tensor_adaptor_sub_module, "DoubleCombinedTensorAdaptor"); - - py::class_(tensor_adaptor_sub_module, "HistoricalVariableTensorAdaptor") - .def(py::init(), py::arg("container"), py::arg("variable"), py::arg("step_index") = 0) - .def(py::init&, const int>(), py::arg("container"), py::arg("variable"), py::arg("data_shape"), py::arg("step_index") = 0) - .def(py::init(), py::arg("tensor_adaptor"), py::arg("variable"), py::arg("step_index") = 0, py::arg("copy") = true) - ; - - py::class_(tensor_adaptor_sub_module, "VariableTensorAdaptor") - .def(py::init(), py::arg("container"), py::arg("variable")) - .def(py::init&>(), py::arg("container"), py::arg("variable"), py::arg("data_shape")) - .def(py::init(), py::arg("container"), py::arg("variable")) - .def(py::init&>(), py::arg("container"), py::arg("variable"), py::arg("data_shape")) - .def(py::init(), py::arg("container"), py::arg("variable")) - .def(py::init&>(), py::arg("container"), py::arg("variable"), py::arg("data_shape")) - .def(py::init(), py::arg("container"), py::arg("variable")) - .def(py::init&>(), py::arg("container"), py::arg("variable"), py::arg("data_shape")) - .def(py::init(), py::arg("container"), py::arg("variable")) - .def(py::init&>(), py::arg("container"), py::arg("variable"), py::arg("data_shape")) - .def(py::init(), py::arg("container"), py::arg("variable")) - .def(py::init&>(), py::arg("container"), py::arg("variable"), py::arg("data_shape")) - .def(py::init(), py::arg("tensor_adaptor"), py::arg("variable"), py::arg("copy") = true) - ; - - py::class_(tensor_adaptor_sub_module, "GaussPointVariableTensorAdaptor") - .def(py::init(), py::arg("container"), py::arg("variable"), py::arg("process_info")) - .def(py::init(), py::arg("container"), py::arg("variable"), py::arg("process_info")) - .def(py::init(), py::arg("tensor_adaptor"), py::arg("variable"), py::arg("process_info"), py::arg("copy") = true) - ; - - py::class_(tensor_adaptor_sub_module, "EquationIdsTensorAdaptor") - .def(py::init(), py::arg("container"), py::arg("process_info")) - .def(py::init(), py::arg("container"), py::arg("process_info")) - .def(py::init(), py::arg("tensor_adaptor"), py::arg("process_info"), py::arg("copy") = true) - ; - - py::class_(tensor_adaptor_sub_module, "FlagsTensorAdaptor") - .def(py::init(), py::arg("container"), py::arg("flag")) - .def(py::init(), py::arg("container"), py::arg("flag")) - .def(py::init(), py::arg("container"), py::arg("flag")) - .def(py::init(), py::arg("tensor_adaptor"), py::arg("flag"), py::arg("copy") = true) - ; - - py::class_(tensor_adaptor_sub_module, "NodePositionTensorAdaptor") - .def(py::init(), py::arg("container"), py::arg("configuration")) - .def(py::init&>(), py::arg("container"), py::arg("configuration"), py::arg("data_shape")) - .def(py::init(), py::arg("tensor_adaptor"), py::arg("configuration"), py::arg("copy") = true) - ; +void AddTensorAdaptorsToPython(pybind11::module &m) { + namespace py = pybind11; + + auto tensor_adaptor_sub_module = m.def_submodule("TensorAdaptors"); + Detail::AddBaseTensorAdaptor(tensor_adaptor_sub_module, "BoolTensor"); + Detail::AddBaseTensorAdaptor(tensor_adaptor_sub_module, "IntTensor"); + Detail::AddBaseTensorAdaptor(tensor_adaptor_sub_module, + "DoubleTensor"); + + Detail::AddCombinedTensorAdaptor(tensor_adaptor_sub_module, + "BoolCombinedTensorAdaptor"); + Detail::AddCombinedTensorAdaptor(tensor_adaptor_sub_module, + "IntCombinedTensorAdaptor"); + Detail::AddCombinedTensorAdaptor(tensor_adaptor_sub_module, + "DoubleCombinedTensorAdaptor"); + + py::class_( + tensor_adaptor_sub_module, "HistoricalVariableTensorAdaptor") + .def(py::init(), + py::arg("container"), py::arg("variable"), py::arg("step_index") = 0) + .def(py::init &, const int>(), + py::arg("container"), py::arg("variable"), py::arg("data_shape"), + py::arg("step_index") = 0) + .def(py::init(), + py::arg("tensor_adaptor"), py::arg("variable"), + py::arg("step_index") = 0, py::arg("copy") = true); + + py::class_(tensor_adaptor_sub_module, + "VariableTensorAdaptor") + .def(py::init(), + py::arg("container"), py::arg("variable")) + .def(py::init &>(), + py::arg("container"), py::arg("variable"), py::arg("data_shape")) + .def(py::init(), + py::arg("container"), py::arg("variable")) + .def(py::init &>(), + py::arg("container"), py::arg("variable"), py::arg("data_shape")) + .def(py::init(), + py::arg("container"), py::arg("variable")) + .def(py::init &>(), + py::arg("container"), py::arg("variable"), py::arg("data_shape")) + .def(py::init(), + py::arg("container"), py::arg("variable")) + .def(py::init &>(), + py::arg("container"), py::arg("variable"), py::arg("data_shape")) + .def(py::init(), + py::arg("container"), py::arg("variable")) + .def(py::init &>(), + py::arg("container"), py::arg("variable"), py::arg("data_shape")) + .def(py::init(), + py::arg("container"), py::arg("variable")) + .def(py::init &>(), + py::arg("container"), py::arg("variable"), py::arg("data_shape")) + .def(py::init(), + py::arg("tensor_adaptor"), py::arg("variable"), + py::arg("copy") = true); + + py::class_( + tensor_adaptor_sub_module, "GaussPointVariableTensorAdaptor") + .def(py::init(), + py::arg("container"), py::arg("variable"), py::arg("process_info")) + .def(py::init(), + py::arg("container"), py::arg("variable"), py::arg("process_info")) + .def(py::init(), + py::arg("tensor_adaptor"), py::arg("variable"), + py::arg("process_info"), py::arg("copy") = true); + + py::class_(tensor_adaptor_sub_module, + "EquationIdsTensorAdaptor") + .def(py::init(), + py::arg("container"), py::arg("process_info")) + .def(py::init(), + py::arg("container"), py::arg("process_info")) + .def(py::init(), + py::arg("tensor_adaptor"), py::arg("process_info"), + py::arg("copy") = true); + + py::class_(tensor_adaptor_sub_module, + "FlagsTensorAdaptor") + .def(py::init(), + py::arg("container"), py::arg("flag")) + .def(py::init(), + py::arg("container"), py::arg("flag")) + .def(py::init(), + py::arg("container"), py::arg("flag")) + .def(py::init(), + py::arg("tensor_adaptor"), py::arg("flag"), py::arg("copy") = true); + + py::class_(tensor_adaptor_sub_module, + "NodePositionTensorAdaptor") + .def(py::init(), + py::arg("container"), py::arg("configuration")) + .def( + py::init &>(), + py::arg("container"), py::arg("configuration"), py::arg("data_shape")) + .def(py::init(), + py::arg("tensor_adaptor"), py::arg("configuration"), + py::arg("copy") = true); + + py::class_( + tensor_adaptor_sub_module, "ConnectivityIdsTensorAdaptor") + .def(py::init(), + py::arg("container")) + .def(py::init(), + py::arg("container")) + .def(py::init(), + py::arg("container")) + .def(py::init(), + py::arg("tensor_adaptor"), py::arg("copy") = true); } } // namespace Kratos::Python. \ No newline at end of file diff --git a/kratos/tensor_adaptors/connectivity_ids_tensor_adaptor.cpp b/kratos/tensor_adaptors/connectivity_ids_tensor_adaptor.cpp new file mode 100644 index 000000000000..3c1fe9fd6444 --- /dev/null +++ b/kratos/tensor_adaptors/connectivity_ids_tensor_adaptor.cpp @@ -0,0 +1,156 @@ +// | / | +// ' / __| _` | __| _ \ __| +// . \ | ( | | ( |\__ ` +// _|\_\_| \__,_|\__|\___/ ____/ +// Multi-Physics +// +// License: BSD License +// Kratos default license: kratos/license.txt +// +// Main authors: Riccardo Rossi +// + +// System includes +#include +#include + +// External includes + +// Project includes +#include "tensor_adaptors/connectivity_ids_tensor_adaptor.h" +#include "tensor_adaptors/tensor_adaptor_utils.h" +#include "utilities/parallel_utilities.h" + +namespace Kratos { + +namespace { + +template constexpr bool IsSupportedContainer() { + return IsInList; +} + +template const auto &GetGeometry(const TEntity &rEntity) { + if constexpr (std::is_same_v>) { + return rEntity; + } else { + return rEntity.GetGeometry(); + } +} + +template +DenseVector GetShape(const TContainerType &rContainer) { + if (rContainer.empty()) { + return zero_vector(2); + } + + const auto &r_first = *rContainer.begin(); + const std::size_t num_nodes = GetGeometry(r_first).size(); + + DenseVector shape(2); + shape[0] = static_cast(rContainer.size()); + shape[1] = static_cast(num_nodes); + return shape; +} + +template +void CheckContainer(const TContainerType &rContainer) { + if (rContainer.empty()) { + return; + } + const auto &r_first = *rContainer.begin(); + const std::size_t num_nodes = GetGeometry(r_first).size(); + + IndexPartition(rContainer.size()).for_each([&](std::size_t i) { + const auto &r_entity = *(rContainer.begin() + i); + const auto &r_geometry = GetGeometry(r_entity); + KRATOS_ERROR_IF(r_geometry.size() != num_nodes) + << "Entity #" << r_entity.Id() << " (Index: " << i << ") has " + << r_geometry.size() << " nodes, but expected " << num_nodes << "." + << std::endl; + }); +} + +template +void CollectIds(Kratos::span Span, const TContainerType &rContainer, + const std::size_t NumNodes) { + IndexPartition(rContainer.size()).for_each([&](std::size_t i) { + auto it = rContainer.begin() + i; + const auto &r_geometry = GetGeometry(*it); + + // Check compatibility + KRATOS_DEBUG_ERROR_IF(r_geometry.size() != NumNodes) + << "Entity at index " << i << " has " << r_geometry.size() + << " nodes, but expected " << NumNodes << "." << std::endl; + + for (std::size_t n = 0; n < NumNodes; ++n) { + Span[i * NumNodes + n] = static_cast(r_geometry[n].Id()); + } + }); +} + +} // namespace + +ConnectivityIdsTensorAdaptor::ConnectivityIdsTensorAdaptor( + ContainerPointerType pContainer) + : TensorAdaptor() { + mpContainer = pContainer; + std::visit( + [this](auto &&p_container) { + using ContainerType = BareType; + if constexpr (IsSupportedContainer()) { + mpStorage = Kratos::make_shared>(GetShape(*p_container)); + } else { + KRATOS_ERROR + << "Unsupported container type for ConnectivityIdsTensorAdaptor. " + "Only Geometries, Elements, and Conditions are supported." + << std::endl; + } + }, + *mpContainer); +} + +ConnectivityIdsTensorAdaptor::ConnectivityIdsTensorAdaptor( + const BaseType &rOther, const bool Copy) + : TensorAdaptor(rOther, Copy) {} + +void ConnectivityIdsTensorAdaptor::Check() const { + std::visit( + [](auto &&p_container) { + using ContainerType = BareType; + if constexpr (IsSupportedContainer()) { + CheckContainer(*p_container); + } + }, + *mpContainer); +} + +void ConnectivityIdsTensorAdaptor::CollectData() { + + const auto &shape = mpStorage->Shape(); + + const std::size_t num_nodes = shape[1]; + + std::visit( + [&](auto &&p_container) { + using ContainerType = BareType; + if constexpr (IsSupportedContainer()) { + CollectIds(this->ViewData(), *p_container, num_nodes); + } + }, + *mpContainer); +} + +void ConnectivityIdsTensorAdaptor::StoreData() { + KRATOS_ERROR + << "StoreData is not implemented for ConnectivityIdsTensorAdaptor. " + "Renumbering via TensorAdaptor is disabled." + << std::endl; +} + +std::string ConnectivityIdsTensorAdaptor::Info() const { + return "ConnectivityIdsTensorAdaptor"; +} + +} // namespace Kratos diff --git a/kratos/tensor_adaptors/connectivity_ids_tensor_adaptor.h b/kratos/tensor_adaptors/connectivity_ids_tensor_adaptor.h new file mode 100644 index 000000000000..6d603b835e63 --- /dev/null +++ b/kratos/tensor_adaptors/connectivity_ids_tensor_adaptor.h @@ -0,0 +1,101 @@ +// | / | +// ' / __| _` | __| _ \ __| +// . \ | ( | | ( |\__ ` +// _|\_\_| \__,_|\__|\___/ ____/ +// Multi-Physics +// +// License: BSD License +// Kratos default license: kratos/license.txt +// +// Main authors: Riccardo Rossi +// + +#pragma once + +// System includes +#include + +// External includes + +// Project includes +#include "tensor_adaptor.h" + +namespace Kratos { + +///@name Kratos Classes +///@{ + +/** + * @class ConnectivityIdsTensorAdaptor + * @ingroup TensorAdaptors + * @brief Adaptor class for handling node indices of Geometries, Elements, or + * Conditions. + * + * @details This class provides an interface to collect and store node IDs from + * the geometries of various Kratos containers (Geometries, Elements, + * Conditions). It extends TensorAdaptor. + * + * @section ConnectivityIdsTensorAdaptor_supported_container Supported container + * types + * - @ref ModelPart::GeometryContainerType + * - @ref ModelPart::ElementsContainerType + * - @ref ModelPart::ConditionsContainerType + * + * @section ConnectivityIdsTensorAdaptor_usage Usage + * - Use @ref Check to verify the container is valid. + * - Use @ref CollectData to read Node IDs from the entities. + * - Use @ref StoreData Not allowed. Throws an error. + * + * @author Antigravity AI + */ +class KRATOS_API(KRATOS_CORE) ConnectivityIdsTensorAdaptor + : public TensorAdaptor { +public: + ///@name Type definitions + ///@{ + + KRATOS_CLASS_POINTER_DEFINITION(ConnectivityIdsTensorAdaptor); + + using BaseType = TensorAdaptor; + + ///@} + ///@name Life cycle + ///@{ + + ConnectivityIdsTensorAdaptor(ContainerPointerType pContainer); + + ConnectivityIdsTensorAdaptor(const BaseType &rOther, const bool Copy = true); + + // Destructor + ~ConnectivityIdsTensorAdaptor() override = default; + + ///@} + ///@name Public operations + ///@{ + + /** + * @brief Check if the container is valid. + */ + void Check() const override; + + /** + * @brief Fill the internal data from Kratos data structures (Node IDs). + */ + void CollectData() override; + + /** + * @brief Store internal data to the given container (Set Node IDs). + */ + void StoreData() override; + + ///@} + ///@name Input and output + ///@{ + + std::string Info() const override; + + ///@} +}; + +/// @} +} // namespace Kratos diff --git a/kratos/tests/test_KratosCore.py b/kratos/tests/test_KratosCore.py index fa80dfc452fc..74f00fa2983a 100644 --- a/kratos/tests/test_KratosCore.py +++ b/kratos/tests/test_KratosCore.py @@ -103,6 +103,7 @@ import test_tetrahedral_mesh_orientation_check import test_nd_data import test_tensor_adaptors +import test_connectivity_ids_tensor_adaptor # Import modules required for sequential orchestrator test from test_sequential_orchestrator import EmptyAnalysisStage @@ -233,6 +234,7 @@ def AssembleTestSuites(): smallSuite.addTests(KratosUnittest.TestLoader().loadTestsFromTestCases([test_tetrahedral_mesh_orientation_check.TestTetrahedralMeshOrientationCheck])) smallSuite.addTests(KratosUnittest.TestLoader().loadTestsFromTestCases([test_nd_data.TestNDData])) smallSuite.addTests(KratosUnittest.TestLoader().loadTestsFromTestCases([test_tensor_adaptors.TestTensorAdaptors])) + smallSuite.addTests(KratosUnittest.TestLoader().loadTestsFromTestCases([test_connectivity_ids_tensor_adaptor.TestConnectivityIdsTensorAdaptor])) if sympy_available: diff --git a/kratos/tests/test_connectivity_ids_tensor_adaptor.py b/kratos/tests/test_connectivity_ids_tensor_adaptor.py new file mode 100644 index 000000000000..852146dbdf5e --- /dev/null +++ b/kratos/tests/test_connectivity_ids_tensor_adaptor.py @@ -0,0 +1,60 @@ + +import KratosMultiphysics as Kratos +import KratosMultiphysics.KratosUnittest as KratosUnittest +import numpy + +class TestConnectivityIdsTensorAdaptor(KratosUnittest.TestCase): + @classmethod + def setUpClass(cls): + cls.current_model = Kratos.Model() + cls.model_part = cls.current_model.CreateModelPart("Main") + + # Create 4 nodes + n1 = cls.model_part.CreateNewNode(1, 0.0, 0.0, 0.0) + n2 = cls.model_part.CreateNewNode(2, 1.0, 0.0, 0.0) + n3 = cls.model_part.CreateNewNode(3, 0.0, 1.0, 0.0) + n4 = cls.model_part.CreateNewNode(4, 1.0, 1.0, 0.0) + + # Create Properties + props = cls.model_part.CreateNewProperties(1) + + # Create 2 Elements (Triangles) + cls.model_part.CreateNewElement("Element2D3N", 1, [1, 2, 3], props) + cls.model_part.CreateNewElement("Element2D3N", 2, [2, 4, 3], props) + + def test_ConnectivityIdsTensorAdaptorCollect(self): + # Test Element Container with ConnectivityIdsTensorAdaptor + # Assuming ConnectivityIdsTensorAdaptor is exposed under Kratos.TensorAdaptors or similar + adaptor = Kratos.TensorAdaptors.ConnectivityIdsTensorAdaptor(self.model_part.Elements) + + # Check compatibility + adaptor.Check() + + # Collect Data + adaptor.CollectData() + data = adaptor.data + + expected_indices = numpy.array([ + [1, 2, 3], + [2, 4, 3] + ], dtype=numpy.int32) + + # Check shape + self.assertEqual(data.shape[0], 2) + self.assertEqual(data.shape[1], 3) + + # Check content + # assertVectorAlmostEqual might not work for 2D arrays directly depending on implementation, + # but numpy.array_equal is good for exact int matches. + self.assertTrue(numpy.array_equal(data, expected_indices), + msg=f"Collected indices mismatch.\nExpected:\n{expected_indices}\nGot:\n{data}") + + def test_ConnectivityIdsTensorAdaptorStoreDataError(self): + adaptor = Kratos.TensorAdaptors.ConnectivityIdsTensorAdaptor(self.model_part.Elements) + + # Test Store Data (Should Throw Error) + with self.assertRaisesRegex(RuntimeError, "StoreData is not implemented"): + adaptor.StoreData() + +if __name__ == "__main__": + KratosUnittest.main()