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

Open-Sn / opensn / 25716759538

12 May 2026 03:04AM UTC coverage: 75.658% (-0.03%) from 75.687%
25716759538

push

github

web-flow
Merge pull request #1063 from wdhawkins/notebook_testing

Don't require nbconvert/nbformat for non-notebook testing.

22264 of 29427 relevant lines covered (75.66%)

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

29
namespace opensn
30
{
31

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

37
  params.ChangeExistingParamToOptional("name", "LBSProblem");
2,104✔
38

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

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

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

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

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

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

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

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

64
  return params;
1,052✔
65
}
×
66

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

84
  // Initialize options
85
  if (params.IsParameterValid("options"))
1,052✔
86
  {
87
    auto options_params = LBSProblem::GetOptionsBlock();
838✔
88
    options_params.AssignParameters(params.GetParam("options"));
840✔
89
    ParseOptions(options_params);
836✔
90
  }
838✔
91

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

97
  InitializeGroupsets(params);
1,050✔
98
  InitializeSources(params);
1,050✔
99
  InitializeXSMap(params);
1,050✔
100
  InitializeMaterials();
1,050✔
101
}
1,070✔
102

103
const LBSOptions&
104
LBSProblem::GetOptions() const
1,342,858,309✔
105
{
106
  return options_;
1,342,858,309✔
107
}
108

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

364
std::vector<CellOutflowView>&
365
LBSProblem::GetCellOutflowViews()
2,324✔
366
{
367
  return cell_outflow_views_;
2,324✔
368
}
369

370
const std::vector<CellOutflowView>&
371
LBSProblem::GetCellOutflowViews() const
×
372
{
373
  return cell_outflow_views_;
×
374
}
375

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

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

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

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

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

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

412
void
413
LBSProblem::SetExtSrcMomentsFrom(const std::vector<double>& ext_src_moments)
4✔
414
{
415
  if (not phi_old_local_.empty())
4✔
416
    OpenSnLogicalErrorIf(ext_src_moments.size() != phi_old_local_.size(),
4✔
417
                         "SetExtSrcMomentsFrom size mismatch. Provided size=" +
418
                           std::to_string(ext_src_moments.size()) +
419
                           ", expected local DOFs=" + std::to_string(phi_old_local_.size()) + ".");
420

421
  if (ext_src_moments_local_.empty())
4✔
422
  {
423
    ext_src_moments_local_ = ext_src_moments;
4✔
424
    return;
4✔
425
  }
426

427
  assert(ext_src_moments.size() == ext_src_moments_local_.size() &&
×
428
         "SetExtSrcMomentsFrom size mismatch.");
429
  ext_src_moments_local_ = ext_src_moments;
×
430
}
431

432
std::vector<double>&
433
LBSProblem::GetPhiOldLocal()
250,843✔
434
{
435
  return phi_old_local_;
250,843✔
436
}
437

438
const std::vector<double>&
439
LBSProblem::GetPhiOldLocal() const
×
440
{
441
  return phi_old_local_;
×
442
}
443

444
std::vector<double>&
445
LBSProblem::GetPhiNewLocal()
172,814✔
446
{
447
  return phi_new_local_;
172,814✔
448
}
449

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

456
std::vector<double>&
457
LBSProblem::GetPrecursorsNewLocal()
6,346✔
458
{
459
  return precursor_new_local_;
6,346✔
460
}
461

462
const std::vector<double>&
463
LBSProblem::GetPrecursorsNewLocal() const
×
464
{
465
  return precursor_new_local_;
×
466
}
467

468
SetSourceFunction
469
LBSProblem::GetActiveSetSourceFunction() const
5,994✔
470
{
471
  return active_set_source_function_;
5,994✔
472
}
473

474
void
475
LBSProblem::SetActiveSetSourceFunction(SetSourceFunction source_function)
152✔
476
{
477
  active_set_source_function_ = std::move(source_function);
152✔
478
}
152✔
479

