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

Open-Sn / opensn / 25903849510

14 May 2026 09:05PM UTC coverage: 76.114% (+0.4%) from 75.683%
25903849510

push

github

web-flow
Merge pull request #1071 from wdhawkins/input_wildcard

Add wildcard support to regression test script.

22612 of 29708 relevant lines covered (76.11%)

65089871.83 hits per line

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

83.99
/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/caliper_scopes.h"
17
#include "caliper/cali.h"
18
#include <algorithm>
19
#include <iomanip>
20
#include <fstream>
21
#include <cstring>
22
#include <cassert>
23
#include <memory>
24
#include <stdexcept>
25
#include <sys/stat.h>
26
#include <unordered_map>
27
#include <functional>
28
#include <utility>
29

30
namespace opensn
31
{
32

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

38
  params.ChangeExistingParamToOptional("name", "LBSProblem");
2,108✔
39

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

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

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

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

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

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

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

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

65
  return params;
1,054✔
66
}
×
67

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

85
  // Initialize options
86
  if (params.IsParameterValid("options"))
1,054✔
87
  {
88
    auto options_params = LBSProblem::GetOptionsBlock();
839✔
89
    options_params.AssignParameters(params.GetParam("options"));
841✔
90
    ParseOptions(options_params);
837✔
91
  }
839✔
92

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

98
  InitializeGroupsets(params);
1,052✔
99
  InitializeSources(params);
1,052✔
100
  InitializeXSMap(params);
1,052✔
101
  InitializeMaterials();
1,052✔
102
}
1,074✔
103

104
const LBSOptions&
105
LBSProblem::GetOptions() const
1,343,973,436✔
106
{
107
  return options_;
1,343,973,436✔
108
}
109

110
double
111
LBSProblem::GetTime() const
231,526✔
112
{
113
  return time_;
231,526✔
114
}
115

116
void
117
LBSProblem::SetTime(double time)
7,180✔
118
{
119
  time_ = time;
7,180✔
120
}
7,180✔
121

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

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

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

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

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

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

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

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

173
unsigned int
174
LBSProblem::GetNumMoments() const
401,172✔
175
{
176
  return num_moments_;
401,172✔
177
}
178

179
unsigned int
180
LBSProblem::GetMaxCellDOFCount() const
1,857✔
181
{
182
  return max_cell_dof_count_;
1,857✔
183
}
184

185
unsigned int
186
LBSProblem::GetMinCellDOFCount() const
1,857✔
187
{
188
  return min_cell_dof_count_;
1,857✔
189
}
190

191
bool
192
LBSProblem::UseGPUs() const
2,649✔
193
{
194
  return use_gpus_;
2,649✔
195
}
196

197
unsigned int
198
LBSProblem::GetNumGroups() const
590,719✔
199
{
200
  return num_groups_;
590,719✔
201
}
202

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

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

215
unsigned int
216
LBSProblem::GetMaxPrecursorsPerMaterial() const
11,696✔
217
{
218
  return max_precursors_per_material_;
11,696✔
219
}
220

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

227
LBSGroupset&
228
LBSProblem::GetGroupset(size_t groupset_id)
8,744,312✔
229
{
230
  return groupsets_.at(groupset_id);
8,744,312✔
231
}
232

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

239
size_t
240
LBSProblem::GetNumGroupsets() const
36✔
241
{
242
  return groupsets_.size();
36✔
243
}
244

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

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

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

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

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

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

283
const BlockID2XSMap&
284
LBSProblem::GetBlockID2XSMap() const
2,078,674✔
285
{
286
  return block_id_to_xs_map_;
2,078,674✔
287
}
288

289
void
290
LBSProblem::SetBlockID2XSMap(const BlockID2XSMap& xs_map)
184✔
291
{
292
  const BlockID2XSMap old_xs_map = block_id_to_xs_map_;
184✔
293
  const size_t old_max_precursors_per_material = max_precursors_per_material_;
184✔
294
  const auto old_precursor_state = precursor_new_local_;
184✔
295

296
  block_id_to_xs_map_ = xs_map;
184✔
297
  InitializeMaterials();
184✔
298

299
  if (options_.use_precursors)
184✔
300
  {
301
    const size_t num_cells = grid_->local_cells.size();
136✔
302
    const size_t new_max_precursors_per_material = max_precursors_per_material_;
136✔
303
    const size_t num_precursor_dofs = num_cells * new_max_precursors_per_material;
136✔
304

305
    std::vector<double> remapped_precursors(num_precursor_dofs, 0.0);
136✔
306
    if (old_precursor_state.size() == num_cells * old_max_precursors_per_material)
136✔
307
    {
308
      for (const auto& cell : grid_->local_cells)
13,792✔
309
      {
310
        unsigned int old_num_precursors = 0;
13,656✔
311
        if (const auto old_xs_it = old_xs_map.find(cell.block_id); old_xs_it != old_xs_map.end())
13,656✔
312
          old_num_precursors = old_xs_it->second->GetNumPrecursors();
13,656✔
313

314
        const unsigned int new_num_precursors =
13,656✔
315
          block_id_to_xs_map_.at(cell.block_id)->GetNumPrecursors();
13,656✔
316
        const unsigned int num_precursors_to_copy =
13,656✔
317
          std::min(old_num_precursors, new_num_precursors);
13,656✔
318

319
        const size_t old_base = cell.local_id * old_max_precursors_per_material;
13,656✔
320
        const size_t new_base = cell.local_id * new_max_precursors_per_material;
13,656✔
321
        for (unsigned int j = 0; j < num_precursors_to_copy; ++j)
15,660✔
322
          remapped_precursors[new_base + j] = old_precursor_state[old_base + j];
2,004✔
323
      }
324
    }
325

326
    precursor_new_local_ = std::move(remapped_precursors);
136✔
327
  }
136✔
328
  else
329
    precursor_new_local_.clear();
48✔
330

331
  ResetGPUCarriers();
184✔
332
  InitializeGPUExtras();
184✔
333
}
184✔
334

335
std::shared_ptr<MeshContinuum>
336
LBSProblem::GetGrid() const
533,428✔
337
{
338
  return grid_;
533,428✔
339
}
340

341
const SpatialDiscretization&
342
LBSProblem::GetSpatialDiscretization() const
168,741✔
343
{
344
  return *discretization_;
168,741✔
345
}
346

347
const std::vector<UnitCellMatrices>&
348
LBSProblem::GetUnitCellMatrices() const
2,065,612✔
349
{
350
  return unit_cell_matrices_;
2,065,612✔
351
}
352

353
const std::map<uint64_t, UnitCellMatrices>&
354
LBSProblem::GetUnitGhostCellMatrices() const
16✔
355
{
356
  return unit_ghost_cell_matrices_;
16✔
357
}
358

359
const std::vector<CellLBSView>&
360
LBSProblem::GetCellTransportViews() const
596,348✔
361
{
362
  return cell_transport_views_;
596,348✔
363
}
364

