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

Open-Sn / opensn / 20185909776

12 Dec 2025 06:55PM UTC coverage: 74.333% (+0.3%) from 74.037%
20185909776

push

github

web-flow
Merge pull request #859 from wdhawkins/td_source_driver

Adding time-dependent solver and time-dependent sources

367 of 398 new or added lines in 23 files covered. (92.21%)

113 existing lines in 28 files now uncovered.

18610 of 25036 relevant lines covered (74.33%)

68947552.69 hits per line

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

82.82
/modules/linear_boltzmann_solvers/lbs_problem/lbs_problem.cc
1
// SPDX-FileCopyrightText: 2024 The OpenSn Authors <https://open-sn.github.io/opensn/>
2
// SPDX-License-Identifier: MIT
3

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

28
namespace opensn
29
{
30

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

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

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

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

45
  params.AddRequiredParameter<size_t>("num_groups", "The total number of groups within the solver");
708✔
46

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

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

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

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

61
  params.AddOptionalParameterArray(
708✔
62
    "boundary_conditions", {}, "An array containing tables for each boundary specification.");
63
  params.LinkParameterToBlock("boundary_conditions", "BoundaryOptionsBlock");
708✔
64

65
  params.AddOptionalParameterBlock(
708✔
66
    "options", ParameterBlock(), "Block of options. See <TT>OptionsBlock</TT>.");
708✔
67
  params.LinkParameterToBlock("options", "OptionsBlock");
708✔
68

69
  params.AddOptionalParameter("use_gpus", false, "Offload the sweep computation to GPUs.");
708✔
70

71
  params.AddOptionalParameter(
708✔
72
    "time_dependent", false, "Flag indicating whether the problem is time dependent.");
73
  return params;
354✔
74
}
×
75

76
LBSProblem::LBSProblem(const InputParameters& params)
354✔
77
  : Problem(params),
78
    time_dependent_(params.GetParamValue<bool>("time_dependent")),
354✔
79
    num_groups_(params.GetParamValue<size_t>("num_groups")),
354✔
80
    grid_(params.GetSharedPtrParam<MeshContinuum>("mesh")),
354✔
81
    use_gpus_(params.GetParamValue<bool>("use_gpus"))
1,416✔
82
{
83
  // Check system for GPU acceleration
84
  if (use_gpus_)
354✔
85
  {
NEW
86
    if (time_dependent_)
×
NEW
87
      throw std::invalid_argument(GetName() +
×
NEW
88
                                  ": Time dependent problems are not supported on GPUs.");
×
89
#ifdef __OPENSN_USE_CUDA__
90
    CheckCapableDevices();
91
#else
92
    throw std::invalid_argument(
×
93
      GetName() + ": GPU support was requested, but OpenSn was built without CUDA enabled.");
×
94
#endif // __OPENSN_USE_CUDA__
95
  }
96

97
  // Initialize options
98
  if (params.IsParameterValid("options"))
354✔
99
  {
100
    auto options_params = LBSProblem::GetOptionsBlock();
198✔
101
    options_params.AssignParameters(params.GetParam("options"));
200✔
102
    SetOptions(options_params);
196✔
103
  }
198✔
104

105
  // Set geometry type
106
  geometry_type_ = grid_->GetGeometryType();
352✔
107
  if (geometry_type_ == GeometryType::INVALID)
352✔
108
    throw std::runtime_error(GetName() + ": Invalid geometry type.");
×
109

110
  // Set boundary conditions
111
  if (params.Has("boundary_conditions"))
352✔
112
  {
113
    const auto& bcs = params.GetParam("boundary_conditions");
352✔
114
    bcs.RequireBlockTypeIs(ParameterBlockType::ARRAY);
352✔
115
    for (size_t b = 0; b < bcs.GetNumParameters(); ++b)
1,074✔
116
    {
117
      auto bndry_params = GetBoundaryOptionsBlock();
722✔
118
      bndry_params.AssignParameters(bcs.GetParam(b));
722✔
119
      SetBoundaryOptions(bndry_params);
722✔
120
    }
722✔
121
  }
122

123
  InitializeGroupsets(params);
352✔
124
  InitializeSources(params);
352✔
125
  InitializeXSmapAndDensities(params);
352✔
126
  InitializeMaterials();
352✔
127
}
372✔
128

129
LBSOptions&
130
LBSProblem::GetOptions()
20,651✔
131
{
132
  return options_;
20,651✔
133
}
134

135
const LBSOptions&
136
LBSProblem::GetOptions() const
598,440,916✔
137
{
138
  return options_;
598,440,916✔
139
}
140

141
double
142
LBSProblem::GetTime() const
116,990✔
143
{
144
  return time_;
116,990✔
145
}
146

147
void
148
LBSProblem::SetTime(double time)
1,640✔
149
{
150
  time_ = time;
1,640✔
151
}
1,640✔
152

153
void
154
LBSProblem::SetTimeStep(double dt)
820✔
155
{
156
  dt_ = dt;
820✔
157
}
820✔
158

159
double
160
LBSProblem::GetTimeStep() const
1,971,164,160✔
161
{
162
  return dt_;
1,971,164,160✔
163
}
164

165
bool
166
LBSProblem::IsTimeDependent() const
39,998✔
167
{
168
  return time_dependent_;
39,998✔
169
}
170

171
void
172
LBSProblem::SetTheta(double theta)
820✔
173
{
174
  theta_ = theta;
820✔
175
}
820✔
176

177
double
178
LBSProblem::GetTheta() const
1,971,164,160✔
179
{
180
  return theta_;
1,971,164,160✔
181
}
182

183
GeometryType
184
LBSProblem::GetGeometryType() const
4✔
185
{
186
  return geometry_type_;
4✔
187
}
188

