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

Open-Sn / opensn / 18581001536

16 Oct 2025 03:16PM UTC coverage: 75.124% (-0.06%) from 75.188%
18581001536

push

github

web-flow
Merge pull request #797 from wdhawkins/problem_updates

Minor refactoring of problem classes

140 of 199 new or added lines in 4 files covered. (70.35%)

2 existing lines in 2 files now uncovered.

17996 of 23955 relevant lines covered (75.12%)

54568204.05 hits per line

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

82.7
/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/iterative_methods/wgs_context.h"
6
#include "modules/linear_boltzmann_solvers/lbs_problem/iterative_methods/ags_linear_solver.h"
7
#include "modules/linear_boltzmann_solvers/lbs_problem/point_source/point_source.h"
8
#include "modules/linear_boltzmann_solvers/lbs_problem/groupset/lbs_groupset.h"
9
#include "framework/math/spatial_discretization/finite_element/piecewise_linear/piecewise_linear_discontinuous.h"
10
#include "framework/field_functions/field_function_grid_based.h"
11
#include "framework/materials/multi_group_xs/multi_group_xs.h"
12
#include "framework/mesh/mesh_continuum/mesh_continuum.h"
13
#include "framework/utils/hdf_utils.h"
14
#include "framework/object_factory.h"
15
#include "framework/logging/log.h"
16
#include "framework/runtime.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 <sys/stat.h>
25