365
OutflowBank&
366
LBSProblem::GetOutflowBank()
157✔
367
{
368
  return outflow_bank_;
157✔
369
}
370

371
std::vector<CellOutflowView>&
372
LBSProblem::GetCellOutflowViews()
2,328✔
373
{
374
  return cell_outflow_views_;
2,328✔
375
}
376

377
const std::vector<CellOutflowView>&
378
LBSProblem::GetCellOutflowViews() const
×
379
{
380
  return cell_outflow_views_;
×
381
}
382

383
const UnknownManager&
384
LBSProblem::GetUnknownManager() const
49,597✔
385
{
386
  return flux_moments_uk_man_;
49,597✔
387
}
388

389
size_t
390
LBSProblem::GetLocalNodeCount() const
20,747✔
391
{
392
  return local_node_count_;
20,747✔
393
}
394

395
std::uint64_t
396
LBSProblem::GetGlobalNodeCount() const
2,861✔
397
{
398
  return global_node_count_;
2,861✔
399
}
400

401
std::vector<double>&
402
LBSProblem::GetQMomentsLocal()
122,917✔
403
{
404
  return q_moments_local_;
122,917✔
405
}
406

407
const std::vector<double>&
408
LBSProblem::GetQMomentsLocal() const
×
409
{
410
  return q_moments_local_;
×
411
}
412

413
const std::vector<double>&
414
LBSProblem::GetExtSrcMomentsLocal() const
114,807✔
415
{
416
  return ext_src_moments_local_;
114,807✔
417
}
418

419
void
420
LBSProblem::SetExtSrcMomentsFrom(const std::vector<double>& ext_src_moments)
4✔
421
{
422
  if (not phi_old_local_.empty())
4✔
423
    OpenSnLogicalErrorIf(ext_src_moments.size() != phi_old_local_.size(),
4✔
424
                         "SetExtSrcMomentsFrom size mismatch. Provided size=" +
425
                           std::to_string(ext_src_moments.size()) +
426
                           ", expected local DOFs=" + std::to_string(phi_old_local_.size()) + ".");
427

428
  if (ext_src_moments_local_.empty())
4✔
429
  {
430
    ext_src_moments_local_ = ext_src_moments;
4✔
431
    return;
4✔
432
  }
433

434
  assert(ext_src_moments.size() == ext_src_moments_local_.size() &&
×
435
         "SetExtSrcMomentsFrom size mismatch.");
436
  ext_src_moments_local_ = ext_src_moments;
×
437
}
438

439
std::vector<double>&
440
LBSProblem::GetPhiOldLocal()
250,859✔
441
{
442
  return phi_old_local_;
250,859✔
443
}
444

445
const std::vector<double>&
446
LBSProblem::GetPhiOldLocal() const
×
447
{
448
  return phi_old_local_;
×
449
}
450

451
std::vector<double>&
452
LBSProblem::GetPhiNewLocal()
172,821✔
453
{
454
  return phi_new_local_;
172,821✔
455
}
456

457
const std::vector<double>&
458
LBSProblem::GetPhiNewLocal() const
11,279✔
459
{
460
  return phi_new_local_;
11,279✔
461
}
462

463
std::vector<double>&
464
LBSProblem::GetPrecursorsNewLocal()
6,348✔
465
{
466
  return precursor_new_local_;
6,348✔
467
}
468

469
const std::vector<double>&
470
LBSProblem::GetPrecursorsNewLocal() const
×
471
{
472
  return precursor_new_local_;
×
473
}
474

475
SetSourceFunction
476
LBSProblem::GetActiveSetSourceFunction() const
5,997✔
477
{
478
  return active_set_source_function_;
5,997✔
479
}
480

481
void
482
LBSProblem::SetActiveSetSourceFunction(SetSourceFunction source_function)
152✔
483
{
484
  active_set_source_function_ = std::move(source_function);
152✔
485
}
152✔
486

487
std::pair<std::uint64_t, std::uint64_t>
488
LBSProblem::GetNumPhiIterativeUnknowns()
×
489
{
490
  const auto& sdm = *discretization_;
×
491
  const auto num_local_phi_dofs = sdm.GetNumLocalDOFs(flux_moments_uk_man_);
×
492
  const auto num_global_phi_dofs = sdm.GetNumGlobalDOFs(flux_moments_uk_man_);
×
493

494
  return {num_local_phi_dofs, num_global_phi_dofs};
×
495
}
496

