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

Open-Sn / opensn / 17570347780

08 Sep 2025 10:28PM UTC coverage: 74.731% (+0.02%) from 74.716%
17570347780

push

github

web-flow
Merge pull request #723 from wdhawkins/boundary_conditions

Adding boundary conditions as an optional solver input parameter

11 of 11 new or added lines in 1 file covered. (100.0%)

106 existing lines in 2 files now uncovered.

17718 of 23709 relevant lines covered (74.73%)

44973220.69 hits per line

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

84.93
/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()
263✔
42
{
43
  InputParameters params = Problem::GetInputParameters();
263✔
44

45
  params.ChangeExistingParamToOptional("name", "LBSDatablock");
526✔
46

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

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

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

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

59
  params.AddRequiredParameter<size_t>("scattering_order",
526✔
60
                                      "The level of harmonic expansion for the scattering source.");
61

62
  params.AddOptionalParameterArray<std::shared_ptr<VolumetricSource>>(
526✔
63
    "volumetric_sources", {}, "An array of handles to volumetric sources.");
64

65
  params.AddOptionalParameterArray<std::shared_ptr<PointSource>>(
526✔
66
    "point_sources", {}, "An array of point sources.");
67

68
  params.AddOptionalParameterArray(
526✔
69
    "boundary_conditions", {}, "An array containing tables for each boundary specification.");
70
  params.LinkParameterToBlock("boundary_conditions", "BoundaryOptionsBlock");
526✔
71

72
  params.AddOptionalParameterBlock(
526✔
73
    "options", ParameterBlock(), "Block of options. See <TT>OptionsBlock</TT>.");
526✔
74
  params.LinkParameterToBlock("options", "OptionsBlock");
526✔
75

76
  params.AddOptionalParameter("use_gpus", false, "Offload the sweep computation to GPUs.");
526✔
77

78
  return params;
263✔
UNCOV
79
}
×
80

81
LBSProblem::LBSProblem(const InputParameters& params)
263✔
82
  : Problem(params),
83
    scattering_order_(params.GetParamValue<size_t>("scattering_order")),
263✔
84
    grid_(params.GetSharedPtrParam<MeshContinuum>("mesh")),
263✔
85
    use_gpus_(params.GetParamValue<bool>("use_gpus"))
789✔
86
{
87
  // Make groups
88
  const auto num_groups = params.GetParamValue<size_t>("num_groups");
263✔
89
  for (size_t g = 0; g < num_groups; ++g)
18,578✔
90
    groups_.emplace_back(static_cast<int>(g));
18,315✔
91

92
  // Make groupsets
93
  const auto& groupsets_array = params.GetParam("groupsets");
263✔
94

95
  const size_t num_gs = groupsets_array.GetNumParameters();
263✔
96
  for (size_t gs = 0; gs < num_gs; ++gs)
581✔
97
  {
98
    const auto& groupset_params = groupsets_array.GetParam(gs);
318✔
99

100
    InputParameters gs_input_params = LBSGroupset::GetInputParameters();
318✔
101
    gs_input_params.SetObjectType("LBSProblem:LBSGroupset");
318✔
102
    gs_input_params.AssignParameters(groupset_params);
318✔
103

104
    groupsets_.emplace_back(gs_input_params, gs, *this);
318✔
105
  }
318✔
106

107
  // Build XS map
108
  const auto& xs_array = params.GetParam("xs_map");
263✔
109
  const size_t num_xs = xs_array.GetNumParameters();
263✔
110
  for (size_t i = 0; i < num_xs; ++i)
671✔
111
  {
112
    const auto& item_params = xs_array.GetParam(i);
408✔
113
    InputParameters xs_entry_pars = GetXSMapEntryBlock();
408✔
114
    xs_entry_pars.AssignParameters(item_params);
408✔
115

116
    const auto& block_ids_param = xs_entry_pars.GetParam("block_ids");
408✔
117
    block_ids_param.RequireBlockTypeIs(ParameterBlockType::ARRAY);
408✔
118
    const auto& block_ids = block_ids_param.GetVectorValue<int>();
408✔
119
    auto xs = xs_entry_pars.GetSharedPtrParam<MultiGroupXS>("xs");
408✔
120
    for (const auto& block_id : block_ids)
901✔
121
      block_id_to_xs_map_[block_id] = xs;
493✔
122
  }
408✔
123

124
  // Initialize sources
125
  if (params.Has("volumetric_sources"))
263✔
126
  {
127
    auto& vol_srcs = params.GetParam("volumetric_sources");
263✔
128
    vol_srcs.RequireBlockTypeIs(ParameterBlockType::ARRAY);
263✔
129
    for (const auto& src : vol_srcs)
548✔
130
      volumetric_sources_.push_back(src.GetValue<std::shared_ptr<VolumetricSource>>());
570✔
131
  }
132

133
  if (params.Has("point_sources"))
263✔
134
  {
135
    auto& pt_srcs = params.GetParam("point_sources");
263✔
136
    pt_srcs.RequireBlockTypeIs(ParameterBlockType::ARRAY);
263✔
137
    for (const auto& src : pt_srcs)
271✔
138
      point_sources_.push_back(src.GetValue<std::shared_ptr<PointSource>>());
16✔
139
  }
140

141
  // Initialize boundary conditions
142
  if (params.Has("boundary_conditions"))
263✔
143
  {
144
    auto& bcs = params.GetParam("boundary_conditions");
263✔
145
    bcs.RequireBlockTypeIs(ParameterBlockType::ARRAY);
263✔
146
    for (size_t b = 0; b < bcs.GetNumParameters(); ++b)
696✔
147
    {
148
      auto bndry_params = GetBoundaryOptionsBlock();
433✔
149
      bndry_params.AssignParameters(bcs.GetParam(b));
433✔
150
      SetBoundaryOptions(bndry_params);
433✔
151
    }
433✔
152
  }
153

154
  // Check system for GPU acceleration
155
  if (use_gpus_)
263✔
156
  {
157
#ifdef __OPENSN_USE_CUDA__
158
    CheckCapableDevices();
159
#else
UNCOV
160
    throw std::invalid_argument(
×
UNCOV
161
      "GPU support was requested, but OpenSn was built without CUDA enabled.\n");
×
162
#endif // __OPENSN_USE_CUDA__
163
  }
164

165
  // Options
166
  if (params.IsParameterValid("options"))
263✔
167
  {
168
    auto options_params = LBSProblem::GetOptionsBlock();
153✔
169
    options_params.AssignParameters(params.GetParam("options"));
155✔
170
    SetOptions(options_params);
151✔
171
  }
153✔
172
}
283✔
173

174
LBSOptions&
175
LBSProblem::GetOptions()
16,976✔
176
{
177
  return options_;
16,976✔
178
}
179

180
const LBSOptions&
181
LBSProblem::GetOptions() const
529,491,994✔
182
{
183
  return options_;
529,491,994✔
184
}
185

186
size_t
187
LBSProblem::GetNumMoments() const
124,725✔
188
{
189
  return num_moments_;
124,725✔
190
}
191

192
size_t
193
LBSProblem::GetNumGroups() const
50,321✔
194
{
195
  return num_groups_;
50,321✔
196
}
197

198
size_t
199
LBSProblem::GetScatteringOrder() const
4✔
200
{
201
  return scattering_order_;
4✔
202
}
203

204
size_t
UNCOV
205
LBSProblem::GetNumPrecursors() const
×
206
{
UNCOV
207
  return num_precursors_;
×
208
}
209

210
size_t
211
LBSProblem::GetMaxPrecursorsPerMaterial() const
8✔
212
{
213
  return max_precursors_per_material_;
8✔
214
}
215

216
void
UNCOV
217
LBSProblem::AddGroup(int id)
×
218
{
UNCOV
219
  if (id < 0)
×
220
    groups_.emplace_back(static_cast<int>(groups_.size()));
×
221
  else
222
    groups_.emplace_back(id);
×
UNCOV
223
}
×
224

225
const std::vector<LBSGroup>&
226
LBSProblem::GetGroups() const
136,548✔
227
{
228
  return groups_;
136,548✔
229
}
230

231
void
232
LBSProblem::AddGroupset()
×
233
{
234
  groupsets_.emplace_back(static_cast<int>(groupsets_.size()));
×
235
}
×
236

237
std::vector<LBSGroupset>&
238
LBSProblem::GetGroupsets()
8,170,719✔
239
{
240
  return groupsets_;
8,170,719✔
241
}
242

243
const std::vector<LBSGroupset>&
244
LBSProblem::GetGroupsets() const
×
245
{
246
  return groupsets_;
×
247
}
248

249
void
250
LBSProblem::AddPointSource(std::shared_ptr<PointSource> point_source)
×
251
{
252
  point_sources_.push_back(point_source);
×
253
}
×
254

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

261
const std::vector<std::shared_ptr<PointSource>>&
262
LBSProblem::GetPointSources() const
7,334✔
263
{
264
  return point_sources_;
7,334✔
265
}
266

