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

Open-Sn / opensn / 19881058708

02 Dec 2025 03:32PM UTC coverage: 74.147% (+0.003%) from 74.144%
19881058708

push

github

web-flow
Merge pull request #842 from andrsd/issue/90-block-id

Block ID is `unsigned int`

42 of 46 new or added lines in 18 files covered. (91.3%)

2 existing lines in 1 file now uncovered.

18312 of 24697 relevant lines covered (74.15%)

58507748.83 hits per line

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

82.61
/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 <stdexcept>
25
#include <sys/stat.h>
26

27
namespace opensn
28
{
29

30
LBSProblem::LBSProblem(const std::string& name, std::shared_ptr<MeshContinuum> grid)
×
31
  : Problem(name), grid_(grid), use_gpus_(false)
×
32
{
33
}
×
34

35
InputParameters
36
LBSProblem::GetInputParameters()
326✔
37
{
38
  InputParameters params = Problem::GetInputParameters();
326✔
39

40
  params.ChangeExistingParamToOptional("name", "LBSProblem");
652✔
41

42
  params.AddRequiredParameter<std::shared_ptr<MeshContinuum>>("mesh", "Mesh");
652✔
43

44
  params.AddRequiredParameter<size_t>("num_groups", "The total number of groups within the solver");
652✔
45

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

51
  params.AddRequiredParameterArray("xs_map",
652✔
52
                                   "Cross-section map from block IDs to cross-section objects.");
53

54
  params.AddOptionalParameterArray<std::shared_ptr<VolumetricSource>>(
652✔
55
    "volumetric_sources", {}, "An array of handles to volumetric sources.");
56

57
  params.AddOptionalParameterArray<std::shared_ptr<PointSource>>(
652✔
58
    "point_sources", {}, "An array of point sources.");
59

60
  params.AddOptionalParameterArray(
652✔
61
    "boundary_conditions", {}, "An array containing tables for each boundary specification.");
62
  params.LinkParameterToBlock("boundary_conditions", "BoundaryOptionsBlock");
652✔
63

64
  params.AddOptionalParameterBlock(
652✔
65
    "options", ParameterBlock(), "Block of options. See <TT>OptionsBlock</TT>.");
652✔
66
  params.LinkParameterToBlock("options", "OptionsBlock");
652✔
67

68
  params.AddOptionalParameter("use_gpus", false, "Offload the sweep computation to GPUs.");
652✔
69

70
  return params;
326✔
71
}
×
72

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

90
  // Initialize options
91
  if (params.IsParameterValid("options"))
326✔
92
  {
93
    auto options_params = LBSProblem::GetOptionsBlock();
190✔
94
    options_params.AssignParameters(params.GetParam("options"));
192✔
95
    SetOptions(options_params);
188✔
96
  }
190✔
97

98
  // Set geometry type
99
  geometry_type_ = grid_->GetGeometryType();
324✔
100
  if (geometry_type_ == GeometryType::INVALID)
324✔
101
    throw std::runtime_error(GetName() + ": Invalid geometry type.");
×
102

103
  // Set boundary conditions
104
  if (params.Has("boundary_conditions"))
324✔
105
  {
106
    const auto& bcs = params.GetParam("boundary_conditions");
324✔
107
    bcs.RequireBlockTypeIs(ParameterBlockType::ARRAY);
324✔
108
    for (size_t b = 0; b < bcs.GetNumParameters(); ++b)
878✔
109
    {
110
      auto bndry_params = GetBoundaryOptionsBlock();
554✔
111
      bndry_params.AssignParameters(bcs.GetParam(b));
554✔
112
      SetBoundaryOptions(bndry_params);
554✔
113
    }
554✔
114
  }
115

116
  InitializeGroupsets(params);
324✔
117
  InitializeSources(params);
324✔
118
  InitializeXSmapAndDensities(params);
324✔
119
  InitializeMaterials();
324✔
120
}
344✔
121

122
LBSOptions&
123
LBSProblem::GetOptions()
17,371✔
124
{
125
  return options_;
17,371✔
126
}
127

128
const LBSOptions&
129
LBSProblem::GetOptions() const
475,201,252✔
130
{
131
  return options_;
475,201,252✔
132
}
133

134
GeometryType
135
LBSProblem::GetGeometryType() const
4✔
136
{
137
  return geometry_type_;
4✔
138
}
139

140
size_t
141
LBSProblem::GetNumMoments() const
132,763✔
142
{
143
  return num_moments_;
132,763✔
144
}
145

146
size_t
147
LBSProblem::GetNumGroups() const
54,048✔
148
{
149
  return num_groups_;
54,048✔
150
}
151

152
unsigned int
153
LBSProblem::GetScatteringOrder() const
4✔
154
{
155
  return scattering_order_;
4✔
156
}
157

158
size_t
159
LBSProblem::GetNumPrecursors() const
×
160
{
161
  return num_precursors_;
×
162
}
163

164
size_t
165
LBSProblem::GetMaxPrecursorsPerMaterial() const
8✔
166
{
167
  return max_precursors_per_material_;
8✔
168
}
169

170
const std::vector<LBSGroup>&
171
LBSProblem::GetGroups() const
140,710✔
172
{
173
  return groups_;
140,710✔
174
}
175

176
std::vector<LBSGroupset>&
177
LBSProblem::GetGroupsets()
8,170,791✔
178
{
179
  return groupsets_;
8,170,791✔
180
}
181

182
const std::vector<LBSGroupset>&
183
LBSProblem::GetGroupsets() const
×
184
{
185
  return groupsets_;
×
186
}
187

188
void
189
LBSProblem::AddPointSource(std::shared_ptr<PointSource> point_source)
×
190
{
191
  point_sources_.push_back(point_source);
×
192
  if (discretization_)
×
193
    point_sources_.back()->Initialize(*this);
×
194
}
×
195

196
void
197
LBSProblem::ClearPointSources()
×
198
{
199
  point_sources_.clear();
×
200
}
×
201

202
const std::vector<std::shared_ptr<PointSource>>&
203
LBSProblem::GetPointSources() const
7,484✔
204
{
205
  return point_sources_;
7,484✔
206
}
207

