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

Open-Sn / opensn / 22654533986

04 Mar 2026 12:48AM UTC coverage: 74.268% (+0.08%) from 74.192%
22654533986

push

github

web-flow
Merge pull request #960 from andrsd/fv-removal

Removing finite-volume spatial discretization

0 of 2 new or added lines in 1 file covered. (0.0%)

183 existing lines in 9 files now uncovered.

19993 of 26920 relevant lines covered (74.27%)

67230185.92 hits per line

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

81.36
/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/discrete_ordinates_problem/discrete_ordinates_problem.h"
6
#include "modules/linear_boltzmann_solvers/lbs_problem/iterative_methods/wgs_context.h"
7
#include "modules/linear_boltzmann_solvers/lbs_problem/iterative_methods/ags_linear_solver.h"
8
#include "modules/linear_boltzmann_solvers/lbs_problem/point_source/point_source.h"
9
#include "modules/linear_boltzmann_solvers/lbs_problem/groupset/lbs_groupset.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
InputParameters
32
LBSProblem::GetInputParameters()
579✔
33
{
34
  InputParameters params = Problem::GetInputParameters();
579✔
35

36
  params.ChangeExistingParamToOptional("name", "LBSProblem");
1,158✔
37

38
  params.AddRequiredParameter<std::shared_ptr<MeshContinuum>>("mesh", "Mesh");
1,158✔
39

40
  params.AddRequiredParameter<unsigned int>("num_groups",
1,158✔
41
                                            "The total number of groups within the solver");
42

43
  params.AddRequiredParameterArray("groupsets",
1,158✔
44
                                   "An array of blocks each specifying the input parameters for a "
45
                                   "<TT>LBSGroupset</TT>.");
46
  params.LinkParameterToBlock("groupsets", "LBSGroupset");
1,158✔
47

48
  params.AddRequiredParameterArray("xs_map",
1,158✔
49
                                   "Cross-section map from block IDs to cross-section objects.");
50

51
  params.AddOptionalParameterArray<std::shared_ptr<VolumetricSource>>(
1,158✔
52
    "volumetric_sources", {}, "An array of handles to volumetric sources.");
53

54
  params.AddOptionalParameterArray<std::shared_ptr<PointSource>>(
1,158✔
55
    "point_sources", {}, "An array of point sources.");
56

57
  params.AddOptionalParameterBlock(
1,158✔
58
    "options", ParameterBlock(), "Block of options. See <TT>OptionsBlock</TT>.");
1,158✔
59
  params.LinkParameterToBlock("options", "OptionsBlock");
1,158✔
60

61
  params.AddOptionalParameter("use_gpus", false, "Offload the sweep computation to GPUs.");
1,158✔
62

63
  return params;
579✔
64
}
×
65

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

83
  // Initialize options
84
  if (params.IsParameterValid("options"))
579✔
85
  {
86
    auto options_params = LBSProblem::GetOptionsBlock();
351✔
87
    options_params.AssignParameters(params.GetParam("options"));
353✔
88
    ParseOptions(options_params);
349✔
89
  }
351✔
90
  applied_adjoint_ = options_.adjoint;
577✔
91
  applied_save_angular_flux_ = options_.save_angular_flux;
577✔
92

93
  // Set geometry type
94
  geometry_type_ = grid_->GetGeometryType();
577✔
95
  if (geometry_type_ == GeometryType::INVALID)
577✔
96
    throw std::runtime_error(GetName() + ": Invalid geometry type.");
×
97

98
  InitializeGroupsets(params);
577✔
99
  InitializeSources(params);
577✔
100
  InitializeXSmapAndDensities(params);
577✔
101
  InitializeMaterials();
577✔
102
}
597✔
103

104
LBSOptions&
105
LBSProblem::GetOptions()
167,442✔
106
{
107
  return options_;
167,442✔
108
}
109

110
const LBSOptions&
111
LBSProblem::GetOptions() const
683,101,244✔
112
{
113
  return options_;
683,101,244✔
114
}
115

116
double
117
LBSProblem::GetTime() const
422,700✔
118
{
119
  return time_;
422,700✔
120
}
121

122
void
123
LBSProblem::SetTime(double time)
5,564✔
124
{
125
  time_ = time;
5,564✔
126
}
5,564✔
127

128
void
129
LBSProblem::SetTimeStep(double dt)
1,800✔
130
{
131
  if (dt <= 0.0)
1,800✔
132
    throw std::runtime_error(GetName() + " dt must be greater than zero.");
×
133
  dt_ = dt;
1,800✔
134
}
1,800✔
135

136
double
137
LBSProblem::GetTimeStep() const
2,147,483,647✔
138
{
139
  return dt_;
2,147,483,647✔
140
}
141

142
void
143
LBSProblem::SetTheta(double theta)
264✔
144
{
145
  if (theta <= 0.0 or theta > 1.0)
264✔
146
    throw std::runtime_error(GetName() + " theta must be in (0.0, 1.0].");
×
147
  theta_ = theta;
264✔
148
}
264✔
149

150
double
151
LBSProblem::GetTheta() const
2,147,483,647✔
152
{
153
  return theta_;
2,147,483,647✔
154
}
155

156
bool
157
LBSProblem::IsTimeDependent() const
×
158
{
159
  return false;
×
160
}
161

162
void
163
LBSProblem::SetTimeDependentMode()
×
164
{
165
  throw std::logic_error(GetName() +
×
166
                         ": Time-dependent mode is not supported for this problem type.");
×
167
}
168

169
void
170
LBSProblem::SetSteadyStateMode()
×
171
{
172
  // Steady-state is the default for problem types without time-dependent support.
173
}
×
174

175
GeometryType
176
LBSProblem::GetGeometryType() const
4✔
177
{
178
  return geometry_type_;
4✔
179
}
180

181
unsigned int
182
LBSProblem::GetNumMoments() const
527,054✔
183
{
184
  return num_moments_;
527,054✔
185
}
186

187
unsigned int
188
LBSProblem::GetMaxCellDOFCount() const
736✔
189
{
190
  return max_cell_dof_count_;
736✔
191
}
192

193
unsigned int
194
LBSProblem::GetMinCellDOFCount() const
736✔
195
{
196
  return min_cell_dof_count_;
736✔
197
}
198

199
bool
200
LBSProblem::UseGPUs() const
1,040✔
201
{
202
  return use_gpus_;
1,040✔
203
}
204

205
unsigned int
206
LBSProblem::GetNumGroups() const
820,164✔
207
{
208
  return num_groups_;
820,164✔
209
}
210

211
unsigned int
212
LBSProblem::GetScatteringOrder() const
4✔
213
{
214
  return scattering_order_;
4✔
215
}
216

217
unsigned int
218
LBSProblem::GetNumPrecursors() const
×
219
{
220
  return num_precursors_;
×
221
}
222

223
unsigned int
224
LBSProblem::GetMaxPrecursorsPerMaterial() const
9,550✔
225
{
226
  return max_precursors_per_material_;
9,550✔
227
}
228

