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

Open-Sn / opensn / 17366614522

25 Aug 2025 08:16PM UTC coverage: 74.602%. Remained the same
17366614522

push

github

web-flow
Merge pull request #744 from andrsd/issue/740

Don't call `InitializeBoundaries` from a ctor

17548 of 23522 relevant lines covered (74.6%)

45241954.71 hits per line

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

84.51
/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.AddOptionalParameterBlock(
526✔
63
    "options", ParameterBlock(), "Block of options. See <TT>OptionsBlock</TT>.");
526✔
64
  params.LinkParameterToBlock("options", "OptionsBlock");
526✔
65

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

68
  return params;
263✔
69
}
×
70

71
LBSProblem::LBSProblem(const InputParameters& params)
263✔
72
  : Problem(params),
73
    scattering_order_(params.GetParamValue<size_t>("scattering_order")),
263✔
74
    grid_(params.GetSharedPtrParam<MeshContinuum>("mesh")),
263✔
75
    use_gpus_(params.GetParamValue<bool>("use_gpus"))
789✔
76
{
77
  // Make groups
78
  const auto num_groups = params.GetParamValue<size_t>("num_groups");
263✔
79
  for (size_t g = 0; g < num_groups; ++g)
18,578✔
80
    groups_.emplace_back(static_cast<int>(g));
18,315✔
81

82
  // Make groupsets
83
  const auto& groupsets_array = params.GetParam("groupsets");
263✔
84

85
  const size_t num_gs = groupsets_array.GetNumParameters();
263✔
86
  for (size_t gs = 0; gs < num_gs; ++gs)
581✔
87
  {
88
    const auto& groupset_params = groupsets_array.GetParam(gs);
318✔
89

90
    InputParameters gs_input_params = LBSGroupset::GetInputParameters();
318✔
91
    gs_input_params.SetObjectType("LBSProblem:LBSGroupset");
318✔
92
    gs_input_params.AssignParameters(groupset_params);
318✔
93

94
    groupsets_.emplace_back(gs_input_params, gs, *this);
318✔
95
  }
318✔
96

97
  // Build XS map
98
  const auto& xs_array = params.GetParam("xs_map");
263✔
99
  const size_t num_xs = xs_array.GetNumParameters();
263✔
100
  for (size_t i = 0; i < num_xs; ++i)
671✔
101
  {
102
    const auto& item_params = xs_array.GetParam(i);
408✔
103
    InputParameters xs_entry_pars = GetXSMapEntryBlock();
408✔
104
    xs_entry_pars.AssignParameters(item_params);
408✔
105

106
    const auto& block_ids_param = xs_entry_pars.GetParam("block_ids");
408✔
107
    block_ids_param.RequireBlockTypeIs(ParameterBlockType::ARRAY);
408✔
108
    const auto& block_ids = block_ids_param.GetVectorValue<int>();
408✔
109
    auto xs = xs_entry_pars.GetSharedPtrParam<MultiGroupXS>("xs");
408✔
110
    for (const auto& block_id : block_ids)
901✔
111
      block_id_to_xs_map_[block_id] = xs;
493✔
112
  }
408✔
113
  // Check system for GPU acceleration
114
  if (use_gpus_)
263✔
115
  {
116
#ifdef __OPENSN_USE_CUDA__
117
    CheckCapableDevices();
118
#else
119
    throw std::invalid_argument(
×
120
      "GPU support was requested, but OpenSn was built without CUDA enabled.\n");
×
121
#endif // __OPENSN_USE_CUDA__
122
  }
123

124
  // Options
125
  if (params.IsParameterValid("options"))
263✔
126
  {
127
    auto options_params = LBSProblem::GetOptionsBlock();
255✔
128
    options_params.AssignParameters(params.GetParam("options"));
257✔
129
    SetOptions(options_params);
253✔
130
  }
255✔
131
}
283✔
132

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

139
const LBSOptions&
140
LBSProblem::GetOptions() const
529,672,538✔
141
{
142
  return options_;
529,672,538✔
143
}
144

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

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

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

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

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

175
void
176
LBSProblem::AddGroup(int id)
×
177
{
178
  if (id < 0)
×
179
    groups_.emplace_back(static_cast<int>(groups_.size()));
×
180
  else
181
    groups_.emplace_back(id);
×
182
}
×
183

184
const std::vector<LBSGroup>&
185
LBSProblem::GetGroups() const
136,604✔
186
{
187
  return groups_;
136,604✔
188
}
189

190
void
191
LBSProblem::AddGroupset()
×
192
{
193
  groupsets_.emplace_back(static_cast<int>(groupsets_.size()));
×
194
}
×
195

196
std::vector<LBSGroupset>&
197
LBSProblem::GetGroupsets()
8,170,775✔
198
{
199
  return groupsets_;
8,170,775✔
200
}
201

202
const std::vector<LBSGroupset>&
203
LBSProblem::GetGroupsets() const
×
204
{
205
  return groupsets_;
×
206
}
207

208
void
209
LBSProblem::AddPointSource(std::shared_ptr<PointSource> point_source)
×
210
{
211
  point_sources_.push_back(point_source);
×
212
}
×
213

214
void
215
LBSProblem::ClearPointSources()
×
216
{
217
  point_sources_.clear();
×
218
}
×
219

220
const std::vector<std::shared_ptr<PointSource>>&
221
LBSProblem::GetPointSources() const
7,334✔
222
{
223
  return point_sources_;
7,334✔
224
}
225

226
void
227
LBSProblem::AddVolumetricSource(std::shared_ptr<VolumetricSource> volumetric_source)
×
228
{
229
  volumetric_sources_.push_back(volumetric_source);
×
230
}
×
231

232
void
233
LBSProblem::ClearVolumetricSources()
×
234
{
235
  volumetric_sources_.clear();
×
236
}
×
237

238
const std::vector<std::shared_ptr<VolumetricSource>>&
239
LBSProblem::GetVolumetricSources() const
7,334✔
240
{
241
  return volumetric_sources_;
7,334✔
242
}
243

244
const std::map<int, std::shared_ptr<MultiGroupXS>>&
245
LBSProblem::GetMatID2XSMap() const
6,681✔
246
{
247
  return block_id_to_xs_map_;
6,681✔
248
}
249

250
std::shared_ptr<MeshContinuum>
251
LBSProblem::GetGrid() const
179,792✔
252
{
253
  return grid_;
179,792✔
254
}
255

256
const SpatialDiscretization&
257
LBSProblem::GetSpatialDiscretization() const
63,912✔
258
{
259
  return *discretization_;
63,912✔
260
}
261

262
const std::vector<UnitCellMatrices>&
263
LBSProblem::GetUnitCellMatrices() const
7,239✔
264
{
265
  return unit_cell_matrices_;
7,239✔
266
}
267

268
const std::map<uint64_t, UnitCellMatrices>&
269
LBSProblem::GetUnitGhostCellMatrices() const
17✔
270
{
271
  return unit_ghost_cell_matrices_;
17✔
272
}
273

274
const std::vector<CellLBSView>&
275
LBSProblem::GetCellTransportViews() const
189,820✔
276
{
277
  return cell_transport_views_;
189,820✔
278
}
279

280
const UnknownManager&
281
LBSProblem::GetUnknownManager() const
26,663✔
282
{
283
  return flux_moments_uk_man_;
26,663✔
284
}
285

286
size_t
287
LBSProblem::GetLocalNodeCount() const
3,017✔
288
{
289
  return local_node_count_;
3,017✔
290
}
291

292
size_t
293
LBSProblem::GetGlobalNodeCount() const
1,418✔
294
{
295
  return global_node_count_;
1,418✔
296
}
297

298
std::vector<double>&
299
LBSProblem::GetQMomentsLocal()
37,237✔
300
{
301
  return q_moments_local_;
37,237✔
302
}
303

304
const std::vector<double>&
305
LBSProblem::GetQMomentsLocal() const
×
306
{
307
  return q_moments_local_;
×
308
}
309

310
std::vector<double>&
311
LBSProblem::GetExtSrcMomentsLocal()
4✔
312
{
313
  return ext_src_moments_local_;
4✔
314
}
315

316
const std::vector<double>&
317
LBSProblem::GetExtSrcMomentsLocal() const
37,079✔
318
{
319
  return ext_src_moments_local_;
37,079✔
320
}
321

