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

Open-Sn / opensn / 21196332731

20 Jan 2026 08:32PM UTC coverage: 74.367% (-0.07%) from 74.437%
21196332731

push

github

web-flow
Merge pull request #891 from quocdang1998/angle-aggregation

Removing one level of std::vector in AngleAggregation

125 of 143 new or added lines in 5 files covered. (87.41%)

302 existing lines in 10 files now uncovered.

18745 of 25206 relevant lines covered (74.37%)

67731135.13 hits per line

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

82.68
/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()
370✔
38
{
39
  InputParameters params = Problem::GetInputParameters();
370✔
40

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

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

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

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

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

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

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

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

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

67
  params.AddOptionalParameter(
740✔
68
    "time_dependent", false, "Flag indicating whether the problem is time dependent.");
69
  return params;
370✔
UNCOV
70
}
×
71

72
LBSProblem::LBSProblem(const InputParameters& params)
370✔
73
  : Problem(params),
74
    time_dependent_(params.GetParamValue<bool>("time_dependent")),
370✔
75
    num_groups_(params.GetParamValue<size_t>("num_groups")),
370✔
76
    grid_(params.GetSharedPtrParam<MeshContinuum>("mesh")),
370✔
77
    use_gpus_(params.GetParamValue<bool>("use_gpus"))
1,480✔
78
{
79
  // Check system for GPU acceleration
80
  if (use_gpus_)
370✔
81
  {
UNCOV
82
    if (time_dependent_)
×
UNCOV
83
      throw std::invalid_argument(GetName() +
×
UNCOV
84
                                  ": Time dependent problems are not supported on GPUs.");
×
85
#ifdef __OPENSN_WITH_GPU__
86
    CheckCapableDevices();
87
#else
88
    throw std::invalid_argument(
×
UNCOV
89
      GetName() + ": GPU support was requested, but OpenSn was built without CUDA enabled.");
×
90
#endif // __OPENSN_WITH_GPU__
91
  }
92

93
  // Initialize options
94
  if (params.IsParameterValid("options"))
370✔
95
  {
96
    auto options_params = LBSProblem::GetOptionsBlock();
202✔
97
    options_params.AssignParameters(params.GetParam("options"));
204✔
98
    SetOptions(options_params);
200✔
99
  }
202✔
100

101
  // Set geometry type
102
  geometry_type_ = grid_->GetGeometryType();
368✔
103
  if (geometry_type_ == GeometryType::INVALID)
368✔
UNCOV
104
    throw std::runtime_error(GetName() + ": Invalid geometry type.");
×
105

106
  InitializeGroupsets(params);
368✔
107
  InitializeSources(params);
368✔
108
  InitializeXSmapAndDensities(params);
368✔
109
  InitializeMaterials();
368✔
110
}
388✔
111

112
LBSOptions&
113
LBSProblem::GetOptions()
20,707✔
114
{
115
  return options_;
20,707✔
116
}
117

118
const LBSOptions&
119
LBSProblem::GetOptions() const
598,349,346✔
120
{
121
  return options_;
598,349,346✔
122
}
123

124
double
125
LBSProblem::GetTime() const
118,480✔
126
{
127
  return time_;
118,480✔
128
}
129

130
void
131
LBSProblem::SetTime(double time)
1,660✔
132
{
133
  time_ = time;
1,660✔
134
}
1,660✔
135

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

144
double
145
LBSProblem::GetTimeStep() const
2,065,145,364✔
146
{
147
  return dt_;
2,065,145,364✔
148
}
149

150
bool
151
LBSProblem::IsTimeDependent() const
41,479✔
152
{
153
  return time_dependent_;
41,479✔
154
}
155

156
void
157
LBSProblem::SetTheta(double theta)
56✔
158
{
159
  if (theta < 0.0 or theta > 1.0)
56✔
UNCOV
160
    throw std::runtime_error(GetName() + " theta must be between 0.0 and 1.0.");
×
161
  theta_ = theta;
56✔
162
}
56✔
163

164
double
165
LBSProblem::GetTheta() const
2,065,145,340✔
166
{
167
  return theta_;
2,065,145,340✔
168
}
169

170
GeometryType
171
LBSProblem::GetGeometryType() const
4✔
172
{
173
  return geometry_type_;
4✔
174
}
175

176
size_t
177
LBSProblem::GetNumMoments() const
198,977✔
178
{
179
  return num_moments_;
198,977✔
180
}
181

182
unsigned int
183
LBSProblem::GetMaxCellDOFCount() const
427✔
184
{
185
  return max_cell_dof_count_;
427✔
186
}
187

188
unsigned int
189
LBSProblem::GetMinCellDOFCount() const
427✔
190
{
191
  return min_cell_dof_count_;
427✔
192
}
193

194
bool
195
LBSProblem::UseGPUs() const
382✔
196
{
197
  return use_gpus_;
382✔
198
}
199

200
size_t
201
LBSProblem::GetNumGroups() const
78,237✔
202
{
203
  return num_groups_;
78,237✔
204
}
205

206
unsigned int
207
LBSProblem::GetScatteringOrder() const
4✔
208
{
209
  return scattering_order_;
4✔
210
}
211

212
size_t
UNCOV
213
LBSProblem::GetNumPrecursors() const
×
214
{
UNCOV
215
  return num_precursors_;
×
216
}
217

218
size_t
219
LBSProblem::GetMaxPrecursorsPerMaterial() const
8✔
220
{
221
  return max_precursors_per_material_;
8✔
222
}
223

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

230
std::vector<LBSGroupset>&
231
LBSProblem::GetGroupsets()
7,312,263✔
232
{
233
  return groupsets_;
7,312,263✔
234
}
235

236
const std::vector<LBSGroupset>&
UNCOV
237
LBSProblem::GetGroupsets() const
×
238
{
UNCOV
239
  return groupsets_;
×
240
}
241

242
void
UNCOV
243
LBSProblem::AddPointSource(std::shared_ptr<PointSource> point_source)
×
244
{
UNCOV
245
  point_sources_.push_back(point_source);
×
UNCOV
246
  if (discretization_)
×
UNCOV
247
    point_sources_.back()->Initialize(*this);
×
UNCOV
248
}
×
249

250
void
UNCOV
251
LBSProblem::ClearPointSources()
×
252
{
UNCOV
253
  point_sources_.clear();
×
254
}
×
255

256
const std::vector<std::shared_ptr<PointSource>>&
257
LBSProblem::GetPointSources() const
8,652✔
258
{
259
  return point_sources_;
8,652✔
260
}
261

