PRISMS-PF Manual v3.0-pre
All Classes Functions Variables Enumerations Pages
boundary_parameters.h
1// SPDX-FileCopyrightText: © 2025 PRISMS Center at the University of Michigan
2// SPDX-License-Identifier: GNU Lesser General Public Version 2.1
3
4#ifndef boundary_parameters_h
5#define boundary_parameters_h
6
7#include <deal.II/base/point.h>
8#include <deal.II/base/types.h>
9#include <deal.II/base/utilities.h>
10
11#include <boost/algorithm/string/predicate.hpp>
12
13#include <prismspf/config.h>
14#include <prismspf/core/conditional_ostreams.h>
15#include <prismspf/core/exceptions.h>
16#include <prismspf/core/type_enums.h>
17#include <prismspf/core/types.h>
18#include <prismspf/core/variable_attributes.h>
19
20#include <cstddef>
21#include <map>
22#include <string>
23#include <vector>
24
25PRISMS_PF_BEGIN_NAMESPACE
26
32{
33public:
37 enum type : std::uint8_t
38 {
39 UNDEFINED_BOUNDARY,
40 NATURAL,
41 DIRICHLET,
42 PERIODIC,
43 NEUMANN,
44 NON_UNIFORM_DIRICHLET,
45 NON_UNIFORM_NEUMANN
46 };
47
51 bool
52 operator==(const boundaryCondition &other) const
53 {
54 return boundary_condition_map == other.boundary_condition_map &&
55 dirichlet_value_map == other.dirichlet_value_map;
56 }
57
58 // Map of boundary conditions and domain boundary for which they correspond to. For a
59 // simple geometry like a square the boundary ids are marked, in order, by x=0, x=max,
60 // y=0, y=max. More complex geometries can have somewhat arbitrary ordering, but will
61 // render some of our assertions moot.
62 std::map<dealii::types::boundary_id, type> boundary_condition_map;
63
64 // A map of boundary values for dirichlet boundary conditions
65 std::map<dealii::types::boundary_id, double> dirichlet_value_map;
66
70 [[nodiscard]] std::string
71 to_string(type i) const
72 {
73 switch (i)
74 {
75 case type::UNDEFINED_BOUNDARY:
76 return "UNDEFINED_BOUNDARY";
77 case type::NATURAL:
78 return "NATURAL";
79 case type::DIRICHLET:
80 return "DIRICHLET";
81 case type::PERIODIC:
82 return "PERIODIC";
83 case type::NEUMANN:
84 return "NEUMANN";
85 case type::NON_UNIFORM_DIRICHLET:
86 return "NON_UNIFORM_DIRICHLET";
87 case type::NON_UNIFORM_NEUMANN:
88 return "NON_UNIFORM_NEUMANN";
89 default:
90 return "UNKNOWN";
91 }
92 }
93};
94
98template <int dim>
100{
101public:
102 using BoundaryConditionMap =
103 std::map<types::index, std::map<unsigned int, boundaryCondition>>;
104 using BCList = std::map<types::index, std::map<unsigned int, std::string>>;
105 using PinnedPointMap = std::map<types::index, std::pair<double, dealii::Point<dim>>>;
106
110 void
112 const std::map<unsigned int, variableAttributes> &var_attributes);
113
117 [[nodiscard]] bool
118 check_duplicate_boundary_conditions(const types::index &index_1,
119 const types::index &index_2) const;
120
124 void
126
127 // Map of unfiltered boundary conditions strings. The first key is the global index. The
128 // second key is the number of dimensions.
129 BCList BC_list;
130
131 // Map of pinned points. The first key is the global index. The pair is the pinned
132 // value and point.
133 PinnedPointMap pinned_point_list;
134
135 // Map of boundary conditions. The first key is the global index. The second key is the
136 // number of dimensions.
137 BoundaryConditionMap boundary_condition_list;
138
139private:
143 void
144 set_boundary(const std::string &BC_string,
145 const types::index &index,
146 const unsigned int &component);
147
151 void
152 validate_boundary_conditions() const;
153};
154
155template <int dim>
156inline void
158 const std::map<unsigned int, variableAttributes> &var_attributes)
159{
160 for (const auto &[index, variable] : var_attributes)
161 {
162 // Ensure that boundary conditions are specified for all variables and their
163 // components
164 if (variable.field_type == fieldType::VECTOR)
165 {
166 for (unsigned int i = 0; i < dim; i++)
167 {
168 if (variable.is_postprocess)
169 {
170 set_boundary("NATURAL", variable.field_index, i);
171 }
172 else
173 {
174 AssertThrow(BC_list.find(variable.field_index) != BC_list.end(),
175 dealii::ExcMessage("Invalid entry"));
176 AssertThrow(BC_list.at(variable.field_index).find(i) !=
177 BC_list.at(variable.field_index).end(),
178 dealii::ExcMessage("Invalid entry"));
179 AssertThrow(!BC_list.at(variable.field_index).at(i).empty(),
180 dealii::ExcMessage(
181 "Boundary conditions must be specified "
182 "for all components in all vector field."));
183
184 set_boundary(BC_list.at(variable.field_index).at(i),
185 variable.field_index,
186 i);
187 }
188 }
189 }
190 else
191 {
192 if (variable.is_postprocess)
193 {
194 set_boundary("NATURAL", variable.field_index, 0);
195 }
196 else
197 {
198 AssertThrow(BC_list.find(variable.field_index) != BC_list.end(),
199 dealii::ExcMessage("Invalid entry"));
200 AssertThrow(BC_list.at(variable.field_index).find(0) !=
201 BC_list.at(variable.field_index).end(),
202 dealii::ExcMessage("Invalid entry"));
203 AssertThrow(!BC_list.at(variable.field_index).at(0).empty(),
204 dealii::ExcMessage("Boundary conditions must be specified "
205 "for all scalar fields."));
206
207 set_boundary(BC_list.at(variable.field_index).at(0),
208 variable.field_index,
209 0);
210 }
211 }
212 }
213
214 // Validate boundary conditions
215 validate_boundary_conditions();
216
217 // Clear the BC_list now that it's no longer necessary
218 BC_list.clear();
219
220#ifdef ADDITIONAL_OPTIMIZATIONS
221 // Check if any fields are duplicates in terms of boundary conditions
222 // TODO (landinjm): Clean this up
223 for (const auto &[index_1, variable_1] : var_attributes)
224 {
225 // Skip is the duplicate index has already been assigned
226 if (variable_1.duplicate_field_index != numbers::invalid_index)
227 {
228 continue;
229 }
230 if (variable_1.is_postprocess)
231 {
232 continue;
233 }
234
235 const auto field_type_1 = variable_1.field_type;
236
237 for (const auto &[index_2, variable_2] : var_attributes)
238 {
239 if (variable_2.is_postprocess)
240 {
241 continue;
242 }
243
244 bool is_duplicate = false;
245
246 const auto field_type_2 = variable_2.field_type;
247
248 is_duplicate = field_type_1 == field_type_2 &&
249 check_duplicate_boundary_conditions(index_1, index_2);
250
251 if (is_duplicate)
252 {
254 << "Field " << variable_1.name << " has the same boundary conditions as "
255 << variable_2.name << ". Using optimizations...\n";
256 variable_2.duplicate_field_index = index_1;
257 }
258 }
259 }
260#endif
261}
262
263template <int dim>
264inline bool
266 const types::index &index_1,
267 const types::index &index_2) const
268{
269 // If the indices are the same return false
270 if (index_1 == index_2)
271 {
272 return false;
273 }
274
275 Assert(boundary_condition_list.find(index_1) != boundary_condition_list.end(),
276 dealii::ExcMessage("Invalid entry for index = " + std::to_string(index_1)));
277 Assert(boundary_condition_list.find(index_2) != boundary_condition_list.end(),
278 dealii::ExcMessage("Invalid entry for index = " + std::to_string(index_2)));
279
280 bool is_duplicate = false;
281
282 // First check the boundary_condition_list
283 const auto &boundary_condition_1 = boundary_condition_list.at(index_1);
284 const auto &boundary_condition_2 = boundary_condition_list.at(index_2);
285
286 is_duplicate = boundary_condition_1 == boundary_condition_2;
287
288 // Check the pinned points
289 if (pinned_point_list.find(index_1) != pinned_point_list.end() &&
290 pinned_point_list.find(index_2) != pinned_point_list.end())
291 {
292 is_duplicate =
293 is_duplicate && pinned_point_list.at(index_1) == pinned_point_list.at(index_2);
294 }
295
296 return is_duplicate;
297}
298
299template <int dim>
300inline void
302{
304 << "================================================\n"
305 << " Boundary Parameters\n"
306 << "================================================\n";
307
308 for (const auto &[index, component_map] : boundary_condition_list)
309 {
310 conditionalOStreams::pout_summary() << "Index: " << index << "\n";
311 for (const auto &[component, boundary_condition] : component_map)
312 {
313 conditionalOStreams::pout_summary() << " Component: " << component << "\n";
314 for (const auto &[domain_id, boundary_type] :
315 boundary_condition.boundary_condition_map)
316 {
318 << " Boundary id: " << domain_id << " "
319 << "Condition: " << boundary_condition.to_string(boundary_type);
320 if (boundary_type == boundaryCondition::type::DIRICHLET)
321 {
323 << " = " << boundary_condition.dirichlet_value_map.at(domain_id);
324 }
326 }
327 }
328 }
329
330 if (!pinned_point_list.empty())
331 {
332 conditionalOStreams::pout_summary() << "Pinned field index: ";
333 }
334 for (const auto &[index, point_value_map] : pinned_point_list)
335 {
337 << index << "\n"
338 << " Value: " << point_value_map.first << "\n"
339 << " Point: " << point_value_map.second << "\n";
340 }
341 conditionalOStreams::pout_summary() << "\n" << std::flush;
342}
343
344template <int dim>
345inline void
346boundaryParameters<dim>::set_boundary(const std::string &BC_string,
347 const types::index &index,
348 const unsigned int &component)
349{
350 // Split string
351 auto BC_string_list = dealii::Utilities::split_string_list(BC_string);
352
353 // Check that there is either 1 or 2*dim entries in the vector. This can be changed
354 // later to support other geometries.
355 AssertThrow(
356 BC_string_list.size() == 1 || BC_string_list.size() == static_cast<size_t>(2 * dim),
357 dealii::ExcMessage("Either 1 or 2*dim boundary conditions must be specified."));
358
359 // If there is only 1 boundary condition resize BC_string_list, copying the first
360 // entry.
361 if (BC_string_list.size() == 1)
362 {
363 BC_string_list.resize(static_cast<size_t>(2 * dim), BC_string_list[0]);
364 }
365
366 // Assign boundary condition
367 boundaryCondition condition;
368 for (unsigned int i = 0; i < (2 * dim); i++)
369 {
370 if (boost::iequals(BC_string_list[i], "NATURAL"))
371 {
372 condition.boundary_condition_map.emplace(i, boundaryCondition::type::NATURAL);
373 }
374 else if (boost::iequals(BC_string_list[i].substr(0, 9), "DIRICHLET"))
375 {
376 condition.boundary_condition_map.emplace(i, boundaryCondition::type::DIRICHLET);
377 std::string dirichlet_value =
378 BC_string_list[i].substr(10, BC_string_list[i].size());
379 dirichlet_value = dealii::Utilities::trim(dirichlet_value);
380 condition.dirichlet_value_map.emplace(i,
381 dealii::Utilities::string_to_double(
382 dirichlet_value));
383 }
384 else if (boost::iequals(BC_string_list[i], "PERIODIC"))
385 {
386 condition.boundary_condition_map.emplace(i, boundaryCondition::type::PERIODIC);
387 }
388 else if (boost::iequals(BC_string_list[i].substr(0, 7), "NEUMANN"))
389 {
390 AssertThrow(false, FeatureNotImplemented("Neumann boundary conditions"));
391 }
392 else if (boost::iequals(BC_string_list[i], "NON_UNIFORM_DIRICHLET"))
393 {
394 condition.boundary_condition_map
395 .emplace(i, boundaryCondition::type::NON_UNIFORM_DIRICHLET);
396 }
397 else if (boost::iequals(BC_string_list[i], "NON_UNIFORM_NEUMANN"))
398 {
399 AssertThrow(false,
400 FeatureNotImplemented("Nonuniform neumann boundary conditions"));
401 }
402 else
403 {
404 AssertThrow(false,
405 dealii::ExcMessage("Invalid boundary condition " +
406 BC_string_list[i]));
407 }
408 // If periodic boundary conditions are used, ensure that they are applied on
409 // both sides of the domain.
410 if (i % 2 == 0)
411 {
412 AssertThrow(boost::iequals(BC_string_list[i], "PERIODIC") ==
413 boost::iequals(BC_string_list[i + 1], "PERIODIC"),
414 dealii::ExcMessage("Periodic boundary condition must be "
415 "specified on both sides of domain"));
416 }
417 }
418
419 boundary_condition_list[index].emplace(component, condition);
420}
421
422template <int dim>
423inline void
425{
426 // Throw a warning if the pinned point is not on a vertex
427 for (const auto &[index, point_value_map] : pinned_point_list)
428 {
429 const auto point = point_value_map.second;
430 bool on_vertex = false;
431 if constexpr (dim == 1)
432 {
433 const dealii::Point<1> vertex_1(0);
434
435 on_vertex = point == vertex_1;
436 }
437 else if constexpr (dim == 2)
438 {
439 const dealii::Point<2> vertex_1(0, 0);
440
441 on_vertex = point == vertex_1;
442 }
443 else if constexpr (dim == 3)
444 {
445 const dealii::Point<3> vertex_1(0, 0, 0);
446
447 on_vertex = point == vertex_1;
448 }
449 else
450 {
451 AssertThrow(false, UnreachableCode());
452 }
453
454 AssertThrow(on_vertex, dealii::ExcMessage("Pinned point must be on the origin"));
455 }
456
457 // Throw a warning if only some fields have periodic boundary conditions
458 std::vector<bool> periodic_ids(static_cast<dealii::types::boundary_id>(2 * dim), false);
459 for (const auto &[index, component_map] : boundary_condition_list)
460 {
461 for (const auto &[component, boundary_condition] : component_map)
462 {
463 for (const auto &[domain_id, boundary_type] :
464 boundary_condition.boundary_condition_map)
465 {
466 if (boundary_type == boundaryCondition::type::PERIODIC)
467 {
468 periodic_ids[domain_id] = true;
469 }
470 }
471 }
472 }
473 for (const auto &[index, component_map] : boundary_condition_list)
474 {
475 for (const auto &[component, boundary_condition] : component_map)
476 {
477 for (const auto &[domain_id, boundary_type] :
478 boundary_condition.boundary_condition_map)
479 {
480 if (boundary_type != boundaryCondition::type::PERIODIC &&
481 periodic_ids[domain_id])
482 {
483 AssertThrow(
484 false,
485 dealii::ExcMessage(
486 "All fields for a given domain id (side) must have periodic "
487 "boundary conditions if any field has periodic boundary "
488 "conditions"));
489 }
490 }
491 }
492 }
493}
494
495PRISMS_PF_END_NAMESPACE
496
497#endif
static dealii::ConditionalOStream & pout_verbose()
Verbose parallel output stream. Used for additional information in debug mode.
Definition conditional_ostreams.cc:41
static dealii::ConditionalOStream & pout_summary()
Log output stream for writing a summary.log file.
Definition conditional_ostreams.cc:22
Struct that stores relevant information for boundary conditions of a certain field.
Definition boundary_parameters.h:32
std::string to_string(type i) const
Enum to string for type.
Definition boundary_parameters.h:71
bool operator==(const boundaryCondition &other) const
Test for equality of two boundary conditions.
Definition boundary_parameters.h:52
type
Type of boundary condition.
Definition boundary_parameters.h:38
Struct that holds boundary parameters.
Definition boundary_parameters.h:100
void postprocess_and_validate(const std::map< unsigned int, variableAttributes > &var_attributes)
Postprocess and validate parameters.
Definition boundary_parameters.h:157
bool check_duplicate_boundary_conditions(const types::index &index_1, const types::index &index_2) const
Check whether the boundary conditions for two fields are the same.
Definition boundary_parameters.h:265
void print_parameter_summary() const
Print parameters to summary.log.
Definition boundary_parameters.h:301