229
std::vector<LBSGroupset>&
230
LBSProblem::GetGroupsets()
21,378,121✔
231
{
232
  return groupsets_;
21,378,121✔
233
}
234

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

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

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

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

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

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

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

281
const BlockID2XSMap&
282
LBSProblem::GetBlockID2XSMap() const
19,215✔
283
{
284
  return block_id_to_xs_map_;
19,215✔
285
}
286

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

296
std::shared_ptr<MeshContinuum>
297
LBSProblem::GetGrid() const
751,371✔
298
{
299
  return grid_;
751,371✔
300
}
301

302
const SpatialDiscretization&
303
LBSProblem::GetSpatialDiscretization() const
238,732✔
304
{
305
  return *discretization_;
238,732✔
306
}
307

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

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

320
std::vector<CellLBSView>&
321
LBSProblem::GetCellTransportViews()
302,245✔
322
{
323
  return cell_transport_views_;
302,245✔
324
}
325

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

332
const UnknownManager&
333
LBSProblem::GetUnknownManager() const
27,040✔
334
{
335
  return flux_moments_uk_man_;
27,040✔
336
}
337

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

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

350
std::vector<double>&
351
LBSProblem::GetQMomentsLocal()
357,849✔
352
{
353
  return q_moments_local_;
357,849✔
354
}
355

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

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

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

374
std::vector<double>&
375
LBSProblem::GetPhiOldLocal()
277,677✔
376
{
377
  return phi_old_local_;
277,677✔
378
}
379

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

386
std::vector<double>&
387
LBSProblem::GetPhiNewLocal()
249,360✔
388
{
389
  return phi_new_local_;
249,360✔
390
}
391

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

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

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

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

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

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

428
void
429
LBSProblem::SetActiveSetSourceFunction(SetSourceFunction source_function)
100✔
430
{
431
  active_set_source_function_ = std::move(source_function);
100✔
432
}
100✔
433

434
std::shared_ptr<AGSLinearSolver>
435
LBSProblem::GetAGSSolver()
2,884✔
436
{
437
  return ags_solver_;
2,884✔
438
}
439

440
std::vector<std::shared_ptr<LinearSolver>>&
441
LBSProblem::GetWGSSolvers()
2,761✔
442
{
443
  return wgs_solvers_;
2,761✔
444
}
445

446
WGSContext&
447
LBSProblem::GetWGSContext(int groupset_id)
11,901✔
448
{
449
  auto& wgs_solver = wgs_solvers_[groupset_id];
11,901✔
450
  auto raw_context = wgs_solver->GetContext();
11,901✔
451
  auto wgs_context_ptr = std::dynamic_pointer_cast<WGSContext>(raw_context);
11,901✔
452
  OpenSnLogicalErrorIf(not wgs_context_ptr, "Failed to cast WGSContext");
11,901✔
453
  return *wgs_context_ptr;
11,901✔
454
}
23,802✔
455

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

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

466
std::shared_ptr<FieldFunctionGridBased>
467
LBSProblem::GetScalarFluxFieldFunction(unsigned int g, unsigned int m) const
28,985✔
468
{
469
  OpenSnLogicalErrorIf(g >= num_groups_, "Group index out of range.");
28,985✔
470
  OpenSnLogicalErrorIf(m >= num_moments_, "Moment index out of range.");
28,985✔
471

472
  const auto map_it = phi_field_functions_local_map_.find({g, m});
28,985✔
473
  OpenSnLogicalErrorIf(map_it == phi_field_functions_local_map_.end(),
28,985✔
474
                       std::string("Failure to map phi field function g") + std::to_string(g) +
475
                         " m" + std::to_string(m));
476

477
  return field_functions_.at(map_it->second);
57,970✔
478
}
479

480
std::shared_ptr<FieldFunctionGridBased>
481
LBSProblem::GetPowerFieldFunction() const
1✔
482
{
483
  OpenSnLogicalErrorIf(not options_.power_field_function_on,
1✔
484
                       "Called when options_.power_field_function_on == false");
485

486
  return field_functions_[power_gen_fieldfunc_local_handle_];
2✔
487
}
488

489
InputParameters
490
LBSProblem::GetOptionsBlock()
724✔
491
{
492
  InputParameters params;
724✔
493

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

572
  return params;
724✔
UNCOV
573
}
×
574

575
InputParameters
576
LBSProblem::GetXSMapEntryBlock()
1,002✔
577
{
578
  InputParameters params;
1,002✔
579
  params.SetGeneralDescription("Set the cross-section map for the solver.");
2,004✔
580
  params.AddRequiredParameterArray("block_ids", "Mesh block IDs");
2,004✔
581
  params.AddRequiredParameter<std::shared_ptr<MultiGroupXS>>("xs", "Cross-section object");
2,004✔
582
  return params;
1,002✔
UNCOV
583
}
×
584

585
void
586
LBSProblem::SetOptions(const InputParameters& input)
12✔
587
{
588
  ParseOptions(input);
12✔
589
  ApplyOptions();
12✔
590
}
12✔
591