189
size_t
190
LBSProblem::GetNumMoments() const
196,878✔
191
{
192
  return num_moments_;
196,878✔
193
}
194

195
unsigned int
196
LBSProblem::GetMaxCellDOFCount() const
411✔
197
{
198
  return max_cell_dof_count_;
411✔
199
}
200

201
unsigned int
202
LBSProblem::GetMinCellDOFCount() const
411✔
203
{
204
  return min_cell_dof_count_;
411✔
205
}
206

207
bool
208
LBSProblem::UseGPUs() const
366✔
209
{
210
  return use_gpus_;
366✔
211
}
212

213
size_t
214
LBSProblem::GetNumGroups() const
75,100✔
215
{
216
  return num_groups_;
75,100✔
217
}
218

219
unsigned int
220
LBSProblem::GetScatteringOrder() const
4✔
221
{
222
  return scattering_order_;
4✔
223
}
224

225
size_t
226
LBSProblem::GetNumPrecursors() const
×
227
{
228
  return num_precursors_;
×
229
}
230

231
size_t
232
LBSProblem::GetMaxPrecursorsPerMaterial() const
8✔
233
{
234
  return max_precursors_per_material_;
8✔
235
}
236

237
const std::vector<LBSGroup>&
238
LBSProblem::GetGroups() const
203,606✔
239
{
240
  return groups_;
203,606✔
241
}
242

243
std::vector<LBSGroupset>&
244
LBSProblem::GetGroupsets()
8,170,791✔
245
{
246
  return groupsets_;
8,170,791✔
247
}
248

249
const std::vector<LBSGroupset>&
250
LBSProblem::GetGroupsets() const
×
251
{
252
  return groupsets_;
×
253
}
254

255
void
256
LBSProblem::AddPointSource(std::shared_ptr<PointSource> point_source)
×
257
{
258
  point_sources_.push_back(point_source);
×
259
  if (discretization_)
×
260
    point_sources_.back()->Initialize(*this);
×
261
}
×
262

263
void
264
LBSProblem::ClearPointSources()
×
265
{
266
  point_sources_.clear();
×
267
}
×
268

269
const std::vector<std::shared_ptr<PointSource>>&
270
LBSProblem::GetPointSources() const
9,124✔
271
{
272
  return point_sources_;
9,124✔
273
}
274

275
void
276
LBSProblem::AddVolumetricSource(std::shared_ptr<VolumetricSource> volumetric_source)
16✔
277
{
278
  volumetric_sources_.push_back(volumetric_source);
16✔
279
  if (discretization_)
16✔
280
    volumetric_sources_.back()->Initialize(*this);
16✔
281
}
16✔
282

283
void
284
LBSProblem::ClearVolumetricSources()
4✔
285
{
286
  volumetric_sources_.clear();
4✔
287
}
4✔
288

289
const std::vector<std::shared_ptr<VolumetricSource>>&
290
LBSProblem::GetVolumetricSources() const
9,124✔
291
{
292
  return volumetric_sources_;
9,124✔
293
}
294

295
void
296
LBSProblem::ClearBoundaries()
×
297
{
298
  boundary_preferences_.clear();
×
299
}
×
300

301
const BlockID2XSMap&
302
LBSProblem::GetBlockID2XSMap() const
7,092✔
303
{
304
  return block_id_to_xs_map_;
7,092✔
305
}
306

307
std::shared_ptr<MeshContinuum>
308
LBSProblem::GetGrid() const
267,657✔
309
{
310
  return grid_;
267,657✔
311
}
312

313
const SpatialDiscretization&
314
LBSProblem::GetSpatialDiscretization() const
85,815✔
315
{
316
  return *discretization_;
85,815✔
317
}
318

319
const std::vector<UnitCellMatrices>&
320
LBSProblem::GetUnitCellMatrices() const
7,686✔
321
{
322
  return unit_cell_matrices_;
7,686✔
323
}
324

325
const std::map<uint64_t, UnitCellMatrices>&
326
LBSProblem::GetUnitGhostCellMatrices() const
17✔
327
{
328
  return unit_ghost_cell_matrices_;
17✔
329
}
330

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

337
const std::vector<CellLBSView>&
338
LBSProblem::GetCellTransportViews() const
175,485✔
339
{
340
  return cell_transport_views_;
175,485✔
341
}
342

343
const UnknownManager&
344
LBSProblem::GetUnknownManager() const
26,703✔
345
{
346
  return flux_moments_uk_man_;
26,703✔
347
}
348

349
size_t
350
LBSProblem::GetLocalNodeCount() const
3,112✔
351
{
352
  return local_node_count_;
3,112✔
353
}
354

355
size_t
356
LBSProblem::GetGlobalNodeCount() const
2,184✔
357
{
358
  return global_node_count_;
2,184✔
359
}
360

361
std::vector<double>&
362
LBSProblem::GetQMomentsLocal()
62,620✔
363
{
364
  return q_moments_local_;
62,620✔
365
}
366

367
const std::vector<double>&
368
LBSProblem::GetQMomentsLocal() const
×
369
{
370
  return q_moments_local_;
×
371
}
372

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

379
const std::vector<double>&
380
LBSProblem::GetExtSrcMomentsLocal() const
58,495✔
381
{
382
  return ext_src_moments_local_;
58,495✔
383
}
384

385
std::vector<double>&
386
LBSProblem::GetPhiOldLocal()
100,417✔
387
{
388
  return phi_old_local_;
100,417✔
389
}
390

391
const std::vector<double>&
392
LBSProblem::GetPhiOldLocal() const
×
393
{
394
  return phi_old_local_;
×
395
}
396