262
void
263
LBSProblem::AddVolumetricSource(std::shared_ptr<VolumetricSource> volumetric_source)
16✔
264
{
265
  volumetric_sources_.push_back(volumetric_source);
16✔
266
  if (discretization_)
16✔
267
    volumetric_sources_.back()->Initialize(*this);
16✔
268
}
16✔
269

270
void
271
LBSProblem::ClearVolumetricSources()
4✔
272
{
273
  volumetric_sources_.clear();
4✔
274
}
4✔
275

276
const std::vector<std::shared_ptr<VolumetricSource>>&
277
LBSProblem::GetVolumetricSources() const
8,652✔
278
{
279
  return volumetric_sources_;
8,652✔
280
}
281

282
const BlockID2XSMap&
283
LBSProblem::GetBlockID2XSMap() const
7,048✔
284
{
285
  return block_id_to_xs_map_;
7,048✔
286
}
287

288
void
289
LBSProblem::SetBlockID2XSMap(const BlockID2XSMap& xs_map)
8✔
290
{
291
  block_id_to_xs_map_ = xs_map;
8✔
292
  InitializeMaterials();
8✔
293
  ResetGPUCarriers();
8✔
294
  InitializeGPUExtras();
8✔
295
}
8✔
296

297
std::shared_ptr<MeshContinuum>
298
LBSProblem::GetGrid() const
270,095✔
299
{
300
  return grid_;
270,095✔
301
}
302

303
const SpatialDiscretization&
304
LBSProblem::GetSpatialDiscretization() const
86,804✔
305
{
306
  return *discretization_;
86,804✔
307
}
308

309
const std::vector<UnitCellMatrices>&
310
LBSProblem::GetUnitCellMatrices() const
7,794✔
311
{
312
  return unit_cell_matrices_;
7,794✔
313
}
314

315
const std::map<uint64_t, UnitCellMatrices>&
316
LBSProblem::GetUnitGhostCellMatrices() const
17✔
317
{
318
  return unit_ghost_cell_matrices_;
17✔
319
}
320

321
std::vector<CellLBSView>&
322
LBSProblem::GetCellTransportViews()
124,194✔
323
{
324
  return cell_transport_views_;
124,194✔
325
}
326

327
const std::vector<CellLBSView>&
328
LBSProblem::GetCellTransportViews() const
177,720✔
329
{
330
  return cell_transport_views_;
177,720✔
331
}
332

333
const UnknownManager&
334
LBSProblem::GetUnknownManager() const
26,927✔
335
{
336
  return flux_moments_uk_man_;
26,927✔
337
}
338

339
size_t
340
LBSProblem::GetLocalNodeCount() const
3,096✔
341
{
342
  return local_node_count_;
3,096✔
343
}
344

345
size_t
346
LBSProblem::GetGlobalNodeCount() const
2,424✔
347
{
348
  return global_node_count_;
2,424✔
349
}
350

351
std::vector<double>&
352
LBSProblem::GetQMomentsLocal()
63,233✔
353
{
354
  return q_moments_local_;
63,233✔
355
}
356

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

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

369
const std::vector<double>&
370
LBSProblem::GetExtSrcMomentsLocal() const
59,240✔
371
{
372
  return ext_src_moments_local_;
59,240✔
373
}
374

375
std::vector<double>&
376
LBSProblem::GetPhiOldLocal()
101,815✔
377
{
378
  return phi_old_local_;
101,815✔
379
}
380

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

387
std::vector<double>&
388
LBSProblem::GetPhiNewLocal()
83,970✔
389
{
390
  return phi_new_local_;
83,970✔
391
}
392

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

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

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

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

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

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

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

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

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

451
std::pair<size_t, size_t>
UNCOV
452
LBSProblem::GetNumPhiIterativeUnknowns()
×
453
{
UNCOV
454
  const auto& sdm = *discretization_;
×
UNCOV
455
  const size_t num_local_phi_dofs = sdm.GetNumLocalDOFs(flux_moments_uk_man_);
×
UNCOV
456
  const size_t num_global_phi_dofs = sdm.GetNumGlobalDOFs(flux_moments_uk_man_);
×
457

UNCOV
458
  return {num_local_phi_dofs, num_global_phi_dofs};
×
459
}
460

461
size_t
462
LBSProblem::MapPhiFieldFunction(size_t g, size_t m) const
27,648✔
463
{
464
  OpenSnLogicalErrorIf(phi_field_functions_local_map_.count({g, m}) == 0,
27,648✔
465
                       std::string("Failure to map phi field function g") + std::to_string(g) +
466
                         " m" + std::to_string(m));
467

468
  return phi_field_functions_local_map_.at({g, m});
27,648✔
469
}
470

471
std::shared_ptr<FieldFunctionGridBased>
472
LBSProblem::GetPowerFieldFunction() const
×
473
{
474
  OpenSnLogicalErrorIf(not options_.power_field_function_on,
×
475
                       "Called when options_.power_field_function_on == false");
476

UNCOV
477
  return field_functions_[power_gen_fieldfunc_local_handle_];
×
478
}
479

480
InputParameters
481
LBSProblem::GetOptionsBlock()
426✔
482
{
483
  InputParameters params;
426✔
484

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

558
  return params;
426✔
UNCOV
559
}
×
560

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

