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

Open-Sn / opensn / 22512725730

27 Feb 2026 08:22PM UTC coverage: 74.152% (+0.003%) from 74.149%
22512725730

push

github

web-flow
Merge pull request #950 from wdhawkins/solvers_refactor

Update steady-state <--> time-dependent mode transitions

79 of 113 new or added lines in 7 files covered. (69.91%)

285 existing lines in 10 files now uncovered.

20096 of 27101 relevant lines covered (74.15%)

66856917.7 hits per line

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

81.28
/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/math/spatial_discretization/finite_element/piecewise_linear/piecewise_linear_discontinuous.h"
11
#include "framework/field_functions/field_function_grid_based.h"
12
#include "framework/materials/multi_group_xs/multi_group_xs.h"
13
#include "framework/mesh/mesh_continuum/mesh_continuum.h"
14
#include "framework/utils/hdf_utils.h"
15
#include "framework/object_factory.h"
16
#include "framework/logging/log.h"
17
#include "framework/runtime.h"
18
#include "framework/data_types/allowable_range.h"
19
#include "caliper/cali.h"
20
#include <algorithm>
21
#include <iomanip>
22
#include <fstream>
23
#include <cstring>
24
#include <cassert>
25
#include <memory>
26
#include <stdexcept>
27
#include <sys/stat.h>
28

29
namespace opensn
30
{
31

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

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

42
  params.ChangeExistingParamToOptional("name", "LBSProblem");
1,156✔
43

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

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

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

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

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

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

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

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

69
  return params;
578✔
UNCOV
70
}
×
71

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

89
  // Initialize options
90
  if (params.IsParameterValid("options"))
578✔
91
  {
92
    auto options_params = LBSProblem::GetOptionsBlock();
350✔
93
    options_params.AssignParameters(params.GetParam("options"));
352✔
94
    ParseOptions(options_params);
348✔
95
  }
350✔
96
  applied_adjoint_ = options_.adjoint;
576✔
97
  applied_save_angular_flux_ = options_.save_angular_flux;
576✔
98

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

104
  InitializeGroupsets(params);
576✔
105
  InitializeSources(params);
576✔
106
  InitializeXSmapAndDensities(params);
576✔
107
  InitializeMaterials();
576✔
108
}
596✔
109

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

116
const LBSOptions&
117
LBSProblem::GetOptions() const
682,961,280✔
118
{
119
  return options_;
682,961,280✔
120
}
121

122
double
123
LBSProblem::GetTime() const
422,192✔
124
{
125
  return time_;
422,192✔
126
}
127

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

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

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

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

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

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

168
unsigned int
169
LBSProblem::GetNumMoments() const
526,289✔
170
{
171
  return num_moments_;
526,289✔
172
}
173

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

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

186
bool
187
LBSProblem::UseGPUs() const
987✔
188
{
189
  return use_gpus_;
987✔
190
}
191

192
unsigned int
193
LBSProblem::GetNumGroups() const
819,270✔
194
{
195
  return num_groups_;
819,270✔
196
}
197

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

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

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

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

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

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

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

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

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

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

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

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

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

283
std::shared_ptr<MeshContinuum>
284
LBSProblem::GetGrid() const
750,223✔
285
{
286
  return grid_;
750,223✔
287
}
288

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

397
std::vector<double>&
398
LBSProblem::GetDensitiesLocal()
985✔
399
{
400
  return densities_local_;
985✔
401
}
402

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

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

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

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

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

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

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

UNCOV
450
  return {num_local_phi_dofs, num_global_phi_dofs};
×
451
}
452

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

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

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

UNCOV
469
  return field_functions_[power_gen_fieldfunc_local_handle_];
×
470
}
471

472
InputParameters
473
LBSProblem::GetOptionsBlock()
722✔
474
{
475
  InputParameters params;
722✔
476

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

555
  return params;
722✔
UNCOV
556
}
×
557

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

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

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

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

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

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

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

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

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

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

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

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

623
    else if (spec.GetName() == "verbose_inner_iterations")
633✔
624
      options_.verbose_inner_iterations = spec.GetValue<bool>();
211✔
625

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

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

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

638
    else if (spec.GetName() == "verbose_ags_iterations")
364✔
639
      options_.verbose_ags_iterations = spec.GetValue<bool>();
128✔
640

641
    else if (spec.GetName() == "verbose_outer_iterations")
236✔
642
      options_.verbose_outer_iterations = spec.GetValue<bool>();
187✔
643

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

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

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

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

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

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

664
  } // for specified options
665

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

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

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

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

685
  if (options_.restart_writes_enabled)
