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

Open-Sn / opensn / 23971600730

02 Apr 2026 07:54PM UTC coverage: 75.04%. Remained the same
23971600730

push

github

web-flow
Merge pull request #1010 from wdhawkins/field_function_refactor

Refactoring field function implementation

107 of 158 new or added lines in 3 files covered. (67.72%)

258 existing lines in 7 files now uncovered.

20988 of 27969 relevant lines covered (75.04%)

66023505.05 hits per line

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

83.59
/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 "framework/utils/error.h"
19
#include "caliper/cali.h"
20
#include <algorithm>
21
#include <iomanip>
22
#include <fstream>
23
#include <cstring>
24
#include <cassert>
25
#include <memory>
26
#include <stdexcept>
27
#include <sys/stat.h>
28
#include <unordered_map>
29
#include <functional>
30

31
namespace opensn
32
{
33

34
InputParameters
35
LBSProblem::GetInputParameters()
643✔
36
{
37
  InputParameters params = Problem::GetInputParameters();
643✔
38

39
  params.ChangeExistingParamToOptional("name", "LBSProblem");
1,286✔
40

41
  params.AddRequiredParameter<std::shared_ptr<MeshContinuum>>("mesh", "Mesh");
1,286✔
42

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

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

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

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

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

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

64
  params.AddOptionalParameter("use_gpus", false, "Offload the sweep computation to GPUs.");
1,286✔
65

66
  return params;
643✔
67
}
×
68

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

86
  // Initialize options
87
  if (params.IsParameterValid("options"))
643✔
88
  {
89
    auto options_params = LBSProblem::GetOptionsBlock();
430✔
90
    options_params.AssignParameters(params.GetParam("options"));
432✔
91
    ParseOptions(options_params);
428✔
92
  }
430✔
93

94
  // Set geometry type
95
  geometry_type_ = grid_->GetGeometryType();
641✔
96
  OpenSnInvalidArgumentIf(geometry_type_ == GeometryType::INVALID,
641✔
97
                          GetName() + ": Invalid geometry type.");
98

99
  InitializeGroupsets(params);
641✔
100
  InitializeSources(params);
641✔
101
  InitializeXSMap(params);
641✔
102
  InitializeMaterials();
641✔
103
}
663✔
104

105
const LBSOptions&
106
LBSProblem::GetOptions() const
1,246,841,981✔
107
{
108
  return options_;
1,246,841,981✔
109
}
110

111
double
112
LBSProblem::GetTime() const
447,814✔
113
{
114
  return time_;
447,814✔
115
}
116

117
void
118
LBSProblem::SetTime(double time)
5,612✔
119
{
120
  time_ = time;
5,612✔
121
}
5,612✔
122

123
void
124
LBSProblem::SetTimeStep(double dt)
1,804✔
125
{
126
  OpenSnInvalidArgumentIf(dt <= 0.0, GetName() + ": dt must be greater than zero.");
1,804✔
127
  dt_ = dt;
1,804✔
128
}
1,804✔
129

130
double
131
LBSProblem::GetTimeStep() const
2,147,483,647✔
132
{
133
  return dt_;
2,147,483,647✔
134
}
135

136
void
137
LBSProblem::SetTheta(double theta)
312✔
138
{
139
  OpenSnInvalidArgumentIf(theta <= 0.0 or theta > 1.0,
312✔
140
                          GetName() + ": theta must be in (0.0, 1.0].");
141
  theta_ = theta;
312✔
142
}
312✔
143

144
double
145
LBSProblem::GetTheta() const
2,147,483,647✔
146
{
147
  return theta_;
2,147,483,647✔
148
}
149

150
bool
151
LBSProblem::IsTimeDependent() const
×
152
{
153
  return false;
×
154
}
155

156
void
157
LBSProblem::SetTimeDependentMode()
×
158
{
159
  OpenSnLogicalError(GetName() + ": Time-dependent mode is not supported for this problem type.");
×
160
}
161

162
void
163
LBSProblem::SetSteadyStateMode()
×
164
{
165
  // Steady-state is the default for problem types without time-dependent support.
166
}
×
167

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

174
unsigned int
175
LBSProblem::GetNumMoments() const
568,969✔
176
{
177
  return num_moments_;
568,969✔
178
}
179

180
unsigned int
181
LBSProblem::GetMaxCellDOFCount() const
926✔
182
{
183
  return max_cell_dof_count_;
926✔
184
}
185

186
unsigned int
187
LBSProblem::GetMinCellDOFCount() const
926✔
188
{
189
  return min_cell_dof_count_;
926✔
190
}
191

192
bool
193
LBSProblem::UseGPUs() const
1,238✔
194
{
195
  return use_gpus_;
1,238✔
196
}
197

198
unsigned int
199
LBSProblem::GetNumGroups() const
879,069✔
200
{
201
  return num_groups_;
879,069✔
202
}
203

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

210
unsigned int
211
LBSProblem::GetNumPrecursors() const
×
212
{
213
  return num_precursors_;
×
214
}
215

216
unsigned int
217
LBSProblem::GetMaxPrecursorsPerMaterial() const
9,550✔
218
{
219
  return max_precursors_per_material_;
9,550✔
220
}
221

222
const std::vector<LBSGroupset>&
223
LBSProblem::GetGroupsets() const
33,061✔
224
{
225
  return groupsets_;
33,061✔
226
}
227

228
LBSGroupset&
229
LBSProblem::GetGroupset(size_t groupset_id)
22,578,778✔
230
{
231
  return groupsets_.at(groupset_id);
22,578,778✔
232
}
233

234
const LBSGroupset&
235
LBSProblem::GetGroupset(size_t groupset_id) const
×
236
{
237
  return groupsets_.at(groupset_id);
×
238
}
239

240
size_t
241
LBSProblem::GetNumGroupsets() const
69✔
242
{
243
  return groupsets_.size();
69✔
244
}
245

246
void
247
LBSProblem::AddPointSource(std::shared_ptr<PointSource> point_source)
×
248
{
249
  point_sources_.push_back(point_source);
×
250
  if (initialized_)
×
251
    point_sources_.back()->Initialize(*this);
×
252
}
×
253

254
void
255
LBSProblem::ClearPointSources()
×
256
{
257
  point_sources_.clear();
×
258
}
×
259

260
const std::vector<std::shared_ptr<PointSource>>&
261
LBSProblem::GetPointSources() const
156,416✔
262
{
263
  return point_sources_;
156,416✔
264
}
265

266
void
267
LBSProblem::AddVolumetricSource(std::shared_ptr<VolumetricSource> volumetric_source)
24✔
268
{
269
  volumetric_sources_.push_back(volumetric_source);
24✔
270
  if (initialized_)
24✔
271
    volumetric_sources_.back()->Initialize(*this);
24✔
272
}
24✔
273

274
void
275
LBSProblem::ClearVolumetricSources()
12✔
276
{
277
  volumetric_sources_.clear();
12✔
278
}
12✔
279

280
const std::vector<std::shared_ptr<VolumetricSource>>&
281
LBSProblem::GetVolumetricSources() const
156,416✔
282
{
283
  return volumetric_sources_;
156,416✔
284
}
285

286
const BlockID2XSMap&
287
LBSProblem::GetBlockID2XSMap() const
17,301✔
288
{
289
  return block_id_to_xs_map_;
17,301✔
290
}
291

292
void
293
LBSProblem::SetBlockID2XSMap(const BlockID2XSMap& xs_map)
172✔
294
{
295
  block_id_to_xs_map_ = xs_map;
172✔
296
  InitializeMaterials();
172✔
297
  ResetGPUCarriers();
172✔
298
  InitializeGPUExtras();
172✔
299
}
172✔
300

301
std::shared_ptr<MeshContinuum>
302
LBSProblem::GetGrid() const
793,817✔
303
{
304
  return grid_;
793,817✔
305
}
306

307
const SpatialDiscretization&
308
LBSProblem::GetSpatialDiscretization() const
252,015✔
309
{
310
  return *discretization_;
252,015✔
311
}
312

313
const std::vector<UnitCellMatrices>&
314
LBSProblem::GetUnitCellMatrices() const
21,203✔
315
{
316
  return unit_cell_matrices_;
21,203✔
317
}
318

319
const std::map<uint64_t, UnitCellMatrices>&
320
LBSProblem::GetUnitGhostCellMatrices() const
16✔
321
{
322
  return unit_ghost_cell_matrices_;
16✔
323
}
324

325
std::vector<CellLBSView>&
326
LBSProblem::GetCellTransportViews()
310,577✔
327
{
328
  return cell_transport_views_;
310,577✔
329
}
330