497
InputParameters
498
LBSProblem::GetOptionsBlock()
1,676✔
499
{
500
  InputParameters params;
1,676✔
501

502
  params.SetGeneralDescription("Set options from a large list of parameters");
3,352✔
503
  params.AddOptionalParameter("max_mpi_message_size",
3,352✔
504
                              32768,
505
                              "The maximum MPI message size used during sweep initialization.");
506
  params.AddOptionalParameter(
3,352✔
507
    "restart_writes_enabled", false, "Flag that controls writing of restart dumps");
508
  params.AddOptionalParameter("write_delayed_psi_to_restart",
3,352✔
509
                              true,
510
                              "Flag that controls writing of delayed angular fluxes to restarts.");
511
  params.AddOptionalParameter("write_angular_flux_to_restart",
3,352✔
512
                              true,
513
                              "Flag that controls writing angular fluxes to restart dumps when "
514
                              "`save_angular_flux` is enabled.");
515
  params.AddOptionalParameter(
3,352✔
516
    "read_restart_path", "", "Full path for reading restart dumps including file stem.");
517
  params.AddOptionalParameter(
3,352✔
518
    "read_initial_condition_path",
519
    "",
520
    "Full path for reading restart data as an initial condition, including file stem.");
521
  params.AddOptionalParameter(
3,352✔
522
    "write_restart_path", "", "Full path for writing restart dumps including file stem.");
523
  params.AddOptionalParameter("write_restart_time_interval",
3,352✔
524
                              0,
525
                              "Time interval in seconds at which restart data is to be written.");
526
  params.AddOptionalParameter("use_precursors", true, "Flag for using delayed neutron precursors.");
3,352✔
527
  params.AddOptionalParameter("use_source_moments",
3,352✔
528
                              false,
529
                              "Flag for ignoring fixed sources and selectively using source "
530
                              "moments obtained elsewhere.");
531
  params.AddOptionalParameter(
3,352✔
532
    "save_angular_flux", false, "Flag indicating whether angular fluxes are to be stored or not.");
533
  params.AddOptionalParameter(
3,352✔
534
    "adjoint", false, "Flag for toggling whether the solver is in adjoint mode.");
535
  params.AddOptionalParameter(
3,352✔
536
    "verbose_inner_iterations",
537
    true,
538
    "Flag to control verbosity of inner iterations, including WGS and AGS iterations.");
539
  params.AddOptionalParameter(
3,352✔
540
    "verbose_outer_iterations", true, "Flag to control verbosity of outer iterations.");
541
  params.AddOptionalParameter(
3,352✔
542
    "max_ags_iterations", 100, "Maximum number of across-groupset iterations.");
543
  params.AddOptionalParameter("ags_tolerance", 1.0e-6, "Across-groupset iterations tolerance.");
3,352✔
544
  params.AddOptionalParameter("ags_convergence_check",
3,352✔
545
                              "l2",
546
                              "Type of convergence check for AGS iterations. Valid values are "
547
                              "`\"l2\"` and '\"pointwise\"'");
548
  params.AddOptionalParameter("power_default_kappa",
3,352✔
549
                              3.20435e-11,
550
                              "Default `kappa` value (Energy released per fission) to use for "
551
                              "power generation when cross sections do not have `kappa` values. "
552
                              "Default: 3.20435e-11 Joule (corresponding to 200 MeV per fission).");
553
  params.AddOptionalParameter("field_function_prefix_option",
3,352✔
554
                              "prefix",
555
                              "Prefix option on field function names. Default: `\"prefix\"`. Can "
556
                              "be `\"prefix\"` or `\"solver_name\"`. By default this option uses "
557
                              "the value of the `field_function_prefix` parameter. If this "
558
                              "parameter is not set, flux field functions will be exported as "
559
                              "`phi_gXXX_mYYY` where `XXX` is the zero padded 3 digit group number "
560
                              "and `YYY` is the zero padded 3 digit moment.");
561
  params.AddOptionalParameter("field_function_prefix",
3,352✔
562
                              "",
563
                              "Prefix to use on all field functions. Default: `\"\"`. By default "
564
                              "this option is empty. Ff specified, flux moments will be exported "
565
                              "as `prefix_phi_gXXX_mYYY` where `XXX` is the zero padded 3 digit "
566
                              "group number and `YYY` is the zero padded 3 digit moment. The "
567
                              "underscore after \"prefix\" is added automatically.");
568
  params.ConstrainParameterRange("ags_convergence_check",
5,028✔
569
                                 AllowableRangeList::New({"l2", "pointwise"}));
1,676✔
570
  params.ConstrainParameterRange("field_function_prefix_option",
5,028✔
571
                                 AllowableRangeList::New({"prefix", "solver_name"}));
1,676✔
572
  params.ConstrainParameterRange("max_mpi_message_size", AllowableRangeLowLimit::New(1024));
5,028✔
573
  params.ConstrainParameterRange("write_restart_time_interval", AllowableRangeLowLimit::New(0));
5,028✔
574
  params.ConstrainParameterRange("max_ags_iterations", AllowableRangeLowLimit::New(0));
5,028✔
575
  params.ConstrainParameterRange("ags_tolerance", AllowableRangeLowLimit::New(1.0e-18));
5,028✔
576
  params.ConstrainParameterRange("power_default_kappa", AllowableRangeLowLimit::New(0.0, false));
5,028✔
577

578
  return params;
1,676✔
579
}
×
580

581
InputParameters
582
LBSProblem::GetXSMapEntryBlock()
1,505✔
583
{
584
  InputParameters params;
1,505✔
585
  params.SetGeneralDescription("Set the cross-section map for the solver.");
3,010✔
586
  params.AddRequiredParameterArray("block_ids", "Mesh block IDs");
3,010✔
587
  params.AddRequiredParameter<std::shared_ptr<MultiGroupXS>>("xs", "Cross-section object");
3,010✔
588
  return params;
1,505✔
589
}
×
590