397
std::vector<double>&
398
LBSProblem::GetPhiNewLocal()
83,665✔
399
{
400
  return phi_new_local_;
83,665✔
401
}
402

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

409
std::vector<double>&
410
LBSProblem::GetPrecursorsNewLocal()
16✔
411
{
412
  return precursor_new_local_;
16✔
413
}
414

415
const std::vector<double>&
416
LBSProblem::GetPrecursorsNewLocal() const
×
417
{
418
  return precursor_new_local_;
×
419
}
420

421
std::vector<double>&
422
LBSProblem::GetDensitiesLocal()
661✔
423
{
424
  return densities_local_;
661✔
425
}
426

427
const std::vector<double>&
428
LBSProblem::GetDensitiesLocal() const
58,495✔
429
{
430
  return densities_local_;
58,495✔
431
}
432

433
SetSourceFunction
434
LBSProblem::GetActiveSetSourceFunction() const
4,154✔
435
{
436
  return active_set_source_function_;
4,154✔
437
}
438

439
std::shared_ptr<AGSLinearSolver>
440
LBSProblem::GetAGSSolver()
336✔
441
{
442
  return ags_solver_;
336✔
443
}
444

445
std::vector<std::shared_ptr<LinearSolver>>&
446
LBSProblem::GetWGSSolvers()
113✔
447
{
448
  return wgs_solvers_;
113✔
449
}
450

451
WGSContext&
452
LBSProblem::GetWGSContext(int groupset_id)
11,508✔
453
{
454
  auto& wgs_solver = wgs_solvers_[groupset_id];
11,508✔
455
  auto raw_context = wgs_solver->GetContext();
11,508✔
456
  auto wgs_context_ptr = std::dynamic_pointer_cast<WGSContext>(raw_context);
11,508✔
457
  OpenSnLogicalErrorIf(not wgs_context_ptr, "Failed to cast WGSContext");
11,508✔
458
  return *wgs_context_ptr;
11,508✔
459
}
23,016✔
460

461
std::map<uint64_t, BoundaryPreference>&
462
LBSProblem::GetBoundaryPreferences()
4✔
463
{
464
  return boundary_preferences_;
4✔
465
}
466

467
std::pair<size_t, size_t>
468
LBSProblem::GetNumPhiIterativeUnknowns()
×
469
{
470
  const auto& sdm = *discretization_;
×
471
  const size_t num_local_phi_dofs = sdm.GetNumLocalDOFs(flux_moments_uk_man_);
×
472
  const size_t num_global_phi_dofs = sdm.GetNumGlobalDOFs(flux_moments_uk_man_);
×
473

474
  return {num_local_phi_dofs, num_global_phi_dofs};
×
475
}
476

477
size_t
478
LBSProblem::MapPhiFieldFunction(size_t g, size_t m) const
25,500✔
479
{
480
  OpenSnLogicalErrorIf(phi_field_functions_local_map_.count({g, m}) == 0,
25,500✔
481
                       std::string("Failure to map phi field function g") + std::to_string(g) +
482
                         " m" + std::to_string(m));
483

484
  return phi_field_functions_local_map_.at({g, m});
25,500✔
485
}
486

487
std::shared_ptr<FieldFunctionGridBased>
488
LBSProblem::GetPowerFieldFunction() const
×
489
{
490
  OpenSnLogicalErrorIf(not options_.power_field_function_on,
×
491
                       "Called when options_.power_field_function_on == false");
492

493
  return field_functions_[power_gen_fieldfunc_local_handle_];
×
494
}
495

496
InputParameters
497
LBSProblem::GetOptionsBlock()
418✔
498
{
499
  InputParameters params;
418✔
500

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

574
  return params;
418✔
575
}
×
576

577
InputParameters
578
LBSProblem::GetBoundaryOptionsBlock()
722✔
579
{
580
  InputParameters params;
722✔
581

582
  params.SetGeneralDescription("Set options for boundary conditions.");
1,444✔
583
  params.AddRequiredParameter<std::string>("name",
1,444✔
584
                                           "Boundary name that identifies the specific boundary");
585
  params.AddRequiredParameter<std::string>("type", "Boundary type specification.");
1,444✔
586
  params.AddOptionalParameterArray<double>("group_strength",
1,444✔
587
                                           {},
588
                                           "Required only if \"type\" is \"isotropic\". An array "
589
                                           "of isotropic strength per group");
590
  params.AddOptionalParameter(
1,444✔
591
    "function_name", "", "Text name of the function to be called for this boundary condition.");
592
  params.ConstrainParameterRange("type",
2,166✔
593
                                 AllowableRangeList::New({"vacuum", "isotropic", "reflecting"}));
722✔
594

595
  return params;
722✔
596
}
×
597

598
InputParameters
599
LBSProblem::GetXSMapEntryBlock()
609✔
600
{
601
  InputParameters params;
609✔
602
  params.SetGeneralDescription("Set the cross-section map for the solver.");
1,218✔
603
  params.AddRequiredParameterArray("block_ids", "Mesh block IDs");
1,218✔
604
  params.AddRequiredParameter<std::shared_ptr<MultiGroupXS>>("xs", "Cross-section object");
1,218✔
605
  return params;
609✔
606
}
×
607