592
void
593
LBSProblem::ParseOptions(const InputParameters& input)
361✔
594
{
595
  auto params = LBSProblem::GetOptionsBlock();
361✔
596
  params.AssignParameters(input);
361✔
597
  const auto& params_at_assignment = input.GetParametersAtAssignment();
361✔
598
  const auto& specified_params = params_at_assignment.GetNumParameters() > 0
361✔
599
                                   ? params_at_assignment
361✔
600
                                   : static_cast<const ParameterBlock&>(input);
361✔
601

602
  // Apply only options explicitly specified by the caller.
603
  for (const auto& spec : specified_params.GetParameters())
1,333✔
604
  {
605
    if (spec.GetName() == "max_mpi_message_size")
972✔
UNCOV
606
      options_.max_mpi_message_size = spec.GetValue<int>();
×
607

608
    else if (spec.GetName() == "restart_writes_enabled")
972✔
UNCOV
609
      options_.restart_writes_enabled = spec.GetValue<bool>();
×
610

611
    else if (spec.GetName() == "write_delayed_psi_to_restart")
972✔
UNCOV
612
      options_.write_delayed_psi_to_restart = spec.GetValue<bool>();
×
613

614
    else if (spec.GetName() == "read_restart_path")
972✔
615
    {
616
      options_.read_restart_path = spec.GetValue<std::string>();
12✔
617
      if (not options_.read_restart_path.empty())
12✔
618
        options_.read_restart_path += std::to_string(opensn::mpi_comm.rank()) + ".restart.h5";
36✔
619
    }
620

621
    else if (spec.GetName() == "write_restart_path")
960✔
622
    {
UNCOV
623
      options_.write_restart_path = spec.GetValue<std::string>();
×
UNCOV
624
      if (not options_.write_restart_path.empty())
×
625
        options_.write_restart_path += std::to_string(opensn::mpi_comm.rank()) + ".restart.h5";
×
626
    }
627

628
    else if (spec.GetName() == "write_restart_time_interval")
960✔
UNCOV
629
      options_.write_restart_time_interval = std::chrono::seconds(spec.GetValue<int>());
×
630

631
    else if (spec.GetName() == "use_precursors")
960✔
632
      options_.use_precursors = spec.GetValue<bool>();
120✔
633

634
    else if (spec.GetName() == "use_source_moments")
840✔
635
      options_.use_src_moments = spec.GetValue<bool>();
4✔
636

637
    else if (spec.GetName() == "save_angular_flux")
836✔
638
      options_.save_angular_flux = spec.GetValue<bool>();
198✔
639

640
    else if (spec.GetName() == "verbose_inner_iterations")
638✔
641
      options_.verbose_inner_iterations = spec.GetValue<bool>();
212✔
642

643
    else if (spec.GetName() == "max_ags_iterations")
426✔
644
      options_.max_ags_iterations = spec.GetValue<int>();
50✔
645

646
    else if (spec.GetName() == "ags_tolerance")
376✔
647
      options_.ags_tolerance = spec.GetValue<double>();
8✔
648

649
    else if (spec.GetName() == "ags_convergence_check")
368✔
650
    {
UNCOV
651
      auto check = spec.GetValue<std::string>();
×
UNCOV
652
      options_.ags_pointwise_convergence = (check == "pointwise");
×
UNCOV
653
    }
×
654

655
    else if (spec.GetName() == "verbose_ags_iterations")
368✔
656
      options_.verbose_ags_iterations = spec.GetValue<bool>();
128✔
657

658
    else if (spec.GetName() == "verbose_outer_iterations")
240✔
659
      options_.verbose_outer_iterations = spec.GetValue<bool>();
188✔
660

661
    else if (spec.GetName() == "power_field_function_on")
52✔
662
      options_.power_field_function_on = spec.GetValue<bool>();
5✔
663

664
    else if (spec.GetName() == "power_default_kappa")
47✔
665
      options_.power_default_kappa = spec.GetValue<double>();
13✔
666

667
    else if (spec.GetName() == "power_normalization")
34✔
668
      options_.power_normalization = spec.GetValue<double>();
13✔
669

670
    else if (spec.GetName() == "field_function_prefix_option")
21✔
671
    {
UNCOV
672
      options_.field_function_prefix_option = spec.GetValue<std::string>();
×
673
    }
674

675
    else if (spec.GetName() == "field_function_prefix")
21✔
676
      options_.field_function_prefix = spec.GetValue<std::string>();
1✔
677

678
    else if (spec.GetName() == "adjoint")
20✔
679
      options_.adjoint = spec.GetValue<bool>();
20✔
680

681
  } // for specified options
682

683
  OpenSnInvalidArgumentIf(options_.write_restart_time_interval > std::chrono::seconds(0) and
361✔
684
                            not options_.restart_writes_enabled,
685
                          GetName() + ": `write_restart_time_interval>0` requires "
686
                                      "`restart_writes_enabled=true`.");
687

688
  OpenSnInvalidArgumentIf(options_.write_restart_time_interval > std::chrono::seconds(0) and
361✔
689
                            options_.write_restart_time_interval < std::chrono::seconds(30),
690
                          GetName() + ": `write_restart_time_interval` must be 0 (disabled) "
691
                                      "or at least 30 seconds.");
692

693
  OpenSnInvalidArgumentIf(options_.restart_writes_enabled and options_.write_restart_path.empty(),
361✔
694
                          GetName() + ": `restart_writes_enabled=true` requires a non-empty "
695
                                      "`write_restart_path`.");
696

697
  OpenSnInvalidArgumentIf(not options_.field_function_prefix.empty() and
361✔
698
                            options_.field_function_prefix_option != "prefix",
699
                          GetName() + ": non-empty `field_function_prefix` requires "
700
                                      "`field_function_prefix_option=\"prefix\"`.");
701

702
  if (options_.restart_writes_enabled)
361✔
703
  {
704
    const auto dir = options_.write_restart_path.parent_path();
×
705

706
    // Create restart directory if necessary.
707
    // If dir is empty, write path resolves relative to the working directory.
708
    if ((not dir.empty()) and opensn::mpi_comm.rank() == 0)
×
709
    {
710
      if (not std::filesystem::exists(dir))
×
711
      {
712
        if (not std::filesystem::create_directories(dir))
×
713
          throw std::runtime_error(GetName() + ": Failed to create restart directory " +
×
714
                                   dir.string());
×
715
      }
716
      else if (not std::filesystem::is_directory(dir))
×
717
        throw std::runtime_error(GetName() + ": Restart path exists but is not a directory " +
×
718
                                 dir.string());
×
719
    }
UNCOV
720
    opensn::mpi_comm.barrier();
×
UNCOV
721
    UpdateRestartWriteTime();
×
UNCOV
722
  }
×
723
}
361✔
724

725
void
726
LBSProblem::ApplyOptions()
12✔
727
{
728
  if (not initialized_)
12✔
729
    return;
730

731
  if (options_.adjoint != applied_adjoint_)
12✔
732
    SetAdjoint(options_.adjoint);
4✔
733

734
  if (options_.save_angular_flux != applied_save_angular_flux_)
12✔
UNCOV
735
    SetSaveAngularFlux(options_.save_angular_flux);
×
736
}
737

738
void
739
LBSProblem::BuildRuntime()
577✔
740
{
741
  CALI_CXX_MARK_SCOPE("LBSProblem::BuildRuntime");
577✔
742
  if (initialized_)
577✔
UNCOV
743
    return;
×
744

745
  PrintSimHeader();
577✔
746
  mpi_comm.barrier();
577✔
747

748
  InitializeSpatialDiscretization();
577✔
749
  InitializeParrays();
577✔
750
  InitializeBoundaries();
577✔
751
  InitializeGPUExtras();
577✔
752
  if (options_.adjoint)
577✔
753
    if (const auto* do_problem = dynamic_cast<const DiscreteOrdinatesProblem*>(this);
16✔
754
        do_problem and do_problem->IsTimeDependent())
16✔
UNCOV
755
      throw std::runtime_error(GetName() + ": Time-dependent adjoint problems are not supported.");
×
756

757
  // Initialize point sources
758
  for (auto& point_source : point_sources_)
586✔
759
    point_source->Initialize(*this);
9✔
760

761
  // Initialize volumetric sources
762
  for (auto& volumetric_source : volumetric_sources_)
1,060✔
763
    volumetric_source->Initialize(*this);
483✔
764

765
  initialized_ = true;
577✔
766
}
577✔
767