322
std::vector<double>&
323
LBSProblem::GetPhiOldLocal()
56,619✔
324
{
325
  return phi_old_local_;
56,619✔
326
}
327

328
const std::vector<double>&
329
LBSProblem::GetPhiOldLocal() const
×
330
{
331
  return phi_old_local_;
×
332
}
333

334
std::vector<double>&
335
LBSProblem::GetPhiNewLocal()
59,153✔
336
{
337
  return phi_new_local_;
59,153✔
338
}
339

340
const std::vector<double>&
341
LBSProblem::GetPhiNewLocal() const
×
342
{
343
  return phi_new_local_;
×
344
}
345

346
std::vector<double>&
347
LBSProblem::GetPrecursorsNewLocal()
16✔
348
{
349
  return precursor_new_local_;
16✔
350
}
351

352
const std::vector<double>&
353
LBSProblem::GetPrecursorsNewLocal() const
×
354
{
355
  return precursor_new_local_;
×
356
}
357

358
std::vector<std::vector<double>>&
359
LBSProblem::GetPsiNewLocal()
31✔
360
{
361
  return psi_new_local_;
31✔
362
}
363

364
const std::vector<std::vector<double>>&
365
LBSProblem::GetPsiNewLocal() const
×
366
{
367
  return psi_new_local_;
×
368
}
369

370
std::vector<double>&
371
LBSProblem::GetDensitiesLocal()
250✔
372
{
373
  return densities_local_;
250✔
374
}
375

376
const std::vector<double>&
377
LBSProblem::GetDensitiesLocal() const
37,079✔
378
{
379
  return densities_local_;
37,079✔
380
}
381

382
SetSourceFunction
383
LBSProblem::GetActiveSetSourceFunction() const
4,146✔
384
{
385
  return active_set_source_function_;
4,146✔
386
}
387

388
std::shared_ptr<AGSLinearSolver>
389
LBSProblem::GetAGSSolver()
241✔
390
{
391
  return ags_solver_;
241✔
392
}
393

394
std::vector<std::shared_ptr<LinearSolver>>&
395
LBSProblem::GetWGSSolvers()
113✔
396
{
397
  return wgs_solvers_;
113✔
398
}
399

400
WGSContext&
401
LBSProblem::GetWGSContext(int groupset_id)
11,508✔
402
{
403
  auto& wgs_solver = wgs_solvers_[groupset_id];
11,508✔
404
  auto raw_context = wgs_solver->GetContext();
11,508✔
405
  auto wgs_context_ptr = std::dynamic_pointer_cast<WGSContext>(raw_context);
11,508✔
406
  OpenSnLogicalErrorIf(not wgs_context_ptr, "Failed to cast WGSContext");
11,508✔
407
  return *wgs_context_ptr;
11,508✔
408
}
23,016✔
409

410
std::map<uint64_t, BoundaryPreference>&
411
LBSProblem::GetBoundaryPreferences()
362✔
412
{
413
  return boundary_preferences_;
362✔
414
}
415

416
std::pair<size_t, size_t>
417
LBSProblem::GetNumPhiIterativeUnknowns()
×
418
{
419
  const auto& sdm = *discretization_;
×
420
  const size_t num_local_phi_dofs = sdm.GetNumLocalDOFs(flux_moments_uk_man_);
×
421
  const size_t num_global_phi_dofs = sdm.GetNumGlobalDOFs(flux_moments_uk_man_);
×
422

423
  return {num_local_phi_dofs, num_global_phi_dofs};
×
424
}
425

426
size_t
427
LBSProblem::MapPhiFieldFunction(size_t g, size_t m) const
20,822✔
428
{
429
  OpenSnLogicalErrorIf(phi_field_functions_local_map_.count({g, m}) == 0,
20,822✔
430
                       std::string("Failure to map phi field function g") + std::to_string(g) +
431
                         " m" + std::to_string(m));
432

433
  return phi_field_functions_local_map_.at({g, m});
20,822✔
434
}
435

436
std::shared_ptr<FieldFunctionGridBased>
437
LBSProblem::GetPowerFieldFunction() const
×
438
{
439
  OpenSnLogicalErrorIf(not options_.power_field_function_on,
×
440
                       "Called when options_.power_field_function_on == false");
441

442
  return field_functions_[power_gen_fieldfunc_local_handle_];
×
443
}
444

445
InputParameters
446
LBSProblem::GetOptionsBlock()
548✔
447
{
448
  InputParameters params;
548✔
449

450
  params.SetGeneralDescription("Set options from a large list of parameters");
1,096✔
451
  params.AddOptionalParameter("max_mpi_message_size",
1,096✔
452
                              32768,
453
                              "The maximum MPI message size used during sweep initialization.");
454
  params.AddOptionalParameter(
1,096✔
455
    "restart_writes_enabled", false, "Flag that controls writing of restart dumps");
456
  params.AddOptionalParameter("write_delayed_psi_to_restart",
1,096✔
457
                              true,
458
                              "Flag that controls writing of delayed angular fluxes to restarts.");
459
  params.AddOptionalParameter(
1,096✔
460
    "read_restart_path", "", "Full path for reading restart dumps including file stem.");
461
  params.AddOptionalParameter(
1,096✔
462
    "write_restart_path", "", "Full path for writing restart dumps including file stem.");
463
  params.AddOptionalParameter("write_restart_time_interval",
1,096✔
464
                              0,
465
                              "Time interval in seconds at which restart data is to be written.");
466
  params.AddOptionalParameter(
1,096✔
467
    "use_precursors", false, "Flag for using delayed neutron precursors.");
468
  params.AddOptionalParameter("use_source_moments",
1,096✔
469
                              false,
470
                              "Flag for ignoring fixed sources and selectively using source "
471
                              "moments obtained elsewhere.");
472
  params.AddOptionalParameter(
1,096✔
473
    "save_angular_flux", false, "Flag indicating whether angular fluxes are to be stored or not.");
474
  params.AddOptionalParameter(
1,096✔
475
    "adjoint", false, "Flag for toggling whether the solver is in adjoint mode.");
476
  params.AddOptionalParameter(
1,096✔
477
    "verbose_inner_iterations", true, "Flag to control verbosity of inner iterations.");
478
  params.AddOptionalParameter(
1,096✔
479
    "verbose_outer_iterations", true, "Flag to control verbosity of across-groupset iterations.");
480
  params.AddOptionalParameter(
1,096✔
481
    "max_ags_iterations", 100, "Maximum number of across-groupset iterations.");
482
  params.AddOptionalParameter("ags_tolerance", 1.0e-6, "Across-groupset iterations tolerance.");
1,096✔
483
  params.AddOptionalParameter("ags_convergence_check",
1,096✔
484
                              "l2",
485
                              "Type of convergence check for AGS iterations. Valid values are "
486
                              "`\"l2\"` and '\"pointwise\"'");
487
  params.AddOptionalParameter(
1,096✔
488
    "verbose_ags_iterations", true, "Flag to control verbosity of across-groupset iterations.");
489
  params.AddOptionalParameter("power_field_function_on",
1,096✔
490
                              false,
491
                              "Flag to control the creation of the power generation field "
492
                              "function. If set to `true` then a field function will be created "
493
                              "with the general name <solver_name>_power_generation`.");
494
  params.AddOptionalParameter("power_default_kappa",
1,096✔
495
                              3.20435e-11,
496
                              "Default `kappa` value (Energy released per fission) to use for "
497
                              "power generation when cross sections do not have `kappa` values. "
498
                              "Default: 3.20435e-11 Joule (corresponding to 200 MeV per fission).");
499
  params.AddOptionalParameter("power_normalization",
1,096✔
500
                              -1.0,
501
                              "Power normalization factor to use. Supply a negative or zero number "
502
                              "to turn this off.");
503
  params.AddOptionalParameter("field_function_prefix_option",
1,096✔
504
                              "prefix",
505
                              "Prefix option on field function names. Default: `\"prefix\"`. Can "
506
                              "be `\"prefix\"` or `\"solver_name\"`. By default this option uses "
507
                              "the value of the `field_function_prefix` parameter. If this "
508
                              "parameter is not set, flux field functions will be exported as "
509
                              "`phi_gXXX_mYYY` where `XXX` is the zero padded 3 digit group number "
510
                              "and `YYY` is the zero padded 3 digit moment.");
511
  params.AddOptionalParameter("field_function_prefix",
1,096✔
512
                              "",
513
                              "Prefix to use on all field functions. Default: `\"\"`. By default "
514
                              "this option is empty. Ff specified, flux moments will be exported "
515
                              "as `prefix_phi_gXXX_mYYY` where `XXX` is the zero padded 3 digit "
516
                              "group number and `YYY` is the zero padded 3 digit moment. The "
517
                              "underscore after \"prefix\" is added automatically.");
518
  params.AddOptionalParameterArray(
1,096✔
519
    "boundary_conditions", {}, "An array containing tables for each boundary specification.");
520
  params.LinkParameterToBlock("boundary_conditions", "BoundaryOptionsBlock");
1,096✔
521
  params.AddOptionalParameter("clear_boundary_conditions",
1,096✔
522
                              false,
523
                              "Clears all boundary conditions. If no additional boundary "
524
                              "conditions are supplied, this results in all boundaries being "
525
                              "vacuum.");
526
  params.AddOptionalParameterArray<std::shared_ptr<PointSource>>(
1,096✔
527
    "point_sources", {}, "An array of point sources.");
528
  params.AddOptionalParameter("clear_point_sources", false, "Clears all point sources.");
1,096✔
529
  params.AddOptionalParameterArray<std::shared_ptr<VolumetricSource>>(
1,096✔
530
    "volumetric_sources", {}, "An array of handles to volumetric sources.");
531
  params.AddOptionalParameter("clear_volumetric_sources", false, "Clears all volumetric sources.");
1,096✔
532
  params.ConstrainParameterRange("ags_convergence_check",
1,644✔
533
                                 AllowableRangeList::New({"l2", "pointwise"}));
548✔
534
  params.ConstrainParameterRange("field_function_prefix_option",
1,644✔
535
                                 AllowableRangeList::New({"prefix", "solver_name"}));
548✔
536

537
  return params;
548✔
538
}
×
539