331
const std::vector<CellLBSView>&
332
LBSProblem::GetCellTransportViews() const
679,964✔
333
{
334
  return cell_transport_views_;
679,964✔
335
}
336

337
const UnknownManager&
338
LBSProblem::GetUnknownManager() const
26,754✔
339
{
340
  return flux_moments_uk_man_;
26,754✔
341
}
342

343
size_t
344
LBSProblem::GetLocalNodeCount() const
3,358✔
345
{
346
  return local_node_count_;
3,358✔
347
}
348

349
size_t
350
LBSProblem::GetGlobalNodeCount() const
2,846✔
351
{
352
  return global_node_count_;
2,846✔
353
}
354

355
std::vector<double>&
356
LBSProblem::GetQMomentsLocal()
227,149✔
357
{
358
  return q_moments_local_;
227,149✔
359
}
360

361
const std::vector<double>&
362
LBSProblem::GetQMomentsLocal() const
×
363
{
364
  return q_moments_local_;
×
365
}
366

367
std::vector<double>&
368
LBSProblem::GetExtSrcMomentsLocal()
4✔
369
{
370
  return ext_src_moments_local_;
4✔
371
}
372

373
const std::vector<double>&
374
LBSProblem::GetExtSrcMomentsLocal() const
223,227✔
375
{
376
  return ext_src_moments_local_;
223,227✔
377
}
378

379
void
380
LBSProblem::SetExtSrcMomentsFrom(const std::vector<double>& ext_src_moments)
4✔
381
{
382
  if (not phi_old_local_.empty())
4✔
383
    OpenSnLogicalErrorIf(ext_src_moments.size() != phi_old_local_.size(),
4✔
384
                         "SetExtSrcMomentsFrom size mismatch. Provided size=" +
385
                           std::to_string(ext_src_moments.size()) +
386
                           ", expected local DOFs=" + std::to_string(phi_old_local_.size()) + ".");
387

388
  if (ext_src_moments_local_.empty())
4✔
389
  {
390
    ext_src_moments_local_ = ext_src_moments;
4✔
391
    return;
4✔
392
  }
393

394
  assert(ext_src_moments.size() == ext_src_moments_local_.size() &&
×
395
         "SetExtSrcMomentsFrom size mismatch.");
396
  ext_src_moments_local_ = ext_src_moments;
×
397
}
398

399
std::vector<double>&
400
LBSProblem::GetPhiOldLocal()
449,545✔
401
{
402
  return phi_old_local_;
449,545✔
403
}
404

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

411
std::vector<double>&
412
LBSProblem::GetPhiNewLocal()
263,701✔
413
{
414
  return phi_new_local_;
263,701✔
415
}
416

417
const std::vector<double>&
418
LBSProblem::GetPhiNewLocal() const
10,283✔
419
{
420
  return phi_new_local_;
10,283✔
421
}
422

423
std::vector<double>&
424
LBSProblem::GetPrecursorsNewLocal()
380✔
425
{
426
  return precursor_new_local_;
380✔
427
}
428

429
const std::vector<double>&
430
LBSProblem::GetPrecursorsNewLocal() const
×
431
{
432
  return precursor_new_local_;
×
433
}
434

435
SetSourceFunction
436
LBSProblem::GetActiveSetSourceFunction() const
5,126✔
437
{
438
  return active_set_source_function_;
5,126✔
439
}
440

441
void
442
LBSProblem::SetActiveSetSourceFunction(SetSourceFunction source_function)
104✔
443
{
444
  active_set_source_function_ = std::move(source_function);
104✔
445
}
104✔
446

447
std::shared_ptr<AGSLinearSolver>
448
LBSProblem::GetAGSSolver()
2,988✔
449
{
450
  CheckAGSSolverInitialized();
2,988✔
451
  return ags_solver_;
2,988✔
452
}
453

454
std::shared_ptr<LinearSolver>
455
LBSProblem::GetWGSSolver(size_t groupset_id)
3,002✔
456
{
457
  CheckWGSSolversInitialized();
3,002✔
458
  return wgs_solvers_.at(groupset_id);
3,002✔
459
}
460

461
size_t
462
LBSProblem::GetNumWGSSolvers()
8,244✔
463
{
464
  CheckWGSSolversInitialized();
8,244✔
465
  return wgs_solvers_.size();
8,244✔
466
}
467

468
WGSContext&
469
LBSProblem::GetWGSContext(int groupset_id)
14,013✔
470
{
471
  CheckWGSContextsInitialized();
14,013✔
472
  auto& wgs_context_ptr = wgs_contexts_.at(groupset_id);
14,013✔
473
  OpenSnLogicalErrorIf(not wgs_context_ptr, GetName() + ": Null WGS context.");
14,013✔
474
  return *wgs_context_ptr;
14,013✔
475
}
476

477
void
478
LBSProblem::CheckWGSContextsInitialized()
29,055✔
479
{
480
  if (wgs_contexts_.empty())
29,055✔
481
    InitializeWGSContexts();
×
482
}
29,055✔
483

484
void
485
LBSProblem::CheckWGSSolversInitialized()
14,234✔
486
{
487
  CheckWGSContextsInitialized();
14,234✔
488
  if (wgs_solvers_.empty())
14,234✔
489
    InitializeWGSSolvers();
808✔
490
}
14,234✔
491

492
void
493
LBSProblem::CheckAGSSolverInitialized()
2,988✔
494
{
495
  CheckWGSSolversInitialized();
2,988✔
496
  if (ags_solver_)
2,988✔
497
    return;
498

499
  ags_solver_ = std::make_shared<AGSLinearSolver>(*this, wgs_solvers_);
704✔
500
  if (groupsets_.size() == 1)
704✔
501
  {
502
    ags_solver_->SetMaxIterations(1);
631✔
503
    ags_solver_->SetVerbosity(false);
631✔
504
  }
505
  else
506
  {
507
    ags_solver_->SetMaxIterations(options_.max_ags_iterations);
73✔
508
    ags_solver_->SetVerbosity(options_.verbose_ags_iterations);
73✔
509
  }
510
  ags_solver_->SetTolerance(options_.ags_tolerance);
704✔
511
}
512

513
std::pair<size_t, size_t>
514
LBSProblem::GetNumPhiIterativeUnknowns()
×
515
{
516
  const auto& sdm = *discretization_;
×
517
  const size_t num_local_phi_dofs = sdm.GetNumLocalDOFs(flux_moments_uk_man_);
×
518
  const size_t num_global_phi_dofs = sdm.GetNumGlobalDOFs(flux_moments_uk_man_);
×
519

520
  return {num_local_phi_dofs, num_global_phi_dofs};
×
521
}
522

523
std::shared_ptr<FieldFunctionGridBased>
524
LBSProblem::CreateScalarFluxFieldFunction(unsigned int g, unsigned int m)
42,311✔
525
{
526
  OpenSnLogicalErrorIf(g >= num_groups_, GetName() + ": Group index out of range.");
42,311✔
527
  OpenSnLogicalErrorIf(m >= num_moments_, GetName() + ": Moment index out of range.");
42,311✔
528

529
  auto ff_ptr = CreateEmptyFieldFunction(MakeScalarFluxFieldFunctionName(g, m));
42,311✔
530
  ff_ptr->UpdateFieldVector(ComputeScalarFluxFieldFunctionData(g, m));
42,311✔
531
  return ff_ptr;
42,311✔
532
}
×
533

534
std::shared_ptr<FieldFunctionGridBased>
535
LBSProblem::CreateFieldFunction(const std::string& name,
2✔
536
                                const std::string& xs_name,
537
                                const double power_normalization_target)
538
{
539
  const std::string ff_name = MakeFieldFunctionName(name);
2✔
540
  auto ff_ptr = CreateEmptyFieldFunction(ff_name);
2✔
541

542
  std::vector<double> data_vector_local;
2✔
543
  if (xs_name == "power")
2✔
544
  {
545
    double local_total_power = 0.0;
1✔
546
    data_vector_local = ComputePowerFieldFunctionData(local_total_power);
2✔
547
  }
548
  else
549
  {
550
    data_vector_local = ComputeXSFieldFunctionData(xs_name);
2✔
551
  }
552

553
  if (power_normalization_target > 0.0)
2✔
554
  {
555
    const double scale_factor = ComputeFieldFunctionPowerScaleFactor(power_normalization_target);
1✔
556
    Scale(data_vector_local, scale_factor);
1✔
557
  }
558

559
  ff_ptr->UpdateFieldVector(data_vector_local);
2✔
560
  return ff_ptr;
2✔
561
}
2✔
562