571
void
572
LBSProblem::SetOptions(const InputParameters& input)
212✔
573
{
574
  auto params = LBSProblem::GetOptionsBlock();
212✔
575
  params.AssignParameters(input);
212✔
576

577
  // Handle order insensitive options
578
  for (size_t p = 0; p < params.GetNumParameters(); ++p)
4,664✔
579
  {
580
    const auto& spec = params.GetParam(p);
4,452✔
581

582
    if (spec.GetName() == "max_mpi_message_size")
4,452✔
583
      options_.max_mpi_message_size = spec.GetValue<int>();
212✔
584

585
    else if (spec.GetName() == "restart_writes_enabled")
4,240✔
586
      options_.restart_writes_enabled = spec.GetValue<bool>();
212✔
587

588
    else if (spec.GetName() == "write_delayed_psi_to_restart")
4,028✔
589
      options_.write_delayed_psi_to_restart = spec.GetValue<bool>();
212✔
590

591
    else if (spec.GetName() == "read_restart_path")
3,816✔
592
    {
593
      options_.read_restart_path = spec.GetValue<std::string>();
212✔
594
      if (not options_.read_restart_path.empty())
212✔
595
        options_.read_restart_path += std::to_string(opensn::mpi_comm.rank()) + ".restart.h5";
36✔
596
    }
597

598
    else if (spec.GetName() == "write_restart_path")
3,604✔
599
    {
600
      options_.write_restart_path = spec.GetValue<std::string>();
212✔
601
      if (not options_.write_restart_path.empty())
212✔
UNCOV
602
        options_.write_restart_path += std::to_string(opensn::mpi_comm.rank()) + ".restart.h5";
×
603
    }
604

605
    else if (spec.GetName() == "write_restart_time_interval")
3,392✔
606
      options_.write_restart_time_interval = std::chrono::seconds(spec.GetValue<int>());
212✔
607

608
    else if (spec.GetName() == "use_precursors")
3,180✔
609
      options_.use_precursors = spec.GetValue<bool>();
212✔
610

611
    else if (spec.GetName() == "use_source_moments")
2,968✔
612
      options_.use_src_moments = spec.GetValue<bool>();
212✔
613

614
    else if (spec.GetName() == "save_angular_flux")
2,756✔
615
      options_.save_angular_flux = spec.GetValue<bool>();
212✔
616

617
    else if (spec.GetName() == "verbose_inner_iterations")
2,544✔
618
      options_.verbose_inner_iterations = spec.GetValue<bool>();
212✔
619

620
    else if (spec.GetName() == "max_ags_iterations")
2,332✔
621
      options_.max_ags_iterations = spec.GetValue<int>();
212✔
622

623
    else if (spec.GetName() == "ags_tolerance")
2,120✔
624
      options_.ags_tolerance = spec.GetValue<double>();
212✔
625

626
    else if (spec.GetName() == "ags_convergence_check")
1,908✔
627
    {
628
      auto check = spec.GetValue<std::string>();
212✔
629
      if (check == "pointwise")
212✔
UNCOV
630
        options_.ags_pointwise_convergence = true;
×
631
    }
212✔
632

633
    else if (spec.GetName() == "verbose_ags_iterations")
1,696✔
634
      options_.verbose_ags_iterations = spec.GetValue<bool>();
212✔
635

636
    else if (spec.GetName() == "verbose_outer_iterations")
1,484✔
637
      options_.verbose_outer_iterations = spec.GetValue<bool>();
212✔
638

639
    else if (spec.GetName() == "power_field_function_on")
1,272✔
640
      options_.power_field_function_on = spec.GetValue<bool>();
212✔
641

642
    else if (spec.GetName() == "power_default_kappa")
1,060✔
643
      options_.power_default_kappa = spec.GetValue<double>();
212✔
644

645
    else if (spec.GetName() == "power_normalization")
848✔
646
      options_.power_normalization = spec.GetValue<double>();
212✔
647

648
    else if (spec.GetName() == "field_function_prefix_option")
636✔
649
    {
650
      options_.field_function_prefix_option = spec.GetValue<std::string>();
212✔
651
    }
652

653
    else if (spec.GetName() == "field_function_prefix")
424✔
654
      options_.field_function_prefix = spec.GetValue<std::string>();
212✔
655

656
    else if (spec.GetName() == "adjoint")
212✔
657
      options_.adjoint = spec.GetValue<bool>();
212✔
658

659
  } // for p
660

661
  if (options_.restart_writes_enabled)
212✔
662
  {
663
    // Create restart directory if necessary
UNCOV
664
    auto dir = options_.write_restart_path.parent_path();
×
UNCOV
665
    if (opensn::mpi_comm.rank() == 0)
×
666
    {
UNCOV
667
      if (not std::filesystem::exists(dir))
×
668
      {
UNCOV
669
        if (not std::filesystem::create_directories(dir))
×
UNCOV
670
          throw std::runtime_error(GetName() + ": Failed to create restart directory " +
×
UNCOV
671
                                   dir.string());
×
672
      }
UNCOV
673
      else if (not std::filesystem::is_directory(dir))
×
UNCOV
674
        throw std::runtime_error(GetName() + ": Restart path exists but is not a directory " +
×
UNCOV
675
                                 dir.string());
×
676
    }
UNCOV
677
    opensn::mpi_comm.barrier();
×
UNCOV
678
    UpdateRestartWriteTime();
×
UNCOV
679
  }
×
680
}
212✔
681

682
void
683
LBSProblem::Initialize()
368✔
684
{
685
  CALI_CXX_MARK_SCOPE("LBSProblem::Initialize");
368✔
686

687
  PrintSimHeader();
368✔
688
  mpi_comm.barrier();
368✔
689

690
  InitializeSpatialDiscretization();
368✔
691
  InitializeParrays();
368✔
692
  InitializeBoundaries();
368✔
693
  InitializeGPUExtras();
368✔
694
  SetAdjoint(options_.adjoint);
368✔
695

696
  // Initialize point sources
697
  for (auto& point_source : point_sources_)
377✔
698
    point_source->Initialize(*this);
9✔
699

700
  // Initialize volumetric sources
701
  for (auto& volumetric_source : volumetric_sources_)
759✔
702
    volumetric_source->Initialize(*this);
391✔
703
}
368✔
704

705
void
UNCOV
706
LBSProblem::PrintSimHeader()
×
707
{
UNCOV
708
  if (opensn::mpi_comm.rank() == 0)
×
709
  {
710
    std::stringstream outstr;
×
711
    outstr << "\n"
×
UNCOV
712
           << "Initializing " << GetName() << "\n\n"
×
713
           << "Scattering order    : " << scattering_order_ << "\n"
×
UNCOV
714
           << "Number of moments   : " << num_moments_ << "\n"
×
715
           << "Number of groups    : " << groups_.size() << "\n"
×
716
           << "Number of groupsets : " << groupsets_.size() << "\n\n";
×
717

UNCOV
718
    for (const auto& groupset : groupsets_)
×
719
    {
720
      outstr << "***** Groupset " << groupset.id << " *****\n"
×
721
             << "Groups:\n";
×
722
      const auto& groups = groupset.groups;
723
      constexpr int groups_per_line = 12;
724
      for (size_t i = 0; i < groups.size(); ++i)
×
725
      {
UNCOV
726
        outstr << std::setw(5) << groups[i].id << ' ';
×
UNCOV
727
        if ((i + 1) % groups_per_line == 0)
×
UNCOV
728
          outstr << '\n';
×
729
      }
UNCOV
730
      if (!groups.empty() && groups.size() % groups_per_line != 0)
×
UNCOV
731
        outstr << '\n';
×
732
    }
733

UNCOV
734
    log.Log() << outstr.str() << '\n';
×
UNCOV
735
  }
×
UNCOV
736
}
×
737