208
void
209
LBSProblem::AddVolumetricSource(std::shared_ptr<VolumetricSource> volumetric_source)
16✔
210
{
211
  volumetric_sources_.push_back(volumetric_source);
16✔
212
  if (discretization_)
16✔
213
    volumetric_sources_.back()->Initialize(*this);
16✔
214
}
16✔
215

216
void
217
LBSProblem::ClearVolumetricSources()
4✔
218
{
219
  volumetric_sources_.clear();
4✔
220
}
4✔
221

222
const std::vector<std::shared_ptr<VolumetricSource>>&
223
LBSProblem::GetVolumetricSources() const
7,484✔
224
{
225
  return volumetric_sources_;
7,484✔
226
}
227

228
void
229
LBSProblem::ClearBoundaries()
×
230
{
231
  boundary_preferences_.clear();
×
232
}
×
233

234
const BlockID2XSMap&
235
LBSProblem::GetBlockID2XSMap() const
6,681✔
236
{
237
  return block_id_to_xs_map_;
6,681✔
238
}
239

240
std::shared_ptr<MeshContinuum>
241
LBSProblem::GetGrid() const
182,418✔
242
{
243
  return grid_;
182,418✔
244
}
245

246
const SpatialDiscretization&
247
LBSProblem::GetSpatialDiscretization() const
64,452✔
248
{
249
  return *discretization_;
64,452✔
250
}
251

252
const std::vector<UnitCellMatrices>&
253
LBSProblem::GetUnitCellMatrices() const
7,275✔
254
{
255
  return unit_cell_matrices_;
7,275✔
256
}
257

258
const std::map<uint64_t, UnitCellMatrices>&
259
LBSProblem::GetUnitGhostCellMatrices() const
17✔
260
{
261
  return unit_ghost_cell_matrices_;
17✔
262
}
263

264
const std::vector<CellLBSView>&
265
LBSProblem::GetCellTransportViews() const
192,258✔
266
{
267
  return cell_transport_views_;
192,258✔
268
}
269

270
const UnknownManager&
271
LBSProblem::GetUnknownManager() const
26,703✔
272
{
273
  return flux_moments_uk_man_;
26,703✔
274
}
275

276
size_t
277
LBSProblem::GetLocalNodeCount() const
3,084✔
278
{
279
  return local_node_count_;
3,084✔
280
}
281

282
size_t
283
LBSProblem::GetGlobalNodeCount() const
1,556✔
284
{
285
  return global_node_count_;
1,556✔
286
}
287

288
std::vector<double>&
289
LBSProblem::GetQMomentsLocal()
37,977✔
290
{
291
  return q_moments_local_;
37,977✔
292
}
293

294
const std::vector<double>&
295
LBSProblem::GetQMomentsLocal() const
×
296
{
297
  return q_moments_local_;
×
298
}
299

300
std::vector<double>&
301
LBSProblem::GetExtSrcMomentsLocal()
4✔
302
{
303
  return ext_src_moments_local_;
4✔
304
}
305

306
const std::vector<double>&
307
LBSProblem::GetExtSrcMomentsLocal() const
37,543✔
308
{
309
  return ext_src_moments_local_;
37,543✔
310
}
311

312
std::vector<double>&
313
LBSProblem::GetPhiOldLocal()
57,665✔
314
{
315
  return phi_old_local_;
57,665✔
316
}
317

318
const std::vector<double>&
319
LBSProblem::GetPhiOldLocal() const
×
320
{
321
  return phi_old_local_;
×
322
}
323

324
std::vector<double>&
325
LBSProblem::GetPhiNewLocal()
59,842✔
326
{
327
  return phi_new_local_;
59,842✔
328
}
329

330
const std::vector<double>&
331
LBSProblem::GetPhiNewLocal() const
×
332
{
333
  return phi_new_local_;
×
334
}
335

336
std::vector<double>&
337
LBSProblem::GetPrecursorsNewLocal()
16✔
338
{
339
  return precursor_new_local_;
16✔
340
}
341

342
const std::vector<double>&
343
LBSProblem::GetPrecursorsNewLocal() const
×
344
{
345
  return precursor_new_local_;
×
346
}
347

348
std::vector<double>&
349
LBSProblem::GetDensitiesLocal()
250✔
350
{
351
  return densities_local_;
250✔
352
}
353

354
const std::vector<double>&
355
LBSProblem::GetDensitiesLocal() const
37,543✔
356
{
357
  return densities_local_;
37,543✔
358
}
359

360
SetSourceFunction
361
LBSProblem::GetActiveSetSourceFunction() const
4,154✔
362
{
363
  return active_set_source_function_;
4,154✔
364
}
365

366
std::shared_ptr<AGSLinearSolver>
367
LBSProblem::GetAGSSolver()
308✔
368
{
369
  return ags_solver_;
308✔
370
}
371

372
std::vector<std::shared_ptr<LinearSolver>>&
373
LBSProblem::GetWGSSolvers()
113✔
374
{
375
  return wgs_solvers_;
113✔
376
}
377

378
WGSContext&
379
LBSProblem::GetWGSContext(int groupset_id)
11,508✔
380
{
381
  auto& wgs_solver = wgs_solvers_[groupset_id];
11,508✔
382
  auto raw_context = wgs_solver->GetContext();
11,508✔
383
  auto wgs_context_ptr = std::dynamic_pointer_cast<WGSContext>(raw_context);
11,508✔
384
  OpenSnLogicalErrorIf(not wgs_context_ptr, "Failed to cast WGSContext");
11,508✔
385
  return *wgs_context_ptr;
11,508✔
386
}
23,016✔
387

388
std::map<uint64_t, BoundaryPreference>&
389
LBSProblem::GetBoundaryPreferences()
4✔
390
{
391
  return boundary_preferences_;
4✔
392
}
393

394
std::pair<size_t, size_t>
395
LBSProblem::GetNumPhiIterativeUnknowns()
×
396
{
397
  const auto& sdm = *discretization_;
×
398
  const size_t num_local_phi_dofs = sdm.GetNumLocalDOFs(flux_moments_uk_man_);
×
399
  const size_t num_global_phi_dofs = sdm.GetNumGlobalDOFs(flux_moments_uk_man_);
×
400

401
  return {num_local_phi_dofs, num_global_phi_dofs};
×
402
}
403

