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

Open-Sn / opensn / 21892714514

10 Feb 2026 11:58PM UTC coverage: 74.806% (-0.02%) from 74.828%
21892714514

push

github

web-flow
Merge pull request #929 from wdhawkins/iteration_log

Fixing iteration status messages

4 of 4 new or added lines in 1 file covered. (100.0%)

211 existing lines in 16 files now uncovered.

19638 of 26252 relevant lines covered (74.81%)

69654996.54 hits per line

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

82.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/lbs_problem/iterative_methods/wgs_context.h"
6
#include "modules/linear_boltzmann_solvers/lbs_problem/iterative_methods/ags_linear_solver.h"
7
#include "modules/linear_boltzmann_solvers/lbs_problem/point_source/point_source.h"
8
#include "modules/linear_boltzmann_solvers/lbs_problem/groupset/lbs_groupset.h"
9
#include "framework/math/spatial_discretization/finite_element/piecewise_linear/piecewise_linear_discontinuous.h"
10
#include "framework/field_functions/field_function_grid_based.h"
11
#include "framework/materials/multi_group_xs/multi_group_xs.h"
12
#include "framework/mesh/mesh_continuum/mesh_continuum.h"
13
#include "framework/utils/hdf_utils.h"
14
#include "framework/object_factory.h"
15
#include "framework/logging/log.h"
16
#include "framework/runtime.h"
17
#include "framework/data_types/allowable_range.h"
18
#include "caliper/cali.h"
19
#include <algorithm>
20
#include <iomanip>
21
#include <fstream>
22
#include <cstring>
23
#include <cassert>
24
#include <memory>
25
#include <stdexcept>
26
#include <sys/stat.h>
27

28
namespace opensn
29
{
30

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

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

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

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

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

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

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

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

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

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

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

68
  return params;
578✔
UNCOV
69
}
×
70

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

88
  // Initialize options
89
  if (params.IsParameterValid("options"))
578✔
90
  {
91
    auto options_params = LBSProblem::GetOptionsBlock();
346✔
92
    options_params.AssignParameters(params.GetParam("options"));
348✔
93
    SetOptions(options_params);
344✔
94
  }
346✔
95

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

101
  InitializeGroupsets(params);
576✔
102
  InitializeSources(params);
576✔
103
  InitializeXSmapAndDensities(params);
576✔
104
  InitializeMaterials();
576✔
105
}
596✔
106

107
LBSOptions&
108
LBSProblem::GetOptions()
167,951✔
109
{
110
  return options_;
167,951✔
111
}
112

113
const LBSOptions&
114
LBSProblem::GetOptions() const
729,430,956✔
115
{
116
  return options_;
729,430,956✔
117
}
118

119
double
120
LBSProblem::GetTime() const
437,504✔
121
{
122
  return time_;
437,504✔
123
}
124

125
void
126
LBSProblem::SetTime(double time)
5,524✔
127
{
128
  time_ = time;
5,524✔
129
}
5,524✔
130

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

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

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

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

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

165
size_t
166
LBSProblem::GetNumMoments() const
549,585✔
167
{
168
  return num_moments_;
549,585✔
169
}
170

171
unsigned int
172
LBSProblem::GetMaxCellDOFCount() const
759✔
173
{
174
  return max_cell_dof_count_;
759✔
175
}
176

177
unsigned int
178
LBSProblem::GetMinCellDOFCount() const
759✔
179
{
180
  return min_cell_dof_count_;
759✔
181
}
182

183
bool
184
LBSProblem::UseGPUs() const
1,095✔
185
{
186
  return use_gpus_;
1,095✔
187
}
188

189
unsigned int
190
LBSProblem::GetNumGroups() const
841,690✔
191
{
192
  return num_groups_;
841,690✔
193
}
194

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

201
size_t
UNCOV
202
LBSProblem::GetNumPrecursors() const
×
203
{
UNCOV
204
  return num_precursors_;
×
205
}
206

207
size_t
208
LBSProblem::GetMaxPrecursorsPerMaterial() const
9,550✔
209
{
210
  return max_precursors_per_material_;
9,550✔
211
}
212

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

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

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

233
void
234
LBSProblem::ClearPointSources()
×
235
{
236
  point_sources_.clear();
×
UNCOV
237
}
×
238

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

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

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

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

265
const BlockID2XSMap&
266
LBSProblem::GetBlockID2XSMap() const
18,542✔
267
{
268
  return block_id_to_xs_map_;
18,542✔
269
}
270

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

280
std::shared_ptr<MeshContinuum>
281
LBSProblem::GetGrid() const
781,211✔
282
{
283
  return grid_;
781,211✔
284
}
285

286
const SpatialDiscretization&
287
LBSProblem::GetSpatialDiscretization() const
246,156✔
288
{
289
  return *discretization_;
246,156✔
290
}
291

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

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

304
std::vector<CellLBSView>&
305
LBSProblem::GetCellTransportViews()
317,286✔
306
{
307
  return cell_transport_views_;
317,286✔
308
}
309

310
const std::vector<CellLBSView>&
311
LBSProblem::GetCellTransportViews() const
654,432✔
312
{
313
  return cell_transport_views_;
654,432✔
314
}
315

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

322
size_t
323
LBSProblem::GetLocalNodeCount() const
3,292✔
324
{
325
  return local_node_count_;
3,292✔
326
}
327

328
size_t
329
LBSProblem::GetGlobalNodeCount() const
3,040✔
330
{
331
  return global_node_count_;
3,040✔
332
}
333

334
std::vector<double>&
335
LBSProblem::GetQMomentsLocal()
366,713✔
336
{
337
  return q_moments_local_;
366,713✔
338
}
339

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

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

352
const std::vector<double>&
353
LBSProblem::GetExtSrcMomentsLocal() const
218,144✔
354
{
355
  return ext_src_moments_local_;
218,144✔
356
}
357

358
std::vector<double>&
359
LBSProblem::GetPhiOldLocal()
293,043✔
360
{
361
  return phi_old_local_;
293,043✔
362
}
363

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