480
std::pair<size_t, size_t>
481
LBSProblem::GetNumPhiIterativeUnknowns()
×
482
{
483
  const auto& sdm = *discretization_;
×
484
  const size_t num_local_phi_dofs = sdm.GetNumLocalDOFs(flux_moments_uk_man_);
×
485
  const size_t num_global_phi_dofs = sdm.GetNumGlobalDOFs(flux_moments_uk_man_);
×
486

487
  return {num_local_phi_dofs, num_global_phi_dofs};
×
488
}
489

490
InputParameters
491
LBSProblem::GetOptionsBlock()
1,674✔
492
{
493
  InputParameters params;
1,674✔
494

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

571
  return params;
1,674✔
572
}
×
573

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

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

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

653
  for (const auto& spec : specified_params.GetParameters())
3,351✔
654
  {
655
    const auto setter_it = option_setters.find(spec.GetName());
5,030✔
656
    if (setter_it != option_setters.end())
2,515✔
657
      setter_it->second(spec);
2,515✔
658
  }
659

660
  OpenSnInvalidArgumentIf(options_.restart.write_time_interval > std::chrono::seconds(0) and
836✔
661
                            not options_.restart.writes_enabled,
662
                          GetName() + ": `write_restart_time_interval>0` requires "
663
                                      "`restart_writes_enabled=true`.");
664

665
  OpenSnInvalidArgumentIf(options_.restart.write_time_interval > std::chrono::seconds(0) and
836✔
666
                            options_.restart.write_time_interval < std::chrono::seconds(30),
667
                          GetName() + ": `write_restart_time_interval` must be 0 (disabled) "
668
                                      "or at least 30 seconds.");
669

670
  OpenSnInvalidArgumentIf(options_.restart.writes_enabled and options_.restart.write_path.empty(),
836✔
671
                          GetName() + ": `restart_writes_enabled=true` requires a non-empty "
672
                                      "`write_restart_path`.");
673

674
  OpenSnInvalidArgumentIf(not options_.field_function_prefix.empty() and
836✔
675
                            options_.field_function_prefix_option != "prefix",
676
                          GetName() + ": non-empty `field_function_prefix` requires "
677
                                      "`field_function_prefix_option=\"prefix\"`.");
678

679
  if (options_.restart.writes_enabled)
836✔
680
  {
681
    const auto dir = options_.restart.write_path.parent_path();
36✔
682

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

702
std::filesystem::path
703
LBSProblem::BuildRestartPath(const std::string& path_stem)
84✔
704
{
705
  if (path_stem.empty())
84✔
706
    return {};
×
707

708
  auto path = std::filesystem::path(path_stem);
84✔
709
  path += std::to_string(opensn::mpi_comm.rank()) + ".restart.h5";
252✔
710
  return path;
84✔
711
}
84✔
712

713
bool
714
LBSProblem::ReadProblemRestartData(hid_t /*file_id*/,
×
715
                                   bool /*allow_transient_initialization_from_steady*/)
716
{
717
  return true;
×
718
}
719

720
bool
721
LBSProblem::WriteProblemRestartData(hid_t /*file_id*/) const
×
722
{
723
  return true;
×
724
}
725

726
void
727
LBSProblem::BuildRuntime()
1,042✔
728
{
729
  CALI_CXX_MARK_SCOPE("LBSProblem::BuildRuntime");
1,042✔
730

731
  PrintSimHeader();
1,042✔
732
  mpi_comm.barrier();
1,042✔
733

734
  InitializeRuntimeCore();
1,042✔
735
  ValidateRuntimeModeConfiguration();
1,042✔
736
  InitializeSources();
1,042✔
737
}
1,042✔
738

739
void
740
LBSProblem::InitializeRuntimeCore()
1,042✔
741
{
742
  InitializeSpatialDiscretization();
1,042✔
743
  InitializeParrays();
1,042✔
744
  InitializeBoundaries();
1,042✔
745
  InitializeGPUExtras();
1,042✔
746
}
1,042✔
747

748
void
749
LBSProblem::ValidateRuntimeModeConfiguration() const
1,042✔
750
{
751
  if (options_.adjoint)
1,042✔
752
    if (IsTimeDependent())
16✔
753
      OpenSnInvalidArgument(GetName() + ": Time-dependent adjoint problems are not supported.");
×
754
}
1,042✔
755

756
void
757
LBSProblem::InitializeSources()
1,042✔
758
{
759
  // Initialize point sources
760
  for (auto& point_source : point_sources_)
1,050✔
761
    point_source->Initialize(*this);
8✔
762

763
  // Initialize volumetric sources
764
  for (auto& volumetric_source : volumetric_sources_)
1,889✔
765
    volumetric_source->Initialize(*this);
847✔
766
}
1,042✔
767

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

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

797
    log.Log() << outstr.str() << '\n';
×
798
  }
