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

Open-Sn / opensn / 24549169077

15 Apr 2026 06:32PM UTC coverage: 74.822%. Remained the same
24549169077

push

github

web-flow
Merge pull request #1023 from wdhawkins/lbs_do_split

Moving discrete-ordinates code from LBSProblem to DiscreteOrdinatesProblem

58 of 74 new or added lines in 8 files covered. (78.38%)

296 existing lines in 8 files now uncovered.

21206 of 28342 relevant lines covered (74.82%)

66139191.83 hits per line

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

84.84
/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 "modules/linear_boltzmann_solvers/lbs_problem/io/lbs_problem_io.h"
8
#include "framework/field_functions/field_function_grid_based.h"
9
#include "framework/materials/multi_group_xs/multi_group_xs.h"
10
#include "framework/mesh/mesh_continuum/mesh_continuum.h"
11
#include "framework/utils/hdf_utils.h"
12
#include "framework/object_factory.h"
13
#include "framework/logging/log.h"
14
#include "framework/runtime.h"
15
#include "framework/data_types/allowable_range.h"
16
#include "framework/utils/error.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()
663✔
35
{
36
  InputParameters params = Problem::GetInputParameters();
663✔
37

38
  params.ChangeExistingParamToOptional("name", "LBSProblem");
1,326✔
39

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

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

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

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

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

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

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

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

65
  return params;
663✔
UNCOV
66
}
×
67

68
LBSProblem::LBSProblem(const InputParameters& params)
663✔
69
  : Problem(params),
70
    num_groups_(params.GetParamValue<unsigned int>("num_groups")),
663✔
71
    grid_(params.GetSharedPtrParam<MeshContinuum>("mesh")),
663✔
72
    use_gpus_(params.GetParamValue<bool>("use_gpus"))
1,989✔
73
{
74
  // Check system for GPU acceleration
75
  if (use_gpus_)
663✔
76
  {
77
#ifdef __OPENSN_WITH_GPU__
78
    CheckCapableDevices();
52✔
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"))
663✔
87
  {
88
    auto options_params = LBSProblem::GetOptionsBlock();
450✔
89
    options_params.AssignParameters(params.GetParam("options"));
452✔
90
    ParseOptions(options_params);
448✔
91
  }
450✔
92

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

98
  InitializeGroupsets(params);
661✔
99
  InitializeSources(params);
661✔
100
  InitializeXSMap(params);
661✔
101
  InitializeMaterials();
661✔
102
}
679✔
103

104
const LBSOptions&
105
LBSProblem::GetOptions() const
1,264,921,847✔
106
{
107
  return options_;
1,264,921,847✔
108
}
109

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

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

122
void
123
LBSProblem::SetTimeStep(double dt)
1,896✔
124
{
125
  OpenSnInvalidArgumentIf(dt <= 0.0, GetName() + ": dt must be greater than zero.");
1,896✔
126
  dt_ = dt;
1,896✔
127
}
1,896✔
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)
356✔
137
{
138
  OpenSnInvalidArgumentIf(theta <= 0.0 or theta > 1.0,
356✔
139
                          GetName() + ": theta must be in (0.0, 1.0].");
140
  theta_ = theta;
356✔
141
}
356✔
142

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

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

155
void
UNCOV
156
LBSProblem::SetTimeDependentMode()
×
157
{
UNCOV
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.
UNCOV
165
}
×
166

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

285
const BlockID2XSMap&
286
LBSProblem::GetBlockID2XSMap() const
2,060,121✔
287
{
288
  return block_id_to_xs_map_;
2,060,121✔
289
}
290

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

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

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

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

318
          const unsigned int new_num_precursors =
13,656✔
319
            block_id_to_xs_map_.at(cell.block_id)->GetNumPrecursors();
13,656✔
320
          const unsigned int num_precursors_to_copy =
13,656✔
321
            std::min(old_num_precursors, new_num_precursors);
13,656✔
322

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

330
      precursor_new_local_ = std::move(remapped_precursors);
136✔
331
    }
136✔
332
    else
333
      precursor_new_local_.clear();
48✔
334
  }
335

336
  ResetGPUCarriers();
184✔
337
  InitializeGPUExtras();
184✔
338
}
184✔
339

340
std::shared_ptr<MeshContinuum>
341
LBSProblem::GetGrid() const
411,084✔
342
{
343
  return grid_;
411,084✔
344
}
345

346
const SpatialDiscretization&
347
LBSProblem::GetSpatialDiscretization() const
121,287✔
348
{
349
  return *discretization_;
121,287✔
350
}
351

352
const std::vector<UnitCellMatrices>&
353
LBSProblem::GetUnitCellMatrices() const
2,063,264✔
354
{
355
  return unit_cell_matrices_;
2,063,264✔
356
}
357

358
const std::map<uint64_t, UnitCellMatrices>&
359
LBSProblem::GetUnitGhostCellMatrices() const
16✔
360
{
361
  return unit_ghost_cell_matrices_;
16✔
362
}
363

364
std::vector<CellLBSView>&
365
LBSProblem::GetCellTransportViews()
188,536✔
366
{
367
  return cell_transport_views_;
188,536✔
368
}
369