370
std::vector<double>&
371
LBSProblem::GetPhiNewLocal()
257,506✔
372
{
373
  return phi_new_local_;
257,506✔
374
}
375

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

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

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

394
std::vector<double>&
395
LBSProblem::GetDensitiesLocal()
1,009✔
396
{
397
  return densities_local_;
1,009✔
398
}
399

400
const std::vector<double>&
401
LBSProblem::GetDensitiesLocal() const
218,144✔
402
{
403
  return densities_local_;
218,144✔
404
}
405

406
SetSourceFunction
407
LBSProblem::GetActiveSetSourceFunction() const
4,270✔
408
{
409
  return active_set_source_function_;
4,270✔
410
}
411

412
void
413
LBSProblem::SetActiveSetSourceFunction(SetSourceFunction source_function)
124✔
414
{
415
  active_set_source_function_ = std::move(source_function);
124✔
416
}
124✔
417

418
std::shared_ptr<AGSLinearSolver>
419
LBSProblem::GetAGSSolver()
2,212✔
420
{
421
  return ags_solver_;
2,212✔
422
}
423

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

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

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

UNCOV
447
  return {num_local_phi_dofs, num_global_phi_dofs};
×
448
}
449

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

457
  return phi_field_functions_local_map_.at({g, m});
28,468✔
458
}
459

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

466
  return field_functions_[power_gen_fieldfunc_local_handle_];
×
467
}
468

469
InputParameters
470
LBSProblem::GetOptionsBlock()
714✔
471
{
472
  InputParameters params;
714✔
473

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

547
  return params;
714✔
UNCOV
548
}
×
549

550
InputParameters
551
LBSProblem::GetXSMapEntryBlock()
1,001✔
552
{
553
  InputParameters params;
1,001✔
554
  params.SetGeneralDescription("Set the cross-section map for the solver.");
2,002✔
555
  params.AddRequiredParameterArray("block_ids", "Mesh block IDs");
2,002✔
556
  params.AddRequiredParameter<std::shared_ptr<MultiGroupXS>>("xs", "Cross-section object");
2,002✔
557
  return params;
1,001✔
UNCOV
558
}
×
559

560
void
561
LBSProblem::SetOptions(const InputParameters& input)
356✔
562
{
563
  auto params = LBSProblem::GetOptionsBlock();
356✔
564
  params.AssignParameters(input);
356✔
565

566
  // Handle order insensitive options
567
  for (size_t p = 0; p < params.GetNumParameters(); ++p)
7,832✔
568
  {
569
    const auto& spec = params.GetParam(p);
7,476✔
570

571
    if (spec.GetName() == "max_mpi_message_size")
7,476✔
572
      options_.max_mpi_message_size = spec.GetValue<int>();
356✔
573

574
    else if (spec.GetName() == "restart_writes_enabled")
7,120✔
575
      options_.restart_writes_enabled = spec.GetValue<bool>();
356✔
576

577
    else if (spec.GetName() == "write_delayed_psi_to_restart")
6,764✔
578
      options_.write_delayed_psi_to_restart = spec.GetValue<bool>();
356✔
579

580
    else if (spec.GetName() == "read_restart_path")
6,408✔
581
    {
582
      options_.read_restart_path = spec.GetValue<std::string>();
356✔
583
      if (not options_.read_restart_path.empty())
356✔
584
        options_.read_restart_path += std::to_string(opensn::mpi_comm.rank()) + ".restart.h5";
36✔
585
    }
586

587
    else if (spec.GetName() == "write_restart_path")
6,052✔
588
    {
589
      options_.write_restart_path = spec.GetValue<std::string>();
356✔
590
      if (not options_.write_restart_path.empty())
356✔
UNCOV
591
        options_.write_restart_path += std::to_string(opensn::mpi_comm.rank()) + ".restart.h5";
×
592
    }
593

594
    else if (spec.GetName() == "write_restart_time_interval")
5,696✔
595
      options_.write_restart_time_interval = std::chrono::seconds(spec.GetValue<int>());
356✔
596

597
    else if (spec.GetName() == "use_precursors")
5,340✔
598
      options_.use_precursors = spec.GetValue<bool>();
356✔
599

600
    else if (spec.GetName() == "use_source_moments")
4,984✔
601
      options_.use_src_moments = spec.GetValue<bool>();
356✔
602

603
    else if (spec.GetName() == "save_angular_flux")
4,628✔
604
      options_.save_angular_flux = spec.GetValue<bool>();
356✔
605

606
    else if (spec.GetName() == "verbose_inner_iterations")
4,272✔
607
      options_.verbose_inner_iterations = spec.GetValue<bool>();
356✔
608

609
    else if (spec.GetName() == "max_ags_iterations")
3,916✔
610
      options_.max_ags_iterations = spec.GetValue<int>();
356✔
611

612
    else if (spec.GetName() == "ags_tolerance")
3,560✔
613
      options_.ags_tolerance = spec.GetValue<double>();
356✔
614

615
    else if (spec.GetName() == "ags_convergence_check")
3,204✔
616
    {
617
      auto check = spec.GetValue<std::string>();
356✔
618
      if (check == "pointwise")
356✔
UNCOV
619
        options_.ags_pointwise_convergence = true;
×
620
    }
356✔
621

622
    else if (spec.GetName() == "verbose_ags_iterations")
2,848✔
623
      options_.verbose_ags_iterations = spec.GetValue<bool>();
356✔
624

625
    else if (spec.GetName() == "verbose_outer_iterations")
2,492✔
626
      options_.verbose_outer_iterations = spec.GetValue<bool>();
356✔
627

628
    else if (spec.GetName() == "power_field_function_on")
2,136✔
629
      options_.power_field_function_on = spec.GetValue<bool>();
356✔
630

631
    else if (spec.GetName() == "power_default_kappa")
1,780✔
632
      options_.power_default_kappa = spec.GetValue<double>();
356✔
633

634
    else if (spec.GetName() == "power_normalization")
1,424✔
635
      options_.power_normalization = spec.GetValue<double>();
356✔
636

637
    else if (spec.GetName() == "field_function_prefix_option")
1,068✔
638
    {
639
      options_.field_function_prefix_option = spec.GetValue<std::string>();
356✔
640
    }
641

642
    else if (spec.GetName() == "field_function_prefix")
712✔
643
      options_.field_function_prefix = spec.GetValue<std::string>();
356✔
644

645
    else if (spec.GetName() == "adjoint")
356✔
646
      options_.adjoint = spec.GetValue<bool>();
356✔
647

648
  } // for p
649

650
  if (options_.restart_writes_enabled)
356✔
651
  {
652
    // Create restart directory if necessary
UNCOV
653
    auto dir = options_.write_restart_path.parent_path();
×
UNCOV
654
    if (opensn::mpi_comm.rank() == 0)
×
655
    {
UNCOV
656
      if (not std::filesystem::exists(dir))
×
657
      {
658
        if (not std::filesystem::create_directories(dir))
×
659
          throw std::runtime_error(GetName() + ": Failed to create restart directory " +
×
UNCOV
660
                                   dir.string());
×
661
      }
UNCOV
662
      else if (not std::filesystem::is_directory(dir))
×
663
        throw std::runtime_error(GetName() + ": Restart path exists but is not a directory " +
×
664
                                 dir.string());
×
665
    }
UNCOV
666
    opensn::mpi_comm.barrier();
×
667
    UpdateRestartWriteTime();
×
668
  }
×
669
}
356✔
670

