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

Open-Sn / opensn / 22336315317

23 Feb 2026 06:34PM UTC coverage: 74.149% (+0.03%) from 74.123%
22336315317

push

github

web-flow
Merge pull request #889 from andrsd/issue/761

Fix PETSc build issue when using cmake 4

20027 of 27009 relevant lines covered (74.15%)

67102519.58 hits per line

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

82.14
/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 "framework/data_types/allowable_range.h"
18
#include "caliper/cali.h"
19
#include <algorithm>
20
#include <iomanip>
21
#include <fstream>
22
#include <cstring>
23
#include <cassert>
24
#include <memory>
25
#include <stdexcept>
26
#include <sys/stat.h>
27

28
namespace opensn
29
{
30

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

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

41
  params.ChangeExistingParamToOptional("name", "LBSProblem");
1,148✔
42

43
  params.AddRequiredParameter<std::shared_ptr<MeshContinuum>>("mesh", "Mesh");
1,148✔
44

45
  params.AddRequiredParameter<unsigned int>("num_groups",
1,148✔
46
                                            "The total number of groups within the solver");
47

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

53
  params.AddRequiredParameterArray("xs_map",
1,148✔
54
                                   "Cross-section map from block IDs to cross-section objects.");
55

56
  params.AddOptionalParameterArray<std::shared_ptr<VolumetricSource>>(
1,148✔
57
    "volumetric_sources", {}, "An array of handles to volumetric sources.");
58

59
  params.AddOptionalParameterArray<std::shared_ptr<PointSource>>(
1,148✔
60
    "point_sources", {}, "An array of point sources.");
61

62
  params.AddOptionalParameterBlock(
1,148✔
63
    "options", ParameterBlock(), "Block of options. See <TT>OptionsBlock</TT>.");
1,148✔
64
  params.LinkParameterToBlock("options", "OptionsBlock");
1,148✔
65

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

68
  return params;
574✔
69
}
×
70

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

88
  // Initialize options
89
  if (params.IsParameterValid("options"))
574✔
90
  {
91
    auto options_params = LBSProblem::GetOptionsBlock();
346✔
92
    options_params.AssignParameters(params.GetParam("options"));
348✔
93
    ParseOptions(options_params);
344✔
94
  }
346✔
95
  applied_adjoint_ = options_.adjoint;
572✔
96
  applied_save_angular_flux_ = options_.save_angular_flux;
572✔
97

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

103
  InitializeGroupsets(params);
572✔
104
  InitializeSources(params);
572✔
105
  InitializeXSmapAndDensities(params);
572✔
106
  InitializeMaterials();
572✔
107
}
592✔
108

109
LBSOptions&
110
LBSProblem::GetOptions()
167,255✔
111
{
112
  return options_;
167,255✔
113
}
114

115
const LBSOptions&
116
LBSProblem::GetOptions() const
682,999,800✔
117
{
118
  return options_;
682,999,800✔
119
}
120

121
double
122
LBSProblem::GetTime() const
421,896✔
123
{
124
  return time_;
421,896✔
125
}
126

127
void
128
LBSProblem::SetTime(double time)
5,564✔
129
{
130
  time_ = time;
5,564✔
131
}
5,564✔
132

133
void
134
LBSProblem::SetTimeStep(double dt)
1,800✔
135
{
136
  if (dt <= 0.0)
1,800✔
137
    throw std::runtime_error(GetName() + " dt must be greater than zero.");
×
138
  dt_ = dt;
1,800✔
139
}
1,800✔
140

141
double
142
LBSProblem::GetTimeStep() const
2,147,483,647✔
143
{
144
  return dt_;
2,147,483,647✔
145
}
146

147
void
148
LBSProblem::SetTheta(double theta)
264✔
149
{
150
  if (theta <= 0.0 or theta > 1.0)
264✔
151
    throw std::runtime_error(GetName() + " theta must be in (0.0, 1.0].");
×
152
  theta_ = theta;
264✔
153
}
264✔
154

155
double
156
LBSProblem::GetTheta() const
2,147,483,647✔
157
{
158
  return theta_;
2,147,483,647✔
159
}
160

161
GeometryType
162
LBSProblem::GetGeometryType() const
4✔
163
{
164
  return geometry_type_;
4✔
165
}
166

167
unsigned int
168
LBSProblem::GetNumMoments() const
525,845✔
169
{
170
  return num_moments_;
525,845✔
171
}
172

173
unsigned int
174
LBSProblem::GetMaxCellDOFCount() const
787✔
175
{
176
  return max_cell_dof_count_;
787✔
177
}
178

179
unsigned int
180
LBSProblem::GetMinCellDOFCount() const
787✔
181
{
182
  return min_cell_dof_count_;
787✔
183
}
184

185
bool
186
LBSProblem::UseGPUs() const
1,099✔
187
{
188
  return use_gpus_;
1,099✔
189
}
190

191
unsigned int
192
LBSProblem::GetNumGroups() const
818,766✔
193
{
194
  return num_groups_;
818,766✔
195
}
196

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

203
unsigned int
204
LBSProblem::GetNumPrecursors() const
×
205
{
206
  return num_precursors_;
×
207
}
208

209
unsigned int
210
LBSProblem::GetMaxPrecursorsPerMaterial() const
9,550✔
211
{
212
  return max_precursors_per_material_;
9,550✔
213
}
214

215
std::vector<LBSGroupset>&
216
LBSProblem::GetGroupsets()
21,377,286✔
217
{
218
  return groupsets_;
21,377,286✔
219
}
220

221
const std::vector<LBSGroupset>&
222
LBSProblem::GetGroupsets() const
×
223
{
224
  return groupsets_;
×
225
}
226

227
void
228
LBSProblem::AddPointSource(std::shared_ptr<PointSource> point_source)
×
229
{
230
  point_sources_.push_back(point_source);
×
231
  if (discretization_)
×
232
    point_sources_.back()->Initialize(*this);
×
233
}
×
234

235
void
236
LBSProblem::ClearPointSources()
×
237
{
238
  point_sources_.clear();
×
239
}
×
240

241
const std::vector<std::shared_ptr<PointSource>>&
242
LBSProblem::GetPointSources() const
146,244✔
243
{
244
  return point_sources_;
146,244✔
245
}
246

247
void
248
LBSProblem::AddVolumetricSource(std::shared_ptr<VolumetricSource> volumetric_source)
16✔
249
{
250
  volumetric_sources_.push_back(volumetric_source);
16✔
251
  if (discretization_)
16✔
252
    volumetric_sources_.back()->Initialize(*this);
16✔
253
}
16✔
254

255
void
256
LBSProblem::ClearVolumetricSources()
12✔
257
{
258
  volumetric_sources_.clear();
12✔
259
}
12✔
260

261
const std::vector<std::shared_ptr<VolumetricSource>>&
262
LBSProblem::GetVolumetricSources() const
146,244✔
263
{
264
  return volumetric_sources_;
146,244✔
265
}
266

267
const BlockID2XSMap&
268
LBSProblem::GetBlockID2XSMap() const
19,266✔
269
{
270
  return block_id_to_xs_map_;
19,266✔
271
}
272

