BoundaryConditions.hpp
1 // Distributed under the MIT License.
2 // See LICENSE.txt for details.
3 
4 #pragma once
5 
7 
8 #include <array>
9 #include <memory>
10 #include <optional>
11 #include <random>
12 #include <regex>
13 #include <stdexcept>
14 #include <string>
15 
17 #include "DataStructures/DataBox/PrefixHelpers.hpp"
19 #include "DataStructures/DataVector.hpp"
20 #include "DataStructures/Index.hpp"
21 #include "DataStructures/Tensor/EagerMath/Magnitude.hpp"
24 #include "Domain/BoundaryConditions/BoundaryCondition.hpp"
25 #include "Domain/BoundaryConditions/Periodic.hpp"
26 #include "Domain/Tags.hpp"
27 #include "Evolution/BoundaryConditions/Type.hpp"
28 #include "Evolution/DiscontinuousGalerkin/Actions/ComputeTimeDerivativeHelpers.hpp"
29 #include "Evolution/DiscontinuousGalerkin/Actions/NormalCovectorAndMagnitude.hpp"
30 #include "Framework/Pypp.hpp"
32 #include "Framework/TestCreation.hpp"
35 #include "Helpers/Evolution/DiscontinuousGalerkin/NormalVectors.hpp"
37 #include "Utilities/Gsl.hpp"
38 #include "Utilities/NoSuchType.hpp"
39 #include "Utilities/TMPL.hpp"
40 #include "Utilities/TaggedTuple.hpp"
41 
42 namespace TestHelpers::evolution::dg {
43 /// Tags for testing DG code
44 namespace Tags {
45 /// Tag for a `TaggedTuple` that holds the name of the python function that
46 /// gives the result for computing what `Tag` should be.
47 ///
48 /// If a Tag is only needed for or different for a specific boundary correction,
49 /// then the `BoundaryCorrection` template parameter must be set.
50 ///
51 /// The `Tag` template parameter is an input to the `dg_package_data` function.
52 /// That is, the tag is one of the evolved variables, fluxes, or any other
53 /// `dg_package_data_temporary_tags` that the boundary correction needs.
54 template <typename Tag, typename BoundaryCorrection = NoSuchType>
56  using tag = Tag;
57  using boundary_correction = BoundaryCorrection;
58  using type = std::string;
59 };
60 
61 /// The name of the python function that returns the error message.
62 ///
63 /// If `BoundaryCorrection` is `NoSuchType` then the same function will be used
64 /// for all boundary corrections.
65 ///
66 /// The python function must return `None` if there shouldn't be an error
67 /// message.
68 template <typename BoundaryCorrection = NoSuchType>
70  using boundary_correction = BoundaryCorrection;
71  using type = std::string;
72 };
73 
74 /// Tag for a `TaggedTuple` that holds the range of validity for the variable
75 /// associated with `Tag`.
76 template <typename Tag>
77 struct Range {
78  using tag = Tag;
80 };
81 } // namespace Tags
82 
83 namespace detail {
84 template <typename ConversionClassList, typename... Args>
85 static std::optional<std::string> call_for_error_message(
86  const std::string& module_name, const std::string& function_name,
87  const Args&... t) {
88  static_assert(sizeof...(Args) > 0,
89  "Call to python which returns a Tensor of DataVectors must "
90  "pass at least one argument");
91  using ReturnType = std::optional<std::string>;
92 
93  PyObject* python_module = PyImport_ImportModule(module_name.c_str());
94  if (python_module == nullptr) {
95  PyErr_Print();
96  throw std::runtime_error{std::string("Could not find python module.\n") +
97  module_name};
98  }
99  PyObject* func = PyObject_GetAttrString(python_module, function_name.c_str());
100  if (func == nullptr or not PyCallable_Check(func)) {
101  if (PyErr_Occurred()) {
102  PyErr_Print();
103  }
104  throw std::runtime_error{"Could not find python function in module.\n"};
105  }
106 
107  const std::array<size_t, sizeof...(Args)> arg_sizes{
108  {pypp::detail::ContainerPackAndUnpack<
109  Args, ConversionClassList>::get_size(t)...}};
110  const size_t npts = *std::max_element(arg_sizes.begin(), arg_sizes.end());
111  for (size_t i = 0; i < arg_sizes.size(); ++i) {
112  if (gsl::at(arg_sizes, i) != 1 and gsl::at(arg_sizes, i) != npts) {
113  ERROR("Each argument must return size 1 or "
114  << npts
115  << " (the number of points in the DataVector), but argument number "
116  << i << " has size " << gsl::at(arg_sizes, i));
117  }
118  }
119  ReturnType result{};
120 
121  for (size_t s = 0; s < npts; ++s) {
122  PyObject* args = pypp::make_py_tuple(
123  pypp::detail::ContainerPackAndUnpack<Args, ConversionClassList>::unpack(
124  t, s)...);
125  auto ret =
126  pypp::detail::call_work<typename pypp::detail::ContainerPackAndUnpack<
127  ReturnType, ConversionClassList>::unpacked_container>(python_module,
128  func, args);
129  if (ret.has_value()) {
130  result = std::move(ret);
131  break;
132  }
133  }
134  Py_DECREF(func); // NOLINT
135  Py_DECREF(python_module); // NOLINT
136  return result;
137 }
138 
139 template <typename BoundaryCorrection, typename... PythonFunctionNameTags>
140 const std::string& get_python_error_message_function(
142  python_boundary_condition_functions) {
143  return tuples::get<tmpl::conditional_t<
144  tmpl::list_contains_v<tmpl::list<PythonFunctionNameTags...>,
145  Tags::PythonFunctionForErrorMessage<NoSuchType>>,
146  Tags::PythonFunctionForErrorMessage<NoSuchType>,
147  Tags::PythonFunctionForErrorMessage<BoundaryCorrection>>>(
148  python_boundary_condition_functions);
149 }
150 
151 template <typename Tag, typename BoundaryCorrection,
152  typename... PythonFunctionNameTags>
153 const std::string& get_python_tag_function(
155  python_boundary_condition_functions) {
156  return tuples::get<tmpl::conditional_t<
157  tmpl::list_contains_v<tmpl::list<PythonFunctionNameTags...>,
158  Tags::PythonFunctionName<Tag, NoSuchType>>,
159  Tags::PythonFunctionName<Tag, NoSuchType>,
160  Tags::PythonFunctionName<Tag, BoundaryCorrection>>>(
161  python_boundary_condition_functions);
162 }
163 
164 template <typename BoundaryConditionHelper, typename AllTagsOnFaceList,
165  typename... TagsFromFace, typename... VolumeArgs>
166 void apply_boundary_condition_impl(
167  BoundaryConditionHelper& boundary_condition_helper,
168  const Variables<AllTagsOnFaceList>& fields_on_interior_face,
169  tmpl::list<TagsFromFace...> /*meta*/,
170  const VolumeArgs&... volume_args) noexcept {
171  boundary_condition_helper(get<TagsFromFace>(fields_on_interior_face)...,
172  volume_args...);
173 }
174 
175 template <typename System, typename ConversionClassList,
176  typename BoundaryCorrection, typename... PythonFunctionNameTags,
177  typename BoundaryCondition, size_t FaceDim, typename DbTagsList,
178  typename... RangeTags, typename... EvolvedVariablesTags,
179  typename... BoundaryCorrectionPackagedDataInputTags,
180  typename... BoundaryConditionVolumeTags>
181 void test_boundary_condition_with_python_impl(
182  const gsl::not_null<std::mt19937*> generator,
183  const std::string& python_module,
185  python_boundary_condition_functions,
186  const BoundaryCondition& boundary_condition,
187  const Index<FaceDim>& face_points,
188  const db::DataBox<DbTagsList>& box_of_volume_data,
189  const tuples::TaggedTuple<Tags::Range<RangeTags>...>& ranges,
190  const bool use_moving_mesh, tmpl::list<EvolvedVariablesTags...> /*meta*/,
191  tmpl::list<BoundaryCorrectionPackagedDataInputTags...> /*meta*/,
192  tmpl::list<BoundaryConditionVolumeTags...> /*meta*/, const double epsilon) {
193  CAPTURE(FaceDim);
194  CAPTURE(python_module);
195  CAPTURE(face_points);
196  CAPTURE(use_moving_mesh);
197  CAPTURE(epsilon);
198  CAPTURE(pretty_type::short_name<BoundaryCorrection>());
199  const size_t number_of_points_on_face = face_points.product();
200 
201  using variables_tag = typename System::variables_tag;
202  using variables_tags = typename variables_tag::tags_list;
203  using flux_variables = typename System::flux_variables;
204  using dt_variables_tags = db::wrap_tags_in<::Tags::dt, variables_tags>;
205 
206  constexpr bool uses_ghost =
207  BoundaryCondition::bc_type ==
208  ::evolution::BoundaryConditions::Type::Ghost or
209  BoundaryCondition::bc_type ==
210  ::evolution::BoundaryConditions::Type::GhostAndTimeDerivative;
211  constexpr bool uses_time_derivative_condition =
212  BoundaryCondition::bc_type ==
213  ::evolution::BoundaryConditions::Type::TimeDerivative or
214  BoundaryCondition::bc_type ==
215  ::evolution::BoundaryConditions::Type::GhostAndTimeDerivative;
216 
217  // List that holds the inverse spatial metric if it's needed
218  using inverse_spatial_metric_list =
219  ::evolution::dg::Actions::detail::inverse_spatial_metric_tag<System>;
220  constexpr bool has_inv_spatial_metric =
221  ::evolution::dg::Actions::detail::has_inverse_spatial_metric_tag_v<
222  System>;
223 
224  // Set up tags for boundary conditions
225  using bcondition_interior_temp_tags =
226  typename BoundaryCondition::dg_interior_temporary_tags;
227  using bcondition_interior_prim_tags =
228  typename ::evolution::dg::Actions::detail::get_primitive_vars<
229  System::has_primitive_and_conservative_vars>::
230  template boundary_condition_interior_tags<BoundaryCondition>;
231  using bcondition_interior_evolved_vars_tags =
232  typename BoundaryCondition::dg_interior_evolved_variables_tags;
233  using bcondition_interior_dt_evolved_vars_tags =
234  ::evolution::dg::Actions::detail::get_dt_vars_from_boundary_condition<
235  BoundaryCondition>;
236  using bcondition_interior_deriv_evolved_vars_tags =
237  ::evolution::dg::Actions::detail::get_deriv_vars_from_boundary_condition<
238  BoundaryCondition>;
239  using bcondition_interior_tags = tmpl::remove_duplicates<tmpl::append<
240  tmpl::conditional_t<has_inv_spatial_metric,
241  tmpl::list<::evolution::dg::Actions::detail::
242  NormalVector<FaceDim + 1>>,
243  tmpl::list<>>,
244  bcondition_interior_evolved_vars_tags, bcondition_interior_prim_tags,
245  bcondition_interior_temp_tags, bcondition_interior_dt_evolved_vars_tags,
246  bcondition_interior_deriv_evolved_vars_tags>>;
247 
248  std::uniform_real_distribution<> dist(-1., 1.);
249 
250  Variables<tmpl::remove_duplicates<
251  tmpl::append<bcondition_interior_tags, inverse_spatial_metric_list>>>
252  interior_face_fields{number_of_points_on_face};
253  fill_with_random_values(make_not_null(&interior_face_fields), generator,
254  make_not_null(&dist));
255 
256  tmpl::for_each<tmpl::list<RangeTags...>>([&generator, &interior_face_fields,
257  &ranges](auto tag_v) {
258  using tag = tmpl::type_from<decltype(tag_v)>;
259  const std::array<double, 2>& range = tuples::get<Tags::Range<tag>>(ranges);
260  std::uniform_real_distribution<> local_dist(range[0], range[1]);
261  fill_with_random_values(make_not_null(&get<tag>(interior_face_fields)),
262  generator, make_not_null(&local_dist));
263  });
264 
265  auto interior_normal_covector = // Unnormalized at this point
267  tnsr::i<DataVector, FaceDim + 1, Frame::Inertial>>(
268  generator, make_not_null(&dist), number_of_points_on_face);
269  if constexpr (has_inv_spatial_metric) {
270  auto& inv_spatial_metric =
271  get<tmpl::front<inverse_spatial_metric_list>>(interior_face_fields);
272  detail::adjust_inverse_spatial_metric(make_not_null(&inv_spatial_metric));
273  auto& normal_vector =
274  get<::evolution::dg::Actions::detail::NormalVector<FaceDim + 1>>(
275  interior_face_fields);
276  detail::normalize_vector_and_covector(
277  make_not_null(&interior_normal_covector), make_not_null(&normal_vector),
278  inv_spatial_metric);
279  } else {
280  const Scalar<DataVector> magnitude = ::magnitude(interior_normal_covector);
281  for (DataVector& component : interior_normal_covector) {
282  component /= get(magnitude);
283  }
284  }
285 
286  // Set a random mesh velocity. We allow for velocities above 1 to test "faster
287  // than light" mesh movement.
289  if (use_moving_mesh) {
290  std::uniform_real_distribution<> local_dist(-2., 2.);
291  face_mesh_velocity =
292  make_with_random_values<tnsr::I<DataVector, FaceDim + 1>>(
293  generator, make_not_null(&local_dist), number_of_points_on_face);
294  }
295 
296  if constexpr (BoundaryCondition::bc_type ==
297  ::evolution::BoundaryConditions::Type::Outflow) {
298  // Outflow boundary conditions only check that all characteristic speeds
299  // are directed out of the element. If there are any inward directed
300  // fields then the boundary condition should error.
301  const auto apply_bc =
302  [&boundary_condition, &face_mesh_velocity, &interior_normal_covector,
303  &python_boundary_condition_functions,
304  &python_module](const auto&... face_and_volume_args) noexcept {
305  const std::optional<std::string> error_msg =
306  boundary_condition.dg_outflow(face_mesh_velocity,
307  interior_normal_covector,
308  face_and_volume_args...);
309  const std::string& python_error_msg_function =
310  get_python_error_message_function<BoundaryCorrection>(
311  python_boundary_condition_functions);
312  const auto python_error_message =
313  call_for_error_message<ConversionClassList>(
314  python_module, python_error_msg_function, face_mesh_velocity,
315  interior_normal_covector, face_and_volume_args...);
316  CAPTURE(python_error_msg_function);
317  CAPTURE(python_error_message.value_or(""));
318  CAPTURE(error_msg.value_or(""));
319  REQUIRE(python_error_message.has_value() == error_msg.has_value());
320  if (python_error_message.has_value() and error_msg.has_value()) {
321  std::smatch matcher{};
322  CHECK(std::regex_search(*error_msg, matcher,
323  std::regex{*python_error_message}));
324  }
325  };
326  apply_boundary_condition_impl(
327  apply_bc, interior_face_fields, bcondition_interior_tags{},
328  db::get<BoundaryConditionVolumeTags>(box_of_volume_data)...);
329  }
330 
331  if constexpr (uses_time_derivative_condition) {
332  Variables<dt_variables_tags> time_derivative_correction{
333  number_of_points_on_face};
334  auto apply_bc = [&boundary_condition, epsilon, &face_mesh_velocity,
335  &interior_normal_covector,
336  &python_boundary_condition_functions, &python_module,
337  &time_derivative_correction](
338  const auto&... interior_face_and_volume_args) {
339  const std::optional<std::string> error_msg =
340  boundary_condition.dg_time_derivative(
342  time_derivative_correction))...,
343  face_mesh_velocity, interior_normal_covector,
344  interior_face_and_volume_args...);
345 
346  const std::string& python_error_msg_function =
347  get_python_error_message_function<BoundaryCorrection>(
348  python_boundary_condition_functions);
349  const auto python_error_message =
350  call_for_error_message<ConversionClassList>(
351  python_module, python_error_msg_function, face_mesh_velocity,
352  interior_normal_covector, interior_face_and_volume_args...);
353  CAPTURE(python_error_msg_function);
354  CAPTURE(python_error_message.value_or(""));
355  CAPTURE(error_msg.value_or(""));
356  REQUIRE(python_error_message.has_value() == error_msg.has_value());
357  if (python_error_message.has_value() and error_msg.has_value()) {
358  std::smatch matcher{};
359  CHECK(std::regex_search(*error_msg, matcher,
360  std::regex{*python_error_message}));
361  }
362 
363  // Check that the values were set correctly
364  tmpl::for_each<dt_variables_tags>([&](auto dt_var_tag_v) {
365  using DtVarTag = tmpl::type_from<decltype(dt_var_tag_v)>;
366  // Use NoSuchType for the BoundaryCorrection since dt-type corrections
367  // should be boundary correction agnostic.
368  const std::string& python_tag_function =
369  get_python_tag_function<DtVarTag, NoSuchType>(
370  python_boundary_condition_functions);
371  CAPTURE(python_tag_function);
372  CAPTURE(pretty_type::short_name<DtVarTag>());
373  typename DtVarTag::type python_result{};
374  try {
375  python_result =
376  pypp::call<typename DtVarTag::type, ConversionClassList>(
377  python_module, python_tag_function, face_mesh_velocity,
378  interior_normal_covector, interior_face_and_volume_args...);
379  } catch (const std::exception& e) {
380  INFO("Failed python call with '" << e.what() << "'");
381  // Use REQUIRE(false) to print all the CAPTURE variables
382  REQUIRE(false);
383  }
385  get<DtVarTag>(time_derivative_correction), python_result,
386  Approx::custom().epsilon(epsilon).scale(1.0));
387  });
388  };
389  apply_boundary_condition_impl(
390  apply_bc, interior_face_fields, bcondition_interior_tags{},
391  db::get<BoundaryConditionVolumeTags>(box_of_volume_data)...);
392  }
393 
394  if constexpr (uses_ghost) {
395  using fluxes_tags =
396  db::wrap_tags_in<::Tags::Flux, flux_variables,
397  tmpl::size_t<FaceDim + 1>, Frame::Inertial>;
398  using correction_temp_tags =
399  typename BoundaryCorrection::dg_package_data_temporary_tags;
400  using correction_prim_tags =
401  typename ::evolution::dg::Actions::detail::get_primitive_vars<
402  System::has_primitive_and_conservative_vars>::
403  template f<BoundaryCorrection>;
404  using tags_on_exterior_face = tmpl::append<
405  variables_tags, fluxes_tags, correction_temp_tags, correction_prim_tags,
406  inverse_spatial_metric_list,
407  tmpl::list<
408  ::evolution::dg::Actions::detail::OneOverNormalVectorMagnitude,
409  ::evolution::dg::Actions::detail::NormalVector<FaceDim + 1>,
411  Variables<tags_on_exterior_face> exterior_face_fields{
412  number_of_points_on_face};
413  auto apply_bc = [&boundary_condition, epsilon, &exterior_face_fields,
414  &face_mesh_velocity, &interior_normal_covector,
415  &python_boundary_condition_functions, &python_module](
416  const auto&... interior_face_and_volume_args) {
417  const std::optional<std::string> error_msg = boundary_condition.dg_ghost(
418  make_not_null(&get<BoundaryCorrectionPackagedDataInputTags>(
419  exterior_face_fields))...,
420  face_mesh_velocity, interior_normal_covector,
421  interior_face_and_volume_args...);
422 
423  const std::string& python_error_msg_function =
424  get_python_error_message_function<BoundaryCorrection>(
425  python_boundary_condition_functions);
426  const auto python_error_message =
427  call_for_error_message<ConversionClassList>(
428  python_module, python_error_msg_function, face_mesh_velocity,
429  interior_normal_covector, interior_face_and_volume_args...);
430  CAPTURE(python_error_msg_function);
431  CAPTURE(python_error_message.value_or(""));
432  CAPTURE(error_msg.value_or(""));
433  REQUIRE(python_error_message.has_value() == error_msg.has_value());
434  if (python_error_message.has_value() and error_msg.has_value()) {
435  std::smatch matcher{};
436  CHECK(std::regex_search(*error_msg, matcher,
437  std::regex{*python_error_message}));
438  }
439  if (python_error_message.has_value()) {
440  return;
441  }
442 
443  // Check that the values were set correctly
444  tmpl::for_each<tmpl::list<BoundaryCorrectionPackagedDataInputTags...>>(
445  [&](auto boundary_correction_tag_v) {
446  using BoundaryCorrectionTag =
447  tmpl::type_from<decltype(boundary_correction_tag_v)>;
448  const std::string& python_tag_function =
449  get_python_tag_function<BoundaryCorrectionTag,
450  BoundaryCorrection>(
451  python_boundary_condition_functions);
452  CAPTURE(python_tag_function);
453  CAPTURE(pretty_type::short_name<BoundaryCorrectionTag>());
454  typename BoundaryCorrectionTag::type python_result{};
455  try {
456  python_result = pypp::call<typename BoundaryCorrectionTag::type,
457  ConversionClassList>(
458  python_module, python_tag_function, face_mesh_velocity,
459  interior_normal_covector, interior_face_and_volume_args...);
460  } catch (const std::exception& e) {
461  INFO("Failed python call with '" << e.what() << "'");
462  // Use REQUIRE(false) to print all the CAPTURE variables
463  REQUIRE(false);
464  }
466  get<BoundaryCorrectionTag>(exterior_face_fields), python_result,
467  Approx::custom().epsilon(epsilon).scale(1.0));
468  });
469  };
470  // Since any of the variables in `exterior_face_fields` could be mutated
471  // from their projected state, we don't pass in the interior tags explicitly
472  // since that will likely give a false sense of "no aliasing"
473  apply_boundary_condition_impl(
474  apply_bc, interior_face_fields, bcondition_interior_tags{},
475  db::get<BoundaryConditionVolumeTags>(box_of_volume_data)...);
476  }
477 }
478 
479 template <typename T, typename = std::void_t<>>
480 struct get_boundary_conditions_impl {
481  using type = tmpl::list<>;
482 };
483 
484 template <typename T>
485 struct get_boundary_conditions_impl<
486  T, std::void_t<typename T::boundary_conditions_base>> {
487  using type = typename T::boundary_conditions_base::creatable_classes;
488 };
489 
490 template <typename T>
491 using get_boundary_conditions = typename get_boundary_conditions_impl<T>::type;
492 } // namespace detail
493 
494 /*!
495  * \brief Test a boundary condition against python code and that it satisfies
496  * the required interface.
497  *
498  * The boundary conditions return a `std::optional<std::string>` that is the
499  * error message. For ghost cell boundary conditions they must also return the
500  * arguments needed by the boundary correction's `dg_package_data` function by
501  * `gsl::not_null`. Time derivative boundary conditions return the correction
502  * added to the time derivatives by `gsl::not_null`, while outflow boundary
503  * conditions should only check that the boundary is actually an outflow
504  * boundary. Therefore, the comparison implementation in python must have a
505  * function for each of these. Which function is called is specified using a
506  * `python_boundary_condition_functions` and `Tags::PythonFunctionName`.
507  *
508  * The specific boundary condition and system need to be explicitly given.
509  * Once the boundary condition and boundary correction base classes are
510  * listed/available in the `System` class we can remove needing to specify
511  * those. The `ConversionClassList` is forwarded to `pypp` to allow custom
512  * conversions of classes to python, such as analytic solutions or equations of
513  * state.
514  *
515  * - The random number generator is passed in so that the seed can be easily
516  * controlled externally. Use, e.g. `MAKE_GENERATOR(gen)` to create a
517  * generator.
518  * - The python function names are given in a `TaggedTuple`. A
519  * `TestHelpers::evolution::dg::Tags::PythonFunctionForErrorMessage` tag must
520  * be given for specifying the error message that the boundary condition
521  * returns. In many cases this function will simply return `None`. The tag can
522  * optionally specify the boundary correction for which it is to be used, so
523  * that a different error message could be printed if a different boundary
524  * correction is used.
525  * The `TestHelpers::evolution::dg::Tags::PythonFunctionName` tag is used to
526  * give the name of the python function for each return argument. The tags are
527  * the input to the `package_data` function for Ghost boundary conditions, and
528  * `::Tags::dt<evolved_var_tag>` for time derivative boundary conditions.
529  * - `factory_string` is a string used to create the boundary condition from the
530  * factory
531  * - `face_points` is the grid points on the interface. Generally 5 grid points
532  * per dimension in 2d and 3d is recommended to catch indexing errors. In 1d
533  * there is only ever one point on the interface
534  * - `box_of_volume_data` is a `db::DataBox` that contains all of the
535  * `dg_gridless_tags` of the boundary condition. This is not a `TaggedTuple`
536  * so that all the different types of tags, like base tags, can be supported
537  * and easily tested.
538  * - `ranges` is a `TaggedTuple` of
539  * `TestHelpers::evolution::dg::Tags::Range<tag>` specifying a custom range in
540  * which to generate the random values. This can be used for ensuring that
541  * positive quantities are randomly generated on the interval
542  * `[lower_bound,upper_bound)`, choosing `lower_bound` to be `0` or some small
543  * number. The default interval if a tag is not listed is `[-1,1)`.
544  * - `epsilon` is the relative tolerance to use in the random tests
545  */
546 template <typename BoundaryCondition, typename BoundaryConditionBase,
547  typename System, typename BoundaryCorrectionsList,
548  typename ConversionClassList = tmpl::list<>, size_t FaceDim,
549  typename DbTagsList, typename... RangeTags,
550  typename... PythonFunctionNameTags>
551 void test_boundary_condition_with_python(
552  const gsl::not_null<std::mt19937*> generator,
553  const std::string& python_module,
555  python_boundary_condition_functions,
556  const std::string& factory_string, const Index<FaceDim>& face_points,
557  const db::DataBox<DbTagsList>& box_of_volume_data,
558  const tuples::TaggedTuple<Tags::Range<RangeTags>...>& ranges,
559  const double epsilon = 1.0e-12) {
560  PUPable_reg(BoundaryCondition);
561  static_assert(std::is_final_v<std::decay_t<BoundaryCondition>>,
562  "All boundary condition classes must be marked `final`.");
563  using variables_tags = typename System::variables_tag::tags_list;
564  using flux_variables = typename System::flux_variables;
565  using fluxes_tags =
569  boundary_condition =
570  TestHelpers::test_factory_creation<BoundaryConditionBase>(
571  factory_string);
572 
573  REQUIRE_FALSE(
574  domain::BoundaryConditions::is_periodic(boundary_condition->get_clone()));
575 
576  tmpl::for_each<BoundaryCorrectionsList>(
577  [&boundary_condition, &box_of_volume_data, epsilon, &face_points,
578  &generator, &python_boundary_condition_functions, &python_module,
579  &ranges](auto boundary_correction_v) {
580  using BoundaryCorrection =
581  tmpl::type_from<decltype(boundary_correction_v)>;
582  using package_data_input_tags = tmpl::append<
583  variables_tags, fluxes_tags,
584  typename BoundaryCorrection::dg_package_data_temporary_tags,
585  typename ::evolution::dg::Actions::detail::get_primitive_vars<
586  System::has_primitive_and_conservative_vars>::
587  template f<BoundaryCorrection>>;
588  using boundary_condition_dg_gridless_tags =
589  typename BoundaryCondition::dg_gridless_tags;
590  for (const auto use_moving_mesh : {false, true}) {
591  detail::test_boundary_condition_with_python_impl<
592  System, ConversionClassList, BoundaryCorrection>(
593  generator, python_module, python_boundary_condition_functions,
594  dynamic_cast<const BoundaryCondition&>(*boundary_condition),
595  face_points, box_of_volume_data, ranges, use_moving_mesh,
596  variables_tags{}, package_data_input_tags{},
597  boundary_condition_dg_gridless_tags{}, epsilon);
598  // Now serialize and deserialize and test again
599  INFO("Test boundary condition after serialization.");
600  const auto deserialized_bc =
601  serialize_and_deserialize(boundary_condition);
602  detail::test_boundary_condition_with_python_impl<
603  System, ConversionClassList, BoundaryCorrection>(
604  generator, python_module, python_boundary_condition_functions,
605  dynamic_cast<const BoundaryCondition&>(*deserialized_bc),
606  face_points, box_of_volume_data, ranges, use_moving_mesh,
607  variables_tags{}, package_data_input_tags{},
608  boundary_condition_dg_gridless_tags{}, epsilon);
609  }
610  });
611 }
612 
613 /// Test that a boundary condition is correctly identified as periodic.
614 template <typename BoundaryCondition, typename BoundaryConditionBase>
615 void test_periodic_condition(const std::string& factory_string) {
616  PUPable_reg(BoundaryCondition);
617  const auto boundary_condition =
618  TestHelpers::test_factory_creation<BoundaryConditionBase>(factory_string);
619  REQUIRE(typeid(*boundary_condition.get()) == typeid(BoundaryCondition));
620  const auto bc_clone = boundary_condition->get_clone();
622  const auto bc_deserialized = serialize_and_deserialize(bc_clone);
623  CHECK(domain::BoundaryConditions::is_periodic(bc_deserialized));
624 }
625 } // namespace TestHelpers::evolution::dg
gsl::at
constexpr T & at(std::array< T, N > &arr, Size index)
Retrieve a entry from a container, with checks in Debug mode that the index being retrieved is valid.
Definition: Gsl.hpp:125
regex
std::string
Frame::Inertial
Definition: IndexType.hpp:44
std::exception
get
constexpr Tag::type & get(Variables< TagList > &v) noexcept
Return Tag::type pointing into the contiguous array.
Definition: Variables.hpp:660
Tags.hpp
TestingFramework.hpp
random
fill_with_random_values
void fill_with_random_values(const gsl::not_null< T * > data, const gsl::not_null< UniformRandomBitGenerator * > generator, const gsl::not_null< RandomNumberDistribution * > distribution) noexcept
Fill an existing data structure with random values.
Definition: MakeWithRandomValues.hpp:165
MakeWithRandomValues.hpp
Index
Definition: Index.hpp:31
CHECK_ITERABLE_CUSTOM_APPROX
#define CHECK_ITERABLE_CUSTOM_APPROX(a, b, appx)
Same as CHECK_ITERABLE_APPROX with user-defined Approx. The third argument should be of type Approx.
Definition: TestingFramework.hpp:134
Tags::Flux
Prefix indicating a flux.
Definition: Prefixes.hpp:40
TestHelpers::evolution::dg::Tags::Range
Tag for a TaggedTuple that holds the range of validity for the variable associated with Tag.
Definition: BoundaryConditions.hpp:77
TestHelpers::evolution::dg::Tags::PythonFunctionName
Tag for a TaggedTuple that holds the name of the python function that gives the result for computing ...
Definition: BoundaryConditions.hpp:55
get_size
decltype(auto) get_size(const T &t, SizeFunction size=GetContainerSize{}) noexcept
Retrieve the size of t if t.size() is a valid expression, otherwise if T is fundamental or a std::com...
Definition: ContainerHelpers.hpp:145
std::uniform_real_distribution
pypp::call
ReturnType call(const std::string &module_name, const std::string &function_name, const Args &... t)
Calls a Python function from a module/file with given parameters.
Definition: Pypp.hpp:494
TestHelpers.hpp
stdexcept
ERROR
#define ERROR(m)
prints an error message to the standard error stream and aborts the program.
Definition: Error.hpp:36
Pypp.hpp
DataBox.hpp
PyppFundamentals.hpp
array
Index.hpp
std::runtime_error
tuples::TaggedTuple
An associative container that is indexed by structs.
Definition: TaggedTuple.hpp:271
DataVector
Stores a collection of function values.
Definition: DataVector.hpp:46
std::regex
memory
serialize_and_deserialize
T serialize_and_deserialize(const T &t)
Serializes and deserializes an object t of type T
Definition: TestHelpers.hpp:45
std::decay_t
Variables.hpp
pypp::make_py_tuple
PyObject * make_py_tuple(const Args &... t)
Create a python tuple from Args.
Definition: PyppFundamentals.hpp:72
Tags::dt
Prefix indicating a time derivative.
Definition: Prefixes.hpp:29
Scalar
Tensor< T, Symmetry<>, index_list<> > Scalar
Definition: TypeAliases.hpp:21
make_with_random_values
ReturnType make_with_random_values(const gsl::not_null< UniformRandomBitGenerator * > generator, const gsl::not_null< RandomNumberDistribution * > distribution, const T &used_for_size) noexcept
Make a data structure and fill it with random values.
Definition: MakeWithRandomValues.hpp:185
Gsl.hpp
domain::BoundaryConditions::is_periodic
bool is_periodic(const std::unique_ptr< BoundaryCondition > &boundary_condition) noexcept
Check if a boundary condition inherits from MarkAsPeriodic, which constitutes as it being marked as a...
Definition: Periodic.cpp:9
Tensor.hpp
db::wrap_tags_in
tmpl::transform< TagList, tmpl::bind< Wrapper, tmpl::_1, tmpl::pin< Args >... > > wrap_tags_in
Create a new tmpl::list of tags by wrapping each tag in TagList in Wrapper<_, Args....
Definition: PrefixHelpers.hpp:30
magnitude
Scalar< DataType > magnitude(const Tensor< DataType, Symmetry< 1 >, index_list< Index >> &vector) noexcept
Compute the Euclidean magnitude of a rank-1 tensor.
Definition: Magnitude.hpp:27
optional
std::smatch
evolution::dg::Tags::NormalCovector
The normal covector to the interface.
Definition: NormalVectorTags.hpp:24
make_not_null
gsl::not_null< T * > make_not_null(T *ptr) noexcept
Construct a not_null from a pointer. Often this will be done as an implicit conversion,...
Definition: Gsl.hpp:880
Mesh.hpp
std::unique_ptr< domain::BoundaryConditions::BoundaryCondition >
Prefixes.hpp
Index::product
constexpr size_t product() const noexcept
The product of the indices. If Dim = 0, the product is defined as 1.
Definition: Index.hpp:72
TMPL.hpp
TestHelpers::evolution::dg::Tags::PythonFunctionForErrorMessage
The name of the python function that returns the error message.
Definition: BoundaryConditions.hpp:69
gsl::not_null
Require a pointer to not be a nullptr
Definition: ReadSpecThirdOrderPiecewisePolynomial.hpp:13
string