768
void
769
LBSProblem::PrintSimHeader()
×
770
{
771
  if (opensn::mpi_comm.rank() == 0)
×
772
  {
773
    std::stringstream outstr;
×
774
    outstr << "\n"
×
775
           << "Initializing " << GetName() << "\n\n"
×
UNCOV
776
           << "Scattering order    : " << scattering_order_ << "\n"
×
777
           << "Number of moments   : " << num_moments_ << "\n"
×
UNCOV
778
           << "Number of groups    : " << num_groups_ << "\n"
×
779
           << "Number of groupsets : " << groupsets_.size() << "\n\n";
×
780

781
    for (const auto& groupset : groupsets_)
×
782
    {
783
      outstr << "***** Groupset " << groupset.id << " *****\n"
×
UNCOV
784
             << "Groups:\n";
×
785
      const auto n_gs_groups = groupset.GetNumGroups();
×
786
      constexpr int groups_per_line = 12;
787
      for (size_t i = 0; i < n_gs_groups; ++i)
×
788
      {
789
        outstr << std::setw(5) << groupset.first_group + i << ' ';
×
790
        if ((i + 1) % groups_per_line == 0)
×
UNCOV
791
          outstr << '\n';
×
792
      }
793
      if (n_gs_groups > 0 && n_gs_groups % groups_per_line != 0)
×
794
        outstr << '\n';
×
795
    }
796

UNCOV
797
    log.Log() << outstr.str() << '\n';
×
UNCOV
798
  }
×
UNCOV
799
}
×
800

801
void
802
LBSProblem::InitializeSources(const InputParameters& params)
577✔
803
{
804
  if (params.Has("volumetric_sources"))
577✔
805
  {
806
    const auto& vol_srcs = params.GetParam("volumetric_sources");
577✔
807
    vol_srcs.RequireBlockTypeIs(ParameterBlockType::ARRAY);
577✔
808
    for (const auto& src : vol_srcs)
1,060✔
809
      volumetric_sources_.push_back(src.GetValue<std::shared_ptr<VolumetricSource>>());
966✔
810
  }
811

812
  if (params.Has("point_sources"))
577✔
813
  {
814
    const auto& pt_srcs = params.GetParam("point_sources");
577✔
815
    pt_srcs.RequireBlockTypeIs(ParameterBlockType::ARRAY);
577✔
816
    for (const auto& src : pt_srcs)
586✔
817
      point_sources_.push_back(src.GetValue<std::shared_ptr<PointSource>>());
18✔
818
  }
819
}
577✔
820

821
void
822
LBSProblem::InitializeGroupsets(const InputParameters& params)
577✔
823
{
824
  // Initialize groups
825
  if (num_groups_ == 0)
577✔
UNCOV
826
    throw std::invalid_argument(GetName() + ": Number of groups must be > 0");
×
827

828
  // Initialize groupsets
829
  const auto& groupsets_array = params.GetParam("groupsets");
577✔
830
  const size_t num_gs = groupsets_array.GetNumParameters();
577✔
831
  if (num_gs == 0)
577✔
UNCOV
832
    throw std::invalid_argument(GetName() + ": At least one groupset must be specified");
×
833
  for (size_t gs = 0; gs < num_gs; ++gs)
1,213✔
834
  {
835
    const auto& groupset_params = groupsets_array.GetParam(gs);
636✔
836
    InputParameters gs_input_params = LBSGroupset::GetInputParameters();
636✔
837
    gs_input_params.SetObjectType("LBSProblem:LBSGroupset");
636✔
838
    gs_input_params.AssignParameters(groupset_params);
636✔
839
    groupsets_.emplace_back(gs_input_params, gs, *this);
636✔
840
    if (groupsets_.back().GetNumGroups() == 0)
636✔
841
    {
UNCOV
842
      std::stringstream oss;
×
UNCOV
843
      oss << GetName() << ": No groups added to groupset " << groupsets_.back().id;
×
UNCOV
844
      throw std::runtime_error(oss.str());
×
UNCOV
845
    }
×
846
  }
636✔
847
}
577✔
848

849
void
850
LBSProblem::InitializeXSmapAndDensities(const InputParameters& params)
577✔
851
{
852
  // Build XS map
853
  const auto& xs_array = params.GetParam("xs_map");
577✔
854
  const size_t num_xs = xs_array.GetNumParameters();
577✔
855
  for (size_t i = 0; i < num_xs; ++i)
1,411✔
856
  {
857
    const auto& item_params = xs_array.GetParam(i);
834✔
858
    InputParameters xs_entry_pars = GetXSMapEntryBlock();
834✔
859
    xs_entry_pars.AssignParameters(item_params);
834✔
860

861
    const auto& block_ids_param = xs_entry_pars.GetParam("block_ids");
834✔
862
    block_ids_param.RequireBlockTypeIs(ParameterBlockType::ARRAY);
834✔
863
    const auto& block_ids = block_ids_param.GetVectorValue<unsigned int>();
834✔
864
    auto xs = xs_entry_pars.GetSharedPtrParam<MultiGroupXS>("xs");
834✔
865
    for (const auto& block_id : block_ids)
1,776✔
866
      block_id_to_xs_map_[block_id] = xs;
942✔
867
  }
834✔
868

869
  // Assign placeholder unit densities
870
  densities_local_.assign(grid_->local_cells.size(), 1.0);
577✔
871
}
577✔
872

873
void
874
LBSProblem::InitializeMaterials()
769✔
875
{
876
  CALI_CXX_MARK_SCOPE("LBSProblem::InitializeMaterials");
769✔
877

878
  log.Log0Verbose1() << "Initializing Materials";
1,538✔
879

880
  // Create set of material ids locally relevant
881
  int invalid_mat_cell_count = 0;
769✔
882
  std::set<unsigned int> unique_block_ids;
769✔
883
  for (auto& cell : grid_->local_cells)
601,284✔
884
  {
885
    unique_block_ids.insert(cell.block_id);
600,515✔
886
    if (cell.block_id == std::numeric_limits<unsigned int>::max() or
600,515✔
887
        (block_id_to_xs_map_.find(cell.block_id) == block_id_to_xs_map_.end()))
600,515✔
UNCOV
888
      ++invalid_mat_cell_count;
×
889
  }
890
  const auto& ghost_cell_ids = grid_->cells.GetGhostGlobalIDs();
769✔
891
  for (uint64_t cell_id : ghost_cell_ids)
99,280✔
892
  {
893
    const auto& cell = grid_->cells[cell_id];
98,511✔
894
    unique_block_ids.insert(cell.block_id);
98,511✔
895
    if (cell.block_id == std::numeric_limits<unsigned int>::max() or
98,511✔
896
        (block_id_to_xs_map_.find(cell.block_id) == block_id_to_xs_map_.end()))
98,511✔
UNCOV
897
      ++invalid_mat_cell_count;
×
898
  }
899
  OpenSnLogicalErrorIf(invalid_mat_cell_count > 0,
769✔
900
                       std::to_string(invalid_mat_cell_count) +
901
                         " cells encountered with an invalid material id.");
902

903
  // Get ready for processing
904
  for (const auto& [blk_id, mat] : block_id_to_xs_map_)
1,927✔
905
  {
906
    mat->SetAdjointMode(options_.adjoint);
1,158✔
907

908
    OpenSnLogicalErrorIf(mat->GetNumGroups() < num_groups_,
1,158✔
909
                         "Cross-sections for block \"" + std::to_string(blk_id) +
910
                           "\" have fewer groups (" + std::to_string(mat->GetNumGroups()) +
911
                           ") than the simulation (" + std::to_string(num_groups_) + "). " +
912
                           "Cross-sections must have at least as many groups as the simulation.");
913
  }
914

915
  // Initialize precursor properties
916
  num_precursors_ = 0;
769✔
917
  max_precursors_per_material_ = 0;
769✔
918
  for (const auto& mat_id_xs : block_id_to_xs_map_)
1,927✔
919
  {
920
    const auto& xs = mat_id_xs.second;
1,158✔
921
    num_precursors_ += xs->GetNumPrecursors();
1,158✔
922
    max_precursors_per_material_ = std::max(xs->GetNumPrecursors(), max_precursors_per_material_);
1,158✔
923
  }
924

925
  // if no precursors, turn off precursors
926
  if (num_precursors_ == 0)
769✔
927
    options_.use_precursors = false;
572✔
928

929
  // check compatibility when precursors are on
930
  if (options_.use_precursors)
769✔
931
  {
932
    for (const auto& [mat_id, xs] : block_id_to_xs_map_)
352✔
933
    {
934
      OpenSnLogicalErrorIf(xs->IsFissionable() and num_precursors_ == 0,
176✔
935
                           "Incompatible cross-section data encountered for material id " +
936
                             std::to_string(mat_id) + ". When delayed neutron data is present " +
937
                             "for one fissionable matrial, it must be present for all fissionable "
938
                             "materials.");
939
    }
940
  }
941

942
  // Update transport views if available
943
  if (grid_->local_cells.size() == cell_transport_views_.size())
769✔
944
    for (const auto& cell : grid_->local_cells)
27,908✔
945
    {
946
      const auto& xs_ptr = block_id_to_xs_map_[cell.block_id];
27,716✔
947
      auto& transport_view = cell_transport_views_[cell.local_id];
27,716✔
948
      transport_view.ReassignXS(*xs_ptr);
27,716✔
949
    }
950

951
  mpi_comm.barrier();
769✔
952
}
769✔
953