360✔
686
  {
UNCOV
687
    const auto dir = options_.write_restart_path.parent_path();
×
688

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

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

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

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

721
void
722
LBSProblem::Initialize()
576✔
723
{
724
  CALI_CXX_MARK_SCOPE("LBSProblem::Initialize");
576✔
725

726
  PrintSimHeader();
576✔
727
  mpi_comm.barrier();
576✔
728

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

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

739
  // Initialize volumetric sources
740
  for (auto& volumetric_source : volumetric_sources_)
1,059✔
741
    volumetric_source->Initialize(*this);
483✔
742
}
576✔
743

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

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

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

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

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

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

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

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

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

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

849
void
850
LBSProblem::InitializeMaterials()
768✔
851
{
852
  CALI_CXX_MARK_SCOPE("LBSProblem::InitializeMaterials");
768✔
853

854
  log.Log0Verbose1() << "Initializing Materials";
1,536✔
855

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

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

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

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

901
  // if no precursors, turn off precursors
902
  if (num_precursors_ == 0)
768✔
903
    options_.use_precursors = false;
572✔
904

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

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

927
  mpi_comm.barrier();
768✔
928
}
768✔
929

930
void
931
LBSProblem::InitializeSpatialDiscretization()
496✔
932
{
933
  CALI_CXX_MARK_SCOPE("LBSProblem::InitializeSpatialDiscretization");
496✔
934

935
  log.Log() << "Initializing spatial discretization.\n";
992✔
936
  discretization_ = PieceWiseLinearDiscontinuous::New(grid_);
496✔
937

938
  ComputeUnitIntegrals();
496✔
939
}
496✔
940

941
void
942
LBSProblem::ComputeUnitIntegrals()
576✔
943
{
944
  CALI_CXX_MARK_SCOPE("LBSProblem::ComputeUnitIntegrals");
576✔
945

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

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

952
  for (const auto& cell : grid_->local_cells)
573,325✔
953
    unit_cell_matrices_[cell.local_id] =
572,749✔
954
      ComputeUnitCellIntegrals(sdm, cell, grid_->GetCoordinateSystem());
572,749✔
955

956
  const auto ghost_ids = grid_->cells.GetGhostGlobalIDs();
576✔
957
  for (auto ghost_id : ghost_ids)
90,325✔
958
    unit_ghost_cell_matrices_[ghost_id] =
89,749✔
959
      ComputeUnitCellIntegrals(sdm, grid_->cells[ghost_id], grid_->GetCoordinateSystem());
179,498✔
960

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

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

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

974
void
975
LBSProblem::InitializeParrays()
576✔
976
{
977
  CALI_CXX_MARK_SCOPE("LBSProblem::InitializeParrays");
576✔
978

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

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

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

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

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

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

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

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

1025
  const Vector3 ihat(1.0, 0.0, 0.0);
576✔
1026
  const Vector3 jhat(0.0, 1.0, 0.0);
576✔
1027
  const Vector3 khat(0.0, 0.0, 1.0);
576✔
1028

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

1037
    // compute cell volumes
1038
    double cell_volume = 0.0;
572,749✔
1039
    const auto& IntV_shapeI = unit_cell_matrices_[cell.local_id].intV_shapeI;
572,749✔
1040
    for (size_t i = 0; i < num_nodes; ++i)
3,971,101✔
1041
      cell_volume += IntV_shapeI(i);
3,398,352✔
1042

1043
    size_t cell_phi_address = block_MG_counter;
572,749✔
1044

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

1067
      ++f;
2,830,872✔
1068
    } // for f
1069

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

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

1095
    for (auto& face : cell.faces)
3,403,621✔
1096
    {
1097
      std::vector<short> face_node_mapping;
2,830,872✔
1098
      std::vector<short> cell_node_mapping;
2,830,872✔
1099
      int adj_face_idx = -1;
2,830,872✔
1100

1101
      if (face.has_neighbor)
2,830,872✔
1102
      {
1103
        grid_->FindAssociatedVertices(face, face_node_mapping);
2,739,192✔
1104
        grid_->FindAssociatedCellVertices(face, cell_node_mapping);
2,739,192✔
1105
        adj_face_idx = face.GetNeighborAdjacentFaceIndex(grid_.get());
2,739,192✔
1106
      }
1107

1108
      cell_nodal_mapping.emplace_back(adj_face_idx, face_node_mapping, cell_node_mapping);
2,830,872✔
1109
    } // for f
2,830,872✔
1110

1111
    grid_nodal_mappings_.push_back(cell_nodal_mapping);
572,749✔
1112
  } // for local cell
572,749✔
1113

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

1117
  // Initialize Field Functions
1118
  InitializeFieldFunctions();
576✔
1119

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

1124
void
1125
LBSProblem::InitializeFieldFunctions()
576✔
1126
{
1127
  CALI_CXX_MARK_SCOPE("LBSProblem::InitializeFieldFunctions");
576✔
1128

1129
  if (not field_functions_.empty())
576✔
UNCOV
1130
    return;
×
1131

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

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

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

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

1157
      field_function_stack.push_back(group_ff);
155,966✔
1158
      field_functions_.push_back(group_ff);
77,983✔
1159

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

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

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

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

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

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

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

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

1213
void
1214
LBSProblem::ResetGPUCarriers()
732✔
1215
{
1216
}
732✔
1217

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

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

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

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

1241
  return source_moments;
4✔
1242
}
4✔
1243

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

1249
  const auto& sdm = *discretization_;
2,756✔
1250
  const auto& phi_uk_man = flux_moments_uk_man_;
2,756✔
1251

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

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

1260
    for (const auto& cell : grid_->local_cells)
24,449,908✔
1261
    {
1262
      const auto& cell_mapping = sdm.GetCellMapping(cell);
24,364,921✔
1263
      const size_t num_nodes = cell_mapping.GetNumNodes();
24,364,921✔
1264

1265
      for (size_t i = 0; i < num_nodes; ++i)
164,655,997✔
1266
      {
1267
        const auto imapA = sdm.MapDOFLocal(cell, i, phi_uk_man, m, g);
140,291,076✔
1268
        const auto imapB = sdm.MapDOFLocal(cell, i);
140,291,076✔
1269

1270
        data_vector_local[imapB] = phi_new_local_[imapA];
140,291,076✔
1271
      } // for node
1272
    } // for cell
1273

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

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

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

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

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

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

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

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

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

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

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

1325
    const size_t ff_index = power_gen_fieldfunc_local_handle_;
4✔
1326

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

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

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

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

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

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

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

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

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

1393
LBSProblem::~LBSProblem()
564✔
1394
{
1395
  ResetGPUCarriers();
1396
}
2,820✔
1397

564✔
1398
void
1399
LBSProblem::SetSaveAngularFlux(bool save)
48✔
1400
{
1401
  options_.save_angular_flux = save;
48✔
1402
  if (discretization_)
48✔
UNCOV
1403
    applied_save_angular_flux_ = save;
×
1404
}
48✔
1405

1406
void
NEW
1407
LBSProblem::ZeroPhi()
×
1408
{
NEW
1409
  phi_old_local_.assign(phi_old_local_.size(), 0.0);
×
NEW
1410
  phi_new_local_.assign(phi_new_local_.size(), 0.0);
×
NEW
1411
}
×
1412

1413
void
1414
LBSProblem::SetAdjoint(bool adjoint)
600✔
1415
{
1416
  if (adjoint and discretization_)
600✔
1417
    if (const auto* do_problem = dynamic_cast<const DiscreteOrdinatesProblem*>(this);
36✔
1418
        do_problem and do_problem->IsTimeDependent())
72✔
UNCOV
1419
      throw std::runtime_error(GetName() + ": Time-dependent adjoint problems are not supported.");
×
1420

1421
  options_.adjoint = adjoint;
600✔
1422

1423
  if (not discretization_)
600✔
1424
    return;
1425

1426
  if (adjoint != applied_adjoint_)
600✔
1427
  {
1428
    // If a discretization exists, the solver has already been initialized.
1429
    // Reinitialize the materials to obtain the appropriate xs and clear the
1430
    // sources to prepare for defining the adjoint problem
1431
    // The materials are reinitialized here to ensure that the proper cross sections
1432
    // are available to the solver. Because an adjoint solve requires volumetric or
1433
    // point sources, the material-based sources are not set within the initialize routine.
1434
    InitializeMaterials();
24✔
1435

1436
    // Forward and adjoint sources are fundamentally different, so any existing sources
1437
    // should be cleared and reset through options upon changing modes.
1438
    point_sources_.clear();
24✔
1439
    volumetric_sources_.clear();
24✔
1440
    ClearBoundaries();
24✔
1441

1442
    // Set all solutions to zero.
1443
    phi_old_local_.assign(phi_old_local_.size(), 0.0);
24✔
1444
    phi_new_local_.assign(phi_new_local_.size(), 0.0);
24✔
1445
    ZeroPsi();
24✔
1446
    precursor_new_local_.assign(precursor_new_local_.size(), 0.0);
24✔
1447

1448
    applied_adjoint_ = adjoint;
24✔
1449
  }
1450
}
1451

1452
bool
UNCOV
1453
LBSProblem::IsAdjoint() const
×
1454
{
UNCOV
1455
  return options_.adjoint;
×
1456
}
1457

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