×
799
}
×
800

801
void
802
LBSProblem::InitializeSources(const InputParameters& params)
1,050✔
803
{
804
  if (params.Has("volumetric_sources"))
1,050✔
805
  {
806
    const auto& vol_srcs = params.GetParam("volumetric_sources");
1,050✔
807
    vol_srcs.RequireBlockTypeIs(ParameterBlockType::ARRAY);
1,050✔
808
    for (const auto& src : vol_srcs)
1,897✔
809
      volumetric_sources_.push_back(src.GetValue<std::shared_ptr<VolumetricSource>>());
1,694✔
810
  }
811

812
  if (params.Has("point_sources"))
1,050✔
813
  {
814
    const auto& pt_srcs = params.GetParam("point_sources");
1,050✔
815
    pt_srcs.RequireBlockTypeIs(ParameterBlockType::ARRAY);
1,050✔
816
    for (const auto& src : pt_srcs)
1,058✔
817
      point_sources_.push_back(src.GetValue<std::shared_ptr<PointSource>>());
16✔
818
  }
819
}
1,050✔
820

821
void
822
LBSProblem::InitializeGroupsets(const InputParameters& params)
1,050✔
823
{
824
  // Initialize groups
825
  OpenSnInvalidArgumentIf(num_groups_ == 0, GetName() + ": Number of groups must be > 0.");
1,050✔
826

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

845
    if (groupsets_.back().last_group >= num_groups_)
1,171✔
846
    {
847
      std::stringstream oss;
×
848
      oss << GetName() << ": Groupset " << groupsets_.back().id << " has last group "
×
849
          << groupsets_.back().last_group << ", but the problem only has " << num_groups_
×
850
          << " groups.";
×
851
      OpenSnInvalidArgument(oss.str());
×
852
    }
×
853

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

872
void
873
LBSProblem::InitializeXSMap(const InputParameters& params)
1,050✔
874
{
875
  // Build XS map
876
  const auto& xs_array = params.GetParam("xs_map");
1,050✔
877
  const size_t num_xs = xs_array.GetNumParameters();
1,050✔
878
  for (size_t i = 0; i < num_xs; ++i)
2,365✔
879
  {
880
    const auto& item_params = xs_array.GetParam(i);
1,315✔
881
    InputParameters xs_entry_pars = GetXSMapEntryBlock();
1,315✔
882
    xs_entry_pars.AssignParameters(item_params);
1,315✔
883

884
    const auto& block_ids_param = xs_entry_pars.GetParam("block_ids");
1,315✔
885
    block_ids_param.RequireBlockTypeIs(ParameterBlockType::ARRAY);
1,315✔
886
    const auto& block_ids = block_ids_param.GetVectorValue<unsigned int>();
1,315✔
887
    auto xs = xs_entry_pars.GetSharedPtrParam<MultiGroupXS>("xs");
1,315✔
888
    for (const auto& block_id : block_ids)
2,774✔
889
      block_id_to_xs_map_[block_id] = xs;
1,459✔
890
  }
1,315✔
891
}
1,050✔
892

893
void
894
LBSProblem::InitializeMaterials()
1,258✔
895
{
896
  CALI_CXX_MARK_SCOPE("LBSProblem::InitializeMaterials");
1,258✔
897

898
  log.Log0Verbose1() << "Initializing Materials";
2,516✔
899

900
  // Create set of material ids locally relevant
901
  int invalid_mat_cell_count = 0;
1,258✔
902
  std::set<unsigned int> unique_block_ids;
1,258✔
903
  for (auto& cell : grid_->local_cells)
683,545✔
904
  {
905
    unique_block_ids.insert(cell.block_id);
682,287✔
906
    if (cell.block_id == std::numeric_limits<unsigned int>::max() or
682,287✔
907
        (block_id_to_xs_map_.find(cell.block_id) == block_id_to_xs_map_.end()))
682,287✔
908
      ++invalid_mat_cell_count;
×
909
  }
910
  const auto& ghost_cell_ids = grid_->cells.GetGhostGlobalIDs();
1,258✔
911
  for (uint64_t cell_id : ghost_cell_ids)
113,027✔
912
  {
913
    const auto& cell = grid_->cells[cell_id];
111,769✔
914
    unique_block_ids.insert(cell.block_id);
111,769✔
915
    if (cell.block_id == std::numeric_limits<unsigned int>::max() or
111,769✔
916
        (block_id_to_xs_map_.find(cell.block_id) == block_id_to_xs_map_.end()))
111,769✔
917
      ++invalid_mat_cell_count;
×
918
  }
919
  OpenSnLogicalErrorIf(invalid_mat_cell_count > 0,
1,258✔
920
                       std::to_string(invalid_mat_cell_count) +
921
                         " cells encountered with an invalid material id.");
922

923
  // Get ready for processing
924
  for (const auto& [blk_id, mat] : block_id_to_xs_map_)
2,949✔
925
  {
926
    mat->SetAdjointMode(options_.adjoint);
1,691✔
927

928
    OpenSnLogicalErrorIf(mat->GetNumGroups() < num_groups_,
1,691✔
929
                         "Cross-sections for block \"" + std::to_string(blk_id) +
930
                           "\" have fewer groups (" + std::to_string(mat->GetNumGroups()) +
931
                           ") than the simulation (" + std::to_string(num_groups_) + "). " +
932
                           "Cross-sections must have at least as many groups as the simulation.");
933
  }
934

935
  // Initialize precursor properties
936
  num_precursors_ = 0;
1,258✔
937
  max_precursors_per_material_ = 0;
1,258✔
938
  for (const auto& mat_id_xs : block_id_to_xs_map_)
2,949✔
939
  {
940
    const auto& xs = mat_id_xs.second;
1,691✔
941
    num_precursors_ += xs->GetNumPrecursors();
1,691✔
942
    max_precursors_per_material_ = std::max(xs->GetNumPrecursors(), max_precursors_per_material_);
1,691✔
943
  }
944

945
  const bool has_fissionable_precursors =
1,258✔
946
    std::any_of(block_id_to_xs_map_.begin(),
1,258✔
947
                block_id_to_xs_map_.end(),
948
                [](const auto& mat_id_xs)
1,691✔
949
                {
950
                  const auto& xs = mat_id_xs.second;
1,691✔
951
                  return xs->IsFissionable() and xs->GetNumPrecursors() > 0;
1,691✔
952
                });
953
  const bool has_fissionable_material =
1,258✔
954
    std::any_of(block_id_to_xs_map_.begin(),
1,258✔
955
                block_id_to_xs_map_.end(),
956
                [](const auto& mat_id_xs) { return mat_id_xs.second->IsFissionable(); });
1,647✔
957

958
  const bool has_any_precursor_data =
1,258✔
959
    std::any_of(block_id_to_xs_map_.begin(),
1,258✔
960
                block_id_to_xs_map_.end(),
961
                [](const auto& mat_id_xs) { return mat_id_xs.second->GetNumPrecursors() > 0; });
1,691✔
962

963
  if (options_.use_precursors and has_fissionable_material and not has_any_precursor_data)
1,258✔
964
  {
965
    log.Log0Warning() << GetName()
255✔
966
                      << ": options.use_precursors is enabled, but no precursor data was found "
967
                         "in the active cross-section map. Running without delayed-neutron "
968
                         "precursor coupling.";
85✔
969
  }
970

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

985
  // Update transport views if available
986
  if (grid_->local_cells.size() == cell_transport_views_.size())
1,258✔
987
    for (const auto& cell : grid_->local_cells)
28,108✔
988
    {
989
      const auto& xs_ptr = block_id_to_xs_map_[cell.block_id];
27,900✔
990
      auto& transport_view = cell_transport_views_[cell.local_id];
27,900✔
991
      transport_view.ReassignXS(*xs_ptr);
27,900✔
992
    }
993

994
  mpi_comm.barrier();
1,258✔
995
}
1,258✔
996

997
void
998
LBSProblem::InitializeSpatialDiscretization()
962✔
999
{
1000
  CALI_CXX_MARK_SCOPE("LBSProblem::InitializeSpatialDiscretization");
962✔
1001

1002
  OpenSnLogicalErrorIf(not discretization_,
962✔
1003
                       GetName() + ": Missing spatial discretization. Construct the problem "
1004
                                   "through its factory Create(...) entry point.");
1005
  log.Log() << "Initializing spatial discretization metadata.\n";
1,924✔
1006

1007
  ComputeUnitIntegrals();
962✔
1008
}
962✔
1009

1010
void
1011
LBSProblem::ComputeUnitIntegrals()
1,042✔
1012
{
1013
  CALI_CXX_MARK_SCOPE("LBSProblem::ComputeUnitIntegrals");
1,042✔
1014

1015
  log.Log() << "Computing unit integrals.\n";
2,084✔
1016
  const auto& sdm = *discretization_;
1,042✔
1017

1018
  const size_t num_local_cells = grid_->local_cells.size();
1,042✔
1019
  unit_cell_matrices_.resize(num_local_cells);
1,042✔
1020

1021
  for (const auto& cell : grid_->local_cells)
655,409✔
1022
    unit_cell_matrices_[cell.local_id] =
654,367✔
1023
      ComputeUnitCellIntegrals(sdm, cell, grid_->GetCoordinateSystem());
654,367✔
1024

1025
  const auto ghost_ids = grid_->cells.GetGhostGlobalIDs();
1,042✔
1026
  for (auto ghost_id : ghost_ids)
103,939✔
1027
    unit_ghost_cell_matrices_[ghost_id] =
102,897✔
1028
      ComputeUnitCellIntegrals(sdm, grid_->cells[ghost_id], grid_->GetCoordinateSystem());
205,794✔
1029

1030
  // Assessing global unit cell matrix storage
1031
  std::array<size_t, 2> num_local_ucms = {unit_cell_matrices_.size(),
1,042✔
1032
                                          unit_ghost_cell_matrices_.size()};
1,042✔
1033
  std::array<size_t, 2> num_global_ucms = {0, 0};
1,042✔
1034

1035
  mpi_comm.all_reduce(num_local_ucms.data(), 2, num_global_ucms.data(), mpi::op::sum<size_t>());
1,042✔
1036

1037
  opensn::mpi_comm.barrier();
1,042✔
1038
  log.Log() << "Ghost cell unit cell-matrix ratio: "
1,042✔
1039
            << (double)num_global_ucms[1] * 100 / (double)num_global_ucms[0] << "%";
2,084✔
1040
  log.Log() << "Cell matrices computed.";
2,084✔
1041
}
1,042✔
1042

1043
void
1044
LBSProblem::InitializeParrays()
1,042✔
1045
{
1046
  CALI_CXX_MARK_SCOPE("LBSProblem::InitializeParrays");
1,042✔
1047

1048
  log.Log() << "Initializing parallel arrays."
2,084✔
1049
            << " G=" << num_groups_ << " M=" << num_moments_ << std::endl;
1,042✔
1050

1051
  // Initialize unknown
1052
  // structure
1053
  flux_moments_uk_man_.unknowns.clear();
1,042✔
1054
  for (unsigned int m = 0; m < num_moments_; ++m)
3,017✔
1055
  {
1056
    flux_moments_uk_man_.AddUnknown(UnknownType::VECTOR_N, num_groups_);
1,975✔
1057
    flux_moments_uk_man_.unknowns.back().name = "m" + std::to_string(m);
1,975✔
1058
  }
1059

1060
  // Compute local # of dof
1061
  local_node_count_ = discretization_->GetNumLocalNodes();
1,042✔
1062
  global_node_count_ = discretization_->GetNumGlobalNodes();
1,042✔
1063

1064
  // Compute num of unknowns
1065
  size_t local_unknown_count = local_node_count_ * num_groups_ * num_moments_;
1,042✔
1066
  log.LogAllVerbose1() << "LBS Number of phi unknowns: " << local_unknown_count;
2,084✔
1067

1068
  // Size local vectors
1069
  q_moments_local_.assign(local_unknown_count, 0.0);
1,042✔
1070
  phi_old_local_.assign(local_unknown_count, 0.0);
1,042✔
1071
  phi_new_local_.assign(local_unknown_count, 0.0);
1,042✔
1072

1073
  // Setup precursor vector
1074
  if (options_.use_precursors)
1,042✔
1075
  {
1076
    size_t num_precursor_dofs = grid_->local_cells.size() * max_precursors_per_material_;
838✔
1077
    precursor_new_local_.assign(num_precursor_dofs, 0.0);
838✔
1078
  }
1079

1080
  // Initialize cell transport metadata and outflow tallies.
1081
  size_t block_MG_counter = 0; // Counts the strides of moment and group
1,042✔
1082
  min_cell_dof_count_ = std::numeric_limits<unsigned int>::max();
1,042✔
1083
  max_cell_dof_count_ = 0;
1,042✔
1084
  cell_transport_views_.clear();
1,042✔
1085
  cell_transport_views_.reserve(grid_->local_cells.size());
1,042✔
1086
  cell_outflow_views_.clear();
1,042✔
1087
  cell_outflow_views_.reserve(grid_->local_cells.size());
1,042✔
1088
  for (auto& cell : grid_->local_cells)
655,409✔
1089
  {
1090
    size_t num_nodes = discretization_->GetCellNumNodes(cell);
654,367✔
1091

1092
    // compute cell volumes
1093
    double cell_volume = 0.0;
654,367✔
1094
    const auto& IntV_shapeI = unit_cell_matrices_[cell.local_id].intV_shapeI;
654,367✔
1095
    for (size_t i = 0; i < num_nodes; ++i)
4,603,083✔
1096
      cell_volume += IntV_shapeI(i);
3,948,716✔
1097

1098
    size_t cell_phi_address = block_MG_counter;
654,367✔
1099

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

1122
      ++f;
3,266,464✔
1123
    }
1124

1125
    max_cell_dof_count_ = std::max(max_cell_dof_count_, static_cast<unsigned int>(num_nodes));
654,367✔
1126
    min_cell_dof_count_ = std::min(min_cell_dof_count_, static_cast<unsigned int>(num_nodes));
654,367✔
1127
    cell_transport_views_.emplace_back(cell_phi_address,
1,308,734✔
1128
                                       num_nodes,
1129
                                       num_groups_,
654,367✔
1130
                                       num_moments_,
654,367✔
1131
                                       *block_id_to_xs_map_[cell.block_id],
654,367✔
1132
                                       cell_volume,
1133
                                       face_local_flags,
1134
                                       face_locality,
1135
                                       neighbor_cell_ptrs);
1136
    cell_outflow_views_.emplace_back(num_faces, num_groups_, face_locality, cell_on_boundary);
654,367✔
1137
    block_MG_counter += num_nodes * num_groups_ * num_moments_;
654,367✔
1138
  } // for local cell
654,367✔
1139

1140
  // Populate grid nodal mappings
1141
  // This is used in the Flux Data Structure (FLUDS).
1142
  grid_nodal_mappings_.clear();
1,042✔
1143
  grid_nodal_mappings_.reserve(grid_->local_cells.size());
1,042✔
1144
  for (auto& cell : grid_->local_cells)
655,409✔
1145
  {
1146
    CellFaceNodalMapping cell_nodal_mapping;
654,367✔
1147
    cell_nodal_mapping.reserve(cell.faces.size());
654,367✔
1148

1149
    for (auto& face : cell.faces)
3,920,831✔
1150
    {
1151
      std::vector<short> face_node_mapping;
3,266,464✔
1152
      std::vector<short> cell_node_mapping;
3,266,464✔
1153
      int adj_face_idx = -1;
3,266,464✔
1154

1155
      if (face.has_neighbor)
3,266,464✔
1156
      {
1157
        grid_->FindAssociatedVertices(face, face_node_mapping);
3,148,178✔
1158
        grid_->FindAssociatedCellVertices(face, cell_node_mapping);
3,148,178✔
1159
        adj_face_idx = face.GetNeighborAdjacentFaceIndex(grid_.get());
3,148,178✔
1160
      }
1161

1162
      cell_nodal_mapping.emplace_back(adj_face_idx, face_node_mapping, cell_node_mapping);
3,266,464✔
1163
    }
3,266,464✔
1164

1165
    grid_nodal_mappings_.push_back(cell_nodal_mapping);
654,367✔
1166
  }
654,367✔
1167

1168
  // Get grid localized communicator set
1169
  grid_local_comm_set_ = grid_->MakeMPILocalCommunicatorSet();
1,042✔
1170

1171
  opensn::mpi_comm.barrier();
1,042✔
1172
  log.Log() << "Done with parallel arrays." << std::endl;
2,084✔
1173
}
1,042✔
1174