954
void
955
LBSProblem::InitializeSpatialDiscretization()
497✔
956
{
957
  CALI_CXX_MARK_SCOPE("LBSProblem::InitializeSpatialDiscretization");
497✔
958

959
  OpenSnLogicalErrorIf(not discretization_,
497✔
960
                       GetName() + ": Missing spatial discretization. Construct the problem "
961
                                   "through its factory Create(...) entry point.");
962
  log.Log() << "Initializing spatial discretization metadata.\n";
994✔
963

964
  ComputeUnitIntegrals();
497✔
965
}
497✔
966

967
void
968
LBSProblem::ComputeUnitIntegrals()
577✔
969
{
970
  CALI_CXX_MARK_SCOPE("LBSProblem::ComputeUnitIntegrals");
577✔
971

972
  log.Log() << "Computing unit integrals.\n";
1,154✔
973
  const auto& sdm = *discretization_;
577✔
974

975
  const size_t num_local_cells = grid_->local_cells.size();
577✔
976
  unit_cell_matrices_.resize(num_local_cells);
577✔
977

978
  for (const auto& cell : grid_->local_cells)
573,376✔
979
    unit_cell_matrices_[cell.local_id] =
572,799✔
980
      ComputeUnitCellIntegrals(sdm, cell, grid_->GetCoordinateSystem());
572,799✔
981

982
  const auto ghost_ids = grid_->cells.GetGhostGlobalIDs();
577✔
983
  for (auto ghost_id : ghost_ids)
90,326✔
984
    unit_ghost_cell_matrices_[ghost_id] =
89,749✔
985
      ComputeUnitCellIntegrals(sdm, grid_->cells[ghost_id], grid_->GetCoordinateSystem());
179,498✔
986

987
  // Assessing global unit cell matrix storage
988
  std::array<size_t, 2> num_local_ucms = {unit_cell_matrices_.size(),
577✔
989
                                          unit_ghost_cell_matrices_.size()};
577✔
990
  std::array<size_t, 2> num_global_ucms = {0, 0};
577✔
991

992
  mpi_comm.all_reduce(num_local_ucms.data(), 2, num_global_ucms.data(), mpi::op::sum<size_t>());
577✔
993

994
  opensn::mpi_comm.barrier();
577✔
995
  log.Log() << "Ghost cell unit cell-matrix ratio: "
577✔
996
            << (double)num_global_ucms[1] * 100 / (double)num_global_ucms[0] << "%";
1,154✔
997
  log.Log() << "Cell matrices computed.";
1,154✔
998
}
577✔
999