563
InputParameters
564
LBSProblem::GetOptionsBlock()
858✔
565
{
566
  InputParameters params;
858✔
567

568
  params.SetGeneralDescription("Set options from a large list of parameters");
1,716✔
569
  params.AddOptionalParameter("max_mpi_message_size",
1,716✔
570
                              32768,
571
                              "The maximum MPI message size used during sweep initialization.");
572
  params.AddOptionalParameter(
1,716✔
573
    "restart_writes_enabled", false, "Flag that controls writing of restart dumps");
574
  params.AddOptionalParameter("write_delayed_psi_to_restart",
1,716✔
575
                              true,
576
                              "Flag that controls writing of delayed angular fluxes to restarts.");
577
  params.AddOptionalParameter(
1,716✔
578
    "read_restart_path", "", "Full path for reading restart dumps including file stem.");
579
  params.AddOptionalParameter(
1,716✔
580
    "write_restart_path", "", "Full path for writing restart dumps including file stem.");
581
  params.AddOptionalParameter("write_restart_time_interval",
1,716✔
582
                              0,
583
                              "Time interval in seconds at which restart data is to be written.");
584
  params.AddOptionalParameter(
1,716✔
585
    "use_precursors", false, "Flag for using delayed neutron precursors.");
586
  params.AddOptionalParameter("use_source_moments",
1,716✔
587
                              false,
588
                              "Flag for ignoring fixed sources and selectively using source "
589
                              "moments obtained elsewhere.");
590
  params.AddOptionalParameter(
1,716✔
591
    "save_angular_flux", false, "Flag indicating whether angular fluxes are to be stored or not.");
592
  params.AddOptionalParameter(
1,716✔
593
    "adjoint", false, "Flag for toggling whether the solver is in adjoint mode.");
594
  params.AddOptionalParameter(
1,716✔
595
    "verbose_inner_iterations", true, "Flag to control verbosity of inner iterations.");
596
  params.AddOptionalParameter(
1,716✔
597
    "verbose_outer_iterations", true, "Flag to control verbosity of across-groupset iterations.");
598
  params.AddOptionalParameter(
1,716✔
599
    "max_ags_iterations", 100, "Maximum number of across-groupset iterations.");
600
  params.AddOptionalParameter("ags_tolerance", 1.0e-6, "Across-groupset iterations tolerance.");
1,716✔
601
  params.AddOptionalParameter("ags_convergence_check",
1,716✔
602
                              "l2",
603
                              "Type of convergence check for AGS iterations. Valid values are "
604
                              "`\"l2\"` and '\"pointwise\"'");
605
  params.AddOptionalParameter(
1,716✔
606
    "verbose_ags_iterations", true, "Flag to control verbosity of across-groupset iterations.");
607
  params.AddOptionalParameter("power_default_kappa",
1,716✔
608
                              3.20435e-11,
609
                              "Default `kappa` value (Energy released per fission) to use for "
610
                              "power generation when cross sections do not have `kappa` values. "
611
                              "Default: 3.20435e-11 Joule (corresponding to 200 MeV per fission).");
612
  params.AddOptionalParameter("field_function_prefix_option",
1,716✔
613
                              "prefix",
614
                              "Prefix option on field function names. Default: `\"prefix\"`. Can "
615
                              "be `\"prefix\"` or `\"solver_name\"`. By default this option uses "
616
                              "the value of the `field_function_prefix` parameter. If this "
617
                              "parameter is not set, flux field functions will be exported as "
618
                              "`phi_gXXX_mYYY` where `XXX` is the zero padded 3 digit group number "
619
                              "and `YYY` is the zero padded 3 digit moment.");
620
  params.AddOptionalParameter("field_function_prefix",
1,716✔
621
                              "",
622
                              "Prefix to use on all field functions. Default: `\"\"`. By default "
623
                              "this option is empty. Ff specified, flux moments will be exported "
624
                              "as `prefix_phi_gXXX_mYYY` where `XXX` is the zero padded 3 digit "
625
                              "group number and `YYY` is the zero padded 3 digit moment. The "
626
                              "underscore after \"prefix\" is added automatically.");
627
  params.ConstrainParameterRange("ags_convergence_check",
2,574✔
628
                                 AllowableRangeList::New({"l2", "pointwise"}));
858✔
629
  params.ConstrainParameterRange("field_function_prefix_option",
2,574✔
630
                                 AllowableRangeList::New({"prefix", "solver_name"}));
858✔
631
  params.ConstrainParameterRange("max_mpi_message_size", AllowableRangeLowLimit::New(1024));
2,574✔
632
  params.ConstrainParameterRange("write_restart_time_interval", AllowableRangeLowLimit::New(0));
2,574✔
633
  params.ConstrainParameterRange("max_ags_iterations", AllowableRangeLowLimit::New(0));
2,574✔
634
  params.ConstrainParameterRange("ags_tolerance", AllowableRangeLowLimit::New(1.0e-18));
2,574✔
635
  params.ConstrainParameterRange("power_default_kappa", AllowableRangeLowLimit::New(0.0, false));
2,574✔
636

637
  return params;
858✔
UNCOV
638
}
×
639

640
InputParameters
641
LBSProblem::GetXSMapEntryBlock()
1,070✔
642
{
643
  InputParameters params;
1,070✔
644
  params.SetGeneralDescription("Set the cross-section map for the solver.");
2,140✔
645
  params.AddRequiredParameterArray("block_ids", "Mesh block IDs");
2,140✔
646
  params.AddRequiredParameter<std::shared_ptr<MultiGroupXS>>("xs", "Cross-section object");
2,140✔
647
  return params;
1,070✔
UNCOV
648
}
×
649

