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

Open-Sn / opensn / 30332291031

26 Jul 2026 04:28AM UTC coverage: 77.831% (+0.02%) from 77.816%
30332291031

push

github

web-flow
Merge pull request #1117 from wdhawkins/precursor_fixes

Precursor fixes for transient problems

112 of 134 new or added lines in 13 files covered. (83.58%)

4 existing lines in 3 files now uncovered.

26510 of 34061 relevant lines covered (77.83%)

81752699.98 hits per line

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

84.59
/modules/linear_boltzmann_solvers/lbs_problem/lbs_problem.cc
1
// SPDX-FileCopyrightText: 2024 The OpenSn Authors <https://open-sn.github.io/opensn/>
2
// SPDX-License-Identifier: MIT
3

4
#include "modules/linear_boltzmann_solvers/lbs_problem/lbs_problem.h"
5
#include "modules/linear_boltzmann_solvers/lbs_problem/point_source/point_source.h"
6
#include "modules/linear_boltzmann_solvers/lbs_problem/groupset/lbs_groupset.h"
7
#include "framework/field_functions/field_function_grid_based.h"
8
#include "framework/materials/multi_group_xs/multi_group_xs.h"
9
#include "framework/mesh/mesh_continuum/mesh_continuum.h"
10
#include "framework/utils/hdf_utils.h"
11
#include "framework/object_factory.h"
12
#include "framework/logging/log.h"
13
#include "framework/runtime.h"
14
#include "framework/data_types/allowable_range.h"
15
#include "framework/utils/error.h"
16
#include "framework/utils/timer.h"
17
#include "framework/utils/caliper_scopes.h"
18
#include "caliper/cali.h"
19
#include <algorithm>
20
#include <iomanip>
21
#include <fstream>
22
#include <cstring>
23
#include <cassert>
24
#include <memory>
25
#include <stdexcept>
26
#include <sys/stat.h>
27
#include <unordered_map>
28
#include <functional>
29
#include <utility>
30