540
InputParameters
541
LBSProblem::GetBoundaryOptionsBlock()
439✔
542
{
543
  InputParameters params;
439✔
544

545
  params.SetGeneralDescription("Set options for boundary conditions. See \\ref LBSBCs");
878✔
546
  params.AddRequiredParameter<std::string>("name",
878✔
547
                                           "Boundary name that identifies the specific boundary");
548
  params.AddRequiredParameter<std::string>("type", "Boundary type specification.");
878✔
549
  params.AddOptionalParameterArray<double>("group_strength",
878✔
550
                                           {},
551
                                           "Required only if \"type\" is \"isotropic\". An array "
552
                                           "of isotropic strength per group");
553
  params.AddOptionalParameter(
878✔
554
    "function_name", "", "Text name of the function to be called for this boundary condition.");
555
  params.ConstrainParameterRange(
1,317✔
556
    "name", AllowableRangeList::New({"xmin", "xmax", "ymin", "ymax", "zmin", "zmax"}));
439✔
557
  params.ConstrainParameterRange("type",
1,317✔
558
                                 AllowableRangeList::New({"vacuum", "isotropic", "reflecting"}));
439✔
559

560
  return params;
439✔
561
}
×
562

563
InputParameters
564
LBSProblem::GetXSMapEntryBlock()
408✔
565
{
566
  InputParameters params;
408✔
567
  params.SetGeneralDescription("Set the cross-section map for the solver.");
816✔
568
  params.AddRequiredParameterArray("block_ids", "Mesh block IDs");
816✔
569
  params.AddRequiredParameter<std::shared_ptr<MultiGroupXS>>("xs", "Cross-section object");
816✔
570
  return params;
408✔
571
}
×
572

573
void
574
LBSProblem::SetOptions(const InputParameters& input)
273✔
575
{
576
  auto params = LBSProblem::GetOptionsBlock();
273✔
577
  params.AssignParameters(input);
273✔
578

579
  // Handle order sensitive options
580
  if (params.IsParameterValid("clear_boundary_conditions"))
273✔
581
  {
582
    if (params.GetParamValue<bool>("clear_boundary_conditions"))
273✔
583
      boundary_preferences_.clear();
×
584
  }
585

586
  if (params.IsParameterValid("clear_point_sources"))
273✔
587
  {
588
    if (params.GetParamValue<bool>("clear_point_sources"))
273✔
589
      point_sources_.clear();
×
590
  }
591

592
  if (params.IsParameterValid("clear_volumetric_sources"))
273✔
593
  {
594
    if (params.GetParamValue<bool>("clear_volumetric_sources"))
273✔
595
      volumetric_sources_.clear();
×
596
  }
597

598
  if (params.IsParameterValid("adjoint"))
273✔
599
  {
600
    const bool adjoint = params.GetParamValue<bool>("adjoint");
273✔
601
    if (adjoint != options_.adjoint)
273✔
602
    {
603
      options_.adjoint = adjoint;
12✔
604

605
      // If a discretization exists, the solver has already been initialized.
606
      // Reinitialize the materials to obtain the appropriate xs and clear the
607
      // sources to prepare for defining the adjoint problem
608
      if (discretization_)
12✔
609
      {
610
        // The materials are reinitialized here to ensure that the proper cross sections
611
        // are available to the solver. Because an adjoint solve requires volumetric or
612
        // point sources, the material-based sources are not set within the initialize routine.
613
        InitializeMaterials();
12✔
614

615
        // Forward and adjoint sources are fundamentally different, so any existing sources
616
        // should be cleared and reset through options upon changing modes.
617
        point_sources_.clear();
12✔
618
        volumetric_sources_.clear();
12✔
619
        boundary_preferences_.clear();
12✔
620

621
        // Set all solutions to zero.
622
        phi_old_local_.assign(phi_old_local_.size(), 0.0);
12✔
623
        phi_new_local_.assign(phi_new_local_.size(), 0.0);
12✔
624
        for (auto& psi : psi_new_local_)
24✔
625
          psi.assign(psi.size(), 0.0);
12✔
626
        precursor_new_local_.assign(precursor_new_local_.size(), 0.0);
12✔
627
      }
628
    }
629
  }
630

631
  // Handle order insensitive options
632
  for (size_t p = 0; p < params.GetNumParameters(); ++p)
7,644✔
633
  {
634
    const auto& spec = params.GetParam(p);
7,371✔
635

636
    if (spec.GetName() == "max_mpi_message_size")
7,371✔
637
      options_.max_mpi_message_size = spec.GetValue<int>();
273✔
638

639
    else if (spec.GetName() == "restart_writes_enabled")
7,098✔
640
      options_.restart_writes_enabled = spec.GetValue<bool>();
273✔
641

642
    else if (spec.GetName() == "write_delayed_psi_to_restart")
6,825✔
643
      options_.write_delayed_psi_to_restart = spec.GetValue<bool>();
273✔
644

645
    else if (spec.GetName() == "read_restart_path")
6,552✔
646
    {
647
      options_.read_restart_path = spec.GetValue<std::string>();
273✔
648
      if (not options_.read_restart_path.empty())
273✔
649
        options_.read_restart_path += std::to_string(opensn::mpi_comm.rank()) + ".restart.h5";
36✔
650
    }
651

652
    else if (spec.GetName() == "write_restart_path")
6,279✔
653
    {
654
      options_.write_restart_path = spec.GetValue<std::string>();
273✔
655
      if (not options_.write_restart_path.empty())
273✔
656
        options_.write_restart_path += std::to_string(opensn::mpi_comm.rank()) + ".restart.h5";
×
657
    }
658

659
    else if (spec.GetName() == "write_restart_time_interval")
6,006✔
660
      options_.write_restart_time_interval = std::chrono::seconds(spec.GetValue<int>());
273✔
661

662
    else if (spec.GetName() == "use_precursors")
5,733✔
663
      options_.use_precursors = spec.GetValue<bool>();
273✔
664

665
    else if (spec.GetName() == "use_source_moments")
5,460✔
666
      options_.use_src_moments = spec.GetValue<bool>();
273✔
667

668
    else if (spec.GetName() == "save_angular_flux")
5,187✔
669
      options_.save_angular_flux = spec.GetValue<bool>();
273✔
670

671
    else if (spec.GetName() == "verbose_inner_iterations")
4,914✔
672
      options_.verbose_inner_iterations = spec.GetValue<bool>();
273✔
673

674
    else if (spec.GetName() == "max_ags_iterations")
4,641✔
675
      options_.max_ags_iterations = spec.GetValue<int>();
273✔
676

677
    else if (spec.GetName() == "ags_tolerance")
4,368✔
678
      options_.ags_tolerance = spec.GetValue<double>();
273✔
679

680
    else if (spec.GetName() == "ags_convergence_check")
4,095✔
681
    {
682
      auto check = spec.GetValue<std::string>();
273✔
683
      if (check == "pointwise")
273✔
684
        options_.ags_pointwise_convergence = true;
×
685
    }
273✔
686

687
    else if (spec.GetName() == "verbose_ags_iterations")
3,822✔
688
      options_.verbose_ags_iterations = spec.GetValue<bool>();
273✔
689

690
    else if (spec.GetName() == "verbose_outer_iterations")
3,549✔
691
      options_.verbose_outer_iterations = spec.GetValue<bool>();
273✔
692

693
    else if (spec.GetName() == "power_field_function_on")
3,276✔
694
      options_.power_field_function_on = spec.GetValue<bool>();
273✔
695

696
    else if (spec.GetName() == "power_default_kappa")
3,003✔
697
      options_.power_default_kappa = spec.GetValue<double>();
273✔
698

699
    else if (spec.GetName() == "power_normalization")
2,730✔
700
      options_.power_normalization = spec.GetValue<double>();
273✔
701

702
    else if (spec.GetName() == "field_function_prefix_option")
2,457✔
703
    {
704
      options_.field_function_prefix_option = spec.GetValue<std::string>();
273✔
705
    }
706

707
    else if (spec.GetName() == "field_function_prefix")
2,184✔
708
      options_.field_function_prefix = spec.GetValue<std::string>();
273✔
709

710
    else if (spec.GetName() == "boundary_conditions")
1,911✔
711
    {
712
      spec.RequireBlockTypeIs(ParameterBlockType::ARRAY);
273✔
713
      for (size_t b = 0; b < spec.GetNumParameters(); ++b)
712✔
714
      {
715
        auto bndry_params = GetBoundaryOptionsBlock();
439✔
716
        bndry_params.AssignParameters(spec.GetParam(b));
439✔
717
        SetBoundaryOptions(bndry_params);
439✔
718
      }
439✔
719
    }
720

721
    else if (spec.GetName() == "point_sources")
1,638✔
722
    {
723
      spec.RequireBlockTypeIs(ParameterBlockType::ARRAY);
273✔
724
      for (const auto& sub_param : spec)
282✔
725
      {
726
        point_sources_.push_back(sub_param.GetValue<std::shared_ptr<PointSource>>());
18✔
727

728
        // If a discretization exists, the point source can be initialized.
729
        if (discretization_)
9✔
730
          point_sources_.back()->Initialize(*this);
×
731
      }
732
    }
733

734
    else if (spec.GetName() == "volumetric_sources")
1,365✔
735
    {
736
      spec.RequireBlockTypeIs(ParameterBlockType::ARRAY);
273✔
737
      for (const auto& sub_param : spec)
570✔
738
      {
739
        volumetric_sources_.push_back(sub_param.GetValue<std::shared_ptr<VolumetricSource>>());
594✔
740

741
        // If the discretization exists, the volumetric source can be initialized.
742
        if (discretization_)
297✔
743
          volumetric_sources_.back()->Initialize(*this);
12✔
744
      }
745
    }
746
  } // for p
747

748
  if (options_.restart_writes_enabled)
273✔
749
  {
750
    // Create restart directory if necessary
751
    auto dir = options_.write_restart_path.parent_path();
×
752
    if (opensn::mpi_comm.rank() == 0)
×
753
    {
754
      if (not std::filesystem::exists(dir))
×
755
      {
756
        if (not std::filesystem::create_directories(dir))
×
757
          throw std::runtime_error("Failed to create restart directory: " + dir.string());
×
758
      }
759
      else if (not std::filesystem::is_directory(dir))
×
760
        throw std::runtime_error("Restart path exists but is not a directory: " + dir.string());
×
761
    }
762
    opensn::mpi_comm.barrier();
×
763
    UpdateRestartWriteTime();
×
764
  }
×
765
}
273✔
766

