• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

sxs-collaboration / spectre / 4262558483

pending completion
4262558483

Pull #4753

github

GitHub
Merge 46028a9d8 into 37d7710c7
Pull Request #4753: Add worldtube boundary conditions to CSW

46 of 46 new or added lines in 2 files covered. (100.0%)

64002 of 66707 relevant lines covered (95.94%)

422054.68 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.93
/src/Domain/Creators/BinaryCompactObject.cpp
1
// Distributed under the MIT License.
2
// See LICENSE.txt for details.
3

4
#include "Domain/Creators/BinaryCompactObject.hpp"
5

6
#include <algorithm>
7
#include <array>
8
#include <cmath>
9
#include <cstddef>
10
#include <iterator>
11
#include <limits>
12
#include <memory>
13
#include <optional>
14
#include <string>
15
#include <unordered_map>
16
#include <unordered_set>
17
#include <utility>
18
#include <variant>
19
#include <vector>
20

21
#include "DataStructures/Tensor/TypeAliases.hpp"
22
#include "Domain/Block.hpp"  // IWYU pragma: keep
23
#include "Domain/BoundaryConditions/Periodic.hpp"
24
#include "Domain/CoordinateMaps/Affine.hpp"
25
#include "Domain/CoordinateMaps/CoordinateMap.hpp"
26
#include "Domain/CoordinateMaps/CoordinateMap.tpp"
27
#include "Domain/CoordinateMaps/Distribution.hpp"
28
#include "Domain/CoordinateMaps/Equiangular.hpp"
29
#include "Domain/CoordinateMaps/Frustum.hpp"
30
#include "Domain/CoordinateMaps/Identity.hpp"
31
#include "Domain/CoordinateMaps/Interval.hpp"
32
#include "Domain/CoordinateMaps/ProductMaps.hpp"
33
#include "Domain/CoordinateMaps/ProductMaps.tpp"
34
#include "Domain/CoordinateMaps/TimeDependent/CubicScale.hpp"
35
#include "Domain/CoordinateMaps/TimeDependent/ProductMaps.hpp"
36
#include "Domain/CoordinateMaps/TimeDependent/ProductMaps.tpp"
37
#include "Domain/CoordinateMaps/TimeDependent/Rotation.hpp"
38
#include "Domain/CoordinateMaps/TimeDependent/SphericalCompression.hpp"
39
#include "Domain/CoordinateMaps/Wedge.hpp"
40
#include "Domain/Creators/DomainCreator.hpp"  // IWYU pragma: keep
41
#include "Domain/Creators/ExpandOverBlocks.hpp"
42
#include "Domain/Domain.hpp"
43
#include "Domain/DomainHelpers.hpp"
44
#include "Domain/FunctionsOfTime/FixedSpeedCubic.hpp"
45
#include "Domain/FunctionsOfTime/PiecewisePolynomial.hpp"
46
#include "Domain/FunctionsOfTime/QuaternionFunctionOfTime.hpp"
47
#include "Domain/Structure/BlockNeighbor.hpp"  // IWYU pragma: keep
48
#include "Domain/Structure/ExcisionSphere.hpp"
49
#include "Utilities/MakeArray.hpp"
50

51
namespace Frame {
52
struct BlockLogical;
53
}  // namespace Frame
54

