OGS
HeatTransportBHE/BHE/MeshUtils.cpp
Go to the documentation of this file.
1// SPDX-FileCopyrightText: Copyright (c) OpenGeoSys Community (opengeosys.org)
2// SPDX-License-Identifier: BSD-3-Clause
3
4#include "MeshUtils.h"
5
6#include <algorithm>
7#include <array>
8#include <set>
9#include <unordered_map>
10
11#include "BaseLib/Algorithm.h"
12#include "BaseLib/Error.h"
14#include "MeshLib/Mesh.h"
16#include "MeshLib/Node.h"
17
18namespace
19{
20std::vector<MeshLib::Element*> extractOneDimensionalElements(
21 std::vector<MeshLib::Element*> const& elements)
22{
23 std::vector<MeshLib::Element*> one_dimensional_elements;
24
25 copy_if(
26 begin(elements), end(elements), back_inserter(one_dimensional_elements),
27 [](MeshLib::Element const* const e) { return e->getDimension() == 1; });
28
29 return one_dimensional_elements;
30}
31
32std::vector<int> getUniqueMaterialIds(
33 MeshLib::PropertyVector<int> const& material_ids,
34 std::vector<MeshLib::Element*> const& elements)
35{
36 std::set<int> unique_material_ids;
37 std::transform(begin(elements), end(elements),
38 inserter(unique_material_ids, end(unique_material_ids)),
39 [&material_ids](MeshLib::Element const* const e)
40 { return material_ids[e->getID()]; });
41 return {begin(unique_material_ids), end(unique_material_ids)};
42}
43
44std::array<MeshLib::Node*, 2> getElementEndpoints(MeshLib::Element& element)
45{
47 {
49 "Expected a line element for BHE element id {:d}, but got {:s}.",
50 element.getID(),
52 }
53
54 return {element.getNode(0), element.getNode(1)};
55}
56
58std::unordered_map<std::size_t, std::vector<MeshLib::Element*>>
59buildNodeToElementMap(std::vector<MeshLib::Element*> const& elements)
60{
61 std::unordered_map<std::size_t, std::vector<MeshLib::Element*>> map;
62 for (auto* element : elements)
63 {
64 auto const [n0, n1] = getElementEndpoints(*element);
65 map[n0->getID()].push_back(element);
66 map[n1->getID()].push_back(element);
67 }
68 return map;
69}
70
76 std::vector<MeshLib::Node*> const& bhe_nodes,
77 std::unordered_map<std::size_t, std::vector<MeshLib::Element*>> const&
78 node_to_elements)
79{
80 if (bhe_nodes.empty())
81 {
82 OGS_FATAL("BHE node list is empty while constructing mesh data.");
83 }
84
85 std::vector<MeshLib::Node*> endpoints;
86 std::copy_if(
87 bhe_nodes.begin(), bhe_nodes.end(), std::back_inserter(endpoints),
88 [&node_to_elements](MeshLib::Node* node)
89 {
90 auto const it = node_to_elements.find(node->getID());
91 return it != node_to_elements.end() && it->second.size() == 1;
92 });
93
94 if (endpoints.size() != 2)
95 {
97 "The BHE mesh must form a single continuous linear chain with "
98 "exactly 2 endpoints. Found {:d} endpoints.",
99 endpoints.size());
100 }
101
102 auto* a = endpoints[0];
103 auto* b = endpoints[1];
104
105 if ((*a)[2] == (*b)[2])
106 {
107 OGS_FATAL(
108 "Both BHE chain endpoints share the same z-coordinate ({:g}). "
109 "The wellhead choice is ambiguous.",
110 (*a)[2]);
111 }
112
113 return ((*a)[2] > (*b)[2]) ? a : b;
114}
115
123{
124 std::vector<MeshLib::Element*> ordered_elements;
125 std::vector<MeshLib::Node*> ordered_nodes;
126 std::unordered_map<std::size_t, double> element_distances_from_wellhead;
127};
128
130 std::vector<MeshLib::Element*> const& bhe_elements,
131 std::vector<MeshLib::Node*> const& bhe_nodes)
132{
133 if (bhe_elements.empty())
134 {
135 return {};
136 }
137
138 auto const node_to_elements = buildNodeToElementMap(bhe_elements);
139 MeshLib::Node* wellhead = findWellheadNode(bhe_nodes, node_to_elements);
140
141 // The wellhead node must be an endpoint of exactly one element.
142 auto const it = node_to_elements.find(wellhead->getID());
143 if (it == node_to_elements.end() || it->second.empty())
144 {
145 OGS_FATAL("Wellhead node {:d} is not connected to any BHE element.",
146 wellhead->getID());
147 }
148 if (it->second.size() != 1)
149 {
150 OGS_FATAL(
151 "Wellhead node {:d} is connected to {:d} BHE elements; "
152 "expected exactly 1 (chain endpoint).",
153 wellhead->getID(), it->second.size());
154 }
155
156 std::vector<MeshLib::Element*> ordered;
157 ordered.reserve(bhe_elements.size());
158 std::vector<MeshLib::Node*> ordered_nodes;
159 ordered_nodes.reserve(bhe_elements.size() + 1);
160 ordered_nodes.push_back(wellhead);
161 std::unordered_map<std::size_t, double> distances;
162 distances.reserve(bhe_elements.size());
163
164 MeshLib::Node* prev_node = wellhead;
165 MeshLib::Element* current = it->second.front();
166 double accumulated_distance = 0.0;
167
168 while (current != nullptr)
169 {
170 ordered.push_back(current);
171 double const len = current->computeVolume();
172 distances[current->getID()] = accumulated_distance + 0.5 * len;
173 accumulated_distance += len;
174
175 // Find exit node (the endpoint not shared with prev_node).
176 auto const [n0, n1] = getElementEndpoints(*current);
177 MeshLib::Node* exit_node = (n0 == prev_node) ? n1 : n0;
178 ordered_nodes.push_back(exit_node);
179
180 // Find the next element connected to exit_node.
181 auto const exit_it = node_to_elements.find(exit_node->getID());
182 MeshLib::Element* next = nullptr;
183 if (exit_it != node_to_elements.end())
184 {
185 auto const& elems = exit_it->second;
186 auto const next_it =
187 std::find_if(elems.begin(), elems.end(),
188 [current](auto* e) { return e != current; });
189 if (next_it != elems.end())
190 {
191 next = *next_it;
192 }
193 }
194
195 prev_node = exit_node;
196 current = next;
197 }
198
199 if (ordered.size() != bhe_elements.size())
200 {
201 OGS_FATAL(
202 "BHE chain walk visited {:d} elements but the group has {:d}. "
203 "The BHE mesh must form a single continuous linear chain.",
204 ordered.size(), bhe_elements.size());
205 }
206
207 return {std::move(ordered), std::move(ordered_nodes), std::move(distances)};
208}
209} // namespace
210
211namespace ProcessLib
212{
213namespace HeatTransportBHE
214{
216{
217 std::vector<MeshLib::Element*> const all_bhe_elements =
218 extractOneDimensionalElements(mesh.getElements());
219
220 // finally counting two types of elements
221 // They are (i) soil, and (ii) BHE type of elements
222 DBUG("-> found total {:d} soil elements and {:d} BHE elements",
223 mesh.getNumberOfElements() - all_bhe_elements.size(),
224 all_bhe_elements.size());
225
226 // get BHE material IDs
227 auto const* const opt_material_ids = MeshLib::materialIDs(mesh);
228 if (opt_material_ids == nullptr)
229 {
230 OGS_FATAL("Not able to get material IDs! ");
231 }
232 auto const& material_ids = *opt_material_ids;
233
234 auto const& bhe_material_ids =
235 getUniqueMaterialIds(material_ids, all_bhe_elements);
236 DBUG("-> found {:d} BHE material groups", bhe_material_ids.size());
237
238 // create a vector of BHE elements for each group
239 std::vector<std::vector<MeshLib::Element*>> bhe_elements;
240 bhe_elements.resize(bhe_material_ids.size());
241 for (unsigned bhe_id = 0; bhe_id < bhe_material_ids.size(); bhe_id++)
242 {
243 const auto bhe_mat_id = bhe_material_ids[bhe_id];
244 std::vector<MeshLib::Element*>& vec_elements = bhe_elements[bhe_id];
245 copy_if(begin(all_bhe_elements), end(all_bhe_elements),
246 back_inserter(vec_elements),
247 [&](MeshLib::Element const* const e)
248 { return material_ids[e->getID()] == bhe_mat_id; });
249 DBUG("-> found {:d} elements on the BHE_{:d}", vec_elements.size(),
250 bhe_id);
251 }
252
253 // get a vector of BHE nodes
254 std::vector<std::vector<MeshLib::Node*>> bhe_nodes;
255 bhe_nodes.resize(bhe_material_ids.size());
256 for (unsigned bhe_id = 0; bhe_id < bhe_material_ids.size(); bhe_id++)
257 {
258 std::vector<MeshLib::Node*>& vec_nodes = bhe_nodes[bhe_id];
259 for (MeshLib::Element* e : bhe_elements[bhe_id])
260 {
261 for (unsigned i = 0; i < e->getNumberOfNodes(); i++)
262 {
263 vec_nodes.push_back(const_cast<MeshLib::Node*>(e->getNode(i)));
264 }
265 }
268
269 DBUG("-> found {:d} nodes on the BHE_{:d}", vec_nodes.size(), bhe_id);
270 }
271
272 std::unordered_map<std::size_t, double> bhe_element_distances_from_wellhead;
273 std::vector<std::vector<MeshLib::Node*>> bhe_topology_ordered_nodes(
274 bhe_material_ids.size());
275
276 std::unordered_map<std::size_t, int> bhe_element_section_indices;
277
278 for (unsigned bhe_id = 0; bhe_id < bhe_material_ids.size(); bhe_id++)
279 {
280 auto walk_result =
281 walkChainFromWellhead(bhe_elements[bhe_id], bhe_nodes[bhe_id]);
282
283 bhe_elements[bhe_id] = std::move(walk_result.ordered_elements);
284 bhe_topology_ordered_nodes[bhe_id] =
285 std::move(walk_result.ordered_nodes);
286
287 for (auto const& [element_id, distance] :
288 walk_result.element_distances_from_wellhead)
289 {
290 bhe_element_distances_from_wellhead[element_id] = distance;
291 }
292 }
293
294 return {bhe_material_ids,
295 bhe_elements,
296 bhe_nodes,
297 bhe_topology_ordered_nodes,
298 std::move(bhe_element_distances_from_wellhead),
299 std::move(bhe_element_section_indices)};
300}
301
303 std::vector<BHE::BHETypes> const& bhes)
304{
305 if (bhes.size() != BHE_elements.size())
306 {
307 OGS_FATAL(
308 "Mismatch between number of BHE definitions ({:d}) and BHE mesh "
309 "groups ({:d}) while creating per-element section indices.",
310 bhes.size(), BHE_elements.size());
311 }
312
313 for (std::size_t bhe_id = 0; bhe_id < bhes.size(); ++bhe_id)
314 {
315 for (auto const* const element : BHE_elements[bhe_id])
316 {
317 auto const element_id = element->getID();
318 auto const dist_it =
320 if (dist_it == BHE_element_distances_from_wellhead.end())
321 {
322 OGS_FATAL(
323 "Could not determine section index: no distance found for "
324 "element id {:d}.",
325 element_id);
326 }
327
328 double const distance_from_wellhead = dist_it->second;
329
330 BHE_element_section_indices[element_id] = visit(
331 [distance_from_wellhead](auto const& bhe)
332 {
333 return bhe.borehole_geometry.sections.getSectionIndex(
334 distance_from_wellhead);
335 },
336 bhes[bhe_id]);
337 }
338 }
339}
340
341} // end of namespace HeatTransportBHE
342} // namespace ProcessLib
#define OGS_FATAL(...)
Definition Error.h:19
void DBUG(fmt::format_string< Args... > fmt, Args &&... args)
Definition Logging.h:22
std::size_t getID() const
virtual MeshElemType getGeomType() const =0
virtual double computeVolume()=0
virtual const Node * getNode(unsigned idx) const =0
virtual constexpr unsigned getDimension() const =0
Get dimension of the mesh element.
std::size_t getID() const
Returns the ID of the element.
Definition Element.h:80
std::vector< Element * > const & getElements() const
Get the element-vector for the mesh.
Definition Mesh.h:101
std::size_t getNumberOfElements() const
Get the number of elements.
Definition Mesh.h:89
void makeVectorUnique(std::vector< T > &v)
Definition Algorithm.h:173
std::string MeshElemType2String(const MeshElemType t)
Given a MeshElemType this returns the appropriate string.
Definition MeshEnums.cpp:10
bool idsComparator(T const a, T const b)
Definition Mesh.h:199
PropertyVector< int > const * materialIDs(Mesh const &mesh)
Definition Mesh.cpp:258
BHEMeshData getBHEDataInMesh(MeshLib::Mesh const &mesh)
std::unordered_map< std::size_t, std::vector< MeshLib::Element * > > buildNodeToElementMap(std::vector< MeshLib::Element * > const &elements)
Build a node-to-elements adjacency map for a set of BHE elements.
std::array< MeshLib::Node *, 2 > getElementEndpoints(MeshLib::Element &element)
ChainWalkResult walkChainFromWellhead(std::vector< MeshLib::Element * > const &bhe_elements, std::vector< MeshLib::Node * > const &bhe_nodes)
std::vector< MeshLib::Element * > extractOneDimensionalElements(std::vector< MeshLib::Element * > const &elements)
MeshLib::Node * findWellheadNode(std::vector< MeshLib::Node * > const &bhe_nodes, std::unordered_map< std::size_t, std::vector< MeshLib::Element * > > const &node_to_elements)
std::vector< int > getUniqueMaterialIds(MeshLib::PropertyVector< int > const &material_ids, std::vector< MeshLib::Element * > const &elements)
std::unordered_map< std::size_t, int > BHE_element_section_indices
std::vector< std::vector< MeshLib::Element * > > BHE_elements
void updateElementSectionIndices(std::vector< BHE::BHETypes > const &bhes)
std::unordered_map< std::size_t, double > BHE_element_distances_from_wellhead
std::unordered_map< std::size_t, double > element_distances_from_wellhead