767
void
768
LBSProblem::SetBoundaryOptions(const InputParameters& params)
439✔
769
{
770
  const auto boundary_name = params.GetParamValue<std::string>("name");
439✔
771
  const auto bndry_type = params.GetParamValue<std::string>("type");
439✔
772

773
  const auto bid = supported_boundary_names.at(boundary_name);
439✔
774
  const std::map<std::string, LBSBoundaryType> type_list = {
439✔
775
    {"vacuum", LBSBoundaryType::VACUUM},
439✔
776
    {"isotropic", LBSBoundaryType::ISOTROPIC},
439✔
777
    {"reflecting", LBSBoundaryType::REFLECTING}};
1,756✔
778

779
  const auto type = type_list.at(bndry_type);
439✔
780
  switch (type)
439✔
781
  {
782
    case LBSBoundaryType::VACUUM:
358✔
783
    case LBSBoundaryType::REFLECTING:
358✔
784
    {
358✔
785
      GetBoundaryPreferences()[bid] = {type};
358✔
786
      break;
358✔
787
    }
788
    case LBSBoundaryType::ISOTROPIC:
81✔
789
    {
81✔
790
      OpenSnInvalidArgumentIf(not params.Has("group_strength"),
81✔
791
                              "Boundary conditions with type=\"isotropic\" require parameter "
792
                              "\"group_strength\"");
793

794
      params.RequireParameterBlockTypeIs("group_strength", ParameterBlockType::ARRAY);
81✔
795
      const auto group_strength = params.GetParamVectorValue<double>("group_strength");
81✔
796
      boundary_preferences_[bid] = {type, group_strength};
81✔
797
      break;
81✔
798
    }
81✔
799
    case LBSBoundaryType::ARBITRARY:
×
800
    {
×
801
      throw std::runtime_error("Arbitrary boundary conditions are not currently supported");
×
802
      break;
439✔
803
    }
804
  }
805
}
1,317✔
806

807
void
808
LBSProblem::Initialize()
261✔
809
{
810
  CALI_CXX_MARK_SCOPE("LBSProblem::Initialize");
261✔
811

812
  PerformInputChecks(); // assigns num_groups and grid
261✔
813
  PrintSimHeader();
261✔
814

815
  mpi_comm.barrier();
261✔
816

817
  InitializeMaterials();
261✔
818
  InitializeSpatialDiscretization();
261✔
819
  InitializeGroupsets();
261✔
820
  ValidateAndComputeScatteringMoments();
261✔
821
  InitializeParrays();
261✔
822
  InitializeBoundaries();
261✔
823

824
  // Initialize point sources
825
  for (auto& point_source : point_sources_)
270✔
826
    point_source->Initialize(*this);
9✔
827

828
  // Initialize volumetric sources
829
  for (auto& volumetric_source : volumetric_sources_)
546✔
830
    volumetric_source->Initialize(*this);
285✔
831

832
  InitializeGPUExtras();
261✔
833
}
261✔
834

835
void
836
LBSProblem::PerformInputChecks()
261✔
837
{
838
  if (groups_.empty())
261✔
839
    throw std::runtime_error("LBSProblem: No groups added to solver");
×
840

841
  num_groups_ = groups_.size();
261✔
842

843
  if (groupsets_.empty())
261✔
844
    throw std::runtime_error("LBSProblem: No group-sets added to solver");
×
845

846
  int grpset_counter = 0;
847
  for (auto& group_set : groupsets_)
577✔
848
  {
849
    if (group_set.groups.empty())
316✔
850
    {
851
      std::stringstream oss;
×
852
      oss << "LBSPRoblem: No groups added to groupset " << grpset_counter;
×
853
      throw std::runtime_error(oss.str());
×
854
    }
×
855
    ++grpset_counter;
316✔
856
  }
857

858
  if (grid_ == nullptr)
261✔
859
    throw std::runtime_error("LBSProblem: Invalid grid");
×
860

861
  // Determine geometry type
862
  const auto dim = grid_->GetDimension();
261✔
863
  if (dim == 1)
261✔
864
    options_.geometry_type = GeometryType::ONED_SLAB;
32✔
865
  else if (dim == 2)
229✔
866
    options_.geometry_type = GeometryType::TWOD_CARTESIAN;
110✔
867
  else if (dim == 3)
119✔
868
    options_.geometry_type = GeometryType::THREED_CARTESIAN;
119✔
869
  else
870
    OpenSnLogicalError("Cannot deduce geometry type from mesh.");
×
871

872
  // Assign placeholder unit densities
873
  densities_local_.assign(grid_->local_cells.size(), 1.0);
261✔
874
}
261✔
875