650
void
651
LBSProblem::ParseOptions(const InputParameters& input)
428✔
652
{
653
  auto params = LBSProblem::GetOptionsBlock();
428✔
654
  params.AssignParameters(input);
428✔
655
  const auto& params_at_assignment = input.GetParametersAtAssignment();
428✔
656
  const auto& specified_params = params_at_assignment.GetNumParameters() > 0
428✔
657
                                   ? params_at_assignment
428✔
658
                                   : static_cast<const ParameterBlock&>(input);
428✔
659

660
  using OptionSetter = std::function<void(const ParameterBlock&)>;
428✔
661
  const std::unordered_map<std::string, OptionSetter> option_setters = {
428✔
662
    {"max_mpi_message_size",
UNCOV
663
     [this](const ParameterBlock& spec) { options_.max_mpi_message_size = spec.GetValue<int>(); }},
×
664
    {"restart_writes_enabled",
665
     [this](const ParameterBlock& spec)
856✔
UNCOV
666
     { options_.restart_writes_enabled = spec.GetValue<bool>(); }},
×
667
    {"write_delayed_psi_to_restart",
668
     [this](const ParameterBlock& spec)
856✔
UNCOV
669
     { options_.write_delayed_psi_to_restart = spec.GetValue<bool>(); }},
×
670
    {"read_restart_path",
671
     [this](const ParameterBlock& spec)
868✔
672
     { options_.read_restart_path = BuildRestartPath(spec.GetValue<std::string>()); }},
24✔
673
    {"write_restart_path",
674
     [this](const ParameterBlock& spec)
856✔
UNCOV
675
     { options_.write_restart_path = BuildRestartPath(spec.GetValue<std::string>()); }},
×
676
    {"write_restart_time_interval",
677
     [this](const ParameterBlock& spec)
856✔
UNCOV
678
     { options_.write_restart_time_interval = std::chrono::seconds(spec.GetValue<int>()); }},
×
679
    {"use_precursors",
680
     [this](const ParameterBlock& spec) { options_.use_precursors = spec.GetValue<bool>(); }},
124✔
681
    {"use_source_moments",
682
     [this](const ParameterBlock& spec) { options_.use_src_moments = spec.GetValue<bool>(); }},
4✔
683
    {"save_angular_flux",
684
     [this](const ParameterBlock& spec) { options_.save_angular_flux = spec.GetValue<bool>(); }},
258✔
685
    {"verbose_inner_iterations",
686
     [this](const ParameterBlock& spec)
1,082✔
687
     { options_.verbose_inner_iterations = spec.GetValue<bool>(); }},
226✔
688
    {"max_ags_iterations",
689
     [this](const ParameterBlock& spec) { options_.max_ags_iterations = spec.GetValue<int>(); }},
68✔
690
    {"ags_tolerance",
691
     [this](const ParameterBlock& spec) { options_.ags_tolerance = spec.GetValue<double>(); }},
8✔
692
    {"ags_convergence_check",
693
     [this](const ParameterBlock& spec)
856✔
UNCOV
694
     { options_.ags_pointwise_convergence = (spec.GetValue<std::string>() == "pointwise"); }},
×
695
    {"verbose_ags_iterations",
696
     [this](const ParameterBlock& spec)
988✔
697
     { options_.verbose_ags_iterations = spec.GetValue<bool>(); }},
132✔
698
    {"verbose_outer_iterations",
699
     [this](const ParameterBlock& spec)
1,058✔
700
     { options_.verbose_outer_iterations = spec.GetValue<bool>(); }},
202✔
701
    {"power_default_kappa",
702
     [this](const ParameterBlock& spec)
869✔
703
     { options_.power_default_kappa = spec.GetValue<double>(); }},
13✔
704
    {"field_function_prefix_option",
705
     [this](const ParameterBlock& spec)
856✔
UNCOV
706
     { options_.field_function_prefix_option = spec.GetValue<std::string>(); }},
×
707
    {"field_function_prefix",
708
     [this](const ParameterBlock& spec)
856✔
UNCOV
709
     { options_.field_function_prefix = spec.GetValue<std::string>(); }},
×
710
    {"adjoint", [this](const ParameterBlock& spec) { options_.adjoint = spec.GetValue<bool>(); }},
16✔
711
  };
8,988✔
712

713
  for (const auto& spec : specified_params.GetParameters())
1,491✔
714
  {
715
    const auto setter_it = option_setters.find(spec.GetName());
2,126✔
716
    if (setter_it != option_setters.end())
1,063✔
717
      setter_it->second(spec);
1,063✔
718
  }
719

720
  OpenSnInvalidArgumentIf(options_.write_restart_time_interval > std::chrono::seconds(0) and
428✔
721
                            not options_.restart_writes_enabled,
722
                          GetName() + ": `write_restart_time_interval>0` requires "
723
                                      "`restart_writes_enabled=true`.");
724

725
  OpenSnInvalidArgumentIf(options_.write_restart_time_interval > std::chrono::seconds(0) and
428✔
726
                            options_.write_restart_time_interval < std::chrono::seconds(30),
727
                          GetName() + ": `write_restart_time_interval` must be 0 (disabled) "
728
                                      "or at least 30 seconds.");
729

730
  OpenSnInvalidArgumentIf(options_.restart_writes_enabled and options_.write_restart_path.empty(),
428✔
731
                          GetName() + ": `restart_writes_enabled=true` requires a non-empty "
732
                                      "`write_restart_path`.");
733

734
  OpenSnInvalidArgumentIf(not options_.field_function_prefix.empty() and
428✔
735
                            options_.field_function_prefix_option != "prefix",
736
                          GetName() + ": non-empty `field_function_prefix` requires "
737
                                      "`field_function_prefix_option=\"prefix\"`.");
738

739
  if (options_.restart_writes_enabled)
428✔
740
  {
UNCOV
741
    const auto dir = options_.write_restart_path.parent_path();
×
742

743
    // Create restart directory if necessary.
744
    // If dir is empty, write path resolves relative to the working directory.
UNCOV
745
    if ((not dir.empty()) and opensn::mpi_comm.rank() == 0)
×
746
    {
UNCOV
747
      if (not std::filesystem::exists(dir))
×
748
      {
UNCOV
749
        OpenSnLogicalErrorIf(not std::filesystem::create_directories(dir),
×
750
                             GetName() + ": Failed to create restart directory " + dir.string());
751
      }
752
      else
UNCOV
753
        OpenSnLogicalErrorIf(not std::filesystem::is_directory(dir),
×
754
                             GetName() + ": Restart path exists but is not a directory " +
755
                               dir.string());
756
    }
UNCOV
757
    opensn::mpi_comm.barrier();
×
758
    UpdateRestartWriteTime();
×
UNCOV
759
  }
×
760
}
856✔
761

762
std::filesystem::path
763
LBSProblem::BuildRestartPath(const std::string& path_stem)
12✔
764
{
765
  if (path_stem.empty())
12✔
UNCOV
766
    return {};
×
767

768
  auto path = std::filesystem::path(path_stem);
12✔
769
  path += std::to_string(opensn::mpi_comm.rank()) + ".restart.h5";
36✔
770
  return path;
12✔
771
}
12✔
772

773
void
774
LBSProblem::BuildRuntime()
641✔
775
{
776
  CALI_CXX_MARK_SCOPE("LBSProblem::BuildRuntime");
641✔
777
  if (initialized_)
641✔
UNCOV
778
    return;
×
779

780
  PrintSimHeader();
641✔
781
  mpi_comm.barrier();
641✔
782

783
  InitializeRuntimeCore();
641✔
784
  ValidateRuntimeModeConfiguration();
641✔
785
  InitializeSources();
641✔
786

787
  initialized_ = true;
641✔
788
}
641✔
789

790
void
791
LBSProblem::InitializeRuntimeCore()
641✔
792
{
793
  InitializeSpatialDiscretization();
641✔
794
  InitializeParrays();
641✔
795
  InitializeBoundaries();
641✔
796
  InitializeGPUExtras();
641✔
797
}
641✔
798

799
void
800
LBSProblem::ValidateRuntimeModeConfiguration() const
641✔
801
{
802
  if (options_.adjoint)
641✔
803
    if (const auto* do_problem = dynamic_cast<const DiscreteOrdinatesProblem*>(this);
16✔
804
        do_problem and do_problem->IsTimeDependent())
16✔
UNCOV
805
      OpenSnInvalidArgument(GetName() + ": Time-dependent adjoint problems are not supported.");
×
806
}
641✔
807

808
void
809
LBSProblem::InitializeSources()
641✔
810
{
811
  // Initialize point sources
812
  for (auto& point_source : point_sources_)
649✔
813
    point_source->Initialize(*this);
8✔
814

815
  // Initialize volumetric sources
816
  for (auto& volumetric_source : volumetric_sources_)
1,198✔
817
    volumetric_source->Initialize(*this);
557✔
818
}
641✔
819

820
void
UNCOV
821
LBSProblem::PrintSimHeader()
×
822
{
UNCOV
823
  if (opensn::mpi_comm.rank() == 0)
×
824
  {
UNCOV
825
    std::stringstream outstr;
×
826
    outstr << "\n"
×
UNCOV
827
           << "Initializing " << GetName() << "\n\n"
×
828
           << "Scattering order    : " << scattering_order_ << "\n"
×
UNCOV
829
           << "Number of moments   : " << num_moments_ << "\n"
×
830
           << "Number of groups    : " << num_groups_ << "\n"
×
831
           << "Number of groupsets : " << groupsets_.size() << "\n\n";
×
832

833
    for (const auto& groupset : groupsets_)
×
834
    {
835
      outstr << "***** Groupset " << groupset.id << " *****\n"
×
836
             << "Groups:\n";
×
UNCOV
837
      const auto n_gs_groups = groupset.GetNumGroups();
×
838
      constexpr int groups_per_line = 12;
UNCOV
839
      for (size_t i = 0; i < n_gs_groups; ++i)
×
840
      {
841
        outstr << std::setw(5) << groupset.first_group + i << ' ';
×
842
        if ((i + 1) % groups_per_line == 0)
×
UNCOV
843
          outstr << '\n';
×
844
      }
UNCOV
845
      if (n_gs_groups > 0 && n_gs_groups % groups_per_line != 0)
×
846
        outstr << '\n';
×
847
    }
848

UNCOV
849
    log.Log() << outstr.str() << '\n';
×
850
  }
×
851
}
×
852

853
void
854
LBSProblem::InitializeSources(const InputParameters& params)
641✔
855
{
856
  if (params.Has("volumetric_sources"))
641✔
857
  {
858
    const auto& vol_srcs = params.GetParam("volumetric_sources");
641✔
859
    vol_srcs.RequireBlockTypeIs(ParameterBlockType::ARRAY);
641✔
860
    for (const auto& src : vol_srcs)
1,198✔
861
      volumetric_sources_.push_back(src.GetValue<std::shared_ptr<VolumetricSource>>());
1,114✔
862
  }
863

864
  if (params.Has("point_sources"))
641✔
865
  {
866
    const auto& pt_srcs = params.GetParam("point_sources");
641✔
867
    pt_srcs.RequireBlockTypeIs(ParameterBlockType::ARRAY);
641✔
868
    for (const auto& src : pt_srcs)
649✔
869
      point_sources_.push_back(src.GetValue<std::shared_ptr<PointSource>>());
16✔
870
  }
871
}
641✔
872