671
void
672
LBSProblem::Initialize()
576✔
673
{
674
  CALI_CXX_MARK_SCOPE("LBSProblem::Initialize");
576✔
675

676
  PrintSimHeader();
576✔
677
  mpi_comm.barrier();
576✔
678

679
  InitializeSpatialDiscretization();
576✔
680
  InitializeParrays();
576✔
681
  InitializeBoundaries();
576✔
682
  InitializeGPUExtras();
576✔
683
  SetAdjoint(options_.adjoint);
576✔
684

685
  // Initialize point sources
686
  for (auto& point_source : point_sources_)
585✔
687
    point_source->Initialize(*this);
9✔
688

689
  // Initialize volumetric sources
690
  for (auto& volumetric_source : volumetric_sources_)
1,063✔
691
    volumetric_source->Initialize(*this);
487✔
692
}
576✔
693

694
void
UNCOV
695
LBSProblem::PrintSimHeader()
×
696
{
UNCOV
697
  if (opensn::mpi_comm.rank() == 0)
×
698
  {
UNCOV
699
    std::stringstream outstr;
×
700
    outstr << "\n"
×
UNCOV
701
           << "Initializing " << GetName() << "\n\n"
×
702
           << "Scattering order    : " << scattering_order_ << "\n"
×
UNCOV
703
           << "Number of moments   : " << num_moments_ << "\n"
×
704
           << "Number of groups    : " << num_groups_ << "\n"
×
705
           << "Number of groupsets : " << groupsets_.size() << "\n\n";
×
706

707
    for (const auto& groupset : groupsets_)
×
708
    {
709
      outstr << "***** Groupset " << groupset.id << " *****\n"
×
710
             << "Groups:\n";
×
UNCOV
711
      const auto n_gs_groups = groupset.GetNumGroups();
×
712
      constexpr int groups_per_line = 12;
UNCOV
713
      for (size_t i = 0; i < n_gs_groups; ++i)
×
714
      {
715
        outstr << std::setw(5) << groupset.first_group + i << ' ';
×
UNCOV
716
        if ((i + 1) % groups_per_line == 0)
×
UNCOV
717
          outstr << '\n';
×
718
      }
UNCOV
719
      if (n_gs_groups > 0 && n_gs_groups % groups_per_line != 0)
×
720
        outstr << '\n';
×
721
    }
722

UNCOV
723
    log.Log() << outstr.str() << '\n';
×
724
  }
×
725
}
×
726

727
void
728
LBSProblem::InitializeSources(const InputParameters& params)
576✔
729
{
730
  if (params.Has("volumetric_sources"))
576✔
731
  {
732
    const auto& vol_srcs = params.GetParam("volumetric_sources");
576✔
733
    vol_srcs.RequireBlockTypeIs(ParameterBlockType::ARRAY);
576✔
734
    for (const auto& src : vol_srcs)
1,063✔
735
      volumetric_sources_.push_back(src.GetValue<std::shared_ptr<VolumetricSource>>());
974✔
736
  }
737

738
  if (params.Has("point_sources"))
576✔
739
  {
740
    const auto& pt_srcs = params.GetParam("point_sources");
576✔
741
    pt_srcs.RequireBlockTypeIs(ParameterBlockType::ARRAY);
576✔
742
    for (const auto& src : pt_srcs)
585✔
743
      point_sources_.push_back(src.GetValue<std::shared_ptr<PointSource>>());
18✔
744
  }
745
}
576✔
746

747
void
748
LBSProblem::InitializeGroupsets(const InputParameters& params)
576✔
749
{
750
  // Initialize groups
751
  if (num_groups_ == 0)
576✔
UNCOV
752
    throw std::invalid_argument(GetName() + ": Number of groups must be > 0");
×
753

754
  // Initialize groupsets
755
  const auto& groupsets_array = params.GetParam("groupsets");
576✔
756
  const size_t num_gs = groupsets_array.GetNumParameters();
576✔
757
  if (num_gs == 0)
576✔
UNCOV
758
    throw std::invalid_argument(GetName() + ": At least one groupset must be specified");
×
759
  for (size_t gs = 0; gs < num_gs; ++gs)
1,211✔
760
  {
761
    const auto& groupset_params = groupsets_array.GetParam(gs);
635✔
762
    InputParameters gs_input_params = LBSGroupset::GetInputParameters();
635✔
763
    gs_input_params.SetObjectType("LBSProblem:LBSGroupset");
635✔
764
    gs_input_params.AssignParameters(groupset_params);
635✔
765
    groupsets_.emplace_back(gs_input_params, gs, *this);
635✔
766
    if (groupsets_.back().GetNumGroups() == 0)
635✔
767
    {
UNCOV
768
      std::stringstream oss;
×
UNCOV
769
      oss << GetName() << ": No groups added to groupset " << groupsets_.back().id;
×
UNCOV
770
      throw std::runtime_error(oss.str());
×
UNCOV
771
    }
×
772
  }
635✔
773
}
576✔
774

