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

Open-Sn / opensn / 23035733467

12 Mar 2026 09:28PM UTC coverage: 74.343% (-0.05%) from 74.39%
23035733467

push

github

web-flow
Merge pull request #970 from wdhawkins/options_cleanup

Improved options handling

30 of 34 new or added lines in 10 files covered. (88.24%)

10 existing lines in 2 files now uncovered.

20100 of 27037 relevant lines covered (74.34%)

66946851.77 hits per line

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

80.66
/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()
583✔
36
{
37
  InputParameters params = Problem::GetInputParameters();
583✔
38

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

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

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

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

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

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

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

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

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

66
  return params;
583✔
67
}
×
68

69
LBSProblem::LBSProblem(const InputParameters& params)
583✔
70
  : Problem(params),
71
    num_groups_(params.GetParamValue<unsigned int>("num_groups")),
583✔
72
    grid_(params.GetSharedPtrParam<MeshContinuum>("mesh")),
583✔
73
    use_gpus_(params.GetParamValue<bool>("use_gpus"))
1,749✔
74
{
75
  // Check system for GPU acceleration
76
  if (use_gpus_)
583✔
77
  {
78
#ifdef __OPENSN_WITH_GPU__
79
    CheckCapableDevices();
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"))
583✔
88
  {
89
    auto options_params = LBSProblem::GetOptionsBlock();
395✔
90
    options_params.AssignParameters(params.GetParam("options"));
397✔
91
    ParseOptions(options_params);
393✔
92
  }
395✔
93

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

99
  InitializeGroupsets(params);
581✔
100
  InitializeSources(params);
581✔
101
  InitializeXSmapAndDensities(params);
581✔
102
  InitializeMaterials();
581✔
103
}
601✔
104

105
const LBSOptions&
106
LBSProblem::GetOptions() const
685,897,234✔
107
{
108
  return options_;
685,897,234✔
109
}
110

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

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

123
void
124
LBSProblem::SetTimeStep(double dt)
1,808✔
125
{
126
  OpenSnInvalidArgumentIf(dt <= 0.0, GetName() + ": dt must be greater than zero.");
1,808✔
127
  dt_ = dt;
1,808✔
128
}
1,808✔
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)
272✔
138
{
139
  OpenSnInvalidArgumentIf(theta <= 0.0 or theta > 1.0,
272✔
140
                          GetName() + ": theta must be in (0.0, 1.0].");
141
  theta_ = theta;
272✔
142
}
272✔
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
547,522✔
176
{
177
  return num_moments_;
547,522✔
178
}
179

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

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

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