1175
#ifndef __OPENSN_WITH_GPU__
1176
void
1177
LBSProblem::InitializeGPUExtras()
1178
{
1179
}
1180

1181
void
1182
LBSProblem::ResetGPUCarriers()
1183
{
1184
}
1185

1186
void
1187
LBSProblem::CheckCapableDevices()
1188
{
1189
}
1190
#endif // __OPENSN_WITH_GPU__
1191

1192
std::vector<double>
1193
LBSProblem::MakeSourceMomentsFromPhi()
4✔
1194
{
1195
  CALI_CXX_MARK_SCOPE("LBSProblem::MakeSourceMomentsFromPhi");
4✔
1196

1197
  size_t num_local_dofs = discretization_->GetNumLocalDOFs(flux_moments_uk_man_);
4✔
1198

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

1209
  return source_moments;
4✔
1210
}
4✔
1211

1212
LBSProblem::~LBSProblem()
998✔
1213
{
1214
  ResetGPUCarriers();
1215
}
4,982✔
1216

998✔
1217
void
1218
LBSProblem::ZeroPhi()
360✔
1219
{
1220
  std::fill(phi_old_local_.begin(), phi_old_local_.end(), 0.0);
360✔
1221
  std::fill(phi_new_local_.begin(), phi_new_local_.end(), 0.0);
360✔
1222
}
360✔
1223