738
void
739
LBSProblem::InitializeSources(const InputParameters& params)
368✔
740
{
741
  if (params.Has("volumetric_sources"))
368✔
742
  {
743
    const auto& vol_srcs = params.GetParam("volumetric_sources");
368✔
744
    vol_srcs.RequireBlockTypeIs(ParameterBlockType::ARRAY);
368✔
745
    for (const auto& src : vol_srcs)
759✔
746
      volumetric_sources_.push_back(src.GetValue<std::shared_ptr<VolumetricSource>>());
782✔
747
  }
748

749
  if (params.Has("point_sources"))
368✔
750
  {
751
    const auto& pt_srcs = params.GetParam("point_sources");
368✔
752
    pt_srcs.RequireBlockTypeIs(ParameterBlockType::ARRAY);
368✔
753
    for (const auto& src : pt_srcs)
377✔
754
      point_sources_.push_back(src.GetValue<std::shared_ptr<PointSource>>());
18✔
755
  }
756
}
368✔
757

758
void
759
LBSProblem::InitializeGroupsets(const InputParameters& params)
368✔
760
{
761
  // Initialize groups
762
  if (num_groups_ == 0)
368✔
UNCOV
763
    throw std::invalid_argument(GetName() + ": Number of groups must be > 0");
×
764
  for (size_t g = 0; g < num_groups_; ++g)
21,573✔
765
    groups_.emplace_back(static_cast<int>(g));
21,205✔
766

767
  // Initialize groupsets
768
  const auto& groupsets_array = params.GetParam("groupsets");
368✔
769
  const size_t num_gs = groupsets_array.GetNumParameters();
368✔
770
  if (num_gs == 0)
368✔
771
    throw std::invalid_argument(GetName() + ": At least one groupset must be specified");
×
772
  for (size_t gs = 0; gs < num_gs; ++gs)
795✔
773
  {
774
    const auto& groupset_params = groupsets_array.GetParam(gs);
427✔
775
    InputParameters gs_input_params = LBSGroupset::GetInputParameters();
427✔
776
    gs_input_params.SetObjectType("LBSProblem:LBSGroupset");
427✔
777
    gs_input_params.AssignParameters(groupset_params);
427✔
778
    groupsets_.emplace_back(gs_input_params, gs, *this);
427✔
779
    if (groupsets_.back().groups.empty())
427✔
780
    {
UNCOV
781
      std::stringstream oss;
×
UNCOV
782
      oss << GetName() << ": No groups added to groupset " << groupsets_.back().id;
×
UNCOV
783
      throw std::runtime_error(oss.str());
×
UNCOV
784
    }
×
785
  }
427✔
786
}
368✔
787

788
void
789
LBSProblem::InitializeXSmapAndDensities(const InputParameters& params)
368✔
790
{
791
  // Build XS map
792
  const auto& xs_array = params.GetParam("xs_map");
368✔
793
  const size_t num_xs = xs_array.GetNumParameters();
368✔
794
  for (size_t i = 0; i < num_xs; ++i)
993✔
795
  {
796
    const auto& item_params = xs_array.GetParam(i);
625✔
797
    InputParameters xs_entry_pars = GetXSMapEntryBlock();
625✔
798
    xs_entry_pars.AssignParameters(item_params);
625✔
799

800
    const auto& block_ids_param = xs_entry_pars.GetParam("block_ids");
625✔
801
    block_ids_param.RequireBlockTypeIs(ParameterBlockType::ARRAY);
625✔
802
    const auto& block_ids = block_ids_param.GetVectorValue<unsigned int>();
625✔
803
    auto xs = xs_entry_pars.GetSharedPtrParam<MultiGroupXS>("xs");
625✔
804
    for (const auto& block_id : block_ids)
1,346✔
805
      block_id_to_xs_map_[block_id] = xs;
721✔
806
  }
625✔
807

808
  // Assign placeholder unit densities
809
  densities_local_.assign(grid_->local_cells.size(), 1.0);
368✔
810
}
368✔
811

812
void
813
LBSProblem::InitializeMaterials()
388✔
814
{
815
  CALI_CXX_MARK_SCOPE("LBSProblem::InitializeMaterials");
388✔
816

817
  log.Log0Verbose1() << "Initializing Materials";
776✔
818

819
  // Create set of material ids locally relevant
820
  int invalid_mat_cell_count = 0;
388✔
821
  std::set<unsigned int> unique_block_ids;
388✔
822
  for (auto& cell : grid_->local_cells)
418,488✔
823
  {
824
    unique_block_ids.insert(cell.block_id);
418,100✔
825
    if (cell.block_id == std::numeric_limits<unsigned int>::max() or
418,100✔
826
        (block_id_to_xs_map_.find(cell.block_id) == block_id_to_xs_map_.end()))
418,100✔
UNCOV
827
      ++invalid_mat_cell_count;
×
828
  }
829
  const auto& ghost_cell_ids = grid_->cells.GetGhostGlobalIDs();
388✔
830
  for (uint64_t cell_id : ghost_cell_ids)
81,778✔
831
  {
832
    const auto& cell = grid_->cells[cell_id];
81,390✔
833
    unique_block_ids.insert(cell.block_id);
81,390✔
834
    if (cell.block_id == std::numeric_limits<unsigned int>::max() or
81,390✔
835
        (block_id_to_xs_map_.find(cell.block_id) == block_id_to_xs_map_.end()))
81,390✔
836
      ++invalid_mat_cell_count;
×
837
  }
838
  OpenSnLogicalErrorIf(invalid_mat_cell_count > 0,
388✔
839
                       std::to_string(invalid_mat_cell_count) +
840
                         " cells encountered with an invalid material id.");
841

842
  // Get ready for processing
843
  for (const auto& [blk_id, mat] : block_id_to_xs_map_)
1,145✔
844
  {
845
    mat->SetAdjointMode(options_.adjoint);
757✔
846

847
    OpenSnLogicalErrorIf(mat->GetNumGroups() < groups_.size(),
757✔
848
                         "Cross-sections for block \"" + std::to_string(blk_id) +
849
                           "\" have fewer groups (" + std::to_string(mat->GetNumGroups()) +
850
                           ") than the simulation (" + std::to_string(groups_.size()) + "). " +
851
                           "Cross-sections must have at least as many groups as the simulation.");
852
  }
853

854
  // Initialize precursor properties
855
  num_precursors_ = 0;
388✔
856
  max_precursors_per_material_ = 0;
388✔
857
  for (const auto& mat_id_xs : block_id_to_xs_map_)
1,145✔
858
  {
859
    const auto& xs = mat_id_xs.second;
757✔
860
    num_precursors_ += xs->GetNumPrecursors();
757✔
861
    max_precursors_per_material_ = std::max(xs->GetNumPrecursors(), max_precursors_per_material_);
757✔
862
  }
863

864
  // if no precursors, turn off precursors
865
  if (num_precursors_ == 0)
388✔
866
    options_.use_precursors = false;
380✔
867

868
  // check compatibility when precursors are on
869
  if (options_.use_precursors)
388✔
870
  {
871
    for (const auto& [mat_id, xs] : block_id_to_xs_map_)
16✔
872
    {
873
      OpenSnLogicalErrorIf(xs->IsFissionable() and num_precursors_ == 0,
8✔
874
                           "Incompatible cross-section data encountered for material id " +
875
                             std::to_string(mat_id) + ". When delayed neutron data is present " +
876
                             "for one fissionable matrial, it must be present for all fissionable "
877
                             "materials.");
878
    }
879
  }
880

881
  // Update transport views if available
882
  if (grid_->local_cells.size() == cell_transport_views_.size())
388✔
883
    for (const auto& cell : grid_->local_cells)
22,580✔
884
    {
885
      const auto& xs_ptr = block_id_to_xs_map_[cell.block_id];
22,560✔
886
      auto& transport_view = cell_transport_views_[cell.local_id];
22,560✔
887
      transport_view.ReassignXS(*xs_ptr);
22,560✔
888
    }
889

890
  mpi_comm.barrier();
388✔
891
}
388✔
892