608
void
609
LBSProblem::SetOptions(const InputParameters& input)
208✔
610
{
611
  auto params = LBSProblem::GetOptionsBlock();
208✔
612
  params.AssignParameters(input);
208✔
613

614
  // Handle order insensitive options
615
  for (size_t p = 0; p < params.GetNumParameters(); ++p)
4,576✔
616
  {
617
    const auto& spec = params.GetParam(p);
4,368✔
618

619
    if (spec.GetName() == "max_mpi_message_size")
4,368✔
620
      options_.max_mpi_message_size = spec.GetValue<int>();
208✔
621

622
    else if (spec.GetName() == "restart_writes_enabled")
4,160✔
623
      options_.restart_writes_enabled = spec.GetValue<bool>();
208✔
624

625
    else if (spec.GetName() == "write_delayed_psi_to_restart")
3,952✔
626
      options_.write_delayed_psi_to_restart = spec.GetValue<bool>();
208✔
627

628
    else if (spec.GetName() == "read_restart_path")
3,744✔
629
    {
630
      options_.read_restart_path = spec.GetValue<std::string>();
208✔
631
      if (not options_.read_restart_path.empty())
208✔
632
        options_.read_restart_path += std::to_string(opensn::mpi_comm.rank()) + ".restart.h5";
36✔
633
    }
634

635
    else if (spec.GetName() == "write_restart_path")
3,536✔
636
    {
637
      options_.write_restart_path = spec.GetValue<std::string>();
208✔
638
      if (not options_.write_restart_path.empty())
208✔
639
        options_.write_restart_path += std::to_string(opensn::mpi_comm.rank()) + ".restart.h5";
×
640
    }
641

642
    else if (spec.GetName() == "write_restart_time_interval")
3,328✔
643
      options_.write_restart_time_interval = std::chrono::seconds(spec.GetValue<int>());
208✔
644

645
    else if (spec.GetName() == "use_precursors")
3,120✔
646
      options_.use_precursors = spec.GetValue<bool>();
208✔
647

648
    else if (spec.GetName() == "use_source_moments")
2,912✔
649
      options_.use_src_moments = spec.GetValue<bool>();
208✔
650

651
    else if (spec.GetName() == "save_angular_flux")
2,704✔
652
      options_.save_angular_flux = spec.GetValue<bool>();
208✔
653

654
    else if (spec.GetName() == "verbose_inner_iterations")
2,496✔
655
      options_.verbose_inner_iterations = spec.GetValue<bool>();
208✔
656

657
    else if (spec.GetName() == "max_ags_iterations")
2,288✔
658
      options_.max_ags_iterations = spec.GetValue<int>();
208✔
659

660
    else if (spec.GetName() == "ags_tolerance")
2,080✔
661
      options_.ags_tolerance = spec.GetValue<double>();
208✔
662

663
    else if (spec.GetName() == "ags_convergence_check")
1,872✔
664
    {
665
      auto check = spec.GetValue<std::string>();
208✔
666
      if (check == "pointwise")
208✔
667
        options_.ags_pointwise_convergence = true;
×
668
    }
208✔
669

670
    else if (spec.GetName() == "verbose_ags_iterations")
1,664✔
671
      options_.verbose_ags_iterations = spec.GetValue<bool>();
208✔
672

673
    else if (spec.GetName() == "verbose_outer_iterations")
1,456✔
674
      options_.verbose_outer_iterations = spec.GetValue<bool>();
208✔
675

676
    else if (spec.GetName() == "power_field_function_on")
1,248✔
677
      options_.power_field_function_on = spec.GetValue<bool>();
208✔
678

679
    else if (spec.GetName() == "power_default_kappa")
1,040✔
680
      options_.power_default_kappa = spec.GetValue<double>();
208✔
681

682
    else if (spec.GetName() == "power_normalization")
832✔
683
      options_.power_normalization = spec.GetValue<double>();
208✔
684

685
    else if (spec.GetName() == "field_function_prefix_option")
624✔
686
    {
687
      options_.field_function_prefix_option = spec.GetValue<std::string>();
208✔
688
    }
689

690
    else if (spec.GetName() == "field_function_prefix")
416✔
691
      options_.field_function_prefix = spec.GetValue<std::string>();
208✔
692

693
    else if (spec.GetName() == "adjoint")
208✔
694
      options_.adjoint = spec.GetValue<bool>();
208✔
695

696
  } // for p
697

698
  if (options_.restart_writes_enabled)
208✔
699
  {
700
    // Create restart directory if necessary
701
    auto dir = options_.write_restart_path.parent_path();
×
702
    if (opensn::mpi_comm.rank() == 0)
×
703
    {
704
      if (not std::filesystem::exists(dir))
×
705
      {
706
        if (not std::filesystem::create_directories(dir))
×
707
          throw std::runtime_error(GetName() + ": Failed to create restart directory " +
×
708
                                   dir.string());
×
709
      }
710
      else if (not std::filesystem::is_directory(dir))
×
711
        throw std::runtime_error(GetName() + ": Restart path exists but is not a directory " +
×
712
                                 dir.string());
×
713
    }
714
    opensn::mpi_comm.barrier();
×
715
    UpdateRestartWriteTime();
×
716
  }
×
717
}
208✔
718