273
void
274
LBSProblem::SetBlockID2XSMap(const BlockID2XSMap& xs_map)
168✔
275
{
276
  block_id_to_xs_map_ = xs_map;
168✔
277
  InitializeMaterials();
168✔
278
  ResetGPUCarriers();
168✔
279
  InitializeGPUExtras();
168✔
280
}
168✔
281

282
std::shared_ptr<MeshContinuum>
283
LBSProblem::GetGrid() const
749,667✔
284
{
285
  return grid_;
749,667✔
286
}
287

288
const SpatialDiscretization&
289
LBSProblem::GetSpatialDiscretization() const
238,380✔
290
{
291
  return *discretization_;
238,380✔
292
}
293

294
const std::vector<UnitCellMatrices>&
295
LBSProblem::GetUnitCellMatrices() const
20,100✔
296
{
297
  return unit_cell_matrices_;
20,100✔
298
}
299

300
const std::map<uint64_t, UnitCellMatrices>&
301
LBSProblem::GetUnitGhostCellMatrices() const
17✔
302
{
303
  return unit_ghost_cell_matrices_;
17✔
304
}
305

306
std::vector<CellLBSView>&
307
LBSProblem::GetCellTransportViews()
301,358✔
308
{
309
  return cell_transport_views_;
301,358✔
310
}
311

312
const std::vector<CellLBSView>&
313
LBSProblem::GetCellTransportViews() const
631,020✔
314
{
315
  return cell_transport_views_;
631,020✔
316
}
317

318
const UnknownManager&
319
LBSProblem::GetUnknownManager() const
27,039✔
320
{
321
  return flux_moments_uk_man_;
27,039✔
322
}
323

324
size_t
325
LBSProblem::GetLocalNodeCount() const
3,288✔
326
{
327
  return local_node_count_;
3,288✔
328
}
329

330
size_t
331
LBSProblem::GetGlobalNodeCount() const
2,684✔
332
{
333
  return global_node_count_;
2,684✔
334
}
335

336
std::vector<double>&
337
LBSProblem::GetQMomentsLocal()
357,621✔
338
{
339
  return q_moments_local_;
357,621✔
340
}
341

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

348
std::vector<double>&
349
LBSProblem::GetExtSrcMomentsLocal()
4✔
350
{
351
  return ext_src_moments_local_;
4✔
352
}
353

354
const std::vector<double>&
355
LBSProblem::GetExtSrcMomentsLocal() const
210,340✔
356
{
357
  return ext_src_moments_local_;
210,340✔
358
}
359

360
std::vector<double>&
361
LBSProblem::GetPhiOldLocal()
277,199✔
362
{
363
  return phi_old_local_;
277,199✔
364
}
365

366
const std::vector<double>&
367
LBSProblem::GetPhiOldLocal() const
×
368
{
369
  return phi_old_local_;
×
370
}
371

372
std::vector<double>&
373
LBSProblem::GetPhiNewLocal()
248,778✔
374
{
375
  return phi_new_local_;
248,778✔
376
}
377

378
const std::vector<double>&
379
LBSProblem::GetPhiNewLocal() const
×
380
{
381
  return phi_new_local_;
×
382
}
383

384
std::vector<double>&
385
LBSProblem::GetPrecursorsNewLocal()
480✔
386
{
387
  return precursor_new_local_;
480✔
388
}
389

390
const std::vector<double>&
391
LBSProblem::GetPrecursorsNewLocal() const
×
392
{
393
  return precursor_new_local_;
×
394
}
395

396
std::vector<double>&
397
LBSProblem::GetDensitiesLocal()
1,037✔
398
{
399
  return densities_local_;
1,037✔
400
}
401

402
const std::vector<double>&
403
LBSProblem::GetDensitiesLocal() const
210,340✔
404
{
405
  return densities_local_;
210,340✔
406
}
407

408
SetSourceFunction
409
LBSProblem::GetActiveSetSourceFunction() const
4,274✔
410
{
411
  return active_set_source_function_;
4,274✔
412
}
413

414
void
415
LBSProblem::SetActiveSetSourceFunction(SetSourceFunction source_function)
156✔
416
{
417
  active_set_source_function_ = std::move(source_function);
156✔
418
}
156✔
419

420
std::shared_ptr<AGSLinearSolver>
421
LBSProblem::GetAGSSolver()
2,872✔
422
{
423
  return ags_solver_;
2,872✔
424
}
425

426
std::vector<std::shared_ptr<LinearSolver>>&
427
LBSProblem::GetWGSSolvers()
2,817✔
428
{
429
  return wgs_solvers_;
2,817✔
430
}
431

432
WGSContext&
433
LBSProblem::GetWGSContext(int groupset_id)
11,544✔
434
{
435
  auto& wgs_solver = wgs_solvers_[groupset_id];
11,544✔
436
  auto raw_context = wgs_solver->GetContext();
11,544✔
437
  auto wgs_context_ptr = std::dynamic_pointer_cast<WGSContext>(raw_context);
11,544✔
438
  OpenSnLogicalErrorIf(not wgs_context_ptr, "Failed to cast WGSContext");
11,544✔
439
  return *wgs_context_ptr;
11,544✔
440
}
23,088✔
441

442
std::pair<size_t, size_t>
443
LBSProblem::GetNumPhiIterativeUnknowns()
×
444
{
445
  const auto& sdm = *discretization_;
×
446
  const size_t num_local_phi_dofs = sdm.GetNumLocalDOFs(flux_moments_uk_man_);
×
447
  const size_t num_global_phi_dofs = sdm.GetNumGlobalDOFs(flux_moments_uk_man_);
×
448

449
  return {num_local_phi_dofs, num_global_phi_dofs};
×
450
}
451

452
size_t
453
LBSProblem::MapPhiFieldFunction(unsigned int g, unsigned int m) const
28,964✔
454
{
455
  OpenSnLogicalErrorIf(phi_field_functions_local_map_.count({g, m}) == 0,
28,964✔
456
                       std::string("Failure to map phi field function g") + std::to_string(g) +
457
                         " m" + std::to_string(m));
458

459
  return phi_field_functions_local_map_.at({g, m});
28,964✔
460
}
461

462
std::shared_ptr<FieldFunctionGridBased>
463
LBSProblem::GetPowerFieldFunction() const
×
464
{
465
  OpenSnLogicalErrorIf(not options_.power_field_function_on,
×
466
                       "Called when options_.power_field_function_on == false");
467

468
  return field_functions_[power_gen_fieldfunc_local_handle_];
×
469
}
470

471
InputParameters
472
LBSProblem::GetOptionsBlock()
714✔
473
{
474
  InputParameters params;
714✔
475

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

554
  return params;
714✔
555
}
×
556

557
InputParameters
558
LBSProblem::GetXSMapEntryBlock()
997✔
559
{
560
  InputParameters params;
997✔
561
  params.SetGeneralDescription("Set the cross-section map for the solver.");
1,994✔
562
  params.AddRequiredParameterArray("block_ids", "Mesh block IDs");
1,994✔
563
  params.AddRequiredParameter<std::shared_ptr<MultiGroupXS>>("xs", "Cross-section object");
1,994✔
564
  return params;
997✔
565
}
×
566