370
const std::vector<CellLBSView>&
371
LBSProblem::GetCellTransportViews() const
288,296✔
372
{
373
  return cell_transport_views_;
288,296✔
374
}
375

376
const UnknownManager&
377
LBSProblem::GetUnknownManager() const
26,762✔
378
{
379
  return flux_moments_uk_man_;
26,762✔
380
}
381

382
size_t
383
LBSProblem::GetLocalNodeCount() const
3,402✔
384
{
385
  return local_node_count_;
3,402✔
386
}
387

388
size_t
389
LBSProblem::GetGlobalNodeCount() const
2,762✔
390
{
391
  return global_node_count_;
2,762✔
392
}
393

394
std::vector<double>&
395
LBSProblem::GetQMomentsLocal()
96,885✔
396
{
397
  return q_moments_local_;
96,885✔
398
}
399

400
const std::vector<double>&
UNCOV
401
LBSProblem::GetQMomentsLocal() const
×
402
{
UNCOV
403
  return q_moments_local_;
×
404
}
405

406
std::vector<double>&
407
LBSProblem::GetExtSrcMomentsLocal()
4✔
408
{
409
  return ext_src_moments_local_;
4✔
410
}
411

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

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

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

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

438
std::vector<double>&
439
LBSProblem::GetPhiOldLocal()
197,216✔
440
{
441
  return phi_old_local_;
197,216✔
442
}
443

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

450
std::vector<double>&
451
LBSProblem::GetPhiNewLocal()
142,539✔
452
{
453
  return phi_new_local_;
142,539✔
454
}
455

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

462
std::vector<double>&
463
LBSProblem::GetPrecursorsNewLocal()
5,433✔
464
{
465
  return precursor_new_local_;
5,433✔
466
}
467

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

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

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

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

UNCOV
493
  return {num_local_phi_dofs, num_global_phi_dofs};
×
494
}
495

496
InputParameters
497
LBSProblem::GetOptionsBlock()
898✔
498
{
499
  InputParameters params;
898✔
500

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

569
  return params;
898✔
UNCOV
570
}
×
571

572
InputParameters
573
LBSProblem::GetXSMapEntryBlock()
1,102✔
574
{
575
  InputParameters params;
1,102✔
576
  params.SetGeneralDescription("Set the cross-section map for the solver.");
2,204✔
577
  params.AddRequiredParameterArray("block_ids", "Mesh block IDs");
2,204✔
578
  params.AddRequiredParameter<std::shared_ptr<MultiGroupXS>>("xs", "Cross-section object");
2,204✔
579
  return params;
1,102✔
UNCOV
580
}
×
581