876
void
877
LBSProblem::PrintSimHeader()
261✔
878
{
879
  if (opensn::mpi_comm.rank() == 0)
261✔
880
  {
881
    std::stringstream outstr;
71✔
882
    outstr << "\nInitializing LBS SteadyStateSourceSolver with name: " << GetName() << "\n\n"
71✔
883
           << "Scattering order    : " << scattering_order_ << "\n"
142✔
884
           << "Number of Groups    : " << groups_.size() << "\n"
71✔
885
           << "Number of Group sets: " << groupsets_.size() << std::endl;
71✔
886

887
    // Output Groupsets
888
    for (const auto& groupset : groupsets_)
157✔
889
    {
890
      char buf_pol[20];
86✔
891

892
      outstr << "\n***** Groupset " << groupset.id << " *****\n"
86✔
893
             << "Groups:\n";
86✔
894
      int counter = 0;
86✔
895
      for (auto group : groupset.groups)
4,352✔
896
      {
897
        snprintf(buf_pol, 20, "%5d ", group.id);
4,266✔
898
        outstr << std::string(buf_pol);
8,532✔
899
        counter++;
4,266✔
900
        if (counter == 12)
4,266✔
901
        {
902
          counter = 0;
323✔
903
          outstr << "\n";
323✔
904
        }
905
      }
906
    }
907
    log.Log() << outstr.str() << "\n" << std::endl;
142✔
908
  }
71✔
909
}
261✔
910

911
void
912
LBSProblem::InitializeMaterials()
273✔
913
{
914
  CALI_CXX_MARK_SCOPE("LBSProblem::InitializeMaterials");
273✔
915

916
  log.Log0Verbose1() << "Initializing Materials";
546✔
917

918
  // Create set of material ids locally relevant
919
  int invalid_mat_cell_count = 0;
273✔
920
  std::set<int> unique_block_ids;
273✔
921
  for (auto& cell : grid_->local_cells)
300,017✔
922
  {
923
    unique_block_ids.insert(cell.block_id);
299,744✔
924
    if (cell.block_id < 0 or (block_id_to_xs_map_.find(cell.block_id) == block_id_to_xs_map_.end()))
299,744✔
925
      ++invalid_mat_cell_count;
×
926
  }
927
  const auto& ghost_cell_ids = grid_->cells.GetGhostGlobalIDs();
273✔
928
  for (uint64_t cell_id : ghost_cell_ids)
43,326✔
929
  {
930
    const auto& cell = grid_->cells[cell_id];
43,053✔
931
    unique_block_ids.insert(cell.block_id);
43,053✔
932
    if (cell.block_id < 0 or (block_id_to_xs_map_.find(cell.block_id) == block_id_to_xs_map_.end()))
43,053✔
933
      ++invalid_mat_cell_count;
×
934
  }
935
  OpenSnLogicalErrorIf(invalid_mat_cell_count > 0,
273✔
936
                       std::to_string(invalid_mat_cell_count) +
937
                         " cells encountered with an invalid material id.");
938

939
  // Get ready for processing
940
  for (const auto& [blk_id, mat] : block_id_to_xs_map_)
792✔
941
  {
942
    mat->SetAdjointMode(options_.adjoint);
519✔
943

944
    OpenSnLogicalErrorIf(mat->GetNumGroups() < groups_.size(),
519✔
945
                         "Cross-sections on block \"" + std::to_string(blk_id) +
946
                           "\" has fewer groups (" + std::to_string(mat->GetNumGroups()) +
947
                           ") than " + "the simulation (" + std::to_string(groups_.size()) + "). " +
948
                           "Cross-sections must have at least as many groups as the simulation.");
949
  }
950

951
  // Initialize precursor properties
952
  num_precursors_ = 0;
273✔
953
  max_precursors_per_material_ = 0;
273✔
954
  for (const auto& mat_id_xs : block_id_to_xs_map_)
792✔
955
  {
956
    const auto& xs = mat_id_xs.second;
519✔
957
    num_precursors_ += xs->GetNumPrecursors();
519✔
958
    if (xs->GetNumPrecursors() > max_precursors_per_material_)
519✔
959
      max_precursors_per_material_ = xs->GetNumPrecursors();
8✔
960
  }
961

962
  // if no precursors, turn off precursors
963
  if (num_precursors_ == 0)
273✔
964
    options_.use_precursors = false;
265✔
965

966
  // check compatibility when precursors are on
967
  if (options_.use_precursors)
273✔
968
  {
969
    for (const auto& [mat_id, xs] : block_id_to_xs_map_)
16✔
970
    {
971
      OpenSnLogicalErrorIf(xs->IsFissionable() and num_precursors_ == 0,
8✔
972
                           "Incompatible cross-section data encountered for material ID " +
973
                             std::to_string(mat_id) + ". When delayed neutron data is present " +
974
                             "for one fissionable matrial, it must be present for all fissionable "
975
                             "materials.");
976
    }
977
  }
978

979
  // Update transport views if available
980
  if (grid_->local_cells.size() == cell_transport_views_.size())
273✔
981
    for (const auto& cell : grid_->local_cells)
10,812✔
982
    {
983
      const auto& xs_ptr = block_id_to_xs_map_[cell.block_id];
10,800✔
984
      auto& transport_view = cell_transport_views_[cell.local_id];
10,800✔
985
      transport_view.ReassignXS(*xs_ptr);
10,800✔
986
    }
987

988
  mpi_comm.barrier();
273✔
989
}
273✔
990

991
void
992
LBSProblem::InitializeSpatialDiscretization()
253✔
993
{
994
  CALI_CXX_MARK_SCOPE("LBSProblem::InitializeSpatialDiscretization");
253✔
995

996
  log.Log() << "Initializing spatial discretization.\n";
506✔
997
  discretization_ = PieceWiseLinearDiscontinuous::New(grid_);
253✔
998

999
  ComputeUnitIntegrals();
253✔
1000
}
253✔
1001

1002
void
1003
LBSProblem::ComputeUnitIntegrals()
261✔
1004
{
1005
  CALI_CXX_MARK_SCOPE("LBSProblem::ComputeUnitIntegrals");
261✔
1006

1007
  log.Log() << "Computing unit integrals.\n";
522✔
1008
  const auto& sdm = *discretization_;
261✔
1009

1010
  const size_t num_local_cells = grid_->local_cells.size();
261✔
1011
  unit_cell_matrices_.resize(num_local_cells);
261✔
1012

1013
  for (const auto& cell : grid_->local_cells)
289,205✔
1014
    unit_cell_matrices_[cell.local_id] =
288,944✔
1015
      ComputeUnitCellIntegrals(sdm, cell, grid_->GetCoordinateSystem());
288,944✔
1016

1017
  const auto ghost_ids = grid_->cells.GetGhostGlobalIDs();
261✔
1018
  for (uint64_t ghost_id : ghost_ids)
42,558✔
1019
    unit_ghost_cell_matrices_[ghost_id] =
42,297✔
1020
      ComputeUnitCellIntegrals(sdm, grid_->cells[ghost_id], grid_->GetCoordinateSystem());
84,594✔
1021

1022
  // Assessing global unit cell matrix storage
1023
  std::array<size_t, 2> num_local_ucms = {unit_cell_matrices_.size(),
261✔
1024
                                          unit_ghost_cell_matrices_.size()};
261✔
1025
  std::array<size_t, 2> num_global_ucms = {0, 0};
261✔
1026

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

1029
  opensn::mpi_comm.barrier();
261✔
1030
  log.Log() << "Ghost cell unit cell-matrix ratio: "
261✔
1031
            << (double)num_global_ucms[1] * 100 / (double)num_global_ucms[0] << "%";
522✔
1032
  log.Log() << "Cell matrices computed.";
522✔
1033
}
261✔
1034