775
void
776
LBSProblem::InitializeXSmapAndDensities(const InputParameters& params)
576✔
777
{
778
  // Build XS map
779
  const auto& xs_array = params.GetParam("xs_map");
576✔
780
  const size_t num_xs = xs_array.GetNumParameters();
576✔
781
  for (size_t i = 0; i < num_xs; ++i)
1,409✔
782
  {
783
    const auto& item_params = xs_array.GetParam(i);
833✔
784
    InputParameters xs_entry_pars = GetXSMapEntryBlock();
833✔
785
    xs_entry_pars.AssignParameters(item_params);
833✔
786

787
    const auto& block_ids_param = xs_entry_pars.GetParam("block_ids");
833✔
788
    block_ids_param.RequireBlockTypeIs(ParameterBlockType::ARRAY);
833✔
789
    const auto& block_ids = block_ids_param.GetVectorValue<unsigned int>();
833✔
790
    auto xs = xs_entry_pars.GetSharedPtrParam<MultiGroupXS>("xs");
833✔
791
    for (const auto& block_id : block_ids)
1,762✔
792
      block_id_to_xs_map_[block_id] = xs;
929✔
793
  }
833✔
794

795
  // Assign placeholder unit densities
796
  densities_local_.assign(grid_->local_cells.size(), 1.0);
576✔
797
}
576✔
798

799
void
800
LBSProblem::InitializeMaterials()
756✔
801
{
802
  CALI_CXX_MARK_SCOPE("LBSProblem::InitializeMaterials");
756✔
803

804
  log.Log0Verbose1() << "Initializing Materials";
1,512✔
805

806
  // Create set of material ids locally relevant
807
  int invalid_mat_cell_count = 0;
756✔
808
  std::set<unsigned int> unique_block_ids;
756✔
809
  for (auto& cell : grid_->local_cells)
603,854✔
810
  {
811
    unique_block_ids.insert(cell.block_id);
603,098✔
812
    if (cell.block_id == std::numeric_limits<unsigned int>::max() or
603,098✔
813
        (block_id_to_xs_map_.find(cell.block_id) == block_id_to_xs_map_.end()))
603,098✔
UNCOV
814
      ++invalid_mat_cell_count;
×
815
  }
816
  const auto& ghost_cell_ids = grid_->cells.GetGhostGlobalIDs();
756✔
817
  for (uint64_t cell_id : ghost_cell_ids)
102,589✔
818
  {
819
    const auto& cell = grid_->cells[cell_id];
101,833✔
820
    unique_block_ids.insert(cell.block_id);
101,833✔
821
    if (cell.block_id == std::numeric_limits<unsigned int>::max() or
101,833✔
822
        (block_id_to_xs_map_.find(cell.block_id) == block_id_to_xs_map_.end()))
101,833✔
UNCOV
823
      ++invalid_mat_cell_count;
×
824
  }
825
  OpenSnLogicalErrorIf(invalid_mat_cell_count > 0,
756✔
826
                       std::to_string(invalid_mat_cell_count) +
827
                         " cells encountered with an invalid material id.");
828

829
  // Get ready for processing
830
  for (const auto& [blk_id, mat] : block_id_to_xs_map_)
1,881✔
831
  {
832
    mat->SetAdjointMode(options_.adjoint);
1,125✔
833

834
    OpenSnLogicalErrorIf(mat->GetNumGroups() < num_groups_,
1,125✔
835
                         "Cross-sections for block \"" + std::to_string(blk_id) +
836
                           "\" have fewer groups (" + std::to_string(mat->GetNumGroups()) +
837
                           ") than the simulation (" + std::to_string(num_groups_) + "). " +
838
                           "Cross-sections must have at least as many groups as the simulation.");
839
  }
840

841
  // Initialize precursor properties
842
  num_precursors_ = 0;
756✔
843
  max_precursors_per_material_ = 0;
756✔
844
  for (const auto& mat_id_xs : block_id_to_xs_map_)
1,881✔
845
  {
846
    const auto& xs = mat_id_xs.second;
1,125✔
847
    num_precursors_ += xs->GetNumPrecursors();
1,125✔
848
    max_precursors_per_material_ = std::max(xs->GetNumPrecursors(), max_precursors_per_material_);
1,125✔
849
  }
850

851
  // if no precursors, turn off precursors
852
  if (num_precursors_ == 0)
756✔
853
    options_.use_precursors = false;
560✔
854

855
  // check compatibility when precursors are on
856
  if (options_.use_precursors)
756✔
857
  {
858
    for (const auto& [mat_id, xs] : block_id_to_xs_map_)
352✔
859
    {
860
      OpenSnLogicalErrorIf(xs->IsFissionable() and num_precursors_ == 0,
176✔
861
                           "Incompatible cross-section data encountered for material id " +
862
                             std::to_string(mat_id) + ". When delayed neutron data is present " +
863
                             "for one fissionable matrial, it must be present for all fissionable "
864
                             "materials.");
865
    }
866
  }
867

868
  // Update transport views if available
869
  if (grid_->local_cells.size() == cell_transport_views_.size())
756✔
870
    for (const auto& cell : grid_->local_cells)
25,168✔
871
    {
872
      const auto& xs_ptr = block_id_to_xs_map_[cell.block_id];
24,988✔
873
      auto& transport_view = cell_transport_views_[cell.local_id];
24,988✔
874
      transport_view.ReassignXS(*xs_ptr);
24,988✔
875
    }
876

877
  mpi_comm.barrier();
756✔
878
}
756✔
879