582
void
583
LBSProblem::ParseOptions(const InputParameters& input)
448✔
584
{
585
  auto params = LBSProblem::GetOptionsBlock();
448✔
586
  params.AssignParameters(input);
448✔
587
  const auto& params_at_assignment = input.GetParametersAtAssignment();
448✔
588
  const auto& specified_params = params_at_assignment.GetNumParameters() > 0
448✔
589
                                   ? params_at_assignment
448✔
590
                                   : static_cast<const ParameterBlock&>(input);
448✔
591

592
  using OptionSetter = std::function<void(const ParameterBlock&)>;
448✔
593
  const std::unordered_map<std::string, OptionSetter> option_setters = {
448✔
594
    {"max_mpi_message_size",
UNCOV
595
     [this](const ParameterBlock& spec) { options_.max_mpi_message_size = spec.GetValue<int>(); }},
×
596
    {"restart_writes_enabled",
597
     [this](const ParameterBlock& spec)
900✔
598
     { options_.restart_writes_enabled = spec.GetValue<bool>(); }},
4✔
599
    {"write_delayed_psi_to_restart",
600
     [this](const ParameterBlock& spec)
896✔
UNCOV
601
     { options_.write_delayed_psi_to_restart = spec.GetValue<bool>(); }},
×
602
    {"read_restart_path",
603
     [this](const ParameterBlock& spec)
912✔
604
     { options_.read_restart_path = BuildRestartPath(spec.GetValue<std::string>()); }},
32✔
605
    {"write_restart_path",
606
     [this](const ParameterBlock& spec)
900✔
607
     { options_.write_restart_path = BuildRestartPath(spec.GetValue<std::string>()); }},
8✔
608
    {"write_restart_time_interval",
609
     [this](const ParameterBlock& spec)
896✔
UNCOV
610
     { options_.write_restart_time_interval = std::chrono::seconds(spec.GetValue<int>()); }},
×
611
    {"use_precursors",
612
     [this](const ParameterBlock& spec) { options_.use_precursors = spec.GetValue<bool>(); }},
144✔
613
    {"use_source_moments",
614
     [this](const ParameterBlock& spec) { options_.use_src_moments = spec.GetValue<bool>(); }},
4✔
615
    {"save_angular_flux",
616
     [this](const ParameterBlock& spec) { options_.save_angular_flux = spec.GetValue<bool>(); }},
278✔
617
    {"verbose_inner_iterations",
618
     [this](const ParameterBlock& spec)
1,142✔
619
     { options_.verbose_inner_iterations = spec.GetValue<bool>(); }},
246✔
620
    {"max_ags_iterations",
621
     [this](const ParameterBlock& spec) { options_.max_ags_iterations = spec.GetValue<int>(); }},
68✔
622
    {"ags_tolerance",
623
     [this](const ParameterBlock& spec) { options_.ags_tolerance = spec.GetValue<double>(); }},
8✔
624
    {"ags_convergence_check",
625
     [this](const ParameterBlock& spec)
896✔
UNCOV
626
     { options_.ags_pointwise_convergence = (spec.GetValue<std::string>() == "pointwise"); }},
×
627
    {"verbose_ags_iterations",
628
     [this](const ParameterBlock& spec)
1,048✔
629
     { options_.verbose_ags_iterations = spec.GetValue<bool>(); }},
152✔
630
    {"verbose_outer_iterations",
631
     [this](const ParameterBlock& spec)
1,118✔
632
     { options_.verbose_outer_iterations = spec.GetValue<bool>(); }},
222✔
633
    {"power_default_kappa",
634
     [this](const ParameterBlock& spec)
909✔
635
     { options_.power_default_kappa = spec.GetValue<double>(); }},
13✔
636
    {"field_function_prefix_option",
637
     [this](const ParameterBlock& spec)
896✔
UNCOV
638
     { options_.field_function_prefix_option = spec.GetValue<std::string>(); }},
×
639
    {"field_function_prefix",
640
     [this](const ParameterBlock& spec)
896✔
UNCOV
641
     { options_.field_function_prefix = spec.GetValue<std::string>(); }},
×
642
    {"adjoint", [this](const ParameterBlock& spec) { options_.adjoint = spec.GetValue<bool>(); }},
16✔
643
  };
9,408✔
644

645
  for (const auto& spec : specified_params.GetParameters())
1,623✔
646
  {
647
    const auto setter_it = option_setters.find(spec.GetName());
2,350✔
648
    if (setter_it != option_setters.end())
1,175✔
649
      setter_it->second(spec);
1,175✔
650
  }
651

652
  OpenSnInvalidArgumentIf(options_.write_restart_time_interval > std::chrono::seconds(0) and
448✔
653
                            not options_.restart_writes_enabled,
654
                          GetName() + ": `write_restart_time_interval>0` requires "
655
                                      "`restart_writes_enabled=true`.");
656

657
  OpenSnInvalidArgumentIf(options_.write_restart_time_interval > std::chrono::seconds(0) and
448✔
658
                            options_.write_restart_time_interval < std::chrono::seconds(30),
659
                          GetName() + ": `write_restart_time_interval` must be 0 (disabled) "
660
                                      "or at least 30 seconds.");
661

662
  OpenSnInvalidArgumentIf(options_.restart_writes_enabled and options_.write_restart_path.empty(),
448✔
663
                          GetName() + ": `restart_writes_enabled=true` requires a non-empty "
664
                                      "`write_restart_path`.");
665

666
  OpenSnInvalidArgumentIf(not options_.field_function_prefix.empty() and
448✔
667
                            options_.field_function_prefix_option != "prefix",
668
                          GetName() + ": non-empty `field_function_prefix` requires "
669
                                      "`field_function_prefix_option=\"prefix\"`.");
670

671
  if (options_.restart_writes_enabled)
448✔
672
  {
673
    const auto dir = options_.write_restart_path.parent_path();
4✔
674

675
    // Create restart directory if necessary.
676
    // If dir is empty, write path resolves relative to the working directory.
677
    if ((not dir.empty()) and opensn::mpi_comm.rank() == 0)
8✔
678
    {
679
      if (not std::filesystem::exists(dir))
1✔
680
      {
UNCOV
681
        OpenSnLogicalErrorIf(not std::filesystem::create_directories(dir),
×
682
                             GetName() + ": Failed to create restart directory " + dir.string());
683
      }
684
      else
685
        OpenSnLogicalErrorIf(not std::filesystem::is_directory(dir),
1✔
686
                             GetName() + ": Restart path exists but is not a directory " +
687
                               dir.string());
688
    }
689
    opensn::mpi_comm.barrier();
4✔
690
    UpdateRestartWriteTime();
4✔
691
  }
4✔
692
}
896✔
693

694
std::filesystem::path
695
LBSProblem::BuildRestartPath(const std::string& path_stem)
20✔
696
{
697
  if (path_stem.empty())
20✔
UNCOV
698
    return {};
×
699

700
  auto path = std::filesystem::path(path_stem);
20✔
701
  path += std::to_string(opensn::mpi_comm.rank()) + ".restart.h5";
60✔
702
  return path;
20✔
703
}
20✔
704

705
bool
706
LBSProblem::ReadRestartData(const RestartDataHook& extra_reader)
16✔
707
{
708
  return LBSSolverIO::ReadRestartData(*this, extra_reader);
16✔
709
}
710

711
bool
712
LBSProblem::WriteRestartData(const RestartDataHook& extra_writer)
4✔
713
{
714
  return LBSSolverIO::WriteRestartData(*this, extra_writer);
4✔
715
}
716

717
bool
UNCOV
718
LBSProblem::ReadProblemRestartData(hid_t /*file_id*/)
×
719
{
UNCOV
720
  return true;
×
721
}
722