873
void
874
LBSProblem::InitializeGroupsets(const InputParameters& params)
641✔
875
{
876
  // Initialize groups
877
  OpenSnInvalidArgumentIf(num_groups_ == 0, GetName() + ": Number of groups must be > 0.");
641✔
878

879
  // Initialize groupsets
880
  const auto& groupsets_array = params.GetParam("groupsets");
641✔
881
  const size_t num_gs = groupsets_array.GetNumParameters();
641✔
882
  OpenSnInvalidArgumentIf(num_gs == 0, GetName() + ": At least one groupset must be specified.");
641✔
883
  for (size_t gs = 0; gs < num_gs; ++gs)
1,359✔
884
  {
885
    const auto& groupset_params = groupsets_array.GetParam(gs);
718✔
886
    InputParameters gs_input_params = LBSGroupset::GetInputParameters();
718✔
887
    gs_input_params.SetObjectType("LBSProblem:LBSGroupset");
718✔
888
    gs_input_params.AssignParameters(groupset_params);
718✔
889
    groupsets_.emplace_back(gs_input_params, gs, *this);
718✔
890
    if (groupsets_.back().GetNumGroups() == 0)
718✔
891
    {
UNCOV
892
      std::stringstream oss;
×
UNCOV
893
      oss << GetName() << ": No groups added to groupset " << groupsets_.back().id;
×
UNCOV
894
      OpenSnInvalidArgument(oss.str());
×
UNCOV
895
    }
×
896
  }
718✔
897
}
641✔
898

899
void
900
LBSProblem::InitializeXSMap(const InputParameters& params)
641✔
901
{
902
  // Build XS map
903
  const auto& xs_array = params.GetParam("xs_map");
641✔
904
  const size_t num_xs = xs_array.GetNumParameters();
641✔
905
  for (size_t i = 0; i < num_xs; ++i)
1,539✔
906
  {
907
    const auto& item_params = xs_array.GetParam(i);
898✔
908
    InputParameters xs_entry_pars = GetXSMapEntryBlock();
898✔
909
    xs_entry_pars.AssignParameters(item_params);
898✔
910

911
    const auto& block_ids_param = xs_entry_pars.GetParam("block_ids");
898✔
912
    block_ids_param.RequireBlockTypeIs(ParameterBlockType::ARRAY);
898✔
913
    const auto& block_ids = block_ids_param.GetVectorValue<unsigned int>();
898✔
914
    auto xs = xs_entry_pars.GetSharedPtrParam<MultiGroupXS>("xs");
898✔
915
    for (const auto& block_id : block_ids)
1,932✔
916
      block_id_to_xs_map_[block_id] = xs;
1,034✔
917
  }
898✔
918
}
641✔
919

920
void
921
LBSProblem::InitializeMaterials()
837✔
922
{
923
  CALI_CXX_MARK_SCOPE("LBSProblem::InitializeMaterials");
837✔
924

925
  log.Log0Verbose1() << "Initializing Materials";
1,674✔
926

927
  // Create set of material ids locally relevant
928
  int invalid_mat_cell_count = 0;
837✔
929
  std::set<unsigned int> unique_block_ids;
837✔
930
  for (auto& cell : grid_->local_cells)
665,633✔
931
  {
932
    unique_block_ids.insert(cell.block_id);
664,796✔
933
    if (cell.block_id == std::numeric_limits<unsigned int>::max() or
664,796✔
934
        (block_id_to_xs_map_.find(cell.block_id) == block_id_to_xs_map_.end()))
664,796✔
UNCOV
935
      ++invalid_mat_cell_count;
×
936
  }
937
  const auto& ghost_cell_ids = grid_->cells.GetGhostGlobalIDs();
837✔
938
  for (uint64_t cell_id : ghost_cell_ids)
106,156✔
939
  {
940
    const auto& cell = grid_->cells[cell_id];
105,319✔
941
    unique_block_ids.insert(cell.block_id);
105,319✔
942
    if (cell.block_id == std::numeric_limits<unsigned int>::max() or
105,319✔
943
        (block_id_to_xs_map_.find(cell.block_id) == block_id_to_xs_map_.end()))
105,319✔
UNCOV
944
      ++invalid_mat_cell_count;
×
945
  }
946
  OpenSnLogicalErrorIf(invalid_mat_cell_count > 0,
837✔
947
                       std::to_string(invalid_mat_cell_count) +
948
                         " cells encountered with an invalid material id.");
949

950
  // Get ready for processing
951
  for (const auto& [blk_id, mat] : block_id_to_xs_map_)
2,091✔
952
  {
953
    mat->SetAdjointMode(options_.adjoint);
1,254✔
954

955
    OpenSnLogicalErrorIf(mat->GetNumGroups() < num_groups_,
1,254✔
956
                         "Cross-sections for block \"" + std::to_string(blk_id) +
957
                           "\" have fewer groups (" + std::to_string(mat->GetNumGroups()) +
958
                           ") than the simulation (" + std::to_string(num_groups_) + "). " +
959
                           "Cross-sections must have at least as many groups as the simulation.");
960
  }
961

962
  // Initialize precursor properties
963
  num_precursors_ = 0;
837✔
964
  max_precursors_per_material_ = 0;
837✔
965
  for (const auto& mat_id_xs : block_id_to_xs_map_)
2,091✔
966
  {
967
    const auto& xs = mat_id_xs.second;
1,254✔
968
    num_precursors_ += xs->GetNumPrecursors();
1,254✔
969
    max_precursors_per_material_ = std::max(xs->GetNumPrecursors(), max_precursors_per_material_);
1,254✔
970
  }
971

972
  // if no precursors, turn off precursors
973
  if (num_precursors_ == 0)
837✔
974
    options_.use_precursors = false;
640✔
975

976
  // check compatibility when precursors are on
977
  if (options_.use_precursors)
837✔
978
  {
979
    for (const auto& [mat_id, xs] : block_id_to_xs_map_)
352✔
980
    {
981
      OpenSnLogicalErrorIf(xs->IsFissionable() and xs->GetNumPrecursors() == 0,
176✔
982
                           "Incompatible cross-section data encountered for material id " +
983
                             std::to_string(mat_id) + ". When delayed neutron data is present " +
984
                             "for one fissionable material, it must be present for all fissionable "
985
                             "materials.");
986
    }
987
  }
988

989
  // Update transport views if available
990
  if (grid_->local_cells.size() == cell_transport_views_.size())
837✔
991
    for (const auto& cell : grid_->local_cells)
27,976✔
992
    {
993
      const auto& xs_ptr = block_id_to_xs_map_[cell.block_id];
27,780✔
994
      auto& transport_view = cell_transport_views_[cell.local_id];
27,780✔
995
      transport_view.ReassignXS(*xs_ptr);
27,780✔
996
    }
997

998
  mpi_comm.barrier();
837✔
999
}
837✔
1000

1001
void
1002
LBSProblem::InitializeSpatialDiscretization()
561✔
1003
{
1004
  CALI_CXX_MARK_SCOPE("LBSProblem::InitializeSpatialDiscretization");
561✔
1005

1006
  OpenSnLogicalErrorIf(not discretization_,
561✔
1007
                       GetName() + ": Missing spatial discretization. Construct the problem "
1008
                                   "through its factory Create(...) entry point.");
1009
  log.Log() << "Initializing spatial discretization metadata.\n";
1,122✔
1010

1011
  ComputeUnitIntegrals();
561✔
1012
}
561✔
1013

1014
void
1015
LBSProblem::ComputeUnitIntegrals()
641✔
1016
{
1017
  CALI_CXX_MARK_SCOPE("LBSProblem::ComputeUnitIntegrals");
641✔
1018

1019
  log.Log() << "Computing unit integrals.\n";
1,282✔
1020
  const auto& sdm = *discretization_;
641✔
1021

1022
  const size_t num_local_cells = grid_->local_cells.size();
641✔
1023
  unit_cell_matrices_.resize(num_local_cells);
641✔
1024

1025
  for (const auto& cell : grid_->local_cells)
637,657✔
1026
    unit_cell_matrices_[cell.local_id] =
637,016✔
1027
      ComputeUnitCellIntegrals(sdm, cell, grid_->GetCoordinateSystem());
637,016✔
1028

1029
  const auto ghost_ids = grid_->cells.GetGhostGlobalIDs();
641✔
1030
  for (auto ghost_id : ghost_ids)
97,118✔
1031
    unit_ghost_cell_matrices_[ghost_id] =
96,477✔
1032
      ComputeUnitCellIntegrals(sdm, grid_->cells[ghost_id], grid_->GetCoordinateSystem());
192,954✔
1033

1034
  // Assessing global unit cell matrix storage
1035
  std::array<size_t, 2> num_local_ucms = {unit_cell_matrices_.size(),
641✔
1036
                                          unit_ghost_cell_matrices_.size()};
641✔
1037
  std::array<size_t, 2> num_global_ucms = {0, 0};
641✔
1038

1039
  mpi_comm.all_reduce(num_local_ucms.data(), 2, num_global_ucms.data(), mpi::op::sum<size_t>());
641✔
1040

1041
  opensn::mpi_comm.barrier();
641✔
1042
  log.Log() << "Ghost cell unit cell-matrix ratio: "
641✔
1043
            << (double)num_global_ucms[1] * 100 / (double)num_global_ucms[0] << "%";
1,282✔
1044
  log.Log() << "Cell matrices computed.";
1,282✔
1045
}
641✔
1046