591
void
592
LBSProblem::ParseOptions(const InputParameters& input)
837✔
593
{
594
  auto params = LBSProblem::GetOptionsBlock();
837✔
595
  params.AssignParameters(input);
837✔
596
  const auto& params_at_assignment = input.GetParametersAtAssignment();
837✔
597
  const auto& specified_params = params_at_assignment.GetNumParameters() > 0
837✔
598
                                   ? params_at_assignment
837✔
599
                                   : static_cast<const ParameterBlock&>(input);
837✔
600

601
  using OptionSetter = std::function<void(const ParameterBlock&)>;
837✔
602
  const std::unordered_map<std::string, OptionSetter> option_setters = {
837✔
603
    {"max_mpi_message_size",
604
     [this](const ParameterBlock& spec) { options_.max_mpi_message_size = spec.GetValue<int>(); }},
×
605
    {"restart_writes_enabled",
606
     [this](const ParameterBlock& spec)
1,710✔
607
     { options_.restart.writes_enabled = spec.GetValue<bool>(); }},
36✔
608
    {"write_delayed_psi_to_restart",
609
     [this](const ParameterBlock& spec)
1,706✔
610
     { options_.restart.write_delayed_psi = spec.GetValue<bool>(); }},
32✔
611
    {"write_angular_flux_to_restart",
612
     [this](const ParameterBlock& spec)
1,706✔
613
     { options_.restart.write_angular_flux = spec.GetValue<bool>(); }},
32✔
614
    {"read_restart_path",
615
     [this](const ParameterBlock& spec)
1,690✔
616
     { options_.restart.read_path = BuildRestartPath(spec.GetValue<std::string>()); }},
32✔
617
    {"read_initial_condition_path",
618
     [this](const ParameterBlock& spec)
1,706✔
619
     {
620
       options_.restart.read_initial_condition_path =
64✔
621
         BuildRestartPath(spec.GetValue<std::string>());
64✔
622
     }},
32✔
623
    {"write_restart_path",
624
     [this](const ParameterBlock& spec)
1,710✔
625
     { options_.restart.write_path = BuildRestartPath(spec.GetValue<std::string>()); }},
72✔
626
    {"write_restart_time_interval",
627
     [this](const ParameterBlock& spec)
1,674✔
628
     { options_.restart.write_time_interval = std::chrono::seconds(spec.GetValue<int>()); }},
×
629
    {"use_precursors",
630
     [this](const ParameterBlock& spec) { options_.use_precursors = spec.GetValue<bool>(); }},
316✔
631
    {"use_source_moments",
632
     [this](const ParameterBlock& spec) { options_.use_src_moments = spec.GetValue<bool>(); }},
4✔
633
    {"save_angular_flux",
634
     [this](const ParameterBlock& spec) { options_.save_angular_flux = spec.GetValue<bool>(); }},
442✔
635
    {"verbose_inner_iterations",
636
     [this](const ParameterBlock& spec)
2,305✔
637
     { options_.verbose_inner_iterations = spec.GetValue<bool>(); }},
631✔
638
    {"max_ags_iterations",
639
     [this](const ParameterBlock& spec) { options_.max_ags_iterations = spec.GetValue<int>(); }},
292✔
640
    {"ags_tolerance",
641
     [this](const ParameterBlock& spec) { options_.ags_tolerance = spec.GetValue<double>(); }},
16✔
642
    {"ags_convergence_check",
643
     [this](const ParameterBlock& spec)
1,674✔
644
     { options_.ags_pointwise_convergence = (spec.GetValue<std::string>() == "pointwise"); }},
×
645
    {"verbose_outer_iterations",
646
     [this](const ParameterBlock& spec)
2,277✔
647
     { options_.verbose_outer_iterations = spec.GetValue<bool>(); }},
603✔
648
    {"power_default_kappa",
649
     [this](const ParameterBlock& spec)
1,687✔
650
     { options_.power_default_kappa = spec.GetValue<double>(); }},
13✔
651
    {"field_function_prefix_option",
652
     [this](const ParameterBlock& spec)
1,674✔
653
     { options_.field_function_prefix_option = spec.GetValue<std::string>(); }},
×
654
    {"field_function_prefix",
655
     [this](const ParameterBlock& spec)
1,674✔
656
     { options_.field_function_prefix = spec.GetValue<std::string>(); }},
×
657
    {"adjoint", [this](const ParameterBlock& spec) { options_.adjoint = spec.GetValue<bool>(); }},
16✔
658
  };
18,414✔
659

660
  for (const auto& spec : specified_params.GetParameters())
3,354✔
661
  {
662
    const auto setter_it = option_setters.find(spec.GetName());
5,034✔
663
    if (setter_it != option_setters.end())
2,517✔
664
      setter_it->second(spec);
2,517✔
665
  }
666

667
  OpenSnInvalidArgumentIf(options_.restart.write_time_interval > std::chrono::seconds(0) and
837✔
668
                            not options_.restart.writes_enabled,
669
                          GetName() + ": `write_restart_time_interval>0` requires "
670
                                      "`restart_writes_enabled=true`.");
671

672
  OpenSnInvalidArgumentIf(options_.restart.write_time_interval > std::chrono::seconds(0) and
837✔
673
                            options_.restart.write_time_interval < std::chrono::seconds(30),
674
                          GetName() + ": `write_restart_time_interval` must be 0 (disabled) "
675
                                      "or at least 30 seconds.");
676

677
  OpenSnInvalidArgumentIf(options_.restart.writes_enabled and options_.restart.write_path.empty(),
837✔
678
                          GetName() + ": `restart_writes_enabled=true` requires a non-empty "
679
                                      "`write_restart_path`.");
680

681
  OpenSnInvalidArgumentIf(not options_.field_function_prefix.empty() and
837✔
682
                            options_.field_function_prefix_option != "prefix",
683
                          GetName() + ": non-empty `field_function_prefix` requires "
684
                                      "`field_function_prefix_option=\"prefix\"`.");
685

686
  if (options_.restart.writes_enabled)
837✔
687
  {
688
    const auto dir = options_.restart.write_path.parent_path();
36✔
689

690
    // Create restart directory if necessary.
691
    // If dir is empty, write path resolves relative to the working directory.
692
    if ((not dir.empty()) and opensn::mpi_comm.rank() == 0)
40✔
693
    {
694
      if (not std::filesystem::exists(dir))
1✔
695
      {
696
        OpenSnLogicalErrorIf(not std::filesystem::create_directories(dir),
×
697
                             GetName() + ": Failed to create restart directory " + dir.string());
698
      }
699
      else
700
        OpenSnLogicalErrorIf(not std::filesystem::is_directory(dir),
1✔
701
                             GetName() + ": Restart path exists but is not a directory " +
702
                               dir.string());
703
    }
704
    opensn::mpi_comm.barrier();
36✔
705
    options_.restart.MarkWriteComplete();
36✔
706
  }
36✔
707
}
1,674✔
708

709
std::filesystem::path
710
LBSProblem::BuildRestartPath(const std::string& path_stem)
84✔
711
{
712
  if (path_stem.empty())
84✔
713
    return {};
×
714

715
  auto path = std::filesystem::path(path_stem);
84✔
716
  path += std::to_string(opensn::mpi_comm.rank()) + ".restart.h5";
252✔
717
  return path;
84✔
718
}
84✔
719

720
bool
721
LBSProblem::ReadProblemRestartData(hid_t /*file_id*/,
×
722
                                   bool /*allow_transient_initialization_from_steady*/)
723
{
724
  return true;
×
725
}
726

727
bool
728
LBSProblem::WriteProblemRestartData(hid_t /*file_id*/) const
×
729
{
730
  return true;
×
731
}
732

733
void
734
LBSProblem::BuildRuntime()
1,044✔
735
{
736
  PrintSimHeader();
1,044✔
737
  mpi_comm.barrier();
1,044✔
738

739
  InitializeRuntimeCore();
1,044✔
740
  ValidateRuntimeModeConfiguration();
1,044✔
741
  InitializeSources();
1,044✔
742
}
1,044✔
743

744
void
745
LBSProblem::InitializeRuntimeCore()
1,044✔
746
{
747
  InitializeSpatialDiscretization();
1,044✔
748
  InitializeParrays();
1,044✔
749
  InitializeBoundaries();
1,044✔
750
  InitializeGPUExtras();
1,044✔
751
}
1,044✔
752

753
void
754
LBSProblem::ValidateRuntimeModeConfiguration() const
1,044✔
755
{
756
  if (options_.adjoint)
1,044✔
757
    if (IsTimeDependent())
16✔
758
      OpenSnInvalidArgument(GetName() + ": Time-dependent adjoint problems are not supported.");
×
759
}
1,044✔
760

761
void
762
LBSProblem::InitializeSources()
1,044✔
763
{
764
  // Initialize point sources
765
  for (auto& point_source : point_sources_)
1,052✔
766
    point_source->Initialize(*this);
8✔
767

768
  // Initialize volumetric sources
769
  for (auto& volumetric_source : volumetric_sources_)
1,893✔
770
    volumetric_source->Initialize(*this);
849✔
771
}
1,044✔
772

773
void
774
LBSProblem::PrintSimHeader()
×
775
{
776
  if (opensn::mpi_comm.rank() == 0)
×
777
  {
778
    std::stringstream outstr;
×
779
    outstr << "\n"
×
780
           << "Initializing " << GetName() << "\n\n"
×
781
           << "Scattering order    : " << scattering_order_ << "\n"
×
782
           << "Number of moments   : " << num_moments_ << "\n"
×
783
           << "Number of groups    : " << num_groups_ << "\n"
×
784
           << "Number of groupsets : " << groupsets_.size() << "\n\n";
×
785

786
    for (const auto& groupset : groupsets_)
×
787
    {
788
      outstr << "***** Groupset " << groupset.id << " *****\n"
×
789
             << "Groups:\n";
×
790
      const auto n_gs_groups = groupset.GetNumGroups();
×
791
      constexpr int groups_per_line = 12;
792
      for (size_t i = 0; i < n_gs_groups; ++i)
×
793
      {
794
        outstr << std::setw(5) << groupset.first_group + i << ' ';
×
795
        if ((i + 1) % groups_per_line == 0)
×
796
          outstr << '\n';
×
797
      }
798
      if (n_gs_groups > 0 && n_gs_groups % groups_per_line != 0)
×
799
        outstr << '\n';
×
800
    }
801

802
    log.Log() << outstr.str() << '\n';
×
803
  }
×
804
}
×
805