567
void
568
LBSProblem::SetOptions(const InputParameters& input)
12✔
569
{
570
  ParseOptions(input);
12✔
571
  ApplyOptions();
12✔
572
}
12✔
573

574
void
575
LBSProblem::ParseOptions(const InputParameters& input)
356✔
576
{
577
  auto params = LBSProblem::GetOptionsBlock();
356✔
578
  params.AssignParameters(input);
356✔
579
  const auto& params_at_assignment = input.GetParametersAtAssignment();
356✔
580
  const auto& specified_params = params_at_assignment.GetNumParameters() > 0
356✔
581
                                   ? params_at_assignment
356✔
582
                                   : static_cast<const ParameterBlock&>(input);
356✔
583

584
  // Apply only options explicitly specified by the caller.
585
  for (const auto& spec : specified_params.GetParameters())
1,311✔
586
  {
587
    if (spec.GetName() == "max_mpi_message_size")
955✔
588
      options_.max_mpi_message_size = spec.GetValue<int>();
×
589

590
    else if (spec.GetName() == "restart_writes_enabled")
955✔
591
      options_.restart_writes_enabled = spec.GetValue<bool>();
×
592

593
    else if (spec.GetName() == "write_delayed_psi_to_restart")
955✔
594
      options_.write_delayed_psi_to_restart = spec.GetValue<bool>();
×
595

596
    else if (spec.GetName() == "read_restart_path")
955✔
597
    {
598
      options_.read_restart_path = spec.GetValue<std::string>();
12✔
599
      if (not options_.read_restart_path.empty())
12✔
600
        options_.read_restart_path += std::to_string(opensn::mpi_comm.rank()) + ".restart.h5";
36✔
601
    }
602

603
    else if (spec.GetName() == "write_restart_path")
943✔
604
    {
605
      options_.write_restart_path = spec.GetValue<std::string>();
×
606
      if (not options_.write_restart_path.empty())
×
607
        options_.write_restart_path += std::to_string(opensn::mpi_comm.rank()) + ".restart.h5";
×
608
    }
609

610
    else if (spec.GetName() == "write_restart_time_interval")
943✔
611
      options_.write_restart_time_interval = std::chrono::seconds(spec.GetValue<int>());
×
612

613
    else if (spec.GetName() == "use_precursors")
943✔
614
      options_.use_precursors = spec.GetValue<bool>();
120✔
615

616
    else if (spec.GetName() == "use_source_moments")
823✔
617
      options_.use_src_moments = spec.GetValue<bool>();
4✔
618

619
    else if (spec.GetName() == "save_angular_flux")
819✔
620
      options_.save_angular_flux = spec.GetValue<bool>();
198✔
621

622
    else if (spec.GetName() == "verbose_inner_iterations")
621✔
623
      options_.verbose_inner_iterations = spec.GetValue<bool>();
207✔
624

625
    else if (spec.GetName() == "max_ags_iterations")
414✔
626
      options_.max_ags_iterations = spec.GetValue<int>();
50✔
627

628
    else if (spec.GetName() == "ags_tolerance")
364✔
629
      options_.ags_tolerance = spec.GetValue<double>();
8✔
630

631
    else if (spec.GetName() == "ags_convergence_check")
356✔
632
    {
633
      auto check = spec.GetValue<std::string>();
×
634
      options_.ags_pointwise_convergence = (check == "pointwise");
×
635
    }
×
636

637
    else if (spec.GetName() == "verbose_ags_iterations")
356✔
638
      options_.verbose_ags_iterations = spec.GetValue<bool>();
124✔
639

640
    else if (spec.GetName() == "verbose_outer_iterations")
232✔
641
      options_.verbose_outer_iterations = spec.GetValue<bool>();
183✔
642

643
    else if (spec.GetName() == "power_field_function_on")
49✔
644
      options_.power_field_function_on = spec.GetValue<bool>();
4✔
645

646
    else if (spec.GetName() == "power_default_kappa")
45✔
647
      options_.power_default_kappa = spec.GetValue<double>();
12✔
648

649
    else if (spec.GetName() == "power_normalization")
33✔
650
      options_.power_normalization = spec.GetValue<double>();
12✔
651

652
    else if (spec.GetName() == "field_function_prefix_option")
21✔
653
    {
654
      options_.field_function_prefix_option = spec.GetValue<std::string>();
×
655
    }
656

657
    else if (spec.GetName() == "field_function_prefix")
21✔
658
      options_.field_function_prefix = spec.GetValue<std::string>();
1✔
659

660
    else if (spec.GetName() == "adjoint")
20✔
661
      options_.adjoint = spec.GetValue<bool>();
20✔
662

663
  } // for specified options
664

665
  OpenSnInvalidArgumentIf(options_.write_restart_time_interval > std::chrono::seconds(0) and
356✔
666
                            not options_.restart_writes_enabled,
667
                          GetName() + ": `write_restart_time_interval>0` requires "
668
                                      "`restart_writes_enabled=true`.");
669

670
  OpenSnInvalidArgumentIf(options_.write_restart_time_interval > std::chrono::seconds(0) and
356✔
671
                            options_.write_restart_time_interval < std::chrono::seconds(30),
672
                          GetName() + ": `write_restart_time_interval` must be 0 (disabled) "
673
                                      "or at least 30 seconds.");
674

675
  OpenSnInvalidArgumentIf(options_.restart_writes_enabled and options_.write_restart_path.empty(),
356✔
676
                          GetName() + ": `restart_writes_enabled=true` requires a non-empty "
677
                                      "`write_restart_path`.");
678

679
  OpenSnInvalidArgumentIf(not options_.field_function_prefix.empty() and
356✔
680
                            options_.field_function_prefix_option != "prefix",
681
                          GetName() + ": non-empty `field_function_prefix` requires "
682
                                      "`field_function_prefix_option=\"prefix\"`.");
683

684
  if (options_.restart_writes_enabled)
356✔
685
  {
686
    const auto dir = options_.write_restart_path.parent_path();
×
687

688
    // Create restart directory if necessary.
689
    // If dir is empty, write path resolves relative to the working directory.
690
    if ((not dir.empty()) and opensn::mpi_comm.rank() == 0)
×
691
    {
692
      if (not std::filesystem::exists(dir))
×
693
      {
694
        if (not std::filesystem::create_directories(dir))
×
695
          throw std::runtime_error(GetName() + ": Failed to create restart directory " +
×
696
                                   dir.string());
×
697
      }
698
      else if (not std::filesystem::is_directory(dir))
×
699
        throw std::runtime_error(GetName() + ": Restart path exists but is not a directory " +
×
700
                                 dir.string());
×
701
    }
702
    opensn::mpi_comm.barrier();
×
703
    UpdateRestartWriteTime();
×
704
  }
×
705
}
356✔
706