1035
void
1036
LBSProblem::InitializeGroupsets()
261✔
1037
{
1038
  CALI_CXX_MARK_SCOPE("LBSProblem::InitializeGroupsets");
261✔
1039

1040
  for (auto& groupset : groupsets_)
577✔
1041
  {
1042
    // Build groupset angular flux unknown manager
1043
    groupset.psi_uk_man_.unknowns.clear();
316✔
1044
    size_t num_angles = groupset.quadrature->abscissae.size();
316✔
1045
    size_t gs_num_groups = groupset.groups.size();
316✔
1046
    auto& grpset_psi_uk_man = groupset.psi_uk_man_;
316✔
1047

1048
    const auto VarVecN = UnknownType::VECTOR_N;
316✔
1049
    for (unsigned int n = 0; n < num_angles; ++n)
41,700✔
1050
      grpset_psi_uk_man.AddUnknown(VarVecN, gs_num_groups);
41,384✔
1051

1052
    if (use_gpus_)
316✔
1053
      groupset.InitializeGPUCarriers();
×
1054
  } // for groupset
1055
}
261✔
1056

1057
void
1058
LBSProblem::ValidateAndComputeScatteringMoments()
261✔
1059
{
1060
  /*
1061
    lfs: Legendre order used in the flux solver
1062
    lxs: Legendre order used in the cross-section library
1063
    laq: Legendre order supported by the angular quadrature
1064
  */
1065

1066
  int lfs = scattering_order_;
261✔
1067
  if (lfs < 0)
261✔
1068
    throw std::invalid_argument("LBSProblem: Scattering order must be >= 0");
×
1069

1070
  for (size_t gs = 1; gs < groupsets_.size(); ++gs)
316✔
1071
    if (groupsets_[gs].quadrature->GetScatteringOrder() !=
55✔
1072
        groupsets_[0].quadrature->GetScatteringOrder())
55✔
1073
      throw std::logic_error("LBSProblem: Number of scattering moments differs between groupsets");
×
1074
  int laq = groupsets_[0].quadrature->GetScatteringOrder();
261✔
1075

1076
  for (const auto& [blk_id, mat] : block_id_to_xs_map_)
752✔
1077
  {
1078
    int lxs = block_id_to_xs_map_[blk_id]->GetScatteringOrder();
491✔
1079

1080
    if (laq > lxs)
491✔
1081
    {
1082
      log.Log0Warning()
132✔
1083
        << "The quadrature set(s) supports more scattering moments than are present in the "
66✔
1084
        << "cross-section data for block " << blk_id << std::endl;
66✔
1085
    }
1086

1087
    if (lfs < lxs)
491✔
1088
    {
1089
      log.Log0Warning()
476✔
1090
        << "Computing the flux with fewer scattering moments than are present in the "
238✔
1091
        << "cross-section data for block " << blk_id << std::endl;
238✔
1092
    }
1093
    else if (lfs > lxs)
253✔
1094
    {
1095
      log.Log0Warning()
132✔
1096
        << "Computing the flux with more scattering moments than are present in the "
66✔
1097
        << "cross-section data for block " << blk_id << std::endl;
66✔
1098
    }
1099
  }
1100

1101
  if (lfs < laq)
261✔
1102
  {
1103
    log.Log0Warning() << "Using fewer rows/columns of angular matrices (M, D) than the quadrature "
×
1104
                      << "supports" << std::endl;
×
1105
  }
1106
  else if (lfs > laq)
261✔
1107
    throw std::logic_error(
×
1108
      "LBSProblem: Solver requires more flux moments than the angular quadrature supports");
×
1109

1110
  // Compute number of solver moments.
1111
  auto geometry_type = options_.geometry_type;
261✔
1112
  if (geometry_type == GeometryType::ONED_SLAB or geometry_type == GeometryType::ONED_CYLINDRICAL or
261✔
1113
      geometry_type == GeometryType::ONED_SPHERICAL or
261✔
1114
      geometry_type == GeometryType::TWOD_CYLINDRICAL)
261✔
1115
  {
1116
    num_moments_ = lfs + 1;
40✔
1117
  }
1118
  else if (geometry_type == GeometryType::TWOD_CARTESIAN)
221✔
1119
    num_moments_ = ((lfs + 1) * (lfs + 2)) / 2;
102✔
1120
  else if (geometry_type == GeometryType::THREED_CARTESIAN)
119✔
1121
    num_moments_ = (lfs + 1) * (lfs + 1);
119✔
1122
}
261✔
1123