404
size_t
405
LBSProblem::MapPhiFieldFunction(size_t g, size_t m) const
25,460✔
406
{
407
  OpenSnLogicalErrorIf(phi_field_functions_local_map_.count({g, m}) == 0,
25,460✔
408
                       std::string("Failure to map phi field function g") + std::to_string(g) +
409
                         " m" + std::to_string(m));
410

411
  return phi_field_functions_local_map_.at({g, m});
25,460✔
412
}
413

414
std::shared_ptr<FieldFunctionGridBased>
415
LBSProblem::GetPowerFieldFunction() const
×
416
{
417
  OpenSnLogicalErrorIf(not options_.power_field_function_on,
×
418
                       "Called when options_.power_field_function_on == false");
419

420
  return field_functions_[power_gen_fieldfunc_local_handle_];
×
421
}
422

423
InputParameters
424
LBSProblem::GetOptionsBlock()
402✔
425
{
426
  InputParameters params;
402✔
427

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

501
  return params;
402✔
502
}
×
503

504
InputParameters
505
LBSProblem::GetBoundaryOptionsBlock()
554✔
506
{
507
  InputParameters params;
554✔
508

509
  params.SetGeneralDescription("Set options for boundary conditions.");
1,108✔
510
  params.AddRequiredParameter<std::string>("name",
1,108✔
511
                                           "Boundary name that identifies the specific boundary");
512
  params.AddRequiredParameter<std::string>("type", "Boundary type specification.");
1,108✔
513
  params.AddOptionalParameterArray<double>("group_strength",
1,108✔
514
                                           {},
515
                                           "Required only if \"type\" is \"isotropic\". An array "
516
                                           "of isotropic strength per group");
517
  params.AddOptionalParameter(
1,108✔
518
    "function_name", "", "Text name of the function to be called for this boundary condition.");
519
  params.ConstrainParameterRange("type",
1,662✔
520
                                 AllowableRangeList::New({"vacuum", "isotropic", "reflecting"}));
554✔
521

522
  return params;
554✔
523
}
×
524

525
InputParameters
526
LBSProblem::GetXSMapEntryBlock()
581✔
527
{
528
  InputParameters params;
581✔
529
  params.SetGeneralDescription("Set the cross-section map for the solver.");
1,162✔
530
  params.AddRequiredParameterArray("block_ids", "Mesh block IDs");
1,162✔
531
  params.AddRequiredParameter<std::shared_ptr<MultiGroupXS>>("xs", "Cross-section object");
1,162✔
532
  return params;
581✔
533
}
×
534