893
void
894
LBSProblem::InitializeSpatialDiscretization()
360✔
895
{
896
  CALI_CXX_MARK_SCOPE("LBSProblem::InitializeSpatialDiscretization");
360✔
897

898
  log.Log() << "Initializing spatial discretization.\n";
720✔
899
  discretization_ = PieceWiseLinearDiscontinuous::New(grid_);
360✔
900

901
  ComputeUnitIntegrals();
360✔
902
}
360✔
903

904
void
905
LBSProblem::ComputeUnitIntegrals()
368✔
906
{
907
  CALI_CXX_MARK_SCOPE("LBSProblem::ComputeUnitIntegrals");
368✔
908

909
  log.Log() << "Computing unit integrals.\n";
736✔
910
  const auto& sdm = *discretization_;
368✔
911

912
  const size_t num_local_cells = grid_->local_cells.size();
368✔
913
  unit_cell_matrices_.resize(num_local_cells);
368✔
914

915
  for (const auto& cell : grid_->local_cells)
395,908✔
916
    unit_cell_matrices_[cell.local_id] =
395,540✔
917
      ComputeUnitCellIntegrals(sdm, cell, grid_->GetCoordinateSystem());
395,540✔
918

919
  const auto ghost_ids = grid_->cells.GetGhostGlobalIDs();
368✔
920
  for (auto ghost_id : ghost_ids)
75,994✔
921
    unit_ghost_cell_matrices_[ghost_id] =
75,626✔
922
      ComputeUnitCellIntegrals(sdm, grid_->cells[ghost_id], grid_->GetCoordinateSystem());
151,252✔
923

924
  // Assessing global unit cell matrix storage
925
  std::array<size_t, 2> num_local_ucms = {unit_cell_matrices_.size(),
368✔
926
                                          unit_ghost_cell_matrices_.size()};
368✔
927
  std::array<size_t, 2> num_global_ucms = {0, 0};
368✔
928

929
  mpi_comm.all_reduce(num_local_ucms.data(), 2, num_global_ucms.data(), mpi::op::sum<size_t>());
368✔
930

931
  opensn::mpi_comm.barrier();
368✔
932
  log.Log() << "Ghost cell unit cell-matrix ratio: "
368✔
933
            << (double)num_global_ucms[1] * 100 / (double)num_global_ucms[0] << "%";
736✔
934
  log.Log() << "Cell matrices computed.";
736✔
935
}
368✔
936