1000
void
1001
LBSProblem::InitializeParrays()
577✔
1002
{
1003
  CALI_CXX_MARK_SCOPE("LBSProblem::InitializeParrays");
577✔
1004

1005
  log.Log() << "Initializing parallel arrays."
1,154✔
1006
            << " G=" << num_groups_ << " M=" << num_moments_ << std::endl;
577✔
1007

1008
  // Initialize unknown
1009
  // structure
1010
  flux_moments_uk_man_.unknowns.clear();
577✔
1011
  for (unsigned int m = 0; m < num_moments_; ++m)
1,728✔
1012
  {
1013
    flux_moments_uk_man_.AddUnknown(UnknownType::VECTOR_N, num_groups_);
1,151✔
1014
    flux_moments_uk_man_.unknowns.back().name = "m" + std::to_string(m);
1,151✔
1015
  }
1016

1017
  // Compute local # of dof
1018
  local_node_count_ = discretization_->GetNumLocalNodes();
577✔
1019
  global_node_count_ = discretization_->GetNumGlobalNodes();
577✔
1020

1021
  // Compute num of unknowns
1022
  size_t local_unknown_count = local_node_count_ * num_groups_ * num_moments_;
577✔
1023

1024
  log.LogAllVerbose1() << "LBS Number of phi unknowns: " << local_unknown_count;
1,154✔
1025

1026
  // Size local vectors
1027
  q_moments_local_.assign(local_unknown_count, 0.0);
577✔
1028
  phi_old_local_.assign(local_unknown_count, 0.0);
577✔
1029
  phi_new_local_.assign(local_unknown_count, 0.0);
577✔
1030

1031
  // Setup precursor vector
1032
  if (options_.use_precursors)
577✔
1033
  {
1034
    size_t num_precursor_dofs = grid_->local_cells.size() * max_precursors_per_material_;
60✔
1035
    precursor_new_local_.assign(num_precursor_dofs, 0.0);
60✔
1036
  }
1037

1038
  // Initialize transport views
1039
  // Transport views act as a data structure to store information
1040
  // related to the transport simulation. The most prominent function
1041
  // here is that it holds the means to know where a given cell's
1042
  // transport quantities are located in the unknown vectors (i.e. phi)
1043
  //
1044
  // Also, for a given cell, within a given sweep chunk,
1045
  // we need to solve a matrix which square size is the
1046
  // amount of nodes on the cell. max_cell_dof_count is
1047
  // initialized here.
1048
  //
1049
  size_t block_MG_counter = 0; // Counts the strides of moment and group
577✔
1050

1051
  const Vector3 ihat(1.0, 0.0, 0.0);
577✔
1052
  const Vector3 jhat(0.0, 1.0, 0.0);
577✔
1053
  const Vector3 khat(0.0, 0.0, 1.0);
577✔
1054

1055
  min_cell_dof_count_ = std::numeric_limits<unsigned int>::max();
577✔
1056
  max_cell_dof_count_ = 0;
577✔
1057
  cell_transport_views_.clear();
577✔
1058
  cell_transport_views_.reserve(grid_->local_cells.size());
577✔
1059
  for (auto& cell : grid_->local_cells)
573,376✔
1060
  {
1061
    size_t num_nodes = discretization_->GetCellNumNodes(cell);
572,799✔
1062

1063
    // compute cell volumes
1064
    double cell_volume = 0.0;
572,799✔
1065
    const auto& IntV_shapeI = unit_cell_matrices_[cell.local_id].intV_shapeI;
572,799✔
1066
    for (size_t i = 0; i < num_nodes; ++i)
3,971,251✔
1067
      cell_volume += IntV_shapeI(i);
3,398,452✔
1068

1069
    size_t cell_phi_address = block_MG_counter;
572,799✔
1070

1071
    const size_t num_faces = cell.faces.size();
572,799✔
1072
    std::vector<bool> face_local_flags(num_faces, true);
572,799✔
1073
    std::vector<int> face_locality(num_faces, opensn::mpi_comm.rank());
572,799✔
1074
    std::vector<const Cell*> neighbor_cell_ptrs(num_faces, nullptr);
572,799✔
1075
    bool cell_on_boundary = false;
572,799✔
1076
    int f = 0;
572,799✔
1077
    for (auto& face : cell.faces)
3,403,771✔
1078
    {
1079
      if (not face.has_neighbor)
2,830,972✔
1080
      {
1081
        cell_on_boundary = true;
91,682✔
1082
        face_local_flags[f] = false;
91,682✔
1083
        face_locality[f] = -1;
91,682✔
1084
      } // if bndry
1085
      else
1086
      {
1087
        const int neighbor_partition = face.GetNeighborPartitionID(grid_.get());
2,739,290✔
1088
        face_local_flags[f] = (neighbor_partition == opensn::mpi_comm.rank());
2,739,290✔
1089
        face_locality[f] = neighbor_partition;
2,739,290✔
1090
        neighbor_cell_ptrs[f] = &grid_->cells[face.neighbor_id];
2,739,290✔
1091
      }
1092

1093
      ++f;
2,830,972✔
1094
    } // for f
1095

1096
    max_cell_dof_count_ = std::max(max_cell_dof_count_, static_cast<unsigned int>(num_nodes));
572,799✔
1097
    min_cell_dof_count_ = std::min(min_cell_dof_count_, static_cast<unsigned int>(num_nodes));
572,799✔
1098
    cell_transport_views_.emplace_back(cell_phi_address,
1,145,598✔
1099
                                       num_nodes,
1100
                                       num_groups_,
572,799✔
1101
                                       num_moments_,
572,799✔
1102
                                       num_faces,
1103
                                       *block_id_to_xs_map_[cell.block_id],
572,799✔
1104
                                       cell_volume,
1105
                                       face_local_flags,
1106
                                       face_locality,
1107
                                       neighbor_cell_ptrs,
1108
                                       cell_on_boundary);
1109
    block_MG_counter += num_nodes * num_groups_ * num_moments_;
572,799✔
1110
  } // for local cell
572,799✔
1111

1112
  // Populate grid nodal mappings
1113
  // This is used in the Flux Data Structures (FLUDS)
1114
  grid_nodal_mappings_.clear();
577✔
1115
  grid_nodal_mappings_.reserve(grid_->local_cells.size());
577✔
1116
  for (auto& cell : grid_->local_cells)
573,376✔
1117
  {
1118
    CellFaceNodalMapping cell_nodal_mapping;
572,799✔
1119
    cell_nodal_mapping.reserve(cell.faces.size());
572,799✔
1120

1121
    for (auto& face : cell.faces)
3,403,771✔
1122
    {
1123
      std::vector<short> face_node_mapping;
2,830,972✔
1124
      std::vector<short> cell_node_mapping;
2,830,972✔
1125
      int adj_face_idx = -1;
2,830,972✔
1126

1127
      if (face.has_neighbor)
2,830,972✔
1128
      {
1129
        grid_->FindAssociatedVertices(face, face_node_mapping);
2,739,290✔
1130
        grid_->FindAssociatedCellVertices(face, cell_node_mapping);
2,739,290✔
1131
        adj_face_idx = face.GetNeighborAdjacentFaceIndex(grid_.get());
2,739,290✔
1132
      }
1133

1134
      cell_nodal_mapping.emplace_back(adj_face_idx, face_node_mapping, cell_node_mapping);
2,830,972✔
1135
    } // for f
2,830,972✔
1136

1137
    grid_nodal_mappings_.push_back(cell_nodal_mapping);
572,799✔
1138
  } // for local cell
572,799✔
1139

1140
  // Get grid localized communicator set
1141
  grid_local_comm_set_ = grid_->MakeMPILocalCommunicatorSet();
577✔
1142

1143
  // Initialize Field Functions
1144
  InitializeFieldFunctions();
577✔
1145

1146
  opensn::mpi_comm.barrier();
577✔
1147
  log.Log() << "Done with parallel arrays." << std::endl;
1,154✔
1148
}
577✔
1149

1150
void
1151
LBSProblem::InitializeFieldFunctions()
577✔
1152
{
1153
  CALI_CXX_MARK_SCOPE("LBSProblem::InitializeFieldFunctions");
577✔
1154

1155
  if (not field_functions_.empty())
577✔
UNCOV
1156
    return;
×
1157

1158
  // Initialize Field Functions for flux moments
1159
  phi_field_functions_local_map_.clear();
577✔
1160

1161
  for (unsigned int g = 0; g < num_groups_; ++g)
23,243✔
1162
  {
1163
    for (unsigned int m = 0; m < num_moments_; ++m)
100,650✔
1164
    {
1165
      std::string prefix;
77,984✔
1166
      if (options_.field_function_prefix_option == "prefix")
77,984✔
1167
      {
1168
        prefix = options_.field_function_prefix;
77,984✔
1169
        if (not prefix.empty())
77,984✔
1170
          prefix += "_";
1✔
1171
      }
1172
      if (options_.field_function_prefix_option == "solver_name")
77,984✔
UNCOV
1173
        prefix = GetName() + "_";
×
1174

1175
      std::ostringstream oss;
77,984✔
1176
      oss << prefix << "phi_g" << std::setw(3) << std::setfill('0') << static_cast<int>(g) << "_m"
77,984✔
1177
          << std::setw(2) << std::setfill('0') << static_cast<int>(m);
77,984✔
1178
      const std::string name = oss.str();
77,984✔
1179

1180
      auto group_ff = std::make_shared<FieldFunctionGridBased>(
77,984✔
1181
        name, discretization_, Unknown(UnknownType::SCALAR));
77,984✔
1182

1183
      field_function_stack.push_back(group_ff);
155,968✔
1184
      field_functions_.push_back(group_ff);
77,984✔
1185

1186
      phi_field_functions_local_map_[{g, m}] = field_functions_.size() - 1;
77,984✔
1187
    } // for m
77,984✔
1188
  } // for g
1189

1190
  // Initialize power generation field function
1191
  if (options_.power_field_function_on)
577✔
1192
  {
1193
    std::string prefix;
5✔
1194
    if (options_.field_function_prefix_option == "prefix")
5✔
1195
    {
1196
      prefix = options_.field_function_prefix;
5✔
1197
      if (not prefix.empty())
5✔
UNCOV
1198
        prefix += "_";
×
1199
    }
1200
    if (options_.field_function_prefix_option == "solver_name")
5✔
UNCOV
1201
      prefix = GetName() + "_";
×
1202

1203
    auto power_ff = std::make_shared<FieldFunctionGridBased>(
5✔
1204
      prefix + "power_generation", discretization_, Unknown(UnknownType::SCALAR));
10✔
1205

1206
    field_function_stack.push_back(power_ff);
10✔
1207
    field_functions_.push_back(power_ff);
5✔
1208

1209
    power_gen_fieldfunc_local_handle_ = field_functions_.size() - 1;
5✔
1210
  }
5✔
1211
}
577✔
1212