707
void
708
LBSProblem::ApplyOptions()
12✔
709
{
710
  if (not discretization_)
12✔
711
    return;
712

713
  if (options_.adjoint != applied_adjoint_)
4✔
714
    SetAdjoint(options_.adjoint);
4✔
715

716
  if (options_.save_angular_flux != applied_save_angular_flux_)
4✔
717
    SetSaveAngularFlux(options_.save_angular_flux);
×
718
}
719

720
void
721
LBSProblem::Initialize()
572✔
722
{
723
  CALI_CXX_MARK_SCOPE("LBSProblem::Initialize");
572✔
724

725
  PrintSimHeader();
572✔
726
  mpi_comm.barrier();
572✔
727

728
  InitializeSpatialDiscretization();
572✔
729
  InitializeParrays();
572✔
730
  InitializeBoundaries();
572✔
731
  InitializeGPUExtras();
572✔
732
  SetAdjoint(options_.adjoint);
572✔
733

734
  // Initialize point sources
735
  for (auto& point_source : point_sources_)
581✔
736
    point_source->Initialize(*this);
9✔
737

738
  // Initialize volumetric sources
739
  for (auto& volumetric_source : volumetric_sources_)
1,051✔
740
    volumetric_source->Initialize(*this);
479✔
741
}
572✔
742

743
void
744
LBSProblem::PrintSimHeader()
×
745
{
746
  if (opensn::mpi_comm.rank() == 0)
×
747
  {
748
    std::stringstream outstr;
×
749
    outstr << "\n"
×
750
           << "Initializing " << GetName() << "\n\n"
×
751
           << "Scattering order    : " << scattering_order_ << "\n"
×
752
           << "Number of moments   : " << num_moments_ << "\n"
×
753
           << "Number of groups    : " << num_groups_ << "\n"
×
754
           << "Number of groupsets : " << groupsets_.size() << "\n\n";
×
755

756
    for (const auto& groupset : groupsets_)
×
757
    {
758
      outstr << "***** Groupset " << groupset.id << " *****\n"
×
759
             << "Groups:\n";
×
760
      const auto n_gs_groups = groupset.GetNumGroups();
×
761
      constexpr int groups_per_line = 12;
762
      for (size_t i = 0; i < n_gs_groups; ++i)
×
763
      {
764
        outstr << std::setw(5) << groupset.first_group + i << ' ';
×
765
        if ((i + 1) % groups_per_line == 0)
×
766
          outstr << '\n';
×
767
      }
768
      if (n_gs_groups > 0 && n_gs_groups % groups_per_line != 0)
×
769
        outstr << '\n';
×
770
    }
771

772
    log.Log() << outstr.str() << '\n';
×
773
  }
×
774
}
×
775

776
void
777
LBSProblem::InitializeSources(const InputParameters& params)
572✔
778
{
779
  if (params.Has("volumetric_sources"))
572✔
780
  {
781
    const auto& vol_srcs = params.GetParam("volumetric_sources");
572✔
782
    vol_srcs.RequireBlockTypeIs(ParameterBlockType::ARRAY);
572✔
783
    for (const auto& src : vol_srcs)
1,051✔
784
      volumetric_sources_.push_back(src.GetValue<std::shared_ptr<VolumetricSource>>());
958✔
785
  }
786

787
  if (params.Has("point_sources"))
572✔
788
  {
789
    const auto& pt_srcs = params.GetParam("point_sources");
572✔
790
    pt_srcs.RequireBlockTypeIs(ParameterBlockType::ARRAY);
572✔
791
    for (const auto& src : pt_srcs)
581✔
792
      point_sources_.push_back(src.GetValue<std::shared_ptr<PointSource>>());
18✔
793
  }
794
}
572✔
795

796
void
797
LBSProblem::InitializeGroupsets(const InputParameters& params)
572✔
798
{
799
  // Initialize groups
800
  if (num_groups_ == 0)
572✔
801
    throw std::invalid_argument(GetName() + ": Number of groups must be > 0");
×
802

803
  // Initialize groupsets
804
  const auto& groupsets_array = params.GetParam("groupsets");
572✔
805
  const size_t num_gs = groupsets_array.GetNumParameters();
572✔
806
  if (num_gs == 0)
572✔
807
    throw std::invalid_argument(GetName() + ": At least one groupset must be specified");
×
808
  for (size_t gs = 0; gs < num_gs; ++gs)
1,203✔
809
  {
810
    const auto& groupset_params = groupsets_array.GetParam(gs);
631✔
811
    InputParameters gs_input_params = LBSGroupset::GetInputParameters();
631✔
812
    gs_input_params.SetObjectType("LBSProblem:LBSGroupset");
631✔
813
    gs_input_params.AssignParameters(groupset_params);
631✔
814
    groupsets_.emplace_back(gs_input_params, gs, *this);
631✔
815
    if (groupsets_.back().GetNumGroups() == 0)
631✔
816
    {
817
      std::stringstream oss;
×
818
      oss << GetName() << ": No groups added to groupset " << groupsets_.back().id;
×
819
      throw std::runtime_error(oss.str());
×
820
    }
×
821
  }
631✔
822
}
572✔
823

824
void
825
LBSProblem::InitializeXSmapAndDensities(const InputParameters& params)
572✔
826
{
827
  // Build XS map
828
  const auto& xs_array = params.GetParam("xs_map");
572✔
829
  const size_t num_xs = xs_array.GetNumParameters();
572✔
830
  for (size_t i = 0; i < num_xs; ++i)
1,401✔
831
  {
832
    const auto& item_params = xs_array.GetParam(i);
829✔
833
    InputParameters xs_entry_pars = GetXSMapEntryBlock();
829✔
834
    xs_entry_pars.AssignParameters(item_params);
829✔
835

836
    const auto& block_ids_param = xs_entry_pars.GetParam("block_ids");
829✔
837
    block_ids_param.RequireBlockTypeIs(ParameterBlockType::ARRAY);
829✔
838
    const auto& block_ids = block_ids_param.GetVectorValue<unsigned int>();
829✔
839
    auto xs = xs_entry_pars.GetSharedPtrParam<MultiGroupXS>("xs");
829✔
840
    for (const auto& block_id : block_ids)
1,766✔
841
      block_id_to_xs_map_[block_id] = xs;
937✔
842
  }
829✔
843

844
  // Assign placeholder unit densities
845
  densities_local_.assign(grid_->local_cells.size(), 1.0);
572✔
846
}
572✔
847