880
void
881
LBSProblem::InitializeSpatialDiscretization()
496✔
882
{
883
  CALI_CXX_MARK_SCOPE("LBSProblem::InitializeSpatialDiscretization");
496✔
884

885
  log.Log() << "Initializing spatial discretization.\n";
992✔
886
  discretization_ = PieceWiseLinearDiscontinuous::New(grid_);
496✔
887

888
  ComputeUnitIntegrals();
496✔
889
}
496✔
890

891
void
892
LBSProblem::ComputeUnitIntegrals()
576✔
893
{
894
  CALI_CXX_MARK_SCOPE("LBSProblem::ComputeUnitIntegrals");
576✔
895

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

899
  const size_t num_local_cells = grid_->local_cells.size();
576✔
900
  unit_cell_matrices_.resize(num_local_cells);
576✔
901

902
  for (const auto& cell : grid_->local_cells)
578,686✔
903
    unit_cell_matrices_[cell.local_id] =
578,110✔
904
      ComputeUnitCellIntegrals(sdm, cell, grid_->GetCoordinateSystem());
578,110✔
905

906
  const auto ghost_ids = grid_->cells.GetGhostGlobalIDs();
576✔
907
  for (auto ghost_id : ghost_ids)
93,925✔
908
    unit_ghost_cell_matrices_[ghost_id] =
93,349✔
909
      ComputeUnitCellIntegrals(sdm, grid_->cells[ghost_id], grid_->GetCoordinateSystem());
186,698✔
910

911
  // Assessing global unit cell matrix storage
912
  std::array<size_t, 2> num_local_ucms = {unit_cell_matrices_.size(),
576✔
913
                                          unit_ghost_cell_matrices_.size()};
576✔
914
  std::array<size_t, 2> num_global_ucms = {0, 0};
576✔
915

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

918
  opensn::mpi_comm.barrier();
576✔
919
  log.Log() << "Ghost cell unit cell-matrix ratio: "
576✔
920
            << (double)num_global_ucms[1] * 100 / (double)num_global_ucms[0] << "%";
1,152✔
921
  log.Log() << "Cell matrices computed.";
1,152✔
922
}
576✔
923

924
void
925
LBSProblem::InitializeParrays()
576✔
926
{
927
  CALI_CXX_MARK_SCOPE("LBSProblem::InitializeParrays");
576✔
928

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

932
  // Initialize unknown
933
  // structure
934
  flux_moments_uk_man_.unknowns.clear();
576✔
935
  for (size_t m = 0; m < num_moments_; ++m)
1,726✔
936
  {
937
    flux_moments_uk_man_.AddUnknown(UnknownType::VECTOR_N, num_groups_);
1,150✔
938
    flux_moments_uk_man_.unknowns.back().name = "m" + std::to_string(m);
1,150✔
939
  }
940

941
  // Compute local # of dof
942
  local_node_count_ = discretization_->GetNumLocalNodes();
576✔
943
  global_node_count_ = discretization_->GetNumGlobalNodes();
576✔
944

945
  // Compute num of unknowns
946
  size_t local_unknown_count = local_node_count_ * num_groups_ * num_moments_;
576✔
947

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

950
  // Size local vectors
951
  q_moments_local_.assign(local_unknown_count, 0.0);
576✔
952
  phi_old_local_.assign(local_unknown_count, 0.0);
576✔
953
  phi_new_local_.assign(local_unknown_count, 0.0);
576✔
954

955
  // Setup precursor vector
956
  if (options_.use_precursors)
576✔
957
  {
958
    size_t num_precursor_dofs = grid_->local_cells.size() * max_precursors_per_material_;
60✔
959
    precursor_new_local_.assign(num_precursor_dofs, 0.0);
60✔
960
  }
961

962
  // Initialize transport views
963
  // Transport views act as a data structure to store information
964
  // related to the transport simulation. The most prominent function
965
  // here is that it holds the means to know where a given cell's
966
  // transport quantities are located in the unknown vectors (i.e. phi)
967
  //
968
  // Also, for a given cell, within a given sweep chunk,
969
  // we need to solve a matrix which square size is the
970
  // amount of nodes on the cell. max_cell_dof_count is
971
  // initialized here.
972
  //
973
  size_t block_MG_counter = 0; // Counts the strides of moment and group
576✔
974

975
  const Vector3 ihat(1.0, 0.0, 0.0);
576✔
976
  const Vector3 jhat(0.0, 1.0, 0.0);
576✔
977
  const Vector3 khat(0.0, 0.0, 1.0);
576✔
978

979
  min_cell_dof_count_ = std::numeric_limits<unsigned int>::max();
576✔
980
  max_cell_dof_count_ = 0;
576✔
981
  cell_transport_views_.clear();
576✔
982
  cell_transport_views_.reserve(grid_->local_cells.size());
576✔
983
  for (auto& cell : grid_->local_cells)
578,686✔
984
  {
985
    size_t num_nodes = discretization_->GetCellNumNodes(cell);
578,110✔
986

987
    // compute cell volumes
988
    double cell_volume = 0.0;
578,110✔
989
    const auto& IntV_shapeI = unit_cell_matrices_[cell.local_id].intV_shapeI;
578,110✔
990
    for (size_t i = 0; i < num_nodes; ++i)
3,998,400✔
991
      cell_volume += IntV_shapeI(i);
3,420,290✔
992

993
    size_t cell_phi_address = block_MG_counter;
578,110✔
994

995
    const size_t num_faces = cell.faces.size();
578,110✔
996
    std::vector<bool> face_local_flags(num_faces, true);
578,110✔
997
    std::vector<int> face_locality(num_faces, opensn::mpi_comm.rank());
578,110✔
998
    std::vector<const Cell*> neighbor_cell_ptrs(num_faces, nullptr);
578,110✔
999
    bool cell_on_boundary = false;
578,110✔
1000
    int f = 0;
578,110✔
1001
    for (auto& face : cell.faces)
3,430,920✔
1002
    {
1003
      if (not face.has_neighbor)
2,852,810✔
1004
      {
1005
        cell_on_boundary = true;
93,612✔
1006
        face_local_flags[f] = false;
93,612✔
1007
        face_locality[f] = -1;
93,612✔
1008
      } // if bndry
1009
      else
1010
      {
1011
        const int neighbor_partition = face.GetNeighborPartitionID(grid_.get());
2,759,198✔
1012
        face_local_flags[f] = (neighbor_partition == opensn::mpi_comm.rank());
2,759,198✔
1013
        face_locality[f] = neighbor_partition;
2,759,198✔
1014
        neighbor_cell_ptrs[f] = &grid_->cells[face.neighbor_id];
2,759,198✔
1015
      }
1016

1017
      ++f;
2,852,810✔
1018
    } // for f
1019

1020
    max_cell_dof_count_ = std::max(max_cell_dof_count_, static_cast<unsigned int>(num_nodes));
578,110✔
1021
    min_cell_dof_count_ = std::min(min_cell_dof_count_, static_cast<unsigned int>(num_nodes));
578,110✔
1022
    cell_transport_views_.emplace_back(cell_phi_address,
1,156,220✔
1023
                                       num_nodes,
1024
                                       num_groups_,
578,110✔
1025
                                       num_moments_,
578,110✔
1026
                                       num_faces,
1027
                                       *block_id_to_xs_map_[cell.block_id],
578,110✔
1028
                                       cell_volume,
1029
                                       face_local_flags,
1030
                                       face_locality,
1031
                                       neighbor_cell_ptrs,
1032
                                       cell_on_boundary);
1033
    block_MG_counter += num_nodes * num_groups_ * num_moments_;
578,110✔
1034
  } // for local cell
578,110✔
1035

1036
  // Populate grid nodal mappings
1037
  // This is used in the Flux Data Structures (FLUDS)
1038
  grid_nodal_mappings_.clear();
576✔
1039
  grid_nodal_mappings_.reserve(grid_->local_cells.size());
576✔
1040
  for (auto& cell : grid_->local_cells)
578,686✔
1041
  {
1042
    CellFaceNodalMapping cell_nodal_mapping;
578,110✔
1043
    cell_nodal_mapping.reserve(cell.faces.size());
578,110✔
1044

1045
    for (auto& face : cell.faces)
3,430,920✔
1046
    {
1047
      std::vector<short> face_node_mapping;
2,852,810✔
1048
      std::vector<short> cell_node_mapping;
2,852,810✔
1049
      int adj_face_idx = -1;
2,852,810✔
1050

1051
      if (face.has_neighbor)
2,852,810✔
1052
      {
1053
        grid_->FindAssociatedVertices(face, face_node_mapping);
2,759,198✔
1054
        grid_->FindAssociatedCellVertices(face, cell_node_mapping);
2,759,198✔
1055
        adj_face_idx = face.GetNeighborAdjacentFaceIndex(grid_.get());
2,759,198✔
1056
      }
1057

1058
      cell_nodal_mapping.emplace_back(adj_face_idx, face_node_mapping, cell_node_mapping);
2,852,810✔
1059
    } // for f
2,852,810✔
1060

1061
    grid_nodal_mappings_.push_back(cell_nodal_mapping);
578,110✔
1062
  } // for local cell
578,110✔
1063

1064
  // Get grid localized communicator set
1065
  grid_local_comm_set_ = grid_->MakeMPILocalCommunicatorSet();
576✔
1066

1067
  // Initialize Field Functions
1068
  InitializeFieldFunctions();
576✔
1069

1070
  opensn::mpi_comm.barrier();
576✔
1071
  log.Log() << "Done with parallel arrays." << std::endl;
1,152✔
1072
}
576✔
1073