719
void
720
LBSProblem::SetBoundaryOptions(const InputParameters& params)
722✔
721
{
722
  const auto boundary_name = params.GetParamValue<std::string>("name");
722✔
723
  const auto bndry_type = params.GetParamValue<std::string>("type");
722✔
724

725
  auto grid = GetGrid();
722✔
726
  const auto bnd_map = grid->GetBoundaryIDMap();
722✔
727
  const auto bnd_name_map = grid->GetBoundaryNameMap();
722✔
728
  auto it = bnd_name_map.find(boundary_name);
722✔
729
  if (it == bnd_name_map.end())
722✔
730
    throw std::runtime_error(std::format("Could not find the specified boundary '{}' - please "
×
731
                                         "check that the 'name' parameter is spelled correctly.",
732
                                         boundary_name));
×
733
  const auto bid = it->second;
722✔
734
  const std::map<std::string, LBSBoundaryType> type_list = {
722✔
735
    {"vacuum", LBSBoundaryType::VACUUM},
722✔
736
    {"isotropic", LBSBoundaryType::ISOTROPIC},
722✔
737
    {"reflecting", LBSBoundaryType::REFLECTING}};
2,888✔
738

739
  const auto type = type_list.at(bndry_type);
722✔
740
  switch (type)
722✔
741
  {
742
    case LBSBoundaryType::VACUUM:
622✔
743
    case LBSBoundaryType::REFLECTING:
622✔
744
    {
622✔
745
      boundary_preferences_[bid] = {type};
622✔
746
      break;
622✔
747
    }
748
    case LBSBoundaryType::ISOTROPIC:
100✔
749
    {
100✔
750
      OpenSnInvalidArgumentIf(not params.Has("group_strength"),
100✔
751
                              "Boundary conditions with type=\"isotropic\" require parameter "
752
                              "\"group_strength\"");
753

754
      params.RequireParameterBlockTypeIs("group_strength", ParameterBlockType::ARRAY);
100✔
755
      const auto group_strength = params.GetParamVectorValue<double>("group_strength");
100✔
756
      boundary_preferences_[bid] = {type, group_strength};
100✔
757
      break;
100✔
758
    }
100✔
759
    case LBSBoundaryType::ARBITRARY:
×
760
    {
×
761
      throw std::runtime_error(GetName() +
×
762
                               ": Arbitrary boundary conditions are not currently supported");
×
763
      break;
722✔
764
    }
765
  }
766
}
2,888✔
767

768
void
769
LBSProblem::Initialize()
352✔
770
{
771
  CALI_CXX_MARK_SCOPE("LBSProblem::Initialize");
352✔
772

773
  PrintSimHeader();
352✔
774
  mpi_comm.barrier();
352✔
775

776
  InitializeSpatialDiscretization();
352✔
777
  InitializeParrays();
352✔
778
  InitializeBoundaries();
352✔
779
  InitializeGPUExtras();
352✔
780
  SetAdjoint(options_.adjoint);
352✔
781

782
  // Initialize point sources
783
  for (auto& point_source : point_sources_)
361✔
784
    point_source->Initialize(*this);
9✔
785

786
  // Initialize volumetric sources
787
  for (auto& volumetric_source : volumetric_sources_)
731✔
788
    volumetric_source->Initialize(*this);
379✔
789
}
352✔
790

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

804
    for (const auto& groupset : groupsets_)
×
805
    {
806
      outstr << "***** Groupset " << groupset.id << " *****\n"
×
807
             << "Groups:\n";
×
808
      const auto& groups = groupset.groups;
809
      constexpr int groups_per_line = 12;
810
      for (size_t i = 0; i < groups.size(); ++i)
×
811
      {
812
        outstr << std::setw(5) << groups[i].id << ' ';
×
813
        if ((i + 1) % groups_per_line == 0)
×
814
          outstr << '\n';
×
815
      }
816
      if (!groups.empty() && groups.size() % groups_per_line != 0)
×
817
        outstr << '\n';
×
818
    }
819

820
    log.Log() << outstr.str() << '\n';
×
821
  }
×
822
}
×
823

824
void
825
LBSProblem::InitializeSources(const InputParameters& params)
352✔
826
{
827
  if (params.Has("volumetric_sources"))
352✔
828
  {
829
    const auto& vol_srcs = params.GetParam("volumetric_sources");
352✔
830
    vol_srcs.RequireBlockTypeIs(ParameterBlockType::ARRAY);
352✔
831
    for (const auto& src : vol_srcs)
731✔
832
      volumetric_sources_.push_back(src.GetValue<std::shared_ptr<VolumetricSource>>());
758✔
833
  }
834

835
  if (params.Has("point_sources"))
352✔
836
  {
837
    const auto& pt_srcs = params.GetParam("point_sources");
352✔
838
    pt_srcs.RequireBlockTypeIs(ParameterBlockType::ARRAY);
352✔
839
    for (const auto& src : pt_srcs)
361✔
840
      point_sources_.push_back(src.GetValue<std::shared_ptr<PointSource>>());
18✔
841
  }
842
}
352✔
843

