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

Open-Sn / opensn / 22607884645

01 Mar 2026 08:01PM UTC coverage: 74.192% (+0.04%) from 74.152%
22607884645

push

github

web-flow
Merge pull request #956 from wdhawkins/refactor_problem

Refactor problem creation to be atomic and clean up mode switching

72 of 101 new or added lines in 6 files covered. (71.29%)

5 existing lines in 4 files now uncovered.

20123 of 27123 relevant lines covered (74.19%)

66818235.8 hits per line

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

80.87
/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()
578✔
33
{
34
  InputParameters params = Problem::GetInputParameters();
578✔
35

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

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

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

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

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

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

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

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

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

63
  return params;
578✔
64
}
×
65

66
LBSProblem::LBSProblem(const InputParameters& params)
578✔
67
  : Problem(params),
68
    num_groups_(params.GetParamValue<unsigned int>("num_groups")),
578✔
69
    grid_(params.GetSharedPtrParam<MeshContinuum>("mesh")),
578✔
70
    use_gpus_(params.GetParamValue<bool>("use_gpus"))
1,734✔
71
{
72
  // Check system for GPU acceleration
73
  if (use_gpus_)
578✔
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"))
578✔
85
  {
86
    auto options_params = LBSProblem::GetOptionsBlock();
350✔
87
    options_params.AssignParameters(params.GetParam("options"));
352✔
88
    ParseOptions(options_params);
348✔
89
  }
350✔
90
  applied_adjoint_ = options_.adjoint;
576✔
91
  applied_save_angular_flux_ = options_.save_angular_flux;
576✔
92

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

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

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

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

116
double
117
LBSProblem::GetTime() const
422,208✔
118
{
119
  return time_;
422,208✔
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
NEW
157
LBSProblem::IsTimeDependent() const
×
158
{
NEW
159
  return false;
×
160
}
161

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

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

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

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

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

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

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

205
unsigned int
206
LBSProblem::GetNumGroups() const
819,298✔
207
{
208
  return num_groups_;
819,298✔
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,377,258✔
231
{
232
  return groupsets_;
21,377,258✔
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);
×
NEW
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,214✔
283
{
284
  return block_id_to_xs_map_;
19,214✔
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
750,259✔
298
{
299
  return grid_;
750,259✔
300
}
301

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

308
const std::vector<UnitCellMatrices>&
309
LBSProblem::GetUnitCellMatrices() const
20,044✔
310
{
311
  return unit_cell_matrices_;
20,044✔
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()
301,626✔
322
{
323
  return cell_transport_views_;
301,626✔
324
}
325

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

332
const UnknownManager&
333
LBSProblem::GetUnknownManager() const
27,039✔
334
{
335
  return flux_moments_uk_man_;
27,039✔
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,725✔
352
{
353
  return q_moments_local_;
357,725✔
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,496✔
370
{
371
  return ext_src_moments_local_;
210,496✔
372
}
373

374
std::vector<double>&
375
LBSProblem::GetPhiOldLocal()
277,427✔
376
{
377
  return phi_old_local_;
277,427✔
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()
248,866✔
388
{
389
  return phi_new_local_;
248,866✔
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()
985✔
412
{
413
  return densities_local_;
985✔
414
}
415

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

422
SetSourceFunction
423
LBSProblem::GetActiveSetSourceFunction() const
4,270✔
424
{
425
  return active_set_source_function_;
4,270✔
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,532✔
448
{
449
  auto& wgs_solver = wgs_solvers_[groupset_id];
11,532✔
450
  auto raw_context = wgs_solver->GetContext();
11,532✔
451
  auto wgs_context_ptr = std::dynamic_pointer_cast<WGSContext>(raw_context);
11,532✔
452
  OpenSnLogicalErrorIf(not wgs_context_ptr, "Failed to cast WGSContext");
11,532✔
453
  return *wgs_context_ptr;
11,532✔
454
}
23,064✔
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
size_t
467
LBSProblem::MapPhiFieldFunction(unsigned int g, unsigned int m) const
28,984✔
468
{
469
  OpenSnLogicalErrorIf(phi_field_functions_local_map_.count({g, m}) == 0,
28,984✔
470
                       std::string("Failure to map phi field function g") + std::to_string(g) +
471
                         " m" + std::to_string(m));
472

473
  return phi_field_functions_local_map_.at({g, m});
28,984✔
474
}
475

476
std::shared_ptr<FieldFunctionGridBased>
477
LBSProblem::GetPowerFieldFunction() const
×
478
{
479
  OpenSnLogicalErrorIf(not options_.power_field_function_on,
×
480
                       "Called when options_.power_field_function_on == false");
481

482
  return field_functions_[power_gen_fieldfunc_local_handle_];
×
483
}
484

485
InputParameters
486
LBSProblem::GetOptionsBlock()
722✔
487
{
488
  InputParameters params;
722✔
489

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

568
  return params;
722✔
569
}
×
570

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

581
void
582
LBSProblem::SetOptions(const InputParameters& input)
12✔
583
{
584
  ParseOptions(input);
12✔
585
  ApplyOptions();
12✔
586
}
12✔
587

588
void
589
LBSProblem::ParseOptions(const InputParameters& input)
360✔
590
{
591
  auto params = LBSProblem::GetOptionsBlock();
360✔
592
  params.AssignParameters(input);
360✔
593
  const auto& params_at_assignment = input.GetParametersAtAssignment();
360✔
594
  const auto& specified_params = params_at_assignment.GetNumParameters() > 0
360✔
595
                                   ? params_at_assignment
360✔
596
                                   : static_cast<const ParameterBlock&>(input);
360✔
597

598
  // Apply only options explicitly specified by the caller.
599
  for (const auto& spec : specified_params.GetParameters())
1,327✔
600
  {
601
    if (spec.GetName() == "max_mpi_message_size")
967✔
602
      options_.max_mpi_message_size = spec.GetValue<int>();
×
603

604
    else if (spec.GetName() == "restart_writes_enabled")
967✔
605
      options_.restart_writes_enabled = spec.GetValue<bool>();
×
606

607
    else if (spec.GetName() == "write_delayed_psi_to_restart")
967✔
608
      options_.write_delayed_psi_to_restart = spec.GetValue<bool>();
×
609

610
    else if (spec.GetName() == "read_restart_path")
967✔
611
    {
612
      options_.read_restart_path = spec.GetValue<std::string>();
12✔
613
      if (not options_.read_restart_path.empty())
12✔
614
        options_.read_restart_path += std::to_string(opensn::mpi_comm.rank()) + ".restart.h5";
36✔
615
    }
616

617
    else if (spec.GetName() == "write_restart_path")
955✔
618
    {
619
      options_.write_restart_path = spec.GetValue<std::string>();
×
620
      if (not options_.write_restart_path.empty())
×
621
        options_.write_restart_path += std::to_string(opensn::mpi_comm.rank()) + ".restart.h5";
×
622
    }
623

624
    else if (spec.GetName() == "write_restart_time_interval")
955✔
625
      options_.write_restart_time_interval = std::chrono::seconds(spec.GetValue<int>());
×
626

627
    else if (spec.GetName() == "use_precursors")
955✔
628
      options_.use_precursors = spec.GetValue<bool>();
120✔
629

630
    else if (spec.GetName() == "use_source_moments")
835✔
631
      options_.use_src_moments = spec.GetValue<bool>();
4✔
632

633
    else if (spec.GetName() == "save_angular_flux")
831✔
634
      options_.save_angular_flux = spec.GetValue<bool>();
198✔
635

636
    else if (spec.GetName() == "verbose_inner_iterations")
633✔
637
      options_.verbose_inner_iterations = spec.GetValue<bool>();
211✔
638

639
    else if (spec.GetName() == "max_ags_iterations")
422✔
640
      options_.max_ags_iterations = spec.GetValue<int>();
50✔
641

642
    else if (spec.GetName() == "ags_tolerance")
372✔
643
      options_.ags_tolerance = spec.GetValue<double>();
8✔
644

645
    else if (spec.GetName() == "ags_convergence_check")
364✔
646
    {
647
      auto check = spec.GetValue<std::string>();
×
648
      options_.ags_pointwise_convergence = (check == "pointwise");
×
649
    }
×
650

651
    else if (spec.GetName() == "verbose_ags_iterations")
364✔
652
      options_.verbose_ags_iterations = spec.GetValue<bool>();
128✔
653

654
    else if (spec.GetName() == "verbose_outer_iterations")
236✔
655
      options_.verbose_outer_iterations = spec.GetValue<bool>();
187✔
656

657
    else if (spec.GetName() == "power_field_function_on")
49✔
658
      options_.power_field_function_on = spec.GetValue<bool>();
4✔
659

660
    else if (spec.GetName() == "power_default_kappa")
45✔
661
      options_.power_default_kappa = spec.GetValue<double>();
12✔
662

663
    else if (spec.GetName() == "power_normalization")
33✔
664
      options_.power_normalization = spec.GetValue<double>();
12✔
665

666
    else if (spec.GetName() == "field_function_prefix_option")
21✔
667
    {
668
      options_.field_function_prefix_option = spec.GetValue<std::string>();
×
669
    }
670

671
    else if (spec.GetName() == "field_function_prefix")
21✔
672
      options_.field_function_prefix = spec.GetValue<std::string>();
1✔
673

674
    else if (spec.GetName() == "adjoint")
20✔
675
      options_.adjoint = spec.GetValue<bool>();
20✔
676

677
  } // for specified options
678

679
  OpenSnInvalidArgumentIf(options_.write_restart_time_interval > std::chrono::seconds(0) and
360✔
680
                            not options_.restart_writes_enabled,
681
                          GetName() + ": `write_restart_time_interval>0` requires "
682
                                      "`restart_writes_enabled=true`.");
683

684
  OpenSnInvalidArgumentIf(options_.write_restart_time_interval > std::chrono::seconds(0) and
360✔
685
                            options_.write_restart_time_interval < std::chrono::seconds(30),
686
                          GetName() + ": `write_restart_time_interval` must be 0 (disabled) "
687
                                      "or at least 30 seconds.");
688

689
  OpenSnInvalidArgumentIf(options_.restart_writes_enabled and options_.write_restart_path.empty(),
360✔
690
                          GetName() + ": `restart_writes_enabled=true` requires a non-empty "
691
                                      "`write_restart_path`.");
692

693
  OpenSnInvalidArgumentIf(not options_.field_function_prefix.empty() and
360✔
694
                            options_.field_function_prefix_option != "prefix",
695
                          GetName() + ": non-empty `field_function_prefix` requires "
696
                                      "`field_function_prefix_option=\"prefix\"`.");
697

698
  if (options_.restart_writes_enabled)
360✔
699
  {
700
    const auto dir = options_.write_restart_path.parent_path();
×
701

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

721
void
722
LBSProblem::ApplyOptions()
12✔
723
{
724
  if (not initialized_)
12✔
725
    return;
726

727
  if (options_.adjoint != applied_adjoint_)
12✔
728
    SetAdjoint(options_.adjoint);
4✔
729

730
  if (options_.save_angular_flux != applied_save_angular_flux_)
12✔
731
    SetSaveAngularFlux(options_.save_angular_flux);
×
732
}
733

734
void
735
LBSProblem::BuildRuntime()
576✔
736
{
737
  CALI_CXX_MARK_SCOPE("LBSProblem::BuildRuntime");
576✔
738
  if (initialized_)
576✔
NEW
739
    return;
×
740

741
  PrintSimHeader();
576✔
742
  mpi_comm.barrier();
576✔
743

744
  InitializeSpatialDiscretization();
576✔
745
  InitializeParrays();
576✔
746
  InitializeBoundaries();
576✔
747
  InitializeGPUExtras();
576✔
748
  if (options_.adjoint)
576✔
749
    if (const auto* do_problem = dynamic_cast<const DiscreteOrdinatesProblem*>(this);
16✔
750
        do_problem and do_problem->IsTimeDependent())
16✔
NEW
751
      throw std::runtime_error(GetName() + ": Time-dependent adjoint problems are not supported.");
×
752

753
  // Initialize point sources
754
  for (auto& point_source : point_sources_)
585✔
755
    point_source->Initialize(*this);
9✔
756

757
  // Initialize volumetric sources
758
  for (auto& volumetric_source : volumetric_sources_)
1,059✔
759
    volumetric_source->Initialize(*this);
483✔
760

761
  initialized_ = true;
576✔
762
}
576✔
763

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

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

793
    log.Log() << outstr.str() << '\n';
×
794
  }
×
795
}
×
796

797
void
798
LBSProblem::InitializeSources(const InputParameters& params)
576✔
799
{
800
  if (params.Has("volumetric_sources"))
576✔
801
  {
802
    const auto& vol_srcs = params.GetParam("volumetric_sources");
576✔
803
    vol_srcs.RequireBlockTypeIs(ParameterBlockType::ARRAY);
576✔
804
    for (const auto& src : vol_srcs)
1,059✔
805
      volumetric_sources_.push_back(src.GetValue<std::shared_ptr<VolumetricSource>>());
966✔
806
  }
807

808
  if (params.Has("point_sources"))
576✔
809
  {
810
    const auto& pt_srcs = params.GetParam("point_sources");
576✔
811
    pt_srcs.RequireBlockTypeIs(ParameterBlockType::ARRAY);
576✔
812
    for (const auto& src : pt_srcs)
585✔
813
      point_sources_.push_back(src.GetValue<std::shared_ptr<PointSource>>());
18✔
814
  }
815
}
576✔
816

817
void
818
LBSProblem::InitializeGroupsets(const InputParameters& params)
576✔
819
{
820
  // Initialize groups
821
  if (num_groups_ == 0)
576✔
822
    throw std::invalid_argument(GetName() + ": Number of groups must be > 0");
×
823

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

845
void
846
LBSProblem::InitializeXSmapAndDensities(const InputParameters& params)
576✔
847
{
848
  // Build XS map
849
  const auto& xs_array = params.GetParam("xs_map");
576✔
850
  const size_t num_xs = xs_array.GetNumParameters();
576✔
851
  for (size_t i = 0; i < num_xs; ++i)
1,409✔
852
  {
853
    const auto& item_params = xs_array.GetParam(i);
833✔
854
    InputParameters xs_entry_pars = GetXSMapEntryBlock();
833✔
855
    xs_entry_pars.AssignParameters(item_params);
833✔
856

857
    const auto& block_ids_param = xs_entry_pars.GetParam("block_ids");
833✔
858
    block_ids_param.RequireBlockTypeIs(ParameterBlockType::ARRAY);
833✔
859
    const auto& block_ids = block_ids_param.GetVectorValue<unsigned int>();
833✔
860
    auto xs = xs_entry_pars.GetSharedPtrParam<MultiGroupXS>("xs");
833✔
861
    for (const auto& block_id : block_ids)
1,774✔
862
      block_id_to_xs_map_[block_id] = xs;
941✔
863
  }
833✔
864

865
  // Assign placeholder unit densities
866
  densities_local_.assign(grid_->local_cells.size(), 1.0);
576✔
867
}
576✔
868

869
void
870
LBSProblem::InitializeMaterials()
768✔
871
{
872
  CALI_CXX_MARK_SCOPE("LBSProblem::InitializeMaterials");
768✔
873

874
  log.Log0Verbose1() << "Initializing Materials";
1,536✔
875

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

899
  // Get ready for processing
900
  for (const auto& [blk_id, mat] : block_id_to_xs_map_)
1,925✔
901
  {
902
    mat->SetAdjointMode(options_.adjoint);
1,157✔
903

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

911
  // Initialize precursor properties
912
  num_precursors_ = 0;
768✔
913
  max_precursors_per_material_ = 0;
768✔
914
  for (const auto& mat_id_xs : block_id_to_xs_map_)
1,925✔
915
  {
916
    const auto& xs = mat_id_xs.second;
1,157✔
917
    num_precursors_ += xs->GetNumPrecursors();
1,157✔
918
    max_precursors_per_material_ = std::max(xs->GetNumPrecursors(), max_precursors_per_material_);
1,157✔
919
  }
920

921
  // if no precursors, turn off precursors
922
  if (num_precursors_ == 0)
768✔
923
    options_.use_precursors = false;
572✔
924

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

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

947
  mpi_comm.barrier();
768✔
948
}
768✔
949

950
void
951
LBSProblem::InitializeSpatialDiscretization()
496✔
952
{
953
  CALI_CXX_MARK_SCOPE("LBSProblem::InitializeSpatialDiscretization");
496✔
954

955
  OpenSnLogicalErrorIf(not discretization_,
496✔
956
                       GetName() + ": Missing spatial discretization. Construct the problem "
957
                                   "through its factory Create(...) entry point.");
958
  log.Log() << "Initializing spatial discretization metadata.\n";
992✔
959

960
  ComputeUnitIntegrals();
496✔
961
}
496✔
962

963
void
964
LBSProblem::ComputeUnitIntegrals()
576✔
965
{
966
  CALI_CXX_MARK_SCOPE("LBSProblem::ComputeUnitIntegrals");
576✔
967

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

971
  const size_t num_local_cells = grid_->local_cells.size();
576✔
972
  unit_cell_matrices_.resize(num_local_cells);
576✔
973

974
  for (const auto& cell : grid_->local_cells)
573,325✔
975
    unit_cell_matrices_[cell.local_id] =
572,749✔
976
      ComputeUnitCellIntegrals(sdm, cell, grid_->GetCoordinateSystem());
572,749✔
977

978
  const auto ghost_ids = grid_->cells.GetGhostGlobalIDs();
576✔
979
  for (auto ghost_id : ghost_ids)
90,325✔
980
    unit_ghost_cell_matrices_[ghost_id] =
89,749✔
981
      ComputeUnitCellIntegrals(sdm, grid_->cells[ghost_id], grid_->GetCoordinateSystem());
179,498✔
982

983
  // Assessing global unit cell matrix storage
984
  std::array<size_t, 2> num_local_ucms = {unit_cell_matrices_.size(),
576✔
985
                                          unit_ghost_cell_matrices_.size()};
576✔
986
  std::array<size_t, 2> num_global_ucms = {0, 0};
576✔
987

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

990
  opensn::mpi_comm.barrier();
576✔
991
  log.Log() << "Ghost cell unit cell-matrix ratio: "
576✔
992
            << (double)num_global_ucms[1] * 100 / (double)num_global_ucms[0] << "%";
1,152✔
993
  log.Log() << "Cell matrices computed.";
1,152✔
994
}
576✔
995

996
void
997
LBSProblem::InitializeParrays()
576✔
998
{
999
  CALI_CXX_MARK_SCOPE("LBSProblem::InitializeParrays");
576✔
1000

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

1004
  // Initialize unknown
1005
  // structure
1006
  flux_moments_uk_man_.unknowns.clear();
576✔
1007
  for (unsigned int m = 0; m < num_moments_; ++m)
1,726✔
1008
  {
1009
    flux_moments_uk_man_.AddUnknown(UnknownType::VECTOR_N, num_groups_);
1,150✔
1010
    flux_moments_uk_man_.unknowns.back().name = "m" + std::to_string(m);
1,150✔
1011
  }
1012

1013
  // Compute local # of dof
1014
  local_node_count_ = discretization_->GetNumLocalNodes();
576✔
1015
  global_node_count_ = discretization_->GetNumGlobalNodes();
576✔
1016

1017
  // Compute num of unknowns
1018
  size_t local_unknown_count = local_node_count_ * num_groups_ * num_moments_;
576✔
1019

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

1022
  // Size local vectors
1023
  q_moments_local_.assign(local_unknown_count, 0.0);
576✔
1024
  phi_old_local_.assign(local_unknown_count, 0.0);
576✔
1025
  phi_new_local_.assign(local_unknown_count, 0.0);
576✔
1026

1027
  // Setup precursor vector
1028
  if (options_.use_precursors)
576✔
1029
  {
1030
    size_t num_precursor_dofs = grid_->local_cells.size() * max_precursors_per_material_;
60✔
1031
    precursor_new_local_.assign(num_precursor_dofs, 0.0);
60✔
1032
  }
1033

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

1047
  const Vector3 ihat(1.0, 0.0, 0.0);
576✔
1048
  const Vector3 jhat(0.0, 1.0, 0.0);
576✔
1049
  const Vector3 khat(0.0, 0.0, 1.0);
576✔
1050

1051
  min_cell_dof_count_ = std::numeric_limits<unsigned int>::max();
576✔
1052
  max_cell_dof_count_ = 0;
576✔
1053
  cell_transport_views_.clear();
576✔
1054
  cell_transport_views_.reserve(grid_->local_cells.size());
576✔
1055
  for (auto& cell : grid_->local_cells)
573,325✔
1056
  {
1057
    size_t num_nodes = discretization_->GetCellNumNodes(cell);
572,749✔
1058

1059
    // compute cell volumes
1060
    double cell_volume = 0.0;
572,749✔
1061
    const auto& IntV_shapeI = unit_cell_matrices_[cell.local_id].intV_shapeI;
572,749✔
1062
    for (size_t i = 0; i < num_nodes; ++i)
3,971,101✔
1063
      cell_volume += IntV_shapeI(i);
3,398,352✔
1064

1065
    size_t cell_phi_address = block_MG_counter;
572,749✔
1066

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

1089
      ++f;
2,830,872✔
1090
    } // for f
1091

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

1108
  // Populate grid nodal mappings
1109
  // This is used in the Flux Data Structures (FLUDS)
1110
  grid_nodal_mappings_.clear();
576✔
1111
  grid_nodal_mappings_.reserve(grid_->local_cells.size());
576✔
1112
  for (auto& cell : grid_->local_cells)
573,325✔
1113
  {
1114
    CellFaceNodalMapping cell_nodal_mapping;
572,749✔
1115
    cell_nodal_mapping.reserve(cell.faces.size());
572,749✔
1116

1117
    for (auto& face : cell.faces)
3,403,621✔
1118
    {
1119
      std::vector<short> face_node_mapping;
2,830,872✔
1120
      std::vector<short> cell_node_mapping;
2,830,872✔
1121
      int adj_face_idx = -1;
2,830,872✔
1122

1123
      if (face.has_neighbor)
2,830,872✔
1124
      {
1125
        grid_->FindAssociatedVertices(face, face_node_mapping);
2,739,192✔
1126
        grid_->FindAssociatedCellVertices(face, cell_node_mapping);
2,739,192✔
1127
        adj_face_idx = face.GetNeighborAdjacentFaceIndex(grid_.get());
2,739,192✔
1128
      }
1129

1130
      cell_nodal_mapping.emplace_back(adj_face_idx, face_node_mapping, cell_node_mapping);
2,830,872✔
1131
    } // for f
2,830,872✔
1132

1133
    grid_nodal_mappings_.push_back(cell_nodal_mapping);
572,749✔
1134
  } // for local cell
572,749✔
1135

1136
  // Get grid localized communicator set
1137
  grid_local_comm_set_ = grid_->MakeMPILocalCommunicatorSet();
576✔
1138

1139
  // Initialize Field Functions
1140
  InitializeFieldFunctions();
576✔
1141

1142
  opensn::mpi_comm.barrier();
576✔
1143
  log.Log() << "Done with parallel arrays." << std::endl;
1,152✔
1144
}
576✔
1145

1146
void
1147
LBSProblem::InitializeFieldFunctions()
576✔
1148
{
1149
  CALI_CXX_MARK_SCOPE("LBSProblem::InitializeFieldFunctions");
576✔
1150

1151
  if (not field_functions_.empty())
576✔
1152
    return;
×
1153

1154
  // Initialize Field Functions for flux moments
1155
  phi_field_functions_local_map_.clear();
576✔
1156

1157
  for (unsigned int g = 0; g < num_groups_; ++g)
23,241✔
1158
  {
1159
    for (unsigned int m = 0; m < num_moments_; ++m)
100,648✔
1160
    {
1161
      std::string prefix;
77,983✔
1162
      if (options_.field_function_prefix_option == "prefix")
77,983✔
1163
      {
1164
        prefix = options_.field_function_prefix;
77,983✔
1165
        if (not prefix.empty())
77,983✔
1166
          prefix += "_";
1✔
1167
      }
1168
      if (options_.field_function_prefix_option == "solver_name")
77,983✔
1169
        prefix = GetName() + "_";
×
1170

1171
      std::ostringstream oss;
77,983✔
1172
      oss << prefix << "phi_g" << std::setw(3) << std::setfill('0') << static_cast<int>(g) << "_m"
77,983✔
1173
          << std::setw(2) << std::setfill('0') << static_cast<int>(m);
77,983✔
1174
      const std::string name = oss.str();
77,983✔
1175

1176
      auto group_ff = std::make_shared<FieldFunctionGridBased>(
77,983✔
1177
        name, discretization_, Unknown(UnknownType::SCALAR));
77,983✔
1178

1179
      field_function_stack.push_back(group_ff);
155,966✔
1180
      field_functions_.push_back(group_ff);
77,983✔
1181

1182
      phi_field_functions_local_map_[{g, m}] = field_functions_.size() - 1;
77,983✔
1183
    } // for m
77,983✔
1184
  } // for g
1185

1186
  // Initialize power generation field function
1187
  if (options_.power_field_function_on)
576✔
1188
  {
1189
    std::string prefix;
4✔
1190
    if (options_.field_function_prefix_option == "prefix")
4✔
1191
    {
1192
      prefix = options_.field_function_prefix;
4✔
1193
      if (not prefix.empty())
4✔
1194
        prefix += "_";
×
1195
    }
1196
    if (options_.field_function_prefix_option == "solver_name")
4✔
1197
      prefix = GetName() + "_";
×
1198

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

1202
    field_function_stack.push_back(power_ff);
8✔
1203
    field_functions_.push_back(power_ff);
4✔
1204

1205
    power_gen_fieldfunc_local_handle_ = field_functions_.size() - 1;
4✔
1206
  }
4✔
1207
}
576✔
1208

1209
void
1210
LBSProblem::InitializeSolverSchemes()
676✔
1211
{
1212
  CALI_CXX_MARK_SCOPE("LBSProblem::InitializeSolverSchemes");
676✔
1213
  InitializeWGSSolvers();
676✔
1214

1215
  ags_solver_ = std::make_shared<AGSLinearSolver>(*this, wgs_solvers_);
676✔
1216
  if (groupsets_.size() == 1)
676✔
1217
  {
1218
    ags_solver_->SetMaxIterations(1);
621✔
1219
    ags_solver_->SetVerbosity(false);
621✔
1220
  }
1221
  else
1222
  {
1223
    ags_solver_->SetMaxIterations(options_.max_ags_iterations);
55✔
1224
    ags_solver_->SetVerbosity(options_.verbose_ags_iterations);
55✔
1225
  }
1226
  ags_solver_->SetTolerance(options_.ags_tolerance);
676✔
1227
}
676✔
1228

1229
#ifndef __OPENSN_WITH_GPU__
1230
void
1231
LBSProblem::InitializeGPUExtras()
744✔
1232
{
1233
}
744✔
1234

1235
void
1236
LBSProblem::ResetGPUCarriers()
732✔
1237
{
1238
}
732✔
1239

1240
void
1241
LBSProblem::CheckCapableDevices()
×
1242
{
1243
}
×
1244
#endif // __OPENSN_WITH_GPU__
1245

1246
std::vector<double>
1247
LBSProblem::MakeSourceMomentsFromPhi()
4✔
1248
{
1249
  CALI_CXX_MARK_SCOPE("LBSProblem::MakeSourceMomentsFromPhi");
4✔
1250

1251
  size_t num_local_dofs = discretization_->GetNumLocalDOFs(flux_moments_uk_man_);
4✔
1252

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

1263
  return source_moments;
4✔
1264
}
4✔
1265

1266
void
1267
LBSProblem::UpdateFieldFunctions()
2,756✔
1268
{
1269
  CALI_CXX_MARK_SCOPE("LBSProblem::UpdateFieldFunctions");
2,756✔
1270

1271
  const auto& sdm = *discretization_;
2,756✔
1272
  const auto& phi_uk_man = flux_moments_uk_man_;
2,756✔
1273

1274
  // Update flux moments
1275
  for (const auto& [g_and_m, ff_index] : phi_field_functions_local_map_)
87,743✔
1276
  {
1277
    const auto g = g_and_m.first;
84,987✔
1278
    const auto m = g_and_m.second;
84,987✔
1279

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

1282
    for (const auto& cell : grid_->local_cells)
24,449,908✔
1283
    {
1284
      const auto& cell_mapping = sdm.GetCellMapping(cell);
24,364,921✔
1285
      const size_t num_nodes = cell_mapping.GetNumNodes();
24,364,921✔
1286

1287
      for (size_t i = 0; i < num_nodes; ++i)
164,655,997✔
1288
      {
1289
        const auto imapA = sdm.MapDOFLocal(cell, i, phi_uk_man, m, g);
140,291,076✔
1290
        const auto imapB = sdm.MapDOFLocal(cell, i);
140,291,076✔
1291

1292
        data_vector_local[imapB] = phi_new_local_[imapA];
140,291,076✔
1293
      } // for node
1294
    } // for cell
1295

1296
    auto& ff_ptr = field_functions_.at(ff_index);
84,987✔
1297
    ff_ptr->UpdateFieldVector(data_vector_local);
84,987✔
1298
  }
84,987✔
1299

1300
  // Update power generation and scalar flux
1301
  if (options_.power_field_function_on)
2,756✔
1302
  {
1303
    std::vector<double> data_vector_power_local(local_node_count_, 0.0);
4✔
1304

1305
    double local_total_power = 0.0;
4✔
1306
    for (const auto& cell : grid_->local_cells)
83,268✔
1307
    {
1308
      const auto& cell_mapping = sdm.GetCellMapping(cell);
83,264✔
1309
      const size_t num_nodes = cell_mapping.GetNumNodes();
83,264✔
1310

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

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

1315
      if (not xs->IsFissionable())
83,264✔
1316
        continue;
56,360✔
1317

1318
      for (size_t i = 0; i < num_nodes; ++i)
134,520✔
1319
      {
1320
        const auto imapA = sdm.MapDOFLocal(cell, i);
107,616✔
1321
        const auto imapB = sdm.MapDOFLocal(cell, i, phi_uk_man, 0, 0);
107,616✔
1322

1323
        double nodal_power = 0.0;
1324
        for (unsigned int g = 0; g < num_groups_; ++g)
860,928✔
1325
        {
1326
          const double sigma_fg = xs->GetSigmaFission()[g];
753,312✔
1327
          // const double kappa_g = xs->Kappa()[g];
1328
          const double kappa_g = options_.power_default_kappa;
753,312✔
1329

1330
          nodal_power += kappa_g * sigma_fg * phi_new_local_[imapB + g];
753,312✔
1331
        } // for g
1332

1333
        data_vector_power_local[imapA] = nodal_power;
107,616✔
1334
        local_total_power += nodal_power * Vi(i);
107,616✔
1335
      } // for node
1336
    } // for cell
1337

1338
    double scale_factor = 1.0;
4✔
1339
    if (options_.power_normalization > 0.0)
4✔
1340
    {
1341
      double global_total_power = 0.0;
4✔
1342
      mpi_comm.all_reduce(local_total_power, global_total_power, mpi::op::sum<double>());
4✔
1343
      scale_factor = options_.power_normalization / global_total_power;
4✔
1344
      Scale(data_vector_power_local, scale_factor);
4✔
1345
    }
1346

1347
    const size_t ff_index = power_gen_fieldfunc_local_handle_;
4✔
1348

1349
    auto& ff_ptr = field_functions_.at(ff_index);
4✔
1350
    ff_ptr->UpdateFieldVector(data_vector_power_local);
4✔
1351

1352
    // scale scalar flux if neccessary
1353
    if (scale_factor != 1.0)
4✔
1354
    {
1355
      for (unsigned int g = 0; g < num_groups_; ++g)
32✔
1356
      {
1357
        const size_t phi_ff_index = phi_field_functions_local_map_.at({g, size_t{0}});
28✔
1358
        auto& phi_ff_ptr = field_functions_.at(phi_ff_index);
28✔
1359
        const auto& phi_vec = phi_ff_ptr->GetLocalFieldVector();
28✔
1360
        std::vector<double> phi_scaled(phi_vec.begin(), phi_vec.end());
28✔
1361
        Scale(phi_scaled, scale_factor);
28✔
1362
        phi_ff_ptr->UpdateFieldVector(phi_scaled);
28✔
1363
      }
28✔
1364
    }
1365
  } // if power enabled
4✔
1366
}
2,756✔
1367

1368
void
1369
LBSProblem::SetPhiFromFieldFunctions(PhiSTLOption which_phi,
×
1370
                                     const std::vector<unsigned int>& m_indices,
1371
                                     const std::vector<unsigned int>& g_indices)
1372
{
1373
  CALI_CXX_MARK_SCOPE("LBSProblem::SetPhiFromFieldFunctions");
×
1374

1375
  std::vector<unsigned int> m_ids_to_copy = m_indices;
×
1376
  std::vector<unsigned int> g_ids_to_copy = g_indices;
×
1377
  if (m_indices.empty())
×
1378
    for (unsigned int m = 0; m < num_moments_; ++m)
×
1379
      m_ids_to_copy.push_back(m);
×
1380
  if (g_ids_to_copy.empty())
×
1381
    for (unsigned int g = 0; g < num_groups_; ++g)
×
1382
      g_ids_to_copy.push_back(g);
×
1383

1384
  const auto& sdm = *discretization_;
×
1385
  const auto& phi_uk_man = flux_moments_uk_man_;
×
1386

1387
  for (const auto m : m_ids_to_copy)
×
1388
  {
1389
    for (const auto g : g_ids_to_copy)
×
1390
    {
1391
      const size_t ff_index = phi_field_functions_local_map_.at({g, m});
×
1392
      const auto& ff_ptr = field_functions_.at(ff_index);
×
1393
      const auto& ff_data = ff_ptr->GetLocalFieldVector();
×
1394

1395
      for (const auto& cell : grid_->local_cells)
×
1396
      {
1397
        const auto& cell_mapping = sdm.GetCellMapping(cell);
×
1398
        const size_t num_nodes = cell_mapping.GetNumNodes();
×
1399

1400
        for (size_t i = 0; i < num_nodes; ++i)
×
1401
        {
1402
          const auto imapA = sdm.MapDOFLocal(cell, i);
×
1403
          const auto imapB = sdm.MapDOFLocal(cell, i, phi_uk_man, m, g);
×
1404

1405
          if (which_phi == PhiSTLOption::PHI_OLD)
×
1406
            phi_old_local_[imapB] = ff_data[imapA];
×
1407
          else if (which_phi == PhiSTLOption::PHI_NEW)
×
1408
            phi_new_local_[imapB] = ff_data[imapA];
×
1409
        } // for node
1410
      } // for cell
1411
    } // for g
1412
  } // for m
1413
}
×
1414

1415
LBSProblem::~LBSProblem()
564✔
1416
{
1417
  ResetGPUCarriers();
1418
}
2,820✔
1419

564✔
1420
void
1421
LBSProblem::SetSaveAngularFlux(bool save)
48✔
1422
{
1423
  options_.save_angular_flux = save;
48✔
1424
  if (initialized_)
48✔
1425
    applied_save_angular_flux_ = save;
4✔
1426
}
48✔
1427

1428
void
1429
LBSProblem::ZeroPhi()
×
1430
{
1431
  phi_old_local_.assign(phi_old_local_.size(), 0.0);
×
1432
  phi_new_local_.assign(phi_new_local_.size(), 0.0);
×
1433
}
×
1434

1435
void
1436
LBSProblem::SetAdjoint(bool adjoint)
24✔
1437
{
1438
  OpenSnLogicalErrorIf(
24✔
1439
    not initialized_, GetName() + ": Problem must be fully constructed before calling SetAdjoint.");
1440

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

1446
  options_.adjoint = adjoint;
24✔
1447

1448
  if (adjoint != applied_adjoint_)
24✔
1449
  {
1450
    // Reinitialize materials to obtain the proper forward/adjoint cross sections.
1451
    InitializeMaterials();
24✔
1452

1453
    // Forward and adjoint sources are fundamentally different.
1454
    point_sources_.clear();
24✔
1455
    volumetric_sources_.clear();
24✔
1456
    ClearBoundaries();
24✔
1457

1458
    // Reset all solution vectors.
1459
    phi_old_local_.assign(phi_old_local_.size(), 0.0);
24✔
1460
    phi_new_local_.assign(phi_new_local_.size(), 0.0);
24✔
1461
    ZeroPsi();
24✔
1462
    precursor_new_local_.assign(precursor_new_local_.size(), 0.0);
24✔
1463

1464
    applied_adjoint_ = adjoint;
24✔
1465
  }
1466
}
24✔
1467

1468
bool
1469
LBSProblem::IsAdjoint() const
×
1470
{
1471
  return options_.adjoint;
×
1472
}
1473

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