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

Open-Sn / opensn / 24437745168

15 Apr 2026 03:39AM UTC coverage: 74.813% (-0.2%) from 75.028%
24437745168

push

github

web-flow
Merge pull request #955 from wdhawkins/cepxs

Add support for CEPXS cross sections

15 of 212 new or added lines in 6 files covered. (7.08%)

319 existing lines in 10 files now uncovered.

21208 of 28348 relevant lines covered (74.81%)

65820875.2 hits per line

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

85.13
/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 "modules/linear_boltzmann_solvers/lbs_problem/io/lbs_problem_io.h"
11
#include "framework/field_functions/field_function_grid_based.h"
12
#include "framework/materials/multi_group_xs/multi_group_xs.h"
13
#include "framework/mesh/mesh_continuum/mesh_continuum.h"
14
#include "framework/utils/hdf_utils.h"
15
#include "framework/object_factory.h"
16
#include "framework/logging/log.h"
17
#include "framework/runtime.h"
18
#include "framework/data_types/allowable_range.h"
19
#include "framework/utils/error.h"
20
#include "caliper/cali.h"
21
#include <algorithm>
22
#include <iomanip>
23
#include <fstream>
24
#include <cstring>
25
#include <cassert>
26
#include <memory>
27
#include <stdexcept>
28
#include <sys/stat.h>
29
#include <unordered_map>
30
#include <functional>
31
#include <utility>
32

33
namespace opensn
34
{
35

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

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

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

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

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

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

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

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

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

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

68
  return params;
663✔
UNCOV
69
}
×
70

71
LBSProblem::LBSProblem(const InputParameters& params)
663✔
72
  : Problem(params),
73
    num_groups_(params.GetParamValue<unsigned int>("num_groups")),
663✔
74
    grid_(params.GetSharedPtrParam<MeshContinuum>("mesh")),
663✔
75
    use_gpus_(params.GetParamValue<bool>("use_gpus"))