848
void
849
LBSProblem::InitializeMaterials()
756✔
850
{
851
  CALI_CXX_MARK_SCOPE("LBSProblem::InitializeMaterials");
756✔
852

853
  log.Log0Verbose1() << "Initializing Materials";
1,512✔
854

855
  // Create set of material ids locally relevant
856
  int invalid_mat_cell_count = 0;
756✔
857
  std::set<unsigned int> unique_block_ids;
756✔
858
  for (auto& cell : grid_->local_cells)
598,629✔
859
  {
860
    unique_block_ids.insert(cell.block_id);
597,873✔
861
    if (cell.block_id == std::numeric_limits<unsigned int>::max() or
597,873✔
862
        (block_id_to_xs_map_.find(cell.block_id) == block_id_to_xs_map_.end()))
597,873✔
863
      ++invalid_mat_cell_count;
×
864
  }
865
  const auto& ghost_cell_ids = grid_->cells.GetGhostGlobalIDs();
756✔
866
  for (uint64_t cell_id : ghost_cell_ids)
98,859✔
867
  {
868
    const auto& cell = grid_->cells[cell_id];
98,103✔
869
    unique_block_ids.insert(cell.block_id);
98,103✔
870
    if (cell.block_id == std::numeric_limits<unsigned int>::max() or
98,103✔
871
        (block_id_to_xs_map_.find(cell.block_id) == block_id_to_xs_map_.end()))
98,103✔
872
      ++invalid_mat_cell_count;
×
873
  }
874
  OpenSnLogicalErrorIf(invalid_mat_cell_count > 0,
756✔
875
                       std::to_string(invalid_mat_cell_count) +
876
                         " cells encountered with an invalid material id.");
877

878
  // Get ready for processing
879
  for (const auto& [blk_id, mat] : block_id_to_xs_map_)
1,901✔
880
  {
881
    mat->SetAdjointMode(options_.adjoint);
1,145✔
882

883
    OpenSnLogicalErrorIf(mat->GetNumGroups() < num_groups_,
1,145✔
884
                         "Cross-sections for block \"" + std::to_string(blk_id) +
885
                           "\" have fewer groups (" + std::to_string(mat->GetNumGroups()) +
886
                           ") than the simulation (" + std::to_string(num_groups_) + "). " +
887
                           "Cross-sections must have at least as many groups as the simulation.");
888
  }
889

890
  // Initialize precursor properties
891
  num_precursors_ = 0;
756✔
892
  max_precursors_per_material_ = 0;
756✔
893
  for (const auto& mat_id_xs : block_id_to_xs_map_)
1,901✔
894
  {
895
    const auto& xs = mat_id_xs.second;
1,145✔
896
    num_precursors_ += xs->GetNumPrecursors();
1,145✔
897
    max_precursors_per_material_ = std::max(xs->GetNumPrecursors(), max_precursors_per_material_);
1,145✔
898
  }
899

900
  // if no precursors, turn off precursors
901
  if (num_precursors_ == 0)
756✔
902
    options_.use_precursors = false;
560✔
903

904
  // check compatibility when precursors are on
905
  if (options_.use_precursors)
756✔
906
  {
907
    for (const auto& [mat_id, xs] : block_id_to_xs_map_)
352✔
908
    {
909
      OpenSnLogicalErrorIf(xs->IsFissionable() and num_precursors_ == 0,
176✔
910
                           "Incompatible cross-section data encountered for material id " +
911
                             std::to_string(mat_id) + ". When delayed neutron data is present " +
912
                             "for one fissionable matrial, it must be present for all fissionable "
913
                             "materials.");
914
    }
915
  }
916

917
  // Update transport views if available
918
  if (grid_->local_cells.size() == cell_transport_views_.size())
756✔
919
    for (const auto& cell : grid_->local_cells)
26,172✔
920
    {
921
      const auto& xs_ptr = block_id_to_xs_map_[cell.block_id];
25,988✔
922
      auto& transport_view = cell_transport_views_[cell.local_id];
25,988✔
923
      transport_view.ReassignXS(*xs_ptr);
25,988✔
924
    }
925

926
  mpi_comm.barrier();
756✔
927
}
756✔
928

929
void
930
LBSProblem::InitializeSpatialDiscretization()
492✔
931
{
932
  CALI_CXX_MARK_SCOPE("LBSProblem::InitializeSpatialDiscretization");
492✔
933

934
  log.Log() << "Initializing spatial discretization.\n";
984✔
935
  discretization_ = PieceWiseLinearDiscontinuous::New(grid_);
492✔
936

937
  ComputeUnitIntegrals();
492✔
938
}
492✔
939

940
void
941
LBSProblem::ComputeUnitIntegrals()
572✔
942
{
943
  CALI_CXX_MARK_SCOPE("LBSProblem::ComputeUnitIntegrals");
572✔
944

945
  log.Log() << "Computing unit integrals.\n";
1,144✔
946
  const auto& sdm = *discretization_;
572✔
947

948
  const size_t num_local_cells = grid_->local_cells.size();
572✔
949
  unit_cell_matrices_.resize(num_local_cells);
572✔
950

951
  for (const auto& cell : grid_->local_cells)
572,457✔
952
    unit_cell_matrices_[cell.local_id] =
571,885✔
953
      ComputeUnitCellIntegrals(sdm, cell, grid_->GetCoordinateSystem());
571,885✔
954

955
  const auto ghost_ids = grid_->cells.GetGhostGlobalIDs();
572✔
956
  for (auto ghost_id : ghost_ids)
90,185✔
957
    unit_ghost_cell_matrices_[ghost_id] =
89,613✔
958
      ComputeUnitCellIntegrals(sdm, grid_->cells[ghost_id], grid_->GetCoordinateSystem());
179,226✔
959

960
  // Assessing global unit cell matrix storage
961
  std::array<size_t, 2> num_local_ucms = {unit_cell_matrices_.size(),
572✔
962
                                          unit_ghost_cell_matrices_.size()};
572✔
963
  std::array<size_t, 2> num_global_ucms = {0, 0};
572✔
964

965
  mpi_comm.all_reduce(num_local_ucms.data(), 2, num_global_ucms.data(), mpi::op::sum<size_t>());
572✔
966

967
  opensn::mpi_comm.barrier();
572✔
968
  log.Log() << "Ghost cell unit cell-matrix ratio: "
572✔
969
            << (double)num_global_ucms[1] * 100 / (double)num_global_ucms[0] << "%";
1,144✔
970
  log.Log() << "Cell matrices computed.";
1,144✔
971
}
572✔
972