26
namespace opensn
27
{
28

29
std::map<std::string, uint64_t> LBSProblem::supported_boundary_names = {
30
  {"xmin", XMIN}, {"xmax", XMAX}, {"ymin", YMIN}, {"ymax", YMAX}, {"zmin", ZMIN}, {"zmax", ZMAX}};
31

32
std::map<uint64_t, std::string> LBSProblem::supported_boundary_ids = {
33
  {XMIN, "xmin"}, {XMAX, "xmax"}, {YMIN, "ymin"}, {YMAX, "ymax"}, {ZMIN, "zmin"}, {ZMAX, "zmax"}};
34

35
LBSProblem::LBSProblem(const std::string& name, std::shared_ptr<MeshContinuum> grid)
×
36
  : Problem(name), grid_(grid), use_gpus_(false)
×
37
{
38
}
×
39

40
InputParameters
41
LBSProblem::GetInputParameters()
295✔
42
{
43
  InputParameters params = Problem::GetInputParameters();
295✔
44

45
  params.ChangeExistingParamToOptional("name", "LBSProblem");
590✔
46

47
  params.AddRequiredParameter<std::shared_ptr<MeshContinuum>>("mesh", "Mesh");
590✔
48

49
  params.AddRequiredParameter<size_t>("num_groups", "The total number of groups within the solver");
590✔
50

51
  params.AddRequiredParameterArray("groupsets",
590✔
52
                                   "An array of blocks each specifying the input parameters for a "
53
                                   "<TT>LBSGroupset</TT>.");
54
  params.LinkParameterToBlock("groupsets", "LBSGroupset");
590✔
55

56
  params.AddRequiredParameterArray("xs_map",
590✔
57
                                   "Cross-section map from block IDs to cross-section objects.");
58

59
  params.AddOptionalParameterArray<std::shared_ptr<VolumetricSource>>(
590✔
60
    "volumetric_sources", {}, "An array of handles to volumetric sources.");
61

62
  params.AddOptionalParameterArray<std::shared_ptr<PointSource>>(
590✔
63
    "point_sources", {}, "An array of point sources.");
64

65
  params.AddOptionalParameterArray(
590✔
66
    "boundary_conditions", {}, "An array containing tables for each boundary specification.");
67
  params.LinkParameterToBlock("boundary_conditions", "BoundaryOptionsBlock");
590✔
68

69
  params.AddOptionalParameterBlock(
590✔
70
    "options", ParameterBlock(), "Block of options. See <TT>OptionsBlock</TT>.");
590✔
71
  params.LinkParameterToBlock("options", "OptionsBlock");
590✔
72

73
  params.AddOptionalParameter("use_gpus", false, "Offload the sweep computation to GPUs.");
590✔
74

75
  return params;
295✔
76
}
×
77

78
LBSProblem::LBSProblem(const InputParameters& params)
295✔
79
  : Problem(params),
80
    num_groups_(params.GetParamValue<size_t>("num_groups")),
295✔
81
    grid_(params.GetSharedPtrParam<MeshContinuum>("mesh")),
295✔
82
    use_gpus_(params.GetParamValue<bool>("use_gpus"))
885✔
83
{
84
  // Check system for GPU acceleration
85
  if (use_gpus_)
295✔
86
  {
87
#ifdef __OPENSN_USE_CUDA__
88
    CheckCapableDevices();
89
#else
NEW
90
    throw std::invalid_argument(
×
NEW
91
      GetName() + ": GPU support was requested, but OpenSn was built without CUDA enabled");
×
92
#endif // __OPENSN_USE_CUDA__
93
  }
94

95
  // Initialize options
96
  if (params.IsParameterValid("options"))
295✔
97
  {
98
    auto options_params = LBSProblem::GetOptionsBlock();
177✔
99
    options_params.AssignParameters(params.GetParam("options"));
179✔
100
    SetOptions(options_params);
175✔
101
  }
177✔
102

103
  // Set geometry type
104
  const auto dim = grid_->GetDimension();
293✔
105
  if (dim == 1)
293✔
106
    options_.geometry_type = GeometryType::ONED_SLAB;
44✔
107
  else if (dim == 2)
249✔
108
    options_.geometry_type = GeometryType::TWOD_CARTESIAN;
126✔
109
  else if (dim == 3)
123✔
110
    options_.geometry_type = GeometryType::THREED_CARTESIAN;
123✔
111
  else
NEW
112
    OpenSnLogicalError("Cannot deduce geometry type from mesh.");
×
113

114
  // Set boundary conditions
115
  if (params.Has("boundary_conditions"))
293✔
116
  {
117
    const auto& bcs = params.GetParam("boundary_conditions");
293✔
118
    bcs.RequireBlockTypeIs(ParameterBlockType::ARRAY);
293✔
119
    for (size_t b = 0; b < bcs.GetNumParameters(); ++b)
900✔
120
    {
121
      auto bndry_params = GetBoundaryOptionsBlock();
607✔
122
      bndry_params.AssignParameters(bcs.GetParam(b));
607✔
123
      SetBoundaryOptions(bndry_params);
607✔
124
    }
607✔
125
  }
126

127
  InitializeGroupsets(params);
293✔
128
  InitializeSources(params);
293✔
129
  InitializeXSmapAndDensities(params);
293✔
130
  InitializeMaterials();
293✔
131
}
313✔
132

133
LBSOptions&
134
LBSProblem::GetOptions()
17,200✔
135
{
136
  return options_;
17,200✔
137
}
138

139
const LBSOptions&
140
LBSProblem::GetOptions() const
471,784,946✔
141
{
142
  return options_;
471,784,946✔
143
}
144

145
size_t
146
LBSProblem::GetNumMoments() const
128,221✔
147
{
148
  return num_moments_;
128,221✔
149
}
150

151
size_t
152
LBSProblem::GetNumGroups() const
52,165✔
153
{
154
  return num_groups_;
52,165✔
155
}
156

157
unsigned int
158
LBSProblem::GetScatteringOrder() const
4✔
159
{
160
  return scattering_order_;
4✔
161
}
162

163
size_t
164
LBSProblem::GetNumPrecursors() const
×
165
{
166
  return num_precursors_;
×
167
}
168

169
size_t
170
LBSProblem::GetMaxPrecursorsPerMaterial() const
8✔
171
{
172
  return max_precursors_per_material_;
8✔
173
}
174

175
const std::vector<LBSGroup>&
176
LBSProblem::GetGroups() const
137,956✔
177
{
178
  return groups_;
137,956✔
179
}
180

181
std::vector<LBSGroupset>&
182
LBSProblem::GetGroupsets()
8,170,715✔
183
{
184
  return groupsets_;
8,170,715✔
185
}
186

187
const std::vector<LBSGroupset>&
188
LBSProblem::GetGroupsets() const
×
189
{
190
  return groupsets_;
×
191
}
192

193
void
194
LBSProblem::AddPointSource(std::shared_ptr<PointSource> point_source)
×
195
{
196
  point_sources_.push_back(point_source);
×
197
  if (discretization_)
×
198
    point_sources_.back()->Initialize(*this);
×
199
}
×
200

201
void
202
LBSProblem::ClearPointSources()
×
203
{
204
  point_sources_.clear();
×
205
}
×
206

207
const std::vector<std::shared_ptr<PointSource>>&
208
LBSProblem::GetPointSources() const
7,414✔
209
{
210
  return point_sources_;
7,414✔
211
}
212

213
void
214
LBSProblem::AddVolumetricSource(std::shared_ptr<VolumetricSource> volumetric_source)
16✔
215
{
216
  volumetric_sources_.push_back(volumetric_source);
16✔
217
  if (discretization_)
16✔
218
    volumetric_sources_.back()->Initialize(*this);
16✔
219
}
16✔
220

221
void
222
LBSProblem::ClearVolumetricSources()
4✔
223
{
224
  volumetric_sources_.clear();
4✔
225
}
4✔
226

227
const std::vector<std::shared_ptr<VolumetricSource>>&
228
LBSProblem::GetVolumetricSources() const
7,414✔
229
{
230
  return volumetric_sources_;
7,414✔
231
}
232

233
void
234
LBSProblem::ClearBoundaries()
×
235
{
236
  boundary_preferences_.clear();
×
237
}
×
238

239
const std::map<int, std::shared_ptr<MultiGroupXS>>&
240
LBSProblem::GetMatID2XSMap() const
6,681✔
241
{
242
  return block_id_to_xs_map_;
6,681✔
243
}
244

245
std::shared_ptr<MeshContinuum>
246
LBSProblem::GetGrid() const
179,964✔
247
{
248
  return grid_;
179,964✔
249
}
250

251
const SpatialDiscretization&
252
LBSProblem::GetSpatialDiscretization() const
63,996✔
253
{
254
  return *discretization_;
63,996✔
255
}
256

257
const std::vector<UnitCellMatrices>&
258
LBSProblem::GetUnitCellMatrices() const
7,267✔
259
{
260
  return unit_cell_matrices_;
7,267✔
261
}
262

263
const std::map<uint64_t, UnitCellMatrices>&
264
LBSProblem::GetUnitGhostCellMatrices() const
17✔
265
{
266
  return unit_ghost_cell_matrices_;
17✔
267
}
268

269
const std::vector<CellLBSView>&
270
LBSProblem::GetCellTransportViews() const
189,920✔
271
{
272
  return cell_transport_views_;
189,920✔
273
}
274

275
const UnknownManager&
276
LBSProblem::GetUnknownManager() const
26,695✔
277
{
278
  return flux_moments_uk_man_;
26,695✔
279
}
280

281
size_t
282
LBSProblem::GetLocalNodeCount() const
3,049✔
283
{
284
  return local_node_count_;
3,049✔
285
}
286

287
size_t
288
LBSProblem::GetGlobalNodeCount() const
1,486✔
289
{
290
  return global_node_count_;
1,486✔
291
}
292

293
std::vector<double>&
294
LBSProblem::GetQMomentsLocal()
37,405✔
295
{
296
  return q_moments_local_;
37,405✔
297
}
298

299
const std::vector<double>&
300
LBSProblem::GetQMomentsLocal() const
×
301
{
302
  return q_moments_local_;
×
303
}
304

305
std::vector<double>&
306
LBSProblem::GetExtSrcMomentsLocal()
4✔
307
{
308
  return ext_src_moments_local_;
4✔
309
}
310

311
const std::vector<double>&
312
LBSProblem::GetExtSrcMomentsLocal() const
37,091✔
313
{
314
  return ext_src_moments_local_;
37,091✔
315
}
316

317
std::vector<double>&
318
LBSProblem::GetPhiOldLocal()
56,719✔
319
{
320
  return phi_old_local_;
56,719✔
321
}
322

323
const std::vector<double>&
324
LBSProblem::GetPhiOldLocal() const
×
325
{
326
  return phi_old_local_;
×
327
}
328

329
std::vector<double>&
330
LBSProblem::GetPhiNewLocal()
59,265✔
331
{
332
  return phi_new_local_;
59,265✔
333
}
334

335
const std::vector<double>&
336
LBSProblem::GetPhiNewLocal() const
×
337
{
338
  return phi_new_local_;
×
339
}
340

341
std::vector<double>&
342
LBSProblem::GetPrecursorsNewLocal()
16✔
343
{
344
  return precursor_new_local_;
16✔
345
}
346

347
const std::vector<double>&
348
LBSProblem::GetPrecursorsNewLocal() const
×
349
{
350
  return precursor_new_local_;
×
351
}
352

353
std::vector<double>&
354
LBSProblem::GetDensitiesLocal()
250✔
355
{
356
  return densities_local_;
250✔
357
}
358

359
const std::vector<double>&
360
LBSProblem::GetDensitiesLocal() const
37,091✔
361
{
362
  return densities_local_;
37,091✔
363
}
364

365
SetSourceFunction
366
LBSProblem::GetActiveSetSourceFunction() const
4,142✔
367
{
368
  return active_set_source_function_;
4,142✔
369
}
370

371
std::shared_ptr<AGSLinearSolver>
372
LBSProblem::GetAGSSolver()
277✔
373
{
374
  return ags_solver_;
277✔
375
}
376

377
std::vector<std::shared_ptr<LinearSolver>>&
378
LBSProblem::GetWGSSolvers()
113✔
379
{
380
  return wgs_solvers_;
113✔
381
}
382

383
WGSContext&
384
LBSProblem::GetWGSContext(int groupset_id)
11,472✔
385
{
386
  auto& wgs_solver = wgs_solvers_[groupset_id];
11,472✔
387
  auto raw_context = wgs_solver->GetContext();
11,472✔
388
  auto wgs_context_ptr = std::dynamic_pointer_cast<WGSContext>(raw_context);
11,472✔
389
  OpenSnLogicalErrorIf(not wgs_context_ptr, "Failed to cast WGSContext");
11,472✔
390
  return *wgs_context_ptr;
11,472✔
391
}
22,944✔
392

393
std::map<uint64_t, BoundaryPreference>&
394
LBSProblem::GetBoundaryPreferences()
4✔
395
{
396
  return boundary_preferences_;
4✔
397
}
398

399
std::pair<size_t, size_t>
400
LBSProblem::GetNumPhiIterativeUnknowns()
×
401
{
402
  const auto& sdm = *discretization_;
×
403
  const size_t num_local_phi_dofs = sdm.GetNumLocalDOFs(flux_moments_uk_man_);
×
404
  const size_t num_global_phi_dofs = sdm.GetNumGlobalDOFs(flux_moments_uk_man_);
×
405

406
  return {num_local_phi_dofs, num_global_phi_dofs};
×
407
}
408

409
size_t
410
LBSProblem::MapPhiFieldFunction(size_t g, size_t m) const
22,490✔
411
{
412
  OpenSnLogicalErrorIf(phi_field_functions_local_map_.count({g, m}) == 0,
22,490✔
413
                       std::string("Failure to map phi field function g") + std::to_string(g) +
414
                         " m" + std::to_string(m));
415

416
  return phi_field_functions_local_map_.at({g, m});
22,490✔
417
}
418

419
std::shared_ptr<FieldFunctionGridBased>
420
LBSProblem::GetPowerFieldFunction() const
×
421
{
422
  OpenSnLogicalErrorIf(not options_.power_field_function_on,
×
423
                       "Called when options_.power_field_function_on == false");
424

425
  return field_functions_[power_gen_fieldfunc_local_handle_];
×
426
}
427

428
InputParameters
429
LBSProblem::GetOptionsBlock()
376✔
430
{
431
  InputParameters params;
376✔
432

433
  params.SetGeneralDescription("Set options from a large list of parameters");
752✔
434
  params.AddOptionalParameter("max_mpi_message_size",
752✔
435
                              32768,
436
                              "The maximum MPI message size used during sweep initialization.");
437
  params.AddOptionalParameter(
752✔
438
    "restart_writes_enabled", false, "Flag that controls writing of restart dumps");
439
  params.AddOptionalParameter("write_delayed_psi_to_restart",
752✔
440
                              true,
441
                              "Flag that controls writing of delayed angular fluxes to restarts.");
442
  params.AddOptionalParameter(
752✔
443
    "read_restart_path", "", "Full path for reading restart dumps including file stem.");
444
  params.AddOptionalParameter(
752✔
445
    "write_restart_path", "", "Full path for writing restart dumps including file stem.");
446
  params.AddOptionalParameter("write_restart_time_interval",
752✔
447
                              0,
448
                              "Time interval in seconds at which restart data is to be written.");
449
  params.AddOptionalParameter(
752✔
450
    "use_precursors", false, "Flag for using delayed neutron precursors.");
451
  params.AddOptionalParameter("use_source_moments",
752✔
452
                              false,
453
                              "Flag for ignoring fixed sources and selectively using source "
454
                              "moments obtained elsewhere.");
455
  params.AddOptionalParameter(
752✔
456
    "save_angular_flux", false, "Flag indicating whether angular fluxes are to be stored or not.");
457
  params.AddOptionalParameter(
752✔
458
    "adjoint", false, "Flag for toggling whether the solver is in adjoint mode.");
459
  params.AddOptionalParameter(
752✔
460
    "verbose_inner_iterations", true, "Flag to control verbosity of inner iterations.");
461
  params.AddOptionalParameter(
752✔
462
    "verbose_outer_iterations", true, "Flag to control verbosity of across-groupset iterations.");
463
  params.AddOptionalParameter(
752✔
464
    "max_ags_iterations", 100, "Maximum number of across-groupset iterations.");
465
  params.AddOptionalParameter("ags_tolerance", 1.0e-6, "Across-groupset iterations tolerance.");
752✔
466
  params.AddOptionalParameter("ags_convergence_check",
752✔
467
                              "l2",
468
                              "Type of convergence check for AGS iterations. Valid values are "
469
                              "`\"l2\"` and '\"pointwise\"'");
470
  params.AddOptionalParameter(
752✔
471
    "verbose_ags_iterations", true, "Flag to control verbosity of across-groupset iterations.");
472
  params.AddOptionalParameter("power_field_function_on",
752✔
473
                              false,
474
                              "Flag to control the creation of the power generation field "
475
                              "function. If set to `true` then a field function will be created "
476
                              "with the general name <solver_name>_power_generation`.");
477
  params.AddOptionalParameter("power_default_kappa",
752✔
478
                              3.20435e-11,
479
                              "Default `kappa` value (Energy released per fission) to use for "
480
                              "power generation when cross sections do not have `kappa` values. "
481
                              "Default: 3.20435e-11 Joule (corresponding to 200 MeV per fission).");
482
  params.AddOptionalParameter("power_normalization",
752✔
483
                              -1.0,
484
                              "Power normalization factor to use. Supply a negative or zero number "
485
                              "to turn this off.");
486
  params.AddOptionalParameter("field_function_prefix_option",
752✔
487
                              "prefix",
488
                              "Prefix option on field function names. Default: `\"prefix\"`. Can "
489
                              "be `\"prefix\"` or `\"solver_name\"`. By default this option uses "
490
                              "the value of the `field_function_prefix` parameter. If this "
491
                              "parameter is not set, flux field functions will be exported as "
492
                              "`phi_gXXX_mYYY` where `XXX` is the zero padded 3 digit group number "
493
                              "and `YYY` is the zero padded 3 digit moment.");
494
  params.AddOptionalParameter("field_function_prefix",
752✔
495
                              "",
496
                              "Prefix to use on all field functions. Default: `\"\"`. By default "
497
                              "this option is empty. Ff specified, flux moments will be exported "
498
                              "as `prefix_phi_gXXX_mYYY` where `XXX` is the zero padded 3 digit "
499
                              "group number and `YYY` is the zero padded 3 digit moment. The "
500
                              "underscore after \"prefix\" is added automatically.");
501
  params.ConstrainParameterRange("ags_convergence_check",
1,128✔
502
                                 AllowableRangeList::New({"l2", "pointwise"}));
376✔
503
  params.ConstrainParameterRange("field_function_prefix_option",
1,128✔
504
                                 AllowableRangeList::New({"prefix", "solver_name"}));
376✔
505

506
  return params;
376✔
507
}
×
508

509
InputParameters
510
LBSProblem::GetBoundaryOptionsBlock()
607✔
511
{
512
  InputParameters params;
607✔
513

514
  params.SetGeneralDescription("Set options for boundary conditions.");
1,214✔
515
  params.AddRequiredParameter<std::string>("name",
1,214✔
516
                                           "Boundary name that identifies the specific boundary");
517
  params.AddRequiredParameter<std::string>("type", "Boundary type specification.");
1,214✔
518
  params.AddOptionalParameterArray<double>("group_strength",
1,214✔
519
                                           {},
520
                                           "Required only if \"type\" is \"isotropic\". An array "
521
                                           "of isotropic strength per group");
522
  params.AddOptionalParameter(
1,214✔
523
    "function_name", "", "Text name of the function to be called for this boundary condition.");
524
  params.ConstrainParameterRange(
1,821✔
525
    "name", AllowableRangeList::New({"xmin", "xmax", "ymin", "ymax", "zmin", "zmax"}));
607✔
526
  params.ConstrainParameterRange("type",
1,821✔
527
                                 AllowableRangeList::New({"vacuum", "isotropic", "reflecting"}));
607✔
528

529
  return params;
607✔
530
}
×
531

532
InputParameters
533
LBSProblem::GetXSMapEntryBlock()
542✔
534
{
535
  InputParameters params;
542✔
536
  params.SetGeneralDescription("Set the cross-section map for the solver.");
1,084✔
537
  params.AddRequiredParameterArray("block_ids", "Mesh block IDs");
1,084✔
538
  params.AddRequiredParameter<std::shared_ptr<MultiGroupXS>>("xs", "Cross-section object");
1,084✔
539
  return params;
542✔
540
}
×
541

542
void
543
LBSProblem::SetOptions(const InputParameters& input)
187✔
544
{
545
  auto params = LBSProblem::GetOptionsBlock();
187✔
546
  params.AssignParameters(input);
187✔
547

548
  // Handle order insensitive options
549
  for (size_t p = 0; p < params.GetNumParameters(); ++p)
4,114✔
550
  {
551
    const auto& spec = params.GetParam(p);
3,927✔
552

553
    if (spec.GetName() == "max_mpi_message_size")
3,927✔
554
      options_.max_mpi_message_size = spec.GetValue<int>();
187✔
555

556
    else if (spec.GetName() == "restart_writes_enabled")
3,740✔
557
      options_.restart_writes_enabled = spec.GetValue<bool>();
187✔
558

559
    else if (spec.GetName() == "write_delayed_psi_to_restart")
3,553✔
560
      options_.write_delayed_psi_to_restart = spec.GetValue<bool>();
187✔
561

562
    else if (spec.GetName() == "read_restart_path")
3,366✔
563
    {
564
      options_.read_restart_path = spec.GetValue<std::string>();
187✔
565
      if (not options_.read_restart_path.empty())
187✔
566
        options_.read_restart_path += std::to_string(opensn::mpi_comm.rank()) + ".restart.h5";
36✔
567
    }
568

569
    else if (spec.GetName() == "write_restart_path")
3,179✔
570
    {
571
      options_.write_restart_path = spec.GetValue<std::string>();
187✔
572
      if (not options_.write_restart_path.empty())
187✔
573
        options_.write_restart_path += std::to_string(opensn::mpi_comm.rank()) + ".restart.h5";
×
574
    }
575

576
    else if (spec.GetName() == "write_restart_time_interval")
2,992✔
577
      options_.write_restart_time_interval = std::chrono::seconds(spec.GetValue<int>());
187✔
578

579
    else if (spec.GetName() == "use_precursors")
2,805✔
580
      options_.use_precursors = spec.GetValue<bool>();
187✔
581

582
    else if (spec.GetName() == "use_source_moments")
2,618✔
583
      options_.use_src_moments = spec.GetValue<bool>();
187✔
584

585
    else if (spec.GetName() == "save_angular_flux")
2,431✔
586
      options_.save_angular_flux = spec.GetValue<bool>();
187✔
587

588
    else if (spec.GetName() == "verbose_inner_iterations")
2,244✔
589
      options_.verbose_inner_iterations = spec.GetValue<bool>();
187✔
590

591
    else if (spec.GetName() == "max_ags_iterations")
2,057✔
592
      options_.max_ags_iterations = spec.GetValue<int>();
187✔
593

594
    else if (spec.GetName() == "ags_tolerance")
1,870✔
595
      options_.ags_tolerance = spec.GetValue<double>();
187✔
596

597
    else if (spec.GetName() == "ags_convergence_check")
1,683✔
598
    {
599
      auto check = spec.GetValue<std::string>();
187✔
600
      if (check == "pointwise")
187✔
601
        options_.ags_pointwise_convergence = true;
×
602
    }
187✔
603

604
    else if (spec.GetName() == "verbose_ags_iterations")
1,496✔
605
      options_.verbose_ags_iterations = spec.GetValue<bool>();
187✔
606

607
    else if (spec.GetName() == "verbose_outer_iterations")
1,309✔
608
      options_.verbose_outer_iterations = spec.GetValue<bool>();
187✔
609

610
    else if (spec.GetName() == "power_field_function_on")
1,122✔
611
      options_.power_field_function_on = spec.GetValue<bool>();
187✔
612

613
    else if (spec.GetName() == "power_default_kappa")
935✔
614
      options_.power_default_kappa = spec.GetValue<double>();
187✔
615

616
    else if (spec.GetName() == "power_normalization")
748✔
617
      options_.power_normalization = spec.GetValue<double>();
187✔
618

619
    else if (spec.GetName() == "field_function_prefix_option")
561✔
620
    {
621
      options_.field_function_prefix_option = spec.GetValue<std::string>();
187✔
622
    }
623

624
    else if (spec.GetName() == "field_function_prefix")
374✔
625
      options_.field_function_prefix = spec.GetValue<std::string>();
187✔
626
  } // for p
627

628
  if (options_.restart_writes_enabled)
187✔
629
  {
630
    // Create restart directory if necessary
631
    auto dir = options_.write_restart_path.parent_path();
×
632
    if (opensn::mpi_comm.rank() == 0)
×
633
    {
634
      if (not std::filesystem::exists(dir))
×
635
      {
636
        if (not std::filesystem::create_directories(dir))
×
NEW
637
          throw std::runtime_error(GetName() + ": Failed to create restart directory " +
×
NEW
638
                                   dir.string());
×
639
      }
640
      else if (not std::filesystem::is_directory(dir))
×
NEW
641
        throw std::runtime_error(GetName() + ": Restart path exists but is not a directory " +
×
NEW
642
                                 dir.string());
×
643
    }
644
    opensn::mpi_comm.barrier();
×
645
    UpdateRestartWriteTime();
×
646
  }
×
647
}
187✔
648

649
void
650
LBSProblem::SetBoundaryOptions(const InputParameters& params)
607✔
651
{
652
  const auto boundary_name = params.GetParamValue<std::string>("name");
607✔
653
  const auto bndry_type = params.GetParamValue<std::string>("type");
607✔
654

655
  const auto bid = supported_boundary_names.at(boundary_name);
607✔
656
  const std::map<std::string, LBSBoundaryType> type_list = {
607✔
657
    {"vacuum", LBSBoundaryType::VACUUM},
607✔
658
    {"isotropic", LBSBoundaryType::ISOTROPIC},
607✔
659
    {"reflecting", LBSBoundaryType::REFLECTING}};
2,428✔
660

661
  const auto type = type_list.at(bndry_type);
607✔
662
  switch (type)
607✔
663
  {
664
    case LBSBoundaryType::VACUUM:
526✔
665
    case LBSBoundaryType::REFLECTING:
526✔
666
    {
526✔
667
      boundary_preferences_[bid] = {type};
526✔
668
      break;
526✔
669
    }
670
    case LBSBoundaryType::ISOTROPIC:
81✔
671
    {
81✔
672
      OpenSnInvalidArgumentIf(not params.Has("group_strength"),
81✔
673
                              "Boundary conditions with type=\"isotropic\" require parameter "
674
                              "\"group_strength\"");
675

676
      params.RequireParameterBlockTypeIs("group_strength", ParameterBlockType::ARRAY);
81✔
677
      const auto group_strength = params.GetParamVectorValue<double>("group_strength");
81✔
678
      boundary_preferences_[bid] = {type, group_strength};
81✔
679
      break;
81✔
680
    }
81✔
681
    case LBSBoundaryType::ARBITRARY:
×
682
    {
×
NEW
683
      throw std::runtime_error(GetName() +
×
NEW
684
                               ": Arbitrary boundary conditions are not currently supported");
×
685
      break;
607✔
686
    }
687
  }
688
}
1,821✔
689

690
void
691
LBSProblem::Initialize()
293✔
692
{
693
  CALI_CXX_MARK_SCOPE("LBSProblem::Initialize");
293✔
694

695
  PrintSimHeader();
293✔
696
  mpi_comm.barrier();
293✔
697

698
  InitializeSpatialDiscretization();
293✔
699
  InitializeParrays();
293✔
700
  InitializeBoundaries();
293✔
701
  InitializeGPUExtras();
293✔
702
  SetAdjoint(false);
293✔
703

704
  // Initialize point sources
705
  for (auto& point_source : point_sources_)
302✔
706
    point_source->Initialize(*this);
9✔
707

708
  // Initialize volumetric sources
709
  for (auto& volumetric_source : volumetric_sources_)
610✔
710
    volumetric_source->Initialize(*this);
317✔
711
}
293✔
712

713
void
NEW
714
LBSProblem::PrintSimHeader()
×
715
{
NEW
716
  if (opensn::mpi_comm.rank() == 0)
×
717
  {
NEW
718
    std::stringstream outstr;
×
NEW
719
    outstr << "\n"
×
NEW
720
           << "Initializing " << GetName() << "\n\n"
×
NEW
721
           << "Scattering order    : " << scattering_order_ << "\n"
×
NEW
722
           << "Number of moments   : " << num_moments_ << "\n"
×
NEW
723
           << "Number of groups    : " << groups_.size() << "\n"
×
NEW
724
           << "Number of groupsets : " << groupsets_.size() << "\n\n";
×
725

NEW
726
    for (const auto& groupset : groupsets_)
×
727
    {
NEW
728
      outstr << "***** Groupset " << groupset.id << " *****\n"
×
NEW
729
             << "Groups:\n";
×
730
      const auto& groups = groupset.groups;
731
      constexpr int groups_per_line = 12;
NEW
732
      for (size_t i = 0; i < groups.size(); ++i)
×
733
      {
NEW
734
        outstr << std::setw(5) << groups[i].id << ' ';
×
NEW
735
        if ((i + 1) % groups_per_line == 0)
×
NEW
736
          outstr << '\n';
×
737
      }
NEW
738
      if (!groups.empty() && groups.size() % groups_per_line != 0)
×
NEW
739
        outstr << '\n';
×
740
    }
741

NEW
742
    log.Log() << outstr.str() << '\n';
×
NEW
743
  }
×
UNCOV
744
}
×
745

746
void
747
LBSProblem::InitializeSources(const InputParameters& params)
293✔
748
{
749
  if (params.Has("volumetric_sources"))
293✔
750
  {
751
    const auto& vol_srcs = params.GetParam("volumetric_sources");
293✔
752
    vol_srcs.RequireBlockTypeIs(ParameterBlockType::ARRAY);
293✔
753
    for (const auto& src : vol_srcs)
610✔
754
      volumetric_sources_.push_back(src.GetValue<std::shared_ptr<VolumetricSource>>());
634✔
755
  }
756

757
  if (params.Has("point_sources"))
293✔
758
  {
759
    const auto& pt_srcs = params.GetParam("point_sources");
293✔
760
    pt_srcs.RequireBlockTypeIs(ParameterBlockType::ARRAY);
293✔
761
    for (const auto& src : pt_srcs)
302✔
762
      point_sources_.push_back(src.GetValue<std::shared_ptr<PointSource>>());
18✔
763
  }
764
}
293✔
765

766
void
767
LBSProblem::InitializeGroupsets(const InputParameters& params)
293✔
768
{
769
  // Initialize groups
770
  if (num_groups_ == 0)
293✔
NEW
771
    throw std::invalid_argument(GetName() + ": Number of groups must be > 0");
×
772
  for (size_t g = 0; g < num_groups_; ++g)
19,936✔
773
    groups_.emplace_back(static_cast<int>(g));
19,643✔
774

775
  // Initialize groupsets
776
  const auto& groupsets_array = params.GetParam("groupsets");
293✔
777
  const size_t num_gs = groupsets_array.GetNumParameters();
293✔
778
  if (num_gs == 0)
293✔
NEW
779
    throw std::invalid_argument(GetName() + ": At least one groupset must be specified");
×
780
  for (size_t gs = 0; gs < num_gs; ++gs)
641✔
781
  {
782
    const auto& groupset_params = groupsets_array.GetParam(gs);
348✔
783
    InputParameters gs_input_params = LBSGroupset::GetInputParameters();
348✔
784
    gs_input_params.SetObjectType("LBSProblem:LBSGroupset");
348✔
785
    gs_input_params.AssignParameters(groupset_params);
348✔
786
    groupsets_.emplace_back(gs_input_params, gs, *this);
348✔
787
    if (groupsets_.back().groups.empty())
348✔
788
    {
789
      std::stringstream oss;
×
NEW
790
      oss << GetName() << ": No groups added to groupset " << groupsets_.back().id;
×
791
      throw std::runtime_error(oss.str());
×
792
    }
×
793
  }
348✔
794
}
293✔
795

796
void
797
LBSProblem::InitializeXSmapAndDensities(const InputParameters& params)
293✔
798
{
799
  // Build XS map
800
  const auto& xs_array = params.GetParam("xs_map");
293✔
801
  const size_t num_xs = xs_array.GetNumParameters();
293✔
802
  for (size_t i = 0; i < num_xs; ++i)
835✔
803
  {
804
    const auto& item_params = xs_array.GetParam(i);
542✔
805
    InputParameters xs_entry_pars = GetXSMapEntryBlock();
542✔
806
    xs_entry_pars.AssignParameters(item_params);
542✔
807

808
    const auto& block_ids_param = xs_entry_pars.GetParam("block_ids");
542✔
809
    block_ids_param.RequireBlockTypeIs(ParameterBlockType::ARRAY);
542✔
810
    const auto& block_ids = block_ids_param.GetVectorValue<int>();
542✔
811
    auto xs = xs_entry_pars.GetSharedPtrParam<MultiGroupXS>("xs");
542✔
812
    for (const auto& block_id : block_ids)
1,169✔
813
      block_id_to_xs_map_[block_id] = xs;
627✔
814
  }
542✔
815

816
  // Assign placeholder unit densities
817
  densities_local_.assign(grid_->local_cells.size(), 1.0);
293✔
818
}
293✔
819

820
void
821
LBSProblem::InitializeMaterials()
305✔
822
{
823
  CALI_CXX_MARK_SCOPE("LBSProblem::InitializeMaterials");
305✔
824

825
  log.Log0Verbose1() << "Initializing Materials";
610✔
826

827
  // Create set of material ids locally relevant
828
  int invalid_mat_cell_count = 0;
305✔
829
  std::set<int> unique_block_ids;
305✔
830
  for (auto& cell : grid_->local_cells)
320,397✔
831
  {
832
    unique_block_ids.insert(cell.block_id);
320,092✔
833
    if (cell.block_id < 0 or (block_id_to_xs_map_.find(cell.block_id) == block_id_to_xs_map_.end()))
320,092✔
834
      ++invalid_mat_cell_count;
×
835
  }
836
  const auto& ghost_cell_ids = grid_->cells.GetGhostGlobalIDs();
305✔
837
  for (uint64_t cell_id : ghost_cell_ids)
45,320✔
838
  {
839
    const auto& cell = grid_->cells[cell_id];
45,015✔
840
    unique_block_ids.insert(cell.block_id);
45,015✔
841
    if (cell.block_id < 0 or (block_id_to_xs_map_.find(cell.block_id) == block_id_to_xs_map_.end()))
45,015✔
842
      ++invalid_mat_cell_count;
×
843
  }
844
  OpenSnLogicalErrorIf(invalid_mat_cell_count > 0,
305✔
845
                       std::to_string(invalid_mat_cell_count) +
846
                         " cells encountered with an invalid material id.");
847

848
  // Get ready for processing
849
  for (const auto& [blk_id, mat] : block_id_to_xs_map_)
960✔
850
  {
851
    mat->SetAdjointMode(options_.adjoint);
655✔
852

853
    OpenSnLogicalErrorIf(mat->GetNumGroups() < groups_.size(),
655✔
854
                         "Cross-sections for block \"" + std::to_string(blk_id) +
855
                           "\" have fewer groups (" + std::to_string(mat->GetNumGroups()) +
856
                           ") than the simulation (" + std::to_string(groups_.size()) + "). " +
857
                           "Cross-sections must have at least as many groups as the simulation.");
858
  }
859

860
  // Initialize precursor properties
861
  num_precursors_ = 0;
305✔
862
  max_precursors_per_material_ = 0;
305✔
863
  for (const auto& mat_id_xs : block_id_to_xs_map_)
960✔
864
  {
865
    const auto& xs = mat_id_xs.second;
655✔
866
    num_precursors_ += xs->GetNumPrecursors();
655✔
867
    if (xs->GetNumPrecursors() > max_precursors_per_material_)
655✔
868
      max_precursors_per_material_ = xs->GetNumPrecursors();
8✔
869
  }
870

871
  // if no precursors, turn off precursors
872
  if (num_precursors_ == 0)
305✔
873
    options_.use_precursors = false;
297✔
874

875
  // check compatibility when precursors are on
876
  if (options_.use_precursors)
305✔
877
  {
878
    for (const auto& [mat_id, xs] : block_id_to_xs_map_)
16✔
879
    {
880
      OpenSnLogicalErrorIf(xs->IsFissionable() and num_precursors_ == 0,
8✔
881
                           "Incompatible cross-section data encountered for material id " +
882
                             std::to_string(mat_id) + ". When delayed neutron data is present " +
883
                             "for one fissionable matrial, it must be present for all fissionable "
884
                             "materials.");
885
    }
886
  }
887

888
  // Update transport views if available
889
  if (grid_->local_cells.size() == cell_transport_views_.size())
305✔
890
    for (const auto& cell : grid_->local_cells)
10,812✔
891
    {
892
      const auto& xs_ptr = block_id_to_xs_map_[cell.block_id];
10,800✔
893
      auto& transport_view = cell_transport_views_[cell.local_id];
10,800✔
894
      transport_view.ReassignXS(*xs_ptr);
10,800✔
895
    }
896

897
  mpi_comm.barrier();
305✔
898
}
305✔
899

900
void
901
LBSProblem::InitializeSpatialDiscretization()
285✔
902
{
903
  CALI_CXX_MARK_SCOPE("LBSProblem::InitializeSpatialDiscretization");
285✔
904

905
  log.Log() << "Initializing spatial discretization.\n";
570✔
906
  discretization_ = PieceWiseLinearDiscontinuous::New(grid_);
285✔
907

908
  ComputeUnitIntegrals();
285✔
909
}
285✔
910

911
void
912
LBSProblem::ComputeUnitIntegrals()
293✔
913
{
914
  CALI_CXX_MARK_SCOPE("LBSProblem::ComputeUnitIntegrals");
293✔
915

916
  log.Log() << "Computing unit integrals.\n";
586✔
917
  const auto& sdm = *discretization_;
293✔
918

919
  const size_t num_local_cells = grid_->local_cells.size();
293✔
920
  unit_cell_matrices_.resize(num_local_cells);
293✔
921

922
  for (const auto& cell : grid_->local_cells)
309,585✔
923
    unit_cell_matrices_[cell.local_id] =
309,292✔
924
      ComputeUnitCellIntegrals(sdm, cell, grid_->GetCoordinateSystem());
309,292✔
925

926
  const auto ghost_ids = grid_->cells.GetGhostGlobalIDs();
293✔
927
  for (auto ghost_id : ghost_ids)
44,552✔
928
    unit_ghost_cell_matrices_[ghost_id] =
44,259✔
929
      ComputeUnitCellIntegrals(sdm, grid_->cells[ghost_id], grid_->GetCoordinateSystem());
88,518✔
930

931
  // Assessing global unit cell matrix storage
932
  std::array<size_t, 2> num_local_ucms = {unit_cell_matrices_.size(),
293✔
933
                                          unit_ghost_cell_matrices_.size()};
293✔
934
  std::array<size_t, 2> num_global_ucms = {0, 0};
293✔
935

936
  mpi_comm.all_reduce(num_local_ucms.data(), 2, num_global_ucms.data(), mpi::op::sum<size_t>());
293✔
937

938
  opensn::mpi_comm.barrier();
293✔
939
  log.Log() << "Ghost cell unit cell-matrix ratio: "
293✔
940
            << (double)num_global_ucms[1] * 100 / (double)num_global_ucms[0] << "%";
586✔
941
  log.Log() << "Cell matrices computed.";
586✔
942
}
293✔
943

944
void
945
LBSProblem::InitializeParrays()
293✔
946
{
947
  CALI_CXX_MARK_SCOPE("LBSProblem::InitializeParrays");
293✔
948

949
  log.Log() << "Initializing parallel arrays."
586✔
950
            << " G=" << num_groups_ << " M=" << num_moments_ << std::endl;
293✔
951

952
  // Initialize unknown
953
  // structure
954
  flux_moments_uk_man_.unknowns.clear();
293✔
955
  for (size_t m = 0; m < num_moments_; ++m)
1,120✔
956
  {
957
    flux_moments_uk_man_.AddUnknown(UnknownType::VECTOR_N, groups_.size());
827✔
958
    flux_moments_uk_man_.unknowns.back().name = "m" + std::to_string(m);
827✔
959
  }
960

961
  // Compute local # of dof
962
  local_node_count_ = discretization_->GetNumLocalNodes();
293✔
963
  global_node_count_ = discretization_->GetNumGlobalNodes();
293✔
964

965
  // Compute num of unknowns
966
  size_t num_grps = groups_.size();
293✔
967
  size_t local_unknown_count = local_node_count_ * num_grps * num_moments_;
293✔
968

969
  log.LogAllVerbose1() << "LBS Number of phi unknowns: " << local_unknown_count;
586✔
970

971
  // Size local vectors
972
  q_moments_local_.assign(local_unknown_count, 0.0);
293✔
973
  phi_old_local_.assign(local_unknown_count, 0.0);
293✔
974
  phi_new_local_.assign(local_unknown_count, 0.0);
293✔
975

976
  // Setup precursor vector
977
  if (options_.use_precursors)
293✔
978
  {
979
    size_t num_precursor_dofs = grid_->local_cells.size() * max_precursors_per_material_;
8✔
980
    precursor_new_local_.assign(num_precursor_dofs, 0.0);
8✔
981
  }
982

983
  // Initialize transport views
984
  // Transport views act as a data structure to store information
985
  // related to the transport simulation. The most prominent function
986
  // here is that it holds the means to know where a given cell's
987
  // transport quantities are located in the unknown vectors (i.e. phi)
988
  //
989
  // Also, for a given cell, within a given sweep chunk,
990
  // we need to solve a matrix which square size is the
991
  // amount of nodes on the cell. max_cell_dof_count is
992
  // initialized here.
993
  //
994
  size_t block_MG_counter = 0; // Counts the strides of moment and group
293✔
995

996
  const Vector3 ihat(1.0, 0.0, 0.0);
293✔
997
  const Vector3 jhat(0.0, 1.0, 0.0);
293✔
998
  const Vector3 khat(0.0, 0.0, 1.0);
293✔
999

1000
  min_cell_dof_count_ = static_cast<size_t>(-1);
293✔
1001
  max_cell_dof_count_ = 0;
293✔
1002
  cell_transport_views_.clear();
293✔
1003
  cell_transport_views_.reserve(grid_->local_cells.size());
293✔
1004
  for (auto& cell : grid_->local_cells)
309,585✔
1005
  {
1006
    size_t num_nodes = discretization_->GetCellNumNodes(cell);
309,292✔
1007

1008
    // compute cell volumes
1009
    double cell_volume = 0.0;
309,292✔
1010
    const auto& IntV_shapeI = unit_cell_matrices_[cell.local_id].intV_shapeI;
309,292✔
1011
    for (size_t i = 0; i < num_nodes; ++i)
2,626,530✔
1012
      cell_volume += IntV_shapeI(i);
2,317,238✔
1013

1014
    size_t cell_phi_address = block_MG_counter;
309,292✔
1015

1016
    const size_t num_faces = cell.faces.size();
309,292✔
1017
    std::vector<bool> face_local_flags(num_faces, true);
309,292✔
1018
    std::vector<int> face_locality(num_faces, opensn::mpi_comm.rank());
309,292✔
1019
    std::vector<const Cell*> neighbor_cell_ptrs(num_faces, nullptr);
309,292✔
1020
    bool cell_on_boundary = false;
309,292✔
1021
    int f = 0;
309,292✔
1022
    for (auto& face : cell.faces)
2,077,994✔
1023
    {
1024
      if (not face.has_neighbor)
1,768,702✔
1025
      {
1026
        Vector3& n = face.normal;
58,328✔
1027

1028
        int boundary_id = -1;
58,328✔
1029
        if (n.Dot(ihat) < -0.999)
58,328✔
1030
          boundary_id = XMIN;
1031
        else if (n.Dot(ihat) > 0.999)
49,947✔
1032
          boundary_id = XMAX;
1033
        else if (n.Dot(jhat) < -0.999)
41,728✔
1034
          boundary_id = YMIN;
1035
        else if (n.Dot(jhat) > 0.999)
33,541✔
1036
          boundary_id = YMAX;
1037
        else if (n.Dot(khat) < -0.999)
25,516✔
1038
          boundary_id = ZMIN;
1039
        else if (n.Dot(khat) > 0.999)
12,758✔
1040
          boundary_id = ZMAX;
1041

1042
        if (boundary_id >= 0)
1043
          face.neighbor_id = boundary_id;
58,328✔
1044
        cell_on_boundary = true;
58,328✔
1045

1046
        face_local_flags[f] = false;
58,328✔
1047
        face_locality[f] = -1;
58,328✔
1048
      } // if bndry
1049
      else
1050
      {
1051
        const int neighbor_partition = face.GetNeighborPartitionID(grid_.get());
1,710,374✔
1052
        face_local_flags[f] = (neighbor_partition == opensn::mpi_comm.rank());
1,710,374✔
1053
        face_locality[f] = neighbor_partition;
1,710,374✔
1054
        neighbor_cell_ptrs[f] = &grid_->cells[face.neighbor_id];
1,710,374✔
1055
      }
1056

1057
      ++f;
1,768,702✔
1058
    } // for f
1059

1060
    max_cell_dof_count_ = std::max(max_cell_dof_count_, num_nodes);
309,292✔
1061
    min_cell_dof_count_ = std::min(min_cell_dof_count_, num_nodes);
309,292✔
1062
    cell_transport_views_.emplace_back(cell_phi_address,
618,584✔
1063
                                       num_nodes,
1064
                                       num_grps,
1065
                                       num_moments_,
309,292✔
1066
                                       num_faces,
1067
                                       *block_id_to_xs_map_[cell.block_id],
309,292✔
1068
                                       cell_volume,
1069
                                       face_local_flags,
1070
                                       face_locality,
1071
                                       neighbor_cell_ptrs,
1072
                                       cell_on_boundary);
1073
    block_MG_counter += num_nodes * num_grps * num_moments_;
309,292✔
1074
  } // for local cell
309,292✔
1075

1076
  // Populate grid nodal mappings
1077
  // This is used in the Flux Data Structures (FLUDS)
1078
  grid_nodal_mappings_.clear();
293✔
1079
  grid_nodal_mappings_.reserve(grid_->local_cells.size());
293✔
1080
  for (auto& cell : grid_->local_cells)
309,585✔
1081
  {
1082
    CellFaceNodalMapping cell_nodal_mapping;
309,292✔
1083
    cell_nodal_mapping.reserve(cell.faces.size());
309,292✔
1084

1085
    for (auto& face : cell.faces)
2,077,994✔
1086
    {
1087
      std::vector<short> face_node_mapping;
1,768,702✔
1088
      std::vector<short> cell_node_mapping;
1,768,702✔
1089
      int adj_face_idx = -1;
1,768,702✔
1090

1091
      if (face.has_neighbor)
1,768,702✔
1092
      {
1093
        grid_->FindAssociatedVertices(face, face_node_mapping);
1,710,374✔
1094
        grid_->FindAssociatedCellVertices(face, cell_node_mapping);
1,710,374✔
1095
        adj_face_idx = face.GetNeighborAdjacentFaceIndex(grid_.get());
1,710,374✔
1096
      }
1097

1098
      cell_nodal_mapping.emplace_back(adj_face_idx, face_node_mapping, cell_node_mapping);
1,768,702✔
1099
    } // for f
1,768,702✔
1100

1101
    grid_nodal_mappings_.push_back(cell_nodal_mapping);
309,292✔
1102
  } // for local cell
309,292✔
1103

1104
  // Get grid localized communicator set
1105
  grid_local_comm_set_ = grid_->MakeMPILocalCommunicatorSet();
293✔
1106

1107
  // Initialize Field Functions
1108
  InitializeFieldFunctions();
293✔
1109

1110
  opensn::mpi_comm.barrier();
293✔
1111
  log.Log() << "Done with parallel arrays." << std::endl;
586✔
1112
}
293✔
1113

1114
void
1115
LBSProblem::InitializeFieldFunctions()
293✔
1116
{
1117
  CALI_CXX_MARK_SCOPE("LBSProblem::InitializeFieldFunctions");
293✔
1118

1119
  if (not field_functions_.empty())
293✔
1120
    return;
×
1121

1122
  // Initialize Field Functions for flux moments
1123
  phi_field_functions_local_map_.clear();
293✔
1124

1125
  for (size_t g = 0; g < groups_.size(); ++g)
19,936✔
1126
  {
1127
    for (size_t m = 0; m < num_moments_; ++m)
92,394✔
1128
    {
1129
      std::string prefix;
72,751✔
1130
      if (options_.field_function_prefix_option == "prefix")
72,751✔
1131
      {
1132
        prefix = options_.field_function_prefix;
72,751✔
1133
        if (not prefix.empty())
72,751✔
1134
          prefix += "_";
1✔
1135
      }
1136
      if (options_.field_function_prefix_option == "solver_name")
72,751✔
1137
        prefix = GetName() + "_";
×
1138

1139
      char buff[100];
72,751✔
1140
      snprintf(
72,751✔
1141
        buff, 99, "%sphi_g%03d_m%02d", prefix.c_str(), static_cast<int>(g), static_cast<int>(m));
1142
      const std::string name = std::string(buff);
72,751✔
1143

1144
      auto group_ff = std::make_shared<FieldFunctionGridBased>(
72,751✔
1145
        name, discretization_, Unknown(UnknownType::SCALAR));
72,751✔
1146

1147
      field_function_stack.push_back(group_ff);
145,502✔
1148
      field_functions_.push_back(group_ff);
72,751✔
1149

1150
      phi_field_functions_local_map_[{g, m}] = field_functions_.size() - 1;
72,751✔
1151
    } // for m
72,751✔
1152
  } // for g
1153

1154
  // Initialize power generation field function
1155
  if (options_.power_field_function_on)
293✔
1156
  {
1157
    std::string prefix;
4✔
1158
    if (options_.field_function_prefix_option == "prefix")
4✔
1159
    {
1160
      prefix = options_.field_function_prefix;
4✔
1161
      if (not prefix.empty())
4✔
1162
        prefix += "_";
×
1163
    }
1164
    if (options_.field_function_prefix_option == "solver_name")
4✔
1165
      prefix = GetName() + "_";
×
1166

1167
    auto power_ff = std::make_shared<FieldFunctionGridBased>(
4✔
1168
      prefix + "power_generation", discretization_, Unknown(UnknownType::SCALAR));
8✔
1169

1170
    field_function_stack.push_back(power_ff);
8✔
1171
    field_functions_.push_back(power_ff);
4✔
1172

1173
    power_gen_fieldfunc_local_handle_ = field_functions_.size() - 1;
4✔
1174
  }
4✔
1175
}
293✔
1176

1177
void
1178
LBSProblem::InitializeSolverSchemes()
293✔
1179
{
1180
  CALI_CXX_MARK_SCOPE("LBSProblem::InitializeSolverSchemes");
293✔
1181

1182
  log.Log() << "Initializing WGS and AGS solvers";
586✔
1183

1184
  InitializeWGSSolvers();
293✔
1185

1186
  ags_solver_ = std::make_shared<AGSLinearSolver>(*this, wgs_solvers_);
293✔
1187
  if (groupsets_.size() == 1)
293✔
1188
  {
1189
    ags_solver_->SetMaxIterations(1);
242✔
1190
    ags_solver_->SetVerbosity(false);
242✔
1191
  }
1192
  else
1193
  {
1194
    ags_solver_->SetMaxIterations(options_.max_ags_iterations);
51✔
1195
    ags_solver_->SetVerbosity(options_.verbose_ags_iterations);
51✔
1196
  }
1197
  ags_solver_->SetTolerance(options_.ags_tolerance);
293✔
1198
}
293✔
1199

1200
#ifndef __OPENSN_USE_CUDA__
1201
void
1202
LBSProblem::InitializeGPUExtras()
293✔
1203
{
1204
}
293✔
1205

1206
void
1207
LBSProblem::ResetGPUCarriers()
289✔
1208
{
1209
}
289✔
1210

1211
void
1212
LBSProblem::CheckCapableDevices()
×
1213
{
1214
}
×
1215
#endif // __OPENSN_USE_CUDA__
1216

1217
std::vector<double>
1218
LBSProblem::MakeSourceMomentsFromPhi()
4✔
1219
{
1220
  CALI_CXX_MARK_SCOPE("LBSProblem::MakeSourceMomentsFromPhi");
4✔
1221

1222
  size_t num_local_dofs = discretization_->GetNumLocalDOFs(flux_moments_uk_man_);
4✔
1223

1224
  std::vector<double> source_moments(num_local_dofs, 0.0);
4✔
1225
  for (auto& groupset : groupsets_)
8✔
1226
  {
1227
    active_set_source_function_(groupset,
4✔
1228
                                source_moments,
1229
                                phi_new_local_,
4✔
1230
                                APPLY_AGS_SCATTER_SOURCES | APPLY_WGS_SCATTER_SOURCES |
1231
                                  APPLY_AGS_FISSION_SOURCES | APPLY_WGS_FISSION_SOURCES);
4✔
1232
  }
1233

1234
  return source_moments;
4✔
1235
}
4✔
1236

1237
void
1238
LBSProblem::UpdateFieldFunctions()
305✔
1239
{
1240
  CALI_CXX_MARK_SCOPE("LBSProblem::UpdateFieldFunctions");
305✔
1241

1242
  const auto& sdm = *discretization_;
305✔
1243
  const auto& phi_uk_man = flux_moments_uk_man_;
305✔
1244

1245
  // Update flux moments
1246
  for (const auto& [g_and_m, ff_index] : phi_field_functions_local_map_)
73,104✔
1247
  {
1248
    const size_t g = g_and_m.first;
72,799✔
1249
    const size_t m = g_and_m.second;
72,799✔
1250

1251
    std::vector<double> data_vector_local(local_node_count_, 0.0);
72,799✔
1252

1253
    for (const auto& cell : grid_->local_cells)
17,509,640✔
1254
    {
1255
      const auto& cell_mapping = sdm.GetCellMapping(cell);
17,436,841✔
1256
      const size_t num_nodes = cell_mapping.GetNumNodes();
17,436,841✔
1257

1258
      for (size_t i = 0; i < num_nodes; ++i)
123,343,861✔
1259
      {
1260
        const auto imapA = sdm.MapDOFLocal(cell, i, phi_uk_man, m, g);
105,907,020✔
1261
        const auto imapB = sdm.MapDOFLocal(cell, i);
105,907,020✔
1262

1263
        data_vector_local[imapB] = phi_new_local_[imapA];
105,907,020✔
1264
      } // for node
1265
    } // for cell
1266

1267
    auto& ff_ptr = field_functions_.at(ff_index);
72,799✔
1268
    ff_ptr->UpdateFieldVector(data_vector_local);
72,799✔
1269
  }
72,799✔
1270

1271
  // Update power generation
1272
  if (options_.power_field_function_on)
305✔
1273
  {
1274
    std::vector<double> data_vector_local(local_node_count_, 0.0);
4✔
1275

1276
    double local_total_power = 0.0;
4✔
1277
    for (const auto& cell : grid_->local_cells)
83,268✔
1278
    {
1279
      const auto& cell_mapping = sdm.GetCellMapping(cell);
83,264✔
1280
      const size_t num_nodes = cell_mapping.GetNumNodes();
83,264✔
1281

1282
      const auto& Vi = unit_cell_matrices_[cell.local_id].intV_shapeI;
83,264✔
1283

1284
      const auto& xs = block_id_to_xs_map_.at(cell.block_id);
83,264✔
1285

1286
      if (not xs->IsFissionable())
83,264✔
1287
        continue;
56,360✔
1288

1289
      for (size_t i = 0; i < num_nodes; ++i)
134,520✔
1290
      {
1291
        const auto imapA = sdm.MapDOFLocal(cell, i);
107,616✔
1292
        const auto imapB = sdm.MapDOFLocal(cell, i, phi_uk_man, 0, 0);
107,616✔
1293

1294
        double nodal_power = 0.0;
1295
        for (size_t g = 0; g < groups_.size(); ++g)
860,928✔
1296
        {
1297
          const double sigma_fg = xs->GetSigmaFission()[g];
753,312✔
1298
          // const double kappa_g = xs->Kappa()[g];
1299
          const double kappa_g = options_.power_default_kappa;
753,312✔
1300

1301
          nodal_power += kappa_g * sigma_fg * phi_new_local_[imapB + g];
753,312✔
1302
        } // for g
1303

1304
        data_vector_local[imapA] = nodal_power;
107,616✔
1305
        local_total_power += nodal_power * Vi(i);
107,616✔
1306
      } // for node
1307
    } // for cell
1308

1309
    if (options_.power_normalization > 0.0)
4✔
1310
    {
1311
      double global_total_power;
4✔
1312
      mpi_comm.all_reduce(local_total_power, global_total_power, mpi::op::sum<double>());
4✔
1313

1314
      Scale(data_vector_local, options_.power_normalization / global_total_power);
4✔
1315
    }
1316

1317
    const size_t ff_index = power_gen_fieldfunc_local_handle_;
4✔
1318

1319
    auto& ff_ptr = field_functions_.at(ff_index);
4✔
1320
    ff_ptr->UpdateFieldVector(data_vector_local);
4✔
1321

1322
  } // if power enabled
4✔
1323
}
305✔
1324

1325
void
1326
LBSProblem::SetPhiFromFieldFunctions(PhiSTLOption which_phi,
×
1327
                                     const std::vector<size_t>& m_indices,
1328
                                     const std::vector<size_t>& g_indices)
1329
{
1330
  CALI_CXX_MARK_SCOPE("LBSProblem::SetPhiFromFieldFunctions");
×
1331

1332
  std::vector<size_t> m_ids_to_copy = m_indices;
×
1333
  std::vector<size_t> g_ids_to_copy = g_indices;
×
1334
  if (m_indices.empty())
×
1335
    for (size_t m = 0; m < num_moments_; ++m)
×
1336
      m_ids_to_copy.push_back(m);
×
1337
  if (g_ids_to_copy.empty())
×
1338
    for (size_t g = 0; g < num_groups_; ++g)
×
1339
      g_ids_to_copy.push_back(g);
×
1340

1341
  const auto& sdm = *discretization_;
×
1342
  const auto& phi_uk_man = flux_moments_uk_man_;
×
1343

1344
  for (const size_t m : m_ids_to_copy)
×
1345
  {
1346
    for (const size_t g : g_ids_to_copy)
×
1347
    {
1348
      const size_t ff_index = phi_field_functions_local_map_.at({g, m});
×
1349
      const auto& ff_ptr = field_functions_.at(ff_index);
×
1350
      const auto& ff_data = ff_ptr->GetLocalFieldVector();
×
1351

1352
      for (const auto& cell : grid_->local_cells)
×
1353
      {
1354
        const auto& cell_mapping = sdm.GetCellMapping(cell);
×
1355
        const size_t num_nodes = cell_mapping.GetNumNodes();
×
1356

1357
        for (size_t i = 0; i < num_nodes; ++i)
×
1358
        {
1359
          const auto imapA = sdm.MapDOFLocal(cell, i);
×
1360
          const auto imapB = sdm.MapDOFLocal(cell, i, phi_uk_man, m, g);
×
1361

1362
          if (which_phi == PhiSTLOption::PHI_OLD)
×
1363
            phi_old_local_[imapB] = ff_data[imapA];
×
1364
          else if (which_phi == PhiSTLOption::PHI_NEW)
×
1365
            phi_new_local_[imapB] = ff_data[imapA];
×
1366
        } // for node
1367
      } // for cell
1368
    } // for g
1369
  } // for m
1370
}
×
1371

1372
LBSProblem::~LBSProblem()
289✔
1373
{
1374
  ResetGPUCarriers();
1375
}
1,445✔
1376

289✔
1377
void
1378
LBSProblem::SetAdjoint(bool adjoint)
305✔
1379
{
1380
  if (adjoint != options_.adjoint)
305✔
1381
  {
1382
    options_.adjoint = adjoint;
12✔
1383

1384
    // If a discretization exists, the solver has already been initialized.
1385
    // Reinitialize the materials to obtain the appropriate xs and clear the
1386
    // sources to prepare for defining the adjoint problem
1387
    if (discretization_)
12✔
1388
    {
1389
      // The materials are reinitialized here to ensure that the proper cross sections
1390
      // are available to the solver. Because an adjoint solve requires volumetric or
1391
      // point sources, the material-based sources are not set within the initialize routine.
1392
      InitializeMaterials();
12✔
1393

1394
      // Forward and adjoint sources are fundamentally different, so any existing sources
1395
      // should be cleared and reset through options upon changing modes.
1396
      point_sources_.clear();
12✔
1397
      volumetric_sources_.clear();
12✔
1398
      boundary_preferences_.clear();
12✔
1399

1400
      // Set all solutions to zero.
1401
      phi_old_local_.assign(phi_old_local_.size(), 0.0);
12✔
1402
      phi_new_local_.assign(phi_new_local_.size(), 0.0);
12✔
1403
      ZeroSolutions();
12✔
1404
      precursor_new_local_.assign(precursor_new_local_.size(), 0.0);
12✔
1405
    }
1406
  }
1407
}
305✔
1408

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