723
bool
UNCOV
724
LBSProblem::WriteProblemRestartData(hid_t /*file_id*/) const
×
725
{
UNCOV
726
  return true;
×
727
}
728

729
void
730
LBSProblem::BuildRuntime()
661✔
731
{
732
  CALI_CXX_MARK_SCOPE("LBSProblem::BuildRuntime");
661✔
733
  if (initialized_)
661✔
UNCOV
734
    return;
×
735

736
  PrintSimHeader();
661✔
737
  mpi_comm.barrier();
661✔
738

739
  InitializeRuntimeCore();
661✔
740
  ValidateRuntimeModeConfiguration();
661✔
741
  InitializeSources();
661✔
742

743
  initialized_ = true;
661✔
744
}
661✔
745

746
void
747
LBSProblem::InitializeRuntimeCore()
661✔
748
{
749
  InitializeSpatialDiscretization();
661✔
750
  InitializeParrays();
661✔
751
  InitializeBoundaries();
661✔
752
  InitializeGPUExtras();
661✔
753
}
661✔
754

755
void
756
LBSProblem::ValidateRuntimeModeConfiguration() const
661✔
757
{
758
  if (options_.adjoint)
661✔
759
    if (IsTimeDependent())
16✔
UNCOV
760
      OpenSnInvalidArgument(GetName() + ": Time-dependent adjoint problems are not supported.");
×
761
}
661✔
762

763
void
764
LBSProblem::InitializeSources()
661✔
765
{
766
  // Initialize point sources
767
  for (auto& point_source : point_sources_)
669✔
768
    point_source->Initialize(*this);
8✔
769

770
  // Initialize volumetric sources
771
  for (auto& volumetric_source : volumetric_sources_)
1,230✔
772
    volumetric_source->Initialize(*this);
569✔
773
}
661✔
774

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

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

UNCOV
804
    log.Log() << outstr.str() << '\n';
×
UNCOV
805
  }
×
UNCOV
806
}
×
807

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

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

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

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

854
void
855
LBSProblem::InitializeXSMap(const InputParameters& params)
661✔
856
{
857
  // Build XS map
858
  const auto& xs_array = params.GetParam("xs_map");
661✔
859
  const size_t num_xs = xs_array.GetNumParameters();
661✔
860
  for (size_t i = 0; i < num_xs; ++i)
1,579✔
861
  {
862
    const auto& item_params = xs_array.GetParam(i);
918✔
863
    InputParameters xs_entry_pars = GetXSMapEntryBlock();
918✔
864
    xs_entry_pars.AssignParameters(item_params);
918✔
865

866
    const auto& block_ids_param = xs_entry_pars.GetParam("block_ids");
918✔
867
    block_ids_param.RequireBlockTypeIs(ParameterBlockType::ARRAY);
918✔
868
    const auto& block_ids = block_ids_param.GetVectorValue<unsigned int>();
918✔
869
    auto xs = xs_entry_pars.GetSharedPtrParam<MultiGroupXS>("xs");
918✔
870
    for (const auto& block_id : block_ids)
1,972✔
871
      block_id_to_xs_map_[block_id] = xs;
1,054✔
872
  }
918✔
873
}
661✔
874