1224
void
1225
LBSProblem::CopyPhiNewToOld()
300✔
1226
{
1227
  assert(phi_old_local_.size() == phi_new_local_.size() && "Phi vectors size mismatch.");
300✔
1228
  phi_old_local_ = phi_new_local_;
300✔
1229
}
300✔
1230

1231
void
1232
LBSProblem::SetPhiOldFrom(const std::vector<double>& phi_old)
2,760✔
1233
{
1234
  assert(phi_old.size() == phi_old_local_.size() && "SetPhiOldFrom size mismatch.");
2,760✔
1235
  phi_old_local_ = phi_old;
2,760✔
1236
}
2,760✔
1237

1238
void
1239
LBSProblem::SetPhiNewFrom(const std::vector<double>& phi_new)
×
1240
{
1241
  assert(phi_new.size() == phi_new_local_.size() && "SetPhiNewFrom size mismatch.");
×
1242
  phi_new_local_ = phi_new;
×
1243
}
×
1244

1245
void
1246
LBSProblem::ScalePhiOld(double factor)
41✔
1247
{
1248
  for (auto& value : phi_old_local_)
3,594,261✔
1249
    value *= factor;
3,594,220✔
1250
}
41✔
1251

1252
void
1253
LBSProblem::ScalePhiNew(double factor)
49✔
1254
{
1255
  for (auto& value : phi_new_local_)
3,762,269✔
1256
    value *= factor;
3,762,220✔
1257
}
49✔
1258