806
void
807
LBSProblem::InitializeSources(const InputParameters& params)
1,052✔
808
{
809
  if (params.Has("volumetric_sources"))
1,052✔
810
  {
811
    const auto& vol_srcs = params.GetParam("volumetric_sources");
1,052✔
812
    vol_srcs.RequireBlockTypeIs(ParameterBlockType::ARRAY);
1,052✔
813
    for (const auto& src : vol_srcs)
1,901✔
814
      volumetric_sources_.push_back(src.GetValue<std::shared_ptr<VolumetricSource>>());
1,698✔
815
  }
816

817
  if (params.Has("point_sources"))
1,052✔
818
  {
819
    const auto& pt_srcs = params.GetParam("point_sources");
1,052✔
820
    pt_srcs.RequireBlockTypeIs(ParameterBlockType::ARRAY);
1,052✔
821
    for (const auto& src : pt_srcs)
1,060✔
822
      point_sources_.push_back(src.GetValue<std::shared_ptr<PointSource>>());
16✔
823
  }
824
}
1,052✔
825

826
void
827
LBSProblem::InitializeGroupsets(const InputParameters& params)
1,052✔
828
{
829
  // Initialize groups
830
  OpenSnInvalidArgumentIf(num_groups_ == 0, GetName() + ": Number of groups must be > 0.");
1,052✔
831

832
  // Initialize groupsets
833
  const auto& groupsets_array = params.GetParam("groupsets");
1,052✔
834
  const size_t num_gs = groupsets_array.GetNumParameters();
1,052✔
835
  OpenSnInvalidArgumentIf(num_gs == 0, GetName() + ": At least one groupset must be specified.");
1,052✔
836
  for (size_t gs = 0; gs < num_gs; ++gs)
2,225✔
837
  {
838
    const auto& groupset_params = groupsets_array.GetParam(gs);
1,173✔
839
    InputParameters gs_input_params = LBSGroupset::GetInputParameters();
1,173✔
840
    gs_input_params.SetObjectType("LBSProblem:LBSGroupset");
1,173✔
841
    gs_input_params.AssignParameters(groupset_params);
1,173✔
842
    groupsets_.emplace_back(gs_input_params, gs, *this);
1,173✔
843
    if (groupsets_.back().GetNumGroups() == 0)
1,173✔
844
    {
845
      std::stringstream oss;
×
846
      oss << GetName() << ": No groups added to groupset " << groupsets_.back().id;
×
847
      OpenSnInvalidArgument(oss.str());
×
848
    }
×
849

850
    if (groupsets_.back().last_group >= num_groups_)
1,173✔
851
    {
852
      std::stringstream oss;
×
853
      oss << GetName() << ": Groupset " << groupsets_.back().id << " has last group "
×
854
          << groupsets_.back().last_group << ", but the problem only has " << num_groups_
×
855
          << " groups.";
×
856
      OpenSnInvalidArgument(oss.str());
×
857
    }
×
858

859
    if (gs > 0)
1,173✔
860
    {
861
      const auto& previous_groupset = groupsets_[gs - 1];
121✔
862
      const auto& current_groupset = groupsets_.back();
121✔
863
      const auto expected_first_group = previous_groupset.last_group + 1;
121✔
864
      if (current_groupset.first_group != expected_first_group)
121✔
865
      {
866
        std::stringstream oss;
×
867
        oss << GetName() << ": Groupset " << current_groupset.id << " starts at group "
×
868
            << current_groupset.first_group << ", but it should start at group "
×
869
            << expected_first_group << " to be consecutive with groupset " << previous_groupset.id
×
870
            << ".";
×
871
        OpenSnInvalidArgument(oss.str());
×
872
      }
×
873
    }
874
  }
1,173✔
875
}
1,052✔
876

877
void
878
LBSProblem::InitializeXSMap(const InputParameters& params)
1,052✔
879
{
880
  // Build XS map
881
  const auto& xs_array = params.GetParam("xs_map");
1,052✔
882
  const size_t num_xs = xs_array.GetNumParameters();
1,052✔
883
  for (size_t i = 0; i < num_xs; ++i)
2,373✔
884
  {
885
    const auto& item_params = xs_array.GetParam(i);
1,321✔
886
    InputParameters xs_entry_pars = GetXSMapEntryBlock();
1,321✔
887
    xs_entry_pars.AssignParameters(item_params);
1,321✔
888

889
    const auto& block_ids_param = xs_entry_pars.GetParam("block_ids");
1,321✔
890
    block_ids_param.RequireBlockTypeIs(ParameterBlockType::ARRAY);
1,321✔
891
    const auto& block_ids = block_ids_param.GetVectorValue<unsigned int>();
1,321✔
892
    auto xs = xs_entry_pars.GetSharedPtrParam<MultiGroupXS>("xs");
1,321✔
893
    for (const auto& block_id : block_ids)
2,786✔
894
      block_id_to_xs_map_[block_id] = xs;
1,465✔
895
  }
1,321✔
896
}
1,052✔
897