875
void
876
LBSProblem::InitializeMaterials()
869✔
877
{
878
  CALI_CXX_MARK_SCOPE("LBSProblem::InitializeMaterials");
869✔
879

880
  log.Log0Verbose1() << "Initializing Materials";
1,738✔
881

882
  // Create set of material ids locally relevant
883
  int invalid_mat_cell_count = 0;
869✔
884
  std::set<unsigned int> unique_block_ids;
869✔
885
  for (auto& cell : grid_->local_cells)
665,925✔
886
  {
887
    unique_block_ids.insert(cell.block_id);
665,056✔
888
    if (cell.block_id == std::numeric_limits<unsigned int>::max() or
665,056✔
889
        (block_id_to_xs_map_.find(cell.block_id) == block_id_to_xs_map_.end()))
665,056✔
UNCOV
890
      ++invalid_mat_cell_count;
×
891
  }
892
  const auto& ghost_cell_ids = grid_->cells.GetGhostGlobalIDs();
869✔
893
  for (uint64_t cell_id : ghost_cell_ids)
106,236✔
894
  {
895
    const auto& cell = grid_->cells[cell_id];
105,367✔
896
    unique_block_ids.insert(cell.block_id);
105,367✔
897
    if (cell.block_id == std::numeric_limits<unsigned int>::max() or
105,367✔
898
        (block_id_to_xs_map_.find(cell.block_id) == block_id_to_xs_map_.end()))
105,367✔
UNCOV
899
      ++invalid_mat_cell_count;
×
900
  }
901
  OpenSnLogicalErrorIf(invalid_mat_cell_count > 0,
869✔
902
                       std::to_string(invalid_mat_cell_count) +
903
                         " cells encountered with an invalid material id.");
904

905
  // Get ready for processing
906
  for (const auto& [blk_id, mat] : block_id_to_xs_map_)
2,155✔
907
  {
908
    mat->SetAdjointMode(options_.adjoint);
1,286✔
909

910
    OpenSnLogicalErrorIf(mat->GetNumGroups() < num_groups_,
1,286✔
911
                         "Cross-sections for block \"" + std::to_string(blk_id) +
912
                           "\" have fewer groups (" + std::to_string(mat->GetNumGroups()) +
913
                           ") than the simulation (" + std::to_string(num_groups_) + "). " +
914
                           "Cross-sections must have at least as many groups as the simulation.");
915
  }
916

917
  // Initialize precursor properties
918
  num_precursors_ = 0;
869✔
919
  max_precursors_per_material_ = 0;
869✔
920
  for (const auto& mat_id_xs : block_id_to_xs_map_)
2,155✔
921
  {
922
    const auto& xs = mat_id_xs.second;
1,286✔
923
    num_precursors_ += xs->GetNumPrecursors();
1,286✔
924
    max_precursors_per_material_ = std::max(xs->GetNumPrecursors(), max_precursors_per_material_);
1,286✔
925
  }
926

927
  const bool has_fissionable_precursors =
869✔
928
    std::any_of(block_id_to_xs_map_.begin(),
869✔
929
                block_id_to_xs_map_.end(),
930
                [](const auto& mat_id_xs)
1,286✔
931
                {
932
                  const auto& xs = mat_id_xs.second;
1,286✔
933
                  return xs->IsFissionable() and xs->GetNumPrecursors() > 0;
1,286✔
934
                });
935

936
  const bool has_any_precursor_data =
869✔
937
    std::any_of(block_id_to_xs_map_.begin(),
869✔
938
                block_id_to_xs_map_.end(),
939
                [](const auto& mat_id_xs) { return mat_id_xs.second->GetNumPrecursors() > 0; });
1,286✔
940

941
  if (options_.use_precursors and not has_any_precursor_data)
869✔
942
  {
943
    log.Log0Warning() << GetName()
1,668✔
944
                      << ": options.use_precursors is enabled, but no precursor data was found "
945
                         "in the active cross-section map. Running without delayed-neutron "
946
                         "precursor coupling.";
556✔
947
  }
948

949
  // check compatibility when at least one fissionable material has delayed-neutron data
950
  if (options_.use_precursors and has_fissionable_precursors)
869✔
951
  {
952
    for (const auto& [mat_id, xs] : block_id_to_xs_map_)
378✔
953
    {
954
      OpenSnInvalidArgumentIf(xs->IsFissionable() and xs->GetNumPrecursors() == 0,
189✔
955
                              GetName() + ": incompatible cross-section data for material id " +
956
                                std::to_string(mat_id) +
957
                                ". When options.use_precursors=true and "
958
                                "delayed-neutron precursor data is present for one fissionable "
959
                                "material, it must be present for all fissionable materials.");
960
    }
961
  }
962

963
  // Update transport views if available
964
  if (grid_->local_cells.size() == cell_transport_views_.size())
869✔
965
    for (const auto& cell : grid_->local_cells)
28,108✔
966
    {
967
      const auto& xs_ptr = block_id_to_xs_map_[cell.block_id];
27,900✔
968
      auto& transport_view = cell_transport_views_[cell.local_id];
27,900✔
969
      transport_view.ReassignXS(*xs_ptr);
27,900✔
970
    }
971

972
  mpi_comm.barrier();
869✔
973
}
869✔
974

975
void
976
LBSProblem::InitializeSpatialDiscretization()
581✔
977
{
978
  CALI_CXX_MARK_SCOPE("LBSProblem::InitializeSpatialDiscretization");
581✔
979

980
  OpenSnLogicalErrorIf(not discretization_,
581✔
981
                       GetName() + ": Missing spatial discretization. Construct the problem "
982
                                   "through its factory Create(...) entry point.");
983
  log.Log() << "Initializing spatial discretization metadata.\n";
1,162✔
984

985
  ComputeUnitIntegrals();
581✔
986
}
581✔
987

988
void
989
LBSProblem::ComputeUnitIntegrals()
661✔
990
{
991
  CALI_CXX_MARK_SCOPE("LBSProblem::ComputeUnitIntegrals");
661✔
992

993
  log.Log() << "Computing unit integrals.\n";
1,322✔
994
  const auto& sdm = *discretization_;
661✔
995

996
  const size_t num_local_cells = grid_->local_cells.size();
661✔
997
  unit_cell_matrices_.resize(num_local_cells);
661✔
998

999
  for (const auto& cell : grid_->local_cells)
637,817✔
1000
    unit_cell_matrices_[cell.local_id] =
637,156✔
1001
      ComputeUnitCellIntegrals(sdm, cell, grid_->GetCoordinateSystem());
637,156✔
1002

1003
  const auto ghost_ids = grid_->cells.GetGhostGlobalIDs();
661✔
1004
  for (auto ghost_id : ghost_ids)
97,168✔
1005
    unit_ghost_cell_matrices_[ghost_id] =
96,507✔
1006
      ComputeUnitCellIntegrals(sdm, grid_->cells[ghost_id], grid_->GetCoordinateSystem());
193,014✔
1007

1008
  // Assessing global unit cell matrix storage
1009
  std::array<size_t, 2> num_local_ucms = {unit_cell_matrices_.size(),
661✔
1010
                                          unit_ghost_cell_matrices_.size()};
661✔
1011
  std::array<size_t, 2> num_global_ucms = {0, 0};
661✔
1012

1013
  mpi_comm.all_reduce(num_local_ucms.data(), 2, num_global_ucms.data(), mpi::op::sum<size_t>());
661✔
1014

1015
  opensn::mpi_comm.barrier();
661✔
1016
  log.Log() << "Ghost cell unit cell-matrix ratio: "
661✔
1017
            << (double)num_global_ucms[1] * 100 / (double)num_global_ucms[0] << "%";
1,322✔
1018
  log.Log() << "Cell matrices computed.";
1,322✔
1019
}
661✔
1020