267
void
UNCOV
268
LBSProblem::AddVolumetricSource(std::shared_ptr<VolumetricSource> volumetric_source)
×
269
{
UNCOV
270
  volumetric_sources_.push_back(volumetric_source);
×
UNCOV
271
}
×
272

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

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

285
const std::map<int, std::shared_ptr<MultiGroupXS>>&
286
LBSProblem::GetMatID2XSMap() const
6,681✔
287
{
288
  return block_id_to_xs_map_;
6,681✔
289
}
290

291
std::shared_ptr<MeshContinuum>
292
LBSProblem::GetGrid() const
179,720✔
293
{
294
  return grid_;
179,720✔
295
}
296

297
const SpatialDiscretization&
298
LBSProblem::GetSpatialDiscretization() const
63,896✔
299
{
300
  return *discretization_;
63,896✔
301
}
302

303
const std::vector<UnitCellMatrices>&
304
LBSProblem::GetUnitCellMatrices() const
7,231✔
305
{
306
  return unit_cell_matrices_;
7,231✔
307
}
308

309
const std::map<uint64_t, UnitCellMatrices>&
310
LBSProblem::GetUnitGhostCellMatrices() const
17✔
311
{
312
  return unit_ghost_cell_matrices_;
17✔
313
}
314

315
const std::vector<CellLBSView>&
316
LBSProblem::GetCellTransportViews() const
189,732✔
317
{
318
  return cell_transport_views_;
189,732✔
319
}
320

321
const UnknownManager&
322
LBSProblem::GetUnknownManager() const
26,663✔
323
{
324
  return flux_moments_uk_man_;
26,663✔
325
}
326

327
size_t
328
LBSProblem::GetLocalNodeCount() const
3,017✔
329
{
330
  return local_node_count_;
3,017✔
331
}
332

333
size_t
334
LBSProblem::GetGlobalNodeCount() const
1,418✔
335
{
336
  return global_node_count_;
1,418✔
337
}
338

339
std::vector<double>&
340
LBSProblem::GetQMomentsLocal()
37,229✔
341
{
342
  return q_moments_local_;
37,229✔
343
}
344

345
const std::vector<double>&
346
LBSProblem::GetQMomentsLocal() const
×
347
{
348
  return q_moments_local_;
×
349
}
350

351
std::vector<double>&
352
LBSProblem::GetExtSrcMomentsLocal()
4✔
353
{
354
  return ext_src_moments_local_;
4✔
355
}
356

357
const std::vector<double>&
358
LBSProblem::GetExtSrcMomentsLocal() const
37,063✔
359
{
360
  return ext_src_moments_local_;
37,063✔
361
}
362

363
std::vector<double>&
364
LBSProblem::GetPhiOldLocal()
56,603✔
365
{
366
  return phi_old_local_;
56,603✔
367
}
368

369
const std::vector<double>&
370
LBSProblem::GetPhiOldLocal() const
×
371
{
372
  return phi_old_local_;
×
373
}
374

375
std::vector<double>&
376
LBSProblem::GetPhiNewLocal()
59,121✔
377
{
378
  return phi_new_local_;
59,121✔
379
}
380

381
const std::vector<double>&
382
LBSProblem::GetPhiNewLocal() const
×
383
{
384
  return phi_new_local_;
×
385
}
386

387
std::vector<double>&
388
LBSProblem::GetPrecursorsNewLocal()
16✔
389
{
390
  return precursor_new_local_;
16✔
391
}
392

393
const std::vector<double>&
UNCOV
394
LBSProblem::GetPrecursorsNewLocal() const
×
395
{
UNCOV
396
  return precursor_new_local_;
×
397
}
398

399
std::vector<std::vector<double>>&
400
LBSProblem::GetPsiNewLocal()
31✔
401
{
402
  return psi_new_local_;
31✔
403
}
404

405
const std::vector<std::vector<double>>&
UNCOV
406
LBSProblem::GetPsiNewLocal() const
×
407
{
UNCOV
408
  return psi_new_local_;
×
409
}
410

411
std::vector<double>&
412
LBSProblem::GetDensitiesLocal()
250✔
413
{
414
  return densities_local_;
250✔
415
}
416

417
const std::vector<double>&
418
LBSProblem::GetDensitiesLocal() const
37,063✔
419
{
420
  return densities_local_;
37,063✔
421
}
422

423
SetSourceFunction
424
LBSProblem::GetActiveSetSourceFunction() const
4,138✔
425
{
426
  return active_set_source_function_;
4,138✔
427
}
428

429
std::shared_ptr<AGSLinearSolver>
430
LBSProblem::GetAGSSolver()
241✔
431
{
432
  return ags_solver_;
241✔
433
}
434

435
std::vector<std::shared_ptr<LinearSolver>>&
436
LBSProblem::GetWGSSolvers()
113✔
437
{
438
  return wgs_solvers_;
113✔
439
}
440

441
WGSContext&
442
LBSProblem::GetWGSContext(int groupset_id)
11,484✔
443
{
444
  auto& wgs_solver = wgs_solvers_[groupset_id];
11,484✔
445
  auto raw_context = wgs_solver->GetContext();
11,484✔
446
  auto wgs_context_ptr = std::dynamic_pointer_cast<WGSContext>(raw_context);
11,484✔
447
  OpenSnLogicalErrorIf(not wgs_context_ptr, "Failed to cast WGSContext");
11,484✔
448
  return *wgs_context_ptr;
11,484✔
449
}
22,968✔
450

451
std::map<uint64_t, BoundaryPreference>&
452
LBSProblem::GetBoundaryPreferences()
362✔
453
{
454
  return boundary_preferences_;
362✔
455
}
456

457
std::pair<size_t, size_t>
UNCOV
458
LBSProblem::GetNumPhiIterativeUnknowns()
×
459
{
UNCOV
460
  const auto& sdm = *discretization_;
×
UNCOV
461
  const size_t num_local_phi_dofs = sdm.GetNumLocalDOFs(flux_moments_uk_man_);
×
UNCOV
462
  const size_t num_global_phi_dofs = sdm.GetNumGlobalDOFs(flux_moments_uk_man_);
×
463

UNCOV
464
  return {num_local_phi_dofs, num_global_phi_dofs};
×
465
}
466

467
size_t
468
LBSProblem::MapPhiFieldFunction(size_t g, size_t m) const
20,822✔
469
{
470
  OpenSnLogicalErrorIf(phi_field_functions_local_map_.count({g, m}) == 0,
20,822✔
471
                       std::string("Failure to map phi field function g") + std::to_string(g) +
472
                         " m" + std::to_string(m));
473

474
  return phi_field_functions_local_map_.at({g, m});
20,822✔
475
}
476

477
std::shared_ptr<FieldFunctionGridBased>
UNCOV
478
LBSProblem::GetPowerFieldFunction() const
×
479
{
UNCOV
480
  OpenSnLogicalErrorIf(not options_.power_field_function_on,
×
481
                       "Called when options_.power_field_function_on == false");
482

UNCOV
483
  return field_functions_[power_gen_fieldfunc_local_handle_];
×
484
}
485

486
InputParameters
487
LBSProblem::GetOptionsBlock()
344✔
488
{
489
  InputParameters params;
344✔
490

491
  params.SetGeneralDescription("Set options from a large list of parameters");
688✔
492
  params.AddOptionalParameter("max_mpi_message_size",
688✔
493
                              32768,
494
                              "The maximum MPI message size used during sweep initialization.");
495
  params.AddOptionalParameter(
688✔
496
    "restart_writes_enabled", false, "Flag that controls writing of restart dumps");
497
  params.AddOptionalParameter("write_delayed_psi_to_restart",
688✔
498
                              true,
499
                              "Flag that controls writing of delayed angular fluxes to restarts.");
500
  params.AddOptionalParameter(
688✔
501
    "read_restart_path", "", "Full path for reading restart dumps including file stem.");
502
  params.AddOptionalParameter(
688✔
503
    "write_restart_path", "", "Full path for writing restart dumps including file stem.");
504
  params.AddOptionalParameter("write_restart_time_interval",
688✔
505
                              0,
506
                              "Time interval in seconds at which restart data is to be written.");
507
  params.AddOptionalParameter(
688✔
508
    "use_precursors", false, "Flag for using delayed neutron precursors.");
509
  params.AddOptionalParameter("use_source_moments",
688✔
510
                              false,
511
                              "Flag for ignoring fixed sources and selectively using source "
512
                              "moments obtained elsewhere.");
513
  params.AddOptionalParameter(
688✔
514
    "save_angular_flux", false, "Flag indicating whether angular fluxes are to be stored or not.");
515
  params.AddOptionalParameter(
688✔
516
    "adjoint", false, "Flag for toggling whether the solver is in adjoint mode.");
517
  params.AddOptionalParameter(
688✔
518
    "verbose_inner_iterations", true, "Flag to control verbosity of inner iterations.");
519
  params.AddOptionalParameter(
688✔
520
    "verbose_outer_iterations", true, "Flag to control verbosity of across-groupset iterations.");
521
  params.AddOptionalParameter(
688✔
522
    "max_ags_iterations", 100, "Maximum number of across-groupset iterations.");
523
  params.AddOptionalParameter("ags_tolerance", 1.0e-6, "Across-groupset iterations tolerance.");
688✔
524
  params.AddOptionalParameter("ags_convergence_check",
688✔
525
                              "l2",
526
                              "Type of convergence check for AGS iterations. Valid values are "
527
                              "`\"l2\"` and '\"pointwise\"'");
528
  params.AddOptionalParameter(
688✔
529
    "verbose_ags_iterations", true, "Flag to control verbosity of across-groupset iterations.");
530
  params.AddOptionalParameter("power_field_function_on",
688✔
531
                              false,
532
                              "Flag to control the creation of the power generation field "
533
                              "function. If set to `true` then a field function will be created "
534
                              "with the general name <solver_name>_power_generation`.");
535
  params.AddOptionalParameter("power_default_kappa",
688✔
536
                              3.20435e-11,
537
                              "Default `kappa` value (Energy released per fission) to use for "
538
                              "power generation when cross sections do not have `kappa` values. "
539
                              "Default: 3.20435e-11 Joule (corresponding to 200 MeV per fission).");
540
  params.AddOptionalParameter("power_normalization",
688✔
541
                              -1.0,
542
                              "Power normalization factor to use. Supply a negative or zero number "
543
                              "to turn this off.");
544
  params.AddOptionalParameter("field_function_prefix_option",
688✔
545
                              "prefix",
546
                              "Prefix option on field function names. Default: `\"prefix\"`. Can "
547
                              "be `\"prefix\"` or `\"solver_name\"`. By default this option uses "
548
                              "the value of the `field_function_prefix` parameter. If this "
549
                              "parameter is not set, flux field functions will be exported as "
550
                              "`phi_gXXX_mYYY` where `XXX` is the zero padded 3 digit group number "
551
                              "and `YYY` is the zero padded 3 digit moment.");
552
  params.AddOptionalParameter("field_function_prefix",
688✔
553
                              "",
554
                              "Prefix to use on all field functions. Default: `\"\"`. By default "
555
                              "this option is empty. Ff specified, flux moments will be exported "
556
                              "as `prefix_phi_gXXX_mYYY` where `XXX` is the zero padded 3 digit "
557
                              "group number and `YYY` is the zero padded 3 digit moment. The "
558
                              "underscore after \"prefix\" is added automatically.");
559
  params.AddOptionalParameterArray(
688✔
560
    "boundary_conditions", {}, "An array containing tables for each boundary specification.");
561
  params.LinkParameterToBlock("boundary_conditions", "BoundaryOptionsBlock");
688✔
562
  params.AddOptionalParameter("clear_boundary_conditions",
688✔
563
                              false,
564
                              "Clears all boundary conditions. If no additional boundary "
565
                              "conditions are supplied, this results in all boundaries being "
566
                              "vacuum.");
567
  params.AddOptionalParameterArray<std::shared_ptr<PointSource>>(
688✔
568
    "point_sources", {}, "An array of point sources.");
569
  params.AddOptionalParameter("clear_point_sources", false, "Clears all point sources.");
688✔
570
  params.AddOptionalParameterArray<std::shared_ptr<VolumetricSource>>(
688✔
571
    "volumetric_sources", {}, "An array of handles to volumetric sources.");
572
  params.AddOptionalParameter("clear_volumetric_sources", false, "Clears all volumetric sources.");
688✔
573
  params.ConstrainParameterRange("ags_convergence_check",
1,032✔
574
                                 AllowableRangeList::New({"l2", "pointwise"}));
344✔
575
  params.ConstrainParameterRange("field_function_prefix_option",
1,032✔
576
                                 AllowableRangeList::New({"prefix", "solver_name"}));
344✔
577

578
  return params;
344✔
UNCOV
579
}
×
580

581
InputParameters
582
LBSProblem::GetBoundaryOptionsBlock()
439✔
583
{
584
  InputParameters params;
439✔
585

586
  params.SetGeneralDescription("Set options for boundary conditions.");
878✔
587
  params.AddRequiredParameter<std::string>("name",
878✔
588
                                           "Boundary name that identifies the specific boundary");
589
  params.AddRequiredParameter<std::string>("type", "Boundary type specification.");
878✔
590
  params.AddOptionalParameterArray<double>("group_strength",
878✔
591
                                           {},
592
                                           "Required only if \"type\" is \"isotropic\". An array "
593
                                           "of isotropic strength per group");
594
  params.AddOptionalParameter(
878✔
595
    "function_name", "", "Text name of the function to be called for this boundary condition.");
596
  params.ConstrainParameterRange(
1,317✔
597
    "name", AllowableRangeList::New({"xmin", "xmax", "ymin", "ymax", "zmin", "zmax"}));
439✔
598
  params.ConstrainParameterRange("type",
1,317✔
599
                                 AllowableRangeList::New({"vacuum", "isotropic", "reflecting"}));
439✔
600

601
  return params;
439✔
UNCOV
602
}
×
603

604
InputParameters
605
LBSProblem::GetXSMapEntryBlock()
408✔
606
{
607
  InputParameters params;
408✔
608
  params.SetGeneralDescription("Set the cross-section map for the solver.");
816✔
609
  params.AddRequiredParameterArray("block_ids", "Mesh block IDs");
816✔
610
  params.AddRequiredParameter<std::shared_ptr<MultiGroupXS>>("xs", "Cross-section object");
816✔
611
  return params;
408✔
612
}
×
613

614
void
615
LBSProblem::SetOptions(const InputParameters& input)
171✔
616
{
617
  auto params = LBSProblem::GetOptionsBlock();
171✔
618
  params.AssignParameters(input);
171✔
619

620
  // Handle order sensitive options
621
  if (params.IsParameterValid("clear_boundary_conditions"))
171✔
622
  {
623
    if (params.GetParamValue<bool>("clear_boundary_conditions"))
171✔
UNCOV
624
      boundary_preferences_.clear();
×
625
  }
626

627
  if (params.IsParameterValid("clear_point_sources"))
171✔
628
  {
629
    if (params.GetParamValue<bool>("clear_point_sources"))
171✔
UNCOV
630
      point_sources_.clear();
×
631
  }
632

633
  if (params.IsParameterValid("clear_volumetric_sources"))
171✔
634
  {
635
    if (params.GetParamValue<bool>("clear_volumetric_sources"))
171✔
UNCOV
636
      volumetric_sources_.clear();
×
637
  }
638

639
  if (params.IsParameterValid("adjoint"))
171✔
640
  {
641
    const bool adjoint = params.GetParamValue<bool>("adjoint");
171✔
642
    if (adjoint != options_.adjoint)
171✔
643
    {
644
      options_.adjoint = adjoint;
12✔
645

646
      // If a discretization exists, the solver has already been initialized.
647
      // Reinitialize the materials to obtain the appropriate xs and clear the
648
      // sources to prepare for defining the adjoint problem
649
      if (discretization_)
12✔
650
      {
651
        // The materials are reinitialized here to ensure that the proper cross sections
652
        // are available to the solver. Because an adjoint solve requires volumetric or
653
        // point sources, the material-based sources are not set within the initialize routine.
654
        InitializeMaterials();
12✔
655

656
        // Forward and adjoint sources are fundamentally different, so any existing sources
657
        // should be cleared and reset through options upon changing modes.
658
        point_sources_.clear();
12✔
659
        volumetric_sources_.clear();
12✔
660
        boundary_preferences_.clear();
12✔
661

662
        // Set all solutions to zero.
663
        phi_old_local_.assign(phi_old_local_.size(), 0.0);
12✔
664
        phi_new_local_.assign(phi_new_local_.size(), 0.0);
12✔
665
        for (auto& psi : psi_new_local_)
24✔
666
          psi.assign(psi.size(), 0.0);
12✔
667
        precursor_new_local_.assign(precursor_new_local_.size(), 0.0);
12✔
668
      }
669
    }
670
  }
671

672
  // Handle order insensitive options
673
  for (size_t p = 0; p < params.GetNumParameters(); ++p)
4,788✔
674
  {
675
    const auto& spec = params.GetParam(p);
4,617✔
676

677
    if (spec.GetName() == "max_mpi_message_size")
4,617✔
678
      options_.max_mpi_message_size = spec.GetValue<int>();
171✔
679

680
    else if (spec.GetName() == "restart_writes_enabled")
4,446✔
681
      options_.restart_writes_enabled = spec.GetValue<bool>();
171✔
682

683
    else if (spec.GetName() == "write_delayed_psi_to_restart")
4,275✔
684
      options_.write_delayed_psi_to_restart = spec.GetValue<bool>();
171✔
685

686
    else if (spec.GetName() == "read_restart_path")
4,104✔
687
    {
688
      options_.read_restart_path = spec.GetValue<std::string>();
171✔
689
      if (not options_.read_restart_path.empty())
171✔
690
        options_.read_restart_path += std::to_string(opensn::mpi_comm.rank()) + ".restart.h5";
36✔
691
    }
692

693
    else if (spec.GetName() == "write_restart_path")
3,933✔
694
    {
695
      options_.write_restart_path = spec.GetValue<std::string>();
171✔
696
      if (not options_.write_restart_path.empty())
171✔
UNCOV
697
        options_.write_restart_path += std::to_string(opensn::mpi_comm.rank()) + ".restart.h5";
×
698
    }
699

700
    else if (spec.GetName() == "write_restart_time_interval")
3,762✔
701
      options_.write_restart_time_interval = std::chrono::seconds(spec.GetValue<int>());
171✔
702

703
    else if (spec.GetName() == "use_precursors")
3,591✔
704
      options_.use_precursors = spec.GetValue<bool>();
171✔
705

706
    else if (spec.GetName() == "use_source_moments")
3,420✔
707
      options_.use_src_moments = spec.GetValue<bool>();
171✔
708

709
    else if (spec.GetName() == "save_angular_flux")
3,249✔
710
      options_.save_angular_flux = spec.GetValue<bool>();
171✔
711

712
    else if (spec.GetName() == "verbose_inner_iterations")
3,078✔
713
      options_.verbose_inner_iterations = spec.GetValue<bool>();
171✔
714

715
    else if (spec.GetName() == "max_ags_iterations")
2,907✔
716
      options_.max_ags_iterations = spec.GetValue<int>();
171✔
717

718
    else if (spec.GetName() == "ags_tolerance")
2,736✔
719
      options_.ags_tolerance = spec.GetValue<double>();
171✔
720

721
    else if (spec.GetName() == "ags_convergence_check")
2,565✔
722
    {
723
      auto check = spec.GetValue<std::string>();
171✔
724
      if (check == "pointwise")
171✔
UNCOV
725
        options_.ags_pointwise_convergence = true;
×
726
    }
171✔
727

728
    else if (spec.GetName() == "verbose_ags_iterations")
2,394✔
729
      options_.verbose_ags_iterations = spec.GetValue<bool>();
171✔
730

731
    else if (spec.GetName() == "verbose_outer_iterations")
2,223✔
732
      options_.verbose_outer_iterations = spec.GetValue<bool>();
171✔
733

734
    else if (spec.GetName() == "power_field_function_on")
2,052✔
735
      options_.power_field_function_on = spec.GetValue<bool>();
171✔
736

737
    else if (spec.GetName() == "power_default_kappa")
1,881✔
738
      options_.power_default_kappa = spec.GetValue<double>();
171✔
739

740
    else if (spec.GetName() == "power_normalization")
1,710✔
741
      options_.power_normalization = spec.GetValue<double>();
171✔
742

743
    else if (spec.GetName() == "field_function_prefix_option")
1,539✔
744
    {
745
      options_.field_function_prefix_option = spec.GetValue<std::string>();
171✔
746
    }
747

748
    else if (spec.GetName() == "field_function_prefix")
1,368✔
749
      options_.field_function_prefix = spec.GetValue<std::string>();
171✔
750

751
    else if (spec.GetName() == "boundary_conditions")
1,197✔
752
    {
753
      spec.RequireBlockTypeIs(ParameterBlockType::ARRAY);
171✔
754
      for (size_t b = 0; b < spec.GetNumParameters(); ++b)
177✔
755
      {
756
        auto bndry_params = GetBoundaryOptionsBlock();
6✔
757
        bndry_params.AssignParameters(spec.GetParam(b));
6✔
758
        SetBoundaryOptions(bndry_params);
6✔
759
      }
6✔
760
    }
761

762
    else if (spec.GetName() == "point_sources")
1,026✔
763
    {
764
      spec.RequireBlockTypeIs(ParameterBlockType::ARRAY);
171✔
765
      for (const auto& sub_param : spec)
172✔
766
      {
767
        point_sources_.push_back(sub_param.GetValue<std::shared_ptr<PointSource>>());
2✔
768

769
        // If a discretization exists, the point source can be initialized.
770
        if (discretization_)
1✔
771
          point_sources_.back()->Initialize(*this);
×
772
      }
773
    }
774

775
    else if (spec.GetName() == "volumetric_sources")
855✔
776
    {
777
      spec.RequireBlockTypeIs(ParameterBlockType::ARRAY);
171✔
778
      for (const auto& sub_param : spec)
183✔
779
      {
780
        volumetric_sources_.push_back(sub_param.GetValue<std::shared_ptr<VolumetricSource>>());
24✔
781

782
        // If the discretization exists, the volumetric source can be initialized.
783
        if (discretization_)
12✔
784
          volumetric_sources_.back()->Initialize(*this);
12✔
785
      }
786
    }
787
  } // for p
788

789
  if (options_.restart_writes_enabled)
171✔
790
  {
791
    // Create restart directory if necessary
UNCOV
792
    auto dir = options_.write_restart_path.parent_path();
×
UNCOV
793
    if (opensn::mpi_comm.rank() == 0)
×
794
    {
UNCOV
795
      if (not std::filesystem::exists(dir))
×
796
      {
UNCOV
797
        if (not std::filesystem::create_directories(dir))
×
UNCOV
798
          throw std::runtime_error("Failed to create restart directory: " + dir.string());
×
799
      }
UNCOV
800
      else if (not std::filesystem::is_directory(dir))
×
UNCOV
801
        throw std::runtime_error("Restart path exists but is not a directory: " + dir.string());
×
802
    }
UNCOV
803
    opensn::mpi_comm.barrier();
×
UNCOV
804
    UpdateRestartWriteTime();
×
UNCOV
805
  }
×
806
}
171✔
807

808
void
809
LBSProblem::SetBoundaryOptions(const InputParameters& params)
439✔
810
{
811
  const auto boundary_name = params.GetParamValue<std::string>("name");
439✔
812
  const auto bndry_type = params.GetParamValue<std::string>("type");
439✔
813

814
  const auto bid = supported_boundary_names.at(boundary_name);
439✔
815
  const std::map<std::string, LBSBoundaryType> type_list = {
439✔
816
    {"vacuum", LBSBoundaryType::VACUUM},
439✔
817
    {"isotropic", LBSBoundaryType::ISOTROPIC},
439✔
818
    {"reflecting", LBSBoundaryType::REFLECTING}};
1,756✔
819

820
  const auto type = type_list.at(bndry_type);
439✔
821
  switch (type)
439✔
822
  {
823
    case LBSBoundaryType::VACUUM:
358✔
824
    case LBSBoundaryType::REFLECTING:
358✔
825
    {
358✔
826
      GetBoundaryPreferences()[bid] = {type};
358✔
827
      break;
358✔
828
    }
829
    case LBSBoundaryType::ISOTROPIC:
81✔
830
    {
81✔
831
      OpenSnInvalidArgumentIf(not params.Has("group_strength"),
81✔
832
                              "Boundary conditions with type=\"isotropic\" require parameter "
833
                              "\"group_strength\"");
834

835
      params.RequireParameterBlockTypeIs("group_strength", ParameterBlockType::ARRAY);
81✔
836
      const auto group_strength = params.GetParamVectorValue<double>("group_strength");
81✔
837
      boundary_preferences_[bid] = {type, group_strength};
81✔
838
      break;
81✔
839
    }
81✔
UNCOV
840
    case LBSBoundaryType::ARBITRARY:
×
UNCOV
841
    {
×
UNCOV
842
      throw std::runtime_error("Arbitrary boundary conditions are not currently supported");
×
843
      break;
439✔
844
    }
845
  }
846
}
1,317✔
847

848
void
849
LBSProblem::Initialize()
261✔
850
{
851
  CALI_CXX_MARK_SCOPE("LBSProblem::Initialize");
261✔
852

853
  PerformInputChecks(); // assigns num_groups and grid
261✔
854
  PrintSimHeader();
261✔
855

856
  mpi_comm.barrier();
261✔
857

858
  InitializeMaterials();
261✔
859
  InitializeSpatialDiscretization();
261✔
860
  InitializeGroupsets();
261✔
861
  ValidateAndComputeScatteringMoments();
261✔
862
  InitializeParrays();
261✔
863
  InitializeBoundaries();
261✔
864

865
  // Initialize point sources
866
  for (auto& point_source : point_sources_)
270✔
867
    point_source->Initialize(*this);
9✔
868

869
  // Initialize volumetric sources
870
  for (auto& volumetric_source : volumetric_sources_)
546✔
871
    volumetric_source->Initialize(*this);
285✔
872

873
  InitializeGPUExtras();
261✔
874
}
261✔
875

876
void
877
LBSProblem::PerformInputChecks()
261✔
878
{
879
  if (groups_.empty())
261✔
UNCOV
880
    throw std::runtime_error("LBSProblem: No groups added to solver");
×
881

882
  num_groups_ = groups_.size();
261✔
883

884
  if (groupsets_.empty())
261✔
UNCOV
885
    throw std::runtime_error("LBSProblem: No group-sets added to solver");
×
886

887
  int grpset_counter = 0;
888
  for (auto& group_set : groupsets_)
577✔
889
  {
890
    if (group_set.groups.empty())
316✔
891
    {
UNCOV
892
      std::stringstream oss;
×
UNCOV
893
      oss << "LBSPRoblem: No groups added to groupset " << grpset_counter;
×
UNCOV
894
      throw std::runtime_error(oss.str());
×
UNCOV
895
    }
×
896
    ++grpset_counter;
316✔
897
  }
898

899
  if (grid_ == nullptr)
261✔
UNCOV
900
    throw std::runtime_error("LBSProblem: Invalid grid");
×
901

902
  // Determine geometry type
903
  const auto dim = grid_->GetDimension();
261✔
904
  if (dim == 1)
261✔
905
    options_.geometry_type = GeometryType::ONED_SLAB;
32✔
906
  else if (dim == 2)
229✔
907
    options_.geometry_type = GeometryType::TWOD_CARTESIAN;
110✔
908
  else if (dim == 3)
119✔
909
    options_.geometry_type = GeometryType::THREED_CARTESIAN;
119✔
910
  else
UNCOV
911
    OpenSnLogicalError("Cannot deduce geometry type from mesh.");
×
912

913
  // Assign placeholder unit densities
914
  densities_local_.assign(grid_->local_cells.size(), 1.0);
261✔
915
}
261✔
916

917
void
918
LBSProblem::PrintSimHeader()
261✔
919
{
920
  if (opensn::mpi_comm.rank() == 0)
261✔
921
  {
922
    std::stringstream outstr;
71✔
923
    outstr << "\nInitializing LBS SteadyStateSourceSolver with name: " << GetName() << "\n\n"
71✔
924
           << "Scattering order    : " << scattering_order_ << "\n"
142✔
925
           << "Number of Groups    : " << groups_.size() << "\n"
71✔
926
           << "Number of Group sets: " << groupsets_.size() << std::endl;
71✔
927

928
    // Output Groupsets
929
    for (const auto& groupset : groupsets_)
157✔
930
    {
931
      char buf_pol[20];
86✔
932

933
      outstr << "\n***** Groupset " << groupset.id << " *****\n"
86✔
934
             << "Groups:\n";
86✔
935
      int counter = 0;
86✔
936
      for (auto group : groupset.groups)
4,352✔
937
      {
938
        snprintf(buf_pol, 20, "%5d ", group.id);
4,266✔
939
        outstr << std::string(buf_pol);
8,532✔
940
        counter++;
4,266✔
941
        if (counter == 12)
4,266✔
942
        {
943
          counter = 0;
323✔
944
          outstr << "\n";
323✔
945
        }
946
      }
947
    }
948
    log.Log() << outstr.str() << "\n" << std::endl;
142✔
949
  }
71✔
950
}
261✔
951

952
void
953
LBSProblem::InitializeMaterials()
273✔
954
{
955
  CALI_CXX_MARK_SCOPE("LBSProblem::InitializeMaterials");
273✔
956

957
  log.Log0Verbose1() << "Initializing Materials";
546✔
958

959
  // Create set of material ids locally relevant
960
  int invalid_mat_cell_count = 0;
273✔
961
  std::set<int> unique_block_ids;
273✔
962
  for (auto& cell : grid_->local_cells)
300,017✔
963
  {
964
    unique_block_ids.insert(cell.block_id);
299,744✔
965
    if (cell.block_id < 0 or (block_id_to_xs_map_.find(cell.block_id) == block_id_to_xs_map_.end()))
299,744✔
UNCOV
966
      ++invalid_mat_cell_count;
×
967
  }
968
  const auto& ghost_cell_ids = grid_->cells.GetGhostGlobalIDs();
273✔
969
  for (uint64_t cell_id : ghost_cell_ids)
43,326✔
970
  {
971
    const auto& cell = grid_->cells[cell_id];
43,053✔
972
    unique_block_ids.insert(cell.block_id);
43,053✔
973
    if (cell.block_id < 0 or (block_id_to_xs_map_.find(cell.block_id) == block_id_to_xs_map_.end()))
43,053✔
UNCOV
974
      ++invalid_mat_cell_count;
×
975
  }
976
  OpenSnLogicalErrorIf(invalid_mat_cell_count > 0,
273✔
977
                       std::to_string(invalid_mat_cell_count) +
978
                         " cells encountered with an invalid material id.");
979

980
  // Get ready for processing
981
  for (const auto& [blk_id, mat] : block_id_to_xs_map_)
792✔
982
  {
983
    mat->SetAdjointMode(options_.adjoint);
519✔
984

985
    OpenSnLogicalErrorIf(mat->GetNumGroups() < groups_.size(),
519✔
986
                         "Cross-sections on block \"" + std::to_string(blk_id) +
987
                           "\" has fewer groups (" + std::to_string(mat->GetNumGroups()) +
988
                           ") than " + "the simulation (" + std::to_string(groups_.size()) + "). " +
989
                           "Cross-sections must have at least as many groups as the simulation.");
990
  }
991

992
  // Initialize precursor properties
993
  num_precursors_ = 0;
273✔
994
  max_precursors_per_material_ = 0;
273✔
995
  for (const auto& mat_id_xs : block_id_to_xs_map_)
792✔
996
  {
997
    const auto& xs = mat_id_xs.second;
519✔
998
    num_precursors_ += xs->GetNumPrecursors();
519✔
999
    if (xs->GetNumPrecursors() > max_precursors_per_material_)
519✔
1000
      max_precursors_per_material_ = xs->GetNumPrecursors();
8✔
1001
  }
1002

1003
  // if no precursors, turn off precursors
1004
  if (num_precursors_ == 0)
273✔
1005
    options_.use_precursors = false;
265✔
1006

1007
  // check compatibility when precursors are on
1008
  if (options_.use_precursors)
273✔
1009
  {
1010
    for (const auto& [mat_id, xs] : block_id_to_xs_map_)
16✔
1011
    {
1012
      OpenSnLogicalErrorIf(xs->IsFissionable() and num_precursors_ == 0,
8✔
1013
                           "Incompatible cross-section data encountered for material ID " +
1014
                             std::to_string(mat_id) + ". When delayed neutron data is present " +
1015
                             "for one fissionable matrial, it must be present for all fissionable "
1016
                             "materials.");
1017
    }
1018
  }
1019

1020
  // Update transport views if available
1021
  if (grid_->local_cells.size() == cell_transport_views_.size())
273✔
1022
    for (const auto& cell : grid_->local_cells)
10,812✔
1023
    {
1024
      const auto& xs_ptr = block_id_to_xs_map_[cell.block_id];
10,800✔
1025
      auto& transport_view = cell_transport_views_[cell.local_id];
10,800✔
1026
      transport_view.ReassignXS(*xs_ptr);
10,800✔
1027
    }
1028

1029
  mpi_comm.barrier();
273✔
1030
}
273✔
1031

1032
void
1033
LBSProblem::InitializeSpatialDiscretization()
253✔
1034
{
1035
  CALI_CXX_MARK_SCOPE("LBSProblem::InitializeSpatialDiscretization");
253✔
1036

1037
  log.Log() << "Initializing spatial discretization.\n";
506✔
1038
  discretization_ = PieceWiseLinearDiscontinuous::New(grid_);
253✔
1039

1040
  ComputeUnitIntegrals();
253✔
1041
}
253✔
1042

1043
void
1044
LBSProblem::ComputeUnitIntegrals()
261✔
1045
{
1046
  CALI_CXX_MARK_SCOPE("LBSProblem::ComputeUnitIntegrals");
261✔
1047

1048
  log.Log() << "Computing unit integrals.\n";
522✔
1049
  const auto& sdm = *discretization_;
261✔
1050

1051
  const size_t num_local_cells = grid_->local_cells.size();
261✔
1052
  unit_cell_matrices_.resize(num_local_cells);
261✔
1053

1054
  for (const auto& cell : grid_->local_cells)
289,205✔
1055
    unit_cell_matrices_[cell.local_id] =
288,944✔
1056
      ComputeUnitCellIntegrals(sdm, cell, grid_->GetCoordinateSystem());
288,944✔
1057

1058
  const auto ghost_ids = grid_->cells.GetGhostGlobalIDs();
261✔
1059
  for (uint64_t ghost_id : ghost_ids)
42,558✔
1060
    unit_ghost_cell_matrices_[ghost_id] =
42,297✔
1061
      ComputeUnitCellIntegrals(sdm, grid_->cells[ghost_id], grid_->GetCoordinateSystem());
84,594✔
1062

1063
  // Assessing global unit cell matrix storage
1064
  std::array<size_t, 2> num_local_ucms = {unit_cell_matrices_.size(),
261✔
1065
                                          unit_ghost_cell_matrices_.size()};
261✔
1066
  std::array<size_t, 2> num_global_ucms = {0, 0};
261✔
1067

1068
  mpi_comm.all_reduce(num_local_ucms.data(), 2, num_global_ucms.data(), mpi::op::sum<size_t>());
261✔
1069

1070
  opensn::mpi_comm.barrier();
261✔
1071
  log.Log() << "Ghost cell unit cell-matrix ratio: "
261✔
1072
            << (double)num_global_ucms[1] * 100 / (double)num_global_ucms[0] << "%";
522✔
1073
  log.Log() << "Cell matrices computed.";
522✔
1074
}
261✔
1075

1076
void
1077
LBSProblem::InitializeGroupsets()
261✔
1078
{
1079
  CALI_CXX_MARK_SCOPE("LBSProblem::InitializeGroupsets");
261✔
1080

1081
  for (auto& groupset : groupsets_)
577✔
1082
  {
1083
    // Build groupset angular flux unknown manager
1084
    groupset.psi_uk_man_.unknowns.clear();
316✔
1085
    size_t num_angles = groupset.quadrature->abscissae.size();
316✔
1086
    size_t gs_num_groups = groupset.groups.size();
316✔
1087
    auto& grpset_psi_uk_man = groupset.psi_uk_man_;
316✔
1088

1089
    const auto VarVecN = UnknownType::VECTOR_N;
316✔
1090
    for (unsigned int n = 0; n < num_angles; ++n)
41,700✔
1091
      grpset_psi_uk_man.AddUnknown(VarVecN, gs_num_groups);
41,384✔
1092

1093
    if (use_gpus_)
316✔
UNCOV
1094
      groupset.InitializeGPUCarriers();
×
1095
  } // for groupset
1096
}
261✔
1097

1098
void
1099
LBSProblem::ValidateAndComputeScatteringMoments()
261✔
1100
{
1101
  /*
1102
    lfs: Legendre order used in the flux solver
1103
    lxs: Legendre order used in the cross-section library
1104
    laq: Legendre order supported by the angular quadrature
1105
  */
1106

1107
  int lfs = scattering_order_;
261✔
1108
  if (lfs < 0)
261✔
UNCOV
1109
    throw std::invalid_argument("LBSProblem: Scattering order must be >= 0");
×
1110

1111
  for (size_t gs = 1; gs < groupsets_.size(); ++gs)
316✔
1112
    if (groupsets_[gs].quadrature->GetScatteringOrder() !=
55✔
1113
        groupsets_[0].quadrature->GetScatteringOrder())
55✔
UNCOV
1114
      throw std::logic_error("LBSProblem: Number of scattering moments differs between groupsets");
×
1115
  int laq = groupsets_[0].quadrature->GetScatteringOrder();
261✔
1116

1117
  for (const auto& [blk_id, mat] : block_id_to_xs_map_)
752✔
1118
  {
1119
    int lxs = block_id_to_xs_map_[blk_id]->GetScatteringOrder();
491✔
1120

1121
    if (laq > lxs)
491✔
1122
    {
1123
      log.Log0Warning()
132✔
1124
        << "The quadrature set(s) supports more scattering moments than are present in the "
66✔
1125
        << "cross-section data for block " << blk_id << std::endl;
66✔
1126
    }
1127

1128
    if (lfs < lxs)
491✔
1129
    {
1130
      log.Log0Warning()
476✔
1131
        << "Computing the flux with fewer scattering moments than are present in the "
238✔
1132
        << "cross-section data for block " << blk_id << std::endl;
238✔
1133
    }
1134
    else if (lfs > lxs)
253✔
1135
    {
1136
      log.Log0Warning()
132✔
1137
        << "Computing the flux with more scattering moments than are present in the "
66✔
1138
        << "cross-section data for block " << blk_id << std::endl;
66✔
1139
    }
1140
  }
1141

1142
  if (lfs < laq)
261✔
1143
  {
UNCOV
1144
    log.Log0Warning() << "Using fewer rows/columns of angular matrices (M, D) than the quadrature "
×
UNCOV
1145
                      << "supports" << std::endl;
×
1146
  }
1147
  else if (lfs > laq)
261✔
UNCOV
1148
    throw std::logic_error(
×
UNCOV
1149
      "LBSProblem: Solver requires more flux moments than the angular quadrature supports");
×
1150

1151
  // Compute number of solver moments.
1152
  auto geometry_type = options_.geometry_type;
261✔
1153
  if (geometry_type == GeometryType::ONED_SLAB or geometry_type == GeometryType::ONED_CYLINDRICAL or
261✔
1154
      geometry_type == GeometryType::ONED_SPHERICAL or
261✔
1155
      geometry_type == GeometryType::TWOD_CYLINDRICAL)
261✔
1156
  {
1157
    num_moments_ = lfs + 1;
40✔
1158
  }
1159
  else if (geometry_type == GeometryType::TWOD_CARTESIAN)
221✔
1160
    num_moments_ = ((lfs + 1) * (lfs + 2)) / 2;
102✔
1161
  else if (geometry_type == GeometryType::THREED_CARTESIAN)
119✔
1162
    num_moments_ = (lfs + 1) * (lfs + 1);
119✔
1163
}
261✔
1164

1165
void
1166
LBSProblem::InitializeParrays()
261✔
1167
{
1168
  CALI_CXX_MARK_SCOPE("LBSProblem::InitializeParrays");
261✔
1169

1170
  log.Log() << "Initializing parallel arrays."
522✔
1171
            << " G=" << num_groups_ << " M=" << num_moments_ << std::endl;
261✔
1172

1173
  // Initialize unknown
1174
  // structure
1175
  flux_moments_uk_man_.unknowns.clear();
261✔
1176
  for (size_t m = 0; m < num_moments_; ++m)
1,056✔
1177
  {
1178
    flux_moments_uk_man_.AddUnknown(UnknownType::VECTOR_N, groups_.size());
795✔
1179
    flux_moments_uk_man_.unknowns.back().name = "m" + std::to_string(m);
795✔
1180
  }
1181

1182
  // Compute local # of dof
1183
  local_node_count_ = discretization_->GetNumLocalNodes();
261✔
1184
  global_node_count_ = discretization_->GetNumGlobalNodes();
261✔
1185

1186
  // Compute num of unknowns
1187
  size_t num_grps = groups_.size();
261✔
1188
  size_t local_unknown_count = local_node_count_ * num_grps * num_moments_;
261✔
1189

1190
  log.LogAllVerbose1() << "LBS Number of phi unknowns: " << local_unknown_count;
522✔
1191

1192
  // Size local vectors
1193
  q_moments_local_.assign(local_unknown_count, 0.0);
261✔
1194
  phi_old_local_.assign(local_unknown_count, 0.0);
261✔
1195
  phi_new_local_.assign(local_unknown_count, 0.0);
261✔
1196

1197
  // Setup groupset psi vectors
1198
  psi_new_local_.clear();
261✔
1199
  for (auto& groupset : groupsets_)
577✔
1200
  {
1201
    psi_new_local_.emplace_back();
316✔
1202
    if (options_.save_angular_flux)
316✔
1203
    {
1204
      size_t num_ang_unknowns = discretization_->GetNumLocalDOFs(groupset.psi_uk_man_);
83✔
1205
      psi_new_local_.back().assign(num_ang_unknowns, 0.0);
83✔
1206
    }
1207
  }
1208

1209
  // Setup precursor vector
1210
  if (options_.use_precursors)
261✔
1211
  {
1212
    size_t num_precursor_dofs = grid_->local_cells.size() * max_precursors_per_material_;
8✔
1213
    precursor_new_local_.assign(num_precursor_dofs, 0.0);
8✔
1214
  }
1215

1216
  // Initialize transport views
1217
  // Transport views act as a data structure to store information
1218
  // related to the transport simulation. The most prominent function
1219
  // here is that it holds the means to know where a given cell's
1220
  // transport quantities are located in the unknown vectors (i.e. phi)
1221
  //
1222
  // Also, for a given cell, within a given sweep chunk,
1223
  // we need to solve a matrix which square size is the
1224
  // amount of nodes on the cell. max_cell_dof_count is
1225
  // initialized here.
1226
  //
1227
  size_t block_MG_counter = 0; // Counts the strides of moment and group
261✔
1228

1229
  const Vector3 ihat(1.0, 0.0, 0.0);
261✔
1230
  const Vector3 jhat(0.0, 1.0, 0.0);
261✔
1231
  const Vector3 khat(0.0, 0.0, 1.0);
261✔
1232

1233
  max_cell_dof_count_ = 0;
261✔
1234
  cell_transport_views_.clear();
261✔
1235
  cell_transport_views_.reserve(grid_->local_cells.size());
261✔
1236
  for (auto& cell : grid_->local_cells)
289,205✔
1237
  {
1238
    size_t num_nodes = discretization_->GetCellNumNodes(cell);
288,944✔
1239

1240
    // compute cell volumes
1241
    double cell_volume = 0.0;
288,944✔
1242
    const auto& IntV_shapeI = unit_cell_matrices_[cell.local_id].intV_shapeI;
288,944✔
1243
    for (size_t i = 0; i < num_nodes; ++i)
2,520,630✔
1244
      cell_volume += IntV_shapeI(i);
2,231,686✔
1245

1246
    size_t cell_phi_address = block_MG_counter;
288,944✔
1247

1248
    const size_t num_faces = cell.faces.size();
288,944✔
1249
    std::vector<bool> face_local_flags(num_faces, true);
288,944✔
1250
    std::vector<int> face_locality(num_faces, opensn::mpi_comm.rank());
288,944✔
1251
    std::vector<const Cell*> neighbor_cell_ptrs(num_faces, nullptr);
288,944✔
1252
    bool cell_on_boundary = false;
288,944✔
1253
    int f = 0;
288,944✔
1254
    for (auto& face : cell.faces)
1,976,094✔
1255
    {
1256
      if (not face.has_neighbor)
1,687,150✔
1257
      {
1258
        Vector3& n = face.normal;
56,530✔
1259

1260
        int boundary_id = -1;
56,530✔
1261
        if (n.Dot(ihat) < -0.999)
56,530✔
1262
          boundary_id = XMIN;
1263
        else if (n.Dot(ihat) > 0.999)
48,429✔
1264
          boundary_id = XMAX;
1265
        else if (n.Dot(jhat) < -0.999)
40,490✔
1266
          boundary_id = YMIN;
1267
        else if (n.Dot(jhat) > 0.999)
32,719✔
1268
          boundary_id = YMAX;
1269
        else if (n.Dot(khat) < -0.999)
25,110✔
1270
          boundary_id = ZMIN;
1271
        else if (n.Dot(khat) > 0.999)
12,555✔
1272
          boundary_id = ZMAX;
1273

1274
        if (boundary_id >= 0)
1275
          face.neighbor_id = boundary_id;
56,530✔
1276
        cell_on_boundary = true;
56,530✔
1277

1278
        face_local_flags[f] = false;
56,530✔
1279
        face_locality[f] = -1;
56,530✔
1280
      } // if bndry
1281
      else
1282
      {
1283
        const int neighbor_partition = face.GetNeighborPartitionID(grid_.get());
1,630,620✔
1284
        face_local_flags[f] = (neighbor_partition == opensn::mpi_comm.rank());
1,630,620✔
1285
        face_locality[f] = neighbor_partition;
1,630,620✔
1286
        neighbor_cell_ptrs[f] = &grid_->cells[face.neighbor_id];
1,630,620✔
1287
      }
1288

1289
      ++f;
1,687,150✔
1290
    } // for f
1291

1292
    if (num_nodes > max_cell_dof_count_)
288,944✔
1293
      max_cell_dof_count_ = num_nodes;
421✔
1294
    cell_transport_views_.emplace_back(cell_phi_address,
577,888✔
1295
                                       num_nodes,
1296
                                       num_grps,
1297
                                       num_moments_,
288,944✔
1298
                                       num_faces,
1299
                                       *block_id_to_xs_map_[cell.block_id],
288,944✔
1300
                                       cell_volume,
1301
                                       face_local_flags,
1302
                                       face_locality,
1303
                                       neighbor_cell_ptrs,
1304
                                       cell_on_boundary);
1305
    block_MG_counter += num_nodes * num_grps * num_moments_;
288,944✔
1306
  } // for local cell
288,944✔
1307

1308
  // Populate grid nodal mappings
1309
  // This is used in the Flux Data Structures (FLUDS)
1310
  grid_nodal_mappings_.clear();
261✔
1311
  grid_nodal_mappings_.reserve(grid_->local_cells.size());
261✔
1312
  for (auto& cell : grid_->local_cells)
289,205✔
1313
  {
1314
    CellFaceNodalMapping cell_nodal_mapping;
288,944✔
1315
    cell_nodal_mapping.reserve(cell.faces.size());
288,944✔
1316

1317
    for (auto& face : cell.faces)
1,976,094✔
1318
    {
1319
      std::vector<short> face_node_mapping;
1,687,150✔
1320
      std::vector<short> cell_node_mapping;
1,687,150✔
1321
      int adj_face_idx = -1;
1,687,150✔
1322

1323
      if (face.has_neighbor)
1,687,150✔
1324
      {
1325
        grid_->FindAssociatedVertices(face, face_node_mapping);
1,630,620✔
1326
        grid_->FindAssociatedCellVertices(face, cell_node_mapping);
1,630,620✔
1327
        adj_face_idx = face.GetNeighborAdjacentFaceIndex(grid_.get());
1,630,620✔
1328
      }
1329

1330
      cell_nodal_mapping.emplace_back(adj_face_idx, face_node_mapping, cell_node_mapping);
1,687,150✔
1331
    } // for f
1,687,150✔
1332

1333
    grid_nodal_mappings_.push_back(cell_nodal_mapping);
288,944✔
1334
  } // for local cell
288,944✔
1335

1336
  // Get grid localized communicator set
1337
  grid_local_comm_set_ = grid_->MakeMPILocalCommunicatorSet();
261✔
1338

1339
  // Initialize Field Functions
1340
  InitializeFieldFunctions();
261✔
1341

1342
  opensn::mpi_comm.barrier();
261✔
1343
  log.Log() << "Done with parallel arrays." << std::endl;
522✔
1344
}
261✔
1345

1346
void
1347
LBSProblem::InitializeFieldFunctions()
261✔
1348
{
1349
  CALI_CXX_MARK_SCOPE("LBSProblem::InitializeFieldFunctions");
261✔
1350

1351
  if (not field_functions_.empty())
261✔
UNCOV
1352
    return;
×
1353

1354
  // Initialize Field Functions for flux moments
1355
  phi_field_functions_local_map_.clear();
261✔
1356

1357
  for (size_t g = 0; g < groups_.size(); ++g)
18,240✔
1358
  {
1359
    for (size_t m = 0; m < num_moments_; ++m)
89,066✔
1360
    {
1361
      std::string prefix;
71,087✔
1362
      if (options_.field_function_prefix_option == "prefix")
71,087✔
1363
      {
1364
        prefix = options_.field_function_prefix;
71,087✔
1365
        if (not prefix.empty())
71,087✔
1366
          prefix += "_";
1✔
1367
      }
1368
      if (options_.field_function_prefix_option == "solver_name")
71,087✔
UNCOV
1369
        prefix = GetName() + "_";
×
1370

1371
      char buff[100];
71,087✔
1372
      snprintf(
71,087✔
1373
        buff, 99, "%sphi_g%03d_m%02d", prefix.c_str(), static_cast<int>(g), static_cast<int>(m));
1374
      const std::string name = std::string(buff);
71,087✔
1375

1376
      auto group_ff = std::make_shared<FieldFunctionGridBased>(
71,087✔
1377
        name, discretization_, Unknown(UnknownType::SCALAR));
71,087✔
1378

1379
      field_function_stack.push_back(group_ff);
142,174✔
1380
      field_functions_.push_back(group_ff);
71,087✔
1381

1382
      phi_field_functions_local_map_[{g, m}] = field_functions_.size() - 1;
71,087✔
1383
    } // for m
71,087✔
1384
  } // for g
1385

1386
  // Initialize power generation field function
1387
  if (options_.power_field_function_on)
261✔
1388
  {
1389
    std::string prefix;
4✔
1390
    if (options_.field_function_prefix_option == "prefix")
4✔
1391
    {
1392
      prefix = options_.field_function_prefix;
4✔
1393
      if (not prefix.empty())
4✔
UNCOV
1394
        prefix += "_";
×
1395
    }
1396
    if (options_.field_function_prefix_option == "solver_name")
4✔
UNCOV
1397
      prefix = GetName() + "_";
×
1398

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

1402
    field_function_stack.push_back(power_ff);
8✔
1403
    field_functions_.push_back(power_ff);
4✔
1404

1405
    power_gen_fieldfunc_local_handle_ = field_functions_.size() - 1;
4✔
1406
  }
4✔
1407
}
261✔
1408

1409
void
1410
LBSProblem::InitializeSolverSchemes()
261✔
1411
{
1412
  CALI_CXX_MARK_SCOPE("LBSProblem::InitializeSolverSchemes");
261✔
1413

1414
  log.Log() << "Initializing WGS and AGS solvers";
522✔
1415

1416
  InitializeWGSSolvers();
261✔
1417

1418
  ags_solver_ = std::make_shared<AGSLinearSolver>(*this, wgs_solvers_);
261✔
1419
  if (groupsets_.size() == 1)
261✔
1420
  {
1421
    ags_solver_->SetMaxIterations(1);
210✔
1422
    ags_solver_->SetVerbosity(false);
210✔
1423
  }
1424
  else
1425
  {
1426
    ags_solver_->SetMaxIterations(options_.max_ags_iterations);
51✔
1427
    ags_solver_->SetVerbosity(options_.verbose_ags_iterations);
51✔
1428
  }
1429
  ags_solver_->SetTolerance(options_.ags_tolerance);
261✔
1430
}
261✔
1431

1432
#ifndef __OPENSN_USE_CUDA__
1433
void
1434
LBSProblem::InitializeGPUExtras()
261✔
1435
{
1436
}
261✔
1437

1438
void
1439
LBSProblem::ResetGPUCarriers()
257✔
1440
{
1441
}
257✔
1442

1443
void
UNCOV
1444
LBSProblem::CheckCapableDevices()
×
1445
{
UNCOV
1446
}
×
1447

1448
#endif // __OPENSN_USE_CUDA__
1449

1450
std::vector<double>
1451
LBSProblem::MakeSourceMomentsFromPhi()
4✔
1452
{
1453
  CALI_CXX_MARK_SCOPE("LBSProblem::MakeSourceMomentsFromPhi");
4✔
1454

1455
  size_t num_local_dofs = discretization_->GetNumLocalDOFs(flux_moments_uk_man_);
4✔
1456

1457
  std::vector<double> source_moments(num_local_dofs, 0.0);
4✔
1458
  for (auto& groupset : groupsets_)
8✔
1459
  {
1460
    active_set_source_function_(groupset,
4✔
1461
                                source_moments,
1462
                                phi_new_local_,
4✔
1463
                                APPLY_AGS_SCATTER_SOURCES | APPLY_WGS_SCATTER_SOURCES |
1464
                                  APPLY_AGS_FISSION_SOURCES | APPLY_WGS_FISSION_SOURCES);
4✔
1465
  }
1466

1467
  return source_moments;
4✔
1468
}
4✔
1469

1470
void
1471
LBSProblem::UpdateFieldFunctions()
269✔
1472
{
1473
  CALI_CXX_MARK_SCOPE("LBSProblem::UpdateFieldFunctions");
269✔
1474

1475
  const auto& sdm = *discretization_;
269✔
1476
  const auto& phi_uk_man = flux_moments_uk_man_;
269✔
1477

1478
  // Update flux moments
1479
  for (const auto& [g_and_m, ff_index] : phi_field_functions_local_map_)
71,400✔
1480
  {
1481
    const size_t g = g_and_m.first;
71,131✔
1482
    const size_t m = g_and_m.second;
71,131✔
1483

1484
    std::vector<double> data_vector_local(local_node_count_, 0.0);
71,131✔
1485

1486
    for (const auto& cell : grid_->local_cells)
16,306,960✔
1487
    {
1488
      const auto& cell_mapping = sdm.GetCellMapping(cell);
16,235,829✔
1489
      const size_t num_nodes = cell_mapping.GetNumNodes();
16,235,829✔
1490

1491
      for (size_t i = 0; i < num_nodes; ++i)
117,461,761✔
1492
      {
1493
        const int64_t imapA = sdm.MapDOFLocal(cell, i, phi_uk_man, m, g);
101,225,932✔
1494
        const int64_t imapB = sdm.MapDOFLocal(cell, i);
101,225,932✔
1495

1496
        data_vector_local[imapB] = phi_new_local_[imapA];
101,225,932✔
1497
      } // for node
1498
    } // for cell
1499

1500
    auto& ff_ptr = field_functions_.at(ff_index);
71,131✔
1501
    ff_ptr->UpdateFieldVector(data_vector_local);
71,131✔
1502
  }
71,131✔
1503

1504
  // Update power generation
1505
  if (options_.power_field_function_on)
269✔
1506
  {
1507
    std::vector<double> data_vector_local(local_node_count_, 0.0);
4✔
1508

1509
    double local_total_power = 0.0;
4✔
1510
    for (const auto& cell : grid_->local_cells)
83,268✔
1511
    {
1512
      const auto& cell_mapping = sdm.GetCellMapping(cell);
83,264✔
1513
      const size_t num_nodes = cell_mapping.GetNumNodes();
83,264✔
1514

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

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

1519
      if (not xs->IsFissionable())
83,264✔
1520
        continue;
56,360✔
1521

1522
      for (size_t i = 0; i < num_nodes; ++i)
134,520✔
1523
      {
1524
        const int64_t imapA = sdm.MapDOFLocal(cell, i);
107,616✔
1525
        const int64_t imapB = sdm.MapDOFLocal(cell, i, phi_uk_man, 0, 0);
107,616✔
1526

1527
        double nodal_power = 0.0;
1528
        for (size_t g = 0; g < groups_.size(); ++g)
860,928✔
1529
        {
1530
          const double sigma_fg = xs->GetSigmaFission()[g];
753,312✔
1531
          // const double kappa_g = xs->Kappa()[g];
1532
          const double kappa_g = options_.power_default_kappa;
753,312✔
1533

1534
          nodal_power += kappa_g * sigma_fg * phi_new_local_[imapB + g];
753,312✔
1535
        } // for g
1536

1537
        data_vector_local[imapA] = nodal_power;
107,616✔
1538
        local_total_power += nodal_power * Vi(i);
107,616✔
1539
      } // for node
1540
    } // for cell
1541

1542
    if (options_.power_normalization > 0.0)
4✔
1543
    {
1544
      double global_total_power;
4✔
1545
      mpi_comm.all_reduce(local_total_power, global_total_power, mpi::op::sum<double>());
4✔
1546

1547
      Scale(data_vector_local, options_.power_normalization / global_total_power);
4✔
1548
    }
1549

1550
    const size_t ff_index = power_gen_fieldfunc_local_handle_;
4✔
1551

1552
    auto& ff_ptr = field_functions_.at(ff_index);
4✔
1553
    ff_ptr->UpdateFieldVector(data_vector_local);
4✔
1554

1555
  } // if power enabled
4✔
1556
}
269✔
1557

1558
void
1559
LBSProblem::SetPhiFromFieldFunctions(PhiSTLOption which_phi,
×
1560
                                     const std::vector<size_t>& m_indices,
1561
                                     const std::vector<size_t>& g_indices)
1562
{
1563
  CALI_CXX_MARK_SCOPE("LBSProblem::SetPhiFromFieldFunctions");
×
1564

UNCOV
1565
  std::vector<size_t> m_ids_to_copy = m_indices;
×
1566
  std::vector<size_t> g_ids_to_copy = g_indices;
×
UNCOV
1567
  if (m_indices.empty())
×
1568
    for (size_t m = 0; m < num_moments_; ++m)
×
1569
      m_ids_to_copy.push_back(m);
×
UNCOV
1570
  if (g_ids_to_copy.empty())
×
1571
    for (size_t g = 0; g < num_groups_; ++g)
×
1572
      g_ids_to_copy.push_back(g);
×
1573

1574
  const auto& sdm = *discretization_;
×
UNCOV
1575
  const auto& phi_uk_man = flux_moments_uk_man_;
×
1576

UNCOV
1577
  for (const size_t m : m_ids_to_copy)
×
1578
  {
1579
    for (const size_t g : g_ids_to_copy)
×
1580
    {
UNCOV
1581
      const size_t ff_index = phi_field_functions_local_map_.at({g, m});
×
UNCOV
1582
      const auto& ff_ptr = field_functions_.at(ff_index);
×
UNCOV
1583
      const auto& ff_data = ff_ptr->GetLocalFieldVector();
×
1584

UNCOV
1585
      for (const auto& cell : grid_->local_cells)
×
1586
      {
UNCOV
1587
        const auto& cell_mapping = sdm.GetCellMapping(cell);
×
UNCOV
1588
        const size_t num_nodes = cell_mapping.GetNumNodes();
×
1589

UNCOV
1590
        for (size_t i = 0; i < num_nodes; ++i)
×
1591
        {
UNCOV
1592
          const int64_t imapA = sdm.MapDOFLocal(cell, i);
×
UNCOV
1593
          const int64_t imapB = sdm.MapDOFLocal(cell, i, phi_uk_man, m, g);
×
1594

UNCOV
1595
          if (which_phi == PhiSTLOption::PHI_OLD)
×
UNCOV
1596
            phi_old_local_[imapB] = ff_data[imapA];
×
UNCOV
1597
          else if (which_phi == PhiSTLOption::PHI_NEW)
×
UNCOV
1598
            phi_new_local_[imapB] = ff_data[imapA];
×
1599
        } // for node
1600
      } // for cell
1601
    } // for g
1602
  } // for m
UNCOV
1603
}
×
1604

1605
LBSProblem::~LBSProblem()
257✔
1606
{
1607
  ResetGPUCarriers();
1608
}
1,285✔
1609

257✔
1610
} // 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