1259
void
1260
LBSProblem::ZeroQMoments()
83,917✔
1261
{
1262
  assert(q_moments_local_.size() == phi_old_local_.size() && "Q moments/Phi size mismatch.");
83,917✔
1263
  std::fill(q_moments_local_.begin(), q_moments_local_.end(), 0.0);
83,917✔
1264
}
83,917✔
1265

1266
void
1267
LBSProblem::ScaleQMoments(double factor)
9,585✔
1268
{
1269
  for (auto& value : q_moments_local_)
702,715,941✔
1270
    value *= factor;
702,706,356✔
1271
}
9,585✔
1272

1273
void
1274
LBSProblem::SetQMomentsFrom(const std::vector<double>& q_moments)
28,011✔
1275
{
1276
  assert(q_moments.size() == q_moments_local_.size() && "SetQMomentsFrom size mismatch.");
28,011✔
1277
  q_moments_local_ = q_moments;
28,011✔
1278
}
28,011✔
1279

1280
void
1281
LBSProblem::ScalePrecursors(double factor)
122✔
1282
{
1283
  for (auto& value : precursor_new_local_)
1,240✔
1284
    value *= factor;
1,118✔
1285
}
122✔
1286

1287
void
1288
LBSProblem::ZeroPrecursors()
2,460✔
1289
{
1290
  std::fill(precursor_new_local_.begin(), precursor_new_local_.end(), 0.0);
2,460✔
1291
}
2,460✔
1292