1047
void
1048
LBSProblem::InitializeParrays()
641✔
1049
{
1050
  CALI_CXX_MARK_SCOPE("LBSProblem::InitializeParrays");
641✔
1051

1052
  log.Log() << "Initializing parallel arrays."
1,282✔
1053
            << " G=" << num_groups_ << " M=" << num_moments_ << std::endl;
641✔
1054

1055
  // Initialize unknown
1056
  // structure
1057
  flux_moments_uk_man_.unknowns.clear();
641✔
1058
  for (unsigned int m = 0; m < num_moments_; ++m)
2,195✔
1059
  {
1060
    flux_moments_uk_man_.AddUnknown(UnknownType::VECTOR_N, num_groups_);
1,554✔
1061
    flux_moments_uk_man_.unknowns.back().name = "m" + std::to_string(m);
1,554✔
1062
  }
1063

1064
  // Compute local # of dof
1065
  local_node_count_ = discretization_->GetNumLocalNodes();
641✔
1066
  global_node_count_ = discretization_->GetNumGlobalNodes();
641✔
1067

1068
  // Compute num of unknowns
1069
  size_t local_unknown_count = local_node_count_ * num_groups_ * num_moments_;
641✔
1070

1071
  log.LogAllVerbose1() << "LBS Number of phi unknowns: " << local_unknown_count;
1,282✔
1072

1073
  // Size local vectors
1074
  q_moments_local_.assign(local_unknown_count, 0.0);
641✔
1075
  phi_old_local_.assign(local_unknown_count, 0.0);
641✔
1076
  phi_new_local_.assign(local_unknown_count, 0.0);
641✔
1077

1078
  // Setup precursor vector
1079
  if (options_.use_precursors)
641✔
1080
  {
1081
    size_t num_precursor_dofs = grid_->local_cells.size() * max_precursors_per_material_;
60✔
1082
    precursor_new_local_.assign(num_precursor_dofs, 0.0);
60✔
1083
  }
1084

1085
  // Initialize transport views
1086
  // Transport views act as a data structure to store information
1087
  // related to the transport simulation. The most prominent function
1088
  // here is that it holds the means to know where a given cell's
1089
  // transport quantities are located in the unknown vectors (i.e. phi)
1090
  //
1091
  // Also, for a given cell, within a given sweep chunk,
1092
  // we need to solve a matrix which square size is the
1093
  // amount of nodes on the cell. max_cell_dof_count is
1094
  // initialized here.
1095
  //
1096
  size_t block_MG_counter = 0; // Counts the strides of moment and group
641✔
1097

1098
  const Vector3 ihat(1.0, 0.0, 0.0);
641✔
1099
  const Vector3 jhat(0.0, 1.0, 0.0);
641✔
1100
  const Vector3 khat(0.0, 0.0, 1.0);
641✔
1101

1102
  min_cell_dof_count_ = std::numeric_limits<unsigned int>::max();
641✔
1103
  max_cell_dof_count_ = 0;
641✔
1104
  cell_transport_views_.clear();
641✔
1105
  cell_transport_views_.reserve(grid_->local_cells.size());
641✔
1106
  for (auto& cell : grid_->local_cells)
637,657✔
1107
  {
1108
    size_t num_nodes = discretization_->GetCellNumNodes(cell);
637,016✔
1109

1110
    // compute cell volumes
1111
    double cell_volume = 0.0;
637,016✔
1112
    const auto& IntV_shapeI = unit_cell_matrices_[cell.local_id].intV_shapeI;
637,016✔
1113
    for (size_t i = 0; i < num_nodes; ++i)
4,518,436✔
1114
      cell_volume += IntV_shapeI(i);
3,881,420✔
1115

1116
    size_t cell_phi_address = block_MG_counter;
637,016✔
1117

1118
    const size_t num_faces = cell.faces.size();
637,016✔
1119
    std::vector<bool> face_local_flags(num_faces, true);
637,016✔
1120
    std::vector<int> face_locality(num_faces, opensn::mpi_comm.rank());
637,016✔
1121
    std::vector<const Cell*> neighbor_cell_ptrs(num_faces, nullptr);
637,016✔
1122
    bool cell_on_boundary = false;
637,016✔
1123
    int f = 0;
637,016✔
1124
    for (auto& face : cell.faces)
3,836,600✔
1125
    {
1126
      if (not face.has_neighbor)
3,199,584✔
1127
      {
1128
        cell_on_boundary = true;
113,136✔
1129
        face_local_flags[f] = false;
113,136✔
1130
        face_locality[f] = -1;
113,136✔
1131
      } // if bndry
1132
      else
1133
      {
1134
        const int neighbor_partition = face.GetNeighborPartitionID(grid_.get());
3,086,448✔
1135
        face_local_flags[f] = (neighbor_partition == opensn::mpi_comm.rank());
3,086,448✔
1136
        face_locality[f] = neighbor_partition;
3,086,448✔
1137
        neighbor_cell_ptrs[f] = &grid_->cells[face.neighbor_id];
3,086,448✔
1138
      }
1139

1140
      ++f;
3,199,584✔
1141
    } // for f
1142

1143
    max_cell_dof_count_ = std::max(max_cell_dof_count_, static_cast<unsigned int>(num_nodes));
637,016✔
1144
    min_cell_dof_count_ = std::min(min_cell_dof_count_, static_cast<unsigned int>(num_nodes));
637,016✔
1145
    cell_transport_views_.emplace_back(cell_phi_address,
1,274,032✔
1146
                                       num_nodes,
1147
                                       num_groups_,
637,016✔
1148
                                       num_moments_,
637,016✔
1149
                                       num_faces,
1150
                                       *block_id_to_xs_map_[cell.block_id],
637,016✔
1151
                                       cell_volume,
1152
                                       face_local_flags,
1153
                                       face_locality,
1154
                                       neighbor_cell_ptrs,
1155
                                       cell_on_boundary);
1156
    block_MG_counter += num_nodes * num_groups_ * num_moments_;
637,016✔
1157
  } // for local cell
637,016✔
1158

1159
  // Populate grid nodal mappings
1160
  // This is used in the Flux Data Structures (FLUDS)
1161
  grid_nodal_mappings_.clear();
641✔
1162
  grid_nodal_mappings_.reserve(grid_->local_cells.size());
641✔
1163
  for (auto& cell : grid_->local_cells)
637,657✔
1164
  {
1165
    CellFaceNodalMapping cell_nodal_mapping;
637,016✔
1166
    cell_nodal_mapping.reserve(cell.faces.size());
637,016✔
1167

1168
    for (auto& face : cell.faces)
3,836,600✔
1169
    {
1170
      std::vector<short> face_node_mapping;
3,199,584✔
1171
      std::vector<short> cell_node_mapping;
3,199,584✔
1172
      int adj_face_idx = -1;
3,199,584✔
1173

1174
      if (face.has_neighbor)
3,199,584✔
1175
      {
1176
        grid_->FindAssociatedVertices(face, face_node_mapping);
3,086,448✔
1177
        grid_->FindAssociatedCellVertices(face, cell_node_mapping);
3,086,448✔
1178
        adj_face_idx = face.GetNeighborAdjacentFaceIndex(grid_.get());
3,086,448✔
1179
      }
1180

1181
      cell_nodal_mapping.emplace_back(adj_face_idx, face_node_mapping, cell_node_mapping);
3,199,584✔
1182
    } // for f
3,199,584✔
1183

1184
    grid_nodal_mappings_.push_back(cell_nodal_mapping);
637,016✔
1185
  } // for local cell
637,016✔
1186

1187
  // Get grid localized communicator set
1188
  grid_local_comm_set_ = grid_->MakeMPILocalCommunicatorSet();
641✔
1189

1190
  opensn::mpi_comm.barrier();
641✔
1191
  log.Log() << "Done with parallel arrays." << std::endl;
1,282✔
1192
}
641✔
1193