898
void
899
LBSProblem::InitializeMaterials()
1,260✔
900
{
901
  CaliperPhaseScope cali_setup_phase("Setup", CaliperSetupPhaseDepth());
1,260✔
902
  CALI_CXX_MARK_SCOPE("Materials");
1,260✔
903

904
  log.Log0Verbose1() << "Initializing Materials";
2,520✔
905

906
  // Create set of material ids locally relevant
907
  int invalid_mat_cell_count = 0;
1,260✔
908
  std::set<unsigned int> unique_block_ids;
1,260✔
909
  for (auto& cell : grid_->local_cells)
685,547✔
910
  {
911
    unique_block_ids.insert(cell.block_id);
684,287✔
912
    if (cell.block_id == std::numeric_limits<unsigned int>::max() or
684,287✔
913
        (block_id_to_xs_map_.find(cell.block_id) == block_id_to_xs_map_.end()))
684,287✔
914
      ++invalid_mat_cell_count;
×
915
  }
916
  const auto& ghost_cell_ids = grid_->cells.GetGhostGlobalIDs();
1,260✔
917
  for (uint64_t cell_id : ghost_cell_ids)
113,029✔
918
  {
919
    const auto& cell = grid_->cells[cell_id];
111,769✔
920
    unique_block_ids.insert(cell.block_id);
111,769✔
921
    if (cell.block_id == std::numeric_limits<unsigned int>::max() or
111,769✔
922
        (block_id_to_xs_map_.find(cell.block_id) == block_id_to_xs_map_.end()))
111,769✔
923
      ++invalid_mat_cell_count;
×
924
  }
925
  OpenSnLogicalErrorIf(invalid_mat_cell_count > 0,
1,260✔
926
                       std::to_string(invalid_mat_cell_count) +
927
                         " cells encountered with an invalid material id.");
928

929
  // Get ready for processing
930
  for (const auto& [blk_id, mat] : block_id_to_xs_map_)
2,957✔
931
  {
932
    mat->SetAdjointMode(options_.adjoint);
1,697✔
933

934
    OpenSnLogicalErrorIf(mat->GetNumGroups() < num_groups_,
1,697✔
935
                         "Cross-sections for block \"" + std::to_string(blk_id) +
936
                           "\" have fewer groups (" + std::to_string(mat->GetNumGroups()) +
937
                           ") than the simulation (" + std::to_string(num_groups_) + "). " +
938
                           "Cross-sections must have at least as many groups as the simulation.");
939
  }
940

941
  // Initialize precursor properties
942
  num_precursors_ = 0;
1,260✔
943
  max_precursors_per_material_ = 0;
1,260✔
944
  for (const auto& mat_id_xs : block_id_to_xs_map_)
2,957✔
945
  {
946
    const auto& xs = mat_id_xs.second;
1,697✔
947
    num_precursors_ += xs->GetNumPrecursors();
1,697✔
948
    max_precursors_per_material_ = std::max(xs->GetNumPrecursors(), max_precursors_per_material_);
1,697✔
949
  }
950

951
  const bool has_fissionable_precursors =
1,260✔
952
    std::any_of(block_id_to_xs_map_.begin(),
1,260✔
953
                block_id_to_xs_map_.end(),
954
                [](const auto& mat_id_xs)
1,697✔
955
                {
956
                  const auto& xs = mat_id_xs.second;
1,697✔
957
                  return xs->IsFissionable() and xs->GetNumPrecursors() > 0;
1,697✔
958
                });
959
  const bool has_fissionable_material =
1,260✔
960
    std::any_of(block_id_to_xs_map_.begin(),
1,260✔
961
                block_id_to_xs_map_.end(),
962
                [](const auto& mat_id_xs) { return mat_id_xs.second->IsFissionable(); });
1,653✔
963

964
  const bool has_any_precursor_data =
1,260✔
965
    std::any_of(block_id_to_xs_map_.begin(),
1,260✔
966
                block_id_to_xs_map_.end(),
967
                [](const auto& mat_id_xs) { return mat_id_xs.second->GetNumPrecursors() > 0; });
1,697✔
968

969
  if (options_.use_precursors and has_fissionable_material and not has_any_precursor_data)
1,260✔
970
  {
971
    log.Log0Warning() << GetName()
258✔
972
                      << ": options.use_precursors is enabled, but no precursor data was found "
973
                         "in the active cross-section map. Running without delayed-neutron "
974
                         "precursor coupling.";
86✔
975
  }
976

977
  // check compatibility when at least one fissionable material has delayed-neutron data
978
  if (options_.use_precursors and has_fissionable_precursors)
1,260✔
979
  {
980
    for (const auto& [mat_id, xs] : block_id_to_xs_map_)
450✔
981
    {
982
      OpenSnInvalidArgumentIf(xs->IsFissionable() and xs->GetNumPrecursors() == 0,
225✔
983
                              GetName() + ": incompatible cross-section data for material id " +
984
                                std::to_string(mat_id) +
985
                                ". When options.use_precursors=true and "
986
                                "delayed-neutron precursor data is present for one fissionable "
987
                                "material, it must be present for all fissionable materials.");
988
    }
989
  }
990

991
  // Update transport views if available
992
  if (grid_->local_cells.size() == cell_transport_views_.size())
1,260✔
993
    for (const auto& cell : grid_->local_cells)
28,108✔
994
    {
995
      const auto& xs_ptr = block_id_to_xs_map_[cell.block_id];
27,900✔
996
      auto& transport_view = cell_transport_views_[cell.local_id];
27,900✔
997
      transport_view.ReassignXS(*xs_ptr);
27,900✔
998
    }
999

1000
  mpi_comm.barrier();
1,260✔
1001
}
1,260✔
1002

1003
void
1004
LBSProblem::InitializeSpatialDiscretization()
964✔
1005
{
1006
  CALI_CXX_MARK_SCOPE("SpatialDiscretization");
964✔
1007

1008
  OpenSnLogicalErrorIf(not discretization_,
964✔
1009
                       GetName() + ": Missing spatial discretization. Construct the problem "
1010
                                   "through its factory Create(...) entry point.");
1011
  log.Log() << "Initializing spatial discretization metadata.\n";
1,928✔
1012

1013
  ComputeUnitIntegrals();
964✔
1014
}
964✔
1015

1016
void
1017
LBSProblem::ComputeUnitIntegrals()
1,044✔
1018
{
1019
  CALI_CXX_MARK_SCOPE("UnitIntegrals");
1,044✔
1020

1021
  log.Log() << "Computing unit integrals.\n";
2,088✔
1022
  const auto& sdm = *discretization_;
1,044✔
1023

1024
  const size_t num_local_cells = grid_->local_cells.size();
1,044✔
1025
  unit_cell_matrices_.resize(num_local_cells);
1,044✔
1026

1027
  for (const auto& cell : grid_->local_cells)
657,411✔
1028
    unit_cell_matrices_[cell.local_id] =
656,367✔
1029
      ComputeUnitCellIntegrals(sdm, cell, grid_->GetCoordinateSystem());
656,367✔
1030

1031
  const auto ghost_ids = grid_->cells.GetGhostGlobalIDs();
1,044✔
1032
  for (auto ghost_id : ghost_ids)
103,941✔
1033
    unit_ghost_cell_matrices_[ghost_id] =
102,897✔
1034
      ComputeUnitCellIntegrals(sdm, grid_->cells[ghost_id], grid_->GetCoordinateSystem());
205,794✔
1035

1036
  // Assessing global unit cell matrix storage
1037
  std::array<size_t, 2> num_local_ucms = {unit_cell_matrices_.size(),
1,044✔
1038
                                          unit_ghost_cell_matrices_.size()};
1,044✔
1039
  std::array<size_t, 2> num_global_ucms = {0, 0};
1,044✔
1040

1041
  mpi_comm.all_reduce(num_local_ucms.data(), 2, num_global_ucms.data(), mpi::op::sum<size_t>());
1,044✔
1042

1043
  opensn::mpi_comm.barrier();
1,044✔
1044
  log.Log() << "Ghost cell unit cell-matrix ratio: "
1,044✔
1045
            << (double)num_global_ucms[1] * 100 / (double)num_global_ucms[0] << "%";
2,088✔
1046
  log.Log() << "Cell matrices computed.";
2,088✔
1047
}
1,044✔
1048