535
void
536
LBSProblem::SetOptions(const InputParameters& input)
200✔
537
{
538
  auto params = LBSProblem::GetOptionsBlock();
200✔
539
  params.AssignParameters(input);
200✔
540

541
  // Handle order insensitive options
542
  for (size_t p = 0; p < params.GetNumParameters(); ++p)
4,400✔
543
  {
544
    const auto& spec = params.GetParam(p);
4,200✔
545

546
    if (spec.GetName() == "max_mpi_message_size")
4,200✔
547
      options_.max_mpi_message_size = spec.GetValue<int>();
200✔
548

549
    else if (spec.GetName() == "restart_writes_enabled")
4,000✔
550
      options_.restart_writes_enabled = spec.GetValue<bool>();
200✔
551

552
    else if (spec.GetName() == "write_delayed_psi_to_restart")
3,800✔
553
      options_.write_delayed_psi_to_restart = spec.GetValue<bool>();
200✔
554

555
    else if (spec.GetName() == "read_restart_path")
3,600✔
556
    {
557
      options_.read_restart_path = spec.GetValue<std::string>();
200✔
558
      if (not options_.read_restart_path.empty())
200✔
559
        options_.read_restart_path += std::to_string(opensn::mpi_comm.rank()) + ".restart.h5";
36✔
560
    }
561

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

569
    else if (spec.GetName() == "write_restart_time_interval")
3,200✔
570
      options_.write_restart_time_interval = std::chrono::seconds(spec.GetValue<int>());
200✔
571

572
    else if (spec.GetName() == "use_precursors")
3,000✔
573
      options_.use_precursors = spec.GetValue<bool>();
200✔
574

575
    else if (spec.GetName() == "use_source_moments")
2,800✔
576
      options_.use_src_moments = spec.GetValue<bool>();
200✔
577

578
    else if (spec.GetName() == "save_angular_flux")
2,600✔
579
      options_.save_angular_flux = spec.GetValue<bool>();
200✔
580

581
    else if (spec.GetName() == "verbose_inner_iterations")
2,400✔
582
      options_.verbose_inner_iterations = spec.GetValue<bool>();
200✔
583

584
    else if (spec.GetName() == "max_ags_iterations")
2,200✔
585
      options_.max_ags_iterations = spec.GetValue<int>();
200✔
586

587
    else if (spec.GetName() == "ags_tolerance")
2,000✔
588
      options_.ags_tolerance = spec.GetValue<double>();
200✔
589

590
    else if (spec.GetName() == "ags_convergence_check")
1,800✔
591
    {
592
      auto check = spec.GetValue<std::string>();
200✔
593
      if (check == "pointwise")
200✔
594
        options_.ags_pointwise_convergence = true;
×
595
    }
200✔
596

597
    else if (spec.GetName() == "verbose_ags_iterations")
1,600✔
598
      options_.verbose_ags_iterations = spec.GetValue<bool>();
200✔
599

600
    else if (spec.GetName() == "verbose_outer_iterations")
1,400✔
601
      options_.verbose_outer_iterations = spec.GetValue<bool>();
200✔
602

603
    else if (spec.GetName() == "power_field_function_on")
1,200✔
604
      options_.power_field_function_on = spec.GetValue<bool>();
200✔
605

606
    else if (spec.GetName() == "power_default_kappa")
1,000✔
607
      options_.power_default_kappa = spec.GetValue<double>();
200✔
608

609
    else if (spec.GetName() == "power_normalization")
800✔
610
      options_.power_normalization = spec.GetValue<double>();
200✔
611

612
    else if (spec.GetName() == "field_function_prefix_option")
600✔
613
    {
614
      options_.field_function_prefix_option = spec.GetValue<std::string>();
200✔
615
    }
616

617
    else if (spec.GetName() == "field_function_prefix")
400✔
618
      options_.field_function_prefix = spec.GetValue<std::string>();
200✔
619

620
    else if (spec.GetName() == "adjoint")
200✔
621
      options_.adjoint = spec.GetValue<bool>();
200✔
622

623
  } // for p
624

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

646
void
647
LBSProblem::SetBoundaryOptions(const InputParameters& params)
554✔
648
{
649
  const auto boundary_name = params.GetParamValue<std::string>("name");
554✔
650
  const auto bndry_type = params.GetParamValue<std::string>("type");
554✔
651

652
  auto grid = GetGrid();
554✔
653
  const auto bnd_map = grid->GetBoundaryIDMap();
554✔
654
  const auto bnd_name_map = grid->GetBoundaryNameMap();
554✔
655
  auto it = bnd_name_map.find(boundary_name);
554✔
656
  if (it == bnd_name_map.end())
554✔
657
    throw std::runtime_error(std::format("Could not find the specified boundary '{}' - please "
×
658
                                         "check that the 'name' parameter is spelled correctly.",
659
                                         boundary_name));
×
660
  const auto bid = it->second;
554✔
661
  const std::map<std::string, LBSBoundaryType> type_list = {
554✔
662
    {"vacuum", LBSBoundaryType::VACUUM},
554✔
663
    {"isotropic", LBSBoundaryType::ISOTROPIC},
554✔
664
    {"reflecting", LBSBoundaryType::REFLECTING}};
2,216✔
665

666
  const auto type = type_list.at(bndry_type);
554✔
667
  switch (type)
554✔
668
  {
669
    case LBSBoundaryType::VACUUM:
454✔
670
    case LBSBoundaryType::REFLECTING:
454✔
671
    {
454✔
672
      boundary_preferences_[bid] = {type};
454✔
673
      break;
454✔
674
    }
675
    case LBSBoundaryType::ISOTROPIC:
100✔
676
    {
100✔
677
      OpenSnInvalidArgumentIf(not params.Has("group_strength"),
100✔
678
                              "Boundary conditions with type=\"isotropic\" require parameter "
679
                              "\"group_strength\"");
680

681
      params.RequireParameterBlockTypeIs("group_strength", ParameterBlockType::ARRAY);
100✔
682
      const auto group_strength = params.GetParamVectorValue<double>("group_strength");
100✔
683
      boundary_preferences_[bid] = {type, group_strength};
100✔
684
      break;
100✔
685
    }
100✔
686
    case LBSBoundaryType::ARBITRARY:
×
687
    {
×
688
      throw std::runtime_error(GetName() +
×
689
                               ": Arbitrary boundary conditions are not currently supported");
×
690
      break;
554✔
691
    }
692
  }
693
}
2,216✔
694

695
void
696
LBSProblem::Initialize()
324✔
697
{
698
  CALI_CXX_MARK_SCOPE("LBSProblem::Initialize");
324✔
699

700
  PrintSimHeader();
324✔
701
  mpi_comm.barrier();
324✔
702

703
  InitializeSpatialDiscretization();
324✔
704
  InitializeParrays();
324✔
705
  InitializeBoundaries();
324✔
706
  InitializeGPUExtras();
324✔
707
  SetAdjoint(options_.adjoint);
324✔
708

709
  // Initialize point sources
710
  for (auto& point_source : point_sources_)
333✔
711
    point_source->Initialize(*this);
9✔
712

713
  // Initialize volumetric sources
714
  for (auto& volumetric_source : volumetric_sources_)
671✔
715
    volumetric_source->Initialize(*this);
347✔
716
}
324✔
717

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

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

747
    log.Log() << outstr.str() << '\n';
×
748
  }
×
749
}
×
750

751
void
752
LBSProblem::InitializeSources(const InputParameters& params)
324✔
753
{
754
  if (params.Has("volumetric_sources"))
324✔
755
  {
756
    const auto& vol_srcs = params.GetParam("volumetric_sources");
324✔
757
    vol_srcs.RequireBlockTypeIs(ParameterBlockType::ARRAY);
324✔
758
    for (const auto& src : vol_srcs)
671✔
759
      volumetric_sources_.push_back(src.GetValue<std::shared_ptr<VolumetricSource>>());
694✔
760
  }
761

762
  if (params.Has("point_sources"))
324✔
763
  {
764
    const auto& pt_srcs = params.GetParam("point_sources");
324✔
765
    pt_srcs.RequireBlockTypeIs(ParameterBlockType::ARRAY);
324✔
766
    for (const auto& src : pt_srcs)
333✔
767
      point_sources_.push_back(src.GetValue<std::shared_ptr<PointSource>>());
18✔
768
  }
769
}
324✔
770