1074
void
1075
LBSProblem::InitializeFieldFunctions()
576✔
1076
{
1077
  CALI_CXX_MARK_SCOPE("LBSProblem::InitializeFieldFunctions");
576✔
1078

1079
  if (not field_functions_.empty())
576✔
UNCOV
1080
    return;
×
1081

1082
  // Initialize Field Functions for flux moments
1083
  phi_field_functions_local_map_.clear();
576✔
1084

1085
  for (unsigned int g = 0; g < num_groups_; ++g)
22,741✔
1086
  {
1087
    for (size_t m = 0; m < num_moments_; ++m)
99,648✔
1088
    {
1089
      std::string prefix;
77,483✔
1090
      if (options_.field_function_prefix_option == "prefix")
77,483✔
1091
      {
1092
        prefix = options_.field_function_prefix;
77,483✔
1093
        if (not prefix.empty())
77,483✔
1094
          prefix += "_";
1✔
1095
      }
1096
      if (options_.field_function_prefix_option == "solver_name")
77,483✔
UNCOV
1097
        prefix = GetName() + "_";
×
1098

1099
      std::ostringstream oss;
77,483✔
1100
      oss << prefix << "phi_g" << std::setw(3) << std::setfill('0') << static_cast<int>(g) << "_m"
77,483✔
1101
          << std::setw(2) << std::setfill('0') << static_cast<int>(m);
77,483✔
1102
      const std::string name = oss.str();
77,483✔
1103

1104
      auto group_ff = std::make_shared<FieldFunctionGridBased>(
77,483✔
1105
        name, discretization_, Unknown(UnknownType::SCALAR));
77,483✔
1106

1107
      field_function_stack.push_back(group_ff);
154,966✔
1108
      field_functions_.push_back(group_ff);
77,483✔
1109

1110
      phi_field_functions_local_map_[{g, m}] = field_functions_.size() - 1;
77,483✔
1111
    } // for m
77,483✔
1112
  } // for g
1113

1114
  // Initialize power generation field function
1115
  if (options_.power_field_function_on)
576✔
1116
  {
1117
    std::string prefix;
4✔
1118
    if (options_.field_function_prefix_option == "prefix")
4✔
1119
    {
1120
      prefix = options_.field_function_prefix;
4✔
1121
      if (not prefix.empty())
4✔
UNCOV
1122
        prefix += "_";
×
1123
    }
1124
    if (options_.field_function_prefix_option == "solver_name")
4✔
UNCOV
1125
      prefix = GetName() + "_";
×
1126

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

1130
    field_function_stack.push_back(power_ff);
8✔
1131
    field_functions_.push_back(power_ff);
4✔
1132

1133
    power_gen_fieldfunc_local_handle_ = field_functions_.size() - 1;
4✔
1134
  }
4✔
1135
}
576✔
1136