1194
std::string
1195
LBSProblem::MakeFieldFunctionName(const std::string& base_name) const
42,313✔
1196
{
1197
  std::string prefix;
42,313✔
1198
  if (options_.field_function_prefix_option == "prefix")
42,313✔
1199
  {
1200
    prefix = options_.field_function_prefix;
42,313✔
1201
    if (not prefix.empty())
42,313✔
NEW
1202
      prefix += "_";
×
1203
  }
1204
  if (options_.field_function_prefix_option == "solver_name")
42,313✔
NEW
1205
    prefix = GetName() + "_";
×
1206

1207
  return prefix + base_name;
42,313✔
1208
}
42,313✔
1209

1210
std::string
1211
LBSProblem::MakeScalarFluxFieldFunctionName(const unsigned int g, const unsigned int m) const
42,311✔
1212
{
1213
  std::ostringstream oss;
42,311✔
1214
  oss << MakeFieldFunctionName("phi_g") << std::setw(3) << std::setfill('0') << static_cast<int>(g)
126,933✔
1215
      << "_m" << std::setw(2) << std::setfill('0') << static_cast<int>(m);
42,311✔
1216
  return oss.str();
84,622✔
1217
}
42,311✔
1218

1219
std::shared_ptr<FieldFunctionGridBased>
1220
LBSProblem::CreateEmptyFieldFunction(const std::string& name) const
42,313✔
1221
{
1222
  auto discretization = discretization_;
42,313✔
1223
  return std::make_shared<FieldFunctionGridBased>(
42,313✔
1224
    name, discretization, Unknown(UnknownType::SCALAR));
84,626✔
1225
}
42,313✔
1226

1227
std::vector<double>
1228
LBSProblem::ComputeScalarFluxFieldFunctionData(const unsigned int g, const unsigned int m) const
42,311✔
1229
{
1230
  const auto& sdm = *discretization_;
42,311✔
1231
  const auto& phi_uk_man = flux_moments_uk_man_;
42,311✔
1232
  std::vector<double> data_vector_local(local_node_count_, 0.0);
42,311✔
1233

1234
  for (const auto& cell : grid_->local_cells)
13,724,497✔
1235
  {
1236
    const auto& cell_mapping = sdm.GetCellMapping(cell);
13,682,186✔
1237
    const size_t num_nodes = cell_mapping.GetNumNodes();
13,682,186✔
1238

1239
    for (size_t i = 0; i < num_nodes; ++i)
85,394,722✔
1240
    {
1241
      const auto imapA = sdm.MapDOFLocal(cell, i, phi_uk_man, m, g);
71,712,536✔
1242
      const auto imapB = sdm.MapDOFLocal(cell, i);
71,712,536✔
1243
      data_vector_local[imapB] = phi_new_local_[imapA];
71,712,536✔
1244
    }
1245
  }
1246

1247
  return data_vector_local;
42,311✔
NEW
1248
}
×
1249

1250
double
1251
LBSProblem::ComputeFieldFunctionPowerScaleFactor(const double power_normalization_target) const
1✔
1252
{
1253
  OpenSnInvalidArgumentIf(power_normalization_target <= 0.0,
1✔
1254
                          GetName() + ": power_normalization_target must be positive.");
1255

1256
  double local_total_power = 0.0;
1✔
1257
  auto power_vector = ComputePowerFieldFunctionData(local_total_power);
1✔
1258
  (void)power_vector;
1✔
1259

1260
  double global_total_power = 0.0;
1✔
1261
  mpi_comm.all_reduce(local_total_power, global_total_power, mpi::op::sum<double>());
1✔
1262
  OpenSnLogicalErrorIf(
1✔
1263
    global_total_power <= 0.0,
1264
    GetName() + ": Power normalization requested, but global total power is non-positive.");
1265

1266
  return power_normalization_target / global_total_power;
1✔
1267
}
1✔
1268

1269
const std::vector<double>*
1270
LBSProblem::GetFieldFunctionCoefficients(const MultiGroupXS& xs, const std::string& xs_name) const
50✔
1271
{
1272
  if (xs_name == "sigma_t")
50✔
NEW
1273
    return &xs.GetSigmaTotal();
×
1274
  if (xs_name == "sigma_a")
50✔
NEW
1275
    return &xs.GetSigmaAbsorption();
×
1276
  if (xs_name == "sigma_f")
50✔
1277
    return xs.GetSigmaFission().empty() ? nullptr : &xs.GetSigmaFission();
50✔
NEW
1278
  if (xs_name == "nu_sigma_f")
×
NEW
1279
    return xs.GetNuSigmaF().empty() ? nullptr : &xs.GetNuSigmaF();
×
NEW
1280
  if (xs_name == "chi")
×
NEW
1281
    return xs.GetChi().empty() ? nullptr : &xs.GetChi();
×
NEW
1282
  if (xs_name == "inv_velocity")
×
NEW
1283
    return xs.GetInverseVelocity().empty() ? nullptr : &xs.GetInverseVelocity();
×
NEW
1284
  if (xs.HasCustomXS(xs_name))
×
NEW
1285
    return &xs.GetCustomXS(xs_name);
×
1286

NEW
1287
  throw std::logic_error(GetName() + ": Unknown 1D cross section \"" + xs_name +
×
NEW
1288
                         "\" requested for field function generation.");
×
1289
}
1290

1291
std::vector<double>
1292
LBSProblem::ComputeXSFieldFunctionData(const std::string& xs_name) const
1✔
1293
{
1294
  const auto& sdm = *discretization_;
1✔
1295
  const auto& phi_uk_man = flux_moments_uk_man_;
1✔
1296
  std::vector<double> data_vector_local(local_node_count_, 0.0);
1✔
1297

1298
  for (const auto& cell : grid_->local_cells)
51✔
1299
  {
1300
    const auto& cell_mapping = sdm.GetCellMapping(cell);
50✔
1301
    const size_t num_nodes = cell_mapping.GetNumNodes();
50✔
1302
    const auto& xs = block_id_to_xs_map_.at(cell.block_id);
50✔
1303
    const auto* coeffs = GetFieldFunctionCoefficients(*xs, xs_name);
50✔
1304

1305
    if (coeffs == nullptr)
50✔
NEW
1306
      continue;
×
1307

1308
    OpenSnLogicalErrorIf(coeffs->size() != num_groups_,
50✔
1309
                         GetName() + ": 1D cross section \"" + xs_name +
1310
                           "\" has incompatible group size for field function generation.");
1311

1312
    for (size_t i = 0; i < num_nodes; ++i)
150✔
1313
    {
1314
      const auto imapA = sdm.MapDOFLocal(cell, i);
100✔
1315
      const auto imapB = sdm.MapDOFLocal(cell, i, phi_uk_man, 0, 0);
100✔
1316

1317
      double nodal_value = 0.0;
1318
      for (unsigned int g = 0; g < num_groups_; ++g)
200✔
1319
        nodal_value += coeffs->at(g) * phi_new_local_[imapB + g];
100✔
1320

1321
      data_vector_local[imapA] = nodal_value;
100✔
1322
    }
1323
  }
1324

1325
  return data_vector_local;
1✔
NEW
1326
}
×
1327

1328
std::vector<double>
1329
LBSProblem::ComputePowerFieldFunctionData(double& local_total_power) const
2✔
1330
{
1331
  const auto& sdm = *discretization_;
2✔
1332
  const auto& phi_uk_man = flux_moments_uk_man_;
2✔
1333
  std::vector<double> data_vector_power_local(local_node_count_, 0.0);
2✔
1334
  local_total_power = 0.0;
2✔
1335

1336
  for (const auto& cell : grid_->local_cells)
102✔
1337
  {
1338
    const auto& cell_mapping = sdm.GetCellMapping(cell);
100✔
1339
    const size_t num_nodes = cell_mapping.GetNumNodes();
100✔
1340

1341
    const auto& Vi = unit_cell_matrices_[cell.local_id].intV_shapeI;
100✔
1342
    const auto& xs = block_id_to_xs_map_.at(cell.block_id);
100✔
1343

1344
    if (not xs->IsFissionable())
100✔
NEW
1345
      continue;
×
1346

1347
    for (size_t i = 0; i < num_nodes; ++i)
300✔
1348
    {
1349
      const auto imapA = sdm.MapDOFLocal(cell, i);
200✔
1350
      const auto imapB = sdm.MapDOFLocal(cell, i, phi_uk_man, 0, 0);
200✔
1351

1352
      double nodal_power = 0.0;
1353
      for (unsigned int g = 0; g < num_groups_; ++g)
400✔
1354
      {
1355
        const double sigma_fg = xs->GetSigmaFission()[g];
200✔
1356
        const double kappa_g = options_.power_default_kappa;
200✔
1357
        nodal_power += kappa_g * sigma_fg * phi_new_local_[imapB + g];
200✔
1358
      }
1359

1360
      data_vector_power_local[imapA] = nodal_power;
200✔
1361
      local_total_power += nodal_power * Vi(i);
200✔
1362
    }
1363
  }
1364

1365
  return data_vector_power_local;
2✔
1366
}
×
1367