1049
void
1050
LBSProblem::InitializeParrays()
1,044✔
1051
{
1052
  CALI_CXX_MARK_SCOPE("ParallelArrays");
1,044✔
1053

1054
  log.Log() << "Initializing parallel arrays."
2,088✔
1055
            << " G=" << num_groups_ << " M=" << num_moments_ << std::endl;
1,044✔
1056

1057
  // Initialize unknown
1058
  // structure
1059
  flux_moments_uk_man_.unknowns.clear();
1,044✔
1060
  for (unsigned int m = 0; m < num_moments_; ++m)
3,024✔
1061
  {
1062
    flux_moments_uk_man_.AddUnknown(UnknownType::VECTOR_N, num_groups_);
1,980✔
1063
    flux_moments_uk_man_.unknowns.back().name = "m" + std::to_string(m);
1,980✔
1064
  }
1065

1066
  // Compute local # of dof
1067
  local_node_count_ = discretization_->GetNumLocalNodes();
1,044✔
1068
  global_node_count_ = discretization_->GetNumGlobalNodes();
1,044✔
1069

1070
  // Compute num of unknowns
1071
  size_t local_unknown_count = local_node_count_ * num_groups_ * num_moments_;
1,044✔
1072
  log.LogAllVerbose1() << "LBS Number of phi unknowns: " << local_unknown_count;
2,088✔
1073

1074
  // Size local vectors
1075
  q_moments_local_.assign(local_unknown_count, 0.0);
1,044✔
1076
  phi_old_local_.assign(local_unknown_count, 0.0);
1,044✔
1077
  phi_new_local_.assign(local_unknown_count, 0.0);
1,044✔
1078

1079
  // Setup precursor vector
1080
  if (options_.use_precursors)
1,044✔
1081
  {
1082
    size_t num_precursor_dofs = grid_->local_cells.size() * max_precursors_per_material_;
840✔
1083
    precursor_new_local_.assign(num_precursor_dofs, 0.0);
840✔
1084
  }
1085

1086
  // Initialize cell transport metadata and outflow tallies.
1087
  size_t block_MG_counter = 0; // Counts the strides of moment and group
1,044✔
1088
  min_cell_dof_count_ = std::numeric_limits<unsigned int>::max();
1,044✔
1089
  max_cell_dof_count_ = 0;
1,044✔
1090
  cell_transport_views_.clear();
1,044✔
1091
  cell_transport_views_.reserve(grid_->local_cells.size());
1,044✔
1092
  for (auto& cell : grid_->local_cells)
657,411✔
1093
  {
1094
    size_t num_nodes = discretization_->GetCellNumNodes(cell);
656,367✔
1095

1096
    // compute cell volumes
1097
    double cell_volume = 0.0;
656,367✔
1098
    const auto& IntV_shapeI = unit_cell_matrices_[cell.local_id].intV_shapeI;
656,367✔
1099
    for (size_t i = 0; i < num_nodes; ++i)
4,615,083✔
1100
      cell_volume += IntV_shapeI(i);
3,958,716✔
1101

1102
    size_t cell_phi_address = block_MG_counter;
656,367✔
1103

1104
    const size_t num_faces = cell.faces.size();
656,367✔
1105
    std::vector<bool> face_local_flags(num_faces, true);
656,367✔
1106
    std::vector<int> face_locality(num_faces, opensn::mpi_comm.rank());
656,367✔
1107
    std::vector<const Cell*> neighbor_cell_ptrs(num_faces, nullptr);
656,367✔
1108
    int f = 0;
656,367✔
1109
    for (auto& face : cell.faces)
3,930,831✔
1110
    {
1111
      if (not face.has_neighbor)
3,274,464✔
1112
      {
1113
        face_local_flags[f] = false;
118,888✔
1114
        face_locality[f] = -1;
118,888✔
1115
      } // if bndry
1116
      else
1117
      {
1118
        const int neighbor_partition = face.GetNeighborPartitionID(grid_.get());
3,155,576✔
1119
        face_local_flags[f] = (neighbor_partition == opensn::mpi_comm.rank());
3,155,576✔
1120
        face_locality[f] = neighbor_partition;
3,155,576✔
1121
        neighbor_cell_ptrs[f] = &grid_->cells[face.neighbor_id];
3,155,576✔
1122
      }
1123

1124
      ++f;
3,274,464✔
1125
    }
1126

1127
    max_cell_dof_count_ = std::max(max_cell_dof_count_, static_cast<unsigned int>(num_nodes));
656,367✔
1128
    min_cell_dof_count_ = std::min(min_cell_dof_count_, static_cast<unsigned int>(num_nodes));
656,367✔
1129
    cell_transport_views_.emplace_back(cell_phi_address,
1,312,734✔
1130
                                       num_nodes,
1131
                                       num_groups_,
656,367✔
1132
                                       num_moments_,
656,367✔
1133
                                       *block_id_to_xs_map_[cell.block_id],
656,367✔
1134
                                       cell_volume,
1135
                                       face_local_flags,
1136
                                       face_locality,
1137
                                       neighbor_cell_ptrs);
1138
    block_MG_counter += num_nodes * num_groups_ * num_moments_;
656,367✔
1139
  } // for local cell
656,367✔
1140
  cell_outflow_views_.clear();
1,044✔
1141
  outflow_bank_ = OutflowBank(*grid_, num_groups_);
1,044✔
1142
  cell_outflow_views_ = outflow_bank_.GetCellOutflowViews();
1,044✔
1143

1144
  // Populate grid nodal mappings
1145
  // This is used in the Flux Data Structure (FLUDS).
1146
  grid_nodal_mappings_.clear();
1,044✔
1147
  grid_nodal_mappings_.reserve(grid_->local_cells.size());
1,044✔
1148
  for (auto& cell : grid_->local_cells)
657,411✔
1149
  {
1150
    CellFaceNodalMapping cell_nodal_mapping;
656,367✔
1151
    cell_nodal_mapping.reserve(cell.faces.size());
656,367✔
1152

1153
    for (auto& face : cell.faces)
3,930,831✔
1154
    {
1155
      std::vector<short> face_node_mapping;
3,274,464✔
1156
      std::vector<short> cell_node_mapping;
3,274,464✔
1157
      int adj_face_idx = -1;
3,274,464✔
1158

1159
      if (face.has_neighbor)
3,274,464✔
1160
      {
1161
        grid_->FindAssociatedVertices(face, face_node_mapping);
3,155,576✔
1162
        grid_->FindAssociatedCellVertices(face, cell_node_mapping);
3,155,576✔
1163
        adj_face_idx = face.GetNeighborAdjacentFaceIndex(grid_.get());
3,155,576✔
1164
      }
1165

1166
      cell_nodal_mapping.emplace_back(adj_face_idx, face_node_mapping, cell_node_mapping);
3,274,464✔
1167
    }
3,274,464✔
1168

1169
    grid_nodal_mappings_.push_back(cell_nodal_mapping);
656,367✔
1170
  }
656,367✔
1171

1172
  // Get grid localized communicator set
1173
  grid_local_comm_set_ = grid_->MakeMPILocalCommunicatorSet();
1,044✔
1174

1175
  opensn::mpi_comm.barrier();
1,044✔
1176
  log.Log() << "Done with parallel arrays." << std::endl;
2,088✔
1177
}
1,044✔
1178