1293
void
1294
LBSProblem::ZeroExtSrcMoments()
×
1295
{
1296
  std::fill(ext_src_moments_local_.begin(), ext_src_moments_local_.end(), 0.0);
×
1297
}
×
1298

1299
void
1300
LBSProblem::ScaleExtSrcMoments(double factor)
×
1301
{
1302
  for (auto& value : ext_src_moments_local_)
×
1303
    value *= factor;
×
1304
}
×
1305

1306
void
1307
LBSProblem::SetAdjoint(bool adjoint)
24✔
1308
{
1309
  if (adjoint)
24✔
1310
    if (IsTimeDependent())
20✔
1311
      OpenSnInvalidArgument(GetName() + ": Time-dependent adjoint problems are not supported.");
×
1312

1313
  const bool mode_changed = (adjoint != options_.adjoint);
24✔
1314
  if (not mode_changed)
24✔
1315
    return;
1316

1317
  options_.adjoint = adjoint;
24✔
1318

1319
  // Reinitialize materials to obtain the proper forward/adjoint cross sections.
1320
  InitializeMaterials();
24✔
1321

1322
  // Forward and adjoint sources are fundamentally different.
1323
  point_sources_.clear();
24✔
1324
  volumetric_sources_.clear();
24✔
1325
  ClearBoundaries();
24✔
1326

1327
  // Reset all solution vectors.
1328
  ZeroPhi();
24✔
1329
  ResetDerivedSolutionVectors();
24✔
1330
  ZeroPrecursors();
24✔
1331
}
1332

1333
void
1334
LBSProblem::SetForward()
×
1335
{
1336
  SetAdjoint(false);
×
1337
}
×
1338

1339
bool
1340
LBSProblem::IsAdjoint() const
×
1341
{
1342
  return options_.adjoint;
×
1343
}
1344

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