937
void
938
LBSProblem::InitializeParrays()
368✔
939
{
940
  CALI_CXX_MARK_SCOPE("LBSProblem::InitializeParrays");
368✔
941

942
  log.Log() << "Initializing parallel arrays."
736✔
943
            << " G=" << num_groups_ << " M=" << num_moments_ << std::endl;
368✔
944

945
  // Initialize unknown
946
  // structure
947
  flux_moments_uk_man_.unknowns.clear();
368✔
948
  for (size_t m = 0; m < num_moments_; ++m)
1,310✔
949
  {
950
    flux_moments_uk_man_.AddUnknown(UnknownType::VECTOR_N, groups_.size());
942✔
951
    flux_moments_uk_man_.unknowns.back().name = "m" + std::to_string(m);
942✔
952
  }
953

954
  // Compute local # of dof
955
  local_node_count_ = discretization_->GetNumLocalNodes();
368✔
956
  global_node_count_ = discretization_->GetNumGlobalNodes();
368✔
957

958
  // Compute num of unknowns
959
  size_t num_grps = groups_.size();
368✔
960
  size_t local_unknown_count = local_node_count_ * num_grps * num_moments_;
368✔
961

962
  log.LogAllVerbose1() << "LBS Number of phi unknowns: " << local_unknown_count;
736✔
963

964
  // Size local vectors
965
  q_moments_local_.assign(local_unknown_count, 0.0);
368✔
966
  phi_old_local_.assign(local_unknown_count, 0.0);
368✔
967
  phi_new_local_.assign(local_unknown_count, 0.0);
368✔
968

969
  // Setup precursor vector
970
  if (options_.use_precursors)
368✔
971
  {
972
    size_t num_precursor_dofs = grid_->local_cells.size() * max_precursors_per_material_;
8✔
973
    precursor_new_local_.assign(num_precursor_dofs, 0.0);
8✔
974
  }
975

976
  // Initialize transport views
977
  // Transport views act as a data structure to store information
978
  // related to the transport simulation. The most prominent function
979
  // here is that it holds the means to know where a given cell's
980
  // transport quantities are located in the unknown vectors (i.e. phi)
981
  //
982
  // Also, for a given cell, within a given sweep chunk,
983
  // we need to solve a matrix which square size is the
984
  // amount of nodes on the cell. max_cell_dof_count is
985
  // initialized here.
986
  //
987
  size_t block_MG_counter = 0; // Counts the strides of moment and group
368✔
988

989
  const Vector3 ihat(1.0, 0.0, 0.0);
368✔
990
  const Vector3 jhat(0.0, 1.0, 0.0);
368✔
991
  const Vector3 khat(0.0, 0.0, 1.0);
368✔
992

993
  min_cell_dof_count_ = std::numeric_limits<unsigned int>::max();
368✔
994
  max_cell_dof_count_ = 0;
368✔
995
  cell_transport_views_.clear();
368✔
996
  cell_transport_views_.reserve(grid_->local_cells.size());
368✔
997
  for (auto& cell : grid_->local_cells)
395,908✔
998
  {
999
    size_t num_nodes = discretization_->GetCellNumNodes(cell);
395,540✔
1000

1001
    // compute cell volumes
1002
    double cell_volume = 0.0;
395,540✔
1003
    const auto& IntV_shapeI = unit_cell_matrices_[cell.local_id].intV_shapeI;
395,540✔
1004
    for (size_t i = 0; i < num_nodes; ++i)
3,091,668✔
1005
      cell_volume += IntV_shapeI(i);
2,696,128✔
1006

1007
    size_t cell_phi_address = block_MG_counter;
395,540✔
1008

1009
    const size_t num_faces = cell.faces.size();
395,540✔
1010
    std::vector<bool> face_local_flags(num_faces, true);
395,540✔
1011
    std::vector<int> face_locality(num_faces, opensn::mpi_comm.rank());
395,540✔
1012
    std::vector<const Cell*> neighbor_cell_ptrs(num_faces, nullptr);
395,540✔
1013
    bool cell_on_boundary = false;
395,540✔
1014
    int f = 0;
395,540✔
1015
    for (auto& face : cell.faces)
2,525,852✔
1016
    {
1017
      if (not face.has_neighbor)
2,130,312✔
1018
      {
1019
        cell_on_boundary = true;
80,898✔
1020
        face_local_flags[f] = false;
80,898✔
1021
        face_locality[f] = -1;
80,898✔
1022
      } // if bndry
1023
      else
1024
      {
1025
        const int neighbor_partition = face.GetNeighborPartitionID(grid_.get());
2,049,414✔
1026
        face_local_flags[f] = (neighbor_partition == opensn::mpi_comm.rank());
2,049,414✔
1027
        face_locality[f] = neighbor_partition;
2,049,414✔
1028
        neighbor_cell_ptrs[f] = &grid_->cells[face.neighbor_id];
2,049,414✔
1029
      }
1030

1031
      ++f;
2,130,312✔
1032
    } // for f
1033

1034
    max_cell_dof_count_ = std::max(max_cell_dof_count_, static_cast<unsigned int>(num_nodes));
395,540✔
1035
    min_cell_dof_count_ = std::min(min_cell_dof_count_, static_cast<unsigned int>(num_nodes));
395,540✔
1036
    cell_transport_views_.emplace_back(cell_phi_address,
791,080✔
1037
                                       num_nodes,
1038
                                       num_grps,
1039
                                       num_moments_,
395,540✔
1040
                                       num_faces,
1041
                                       *block_id_to_xs_map_[cell.block_id],
395,540✔
1042
                                       cell_volume,
1043
                                       face_local_flags,
1044
                                       face_locality,
1045
                                       neighbor_cell_ptrs,
1046
                                       cell_on_boundary);
1047
    block_MG_counter += num_nodes * num_grps * num_moments_;
395,540✔
1048
  } // for local cell
395,540✔
1049

1050
  // Populate grid nodal mappings
1051
  // This is used in the Flux Data Structures (FLUDS)
1052
  grid_nodal_mappings_.clear();
368✔
1053
  grid_nodal_mappings_.reserve(grid_->local_cells.size());
368✔
1054
  for (auto& cell : grid_->local_cells)
395,908✔
1055
  {
1056
    CellFaceNodalMapping cell_nodal_mapping;
395,540✔
1057
    cell_nodal_mapping.reserve(cell.faces.size());
395,540✔
1058

1059
    for (auto& face : cell.faces)
2,525,852✔
1060
    {
1061
      std::vector<short> face_node_mapping;
2,130,312✔
1062
      std::vector<short> cell_node_mapping;
2,130,312✔
1063
      int adj_face_idx = -1;
2,130,312✔
1064

1065
      if (face.has_neighbor)
2,130,312✔
1066
      {
1067
        grid_->FindAssociatedVertices(face, face_node_mapping);
2,049,414✔
1068
        grid_->FindAssociatedCellVertices(face, cell_node_mapping);
2,049,414✔
1069
        adj_face_idx = face.GetNeighborAdjacentFaceIndex(grid_.get());
2,049,414✔
1070
      }
1071

1072
      cell_nodal_mapping.emplace_back(adj_face_idx, face_node_mapping, cell_node_mapping);
2,130,312✔
1073
    } // for f
2,130,312✔
1074

1075
    grid_nodal_mappings_.push_back(cell_nodal_mapping);
395,540✔
1076
  } // for local cell
395,540✔
1077

1078
  // Get grid localized communicator set
1079
  grid_local_comm_set_ = grid_->MakeMPILocalCommunicatorSet();
368✔
1080

1081
  // Initialize Field Functions
1082
  InitializeFieldFunctions();
368✔
1083

1084
  opensn::mpi_comm.barrier();
368✔
1085
  log.Log() << "Done with parallel arrays." << std::endl;
736✔
1086
}
368✔
1087

1088
void
1089
LBSProblem::InitializeFieldFunctions()
368✔
1090
{
1091
  CALI_CXX_MARK_SCOPE("LBSProblem::InitializeFieldFunctions");
368✔
1092

1093
  if (not field_functions_.empty())
368✔
UNCOV
1094
    return;
×
1095

1096
  // Initialize Field Functions for flux moments
1097
  phi_field_functions_local_map_.clear();
368✔
1098

1099
  for (size_t g = 0; g < groups_.size(); ++g)
21,573✔
1100
  {
1101
    for (size_t m = 0; m < num_moments_; ++m)
97,728✔
1102
    {
1103
      std::string prefix;
76,523✔
1104
      if (options_.field_function_prefix_option == "prefix")
76,523✔
1105
      {
1106
        prefix = options_.field_function_prefix;
76,523✔
1107
        if (not prefix.empty())
76,523✔
1108
          prefix += "_";
1✔
1109
      }
1110
      if (options_.field_function_prefix_option == "solver_name")
76,523✔
UNCOV
1111
        prefix = GetName() + "_";
×
1112

1113
      std::ostringstream oss;
76,523✔
1114
      oss << prefix << "phi_g" << std::setw(3) << std::setfill('0') << static_cast<int>(g) << "_m"
76,523✔
1115
          << std::setw(2) << std::setfill('0') << static_cast<int>(m);
76,523✔
1116
      const std::string name = oss.str();
76,523✔
1117

1118
      auto group_ff = std::make_shared<FieldFunctionGridBased>(
76,523✔
1119
        name, discretization_, Unknown(UnknownType::SCALAR));
76,523✔
1120

1121
      field_function_stack.push_back(group_ff);
153,046✔
1122
      field_functions_.push_back(group_ff);
76,523✔
1123

1124
      phi_field_functions_local_map_[{g, m}] = field_functions_.size() - 1;
76,523✔
1125
    } // for m
76,523✔
1126
  } // for g
1127

1128
  // Initialize power generation field function
1129
  if (options_.power_field_function_on)
368✔
1130
  {
1131
    std::string prefix;
4✔
1132
    if (options_.field_function_prefix_option == "prefix")
4✔
1133
    {
1134
      prefix = options_.field_function_prefix;
4✔
1135
      if (not prefix.empty())
4✔
UNCOV
1136
        prefix += "_";
×
1137
    }
1138
    if (options_.field_function_prefix_option == "solver_name")
4✔
UNCOV
1139
      prefix = GetName() + "_";
×
1140

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

1144
    field_function_stack.push_back(power_ff);
8✔
1145
    field_functions_.push_back(power_ff);
4✔
1146

1147
    power_gen_fieldfunc_local_handle_ = field_functions_.size() - 1;
4✔
1148
  }
4✔
1149
}
368✔
1150