1021
void
1022
LBSProblem::InitializeParrays()
661✔
1023
{
1024
  CALI_CXX_MARK_SCOPE("LBSProblem::InitializeParrays");
661✔
1025

1026
  log.Log() << "Initializing parallel arrays."
1,322✔
1027
            << " G=" << num_groups_ << " M=" << num_moments_ << std::endl;
661✔
1028

1029
  // Initialize unknown
1030
  // structure
1031
  flux_moments_uk_man_.unknowns.clear();
661✔
1032
  for (unsigned int m = 0; m < num_moments_; ++m)
2,235✔
1033
  {
1034
    flux_moments_uk_man_.AddUnknown(UnknownType::VECTOR_N, num_groups_);
1,574✔
1035
    flux_moments_uk_man_.unknowns.back().name = "m" + std::to_string(m);
1,574✔
1036
  }
1037

1038
  // Compute local # of dof
1039
  local_node_count_ = discretization_->GetNumLocalNodes();
661✔
1040
  global_node_count_ = discretization_->GetNumGlobalNodes();
661✔
1041

1042
  // Compute num of unknowns
1043
  size_t local_unknown_count = local_node_count_ * num_groups_ * num_moments_;
661✔
1044

1045
  log.LogAllVerbose1() << "LBS Number of phi unknowns: " << local_unknown_count;
1,322✔
1046

1047
  // Size local vectors
1048
  q_moments_local_.assign(local_unknown_count, 0.0);
661✔
1049
  phi_old_local_.assign(local_unknown_count, 0.0);
661✔
1050
  phi_new_local_.assign(local_unknown_count, 0.0);
661✔
1051

1052
  // Setup precursor vector
1053
  if (options_.use_precursors)
661✔
1054
  {
1055
    size_t num_precursor_dofs = grid_->local_cells.size() * max_precursors_per_material_;
585✔
1056
    precursor_new_local_.assign(num_precursor_dofs, 0.0);
585✔
1057
  }
1058

1059
  // Initialize transport views
1060
  // Transport views act as a data structure to store information
1061
  // related to the transport simulation. The most prominent function
1062
  // here is that it holds the means to know where a given cell's
1063
  // transport quantities are located in the unknown vectors (i.e. phi)
1064
  //
1065
  // Also, for a given cell, within a given sweep chunk,
1066
  // we need to solve a matrix which square size is the
1067
  // amount of nodes on the cell. max_cell_dof_count is
1068
  // initialized here.
1069
  //
1070
  size_t block_MG_counter = 0; // Counts the strides of moment and group
661✔
1071

1072
  const Vector3 ihat(1.0, 0.0, 0.0);
661✔
1073
  const Vector3 jhat(0.0, 1.0, 0.0);
661✔
1074
  const Vector3 khat(0.0, 0.0, 1.0);
661✔
1075

1076
  min_cell_dof_count_ = std::numeric_limits<unsigned int>::max();
661✔
1077
  max_cell_dof_count_ = 0;
661✔
1078
  cell_transport_views_.clear();
661✔
1079
  cell_transport_views_.reserve(grid_->local_cells.size());
661✔
1080
  for (auto& cell : grid_->local_cells)
637,817✔
1081
  {
1082
    size_t num_nodes = discretization_->GetCellNumNodes(cell);
637,156✔
1083

1084
    // compute cell volumes
1085
    double cell_volume = 0.0;
637,156✔
1086
    const auto& IntV_shapeI = unit_cell_matrices_[cell.local_id].intV_shapeI;
637,156✔
1087
    for (size_t i = 0; i < num_nodes; ++i)
4,518,856✔
1088
      cell_volume += IntV_shapeI(i);
3,881,700✔
1089

1090
    size_t cell_phi_address = block_MG_counter;
637,156✔
1091

1092
    const size_t num_faces = cell.faces.size();
637,156✔
1093
    std::vector<bool> face_local_flags(num_faces, true);
637,156✔
1094
    std::vector<int> face_locality(num_faces, opensn::mpi_comm.rank());
637,156✔
1095
    std::vector<const Cell*> neighbor_cell_ptrs(num_faces, nullptr);
637,156✔
1096
    bool cell_on_boundary = false;
637,156✔
1097
    int f = 0;
637,156✔
1098
    for (auto& face : cell.faces)
3,837,020✔
1099
    {
1100
      if (not face.has_neighbor)
3,199,864✔
1101
      {
1102
        cell_on_boundary = true;
113,146✔
1103
        face_local_flags[f] = false;
113,146✔
1104
        face_locality[f] = -1;
113,146✔
1105
      } // if bndry
1106
      else
1107
      {
1108
        const int neighbor_partition = face.GetNeighborPartitionID(grid_.get());
3,086,718✔
1109
        face_local_flags[f] = (neighbor_partition == opensn::mpi_comm.rank());
3,086,718✔
1110
        face_locality[f] = neighbor_partition;
3,086,718✔
1111
        neighbor_cell_ptrs[f] = &grid_->cells[face.neighbor_id];
3,086,718✔
1112
      }
1113

1114
      ++f;
3,199,864✔
1115
    } // for f
1116

1117
    max_cell_dof_count_ = std::max(max_cell_dof_count_, static_cast<unsigned int>(num_nodes));
637,156✔
1118
    min_cell_dof_count_ = std::min(min_cell_dof_count_, static_cast<unsigned int>(num_nodes));
637,156✔
1119
    cell_transport_views_.emplace_back(cell_phi_address,
1,274,312✔
1120
                                       num_nodes,
1121
                                       num_groups_,
637,156✔
1122
                                       num_moments_,
637,156✔
1123
                                       num_faces,
1124
                                       *block_id_to_xs_map_[cell.block_id],
637,156✔
1125
                                       cell_volume,
1126
                                       face_local_flags,
1127
                                       face_locality,
1128
                                       neighbor_cell_ptrs,
1129
                                       cell_on_boundary);
1130
    block_MG_counter += num_nodes * num_groups_ * num_moments_;
637,156✔
1131
  } // for local cell
637,156✔
1132

1133
  // Populate grid nodal mappings
1134
  // This is used in the Flux Data Structures (FLUDS)
1135
  grid_nodal_mappings_.clear();
661✔
1136
  grid_nodal_mappings_.reserve(grid_->local_cells.size());
661✔
1137
  for (auto& cell : grid_->local_cells)
637,817✔
1138
  {
1139
    CellFaceNodalMapping cell_nodal_mapping;
637,156✔
1140
    cell_nodal_mapping.reserve(cell.faces.size());
637,156✔
1141

1142
    for (auto& face : cell.faces)
3,837,020✔
1143
    {
1144
      std::vector<short> face_node_mapping;
3,199,864✔
1145
      std::vector<short> cell_node_mapping;
3,199,864✔
1146
      int adj_face_idx = -1;
3,199,864✔
1147

1148
      if (face.has_neighbor)
3,199,864✔
1149
      {
1150
        grid_->FindAssociatedVertices(face, face_node_mapping);
3,086,718✔
1151
        grid_->FindAssociatedCellVertices(face, cell_node_mapping);
3,086,718✔
1152
        adj_face_idx = face.GetNeighborAdjacentFaceIndex(grid_.get());
3,086,718✔
1153
      }
1154

1155
      cell_nodal_mapping.emplace_back(adj_face_idx, face_node_mapping, cell_node_mapping);
3,199,864✔
1156
    } // for f
3,199,864✔
1157

1158
    grid_nodal_mappings_.push_back(cell_nodal_mapping);
637,156✔
1159
  } // for local cell
637,156✔
1160

1161
  // Get grid localized communicator set
1162
  grid_local_comm_set_ = grid_->MakeMPILocalCommunicatorSet();
661✔
1163

1164
  opensn::mpi_comm.barrier();
661✔
1165
  log.Log() << "Done with parallel arrays." << std::endl;
1,322✔
1166
}
661✔
1167