1137
void
1138
LBSProblem::InitializeSolverSchemes()
700✔
1139
{
1140
  CALI_CXX_MARK_SCOPE("LBSProblem::InitializeSolverSchemes");
700✔
1141
  InitializeWGSSolvers();
700✔
1142

1143
  ags_solver_ = std::make_shared<AGSLinearSolver>(*this, wgs_solvers_);
700✔
1144
  if (groupsets_.size() == 1)
700✔
1145
  {
1146
    ags_solver_->SetMaxIterations(1);
645✔
1147
    ags_solver_->SetVerbosity(false);
645✔
1148
  }
1149
  else
1150
  {
1151
    ags_solver_->SetMaxIterations(options_.max_ags_iterations);
55✔
1152
    ags_solver_->SetVerbosity(options_.verbose_ags_iterations);
55✔
1153
  }
1154
  ags_solver_->SetTolerance(options_.ags_tolerance);
700✔
1155
}
700✔
1156

1157
#ifndef __OPENSN_WITH_GPU__
1158
void
1159
LBSProblem::InitializeGPUExtras()
744✔
1160
{
1161
}
744✔
1162

1163
void
1164
LBSProblem::ResetGPUCarriers()
732✔
1165
{
1166
}
732✔
1167

1168
void
UNCOV
1169
LBSProblem::CheckCapableDevices()
×
1170
{
UNCOV
1171
}
×
1172
#endif // __OPENSN_WITH_GPU__
1173

1174
std::vector<double>
1175
LBSProblem::MakeSourceMomentsFromPhi()
4✔
1176
{
1177
  CALI_CXX_MARK_SCOPE("LBSProblem::MakeSourceMomentsFromPhi");
4✔
1178

1179
  size_t num_local_dofs = discretization_->GetNumLocalDOFs(flux_moments_uk_man_);
4✔
1180

1181
  std::vector<double> source_moments(num_local_dofs, 0.0);
4✔
1182
  for (auto& groupset : groupsets_)
8✔
1183
  {
1184
    active_set_source_function_(groupset,
4✔
1185
                                source_moments,
1186
                                phi_new_local_,
4✔
1187
                                APPLY_AGS_SCATTER_SOURCES | APPLY_WGS_SCATTER_SOURCES |
1188
                                  APPLY_AGS_FISSION_SOURCES | APPLY_WGS_FISSION_SOURCES);
4✔
1189
  }
1190

1191
  return source_moments;
4✔
1192
}
4✔
1193

1194
void
1195
LBSProblem::UpdateFieldFunctions()
3,096✔
1196
{
1197
  CALI_CXX_MARK_SCOPE("LBSProblem::UpdateFieldFunctions");
3,096✔
1198

1199
  const auto& sdm = *discretization_;
3,096✔
1200
  const auto& phi_uk_man = flux_moments_uk_man_;
3,096✔
1201

1202
  // Update flux moments
1203
  for (const auto& [g_and_m, ff_index] : phi_field_functions_local_map_)
88,079✔
1204
  {
1205
    const auto g = g_and_m.first;
84,983✔
1206
    const size_t m = g_and_m.second;
84,983✔
1207

1208
    std::vector<double> data_vector_local(local_node_count_, 0.0);
84,983✔
1209

1210
    for (const auto& cell : grid_->local_cells)
24,481,152✔
1211
    {
1212
      const auto& cell_mapping = sdm.GetCellMapping(cell);
24,396,169✔
1213
      const size_t num_nodes = cell_mapping.GetNumNodes();
24,396,169✔
1214

1215
      for (size_t i = 0; i < num_nodes; ++i)
164,843,853✔
1216
      {
1217
        const auto imapA = sdm.MapDOFLocal(cell, i, phi_uk_man, m, g);
140,447,684✔
1218
        const auto imapB = sdm.MapDOFLocal(cell, i);
140,447,684✔
1219

1220
        data_vector_local[imapB] = phi_new_local_[imapA];
140,447,684✔
1221
      } // for node
1222
    } // for cell
1223

1224
    auto& ff_ptr = field_functions_.at(ff_index);
84,983✔
1225
    ff_ptr->UpdateFieldVector(data_vector_local);
84,983✔
1226
  }
84,983✔
1227

1228
  // Update power generation and scalar flux
1229
  if (options_.power_field_function_on)
3,096✔
1230
  {
1231
    std::vector<double> data_vector_power_local(local_node_count_, 0.0);
4✔
1232

1233
    double local_total_power = 0.0;
4✔
1234
    for (const auto& cell : grid_->local_cells)
83,268✔
1235
    {
1236
      const auto& cell_mapping = sdm.GetCellMapping(cell);
83,264✔
1237
      const size_t num_nodes = cell_mapping.GetNumNodes();
83,264✔
1238

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

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

1243
      if (not xs->IsFissionable())
83,264✔
1244
        continue;
56,360✔
1245

1246
      for (size_t i = 0; i < num_nodes; ++i)
134,520✔
1247
      {
1248
        const auto imapA = sdm.MapDOFLocal(cell, i);
107,616✔
1249
        const auto imapB = sdm.MapDOFLocal(cell, i, phi_uk_man, 0, 0);
107,616✔
1250

1251
        double nodal_power = 0.0;
1252
        for (unsigned int g = 0; g < num_groups_; ++g)
860,928✔
1253
        {
1254
          const double sigma_fg = xs->GetSigmaFission()[g];
753,312✔
1255
          // const double kappa_g = xs->Kappa()[g];
1256
          const double kappa_g = options_.power_default_kappa;
753,312✔
1257

1258
          nodal_power += kappa_g * sigma_fg * phi_new_local_[imapB + g];
753,312✔
1259
        } // for g
1260

1261
        data_vector_power_local[imapA] = nodal_power;
107,616✔
1262
        local_total_power += nodal_power * Vi(i);
107,616✔
1263
      } // for node
1264
    } // for cell
1265

1266
    double scale_factor = 1.0;
4✔
1267
    if (options_.power_normalization > 0.0)
4✔
1268
    {
1269
      double global_total_power = 0.0;
4✔
1270
      mpi_comm.all_reduce(local_total_power, global_total_power, mpi::op::sum<double>());
4✔
1271
      scale_factor = options_.power_normalization / global_total_power;
4✔
1272
      Scale(data_vector_power_local, scale_factor);
4✔
1273
    }
1274

1275
    const size_t ff_index = power_gen_fieldfunc_local_handle_;
4✔
1276

1277
    auto& ff_ptr = field_functions_.at(ff_index);
4✔
1278
    ff_ptr->UpdateFieldVector(data_vector_power_local);
4✔
1279

1280
    // scale scalar flux if neccessary
1281
    if (scale_factor != 1.0)
4✔
1282
    {
1283
      for (unsigned int g = 0; g < num_groups_; ++g)
32✔
1284
      {
1285
        const size_t phi_ff_index = phi_field_functions_local_map_.at({g, size_t{0}});
28✔
1286
        auto& phi_ff_ptr = field_functions_.at(phi_ff_index);
28✔
1287
        const auto& phi_vec = phi_ff_ptr->GetLocalFieldVector();
28✔
1288
        std::vector<double> phi_scaled(phi_vec.begin(), phi_vec.end());
28✔
1289
        Scale(phi_scaled, scale_factor);
28✔
1290
        phi_ff_ptr->UpdateFieldVector(phi_scaled);
28✔
1291
      }
28✔
1292
    }
1293
  } // if power enabled
4✔
1294
}
3,096✔
1295