973
void
974
LBSProblem::InitializeParrays()
572✔
975
{
976
  CALI_CXX_MARK_SCOPE("LBSProblem::InitializeParrays");
572✔
977

978
  log.Log() << "Initializing parallel arrays."
1,144✔
979
            << " G=" << num_groups_ << " M=" << num_moments_ << std::endl;
572✔
980

981
  // Initialize unknown
982
  // structure
983
  flux_moments_uk_man_.unknowns.clear();
572✔
984
  for (unsigned int m = 0; m < num_moments_; ++m)
1,718✔
985
  {
986
    flux_moments_uk_man_.AddUnknown(UnknownType::VECTOR_N, num_groups_);
1,146✔
987
    flux_moments_uk_man_.unknowns.back().name = "m" + std::to_string(m);
1,146✔
988
  }
989

990
  // Compute local # of dof
991
  local_node_count_ = discretization_->GetNumLocalNodes();
572✔
992
  global_node_count_ = discretization_->GetNumGlobalNodes();
572✔
993

994
  // Compute num of unknowns
995
  size_t local_unknown_count = local_node_count_ * num_groups_ * num_moments_;
572✔
996

997
  log.LogAllVerbose1() << "LBS Number of phi unknowns: " << local_unknown_count;
1,144✔
998

999
  // Size local vectors
1000
  q_moments_local_.assign(local_unknown_count, 0.0);
572✔
1001
  phi_old_local_.assign(local_unknown_count, 0.0);
572✔
1002
  phi_new_local_.assign(local_unknown_count, 0.0);
572✔
1003

1004
  // Setup precursor vector
1005
  if (options_.use_precursors)
572✔
1006
  {
1007
    size_t num_precursor_dofs = grid_->local_cells.size() * max_precursors_per_material_;
60✔
1008
    precursor_new_local_.assign(num_precursor_dofs, 0.0);
60✔
1009
  }
1010

1011
  // Initialize transport views
1012
  // Transport views act as a data structure to store information
1013
  // related to the transport simulation. The most prominent function
1014
  // here is that it holds the means to know where a given cell's
1015
  // transport quantities are located in the unknown vectors (i.e. phi)
1016
  //
1017
  // Also, for a given cell, within a given sweep chunk,
1018
  // we need to solve a matrix which square size is the
1019
  // amount of nodes on the cell. max_cell_dof_count is
1020
  // initialized here.
1021
  //
1022
  size_t block_MG_counter = 0; // Counts the strides of moment and group
572✔
1023

1024
  const Vector3 ihat(1.0, 0.0, 0.0);
572✔
1025
  const Vector3 jhat(0.0, 1.0, 0.0);
572✔
1026
  const Vector3 khat(0.0, 0.0, 1.0);
572✔
1027

1028
  min_cell_dof_count_ = std::numeric_limits<unsigned int>::max();
572✔
1029
  max_cell_dof_count_ = 0;
572✔
1030
  cell_transport_views_.clear();
572✔
1031
  cell_transport_views_.reserve(grid_->local_cells.size());
572✔
1032
  for (auto& cell : grid_->local_cells)
572,457✔
1033
  {
1034
    size_t num_nodes = discretization_->GetCellNumNodes(cell);
571,885✔
1035

1036
    // compute cell volumes
1037
    double cell_volume = 0.0;
571,885✔
1038
    const auto& IntV_shapeI = unit_cell_matrices_[cell.local_id].intV_shapeI;
571,885✔
1039
    for (size_t i = 0; i < num_nodes; ++i)
3,966,781✔
1040
      cell_volume += IntV_shapeI(i);
3,394,896✔
1041

1042
    size_t cell_phi_address = block_MG_counter;
571,885✔
1043

1044
    const size_t num_faces = cell.faces.size();
571,885✔
1045
    std::vector<bool> face_local_flags(num_faces, true);
571,885✔
1046
    std::vector<int> face_locality(num_faces, opensn::mpi_comm.rank());
571,885✔
1047
    std::vector<const Cell*> neighbor_cell_ptrs(num_faces, nullptr);
571,885✔
1048
    bool cell_on_boundary = false;
571,885✔
1049
    int f = 0;
571,885✔
1050
    for (auto& face : cell.faces)
3,399,301✔
1051
    {
1052
      if (not face.has_neighbor)
2,827,416✔
1053
      {
1054
        cell_on_boundary = true;
91,560✔
1055
        face_local_flags[f] = false;
91,560✔
1056
        face_locality[f] = -1;
91,560✔
1057
      } // if bndry
1058
      else
1059
      {
1060
        const int neighbor_partition = face.GetNeighborPartitionID(grid_.get());
2,735,856✔
1061
        face_local_flags[f] = (neighbor_partition == opensn::mpi_comm.rank());
2,735,856✔
1062
        face_locality[f] = neighbor_partition;
2,735,856✔
1063
        neighbor_cell_ptrs[f] = &grid_->cells[face.neighbor_id];
2,735,856✔
1064
      }
1065

1066
      ++f;
2,827,416✔
1067
    } // for f
1068

1069
    max_cell_dof_count_ = std::max(max_cell_dof_count_, static_cast<unsigned int>(num_nodes));
571,885✔
1070
    min_cell_dof_count_ = std::min(min_cell_dof_count_, static_cast<unsigned int>(num_nodes));
571,885✔
1071
    cell_transport_views_.emplace_back(cell_phi_address,
1,143,770✔
1072
                                       num_nodes,
1073
                                       num_groups_,
571,885✔
1074
                                       num_moments_,
571,885✔
1075
                                       num_faces,
1076
                                       *block_id_to_xs_map_[cell.block_id],
571,885✔
1077
                                       cell_volume,
1078
                                       face_local_flags,
1079
                                       face_locality,
1080
                                       neighbor_cell_ptrs,
1081
                                       cell_on_boundary);
1082
    block_MG_counter += num_nodes * num_groups_ * num_moments_;
571,885✔
1083
  } // for local cell
571,885✔
1084

1085
  // Populate grid nodal mappings
1086
  // This is used in the Flux Data Structures (FLUDS)
1087
  grid_nodal_mappings_.clear();
572✔
1088
  grid_nodal_mappings_.reserve(grid_->local_cells.size());
572✔
1089
  for (auto& cell : grid_->local_cells)
572,457✔
1090
  {
1091
    CellFaceNodalMapping cell_nodal_mapping;
571,885✔
1092
    cell_nodal_mapping.reserve(cell.faces.size());
571,885✔
1093

1094
    for (auto& face : cell.faces)
3,399,301✔
1095
    {
1096
      std::vector<short> face_node_mapping;
2,827,416✔
1097
      std::vector<short> cell_node_mapping;
2,827,416✔
1098
      int adj_face_idx = -1;
2,827,416✔
1099

1100
      if (face.has_neighbor)
2,827,416✔
1101
      {
1102
        grid_->FindAssociatedVertices(face, face_node_mapping);
2,735,856✔
1103
        grid_->FindAssociatedCellVertices(face, cell_node_mapping);
2,735,856✔
1104
        adj_face_idx = face.GetNeighborAdjacentFaceIndex(grid_.get());
2,735,856✔
1105
      }
1106

1107
      cell_nodal_mapping.emplace_back(adj_face_idx, face_node_mapping, cell_node_mapping);
2,827,416✔
1108
    } // for f
2,827,416✔
1109

1110
    grid_nodal_mappings_.push_back(cell_nodal_mapping);
571,885✔
1111
  } // for local cell
571,885✔
1112

1113
  // Get grid localized communicator set
1114
  grid_local_comm_set_ = grid_->MakeMPILocalCommunicatorSet();
572✔
1115

1116
  // Initialize Field Functions
1117
  InitializeFieldFunctions();
572✔
1118

1119
  opensn::mpi_comm.barrier();
572✔
1120
  log.Log() << "Done with parallel arrays." << std::endl;
1,144✔
1121
}
572✔
1122