1151
void
1152
LBSProblem::InitializeSolverSchemes()
368✔
1153
{
1154
  CALI_CXX_MARK_SCOPE("LBSProblem::InitializeSolverSchemes");
368✔
1155

1156
  log.Log() << "Initializing WGS and AGS solvers";
736✔
1157

1158
  InitializeWGSSolvers();
368✔
1159

1160
  ags_solver_ = std::make_shared<AGSLinearSolver>(*this, wgs_solvers_);
368✔
1161
  if (groupsets_.size() == 1)
368✔
1162
  {
1163
    ags_solver_->SetMaxIterations(1);
313✔
1164
    ags_solver_->SetVerbosity(false);
313✔
1165
  }
1166
  else
1167
  {
1168
    ags_solver_->SetMaxIterations(options_.max_ags_iterations);
55✔
1169
    ags_solver_->SetVerbosity(options_.verbose_ags_iterations);
55✔
1170
  }
1171
  ags_solver_->SetTolerance(options_.ags_tolerance);
368✔
1172
}
368✔
1173

1174
#ifndef __OPENSN_WITH_GPU__
1175
void
1176
LBSProblem::InitializeGPUExtras()
376✔
1177
{
1178
}
376✔
1179

1180
void
1181
LBSProblem::ResetGPUCarriers()
368✔
1182
{
1183
}
368✔
1184

1185
void
UNCOV
1186
LBSProblem::CheckCapableDevices()
×
1187
{
UNCOV
1188
}
×
1189
#endif // __OPENSN_WITH_GPU__
1190

1191
std::vector<double>
1192
LBSProblem::MakeSourceMomentsFromPhi()
4✔
1193
{
1194
  CALI_CXX_MARK_SCOPE("LBSProblem::MakeSourceMomentsFromPhi");
4✔
1195

1196
  size_t num_local_dofs = discretization_->GetNumLocalDOFs(flux_moments_uk_man_);
4✔
1197

1198
  std::vector<double> source_moments(num_local_dofs, 0.0);
4✔
1199
  for (auto& groupset : groupsets_)
8✔
1200
  {
1201
    active_set_source_function_(groupset,
4✔
1202
                                source_moments,
1203
                                phi_new_local_,
4✔
1204
                                APPLY_AGS_SCATTER_SOURCES | APPLY_WGS_SCATTER_SOURCES |
1205
                                  APPLY_AGS_FISSION_SOURCES | APPLY_WGS_FISSION_SOURCES);
4✔
1206
  }
1207

1208
  return source_moments;
4✔
1209
}
4✔
1210

1211
void
1212
LBSProblem::UpdateFieldFunctions()
1,360✔
1213
{
1214
  CALI_CXX_MARK_SCOPE("LBSProblem::UpdateFieldFunctions");
1,360✔
1215

1216
  const auto& sdm = *discretization_;
1,360✔
1217
  const auto& phi_uk_man = flux_moments_uk_man_;
1,360✔
1218

1219
  // Update flux moments
1220
  for (const auto& [g_and_m, ff_index] : phi_field_functions_local_map_)
83,639✔
1221
  {
1222
    const size_t g = g_and_m.first;
82,279✔
1223
    const size_t m = g_and_m.second;
82,279✔
1224

1225
    std::vector<double> data_vector_local(local_node_count_, 0.0);
82,279✔
1226

1227
    for (const auto& cell : grid_->local_cells)
21,968,650✔
1228
    {
1229
      const auto& cell_mapping = sdm.GetCellMapping(cell);
21,886,371✔
1230
      const size_t num_nodes = cell_mapping.GetNumNodes();
21,886,371✔
1231

1232
      for (size_t i = 0; i < num_nodes; ++i)
152,268,935✔
1233
      {
1234
        const auto imapA = sdm.MapDOFLocal(cell, i, phi_uk_man, m, g);
130,382,564✔
1235
        const auto imapB = sdm.MapDOFLocal(cell, i);
130,382,564✔
1236

1237
        data_vector_local[imapB] = phi_new_local_[imapA];
130,382,564✔
1238
      } // for node
1239
    } // for cell
1240

1241
    auto& ff_ptr = field_functions_.at(ff_index);
82,279✔
1242
    ff_ptr->UpdateFieldVector(data_vector_local);
82,279✔
1243
  }
82,279✔
1244

1245
  // Update power generation and scalar flux
1246
  if (options_.power_field_function_on)
1,360✔
1247
  {
1248
    std::vector<double> data_vector_power_local(local_node_count_, 0.0);
4✔
1249

1250
    double local_total_power = 0.0;
4✔
1251
    for (const auto& cell : grid_->local_cells)
83,268✔
1252
    {
1253
      const auto& cell_mapping = sdm.GetCellMapping(cell);
83,264✔
1254
      const size_t num_nodes = cell_mapping.GetNumNodes();
83,264✔
1255

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

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

1260
      if (not xs->IsFissionable())
83,264✔
1261
        continue;
56,360✔
1262

1263
      for (size_t i = 0; i < num_nodes; ++i)
134,520✔
1264
      {
1265
        const auto imapA = sdm.MapDOFLocal(cell, i);
107,616✔
1266
        const auto imapB = sdm.MapDOFLocal(cell, i, phi_uk_man, 0, 0);
107,616✔
1267

1268
        double nodal_power = 0.0;
1269
        for (size_t g = 0; g < groups_.size(); ++g)
860,928✔
1270
        {
1271
          const double sigma_fg = xs->GetSigmaFission()[g];
753,312✔
1272
          // const double kappa_g = xs->Kappa()[g];
1273
          const double kappa_g = options_.power_default_kappa;
753,312✔
1274

1275
          nodal_power += kappa_g * sigma_fg * phi_new_local_[imapB + g];
753,312✔
1276
        } // for g
1277

1278
        data_vector_power_local[imapA] = nodal_power;
107,616✔
1279
        local_total_power += nodal_power * Vi(i);
107,616✔
1280
      } // for node
1281
    } // for cell
1282

1283
    double scale_factor = 1.0;
4✔
1284
    if (options_.power_normalization > 0.0)
4✔
1285
    {
1286
      double global_total_power = 0.0;
4✔
1287
      mpi_comm.all_reduce(local_total_power, global_total_power, mpi::op::sum<double>());
4✔
1288
      scale_factor = options_.power_normalization / global_total_power;
4✔
1289
      Scale(data_vector_power_local, scale_factor);
4✔
1290
    }
1291

1292
    const size_t ff_index = power_gen_fieldfunc_local_handle_;
4✔
1293

1294
    auto& ff_ptr = field_functions_.at(ff_index);
4✔
1295
    ff_ptr->UpdateFieldVector(data_vector_power_local);
4✔
1296

1297
    // scale scalar flux if neccessary
1298
    if (scale_factor != 1.0)
4✔
1299
    {
1300
      for (size_t g = 0; g < groups_.size(); ++g)
32✔
1301
      {
1302
        const size_t phi_ff_index = phi_field_functions_local_map_.at({g, size_t{0}});
28✔
1303
        auto& phi_ff_ptr = field_functions_.at(phi_ff_index);
28✔
1304
        const auto& phi_vec = phi_ff_ptr->GetLocalFieldVector();
28✔
1305
        std::vector<double> phi_scaled(phi_vec.begin(), phi_vec.end());
28✔
1306
        Scale(phi_scaled, scale_factor);
28✔
1307
        phi_ff_ptr->UpdateFieldVector(phi_scaled);
28✔
1308
      }
28✔
1309
    }
1310
  } // if power enabled
4✔
1311
}
1,360✔
1312