1168
#ifndef __OPENSN_WITH_GPU__
1169
void
1170
LBSProblem::InitializeGPUExtras()
1171
{
1172
}
1173

1174
void
1175
LBSProblem::ResetGPUCarriers()
1176
{
1177
}
1178

1179
void
1180
LBSProblem::CheckCapableDevices()
1181
{
1182
}
1183
#endif // __OPENSN_WITH_GPU__
1184

1185
std::vector<double>
1186
LBSProblem::MakeSourceMomentsFromPhi()
4✔
1187
{
1188
  CALI_CXX_MARK_SCOPE("LBSProblem::MakeSourceMomentsFromPhi");
4✔
1189

1190
  size_t num_local_dofs = discretization_->GetNumLocalDOFs(flux_moments_uk_man_);
4✔
1191

1192
  std::vector<double> source_moments(num_local_dofs, 0.0);
4✔
1193
  for (auto& groupset : groupsets_)
8✔
1194
  {
1195
    active_set_source_function_(groupset,
4✔
1196
                                source_moments,
1197
                                phi_new_local_,
4✔
1198
                                APPLY_AGS_SCATTER_SOURCES | APPLY_WGS_SCATTER_SOURCES |
1199
                                  APPLY_AGS_FISSION_SOURCES | APPLY_WGS_FISSION_SOURCES);
4✔
1200
  }
1201

1202
  return source_moments;
4✔
1203
}
4✔
1204

1205
LBSProblem::~LBSProblem()
649✔
1206
{
1207
  ResetGPUCarriers();
1208
}
3,245✔
1209

649✔
1210
void
1211
LBSProblem::ZeroPhi()
84✔
1212
{
1213
  std::fill(phi_old_local_.begin(), phi_old_local_.end(), 0.0);
84✔
1214
  std::fill(phi_new_local_.begin(), phi_new_local_.end(), 0.0);
84✔
1215
}
84✔
1216