198
unsigned int
199
LBSProblem::GetNumGroups() const
850,040✔
200
{
201
  return num_groups_;
850,040✔
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
28,120✔
224
{
225
  return groupsets_;
28,120✔
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
67✔
242
{
243
  return groupsets_.size();
67✔
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,196✔
262
{
263
  return point_sources_;
156,196✔
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,196✔
282
{
283
  return volumetric_sources_;
156,196✔
284
}
285

286
const BlockID2XSMap&
287
LBSProblem::GetBlockID2XSMap() const
19,375✔
288
{
289
  return block_id_to_xs_map_;
19,375✔
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
781,843✔
303
{
304
  return grid_;
781,843✔
305
}
306

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

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

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

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

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

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

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

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

355
std::vector<double>&
356
LBSProblem::GetQMomentsLocal()
224,578✔
357
{
358
  return q_moments_local_;
224,578✔
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
220,678✔
375
{
376
  return ext_src_moments_local_;
220,678✔
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()
444,550✔
401
{
402
  return phi_old_local_;
444,550✔
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()
259,327✔
413
{
414
  return phi_new_local_;
259,327✔
415
}
416

417
const std::vector<double>&
418
LBSProblem::GetPhiNewLocal() const
×
419
{
420
  return phi_new_local_;
×
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
std::vector<double>&
436
LBSProblem::GetDensitiesLocal()
1,098✔
437
{
438
  return densities_local_;
1,098✔
439
}
440

441
const std::vector<double>&
442
LBSProblem::GetDensitiesLocal() const
220,678✔
443
{
444
  return densities_local_;
220,678✔
445
}
446

447
SetSourceFunction
448
LBSProblem::GetActiveSetSourceFunction() const
4,401✔
449
{
450
  return active_set_source_function_;
4,401✔
451
}
452

453
void
454
LBSProblem::SetActiveSetSourceFunction(SetSourceFunction source_function)
104✔
455
{
456
  active_set_source_function_ = std::move(source_function);
104✔
457
}
104✔
458

459
std::shared_ptr<AGSLinearSolver>
460
LBSProblem::GetAGSSolver() const
2,936✔
461
{
462
  return ags_solver_;
2,936✔
463
}
464

465
std::shared_ptr<LinearSolver>
466
LBSProblem::GetWGSSolver(size_t groupset_id) const
3,149✔
467
{
468
  return wgs_solvers_.at(groupset_id);
3,149✔
469
}
470

471
size_t
472
LBSProblem::GetNumWGSSolvers() const
5,980✔
473
{
474
  return wgs_solvers_.size();
5,980✔
475
}
476

477
WGSContext&
478
LBSProblem::GetWGSContext(int groupset_id)
11,913✔
479
{
480
  auto& wgs_solver = wgs_solvers_[groupset_id];
11,913✔
481
  auto raw_context = wgs_solver->GetContext();
11,913✔
482
  auto wgs_context_ptr = std::dynamic_pointer_cast<WGSContext>(raw_context);
11,913✔
483
  OpenSnLogicalErrorIf(not wgs_context_ptr,
11,913✔
484
                       GetName() + ": Failed to cast solver context to WGSContext.");
485
  return *wgs_context_ptr;
11,913✔
486
}
23,826✔
487

488
std::pair<size_t, size_t>
489
LBSProblem::GetNumPhiIterativeUnknowns()
×
490
{
491
  const auto& sdm = *discretization_;
×
492
  const size_t num_local_phi_dofs = sdm.GetNumLocalDOFs(flux_moments_uk_man_);
×
493
  const size_t num_global_phi_dofs = sdm.GetNumGlobalDOFs(flux_moments_uk_man_);
×
494

495
  return {num_local_phi_dofs, num_global_phi_dofs};
×
496
}
497

498
std::shared_ptr<FieldFunctionGridBased>
499
LBSProblem::GetScalarFluxFieldFunction(unsigned int g, unsigned int m) const
28,985✔
500
{
501
  OpenSnLogicalErrorIf(g >= num_groups_, GetName() + ": Group index out of range.");
28,985✔
502
  OpenSnLogicalErrorIf(m >= num_moments_, GetName() + ": Moment index out of range.");
28,985✔
503

504
  const auto map_it = phi_field_functions_local_map_.find({g, m});
28,985✔
505
  OpenSnLogicalErrorIf(map_it == phi_field_functions_local_map_.end(),
28,985✔
506
                       GetName() + ": Failed to map phi field function for g=" + std::to_string(g) +
507
                         ", m=" + std::to_string(m) + ".");
508

509
  return field_functions_.at(map_it->second);
57,970✔
510
}
511

512
std::shared_ptr<FieldFunctionGridBased>
513
LBSProblem::GetPowerFieldFunction() const
1✔
514
{
515
  OpenSnLogicalErrorIf(not options_.power_field_function_on,
1✔
516
                       GetName() + ": GetPowerFieldFunction called with "
517
                                   "options_.power_field_function_on=false.");
518

519
  return field_functions_[power_gen_fieldfunc_local_handle_];
2✔
520
}
521

522
InputParameters
523
LBSProblem::GetOptionsBlock()
788✔
524
{
525
  InputParameters params;
788✔
526

527
  params.SetGeneralDescription("Set options from a large list of parameters");
1,576✔
528
  params.AddOptionalParameter("max_mpi_message_size",
1,576✔
529
                              32768,
530
                              "The maximum MPI message size used during sweep initialization.");
531
  params.AddOptionalParameter(
1,576✔
532
    "restart_writes_enabled", false, "Flag that controls writing of restart dumps");
533
  params.AddOptionalParameter("write_delayed_psi_to_restart",
1,576✔
534
                              true,
535
                              "Flag that controls writing of delayed angular fluxes to restarts.");
536
  params.AddOptionalParameter(
1,576✔
537
    "read_restart_path", "", "Full path for reading restart dumps including file stem.");
538
  params.AddOptionalParameter(
1,576✔
539
    "write_restart_path", "", "Full path for writing restart dumps including file stem.");
540
  params.AddOptionalParameter("write_restart_time_interval",
1,576✔
541
                              0,
542
                              "Time interval in seconds at which restart data is to be written.");
543
  params.AddOptionalParameter(
1,576✔
544
    "use_precursors", false, "Flag for using delayed neutron precursors.");
545
  params.AddOptionalParameter("use_source_moments",
1,576✔
546
                              false,
547
                              "Flag for ignoring fixed sources and selectively using source "
548
                              "moments obtained elsewhere.");
549
  params.AddOptionalParameter(
1,576✔
550
    "save_angular_flux", false, "Flag indicating whether angular fluxes are to be stored or not.");
551
  params.AddOptionalParameter(
1,576✔
552
    "adjoint", false, "Flag for toggling whether the solver is in adjoint mode.");
553
  params.AddOptionalParameter(
1,576✔
554
    "verbose_inner_iterations", true, "Flag to control verbosity of inner iterations.");
555
  params.AddOptionalParameter(
1,576✔
556
    "verbose_outer_iterations", true, "Flag to control verbosity of across-groupset iterations.");
557
  params.AddOptionalParameter(
1,576✔
558
    "max_ags_iterations", 100, "Maximum number of across-groupset iterations.");
559
  params.AddOptionalParameter("ags_tolerance", 1.0e-6, "Across-groupset iterations tolerance.");
1,576✔
560
  params.AddOptionalParameter("ags_convergence_check",
1,576✔
561
                              "l2",
562
                              "Type of convergence check for AGS iterations. Valid values are "
563
                              "`\"l2\"` and '\"pointwise\"'");
564
  params.AddOptionalParameter(
1,576✔
565
    "verbose_ags_iterations", true, "Flag to control verbosity of across-groupset iterations.");
566
  params.AddOptionalParameter("power_field_function_on",
1,576✔
567
                              false,
568
                              "Flag to control the creation of the power generation field "
569
                              "function. If set to `true` then a field function will be created "
570
                              "with the general name <solver_name>_power_generation`.");
571
  params.AddOptionalParameter("power_default_kappa",
1,576✔
572
                              3.20435e-11,
573
                              "Default `kappa` value (Energy released per fission) to use for "
574
                              "power generation when cross sections do not have `kappa` values. "
575
                              "Default: 3.20435e-11 Joule (corresponding to 200 MeV per fission).");
576
  params.AddOptionalParameter("power_normalization",
1,576✔
577
                              -1.0,
578
                              "Power normalization factor to use. Supply a negative or zero number "
579
                              "to turn this off.");
580
  params.AddOptionalParameter("field_function_prefix_option",
1,576✔
581
                              "prefix",
582
                              "Prefix option on field function names. Default: `\"prefix\"`. Can "
583
                              "be `\"prefix\"` or `\"solver_name\"`. By default this option uses "
584
                              "the value of the `field_function_prefix` parameter. If this "
585
                              "parameter is not set, flux field functions will be exported as "
586
                              "`phi_gXXX_mYYY` where `XXX` is the zero padded 3 digit group number "
587
                              "and `YYY` is the zero padded 3 digit moment.");
588
  params.AddOptionalParameter("field_function_prefix",
1,576✔
589
                              "",
590
                              "Prefix to use on all field functions. Default: `\"\"`. By default "
591
                              "this option is empty. Ff specified, flux moments will be exported "
592
                              "as `prefix_phi_gXXX_mYYY` where `XXX` is the zero padded 3 digit "
593
                              "group number and `YYY` is the zero padded 3 digit moment. The "
594
                              "underscore after \"prefix\" is added automatically.");
595
  params.ConstrainParameterRange("ags_convergence_check",
2,364✔
596
                                 AllowableRangeList::New({"l2", "pointwise"}));
788✔
597
  params.ConstrainParameterRange("field_function_prefix_option",
2,364✔
598
                                 AllowableRangeList::New({"prefix", "solver_name"}));
788✔
599
  params.ConstrainParameterRange("max_mpi_message_size", AllowableRangeLowLimit::New(1024));
2,364✔
600
  params.ConstrainParameterRange("write_restart_time_interval", AllowableRangeLowLimit::New(0));
2,364✔
601
  params.ConstrainParameterRange("max_ags_iterations", AllowableRangeLowLimit::New(0));
2,364✔
602
  params.ConstrainParameterRange("ags_tolerance", AllowableRangeLowLimit::New(1.0e-18));
2,364✔
603
  params.ConstrainParameterRange("power_default_kappa", AllowableRangeLowLimit::New(0.0, false));
2,364✔
604

605
  return params;
788✔
606
}
×
607

608
InputParameters
609
LBSProblem::GetXSMapEntryBlock()
1,010✔
610
{
611
  InputParameters params;
1,010✔
612
  params.SetGeneralDescription("Set the cross-section map for the solver.");
2,020✔
613
  params.AddRequiredParameterArray("block_ids", "Mesh block IDs");
2,020✔
614
  params.AddRequiredParameter<std::shared_ptr<MultiGroupXS>>("xs", "Cross-section object");
2,020✔
615
  return params;
1,010✔
616
}
×
617

618
void
619
LBSProblem::ParseOptions(const InputParameters& input)
393✔
620
{
621
  auto params = LBSProblem::GetOptionsBlock();
393✔
622
  params.AssignParameters(input);
393✔
623
  const auto& params_at_assignment = input.GetParametersAtAssignment();
393✔
624
  const auto& specified_params = params_at_assignment.GetNumParameters() > 0
393✔
625
                                   ? params_at_assignment
393✔
626
                                   : static_cast<const ParameterBlock&>(input);
393✔
627

628
  using OptionSetter = std::function<void(const ParameterBlock&)>;
393✔
629
  const std::unordered_map<std::string, OptionSetter> option_setters = {
393✔
630
    {"max_mpi_message_size",
631
     [this](const ParameterBlock& spec) { options_.max_mpi_message_size = spec.GetValue<int>(); }},
×
632
    {"restart_writes_enabled",
633
     [this](const ParameterBlock& spec)
786✔
634
     { options_.restart_writes_enabled = spec.GetValue<bool>(); }},
×
635
    {"write_delayed_psi_to_restart",
636
     [this](const ParameterBlock& spec)
786✔
637
     { options_.write_delayed_psi_to_restart = spec.GetValue<bool>(); }},
×
638
    {"read_restart_path",
639
     [this](const ParameterBlock& spec)
798✔
640
     { options_.read_restart_path = BuildRestartPath(spec.GetValue<std::string>()); }},
24✔
641
    {"write_restart_path",
642
     [this](const ParameterBlock& spec)
786✔
643
     { options_.write_restart_path = BuildRestartPath(spec.GetValue<std::string>()); }},
×
644
    {"write_restart_time_interval",
645
     [this](const ParameterBlock& spec)
786✔
646
     { options_.write_restart_time_interval = std::chrono::seconds(spec.GetValue<int>()); }},
×
647
    {"use_precursors",
648
     [this](const ParameterBlock& spec) { options_.use_precursors = spec.GetValue<bool>(); }},
124✔
649
    {"use_source_moments",
650
     [this](const ParameterBlock& spec) { options_.use_src_moments = spec.GetValue<bool>(); }},
4✔
651
    {"save_angular_flux",
652
     [this](const ParameterBlock& spec) { options_.save_angular_flux = spec.GetValue<bool>(); }},
250✔
653
    {"verbose_inner_iterations",
654
     [this](const ParameterBlock& spec)
1,002✔
655
     { options_.verbose_inner_iterations = spec.GetValue<bool>(); }},
216✔
656
    {"max_ags_iterations",
657
     [this](const ParameterBlock& spec) { options_.max_ags_iterations = spec.GetValue<int>(); }},
50✔
658
    {"ags_tolerance",
659
     [this](const ParameterBlock& spec) { options_.ags_tolerance = spec.GetValue<double>(); }},
8✔
660
    {"ags_convergence_check",
661
     [this](const ParameterBlock& spec)
786✔
662
     { options_.ags_pointwise_convergence = (spec.GetValue<std::string>() == "pointwise"); }},
×
663
    {"verbose_ags_iterations",
664
     [this](const ParameterBlock& spec)
918✔
665
     { options_.verbose_ags_iterations = spec.GetValue<bool>(); }},
132✔
666
    {"verbose_outer_iterations",
667
     [this](const ParameterBlock& spec)
978✔
668
     { options_.verbose_outer_iterations = spec.GetValue<bool>(); }},
192✔
669
    {"power_field_function_on",
670
     [this](const ParameterBlock& spec)
791✔
671
     { options_.power_field_function_on = spec.GetValue<bool>(); }},
5✔
672
    {"power_default_kappa",
673
     [this](const ParameterBlock& spec)
799✔
674
     { options_.power_default_kappa = spec.GetValue<double>(); }},
13✔
675
    {"power_normalization",
676
     [this](const ParameterBlock& spec)
799✔
677
     { options_.power_normalization = spec.GetValue<double>(); }},
13✔
678
    {"field_function_prefix_option",
679
     [this](const ParameterBlock& spec)
786✔
680
     { options_.field_function_prefix_option = spec.GetValue<std::string>(); }},
×
681
    {"field_function_prefix",
682
     [this](const ParameterBlock& spec)
787✔
683
     { options_.field_function_prefix = spec.GetValue<std::string>(); }},
1✔
684
    {"adjoint", [this](const ParameterBlock& spec) { options_.adjoint = spec.GetValue<bool>(); }},
16✔
685
  };
9,039✔
686

687
  for (const auto& spec : specified_params.GetParameters())
1,429✔
688
  {
689
    const auto setter_it = option_setters.find(spec.GetName());
2,072✔
690
    if (setter_it != option_setters.end())
1,036✔
691
      setter_it->second(spec);
1,036✔
692
  }
693

694
  OpenSnInvalidArgumentIf(options_.write_restart_time_interval > std::chrono::seconds(0) and
393✔
695
                            not options_.restart_writes_enabled,
696
                          GetName() + ": `write_restart_time_interval>0` requires "
697
                                      "`restart_writes_enabled=true`.");
698

699
  OpenSnInvalidArgumentIf(options_.write_restart_time_interval > std::chrono::seconds(0) and
393✔
700
                            options_.write_restart_time_interval < std::chrono::seconds(30),
701
                          GetName() + ": `write_restart_time_interval` must be 0 (disabled) "
702
                                      "or at least 30 seconds.");
703

704
  OpenSnInvalidArgumentIf(options_.restart_writes_enabled and options_.write_restart_path.empty(),
393✔
705
                          GetName() + ": `restart_writes_enabled=true` requires a non-empty "
706
                                      "`write_restart_path`.");
707

708
  OpenSnInvalidArgumentIf(not options_.field_function_prefix.empty() and
393✔
709
                            options_.field_function_prefix_option != "prefix",
710
                          GetName() + ": non-empty `field_function_prefix` requires "
711
                                      "`field_function_prefix_option=\"prefix\"`.");
712

713
  if (options_.restart_writes_enabled)
393✔
714
  {
715
    const auto dir = options_.write_restart_path.parent_path();
×
716

717
    // Create restart directory if necessary.
718
    // If dir is empty, write path resolves relative to the working directory.
719
    if ((not dir.empty()) and opensn::mpi_comm.rank() == 0)
×
720
    {
721
      if (not std::filesystem::exists(dir))
×
722
      {
723
        OpenSnLogicalErrorIf(not std::filesystem::create_directories(dir),
×
724
                             GetName() + ": Failed to create restart directory " + dir.string());
725
      }
726
      else
727
        OpenSnLogicalErrorIf(not std::filesystem::is_directory(dir),
×
728
                             GetName() + ": Restart path exists but is not a directory " +
729
                               dir.string());
730
    }
731
    opensn::mpi_comm.barrier();
×
732
    UpdateRestartWriteTime();
×
733
  }
×
734
}
786✔
735

736
std::filesystem::path
737
LBSProblem::BuildRestartPath(const std::string& path_stem)
12✔
738
{
739
  if (path_stem.empty())
12✔
NEW
740
    return {};
×
741

742
  auto path = std::filesystem::path(path_stem);
12✔
743
  path += std::to_string(opensn::mpi_comm.rank()) + ".restart.h5";
36✔
744
  return path;
12✔
745
}
12✔
746

747
void
748
LBSProblem::BuildRuntime()
581✔
749
{
750
  CALI_CXX_MARK_SCOPE("LBSProblem::BuildRuntime");
581✔
751
  if (initialized_)
581✔
752
    return;
×
753

754
  PrintSimHeader();
581✔
755
  mpi_comm.barrier();
581✔
756

757
  InitializeRuntimeCore();
581✔
758
  ValidateRuntimeModeConfiguration();
581✔
759
  InitializeSources();
581✔
760

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

764
void
765
LBSProblem::InitializeRuntimeCore()
581✔
766
{
767
  InitializeSpatialDiscretization();
581✔
768
  InitializeParrays();
581✔
769
  InitializeBoundaries();
581✔
770
  InitializeGPUExtras();
581✔
771
}
581✔
772

773
void
774
LBSProblem::ValidateRuntimeModeConfiguration() const
581✔
775
{
776
  if (options_.adjoint)
581✔
777
    if (const auto* do_problem = dynamic_cast<const DiscreteOrdinatesProblem*>(this);
16✔
778
        do_problem and do_problem->IsTimeDependent())
16✔
779
      OpenSnInvalidArgument(GetName() + ": Time-dependent adjoint problems are not supported.");
×
780
}
581✔
781

782
void
783
LBSProblem::InitializeSources()
581✔
784
{
785
  // Initialize point sources
786
  for (auto& point_source : point_sources_)
590✔
787
    point_source->Initialize(*this);
9✔
788

789
  // Initialize volumetric sources
790
  for (auto& volumetric_source : volumetric_sources_)
1,064✔
791
    volumetric_source->Initialize(*this);
483✔
792
}
581✔
793

794
void
795
LBSProblem::PrintSimHeader()
×
796
{
797
  if (opensn::mpi_comm.rank() == 0)
×
798
  {
799
    std::stringstream outstr;
×
800
    outstr << "\n"
×
801
           << "Initializing " << GetName() << "\n\n"
×
802
           << "Scattering order    : " << scattering_order_ << "\n"
×
803
           << "Number of moments   : " << num_moments_ << "\n"
×
804
           << "Number of groups    : " << num_groups_ << "\n"
×
805
           << "Number of groupsets : " << groupsets_.size() << "\n\n";
×
806

807
    for (const auto& groupset : groupsets_)
×
808
    {
809
      outstr << "***** Groupset " << groupset.id << " *****\n"
×
810
             << "Groups:\n";
×
811
      const auto n_gs_groups = groupset.GetNumGroups();
×
812
      constexpr int groups_per_line = 12;
813
      for (size_t i = 0; i < n_gs_groups; ++i)
×
814
      {
815
        outstr << std::setw(5) << groupset.first_group + i << ' ';
×
816
        if ((i + 1) % groups_per_line == 0)
×
817
          outstr << '\n';
×
818
      }
819
      if (n_gs_groups > 0 && n_gs_groups % groups_per_line != 0)
×
820
        outstr << '\n';
×
821
    }
822

823
    log.Log() << outstr.str() << '\n';
×
824
  }
×
825
}
×
826

827
void
828
LBSProblem::InitializeSources(const InputParameters& params)
581✔
829
{
830
  if (params.Has("volumetric_sources"))
581✔
831
  {
832
    const auto& vol_srcs = params.GetParam("volumetric_sources");
581✔
833
    vol_srcs.RequireBlockTypeIs(ParameterBlockType::ARRAY);
581✔
834
    for (const auto& src : vol_srcs)
1,064✔
835
      volumetric_sources_.push_back(src.GetValue<std::shared_ptr<VolumetricSource>>());
966✔
836
  }
837

838
  if (params.Has("point_sources"))
581✔
839
  {
840
    const auto& pt_srcs = params.GetParam("point_sources");
581✔
841
    pt_srcs.RequireBlockTypeIs(ParameterBlockType::ARRAY);
581✔
842
    for (const auto& src : pt_srcs)
590✔
843
      point_sources_.push_back(src.GetValue<std::shared_ptr<PointSource>>());
18✔
844
  }
845
}
581✔
846

847
void
848
LBSProblem::InitializeGroupsets(const InputParameters& params)
581✔
849
{
850
  // Initialize groups
851
  OpenSnInvalidArgumentIf(num_groups_ == 0, GetName() + ": Number of groups must be > 0.");
581✔
852

853
  // Initialize groupsets
854
  const auto& groupsets_array = params.GetParam("groupsets");
581✔
855
  const size_t num_gs = groupsets_array.GetNumParameters();
581✔
856
  OpenSnInvalidArgumentIf(num_gs == 0, GetName() + ": At least one groupset must be specified.");
581✔
857
  for (size_t gs = 0; gs < num_gs; ++gs)
1,221✔
858
  {
859
    const auto& groupset_params = groupsets_array.GetParam(gs);
640✔
860
    InputParameters gs_input_params = LBSGroupset::GetInputParameters();
640✔
861
    gs_input_params.SetObjectType("LBSProblem:LBSGroupset");
640✔
862
    gs_input_params.AssignParameters(groupset_params);
640✔
863
    groupsets_.emplace_back(gs_input_params, gs, *this);
640✔
864
    if (groupsets_.back().GetNumGroups() == 0)
640✔
865
    {
866
      std::stringstream oss;
×
867
      oss << GetName() << ": No groups added to groupset " << groupsets_.back().id;
×
868
      OpenSnInvalidArgument(oss.str());
×
869
    }
×
870
  }
640✔
871
}
581✔
872

873
void
874
LBSProblem::InitializeXSmapAndDensities(const InputParameters& params)
581✔
875
{
876
  // Build XS map
877
  const auto& xs_array = params.GetParam("xs_map");
581✔
878
  const size_t num_xs = xs_array.GetNumParameters();
581✔
879
  for (size_t i = 0; i < num_xs; ++i)
1,419✔
880
  {
881
    const auto& item_params = xs_array.GetParam(i);
838✔
882
    InputParameters xs_entry_pars = GetXSMapEntryBlock();
838✔
883
    xs_entry_pars.AssignParameters(item_params);
838✔
884

885
    const auto& block_ids_param = xs_entry_pars.GetParam("block_ids");
838✔
886
    block_ids_param.RequireBlockTypeIs(ParameterBlockType::ARRAY);
838✔
887
    const auto& block_ids = block_ids_param.GetVectorValue<unsigned int>();
838✔
888
    auto xs = xs_entry_pars.GetSharedPtrParam<MultiGroupXS>("xs");
838✔
889
    for (const auto& block_id : block_ids)
1,784✔
890
      block_id_to_xs_map_[block_id] = xs;
946✔
891
  }
838✔
892

893
  // Assign placeholder unit densities
894
  densities_local_.assign(grid_->local_cells.size(), 1.0);
581✔
895
}
581✔
896

897
void
898
LBSProblem::InitializeMaterials()
777✔
899
{
900
  CALI_CXX_MARK_SCOPE("LBSProblem::InitializeMaterials");
777✔
901

902
  log.Log0Verbose1() << "Initializing Materials";
1,554✔
903

904
  // Create set of material ids locally relevant
905
  int invalid_mat_cell_count = 0;
777✔
906
  std::set<unsigned int> unique_block_ids;
777✔
907
  for (auto& cell : grid_->local_cells)
601,420✔
908
  {
909
    unique_block_ids.insert(cell.block_id);
600,643✔
910
    if (cell.block_id == std::numeric_limits<unsigned int>::max() or
600,643✔
911
        (block_id_to_xs_map_.find(cell.block_id) == block_id_to_xs_map_.end()))
600,643✔
912
      ++invalid_mat_cell_count;
×
913
  }
914
  const auto& ghost_cell_ids = grid_->cells.GetGhostGlobalIDs();
777✔
915
  for (uint64_t cell_id : ghost_cell_ids)
99,448✔
916
  {
917
    const auto& cell = grid_->cells[cell_id];
98,671✔
918
    unique_block_ids.insert(cell.block_id);
98,671✔
919
    if (cell.block_id == std::numeric_limits<unsigned int>::max() or
98,671✔
920
        (block_id_to_xs_map_.find(cell.block_id) == block_id_to_xs_map_.end()))
98,671✔
921
      ++invalid_mat_cell_count;
×
922
  }
923
  OpenSnLogicalErrorIf(invalid_mat_cell_count > 0,
777✔
924
                       std::to_string(invalid_mat_cell_count) +
925
                         " cells encountered with an invalid material id.");
926

927
  // Get ready for processing
928
  for (const auto& [blk_id, mat] : block_id_to_xs_map_)
1,943✔
929
  {
930
    mat->SetAdjointMode(options_.adjoint);
1,166✔
931

932
    OpenSnLogicalErrorIf(mat->GetNumGroups() < num_groups_,
1,166✔
933
                         "Cross-sections for block \"" + std::to_string(blk_id) +
934
                           "\" have fewer groups (" + std::to_string(mat->GetNumGroups()) +
935
                           ") than the simulation (" + std::to_string(num_groups_) + "). " +
936
                           "Cross-sections must have at least as many groups as the simulation.");
937
  }
938

939
  // Initialize precursor properties
940
  num_precursors_ = 0;
777✔
941
  max_precursors_per_material_ = 0;
777✔
942
  for (const auto& mat_id_xs : block_id_to_xs_map_)
1,943✔
943
  {
944
    const auto& xs = mat_id_xs.second;
1,166✔
945
    num_precursors_ += xs->GetNumPrecursors();
1,166✔
946
    max_precursors_per_material_ = std::max(xs->GetNumPrecursors(), max_precursors_per_material_);
1,166✔
947
  }
948

949
  // if no precursors, turn off precursors
950
  if (num_precursors_ == 0)
777✔
951
    options_.use_precursors = false;
580✔
952

953
  // check compatibility when precursors are on
954
  if (options_.use_precursors)
777✔
955
  {
956
    for (const auto& [mat_id, xs] : block_id_to_xs_map_)
352✔
957
    {
958
      OpenSnLogicalErrorIf(xs->IsFissionable() and xs->GetNumPrecursors() == 0,
176✔
959
                           "Incompatible cross-section data encountered for material id " +
960
                             std::to_string(mat_id) + ". When delayed neutron data is present " +
961
                             "for one fissionable material, it must be present for all fissionable "
962
                             "materials.");
963
    }
964
  }
965

966
  // Update transport views if available
967
  if (grid_->local_cells.size() == cell_transport_views_.size())
777✔
968
    for (const auto& cell : grid_->local_cells)
27,976✔
969
    {
970
      const auto& xs_ptr = block_id_to_xs_map_[cell.block_id];
27,780✔
971
      auto& transport_view = cell_transport_views_[cell.local_id];
27,780✔
972
      transport_view.ReassignXS(*xs_ptr);
27,780✔
973
    }
974

975
  mpi_comm.barrier();
777✔
976
}
777✔
977

978
void
979
LBSProblem::InitializeSpatialDiscretization()
501✔
980
{
981
  CALI_CXX_MARK_SCOPE("LBSProblem::InitializeSpatialDiscretization");
501✔
982

983
  OpenSnLogicalErrorIf(not discretization_,
501✔
984
                       GetName() + ": Missing spatial discretization. Construct the problem "
985
                                   "through its factory Create(...) entry point.");
986
  log.Log() << "Initializing spatial discretization metadata.\n";
1,002✔
987

988
  ComputeUnitIntegrals();
501✔
989
}
501✔
990

991
void
992
LBSProblem::ComputeUnitIntegrals()
581✔
993
{
994
  CALI_CXX_MARK_SCOPE("LBSProblem::ComputeUnitIntegrals");
581✔
995

996
  log.Log() << "Computing unit integrals.\n";
1,162✔
997
  const auto& sdm = *discretization_;
581✔
998

999
  const size_t num_local_cells = grid_->local_cells.size();
581✔
1000
  unit_cell_matrices_.resize(num_local_cells);
581✔
1001

1002
  for (const auto& cell : grid_->local_cells)
573,444✔
1003
    unit_cell_matrices_[cell.local_id] =
572,863✔
1004
      ComputeUnitCellIntegrals(sdm, cell, grid_->GetCoordinateSystem());
572,863✔
1005

1006
  const auto ghost_ids = grid_->cells.GetGhostGlobalIDs();
581✔
1007
  for (auto ghost_id : ghost_ids)
90,410✔
1008
    unit_ghost_cell_matrices_[ghost_id] =
89,829✔
1009
      ComputeUnitCellIntegrals(sdm, grid_->cells[ghost_id], grid_->GetCoordinateSystem());
179,658✔
1010

1011
  // Assessing global unit cell matrix storage
1012
  std::array<size_t, 2> num_local_ucms = {unit_cell_matrices_.size(),
581✔
1013
                                          unit_ghost_cell_matrices_.size()};
581✔
1014
  std::array<size_t, 2> num_global_ucms = {0, 0};
581✔
1015

1016
  mpi_comm.all_reduce(num_local_ucms.data(), 2, num_global_ucms.data(), mpi::op::sum<size_t>());
581✔
1017

1018
  opensn::mpi_comm.barrier();
581✔
1019
  log.Log() << "Ghost cell unit cell-matrix ratio: "
581✔
1020
            << (double)num_global_ucms[1] * 100 / (double)num_global_ucms[0] << "%";
1,162✔
1021
  log.Log() << "Cell matrices computed.";
1,162✔
1022
}
581✔
1023

1024
void
1025
LBSProblem::InitializeParrays()
581✔
1026
{
1027
  CALI_CXX_MARK_SCOPE("LBSProblem::InitializeParrays");
581✔
1028

1029
  log.Log() << "Initializing parallel arrays."
1,162✔
1030
            << " G=" << num_groups_ << " M=" << num_moments_ << std::endl;
581✔
1031

1032
  // Initialize unknown
1033
  // structure
1034
  flux_moments_uk_man_.unknowns.clear();
581✔
1035
  for (unsigned int m = 0; m < num_moments_; ++m)
1,736✔
1036
  {
1037
    flux_moments_uk_man_.AddUnknown(UnknownType::VECTOR_N, num_groups_);
1,155✔
1038
    flux_moments_uk_man_.unknowns.back().name = "m" + std::to_string(m);
1,155✔
1039
  }
1040

1041
  // Compute local # of dof
1042
  local_node_count_ = discretization_->GetNumLocalNodes();
581✔
1043
  global_node_count_ = discretization_->GetNumGlobalNodes();
581✔
1044

1045
  // Compute num of unknowns
1046
  size_t local_unknown_count = local_node_count_ * num_groups_ * num_moments_;
581✔
1047

1048
  log.LogAllVerbose1() << "LBS Number of phi unknowns: " << local_unknown_count;
1,162✔
1049

1050
  // Size local vectors
1051
  q_moments_local_.assign(local_unknown_count, 0.0);
581✔
1052
  phi_old_local_.assign(local_unknown_count, 0.0);
581✔
1053
  phi_new_local_.assign(local_unknown_count, 0.0);
581✔
1054

1055
  // Setup precursor vector
1056
  if (options_.use_precursors)
581✔
1057
  {
1058
    size_t num_precursor_dofs = grid_->local_cells.size() * max_precursors_per_material_;
60✔
1059
    precursor_new_local_.assign(num_precursor_dofs, 0.0);
60✔
1060
  }
1061

1062
  // Initialize transport views
1063
  // Transport views act as a data structure to store information
1064
  // related to the transport simulation. The most prominent function
1065
  // here is that it holds the means to know where a given cell's
1066
  // transport quantities are located in the unknown vectors (i.e. phi)
1067
  //
1068
  // Also, for a given cell, within a given sweep chunk,
1069
  // we need to solve a matrix which square size is the
1070
  // amount of nodes on the cell. max_cell_dof_count is
1071
  // initialized here.
1072
  //
1073
  size_t block_MG_counter = 0; // Counts the strides of moment and group
581✔
1074

1075
  const Vector3 ihat(1.0, 0.0, 0.0);
581✔
1076
  const Vector3 jhat(0.0, 1.0, 0.0);
581✔
1077
  const Vector3 khat(0.0, 0.0, 1.0);
581✔
1078

1079
  min_cell_dof_count_ = std::numeric_limits<unsigned int>::max();
581✔
1080
  max_cell_dof_count_ = 0;
581✔
1081
  cell_transport_views_.clear();
581✔
1082
  cell_transport_views_.reserve(grid_->local_cells.size());
581✔
1083
  for (auto& cell : grid_->local_cells)
573,444✔
1084
  {
1085
    size_t num_nodes = discretization_->GetCellNumNodes(cell);
572,863✔
1086

1087
    // compute cell volumes
1088
    double cell_volume = 0.0;
572,863✔
1089
    const auto& IntV_shapeI = unit_cell_matrices_[cell.local_id].intV_shapeI;
572,863✔
1090
    for (size_t i = 0; i < num_nodes; ++i)
3,971,827✔
1091
      cell_volume += IntV_shapeI(i);
3,398,964✔
1092

1093
    size_t cell_phi_address = block_MG_counter;
572,863✔
1094

1095
    const size_t num_faces = cell.faces.size();
572,863✔
1096
    std::vector<bool> face_local_flags(num_faces, true);
572,863✔
1097
    std::vector<int> face_locality(num_faces, opensn::mpi_comm.rank());
572,863✔
1098
    std::vector<const Cell*> neighbor_cell_ptrs(num_faces, nullptr);
572,863✔
1099
    bool cell_on_boundary = false;
572,863✔
1100
    int f = 0;
572,863✔
1101
    for (auto& face : cell.faces)
3,404,219✔
1102
    {
1103
      if (not face.has_neighbor)
2,831,356✔
1104
      {
1105
        cell_on_boundary = true;
91,778✔
1106
        face_local_flags[f] = false;
91,778✔
1107
        face_locality[f] = -1;
91,778✔
1108
      } // if bndry
1109
      else
1110
      {
1111
        const int neighbor_partition = face.GetNeighborPartitionID(grid_.get());
2,739,578✔
1112
        face_local_flags[f] = (neighbor_partition == opensn::mpi_comm.rank());
2,739,578✔
1113
        face_locality[f] = neighbor_partition;
2,739,578✔
1114
        neighbor_cell_ptrs[f] = &grid_->cells[face.neighbor_id];
2,739,578✔
1115
      }
1116

1117
      ++f;
2,831,356✔
1118
    } // for f
1119

1120
    max_cell_dof_count_ = std::max(max_cell_dof_count_, static_cast<unsigned int>(num_nodes));
572,863✔
1121
    min_cell_dof_count_ = std::min(min_cell_dof_count_, static_cast<unsigned int>(num_nodes));
572,863✔
1122
    cell_transport_views_.emplace_back(cell_phi_address,
1,145,726✔
1123
                                       num_nodes,
1124
                                       num_groups_,
572,863✔
1125
                                       num_moments_,
572,863✔
1126
                                       num_faces,
1127
                                       *block_id_to_xs_map_[cell.block_id],
572,863✔
1128
                                       cell_volume,
1129
                                       face_local_flags,
1130
                                       face_locality,
1131
                                       neighbor_cell_ptrs,
1132
                                       cell_on_boundary);
1133
    block_MG_counter += num_nodes * num_groups_ * num_moments_;
572,863✔
1134
  } // for local cell
572,863✔
1135

1136
  // Populate grid nodal mappings
1137
  // This is used in the Flux Data Structures (FLUDS)
1138
  grid_nodal_mappings_.clear();
581✔
1139
  grid_nodal_mappings_.reserve(grid_->local_cells.size());
581✔
1140
  for (auto& cell : grid_->local_cells)
573,444✔
1141
  {
1142
    CellFaceNodalMapping cell_nodal_mapping;
572,863✔
1143
    cell_nodal_mapping.reserve(cell.faces.size());
572,863✔
1144

1145
    for (auto& face : cell.faces)
3,404,219✔
1146
    {
1147
      std::vector<short> face_node_mapping;
2,831,356✔
1148
      std::vector<short> cell_node_mapping;
2,831,356✔
1149
      int adj_face_idx = -1;
2,831,356✔
1150

1151
      if (face.has_neighbor)
2,831,356✔
1152
      {
1153
        grid_->FindAssociatedVertices(face, face_node_mapping);
2,739,578✔
1154
        grid_->FindAssociatedCellVertices(face, cell_node_mapping);
2,739,578✔
1155
        adj_face_idx = face.GetNeighborAdjacentFaceIndex(grid_.get());
2,739,578✔
1156
      }
1157

1158
      cell_nodal_mapping.emplace_back(adj_face_idx, face_node_mapping, cell_node_mapping);
2,831,356✔
1159
    } // for f
2,831,356✔
1160

1161
    grid_nodal_mappings_.push_back(cell_nodal_mapping);
572,863✔
1162
  } // for local cell
572,863✔
1163

1164
  // Get grid localized communicator set
1165
  grid_local_comm_set_ = grid_->MakeMPILocalCommunicatorSet();
581✔
1166

1167
  // Initialize Field Functions
1168
  InitializeFieldFunctions();
581✔
1169

1170
  opensn::mpi_comm.barrier();
581✔
1171
  log.Log() << "Done with parallel arrays." << std::endl;
1,162✔
1172
}
581✔
1173

1174
void
1175
LBSProblem::InitializeFieldFunctions()
581✔
1176
{
1177
  CALI_CXX_MARK_SCOPE("LBSProblem::InitializeFieldFunctions");
581✔
1178

1179
  if (not field_functions_.empty())
581✔
1180
    return;
×
1181

1182
  // Initialize Field Functions for flux moments
1183
  phi_field_functions_local_map_.clear();
581✔
1184

1185
  for (unsigned int g = 0; g < num_groups_; ++g)
23,251✔
1186
  {
1187
    for (unsigned int m = 0; m < num_moments_; ++m)
100,658✔
1188
    {
1189
      std::string prefix;
77,988✔
1190
      if (options_.field_function_prefix_option == "prefix")
77,988✔
1191
      {
1192
        prefix = options_.field_function_prefix;
77,988✔
1193
        if (not prefix.empty())
77,988✔
1194
          prefix += "_";
1✔
1195
      }
1196
      if (options_.field_function_prefix_option == "solver_name")
77,988✔
1197
        prefix = GetName() + "_";
×
1198

1199
      std::ostringstream oss;
77,988✔
1200
      oss << prefix << "phi_g" << std::setw(3) << std::setfill('0') << static_cast<int>(g) << "_m"
77,988✔
1201
          << std::setw(2) << std::setfill('0') << static_cast<int>(m);
77,988✔
1202
      const std::string name = oss.str();
77,988✔
1203

1204
      auto group_ff = std::make_shared<FieldFunctionGridBased>(
77,988✔
1205
        name, discretization_, Unknown(UnknownType::SCALAR));
77,988✔
1206

1207
      field_function_stack.push_back(group_ff);
155,976✔
1208
      field_functions_.push_back(group_ff);
77,988✔
1209

1210
      phi_field_functions_local_map_[{g, m}] = field_functions_.size() - 1;
77,988✔
1211
    } // for m
77,988✔
1212
  } // for g
1213

1214
  // Initialize power generation field function
1215
  if (options_.power_field_function_on)
581✔
1216
  {
1217
    std::string prefix;
5✔
1218
    if (options_.field_function_prefix_option == "prefix")
5✔
1219
    {
1220
      prefix = options_.field_function_prefix;
5✔
1221
      if (not prefix.empty())
5✔
1222
        prefix += "_";
×
1223
    }
1224
    if (options_.field_function_prefix_option == "solver_name")
5✔
1225
      prefix = GetName() + "_";
×
1226

1227
    auto power_ff = std::make_shared<FieldFunctionGridBased>(
5✔
1228
      prefix + "power_generation", discretization_, Unknown(UnknownType::SCALAR));
10✔
1229

1230
    field_function_stack.push_back(power_ff);
10✔
1231
    field_functions_.push_back(power_ff);
5✔
1232

1233
    power_gen_fieldfunc_local_handle_ = field_functions_.size() - 1;
5✔
1234
  }
5✔
1235
}
581✔
1236

1237
void
1238
LBSProblem::InitializeSolverSchemes()
789✔
1239
{
1240
  CALI_CXX_MARK_SCOPE("LBSProblem::InitializeSolverSchemes");
789✔
1241
  InitializeWGSSolvers();
789✔
1242

1243
  ags_solver_ = std::make_shared<AGSLinearSolver>(*this, wgs_solvers_);
789✔
1244
  if (groupsets_.size() == 1)
789✔
1245
  {
1246
    ags_solver_->SetMaxIterations(1);
734✔
1247
    ags_solver_->SetVerbosity(false);
734✔
1248
  }
1249
  else
1250
  {
1251
    ags_solver_->SetMaxIterations(options_.max_ags_iterations);
55✔
1252
    ags_solver_->SetVerbosity(options_.verbose_ags_iterations);
55✔
1253
  }
1254
  ags_solver_->SetTolerance(options_.ags_tolerance);
789✔
1255
}
789✔
1256

1257
#ifndef __OPENSN_WITH_GPU__
1258
void
1259
LBSProblem::InitializeGPUExtras()
753✔
1260
{
1261
}
753✔
1262

1263
void
1264
LBSProblem::ResetGPUCarriers()
741✔
1265
{
1266
}
741✔
1267

1268
void
1269
LBSProblem::CheckCapableDevices()
×
1270
{
1271
}
×
1272
#endif // __OPENSN_WITH_GPU__
1273

1274
std::vector<double>
1275
LBSProblem::MakeSourceMomentsFromPhi()
4✔
1276
{
1277
  CALI_CXX_MARK_SCOPE("LBSProblem::MakeSourceMomentsFromPhi");
4✔
1278

1279
  size_t num_local_dofs = discretization_->GetNumLocalDOFs(flux_moments_uk_man_);
4✔
1280

1281
  std::vector<double> source_moments(num_local_dofs, 0.0);
4✔
1282
  for (auto& groupset : groupsets_)
8✔
1283
  {
1284
    active_set_source_function_(groupset,
4✔
1285
                                source_moments,
1286
                                phi_new_local_,
4✔
1287
                                APPLY_AGS_SCATTER_SOURCES | APPLY_WGS_SCATTER_SOURCES |
1288
                                  APPLY_AGS_FISSION_SOURCES | APPLY_WGS_FISSION_SOURCES);
4✔
1289
  }
1290

1291
  return source_moments;
4✔
1292
}
4✔
1293

1294
void
1295
LBSProblem::UpdateFieldFunctions()
2,805✔
1296
{
1297
  CALI_CXX_MARK_SCOPE("LBSProblem::UpdateFieldFunctions");
2,805✔
1298

1299
  const auto& sdm = *discretization_;
2,805✔
1300
  const auto& phi_uk_man = flux_moments_uk_man_;
2,805✔
1301

1302
  // Update flux moments
1303
  for (const auto& [g_and_m, ff_index] : phi_field_functions_local_map_)
87,841✔
1304
  {
1305
    const auto g = g_and_m.first;
85,036✔
1306
    const auto m = g_and_m.second;
85,036✔
1307

1308
    std::vector<double> data_vector_local(local_node_count_, 0.0);
85,036✔
1309

1310
    for (const auto& cell : grid_->local_cells)
24,450,775✔
1311
    {
1312
      const auto& cell_mapping = sdm.GetCellMapping(cell);
24,365,739✔
1313
      const size_t num_nodes = cell_mapping.GetNumNodes();
24,365,739✔
1314

1315
      for (size_t i = 0; i < num_nodes; ++i)
164,663,059✔
1316
      {
1317
        const auto imapA = sdm.MapDOFLocal(cell, i, phi_uk_man, m, g);
140,297,320✔
1318
        const auto imapB = sdm.MapDOFLocal(cell, i);
140,297,320✔
1319

1320
        data_vector_local[imapB] = phi_new_local_[imapA];
140,297,320✔
1321
      } // for node
1322
    } // for cell
1323

1324
    auto& ff_ptr = field_functions_.at(ff_index);
85,036✔
1325
    ff_ptr->UpdateFieldVector(data_vector_local);
85,036✔
1326
  }
85,036✔
1327

1328
  // Update power generation and scalar flux
1329
  if (options_.power_field_function_on)
2,805✔
1330
  {
1331
    std::vector<double> data_vector_power_local(local_node_count_, 0.0);
5✔
1332

1333
    double local_total_power = 0.0;
5✔
1334
    for (const auto& cell : grid_->local_cells)
83,319✔
1335
    {
1336
      const auto& cell_mapping = sdm.GetCellMapping(cell);
83,314✔
1337
      const size_t num_nodes = cell_mapping.GetNumNodes();
83,314✔
1338

1339
      const auto& Vi = unit_cell_matrices_[cell.local_id].intV_shapeI;
83,314✔
1340

1341
      const auto& xs = block_id_to_xs_map_.at(cell.block_id);
83,314✔
1342

1343
      if (not xs->IsFissionable())
83,314✔
1344
        continue;
56,360✔
1345

1346
      for (size_t i = 0; i < num_nodes; ++i)
134,670✔
1347
      {
1348
        const auto imapA = sdm.MapDOFLocal(cell, i);
107,716✔
1349
        const auto imapB = sdm.MapDOFLocal(cell, i, phi_uk_man, 0, 0);
107,716✔
1350

1351
        double nodal_power = 0.0;
1352
        for (unsigned int g = 0; g < num_groups_; ++g)
861,128✔
1353
        {
1354
          const double sigma_fg = xs->GetSigmaFission()[g];
753,412✔
1355
          // const double kappa_g = xs->Kappa()[g];
1356
          const double kappa_g = options_.power_default_kappa;
753,412✔
1357

1358
          nodal_power += kappa_g * sigma_fg * phi_new_local_[imapB + g];
753,412✔
1359
        } // for g
1360

1361
        data_vector_power_local[imapA] = nodal_power;
107,716✔
1362
        local_total_power += nodal_power * Vi(i);
107,716✔
1363
      } // for node
1364
    } // for cell
1365

1366
    double scale_factor = 1.0;
5✔
1367
    if (options_.power_normalization > 0.0)
5✔
1368
    {
1369
      double global_total_power = 0.0;
5✔
1370
      mpi_comm.all_reduce(local_total_power, global_total_power, mpi::op::sum<double>());
5✔
1371
      OpenSnLogicalErrorIf(
5✔
1372
        global_total_power <= 0.0,
1373
        GetName() + ": Power normalization requested, but global total power is non-positive.");
1374
      scale_factor = options_.power_normalization / global_total_power;
5✔
1375
      Scale(data_vector_power_local, scale_factor);
5✔
1376
    }
1377

1378
    const size_t ff_index = power_gen_fieldfunc_local_handle_;
5✔
1379

1380
    auto& ff_ptr = field_functions_.at(ff_index);
5✔
1381
    ff_ptr->UpdateFieldVector(data_vector_power_local);
5✔
1382

1383
    // scale scalar flux if neccessary
1384
    if (scale_factor != 1.0)
5✔
1385
    {
1386
      for (unsigned int g = 0; g < num_groups_; ++g)
34✔
1387
      {
1388
        const size_t phi_ff_index = phi_field_functions_local_map_.at({g, size_t{0}});
29✔
1389
        auto& phi_ff_ptr = field_functions_.at(phi_ff_index);
29✔
1390
        const auto& phi_vec = phi_ff_ptr->GetLocalFieldVector();
29✔
1391
        std::vector<double> phi_scaled(phi_vec.begin(), phi_vec.end());
29✔
1392
        Scale(phi_scaled, scale_factor);
29✔
1393
        phi_ff_ptr->UpdateFieldVector(phi_scaled);
29✔
1394
      }
29✔
1395
    }
1396
  } // if power enabled
5✔
1397
}
2,805✔
1398

1399
void
1400
LBSProblem::SetPhiFromFieldFunctions(PhiSTLOption which_phi,
×
1401
                                     const std::vector<unsigned int>& m_indices,
1402
                                     const std::vector<unsigned int>& g_indices)
1403
{
1404
  CALI_CXX_MARK_SCOPE("LBSProblem::SetPhiFromFieldFunctions");
×
1405

1406
  std::vector<unsigned int> m_ids_to_copy = m_indices;
×
1407
  std::vector<unsigned int> g_ids_to_copy = g_indices;
×
1408
  if (m_indices.empty())
×
1409
    for (unsigned int m = 0; m < num_moments_; ++m)
×
1410
      m_ids_to_copy.push_back(m);
×
1411
  if (g_ids_to_copy.empty())
×
1412
    for (unsigned int g = 0; g < num_groups_; ++g)
×
1413
      g_ids_to_copy.push_back(g);
×
1414

1415
  const auto& sdm = *discretization_;
×
1416
  const auto& phi_uk_man = flux_moments_uk_man_;
×
1417

1418
  for (const auto m : m_ids_to_copy)
×
1419
  {
1420
    for (const auto g : g_ids_to_copy)
×
1421
    {
1422
      const size_t ff_index = phi_field_functions_local_map_.at({g, m});
×
1423
      const auto& ff_ptr = field_functions_.at(ff_index);
×
1424
      const auto& ff_data = ff_ptr->GetLocalFieldVector();
×
1425

1426
      for (const auto& cell : grid_->local_cells)
×
1427
      {
1428
        const auto& cell_mapping = sdm.GetCellMapping(cell);
×
1429
        const size_t num_nodes = cell_mapping.GetNumNodes();
×
1430

1431
        for (size_t i = 0; i < num_nodes; ++i)
×
1432
        {
1433
          const auto imapA = sdm.MapDOFLocal(cell, i);
×
1434
          const auto imapB = sdm.MapDOFLocal(cell, i, phi_uk_man, m, g);
×
1435

1436
          if (which_phi == PhiSTLOption::PHI_OLD)
×
1437
            phi_old_local_[imapB] = ff_data[imapA];
×
1438
          else if (which_phi == PhiSTLOption::PHI_NEW)
×
1439
            phi_new_local_[imapB] = ff_data[imapA];
×
1440
        } // for node
1441
      } // for cell
1442
    } // for g
1443
  } // for m
1444
}
×
1445

1446
LBSProblem::~LBSProblem()
569✔
1447
{
1448
  ResetGPUCarriers();
1449
}
2,845✔
1450

569✔
1451
void
UNCOV
1452
LBSProblem::SetSaveAngularFlux(bool save)
×
1453
{
UNCOV
1454
  options_.save_angular_flux = save;
×
UNCOV
1455
}
×
1456

1457
void
1458
LBSProblem::ZeroPhi()
76✔
1459
{
1460
  std::fill(phi_old_local_.begin(), phi_old_local_.end(), 0.0);
76✔
1461
  std::fill(phi_new_local_.begin(), phi_new_local_.end(), 0.0);
76✔
1462
}
76✔
1463

1464
void
1465
LBSProblem::CopyPhiNewToOld()
160✔
1466
{
1467
  assert(phi_old_local_.size() == phi_new_local_.size() && "Phi vectors size mismatch.");
160✔
1468
  phi_old_local_ = phi_new_local_;
160✔
1469
}
160✔
1470

1471
void
1472
LBSProblem::SetPhiOldFrom(const std::vector<double>& phi_old)
2,256✔
1473
{
1474
  assert(phi_old.size() == phi_old_local_.size() && "SetPhiOldFrom size mismatch.");
2,256✔
1475
  phi_old_local_ = phi_old;
2,256✔
1476
}
2,256✔
1477

1478
void
1479
LBSProblem::SetPhiNewFrom(const std::vector<double>& phi_new)
×
1480
{
1481
  assert(phi_new.size() == phi_new_local_.size() && "SetPhiNewFrom size mismatch.");
×
1482
  phi_new_local_ = phi_new;
×
1483
}
×
1484

1485
void
1486
LBSProblem::ScalePhiOld(double factor)
×
1487
{
1488
  for (auto& value : phi_old_local_)
×
1489
    value *= factor;
×
1490
}
×
1491

1492
void
1493
LBSProblem::ScalePhiNew(double factor)
8✔
1494
{
1495
  for (auto& value : phi_new_local_)
168,008✔
1496
    value *= factor;
168,000✔
1497
}
8✔
1498

1499
void
1500
LBSProblem::ZeroQMoments()
62,462✔
1501
{
1502
  assert(q_moments_local_.size() == phi_old_local_.size() && "Q moments/Phi size mismatch.");
62,462✔
1503
  std::fill(q_moments_local_.begin(), q_moments_local_.end(), 0.0);
62,462✔
1504
}
62,462✔
1505

1506
void
1507
LBSProblem::ScaleQMoments(double factor)
7,949✔
1508
{
1509
  for (auto& value : q_moments_local_)
490,320,113✔
1510
    value *= factor;
490,312,164✔
1511
}
7,949✔
1512

1513
void
1514
LBSProblem::SetQMomentsFrom(const std::vector<double>& q_moments)
157,622✔
1515
{
1516
  assert(q_moments.size() == q_moments_local_.size() && "SetQMomentsFrom size mismatch.");
157,622✔
1517
  q_moments_local_ = q_moments;
157,622✔
1518
}
157,622✔
1519

1520
void
1521
LBSProblem::ScalePrecursors(double factor)
56✔
1522
{
1523
  for (auto& value : precursor_new_local_)
1,044✔
1524
    value *= factor;
988✔
1525
}
56✔
1526

1527
void
1528
LBSProblem::ZeroPrecursors()
744✔
1529
{
1530
  std::fill(precursor_new_local_.begin(), precursor_new_local_.end(), 0.0);
744✔
1531
}
744✔
1532

1533
void
1534
LBSProblem::ZeroExtSrcMoments()
×
1535
{
1536
  std::fill(ext_src_moments_local_.begin(), ext_src_moments_local_.end(), 0.0);
×
1537
}
×
1538

1539
void
1540
LBSProblem::ScaleExtSrcMoments(double factor)
×
1541
{
1542
  for (auto& value : ext_src_moments_local_)
×
1543
    value *= factor;
×
1544
}
×
1545

1546
void
1547
LBSProblem::SetUniformDensities(double density)
×
1548
{
1549
  assert(densities_local_.size() == grid_->local_cells.size() &&
×
1550
         "Densities/local-cells size mismatch.");
1551
  std::fill(densities_local_.begin(), densities_local_.end(), density);
×
1552
}
×
1553

1554
void
1555
LBSProblem::SetDensity(size_t cell_local_id, double density)
×
1556
{
1557
  assert(cell_local_id < densities_local_.size() && "SetDensity cell index out of range.");
×
1558
  densities_local_[cell_local_id] = density;
×
1559
}
×
1560

1561
void
1562
LBSProblem::SetDensitiesFrom(const std::vector<double>& densities)
×
1563
{
1564
  assert(densities.size() == densities_local_.size() && "SetDensitiesFrom size mismatch.");
×
1565
  densities_local_ = densities;
×
1566
}
×
1567

1568
void
1569
LBSProblem::SetAdjoint(bool adjoint)
24✔
1570
{
1571
  OpenSnLogicalErrorIf(
24✔
1572
    not initialized_, GetName() + ": Problem must be fully constructed before calling SetAdjoint.");
1573

1574
  if (adjoint)
24✔
1575
    if (const auto* do_problem = dynamic_cast<const DiscreteOrdinatesProblem*>(this);
20✔
1576
        do_problem and do_problem->IsTimeDependent())
20✔
1577
      OpenSnInvalidArgument(GetName() + ": Time-dependent adjoint problems are not supported.");
×
1578

1579
  const bool mode_changed = (adjoint != options_.adjoint);
24✔
1580
  if (not mode_changed)
24✔
1581
    return;
1582

1583
  options_.adjoint = adjoint;
24✔
1584

1585
  // Reinitialize materials to obtain the proper forward/adjoint cross sections.
1586
  InitializeMaterials();
24✔
1587

1588
  // Forward and adjoint sources are fundamentally different.
1589
  point_sources_.clear();
24✔
1590
  volumetric_sources_.clear();
24✔
1591
  ClearBoundaries();
24✔
1592

1593
  // Reset all solution vectors.
1594
  ZeroPhi();
24✔
1595
  ZeroPsi();
24✔
1596
  ZeroPrecursors();
24✔
1597
}
1598

1599
void
NEW
1600
LBSProblem::SetForward()
×
1601
{
NEW
1602
  SetAdjoint(false);
×
UNCOV
1603
}
×
1604

1605
bool
1606
LBSProblem::IsAdjoint() const
×
1607
{
1608
  return options_.adjoint;
×
1609
}
1610

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