1124
void
1125
LBSProblem::InitializeParrays()
261✔
1126
{
1127
  CALI_CXX_MARK_SCOPE("LBSProblem::InitializeParrays");
261✔
1128

1129
  log.Log() << "Initializing parallel arrays."
522✔
1130
            << " G=" << num_groups_ << " M=" << num_moments_ << std::endl;
261✔
1131

1132
  // Initialize unknown
1133
  // structure
1134
  flux_moments_uk_man_.unknowns.clear();
261✔
1135
  for (size_t m = 0; m < num_moments_; ++m)
1,056✔
1136
  {
1137
    flux_moments_uk_man_.AddUnknown(UnknownType::VECTOR_N, groups_.size());
795✔
1138
    flux_moments_uk_man_.unknowns.back().name = "m" + std::to_string(m);
795✔
1139
  }
1140

1141
  // Compute local # of dof
1142
  local_node_count_ = discretization_->GetNumLocalNodes();
261✔
1143
  global_node_count_ = discretization_->GetNumGlobalNodes();
261✔
1144

1145
  // Compute num of unknowns
1146
  size_t num_grps = groups_.size();
261✔
1147
  size_t local_unknown_count = local_node_count_ * num_grps * num_moments_;
261✔
1148

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

1151
  // Size local vectors
1152
  q_moments_local_.assign(local_unknown_count, 0.0);
261✔
1153
  phi_old_local_.assign(local_unknown_count, 0.0);
261✔
1154
  phi_new_local_.assign(local_unknown_count, 0.0);
261✔
1155

1156
  // Setup groupset psi vectors
1157
  psi_new_local_.clear();
261✔
1158
  for (auto& groupset : groupsets_)
577✔
1159
  {
1160
    psi_new_local_.emplace_back();
316✔
1161
    if (options_.save_angular_flux)
316✔
1162
    {
1163
      size_t num_ang_unknowns = discretization_->GetNumLocalDOFs(groupset.psi_uk_man_);
83✔
1164
      psi_new_local_.back().assign(num_ang_unknowns, 0.0);
83✔
1165
    }
1166
  }
1167

1168
  // Setup precursor vector
1169
  if (options_.use_precursors)
261✔
1170
  {
1171
    size_t num_precursor_dofs = grid_->local_cells.size() * max_precursors_per_material_;
8✔
1172
    precursor_new_local_.assign(num_precursor_dofs, 0.0);
8✔
1173
  }
1174

1175
  // Initialize transport views
1176
  // Transport views act as a data structure to store information
1177
  // related to the transport simulation. The most prominent function
1178
  // here is that it holds the means to know where a given cell's
1179
  // transport quantities are located in the unknown vectors (i.e. phi)
1180
  //
1181
  // Also, for a given cell, within a given sweep chunk,
1182
  // we need to solve a matrix which square size is the
1183
  // amount of nodes on the cell. max_cell_dof_count is
1184
  // initialized here.
1185
  //
1186
  size_t block_MG_counter = 0; // Counts the strides of moment and group
261✔
1187

1188
  const Vector3 ihat(1.0, 0.0, 0.0);
261✔
1189
  const Vector3 jhat(0.0, 1.0, 0.0);
261✔
1190
  const Vector3 khat(0.0, 0.0, 1.0);
261✔
1191

1192
  max_cell_dof_count_ = 0;
261✔
1193
  cell_transport_views_.clear();
261✔
1194
  cell_transport_views_.reserve(grid_->local_cells.size());
261✔
1195
  for (auto& cell : grid_->local_cells)
289,205✔
1196
  {
1197
    size_t num_nodes = discretization_->GetCellNumNodes(cell);
288,944✔
1198

1199
    // compute cell volumes
1200
    double cell_volume = 0.0;
288,944✔
1201
    const auto& IntV_shapeI = unit_cell_matrices_[cell.local_id].intV_shapeI;
288,944✔
1202
    for (size_t i = 0; i < num_nodes; ++i)
2,520,630✔
1203
      cell_volume += IntV_shapeI(i);
2,231,686✔
1204

1205
    size_t cell_phi_address = block_MG_counter;
288,944✔
1206

1207
    const size_t num_faces = cell.faces.size();
288,944✔
1208
    std::vector<bool> face_local_flags(num_faces, true);
288,944✔
1209
    std::vector<int> face_locality(num_faces, opensn::mpi_comm.rank());
288,944✔
1210
    std::vector<const Cell*> neighbor_cell_ptrs(num_faces, nullptr);
288,944✔
1211
    bool cell_on_boundary = false;
288,944✔
1212
    int f = 0;
288,944✔
1213
    for (auto& face : cell.faces)
1,976,094✔
1214
    {
1215
      if (not face.has_neighbor)
1,687,150✔
1216
      {
1217
        Vector3& n = face.normal;
56,530✔
1218

1219
        int boundary_id = -1;
56,530✔
1220
        if (n.Dot(ihat) < -0.999)
56,530✔
1221
          boundary_id = XMIN;
1222
        else if (n.Dot(ihat) > 0.999)
48,429✔
1223
          boundary_id = XMAX;
1224
        else if (n.Dot(jhat) < -0.999)
40,490✔
1225
          boundary_id = YMIN;
1226
        else if (n.Dot(jhat) > 0.999)
32,719✔
1227
          boundary_id = YMAX;
1228
        else if (n.Dot(khat) < -0.999)
25,110✔
1229
          boundary_id = ZMIN;
1230
        else if (n.Dot(khat) > 0.999)
12,555✔
1231
          boundary_id = ZMAX;
1232

1233
        if (boundary_id >= 0)
1234
          face.neighbor_id = boundary_id;
56,530✔
1235
        cell_on_boundary = true;
56,530✔
1236

1237
        face_local_flags[f] = false;
56,530✔
1238
        face_locality[f] = -1;
56,530✔
1239
      } // if bndry
1240
      else
1241
      {
1242
        const int neighbor_partition = face.GetNeighborPartitionID(grid_.get());
1,630,620✔
1243
        face_local_flags[f] = (neighbor_partition == opensn::mpi_comm.rank());
1,630,620✔
1244
        face_locality[f] = neighbor_partition;
1,630,620✔
1245
        neighbor_cell_ptrs[f] = &grid_->cells[face.neighbor_id];
1,630,620✔
1246
      }
1247

1248
      ++f;
1,687,150✔
1249
    } // for f
1250

1251
    if (num_nodes > max_cell_dof_count_)
288,944✔
1252
      max_cell_dof_count_ = num_nodes;
421✔
1253
    cell_transport_views_.emplace_back(cell_phi_address,
577,888✔
1254
                                       num_nodes,
1255
                                       num_grps,
1256
                                       num_moments_,
288,944✔
1257
                                       num_faces,
1258
                                       *block_id_to_xs_map_[cell.block_id],
288,944✔
1259
                                       cell_volume,
1260
                                       face_local_flags,
1261
                                       face_locality,
1262
                                       neighbor_cell_ptrs,
1263
                                       cell_on_boundary);
1264
    block_MG_counter += num_nodes * num_grps * num_moments_;
288,944✔
1265
  } // for local cell
288,944✔
1266

1267
  // Populate grid nodal mappings
1268
  // This is used in the Flux Data Structures (FLUDS)
1269
  grid_nodal_mappings_.clear();
261✔
1270
  grid_nodal_mappings_.reserve(grid_->local_cells.size());
261✔
1271
  for (auto& cell : grid_->local_cells)
289,205✔
1272
  {
1273
    CellFaceNodalMapping cell_nodal_mapping;
288,944✔
1274
    cell_nodal_mapping.reserve(cell.faces.size());
288,944✔
1275

1276
    for (auto& face : cell.faces)
1,976,094✔
1277
    {
1278
      std::vector<short> face_node_mapping;
1,687,150✔
1279
      std::vector<short> cell_node_mapping;
1,687,150✔
1280
      int adj_face_idx = -1;
1,687,150✔
1281

1282
      if (face.has_neighbor)
1,687,150✔
1283
      {
1284
        grid_->FindAssociatedVertices(face, face_node_mapping);
1,630,620✔
1285
        grid_->FindAssociatedCellVertices(face, cell_node_mapping);
1,630,620✔
1286
        adj_face_idx = face.GetNeighborAdjacentFaceIndex(grid_.get());
1,630,620✔
1287
      }
1288

1289
      cell_nodal_mapping.emplace_back(adj_face_idx, face_node_mapping, cell_node_mapping);
1,687,150✔
1290
    } // for f
1,687,150✔
1291

1292
    grid_nodal_mappings_.push_back(cell_nodal_mapping);
288,944✔
1293
  } // for local cell
288,944✔
1294

1295
  // Get grid localized communicator set
1296
  grid_local_comm_set_ = grid_->MakeMPILocalCommunicatorSet();
261✔
1297

1298
  // Initialize Field Functions
1299
  InitializeFieldFunctions();
261✔
1300

1301
  opensn::mpi_comm.barrier();
261✔
1302
  log.Log() << "Done with parallel arrays." << std::endl;
522✔
1303
}
261✔
1304

1305
void
1306
LBSProblem::InitializeFieldFunctions()
261✔
1307
{
1308
  CALI_CXX_MARK_SCOPE("LBSProblem::InitializeFieldFunctions");
261✔
1309

1310
  if (not field_functions_.empty())
261✔
1311
    return;
×
1312

1313
  // Initialize Field Functions for flux moments
1314
  phi_field_functions_local_map_.clear();
261✔
1315

1316
  for (size_t g = 0; g < groups_.size(); ++g)
18,240✔
1317
  {
1318
    for (size_t m = 0; m < num_moments_; ++m)
89,066✔
1319
    {
1320
      std::string prefix;
71,087✔
1321
      if (options_.field_function_prefix_option == "prefix")
71,087✔
1322
      {
1323
        prefix = options_.field_function_prefix;
71,087✔
1324
        if (not prefix.empty())
71,087✔
1325
          prefix += "_";
1✔
1326
      }
1327
      if (options_.field_function_prefix_option == "solver_name")
71,087✔
1328
        prefix = GetName() + "_";
×
1329

1330
      char buff[100];
71,087✔
1331
      snprintf(
71,087✔
1332
        buff, 99, "%sphi_g%03d_m%02d", prefix.c_str(), static_cast<int>(g), static_cast<int>(m));
1333
      const std::string name = std::string(buff);
71,087✔
1334

1335
      auto group_ff = std::make_shared<FieldFunctionGridBased>(
71,087✔
1336
        name, discretization_, Unknown(UnknownType::SCALAR));
71,087✔
1337

1338
      field_function_stack.push_back(group_ff);
142,174✔
1339
      field_functions_.push_back(group_ff);
71,087✔
1340

1341
      phi_field_functions_local_map_[{g, m}] = field_functions_.size() - 1;
71,087✔
1342
    } // for m
71,087✔
1343
  } // for g
1344

1345
  // Initialize power generation field function
1346
  if (options_.power_field_function_on)
261✔
1347
  {
1348
    std::string prefix;
4✔
1349
    if (options_.field_function_prefix_option == "prefix")
4✔
1350
    {
1351
      prefix = options_.field_function_prefix;
4✔
1352
      if (not prefix.empty())
4✔
1353
        prefix += "_";
×
1354
    }
1355
    if (options_.field_function_prefix_option == "solver_name")
4✔
1356
      prefix = GetName() + "_";
×
1357

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

1361
    field_function_stack.push_back(power_ff);
8✔
1362
    field_functions_.push_back(power_ff);
4✔
1363

1364
    power_gen_fieldfunc_local_handle_ = field_functions_.size() - 1;
4✔
1365
  }
4✔
1366
}
261✔
1367

1368
void
1369
LBSProblem::InitializeSolverSchemes()
261✔
1370
{
1371
  CALI_CXX_MARK_SCOPE("LBSProblem::InitializeSolverSchemes");
261✔
1372

1373
  log.Log() << "Initializing WGS and AGS solvers";
522✔
1374

1375
  InitializeWGSSolvers();
261✔
1376

1377
  ags_solver_ = std::make_shared<AGSLinearSolver>(*this, wgs_solvers_);
261✔
1378
  if (groupsets_.size() == 1)
261✔
1379
  {
1380
    ags_solver_->SetMaxIterations(1);
210✔
1381
    ags_solver_->SetVerbosity(false);
210✔
1382
  }
1383
  else
1384
  {
1385
    ags_solver_->SetMaxIterations(options_.max_ags_iterations);
51✔
1386
    ags_solver_->SetVerbosity(options_.verbose_ags_iterations);
51✔
1387
  }
1388
  ags_solver_->SetTolerance(options_.ags_tolerance);
261✔
1389
}
261✔
1390