1,989✔
76
{
77
  // Check system for GPU acceleration
78
  if (use_gpus_)
663✔
79
  {
80
#ifdef __OPENSN_WITH_GPU__
81
    CheckCapableDevices();
52✔
82
#else
83
    OpenSnInvalidArgument(
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"))
663✔
90
  {
91
    auto options_params = LBSProblem::GetOptionsBlock();
450✔
92
    options_params.AssignParameters(params.GetParam("options"));
452✔
93
    ParseOptions(options_params);
448✔
94
  }
450✔
95

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

101
  InitializeGroupsets(params);
661✔
102
  InitializeSources(params);
661✔
103
  InitializeXSMap(params);
661✔
104
  InitializeMaterials();
661✔
105
}
683✔
106

107
const LBSOptions&
108
LBSProblem::GetOptions() const
1,265,008,275✔
109
{
110
  return options_;
1,265,008,275✔
111
}
112

113
double
114
LBSProblem::GetTime() const
186,306✔
115
{
116
  return time_;
186,306✔
117
}
118

119
void
120
LBSProblem::SetTime(double time)
6,052✔
121
{
122
  time_ = time;
6,052✔
123
}
6,052✔
124

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

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

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

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

152
bool
UNCOV
153
LBSProblem::IsTimeDependent() const
×
154
{
UNCOV
155
  return false;
×
156
}
157

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

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

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

176
unsigned int
177
LBSProblem::GetNumMoments() const
314,027✔
178
{
179
  return num_moments_;
314,027✔
180
}
181

182
unsigned int
183
LBSProblem::GetMaxCellDOFCount() const
1,146✔
184
{
185
  return max_cell_dof_count_;
1,146✔
186
}
187

188
unsigned int
189
LBSProblem::GetMinCellDOFCount() const
1,146✔
190
{
191
  return min_cell_dof_count_;
1,146✔
192
}
193

194
bool
195
LBSProblem::UseGPUs() const
1,682✔
196
{
197
  return use_gpus_;
1,682✔
198
}
199

200
unsigned int
201
LBSProblem::GetNumGroups() const
490,115✔
202
{
203
  return num_groups_;
490,115✔
204
}
205

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

212
unsigned int
UNCOV
213
LBSProblem::GetNumPrecursors() const
×
214
{
UNCOV
215
  return num_precursors_;
×
216
}
217

218
unsigned int
219
LBSProblem::GetMaxPrecursorsPerMaterial() const
10,758✔
220
{
221
  return max_precursors_per_material_;
10,758✔
222
}
223

224
const std::vector<LBSGroupset>&
225
LBSProblem::GetGroupsets() const
28,426✔
226
{
227
  return groupsets_;
28,426✔
228
}
229

230
LBSGroupset&
231
LBSProblem::GetGroupset(size_t groupset_id)
8,767,688✔
232
{
233
  return groupsets_.at(groupset_id);
8,767,688✔
234
}
235

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

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

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

256
void
UNCOV
257
LBSProblem::ClearPointSources()
×
258
{
259
  point_sources_.clear();
×
UNCOV
260
}
×
261

262
const std::vector<std::shared_ptr<PointSource>>&
263
LBSProblem::GetPointSources() const
24,060✔
264
{
265
  return point_sources_;
24,060✔
266
}
267

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

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

282
const std::vector<std::shared_ptr<VolumetricSource>>&
283
LBSProblem::GetVolumetricSources() const
24,060✔
284
{
285
  return volumetric_sources_;
24,060✔
286
}
287

288
const BlockID2XSMap&
289
LBSProblem::GetBlockID2XSMap() const
21,281✔
290
{
291
  return block_id_to_xs_map_;
21,281✔
292
}
293

294
void
295
LBSProblem::SetBlockID2XSMap(const BlockID2XSMap& xs_map)
184✔
296
{
297
  const BlockID2XSMap old_xs_map = block_id_to_xs_map_;
184✔
298
  const size_t old_max_precursors_per_material = max_precursors_per_material_;
184✔
299
  const auto old_precursor_state = precursor_new_local_;
184✔
300

301
  block_id_to_xs_map_ = xs_map;
184✔
302
  InitializeMaterials();
184✔
303

304
  if (initialized_)
184✔
305
  {
306
    if (options_.use_precursors)
184✔
307
    {
308
      const size_t num_cells = grid_->local_cells.size();
128✔
309
      const size_t new_max_precursors_per_material = max_precursors_per_material_;
128✔
310
      const size_t num_precursor_dofs = num_cells * new_max_precursors_per_material;
128✔
311

312
      std::vector<double> remapped_precursors(num_precursor_dofs, 0.0);
128✔
313
      if (old_precursor_state.size() == num_cells * old_max_precursors_per_material)
128✔
314
      {
315
        for (const auto& cell : grid_->local_cells)
2,024✔
316
        {
317
          unsigned int old_num_precursors = 0;
1,896✔
318
          if (const auto old_xs_it = old_xs_map.find(cell.block_id); old_xs_it != old_xs_map.end())
1,896✔
319
            old_num_precursors = old_xs_it->second->GetNumPrecursors();
1,896✔
320

321
          const unsigned int new_num_precursors =
1,896✔
322
            block_id_to_xs_map_.at(cell.block_id)->GetNumPrecursors();
1,896✔
323
          const unsigned int num_precursors_to_copy =
1,896✔
324
            std::min(old_num_precursors, new_num_precursors);
1,896✔
325

326
          const size_t old_base = cell.local_id * old_max_precursors_per_material;
1,896✔
327
          const size_t new_base = cell.local_id * new_max_precursors_per_material;
1,896✔
328
          for (unsigned int j = 0; j < num_precursors_to_copy; ++j)
3,900✔
329
            remapped_precursors[new_base + j] = old_precursor_state[old_base + j];
2,004✔
330
        }
331
      }
332

333
      precursor_new_local_ = std::move(remapped_precursors);
128✔
334
    }
128✔
335
    else
336
      precursor_new_local_.clear();
56✔
337
  }
338

339
  ResetGPUCarriers();
184✔
340
  InitializeGPUExtras();
184✔
341
}
184✔
342

343
std::shared_ptr<MeshContinuum>
344
LBSProblem::GetGrid() const
408,179✔
345
{
346
  return grid_;
408,179✔
347
}
348

349
const SpatialDiscretization&
350
LBSProblem::GetSpatialDiscretization() const
121,319✔
351
{
352
  return *discretization_;
121,319✔
353
}
354

355
const std::vector<UnitCellMatrices>&
356
LBSProblem::GetUnitCellMatrices() const
22,863✔
357
{
358
  return unit_cell_matrices_;
22,863✔
359
}
360

361
const std::map<uint64_t, UnitCellMatrices>&
362
LBSProblem::GetUnitGhostCellMatrices() const
16✔
363
{
364
  return unit_ghost_cell_matrices_;
16✔
365
}
366

367
std::vector<CellLBSView>&
368
LBSProblem::GetCellTransportViews()
186,651✔
369
{
370
  return cell_transport_views_;
186,651✔
371
}
372

373
const std::vector<CellLBSView>&
374
LBSProblem::GetCellTransportViews() const
287,304✔
375
{
376
  return cell_transport_views_;
287,304✔
377
}
378

379
const UnknownManager&
380
LBSProblem::GetUnknownManager() const
26,762✔
381
{
382
  return flux_moments_uk_man_;
26,762✔
383
}
384

385
size_t
386
LBSProblem::GetLocalNodeCount() const
3,402✔
387
{
388
  return local_node_count_;
3,402✔
389
}
390

391
size_t
392
LBSProblem::GetGlobalNodeCount() const
2,762✔
393
{
394
  return global_node_count_;
2,762✔
395
}
396

397
std::vector<double>&
398
LBSProblem::GetQMomentsLocal()
96,917✔
399
{
400
  return q_moments_local_;
96,917✔
401
}
402

403
const std::vector<double>&
UNCOV
404
LBSProblem::GetQMomentsLocal() const
×
405
{
UNCOV
406
  return q_moments_local_;
×
407
}
408

409
std::vector<double>&
410
LBSProblem::GetExtSrcMomentsLocal()
4✔
411
{
412
  return ext_src_moments_local_;
4✔
413
}
414

415
const std::vector<double>&
416
LBSProblem::GetExtSrcMomentsLocal() const
92,303✔
417
{
418
  return ext_src_moments_local_;
92,303✔
419
}
420

421
void
422
LBSProblem::SetExtSrcMomentsFrom(const std::vector<double>& ext_src_moments)
4✔
423
{
424
  if (not phi_old_local_.empty())
4✔
425
    OpenSnLogicalErrorIf(ext_src_moments.size() != phi_old_local_.size(),
4✔
426
                         "SetExtSrcMomentsFrom size mismatch. Provided size=" +
427
                           std::to_string(ext_src_moments.size()) +
428
                           ", expected local DOFs=" + std::to_string(phi_old_local_.size()) + ".");
429

430
  if (ext_src_moments_local_.empty())
4✔
431
  {
432
    ext_src_moments_local_ = ext_src_moments;
4✔
433
    return;
4✔
434
  }
435

UNCOV
436
  assert(ext_src_moments.size() == ext_src_moments_local_.size() &&
×
437
         "SetExtSrcMomentsFrom size mismatch.");
UNCOV
438
  ext_src_moments_local_ = ext_src_moments;
×
439
}
440

441
std::vector<double>&
442
LBSProblem::GetPhiOldLocal()
197,071✔
443
{
444
  return phi_old_local_;
197,071✔
445
}
446

447
const std::vector<double>&
UNCOV
448
LBSProblem::GetPhiOldLocal() const
×
449
{
UNCOV
450
  return phi_old_local_;
×
451
}
452

453
std::vector<double>&
454
LBSProblem::GetPhiNewLocal()
140,658✔
455
{
456
  return phi_new_local_;
140,658✔
457
}
458

459
const std::vector<double>&
460
LBSProblem::GetPhiNewLocal() const
10,395✔
461
{
462
  return phi_new_local_;
10,395✔
463
}
464

465
std::vector<double>&
466
LBSProblem::GetPrecursorsNewLocal()
3,488✔
467
{
468
  return precursor_new_local_;
3,488✔
469
}
470

471
const std::vector<double>&
UNCOV
472
LBSProblem::GetPrecursorsNewLocal() const
×
473
{
UNCOV
474
  return precursor_new_local_;
×
475
}
476

477
SetSourceFunction
478
LBSProblem::GetActiveSetSourceFunction() const
5,134✔
479
{
480
  return active_set_source_function_;
5,134✔
481
}
482

483
void
484
LBSProblem::SetActiveSetSourceFunction(SetSourceFunction source_function)
112✔
485
{
486
  active_set_source_function_ = std::move(source_function);
112✔
487
}
112✔
488

489
std::shared_ptr<AGSLinearSolver>
490
LBSProblem::GetAGSSolver()
4,662✔
491
{
492
  CheckAGSSolverInitialized();
4,662✔
493
  return ags_solver_;
4,662✔
494
}
495

496
std::shared_ptr<LinearSolver>
497
LBSProblem::GetWGSSolver(size_t groupset_id)
3,206✔
498
{
499
  CheckWGSSolversInitialized();
3,206✔
500
  return wgs_solvers_.at(groupset_id);
3,206✔
501
}
502

503
size_t
504
LBSProblem::GetNumWGSSolvers()
8,816✔
505
{
506
  CheckWGSSolversInitialized();
8,816✔
507
  return wgs_solvers_.size();
8,816✔
508
}
509

510
WGSContext&
511
LBSProblem::GetWGSContext(int groupset_id)
14,013✔
512
{
513
  CheckWGSContextsInitialized();
14,013✔
514
  auto& wgs_context_ptr = wgs_contexts_.at(groupset_id);
14,013✔
515
  OpenSnLogicalErrorIf(not wgs_context_ptr, GetName() + ": Null WGS context.");
14,013✔
516
  return *wgs_context_ptr;
14,013✔
517
}
518

519
void
520
LBSProblem::CheckWGSContextsInitialized()
31,725✔
521
{
522
  if (wgs_contexts_.empty())
31,725✔
UNCOV
523
    InitializeWGSContexts();
×
524
}
31,725✔
525

526
void
527
LBSProblem::CheckWGSSolversInitialized()
16,684✔
528
{
529
  CheckWGSContextsInitialized();
16,684✔
530
  if (wgs_solvers_.empty())
16,684✔
531
    InitializeWGSSolvers();
1,028✔
532
}
16,684✔
533

534
void
535
LBSProblem::CheckAGSSolverInitialized()
4,662✔
536
{
537
  CheckWGSSolversInitialized();
4,662✔
538
  if (ags_solver_)
4,662✔
539
    return;
540

541
  ags_solver_ = std::make_shared<AGSLinearSolver>(*this, wgs_solvers_);
836✔
542
  if (groupsets_.size() == 1)
836✔
543
  {
544
    ags_solver_->SetMaxIterations(1);
763✔
545
    ags_solver_->SetVerbosity(false);
763✔
546
  }
547
  else
548
  {
549
    ags_solver_->SetMaxIterations(options_.max_ags_iterations);
73✔
550
    ags_solver_->SetVerbosity(options_.verbose_ags_iterations);
73✔
551
  }
552
  ags_solver_->SetTolerance(options_.ags_tolerance);
836✔
553
}
554

555
std::pair<size_t, size_t>
UNCOV
556
LBSProblem::GetNumPhiIterativeUnknowns()
×
557
{
UNCOV
558
  const auto& sdm = *discretization_;
×
UNCOV
559
  const size_t num_local_phi_dofs = sdm.GetNumLocalDOFs(flux_moments_uk_man_);
×
UNCOV
560
  const size_t num_global_phi_dofs = sdm.GetNumGlobalDOFs(flux_moments_uk_man_);
×
561

UNCOV
562
  return {num_local_phi_dofs, num_global_phi_dofs};
×
563
}
564

565
InputParameters
566
LBSProblem::GetOptionsBlock()
898✔
567
{
568
  InputParameters params;
898✔
569

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

639
  return params;
898✔
UNCOV
640
}
×
641

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

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

662
  using OptionSetter = std::function<void(const ParameterBlock&)>;
448✔
663
  const std::unordered_map<std::string, OptionSetter> option_setters = {
448✔
664
    {"max_mpi_message_size",
UNCOV
665
     [this](const ParameterBlock& spec) { options_.max_mpi_message_size = spec.GetValue<int>(); }},
×
666
    {"restart_writes_enabled",
667
     [this](const ParameterBlock& spec)
900✔
668
     { options_.restart_writes_enabled = spec.GetValue<bool>(); }},
4✔
669
    {"write_delayed_psi_to_restart",
670
     [this](const ParameterBlock& spec)
896✔
UNCOV
671
     { options_.write_delayed_psi_to_restart = spec.GetValue<bool>(); }},
×
672
    {"read_restart_path",
673
     [this](const ParameterBlock& spec)
912✔
674
     { options_.read_restart_path = BuildRestartPath(spec.GetValue<std::string>()); }},
32✔
675
    {"write_restart_path",
676
     [this](const ParameterBlock& spec)
900✔
677
     { options_.write_restart_path = BuildRestartPath(spec.GetValue<std::string>()); }},
8✔
678
    {"write_restart_time_interval",
679
     [this](const ParameterBlock& spec)
896✔
UNCOV
680
     { options_.write_restart_time_interval = std::chrono::seconds(spec.GetValue<int>()); }},
×
681
    {"use_precursors",
682
     [this](const ParameterBlock& spec) { options_.use_precursors = spec.GetValue<bool>(); }},
132✔
683
    {"use_source_moments",
684
     [this](const ParameterBlock& spec) { options_.use_src_moments = spec.GetValue<bool>(); }},
4✔
685
    {"save_angular_flux",
686
     [this](const ParameterBlock& spec) { options_.save_angular_flux = spec.GetValue<bool>(); }},
278✔
687
    {"verbose_inner_iterations",
688
     [this](const ParameterBlock& spec)
1,142✔
689
     { options_.verbose_inner_iterations = spec.GetValue<bool>(); }},
246✔
690
    {"max_ags_iterations",
691
     [this](const ParameterBlock& spec) { options_.max_ags_iterations = spec.GetValue<int>(); }},
68✔
692
    {"ags_tolerance",
693
     [this](const ParameterBlock& spec) { options_.ags_tolerance = spec.GetValue<double>(); }},
8✔
694
    {"ags_convergence_check",
695
     [this](const ParameterBlock& spec)
896✔
UNCOV
696
     { options_.ags_pointwise_convergence = (spec.GetValue<std::string>() == "pointwise"); }},
×
697
    {"verbose_ags_iterations",
698
     [this](const ParameterBlock& spec)
1,048✔
699
     { options_.verbose_ags_iterations = spec.GetValue<bool>(); }},
152✔
700
    {"verbose_outer_iterations",
701
     [this](const ParameterBlock& spec)
1,118✔
702
     { options_.verbose_outer_iterations = spec.GetValue<bool>(); }},
222✔
703
    {"power_default_kappa",
704
     [this](const ParameterBlock& spec)
909✔
705
     { options_.power_default_kappa = spec.GetValue<double>(); }},
13✔
706
    {"field_function_prefix_option",
707
     [this](const ParameterBlock& spec)
896✔
UNCOV
708
     { options_.field_function_prefix_option = spec.GetValue<std::string>(); }},
×
709
    {"field_function_prefix",
710
     [this](const ParameterBlock& spec)
896✔
UNCOV
711
     { options_.field_function_prefix = spec.GetValue<std::string>(); }},
×
712
    {"adjoint", [this](const ParameterBlock& spec) { options_.adjoint = spec.GetValue<bool>(); }},
16✔
713
  };
9,408✔
714

715
  for (const auto& spec : specified_params.GetParameters())
1,611✔
716
  {
717
    const auto setter_it = option_setters.find(spec.GetName());
2,326✔
718
    if (setter_it != option_setters.end())
1,163✔
719
      setter_it->second(spec);
1,163✔
720
  }
721

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

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

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

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

741
  if (options_.restart_writes_enabled)
448✔
742
  {
743
    const auto dir = options_.write_restart_path.parent_path();
4✔
744

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

764
std::filesystem::path
765
LBSProblem::BuildRestartPath(const std::string& path_stem)
20✔
766
{
767
  if (path_stem.empty())
20✔
768
    return {};
×
769

770
  auto path = std::filesystem::path(path_stem);
20✔
771
  path += std::to_string(opensn::mpi_comm.rank()) + ".restart.h5";
60✔
772
  return path;
20✔
773
}
20✔
774

775
bool
776
LBSProblem::ReadRestartData(const RestartDataHook& extra_reader)
16✔
777
{
778
  return LBSSolverIO::ReadRestartData(*this, extra_reader);
16✔
779
}
780

781
bool
782
LBSProblem::WriteRestartData(const RestartDataHook& extra_writer)
4✔
783
{
784
  return LBSSolverIO::WriteRestartData(*this, extra_writer);
4✔
785
}
786

787
bool
788
LBSProblem::ReadProblemRestartData(hid_t /*file_id*/)
×
789
{
UNCOV
790
  return true;
×
791
}
792

793
bool
UNCOV
794
LBSProblem::WriteProblemRestartData(hid_t /*file_id*/) const
×
795
{
UNCOV
796
  return true;
×
797
}
798

799
void
800
LBSProblem::BuildRuntime()
661✔
801
{
802
  CALI_CXX_MARK_SCOPE("LBSProblem::BuildRuntime");
661✔
803
  if (initialized_)
661✔
UNCOV
804
    return;
×
805

806
  PrintSimHeader();
661✔
807
  mpi_comm.barrier();
661✔
808

809
  InitializeRuntimeCore();
661✔
810
  ValidateRuntimeModeConfiguration();
661✔
811
  InitializeSources();
661✔
812

813
  initialized_ = true;
661✔
814
}
661✔
815

816
void
817
LBSProblem::InitializeRuntimeCore()
661✔
818
{
819
  InitializeSpatialDiscretization();
661✔
820
  InitializeParrays();
661✔
821
  InitializeBoundaries();
661✔
822
  InitializeGPUExtras();
661✔
823
}
661✔
824

825
void
826
LBSProblem::ValidateRuntimeModeConfiguration() const
661✔
827
{
828
  if (options_.adjoint)
661✔
829
    if (const auto* do_problem = dynamic_cast<const DiscreteOrdinatesProblem*>(this);
16✔
830
        do_problem and do_problem->IsTimeDependent())
16✔
831
      OpenSnInvalidArgument(GetName() + ": Time-dependent adjoint problems are not supported.");
×
832
}
661✔
833

834
void
835
LBSProblem::InitializeSources()
661✔
836
{
837
  // Initialize point sources
838
  for (auto& point_source : point_sources_)
669✔
839
    point_source->Initialize(*this);
8✔
840

841
  // Initialize volumetric sources
842
  for (auto& volumetric_source : volumetric_sources_)
1,230✔
843
    volumetric_source->Initialize(*this);
569✔
844
}
661✔
845

846
void
847
LBSProblem::PrintSimHeader()
×
848
{
849
  if (opensn::mpi_comm.rank() == 0)
×
850
  {
851
    std::stringstream outstr;
×
852
    outstr << "\n"
×
853
           << "Initializing " << GetName() << "\n\n"
×
UNCOV
854
           << "Scattering order    : " << scattering_order_ << "\n"
×
855
           << "Number of moments   : " << num_moments_ << "\n"
×
856
           << "Number of groups    : " << num_groups_ << "\n"
×
UNCOV
857
           << "Number of groupsets : " << groupsets_.size() << "\n\n";
×
858

859
    for (const auto& groupset : groupsets_)
×
860
    {
861
      outstr << "***** Groupset " << groupset.id << " *****\n"
×
UNCOV
862
             << "Groups:\n";
×
UNCOV
863
      const auto n_gs_groups = groupset.GetNumGroups();
×
864
      constexpr int groups_per_line = 12;
UNCOV
865
      for (size_t i = 0; i < n_gs_groups; ++i)
×
866
      {
UNCOV
867
        outstr << std::setw(5) << groupset.first_group + i << ' ';
×
UNCOV
868
        if ((i + 1) % groups_per_line == 0)
×
UNCOV
869
          outstr << '\n';
×
870
      }
UNCOV
871
      if (n_gs_groups > 0 && n_gs_groups % groups_per_line != 0)
×
UNCOV
872
        outstr << '\n';
×
873
    }
874

UNCOV
875
    log.Log() << outstr.str() << '\n';
×
UNCOV
876
  }
×
UNCOV
877
}
×
878

879
void
880
LBSProblem::InitializeSources(const InputParameters& params)
661✔
881
{
882
  if (params.Has("volumetric_sources"))
661✔
883
  {
884
    const auto& vol_srcs = params.GetParam("volumetric_sources");
661✔
885
    vol_srcs.RequireBlockTypeIs(ParameterBlockType::ARRAY);
661✔
886
    for (const auto& src : vol_srcs)
1,230✔
887
      volumetric_sources_.push_back(src.GetValue<std::shared_ptr<VolumetricSource>>());
1,138✔
888
  }
889

890
  if (params.Has("point_sources"))
661✔
891
  {
892
    const auto& pt_srcs = params.GetParam("point_sources");
661✔
893
    pt_srcs.RequireBlockTypeIs(ParameterBlockType::ARRAY);
661✔
894
    for (const auto& src : pt_srcs)
669✔
895
      point_sources_.push_back(src.GetValue<std::shared_ptr<PointSource>>());
16✔
896
  }
897
}
661✔
898

899
void
900
LBSProblem::InitializeGroupsets(const InputParameters& params)
661✔
901
{
902
  // Initialize groups
903
  OpenSnInvalidArgumentIf(num_groups_ == 0, GetName() + ": Number of groups must be > 0.");
661✔
904

905
  // Initialize groupsets
906
  const auto& groupsets_array = params.GetParam("groupsets");
661✔
907
  const size_t num_gs = groupsets_array.GetNumParameters();
661✔
908
  OpenSnInvalidArgumentIf(num_gs == 0, GetName() + ": At least one groupset must be specified.");
661✔
909
  for (size_t gs = 0; gs < num_gs; ++gs)
1,399✔
910
  {
911
    const auto& groupset_params = groupsets_array.GetParam(gs);
738✔
912
    InputParameters gs_input_params = LBSGroupset::GetInputParameters();
738✔
913
    gs_input_params.SetObjectType("LBSProblem:LBSGroupset");
738✔
914
    gs_input_params.AssignParameters(groupset_params);
738✔
915
    groupsets_.emplace_back(gs_input_params, gs, *this);
738✔
916
    if (groupsets_.back().GetNumGroups() == 0)
738✔
917
    {
UNCOV
918
      std::stringstream oss;
×
UNCOV
919
      oss << GetName() << ": No groups added to groupset " << groupsets_.back().id;
×
UNCOV
920
      OpenSnInvalidArgument(oss.str());
×
UNCOV
921
    }
×
922
  }
738✔
923
}
661✔
924

925
void
926
LBSProblem::InitializeXSMap(const InputParameters& params)
661✔
927
{
928
  // Build XS map
929
  const auto& xs_array = params.GetParam("xs_map");
661✔
930
  const size_t num_xs = xs_array.GetNumParameters();
661✔
931
  for (size_t i = 0; i < num_xs; ++i)
1,579✔
932
  {
933
    const auto& item_params = xs_array.GetParam(i);
918✔
934
    InputParameters xs_entry_pars = GetXSMapEntryBlock();
918✔
935
    xs_entry_pars.AssignParameters(item_params);
918✔
936

937
    const auto& block_ids_param = xs_entry_pars.GetParam("block_ids");
918✔
938
    block_ids_param.RequireBlockTypeIs(ParameterBlockType::ARRAY);
918✔
939
    const auto& block_ids = block_ids_param.GetVectorValue<unsigned int>();
918✔
940
    auto xs = xs_entry_pars.GetSharedPtrParam<MultiGroupXS>("xs");
918✔
941
    for (const auto& block_id : block_ids)
1,972✔
942
      block_id_to_xs_map_[block_id] = xs;
1,054✔
943
  }
918✔
944
}
661✔
945

946
void
947
LBSProblem::InitializeMaterials()
869✔
948
{
949
  CALI_CXX_MARK_SCOPE("LBSProblem::InitializeMaterials");
869✔
950

951
  log.Log0Verbose1() << "Initializing Materials";
1,738✔
952

953
  // Create set of material ids locally relevant
954
  int invalid_mat_cell_count = 0;
869✔
955
  std::set<unsigned int> unique_block_ids;
869✔
956
  for (auto& cell : grid_->local_cells)
665,925✔
957
  {
958
    unique_block_ids.insert(cell.block_id);
665,056✔
959
    if (cell.block_id == std::numeric_limits<unsigned int>::max() or
665,056✔
960
        (block_id_to_xs_map_.find(cell.block_id) == block_id_to_xs_map_.end()))
665,056✔
UNCOV
961
      ++invalid_mat_cell_count;
×
962
  }
963
  const auto& ghost_cell_ids = grid_->cells.GetGhostGlobalIDs();
869✔
964
  for (uint64_t cell_id : ghost_cell_ids)
106,236✔
965
  {
966
    const auto& cell = grid_->cells[cell_id];
105,367✔
967
    unique_block_ids.insert(cell.block_id);
105,367✔
968
    if (cell.block_id == std::numeric_limits<unsigned int>::max() or
105,367✔
969
        (block_id_to_xs_map_.find(cell.block_id) == block_id_to_xs_map_.end()))
105,367✔
UNCOV
970
      ++invalid_mat_cell_count;
×
971
  }
972
  OpenSnLogicalErrorIf(invalid_mat_cell_count > 0,
869✔
973
                       std::to_string(invalid_mat_cell_count) +
974
                         " cells encountered with an invalid material id.");
975

976
  // Get ready for processing
977
  for (const auto& [blk_id, mat] : block_id_to_xs_map_)
2,155✔
978
  {
979
    mat->SetAdjointMode(options_.adjoint);
1,286✔
980

981
    OpenSnLogicalErrorIf(mat->GetNumGroups() < num_groups_,
1,286✔
982
                         "Cross-sections for block \"" + std::to_string(blk_id) +
983
                           "\" have fewer groups (" + std::to_string(mat->GetNumGroups()) +
984
                           ") than the simulation (" + std::to_string(num_groups_) + "). " +
985
                           "Cross-sections must have at least as many groups as the simulation.");
986
  }
987

988
  // Initialize precursor properties
989
  num_precursors_ = 0;
869✔
990
  max_precursors_per_material_ = 0;
869✔
991
  for (const auto& mat_id_xs : block_id_to_xs_map_)
2,155✔
992
  {
993
    const auto& xs = mat_id_xs.second;
1,286✔
994
    num_precursors_ += xs->GetNumPrecursors();
1,286✔
995
    max_precursors_per_material_ = std::max(xs->GetNumPrecursors(), max_precursors_per_material_);
1,286✔
996
  }
997

998
  const bool has_fissionable_precursors =
869✔
999
    std::any_of(block_id_to_xs_map_.begin(),
869✔
1000
                block_id_to_xs_map_.end(),
1001
                [](const auto& mat_id_xs)
1,286✔
1002
                {
1003
                  const auto& xs = mat_id_xs.second;
1,286✔
1004
                  return xs->IsFissionable() and xs->GetNumPrecursors() > 0;
1,286✔
1005
                });
1006

1007
  // check compatibility when at least one fissionable material has delayed-neutron data
1008
  if (options_.use_precursors and has_fissionable_precursors)
869✔
1009
  {
1010
    for (const auto& [mat_id, xs] : block_id_to_xs_map_)
376✔
1011
    {
1012
      OpenSnLogicalErrorIf(xs->IsFissionable() and xs->GetNumPrecursors() == 0,
188✔
1013
                           "Incompatible cross-section data encountered for material id " +
1014
                             std::to_string(mat_id) + ". When delayed neutron data is present " +
1015
                             "for one fissionable material, it must be present for all fissionable "
1016
                             "materials.");
1017
    }
1018
  }
1019

1020
  // Update transport views if available
1021
  if (grid_->local_cells.size() == cell_transport_views_.size())
869✔
1022
    for (const auto& cell : grid_->local_cells)
28,108✔
1023
    {
1024
      const auto& xs_ptr = block_id_to_xs_map_[cell.block_id];
27,900✔
1025
      auto& transport_view = cell_transport_views_[cell.local_id];
27,900✔
1026
      transport_view.ReassignXS(*xs_ptr);
27,900✔
1027
    }
1028

1029
  mpi_comm.barrier();
869✔
1030
}
869✔
1031

1032
void
1033
LBSProblem::InitializeSpatialDiscretization()
581✔
1034
{
1035
  CALI_CXX_MARK_SCOPE("LBSProblem::InitializeSpatialDiscretization");
581✔
1036

1037
  OpenSnLogicalErrorIf(not discretization_,
581✔
1038
                       GetName() + ": Missing spatial discretization. Construct the problem "
1039
                                   "through its factory Create(...) entry point.");
1040
  log.Log() << "Initializing spatial discretization metadata.\n";
1,162✔
1041

1042
  ComputeUnitIntegrals();
581✔
1043
}
581✔
1044

1045
void
1046
LBSProblem::ComputeUnitIntegrals()
661✔
1047
{
1048
  CALI_CXX_MARK_SCOPE("LBSProblem::ComputeUnitIntegrals");
661✔
1049

1050
  log.Log() << "Computing unit integrals.\n";
1,322✔
1051
  const auto& sdm = *discretization_;
661✔
1052

1053
  const size_t num_local_cells = grid_->local_cells.size();
661✔
1054
  unit_cell_matrices_.resize(num_local_cells);
661✔
1055

1056
  for (const auto& cell : grid_->local_cells)
637,817✔
1057
    unit_cell_matrices_[cell.local_id] =
637,156✔
1058
      ComputeUnitCellIntegrals(sdm, cell, grid_->GetCoordinateSystem());
637,156✔
1059

1060
  const auto ghost_ids = grid_->cells.GetGhostGlobalIDs();
661✔
1061
  for (auto ghost_id : ghost_ids)
97,168✔
1062
    unit_ghost_cell_matrices_[ghost_id] =
96,507✔
1063
      ComputeUnitCellIntegrals(sdm, grid_->cells[ghost_id], grid_->GetCoordinateSystem());
193,014✔
1064

1065
  // Assessing global unit cell matrix storage
1066
  std::array<size_t, 2> num_local_ucms = {unit_cell_matrices_.size(),
661✔
1067
                                          unit_ghost_cell_matrices_.size()};
661✔
1068
  std::array<size_t, 2> num_global_ucms = {0, 0};
661✔
1069

1070
  mpi_comm.all_reduce(num_local_ucms.data(), 2, num_global_ucms.data(), mpi::op::sum<size_t>());
661✔
1071

1072
  opensn::mpi_comm.barrier();
661✔
1073
  log.Log() << "Ghost cell unit cell-matrix ratio: "
661✔
1074
            << (double)num_global_ucms[1] * 100 / (double)num_global_ucms[0] << "%";
1,322✔
1075
  log.Log() << "Cell matrices computed.";
1,322✔
1076
}
661✔
1077

1078
void
1079
LBSProblem::InitializeParrays()
661✔
1080
{
1081
  CALI_CXX_MARK_SCOPE("LBSProblem::InitializeParrays");
661✔
1082

1083
  log.Log() << "Initializing parallel arrays."
1,322✔
1084
            << " G=" << num_groups_ << " M=" << num_moments_ << std::endl;
661✔
1085

1086
  // Initialize unknown
1087
  // structure
1088
  flux_moments_uk_man_.unknowns.clear();
661✔
1089
  for (unsigned int m = 0; m < num_moments_; ++m)
2,235✔
1090
  {
1091
    flux_moments_uk_man_.AddUnknown(UnknownType::VECTOR_N, num_groups_);
1,574✔
1092
    flux_moments_uk_man_.unknowns.back().name = "m" + std::to_string(m);
1,574✔
1093
  }
1094

1095
  // Compute local # of dof
1096
  local_node_count_ = discretization_->GetNumLocalNodes();
661✔
1097
  global_node_count_ = discretization_->GetNumGlobalNodes();
661✔
1098

1099
  // Compute num of unknowns
1100
  size_t local_unknown_count = local_node_count_ * num_groups_ * num_moments_;
661✔
1101

1102
  log.LogAllVerbose1() << "LBS Number of phi unknowns: " << local_unknown_count;
1,322✔
1103

1104
  // Size local vectors
1105
  q_moments_local_.assign(local_unknown_count, 0.0);
661✔
1106
  phi_old_local_.assign(local_unknown_count, 0.0);
661✔
1107
  phi_new_local_.assign(local_unknown_count, 0.0);
661✔
1108

1109
  // Setup precursor vector
1110
  if (options_.use_precursors)
661✔
1111
  {
1112
    size_t num_precursor_dofs = grid_->local_cells.size() * max_precursors_per_material_;
68✔
1113
    precursor_new_local_.assign(num_precursor_dofs, 0.0);
68✔
1114
  }
1115

1116
  // Initialize transport views
1117
  // Transport views act as a data structure to store information
1118
  // related to the transport simulation. The most prominent function
1119
  // here is that it holds the means to know where a given cell's
1120
  // transport quantities are located in the unknown vectors (i.e. phi)
1121
  //
1122
  // Also, for a given cell, within a given sweep chunk,
1123
  // we need to solve a matrix which square size is the
1124
  // amount of nodes on the cell. max_cell_dof_count is
1125
  // initialized here.
1126
  //
1127
  size_t block_MG_counter = 0; // Counts the strides of moment and group
661✔
1128

1129
  const Vector3 ihat(1.0, 0.0, 0.0);
661✔
1130
  const Vector3 jhat(0.0, 1.0, 0.0);
661✔
1131
  const Vector3 khat(0.0, 0.0, 1.0);
661✔
1132

1133
  min_cell_dof_count_ = std::numeric_limits<unsigned int>::max();
661✔
1134
  max_cell_dof_count_ = 0;
661✔
1135
  cell_transport_views_.clear();
661✔
1136
  cell_transport_views_.reserve(grid_->local_cells.size());
661✔
1137
  for (auto& cell : grid_->local_cells)
637,817✔
1138
  {
1139
    size_t num_nodes = discretization_->GetCellNumNodes(cell);
637,156✔
1140

1141
    // compute cell volumes
1142
    double cell_volume = 0.0;
637,156✔
1143
    const auto& IntV_shapeI = unit_cell_matrices_[cell.local_id].intV_shapeI;
637,156✔
1144
    for (size_t i = 0; i < num_nodes; ++i)
4,518,856✔
1145
      cell_volume += IntV_shapeI(i);
3,881,700✔
1146

1147
    size_t cell_phi_address = block_MG_counter;
637,156✔
1148

1149
    const size_t num_faces = cell.faces.size();
637,156✔
1150
    std::vector<bool> face_local_flags(num_faces, true);
637,156✔
1151
    std::vector<int> face_locality(num_faces, opensn::mpi_comm.rank());
637,156✔
1152
    std::vector<const Cell*> neighbor_cell_ptrs(num_faces, nullptr);
637,156✔
1153
    bool cell_on_boundary = false;
637,156✔
1154
    int f = 0;
637,156✔
1155
    for (auto& face : cell.faces)
3,837,020✔
1156
    {
1157
      if (not face.has_neighbor)
3,199,864✔
1158
      {
1159
        cell_on_boundary = true;
113,146✔
1160
        face_local_flags[f] = false;
113,146✔
1161
        face_locality[f] = -1;
113,146✔
1162
      } // if bndry
1163
      else
1164
      {
1165
        const int neighbor_partition = face.GetNeighborPartitionID(grid_.get());
3,086,718✔
1166
        face_local_flags[f] = (neighbor_partition == opensn::mpi_comm.rank());
3,086,718✔
1167
        face_locality[f] = neighbor_partition;
3,086,718✔
1168
        neighbor_cell_ptrs[f] = &grid_->cells[face.neighbor_id];
3,086,718✔
1169
      }
1170

1171
      ++f;
3,199,864✔
1172
    } // for f
1173

1174
    max_cell_dof_count_ = std::max(max_cell_dof_count_, static_cast<unsigned int>(num_nodes));
637,156✔
1175
    min_cell_dof_count_ = std::min(min_cell_dof_count_, static_cast<unsigned int>(num_nodes));
637,156✔
1176
    cell_transport_views_.emplace_back(cell_phi_address,
1,274,312✔
1177
                                       num_nodes,
1178
                                       num_groups_,
637,156✔
1179
                                       num_moments_,
637,156✔
1180
                                       num_faces,
1181
                                       *block_id_to_xs_map_[cell.block_id],
637,156✔
1182
                                       cell_volume,
1183
                                       face_local_flags,
1184
                                       face_locality,
1185
                                       neighbor_cell_ptrs,
1186
                                       cell_on_boundary);
1187
    block_MG_counter += num_nodes * num_groups_ * num_moments_;
637,156✔
1188
  } // for local cell
637,156✔
1189

1190
  // Populate grid nodal mappings
1191
  // This is used in the Flux Data Structures (FLUDS)
1192
  grid_nodal_mappings_.clear();
661✔
1193
  grid_nodal_mappings_.reserve(grid_->local_cells.size());
661✔
1194
  for (auto& cell : grid_->local_cells)
637,817✔
1195
  {
1196
    CellFaceNodalMapping cell_nodal_mapping;
637,156✔
1197
    cell_nodal_mapping.reserve(cell.faces.size());
637,156✔
1198

1199
    for (auto& face : cell.faces)
3,837,020✔
1200
    {
1201
      std::vector<short> face_node_mapping;
3,199,864✔
1202
      std::vector<short> cell_node_mapping;
3,199,864✔
1203
      int adj_face_idx = -1;
3,199,864✔
1204

1205
      if (face.has_neighbor)
3,199,864✔
1206
      {
1207
        grid_->FindAssociatedVertices(face, face_node_mapping);
3,086,718✔
1208
        grid_->FindAssociatedCellVertices(face, cell_node_mapping);
3,086,718✔
1209
        adj_face_idx = face.GetNeighborAdjacentFaceIndex(grid_.get());
3,086,718✔
1210
      }
1211

1212
      cell_nodal_mapping.emplace_back(adj_face_idx, face_node_mapping, cell_node_mapping);
3,199,864✔
1213
    } // for f
3,199,864✔
1214

1215
    grid_nodal_mappings_.push_back(cell_nodal_mapping);
637,156✔
1216
  } // for local cell
637,156✔
1217

1218
  // Get grid localized communicator set
1219
  grid_local_comm_set_ = grid_->MakeMPILocalCommunicatorSet();
661✔
1220

1221
  opensn::mpi_comm.barrier();
661✔
1222
  log.Log() << "Done with parallel arrays." << std::endl;
1,322✔
1223
}
661✔
1224

1225
void
1226
LBSProblem::InitializeSolverSchemes()
1,069✔
1227
{
1228
  CALI_CXX_MARK_SCOPE("LBSProblem::InitializeSolverSchemes");
1,069✔
1229
  ags_solver_.reset();
1,069✔
1230
  wgs_solvers_.clear();
1,069✔
1231
  wgs_contexts_.clear();
1,069✔
1232
  InitializeWGSContexts();
1,069✔
1233
}
1,069✔
1234

1235
#ifndef __OPENSN_WITH_GPU__
1236
void
1237
LBSProblem::InitializeGPUExtras()
1238
{
1239
}
1240

1241
void
1242
LBSProblem::ResetGPUCarriers()
1243
{
1244
}
1245

1246
void
1247
LBSProblem::CheckCapableDevices()
1248
{
1249
}
1250
#endif // __OPENSN_WITH_GPU__
1251

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

1257
  size_t num_local_dofs = discretization_->GetNumLocalDOFs(flux_moments_uk_man_);
4✔
1258

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

1269
  return source_moments;
4✔
1270
}
4✔
1271

1272
LBSProblem::~LBSProblem()
649✔
1273
{
1274
  ResetGPUCarriers();
1275
}
3,853✔
1276

649✔
1277
void
UNCOV
1278
LBSProblem::SetSaveAngularFlux(bool save)
×
1279
{
UNCOV
1280
  options_.save_angular_flux = save;
×
UNCOV
1281
}
×
1282

1283
void
1284
LBSProblem::ZeroPhi()
84✔
1285
{
1286
  std::fill(phi_old_local_.begin(), phi_old_local_.end(), 0.0);
84✔
1287
  std::fill(phi_new_local_.begin(), phi_new_local_.end(), 0.0);
84✔
1288
}
84✔
1289

1290
void
1291
LBSProblem::CopyPhiNewToOld()
176✔
1292
{
1293
  assert(phi_old_local_.size() == phi_new_local_.size() && "Phi vectors size mismatch.");
176✔
1294
  phi_old_local_ = phi_new_local_;
176✔
1295
}
176✔
1296

1297
void
1298
LBSProblem::SetPhiOldFrom(const std::vector<double>& phi_old)
2,420✔
1299
{
1300
  assert(phi_old.size() == phi_old_local_.size() && "SetPhiOldFrom size mismatch.");
2,420✔
1301
  phi_old_local_ = phi_old;
2,420✔
1302
}
2,420✔
1303

1304
void
UNCOV
1305
LBSProblem::SetPhiNewFrom(const std::vector<double>& phi_new)
×
1306
{
UNCOV
1307
  assert(phi_new.size() == phi_new_local_.size() && "SetPhiNewFrom size mismatch.");
×
UNCOV
1308
  phi_new_local_ = phi_new;
×
UNCOV
1309
}
×
1310

1311
void
UNCOV
1312
LBSProblem::ScalePhiOld(double factor)
×
1313
{
UNCOV
1314
  for (auto& value : phi_old_local_)
×
UNCOV
1315
    value *= factor;
×
1316
}
×
1317

1318
void
1319
LBSProblem::ScalePhiNew(double factor)
8✔
1320
{
1321
  for (auto& value : phi_new_local_)
168,008✔
1322
    value *= factor;
168,000✔
1323
}
8✔
1324

1325
void
1326
LBSProblem::ZeroQMoments()
65,714✔
1327
{
1328
  assert(q_moments_local_.size() == phi_old_local_.size() && "Q moments/Phi size mismatch.");
65,714✔
1329
  std::fill(q_moments_local_.begin(), q_moments_local_.end(), 0.0);
65,714✔
1330
}
65,714✔
1331

1332
void
1333
LBSProblem::ScaleQMoments(double factor)
8,697✔
1334
{
1335
  for (auto& value : q_moments_local_)
570,308,253✔
1336
    value *= factor;
570,299,556✔
1337
}
8,697✔
1338

1339
void
1340
LBSProblem::SetQMomentsFrom(const std::vector<double>& q_moments)
25,460✔
1341
{
1342
  assert(q_moments.size() == q_moments_local_.size() && "SetQMomentsFrom size mismatch.");
25,460✔
1343
  q_moments_local_ = q_moments;
25,460✔
1344
}
25,460✔
1345

1346
void
1347
LBSProblem::ScalePrecursors(double factor)
64✔
1348
{
1349
  for (auto& value : precursor_new_local_)
1,132✔
1350
    value *= factor;
1,068✔
1351
}
64✔
1352

1353
void
1354
LBSProblem::ZeroPrecursors()
884✔
1355
{
1356
  std::fill(precursor_new_local_.begin(), precursor_new_local_.end(), 0.0);
884✔
1357
}
884✔
1358

1359
void
UNCOV
1360
LBSProblem::ZeroExtSrcMoments()
×
1361
{
UNCOV
1362
  std::fill(ext_src_moments_local_.begin(), ext_src_moments_local_.end(), 0.0);
×
UNCOV
1363
}
×
1364

1365
void
UNCOV
1366
LBSProblem::ScaleExtSrcMoments(double factor)
×
1367
{
UNCOV
1368
  for (auto& value : ext_src_moments_local_)
×
1369
    value *= factor;
×
UNCOV
1370
}
×
1371

1372
void
1373
LBSProblem::SetAdjoint(bool adjoint)
24✔
1374
{
1375
  OpenSnLogicalErrorIf(
24✔
1376
    not initialized_, GetName() + ": Problem must be fully constructed before calling SetAdjoint.");
1377

1378
  if (adjoint)
24✔
1379
    if (const auto* do_problem = dynamic_cast<const DiscreteOrdinatesProblem*>(this);
20✔
1380
        do_problem and do_problem->IsTimeDependent())
20✔
UNCOV
1381
      OpenSnInvalidArgument(GetName() + ": Time-dependent adjoint problems are not supported.");
×
1382

1383
  const bool mode_changed = (adjoint != options_.adjoint);
24✔
1384
  if (not mode_changed)
24✔
1385
    return;
1386

1387
  options_.adjoint = adjoint;
24✔
1388

1389
  // Reinitialize materials to obtain the proper forward/adjoint cross sections.
1390
  InitializeMaterials();
24✔
1391

1392
  // Forward and adjoint sources are fundamentally different.
1393
  point_sources_.clear();
24✔
1394
  volumetric_sources_.clear();
24✔
1395
  ClearBoundaries();
24✔
1396

1397
  // Reset all solution vectors.
1398
  ZeroPhi();
24✔
1399
  ZeroPsi();
24✔
1400
  ZeroPrecursors();
24✔
1401
}
1402

1403
void
UNCOV
1404
LBSProblem::SetForward()
×
1405
{
UNCOV
1406
  SetAdjoint(false);
×
UNCOV
1407
}
×
1408

1409
bool
UNCOV
1410
LBSProblem::IsAdjoint() const
×
1411
{
UNCOV
1412
  return options_.adjoint;
×
1413
}
1414

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