hummingbird
Solving the self-adjoint angular flux transport equation using spectral elements on Cartesian geometry
Loading...
Searching...
No Matches
mesh.cc
Go to the documentation of this file.
1#include "mesh/mesh.h"
2
3#include <fmt/core.h>
4
5#include <algorithm>
6#include <array>
7#include <cassert>
8#include <format>
9#include <functional>
10#include <iomanip>
11#include <nlohmann/json.hpp>
12#include <stdexcept>
13#include <unordered_map>
14
15#include "mesh/segment.h"
16#include "problem/sem_problem.h"
17#include "utils/enums.h"
18
19namespace hummingbird {
20
21Mesh::Mesh(const std::string& msh_file) {
22 ReadGMSH(msh_file);
24}
25
27 for (const auto& elem : elements_) {
28 if (elem->material_id() < 0)
29 throw std::runtime_error(
30 "Element does not have a material ID assigned. Node ID = " +
31 std::to_string(elem->material_id()));
32 }
33}
34
35void Mesh::AddNode(const Node& node) { nodes_.push_back(node); }
36
37void Mesh::AddNodes(const std::vector<Node>& nodes) {
38 for (auto node : nodes) nodes_.push_back(std::move(node));
39}
40
41void Mesh::AddElement(std::unique_ptr<Element> element) {
42 if (elements_.empty()) {
43 dimension_ = element->dimension();
44 } else if (element->dimension() != dimension_) {
45 throw std::runtime_error(std::format(
46 "Element has dimension {} but Mesh already contains elements of "
47 "dimension {}. All elements in a Mesh must have the same "
48 "dimension.",
49 element->dimension(), dimension_));
50 }
51 elements_.push_back(std::move(element));
52}
53
54void Mesh::Prepare(const GaussLobattoLegendre& gll_quadrature) {
55 CreateInteriorElementNodes(gll_quadrature);
58}
59
61 const GaussLobattoLegendre& gll_quadrature) {
62 for (auto& element : elements_) {
63 auto new_nodes = element->CreateInteriorNodes(nodes_, gll_quadrature);
64 AddNodes(new_nodes);
65 }
66}
67
69 std::unordered_map<size_t, size_t> old_to_new_id;
70 std::vector<Node> renumbered_nodes(nodes_.size());
71
72 // First, assign every old ID a new one and build the renumbered node
73 // storage, without touching any element yet: elements share nodes, so an
74 // ID may be seen again from a later element, but should only get a new ID
75 // (and a slot in renumbered_nodes) the first time.
76 size_t new_node_id = 0;
77 for (auto& element : elements_) {
78 for (auto old_id : element->node_ids()) {
79 if (old_to_new_id.contains(old_id)) continue;
80 Node node = nodes_.at(old_id);
81 node.id = new_node_id;
82 renumbered_nodes.at(new_node_id) = std::move(node);
83 old_to_new_id.emplace(old_id, new_node_id);
84 new_node_id++;
85 }
86 }
87
88 // Now that every old ID maps to a final new ID, rebuild each element's
89 // node ID list from scratch. Updating one ID at a time in place (e.g. via
90 // SetNewNodeID) risks a new ID coincidentally colliding with a
91 // not-yet-remapped old ID elsewhere in the same list.
92 for (auto& element : elements_) {
93 std::vector<size_t> new_ids;
94 for (auto old_id : element->node_ids())
95 new_ids.push_back(old_to_new_id.at(old_id));
96 element->SetNodeIDs(std::move(new_ids));
97 }
98
99 nodes_ = std::move(renumbered_nodes);
100}
101
103 std::vector<size_t> ids;
104 ids.reserve(nodes_.size());
105 for (const auto& node : nodes_) ids.push_back(node.id);
106 std::sort(ids.begin(), ids.end());
107 for (auto i = 0; i < ids.size(); i++)
108 if (ids.at(i) != i)
109 throw std::runtime_error(std::format(
110 "Node ID expected to be {} but was instead {}. Previous node ID: {}.",
111 i, ids.at(i), ids.at(i - 1)));
112}
113
114void Mesh::ReadGMSH(const std::string& msh_file) {
115 std::ifstream file(msh_file);
116 if (!file.is_open())
117 throw std::runtime_error(
118 std::format("Could not open gmsh file: {}", msh_file));
119
120 GmshReadState state;
121
122 // Section headers route to their subroutine here; a new element type
123 // (e.g. quads, once 2D meshes are supported) only needs a change inside
124 // ReadElements, not to this dispatch.
125 const std::unordered_map<std::string, std::function<void(std::ifstream&)>>
126 section_handlers = {
127 {"$PhysicalNames",
128 [&](std::ifstream& f) { ReadPhysicalNames(f, state); }},
129 {"$Entities", [&](std::ifstream& f) { ReadEntities(f, state); }},
130 {"$Nodes", [&](std::ifstream& f) { ReadNodes(f, state); }},
131 {"$Elements", [&](std::ifstream& f) { ReadElements(f, state); }},
132 };
133
134 std::string line;
135 while (std::getline(file, line)) {
136 auto handler = section_handlers.find(line);
137 if (handler != section_handlers.end()) handler->second(file);
138 }
139
140 physical_names_ = std::move(state.physical_names);
141}
142
143void Mesh::ReadPhysicalNames(std::ifstream& file, GmshReadState& state) {
144 size_t n_names = 0;
145 file >> n_names;
146 for (size_t i = 0; i < n_names; i++) {
147 int dim = 0;
148 int tag = 0;
149 std::string name;
150 file >> dim >> tag >> std::quoted(name);
151 state.physical_names.emplace(tag, std::move(name));
152 }
153}
154
155void Mesh::ReadEntities(std::ifstream& file, GmshReadState& state) {
156 size_t n_points = 0;
157 size_t n_curves = 0;
158 size_t n_surfaces = 0;
159 size_t n_volumes = 0;
160 file >> n_points >> n_curves >> n_surfaces >> n_volumes;
161
162 if (n_surfaces > 0 || n_volumes > 0)
163 throw std::runtime_error(
164 "Mesh contains surface or volume entities. Only 1D gmsh meshes "
165 "(points and curves) are currently supported.");
166
167 for (size_t i = 0; i < n_points; i++) {
168 int tag = 0;
169 double x = 0;
170 double y = 0;
171 double z = 0;
172 size_t n_physical_tags = 0;
173 file >> tag >> x >> y >> z >> n_physical_tags;
174
175 std::vector<int> physical_tags(n_physical_tags);
176 for (auto& physical_tag : physical_tags) file >> physical_tag;
177 state.point_physical_tags.emplace(tag, std::move(physical_tags));
178 }
179
180 for (size_t i = 0; i < n_curves; i++) {
181 int tag = 0;
182 double min_x, min_y, min_z, max_x, max_y, max_z;
183 size_t n_physical_tags = 0;
184 file >> tag >> min_x >> min_y >> min_z >> max_x >> max_y >> max_z >>
185 n_physical_tags;
186
187 std::vector<int> physical_tags(n_physical_tags);
188 for (auto& physical_tag : physical_tags) file >> physical_tag;
189 state.curve_physical_tags.emplace(tag, std::move(physical_tags));
190
191 size_t n_bounding_points = 0;
192 file >> n_bounding_points;
193 int bounding_point_tag = 0;
194 for (size_t j = 0; j < n_bounding_points; j++) file >> bounding_point_tag;
195 }
196}
197
198void Mesh::ReadNodes(std::ifstream& file, GmshReadState& state) {
199 size_t n_blocks = 0;
200 size_t n_nodes = 0;
201 size_t min_tag = 0;
202 size_t max_tag = 0;
203 file >> n_blocks >> n_nodes >> min_tag >> max_tag;
204
205 for (size_t block = 0; block < n_blocks; block++) {
206 int entity_dim = 0;
207 int entity_tag = 0;
208 int parametric = 0;
209 size_t n_nodes_in_block = 0;
210 file >> entity_dim >> entity_tag >> parametric >> n_nodes_in_block;
211 if (parametric != 0)
212 throw std::runtime_error(
213 "Parametric node coordinates are not currently supported.");
214
215 std::vector<size_t> node_tags(n_nodes_in_block);
216 for (auto& node_tag : node_tags) file >> node_tag;
217
218 for (size_t i = 0; i < n_nodes_in_block; i++) {
219 Node node;
220 node.id = nodes_.size();
221 file >> node.x >> node.y >> node.z;
222 state.node_tag_to_id.emplace(node_tags.at(i), node.id);
223 AddNode(node);
224 }
225 }
226}
227
228void Mesh::ReadElements(std::ifstream& file, GmshReadState& state) {
229 // gmsh element type codes; see
230 // https://gmsh.info/doc/texinfo/gmsh.html#MSH-file-format
231 constexpr int kPointType = 15;
232 constexpr int kTwoNodeLineType = 1;
233
234 size_t n_blocks = 0;
235 size_t n_elements = 0;
236 size_t min_tag = 0;
237 size_t max_tag = 0;
238 file >> n_blocks >> n_elements >> min_tag >> max_tag;
239
240 for (size_t block = 0; block < n_blocks; block++) {
241 int entity_dim = 0;
242 int entity_tag = 0;
243 int element_type = 0;
244 size_t n_elements_in_block = 0;
245 file >> entity_dim >> entity_tag >> element_type >> n_elements_in_block;
246
247 if (element_type == kPointType) {
248 // Point elements mark boundary entities in gmsh; they don't map to
249 // their own hummingbird Element, but tag the Node they reference.
250 const auto bc_id = GetBCID(state.point_physical_tags.at(entity_tag),
251 state.physical_names);
252
253 size_t element_tag = 0;
254 size_t node_tag = 0;
255 for (size_t i = 0; i < n_elements_in_block; i++) {
256 file >> element_tag >> node_tag;
257 Node& node = nodes_.at(state.node_tag_to_id.at(node_tag));
258 node.bc_id = bc_id;
259 }
260 continue;
261 }
262
263 if (element_type != kTwoNodeLineType)
264 throw std::runtime_error(std::format(
265 "gmsh element type {} is not yet supported. Only 1D meshes "
266 "(2-node line elements) can currently be read.",
267 element_type));
268
269 const auto material_id = GetMaterialID(
270 state.curve_physical_tags.at(entity_tag), state.physical_names);
271 const auto source_id = GetSourceID(state.curve_physical_tags.at(entity_tag),
272 state.physical_names);
273
274 size_t element_tag = 0;
275 size_t node_tag_1 = 0;
276 size_t node_tag_2 = 0;
277 for (size_t i = 0; i < n_elements_in_block; i++) {
278 file >> element_tag >> node_tag_1 >> node_tag_2;
279 std::array<size_t, 2> boundary_node_ids = {
280 state.node_tag_to_id.at(node_tag_1),
281 state.node_tag_to_id.at(node_tag_2)};
282
283 AddElement(std::make_unique<Segment>(boundary_node_ids, material_id,
284 source_id, *this));
285 }
286 }
287}
288
290 const std::vector<int>& curve_physical_tags,
291 const std::unordered_map<int, std::string>& physical_names) const {
292 for (int tag : curve_physical_tags)
293 if (physical_names.at(tag).starts_with("material:")) return tag;
294
295 throw std::runtime_error(
296 "No Physical Group with a name prefixed \"material:\" was found for "
297 "a curve entity.");
298}
299
301 const std::vector<int>& curve_physical_tags,
302 const std::unordered_map<int, std::string>& physical_names) const {
303 for (int tag : curve_physical_tags)
304 if (physical_names.at(tag).starts_with("source:")) return tag;
305 throw std::runtime_error(
306 "No Physical Group with a name prefixed \"source:\" was found for "
307 "a curve entity.");
308}
309
311 const std::vector<int>& point_physical_tags,
312 const std::unordered_map<int, std::string>& physical_names) const {
313 for (int tag : point_physical_tags)
314 if (physical_names.at(tag).starts_with("bc:")) return tag;
315 throw std::runtime_error(
316 "No Physical Group with a name prefixed \"bc:\" was found for "
317 "a point entity.");
318}
319
321 const size_t n_ordinates, const QuadratureBase<Ordinate>& angular_quad_set,
322 const SourceBank& source_bank) {
323 for (const auto& element : elements_) {
324 int source_id = element->source_id();
325 for (const auto node_id : element->node_ids()) {
326 Node& node = nodes_[node_id];
327 node.scalar_flux = 0.0;
328 node.angular_fluxes.assign(n_ordinates, 0.0);
329 node.source_fluxes.assign(n_ordinates, 0.0);
330 for (auto n = 0; n < n_ordinates; n++)
331 node.source_fluxes[n] = source_bank.GetByID(source_id)->EvaluateAtNode(
332 node, angular_quad_set.GetAbscissa(n));
333 }
334 }
335}
336
337void Mesh::ResolveIDs(const MaterialBank& material_bank,
338 const SourceBank& source_bank, const BCBank& bc_bank) {
339 for (auto& element : elements_) {
340 const auto material_name =
341 ExtractName(physical_names_.at(element->material_id()));
342 try {
343 element->SetMaterialID(material_bank.GetIDByName(material_name));
344 } catch (const std::out_of_range&) {
345 throw std::runtime_error(std::format(
346 "Material \"{}\" passed to the mesh is not defined in the input "
347 "file.",
348 material_name));
349 }
350
351 const auto source_name =
352 ExtractName(physical_names_.at(element->source_id()));
353 element->SetSourceID(source_bank.GetIDByName(source_name));
354 }
355
356 for (auto& node : nodes_) {
357 if (node.bc_id == 0) continue;
358 const auto bc_name = ExtractName(physical_names_.at(node.bc_id));
359 node.bc_id = bc_bank.GetIDByName(bc_name);
360 }
361}
362
363std::string Mesh::ExtractName(const std::string& physical_name) const {
364 return physical_name.substr(physical_name.find(':') + 1);
365}
366
368 for (const auto& node : nodes_)
369 if (node.bc_id != 0) boundary_node_ids_.push_back(node.id);
370 if (dimension_ == 1) assert(boundary_node_ids_.size() == 2);
371}
372
374 if (dimension_ != 1)
375 throw std::runtime_error(
376 "SetOutwardNormals is only implemented for 1D meshes.");
377
378 Node& node_a = nodes_.at(boundary_node_ids_.at(0));
379 Node& node_b = nodes_.at(boundary_node_ids_.at(1));
380
381 arma::vec3 direction_a = {node_a.x - node_b.x, 0.0, 0.0};
382 node_a.outward_normal = arma::normalise(direction_a);
383 node_b.outward_normal = -node_a.outward_normal;
384}
385
387 const QuadratureBase<Ordinate>& angular_quad_set) {
388 for (auto& node : nodes_) UpdateScalarFlux(node, angular_quad_set);
389}
390
392 const size_t n_ordinates) {
393 for (auto i = 0; i < this->n_nodes(); i++) {
394 for (auto n = 0; n < n_ordinates; n++) {
395 nodes_[i].angular_fluxes[n] =
396 sem_problem.get()->solution_vectors().at(n)[i];
397 }
398 }
399}
400} // namespace hummingbird
Bank holding the boundary conditions defined in the input file.
Definition bc_bank.h:21
const T & GetByID(const int id) const
Get the object by its ID.
Definition bank_base.h:29
int GetIDByName(const std::string &name) const
Get the ID of an object by its name in the JSON input file.
Definition bank_base.h:47
Class defining a 1D Gauss-Legendre-Lobatto quadrature set on [-1,1]. The quadrature set approximates ...
Bank holding the materials defined in the input file.
void AddNodes(const std::vector< Node > &nodes)
Add nodes in a vector to Mesh.
Definition mesh.cc:37
void ResolveIDs(const MaterialBank &material_bank, const SourceBank &source_bank, const BCBank &bc_bank)
Resolve the raw gmsh Physical Group tags currently stored in material_id()/source_id()/bc_id (set fro...
Definition mesh.cc:337
std::unordered_map< int, std::string > physical_names_
Map from Physical Group tag to its name (e.g. "material:foo"), persisted from GmshReadState::physical...
Definition mesh.h:240
void RenumberNodes()
Renumber nodes in mesh to keep node IDs near each other in a single element.
Definition mesh.cc:68
void ReadPhysicalNames(std::ifstream &file, GmshReadState &state)
Read the $PhysicalNames section into state.physical_names.
Definition mesh.cc:143
void ReadGMSH(const std::string &msh_file)
Construct a Mesh from a gmsh ASCII (format 4.1) .msh file. Reading routes to a section-specific subro...
Definition mesh.cc:114
std::string ExtractName(const std::string &physical_name) const
Extract the name after the ":" in a Physical Group name (e.g. "mms_material" from "material:mms_mater...
Definition mesh.cc:363
Mesh()=default
Construct a new Mesh object.
size_t n_nodes() const
Get the number of nodes in the mesh.
Definition mesh.h:107
void FindBoundaryNodes()
Populates the boundary_node_ids_ member. Must be called after ResolveIDs!
Definition mesh.cc:367
size_t n_elements() const
Get the number of Elements in the mesh.
Definition mesh.h:100
std::vector< std::unique_ptr< Element > > elements_
Elements in the mesh.
Definition mesh.h:231
int GetBCID(const std::vector< int > &point_physical_tags, const std::unordered_map< int, std::string > &physical_names) const
Find the BC ID for a point entity, defined as the tag of the Physical Group on that point whose name ...
Definition mesh.cc:310
void ReadNodes(std::ifstream &file, GmshReadState &state)
Read the $Nodes section, adding Node objects to the mesh and recording the gmsh tag to Node ID mappin...
Definition mesh.cc:198
const std::vector< Node > & nodes() const
Get the Nodes in the mesh.
Definition mesh.h:76
const std::vector< size_t > & boundary_node_ids() const
Get the boundary node IDs.
Definition mesh.h:123
void AddNode(const Node &node)
Add node to Mesh.
Definition mesh.cc:35
void SetOutwardNormals()
Set the outward-pointing unit normal vector on each boundary node (Node::outward_normal),...
Definition mesh.cc:373
void InitializeNodeSolutions(const size_t n_ordinates, const QuadratureBase< Ordinate > &angular_quad_set, const SourceBank &source_bank)
Initialize node solution vectors to the correct lengths. Scalar and angular fluxes are set to 0 and s...
Definition mesh.cc:320
void CreateInteriorElementNodes(const GaussLobattoLegendre &gll_quadrature)
Create interior nodes on elements using Gauss-Lobatto-Legendre quadrature set and add to Mesh.
Definition mesh.cc:60
void UpdateNodeScalarFluxes(const QuadratureBase< Ordinate > &angular_quad_set)
Update the nodes' scalar flux values.
Definition mesh.cc:386
void CheckMaterialIDsOnElements()
Check that the material IDs on the elements are set on construction.
Definition mesh.cc:26
unsigned int dimension_
Spatial dimension of the mesh's elements, set by the first call to AddElement. 0 if no elements have ...
Definition mesh.h:235
int GetMaterialID(const std::vector< int > &curve_physical_tags, const std::unordered_map< int, std::string > &physical_names) const
Find the material ID for a curve entity, defined as the tag of the Physical Group on that curve whose...
Definition mesh.cc:289
void Prepare(const GaussLobattoLegendre &gll_quadrature)
Prepare the mesh for running a simulation by generating interior nodes using the GLL quadrature set,...
Definition mesh.cc:54
int GetSourceID(const std::vector< int > &curve_physical_tags, const std::unordered_map< int, std::string > &physical_names) const
Find the source ID for a curve entity, defined as the tag of the Physical Group on the curve whose na...
Definition mesh.cc:300
void UpdateNodeAngularFluxes(const SEMProblem &sem_problem, const size_t n_ordinates)
Update the nodes' angular flux values.
Definition mesh.cc:391
void ReadEntities(std::ifstream &file, GmshReadState &state)
Read the $Entities section into state.point_physical_tags and state.curve_physical_tags.
Definition mesh.cc:155
void ReadElements(std::ifstream &file, GmshReadState &state)
Read the $Elements section, adding Segment elements to the mesh for 2-node line elements....
Definition mesh.cc:228
std::vector< Node > nodes_
Nodes in the mesh.
Definition mesh.h:224
void CheckNodeIDs()
Check that all Node IDs are unique and continuous from 0 to N-1 for N total nodes.
Definition mesh.cc:102
void AddElement(std::unique_ptr< Element > element)
Add element to Mesh.
Definition mesh.cc:41
std::vector< size_t > boundary_node_ids_
Nodes in the mesh that exist on the mesh boundaries (that is, they have a boundary condition assigned...
Definition mesh.h:228
const std::vector< arma::Col< double > > & solution_vectors() const
Get the solution vectors.
Base class for defining a quadrature set.
T GetAbscissa(const unsigned int index) const
Get the abscissa corresponding to the index.
Wrapper class that owns a ProblemBase of the type corresponding to the given FEFormulation.
Definition sem_problem.h:18
ProblemBase * get() const
Get the underlying ProblemBase object.
Definition sem_problem.h:37
Bank holding the volumetric sources defined in the input file.
Definition source_bank.h:25
void UpdateScalarFlux(Node &node, const QuadratureBase< Ordinate > angular_quadrature)
Update the scalar flux of a node using the angular quadrature set.
Definition node.cc:15
Intermediate state accumulated while reading a gmsh file, shared across the section subroutines below...
Definition mesh.h:263
std::unordered_map< int, std::vector< int > > curve_physical_tags
Map from curve entity tag to its Physical Group tags.
Definition mesh.h:271
std::unordered_map< int, std::vector< int > > point_physical_tags
Map from point entity tag to its Physical Group tags.
Definition mesh.h:268
std::unordered_map< int, std::string > physical_names
Map from Physical Group tag to its name (e.g. "material:foo").
Definition mesh.h:265
std::unordered_map< size_t, size_t > node_tag_to_id
Map from gmsh node tag to the corresponding Node's ID in nodes_.
Definition mesh.h:275
A point in the mesh holding its coordinates, boundary info, and flux solution values.
Definition node.h:26
std::vector< double > angular_fluxes
Angular flux values in order of the SN quadrature set.
Definition node.h:51
double x
X-coordinate of the node.
Definition node.h:31
double scalar_flux
Scalar flux on the node.
Definition node.h:48
double z
Z-coordinate of the node.
Definition node.h:37
std::vector< double > source_fluxes
Source flux values in order of the SN quadrature set. These are scratch values: they are overwritten ...
Definition node.h:57
arma::vec3 outward_normal
Outward pointing normal vector. This vector is only relevant if the node is on a boundary.
Definition node.h:45
int bc_id
BC ID to access the boundary condition (BC) bank. ID of 0 is always NONE, meaning the node is interna...
Definition node.h:41
double y
Y-coordinate of the node.
Definition node.h:34
size_t id
Unique identifier for the node.
Definition node.h:28