1213
void
1214
LBSProblem::InitializeSolverSchemes()
677✔
1215
{
1216
  CALI_CXX_MARK_SCOPE("LBSProblem::InitializeSolverSchemes");
677✔
1217
  InitializeWGSSolvers();
677✔
1218

1219
  ags_solver_ = std::make_shared<AGSLinearSolver>(*this, wgs_solvers_);
677✔
1220
  if (groupsets_.size() == 1)
677✔
1221
  {
1222
    ags_solver_->SetMaxIterations(1);
622✔
1223
    ags_solver_->SetVerbosity(false);
622✔
1224
  }
1225
  else
1226
  {
1227
    ags_solver_->SetMaxIterations(options_.max_ags_iterations);
55✔
1228
    ags_solver_->SetVerbosity(options_.verbose_ags_iterations);
55✔
1229
  }
1230
  ags_solver_->SetTolerance(options_.ags_tolerance);
677✔
1231
}
677✔
1232

1233
#ifndef __OPENSN_WITH_GPU__
1234
void
1235
LBSProblem::InitializeGPUExtras()
745✔
1236
{
1237
}
745✔
1238

1239
void
1240
LBSProblem::ResetGPUCarriers()
733✔
1241
{
1242
}
733✔
1243

1244
void
UNCOV
1245
LBSProblem::CheckCapableDevices()
×
1246
{
UNCOV
1247
}
×
1248
#endif // __OPENSN_WITH_GPU__
1249

1250
std::vector<double>
1251
LBSProblem::MakeSourceMomentsFromPhi()
4✔
1252
{
1253
  CALI_CXX_MARK_SCOPE("LBSProblem::MakeSourceMomentsFromPhi");
4✔
1254

1255
  size_t num_local_dofs = discretization_->GetNumLocalDOFs(flux_moments_uk_man_);
4✔
1256

1257
  std::vector<double> source_moments(num_local_dofs, 0.0);
4✔
1258
  for (auto& groupset : groupsets_)
8✔
1259
  {
1260
    active_set_source_function_(groupset,
4✔
1261
                                source_moments,
1262
                                phi_new_local_,
4✔
1263
                                APPLY_AGS_SCATTER_SOURCES | APPLY_WGS_SCATTER_SOURCES |
1264
                                  APPLY_AGS_FISSION_SOURCES | APPLY_WGS_FISSION_SOURCES);
4✔
1265
  }
1266

1267
  return source_moments;
4✔
1268
}
4✔
1269

1270
void
1271
LBSProblem::UpdateFieldFunctions()
2,757✔
1272
{
1273
  CALI_CXX_MARK_SCOPE("LBSProblem::UpdateFieldFunctions");
2,757✔
1274

1275
  const auto& sdm = *discretization_;
2,757✔
1276
  const auto& phi_uk_man = flux_moments_uk_man_;
2,757✔
1277

1278
  // Update flux moments
1279
  for (const auto& [g_and_m, ff_index] : phi_field_functions_local_map_)
87,745✔
1280
  {
1281
    const auto g = g_and_m.first;
84,988✔
1282
    const auto m = g_and_m.second;
84,988✔
1283

1284
    std::vector<double> data_vector_local(local_node_count_, 0.0);
84,988✔
1285

1286
    for (const auto& cell : grid_->local_cells)
24,449,959✔
1287
    {
1288
      const auto& cell_mapping = sdm.GetCellMapping(cell);
24,364,971✔
1289
      const size_t num_nodes = cell_mapping.GetNumNodes();
24,364,971✔
1290

1291
      for (size_t i = 0; i < num_nodes; ++i)
164,656,147✔
1292
      {
1293
        const auto imapA = sdm.MapDOFLocal(cell, i, phi_uk_man, m, g);
140,291,176✔
1294
        const auto imapB = sdm.MapDOFLocal(cell, i);
140,291,176✔
1295

1296
        data_vector_local[imapB] = phi_new_local_[imapA];
140,291,176✔
1297
      } // for node
1298
    } // for cell
1299

1300
    auto& ff_ptr = field_functions_.at(ff_index);
84,988✔
1301
    ff_ptr->UpdateFieldVector(data_vector_local);
84,988✔
1302
  }
84,988✔
1303

1304
  // Update power generation and scalar flux
1305
  if (options_.power_field_function_on)
2,757✔
1306
  {
1307
    std::vector<double> data_vector_power_local(local_node_count_, 0.0);
5✔
1308

1309
    double local_total_power = 0.0;
5✔
1310
    for (const auto& cell : grid_->local_cells)
83,319✔
1311
    {
1312
      const auto& cell_mapping = sdm.GetCellMapping(cell);
83,314✔
1313
      const size_t num_nodes = cell_mapping.GetNumNodes();
83,314✔
1314

1315
      const auto& Vi = unit_cell_matrices_[cell.local_id].intV_shapeI;
83,314✔
1316

1317
      const auto& xs = block_id_to_xs_map_.at(cell.block_id);
83,314✔
1318

1319
      if (not xs->IsFissionable())
83,314✔
1320
        continue;
56,360✔
1321

1322
      for (size_t i = 0; i < num_nodes; ++i)
134,670✔
1323
      {
1324
        const auto imapA = sdm.MapDOFLocal(cell, i);
107,716✔
1325
        const auto imapB = sdm.MapDOFLocal(cell, i, phi_uk_man, 0, 0);
107,716✔
1326

1327
        double nodal_power = 0.0;
1328
        for (unsigned int g = 0; g < num_groups_; ++g)
861,128✔
1329
        {
1330
          const double sigma_fg = xs->GetSigmaFission()[g];
753,412✔
1331
          // const double kappa_g = xs->Kappa()[g];
1332
          const double kappa_g = options_.power_default_kappa;
753,412✔
1333

1334
          nodal_power += kappa_g * sigma_fg * phi_new_local_[imapB + g];
753,412✔
1335
        } // for g
1336

1337
        data_vector_power_local[imapA] = nodal_power;
107,716✔
1338
        local_total_power += nodal_power * Vi(i);
107,716✔
1339
      } // for node
1340
    } // for cell
1341

1342
    double scale_factor = 1.0;
5✔
1343
    if (options_.power_normalization > 0.0)
5✔
1344
    {
1345
      double global_total_power = 0.0;
5✔
1346
      mpi_comm.all_reduce(local_total_power, global_total_power, mpi::op::sum<double>());
5✔
1347
      scale_factor = options_.power_normalization / global_total_power;
5✔
1348
      Scale(data_vector_power_local, scale_factor);
5✔
1349
    }
1350

1351
    const size_t ff_index = power_gen_fieldfunc_local_handle_;
5✔
1352

1353
    auto& ff_ptr = field_functions_.at(ff_index);
5✔
1354
    ff_ptr->UpdateFieldVector(data_vector_power_local);
5✔
1355

1356
    // scale scalar flux if neccessary
1357
    if (scale_factor != 1.0)
5✔
1358
    {
1359
      for (unsigned int g = 0; g < num_groups_; ++g)
34✔
1360
      {
1361
        const size_t phi_ff_index = phi_field_functions_local_map_.at({g, size_t{0}});
29✔
1362
        auto& phi_ff_ptr = field_functions_.at(phi_ff_index);
29✔
1363
        const auto& phi_vec = phi_ff_ptr->GetLocalFieldVector();
29✔
1364
        std::vector<double> phi_scaled(phi_vec.begin(), phi_vec.end());
29✔
1365
        Scale(phi_scaled, scale_factor);
29✔
1366
        phi_ff_ptr->UpdateFieldVector(phi_scaled);
29✔
1367
      }
29✔
1368
    }
1369
  } // if power enabled
5✔
1370
}
2,757✔
1371