844
void
845
LBSProblem::InitializeGroupsets(const InputParameters& params)
352✔
846
{
847
  // Initialize groups
848
  if (num_groups_ == 0)
352✔
849
    throw std::invalid_argument(GetName() + ": Number of groups must be > 0");
×
850
  for (size_t g = 0; g < num_groups_; ++g)
21,421✔
851
    groups_.emplace_back(static_cast<int>(g));
21,069✔
852

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

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

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

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

898
void
899
LBSProblem::InitializeMaterials()
364✔
900
{
901
  CALI_CXX_MARK_SCOPE("LBSProblem::InitializeMaterials");
364✔
902

903
  log.Log0Verbose1() << "Initializing Materials";
728✔
904

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

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

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

940
  // Initialize precursor properties
941
  num_precursors_ = 0;
364✔
942
  max_precursors_per_material_ = 0;
364✔
943
  for (const auto& mat_id_xs : block_id_to_xs_map_)
1,097✔
944
  {
945
    const auto& xs = mat_id_xs.second;
733✔
946
    num_precursors_ += xs->GetNumPrecursors();
733✔
947
    if (xs->GetNumPrecursors() > max_precursors_per_material_)
733✔
948
      max_precursors_per_material_ = xs->GetNumPrecursors();
8✔
949
  }
950

951
  // if no precursors, turn off precursors
952
  if (num_precursors_ == 0)
364✔
953
    options_.use_precursors = false;
356✔
954

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

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

977
  mpi_comm.barrier();
364✔
978
}
364✔
979

980
void
981
LBSProblem::InitializeSpatialDiscretization()
344✔
982
{
983
  CALI_CXX_MARK_SCOPE("LBSProblem::InitializeSpatialDiscretization");
344✔
984

985
  log.Log() << "Initializing spatial discretization.\n";
688✔
986
  discretization_ = PieceWiseLinearDiscontinuous::New(grid_);
344✔
987

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

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

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

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

1002
  for (const auto& cell : grid_->local_cells)
381,084✔
1003
    unit_cell_matrices_[cell.local_id] =
380,732✔
1004
      ComputeUnitCellIntegrals(sdm, cell, grid_->GetCoordinateSystem());
380,732✔
1005

1006
  const auto ghost_ids = grid_->cells.GetGhostGlobalIDs();
352✔
1007
  for (auto ghost_id : ghost_ids)
70,286✔
1008
    unit_ghost_cell_matrices_[ghost_id] =
69,934✔
1009
      ComputeUnitCellIntegrals(sdm, grid_->cells[ghost_id], grid_->GetCoordinateSystem());
139,868✔
1010

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

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

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

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

1029
  log.Log() << "Initializing parallel arrays."
704✔
1030
            << " G=" << num_groups_ << " M=" << num_moments_ << std::endl;
352✔
1031

1032
  // Initialize unknown
1033
  // structure
1034
  flux_moments_uk_man_.unknowns.clear();
352✔
1035
  for (size_t m = 0; m < num_moments_; ++m)
1,266✔
1036
  {
1037
    flux_moments_uk_man_.AddUnknown(UnknownType::VECTOR_N, groups_.size());
914✔
1038
    flux_moments_uk_man_.unknowns.back().name = "m" + std::to_string(m);
914✔
1039
  }
1040

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

1045
  // Compute num of unknowns
1046
  size_t num_grps = groups_.size();
352✔
1047
  size_t local_unknown_count = local_node_count_ * num_grps * num_moments_;
352✔
1048

1049
  log.LogAllVerbose1() << "LBS Number of phi unknowns: " << local_unknown_count;
704✔
1050

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

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

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

1076
  const Vector3 ihat(1.0, 0.0, 0.0);
352✔
1077
  const Vector3 jhat(0.0, 1.0, 0.0);
352✔
1078
  const Vector3 khat(0.0, 0.0, 1.0);
352✔
1079

1080
  min_cell_dof_count_ = std::numeric_limits<unsigned int>::max();
352✔
1081
  max_cell_dof_count_ = 0;
352✔
1082
  cell_transport_views_.clear();
352✔
1083
  cell_transport_views_.reserve(grid_->local_cells.size());
352✔
1084
  for (auto& cell : grid_->local_cells)
381,084✔
1085
  {
1086
    size_t num_nodes = discretization_->GetCellNumNodes(cell);
380,732✔
1087

1088
    // compute cell volumes
1089
    double cell_volume = 0.0;
380,732✔
1090
    const auto& IntV_shapeI = unit_cell_matrices_[cell.local_id].intV_shapeI;
380,732✔
1091
    for (size_t i = 0; i < num_nodes; ++i)
3,013,628✔
1092
      cell_volume += IntV_shapeI(i);
2,632,896✔
1093

1094
    size_t cell_phi_address = block_MG_counter;
380,732✔
1095

1096
    const size_t num_faces = cell.faces.size();
380,732✔
1097
    std::vector<bool> face_local_flags(num_faces, true);
380,732✔
1098
    std::vector<int> face_locality(num_faces, opensn::mpi_comm.rank());
380,732✔
1099
    std::vector<const Cell*> neighbor_cell_ptrs(num_faces, nullptr);
380,732✔
1100
    bool cell_on_boundary = false;
380,732✔
1101
    int f = 0;
380,732✔
1102
    for (auto& face : cell.faces)
2,449,812✔
1103
    {
1104
      if (not face.has_neighbor)
2,069,080✔
1105
      {
1106
        cell_on_boundary = true;
76,906✔
1107
        face_local_flags[f] = false;
76,906✔
1108
        face_locality[f] = -1;
76,906✔
1109
      } // if bndry
1110
      else
1111
      {
1112
        const int neighbor_partition = face.GetNeighborPartitionID(grid_.get());
1,992,174✔
1113
        face_local_flags[f] = (neighbor_partition == opensn::mpi_comm.rank());
1,992,174✔
1114
        face_locality[f] = neighbor_partition;
1,992,174✔
1115
        neighbor_cell_ptrs[f] = &grid_->cells[face.neighbor_id];
1,992,174✔
1116
      }
1117

1118
      ++f;
2,069,080✔
1119
    } // for f
1120

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

1137
  // Populate grid nodal mappings
1138
  // This is used in the Flux Data Structures (FLUDS)
1139
  grid_nodal_mappings_.clear();
352✔
1140
  grid_nodal_mappings_.reserve(grid_->local_cells.size());
352✔
1141
  for (auto& cell : grid_->local_cells)
381,084✔
1142
  {
1143
    CellFaceNodalMapping cell_nodal_mapping;
380,732✔
1144
    cell_nodal_mapping.reserve(cell.faces.size());
380,732✔
1145

1146
    for (auto& face : cell.faces)
2,449,812✔
1147
    {
1148
      std::vector<short> face_node_mapping;
2,069,080✔
1149
      std::vector<short> cell_node_mapping;
2,069,080✔
1150
      int adj_face_idx = -1;
2,069,080✔
1151

1152
      if (face.has_neighbor)
2,069,080✔
1153
      {
1154
        grid_->FindAssociatedVertices(face, face_node_mapping);
1,992,174✔
1155
        grid_->FindAssociatedCellVertices(face, cell_node_mapping);
1,992,174✔
1156
        adj_face_idx = face.GetNeighborAdjacentFaceIndex(grid_.get());
1,992,174✔
1157
      }
1158

1159
      cell_nodal_mapping.emplace_back(adj_face_idx, face_node_mapping, cell_node_mapping);
2,069,080✔
1160
    } // for f
2,069,080✔
1161

1162
    grid_nodal_mappings_.push_back(cell_nodal_mapping);
380,732✔
1163
  } // for local cell
380,732✔
1164

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

1168
  // Initialize Field Functions
1169
  InitializeFieldFunctions();
352✔
1170

1171
  opensn::mpi_comm.barrier();
352✔
1172
  log.Log() << "Done with parallel arrays." << std::endl;
704✔
1173
}
352✔
1174

1175
void
1176
LBSProblem::InitializeFieldFunctions()
352✔
1177
{
1178
  CALI_CXX_MARK_SCOPE("LBSProblem::InitializeFieldFunctions");
352✔
1179

1180
  if (not field_functions_.empty())
352✔
1181
    return;
×
1182

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

1186
  for (size_t g = 0; g < groups_.size(); ++g)
21,421✔
1187
  {
1188
    for (size_t m = 0; m < num_moments_; ++m)
97,096✔
1189
    {
1190
      std::string prefix;
76,027✔
1191
      if (options_.field_function_prefix_option == "prefix")
76,027✔
1192
      {
1193
        prefix = options_.field_function_prefix;
76,027✔
1194
        if (not prefix.empty())
76,027✔
1195
          prefix += "_";
1✔
1196
      }
1197
      if (options_.field_function_prefix_option == "solver_name")
76,027✔
1198
        prefix = GetName() + "_";
×
1199

1200
      char buff[100];
76,027✔
1201
      snprintf(
76,027✔
1202
        buff, 99, "%sphi_g%03d_m%02d", prefix.c_str(), static_cast<int>(g), static_cast<int>(m));
1203
      const std::string name = std::string(buff);
76,027✔
1204

1205
      auto group_ff = std::make_shared<FieldFunctionGridBased>(
76,027✔
1206
        name, discretization_, Unknown(UnknownType::SCALAR));
76,027✔
1207

1208
      field_function_stack.push_back(group_ff);
152,054✔
1209
      field_functions_.push_back(group_ff);
76,027✔
1210

1211
      phi_field_functions_local_map_[{g, m}] = field_functions_.size() - 1;
76,027✔
1212
    } // for m
76,027✔
1213
  } // for g
1214

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

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

1231
    field_function_stack.push_back(power_ff);
8✔
1232
    field_functions_.push_back(power_ff);
4✔
1233

1234
    power_gen_fieldfunc_local_handle_ = field_functions_.size() - 1;
4✔
1235
  }
4✔
1236
}
352✔
1237

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

1243
  log.Log() << "Initializing WGS and AGS solvers";
704✔
1244

1245
  InitializeWGSSolvers();
352✔
1246

1247
  ags_solver_ = std::make_shared<AGSLinearSolver>(*this, wgs_solvers_);
352✔
1248
  if (groupsets_.size() == 1)
352✔
1249
  {
1250
    ags_solver_->SetMaxIterations(1);
297✔
1251
    ags_solver_->SetVerbosity(false);
297✔
1252
  }
1253
  else
1254
  {
1255
    ags_solver_->SetMaxIterations(options_.max_ags_iterations);
55✔
1256
    ags_solver_->SetVerbosity(options_.verbose_ags_iterations);
55✔
1257
  }
1258
  ags_solver_->SetTolerance(options_.ags_tolerance);
352✔
1259
}
352✔
1260