55
namespace domain::creators {
56

57
bool BinaryCompactObject::Object::is_excised() const {
486✔
58
  return inner_boundary_condition.has_value();
486✔
59
}
60

61
// Time-independent constructor
62
BinaryCompactObject::BinaryCompactObject(
40✔
63
    Object object_A, Object object_B, const double radius_enveloping_cube,
64
    const double outer_radius_domain,
65
    const typename InitialRefinement::type& initial_refinement,
66
    const typename InitialGridPoints::type& initial_number_of_grid_points,
67
    const bool use_equiangular_map, const bool use_projective_map,
68
    const double frustum_sphericity,
69
    const std::optional<double>& radius_enveloping_sphere,
70
    const CoordinateMaps::Distribution radial_distribution_outer_shell,
71
    std::unique_ptr<domain::BoundaryConditions::BoundaryCondition>
72
        outer_boundary_condition,
73
    const Options::Context& context)
40✔
74
    : object_A_(std::move(object_A)),
40✔
75
      object_B_(std::move(object_B)),
40✔
76
      radius_enveloping_cube_(radius_enveloping_cube),
77
      outer_radius_domain_(outer_radius_domain),
78
      use_equiangular_map_(use_equiangular_map),
79
      use_projective_map_(use_projective_map),
80
      frustum_sphericity_(frustum_sphericity),
81
      radial_distribution_outer_shell_(radial_distribution_outer_shell),
82
      outer_boundary_condition_(std::move(outer_boundary_condition)) {
270✔
83
  // Determination of parameters for domain construction:
84
  translation_ = 0.5 * (object_B_.x_coord + object_A_.x_coord);
40✔
85
  length_inner_cube_ = abs(object_A_.x_coord - object_B_.x_coord);
40✔
86
  length_outer_cube_ = 2.0 * radius_enveloping_cube_ / sqrt(3.0);
40✔
87
  if (use_projective_map_) {
40✔
88
    projective_scale_factor_ = length_inner_cube_ / length_outer_cube_;
40✔
89
  } else {
90
    projective_scale_factor_ = 1.0;
×
91
  }
92
  need_cube_to_sphere_transition_ =
40✔
93
      frustum_sphericity != 1.0 or radius_enveloping_sphere.has_value();
40✔
94

95
  // Calculate number of blocks
96
  // Layers 1, 2, 3, 4, and 5 have 12, 12, 10, 10, and 10 blocks, respectively,
97
  // for a total of 44, or 54 when the cube-to-sphere transition is needed.
98
  number_of_blocks_ = 44;
40✔
99
  if (need_cube_to_sphere_transition_) {
40✔
100
    number_of_blocks_ += 10;
28✔
101
  }
102

103
  // For each object whose interior is not excised, add 1 block
104
  if (not object_A_.is_excised()) {
40✔
105
    number_of_blocks_++;
19✔
106
  }
107
  if (not object_B_.is_excised()) {
40✔
108
    number_of_blocks_++;
4✔
109
  }
110

111
  if (object_A_.x_coord <= 0.0) {
40✔
112
    PARSE_ERROR(
1✔
113
        context,
114
        "The x-coordinate of ObjectA's center is expected to be positive.");
115
  }
116
  if (object_B_.x_coord >= 0.0) {
39✔
117
    PARSE_ERROR(
1✔
118
        context,
119
        "The x-coordinate of ObjectB's center is expected to be negative.");
120
  }
121
  if (length_outer_cube_ <= 2.0 * length_inner_cube_) {
38✔
122
    const double suggested_value = 2.0 * length_inner_cube_ * sqrt(3.0);
1✔
123
    PARSE_ERROR(
1✔
124
        context,
125
        "The radius for the enveloping cube is too small! The Frustums will be "
126
        "malformed. A recommended radius is:\n"
127
            << suggested_value);
128
  }
129
  if (object_A_.outer_radius < object_A_.inner_radius) {
37✔
130
    PARSE_ERROR(context,
1✔
131
                "ObjectA's inner radius must be less than its outer radius.");
132
  }
133
  if (object_B_.outer_radius < object_B_.inner_radius) {
36✔
134
    PARSE_ERROR(context,
1✔
135
                "ObjectB's inner radius must be less than its outer radius.");
136
  }
137
  if (object_A_.use_logarithmic_map and not object_A_.is_excised()) {
35✔
138
    PARSE_ERROR(
1✔
139
        context,
140
        "Using a logarithmically spaced radial grid in the part "
141
        "of Layer 1 enveloping Object A requires excising the interior of "
142
        "Object A");
143
  }
144
  if (object_B_.use_logarithmic_map and not object_B_.is_excised()) {
34✔
145
    PARSE_ERROR(
1✔
146
        context,
147
        "Using a logarithmically spaced radial grid in the part "
148
        "of Layer 1 enveloping Object B requires excising the interior of "
149
        "Object B");
150
  }
151
  if (object_A_.is_excised() and
48✔
152
      ((*object_A_.inner_boundary_condition == nullptr) !=
15✔
153
       (outer_boundary_condition_ == nullptr))) {
15✔
154
    PARSE_ERROR(context,
×
155
                "Must specify either both inner and outer boundary conditions "
156
                "or neither.");
157
  }
158
  if (object_B_.is_excised() and
63✔
159
      ((*object_B_.inner_boundary_condition == nullptr) !=
30✔
160
       (outer_boundary_condition_ == nullptr))) {
30✔
161
    PARSE_ERROR(context,
6✔
162
                "Must specify either both inner and outer boundary conditions "
163
                "or neither.");
164
  }
165
  using domain::BoundaryConditions::is_periodic;
166
  if (is_periodic(outer_boundary_condition_) or
27✔
167
      (object_A_.is_excised() and
23✔
168
       is_periodic(*object_A_.inner_boundary_condition)) or
65✔
169
      (object_B_.is_excised() and
23✔
170
       is_periodic(*object_B_.inner_boundary_condition))) {
21✔
171
    PARSE_ERROR(
7✔
172
        context,
173
        "Cannot have periodic boundary conditions with a binary domain");
174
  }
175

176
  // Create block names and groups
177
  static std::array<std::string, 6> wedge_directions{
178
      "UpperZ", "LowerZ", "UpperY", "LowerY", "UpperX", "LowerX"};
20✔
179
  const auto add_object_region = [this](const std::string& object_name,
80✔
180
                                        const std::string& region_name) {
960✔
181
    for (const std::string& wedge_direction : wedge_directions) {
560✔
182
      const std::string block_name =
183
          // NOLINTNEXTLINE(performance-inefficient-string-concatenation)
184
          object_name + region_name + wedge_direction;
480✔
185
      block_names_.push_back(block_name);
480✔
186
      block_groups_[object_name + region_name].insert(block_name);
480✔
187
    }
188
  };
80✔
189
  const auto add_object_interior = [this](const std::string& object_name) {
14✔
190
    const std::string block_name = object_name + "Interior";
14✔
191
    block_names_.push_back(block_name);
7✔
192
  };
7✔
193
  const auto add_outer_region = [this](const std::string& region_name) {
1,008✔
194
    for (const std::string& wedge_direction : wedge_directions) {
336✔
195
      for (const std::string& leftright : {"Left"s, "Right"s}) {
1,440✔
196
        if ((wedge_direction == "UpperX" and leftright == "Left") or
1,200✔
197
            (wedge_direction == "LowerX" and leftright == "Right")) {
624✔
198
          // The outer regions are divided in half perpendicular to the
199
          // x-axis at x=0. Therefore, the left side only has a block in
200
          // negative x-direction, and the right side only has one in
201
          // positive x-direction.
202
          continue;
96✔
203
        }
204
        // NOLINTNEXTLINE(performance-inefficient-string-concatenation)
205
        const std::string block_name =
206
            region_name + wedge_direction +
960✔
207
            (wedge_direction == "UpperX" or wedge_direction == "LowerX"
432✔
208
                 ? ""
1,392✔
209
                 : leftright);
960✔
210
        block_names_.push_back(block_name);
480✔
211
        block_groups_[region_name].insert(block_name);
480✔
212
      }
213
    }
214
  };
48✔
215
  add_object_region("ObjectA", "Shell");  // 6 blocks
20✔
216
  add_object_region("ObjectA", "Cube");   // 6 blocks
20✔
217
  add_object_region("ObjectB", "Shell");  // 6 blocks
20✔
218
  add_object_region("ObjectB", "Cube");   // 6 blocks
20✔
219
  add_outer_region("EnvelopingCube");     // 10 blocks
20✔
220
  if (need_cube_to_sphere_transition_) {
20✔
221
    add_outer_region("CubedShell");  // 10 blocks
8✔
222
  }
223
  add_outer_region("OuterShell");         // 10 blocks
20✔
224
  if (not object_A_.is_excised()) {
20✔
225
    add_object_interior("ObjectA");  // 1 block
5✔
226
  }
227
  if (not object_B_.is_excised()) {
20✔
228
    add_object_interior("ObjectB");  // 1 block
2✔
229
  }
230
  ASSERT(block_names_.size() == number_of_blocks_,
20✔
231
         "Number of block names (" << block_names_.size()
232
                                   << ") doesn't match number of blocks ("
233
                                   << number_of_blocks_ << ").");
234

235
  // Expand initial refinement and number of grid points over all blocks
236
  const ExpandOverBlocks<size_t, 3> expand_over_blocks{block_names_,
20✔
237
                                                       block_groups_};
60✔
238
  try {
239
    initial_refinement_ = std::visit(expand_over_blocks, initial_refinement);
20✔
240
  } catch (const std::exception& error) {
2✔
241
    PARSE_ERROR(context, "Invalid 'InitialRefinement': " << error.what());
1✔
242
  }
243
  try {
244
    initial_number_of_grid_points_ =
245
        std::visit(expand_over_blocks, initial_number_of_grid_points);
19✔
246
  } catch (const std::exception& error) {
2✔
247
    PARSE_ERROR(context, "Invalid 'InitialGridPoints': " << error.what());
1✔
248
  }
249

250
  // Compute the inner radius of the outer spherical shell. The computation
251
  // makes use of the refinement, so this can't be done earlier.
252
  if (radius_enveloping_sphere.has_value()) {
18✔
253
    radius_enveloping_sphere_ = radius_enveloping_sphere.value();
1✔
254
    if (radius_enveloping_sphere_ <= radius_enveloping_cube_ or
1✔
255
        radius_enveloping_sphere_ >= outer_radius_domain_) {
1✔
256
      PARSE_ERROR(
1✔
257
          context,
258
          "The 'OuterShell.InnerRadius' must be within 'EnvelopingCube.Radius' "
259
          "(" << radius_enveloping_cube_
260
              << ") and 'OuterShell.OuterRadius' (" << outer_radius_domain_
261
              << "), but is: " << radius_enveloping_sphere_
262
              << ". Set it to 'Auto' so a reasonable value is chosen "
263
                 "automatically.");
264
    }
265
  } else if (frustum_sphericity == 1.0) {
17✔
266
    radius_enveloping_sphere_ = radius_enveloping_cube_;
12✔
267
  } else {
268
    // Adjust the outer boundary of the cubed sphere to conform to the spacing
269
    // of the spherical shells after refinement, so the cubed sphere is the same
270
    // size as the first radial division of the spherical shell (for linear
271
    // mapping) or smaller by the same factor as adjacent radial divisions in
272
    // the spherical shell (for logarithmic or inverse mapping).
273
    const size_t addition_to_outer_layer_radial_refinement_level =
274
        initial_refinement_[44][2] - initial_refinement_[34][2];
5✔
275
    const size_t radial_divisions_in_outer_layers =
276
        pow(2, addition_to_outer_layer_radial_refinement_level) + 1;
5✔
277
    // Use the `Interval` class to divide the interval between
278
    // `radius_enveloping_cube_` and `outer_radius_domain_` into
279
    // `radial_divisions_in_outer_layers` pieces. Choose
280
    // `radius_enveloping_sphere_` as the first of those pieces.
281
    radius_enveloping_sphere_ =
5✔
282
        domain::CoordinateMaps::Interval{// Source interval
5✔
283
                                         0., 1.,
284
                                         // Target interval
285
                                         radius_enveloping_cube_,
286
                                         outer_radius_domain_,
287
                                         // Distribution in target interval
288
                                         radial_distribution_outer_shell_,
289
                                         // Position of the singularity for log
290
                                         // and 1/r maps (in target interval)
291
                                         0.}(std::array<double, 1>{
10✔
292
            {// Inner radius in source interval that is mapped to target
293
             // interval
294
             1. / static_cast<double>(radial_divisions_in_outer_layers)}})[0];
5✔
295
  }
296
}
17✔
297

298
// Time-dependent constructor, with additional options for specifying
299
// the time-dependent maps
300
BinaryCompactObject::BinaryCompactObject(
4✔
301
    double initial_time, double expansion_map_outer_boundary,
302
    double initial_expansion, double initial_expansion_velocity,
303
    double asymptotic_velocity_outer_boundary,
304
    double decay_timescale_outer_boundary_velocity,
305
    std::array<double, 3> initial_angular_velocity,
306
    std::array<double, 2> initial_size_map_values,
307
    std::array<double, 2> initial_size_map_velocities,
308
    std::array<double, 2> initial_size_map_accelerations, Object object_A,
309
    Object object_B, double radius_enveloping_cube, double outer_radius_domain,
310
    const typename InitialRefinement::type& initial_refinement,
311
    const typename InitialGridPoints::type& initial_number_of_grid_points,
312
    const bool use_equiangular_map, bool use_projective_map,
313
    double frustum_sphericity,
314
    const std::optional<double>& radius_enveloping_sphere,
315
    CoordinateMaps::Distribution radial_distribution_outer_shell,
316
    std::unique_ptr<domain::BoundaryConditions::BoundaryCondition>
317
        outer_boundary_condition,
318
    const Options::Context& context)
4✔
319
    : BinaryCompactObject(std::move(object_A), std::move(object_B),
4✔
320
                          radius_enveloping_cube, outer_radius_domain,
321
                          initial_refinement, initial_number_of_grid_points,
322
                          use_equiangular_map, use_projective_map,
323
                          frustum_sphericity, radius_enveloping_sphere,
324
                          radial_distribution_outer_shell,
325
                          std::move(outer_boundary_condition), context) {
8✔
326
  enable_time_dependence_ = true;
4✔
327
  initial_time_ = initial_time;
4✔
328
  expansion_map_outer_boundary_ = expansion_map_outer_boundary;
4✔
329
  initial_expansion_ = initial_expansion;
4✔
330
  initial_expansion_velocity_ = initial_expansion_velocity;
4✔
331
  asymptotic_velocity_outer_boundary_ = asymptotic_velocity_outer_boundary;
4✔
332
  decay_timescale_outer_boundary_velocity_ =
4✔
333
      decay_timescale_outer_boundary_velocity;
334
  // quat = (cos(theta/2), nhat*sin(theta/2)) but we always take theta = 0
335
  // initially
336
  initial_quaternion_ = DataVector{{1.0, 0.0, 0.0, 0.0}};
4✔
337
  initial_size_map_values_ = initial_size_map_values;
4✔
338
  initial_size_map_velocities_ = initial_size_map_velocities;
4✔
339
  initial_size_map_accelerations_ = initial_size_map_accelerations;
4✔
340

341
  for (size_t i = 0; i < initial_angular_velocity.size(); i++) {
16✔
342
    initial_angular_velocity_[i] = gsl::at(initial_angular_velocity, i);
24✔
343
  }
344
}
4✔
345

346
Domain<3> BinaryCompactObject::create_domain() const {
26✔
347
  const double inner_sphericity_A = object_A_.is_excised() ? 1.0 : 0.0;
26✔
348
  const double inner_sphericity_B = object_B_.is_excised() ? 1.0 : 0.0;
26✔
349

350
  using Maps = std::vector<std::unique_ptr<
351
      CoordinateMapBase<Frame::BlockLogical, Frame::Inertial, 3>>>;
352

353
  const std::vector<domain::CoordinateMaps::Distribution>
354
      object_A_radial_distribution{
355
          object_A_.use_logarithmic_map
26✔
356
              ? domain::CoordinateMaps::Distribution::Logarithmic
26✔
357
              : domain::CoordinateMaps::Distribution::Linear};
52✔
358

359
  const std::vector<domain::CoordinateMaps::Distribution>
360
      object_B_radial_distribution{
361
          object_B_.use_logarithmic_map
26✔
362
              ? domain::CoordinateMaps::Distribution::Logarithmic
26✔
363
              : domain::CoordinateMaps::Distribution::Linear};
52✔
364

365
  Maps maps{};
52✔
366

367
  // --- Blocks enclosing each object (24 blocks) ---
368
  //
369
  // Each object is surrounded by 6 inner wedges that make a sphere, and another
370
  // 6 outer wedges that transition to a cube.
371

372
  // ObjectA/B is on the right/left, respectively.
373
  const Translation translation_A{
374
      Affine{-1.0, 1.0, -1.0 + object_A_.x_coord, 1.0 + object_A_.x_coord},
26✔
375
      Identity2D{}};
26✔
376
  const Translation translation_B{
377
      Affine{-1.0, 1.0, -1.0 + object_B_.x_coord, 1.0 + object_B_.x_coord},
26✔
378
      Identity2D{}};
26✔
379

380
  Maps maps_center_A =
381
      domain::make_vector_coordinate_map_base<Frame::BlockLogical,
382
                                              Frame::Inertial, 3>(
383
          sph_wedge_coordinate_maps(object_A_.inner_radius,
52✔
384
                                    object_A_.outer_radius, inner_sphericity_A,
26✔
385
                                    1.0, use_equiangular_map_, false, {},
26✔
386
                                    object_A_radial_distribution),
387
          translation_A);
52✔
388
  Maps maps_cube_A =
389
      domain::make_vector_coordinate_map_base<Frame::BlockLogical,
390
                                              Frame::Inertial, 3>(
391
          sph_wedge_coordinate_maps(object_A_.outer_radius,
52✔
392
                                    sqrt(3.0) * 0.5 * length_inner_cube_, 1.0,
26✔
393
                                    0.0, use_equiangular_map_),
26✔
394
          translation_A);
52✔
395
  Maps maps_center_B =
396
      domain::make_vector_coordinate_map_base<Frame::BlockLogical,
397
                                              Frame::Inertial, 3>(
398
          sph_wedge_coordinate_maps(object_B_.inner_radius,
52✔
399
                                    object_B_.outer_radius, inner_sphericity_B,
26✔
400
                                    1.0, use_equiangular_map_, false, {},
26✔
401
                                    object_B_radial_distribution),
402
          translation_B);
52✔
403
  Maps maps_cube_B =
404
      domain::make_vector_coordinate_map_base<Frame::BlockLogical,
405
                                              Frame::Inertial, 3>(
406
          sph_wedge_coordinate_maps(object_B_.outer_radius,
52✔
407
                                    sqrt(3.0) * 0.5 * length_inner_cube_, 1.0,
26✔
408
                                    0.0, use_equiangular_map_),
26✔
409
          translation_B);
52✔
410

411
  std::move(maps_center_A.begin(), maps_center_A.end(),
412
            std::back_inserter(maps));
26✔
413
  std::move(maps_cube_A.begin(), maps_cube_A.end(), std::back_inserter(maps));
26✔
414
  std::move(maps_center_B.begin(), maps_center_B.end(),
415
            std::back_inserter(maps));
26✔
416
  std::move(maps_cube_B.begin(), maps_cube_B.end(), std::back_inserter(maps));
26✔
417

418
  // --- Frustums enclosing both objects (10 blocks) ---
419
  //
420
  // The two abutting cubes are enclosed by a layer of frustums that form a cube
421
  // (if frustum_sphericity_ is 0) or a sphere (if frustum_sphericity_ is 1)
422
  // surrounding both objects. While the two objects can be offset from the
423
  // origin to account for their center of mass, the enclosing frustums are
424
  // centered at the origin.
425
  Maps maps_frustums = domain::make_vector_coordinate_map_base<
426
      Frame::BlockLogical, Frame::Inertial, 3>(
427
      frustum_coordinate_maps(length_inner_cube_, length_outer_cube_,
×
428
                              use_equiangular_map_, {{-translation_, 0.0, 0.0}},
26✔
429
                              projective_scale_factor_, frustum_sphericity_));
52✔
430
  std::move(maps_frustums.begin(), maps_frustums.end(),
431
            std::back_inserter(maps));
26✔
432

433
  // --- (Optional) transition from frustums to sphere (10 blocks) ---
434
  //
435
  // Another layer of wedges transitions from the surrounding frustums to a
436
  // surrounding sphere. Not needed when the surrounding frustums are already
437
  // spherical.
438
  if (need_cube_to_sphere_transition_) {
26✔
439
    Maps maps_first_outer_shell = domain::make_vector_coordinate_map_base<
440
        Frame::BlockLogical, Frame::Inertial, 3>(sph_wedge_coordinate_maps(
10✔
441
        radius_enveloping_cube_, radius_enveloping_sphere_, frustum_sphericity_,
5✔
442
        1.0, use_equiangular_map_, true, {},
5✔
443
        {domain::CoordinateMaps::Distribution::Linear}));
10✔
444
    std::move(maps_first_outer_shell.begin(), maps_first_outer_shell.end(),
445
              std::back_inserter(maps));
5✔
446
  }
447

448
  // --- Outer spherical shell (10 blocks) ---
449
  Maps maps_second_outer_shell = domain::make_vector_coordinate_map_base<
450
      Frame::BlockLogical, Frame::Inertial, 3>(sph_wedge_coordinate_maps(
52✔
451
      radius_enveloping_sphere_, outer_radius_domain_, 1.0, 1.0,
26✔
452
      use_equiangular_map_, true, {}, {radial_distribution_outer_shell_}));
78✔
453
  std::move(maps_second_outer_shell.begin(), maps_second_outer_shell.end(),
454
            std::back_inserter(maps));
26✔
455

456
  // --- (Optional) object centers (0 to 2 blocks) ---
457
  //
458
  // Each object can optionally be filled with a cube-shaped block, in which
459
  // case the enclosing wedges configured above transition from the cube to a
460
  // sphere.
461
  if (not object_A_.is_excised()) {
26✔
462
    const double scaled_r_inner_A = object_A_.inner_radius / sqrt(3.0);
5✔
463
    if (use_equiangular_map_) {
5✔
464
      maps.emplace_back(
465
          make_coordinate_map_base<Frame::BlockLogical, Frame::Inertial>(
4✔
466
              Equiangular3D{Equiangular(-1.0, 1.0, -1.0 * scaled_r_inner_A,
4✔
467
                                        scaled_r_inner_A),
468
                            Equiangular(-1.0, 1.0, -1.0 * scaled_r_inner_A,
469
                                        scaled_r_inner_A),
470
                            Equiangular(-1.0, 1.0, -1.0 * scaled_r_inner_A,
471
                                        scaled_r_inner_A)},
472
              translation_A));
2✔
473
    } else {
474
      maps.emplace_back(
475
          make_coordinate_map_base<Frame::BlockLogical, Frame::Inertial>(
6✔
476
              Affine3D{
6✔
477
                  Affine(-1.0, 1.0, -1.0 * scaled_r_inner_A, scaled_r_inner_A),
478
                  Affine(-1.0, 1.0, -1.0 * scaled_r_inner_A, scaled_r_inner_A),
479
                  Affine(-1.0, 1.0, -1.0 * scaled_r_inner_A, scaled_r_inner_A)},
480
              translation_A));
3✔
481
    }
482
  }
483
  if (not object_B_.is_excised()) {
26✔
484
    const double scaled_r_inner_B = object_B_.inner_radius / sqrt(3.0);
2✔
485
    if (use_equiangular_map_) {
2✔
486
      maps.emplace_back(
487
          make_coordinate_map_base<Frame::BlockLogical, Frame::Inertial>(
×
488
              Equiangular3D{Equiangular(-1.0, 1.0, -1.0 * scaled_r_inner_B,
×
489
                                        scaled_r_inner_B),
490
                            Equiangular(-1.0, 1.0, -1.0 * scaled_r_inner_B,
491
                                        scaled_r_inner_B),
492
                            Equiangular(-1.0, 1.0, -1.0 * scaled_r_inner_B,
493
                                        scaled_r_inner_B)},
494
              translation_B));
×
495
    } else {
496
      maps.emplace_back(
497
          make_coordinate_map_base<Frame::BlockLogical, Frame::Inertial>(
4✔
498
              Affine3D{
4✔
499
                  Affine(-1.0, 1.0, -1.0 * scaled_r_inner_B, scaled_r_inner_B),
500
                  Affine(-1.0, 1.0, -1.0 * scaled_r_inner_B, scaled_r_inner_B),
501
                  Affine(-1.0, 1.0, -1.0 * scaled_r_inner_B, scaled_r_inner_B)},
502
              translation_B));
2✔
503
    }
504
  }
505

506
  // Excision spheres
507
  // - Block 0 through 5 enclose object A, and 12 through 17 enclose object B.
508
  // - The 3D wedge map is oriented such that the lower-zeta logical direction
509
  //   points radially inward.
510
  std::unordered_map<std::string, ExcisionSphere<3>> excision_spheres{};
52✔
511
  if (object_A_.is_excised()) {
26✔
512
    excision_spheres.emplace(
513
        "ExcisionSphereA",
514
        ExcisionSphere<3>{
147✔
515
            object_A_.inner_radius,
21✔
516
            tnsr::I<double, 3, Frame::Grid>{{object_A_.x_coord, 0.0, 0.0}},
21✔
517
            {{0, Direction<3>::lower_zeta()},
21✔
518
             {1, Direction<3>::lower_zeta()},
21✔
519
             {2, Direction<3>::lower_zeta()},
21✔
520
             {3, Direction<3>::lower_zeta()},
21✔
521
             {4, Direction<3>::lower_zeta()},
21✔
522
             {5, Direction<3>::lower_zeta()}}});
63✔
523
  }
524
  if (object_B_.is_excised()) {
26✔
525
    excision_spheres.emplace(
526
        "ExcisionSphereB",
527
        ExcisionSphere<3>{
168✔
528
            object_B_.inner_radius,
24✔
529
            tnsr::I<double, 3, Frame::Grid>{{object_B_.x_coord, 0.0, 0.0}},
24✔
530
            {{12, Direction<3>::lower_zeta()},
24✔
531
             {13, Direction<3>::lower_zeta()},
24✔
532
             {14, Direction<3>::lower_zeta()},
24✔
533
             {15, Direction<3>::lower_zeta()},
24✔
534
             {16, Direction<3>::lower_zeta()},
24✔
535
             {17, Direction<3>::lower_zeta()}}});
72✔
536
  }
537

538
  // Have corners determined automatically
539
  Domain<3> domain{std::move(maps), std::move(excision_spheres), block_names_,
52✔
540
                   block_groups_};
78✔
541

542
  // Inject the hard-coded time-dependence
543
  if (enable_time_dependence_) {
26✔
544
    // Note on frames: Because the relevant maps will all be composed before
545
    // they are used, all maps here go from Frame::Grid (the frame after the
546
    // final time-independent map is applied) to Frame::Inertial
547
    // (the frame after the final time-dependent map is applied).
548
    using CubicScaleMap = domain::CoordinateMaps::TimeDependent::CubicScale<3>;
549
    using CubicScaleMapForComposition =
550
        domain::CoordinateMap<Frame::Grid, Frame::Inertial, CubicScaleMap>;
551

552
    using RotationMap3D = domain::CoordinateMaps::TimeDependent::Rotation<3>;
553
    using RotationMapForComposition =
554
        domain::CoordinateMap<Frame::Grid, Frame::Inertial, RotationMap3D>;
555

556
    using CubicScaleAndRotationMapForComposition =
557
        domain::CoordinateMap<Frame::Grid, Frame::Inertial, CubicScaleMap,
558
                              RotationMap3D>;
559

560
    using CompressionMap =
561
        domain::CoordinateMaps::TimeDependent::SphericalCompression<false>;
562
    using CompressionMapForComposition =
563
        domain::CoordinateMap<Frame::Grid, Frame::Inertial, CompressionMap>;
564

565
    using CompressionAndCubicScaleAndRotationMapForComposition =
566
        domain::CoordinateMap<Frame::Grid, Frame::Inertial, CompressionMap,
567
                              CubicScaleMap, RotationMap3D>;
568

569
    std::vector<std::unique_ptr<
570
        domain::CoordinateMapBase<Frame::Grid, Frame::Inertial, 3>>>
571
        block_maps{number_of_blocks_};
8✔
572

573
    // Some maps (e.g. expansion, rotation) are applied to all blocks,
574
    // while other maps (e.g. size) are only applied to some blocks. So
575
    // there are several different distinct combinations of time-dependent
576
    // maps that will be applied.
577
    // Here, set the time-dependent maps for each distinct combination in
578
    // a single block. Then, set the maps of the other blocks by cloning
579
    // the maps from the appropriate block.
580

581
    // All blocks except possibly blocks 0-5 and 12-17 get the same map, so
582
    // initialize the final block with the "base" map (here a composition of an
583
    // expansion and a rotation).
584
    block_maps[number_of_blocks_ - 1] =
4✔
585
        std::make_unique<CubicScaleAndRotationMapForComposition>(
8✔
586
            domain::push_back(
8✔
587
                CubicScaleMapForComposition{CubicScaleMap{
8✔
588
                    expansion_map_outer_boundary_,
4✔
589
                    expansion_function_of_time_name_,
590
                    expansion_function_of_time_name_ + "OuterBoundary"s}},
8✔
591
                RotationMapForComposition{
8✔
592
                    RotationMap3D{rotation_function_of_time_name_}}));
12✔
593

594
    // Initialize the first block of the layer 1 blocks for each object
595
    // (specifically, initialize block 0 and block 12). If excising interior
596
    // A or B, the block maps for the coresponding layer 1 blocks (blocks 0-5
597
    // for object A, blocks 12-17 for object B) should also include a size map.
598
    // If not excising interior A or B, the layer 1 blocks for that object
599
    // will have the same map as the final block.
600
    if (object_A_.is_excised()) {
4✔
601
      block_maps[0] = std::make_unique<
4✔
602
          CompressionAndCubicScaleAndRotationMapForComposition>(
8✔
603
          domain::push_back(
8✔
604
              CompressionMapForComposition{
8✔
605
                  CompressionMap{size_map_function_of_time_names_[0],
8✔
606
                                 object_A_.inner_radius,
4✔
607
                                 object_A_.outer_radius,
4✔
608
                                 {{object_A_.x_coord, 0.0, 0.0}}}},
4✔
609
              domain::push_back(
8✔
610
                  CubicScaleMapForComposition{CubicScaleMap{
8✔
611
                      expansion_map_outer_boundary_,
4✔
612
                      expansion_function_of_time_name_,
613
                      expansion_function_of_time_name_ + "OuterBoundary"s}},
8✔
614
                  RotationMapForComposition{
8✔
615
                      RotationMap3D{rotation_function_of_time_name_}})));
12✔
616
    } else {
617
      block_maps[0] = block_maps[number_of_blocks_ - 1]->get_clone();
×
618
    }
619
    if (object_B_.is_excised()) {
4✔
620
      block_maps[12] = std::make_unique<
4✔
621
          CompressionAndCubicScaleAndRotationMapForComposition>(
8✔
622
          domain::push_back(
8✔
623
              CompressionMapForComposition{
8✔
624
                  CompressionMap{size_map_function_of_time_names_[1],
8✔
625
                                 object_B_.inner_radius,
4✔
626
                                 object_B_.outer_radius,
4✔
627
                                 {{object_B_.x_coord, 0.0, 0.0}}}},
4✔
628
              domain::push_back(
8✔
629
                  CubicScaleMapForComposition{CubicScaleMap{
8✔
630
                      expansion_map_outer_boundary_,
4✔
631
                      expansion_function_of_time_name_,
632
                      expansion_function_of_time_name_ + "OuterBoundary"}},
8✔
633
                  RotationMapForComposition{
8✔
634
                      RotationMap3D{rotation_function_of_time_name_}})));
12✔
635
    } else {
636
      block_maps[12] = block_maps[number_of_blocks_ - 1]->get_clone();
×
637
    }
638

639
    // Fill in the rest of the block maps by cloning the relevant maps
640
    for (size_t block = 1; block < number_of_blocks_ - 1; ++block) {
172✔
641
      if (block < 6) {
168✔
642
        block_maps[block] = block_maps[0]->get_clone();
20✔
643
      } else if (block == 12) {
148✔
644
        continue;  // block 12 already initialized
4✔
645
      } else if (block > 12 and block < 18) {
144✔
646
        block_maps[block] = block_maps[12]->get_clone();
20✔
647
      } else {
648
        block_maps[block] = block_maps[number_of_blocks_ - 1]->get_clone();
124✔
649
      }
650
    }
651

652
    // Finally, inject the time dependent maps into the corresponding blocks
653
    for (size_t block = 0; block < number_of_blocks_; ++block) {
180✔
654
      domain.inject_time_dependent_map_for_block(block,
352✔
655
                                                 std::move(block_maps[block]));
176✔
656
    }
657
  }
658

659
  return domain;
52✔
660
}
661

662
std::vector<DirectionMap<
663
    3, std::unique_ptr<domain::BoundaryConditions::BoundaryCondition>>>
664
BinaryCompactObject::external_boundary_conditions() const {
12✔
665
  if (outer_boundary_condition_ == nullptr) {
12✔
666
    return {};
5✔
667
  }
668
  std::vector<DirectionMap<
669
      3, std::unique_ptr<domain::BoundaryConditions::BoundaryCondition>>>
670
      boundary_conditions{number_of_blocks_};
14✔
671
  // Excision surfaces
672
  for (size_t i = 0; i < 6; ++i) {
49✔
673
    // Block 0 - 5 wrap excision surface A
674
    if (object_A_.is_excised()) {
42✔
675
      boundary_conditions[i][Direction<3>::lower_zeta()] =
18✔
676
          (*object_A_.inner_boundary_condition)->get_clone();
36✔
677
    }
678
    // Blocks 12 - 17 wrap excision surface B
679
    if (object_B_.is_excised()) {
42✔
680
      boundary_conditions[i + 12][Direction<3>::lower_zeta()] =
36✔
681
          (*object_B_.inner_boundary_condition)->get_clone();
72✔
682
    }
683
  }
684
  // Outer boundary
685
  const size_t offset_outer_blocks = need_cube_to_sphere_transition_ ? 44 : 34;
7✔
686
  for (size_t i = 0; i < 10; ++i) {
77✔
687
    boundary_conditions[i + offset_outer_blocks][Direction<3>::upper_zeta()] =
70✔
688
        outer_boundary_condition_->get_clone();
140✔
689
  }
690
  return boundary_conditions;
7✔
691
}
692

693
std::unordered_map<std::string,
694
                   std::unique_ptr<domain::FunctionsOfTime::FunctionOfTime>>
695
BinaryCompactObject::functions_of_time(
12✔
696
    const std::unordered_map<std::string, double>& initial_expiration_times)
697
    const {
698
  std::unordered_map<std::string,
699
                     std::unique_ptr<domain::FunctionsOfTime::FunctionOfTime>>
700
      result{};
12✔
701
  if (not enable_time_dependence_) {
12✔
702
    return result;
×
703
  }
704

705
  // Get existing function of time names that are used for the maps and assign
706
  // their initial expiration time to infinity (i.e. not expiring)
707
  std::unordered_map<std::string, double> expiration_times{
708
      {expansion_function_of_time_name_,
709
       std::numeric_limits<double>::infinity()},
12✔
710
      {rotation_function_of_time_name_,
711
       std::numeric_limits<double>::infinity()},
12✔
712
      {size_map_function_of_time_names_[0],
12✔
713
       std::numeric_limits<double>::infinity()},
12✔
714
      {size_map_function_of_time_names_[1],
12✔
715
       std::numeric_limits<double>::infinity()}};
120✔
716

717
  // If we have control systems, overwrite these expiration times with the ones
718
  // supplied by the control system
719
  for (auto& [name, expr_time] : initial_expiration_times) {
36✔
720
    expiration_times[name] = expr_time;
24✔
721
  }
722

723
  // ExpansionMap FunctionOfTime for the function \f$a(t)\f$ in the
724
  // domain::CoordinateMaps::TimeDependent::CubicScale map
725
  result[expansion_function_of_time_name_] =
12✔
726
      std::make_unique<FunctionsOfTime::PiecewisePolynomial<2>>(
12✔
727
          initial_time_,
12✔
728
          std::array<DataVector, 3>{
24✔
729
              {{initial_expansion_}, {initial_expansion_velocity_}, {0.0}}},
12✔
730
          expiration_times.at(expansion_function_of_time_name_));
24✔
731

732
  // ExpansionMap FunctionOfTime for the function \f$b(t)\f$ in the
733
  // domain::CoordinateMaps::TimeDependent::CubicScale map
734
  result[expansion_function_of_time_name_ + "OuterBoundary"s] =
24✔
735
      std::make_unique<FunctionsOfTime::FixedSpeedCubic>(
12✔
736
          1.0, initial_time_, asymptotic_velocity_outer_boundary_,
×
737
          decay_timescale_outer_boundary_velocity_);
24✔
738

739
  // RotationMap FunctionOfTime for the rotation angles about each axis.
740
  // The initial rotation angles don't matter as we never actually use the
741
  // angles themselves. We only use their derivatives (omega) to determine map
742
  // parameters. In theory we could determine each initital angle from the input
743
  // axis-angle representation, but we don't need to.
744
  result[rotation_function_of_time_name_] =
12✔
745
      std::make_unique<FunctionsOfTime::QuaternionFunctionOfTime<3>>(
12✔
746
          initial_time_, std::array<DataVector, 1>{initial_quaternion_},
24✔
747
          std::array<DataVector, 4>{
24✔
748
              {{3, 0.0}, initial_angular_velocity_, {3, 0.0}, {3, 0.0}}},
12✔
749
          expiration_times.at(rotation_function_of_time_name_));
24✔
750

751
  // CompressionMap FunctionOfTime for objects A and B
752
  for (size_t i = 0; i < size_map_function_of_time_names_.size(); i++) {
36✔
753
    result[gsl::at(size_map_function_of_time_names_, i)] =
24✔
754
        std::make_unique<FunctionsOfTime::PiecewisePolynomial<3>>(
24✔
755
            initial_time_,
24✔
756
            std::array<DataVector, 4>{
48✔
757
                {{gsl::at(initial_size_map_values_, i)},
24✔
758
                 {gsl::at(initial_size_map_velocities_, i)},
48✔
759
                 {gsl::at(initial_size_map_accelerations_, i)},
48✔
760
                 {0.0}}},
761
            expiration_times.at(gsl::at(size_map_function_of_time_names_, i)));
48✔
762
  }
763

764
  return result;
12✔
765
}
766
}  // namespace domain::creators
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc