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

Open-Sn / opensn / 18260070676

05 Oct 2025 11:21AM UTC coverage: 75.031% (+0.01%) from 75.017%
18260070676

push

github

web-flow
Merge pull request #789 from andrsd/ghost-id

Ghost indices are stored as `uint64_t`

98 of 122 new or added lines in 25 files covered. (80.33%)

1 existing line in 1 file now uncovered.

17816 of 23745 relevant lines covered (75.03%)

47038485.78 hits per line

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

85.41
/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", "LBSDatablock");
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.AddRequiredParameter<unsigned int>(
590✔
60
    "scattering_order", "The level of harmonic expansion for the scattering source.");
61

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

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

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

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

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

78
  return params;
295✔
79
}
×
80

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

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

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

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

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

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

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

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

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

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

154
  // Check system for GPU acceleration
155
  if (use_gpus_)
295✔
156
  {
157
#ifdef __OPENSN_USE_CUDA__
158
    CheckCapableDevices();
159
#else
160
    throw std::invalid_argument(
×
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"))
295✔
167
  {
168
    auto options_params = LBSProblem::GetOptionsBlock();
177✔
169
    options_params.AssignParameters(params.GetParam("options"));
179✔
170
    SetOptions(options_params);
175✔
171
  }
177✔
172
}
313✔
173

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

180
const LBSOptions&
181
LBSProblem::GetOptions() const
472,203,762✔
182
{
183
  return options_;
472,203,762✔
184
}
185

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

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

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

204
size_t
205
LBSProblem::GetNumPrecursors() const
×
206
{
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
217
LBSProblem::AddGroup(int id)
×
218
{
219
  if (id < 0)
×
220
    groups_.emplace_back(static_cast<int>(groups_.size()));
×
221
  else
222
    groups_.emplace_back(id);
×
223
}
×
224

225
const std::vector<LBSGroup>&
226
LBSProblem::GetGroups() const
138,504✔
227
{
228
  return groups_;
138,504✔
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,771✔
239
{
240
  return groupsets_;
8,170,771✔
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
  if (discretization_)
×
254
    point_sources_.back()->Initialize(*this);
×
255
}
×
256

257
void
258
LBSProblem::ClearPointSources()
×
259
{
260
  point_sources_.clear();
×
261
}
×
262

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

269
void
270
LBSProblem::AddVolumetricSource(std::shared_ptr<VolumetricSource> volumetric_source)
16✔
271
{
272
  volumetric_sources_.push_back(volumetric_source);
16✔
273
  if (discretization_)
16✔
274
    volumetric_sources_.back()->Initialize(*this);
16✔
275
}
16✔
276

277
void
278
LBSProblem::ClearVolumetricSources()
4✔
279
{
280
  volumetric_sources_.clear();
4✔
281
}
4✔
282

283
const std::vector<std::shared_ptr<VolumetricSource>>&
284
LBSProblem::GetVolumetricSources() const
7,414✔
285
{
286
  return volumetric_sources_;
7,414✔
287
}
288

289
void
290
LBSProblem::ClearBoundaries()
×
291
{
292
  boundary_preferences_.clear();
×
293
}
×
294

295
const std::map<int, std::shared_ptr<MultiGroupXS>>&
296
LBSProblem::GetMatID2XSMap() const
6,681✔
297
{
298
  return block_id_to_xs_map_;
6,681✔
299
}
300

301
std::shared_ptr<MeshContinuum>
302
LBSProblem::GetGrid() const
180,244✔
303
{
304
  return grid_;
180,244✔
305
}
306

307
const SpatialDiscretization&
308
LBSProblem::GetSpatialDiscretization() const
64,064✔
309
{
310
  return *discretization_;
64,064✔
311
}
312

313
const std::vector<UnitCellMatrices>&
314
LBSProblem::GetUnitCellMatrices() const
7,275✔
315
{
316
  return unit_cell_matrices_;
7,275✔
317
}
318

319
const std::map<uint64_t, UnitCellMatrices>&
320
LBSProblem::GetUnitGhostCellMatrices() const
17✔
321
{
322
  return unit_ghost_cell_matrices_;
17✔
323
}
324

325
const std::vector<CellLBSView>&
326
LBSProblem::GetCellTransportViews() const
190,268✔
327
{
328
  return cell_transport_views_;
190,268✔
329
}
330

331
const UnknownManager&
332
LBSProblem::GetUnknownManager() const
26,695✔
333
{
334
  return flux_moments_uk_man_;
26,695✔
335
}
336

337
size_t
338
LBSProblem::GetLocalNodeCount() const
3,049✔
339
{
340
  return local_node_count_;
3,049✔
341
}
342

343
size_t
344
LBSProblem::GetGlobalNodeCount() const
1,486✔
345
{
346
  return global_node_count_;
1,486✔
347
}
348

349
std::vector<double>&
350
LBSProblem::GetQMomentsLocal()
37,465✔
351
{
352
  return q_moments_local_;
37,465✔
353
}
354

355
const std::vector<double>&
356
LBSProblem::GetQMomentsLocal() const
×
357
{
358
  return q_moments_local_;
×
359
}
360

361
std::vector<double>&
362
LBSProblem::GetExtSrcMomentsLocal()
4✔
363
{
364
  return ext_src_moments_local_;
4✔
365
}
366

367
const std::vector<double>&
368
LBSProblem::GetExtSrcMomentsLocal() const
37,159✔
369
{
370
  return ext_src_moments_local_;
37,159✔
371
}
372

373
std::vector<double>&
374
LBSProblem::GetPhiOldLocal()
56,839✔
375
{
376
  return phi_old_local_;
56,839✔
377
}
378

379
const std::vector<double>&
380
LBSProblem::GetPhiOldLocal() const
×
381
{
382
  return phi_old_local_;
×
383
}
384

385
std::vector<double>&
386
LBSProblem::GetPhiNewLocal()
59,349✔
387
{
388
  return phi_new_local_;
59,349✔
389
}
390

391
const std::vector<double>&
392
LBSProblem::GetPhiNewLocal() const
×
393
{
394
  return phi_new_local_;
×
395
}
396

397
std::vector<double>&
398
LBSProblem::GetPrecursorsNewLocal()
16✔
399
{
400
  return precursor_new_local_;
16✔
401
}
402

403
const std::vector<double>&
404
LBSProblem::GetPrecursorsNewLocal() const
×
405
{
406
  return precursor_new_local_;
×
407
}
408

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

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

421
SetSourceFunction
422
LBSProblem::GetActiveSetSourceFunction() const
4,150✔
423
{
424
  return active_set_source_function_;
4,150✔
425
}
426

427
std::shared_ptr<AGSLinearSolver>
428
LBSProblem::GetAGSSolver()
277✔
429
{
430
  return ags_solver_;
277✔
431
}
432

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

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

449
std::map<uint64_t, BoundaryPreference>&
450
LBSProblem::GetBoundaryPreferences()
530✔
451
{
452
  return boundary_preferences_;
530✔
453
}
454

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

462
  return {num_local_phi_dofs, num_global_phi_dofs};
×
463
}
464

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

472
  return phi_field_functions_local_map_.at({g, m});
22,490✔
473
}
474

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

481
  return field_functions_[power_gen_fieldfunc_local_handle_];
×
482
}
483

484
InputParameters
485
LBSProblem::GetOptionsBlock()
400✔
486
{
487
  InputParameters params;
400✔
488

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

562
  return params;
400✔
563
}
×
564

565
InputParameters
566
LBSProblem::GetBoundaryOptionsBlock()
607✔
567
{
568
  InputParameters params;
607✔
569

570
  params.SetGeneralDescription("Set options for boundary conditions.");
1,214✔
571
  params.AddRequiredParameter<std::string>("name",
1,214✔
572
                                           "Boundary name that identifies the specific boundary");
573
  params.AddRequiredParameter<std::string>("type", "Boundary type specification.");
1,214✔
574
  params.AddOptionalParameterArray<double>("group_strength",
1,214✔
575
                                           {},
576
                                           "Required only if \"type\" is \"isotropic\". An array "
577
                                           "of isotropic strength per group");
578
  params.AddOptionalParameter(
1,214✔
579
    "function_name", "", "Text name of the function to be called for this boundary condition.");
580
  params.ConstrainParameterRange(
1,821✔
581
    "name", AllowableRangeList::New({"xmin", "xmax", "ymin", "ymax", "zmin", "zmax"}));
607✔
582
  params.ConstrainParameterRange("type",
1,821✔
583
                                 AllowableRangeList::New({"vacuum", "isotropic", "reflecting"}));
607✔
584

585
  return params;
607✔
586
}
×
587

588
InputParameters
589
LBSProblem::GetXSMapEntryBlock()
544✔
590
{
591
  InputParameters params;
544✔
592
  params.SetGeneralDescription("Set the cross-section map for the solver.");
1,088✔
593
  params.AddRequiredParameterArray("block_ids", "Mesh block IDs");
1,088✔
594
  params.AddRequiredParameter<std::shared_ptr<MultiGroupXS>>("xs", "Cross-section object");
1,088✔
595
  return params;
544✔
596
}
×
597

598
void
599
LBSProblem::SetOptions(const InputParameters& input)
199✔
600
{
601
  auto params = LBSProblem::GetOptionsBlock();
199✔
602
  params.AssignParameters(input);
199✔
603

604
  // Handle order sensitive options
605
  if (params.IsParameterValid("adjoint"))
199✔
606
  {
607
    const bool adjoint = params.GetParamValue<bool>("adjoint");
199✔
608
    if (adjoint != options_.adjoint)
199✔
609
    {
610
      options_.adjoint = adjoint;
28✔
611

612
      // If a discretization exists, the solver has already been initialized.
613
      // Reinitialize the materials to obtain the appropriate xs and clear the
614
      // sources to prepare for defining the adjoint problem
615
      if (discretization_)
28✔
616
      {
617
        // The materials are reinitialized here to ensure that the proper cross sections
618
        // are available to the solver. Because an adjoint solve requires volumetric or
619
        // point sources, the material-based sources are not set within the initialize routine.
620
        InitializeMaterials();
16✔
621

622
        // Forward and adjoint sources are fundamentally different, so any existing sources
623
        // should be cleared and reset through options upon changing modes.
624
        point_sources_.clear();
16✔
625
        volumetric_sources_.clear();
16✔
626
        boundary_preferences_.clear();
16✔
627

628
        // Set all solutions to zero.
629
        phi_old_local_.assign(phi_old_local_.size(), 0.0);
16✔
630
        phi_new_local_.assign(phi_new_local_.size(), 0.0);
16✔
631
        ZeroSolutions();
16✔
632
        precursor_new_local_.assign(precursor_new_local_.size(), 0.0);
16✔
633
      }
634
    }
635
  }
636

637
  // Handle order insensitive options
638
  for (size_t p = 0; p < params.GetNumParameters(); ++p)
4,378✔
639
  {
640
    const auto& spec = params.GetParam(p);
4,179✔
641

642
    if (spec.GetName() == "max_mpi_message_size")
4,179✔
643
      options_.max_mpi_message_size = spec.GetValue<int>();
199✔
644

645
    else if (spec.GetName() == "restart_writes_enabled")
3,980✔
646
      options_.restart_writes_enabled = spec.GetValue<bool>();
199✔
647

648
    else if (spec.GetName() == "write_delayed_psi_to_restart")
3,781✔
649
      options_.write_delayed_psi_to_restart = spec.GetValue<bool>();
199✔
650

651
    else if (spec.GetName() == "read_restart_path")
3,582✔
652
    {
653
      options_.read_restart_path = spec.GetValue<std::string>();
199✔
654
      if (not options_.read_restart_path.empty())
199✔
655
        options_.read_restart_path += std::to_string(opensn::mpi_comm.rank()) + ".restart.h5";
36✔
656
    }
657

658
    else if (spec.GetName() == "write_restart_path")
3,383✔
659
    {
660
      options_.write_restart_path = spec.GetValue<std::string>();
199✔
661
      if (not options_.write_restart_path.empty())
199✔
662
        options_.write_restart_path += std::to_string(opensn::mpi_comm.rank()) + ".restart.h5";
×
663
    }
664

665
    else if (spec.GetName() == "write_restart_time_interval")
3,184✔
666
      options_.write_restart_time_interval = std::chrono::seconds(spec.GetValue<int>());
199✔
667

668
    else if (spec.GetName() == "use_precursors")
2,985✔
669
      options_.use_precursors = spec.GetValue<bool>();
199✔
670

671
    else if (spec.GetName() == "use_source_moments")
2,786✔
672
      options_.use_src_moments = spec.GetValue<bool>();
199✔
673

674
    else if (spec.GetName() == "save_angular_flux")
2,587✔
675
      options_.save_angular_flux = spec.GetValue<bool>();
199✔
676

677
    else if (spec.GetName() == "verbose_inner_iterations")
2,388✔
678
      options_.verbose_inner_iterations = spec.GetValue<bool>();
199✔
679

680
    else if (spec.GetName() == "max_ags_iterations")
2,189✔
681
      options_.max_ags_iterations = spec.GetValue<int>();
199✔
682

683
    else if (spec.GetName() == "ags_tolerance")
1,990✔
684
      options_.ags_tolerance = spec.GetValue<double>();
199✔
685

686
    else if (spec.GetName() == "ags_convergence_check")
1,791✔
687
    {
688
      auto check = spec.GetValue<std::string>();
199✔
689
      if (check == "pointwise")
199✔
690
        options_.ags_pointwise_convergence = true;
×
691
    }
199✔
692

693
    else if (spec.GetName() == "verbose_ags_iterations")
1,592✔
694
      options_.verbose_ags_iterations = spec.GetValue<bool>();
199✔
695

696
    else if (spec.GetName() == "verbose_outer_iterations")
1,393✔
697
      options_.verbose_outer_iterations = spec.GetValue<bool>();
199✔
698

699
    else if (spec.GetName() == "power_field_function_on")
1,194✔
700
      options_.power_field_function_on = spec.GetValue<bool>();
199✔
701

702
    else if (spec.GetName() == "power_default_kappa")
995✔
703
      options_.power_default_kappa = spec.GetValue<double>();
199✔
704

705
    else if (spec.GetName() == "power_normalization")
796✔
706
      options_.power_normalization = spec.GetValue<double>();
199✔
707

708
    else if (spec.GetName() == "field_function_prefix_option")
597✔
709
    {
710
      options_.field_function_prefix_option = spec.GetValue<std::string>();
199✔
711
    }
712

713
    else if (spec.GetName() == "field_function_prefix")
398✔
714
      options_.field_function_prefix = spec.GetValue<std::string>();
199✔
715
  } // for p
716

717
  if (options_.restart_writes_enabled)
199✔
718
  {
719
    // Create restart directory if necessary
720
    auto dir = options_.write_restart_path.parent_path();
×
721
    if (opensn::mpi_comm.rank() == 0)
×
722
    {
723
      if (not std::filesystem::exists(dir))
×
724
      {
725
        if (not std::filesystem::create_directories(dir))
×
726
          throw std::runtime_error("Failed to create restart directory: " + dir.string());
×
727
      }
728
      else if (not std::filesystem::is_directory(dir))
×
729
        throw std::runtime_error("Restart path exists but is not a directory: " + dir.string());
×
730
    }
731
    opensn::mpi_comm.barrier();
×
732
    UpdateRestartWriteTime();
×
733
  }
×
734
}
199✔
735

736
void
737
LBSProblem::SetBoundaryOptions(const InputParameters& params)
607✔
738
{
739
  const auto boundary_name = params.GetParamValue<std::string>("name");
607✔
740
  const auto bndry_type = params.GetParamValue<std::string>("type");
607✔
741

742
  const auto bid = supported_boundary_names.at(boundary_name);
607✔
743
  const std::map<std::string, LBSBoundaryType> type_list = {
607✔
744
    {"vacuum", LBSBoundaryType::VACUUM},
607✔
745
    {"isotropic", LBSBoundaryType::ISOTROPIC},
607✔
746
    {"reflecting", LBSBoundaryType::REFLECTING}};
2,428✔
747

748
  const auto type = type_list.at(bndry_type);
607✔
749
  switch (type)
607✔
750
  {
751
    case LBSBoundaryType::VACUUM:
526✔
752
    case LBSBoundaryType::REFLECTING:
526✔
753
    {
526✔
754
      GetBoundaryPreferences()[bid] = {type};
526✔
755
      break;
526✔
756
    }
757
    case LBSBoundaryType::ISOTROPIC:
81✔
758
    {
81✔
759
      OpenSnInvalidArgumentIf(not params.Has("group_strength"),
81✔
760
                              "Boundary conditions with type=\"isotropic\" require parameter "
761
                              "\"group_strength\"");
762

763
      params.RequireParameterBlockTypeIs("group_strength", ParameterBlockType::ARRAY);
81✔
764
      const auto group_strength = params.GetParamVectorValue<double>("group_strength");
81✔
765
      boundary_preferences_[bid] = {type, group_strength};
81✔
766
      break;
81✔
767
    }
81✔
768
    case LBSBoundaryType::ARBITRARY:
×
769
    {
×
770
      throw std::runtime_error("Arbitrary boundary conditions are not currently supported");
×
771
      break;
607✔
772
    }
773
  }
774
}
1,821✔
775

776
void
777
LBSProblem::Initialize()
293✔
778
{
779
  CALI_CXX_MARK_SCOPE("LBSProblem::Initialize");
293✔
780

781
  PerformInputChecks(); // assigns num_groups and grid
293✔
782
  PrintSimHeader();
293✔
783

784
  mpi_comm.barrier();
293✔
785

786
  InitializeMaterials();
293✔
787
  InitializeSpatialDiscretization();
293✔
788
  InitializeGroupsets();
293✔
789
  ValidateAndComputeScatteringMoments();
293✔
790
  InitializeParrays();
293✔
791
  InitializeBoundaries();
293✔
792

793
  // Initialize point sources
794
  for (auto& point_source : point_sources_)
302✔
795
    point_source->Initialize(*this);
9✔
796

797
  // Initialize volumetric sources
798
  for (auto& volumetric_source : volumetric_sources_)
610✔
799
    volumetric_source->Initialize(*this);
317✔
800

801
  InitializeGPUExtras();
293✔
802
}
293✔
803

804
void
805
LBSProblem::PerformInputChecks()
293✔
806
{
807
  if (groups_.empty())
293✔
808
    throw std::runtime_error("LBSProblem: No groups added to solver");
×
809

810
  num_groups_ = groups_.size();
293✔
811

812
  if (groupsets_.empty())
293✔
813
    throw std::runtime_error("LBSProblem: No group-sets added to solver");
×
814

815
  int grpset_counter = 0;
816
  for (auto& group_set : groupsets_)
641✔
817
  {
818
    if (group_set.groups.empty())
348✔
819
    {
820
      std::stringstream oss;
×
821
      oss << "LBSPRoblem: No groups added to groupset " << grpset_counter;
×
822
      throw std::runtime_error(oss.str());
×
823
    }
×
824
    ++grpset_counter;
348✔
825
  }
826

827
  if (grid_ == nullptr)
293✔
828
    throw std::runtime_error("LBSProblem: Invalid grid");
×
829

830
  // Determine geometry type
831
  const auto dim = grid_->GetDimension();
293✔
832
  if (dim == 1)
293✔
833
    options_.geometry_type = GeometryType::ONED_SLAB;
44✔
834
  else if (dim == 2)
249✔
835
    options_.geometry_type = GeometryType::TWOD_CARTESIAN;
126✔
836
  else if (dim == 3)
123✔
837
    options_.geometry_type = GeometryType::THREED_CARTESIAN;
123✔
838
  else
839
    OpenSnLogicalError("Cannot deduce geometry type from mesh.");
×
840

841
  // Assign placeholder unit densities
842
  densities_local_.assign(grid_->local_cells.size(), 1.0);
293✔
843
}
293✔
844

845
void
846
LBSProblem::PrintSimHeader()
293✔
847
{
848
  if (opensn::mpi_comm.rank() == 0)
293✔
849
  {
850
    std::stringstream outstr;
78✔
851
    outstr << "\nInitializing LBS SteadyStateSourceSolver with name: " << GetName() << "\n\n"
78✔
852
           << "Scattering order    : " << scattering_order_ << "\n"
156✔
853
           << "Number of Groups    : " << groups_.size() << "\n"
78✔
854
           << "Number of Group sets: " << groupsets_.size() << std::endl;
78✔
855

856
    // Output Groupsets
857
    for (const auto& groupset : groupsets_)
171✔
858
    {
859
      char buf_pol[20];
93✔
860

861
      outstr << "\n***** Groupset " << groupset.id << " *****\n"
93✔
862
             << "Groups:\n";
93✔
863
      int counter = 0;
93✔
864
      for (auto group : groupset.groups)
4,638✔
865
      {
866
        snprintf(buf_pol, 20, "%5d ", group.id);
4,545✔
867
        outstr << std::string(buf_pol);
9,090✔
868
        counter++;
4,545✔
869
        if (counter == 12)
4,545✔
870
        {
871
          counter = 0;
343✔
872
          outstr << "\n";
343✔
873
        }
874
      }
875
    }
876
    log.Log() << outstr.str() << "\n" << std::endl;
156✔
877
  }
78✔
878
}
293✔
879

880
void
881
LBSProblem::InitializeMaterials()
309✔
882
{
883
  CALI_CXX_MARK_SCOPE("LBSProblem::InitializeMaterials");
309✔
884

885
  log.Log0Verbose1() << "Initializing Materials";
618✔
886

887
  // Create set of material ids locally relevant
888
  int invalid_mat_cell_count = 0;
309✔
889
  std::set<int> unique_block_ids;
309✔
890
  for (auto& cell : grid_->local_cells)
321,401✔
891
  {
892
    unique_block_ids.insert(cell.block_id);
321,092✔
893
    if (cell.block_id < 0 or (block_id_to_xs_map_.find(cell.block_id) == block_id_to_xs_map_.end()))
321,092✔
894
      ++invalid_mat_cell_count;
×
895
  }
896
  const auto& ghost_cell_ids = grid_->cells.GetGhostGlobalIDs();
309✔
897
  for (uint64_t cell_id : ghost_cell_ids)
45,330✔
898
  {
899
    const auto& cell = grid_->cells[cell_id];
45,021✔
900
    unique_block_ids.insert(cell.block_id);
45,021✔
901
    if (cell.block_id < 0 or (block_id_to_xs_map_.find(cell.block_id) == block_id_to_xs_map_.end()))
45,021✔
902
      ++invalid_mat_cell_count;
×
903
  }
904
  OpenSnLogicalErrorIf(invalid_mat_cell_count > 0,
309✔
905
                       std::to_string(invalid_mat_cell_count) +
906
                         " cells encountered with an invalid material id.");
907

908
  // Get ready for processing
909
  for (const auto& [blk_id, mat] : block_id_to_xs_map_)
976✔
910
  {
911
    mat->SetAdjointMode(options_.adjoint);
667✔
912

913
    OpenSnLogicalErrorIf(mat->GetNumGroups() < groups_.size(),
667✔
914
                         "Cross-sections on block \"" + std::to_string(blk_id) +
915
                           "\" has fewer groups (" + std::to_string(mat->GetNumGroups()) +
916
                           ") than " + "the simulation (" + std::to_string(groups_.size()) + "). " +
917
                           "Cross-sections must have at least as many groups as the simulation.");
918
  }
919

920
  // Initialize precursor properties
921
  num_precursors_ = 0;
309✔
922
  max_precursors_per_material_ = 0;
309✔
923
  for (const auto& mat_id_xs : block_id_to_xs_map_)
976✔
924
  {
925
    const auto& xs = mat_id_xs.second;
667✔
926
    num_precursors_ += xs->GetNumPrecursors();
667✔
927
    if (xs->GetNumPrecursors() > max_precursors_per_material_)
667✔
928
      max_precursors_per_material_ = xs->GetNumPrecursors();
8✔
929
  }
930

931
  // if no precursors, turn off precursors
932
  if (num_precursors_ == 0)
309✔
933
    options_.use_precursors = false;
301✔
934

935
  // check compatibility when precursors are on
936
  if (options_.use_precursors)
309✔
937
  {
938
    for (const auto& [mat_id, xs] : block_id_to_xs_map_)
16✔
939
    {
940
      OpenSnLogicalErrorIf(xs->IsFissionable() and num_precursors_ == 0,
8✔
941
                           "Incompatible cross-section data encountered for material ID " +
942
                             std::to_string(mat_id) + ". When delayed neutron data is present " +
943
                             "for one fissionable matrial, it must be present for all fissionable "
944
                             "materials.");
945
    }
946
  }
947

948
  // Update transport views if available
949
  if (grid_->local_cells.size() == cell_transport_views_.size())
309✔
950
    for (const auto& cell : grid_->local_cells)
11,816✔
951
    {
952
      const auto& xs_ptr = block_id_to_xs_map_[cell.block_id];
11,800✔
953
      auto& transport_view = cell_transport_views_[cell.local_id];
11,800✔
954
      transport_view.ReassignXS(*xs_ptr);
11,800✔
955
    }
956

957
  mpi_comm.barrier();
309✔
958
}
309✔
959

960
void
961
LBSProblem::InitializeSpatialDiscretization()
285✔
962
{
963
  CALI_CXX_MARK_SCOPE("LBSProblem::InitializeSpatialDiscretization");
285✔
964

965
  log.Log() << "Initializing spatial discretization.\n";
570✔
966
  discretization_ = PieceWiseLinearDiscontinuous::New(grid_);
285✔
967

968
  ComputeUnitIntegrals();
285✔
969
}
285✔
970

971
void
972
LBSProblem::ComputeUnitIntegrals()
293✔
973
{
974
  CALI_CXX_MARK_SCOPE("LBSProblem::ComputeUnitIntegrals");
293✔
975

976
  log.Log() << "Computing unit integrals.\n";
586✔
977
  const auto& sdm = *discretization_;
293✔
978

979
  const size_t num_local_cells = grid_->local_cells.size();
293✔
980
  unit_cell_matrices_.resize(num_local_cells);
293✔
981

982
  for (const auto& cell : grid_->local_cells)
309,585✔
983
    unit_cell_matrices_[cell.local_id] =
309,292✔
984
      ComputeUnitCellIntegrals(sdm, cell, grid_->GetCoordinateSystem());
309,292✔
985

986
  const auto ghost_ids = grid_->cells.GetGhostGlobalIDs();
293✔
987
  for (auto ghost_id : ghost_ids)
44,552✔
988
    unit_ghost_cell_matrices_[ghost_id] =
44,259✔
989
      ComputeUnitCellIntegrals(sdm, grid_->cells[ghost_id], grid_->GetCoordinateSystem());
88,518✔
990

991
  // Assessing global unit cell matrix storage
992
  std::array<size_t, 2> num_local_ucms = {unit_cell_matrices_.size(),
293✔
993
                                          unit_ghost_cell_matrices_.size()};
994
  std::array<size_t, 2> num_global_ucms = {0, 0};
293✔
995

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

998
  opensn::mpi_comm.barrier();
293✔
999
  log.Log() << "Ghost cell unit cell-matrix ratio: "
293✔
1000
            << (double)num_global_ucms[1] * 100 / (double)num_global_ucms[0] << "%";
586✔
1001
  log.Log() << "Cell matrices computed.";
586✔
1002
}
293✔
1003

1004
void
1005
LBSProblem::InitializeGroupsets()
293✔
1006
{
1007
  CALI_CXX_MARK_SCOPE("LBSProblem::InitializeGroupsets");
293✔
1008

1009
  for (auto& groupset : groupsets_)
641✔
1010
  {
1011
    // Build groupset angular flux unknown manager
1012
    groupset.psi_uk_man_.unknowns.clear();
348✔
1013
    size_t num_angles = groupset.quadrature->abscissae.size();
348✔
1014
    size_t gs_num_groups = groupset.groups.size();
348✔
1015
    auto& grpset_psi_uk_man = groupset.psi_uk_man_;
348✔
1016

1017
    const auto VarVecN = UnknownType::VECTOR_N;
348✔
1018
    for (unsigned int n = 0; n < num_angles; ++n)
50,276✔
1019
      grpset_psi_uk_man.AddUnknown(VarVecN, gs_num_groups);
49,928✔
1020

1021
    if (use_gpus_)
348✔
1022
      groupset.InitializeGPUCarriers();
×
1023
  } // for groupset
1024
}
293✔
1025

1026
void
1027
LBSProblem::ValidateAndComputeScatteringMoments()
293✔
1028
{
1029
  /*
1030
    lfs: Legendre order used in the flux solver
1031
    lxs: Legendre order used in the cross-section library
1032
    laq: Legendre order supported by the angular quadrature
1033
  */
1034

1035
  unsigned int lfs = scattering_order_;
293✔
1036

1037
  for (size_t gs = 1; gs < groupsets_.size(); ++gs)
348✔
1038
    if (groupsets_[gs].quadrature->GetScatteringOrder() !=
55✔
1039
        groupsets_[0].quadrature->GetScatteringOrder())
55✔
1040
      throw std::logic_error("LBSProblem: Number of scattering moments differs between groupsets");
×
1041
  auto laq = groupsets_[0].quadrature->GetScatteringOrder();
293✔
1042

1043
  for (const auto& [blk_id, mat] : block_id_to_xs_map_)
920✔
1044
  {
1045
    auto lxs = block_id_to_xs_map_[blk_id]->GetScatteringOrder();
627✔
1046

1047
    if (laq > lxs)
627✔
1048
    {
1049
      log.Log0Warning()
140✔
1050
        << "The quadrature set(s) supports more scattering moments than are present in the "
70✔
1051
        << "cross-section data for block " << blk_id << std::endl;
70✔
1052
    }
1053

1054
    if (lfs < lxs)
627✔
1055
    {
1056
      log.Log0Warning()
716✔
1057
        << "Computing the flux with fewer scattering moments than are present in the "
358✔
1058
        << "cross-section data for block " << blk_id << std::endl;
358✔
1059
    }
1060
    else if (lfs > lxs)
269✔
1061
    {
1062
      log.Log0Warning()
132✔
1063
        << "Computing the flux with more scattering moments than are present in the "
66✔
1064
        << "cross-section data for block " << blk_id << std::endl;
66✔
1065
    }
1066
  }
1067

1068
  if (lfs < laq)
293✔
1069
  {
1070
    log.Log0Warning() << "Using fewer rows/columns of angular matrices (M, D) than the quadrature "
8✔
1071
                      << "supports" << std::endl;
4✔
1072
  }
1073
  else if (lfs > laq)
289✔
1074
    throw std::logic_error(
×
1075
      "LBSProblem: Solver requires more flux moments than the angular quadrature supports");
×
1076

1077
  // Compute number of solver moments.
1078
  auto geometry_type = options_.geometry_type;
293✔
1079
  if (geometry_type == GeometryType::ONED_SLAB or geometry_type == GeometryType::ONED_CYLINDRICAL or
293✔
1080
      geometry_type == GeometryType::ONED_SPHERICAL or
293✔
1081
      geometry_type == GeometryType::TWOD_CYLINDRICAL)
293✔
1082
  {
1083
    num_moments_ = lfs + 1;
52✔
1084
  }
1085
  else if (geometry_type == GeometryType::TWOD_CARTESIAN)
241✔
1086
    num_moments_ = ((lfs + 1) * (lfs + 2)) / 2;
118✔
1087
  else if (geometry_type == GeometryType::THREED_CARTESIAN)
123✔
1088
    num_moments_ = (lfs + 1) * (lfs + 1);
123✔
1089
}
293✔
1090

1091
void
1092
LBSProblem::InitializeParrays()
293✔
1093
{
1094
  CALI_CXX_MARK_SCOPE("LBSProblem::InitializeParrays");
293✔
1095

1096
  log.Log() << "Initializing parallel arrays."
586✔
1097
            << " G=" << num_groups_ << " M=" << num_moments_ << std::endl;
293✔
1098

1099
  // Initialize unknown
1100
  // structure
1101
  flux_moments_uk_man_.unknowns.clear();
293✔
1102
  for (size_t m = 0; m < num_moments_; ++m)
1,120✔
1103
  {
1104
    flux_moments_uk_man_.AddUnknown(UnknownType::VECTOR_N, groups_.size());
827✔
1105
    flux_moments_uk_man_.unknowns.back().name = "m" + std::to_string(m);
827✔
1106
  }
1107

1108
  // Compute local # of dof
1109
  local_node_count_ = discretization_->GetNumLocalNodes();
293✔
1110
  global_node_count_ = discretization_->GetNumGlobalNodes();
293✔
1111

1112
  // Compute num of unknowns
1113
  size_t num_grps = groups_.size();
293✔
1114
  size_t local_unknown_count = local_node_count_ * num_grps * num_moments_;
293✔
1115

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

1118
  // Size local vectors
1119
  q_moments_local_.assign(local_unknown_count, 0.0);
293✔
1120
  phi_old_local_.assign(local_unknown_count, 0.0);
293✔
1121
  phi_new_local_.assign(local_unknown_count, 0.0);
293✔
1122

1123
  // Setup precursor vector
1124
  if (options_.use_precursors)
293✔
1125
  {
1126
    size_t num_precursor_dofs = grid_->local_cells.size() * max_precursors_per_material_;
8✔
1127
    precursor_new_local_.assign(num_precursor_dofs, 0.0);
8✔
1128
  }
1129

1130
  // Initialize transport views
1131
  // Transport views act as a data structure to store information
1132
  // related to the transport simulation. The most prominent function
1133
  // here is that it holds the means to know where a given cell's
1134
  // transport quantities are located in the unknown vectors (i.e. phi)
1135
  //
1136
  // Also, for a given cell, within a given sweep chunk,
1137
  // we need to solve a matrix which square size is the
1138
  // amount of nodes on the cell. max_cell_dof_count is
1139
  // initialized here.
1140
  //
1141
  size_t block_MG_counter = 0; // Counts the strides of moment and group
293✔
1142

1143
  const Vector3 ihat(1.0, 0.0, 0.0);
293✔
1144
  const Vector3 jhat(0.0, 1.0, 0.0);
293✔
1145
  const Vector3 khat(0.0, 0.0, 1.0);
293✔
1146

1147
  max_cell_dof_count_ = 0;
293✔
1148
  cell_transport_views_.clear();
293✔
1149
  cell_transport_views_.reserve(grid_->local_cells.size());
293✔
1150
  for (auto& cell : grid_->local_cells)
309,585✔
1151
  {
1152
    size_t num_nodes = discretization_->GetCellNumNodes(cell);
309,292✔
1153

1154
    // compute cell volumes
1155
    double cell_volume = 0.0;
309,292✔
1156
    const auto& IntV_shapeI = unit_cell_matrices_[cell.local_id].intV_shapeI;
309,292✔
1157
    for (size_t i = 0; i < num_nodes; ++i)
2,626,530✔
1158
      cell_volume += IntV_shapeI(i);
2,317,238✔
1159

1160
    size_t cell_phi_address = block_MG_counter;
309,292✔
1161

1162
    const size_t num_faces = cell.faces.size();
309,292✔
1163
    std::vector<bool> face_local_flags(num_faces, true);
309,292✔
1164
    std::vector<int> face_locality(num_faces, opensn::mpi_comm.rank());
309,292✔
1165
    std::vector<const Cell*> neighbor_cell_ptrs(num_faces, nullptr);
309,292✔
1166
    bool cell_on_boundary = false;
309,292✔
1167
    int f = 0;
309,292✔
1168
    for (auto& face : cell.faces)
2,077,994✔
1169
    {
1170
      if (not face.has_neighbor)
1,768,702✔
1171
      {
1172
        Vector3& n = face.normal;
58,328✔
1173

1174
        int boundary_id = -1;
58,328✔
1175
        if (n.Dot(ihat) < -0.999)
58,328✔
1176
          boundary_id = XMIN;
1177
        else if (n.Dot(ihat) > 0.999)
49,947✔
1178
          boundary_id = XMAX;
1179
        else if (n.Dot(jhat) < -0.999)
41,728✔
1180
          boundary_id = YMIN;
1181
        else if (n.Dot(jhat) > 0.999)
33,541✔
1182
          boundary_id = YMAX;
1183
        else if (n.Dot(khat) < -0.999)
25,516✔
1184
          boundary_id = ZMIN;
1185
        else if (n.Dot(khat) > 0.999)
12,758✔
1186
          boundary_id = ZMAX;
1187

1188
        if (boundary_id >= 0)
1189
          face.neighbor_id = boundary_id;
58,328✔
1190
        cell_on_boundary = true;
58,328✔
1191

1192
        face_local_flags[f] = false;
58,328✔
1193
        face_locality[f] = -1;
58,328✔
1194
      } // if bndry
1195
      else
1196
      {
1197
        const int neighbor_partition = face.GetNeighborPartitionID(grid_.get());
1,710,374✔
1198
        face_local_flags[f] = (neighbor_partition == opensn::mpi_comm.rank());
1,710,374✔
1199
        face_locality[f] = neighbor_partition;
1,710,374✔
1200
        neighbor_cell_ptrs[f] = &grid_->cells[face.neighbor_id];
1,710,374✔
1201
      }
1202

1203
      ++f;
1,768,702✔
1204
    } // for f
1205

1206
    if (num_nodes > max_cell_dof_count_)
309,292✔
1207
      max_cell_dof_count_ = num_nodes;
453✔
1208
    cell_transport_views_.emplace_back(cell_phi_address,
618,584✔
1209
                                       num_nodes,
1210
                                       num_grps,
1211
                                       num_moments_,
309,292✔
1212
                                       num_faces,
1213
                                       *block_id_to_xs_map_[cell.block_id],
309,292✔
1214
                                       cell_volume,
1215
                                       face_local_flags,
1216
                                       face_locality,
1217
                                       neighbor_cell_ptrs,
1218
                                       cell_on_boundary);
1219
    block_MG_counter += num_nodes * num_grps * num_moments_;
309,292✔
1220
  } // for local cell
309,292✔
1221

1222
  // Populate grid nodal mappings
1223
  // This is used in the Flux Data Structures (FLUDS)
1224
  grid_nodal_mappings_.clear();
293✔
1225
  grid_nodal_mappings_.reserve(grid_->local_cells.size());
293✔
1226
  for (auto& cell : grid_->local_cells)
309,585✔
1227
  {
1228
    CellFaceNodalMapping cell_nodal_mapping;
309,292✔
1229
    cell_nodal_mapping.reserve(cell.faces.size());
309,292✔
1230

1231
    for (auto& face : cell.faces)
2,077,994✔
1232
    {
1233
      std::vector<short> face_node_mapping;
1,768,702✔
1234
      std::vector<short> cell_node_mapping;
1,768,702✔
1235
      int adj_face_idx = -1;
1,768,702✔
1236

1237
      if (face.has_neighbor)
1,768,702✔
1238
      {
1239
        grid_->FindAssociatedVertices(face, face_node_mapping);
1,710,374✔
1240
        grid_->FindAssociatedCellVertices(face, cell_node_mapping);
1,710,374✔
1241
        adj_face_idx = face.GetNeighborAdjacentFaceIndex(grid_.get());
1,710,374✔
1242
      }
1243

1244
      cell_nodal_mapping.emplace_back(adj_face_idx, face_node_mapping, cell_node_mapping);
1,768,702✔
1245
    } // for f
1,768,702✔
1246

1247
    grid_nodal_mappings_.push_back(cell_nodal_mapping);
309,292✔
1248
  } // for local cell
309,292✔
1249

1250
  // Get grid localized communicator set
1251
  grid_local_comm_set_ = grid_->MakeMPILocalCommunicatorSet();
293✔
1252

1253
  // Initialize Field Functions
1254
  InitializeFieldFunctions();
293✔
1255

1256
  opensn::mpi_comm.barrier();
293✔
1257
  log.Log() << "Done with parallel arrays." << std::endl;
586✔
1258
}
293✔
1259

1260
void
1261
LBSProblem::InitializeFieldFunctions()
293✔
1262
{
1263
  CALI_CXX_MARK_SCOPE("LBSProblem::InitializeFieldFunctions");
293✔
1264

1265
  if (not field_functions_.empty())
293✔
1266
    return;
×
1267

1268
  // Initialize Field Functions for flux moments
1269
  phi_field_functions_local_map_.clear();
293✔
1270

1271
  for (size_t g = 0; g < groups_.size(); ++g)
19,936✔
1272
  {
1273
    for (size_t m = 0; m < num_moments_; ++m)
92,394✔
1274
    {
1275
      std::string prefix;
72,751✔
1276
      if (options_.field_function_prefix_option == "prefix")
72,751✔
1277
      {
1278
        prefix = options_.field_function_prefix;
72,751✔
1279
        if (not prefix.empty())
72,751✔
1280
          prefix += "_";
1✔
1281
      }
1282
      if (options_.field_function_prefix_option == "solver_name")
72,751✔
1283
        prefix = GetName() + "_";
×
1284

1285
      char buff[100];
72,751✔
1286
      snprintf(
72,751✔
1287
        buff, 99, "%sphi_g%03d_m%02d", prefix.c_str(), static_cast<int>(g), static_cast<int>(m));
1288
      const std::string name = std::string(buff);
72,751✔
1289

1290
      auto group_ff = std::make_shared<FieldFunctionGridBased>(
72,751✔
1291
        name, discretization_, Unknown(UnknownType::SCALAR));
72,751✔
1292

1293
      field_function_stack.push_back(group_ff);
145,502✔
1294
      field_functions_.push_back(group_ff);
72,751✔
1295

1296
      phi_field_functions_local_map_[{g, m}] = field_functions_.size() - 1;
72,751✔
1297
    } // for m
72,751✔
1298
  } // for g
1299

1300
  // Initialize power generation field function
1301
  if (options_.power_field_function_on)
293✔
1302
  {
1303
    std::string prefix;
4✔
1304
    if (options_.field_function_prefix_option == "prefix")
4✔
1305
    {
1306
      prefix = options_.field_function_prefix;
4✔
1307
      if (not prefix.empty())
4✔
1308
        prefix += "_";
×
1309
    }
1310
    if (options_.field_function_prefix_option == "solver_name")
4✔
1311
      prefix = GetName() + "_";
×
1312

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

1316
    field_function_stack.push_back(power_ff);
8✔
1317
    field_functions_.push_back(power_ff);
4✔
1318

1319
    power_gen_fieldfunc_local_handle_ = field_functions_.size() - 1;
4✔
1320
  }
4✔
1321
}
293✔
1322

1323
void
1324
LBSProblem::InitializeSolverSchemes()
293✔
1325
{
1326
  CALI_CXX_MARK_SCOPE("LBSProblem::InitializeSolverSchemes");
293✔
1327

1328
  log.Log() << "Initializing WGS and AGS solvers";
586✔
1329

1330
  InitializeWGSSolvers();
293✔
1331

1332
  ags_solver_ = std::make_shared<AGSLinearSolver>(*this, wgs_solvers_);
293✔
1333
  if (groupsets_.size() == 1)
293✔
1334
  {
1335
    ags_solver_->SetMaxIterations(1);
242✔
1336
    ags_solver_->SetVerbosity(false);
242✔
1337
  }
1338
  else
1339
  {
1340
    ags_solver_->SetMaxIterations(options_.max_ags_iterations);
51✔
1341
    ags_solver_->SetVerbosity(options_.verbose_ags_iterations);
51✔
1342
  }
1343
  ags_solver_->SetTolerance(options_.ags_tolerance);
293✔
1344
}
293✔
1345

1346
#ifndef __OPENSN_USE_CUDA__
1347
void
1348
LBSProblem::InitializeGPUExtras()
293✔
1349
{
1350
}
293✔
1351

1352
void
1353
LBSProblem::ResetGPUCarriers()
289✔
1354
{
1355
}
289✔
1356

1357
void
1358
LBSProblem::CheckCapableDevices()
×
1359
{
1360
}
×
1361

1362
#endif // __OPENSN_USE_CUDA__
1363

1364
std::vector<double>
1365
LBSProblem::MakeSourceMomentsFromPhi()
4✔
1366
{
1367
  CALI_CXX_MARK_SCOPE("LBSProblem::MakeSourceMomentsFromPhi");
4✔
1368

1369
  size_t num_local_dofs = discretization_->GetNumLocalDOFs(flux_moments_uk_man_);
4✔
1370

1371
  std::vector<double> source_moments(num_local_dofs, 0.0);
4✔
1372
  for (auto& groupset : groupsets_)
8✔
1373
  {
1374
    active_set_source_function_(groupset,
4✔
1375
                                source_moments,
1376
                                phi_new_local_,
4✔
1377
                                APPLY_AGS_SCATTER_SOURCES | APPLY_WGS_SCATTER_SOURCES |
1378
                                  APPLY_AGS_FISSION_SOURCES | APPLY_WGS_FISSION_SOURCES);
4✔
1379
  }
1380

1381
  return source_moments;
4✔
1382
}
4✔
1383

1384
void
1385
LBSProblem::UpdateFieldFunctions()
305✔
1386
{
1387
  CALI_CXX_MARK_SCOPE("LBSProblem::UpdateFieldFunctions");
305✔
1388

1389
  const auto& sdm = *discretization_;
305✔
1390
  const auto& phi_uk_man = flux_moments_uk_man_;
305✔
1391

1392
  // Update flux moments
1393
  for (const auto& [g_and_m, ff_index] : phi_field_functions_local_map_)
73,104✔
1394
  {
1395
    const size_t g = g_and_m.first;
72,799✔
1396
    const size_t m = g_and_m.second;
72,799✔
1397

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

1400
    for (const auto& cell : grid_->local_cells)
17,509,640✔
1401
    {
1402
      const auto& cell_mapping = sdm.GetCellMapping(cell);
17,436,841✔
1403
      const size_t num_nodes = cell_mapping.GetNumNodes();
17,436,841✔
1404

1405
      for (size_t i = 0; i < num_nodes; ++i)
123,343,861✔
1406
      {
1407
        const auto imapA = sdm.MapDOFLocal(cell, i, phi_uk_man, m, g);
105,907,020✔
1408
        const auto imapB = sdm.MapDOFLocal(cell, i);
105,907,020✔
1409

1410
        data_vector_local[imapB] = phi_new_local_[imapA];
105,907,020✔
1411
      } // for node
1412
    } // for cell
1413

1414
    auto& ff_ptr = field_functions_.at(ff_index);
72,799✔
1415
    ff_ptr->UpdateFieldVector(data_vector_local);
72,799✔
1416
  }
72,799✔
1417

1418
  // Update power generation
1419
  if (options_.power_field_function_on)
305✔
1420
  {
1421
    std::vector<double> data_vector_local(local_node_count_, 0.0);
4✔
1422

1423
    double local_total_power = 0.0;
4✔
1424
    for (const auto& cell : grid_->local_cells)
83,268✔
1425
    {
1426
      const auto& cell_mapping = sdm.GetCellMapping(cell);
83,264✔
1427
      const size_t num_nodes = cell_mapping.GetNumNodes();
83,264✔
1428

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

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

1433
      if (not xs->IsFissionable())
83,264✔
1434
        continue;
56,360✔
1435

1436
      for (size_t i = 0; i < num_nodes; ++i)
134,520✔
1437
      {
1438
        const auto imapA = sdm.MapDOFLocal(cell, i);
107,616✔
1439
        const auto imapB = sdm.MapDOFLocal(cell, i, phi_uk_man, 0, 0);
107,616✔
1440

1441
        double nodal_power = 0.0;
1442
        for (size_t g = 0; g < groups_.size(); ++g)
860,928✔
1443
        {
1444
          const double sigma_fg = xs->GetSigmaFission()[g];
753,312✔
1445
          // const double kappa_g = xs->Kappa()[g];
1446
          const double kappa_g = options_.power_default_kappa;
753,312✔
1447

1448
          nodal_power += kappa_g * sigma_fg * phi_new_local_[imapB + g];
753,312✔
1449
        } // for g
1450

1451
        data_vector_local[imapA] = nodal_power;
107,616✔
1452
        local_total_power += nodal_power * Vi(i);
107,616✔
1453
      } // for node
1454
    } // for cell
1455

1456
    if (options_.power_normalization > 0.0)
4✔
1457
    {
1458
      double global_total_power;
4✔
1459
      mpi_comm.all_reduce(local_total_power, global_total_power, mpi::op::sum<double>());
4✔
1460

1461
      Scale(data_vector_local, options_.power_normalization / global_total_power);
4✔
1462
    }
1463

1464
    const size_t ff_index = power_gen_fieldfunc_local_handle_;
4✔
1465

1466
    auto& ff_ptr = field_functions_.at(ff_index);
4✔
1467
    ff_ptr->UpdateFieldVector(data_vector_local);
4✔
1468

1469
  } // if power enabled
4✔
1470
}
305✔
1471

1472
void
1473
LBSProblem::SetPhiFromFieldFunctions(PhiSTLOption which_phi,
×
1474
                                     const std::vector<size_t>& m_indices,
1475
                                     const std::vector<size_t>& g_indices)
1476
{
1477
  CALI_CXX_MARK_SCOPE("LBSProblem::SetPhiFromFieldFunctions");
×
1478

1479
  std::vector<size_t> m_ids_to_copy = m_indices;
×
1480
  std::vector<size_t> g_ids_to_copy = g_indices;
×
1481
  if (m_indices.empty())
×
1482
    for (size_t m = 0; m < num_moments_; ++m)
×
1483
      m_ids_to_copy.push_back(m);
×
1484
  if (g_ids_to_copy.empty())
×
1485
    for (size_t g = 0; g < num_groups_; ++g)
×
1486
      g_ids_to_copy.push_back(g);
×
1487

1488
  const auto& sdm = *discretization_;
×
1489
  const auto& phi_uk_man = flux_moments_uk_man_;
×
1490

1491
  for (const size_t m : m_ids_to_copy)
×
1492
  {
1493
    for (const size_t g : g_ids_to_copy)
×
1494
    {
1495
      const size_t ff_index = phi_field_functions_local_map_.at({g, m});
×
1496
      const auto& ff_ptr = field_functions_.at(ff_index);
×
1497
      const auto& ff_data = ff_ptr->GetLocalFieldVector();
×
1498

1499
      for (const auto& cell : grid_->local_cells)
×
1500
      {
1501
        const auto& cell_mapping = sdm.GetCellMapping(cell);
×
1502
        const size_t num_nodes = cell_mapping.GetNumNodes();
×
1503

1504
        for (size_t i = 0; i < num_nodes; ++i)
×
1505
        {
NEW
1506
          const auto imapA = sdm.MapDOFLocal(cell, i);
×
NEW
1507
          const auto imapB = sdm.MapDOFLocal(cell, i, phi_uk_man, m, g);
×
1508

1509
          if (which_phi == PhiSTLOption::PHI_OLD)
×
1510
            phi_old_local_[imapB] = ff_data[imapA];
×
1511
          else if (which_phi == PhiSTLOption::PHI_NEW)
×
1512
            phi_new_local_[imapB] = ff_data[imapA];
×
1513
        } // for node
1514
      } // for cell
1515
    } // for g
1516
  } // for m
1517
}
×
1518

1519
LBSProblem::~LBSProblem()
289✔
1520
{
1521
  ResetGPUCarriers();
1522
}
1,445✔
1523

289✔
1524
} // 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