1368
void
1369
LBSProblem::InitializeSolverSchemes()
849✔
1370
{
1371
  CALI_CXX_MARK_SCOPE("LBSProblem::InitializeSolverSchemes");
849✔
1372
  ags_solver_.reset();
849✔
1373
  wgs_solvers_.clear();
849✔
1374
  wgs_contexts_.clear();
849✔
1375
  InitializeWGSContexts();
849✔
1376
}
849✔
1377

1378
#ifndef __OPENSN_WITH_GPU__
1379
void
1380
LBSProblem::InitializeGPUExtras()
1381
{
1382
}
1383

1384
void
1385
LBSProblem::ResetGPUCarriers()
1386
{
1387
}
1388

1389
void
1390
LBSProblem::CheckCapableDevices()
1391
{
1392
}
1393
#endif // __OPENSN_WITH_GPU__
1394

1395
std::vector<double>
1396
LBSProblem::MakeSourceMomentsFromPhi()
4✔
1397
{
1398
  CALI_CXX_MARK_SCOPE("LBSProblem::MakeSourceMomentsFromPhi");
4✔
1399

1400
  size_t num_local_dofs = discretization_->GetNumLocalDOFs(flux_moments_uk_man_);
4✔
1401

1402
  std::vector<double> source_moments(num_local_dofs, 0.0);
4✔
1403
  for (auto& groupset : groupsets_)
8✔
1404
  {
1405
    active_set_source_function_(groupset,
4✔
1406
                                source_moments,
1407
                                phi_new_local_,
4✔
1408
                                APPLY_AGS_SCATTER_SOURCES | APPLY_WGS_SCATTER_SOURCES |
1409
                                  APPLY_AGS_FISSION_SOURCES | APPLY_WGS_FISSION_SOURCES);
4✔
1410
  }
1411

1412
  return source_moments;
4✔
1413
}
4✔
1414

1415
LBSProblem::~LBSProblem()
629✔
1416
{
1417
  ResetGPUCarriers();
1418
}
3,104✔
1419

629✔
1420
void
UNCOV
1421
LBSProblem::SetSaveAngularFlux(bool save)
×
1422
{
UNCOV
1423
  options_.save_angular_flux = save;
×
1424
}
×
1425

1426
void
1427
LBSProblem::ZeroPhi()
76✔
1428
{
1429
  std::fill(phi_old_local_.begin(), phi_old_local_.end(), 0.0);
76✔
1430
  std::fill(phi_new_local_.begin(), phi_new_local_.end(), 0.0);
76✔
1431
}
76✔
1432

1433
void
1434
LBSProblem::CopyPhiNewToOld()
160✔
1435
{
1436
  assert(phi_old_local_.size() == phi_new_local_.size() && "Phi vectors size mismatch.");
160✔
1437
  phi_old_local_ = phi_new_local_;
160✔
1438
}
160✔
1439

1440
void
1441
LBSProblem::SetPhiOldFrom(const std::vector<double>& phi_old)
2,256✔
1442
{
1443
  assert(phi_old.size() == phi_old_local_.size() && "SetPhiOldFrom size mismatch.");
2,256✔
1444
  phi_old_local_ = phi_old;
2,256✔
1445
}
2,256✔
1446

1447
void
UNCOV
1448
LBSProblem::SetPhiNewFrom(const std::vector<double>& phi_new)
×
1449
{
UNCOV
1450
  assert(phi_new.size() == phi_new_local_.size() && "SetPhiNewFrom size mismatch.");
×
UNCOV
1451
  phi_new_local_ = phi_new;
×
UNCOV
1452
}
×
1453

1454
void
UNCOV
1455
LBSProblem::ScalePhiOld(double factor)
×
1456
{
UNCOV
1457
  for (auto& value : phi_old_local_)
×
UNCOV
1458
    value *= factor;
×
UNCOV
1459
}
×
1460

1461
void
1462
LBSProblem::ScalePhiNew(double factor)
8✔
1463
{
1464
  for (auto& value : phi_new_local_)
168,008✔
1465
    value *= factor;
168,000✔
1466
}
8✔
1467

1468
void
1469
LBSProblem::ZeroQMoments()
64,118✔
1470
{
1471
  assert(q_moments_local_.size() == phi_old_local_.size() && "Q moments/Phi size mismatch.");
64,118✔
1472
  std::fill(q_moments_local_.begin(), q_moments_local_.end(), 0.0);
64,118✔
1473
}
64,118✔
1474

1475
void
1476
LBSProblem::ScaleQMoments(double factor)
8,593✔
1477
{
1478
  for (auto& value : q_moments_local_)
610,183,797✔
1479
    value *= factor;
610,175,204✔
1480
}
8,593✔
1481

1482
void
1483
LBSProblem::SetQMomentsFrom(const std::vector<double>& q_moments)
157,784✔
1484
{
1485
  assert(q_moments.size() == q_moments_local_.size() && "SetQMomentsFrom size mismatch.");
157,784✔
1486
  q_moments_local_ = q_moments;
157,784✔
1487
}
157,784✔
1488

1489
void
1490
LBSProblem::ScalePrecursors(double factor)
56✔
1491
{
1492
  for (auto& value : precursor_new_local_)
1,044✔
1493
    value *= factor;
988✔
1494
}
56✔
1495

1496
void
1497
LBSProblem::ZeroPrecursors()
744✔
1498
{
1499
  std::fill(precursor_new_local_.begin(), precursor_new_local_.end(), 0.0);
744✔
1500
}
744✔
1501

1502
void
UNCOV
1503
LBSProblem::ZeroExtSrcMoments()
×
1504
{
UNCOV
1505
  std::fill(ext_src_moments_local_.begin(), ext_src_moments_local_.end(), 0.0);
×
UNCOV
1506
}
×
1507

1508
void
UNCOV
1509
LBSProblem::ScaleExtSrcMoments(double factor)
×
1510
{
UNCOV
1511
  for (auto& value : ext_src_moments_local_)
×
UNCOV
1512
    value *= factor;
×
UNCOV
1513
}
×
1514

1515
void
1516
LBSProblem::SetAdjoint(bool adjoint)
24✔
1517
{
1518
  OpenSnLogicalErrorIf(
24✔
1519
    not initialized_, GetName() + ": Problem must be fully constructed before calling SetAdjoint.");
1520

1521
  if (adjoint)
24✔
1522
    if (const auto* do_problem = dynamic_cast<const DiscreteOrdinatesProblem*>(this);
20✔
1523
        do_problem and do_problem->IsTimeDependent())
20✔
UNCOV
1524
      OpenSnInvalidArgument(GetName() + ": Time-dependent adjoint problems are not supported.");
×
1525

1526
  const bool mode_changed = (adjoint != options_.adjoint);
24✔
1527
  if (not mode_changed)
24✔
1528
    return;
1529

1530
  options_.adjoint = adjoint;
24✔
1531

1532
  // Reinitialize materials to obtain the proper forward/adjoint cross sections.
1533
  InitializeMaterials();
24✔
1534

1535
  // Forward and adjoint sources are fundamentally different.
1536
  point_sources_.clear();
24✔
1537
  volumetric_sources_.clear();
24✔
1538
  ClearBoundaries();
24✔
1539

1540
  // Reset all solution vectors.
1541
  ZeroPhi();
24✔
1542
  ZeroPsi();
24✔
1543
  ZeroPrecursors();
24✔
1544
}
1545

1546
void
UNCOV
1547
LBSProblem::SetForward()
×
1548
{
UNCOV
1549
  SetAdjoint(false);
×
UNCOV
1550
}
×
1551

1552
bool
UNCOV
1553
LBSProblem::IsAdjoint() const
×
1554
{
UNCOV
1555
  return options_.adjoint;
×
1556
}
1557

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