1391
#ifndef __OPENSN_USE_CUDA__
1392
void
1393
LBSProblem::InitializeGPUExtras()
261✔
1394
{
1395
}
261✔
1396

1397
void
1398
LBSProblem::ResetGPUCarriers()
257✔
1399
{
1400
}
257✔
1401

1402
void
1403
LBSProblem::CheckCapableDevices()
×
1404
{
1405
}
×
1406

1407
#endif // __OPENSN_USE_CUDA__
1408

1409
std::vector<double>
1410
LBSProblem::MakeSourceMomentsFromPhi()
4✔
1411
{
1412
  CALI_CXX_MARK_SCOPE("LBSProblem::MakeSourceMomentsFromPhi");
4✔
1413

1414
  size_t num_local_dofs = discretization_->GetNumLocalDOFs(flux_moments_uk_man_);
4✔
1415

1416
  std::vector<double> source_moments(num_local_dofs, 0.0);
4✔
1417
  for (auto& groupset : groupsets_)
8✔
1418
  {
1419
    active_set_source_function_(groupset,
4✔
1420
                                source_moments,
1421
                                phi_new_local_,
4✔
1422
                                APPLY_AGS_SCATTER_SOURCES | APPLY_WGS_SCATTER_SOURCES |
1423
                                  APPLY_AGS_FISSION_SOURCES | APPLY_WGS_FISSION_SOURCES);
4✔
1424
  }
1425

1426
  return source_moments;
4✔
1427
}
4✔
1428

1429
void
1430
LBSProblem::UpdateFieldFunctions()
269✔
1431
{
1432
  CALI_CXX_MARK_SCOPE("LBSProblem::UpdateFieldFunctions");
269✔
1433

1434
  const auto& sdm = *discretization_;
269✔
1435
  const auto& phi_uk_man = flux_moments_uk_man_;
269✔
1436

1437
  // Update flux moments
1438
  for (const auto& [g_and_m, ff_index] : phi_field_functions_local_map_)
71,400✔
1439
  {
1440
    const size_t g = g_and_m.first;
71,131✔
1441
    const size_t m = g_and_m.second;
71,131✔
1442

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

1445
    for (const auto& cell : grid_->local_cells)
16,306,960✔
1446
    {
1447
      const auto& cell_mapping = sdm.GetCellMapping(cell);
16,235,829✔
1448
      const size_t num_nodes = cell_mapping.GetNumNodes();
16,235,829✔
1449

1450
      for (size_t i = 0; i < num_nodes; ++i)
117,461,761✔
1451
      {
1452
        const int64_t imapA = sdm.MapDOFLocal(cell, i, phi_uk_man, m, g);
101,225,932✔
1453
        const int64_t imapB = sdm.MapDOFLocal(cell, i);
101,225,932✔
1454

1455
        data_vector_local[imapB] = phi_new_local_[imapA];
101,225,932✔
1456
      } // for node
1457
    } // for cell
1458

1459
    auto& ff_ptr = field_functions_.at(ff_index);
71,131✔
1460
    ff_ptr->UpdateFieldVector(data_vector_local);
71,131✔
1461
  }
71,131✔
1462

1463
  // Update power generation
1464
  if (options_.power_field_function_on)
269✔
1465
  {
1466
    std::vector<double> data_vector_local(local_node_count_, 0.0);
4✔
1467

1468
    double local_total_power = 0.0;
4✔
1469
    for (const auto& cell : grid_->local_cells)
83,268✔
1470
    {
1471
      const auto& cell_mapping = sdm.GetCellMapping(cell);
83,264✔
1472
      const size_t num_nodes = cell_mapping.GetNumNodes();
83,264✔
1473

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

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

1478
      if (not xs->IsFissionable())
83,264✔
1479
        continue;
56,360✔
1480

1481
      for (size_t i = 0; i < num_nodes; ++i)
134,520✔
1482
      {
1483
        const int64_t imapA = sdm.MapDOFLocal(cell, i);
107,616✔
1484
        const int64_t imapB = sdm.MapDOFLocal(cell, i, phi_uk_man, 0, 0);
107,616✔
1485

1486
        double nodal_power = 0.0;
1487
        for (size_t g = 0; g < groups_.size(); ++g)
860,928✔
1488
        {
1489
          const double sigma_fg = xs->GetSigmaFission()[g];
753,312✔
1490
          // const double kappa_g = xs->Kappa()[g];
1491
          const double kappa_g = options_.power_default_kappa;
753,312✔
1492

1493
          nodal_power += kappa_g * sigma_fg * phi_new_local_[imapB + g];
753,312✔
1494
        } // for g
1495

1496
        data_vector_local[imapA] = nodal_power;
107,616✔
1497
        local_total_power += nodal_power * Vi(i);
107,616✔
1498
      } // for node
1499
    } // for cell
1500

1501
    if (options_.power_normalization > 0.0)
4✔
1502
    {
1503
      double global_total_power;
4✔
1504
      mpi_comm.all_reduce(local_total_power, global_total_power, mpi::op::sum<double>());
4✔
1505

1506
      Scale(data_vector_local, options_.power_normalization / global_total_power);
4✔
1507
    }
1508

1509
    const size_t ff_index = power_gen_fieldfunc_local_handle_;
4✔
1510

1511
    auto& ff_ptr = field_functions_.at(ff_index);
4✔
1512
    ff_ptr->UpdateFieldVector(data_vector_local);
4✔
1513

1514
  } // if power enabled
4✔
1515
}
269✔
1516

1517
void
1518
LBSProblem::SetPhiFromFieldFunctions(PhiSTLOption which_phi,
×
1519
                                     const std::vector<size_t>& m_indices,
1520
                                     const std::vector<size_t>& g_indices)
1521
{
1522
  CALI_CXX_MARK_SCOPE("LBSProblem::SetPhiFromFieldFunctions");
×
1523

1524
  std::vector<size_t> m_ids_to_copy = m_indices;
×
1525
  std::vector<size_t> g_ids_to_copy = g_indices;
×
1526
  if (m_indices.empty())
×
1527
    for (size_t m = 0; m < num_moments_; ++m)
×
1528
      m_ids_to_copy.push_back(m);
×
1529
  if (g_ids_to_copy.empty())
×
1530
    for (size_t g = 0; g < num_groups_; ++g)
×
1531
      g_ids_to_copy.push_back(g);
×
1532

1533
  const auto& sdm = *discretization_;
×
1534
  const auto& phi_uk_man = flux_moments_uk_man_;
×
1535

1536
  for (const size_t m : m_ids_to_copy)
×
1537
  {
1538
    for (const size_t g : g_ids_to_copy)
×
1539
    {
1540
      const size_t ff_index = phi_field_functions_local_map_.at({g, m});
×
1541
      const auto& ff_ptr = field_functions_.at(ff_index);
×
1542
      const auto& ff_data = ff_ptr->GetLocalFieldVector();
×
1543

1544
      for (const auto& cell : grid_->local_cells)
×
1545
      {
1546
        const auto& cell_mapping = sdm.GetCellMapping(cell);
×
1547
        const size_t num_nodes = cell_mapping.GetNumNodes();
×
1548

1549
        for (size_t i = 0; i < num_nodes; ++i)
×
1550
        {
1551
          const int64_t imapA = sdm.MapDOFLocal(cell, i);
×
1552
          const int64_t imapB = sdm.MapDOFLocal(cell, i, phi_uk_man, m, g);
×
1553

1554
          if (which_phi == PhiSTLOption::PHI_OLD)
×
1555
            phi_old_local_[imapB] = ff_data[imapA];
×
1556
          else if (which_phi == PhiSTLOption::PHI_NEW)
×
1557
            phi_new_local_[imapB] = ff_data[imapA];
×
1558
        } // for node
1559
      } // for cell
1560
    } // for g
1561
  } // for m
1562
}
×
1563

1564
LBSProblem::~LBSProblem()
257✔
1565
{
1566
  ResetGPUCarriers();
1567
}
1,285✔
1568

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