1179
#ifndef __OPENSN_WITH_GPU__
1180
void
1181
LBSProblem::InitializeGPUExtras()
1182
{
1183
}
1184

1185
void
1186
LBSProblem::ResetGPUCarriers()
1187
{
1188
}
1189

1190
void
1191
LBSProblem::CheckCapableDevices()
1192
{
1193
}
1194
#endif // __OPENSN_WITH_GPU__
1195

1196
std::vector<double>
1197
LBSProblem::MakeSourceMomentsFromPhi()
4✔
1198
{
1199
  CALI_CXX_MARK_SCOPE("Source/MomentsFromPhi");
4✔
1200

1201
  auto num_local_dofs = discretization_->GetNumLocalDOFs(flux_moments_uk_man_);
4✔
1202

1203
  std::vector<double> source_moments(num_local_dofs, 0.0);
4✔
1204
  for (auto& groupset : groupsets_)
8✔
1205
  {
1206
    active_set_source_function_(groupset,
4✔
1207
                                source_moments,
1208
                                phi_new_local_,
4✔
1209
                                APPLY_AGS_SCATTER_SOURCES | APPLY_WGS_SCATTER_SOURCES |
1210
                                  APPLY_AGS_FISSION_SOURCES | APPLY_WGS_FISSION_SOURCES);
4✔
1211
  }
1212

1213
  return source_moments;
4✔
1214
}
4✔
1215

1216
LBSProblem::~LBSProblem()
1,004✔
1217
{
1218
  ResetGPUCarriers();
1219
}
5,012✔
1220

1,004✔
1221
void
1222
LBSProblem::ZeroPhi()
360✔
1223
{
1224
  std::fill(phi_old_local_.begin(), phi_old_local_.end(), 0.0);
360✔
1225
  std::fill(phi_new_local_.begin(), phi_new_local_.end(), 0.0);
360✔
1226
}
360✔
1227

1228
void
1229
LBSProblem::CopyPhiNewToOld()
300✔
1230
{
1231
  assert(phi_old_local_.size() == phi_new_local_.size() && "Phi vectors size mismatch.");
300✔
1232
  phi_old_local_ = phi_new_local_;
300✔
1233
}
300✔
1234

1235
void
1236
LBSProblem::SetPhiOldFrom(const std::vector<double>& phi_old)
2,760✔
1237
{
1238
  assert(phi_old.size() == phi_old_local_.size() && "SetPhiOldFrom size mismatch.");
2,760✔
1239
  phi_old_local_ = phi_old;
2,760✔
1240
}
2,760✔
1241

1242
void
1243
LBSProblem::SetPhiNewFrom(const std::vector<double>& phi_new)
×
1244
{
1245
  assert(phi_new.size() == phi_new_local_.size() && "SetPhiNewFrom size mismatch.");
×
1246
  phi_new_local_ = phi_new;
×
1247
}
×
1248

1249
void
1250
LBSProblem::ScalePhiOld(double factor)
41✔
1251
{
1252
  for (auto& value : phi_old_local_)
3,594,261✔
1253
    value *= factor;
3,594,220✔
1254
}
41✔
1255

1256
void
1257
LBSProblem::ScalePhiNew(double factor)
49✔
1258
{
1259
  for (auto& value : phi_new_local_)
3,762,269✔
1260
    value *= factor;
3,762,220✔
1261
}
49✔
1262

1263
void
1264
LBSProblem::ZeroQMoments()
83,954✔
1265
{
1266
  assert(q_moments_local_.size() == phi_old_local_.size() && "Q moments/Phi size mismatch.");
83,954✔
1267
  std::fill(q_moments_local_.begin(), q_moments_local_.end(), 0.0);
83,954✔
1268
}
83,954✔
1269

1270
void
1271
LBSProblem::ScaleQMoments(double factor)
9,587✔
1272
{
1273
  for (auto& value : q_moments_local_)
702,843,943✔
1274
    value *= factor;
702,834,356✔
1275
}
9,587✔
1276

1277
void
1278
LBSProblem::SetQMomentsFrom(const std::vector<double>& q_moments)
27,993✔
1279
{
1280
  assert(q_moments.size() == q_moments_local_.size() && "SetQMomentsFrom size mismatch.");
27,993✔
1281
  q_moments_local_ = q_moments;
27,993✔
1282
}
27,993✔
1283

1284
void
1285
LBSProblem::ScalePrecursors(double factor)
123✔
1286
{
1287
  for (auto& value : precursor_new_local_)
1,241✔
1288
    value *= factor;
1,118✔
1289
}
123✔
1290

1291
void
1292
LBSProblem::ZeroPrecursors()
2,460✔
1293
{
1294
  std::fill(precursor_new_local_.begin(), precursor_new_local_.end(), 0.0);
2,460✔
1295
}
2,460✔
1296

1297
void
1298
LBSProblem::ZeroExtSrcMoments()
×
1299
{
1300
  std::fill(ext_src_moments_local_.begin(), ext_src_moments_local_.end(), 0.0);
×
1301
}
×
1302

1303
void
1304
LBSProblem::ScaleExtSrcMoments(double factor)
×
1305
{
1306
  for (auto& value : ext_src_moments_local_)
×
1307
    value *= factor;
×
1308
}
×
1309

1310
void
1311
LBSProblem::SetAdjoint(bool adjoint)
24✔
1312
{
1313
  if (adjoint)
24✔
1314
    if (IsTimeDependent())
20✔
1315
      OpenSnInvalidArgument(GetName() + ": Time-dependent adjoint problems are not supported.");
×
1316

1317
  const bool mode_changed = (adjoint != options_.adjoint);
24✔
1318
  if (not mode_changed)
24✔
1319
    return;
1320

1321
  options_.adjoint = adjoint;
24✔
1322

1323
  // Reinitialize materials to obtain the proper forward/adjoint cross sections.
1324
  InitializeMaterials();
24✔
1325

1326
  // Forward and adjoint sources are fundamentally different.
1327
  point_sources_.clear();
24✔
1328
  volumetric_sources_.clear();
24✔
1329
  ClearBoundaries();
24✔
1330

1331
  // Reset all solution vectors.
1332
  ZeroPhi();
24✔
1333
  ResetDerivedSolutionVectors();
24✔
1334
  ZeroPrecursors();
24✔
1335
}
1336

1337
void
1338
LBSProblem::SetForward()
×
1339
{
1340
  SetAdjoint(false);
×
1341
}
×
1342

1343
bool
1344
LBSProblem::IsAdjoint() const
×
1345
{
1346
  return options_.adjoint;
×
1347
}
1348

1349
} // namespace opensn
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

© 2026 Coveralls, Inc