1313
void
UNCOV
1314
LBSProblem::SetPhiFromFieldFunctions(PhiSTLOption which_phi,
×
1315
                                     const std::vector<size_t>& m_indices,
1316
                                     const std::vector<size_t>& g_indices)
1317
{
UNCOV
1318
  CALI_CXX_MARK_SCOPE("LBSProblem::SetPhiFromFieldFunctions");
×
1319

UNCOV
1320
  std::vector<size_t> m_ids_to_copy = m_indices;
×
UNCOV
1321
  std::vector<size_t> g_ids_to_copy = g_indices;
×
UNCOV
1322
  if (m_indices.empty())
×
UNCOV
1323
    for (size_t m = 0; m < num_moments_; ++m)
×
UNCOV
1324
      m_ids_to_copy.push_back(m);
×
UNCOV
1325
  if (g_ids_to_copy.empty())
×
UNCOV
1326
    for (size_t g = 0; g < num_groups_; ++g)
×
UNCOV
1327
      g_ids_to_copy.push_back(g);
×
1328

UNCOV
1329
  const auto& sdm = *discretization_;
×
UNCOV
1330
  const auto& phi_uk_man = flux_moments_uk_man_;
×
1331

UNCOV
1332
  for (const size_t m : m_ids_to_copy)
×
1333
  {
UNCOV
1334
    for (const size_t g : g_ids_to_copy)
×
1335
    {
UNCOV
1336
      const size_t ff_index = phi_field_functions_local_map_.at({g, m});
×
UNCOV
1337
      const auto& ff_ptr = field_functions_.at(ff_index);
×
UNCOV
1338
      const auto& ff_data = ff_ptr->GetLocalFieldVector();
×
1339

UNCOV
1340
      for (const auto& cell : grid_->local_cells)
×
1341
      {
UNCOV
1342
        const auto& cell_mapping = sdm.GetCellMapping(cell);
×
UNCOV
1343
        const size_t num_nodes = cell_mapping.GetNumNodes();
×
1344

UNCOV
1345
        for (size_t i = 0; i < num_nodes; ++i)
×
1346
        {
UNCOV
1347
          const auto imapA = sdm.MapDOFLocal(cell, i);
×
UNCOV
1348
          const auto imapB = sdm.MapDOFLocal(cell, i, phi_uk_man, m, g);
×
1349

UNCOV
1350
          if (which_phi == PhiSTLOption::PHI_OLD)
×
UNCOV
1351
            phi_old_local_[imapB] = ff_data[imapA];
×
UNCOV
1352
          else if (which_phi == PhiSTLOption::PHI_NEW)
×
UNCOV
1353
            phi_new_local_[imapB] = ff_data[imapA];
×
1354
        } // for node
1355
      } // for cell
1356
    } // for g
1357
  } // for m
UNCOV
1358
}
×
1359

1360
LBSProblem::~LBSProblem()
360✔
1361
{
1362
  ResetGPUCarriers();
1363
}
1,800✔
1364

360✔
1365
void
1366
LBSProblem::SetAdjoint(bool adjoint)
380✔
1367
{
1368
  if (adjoint and time_dependent_)
380✔
UNCOV
1369
    throw std::invalid_argument(GetName() + ": Time-dependent adjoint problems are not supported.");
×
1370

1371
  if (adjoint != options_.adjoint)
380✔
1372
  {
1373
    options_.adjoint = adjoint;
12✔
1374

1375
    // If a discretization exists, the solver has already been initialized.
1376
    // Reinitialize the materials to obtain the appropriate xs and clear the
1377
    // sources to prepare for defining the adjoint problem
1378
    if (discretization_)
12✔
1379
    {
1380
      // The materials are reinitialized here to ensure that the proper cross sections
1381
      // are available to the solver. Because an adjoint solve requires volumetric or
1382
      // point sources, the material-based sources are not set within the initialize routine.
1383
      InitializeMaterials();
12✔
1384

1385
      // Forward and adjoint sources are fundamentally different, so any existing sources
1386
      // should be cleared and reset through options upon changing modes.
1387
      point_sources_.clear();
12✔
1388
      volumetric_sources_.clear();
12✔
1389
      ClearBoundaries();
12✔
1390

1391
      // Set all solutions to zero.
1392
      phi_old_local_.assign(phi_old_local_.size(), 0.0);
12✔
1393
      phi_new_local_.assign(phi_new_local_.size(), 0.0);
12✔
1394
      ZeroSolutions();
12✔
1395
      precursor_new_local_.assign(precursor_new_local_.size(), 0.0);
12✔
1396
    }
1397
  }
1398
}
380✔
1399

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