Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion kratos/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
8 changes: 8 additions & 0 deletions kratos/python/add_tensor_adaptors_to_python.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
#include "tensor_adaptors/equation_ids_tensor_adaptor.h"
#include "tensor_adaptors/gauss_point_variable_tensor_adaptor.h"
#include "tensor_adaptors/node_position_tensor_adaptor.h"
#include "tensor_adaptors/index_tensor_adaptor.h"

// Include base h
#include "add_tensor_adaptors_to_python.h"
Expand Down Expand Up @@ -152,6 +153,13 @@ void AddTensorAdaptorsToPython(pybind11::module& m)
.def(py::init<ModelPart::NodesContainerType::Pointer, Globals::Configuration, const std::vector<unsigned int>&>(), py::arg("container"), py::arg("configuration"), py::arg("data_shape"))
.def(py::init<const NodePositionTensorAdaptor::BaseType&, Globals::Configuration, const bool>(), py::arg("tensor_adaptor"), py::arg("configuration"), py::arg("copy") = true)
;

py::class_<IndexTensorAdaptor, IndexTensorAdaptor::Pointer, IndexTensorAdaptor::BaseType>(tensor_adaptor_sub_module, "IndexTensorAdaptor")
.def(py::init<ModelPart::GeometryContainerType::Pointer>(), py::arg("container"))
.def(py::init<ModelPart::ElementsContainerType::Pointer>(), py::arg("container"))
.def(py::init<ModelPart::ConditionsContainerType::Pointer>(), py::arg("container"))
.def(py::init<const IndexTensorAdaptor::BaseType&, const bool>(), py::arg("tensor_adaptor"), py::arg("copy") = true)
;
}

} // namespace Kratos::Python.
162 changes: 162 additions & 0 deletions kratos/tensor_adaptors/index_tensor_adaptor.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
// | / |
// ' / __| _` | __| _ \ __|
// . \ | ( | | ( |\__ `
// _|\_\_| \__,_|\__|\___/ ____/
// Multi-Physics
//
// License: BSD License
// Kratos default license: kratos/license.txt
//
// Main authors: Riccardo Rossi
//

// System includes
#include <vector>
#include <type_traits>

// External includes

// Project includes
#include "tensor_adaptors/index_tensor_adaptor.h"
#include "utilities/parallel_utilities.h"
#include "tensor_adaptors/tensor_adaptor_utils.h"

namespace Kratos {

namespace {

template <class TContainerType>
constexpr bool IsSupportedContainer()
{
return std::is_same_v<TContainerType, ModelPart::GeometryContainerType> ||
std::is_same_v<TContainerType, ModelPart::ElementsContainerType> ||
std::is_same_v<TContainerType, ModelPart::ConditionsContainerType>;
}

template <class TEntity>
const auto& GetGeometry(const TEntity& rEntity)
{
if constexpr (std::is_same_v<TEntity, Geometry<Node>>) {
return rEntity;
} else {
return rEntity.GetGeometry();
}
}

template <class TContainerType>
DenseVector<unsigned int> GetShape(const TContainerType& rContainer)
{
if (rContainer.empty()) {
return zero_vector<unsigned int>(2);
}

const auto& r_first = *rContainer.begin();
const std::size_t num_nodes = GetGeometry(r_first).size();

DenseVector<unsigned int> shape(2);
shape[0] = static_cast<unsigned int>(rContainer.size());
shape[1] = static_cast<unsigned int>(num_nodes);
return shape;
}

template <class TContainerType>
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<std::size_t>(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<class TContainerType>
void CollectIds(Kratos::span<int> Span, const TContainerType& rContainer, const std::size_t NumNodes)
{
IndexPartition<std::size_t>(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<int>(r_geometry[n].Id());
}
});
}



} // namespace