771
void
772
LBSProblem::InitializeGroupsets(const InputParameters& params)
324✔
773
{
774
  // Initialize groups
775
  if (num_groups_ == 0)
324✔
776
    throw std::invalid_argument(GetName() + ": Number of groups must be > 0");
×
777
  for (size_t g = 0; g < num_groups_; ++g)
21,353✔
778
    groups_.emplace_back(static_cast<int>(g));
21,029✔
779

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

801
void
802
LBSProblem::InitializeXSmapAndDensities(const InputParameters& params)
324✔
803
{
804
  // Build XS map
805
  const auto& xs_array = params.GetParam("xs_map");
324✔
806
  const size_t num_xs = xs_array.GetNumParameters();
324✔
807
  for (size_t i = 0; i < num_xs; ++i)
905✔
808
  {
809
    const auto& item_params = xs_array.GetParam(i);
581✔
810
    InputParameters xs_entry_pars = GetXSMapEntryBlock();
581✔
811
    xs_entry_pars.AssignParameters(item_params);
581✔
812

813
    const auto& block_ids_param = xs_entry_pars.GetParam("block_ids");
581✔
814
    block_ids_param.RequireBlockTypeIs(ParameterBlockType::ARRAY);
581✔
815
    const auto& block_ids = block_ids_param.GetVectorValue<unsigned int>();
581✔
816
    auto xs = xs_entry_pars.GetSharedPtrParam<MultiGroupXS>("xs");
581✔
817
    for (const auto& block_id : block_ids)
1,258✔
818
      block_id_to_xs_map_[block_id] = xs;
677✔
819
  }
581✔
820

821
  // Assign placeholder unit densities
822
  densities_local_.assign(grid_->local_cells.size(), 1.0);
324✔
823
}
324✔
824

825
void
826
LBSProblem::InitializeMaterials()
336✔
827
{
828
  CALI_CXX_MARK_SCOPE("LBSProblem::InitializeMaterials");
336✔
829

830
  log.Log0Verbose1() << "Initializing Materials";
672✔
831

832
  // Create set of material ids locally relevant
833
  int invalid_mat_cell_count = 0;
336✔
834
  std::set<unsigned int> unique_block_ids;
336✔
835
  for (auto& cell : grid_->local_cells)
350,708✔
836
  {
837
    unique_block_ids.insert(cell.block_id);
350,372✔
838
    if (cell.block_id == std::numeric_limits<unsigned int>::max() or
350,372✔
839
        (block_id_to_xs_map_.find(cell.block_id) == block_id_to_xs_map_.end()))
350,372✔
UNCOV
840
      ++invalid_mat_cell_count;
×
841
  }
842
  const auto& ghost_cell_ids = grid_->cells.GetGhostGlobalIDs();
336✔
843
  for (uint64_t cell_id : ghost_cell_ids)
53,498✔
844
  {
845
    const auto& cell = grid_->cells[cell_id];
53,162✔
846
    unique_block_ids.insert(cell.block_id);
53,162✔
847
    if (cell.block_id == std::numeric_limits<unsigned int>::max() or
53,162✔
848
        (block_id_to_xs_map_.find(cell.block_id) == block_id_to_xs_map_.end()))
53,162✔
UNCOV
849
      ++invalid_mat_cell_count;
×
850
  }
851
  OpenSnLogicalErrorIf(invalid_mat_cell_count > 0,
336✔
852
                       std::to_string(invalid_mat_cell_count) +
853
                         " cells encountered with an invalid material id.");
854

855
  // Get ready for processing
856
  for (const auto& [blk_id, mat] : block_id_to_xs_map_)
1,041✔
857
  {
858
    mat->SetAdjointMode(options_.adjoint);
705✔
859

860
    OpenSnLogicalErrorIf(mat->GetNumGroups() < groups_.size(),
705✔
861
                         "Cross-sections for block \"" + std::to_string(blk_id) +
862
                           "\" have fewer groups (" + std::to_string(mat->GetNumGroups()) +
863
                           ") than the simulation (" + std::to_string(groups_.size()) + "). " +
864
                           "Cross-sections must have at least as many groups as the simulation.");
865
  }
866

867
  // Initialize precursor properties
868
  num_precursors_ = 0;
336✔
869
  max_precursors_per_material_ = 0;
336✔
870
  for (const auto& mat_id_xs : block_id_to_xs_map_)
1,041✔
871
  {
872
    const auto& xs = mat_id_xs.second;
705✔
873
    num_precursors_ += xs->GetNumPrecursors();
705✔
874
    if (xs->GetNumPrecursors() > max_precursors_per_material_)
705✔
875
      max_precursors_per_material_ = xs->GetNumPrecursors();
8✔
876
  }
877

878
  // if no precursors, turn off precursors
879
  if (num_precursors_ == 0)
336✔
880
    options_.use_precursors = false;
328✔
881

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

895
  // Update transport views if available
896
  if (grid_->local_cells.size() == cell_transport_views_.size())
336✔
897
    for (const auto& cell : grid_->local_cells)
10,812✔
898
    {
899
      const auto& xs_ptr = block_id_to_xs_map_[cell.block_id];
10,800✔
900
      auto& transport_view = cell_transport_views_[cell.local_id];
10,800✔
901
      transport_view.ReassignXS(*xs_ptr);
10,800✔
902
    }
903

904
  mpi_comm.barrier();
336✔
905
}
336✔
906

907
void
908
LBSProblem::InitializeSpatialDiscretization()
316✔
909
{
910
  CALI_CXX_MARK_SCOPE("LBSProblem::InitializeSpatialDiscretization");
316✔
911

912
  log.Log() << "Initializing spatial discretization.\n";
632✔
913
  discretization_ = PieceWiseLinearDiscontinuous::New(grid_);
316✔
914

915
  ComputeUnitIntegrals();
316✔
916
}
316✔
917

918
void
919
LBSProblem::ComputeUnitIntegrals()
324✔
920
{
921
  CALI_CXX_MARK_SCOPE("LBSProblem::ComputeUnitIntegrals");
324✔
922

923
  log.Log() << "Computing unit integrals.\n";
648✔
924
  const auto& sdm = *discretization_;
324✔
925

926
  const size_t num_local_cells = grid_->local_cells.size();
324✔
927
  unit_cell_matrices_.resize(num_local_cells);
324✔
928

929
  for (const auto& cell : grid_->local_cells)
339,896✔
930
    unit_cell_matrices_[cell.local_id] =
339,572✔
931
      ComputeUnitCellIntegrals(sdm, cell, grid_->GetCoordinateSystem());
339,572✔
932

933
  const auto ghost_ids = grid_->cells.GetGhostGlobalIDs();
324✔
934
  for (auto ghost_id : ghost_ids)
52,730✔
935
    unit_ghost_cell_matrices_[ghost_id] =
52,406✔
936
      ComputeUnitCellIntegrals(sdm, grid_->cells[ghost_id], grid_->GetCoordinateSystem());
104,812✔
937

938
  // Assessing global unit cell matrix storage
939
  std::array<size_t, 2> num_local_ucms = {unit_cell_matrices_.size(),
324✔
940
                                          unit_ghost_cell_matrices_.size()};
324✔
941
  std::array<size_t, 2> num_global_ucms = {0, 0};
324✔
942

943
  mpi_comm.all_reduce(num_local_ucms.data(), 2, num_global_ucms.data(), mpi::op::sum<size_t>());
324✔
944

945
  opensn::mpi_comm.barrier();
324✔
946
  log.Log() << "Ghost cell unit cell-matrix ratio: "
324✔
947
            << (double)num_global_ucms[1] * 100 / (double)num_global_ucms[0] << "%";
648✔
948
  log.Log() << "Cell matrices computed.";
648✔
949
}
324✔
950

951
void
952
LBSProblem::InitializeParrays()
324✔
953
{
954
  CALI_CXX_MARK_SCOPE("LBSProblem::InitializeParrays");
324✔
955

956
  log.Log() << "Initializing parallel arrays."
648✔
957
            << " G=" << num_groups_ << " M=" << num_moments_ << std::endl;
324✔
958

959
  // Initialize unknown
960
  // structure
961
  flux_moments_uk_man_.unknowns.clear();
324✔
962
  for (size_t m = 0; m < num_moments_; ++m)
1,210✔
963
  {
964
    flux_moments_uk_man_.AddUnknown(UnknownType::VECTOR_N, groups_.size());
886✔
965
    flux_moments_uk_man_.unknowns.back().name = "m" + std::to_string(m);
886✔
966
  }
967

968
  // Compute local # of dof
969
  local_node_count_ = discretization_->GetNumLocalNodes();
324✔
970
  global_node_count_ = discretization_->GetNumGlobalNodes();
324✔
971

972
  // Compute num of unknowns
973
  size_t num_grps = groups_.size();
324✔
974
  size_t local_unknown_count = local_node_count_ * num_grps * num_moments_;
324✔
975

976
  log.LogAllVerbose1() << "LBS Number of phi unknowns: " << local_unknown_count;
648✔
977

978
  // Size local vectors
979
  q_moments_local_.assign(local_unknown_count, 0.0);
324✔
980
  phi_old_local_.assign(local_unknown_count, 0.0);
324✔
981
  phi_new_local_.assign(local_unknown_count, 0.0);
324✔
982

983
  // Setup precursor vector
984
  if (options_.use_precursors)
324✔
985
  {
986
    size_t num_precursor_dofs = grid_->local_cells.size() * max_precursors_per_material_;
8✔
987
    precursor_new_local_.assign(num_precursor_dofs, 0.0);
8✔
988
  }
989

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

1003
  const Vector3 ihat(1.0, 0.0, 0.0);
324✔
1004
  const Vector3 jhat(0.0, 1.0, 0.0);
324✔
1005
  const Vector3 khat(0.0, 0.0, 1.0);
324✔
1006

1007
  min_cell_dof_count_ = static_cast<size_t>(-1);
324✔
1008
  max_cell_dof_count_ = 0;
324✔
1009
  cell_transport_views_.clear();
324✔
1010
  cell_transport_views_.reserve(grid_->local_cells.size());
324✔
1011
  for (auto& cell : grid_->local_cells)
339,896✔
1012
  {
1013
    size_t num_nodes = discretization_->GetCellNumNodes(cell);
339,572✔
1014

1015
    // compute cell volumes
1016
    double cell_volume = 0.0;
339,572✔
1017
    const auto& IntV_shapeI = unit_cell_matrices_[cell.local_id].intV_shapeI;
339,572✔
1018
    for (size_t i = 0; i < num_nodes; ++i)
2,807,828✔
1019
      cell_volume += IntV_shapeI(i);
2,468,256✔
1020

1021
    size_t cell_phi_address = block_MG_counter;
339,572✔
1022

1023
    const size_t num_faces = cell.faces.size();
339,572✔
1024
    std::vector<bool> face_local_flags(num_faces, true);
339,572✔
1025
    std::vector<int> face_locality(num_faces, opensn::mpi_comm.rank());
339,572✔
1026
    std::vector<const Cell*> neighbor_cell_ptrs(num_faces, nullptr);
339,572✔
1027
    bool cell_on_boundary = false;
339,572✔
1028
    int f = 0;
339,572✔
1029
    for (auto& face : cell.faces)
2,244,012✔
1030
    {
1031
      if (not face.has_neighbor)
1,904,440✔
1032
      {
1033
        cell_on_boundary = true;
65,706✔
1034
        face_local_flags[f] = false;
65,706✔
1035
        face_locality[f] = -1;
65,706✔
1036
      } // if bndry
1037
      else
1038
      {
1039
        const int neighbor_partition = face.GetNeighborPartitionID(grid_.get());
1,838,734✔
1040
        face_local_flags[f] = (neighbor_partition == opensn::mpi_comm.rank());
1,838,734✔
1041
        face_locality[f] = neighbor_partition;
1,838,734✔
1042
        neighbor_cell_ptrs[f] = &grid_->cells[face.neighbor_id];
1,838,734✔
1043
      }
1044

1045
      ++f;
1,904,440✔
1046
    } // for f
1047

1048
    max_cell_dof_count_ = std::max(max_cell_dof_count_, num_nodes);
339,572✔
1049
    min_cell_dof_count_ = std::min(min_cell_dof_count_, num_nodes);
339,572✔
1050
    cell_transport_views_.emplace_back(cell_phi_address,
679,144✔
1051
                                       num_nodes,
1052
                                       num_grps,
1053
                                       num_moments_,
339,572✔
1054
                                       num_faces,
1055
                                       *block_id_to_xs_map_[cell.block_id],
339,572✔
1056
                                       cell_volume,
1057
                                       face_local_flags,
1058
                                       face_locality,
1059
                                       neighbor_cell_ptrs,
1060
                                       cell_on_boundary);
1061
    block_MG_counter += num_nodes * num_grps * num_moments_;
339,572✔
1062
  } // for local cell
339,572✔
1063

1064
  // Populate grid nodal mappings
1065
  // This is used in the Flux Data Structures (FLUDS)
1066
  grid_nodal_mappings_.clear();
324✔
1067
  grid_nodal_mappings_.reserve(grid_->local_cells.size());
324✔
1068
  for (auto& cell : grid_->local_cells)
339,896✔
1069
  {
1070
    CellFaceNodalMapping cell_nodal_mapping;
339,572✔
1071
    cell_nodal_mapping.reserve(cell.faces.size());
339,572✔
1072

1073
    for (auto& face : cell.faces)
2,244,012✔
1074
    {
1075
      std::vector<short> face_node_mapping;
1,904,440✔
1076
      std::vector<short> cell_node_mapping;
1,904,440✔
1077
      int adj_face_idx = -1;
1,904,440✔
1078

1079
      if (face.has_neighbor)
1,904,440✔
1080
      {
1081
        grid_->FindAssociatedVertices(face, face_node_mapping);
1,838,734✔
1082
        grid_->FindAssociatedCellVertices(face, cell_node_mapping);
1,838,734✔
1083
        adj_face_idx = face.GetNeighborAdjacentFaceIndex(grid_.get());
1,838,734✔
1084
      }
1085

1086
      cell_nodal_mapping.emplace_back(adj_face_idx, face_node_mapping, cell_node_mapping);
1,904,440✔
1087
    } // for f
1,904,440✔
1088

1089
    grid_nodal_mappings_.push_back(cell_nodal_mapping);
339,572✔
1090
  } // for local cell
339,572✔
1091

1092
  // Get grid localized communicator set
1093
  grid_local_comm_set_ = grid_->MakeMPILocalCommunicatorSet();
324✔
1094

1095
  // Initialize Field Functions
1096
  InitializeFieldFunctions();
324✔
1097

1098
  opensn::mpi_comm.barrier();
324✔
1099
  log.Log() << "Done with parallel arrays." << std::endl;
648✔
1100
}
324✔
1101

1102
void
1103
LBSProblem::InitializeFieldFunctions()
324✔
1104
{
1105
  CALI_CXX_MARK_SCOPE("LBSProblem::InitializeFieldFunctions");
324✔
1106

1107
  if (not field_functions_.empty())
324✔
1108
    return;
×
1109

1110
  // Initialize Field Functions for flux moments
1111
  phi_field_functions_local_map_.clear();
324✔
1112

1113
  for (size_t g = 0; g < groups_.size(); ++g)
21,353✔
1114
  {
1115
    for (size_t m = 0; m < num_moments_; ++m)
97,016✔
1116
    {
1117
      std::string prefix;
75,987✔
1118
      if (options_.field_function_prefix_option == "prefix")
75,987✔
1119
      {
1120
        prefix = options_.field_function_prefix;
75,987✔
1121
        if (not prefix.empty())
75,987✔
1122
          prefix += "_";
1✔
1123
      }
1124
      if (options_.field_function_prefix_option == "solver_name")
75,987✔
1125
        prefix = GetName() + "_";
×
1126

1127
      char buff[100];
75,987✔
1128
      snprintf(
75,987✔
1129
        buff, 99, "%sphi_g%03d_m%02d", prefix.c_str(), static_cast<int>(g), static_cast<int>(m));
1130
      const std::string name = std::string(buff);
75,987✔
1131

1132
      auto group_ff = std::make_shared<FieldFunctionGridBased>(
75,987✔
1133
        name, discretization_, Unknown(UnknownType::SCALAR));
75,987✔
1134

1135
      field_function_stack.push_back(group_ff);
151,974✔
1136
      field_functions_.push_back(group_ff);
75,987✔
1137

1138
      phi_field_functions_local_map_[{g, m}] = field_functions_.size() - 1;
75,987✔
1139
    } // for m
75,987✔
1140
  } // for g
1141

1142
  // Initialize power generation field function
1143
  if (options_.power_field_function_on)
324✔
1144
  {
1145
    std::string prefix;
4✔
1146
    if (options_.field_function_prefix_option == "prefix")
4✔
1147
    {
1148
      prefix = options_.field_function_prefix;
4✔
1149
      if (not prefix.empty())
4✔
1150
        prefix += "_";
×
1151
    }
1152
    if (options_.field_function_prefix_option == "solver_name")
4✔
1153
      prefix = GetName() + "_";
×
1154

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

1158
    field_function_stack.push_back(power_ff);
8✔
1159
    field_functions_.push_back(power_ff);
4✔
1160

1161
    power_gen_fieldfunc_local_handle_ = field_functions_.size() - 1;
4✔
1162
  }
4✔
1163
}
324✔
1164

1165
void
1166
LBSProblem::InitializeSolverSchemes()
324✔
1167
{
1168
  CALI_CXX_MARK_SCOPE("LBSProblem::InitializeSolverSchemes");
324✔
1169

1170
  log.Log() << "Initializing WGS and AGS solvers";
648✔
1171

1172
  InitializeWGSSolvers();
324✔
1173

1174
  ags_solver_ = std::make_shared<AGSLinearSolver>(*this, wgs_solvers_);
324✔
1175
  if (groupsets_.size() == 1)
324✔
1176
  {
1177
    ags_solver_->SetMaxIterations(1);
269✔
1178
    ags_solver_->SetVerbosity(false);
269✔
1179
  }
1180
  else
1181
  {
1182
    ags_solver_->SetMaxIterations(options_.max_ags_iterations);
55✔
1183
    ags_solver_->SetVerbosity(options_.verbose_ags_iterations);
55✔
1184
  }
1185
  ags_solver_->SetTolerance(options_.ags_tolerance);
324✔
1186
}
324✔
1187

1188
#ifndef __OPENSN_USE_CUDA__
1189
void
1190
LBSProblem::InitializeGPUExtras()
324✔
1191
{
1192
}
324✔
1193

1194
void
1195
LBSProblem::ResetGPUCarriers()
320✔
1196
{
1197
}
320✔
1198

1199
void
1200
LBSProblem::CheckCapableDevices()
×
1201
{
1202
}
×
1203
#endif // __OPENSN_USE_CUDA__
1204

1205
std::vector<double>
1206
LBSProblem::MakeSourceMomentsFromPhi()
4✔
1207
{
1208
  CALI_CXX_MARK_SCOPE("LBSProblem::MakeSourceMomentsFromPhi");
4✔
1209

1210
  size_t num_local_dofs = discretization_->GetNumLocalDOFs(flux_moments_uk_man_);
4✔
1211

1212
  std::vector<double> source_moments(num_local_dofs, 0.0);
4✔
1213
  for (auto& groupset : groupsets_)
8✔
1214
  {
1215
    active_set_source_function_(groupset,
4✔
1216
                                source_moments,
1217
                                phi_new_local_,
4✔
1218
                                APPLY_AGS_SCATTER_SOURCES | APPLY_WGS_SCATTER_SOURCES |
1219
                                  APPLY_AGS_FISSION_SOURCES | APPLY_WGS_FISSION_SOURCES);
4✔
1220
  }
1221

1222
  return source_moments;
4✔
1223
}
4✔
1224

1225
void
1226
LBSProblem::UpdateFieldFunctions()
336✔
1227
{
1228
  CALI_CXX_MARK_SCOPE("LBSProblem::UpdateFieldFunctions");
336✔
1229

1230
  const auto& sdm = *discretization_;
336✔
1231
  const auto& phi_uk_man = flux_moments_uk_man_;
336✔
1232

1233
  // Update flux moments
1234
  for (const auto& [g_and_m, ff_index] : phi_field_functions_local_map_)
76,371✔
1235
  {
1236
    const size_t g = g_and_m.first;
76,035✔
1237
    const size_t m = g_and_m.second;
76,035✔
1238

1239
    std::vector<double> data_vector_local(local_node_count_, 0.0);
76,035✔
1240

1241
    for (const auto& cell : grid_->local_cells)
18,643,558✔
1242
    {
1243
      const auto& cell_mapping = sdm.GetCellMapping(cell);
18,567,523✔
1244
      const size_t num_nodes = cell_mapping.GetNumNodes();
18,567,523✔
1245

1246
      for (size_t i = 0; i < num_nodes; ++i)
130,874,695✔
1247
      {
1248
        const auto imapA = sdm.MapDOFLocal(cell, i, phi_uk_man, m, g);
112,307,172✔
1249
        const auto imapB = sdm.MapDOFLocal(cell, i);
112,307,172✔
1250

1251
        data_vector_local[imapB] = phi_new_local_[imapA];
112,307,172✔
1252
      } // for node
1253
    } // for cell
1254

1255
    auto& ff_ptr = field_functions_.at(ff_index);
76,035✔
1256
    ff_ptr->UpdateFieldVector(data_vector_local);
76,035✔
1257
  }
76,035✔
1258

1259
  // Update power generation and scalar flux
1260
  if (options_.power_field_function_on)
336✔
1261
  {
1262
    std::vector<double> data_vector_power_local(local_node_count_, 0.0);
4✔
1263

1264
    double local_total_power = 0.0;
4✔
1265
    for (const auto& cell : grid_->local_cells)
83,268✔
1266
    {
1267
      const auto& cell_mapping = sdm.GetCellMapping(cell);
83,264✔
1268
      const size_t num_nodes = cell_mapping.GetNumNodes();
83,264✔
1269

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

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

1274
      if (not xs->IsFissionable())
83,264✔
1275
        continue;
56,360✔
1276

1277
      for (size_t i = 0; i < num_nodes; ++i)
134,520✔
1278
      {
1279
        const auto imapA = sdm.MapDOFLocal(cell, i);
107,616✔
1280
        const auto imapB = sdm.MapDOFLocal(cell, i, phi_uk_man, 0, 0);
107,616✔
1281

1282
        double nodal_power = 0.0;
1283
        for (size_t g = 0; g < groups_.size(); ++g)
860,928✔
1284
        {
1285
          const double sigma_fg = xs->GetSigmaFission()[g];
753,312✔
1286
          // const double kappa_g = xs->Kappa()[g];
1287
          const double kappa_g = options_.power_default_kappa;
753,312✔
1288

1289
          nodal_power += kappa_g * sigma_fg * phi_new_local_[imapB + g];
753,312✔
1290
        } // for g
1291

1292
        data_vector_power_local[imapA] = nodal_power;
107,616✔
1293
        local_total_power += nodal_power * Vi(i);
107,616✔
1294
      } // for node
1295
    } // for cell
1296

1297
    double scale_factor = 1.0;
4✔
1298
    if (options_.power_normalization > 0.0)
4✔
1299
    {
1300
      double global_total_power = 0.0;
4✔
1301
      mpi_comm.all_reduce(local_total_power, global_total_power, mpi::op::sum<double>());
4✔
1302
      scale_factor = options_.power_normalization / global_total_power;
4✔
1303
      Scale(data_vector_power_local, scale_factor);
4✔
1304
    }
1305

1306
    const size_t ff_index = power_gen_fieldfunc_local_handle_;
4✔
1307

1308
    auto& ff_ptr = field_functions_.at(ff_index);
4✔
1309
    ff_ptr->UpdateFieldVector(data_vector_power_local);
4✔
1310

1311
    // scale scalar flux if neccessary
1312
    if (scale_factor != 1.0)
4✔
1313
    {
1314
      for (size_t g = 0; g < groups_.size(); ++g)
32✔
1315
      {
1316
        const size_t phi_ff_index = phi_field_functions_local_map_.at({g, size_t{0}});
28✔
1317
        auto& phi_ff_ptr = field_functions_.at(phi_ff_index);
28✔
1318
        const auto& phi_vec = phi_ff_ptr->GetLocalFieldVector();
28✔
1319
        std::vector<double> phi_scaled(phi_vec.begin(), phi_vec.end());
28✔
1320
        Scale(phi_scaled, scale_factor);
28✔
1321
        phi_ff_ptr->UpdateFieldVector(phi_scaled);
28✔
1322
      }
28✔
1323
    }
1324
  } // if power enabled
4✔
1325
}
336✔
1326

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

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

1343
  const auto& sdm = *discretization_;
×
1344
  const auto& phi_uk_man = flux_moments_uk_man_;
×
1345

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

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

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

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

1374
LBSProblem::~LBSProblem()
320✔
1375
{
1376
  ResetGPUCarriers();
1377
}
1,600✔
1378

320✔
1379
void
1380
LBSProblem::SetAdjoint(bool adjoint)
336✔
1381
{
1382
  if (adjoint != options_.adjoint)
336✔
1383
  {
1384
    options_.adjoint = adjoint;
12✔
1385

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

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

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

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