1261
#ifndef __OPENSN_USE_CUDA__
1262
void
1263
LBSProblem::InitializeGPUExtras()
352✔
1264
{
1265
}
352✔
1266

1267
void
1268
LBSProblem::ResetGPUCarriers()
348✔
1269
{
1270
}
348✔
1271

1272
void
1273
LBSProblem::CheckCapableDevices()
×
1274
{
1275
}
×
1276
#endif // __OPENSN_USE_CUDA__
1277

1278
std::vector<double>
1279
LBSProblem::MakeSourceMomentsFromPhi()
4✔
1280
{
1281
  CALI_CXX_MARK_SCOPE("LBSProblem::MakeSourceMomentsFromPhi");
4✔
1282

1283
  size_t num_local_dofs = discretization_->GetNumLocalDOFs(flux_moments_uk_man_);
4✔
1284

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

1295
  return source_moments;
4✔
1296
}
4✔
1297

1298
void
1299
LBSProblem::UpdateFieldFunctions()
1,156✔
1300
{
1301
  CALI_CXX_MARK_SCOPE("LBSProblem::UpdateFieldFunctions");
1,156✔
1302

1303
  const auto& sdm = *discretization_;
1,156✔
1304
  const auto& phi_uk_man = flux_moments_uk_man_;
1,156✔
1305

1306
  // Update flux moments
1307
  for (const auto& [g_and_m, ff_index] : phi_field_functions_local_map_)
78,391✔
1308
  {
1309
    const size_t g = g_and_m.first;
77,235✔
1310
    const size_t m = g_and_m.second;
77,235✔
1311

1312
    std::vector<double> data_vector_local(local_node_count_, 0.0);
77,235✔
1313

1314
    for (const auto& cell : grid_->local_cells)
20,408,758✔
1315
    {
1316
      const auto& cell_mapping = sdm.GetCellMapping(cell);
20,331,523✔
1317
      const size_t num_nodes = cell_mapping.GetNumNodes();
20,331,523✔
1318

1319
      for (size_t i = 0; i < num_nodes; ++i)
139,694,695✔
1320
      {
1321
        const auto imapA = sdm.MapDOFLocal(cell, i, phi_uk_man, m, g);
119,363,172✔
1322
        const auto imapB = sdm.MapDOFLocal(cell, i);
119,363,172✔
1323

1324
        data_vector_local[imapB] = phi_new_local_[imapA];
119,363,172✔
1325
      } // for node
1326
    } // for cell
1327

1328
    auto& ff_ptr = field_functions_.at(ff_index);
77,235✔
1329
    ff_ptr->UpdateFieldVector(data_vector_local);
77,235✔
1330
  }
77,235✔
1331

1332
  // Update power generation and scalar flux
1333
  if (options_.power_field_function_on)
1,156✔
1334
  {
1335
    std::vector<double> data_vector_power_local(local_node_count_, 0.0);
4✔
1336

1337
    double local_total_power = 0.0;
4✔
1338
    for (const auto& cell : grid_->local_cells)
83,268✔
1339
    {
1340
      const auto& cell_mapping = sdm.GetCellMapping(cell);
83,264✔
1341
      const size_t num_nodes = cell_mapping.GetNumNodes();
83,264✔
1342

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

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

1347
      if (not xs->IsFissionable())
83,264✔
1348
        continue;
56,360✔
1349

1350
      for (size_t i = 0; i < num_nodes; ++i)
134,520✔
1351
      {
1352
        const auto imapA = sdm.MapDOFLocal(cell, i);
107,616✔
1353
        const auto imapB = sdm.MapDOFLocal(cell, i, phi_uk_man, 0, 0);
107,616✔
1354

1355
        double nodal_power = 0.0;
1356
        for (size_t g = 0; g < groups_.size(); ++g)
860,928✔
1357
        {
1358
          const double sigma_fg = xs->GetSigmaFission()[g];
753,312✔
1359
          // const double kappa_g = xs->Kappa()[g];
1360
          const double kappa_g = options_.power_default_kappa;
753,312✔
1361

1362
          nodal_power += kappa_g * sigma_fg * phi_new_local_[imapB + g];
753,312✔
1363
        } // for g
1364

1365
        data_vector_power_local[imapA] = nodal_power;
107,616✔
1366
        local_total_power += nodal_power * Vi(i);
107,616✔
1367
      } // for node
1368
    } // for cell
1369

1370
    double scale_factor = 1.0;
4✔
1371
    if (options_.power_normalization > 0.0)
4✔
1372
    {
1373
      double global_total_power = 0.0;
4✔
1374
      mpi_comm.all_reduce(local_total_power, global_total_power, mpi::op::sum<double>());
4✔
1375
      scale_factor = options_.power_normalization / global_total_power;
4✔
1376
      Scale(data_vector_power_local, scale_factor);
4✔
1377
    }
1378

1379
    const size_t ff_index = power_gen_fieldfunc_local_handle_;
4✔
1380

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

1384
    // scale scalar flux if neccessary
1385
    if (scale_factor != 1.0)
4✔
1386
    {
1387
      for (size_t g = 0; g < groups_.size(); ++g)
32✔
1388
      {
1389
        const size_t phi_ff_index = phi_field_functions_local_map_.at({g, size_t{0}});
28✔
1390
        auto& phi_ff_ptr = field_functions_.at(phi_ff_index);
28✔
1391
        const auto& phi_vec = phi_ff_ptr->GetLocalFieldVector();
28✔
1392
        std::vector<double> phi_scaled(phi_vec.begin(), phi_vec.end());
28✔
1393
        Scale(phi_scaled, scale_factor);
28✔
1394
        phi_ff_ptr->UpdateFieldVector(phi_scaled);
28✔
1395
      }
28✔
1396
    }
1397
  } // if power enabled
4✔
1398
}
1,156✔
1399

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

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

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

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

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

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

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