1217
void
1218
LBSProblem::CopyPhiNewToOld()
176✔
1219
{
1220
  assert(phi_old_local_.size() == phi_new_local_.size() && "Phi vectors size mismatch.");
176✔
1221
  phi_old_local_ = phi_new_local_;
176✔
1222
}
176✔
1223

1224
void
1225
LBSProblem::SetPhiOldFrom(const std::vector<double>& phi_old)
2,420✔
1226
{
1227
  assert(phi_old.size() == phi_old_local_.size() && "SetPhiOldFrom size mismatch.");
2,420✔
1228
  phi_old_local_ = phi_old;
2,420✔
1229
}
2,420✔
1230

1231
void
UNCOV
1232
LBSProblem::SetPhiNewFrom(const std::vector<double>& phi_new)
×
1233
{
UNCOV
1234
  assert(phi_new.size() == phi_new_local_.size() && "SetPhiNewFrom size mismatch.");
×
UNCOV
1235
  phi_new_local_ = phi_new;
×
UNCOV
1236
}
×
1237

1238
void
UNCOV
1239
LBSProblem::ScalePhiOld(double factor)
×
1240
{
UNCOV
1241
  for (auto& value : phi_old_local_)
×
UNCOV
1242
    value *= factor;
×
UNCOV
1243
}
×
1244

1245
void
1246
LBSProblem::ScalePhiNew(double factor)
8✔
1247
{
1248
  for (auto& value : phi_new_local_)
168,008✔
1249
    value *= factor;
168,000✔
1250
}
8✔
1251

1252
void
1253
LBSProblem::ZeroQMoments()
65,710✔
1254
{
1255
  assert(q_moments_local_.size() == phi_old_local_.size() && "Q moments/Phi size mismatch.");
65,710✔
1256
  std::fill(q_moments_local_.begin(), q_moments_local_.end(), 0.0);
65,710✔
1257
}
65,710✔
1258

1259
void
1260
LBSProblem::ScaleQMoments(double factor)
8,693✔
1261
{
1262
  for (auto& value : q_moments_local_)
570,842,649✔
1263
    value *= factor;
570,833,956✔
1264
}
8,693✔
1265

1266
void
1267
LBSProblem::SetQMomentsFrom(const std::vector<double>& q_moments)
25,436✔
1268
{
1269
  assert(q_moments.size() == q_moments_local_.size() && "SetQMomentsFrom size mismatch.");
25,436✔
1270
  q_moments_local_ = q_moments;
25,436✔
1271
}
25,436✔
1272

1273
void
1274
LBSProblem::ScalePrecursors(double factor)
122✔
1275
{
1276
  for (auto& value : precursor_new_local_)
1,240✔
1277
    value *= factor;
1,118✔
1278
}
122✔
1279

1280
void
1281
LBSProblem::ZeroPrecursors()
2,356✔
1282
{
1283
  std::fill(precursor_new_local_.begin(), precursor_new_local_.end(), 0.0);
2,356✔
1284
}
2,356✔
1285

1286
void
UNCOV
1287
LBSProblem::ZeroExtSrcMoments()
×
1288
{
UNCOV
1289
  std::fill(ext_src_moments_local_.begin(), ext_src_moments_local_.end(), 0.0);
×
UNCOV
1290
}
×
1291

1292
void
UNCOV
1293
LBSProblem::ScaleExtSrcMoments(double factor)
×
1294
{
UNCOV
1295
  for (auto& value : ext_src_moments_local_)
×
UNCOV
1296
    value *= factor;
×
UNCOV
1297
}
×
1298

1299
void
1300
LBSProblem::SetAdjoint(bool adjoint)
24✔
1301
{
1302
  OpenSnLogicalErrorIf(
24✔
1303
    not initialized_, GetName() + ": Problem must be fully constructed before calling SetAdjoint.");
1304

1305
  if (adjoint)
24✔
1306
    if (IsTimeDependent())
20✔
UNCOV
1307
      OpenSnInvalidArgument(GetName() + ": Time-dependent adjoint problems are not supported.");
×
1308

1309
  const bool mode_changed = (adjoint != options_.adjoint);
24✔
1310
  if (not mode_changed)
24✔
1311
    return;
1312

1313
  options_.adjoint = adjoint;
24✔
1314

1315
  // Reinitialize materials to obtain the proper forward/adjoint cross sections.
1316
  InitializeMaterials();
24✔
1317

1318
  // Forward and adjoint sources are fundamentally different.
1319
  point_sources_.clear();
24✔
1320
  volumetric_sources_.clear();
24✔
1321
  ClearBoundaries();
24✔
1322

1323
  // Reset all solution vectors.
1324
  ZeroPhi();
24✔
1325
  ResetDerivedSolutionVectors();
24✔
1326
  ZeroPrecursors();
24✔
1327
}
1328

1329
void
UNCOV
1330
LBSProblem::SetForward()
×
1331
{
UNCOV
1332
  SetAdjoint(false);
×
UNCOV
1333
}
×
1334

1335
bool
UNCOV
1336
LBSProblem::IsAdjoint() const
×
1337
{
UNCOV
1338
  return options_.adjoint;
×
1339
}
1340

1341
} // 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