1123
void
1124
LBSProblem::InitializeFieldFunctions()
572✔
1125
{
1126
  CALI_CXX_MARK_SCOPE("LBSProblem::InitializeFieldFunctions");
572✔
1127

1128
  if (not field_functions_.empty())
572✔
1129
    return;
×
1130

1131
  // Initialize Field Functions for flux moments
1132
  phi_field_functions_local_map_.clear();
572✔
1133

1134
  for (unsigned int g = 0; g < num_groups_; ++g)
23,233✔
1135
  {
1136
    for (unsigned int m = 0; m < num_moments_; ++m)
100,640✔
1137
    {
1138
      std::string prefix;
77,979✔
1139
      if (options_.field_function_prefix_option == "prefix")
77,979✔
1140
      {
1141
        prefix = options_.field_function_prefix;
77,979✔
1142
        if (not prefix.empty())
77,979✔
1143
          prefix += "_";
1✔
1144
      }
1145
      if (options_.field_function_prefix_option == "solver_name")
77,979✔
1146
        prefix = GetName() + "_";
×
1147

1148
      std::ostringstream oss;
77,979✔
1149
      oss << prefix << "phi_g" << std::setw(3) << std::setfill('0') << static_cast<int>(g) << "_m"
77,979✔
1150
          << std::setw(2) << std::setfill('0') << static_cast<int>(m);
77,979✔
1151
      const std::string name = oss.str();
77,979✔
1152

1153
      auto group_ff = std::make_shared<FieldFunctionGridBased>(
77,979✔
1154
        name, discretization_, Unknown(UnknownType::SCALAR));
77,979✔
1155

1156
      field_function_stack.push_back(group_ff);
155,958✔
1157
      field_functions_.push_back(group_ff);
77,979✔
1158

1159
      phi_field_functions_local_map_[{g, m}] = field_functions_.size() - 1;
77,979✔
1160
    } // for m
77,979✔
1161
  } // for g
1162

1163
  // Initialize power generation field function
1164
  if (options_.power_field_function_on)
572✔
1165
  {
1166
    std::string prefix;
4✔
1167
    if (options_.field_function_prefix_option == "prefix")
4✔
1168
    {
1169
      prefix = options_.field_function_prefix;
4✔
1170
      if (not prefix.empty())
4✔
1171
        prefix += "_";
×
1172
    }
1173
    if (options_.field_function_prefix_option == "solver_name")
4✔
1174
      prefix = GetName() + "_";
×
1175

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

1179
    field_function_stack.push_back(power_ff);
8✔
1180
    field_functions_.push_back(power_ff);
4✔
1181

1182
    power_gen_fieldfunc_local_handle_ = field_functions_.size() - 1;
4✔
1183
  }
4✔
1184
}
572✔
1185

1186
void
1187
LBSProblem::InitializeSolverSchemes()
728✔
1188
{
1189
  CALI_CXX_MARK_SCOPE("LBSProblem::InitializeSolverSchemes");
728✔
1190
  InitializeWGSSolvers();
728✔
1191

1192
  ags_solver_ = std::make_shared<AGSLinearSolver>(*this, wgs_solvers_);
728✔
1193
  if (groupsets_.size() == 1)
728✔
1194
  {
1195
    ags_solver_->SetMaxIterations(1);
673✔
1196
    ags_solver_->SetVerbosity(false);
673✔
1197
  }
1198
  else
1199
  {
1200
    ags_solver_->SetMaxIterations(options_.max_ags_iterations);
55✔
1201
    ags_solver_->SetVerbosity(options_.verbose_ags_iterations);
55✔
1202
  }
1203
  ags_solver_->SetTolerance(options_.ags_tolerance);
728✔
1204
}
728✔
1205

1206
#ifndef __OPENSN_WITH_GPU__
1207
void
1208
LBSProblem::InitializeGPUExtras()
740✔
1209
{
1210
}
740✔
1211

1212
void
1213
LBSProblem::ResetGPUCarriers()
728✔
1214
{
1215
}
728✔
1216

1217
void
1218
LBSProblem::CheckCapableDevices()
×
1219
{
1220
}
×
1221
#endif // __OPENSN_WITH_GPU__
1222

1223
std::vector<double>
1224
LBSProblem::MakeSourceMomentsFromPhi()
4✔
1225
{
1226
  CALI_CXX_MARK_SCOPE("LBSProblem::MakeSourceMomentsFromPhi");
4✔
1227

1228
  size_t num_local_dofs = discretization_->GetNumLocalDOFs(flux_moments_uk_man_);
4✔
1229

1230
  std::vector<double> source_moments(num_local_dofs, 0.0);
4✔
1231
  for (auto& groupset : groupsets_)
8✔
1232
  {
1233
    active_set_source_function_(groupset,
4✔
1234
                                source_moments,
1235
                                phi_new_local_,
4✔
1236
                                APPLY_AGS_SCATTER_SOURCES | APPLY_WGS_SCATTER_SOURCES |
1237
                                  APPLY_AGS_FISSION_SOURCES | APPLY_WGS_FISSION_SOURCES);
4✔
1238
  }
1239

1240
  return source_moments;
4✔
1241
}
4✔
1242