1447
LBSProblem::~LBSProblem()
348✔
1448
{
1449
  ResetGPUCarriers();
1450
}
1,740✔
1451

348✔
1452
void
1453
LBSProblem::SetAdjoint(bool adjoint)
364✔
1454
{
1455
  if (adjoint and time_dependent_)
364✔
NEW
1456
    throw std::invalid_argument(GetName() + ": Time-dependent adjoint problems are not supported.");
×
1457

1458
  if (adjoint != options_.adjoint)
364✔
1459
  {
1460
    options_.adjoint = adjoint;
12✔
1461

1462
    // If a discretization exists, the solver has already been initialized.
1463
    // Reinitialize the materials to obtain the appropriate xs and clear the
1464
    // sources to prepare for defining the adjoint problem
1465
    if (discretization_)
12✔
1466
    {
1467
      // The materials are reinitialized here to ensure that the proper cross sections
1468
      // are available to the solver. Because an adjoint solve requires volumetric or
1469
      // point sources, the material-based sources are not set within the initialize routine.
1470
      InitializeMaterials();
12✔
1471

1472
      // Forward and adjoint sources are fundamentally different, so any existing sources
1473
      // should be cleared and reset through options upon changing modes.
1474
      point_sources_.clear();
12✔
1475
      volumetric_sources_.clear();
12✔
1476
      boundary_preferences_.clear();
12✔
1477

1478
      // Set all solutions to zero.
1479
      phi_old_local_.assign(phi_old_local_.size(), 0.0);
12✔
1480
      phi_new_local_.assign(phi_new_local_.size(), 0.0);
12✔
1481
      ZeroSolutions();
12✔
1482
      precursor_new_local_.assign(precursor_new_local_.size(), 0.0);
12✔
1483
    }
1484
  }
1485
}
364✔
1486

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