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

Open-Sn / opensn / 22837428894

09 Mar 2026 03:41AM UTC coverage: 74.368% (+0.06%) from 74.311%
22837428894

push

github

web-flow
Merge pull request #969 from wdhawkins/testing_artifacts

Log test output and post as workflow artifacts

20130 of 27068 relevant lines covered (74.37%)

66820174.91 hits per line

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

81.2
/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

29
namespace opensn
30
{
31

32
InputParameters
33
LBSProblem::GetInputParameters()
615✔
34
{
35
  InputParameters params = Problem::GetInputParameters();
615✔
36

37
  params.ChangeExistingParamToOptional("name", "LBSProblem");
1,230✔
38

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

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

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

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

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

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

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

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

64
  return params;
615✔
65
}
×
66

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

84
  // Initialize options
85
  if (params.IsParameterValid("options"))
615✔
86
  {
87
    auto options_params = LBSProblem::GetOptionsBlock();
387✔
88
    options_params.AssignParameters(params.GetParam("options"));
389✔
89
    ParseOptions(options_params);
385✔
90
  }
387✔
91
  applied_adjoint_ = options_.adjoint;
613✔
92
  applied_save_angular_flux_ = options_.save_angular_flux;
613✔
93

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

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

105
const LBSOptions&
106
LBSProblem::GetOptions() const
691,532,922✔
107
{
108
  return options_;
691,532,922✔
109
}
110

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

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

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

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

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

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

198
unsigned int
199
LBSProblem::GetNumGroups() const
961,836✔
200
{
201
  return num_groups_;
961,836✔
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
11,056✔
218
{
219
  return max_precursors_per_material_;
11,056✔
220
}
221

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

228
LBSGroupset&
229
LBSProblem::GetGroupset(size_t groupset_id)
24,844,378✔
230
{
231
  return groupsets_.at(groupset_id);
24,844,378✔
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
176,580✔
262
{
263
  return point_sources_;
176,580✔
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()
16✔
276
{
277
  volumetric_sources_.clear();
16✔
278
}
16✔
279

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

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

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

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

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

313
const std::vector<UnitCellMatrices>&
314
LBSProblem::GetUnitCellMatrices() const
22,315✔
315
{
316
  return unit_cell_matrices_;
22,315✔
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()
343,561✔
327
{
328
  return cell_transport_views_;
343,561✔
329
}
330

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

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

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

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

355
std::vector<double>&
356
LBSProblem::GetQMomentsLocal()
249,510✔
357
{
358
  return q_moments_local_;
249,510✔
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
245,362✔
375
{
376
  return ext_src_moments_local_;
245,362✔
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()
494,218✔
401
{
402
  return phi_old_local_;
494,218✔
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()
285,727✔
413
{
414
  return phi_new_local_;
285,727✔
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()
460✔
425
{
426
  return precursor_new_local_;
460✔
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,090✔
437
{
438
  return densities_local_;
1,090✔
439
}
440

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

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

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

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

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

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

477
WGSContext&
478
LBSProblem::GetWGSContext(int groupset_id)
11,889✔
479
{
480
  auto& wgs_solver = wgs_solvers_[groupset_id];
11,889✔
481
  auto raw_context = wgs_solver->GetContext();
11,889✔
482
  auto wgs_context_ptr = std::dynamic_pointer_cast<WGSContext>(raw_context);
11,889✔
483
  OpenSnLogicalErrorIf(not wgs_context_ptr,
11,889✔
484
                       GetName() + ": Failed to cast solver context to WGSContext.");
485
  return *wgs_context_ptr;
11,889✔
486
}
23,778✔
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,997✔
500
{
501
  OpenSnLogicalErrorIf(g >= num_groups_, GetName() + ": Group index out of range.");
28,997✔
502
  OpenSnLogicalErrorIf(m >= num_moments_, GetName() + ": Moment index out of range.");
28,997✔
503

504
  const auto map_it = phi_field_functions_local_map_.find({g, m});
28,997✔
505
  OpenSnLogicalErrorIf(map_it == phi_field_functions_local_map_.end(),
28,997✔
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,994✔
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()
796✔
524
{
525
  InputParameters params;
796✔
526

527
  params.SetGeneralDescription("Set options from a large list of parameters");
1,592✔
528
  params.AddOptionalParameter("max_mpi_message_size",
1,592✔
529
                              32768,
530
                              "The maximum MPI message size used during sweep initialization.");
531
  params.AddOptionalParameter(
1,592✔
532
    "restart_writes_enabled", false, "Flag that controls writing of restart dumps");
533
  params.AddOptionalParameter("write_delayed_psi_to_restart",
1,592✔
534
                              true,
535
                              "Flag that controls writing of delayed angular fluxes to restarts.");
536
  params.AddOptionalParameter(
1,592✔
537
    "read_restart_path", "", "Full path for reading restart dumps including file stem.");
538
  params.AddOptionalParameter(
1,592✔
539
    "write_restart_path", "", "Full path for writing restart dumps including file stem.");
540
  params.AddOptionalParameter("write_restart_time_interval",
1,592✔
541
                              0,
542
                              "Time interval in seconds at which restart data is to be written.");
543
  params.AddOptionalParameter(
1,592✔
544
    "use_precursors", false, "Flag for using delayed neutron precursors.");
545
  params.AddOptionalParameter("use_source_moments",
1,592✔
546
                              false,
547
                              "Flag for ignoring fixed sources and selectively using source "
548
                              "moments obtained elsewhere.");
549
  params.AddOptionalParameter(
1,592✔
550
    "save_angular_flux", false, "Flag indicating whether angular fluxes are to be stored or not.");
551
  params.AddOptionalParameter(
1,592✔
552
    "adjoint", false, "Flag for toggling whether the solver is in adjoint mode.");
553
  params.AddOptionalParameter(
1,592✔
554
    "verbose_inner_iterations", true, "Flag to control verbosity of inner iterations.");
555
  params.AddOptionalParameter(
1,592✔
556
    "verbose_outer_iterations", true, "Flag to control verbosity of across-groupset iterations.");
557
  params.AddOptionalParameter(
1,592✔
558
    "max_ags_iterations", 100, "Maximum number of across-groupset iterations.");
559
  params.AddOptionalParameter("ags_tolerance", 1.0e-6, "Across-groupset iterations tolerance.");
1,592✔
560
  params.AddOptionalParameter("ags_convergence_check",
1,592✔
561
                              "l2",
562
                              "Type of convergence check for AGS iterations. Valid values are "
563
                              "`\"l2\"` and '\"pointwise\"'");
564
  params.AddOptionalParameter(
1,592✔
565
    "verbose_ags_iterations", true, "Flag to control verbosity of across-groupset iterations.");
566
  params.AddOptionalParameter("power_field_function_on",
1,592✔
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,592✔
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,592✔
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,592✔
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,592✔
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,388✔
596
                                 AllowableRangeList::New({"l2", "pointwise"}));
796✔
597
  params.ConstrainParameterRange("field_function_prefix_option",
2,388✔
598
                                 AllowableRangeList::New({"prefix", "solver_name"}));
796✔
599
  params.ConstrainParameterRange("max_mpi_message_size", AllowableRangeLowLimit::New(1024));
2,388✔
600
  params.ConstrainParameterRange("write_restart_time_interval", AllowableRangeLowLimit::New(0));
2,388✔
601
  params.ConstrainParameterRange("max_ags_iterations", AllowableRangeLowLimit::New(0));
2,388✔
602
  params.ConstrainParameterRange("ags_tolerance", AllowableRangeLowLimit::New(1.0e-18));
2,388✔
603
  params.ConstrainParameterRange("power_default_kappa", AllowableRangeLowLimit::New(0.0, false));
2,388✔
604

605
  return params;
796✔
606
}
×
607

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

618
void
619
LBSProblem::SetOptions(const InputParameters& input)
12✔
620
{
621
  ParseOptions(input);
12✔
622
  ApplyOptions();
12✔
623
}
12✔
624

625
void
626
LBSProblem::ParseOptions(const InputParameters& input)
397✔
627
{
628
  auto params = LBSProblem::GetOptionsBlock();
397✔
629
  params.AssignParameters(input);
397✔
630
  const auto& params_at_assignment = input.GetParametersAtAssignment();
397✔
631
  const auto& specified_params = params_at_assignment.GetNumParameters() > 0
397✔
632
                                   ? params_at_assignment
397✔
633
                                   : static_cast<const ParameterBlock&>(input);
397✔
634

635
  // Apply only options explicitly specified by the caller.
636
  for (const auto& spec : specified_params.GetParameters())
1,545✔
637
  {
638
    if (spec.GetName() == "max_mpi_message_size")
1,148✔
639
      options_.max_mpi_message_size = spec.GetValue<int>();
×
640

641
    else if (spec.GetName() == "restart_writes_enabled")
1,148✔
642
      options_.restart_writes_enabled = spec.GetValue<bool>();
×
643

644
    else if (spec.GetName() == "write_delayed_psi_to_restart")
1,148✔
645
      options_.write_delayed_psi_to_restart = spec.GetValue<bool>();
×
646

647
    else if (spec.GetName() == "read_restart_path")
1,148✔
648
    {
649
      options_.read_restart_path = spec.GetValue<std::string>();
12✔
650
      if (not options_.read_restart_path.empty())
12✔
651
        options_.read_restart_path += std::to_string(opensn::mpi_comm.rank()) + ".restart.h5";
36✔
652
    }
653

654
    else if (spec.GetName() == "write_restart_path")
1,136✔
655
    {
656
      options_.write_restart_path = spec.GetValue<std::string>();
×
657
      if (not options_.write_restart_path.empty())
×
658
        options_.write_restart_path += std::to_string(opensn::mpi_comm.rank()) + ".restart.h5";
×
659
    }
660

661
    else if (spec.GetName() == "write_restart_time_interval")
1,136✔
662
      options_.write_restart_time_interval = std::chrono::seconds(spec.GetValue<int>());
×
663

664
    else if (spec.GetName() == "use_precursors")
1,136✔
665
      options_.use_precursors = spec.GetValue<bool>();
152✔
666

667
    else if (spec.GetName() == "use_source_moments")
984✔
668
      options_.use_src_moments = spec.GetValue<bool>();
4✔
669

670
    else if (spec.GetName() == "save_angular_flux")
980✔
671
      options_.save_angular_flux = spec.GetValue<bool>();
234✔
672

673
    else if (spec.GetName() == "verbose_inner_iterations")
746✔
674
      options_.verbose_inner_iterations = spec.GetValue<bool>();
248✔
675

676
    else if (spec.GetName() == "max_ags_iterations")
498✔
677
      options_.max_ags_iterations = spec.GetValue<int>();
50✔
678

679
    else if (spec.GetName() == "ags_tolerance")
448✔
680
      options_.ags_tolerance = spec.GetValue<double>();
8✔
681

682
    else if (spec.GetName() == "ags_convergence_check")
440✔
683
    {
684
      auto check = spec.GetValue<std::string>();
×
685
      options_.ags_pointwise_convergence = (check == "pointwise");
×
686
    }
×
687

688
    else if (spec.GetName() == "verbose_ags_iterations")
440✔
689
      options_.verbose_ags_iterations = spec.GetValue<bool>();
164✔
690

691
    else if (spec.GetName() == "verbose_outer_iterations")
276✔
692
      options_.verbose_outer_iterations = spec.GetValue<bool>();
224✔
693

694
    else if (spec.GetName() == "power_field_function_on")
52✔
695
      options_.power_field_function_on = spec.GetValue<bool>();
5✔
696

697
    else if (spec.GetName() == "power_default_kappa")
47✔
698
      options_.power_default_kappa = spec.GetValue<double>();
13✔
699

700
    else if (spec.GetName() == "power_normalization")
34✔
701
      options_.power_normalization = spec.GetValue<double>();
13✔
702

703
    else if (spec.GetName() == "field_function_prefix_option")
21✔
704
    {
705
      options_.field_function_prefix_option = spec.GetValue<std::string>();
×
706
    }
707

708
    else if (spec.GetName() == "field_function_prefix")
21✔
709
      options_.field_function_prefix = spec.GetValue<std::string>();
1✔
710

711
    else if (spec.GetName() == "adjoint")
20✔
712
      options_.adjoint = spec.GetValue<bool>();
20✔
713

714
  } // for specified options
715

716
  OpenSnInvalidArgumentIf(options_.write_restart_time_interval > std::chrono::seconds(0) and
397✔
717
                            not options_.restart_writes_enabled,
718
                          GetName() + ": `write_restart_time_interval>0` requires "
719
                                      "`restart_writes_enabled=true`.");
720

721
  OpenSnInvalidArgumentIf(options_.write_restart_time_interval > std::chrono::seconds(0) and
397✔
722
                            options_.write_restart_time_interval < std::chrono::seconds(30),
723
                          GetName() + ": `write_restart_time_interval` must be 0 (disabled) "
724
                                      "or at least 30 seconds.");
725

726
  OpenSnInvalidArgumentIf(options_.restart_writes_enabled and options_.write_restart_path.empty(),
397✔
727
                          GetName() + ": `restart_writes_enabled=true` requires a non-empty "
728
                                      "`write_restart_path`.");
729

730
  OpenSnInvalidArgumentIf(not options_.field_function_prefix.empty() and
397✔
731
                            options_.field_function_prefix_option != "prefix",
732
                          GetName() + ": non-empty `field_function_prefix` requires "
733
                                      "`field_function_prefix_option=\"prefix\"`.");
734

735
  if (options_.restart_writes_enabled)
397✔
736
  {
737
    const auto dir = options_.write_restart_path.parent_path();
×
738

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

758
void
759
LBSProblem::ApplyOptions()
12✔
760
{
761
  if (not initialized_)
12✔
762
    return;
763

764
  if (options_.adjoint != applied_adjoint_)
12✔
765
    SetAdjoint(options_.adjoint);
4✔
766

767
  if (options_.save_angular_flux != applied_save_angular_flux_)
12✔
768
    SetSaveAngularFlux(options_.save_angular_flux);
×
769
}
770

771
void
772
LBSProblem::BuildRuntime()
613✔
773
{
774
  CALI_CXX_MARK_SCOPE("LBSProblem::BuildRuntime");
613✔
775
  if (initialized_)
613✔
776
    return;
×
777

778
  PrintSimHeader();
613✔
779
  mpi_comm.barrier();
613✔
780

781
  InitializeRuntimeCore();
613✔
782
  ValidateRuntimeModeConfiguration();
613✔
783
  InitializeSources();
613✔
784

785
  initialized_ = true;
613✔
786
}
613✔
787

788
void
789
LBSProblem::InitializeRuntimeCore()
613✔
790
{
791
  InitializeSpatialDiscretization();
613✔
792
  InitializeParrays();
613✔
793
  InitializeBoundaries();
613✔
794
  InitializeGPUExtras();
613✔
795
}
613✔
796

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

806
void
807
LBSProblem::InitializeSources()
613✔
808
{
809
  // Initialize point sources
810
  for (auto& point_source : point_sources_)
622✔
811
    point_source->Initialize(*this);
9✔
812

813
  // Initialize volumetric sources
814
  for (auto& volumetric_source : volumetric_sources_)
1,104✔
815
    volumetric_source->Initialize(*this);
491✔
816
}
613✔
817

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

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

847
    log.Log() << outstr.str() << '\n';
×
848
  }
×
849
}
×
850

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

862
  if (params.Has("point_sources"))
613✔
863
  {
864
    const auto& pt_srcs = params.GetParam("point_sources");
613✔
865
    pt_srcs.RequireBlockTypeIs(ParameterBlockType::ARRAY);
613✔
866
    for (const auto& src : pt_srcs)
622✔
867
      point_sources_.push_back(src.GetValue<std::shared_ptr<PointSource>>());
18✔
868
  }
869
}
613✔
870

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

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

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

909
    const auto& block_ids_param = xs_entry_pars.GetParam("block_ids");
870✔
910
    block_ids_param.RequireBlockTypeIs(ParameterBlockType::ARRAY);
870✔
911
    const auto& block_ids = block_ids_param.GetVectorValue<unsigned int>();
870✔
912
    auto xs = xs_entry_pars.GetSharedPtrParam<MultiGroupXS>("xs");
870✔
913
    for (const auto& block_id : block_ids)
1,848✔
914
      block_id_to_xs_map_[block_id] = xs;
978✔
915
  }
870✔
916

917
  // Assign placeholder unit densities
918
  densities_local_.assign(grid_->local_cells.size(), 1.0);
613✔
919
}
613✔
920

921
void
922
LBSProblem::InitializeMaterials()
833✔
923
{
924
  CALI_CXX_MARK_SCOPE("LBSProblem::InitializeMaterials");
833✔
925

926
  log.Log0Verbose1() << "Initializing Materials";
1,666✔
927

928
  // Create set of material ids locally relevant
929
  int invalid_mat_cell_count = 0;
833✔
930
  std::set<unsigned int> unique_block_ids;
833✔
931
  for (auto& cell : grid_->local_cells)
602,208✔
932
  {
933
    unique_block_ids.insert(cell.block_id);
601,375✔
934
    if (cell.block_id == std::numeric_limits<unsigned int>::max() or
601,375✔
935
        (block_id_to_xs_map_.find(cell.block_id) == block_id_to_xs_map_.end()))
601,375✔
936
      ++invalid_mat_cell_count;
×
937
  }
938
  const auto& ghost_cell_ids = grid_->cells.GetGhostGlobalIDs();
833✔
939
  for (uint64_t cell_id : ghost_cell_ids)
100,372✔
940
  {
941
    const auto& cell = grid_->cells[cell_id];
99,539✔
942
    unique_block_ids.insert(cell.block_id);
99,539✔
943
    if (cell.block_id == std::numeric_limits<unsigned int>::max() or
99,539✔
944
        (block_id_to_xs_map_.find(cell.block_id) == block_id_to_xs_map_.end()))
99,539✔
945
      ++invalid_mat_cell_count;
×
946
  }
947
  OpenSnLogicalErrorIf(invalid_mat_cell_count > 0,
833✔
948
                       std::to_string(invalid_mat_cell_count) +
949
                         " cells encountered with an invalid material id.");
950

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

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

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

973
  // if no precursors, turn off precursors
974
  if (num_precursors_ == 0)
833✔
975
    options_.use_precursors = false;
608✔
976

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

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

999
  mpi_comm.barrier();
833✔
1000
}
833✔
1001

1002
void
1003
LBSProblem::InitializeSpatialDiscretization()
533✔
1004
{
1005
  CALI_CXX_MARK_SCOPE("LBSProblem::InitializeSpatialDiscretization");
533✔
1006

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

1012
  ComputeUnitIntegrals();
533✔
1013
}
533✔
1014

1015
void
1016
LBSProblem::ComputeUnitIntegrals()
613✔
1017
{
1018
  CALI_CXX_MARK_SCOPE("LBSProblem::ComputeUnitIntegrals");
613✔
1019

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

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

1026
  for (const auto& cell : grid_->local_cells)
573,852✔
1027
    unit_cell_matrices_[cell.local_id] =
573,239✔
1028
      ComputeUnitCellIntegrals(sdm, cell, grid_->GetCoordinateSystem());
573,239✔
1029

1030
  const auto ghost_ids = grid_->cells.GetGhostGlobalIDs();
613✔
1031
  for (auto ghost_id : ghost_ids)
90,882✔
1032
    unit_ghost_cell_matrices_[ghost_id] =
90,269✔
1033
      ComputeUnitCellIntegrals(sdm, grid_->cells[ghost_id], grid_->GetCoordinateSystem());
180,538✔
1034

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

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

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

1048
void
1049
LBSProblem::InitializeParrays()
613✔
1050
{
1051
  CALI_CXX_MARK_SCOPE("LBSProblem::InitializeParrays");
613✔
1052

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

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

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

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

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

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

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

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

1099
  const Vector3 ihat(1.0, 0.0, 0.0);
613✔
1100
  const Vector3 jhat(0.0, 1.0, 0.0);
613✔
1101
  const Vector3 khat(0.0, 0.0, 1.0);
613✔
1102

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

1111
    // compute cell volumes
1112
    double cell_volume = 0.0;
573,239✔
1113
    const auto& IntV_shapeI = unit_cell_matrices_[cell.local_id].intV_shapeI;
573,239✔
1114
    for (size_t i = 0; i < num_nodes; ++i)
3,974,947✔
1115
      cell_volume += IntV_shapeI(i);
3,401,708✔
1116

1117
    size_t cell_phi_address = block_MG_counter;
573,239✔
1118

1119
    const size_t num_faces = cell.faces.size();
573,239✔
1120
    std::vector<bool> face_local_flags(num_faces, true);
573,239✔
1121
    std::vector<int> face_locality(num_faces, opensn::mpi_comm.rank());
573,239✔
1122
    std::vector<const Cell*> neighbor_cell_ptrs(num_faces, nullptr);
573,239✔
1123
    bool cell_on_boundary = false;
573,239✔
1124
    int f = 0;
573,239✔
1125
    for (auto& face : cell.faces)
3,406,699✔
1126
    {
1127
      if (not face.has_neighbor)
2,833,460✔
1128
      {
1129
        cell_on_boundary = true;
92,286✔
1130
        face_local_flags[f] = false;
92,286✔
1131
        face_locality[f] = -1;
92,286✔
1132
      } // if bndry
1133
      else
1134
      {
1135
        const int neighbor_partition = face.GetNeighborPartitionID(grid_.get());
2,741,174✔
1136
        face_local_flags[f] = (neighbor_partition == opensn::mpi_comm.rank());
2,741,174✔
1137
        face_locality[f] = neighbor_partition;
2,741,174✔
1138
        neighbor_cell_ptrs[f] = &grid_->cells[face.neighbor_id];
2,741,174✔
1139
      }
1140

1141
      ++f;
2,833,460✔
1142
    } // for f
1143

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

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

1169
    for (auto& face : cell.faces)
3,406,699✔
1170
    {
1171
      std::vector<short> face_node_mapping;
2,833,460✔
1172
      std::vector<short> cell_node_mapping;
2,833,460✔
1173
      int adj_face_idx = -1;
2,833,460✔
1174

1175
      if (face.has_neighbor)
2,833,460✔
1176
      {
1177
        grid_->FindAssociatedVertices(face, face_node_mapping);
2,741,174✔
1178
        grid_->FindAssociatedCellVertices(face, cell_node_mapping);
2,741,174✔
1179
        adj_face_idx = face.GetNeighborAdjacentFaceIndex(grid_.get());
2,741,174✔
1180
      }
1181

1182
      cell_nodal_mapping.emplace_back(adj_face_idx, face_node_mapping, cell_node_mapping);
2,833,460✔
1183
    } // for f
2,833,460✔
1184

1185
    grid_nodal_mappings_.push_back(cell_nodal_mapping);
573,239✔
1186
  } // for local cell
573,239✔
1187

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

1191
  // Initialize Field Functions
1192
  InitializeFieldFunctions();
613✔
1193

1194
  opensn::mpi_comm.barrier();
613✔
1195
  log.Log() << "Done with parallel arrays." << std::endl;
1,226✔
1196
}
613✔
1197

1198
void
1199
LBSProblem::InitializeFieldFunctions()
613✔
1200
{
1201
  CALI_CXX_MARK_SCOPE("LBSProblem::InitializeFieldFunctions");
613✔
1202

1203
  if (not field_functions_.empty())
613✔
1204
    return;
×
1205

1206
  // Initialize Field Functions for flux moments
1207
  phi_field_functions_local_map_.clear();
613✔
1208

1209
  for (unsigned int g = 0; g < num_groups_; ++g)
23,339✔
1210
  {
1211
    for (unsigned int m = 0; m < num_moments_; ++m)
100,770✔
1212
    {
1213
      std::string prefix;
78,044✔
1214
      if (options_.field_function_prefix_option == "prefix")
78,044✔
1215
      {
1216
        prefix = options_.field_function_prefix;
78,044✔
1217
        if (not prefix.empty())
78,044✔
1218
          prefix += "_";
1✔
1219
      }
1220
      if (options_.field_function_prefix_option == "solver_name")
78,044✔
1221
        prefix = GetName() + "_";
×
1222

1223
      std::ostringstream oss;
78,044✔
1224
      oss << prefix << "phi_g" << std::setw(3) << std::setfill('0') << static_cast<int>(g) << "_m"
78,044✔
1225
          << std::setw(2) << std::setfill('0') << static_cast<int>(m);
78,044✔
1226
      const std::string name = oss.str();
78,044✔
1227

1228
      auto group_ff = std::make_shared<FieldFunctionGridBased>(
78,044✔
1229
        name, discretization_, Unknown(UnknownType::SCALAR));
78,044✔
1230

1231
      field_function_stack.push_back(group_ff);
156,088✔
1232
      field_functions_.push_back(group_ff);
78,044✔
1233

1234
      phi_field_functions_local_map_[{g, m}] = field_functions_.size() - 1;
78,044✔
1235
    } // for m
78,044✔
1236
  } // for g
1237

1238
  // Initialize power generation field function
1239
  if (options_.power_field_function_on)
613✔
1240
  {
1241
    std::string prefix;
5✔
1242
    if (options_.field_function_prefix_option == "prefix")
5✔
1243
    {
1244
      prefix = options_.field_function_prefix;
5✔
1245
      if (not prefix.empty())
5✔
1246
        prefix += "_";
×
1247
    }
1248
    if (options_.field_function_prefix_option == "solver_name")
5✔
1249
      prefix = GetName() + "_";
×
1250

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

1254
    field_function_stack.push_back(power_ff);
10✔
1255
    field_functions_.push_back(power_ff);
5✔
1256

1257
    power_gen_fieldfunc_local_handle_ = field_functions_.size() - 1;
5✔
1258
  }
5✔
1259
}
613✔
1260

1261
void
1262
LBSProblem::InitializeSolverSchemes()
781✔
1263
{
1264
  CALI_CXX_MARK_SCOPE("LBSProblem::InitializeSolverSchemes");
781✔
1265
  InitializeWGSSolvers();
781✔
1266

1267
  ags_solver_ = std::make_shared<AGSLinearSolver>(*this, wgs_solvers_);
781✔
1268
  if (groupsets_.size() == 1)
781✔
1269
  {
1270
    ags_solver_->SetMaxIterations(1);
726✔
1271
    ags_solver_->SetVerbosity(false);
726✔
1272
  }
1273
  else
1274
  {
1275
    ags_solver_->SetMaxIterations(options_.max_ags_iterations);
55✔
1276
    ags_solver_->SetVerbosity(options_.verbose_ags_iterations);
55✔
1277
  }
1278
  ags_solver_->SetTolerance(options_.ags_tolerance);
781✔
1279
}
781✔
1280

1281
#ifndef __OPENSN_WITH_GPU__
1282
void
1283
LBSProblem::InitializeGPUExtras()
809✔
1284
{
1285
}
809✔
1286

1287
void
1288
LBSProblem::ResetGPUCarriers()
797✔
1289
{
1290
}
797✔
1291

1292
void
1293
LBSProblem::CheckCapableDevices()
×
1294
{
1295
}
×
1296
#endif // __OPENSN_WITH_GPU__
1297

1298
std::vector<double>
1299
LBSProblem::MakeSourceMomentsFromPhi()
4✔
1300
{
1301
  CALI_CXX_MARK_SCOPE("LBSProblem::MakeSourceMomentsFromPhi");
4✔
1302

1303
  size_t num_local_dofs = discretization_->GetNumLocalDOFs(flux_moments_uk_man_);
4✔
1304

1305
  std::vector<double> source_moments(num_local_dofs, 0.0);
4✔
1306
  for (auto& groupset : groupsets_)
8✔
1307
  {
1308
    active_set_source_function_(groupset,
4✔
1309
                                source_moments,
1310
                                phi_new_local_,
4✔
1311
                                APPLY_AGS_SCATTER_SOURCES | APPLY_WGS_SCATTER_SOURCES |
1312
                                  APPLY_AGS_FISSION_SOURCES | APPLY_WGS_FISSION_SOURCES);
4✔
1313
  }
1314

1315
  return source_moments;
4✔
1316
}
4✔
1317

1318
void
1319
LBSProblem::UpdateFieldFunctions()
2,957✔
1320
{
1321
  CALI_CXX_MARK_SCOPE("LBSProblem::UpdateFieldFunctions");
2,957✔
1322

1323
  const auto& sdm = *discretization_;
2,957✔
1324
  const auto& phi_uk_man = flux_moments_uk_man_;
2,957✔
1325

1326
  // Update flux moments
1327
  for (const auto& [g_and_m, ff_index] : phi_field_functions_local_map_)
88,217✔
1328
  {
1329
    const auto g = g_and_m.first;
85,260✔
1330
    const auto m = g_and_m.second;
85,260✔
1331

1332
    std::vector<double> data_vector_local(local_node_count_, 0.0);
85,260✔
1333

1334
    for (const auto& cell : grid_->local_cells)
24,454,199✔
1335
    {
1336
      const auto& cell_mapping = sdm.GetCellMapping(cell);
24,368,939✔
1337
      const size_t num_nodes = cell_mapping.GetNumNodes();
24,368,939✔
1338

1339
      for (size_t i = 0; i < num_nodes; ++i)
164,690,755✔
1340
      {
1341
        const auto imapA = sdm.MapDOFLocal(cell, i, phi_uk_man, m, g);
140,321,816✔
1342
        const auto imapB = sdm.MapDOFLocal(cell, i);
140,321,816✔
1343

1344
        data_vector_local[imapB] = phi_new_local_[imapA];
140,321,816✔
1345
      } // for node
1346
    } // for cell
1347

1348
    auto& ff_ptr = field_functions_.at(ff_index);
85,260✔
1349
    ff_ptr->UpdateFieldVector(data_vector_local);
85,260✔
1350
  }
85,260✔
1351

1352
  // Update power generation and scalar flux
1353
  if (options_.power_field_function_on)
2,957✔
1354
  {
1355
    std::vector<double> data_vector_power_local(local_node_count_, 0.0);
5✔
1356

1357
    double local_total_power = 0.0;
5✔
1358
    for (const auto& cell : grid_->local_cells)
83,319✔
1359
    {
1360
      const auto& cell_mapping = sdm.GetCellMapping(cell);
83,314✔
1361
      const size_t num_nodes = cell_mapping.GetNumNodes();
83,314✔
1362

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

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

1367
      if (not xs->IsFissionable())
83,314✔
1368
        continue;
56,360✔
1369

1370
      for (size_t i = 0; i < num_nodes; ++i)
134,670✔
1371
      {
1372
        const auto imapA = sdm.MapDOFLocal(cell, i);
107,716✔
1373
        const auto imapB = sdm.MapDOFLocal(cell, i, phi_uk_man, 0, 0);
107,716✔
1374

1375
        double nodal_power = 0.0;
1376
        for (unsigned int g = 0; g < num_groups_; ++g)
861,128✔
1377
        {
1378
          const double sigma_fg = xs->GetSigmaFission()[g];
753,412✔
1379
          // const double kappa_g = xs->Kappa()[g];
1380
          const double kappa_g = options_.power_default_kappa;
753,412✔
1381

1382
          nodal_power += kappa_g * sigma_fg * phi_new_local_[imapB + g];
753,412✔
1383
        } // for g
1384

1385
        data_vector_power_local[imapA] = nodal_power;
107,716✔
1386
        local_total_power += nodal_power * Vi(i);
107,716✔
1387
      } // for node
1388
    } // for cell
1389

1390
    double scale_factor = 1.0;
5✔
1391
    if (options_.power_normalization > 0.0)
5✔
1392
    {
1393
      double global_total_power = 0.0;
5✔
1394
      mpi_comm.all_reduce(local_total_power, global_total_power, mpi::op::sum<double>());
5✔
1395
      OpenSnLogicalErrorIf(
5✔
1396
        global_total_power <= 0.0,
1397
        GetName() + ": Power normalization requested, but global total power is non-positive.");
1398
      scale_factor = options_.power_normalization / global_total_power;
5✔
1399
      Scale(data_vector_power_local, scale_factor);
5✔
1400
    }
1401

1402
    const size_t ff_index = power_gen_fieldfunc_local_handle_;
5✔
1403

1404
    auto& ff_ptr = field_functions_.at(ff_index);
5✔
1405
    ff_ptr->UpdateFieldVector(data_vector_power_local);
5✔
1406

1407
    // scale scalar flux if neccessary
1408
    if (scale_factor != 1.0)
5✔
1409
    {
1410
      for (unsigned int g = 0; g < num_groups_; ++g)
34✔
1411
      {
1412
        const size_t phi_ff_index = phi_field_functions_local_map_.at({g, size_t{0}});
29✔
1413
        auto& phi_ff_ptr = field_functions_.at(phi_ff_index);
29✔
1414
        const auto& phi_vec = phi_ff_ptr->GetLocalFieldVector();
29✔
1415
        std::vector<double> phi_scaled(phi_vec.begin(), phi_vec.end());
29✔
1416
        Scale(phi_scaled, scale_factor);
29✔
1417
        phi_ff_ptr->UpdateFieldVector(phi_scaled);
29✔
1418
      }
29✔
1419
    }
1420
  } // if power enabled
5✔
1421
}
2,957✔
1422

1423
void
1424
LBSProblem::SetPhiFromFieldFunctions(PhiSTLOption which_phi,
×
1425
                                     const std::vector<unsigned int>& m_indices,
1426
                                     const std::vector<unsigned int>& g_indices)
1427
{
1428
  CALI_CXX_MARK_SCOPE("LBSProblem::SetPhiFromFieldFunctions");
×
1429

1430
  std::vector<unsigned int> m_ids_to_copy = m_indices;
×
1431
  std::vector<unsigned int> g_ids_to_copy = g_indices;
×
1432
  if (m_indices.empty())
×
1433
    for (unsigned int m = 0; m < num_moments_; ++m)
×
1434
      m_ids_to_copy.push_back(m);
×
1435
  if (g_ids_to_copy.empty())
×
1436
    for (unsigned int g = 0; g < num_groups_; ++g)
×
1437
      g_ids_to_copy.push_back(g);
×
1438

1439
  const auto& sdm = *discretization_;
×
1440
  const auto& phi_uk_man = flux_moments_uk_man_;
×
1441

1442
  for (const auto m : m_ids_to_copy)
×
1443
  {
1444
    for (const auto g : g_ids_to_copy)
×
1445
    {
1446
      const size_t ff_index = phi_field_functions_local_map_.at({g, m});
×
1447
      const auto& ff_ptr = field_functions_.at(ff_index);
×
1448
      const auto& ff_data = ff_ptr->GetLocalFieldVector();
×
1449

1450
      for (const auto& cell : grid_->local_cells)
×
1451
      {
1452
        const auto& cell_mapping = sdm.GetCellMapping(cell);
×
1453
        const size_t num_nodes = cell_mapping.GetNumNodes();
×
1454

1455
        for (size_t i = 0; i < num_nodes; ++i)
×
1456
        {
1457
          const auto imapA = sdm.MapDOFLocal(cell, i);
×
1458
          const auto imapB = sdm.MapDOFLocal(cell, i, phi_uk_man, m, g);
×
1459

1460
          if (which_phi == PhiSTLOption::PHI_OLD)
×
1461
            phi_old_local_[imapB] = ff_data[imapA];
×
1462
          else if (which_phi == PhiSTLOption::PHI_NEW)
×
1463
            phi_new_local_[imapB] = ff_data[imapA];
×
1464
        } // for node
1465
      } // for cell
1466
    } // for g
1467
  } // for m
1468
}
×
1469

1470
LBSProblem::~LBSProblem()
601✔
1471
{
1472
  ResetGPUCarriers();
1473
}
3,005✔
1474

601✔
1475
void
1476
LBSProblem::SetSaveAngularFlux(bool save)
80✔
1477
{
1478
  options_.save_angular_flux = save;
80✔
1479
  if (initialized_)
80✔
1480
    applied_save_angular_flux_ = save;
36✔
1481
}
80✔
1482

1483
void
1484
LBSProblem::ZeroPhi()
76✔
1485
{
1486
  std::fill(phi_old_local_.begin(), phi_old_local_.end(), 0.0);
76✔
1487
  std::fill(phi_new_local_.begin(), phi_new_local_.end(), 0.0);
76✔
1488
}
76✔
1489

1490
void
1491
LBSProblem::CopyPhiNewToOld()
192✔
1492
{
1493
  assert(phi_old_local_.size() == phi_new_local_.size() && "Phi vectors size mismatch.");
192✔
1494
  phi_old_local_ = phi_new_local_;
192✔
1495
}
192✔
1496

1497
void
1498
LBSProblem::SetPhiOldFrom(const std::vector<double>& phi_old)
2,376✔
1499
{
1500
  assert(phi_old.size() == phi_old_local_.size() && "SetPhiOldFrom size mismatch.");
2,376✔
1501
  phi_old_local_ = phi_old;
2,376✔
1502
}
2,376✔
1503

1504
void
1505
LBSProblem::SetPhiNewFrom(const std::vector<double>& phi_new)
×
1506
{
1507
  assert(phi_new.size() == phi_new_local_.size() && "SetPhiNewFrom size mismatch.");
×
1508
  phi_new_local_ = phi_new;
×
1509
}
×
1510

1511
void
1512
LBSProblem::ScalePhiOld(double factor)
×
1513
{
1514
  for (auto& value : phi_old_local_)
×
1515
    value *= factor;
×
1516
}
×
1517

1518
void
1519
LBSProblem::ScalePhiNew(double factor)
8✔
1520
{
1521
  for (auto& value : phi_new_local_)
168,008✔
1522
    value *= factor;
168,000✔
1523
}
8✔
1524

1525
void
1526
LBSProblem::ZeroQMoments()
66,890✔
1527
{
1528
  assert(q_moments_local_.size() == phi_old_local_.size() && "Q moments/Phi size mismatch.");
66,890✔
1529
  std::fill(q_moments_local_.begin(), q_moments_local_.end(), 0.0);
66,890✔
1530
}
66,890✔
1531

1532
void
1533
LBSProblem::ScaleQMoments(double factor)
8,213✔
1534
{
1535
  for (auto& value : q_moments_local_)
490,206,265✔
1536
    value *= factor;
490,198,052✔
1537
}
8,213✔
1538

1539
void
1540
LBSProblem::SetQMomentsFrom(const std::vector<double>& q_moments)
178,422✔
1541
{
1542
  assert(q_moments.size() == q_moments_local_.size() && "SetQMomentsFrom size mismatch.");
178,422✔
1543
  q_moments_local_ = q_moments;
178,422✔
1544
}
178,422✔
1545

1546
void
1547
LBSProblem::ScalePrecursors(double factor)
68✔
1548
{
1549
  for (auto& value : precursor_new_local_)
1,284✔
1550
    value *= factor;
1,216✔
1551
}
68✔
1552

1553
void
1554
LBSProblem::ZeroPrecursors()
844✔
1555
{
1556
  std::fill(precursor_new_local_.begin(), precursor_new_local_.end(), 0.0);
844✔
1557
}
844✔
1558

1559
void
1560
LBSProblem::ZeroExtSrcMoments()
×
1561
{
1562
  std::fill(ext_src_moments_local_.begin(), ext_src_moments_local_.end(), 0.0);
×
1563
}
×
1564

1565
void
1566
LBSProblem::ScaleExtSrcMoments(double factor)
×
1567
{
1568
  for (auto& value : ext_src_moments_local_)
×
1569
    value *= factor;
×
1570
}
×
1571

1572
void
1573
LBSProblem::SetUniformDensities(double density)
×
1574
{
1575
  assert(densities_local_.size() == grid_->local_cells.size() &&
×
1576
         "Densities/local-cells size mismatch.");
1577
  std::fill(densities_local_.begin(), densities_local_.end(), density);
×
1578
}
×
1579

1580
void
1581
LBSProblem::SetDensity(size_t cell_local_id, double density)
×
1582
{
1583
  assert(cell_local_id < densities_local_.size() && "SetDensity cell index out of range.");
×
1584
  densities_local_[cell_local_id] = density;
×
1585
}
×
1586

1587
void
1588
LBSProblem::SetDensitiesFrom(const std::vector<double>& densities)
×
1589
{
1590
  assert(densities.size() == densities_local_.size() && "SetDensitiesFrom size mismatch.");
×
1591
  densities_local_ = densities;
×
1592
}
×
1593

1594
void
1595
LBSProblem::SetAdjoint(bool adjoint)
24✔
1596
{
1597
  OpenSnLogicalErrorIf(
24✔
1598
    not initialized_, GetName() + ": Problem must be fully constructed before calling SetAdjoint.");
1599

1600
  if (adjoint)
24✔
1601
    if (const auto* do_problem = dynamic_cast<const DiscreteOrdinatesProblem*>(this);
20✔
1602
        do_problem and do_problem->IsTimeDependent())
20✔
1603
      OpenSnInvalidArgument(GetName() + ": Time-dependent adjoint problems are not supported.");
×
1604

1605
  options_.adjoint = adjoint;
24✔
1606

1607
  if (adjoint != applied_adjoint_)
24✔
1608
  {
1609
    // Reinitialize materials to obtain the proper forward/adjoint cross sections.
1610
    InitializeMaterials();
24✔
1611

1612
    // Forward and adjoint sources are fundamentally different.
1613
    point_sources_.clear();
24✔
1614
    volumetric_sources_.clear();
24✔
1615
    ClearBoundaries();
24✔
1616

1617
    // Reset all solution vectors.
1618
    ZeroPhi();
24✔
1619
    ZeroPsi();
24✔
1620
    ZeroPrecursors();
24✔
1621

1622
    applied_adjoint_ = adjoint;
24✔
1623
  }
1624
}
24✔
1625

1626
bool
1627
LBSProblem::IsAdjoint() const
×
1628
{
1629
  return options_.adjoint;
×
1630
}
1631

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