1296
void
UNCOV
1297
LBSProblem::SetPhiFromFieldFunctions(PhiSTLOption which_phi,
×
1298
                                     const std::vector<size_t>& m_indices,
1299
                                     const std::vector<size_t>& g_indices)
1300
{
UNCOV
1301
  CALI_CXX_MARK_SCOPE("LBSProblem::SetPhiFromFieldFunctions");
×
1302

UNCOV
1303
  std::vector<size_t> m_ids_to_copy = m_indices;
×
UNCOV
1304
  std::vector<size_t> g_ids_to_copy = g_indices;
×
UNCOV
1305
  if (m_indices.empty())
×
UNCOV
1306
    for (size_t m = 0; m < num_moments_; ++m)
×
UNCOV
1307
      m_ids_to_copy.push_back(m);
×
1308
  if (g_ids_to_copy.empty())
×
UNCOV
1309
    for (unsigned int g = 0; g < num_groups_; ++g)
×
UNCOV
1310
      g_ids_to_copy.push_back(g);
×
1311

1312
  const auto& sdm = *discretization_;
×
UNCOV
1313
  const auto& phi_uk_man = flux_moments_uk_man_;
×
1314

1315
  for (const size_t m : m_ids_to_copy)
×
1316
  {
1317
    for (const size_t g : g_ids_to_copy)
×
1318
    {
1319
      const size_t ff_index = phi_field_functions_local_map_.at({g, m});
×
1320
      const auto& ff_ptr = field_functions_.at(ff_index);
×
1321
      const auto& ff_data = ff_ptr->GetLocalFieldVector();
×
1322

1323
      for (const auto& cell : grid_->local_cells)
×
1324
      {
UNCOV
1325
        const auto& cell_mapping = sdm.GetCellMapping(cell);
×
1326
        const size_t num_nodes = cell_mapping.GetNumNodes();
×
1327

1328
        for (size_t i = 0; i < num_nodes; ++i)
×
1329
        {
1330
          const auto imapA = sdm.MapDOFLocal(cell, i);
×
1331
          const auto imapB = sdm.MapDOFLocal(cell, i, phi_uk_man, m, g);
×
1332

UNCOV
1333
          if (which_phi == PhiSTLOption::PHI_OLD)
×
1334
            phi_old_local_[imapB] = ff_data[imapA];
×
UNCOV
1335
          else if (which_phi == PhiSTLOption::PHI_NEW)
×
1336
            phi_new_local_[imapB] = ff_data[imapA];
×
1337
        } // for node
1338
      } // for cell
1339
    } // for g
1340
  } // for m
1341
}
×
1342

1343
LBSProblem::~LBSProblem()
564✔
1344
{
1345
  ResetGPUCarriers();
1346
}
2,820✔
1347

564✔
1348
void
1349
LBSProblem::SetAdjoint(bool adjoint)
588✔
1350
{
1351
  if (adjoint != options_.adjoint)
588✔
1352
  {
1353
    options_.adjoint = adjoint;
12✔
1354

1355
    // If a discretization exists, the solver has already been initialized.
1356
    // Reinitialize the materials to obtain the appropriate xs and clear the
1357
    // sources to prepare for defining the adjoint problem
1358
    if (discretization_)
12✔
1359
    {
1360
      // The materials are reinitialized here to ensure that the proper cross sections
1361
      // are available to the solver. Because an adjoint solve requires volumetric or
1362
      // point sources, the material-based sources are not set within the initialize routine.
1363
      InitializeMaterials();
12✔
1364

1365
      // Forward and adjoint sources are fundamentally different, so any existing sources
1366
      // should be cleared and reset through options upon changing modes.
1367
      point_sources_.clear();
12✔
1368
      volumetric_sources_.clear();
12✔
1369
      ClearBoundaries();
12✔
1370

1371
      // Set all solutions to zero.
1372
      phi_old_local_.assign(phi_old_local_.size(), 0.0);
12✔
1373
      phi_new_local_.assign(phi_new_local_.size(), 0.0);
12✔
1374
      ZeroSolutions();
12✔
1375
      precursor_new_local_.assign(precursor_new_local_.size(), 0.0);
12✔
1376
    }
1377
  }
1378
}
588✔
1379

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