31
namespace opensn
32
{
33

34
InputParameters
35
LBSProblem::GetInputParameters()
1,420✔
36
{
37
  InputParameters params = Problem::GetInputParameters();
1,420✔
38

39
  params.ChangeExistingParamToOptional("name", "LBSProblem");
2,840✔
40

41
  params.AddRequiredParameter<std::shared_ptr<MeshContinuum>>("mesh", "Mesh");
2,840✔
42

43
  params.AddRequiredParameter<unsigned int>("num_groups",
2,840✔
44
                                            "The total number of groups within the solver");
45

46
  params.AddRequiredParameterArray("groupsets",
2,840✔
47
                                   "An array of blocks each specifying the input parameters for a "
48
                                   "<TT>LBSGroupset</TT>.");
49
  params.LinkParameterToBlock("groupsets", "LBSGroupset");
2,840✔
50

51
  params.AddRequiredParameterArray("xs_map",
2,840✔
52
                                   "Cross-section map from block IDs to cross-section objects.");
53

54
  params.AddOptionalParameterArray<std::shared_ptr<VolumetricSource>>(
2,840✔
55
    "volumetric_sources", {}, "An array of handles to volumetric sources.");
56

57
  params.AddOptionalParameterArray<std::shared_ptr<PointSource>>(
2,840✔
58
    "point_sources", {}, "An array of point sources.");
59

60
  params.AddOptionalParameterBlock(
2,840✔
61
    "options", ParameterBlock(), "Block of options. See <TT>OptionsBlock</TT>.");
2,840✔
62
  params.LinkParameterToBlock("options", "OptionsBlock");
2,840✔
63

64
  params.AddOptionalParameter("use_gpus", false, "Offload the sweep computation to GPUs.");
2,840✔
65

66
  return params;
1,420✔
67
}
×
68

69
LBSProblem::LBSProblem(const InputParameters& params)
1,420✔
70
  : Problem(params),
71
    num_groups_(params.GetParamValue<unsigned int>("num_groups")),
1,420✔
72
    grid_(params.GetSharedPtrParam<MeshContinuum>("mesh")),
1,420✔
73
    use_gpus_(params.GetParamValue<bool>("use_gpus"))
4,260✔
74
{
75
  // Check system for GPU acceleration
76
  if (use_gpus_)
1,420✔
77
  {
78
#ifdef __OPENSN_WITH_GPU__
79
    CheckCapableDevices();
157✔
80
#else
81
    OpenSnInvalidArgument(
82
      GetName() + ": GPU support was requested, but OpenSn was built without CUDA enabled.");
83
#endif // __OPENSN_WITH_GPU__
84
  }
85

86
  // Initialize options
87
  if (params.IsParameterValid("options"))
1,420✔
88
  {
89
    auto options_params = LBSProblem::GetOptionsBlock();
1,163✔
90
    options_params.AssignParameters(params.GetParam("options"));
1,165✔
91
    ParseOptions(options_params);
1,161✔
92
  }
1,163✔
93

94
  // Set geometry type
95
  geometry_type_ = grid_->GetGeometryType();
1,418✔
96
  OpenSnInvalidArgumentIf(geometry_type_ == GeometryType::INVALID,
1,418✔
97
                          GetName() + ": Invalid geometry type.");
98

99
  InitializeGroupsets(params);
1,418✔
100
  InitializeSources(params);
1,418✔
101
  InitializeXSMap(params);
1,418✔
102
  InitializeMaterials();
1,418✔
103
}
1,440✔
104

105
const LBSOptions&
106
LBSProblem::GetOptions() const
1,902,732,211✔
107
{
108
  return options_;
1,902,732,211✔
109
}
110

111
double
112
LBSProblem::GetTime() const
1,419,992✔
113
{
114
  return time_;
1,419,992✔
115
}
116

117
void
118
LBSProblem::SetTime(double time)
27,104✔
119
{
120
  time_ = time;
27,104✔
121
}
27,104✔
122

123
void
124
LBSProblem::SetTimeStep(double dt)
4,132✔
125
{
126
  OpenSnInvalidArgumentIf(dt <= 0.0, GetName() + ": dt must be greater than zero.");
4,132✔
127
  dt_ = dt;
4,132✔
128
}
4,132✔
129

130
double
131
LBSProblem::GetTimeStep() const
2,147,483,647✔
132
{
133
  return dt_;
2,147,483,647✔
134
}
135

136
void
137
LBSProblem::SetTheta(double theta)
856✔
138
{
139
  OpenSnInvalidArgumentIf(theta <= 0.0 or theta > 1.0,
856✔
140
                          GetName() + ": theta must be in (0.0, 1.0].");
141
  theta_ = theta;
856✔
142
}
856✔
143

144
double
145
LBSProblem::GetTheta() const
2,147,483,647✔
146
{
147
  return theta_;
2,147,483,647✔
148
}
149

150
bool
151
LBSProblem::IsTimeDependent() const
×
152
{
153
  return false;
×
154
}
155

156
void
157
LBSProblem::SetTimeDependentMode()
×
158
{
159
  OpenSnLogicalError(GetName() + ": Time-dependent mode is not supported for this problem type.");
×
160
}
161

162
void
163
LBSProblem::SetSteadyStateMode()
×
164
{
165
  // Steady-state is the default for problem types without time-dependent support.
166
}
×
167

168
GeometryType
169
LBSProblem::GetGeometryType() const
4✔
170
{
171
  return geometry_type_;
4✔
172
}
173

174
unsigned int
175
LBSProblem::GetNumMoments() const
2,192,384✔
176
{
177
  return num_moments_;
2,192,384✔
178
}
179

180
unsigned int
181
LBSProblem::GetMaxCellDOFCount() const
2,656✔
182
{
183
  return max_cell_dof_count_;
2,656✔
184
}
185

186
unsigned int
187
LBSProblem::GetMinCellDOFCount() const
2,656✔
188
{
189
  return min_cell_dof_count_;
2,656✔
190
}
191

192
bool
193
LBSProblem::UseGPUs() const
3,932✔
194
{
195
  return use_gpus_;
3,932✔
196
}
197

198
unsigned int
199
LBSProblem::GetNumGroups() const
2,789,297✔
200
{
201
  return num_groups_;
2,789,297✔
202
}
203

204
unsigned int
205
LBSProblem::GetScatteringOrder() const
26✔
206
{
207
  return scattering_order_;
26✔
208
}
209

210
unsigned int
211
LBSProblem::GetNumPrecursors() const
×
212
{
213
  return num_precursors_;
×
214
}
215

216
unsigned int
217
LBSProblem::GetMaxPrecursorsPerMaterial() const
742,677✔
218
{
219
  return max_precursors_per_material_;
742,677✔
220
}
221

222
const std::vector<LBSGroupset>&
223
LBSProblem::GetGroupsets() const
29,176✔
224
{
225
  return groupsets_;
29,176✔
226
}
227

228
LBSGroupset&
229
LBSProblem::GetGroupset(size_t groupset_id)
9,720,178✔
230
{
231
  return groupsets_.at(groupset_id);
9,720,178✔
232
}
233

234
const LBSGroupset&
235
LBSProblem::GetGroupset(size_t groupset_id) const
×
236
{
237
  return groupsets_.at(groupset_id);
×
238
}
239

240
size_t
241
LBSProblem::GetNumGroupsets() const
16,866✔
242
{
243
  return groupsets_.size();
16,866✔
244
}
245

246
void
247
LBSProblem::AddPointSource(std::shared_ptr<PointSource> point_source)
×
248
{
249
  point_sources_.push_back(point_source);
×
250
  point_sources_.back()->Initialize(*this);
×
251
}
×
252

253
void
254
LBSProblem::ClearPointSources()
×
255
{
256
  point_sources_.clear();
×
257
}
×
258

259
const std::vector<std::shared_ptr<PointSource>>&
260
LBSProblem::GetPointSources() const
159,741✔
261
{
262
  return point_sources_;
159,741✔
263
}
264

265
void
266
LBSProblem::AddVolumetricSource(std::shared_ptr<VolumetricSource> volumetric_source)
24✔
267
{
268
  volumetric_sources_.push_back(volumetric_source);
24✔
269
  volumetric_sources_.back()->Initialize(*this);
24✔
270
}
24✔
271

272
void
273
LBSProblem::ClearVolumetricSources()
24✔
274
{
275
  volumetric_sources_.clear();
24✔
276
}
24✔
277

278
const std::vector<std::shared_ptr<VolumetricSource>>&
279
LBSProblem::GetVolumetricSources() const
159,696✔
280
{
281
  return volumetric_sources_;
159,696✔
282
}
283

284
const BlockID2XSMap&
285
LBSProblem::GetBlockID2XSMap() const
4,811,345✔
286
{
287
  return block_id_to_xs_map_;
4,811,345✔
288
}
289

290
void
291
LBSProblem::SetBlockID2XSMap(const BlockID2XSMap& xs_map)
356✔
292
{
293
  const BlockID2XSMap old_xs_map = block_id_to_xs_map_;
356✔
294
  const size_t old_max_precursors_per_material = max_precursors_per_material_;
356✔
295
  const auto old_precursor_new_state = precursor_new_local_;
356✔
296
  const auto old_precursor_old_state = precursor_old_local_;
356✔
297

298
  block_id_to_xs_map_ = xs_map;
356✔
299
  InitializeMaterials();
356✔
300

301
  if (options_.use_precursors)
356✔
302
  {
303
    const size_t num_cells = grid_->local_cells.size();
264✔
304
    const size_t new_max_precursors_per_material = max_precursors_per_material_;
264✔
305
    const size_t num_precursor_dofs = num_cells * new_max_precursors_per_material;
264✔
306

307
    std::vector<double> remapped_precursors_new(num_precursor_dofs, 0.0);
264✔
308
    std::vector<double> remapped_precursors_old(num_precursor_dofs, 0.0);
264✔
309
    if (old_precursor_new_state.size() == num_cells * old_max_precursors_per_material)
264✔
310
    {
311
      for (const auto& cell : grid_->local_cells)
27,476✔
312
      {
313
        unsigned int old_num_precursors = 0;
27,212✔
314
        if (const auto old_xs_it = old_xs_map.find(cell.block_id); old_xs_it != old_xs_map.end())
27,212✔
315
        {
316
          const auto& old_xs = old_xs_it->second;
27,212✔
317
          if (old_xs->IsFissionable())
27,212✔
318
            old_num_precursors = old_xs->GetPrecursors().size();
3,652✔
319
        }
320

321
        const auto& new_xs = block_id_to_xs_map_.at(cell.block_id);
27,212✔
322
        const unsigned int new_num_precursors =
27,212✔
323
          new_xs->IsFissionable() ? new_xs->GetPrecursors().size() : 0;
27,212✔
324
        const unsigned int num_precursors_to_copy =
27,212✔
325
          std::min(old_num_precursors, new_num_precursors);
27,212✔
326

327
        const size_t old_base = cell.local_id * old_max_precursors_per_material;
27,212✔
328
        const size_t new_base = cell.local_id * new_max_precursors_per_material;
27,212✔
329
        for (unsigned int j = 0; j < num_precursors_to_copy; ++j)
31,260✔
330
        {
331
          remapped_precursors_new[new_base + j] = old_precursor_new_state[old_base + j];
4,048✔
332
          remapped_precursors_old[new_base + j] = old_precursor_old_state[old_base + j];
4,048✔
333
        }
334
      }
335
    }
336

337
    precursor_new_local_ = std::move(remapped_precursors_new);
264✔
338
    precursor_old_local_ = std::move(remapped_precursors_old);
264✔
339
  }
264✔
340
  else
341
  {
342
    precursor_new_local_.clear();
92✔
343
    precursor_old_local_.clear();
92✔
344
  }
345

346
  ResetGPUCarriers();
356✔
347
  InitializeGPUExtras();
356✔
348
}
356✔
349

350
std::shared_ptr<MeshContinuum>
351
LBSProblem::GetGrid() const
9,504,324✔
352
{
353
  return grid_;
9,504,324✔
354
}
355

356
const SpatialDiscretization&
357
LBSProblem::GetSpatialDiscretization() const
765,413✔
358
{
359
  return *discretization_;
765,413✔
360
}
361

362
const std::vector<UnitCellMatrices>&
363
LBSProblem::GetUnitCellMatrices() const
4,359,338✔
364
{
365
  return unit_cell_matrices_;
4,359,338✔
366
}
367

368
const std::map<uint64_t, UnitCellMatrices>&
369
LBSProblem::GetUnitGhostCellMatrices() const
50✔
370
{
371
  return unit_ghost_cell_matrices_;
50✔
372
}
373

374
const std::vector<CellLBSView>&
375
LBSProblem::GetCellTransportViews() const
3,858,948✔
376
{
377
  return cell_transport_views_;
3,858,948✔
378
}
379

380
OutflowBank&
381
LBSProblem::GetOutflowBank()
157✔
382
{
383
  return outflow_bank_;
157✔
384
}
385

386
std::vector<CellOutflowView>&
387
LBSProblem::GetCellOutflowViews()
2,679,099✔
388
{
389
  return cell_outflow_views_;
2,679,099✔
390
}
391

392
const std::vector<CellOutflowView>&
393
LBSProblem::GetCellOutflowViews() const
×
394
{
395
  return cell_outflow_views_;
×
396
}
397

398
void
399
LBSProblem::ConfigureOutflowStorage(const bool include_internal_faces)
86✔
400
{
401
  store_internal_outflows_ = include_internal_faces;
86✔
402
  cell_outflow_views_.clear();
86✔
403
  outflow_bank_ = OutflowBank(*grid_, num_groups_, store_internal_outflows_);
86✔
404
  cell_outflow_views_ = outflow_bank_.GetCellOutflowViews();
86✔
405
  ResetGPUCarriers();
86✔
406
  InitializeGPUExtras();
86✔
407
}
86✔
408

409
const UnknownManager&
410
LBSProblem::GetUnknownManager() const
55,425✔
411
{
412
  return flux_moments_uk_man_;
55,425✔
413
}
414

415
size_t
416
LBSProblem::GetLocalNodeCount() const
21,016✔
417
{
418
  return local_node_count_;
21,016✔
419
}
420

421
std::uint64_t
422
LBSProblem::GetGlobalNodeCount() const
4,187✔
423
{
424
  return global_node_count_;
4,187✔
425
}
426

427
std::vector<double>&
428
LBSProblem::GetQMomentsLocal()
795,677✔
429
{
430
  return q_moments_local_;
795,677✔
431
}
432

433
const std::vector<double>&
434
LBSProblem::GetQMomentsLocal() const
×
435
{
436
  return q_moments_local_;
×
437
}
438

439
const std::vector<double>&
440
LBSProblem::GetExtSrcMomentsLocal() const
704,752✔
441
{
442
  return ext_src_moments_local_;
704,752✔
443
}
444

445
void
446
LBSProblem::SetExtSrcMomentsFrom(const std::vector<double>& ext_src_moments)
4✔
447
{
448
  if (not phi_old_local_.empty())
4✔
449
    OpenSnLogicalErrorIf(ext_src_moments.size() != phi_old_local_.size(),
4✔
450
                         "SetExtSrcMomentsFrom size mismatch. Provided size=" +
451
                           std::to_string(ext_src_moments.size()) +
452
                           ", expected local DOFs=" + std::to_string(phi_old_local_.size()) + ".");
453

454
  if (ext_src_moments_local_.empty())
4✔
455
  {
456
    ext_src_moments_local_ = ext_src_moments;
4✔
457
    return;
4✔
458
  }
459

460
  assert(ext_src_moments.size() == ext_src_moments_local_.size() &&
×
461
         "SetExtSrcMomentsFrom size mismatch.");
462
  ext_src_moments_local_ = ext_src_moments;
×
463
}
464

465
std::vector<double>&
466
LBSProblem::GetPhiOldLocal()
1,468,676✔
467
{
468
  return phi_old_local_;
1,468,676✔
469
}
470

471
const std::vector<double>&
472
LBSProblem::GetPhiOldLocal() const
×
473
{
474
  return phi_old_local_;
×
475
}
476

477
std::vector<double>&
478
LBSProblem::GetPhiNewLocal()
933,711✔
479
{
480
  return phi_new_local_;
933,711✔
481
}
482

483
const std::vector<double>&
484
LBSProblem::GetPhiNewLocal() const
245,843✔
485
{
486
  return phi_new_local_;
245,843✔
487
}
488

489
std::vector<double>&
490
LBSProblem::GetPrecursorsNewLocal()
25,351✔
491
{
492
  return precursor_new_local_;
25,351✔
493
}
494

495
const std::vector<double>&
496
LBSProblem::GetPrecursorsNewLocal() const
×
497
{
498
  return precursor_new_local_;
×
499
}
500

501
std::vector<double>&
NEW
502
LBSProblem::GetPrecursorsOldLocal()
×
503
{
NEW
504
  return precursor_old_local_;
×
505
}
506

507
const std::vector<double>&
508
LBSProblem::GetPrecursorsOldLocal() const
685,368✔
509
{
510
  return precursor_old_local_;
685,368✔
511
}
512

513
SetSourceFunction
514
LBSProblem::GetActiveSetSourceFunction() const
6,285✔
515
{
516
  return active_set_source_function_;
6,285✔
517
}
518

519
void
520
LBSProblem::SetActiveSetSourceFunction(SetSourceFunction source_function)
256✔
521
{
522
  active_set_source_function_ = std::move(source_function);
256✔
523
}
256✔
524

525
std::pair<std::uint64_t, std::uint64_t>
526
LBSProblem::GetNumPhiIterativeUnknowns()
×
527
{
528
  const auto& sdm = *discretization_;
×
529
  const auto num_local_phi_dofs = sdm.GetNumLocalDOFs(flux_moments_uk_man_);
×
530
  const auto num_global_phi_dofs = sdm.GetNumGlobalDOFs(flux_moments_uk_man_);
×
531

532
  return {num_local_phi_dofs, num_global_phi_dofs};
×
533
}
534

535
InputParameters
536
LBSProblem::GetOptionsBlock()
2,324✔
537
{
538
  InputParameters params;
2,324✔
539

540
  params.SetGeneralDescription("Set options from a large list of parameters");
4,648✔
541
  params.AddOptionalParameter("max_mpi_message_size",
4,648✔
542
                              32768,
543
                              "The maximum MPI message size used during sweep initialization.");
544
  params.AddOptionalParameter(
4,648✔
545
    "restart_writes_enabled", false, "Flag that controls writing of restart dumps");
546
  params.AddOptionalParameter("write_delayed_psi_to_restart",
4,648✔
547
                              true,
548
                              "Flag that controls writing of delayed angular fluxes to restarts.");
549
  params.AddOptionalParameter("write_angular_flux_to_restart",
4,648✔
550
                              true,
551
                              "Flag that controls writing angular fluxes to restart dumps when "
552
                              "`save_angular_flux` is enabled.");
553
  params.AddOptionalParameter(
4,648✔
554
    "read_restart_path", "", "Full path for reading restart dumps including file stem.");
555
  params.AddOptionalParameter(
4,648✔
556
    "read_initial_condition_path",
557
    "",
558
    "Full path for reading restart data as an initial condition, including file stem.");
559
  params.AddOptionalParameter(
4,648✔
560
    "write_restart_path", "", "Full path for writing restart dumps including file stem.");
561
  params.AddOptionalParameter("write_restart_time_interval",
4,648✔
562
                              0,
563
                              "Time interval in seconds at which restart data is to be written.");
564
  params.AddOptionalParameter("use_precursors", true, "Flag for using delayed neutron precursors.");
4,648✔
565
  params.AddOptionalParameter("use_source_moments",
4,648✔
566
                              false,
567
                              "Flag for ignoring fixed sources and selectively using source "
568
                              "moments obtained elsewhere.");
569
  params.AddOptionalParameter(
4,648✔
570
    "save_angular_flux", false, "Flag indicating whether angular fluxes are to be stored or not.");
571
  params.AddOptionalParameter(
4,648✔
572
    "adjoint", false, "Flag for toggling whether the solver is in adjoint mode.");
573
  params.AddOptionalParameter(
4,648✔
574
    "verbose_inner_iterations",
575
    true,
576
    "Flag to control verbosity of inner iterations, including WGS and AGS iterations.");
577
  params.AddOptionalParameter(
4,648✔
578
    "verbose_outer_iterations", true, "Flag to control verbosity of outer iterations.");
579
  params.AddOptionalParameter(
4,648✔
580
    "max_ags_iterations", 100, "Maximum number of across-groupset iterations.");
581
  params.AddOptionalParameter("ags_tolerance", 1.0e-6, "Across-groupset iterations tolerance.");
4,648✔
582
  params.AddOptionalParameter("ags_convergence_check",
4,648✔
583
                              "l2",
584
                              "Type of convergence check for AGS iterations. Valid values are "
585
                              "`\"l2\"` and '\"pointwise\"'");
586
  params.AddOptionalParameter("power_default_kappa",
4,648✔
587
                              3.20435e-11,
588
                              "Default `kappa` value (Energy released per fission) to use for "
589
                              "power generation when cross sections do not have `kappa` values. "
590
                              "Default: 3.20435e-11 Joule (corresponding to 200 MeV per fission).");
591
  params.AddOptionalParameter("field_function_prefix_option",
4,648✔
592
                              "prefix",
593
                              "Prefix option on field function names. Default: `\"prefix\"`. Can "
594
                              "be `\"prefix\"` or `\"solver_name\"`. By default this option uses "
595
                              "the value of the `field_function_prefix` parameter. If this "
596
                              "parameter is not set, flux field functions will be exported as "
597
                              "`phi_gXXX_mYYY` where `XXX` is the zero padded 3 digit group number "
598
                              "and `YYY` is the zero padded 3 digit moment.");
599
  params.AddOptionalParameter("field_function_prefix",
4,648✔
600
                              "",
601
                              "Prefix to use on all field functions. Default: `\"\"`. By default "
602
                              "this option is empty. Ff specified, flux moments will be exported "
603
                              "as `prefix_phi_gXXX_mYYY` where `XXX` is the zero padded 3 digit "
604
                              "group number and `YYY` is the zero padded 3 digit moment. The "
605
                              "underscore after \"prefix\" is added automatically.");
606
  params.ConstrainParameterRange("ags_convergence_check",
6,972✔
607
                                 AllowableRangeList::New({"l2", "pointwise"}));
2,324✔
608
  params.ConstrainParameterRange("field_function_prefix_option",
6,972✔
609
                                 AllowableRangeList::New({"prefix", "solver_name"}));
2,324✔
610
  params.ConstrainParameterRange("max_mpi_message_size", AllowableRangeLowLimit::New(1024));
6,972✔
611
  params.ConstrainParameterRange("write_restart_time_interval", AllowableRangeLowLimit::New(0));
6,972✔
612
  params.ConstrainParameterRange("max_ags_iterations", AllowableRangeLowLimit::New(0));
6,972✔
613
  params.ConstrainParameterRange("ags_tolerance", AllowableRangeLowLimit::New(1.0e-18));
6,972✔
614
  params.ConstrainParameterRange("power_default_kappa", AllowableRangeLowLimit::New(0.0, false));
6,972✔
615

616
  return params;
2,324✔
617
}
×
618

619
InputParameters
620
LBSProblem::GetXSMapEntryBlock()
2,168✔
621
{
622
  InputParameters params;
2,168✔
623
  params.SetGeneralDescription("Set the cross-section map for the solver.");
4,336✔
624
  params.AddRequiredParameterArray("block_ids", "Mesh block IDs");
4,336✔
625
  params.AddRequiredParameter<std::shared_ptr<MultiGroupXS>>("xs", "Cross-section object");
4,336✔
626
  return params;
2,168✔
627
}
×
628

629
void
630
LBSProblem::ParseOptions(const InputParameters& input)
1,161✔
631
{
632
  auto params = LBSProblem::GetOptionsBlock();
1,161✔
633
  params.AssignParameters(input);
1,161✔
634
  const auto& params_at_assignment = input.GetParametersAtAssignment();
1,161✔
635
  const auto& specified_params = params_at_assignment.GetNumParameters() > 0
1,161✔
636
                                   ? params_at_assignment
1,161✔
637
                                   : static_cast<const ParameterBlock&>(input);
1,161✔
638

639
  using OptionSetter = std::function<void(const ParameterBlock&)>;
1,161✔
640
  const std::unordered_map<std::string, OptionSetter> option_setters = {
1,161✔
641
    {"max_mpi_message_size",
642
     [this](const ParameterBlock& spec) { options_.max_mpi_message_size = spec.GetValue<int>(); }},
×
643
    {"restart_writes_enabled",
644
     [this](const ParameterBlock& spec)
2,358✔
645
     { options_.restart.writes_enabled = spec.GetValue<bool>(); }},
36✔
646
    {"write_delayed_psi_to_restart",
647
     [this](const ParameterBlock& spec)
2,354✔
648
     { options_.restart.write_delayed_psi = spec.GetValue<bool>(); }},
32✔
649
    {"write_angular_flux_to_restart",
650
     [this](const ParameterBlock& spec)
2,354✔
651
     { options_.restart.write_angular_flux = spec.GetValue<bool>(); }},
32✔
652
    {"read_restart_path",
653
     [this](const ParameterBlock& spec)
2,338✔
654
     { options_.restart.read_path = BuildRestartPath(spec.GetValue<std::string>()); }},
32✔
655
    {"read_initial_condition_path",
656
     [this](const ParameterBlock& spec)
2,354✔
657
     {
658
       options_.restart.read_initial_condition_path =
64✔
659
         BuildRestartPath(spec.GetValue<std::string>());
64✔
660
     }},
32✔
661
    {"write_restart_path",
662
     [this](const ParameterBlock& spec)
2,358✔
663
     { options_.restart.write_path = BuildRestartPath(spec.GetValue<std::string>()); }},
72✔
664
    {"write_restart_time_interval",
665
     [this](const ParameterBlock& spec)
2,322✔
666
     { options_.restart.write_time_interval = std::chrono::seconds(spec.GetValue<int>()); }},
×
667
    {"use_precursors",
668
     [this](const ParameterBlock& spec) { options_.use_precursors = spec.GetValue<bool>(); }},
540✔
669
    {"use_source_moments",
670
     [this](const ParameterBlock& spec) { options_.use_src_moments = spec.GetValue<bool>(); }},
4✔
671
    {"save_angular_flux",
672
     [this](const ParameterBlock& spec) { options_.save_angular_flux = spec.GetValue<bool>(); }},
606✔
673
    {"verbose_inner_iterations",
674
     [this](const ParameterBlock& spec)
3,245✔
675
     { options_.verbose_inner_iterations = spec.GetValue<bool>(); }},
923✔
676
    {"max_ags_iterations",
677
     [this](const ParameterBlock& spec) { options_.max_ags_iterations = spec.GetValue<int>(); }},
320✔
678
    {"ags_tolerance",
679
     [this](const ParameterBlock& spec) { options_.ags_tolerance = spec.GetValue<double>(); }},
44✔
680
    {"ags_convergence_check",
681
     [this](const ParameterBlock& spec)
2,334✔
682
     { options_.ags_pointwise_convergence = (spec.GetValue<std::string>() == "pointwise"); }},
12✔
683
    {"verbose_outer_iterations",
684
     [this](const ParameterBlock& spec)
3,193✔
685
     { options_.verbose_outer_iterations = spec.GetValue<bool>(); }},
871✔
686
    {"power_default_kappa",
687
     [this](const ParameterBlock& spec)
2,335✔
688
     { options_.power_default_kappa = spec.GetValue<double>(); }},
13✔
689
    {"field_function_prefix_option",
690
     [this](const ParameterBlock& spec)
2,322✔
691
     { options_.field_function_prefix_option = spec.GetValue<std::string>(); }},
×
692
    {"field_function_prefix",
693
     [this](const ParameterBlock& spec)
2,322✔
694
     { options_.field_function_prefix = spec.GetValue<std::string>(); }},
×
695
    {"adjoint", [this](const ParameterBlock& spec) { options_.adjoint = spec.GetValue<bool>(); }},
16✔
696
  };
25,542✔
697

698
  for (const auto& spec : specified_params.GetParameters())
4,694✔
699
  {
700
    const auto setter_it = option_setters.find(spec.GetName());
7,066✔
701
    if (setter_it != option_setters.end())
3,533✔
702
      setter_it->second(spec);
3,533✔
703
  }
704

705
  OpenSnInvalidArgumentIf(options_.restart.write_time_interval > std::chrono::seconds(0) and
1,161✔
706
                            not options_.restart.writes_enabled,
707
                          GetName() + ": `write_restart_time_interval>0` requires "
708
                                      "`restart_writes_enabled=true`.");
709

710
  OpenSnInvalidArgumentIf(options_.restart.write_time_interval > std::chrono::seconds(0) and
1,161✔
711
                            options_.restart.write_time_interval < std::chrono::seconds(30),
712
                          GetName() + ": `write_restart_time_interval` must be 0 (disabled) "
713
                                      "or at least 30 seconds.");
714

715
  OpenSnInvalidArgumentIf(options_.restart.writes_enabled and options_.restart.write_path.empty(),
1,161✔
716
                          GetName() + ": `restart_writes_enabled=true` requires a non-empty "
717
                                      "`write_restart_path`.");
718

719
  OpenSnInvalidArgumentIf(not options_.field_function_prefix.empty() and
1,161✔
720
                            options_.field_function_prefix_option != "prefix",
721
                          GetName() + ": non-empty `field_function_prefix` requires "
722
                                      "`field_function_prefix_option=\"prefix\"`.");
723

724
  if (options_.restart.writes_enabled)
1,161✔
725
  {
726
    const auto dir = options_.restart.write_path.parent_path();
36✔
727

728
    // Create restart directory if necessary.
729
    // If dir is empty, write path resolves relative to the working directory.
730
    if ((not dir.empty()) and opensn::mpi_comm.rank() == 0)
40✔
731
    {
732
      if (not std::filesystem::exists(dir))
1✔
733
      {
734
        OpenSnLogicalErrorIf(not std::filesystem::create_directories(dir),
×
735
                             GetName() + ": Failed to create restart directory " + dir.string());
736
      }
737
      else
738
        OpenSnLogicalErrorIf(not std::filesystem::is_directory(dir),
1✔
739
                             GetName() + ": Restart path exists but is not a directory " +
740
                               dir.string());
741
    }
742
    opensn::mpi_comm.barrier();
36✔
743
    options_.restart.MarkWriteComplete();
36✔
744
  }
36✔
745
}
2,322✔
746

747
std::filesystem::path
748
LBSProblem::BuildRestartPath(const std::string& path_stem)
84✔
749
{
750
  if (path_stem.empty())
84✔
751
    return {};
×
752

753
  auto path = std::filesystem::path(path_stem);
84✔
754
  path += std::to_string(opensn::mpi_comm.rank()) + ".restart.h5";
252✔
755
  return path;
84✔
756
}
84✔
757

758
bool
759
LBSProblem::ReadProblemRestartData(hid_t /*file_id*/,
×
760
                                   bool /*allow_transient_initialization_from_steady*/)
761
{
762
  return true;
×
763
}
764

765
bool
766
LBSProblem::WriteProblemRestartData(hid_t /*file_id*/) const
×
767
{
768
  return true;
×
769
}
770

771
void
772
LBSProblem::BuildRuntime()
1,410✔
773
{
774
  PrintSimHeader();
1,410✔
775
  mpi_comm.barrier();
1,410✔
776

777
  InitializeRuntimeCore();
1,410✔
778
  ValidateRuntimeModeConfiguration();
1,410✔
779
  InitializeSources();
1,410✔
780
}
1,410✔
781

782
void
783
LBSProblem::InitializeRuntimeCore()
1,410✔
784
{
785
  InitializeSpatialDiscretization();
1,410✔
786
  InitializeParrays();
1,410✔
787
  InitializeBoundaries();
1,410✔
788
  InitializeGPUExtras();
1,410✔
789
}
1,410✔
790

791
void
792
LBSProblem::ValidateRuntimeModeConfiguration() const
1,410✔
793
{
794
  if (options_.adjoint)
1,410✔
795
    if (IsTimeDependent())
16✔
796
      OpenSnInvalidArgument(GetName() + ": Time-dependent adjoint problems are not supported.");
×
797
}
1,410✔
798

799
void
800
LBSProblem::InitializeSources()
1,410✔
801
{
802
  // Initialize point sources
803
  for (auto& point_source : point_sources_)
1,452✔
804
    point_source->Initialize(*this);
42✔
805

806
  // Initialize volumetric sources
807
  for (auto& volumetric_source : volumetric_sources_)
2,352✔
808
    volumetric_source->Initialize(*this);
942✔
809
}
1,410✔
810

811
void
812
LBSProblem::PrintSimHeader()
×
813
{
814
  if (opensn::mpi_comm.rank() == 0)
×
815
  {
816
    std::stringstream outstr;
×
817
    outstr << "\n"
×
818
           << "Initializing " << GetName() << "\n\n"
×
819
           << "Scattering order    : " << scattering_order_ << "\n"
×
820
           << "Number of moments   : " << num_moments_ << "\n"
×
821
           << "Number of groups    : " << num_groups_ << "\n"
×
822
           << "Number of groupsets : " << groupsets_.size() << "\n\n";
×
823

824
    for (const auto& groupset : groupsets_)
×
825
    {
826
      outstr << "***** Groupset " << groupset.id << " *****\n"
×
827
             << "Groups:\n";
×
828
      const auto n_gs_groups = groupset.GetNumGroups();
×
829
      constexpr int groups_per_line = 12;
830
      for (size_t i = 0; i < n_gs_groups; ++i)
×
831
      {
832
        outstr << std::setw(5) << groupset.first_group + i << ' ';
×
833
        if ((i + 1) % groups_per_line == 0)
×
834
          outstr << '\n';
×
835
      }
836
      if (n_gs_groups > 0 && n_gs_groups % groups_per_line != 0)
×
837
        outstr << '\n';
×
838
    }
839

840
    log.Log() << outstr.str() << '\n';
×
841
  }
×
842
}
×
843

844
void
845
LBSProblem::InitializeSources(const InputParameters& params)
1,418✔
846
{
847
  if (params.Has("volumetric_sources"))
1,418✔
848
  {
849
    const auto& vol_srcs = params.GetParam("volumetric_sources");
1,418✔
850
    vol_srcs.RequireBlockTypeIs(ParameterBlockType::ARRAY);
1,418✔
851
    for (const auto& src : vol_srcs)
2,360✔
852
      volumetric_sources_.push_back(src.GetValue<std::shared_ptr<VolumetricSource>>());
1,884✔
853
  }
854

855
  if (params.Has("point_sources"))
1,418✔
856
  {
857
    const auto& pt_srcs = params.GetParam("point_sources");
1,418✔
858
    pt_srcs.RequireBlockTypeIs(ParameterBlockType::ARRAY);
1,418✔
859
    for (const auto& src : pt_srcs)
1,460✔
860
      point_sources_.push_back(src.GetValue<std::shared_ptr<PointSource>>());
84✔
861
  }
862
}
1,418✔
863

864
void
865
LBSProblem::InitializeGroupsets(const InputParameters& params)
1,418✔
866
{
867
  // Initialize groups
868
  OpenSnInvalidArgumentIf(num_groups_ == 0, GetName() + ": Number of groups must be > 0.");
1,418✔
869

870
  // Initialize groupsets
871
  const auto& groupsets_array = params.GetParam("groupsets");
1,418✔
872
  const size_t num_gs = groupsets_array.GetNumParameters();
1,418✔
873
  OpenSnInvalidArgumentIf(num_gs == 0, GetName() + ": At least one groupset must be specified.");
1,418✔
874
  for (size_t gs = 0; gs < num_gs; ++gs)
3,041✔
875
  {
876
    const auto& groupset_params = groupsets_array.GetParam(gs);
1,623✔
877
    InputParameters gs_input_params = LBSGroupset::GetInputParameters();
1,623✔
878
    gs_input_params.SetObjectType("LBSProblem:LBSGroupset");
1,623✔
879
    gs_input_params.AssignParameters(groupset_params);
1,623✔
880
    groupsets_.emplace_back(gs_input_params, gs, *this);
1,623✔
881
    if (groupsets_.back().GetNumGroups() == 0)
1,623✔
882
    {
883
      std::stringstream oss;
×
884
      oss << GetName() << ": No groups added to groupset " << groupsets_.back().id;
×
885
      OpenSnInvalidArgument(oss.str());
×
886
    }
×
887

888
    if (groupsets_.back().last_group >= num_groups_)
1,623✔
889
    {
890
      std::stringstream oss;
×
891
      oss << GetName() << ": Groupset " << groupsets_.back().id << " has last group "
×
892
          << groupsets_.back().last_group << ", but the problem only has " << num_groups_
×
893
          << " groups.";
×
894
      OpenSnInvalidArgument(oss.str());
×
895
    }
×
896

897
    if (gs > 0)
1,623✔
898
    {
899
      const auto& previous_groupset = groupsets_[gs - 1];
205✔
900
      const auto& current_groupset = groupsets_.back();
205✔
901
      const auto expected_first_group = previous_groupset.last_group + 1;
205✔
902
      if (current_groupset.first_group != expected_first_group)
205✔
903
      {
904
        std::stringstream oss;
×
905
        oss << GetName() << ": Groupset " << current_groupset.id << " starts at group "
×
906
            << current_groupset.first_group << ", but it should start at group "
×
907
            << expected_first_group << " to be consecutive with groupset " << previous_groupset.id
×
908
            << ".";
×
909
        OpenSnInvalidArgument(oss.str());
×
910
      }
×
911
    }
912
  }
1,623✔
913
}
1,418✔
914

915
void
916
LBSProblem::InitializeXSMap(const InputParameters& params)
1,418✔
917
{
918
  // Build XS map
919
  const auto& xs_array = params.GetParam("xs_map");
1,418✔
920
  const size_t num_xs = xs_array.GetNumParameters();
1,418✔
921
  for (size_t i = 0; i < num_xs; ++i)
3,230✔
922
  {
923
    const auto& item_params = xs_array.GetParam(i);
1,812✔
924
    InputParameters xs_entry_pars = GetXSMapEntryBlock();
1,812✔
925
    xs_entry_pars.AssignParameters(item_params);
1,812✔
926

927
    const auto& block_ids_param = xs_entry_pars.GetParam("block_ids");
1,812✔
928
    block_ids_param.RequireBlockTypeIs(ParameterBlockType::ARRAY);
1,812✔
929
    const auto& block_ids = block_ids_param.GetVectorValue<unsigned int>();
1,812✔
930
    auto xs = xs_entry_pars.GetSharedPtrParam<MultiGroupXS>("xs");
1,812✔
931
    for (const auto& block_id : block_ids)
3,768✔
932
      block_id_to_xs_map_[block_id] = xs;
1,956✔
933
  }
1,812✔
934
}
1,418✔
935

936
void
937
LBSProblem::InitializeMaterials()
1,798✔
938
{
939
  CaliperPhaseScope cali_setup_phase("Setup", CaliperSetupPhaseDepth());
1,798✔
940
  CALI_CXX_MARK_SCOPE("Materials");
1,798✔
941

942
  log.Log0Verbose1() << "Initializing Materials";
3,596✔
943

944
  // Create set of material ids locally relevant
945
  int invalid_mat_cell_count = 0;
1,798✔
946
  std::set<unsigned int> unique_block_ids;
1,798✔
947
  for (auto& cell : grid_->local_cells)
1,014,930✔
948
  {
949
    unique_block_ids.insert(cell.block_id);
1,013,132✔
950
    if (cell.block_id == std::numeric_limits<unsigned int>::max() or
1,013,132✔
951
        (block_id_to_xs_map_.find(cell.block_id) == block_id_to_xs_map_.end()))
1,013,132✔
952
      ++invalid_mat_cell_count;
×
953
  }
954
  const auto& ghost_cell_ids = grid_->cells.GetGhostGlobalIDs();
1,798✔
955
  for (uint64_t cell_id : ghost_cell_ids)
168,612✔
956
  {
957
    const auto& cell = grid_->cells[cell_id];
166,814✔
958
    unique_block_ids.insert(cell.block_id);
166,814✔
959
    if (cell.block_id == std::numeric_limits<unsigned int>::max() or
166,814✔
960
        (block_id_to_xs_map_.find(cell.block_id) == block_id_to_xs_map_.end()))
166,814✔
961
      ++invalid_mat_cell_count;
×
962
  }
963
  OpenSnLogicalErrorIf(invalid_mat_cell_count > 0,
1,798✔
964
                       std::to_string(invalid_mat_cell_count) +
965
                         " cells encountered with an invalid material id.");
966

967
  // Get ready for processing
968
  for (const auto& [blk_id, mat] : block_id_to_xs_map_)
4,158✔
969
  {
970
    mat->SetAdjointMode(options_.adjoint);
2,360✔
971

972
    OpenSnLogicalErrorIf(mat->GetNumGroups() < num_groups_,
2,360✔
973
                         "Cross-sections for block \"" + std::to_string(blk_id) +
974
                           "\" have fewer groups (" + std::to_string(mat->GetNumGroups()) +
975
                           ") than the simulation (" + std::to_string(num_groups_) + "). " +
976
                           "Cross-sections must have at least as many groups as the simulation.");
977
  }
978

979
  // Initialize precursor properties
980
  num_precursors_ = 0;
1,798✔
981
  max_precursors_per_material_ = 0;
1,798✔
982
  for (const auto& mat_id_xs : block_id_to_xs_map_)
4,158✔
983
  {
984
    const auto& xs = mat_id_xs.second;
2,360✔
985
    if (xs->IsFissionable())
2,360✔
986
    {
987
      num_precursors_ += xs->GetPrecursors().size();
867✔
988
      max_precursors_per_material_ = std::max(static_cast<unsigned int>(xs->GetPrecursors().size()),
867✔
989
                                              max_precursors_per_material_);
867✔
990
    }
991
  }
992

993
  const bool has_fissionable_precursors =
1,798✔
994
    std::any_of(block_id_to_xs_map_.begin(),
1,798✔
995
                block_id_to_xs_map_.end(),
996
                [](const auto& mat_id_xs)
2,360✔
997
                {
998
                  const auto& xs = mat_id_xs.second;
2,360✔
999
                  return xs->IsFissionable() and not xs->GetPrecursors().empty();
2,360✔
1000
                });
1001
  const bool has_fissionable_material =
1,798✔
1002
    std::any_of(block_id_to_xs_map_.begin(),
1,798✔
1003
                block_id_to_xs_map_.end(),
1004
                [](const auto& mat_id_xs) { return mat_id_xs.second->IsFissionable(); });
2,316✔
1005

1006
  const bool has_any_precursor_data =
1,798✔
1007
    std::any_of(block_id_to_xs_map_.begin(),
1,798✔
1008
                block_id_to_xs_map_.end(),
1009
                [](const auto& mat_id_xs)
2,360✔
1010
                {
1011
                  const auto& xs = mat_id_xs.second;
2,360✔
1012
                  return xs->IsFissionable() and not xs->GetPrecursors().empty();
2,360✔
1013
                });
1014

1015
  if (options_.use_precursors and has_fissionable_material and not has_any_precursor_data)
1,798✔
1016
  {
1017
    log.Log0Warning() << GetName()
318✔
1018
                      << ": options.use_precursors is enabled, but no precursor data was found "
1019
                         "in the active cross-section map. Running without delayed-neutron "
1020
                         "precursor coupling.";
106✔
1021
  }
1022

1023
  // check compatibility when at least one fissionable material has delayed-neutron data
1024
  if (options_.use_precursors and has_fissionable_precursors)
1,798✔
1025
  {
1026
    for (const auto& [mat_id, xs] : block_id_to_xs_map_)
818✔
1027
    {
1028
      OpenSnInvalidArgumentIf(xs->IsFissionable() and xs->GetPrecursors().empty(),
409✔
1029
                              GetName() + ": incompatible cross-section data for material id " +
1030
                                std::to_string(mat_id) +
1031
                                ". When options.use_precursors=true and "
1032
                                "delayed-neutron precursor data is present for one fissionable "
1033
                                "material, it must be present for all fissionable materials.");
1034
    }
1035
  }
1036

1037
  // Update transport views if available
1038
  if (grid_->local_cells.size() == cell_transport_views_.size())
1,798✔
1039
    for (const auto& cell : grid_->local_cells)
42,488✔
1040
    {
1041
      const auto& xs_ptr = block_id_to_xs_map_[cell.block_id];
42,108✔
1042
      auto& transport_view = cell_transport_views_[cell.local_id];
42,108✔
1043
      transport_view.ReassignXS(*xs_ptr);
42,108✔
1044
    }
1045

1046
  mpi_comm.barrier();
1,798✔
1047
}
1,798✔
1048

1049
void
1050
LBSProblem::InitializeSpatialDiscretization()
1,302✔
1051
{
1052
  CALI_CXX_MARK_SCOPE("SpatialDiscretization");
1,302✔
1053

1054
  OpenSnLogicalErrorIf(not discretization_,
1,302✔
1055
                       GetName() + ": Missing spatial discretization. Construct the problem "
1056
                                   "through its factory Create(...) entry point.");
1057
  log.Log() << program_timer.GetTimeString() << " Initializing spatial discretization metadata.\n";
3,906✔
1058

1059
  ComputeUnitIntegrals();
1,302✔
1060
}
1,302✔
1061

1062
void
1063
LBSProblem::ComputeUnitIntegrals()
1,410✔
1064
{
1065
  CALI_CXX_MARK_SCOPE("UnitIntegrals");
1,410✔
1066

1067
  log.Log() << program_timer.GetTimeString() << " Computing unit integrals.\n";
4,230✔
1068
  const auto& sdm = *discretization_;
1,410✔
1069

1070
  const size_t num_local_cells = grid_->local_cells.size();
1,410✔
1071
  unit_cell_matrices_.resize(num_local_cells);
1,410✔
1072

1073
  for (const auto& cell : grid_->local_cells)
972,414✔
1074
    unit_cell_matrices_[cell.local_id] =
971,004✔
1075
      ComputeUnitCellIntegrals(sdm, cell, grid_->GetCoordinateSystem());
971,004✔
1076

1077
  const auto ghost_ids = grid_->cells.GetGhostGlobalIDs();
1,410✔
1078
  for (auto ghost_id : ghost_ids)
151,618✔
1079
    unit_ghost_cell_matrices_[ghost_id] =
150,208✔
1080
      ComputeUnitCellIntegrals(sdm, grid_->cells[ghost_id], grid_->GetCoordinateSystem());
300,416✔
1081

1082
  // Assessing global unit cell matrix storage
1083
  std::array<size_t, 2> num_local_ucms = {unit_cell_matrices_.size(),
1,410✔
1084
                                          unit_ghost_cell_matrices_.size()};
1,410✔
1085
  std::array<size_t, 2> num_global_ucms = {0, 0};
1,410✔
1086

1087
  mpi_comm.all_reduce(num_local_ucms.data(), 2, num_global_ucms.data(), mpi::op::sum<size_t>());
1,410✔
1088

1089
  opensn::mpi_comm.barrier();
1,410✔
1090
  log.Log() << program_timer.GetTimeString() << " Ghost cell unit cell-matrix ratio: "
4,230✔
1091
            << static_cast<double>(num_global_ucms[1]) * 100 /
2,820✔
1092
                 static_cast<double>(num_global_ucms[0])
1,410✔
1093
            << "%";
1,410✔
1094
  log.Log() << program_timer.GetTimeString() << " Cell matrices computed.";
4,230✔
1095
}
1,410✔
1096

1097
void
1098
LBSProblem::InitializeParrays()
1,410✔
1099
{
1100
  CALI_CXX_MARK_SCOPE("ParallelArrays");
1,410✔
1101

1102
  log.Log() << program_timer.GetTimeString() << " Initializing parallel arrays."
4,230✔
1103
            << " G=" << num_groups_ << " M=" << num_moments_ << std::endl;
1,410✔
1104

1105
  // Initialize unknown
1106
  // structure
1107
  flux_moments_uk_man_.unknowns.clear();
1,410✔
1108
  for (unsigned int m = 0; m < num_moments_; ++m)
4,132✔
1109
  {
1110
    flux_moments_uk_man_.AddUnknown(UnknownType::VECTOR_N, num_groups_);
2,722✔
1111
    flux_moments_uk_man_.unknowns.back().name = "m" + std::to_string(m);
2,722✔
1112
  }
1113

1114
  // Compute local # of dof
1115
  local_node_count_ = discretization_->GetNumLocalNodes();
1,410✔
1116
  global_node_count_ = discretization_->GetNumGlobalNodes();
1,410✔
1117

1118
  // Compute num of unknowns
1119
  size_t local_unknown_count = local_node_count_ * num_groups_ * num_moments_;
1,410✔
1120
  log.LogAllVerbose1() << "LBS Number of phi unknowns: " << local_unknown_count;
2,820✔
1121

1122
  // Size local vectors
1123
  q_moments_local_.assign(local_unknown_count, 0.0);
1,410✔
1124
  phi_old_local_.assign(local_unknown_count, 0.0);
1,410✔
1125
  phi_new_local_.assign(local_unknown_count, 0.0);
1,410✔
1126

1127
  // Setup precursor vector
1128
  if (options_.use_precursors)
1,410✔
1129
  {
1130
    size_t num_precursor_dofs = grid_->local_cells.size() * max_precursors_per_material_;
1,046✔
1131
    precursor_new_local_.assign(num_precursor_dofs, 0.0);
1,046✔
1132
    precursor_old_local_.assign(num_precursor_dofs, 0.0);
1,046✔
1133
  }
1134

1135
  // Initialize cell transport metadata and outflow tallies.
1136
  size_t block_MG_counter = 0; // Counts the strides of moment and group
1,410✔
1137
  min_cell_dof_count_ = std::numeric_limits<unsigned int>::max();
1,410✔
1138
  max_cell_dof_count_ = 0;
1,410✔
1139
  cell_transport_views_.clear();
1,410✔
1140
  cell_transport_views_.reserve(grid_->local_cells.size());
1,410✔
1141
  for (auto& cell : grid_->local_cells)
972,414✔
1142
  {
1143
    size_t num_nodes = discretization_->GetCellNumNodes(cell);
971,004✔
1144

1145
    // compute cell volumes
1146
    double cell_volume = 0.0;
971,004✔
1147
    const auto& IntV_shapeI = unit_cell_matrices_[cell.local_id].intV_shapeI;
971,004✔
1148
    for (size_t i = 0; i < num_nodes; ++i)
6,126,690✔
1149
      cell_volume += IntV_shapeI(i);
5,155,686✔
1150

1151
    size_t cell_phi_address = block_MG_counter;
971,004✔
1152

1153
    const size_t num_faces = cell.faces.size();
971,004✔
1154
    std::vector<bool> face_local_flags(num_faces, true);
971,004✔
1155
    std::vector<int> face_locality(num_faces, opensn::mpi_comm.rank());
971,004✔
1156
    std::vector<const Cell*> neighbor_cell_ptrs(num_faces, nullptr);
971,004✔
1157
    int f = 0;
971,004✔
1158
    for (auto& face : cell.faces)
5,392,622✔
1159
    {
1160
      if (not face.has_neighbor)
4,421,618✔
1161
      {
1162
        face_local_flags[f] = false;
165,708✔
1163
        face_locality[f] = -1;
165,708✔
1164
      } // if bndry
1165
      else
1166
      {
1167
        const int neighbor_partition = face.GetNeighborPartitionID(grid_.get());
4,255,910✔
1168
        face_local_flags[f] = (neighbor_partition == opensn::mpi_comm.rank());
4,255,910✔
1169
        face_locality[f] = neighbor_partition;
4,255,910✔
1170
        neighbor_cell_ptrs[f] = &grid_->cells[face.neighbor_id];
4,255,910✔
1171
      }
1172

1173
      ++f;
4,421,618✔
1174
    }
1175

1176
    max_cell_dof_count_ = std::max(max_cell_dof_count_, static_cast<unsigned int>(num_nodes));
971,004✔
1177
    min_cell_dof_count_ = std::min(min_cell_dof_count_, static_cast<unsigned int>(num_nodes));
971,004✔
1178
    cell_transport_views_.emplace_back(cell_phi_address,
1,942,008✔
1179
                                       num_nodes,
1180
                                       num_groups_,
971,004✔
1181
                                       num_moments_,
971,004✔
1182
                                       *block_id_to_xs_map_[cell.block_id],
971,004✔
1183
                                       cell_volume,
1184
                                       face_local_flags,
1185
                                       face_locality,
1186
                                       neighbor_cell_ptrs);
1187
    block_MG_counter += num_nodes * num_groups_ * num_moments_;
971,004✔
1188
  } // for local cell
971,004✔
1189
  cell_outflow_views_.clear();
1,410✔
1190
  outflow_bank_ = OutflowBank(*grid_, num_groups_, store_internal_outflows_);
1,410✔
1191
  cell_outflow_views_ = outflow_bank_.GetCellOutflowViews();
1,410✔
1192

1193
  // Populate grid nodal mappings
1194
  // This is used in the Flux Data Structure (FLUDS).
1195
  grid_nodal_mappings_.clear();
1,410✔
1196
  grid_nodal_mappings_.reserve(grid_->local_cells.size());
1,410✔
1197
  for (auto& cell : grid_->local_cells)
972,414✔
1198
  {
1199
    CellFaceNodalMapping cell_nodal_mapping;
971,004✔
1200
    cell_nodal_mapping.reserve(cell.faces.size());
971,004✔
1201

1202
    for (auto& face : cell.faces)
5,392,622✔
1203
    {
1204
      std::vector<short> face_node_mapping;
4,421,618✔
1205
      std::vector<short> cell_node_mapping;
4,421,618✔
1206
      int adj_face_idx = -1;
4,421,618✔
1207

1208
      if (face.has_neighbor)
4,421,618✔
1209
      {
1210
        grid_->FindAssociatedVertices(face, face_node_mapping);
4,255,910✔
1211
        grid_->FindAssociatedCellVertices(face, cell_node_mapping);
4,255,910✔
1212
        adj_face_idx = face.GetNeighborAdjacentFaceIndex(grid_.get());
4,255,910✔
1213
      }
1214

1215
      cell_nodal_mapping.emplace_back(adj_face_idx, face_node_mapping, cell_node_mapping);
4,421,618✔
1216
    }
4,421,618✔
1217

1218
    grid_nodal_mappings_.push_back(cell_nodal_mapping);
971,004✔
1219
  }
971,004✔
1220

1221
  // Get grid localized communicator set
1222
  grid_local_comm_set_ = grid_->MakeMPILocalCommunicatorSet();
1,410✔
1223

1224
  opensn::mpi_comm.barrier();
1,410✔
1225
  log.Log() << program_timer.GetTimeString() << " Done with parallel arrays." << std::endl;
2,820✔
1226
}
1,410✔
1227

1228
#ifndef __OPENSN_WITH_GPU__
1229
void
1230
LBSProblem::InitializeGPUExtras()
1231
{
1232
}
1233

1234
void
1235
LBSProblem::ResetGPUCarriers()
1236
{
1237
}
1238

1239
void
1240
LBSProblem::CheckCapableDevices()
1241
{
1242
}
1243
#endif // __OPENSN_WITH_GPU__
1244

1245
std::vector<double>
1246
LBSProblem::MakeSourceMomentsFromPhi()
4✔
1247
{
1248
  CALI_CXX_MARK_SCOPE("Source/MomentsFromPhi");
4✔
1249

1250
  auto num_local_dofs = discretization_->GetNumLocalDOFs(flux_moments_uk_man_);
4✔
1251

1252
  std::vector<double> source_moments(num_local_dofs, 0.0);
4✔
1253
  for (auto& groupset : groupsets_)
8✔
1254
  {
1255
    active_set_source_function_(groupset,
4✔
1256
                                source_moments,
1257
                                phi_new_local_,
4✔
1258
                                APPLY_AGS_SCATTER_SOURCES | APPLY_WGS_SCATTER_SOURCES |
1259
                                  APPLY_AGS_FISSION_SOURCES | APPLY_WGS_FISSION_SOURCES |
4✔
1260
                                  APPLY_PREVIOUS_PRECURSOR_SOURCES);
4✔
1261
  }
1262

1263
  return source_moments;
4✔
1264
}
4✔
1265

1266
LBSProblem::~LBSProblem()
1,366✔
1267
{
1268
  ResetGPUCarriers();
1269
}
6,822✔
1270

1,366✔
1271
void
1272
LBSProblem::ZeroPhi()
412✔
1273
{
1274
  std::fill(phi_old_local_.begin(), phi_old_local_.end(), 0.0);
412✔
1275
  std::fill(phi_new_local_.begin(), phi_new_local_.end(), 0.0);
412✔
1276
}
412✔
1277

1278
void
1279
LBSProblem::CopyPhiNewToOld()
460✔
1280
{
1281
  assert(phi_old_local_.size() == phi_new_local_.size() && "Phi vectors size mismatch.");
460✔
1282
  phi_old_local_ = phi_new_local_;
460✔
1283
}
460✔
1284

1285
void
1286
LBSProblem::SetPhiOldFrom(const std::vector<double>& phi_old)
12,172✔
1287
{
1288
  assert(phi_old.size() == phi_old_local_.size() && "SetPhiOldFrom size mismatch.");
12,172✔
1289
  phi_old_local_ = phi_old;
12,172✔
1290
}
12,172✔
1291

1292
void
1293
LBSProblem::SetPrecursorsOldFrom(const std::vector<double>& precursors_old)
11,600✔
1294
{
1295
  assert(precursors_old.size() == precursor_old_local_.size() &&
11,600✔
1296
         "SetPrecursorsOldFrom size mismatch.");
1297
  precursor_old_local_ = precursors_old;
11,600✔
1298
}
11,600✔
1299

1300
void
1301
LBSProblem::SetPhiNewFrom(const std::vector<double>& phi_new)
×
1302
{
1303
  assert(phi_new.size() == phi_new_local_.size() && "SetPhiNewFrom size mismatch.");
×
1304
  phi_new_local_ = phi_new;
×
1305
}
×
1306

1307
void
1308
LBSProblem::ScalePhiOld(double factor)
45✔
1309
{
1310
  for (auto& value : phi_old_local_)
3,594,585✔
1311
    value *= factor;
3,594,540✔
1312
}
45✔
1313

1314
void
1315
LBSProblem::ScalePhiNew(double factor)
53✔
1316
{
1317
  for (auto& value : phi_new_local_)
3,762,593✔
1318
    value *= factor;
3,762,540✔
1319
}
53✔
1320

1321
void
1322
LBSProblem::ZeroQMoments()
549,649✔
1323
{
1324
  assert(q_moments_local_.size() == phi_old_local_.size() && "Q moments/Phi size mismatch.");
549,649✔
1325
  std::fill(q_moments_local_.begin(), q_moments_local_.end(), 0.0);
549,649✔
1326
}
549,649✔
1327

1328
void
1329
LBSProblem::ScaleQMoments(double factor)
59,219✔
1330
{
1331
  for (auto& value : q_moments_local_)
737,870,855✔
1332
    value *= factor;
737,811,636✔
1333
}
59,219✔
1334

1335
void
1336
LBSProblem::SetQMomentsFrom(const std::vector<double>& q_moments)
170,657✔
1337
{
1338
  assert(q_moments.size() == q_moments_local_.size() && "SetQMomentsFrom size mismatch.");
170,657✔
1339
  q_moments_local_ = q_moments;
170,657✔
1340
}
170,657✔
1341

1342
void
1343
LBSProblem::ScalePrecursors(double factor)
203✔
1344
{
1345
  for (auto& value : precursor_new_local_)
2,349✔
1346
    value *= factor;
2,146✔
1347
}
203✔
1348

1349
void
1350
LBSProblem::ZeroPrecursors()
11,820✔
1351
{
1352
  std::fill(precursor_new_local_.begin(), precursor_new_local_.end(), 0.0);
11,820✔
1353
}
11,820✔
1354

1355
void
1356
LBSProblem::ZeroExtSrcMoments()
×
1357
{
1358
  std::fill(ext_src_moments_local_.begin(), ext_src_moments_local_.end(), 0.0);
×
1359
}
×
1360

1361
void
1362
LBSProblem::ScaleExtSrcMoments(double factor)
×
1363
{
1364
  for (auto& value : ext_src_moments_local_)
×
1365
    value *= factor;
×
1366
}
×
1367

1368
void
1369
LBSProblem::SetAdjoint(bool adjoint)
24✔
1370
{
1371
  if (adjoint)
24✔
1372
    if (IsTimeDependent())
20✔
1373
      OpenSnInvalidArgument(GetName() + ": Time-dependent adjoint problems are not supported.");
×
1374

1375
  const bool mode_changed = (adjoint != options_.adjoint);
24✔
1376
  if (not mode_changed)
24✔
1377
    return;
1378

1379
  options_.adjoint = adjoint;
24✔
1380

1381
  // Reinitialize materials to obtain the proper forward/adjoint cross sections.
1382
  InitializeMaterials();
24✔
1383

1384
  // Forward and adjoint sources are fundamentally different.
1385
  point_sources_.clear();
24✔
1386
  volumetric_sources_.clear();
24✔
1387
  ClearBoundaries();
24✔
1388

1389
  // Reset all solution vectors.
1390
  ZeroPhi();
24✔
1391
  ResetDerivedSolutionVectors();
24✔
1392
  ZeroPrecursors();
24✔
1393
}
1394

1395
void
1396
LBSProblem::SetForward()
×
1397
{
1398
  SetAdjoint(false);
×
1399
}
×
1400

1401
bool
1402
LBSProblem::IsAdjoint() const
×
1403
{
1404
  return options_.adjoint;
×
1405
}
1406

1407
} // namespace opensn
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc