Caribou
BarycentricContainer.h
1 #pragma once
2 
3 #include <memory>
4 #include <vector>
5 #include <unordered_map>
6 
7 #include <Caribou/config.h>
8 #include <Caribou/macros.h>
9 #include <Caribou/constants.h>
10 #include <Caribou/Topology/Domain.h>
11 #include <Caribou/Topology/HashGrid.h>
12 
13 namespace caribou::topology {
14 
27  template <typename Domain>
29 public:
30  using Mesh = typename Domain::MeshType;
31  using ContainerElement = typename Domain::ElementType;
32 
33  static constexpr INTEGER_TYPE Dimension = ContainerElement::Dimension;
34  using ElementIndex = INTEGER_TYPE;
35  using LocalCoordinates = typename ContainerElement::LocalCoordinates;
36  using WorldCoordinates = typename ContainerElement::WorldCoordinates;
38 
46  BarycentricPoint() : element_index(-1), local_coordinates(LocalCoordinates::Zero()) {}
47  BarycentricPoint(const ElementIndex & index, const LocalCoordinates & coordinates)
48  : element_index(index), local_coordinates(coordinates) {}
49 
50  ElementIndex element_index;
51  LocalCoordinates local_coordinates;
52  };
53 
56 
70  template <typename Derived>
71  BarycentricContainer(const Domain * container_domain, const Eigen::MatrixBase<Derived> & embedded_points)
72  : BarycentricContainer(container_domain) {
73  // Set the embedded points
74  set_embedded_points(embedded_points);
75  }
76 
84  explicit BarycentricContainer(const Domain * container_domain)
85  : p_container_domain(container_domain) {
86  if (container_domain->number_of_elements() == 0) {
87  throw std::runtime_error("Trying to create a barycentric container from an empty domain.");
88  }
89 
90  // Get the mean size of the elements
91  WorldCoordinates H_mean = WorldCoordinates::Zero();
92  for (UNSIGNED_INTEGER_TYPE element_id = 0; element_id < container_domain->number_of_elements(); ++element_id) {
93  const ContainerElement element = container_domain->element(element_id);
94  const auto element_nodes = element.nodes();
95  const auto min = element_nodes.colwise().minCoeff();
96  const auto max = element_nodes.colwise().maxCoeff();
97  H_mean += (max - min);
98  }
99  H_mean /= container_domain->number_of_elements();
100 
101  // Create the Hash grid
102  p_hash_grid = std::make_unique<HashGridT>(H_mean.maxCoeff(), container_domain->number_of_elements());
103 
104  for (UNSIGNED_INTEGER_TYPE element_id = 0; element_id < container_domain->number_of_elements(); ++element_id) {
105  p_hash_grid->add(container_domain->element(element_id), element_id);
106  }
107  }
108 
120  auto barycentric_point(const WorldCoordinates & p) const -> BarycentricPoint {
121  // List of candidate elements that could contain the point
122  const auto candidate_element_indices = p_hash_grid->get(p);
123 
124 
125  for (const auto & element_index : candidate_element_indices) {
126  const ContainerElement e = p_container_domain->element(element_index);
127  const LocalCoordinates local_coordinates = e.local_coordinates(p);
128  if (e.contains_local(local_coordinates)) {
129  // Found one, let's return it
130  return {static_cast<ElementIndex>(element_index), local_coordinates};
131  }
132  }
133 
134  // No elements are containing the queried point
135  return {-1, LocalCoordinates::Zero()};
136  }
137 
141  auto closest_elements(const WorldCoordinates & p) const -> std::vector<BarycentricPoint> {
142  // List of candidate elements that could contain the point
143  const auto candidate_element_indices = p_hash_grid->get(p);
144 
145  std::vector<BarycentricPoint> closest_elements;
146  closest_elements.reserve(candidate_element_indices.size());
147  for (const auto & element_index : candidate_element_indices) {
148  const ContainerElement e = p_container_domain->element(element_index);
149  const LocalCoordinates local_coordinates = e.local_coordinates(p);
150  closest_elements.emplace_back(static_cast<ElementIndex>(element_index), local_coordinates);
151  }
152 
153  return closest_elements;
154  }
155 
173  template <typename Derived1, typename Derived2>
174  void interpolate(const Eigen::MatrixBase<Derived1> & container_field_values,
175  Eigen::MatrixBase<Derived2> & embedded_field_values) const {
176 
177  // Make sure we have one field value per node of the container domain's mesh.
178  if (static_cast<unsigned>(container_field_values.rows()) != p_container_domain->mesh().number_of_nodes()) {
179  std::stringstream ss;
180  ss << "The number of rows of the input matrix must be the same as the number of nodes in the container domain. ";
181  ss << "The number of rows is " << container_field_values.rows() << " and the number of nodes is ";
182  ss << p_container_domain->mesh().number_of_nodes();
183  throw std::runtime_error(ss.str());
184  }
185 
186  // Make sure we can write one field value per node of the embedded mesh.
187  if (static_cast<unsigned>(embedded_field_values.rows()) != p_barycentric_points.size()) {
188  throw std::runtime_error("The number of rows of the output matrix must be the same as the number of embedded nodes.");
189  }
190 
191  if (container_field_values.cols() != embedded_field_values.cols()) {
192  throw std::runtime_error("The number of columns of the input matrix (container field values) must be the "
193  "same as the number of columns of the output matrix (embedded field values).");
194  }
195 
196  // Interpolate field values
197  const auto number_of_embedded_points = static_cast<signed >(p_barycentric_points.size());
198  for (Eigen::Index node_id = 0; node_id < number_of_embedded_points; ++node_id) {
199  const auto & element_index = p_barycentric_points[node_id].element_index;
200  const auto & local_coordinates = p_barycentric_points[node_id].local_coordinates;
201 
202  if (element_index < 0) {
203  continue;
204  }
205 
206  const auto & element_node_indices = p_container_domain->element_indices(element_index);
207  const auto element = ContainerElement(p_container_domain->mesh().positions(element_node_indices));
208 
209  // Get the field values at the nodes of the containing element
210  Eigen::Matrix<typename Eigen::MatrixBase<Derived2>::Scalar,
211  ContainerElement::NumberOfNodesAtCompileTime,
212  Eigen::MatrixBase<Derived2>::ColsAtCompileTime>
213  values (element.number_of_nodes(), container_field_values.cols());
214 
215  for (UNSIGNED_INTEGER_TYPE i = 0; i < element.number_of_nodes(); ++i) {
216  values.row(i) = container_field_values.row(element_node_indices[i]);
217  }
218 
219  // Interpolate the field value at the local position
220  const auto interpolated_value = element.interpolate(local_coordinates, values);
221 
222  // Write back the interpolated value in the output embedded field
223  if constexpr (Eigen::MatrixBase<Derived2>::ColsAtCompileTime == 1) {
224  embedded_field_values[node_id] = interpolated_value;
225  } else {
226  embedded_field_values.row(node_id) = interpolated_value;
227  }
228  }
229  }
230 
234  auto barycentric_points () const -> const std::vector<BarycentricPoint> & {
235  return p_barycentric_points;
236  }
237 
242  [[nodiscard]]
243  auto outside_nodes () const -> const std::vector<UNSIGNED_INTEGER_TYPE> & {
244  return p_outside_nodes;
245  }
246 
247 private:
257  template <typename Derived>
258  void set_embedded_points(const Eigen::MatrixBase<Derived> & embedded_points) {
259  static_assert(Eigen::MatrixBase<Derived>::ColsAtCompileTime == Dimension or Eigen::MatrixBase<Derived>::ColsAtCompileTime == Eigen::Dynamic);
260 
261  if (Eigen::MatrixBase<Derived>::ColsAtCompileTime == Eigen::Dynamic and embedded_points.cols() != Dimension) {
262  throw std::runtime_error("Trying to get the barycentric coordinates of " +
263  std::to_string(embedded_points.cols()) + "D points from a " +
264  std::to_string(Dimension) + "D domain");
265  }
266 
267  p_outside_nodes.resize(0);
268  p_outside_nodes.reserve(std::floor(embedded_points.rows() / 10.)); // Reserve 10% of the mesh size for outside nodes
269 
270  p_barycentric_points.resize(0);
271  p_barycentric_points.reserve(embedded_points.rows());
272 
273  const auto number_of_embedded_points = embedded_points.rows();
274  for (Eigen::Index node_id = 0; node_id < number_of_embedded_points; ++node_id) {
275  const BarycentricPoint bp = barycentric_point(embedded_points.row(node_id).template cast<typename WorldCoordinates::Scalar>());
276  if (bp.element_index < 0) {
277  p_outside_nodes.emplace_back(node_id);
278  }
279 
280  p_barycentric_points.emplace_back(bp);
281  }
282  }
283 
284 
285  const Domain * p_container_domain;
286  std::vector<BarycentricPoint> p_barycentric_points;
287  std::vector<UNSIGNED_INTEGER_TYPE> p_outside_nodes;
288  std::unique_ptr<HashGridT> p_hash_grid;
289 };
290 
291 } // namespace caribou::topology
caribou::topology::Domain::element_indices
auto element_indices(const UNSIGNED_INTEGER_TYPE &index) const
Definition: Domain.h:264
caribou::topology::BarycentricContainer::barycentric_point
auto barycentric_point(const WorldCoordinates &p) const -> BarycentricPoint
Get an element that contains the given point (in world coordinates) and its local coordinates within ...
Definition: BarycentricContainer.h:120
caribou::topology::BarycentricContainer::BarycentricContainer
BarycentricContainer()=delete
Default constructor is not permitted.
caribou::topology::BarycentricContainer::BarycentricPoint
A barycentric point is a structure that contains the element index and the local coordinates within t...
Definition: BarycentricContainer.h:45
caribou::topology::Domain::mesh
auto mesh() const -> const Mesh &
Definition: Domain.h:165
caribou::topology::BarycentricContainer::closest_elements
auto closest_elements(const WorldCoordinates &p) const -> std::vector< BarycentricPoint >
Get the list of closest elements to a point and its barycentric coordinates within these elements.
Definition: BarycentricContainer.h:141
caribou::topology::BarycentricContainer::barycentric_points
auto barycentric_points() const -> const std::vector< BarycentricPoint > &
Barycentric points of the embedded nodes.
Definition: BarycentricContainer.h:234
caribou::topology::BarycentricContainer::interpolate
void interpolate(const Eigen::MatrixBase< Derived1 > &container_field_values, Eigen::MatrixBase< Derived2 > &embedded_field_values) const
Interpolate a field (scalar or vector field) from the container domain to the embedded nodes.
Definition: BarycentricContainer.h:174
caribou::topology::Domain::element
auto element(const UNSIGNED_INTEGER_TYPE &element_id, const Eigen::DenseBase< EigenMatrix > &positions) const -> Element
Definition: Domain.h:274
caribou::topology::Domain
Definition: Domain.h:75
caribou::topology::BarycentricContainer::BarycentricContainer
BarycentricContainer(const Domain *container_domain)
Construct the container from the given domain.
Definition: BarycentricContainer.h:84
caribou::topology::HashGrid
Definition: HashGrid.h:21
caribou::topology::BarycentricContainer::outside_nodes
auto outside_nodes() const -> const std::vector< UNSIGNED_INTEGER_TYPE > &
Indices of nodes found outside of the domain (ie, nodes that were not found inside any container elem...
Definition: BarycentricContainer.h:243
caribou::topology::Mesh
Definition: Mesh.h:16
caribou::topology::BarycentricContainer::BarycentricContainer
BarycentricContainer(const Domain *container_domain, const Eigen::MatrixBase< Derived > &embedded_points)
Construct the container from the given domain.
Definition: BarycentricContainer.h:71
caribou::topology::BarycentricContainer
The BarycentricContainer class allows to embed nodes into a container domain.
Definition: BarycentricContainer.h:28
caribou::topology::Domain::number_of_elements
auto number_of_elements() const -> UNSIGNED_INTEGER_TYPE final
Get the number of elements contained in the domain.
Definition: Domain.h:259