1372
void
1373
LBSProblem::SetPhiFromFieldFunctions(PhiSTLOption which_phi,
×
1374
                                     const std::vector<unsigned int>& m_indices,
1375
                                     const std::vector<unsigned int>& g_indices)
1376
{
1377
  CALI_CXX_MARK_SCOPE("LBSProblem::SetPhiFromFieldFunctions");
×
1378

1379
  std::vector<unsigned int> m_ids_to_copy = m_indices;
×
1380
  std::vector<unsigned int> g_ids_to_copy = g_indices;
×
1381
  if (m_indices.empty())
×
1382
    for (unsigned int m = 0; m < num_moments_; ++m)
×
UNCOV
1383
      m_ids_to_copy.push_back(m);
×
1384
  if (g_ids_to_copy.empty())
×
1385
    for (unsigned int g = 0; g < num_groups_; ++g)
×
UNCOV
1386
      g_ids_to_copy.push_back(g);
×
1387

UNCOV
1388
  const auto& sdm = *discretization_;
×
1389
  const auto& phi_uk_man = flux_moments_uk_man_;
×
1390

1391
  for (const auto m : m_ids_to_copy)
×
1392
  {
1393
    for (const auto g : g_ids_to_copy)
×
1394
    {
1395
      const size_t ff_index = phi_field_functions_local_map_.at({g, m});
×
UNCOV
1396
      const auto& ff_ptr = field_functions_.at(ff_index);
×
1397
      const auto& ff_data = ff_ptr->GetLocalFieldVector();
×
1398

UNCOV
1399
      for (const auto& cell : grid_->local_cells)
×
1400
      {
UNCOV
1401
        const auto& cell_mapping = sdm.GetCellMapping(cell);
×
1402
        const size_t num_nodes = cell_mapping.GetNumNodes();
×
1403

UNCOV
1404
        for (size_t i = 0; i < num_nodes; ++i)
×
1405
        {
1406
          const auto imapA = sdm.MapDOFLocal(cell, i);
×
1407
          const auto imapB = sdm.MapDOFLocal(cell, i, phi_uk_man, m, g);
×
1408

UNCOV
1409
          if (which_phi == PhiSTLOption::PHI_OLD)
×
UNCOV
1410
            phi_old_local_[imapB] = ff_data[imapA];
×
UNCOV
1411
          else if (which_phi == PhiSTLOption::PHI_NEW)
×
UNCOV
1412
            phi_new_local_[imapB] = ff_data[imapA];
×
1413
        } // for node
1414
      } // for cell
1415
    } // for g
1416
  } // for m
UNCOV
1417
}
×
1418

1419
LBSProblem::~LBSProblem()
565✔
1420
{
1421
  ResetGPUCarriers();
1422
}
2,825✔
1423

565✔
1424
void
1425
LBSProblem::SetSaveAngularFlux(bool save)
48✔
1426
{
1427
  options_.save_angular_flux = save;
48✔
1428
  if (initialized_)
48✔
1429
    applied_save_angular_flux_ = save;
4✔
1430
}
48✔
1431

1432
void
1433
LBSProblem::ZeroPhi()
×
1434
{
UNCOV
1435
  phi_old_local_.assign(phi_old_local_.size(), 0.0);
×
UNCOV
1436
  phi_new_local_.assign(phi_new_local_.size(), 0.0);
×
UNCOV
1437
}
×
1438

1439
void
1440
LBSProblem::SetAdjoint(bool adjoint)
24✔
1441
{
1442
  OpenSnLogicalErrorIf(
24✔
1443
    not initialized_, GetName() + ": Problem must be fully constructed before calling SetAdjoint.");
1444

1445
  if (adjoint)
24✔
1446
    if (const auto* do_problem = dynamic_cast<const DiscreteOrdinatesProblem*>(this);
20✔
1447
        do_problem and do_problem->IsTimeDependent())
20✔
UNCOV
1448
      throw std::runtime_error(GetName() + ": Time-dependent adjoint problems are not supported.");
×
1449

1450
  options_.adjoint = adjoint;
24✔
1451

1452
  if (adjoint != applied_adjoint_)
24✔
1453
  {
1454
    // Reinitialize materials to obtain the proper forward/adjoint cross sections.
1455
    InitializeMaterials();
24✔
1456

1457
    // Forward and adjoint sources are fundamentally different.
1458
    point_sources_.clear();
24✔
1459
    volumetric_sources_.clear();
24✔
1460
    ClearBoundaries();
24✔
1461

1462
    // Reset all solution vectors.
1463
    phi_old_local_.assign(phi_old_local_.size(), 0.0);
24✔
1464
    phi_new_local_.assign(phi_new_local_.size(), 0.0);
24✔
1465
    ZeroPsi();
24✔
1466
    precursor_new_local_.assign(precursor_new_local_.size(), 0.0);
24✔
1467

1468
    applied_adjoint_ = adjoint;
24✔
1469
  }
1470
}
24✔
1471

1472
bool
UNCOV
1473
LBSProblem::IsAdjoint() const
×
1474
{
UNCOV
1475
  return options_.adjoint;
×
1476
}
1477

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