IndexTensorAdaptor::IndexTensorAdaptor(ContainerPointerType pContainer)
: TensorAdaptor<int>()
{
mpContainer = pContainer;
std::visit([this](auto&& p_container) {
using ContainerType = std::decay_t<decltype(*p_container)>;
if constexpr (IsSupportedContainer<ContainerType>()) {
mpStorage = Kratos::make_shared<NDData<int>>(GetShape(*p_container));
} else {
KRATOS_ERROR << "Unsupported container type for IndexTensorAdaptor. Only Geometries, Elements, and Conditions are supported." << std::endl;
}
}, *mpContainer);
}

IndexTensorAdaptor::IndexTensorAdaptor(
const TensorAdaptor& rOther,
const bool Copy)
: TensorAdaptor<int>(rOther, Copy)
{
}

void IndexTensorAdaptor::Check() const
{
std::visit([](auto&& p_container) {
using ContainerType = std::decay_t<decltype(*p_container)>;
if constexpr (IsSupportedContainer<ContainerType>()) {
CheckContainer(*p_container);
}
}, *mpContainer);
}

void IndexTensorAdaptor::CollectData()
{
if (!mpStorage) {
// Should not happen if constructed correctly
return;
}

const auto& shape = mpStorage->Shape();
if(shape.size() < 2) return; // Should be [N, M]
const std::size_t num_nodes = shape[1];

std::visit([&](auto&& p_container) {
using ContainerType = std::decay_t<decltype(*p_container)>;
if constexpr (IsSupportedContainer<ContainerType>()) {
CollectIds(this->ViewData(), *p_container, num_nodes);
}
}, *mpContainer);
}

void IndexTensorAdaptor::StoreData()
{
KRATOS_ERROR << "StoreData is not implemented for IndexTensorAdaptor. Renumbering via TensorAdaptor is disabled." << std::endl;
}

std::string IndexTensorAdaptor::Info() const
{
return "IndexTensorAdaptor";
}

} // namespace Kratos
102 changes: 102 additions & 0 deletions kratos/tensor_adaptors/index_tensor_adaptor.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// | / |
// ' / __| _` | __| _ \ __|
// . \ | ( | | ( |\__ `
// _|\_\_| \__,_|\__|\___/ ____/
// Multi-Physics
//
// License: BSD License
// Kratos default license: kratos/license.txt
//
// Main authors: Riccardo Rossi
//

#pragma once

// System includes
#include <string>

// External includes

// Project includes
#include "tensor_adaptor.h"

namespace Kratos {

///@name Kratos Classes
///@{

/**
* @class IndexTensorAdaptor
* @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<int>.
*
* @section IndexTensorAdaptor_supported_container Supported container types
* - @ref ModelPart::GeometryContainerType
* - @ref ModelPart::ElementsContainerType
* - @ref ModelPart::ConditionsContainerType
*
* @section IndexTensorAdaptor_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) IndexTensorAdaptor: public TensorAdaptor<int> {
public:

///@name Type definitions
///@{

KRATOS_CLASS_POINTER_DEFINITION(IndexTensorAdaptor);

using BaseType = TensorAdaptor<int>;

///@}
///@name Life cycle
///@{

IndexTensorAdaptor(
ContainerPointerType pContainer);

IndexTensorAdaptor(
const TensorAdaptor& rOther,
const bool Copy = true);

// Destructor
~IndexTensorAdaptor() 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
60 changes: 60 additions & 0 deletions kratos/tests/test_index_tensor_adaptor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@

import KratosMultiphysics as Kratos
import KratosMultiphysics.KratosUnittest as KratosUnittest
import numpy

class TestIndexTensorAdaptor(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_IndexTensorAdaptorCollect(self):
# Test Element Container with IndexTensorAdaptor
# Assuming IndexTensorAdaptor is exposed under Kratos.TensorAdaptors or similar
adaptor = Kratos.TensorAdaptors.IndexTensorAdaptor(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_IndexTensorAdaptorStoreDataError(self):
adaptor = Kratos.TensorAdaptors.IndexTensorAdaptor(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()
Loading