1243
void
1244
LBSProblem::UpdateFieldFunctions()
2,744✔
1245
{
1246
  CALI_CXX_MARK_SCOPE("LBSProblem::UpdateFieldFunctions");
2,744✔
1247

1248
  const auto& sdm = *discretization_;
2,744✔
1249
  const auto& phi_uk_man = flux_moments_uk_man_;
2,744✔
1250

1251
  // Update flux moments
1252
  for (const auto& [g_and_m, ff_index] : phi_field_functions_local_map_)
87,719✔
1253
  {
1254
    const auto g = g_and_m.first;
84,975✔
1255
    const auto m = g_and_m.second;
84,975✔
1256

1257
    std::vector<double> data_vector_local(local_node_count_, 0.0);
84,975✔
1258

1259
    for (const auto& cell : grid_->local_cells)
24,447,304✔
1260
    {
1261
      const auto& cell_mapping = sdm.GetCellMapping(cell);
24,362,329✔
1262
      const size_t num_nodes = cell_mapping.GetNumNodes();
24,362,329✔
1263

1264
      for (size_t i = 0; i < num_nodes; ++i)
164,643,037✔
1265
      {
1266
        const auto imapA = sdm.MapDOFLocal(cell, i, phi_uk_man, m, g);
140,280,708✔
1267
        const auto imapB = sdm.MapDOFLocal(cell, i);
140,280,708✔
1268

1269
        data_vector_local[imapB] = phi_new_local_[imapA];
140,280,708✔
1270
      } // for node
1271
    } // for cell
1272

1273
    auto& ff_ptr = field_functions_.at(ff_index);
84,975✔
1274
    ff_ptr->UpdateFieldVector(data_vector_local);
84,975✔
1275
  }
84,975✔
1276

1277
  // Update power generation and scalar flux
1278
  if (options_.power_field_function_on)
2,744✔
1279
  {
1280
    std::vector<double> data_vector_power_local(local_node_count_, 0.0);
4✔
1281

1282
    double local_total_power = 0.0;
4✔
1283
    for (const auto& cell : grid_->local_cells)
83,268✔
1284
    {
1285
      const auto& cell_mapping = sdm.GetCellMapping(cell);
83,264✔
1286
      const size_t num_nodes = cell_mapping.GetNumNodes();
83,264✔
1287

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

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

1292
      if (not xs->IsFissionable())
83,264✔
1293
        continue;
56,360✔
1294

1295
      for (size_t i = 0; i < num_nodes; ++i)
134,520✔
1296
      {
1297
        const auto imapA = sdm.MapDOFLocal(cell, i);
107,616✔
1298
        const auto imapB = sdm.MapDOFLocal(cell, i, phi_uk_man, 0, 0);
107,616✔
1299

1300
        double nodal_power = 0.0;
1301
        for (unsigned int g = 0; g < num_groups_; ++g)
860,928✔
1302
        {
1303
          const double sigma_fg = xs->GetSigmaFission()[g];
753,312✔
1304
          // const double kappa_g = xs->Kappa()[g];
1305
          const double kappa_g = options_.power_default_kappa;
753,312✔
1306

1307
          nodal_power += kappa_g * sigma_fg * phi_new_local_[imapB + g];
753,312✔
1308
        } // for g
1309

1310
        data_vector_power_local[imapA] = nodal_power;
107,616✔
1311
        local_total_power += nodal_power * Vi(i);
107,616✔
1312
      } // for node
1313
    } // for cell
1314

1315
    double scale_factor = 1.0;
4✔
1316
    if (options_.power_normalization > 0.0)
4✔
1317
    {
1318
      double global_total_power = 0.0;
4✔
1319
      mpi_comm.all_reduce(local_total_power, global_total_power, mpi::op::sum<double>());
4✔
1320
      scale_factor = options_.power_normalization / global_total_power;
4✔
1321
      Scale(data_vector_power_local, scale_factor);
4✔
1322
    }
1323

1324
    const size_t ff_index = power_gen_fieldfunc_local_handle_;
4✔
1325

1326
    auto& ff_ptr = field_functions_.at(ff_index);
4✔
1327
    ff_ptr->UpdateFieldVector(data_vector_power_local);
4✔
1328

1329
    // scale scalar flux if neccessary
1330
    if (scale_factor != 1.0)
4✔
1331
    {
1332
      for (unsigned int g = 0; g < num_groups_; ++g)
32✔
1333
      {
1334
        const size_t phi_ff_index = phi_field_functions_local_map_.at({g, size_t{0}});
28✔
1335
        auto& phi_ff_ptr = field_functions_.at(phi_ff_index);
28✔
1336
        const auto& phi_vec = phi_ff_ptr->GetLocalFieldVector();
28✔
1337
        std::vector<double> phi_scaled(phi_vec.begin(), phi_vec.end());
28✔
1338
        Scale(phi_scaled, scale_factor);
28✔
1339
        phi_ff_ptr->UpdateFieldVector(phi_scaled);
28✔
1340
      }
28✔
1341
    }
1342
  } // if power enabled
4✔
1343
}
2,744✔
1344

1345
void
1346
LBSProblem::SetPhiFromFieldFunctions(PhiSTLOption which_phi,
×
1347
                                     const std::vector<unsigned int>& m_indices,
1348
                                     const std::vector<unsigned int>& g_indices)
1349
{
1350
  CALI_CXX_MARK_SCOPE("LBSProblem::SetPhiFromFieldFunctions");
×
1351

1352
  std::vector<unsigned int> m_ids_to_copy = m_indices;
×
1353
  std::vector<unsigned int> g_ids_to_copy = g_indices;
×
1354
  if (m_indices.empty())
×
1355
    for (unsigned int m = 0; m < num_moments_; ++m)
×
1356
      m_ids_to_copy.push_back(m);
×
1357
  if (g_ids_to_copy.empty())
×
1358
    for (unsigned int g = 0; g < num_groups_; ++g)
×
1359
      g_ids_to_copy.push_back(g);
×
1360

1361
  const auto& sdm = *discretization_;
×
1362
  const auto& phi_uk_man = flux_moments_uk_man_;
×
1363

1364
  for (const auto m : m_ids_to_copy)
×
1365
  {
1366
    for (const auto g : g_ids_to_copy)
×
1367
    {
1368
      const size_t ff_index = phi_field_functions_local_map_.at({g, m});
×
1369
      const auto& ff_ptr = field_functions_.at(ff_index);
×
1370
      const auto& ff_data = ff_ptr->GetLocalFieldVector();
×
1371

1372
      for (const auto& cell : grid_->local_cells)
×
1373
      {
1374
        const auto& cell_mapping = sdm.GetCellMapping(cell);
×
1375
        const size_t num_nodes = cell_mapping.GetNumNodes();
×
1376

1377
        for (size_t i = 0; i < num_nodes; ++i)
×
1378
        {
1379
          const auto imapA = sdm.MapDOFLocal(cell, i);
×
1380
          const auto imapB = sdm.MapDOFLocal(cell, i, phi_uk_man, m, g);
×
1381

1382
          if (which_phi == PhiSTLOption::PHI_OLD)
×
1383
            phi_old_local_[imapB] = ff_data[imapA];
×
1384
          else if (which_phi == PhiSTLOption::PHI_NEW)
×
1385
            phi_new_local_[imapB] = ff_data[imapA];
×
1386
        } // for node
1387
      } // for cell
1388
    } // for g
1389
  } // for m
1390
}
×
1391

1392
LBSProblem::~LBSProblem()
560✔
1393
{
1394
  ResetGPUCarriers();
1395
}
2,800✔
1396

560✔
1397
void
1398
LBSProblem::SetSaveAngularFlux(bool save)
160✔
1399
{
1400
  options_.save_angular_flux = save;
160✔
1401
  if (discretization_)
160✔
1402
    applied_save_angular_flux_ = save;
104✔
1403
}
160✔
1404

1405
void
1406
LBSProblem::SetAdjoint(bool adjoint)
588✔
1407
{
1408
  options_.adjoint = adjoint;
588✔
1409

1410
  if (not discretization_)
588✔
1411
    return;
1412

1413
  if (adjoint != applied_adjoint_)
588✔
1414
  {
1415
    // If a discretization exists, the solver has already been initialized.
1416
    // Reinitialize the materials to obtain the appropriate xs and clear the
1417
    // sources to prepare for defining the adjoint problem
1418
    // The materials are reinitialized here to ensure that the proper cross sections
1419
    // are available to the solver. Because an adjoint solve requires volumetric or
1420
    // point sources, the material-based sources are not set within the initialize routine.
1421
    InitializeMaterials();
16✔
1422

1423
    // Forward and adjoint sources are fundamentally different, so any existing sources
1424
    // should be cleared and reset through options upon changing modes.
1425
    point_sources_.clear();
16✔
1426
    volumetric_sources_.clear();
16✔
1427
    ClearBoundaries();
16✔
1428

1429
    // Set all solutions to zero.
1430
    phi_old_local_.assign(phi_old_local_.size(), 0.0);
16✔
1431
    phi_new_local_.assign(phi_new_local_.size(), 0.0);
16✔
1432
    ZeroSolutions();
16✔
1433
    precursor_new_local_.assign(precursor_new_local_.size(), 0.0);
16✔
1434

1435
    applied_adjoint_ = adjoint;
16✔
1436
  }
1437
}
1438

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