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

Open-Sn / opensn / 22886555391

09 Mar 2026 10:22PM UTC coverage: 74.39% (+0.02%) from 74.368%
22886555391

push

github

web-flow
Merge pull request #966 from wdhawkins/wgs_context_fixes

Minor sweep context fixes

19 of 19 new or added lines in 2 files covered. (100.0%)

101 existing lines in 3 files now uncovered.

20144 of 27079 relevant lines covered (74.39%)

66852781.33 hits per line

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

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

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

31
namespace opensn
32
{
33

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

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

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

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

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

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

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

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

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

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

66
  return params;
615✔
UNCOV
67
}
×
68

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

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

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

102
  InitializeGroupsets(params);
613✔
103
  InitializeSources(params);
613✔
104
  InitializeXSmapAndDensities(params);
613✔
105
  InitializeMaterials();
613✔
106
}
633✔
107

108
const LBSOptions&
109
LBSProblem::GetOptions() const
691,532,922✔
110
{
111
  return options_;
691,532,922✔
112
}
113

114
double
115
LBSProblem::GetTime() const
492,252✔
116
{
117
  return time_;
492,252✔
118
}
119

120
void
121
LBSProblem::SetTime(double time)
5,988✔
122
{
123
  time_ = time;
5,988✔
124
}
5,988✔
125

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

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

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

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

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

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

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

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

177
unsigned int
178
LBSProblem::GetNumMoments() const
602,386✔
179
{
180
  return num_moments_;
602,386✔
181
}
182

183
unsigned int
184
LBSProblem::GetMaxCellDOFCount() const
840✔
185
{
186
  return max_cell_dof_count_;
840✔
187
}
188

189
unsigned int
190
LBSProblem::GetMinCellDOFCount() const
840✔
191
{
192
  return min_cell_dof_count_;
840✔
193
}
194

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

201
unsigned int
202
LBSProblem::GetNumGroups() const
961,836✔
203
{
204
  return num_groups_;
961,836✔
205
}
206

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

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

219
unsigned int
220
LBSProblem::GetMaxPrecursorsPerMaterial() const
11,056✔
221
{
222
  return max_precursors_per_material_;
11,056✔
223
}
224

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

231
LBSGroupset&
232
LBSProblem::GetGroupset(size_t groupset_id)
24,844,378✔
233
{
234
  return groupsets_.at(groupset_id);
24,844,378✔
235
}
236

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

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

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

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

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

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

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

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

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

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

304
std::shared_ptr<MeshContinuum>
305
LBSProblem::GetGrid() const
861,963✔
306
{
307
  return grid_;
861,963✔
308
}
309

310
const SpatialDiscretization&
311
LBSProblem::GetSpatialDiscretization() const
273,484✔
312
{
313
  return *discretization_;
273,484✔
314
}
315

316
const std::vector<UnitCellMatrices>&
317
LBSProblem::GetUnitCellMatrices() const
22,315✔
318
{
319
  return unit_cell_matrices_;
22,315✔
320
}
321

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

328
std::vector<CellLBSView>&
329
LBSProblem::GetCellTransportViews()
343,561✔
330
{
331
  return cell_transport_views_;
343,561✔
332
}
333

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

340
const UnknownManager&
341
LBSProblem::GetUnknownManager() const
27,068✔
342
{
343
  return flux_moments_uk_man_;
27,068✔
344
}
345

346
size_t
347
LBSProblem::GetLocalNodeCount() const
3,336✔
348
{
349
  return local_node_count_;
3,336✔
350
}
351

352
size_t
353
LBSProblem::GetGlobalNodeCount() const
2,732✔
354
{
355
  return global_node_count_;
2,732✔
356
}
357

358
std::vector<double>&
359
LBSProblem::GetQMomentsLocal()
249,510✔
360
{
361
  return q_moments_local_;
249,510✔
362
}
363

364
const std::vector<double>&
UNCOV
365
LBSProblem::GetQMomentsLocal() const
×
366
{
UNCOV
367
  return q_moments_local_;
×
368
}
369

370
std::vector<double>&
371
LBSProblem::GetExtSrcMomentsLocal()
4✔
372
{
373
  return ext_src_moments_local_;
4✔
374
}
375

376
const std::vector<double>&
377
LBSProblem::GetExtSrcMomentsLocal() const
245,362✔
378
{
379
  return ext_src_moments_local_;
245,362✔
380
}
381

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

391
  if (ext_src_moments_local_.empty())
4✔
392
  {
393
    ext_src_moments_local_ = ext_src_moments;
4✔
394
    return;
4✔
395
  }
396

UNCOV
397
  assert(ext_src_moments.size() == ext_src_moments_local_.size() &&
×
398
         "SetExtSrcMomentsFrom size mismatch.");
UNCOV
399
  ext_src_moments_local_ = ext_src_moments;
×
400
}
401

402
std::vector<double>&
403
LBSProblem::GetPhiOldLocal()
494,218✔
404
{
405
  return phi_old_local_;
494,218✔
406
}
407

408
const std::vector<double>&
UNCOV
409
LBSProblem::GetPhiOldLocal() const
×
410
{
UNCOV
411
  return phi_old_local_;
×
412
}
413

414
std::vector<double>&
415
LBSProblem::GetPhiNewLocal()
285,727✔
416
{
417
  return phi_new_local_;
285,727✔
418
}
419

420
const std::vector<double>&
UNCOV
421
LBSProblem::GetPhiNewLocal() const
×
422
{
UNCOV
423
  return phi_new_local_;
×
424
}
425

426
std::vector<double>&
427
LBSProblem::GetPrecursorsNewLocal()
460✔
428
{
429
  return precursor_new_local_;
460✔
430
}
431

432
const std::vector<double>&
UNCOV
433
LBSProblem::GetPrecursorsNewLocal() const
×
434
{
UNCOV
435
  return precursor_new_local_;
×
436
}
437

438
std::vector<double>&
439
LBSProblem::GetDensitiesLocal()
1,090✔
440
{
441
  return densities_local_;
1,090✔
442
}
443

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

450
SetSourceFunction
451
LBSProblem::GetActiveSetSourceFunction() const
4,417✔
452
{
453
  return active_set_source_function_;
4,417✔
454
}
455

456
void
457
LBSProblem::SetActiveSetSourceFunction(SetSourceFunction source_function)
136✔
458
{
459
  active_set_source_function_ = std::move(source_function);
136✔
460
}
136✔
461

462
std::shared_ptr<AGSLinearSolver>
463
LBSProblem::GetAGSSolver() const
3,120✔
464
{
465
  return ags_solver_;
3,120✔
466
}
467

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

474
size_t
475
LBSProblem::GetNumWGSSolvers() const
5,948✔
476
{
477
  return wgs_solvers_.size();
5,948✔
478
}
479

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

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

UNCOV
498
  return {num_local_phi_dofs, num_global_phi_dofs};
×
499
}
500

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

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

512
  return field_functions_.at(map_it->second);
57,994✔
513
}
514

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

522
  return field_functions_[power_gen_fieldfunc_local_handle_];
2✔
523
}
524

525
InputParameters
526
LBSProblem::GetOptionsBlock()
796✔
527
{
528
  InputParameters params;
796✔
529

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

608
  return params;
796✔
UNCOV
609
}
×
610

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

621
void
622
LBSProblem::SetOptions(const InputParameters& input)
12✔
623
{
624
  ReadOptions(input);
12✔
625
  ValidateOptions();
12✔
626
  ApplyOptions();
12✔
627
}
12✔
628

629
void
630
LBSProblem::ReadOptions(const InputParameters& input)
397✔
631
{
632
  auto params = LBSProblem::GetOptionsBlock();
397✔
633
  params.AssignParameters(input);
397✔
634
  const auto& params_at_assignment = input.GetParametersAtAssignment();
397✔
635
  const auto& specified_params = params_at_assignment.GetNumParameters() > 0
397✔
636
                                   ? params_at_assignment
397✔
637
                                   : static_cast<const ParameterBlock&>(input);
397✔
638

639
  using OptionSetter = std::function<void(const ParameterBlock&)>;
397✔
640
  const std::unordered_map<std::string, OptionSetter> option_setters = {
397✔
641
    {"max_mpi_message_size",
642
     [this](const ParameterBlock& spec) { options_.max_mpi_message_size = spec.GetValue<int>(); }},
×
643
    {"restart_writes_enabled",
644
     [this](const ParameterBlock& spec)
794✔
645
     { options_.restart_writes_enabled = spec.GetValue<bool>(); }},
×
646
    {"write_delayed_psi_to_restart",
647
     [this](const ParameterBlock& spec)
794✔
UNCOV
648
     { options_.write_delayed_psi_to_restart = spec.GetValue<bool>(); }},
×
649
    {"read_restart_path",
650
     [this](const ParameterBlock& spec)
806✔
651
     { options_.read_restart_path = BuildRestartPath(spec.GetValue<std::string>()); }},
24✔
652
    {"write_restart_path",
653
     [this](const ParameterBlock& spec)
794✔
UNCOV
654
     { options_.write_restart_path = BuildRestartPath(spec.GetValue<std::string>()); }},
×
655
    {"write_restart_time_interval",
656
     [this](const ParameterBlock& spec)
794✔
657
     { options_.write_restart_time_interval = std::chrono::seconds(spec.GetValue<int>()); }},
×
658
    {"use_precursors",
659
     [this](const ParameterBlock& spec) { options_.use_precursors = spec.GetValue<bool>(); }},
152✔
660
    {"use_source_moments",
661
     [this](const ParameterBlock& spec) { options_.use_src_moments = spec.GetValue<bool>(); }},
4✔
662
    {"save_angular_flux",
663
     [this](const ParameterBlock& spec) { options_.save_angular_flux = spec.GetValue<bool>(); }},
234✔
664
    {"verbose_inner_iterations",
665
     [this](const ParameterBlock& spec)
1,042✔
666
     { options_.verbose_inner_iterations = spec.GetValue<bool>(); }},
248✔
667
    {"max_ags_iterations",
668
     [this](const ParameterBlock& spec) { options_.max_ags_iterations = spec.GetValue<int>(); }},
50✔
669
    {"ags_tolerance",
670
     [this](const ParameterBlock& spec) { options_.ags_tolerance = spec.GetValue<double>(); }},
8✔
671
    {"ags_convergence_check",
672
     [this](const ParameterBlock& spec)
794✔
UNCOV
673
     { options_.ags_pointwise_convergence = (spec.GetValue<std::string>() == "pointwise"); }},
×
674
    {"verbose_ags_iterations",
675
     [this](const ParameterBlock& spec)
958✔
676
     { options_.verbose_ags_iterations = spec.GetValue<bool>(); }},
164✔
677
    {"verbose_outer_iterations",
678
     [this](const ParameterBlock& spec)
1,018✔
679
     { options_.verbose_outer_iterations = spec.GetValue<bool>(); }},
224✔
680
    {"power_field_function_on",
681
     [this](const ParameterBlock& spec)
799✔
682
     { options_.power_field_function_on = spec.GetValue<bool>(); }},
5✔
683
    {"power_default_kappa",
684
     [this](const ParameterBlock& spec)
807✔
685
     { options_.power_default_kappa = spec.GetValue<double>(); }},
13✔
686
    {"power_normalization",
687
     [this](const ParameterBlock& spec)
807✔
688
     { options_.power_normalization = spec.GetValue<double>(); }},
13✔
689
    {"field_function_prefix_option",
690
     [this](const ParameterBlock& spec)
794✔
UNCOV
691
     { options_.field_function_prefix_option = spec.GetValue<std::string>(); }},
×
692
    {"field_function_prefix",
693
     [this](const ParameterBlock& spec)
795✔
694
     { options_.field_function_prefix = spec.GetValue<std::string>(); }},
1✔
695
    {"adjoint", [this](const ParameterBlock& spec) { options_.adjoint = spec.GetValue<bool>(); }},
20✔
696
  };
9,131✔
697

698
  for (const auto& spec : specified_params.GetParameters())
1,545✔
699
  {
700
    const auto setter_it = option_setters.find(spec.GetName());
2,296✔
701
    if (setter_it != option_setters.end())
1,148✔
702
      setter_it->second(spec);
1,148✔
703
  }
704
}
794✔
705

706
void
707
LBSProblem::ValidateOptions() const
397✔
708
{
709
  OpenSnInvalidArgumentIf(options_.write_restart_time_interval > std::chrono::seconds(0) and
397✔
710
                            not options_.restart_writes_enabled,
711
                          GetName() + ": `write_restart_time_interval>0` requires "
712
                                      "`restart_writes_enabled=true`.");
713

714
  OpenSnInvalidArgumentIf(options_.write_restart_time_interval > std::chrono::seconds(0) and
397✔
715
                            options_.write_restart_time_interval < std::chrono::seconds(30),
716
                          GetName() + ": `write_restart_time_interval` must be 0 (disabled) "
717
                                      "or at least 30 seconds.");
718

719
  OpenSnInvalidArgumentIf(options_.restart_writes_enabled and options_.write_restart_path.empty(),
397✔
720
                          GetName() + ": `restart_writes_enabled=true` requires a non-empty "
721
                                      "`write_restart_path`.");
722

723
  OpenSnInvalidArgumentIf(not options_.field_function_prefix.empty() and
397✔
724
                            options_.field_function_prefix_option != "prefix",
725
                          GetName() + ": non-empty `field_function_prefix` requires "
726
                                      "`field_function_prefix_option=\"prefix\"`.");
727
}
397✔
728

729
std::filesystem::path
730
LBSProblem::BuildRestartPath(const std::string& path_stem)
12✔
731
{
732
  if (path_stem.empty())
12✔
UNCOV
733
    return {};
×
734

735
  auto path = std::filesystem::path(path_stem);
12✔
736
  path += std::to_string(opensn::mpi_comm.rank()) + ".restart.h5";
36✔
737
  return path;
12✔
738
}
12✔
739

740
void
741
LBSProblem::ApplyOptions()
625✔
742
{
743
  if (options_.restart_writes_enabled)
625✔
744
  {
745
    const auto dir = options_.write_restart_path.parent_path();
×
746

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

765
  if (not initialized_)
625✔
766
    return;
767

768
  if (options_.adjoint != applied_adjoint_)
625✔
769
    SetAdjoint(options_.adjoint);
4✔
770

771
  if (options_.save_angular_flux != applied_save_angular_flux_)
625✔
772
    SetSaveAngularFlux(options_.save_angular_flux);
44✔
773
}
774

775
void
776
LBSProblem::BuildRuntime()
613✔
777
{
778
  CALI_CXX_MARK_SCOPE("LBSProblem::BuildRuntime");
613✔
779
  if (initialized_)
613✔
UNCOV
780
    return;
×
781

782
  PrintSimHeader();
613✔
783
  mpi_comm.barrier();
613✔
784

785
  InitializeRuntimeCore();
613✔
786
  ValidateRuntimeModeConfiguration();
613✔
787
  InitializeSources();
613✔
788

789
  initialized_ = true;
613✔
790
  ApplyOptions();
613✔
791
}
613✔
792

793
void
794
LBSProblem::InitializeRuntimeCore()
613✔
795
{
796
  InitializeSpatialDiscretization();
613✔
797
  InitializeParrays();
613✔
798
  InitializeBoundaries();
613✔
799
  InitializeGPUExtras();
613✔
800
}
613✔
801

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

811
void
812
LBSProblem::InitializeSources()
613✔
813
{
814
  // Initialize point sources
815
  for (auto& point_source : point_sources_)
622✔
816
    point_source->Initialize(*this);
9✔
817

818
  // Initialize volumetric sources
819
  for (auto& volumetric_source : volumetric_sources_)
1,104✔
820
    volumetric_source->Initialize(*this);
491✔
821
}
613✔
822

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

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

UNCOV
852
    log.Log() << outstr.str() << '\n';
×
UNCOV
853
  }
×
UNCOV
854
}
×
855

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

867
  if (params.Has("point_sources"))
613✔
868
  {
869
    const auto& pt_srcs = params.GetParam("point_sources");
613✔
870
    pt_srcs.RequireBlockTypeIs(ParameterBlockType::ARRAY);
613✔
871
    for (const auto& src : pt_srcs)
622✔
872
      point_sources_.push_back(src.GetValue<std::shared_ptr<PointSource>>());
18✔
873
  }
874
}
613✔
875

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

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

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

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

922
  // Assign placeholder unit densities
923
  densities_local_.assign(grid_->local_cells.size(), 1.0);
613✔
924
}
613✔
925

926
void
927
LBSProblem::InitializeMaterials()
833✔
928
{
929
  CALI_CXX_MARK_SCOPE("LBSProblem::InitializeMaterials");
833✔
930

931
  log.Log0Verbose1() << "Initializing Materials";
1,666✔
932

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

956
  // Get ready for processing
957
  for (const auto& [blk_id, mat] : block_id_to_xs_map_)
2,055✔
958
  {
959
    mat->SetAdjointMode(options_.adjoint);
1,222✔
960

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

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

978
  // if no precursors, turn off precursors
979
  if (num_precursors_ == 0)
833✔
980
    options_.use_precursors = false;
608✔
981

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

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

1004
  mpi_comm.barrier();
833✔
1005
}
833✔
1006

1007
void
1008
LBSProblem::InitializeSpatialDiscretization()
533✔
1009
{
1010
  CALI_CXX_MARK_SCOPE("LBSProblem::InitializeSpatialDiscretization");
533✔
1011

1012
  OpenSnLogicalErrorIf(not discretization_,
533✔
1013
                       GetName() + ": Missing spatial discretization. Construct the problem "
1014
                                   "through its factory Create(...) entry point.");
1015
  log.Log() << "Initializing spatial discretization metadata.\n";
1,066✔
1016

1017
  ComputeUnitIntegrals();
533✔
1018
}
533✔
1019

1020
void
1021
LBSProblem::ComputeUnitIntegrals()
613✔
1022
{
1023
  CALI_CXX_MARK_SCOPE("LBSProblem::ComputeUnitIntegrals");
613✔
1024

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

1028
  const size_t num_local_cells = grid_->local_cells.size();
613✔
1029
  unit_cell_matrices_.resize(num_local_cells);
613✔
1030

1031
  for (const auto& cell : grid_->local_cells)
573,852✔
1032
    unit_cell_matrices_[cell.local_id] =
573,239✔
1033
      ComputeUnitCellIntegrals(sdm, cell, grid_->GetCoordinateSystem());
573,239✔
1034

1035
  const auto ghost_ids = grid_->cells.GetGhostGlobalIDs();
613✔
1036
  for (auto ghost_id : ghost_ids)
90,882✔
1037
    unit_ghost_cell_matrices_[ghost_id] =
90,269✔
1038
      ComputeUnitCellIntegrals(sdm, grid_->cells[ghost_id], grid_->GetCoordinateSystem());
180,538✔
1039

1040
  // Assessing global unit cell matrix storage
1041
  std::array<size_t, 2> num_local_ucms = {unit_cell_matrices_.size(),
613✔
1042
                                          unit_ghost_cell_matrices_.size()};
613✔
1043
  std::array<size_t, 2> num_global_ucms = {0, 0};
613✔
1044

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

1047
  opensn::mpi_comm.barrier();
613✔
1048
  log.Log() << "Ghost cell unit cell-matrix ratio: "
613✔
1049
            << (double)num_global_ucms[1] * 100 / (double)num_global_ucms[0] << "%";
1,226✔
1050
  log.Log() << "Cell matrices computed.";
1,226✔
1051
}
613✔
1052

1053
void
1054
LBSProblem::InitializeParrays()
613✔
1055
{
1056
  CALI_CXX_MARK_SCOPE("LBSProblem::InitializeParrays");
613✔
1057

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

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

1070
  // Compute local # of dof
1071
  local_node_count_ = discretization_->GetNumLocalNodes();
613✔
1072
  global_node_count_ = discretization_->GetNumGlobalNodes();
613✔
1073

1074
  // Compute num of unknowns
1075
  size_t local_unknown_count = local_node_count_ * num_groups_ * num_moments_;
613✔
1076

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

1079
  // Size local vectors
1080
  q_moments_local_.assign(local_unknown_count, 0.0);
613✔
1081
  phi_old_local_.assign(local_unknown_count, 0.0);
613✔
1082
  phi_new_local_.assign(local_unknown_count, 0.0);
613✔
1083

1084
  // Setup precursor vector
1085
  if (options_.use_precursors)
613✔
1086
  {
1087
    size_t num_precursor_dofs = grid_->local_cells.size() * max_precursors_per_material_;
76✔
1088
    precursor_new_local_.assign(num_precursor_dofs, 0.0);
76✔
1089
  }
1090

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

1104
  const Vector3 ihat(1.0, 0.0, 0.0);
613✔
1105
  const Vector3 jhat(0.0, 1.0, 0.0);
613✔
1106
  const Vector3 khat(0.0, 0.0, 1.0);
613✔
1107

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

1116
    // compute cell volumes
1117
    double cell_volume = 0.0;
573,239✔
1118
    const auto& IntV_shapeI = unit_cell_matrices_[cell.local_id].intV_shapeI;
573,239✔
1119
    for (size_t i = 0; i < num_nodes; ++i)
3,974,947✔
1120
      cell_volume += IntV_shapeI(i);
3,401,708✔
1121

1122
    size_t cell_phi_address = block_MG_counter;
573,239✔
1123

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

1146
      ++f;
2,833,460✔
1147
    } // for f
1148

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

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

1174
    for (auto& face : cell.faces)
3,406,699✔
1175
    {
1176
      std::vector<short> face_node_mapping;
2,833,460✔
1177
      std::vector<short> cell_node_mapping;
2,833,460✔
1178
      int adj_face_idx = -1;
2,833,460✔
1179

1180
      if (face.has_neighbor)
2,833,460✔
1181
      {
1182
        grid_->FindAssociatedVertices(face, face_node_mapping);
2,741,174✔
1183
        grid_->FindAssociatedCellVertices(face, cell_node_mapping);
2,741,174✔
1184
        adj_face_idx = face.GetNeighborAdjacentFaceIndex(grid_.get());
2,741,174✔
1185
      }
1186

1187
      cell_nodal_mapping.emplace_back(adj_face_idx, face_node_mapping, cell_node_mapping);
2,833,460✔
1188
    } // for f
2,833,460✔
1189

1190
    grid_nodal_mappings_.push_back(cell_nodal_mapping);
573,239✔
1191
  } // for local cell
573,239✔
1192

1193
  // Get grid localized communicator set
1194
  grid_local_comm_set_ = grid_->MakeMPILocalCommunicatorSet();
613✔
1195

1196
  // Initialize Field Functions
1197
  InitializeFieldFunctions();
613✔
1198

1199
  opensn::mpi_comm.barrier();
613✔
1200
  log.Log() << "Done with parallel arrays." << std::endl;
1,226✔
1201
}
613✔
1202

1203
void
1204
LBSProblem::InitializeFieldFunctions()
613✔
1205
{
1206
  CALI_CXX_MARK_SCOPE("LBSProblem::InitializeFieldFunctions");
613✔
1207

1208
  if (not field_functions_.empty())
613✔
UNCOV
1209
    return;
×
1210

1211
  // Initialize Field Functions for flux moments
1212
  phi_field_functions_local_map_.clear();
613✔
1213

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

1228
      std::ostringstream oss;
78,044✔
1229
      oss << prefix << "phi_g" << std::setw(3) << std::setfill('0') << static_cast<int>(g) << "_m"
78,044✔
1230
          << std::setw(2) << std::setfill('0') << static_cast<int>(m);
78,044✔
1231
      const std::string name = oss.str();
78,044✔
1232

1233
      auto group_ff = std::make_shared<FieldFunctionGridBased>(
78,044✔
1234
        name, discretization_, Unknown(UnknownType::SCALAR));
78,044✔
1235

1236
      field_function_stack.push_back(group_ff);
156,088✔
1237
      field_functions_.push_back(group_ff);
78,044✔
1238

1239
      phi_field_functions_local_map_[{g, m}] = field_functions_.size() - 1;
78,044✔
1240
    } // for m
78,044✔
1241
  } // for g
1242

1243
  // Initialize power generation field function
1244
  if (options_.power_field_function_on)
613✔
1245
  {
1246
    std::string prefix;
5✔
1247
    if (options_.field_function_prefix_option == "prefix")
5✔
1248
    {
1249
      prefix = options_.field_function_prefix;
5✔
1250
      if (not prefix.empty())
5✔
UNCOV
1251
        prefix += "_";
×
1252
    }
1253
    if (options_.field_function_prefix_option == "solver_name")
5✔
UNCOV
1254
      prefix = GetName() + "_";
×
1255

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

1259
    field_function_stack.push_back(power_ff);
10✔
1260
    field_functions_.push_back(power_ff);
5✔
1261

1262
    power_gen_fieldfunc_local_handle_ = field_functions_.size() - 1;
5✔
1263
  }
5✔
1264
}
613✔
1265

1266
void
1267
LBSProblem::InitializeSolverSchemes()
781✔
1268
{
1269
  CALI_CXX_MARK_SCOPE("LBSProblem::InitializeSolverSchemes");
781✔
1270
  InitializeWGSSolvers();
781✔
1271

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

1286
#ifndef __OPENSN_WITH_GPU__
1287
void
1288
LBSProblem::InitializeGPUExtras()
809✔
1289
{
1290
}
809✔
1291

1292
void
1293
LBSProblem::ResetGPUCarriers()
797✔
1294
{
1295
}
797✔
1296

1297
void
UNCOV
1298
LBSProblem::CheckCapableDevices()
×
1299
{
UNCOV
1300
}
×
1301
#endif // __OPENSN_WITH_GPU__
1302

1303
std::vector<double>
1304
LBSProblem::MakeSourceMomentsFromPhi()
4✔
1305
{
1306
  CALI_CXX_MARK_SCOPE("LBSProblem::MakeSourceMomentsFromPhi");
4✔
1307

1308
  size_t num_local_dofs = discretization_->GetNumLocalDOFs(flux_moments_uk_man_);
4✔
1309

1310
  std::vector<double> source_moments(num_local_dofs, 0.0);
4✔
1311
  for (auto& groupset : groupsets_)
8✔
1312
  {
1313
    active_set_source_function_(groupset,
4✔
1314
                                source_moments,
1315
                                phi_new_local_,
4✔
1316
                                APPLY_AGS_SCATTER_SOURCES | APPLY_WGS_SCATTER_SOURCES |
1317
                                  APPLY_AGS_FISSION_SOURCES | APPLY_WGS_FISSION_SOURCES);
4✔
1318
  }
1319

1320
  return source_moments;
4✔
1321
}
4✔
1322

1323
void
1324
LBSProblem::UpdateFieldFunctions()
2,957✔
1325
{
1326
  CALI_CXX_MARK_SCOPE("LBSProblem::UpdateFieldFunctions");
2,957✔
1327

1328
  const auto& sdm = *discretization_;
2,957✔
1329
  const auto& phi_uk_man = flux_moments_uk_man_;
2,957✔
1330

1331
  // Update flux moments
1332
  for (const auto& [g_and_m, ff_index] : phi_field_functions_local_map_)
88,217✔
1333
  {
1334
    const auto g = g_and_m.first;
85,260✔
1335
    const auto m = g_and_m.second;
85,260✔
1336

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

1339
    for (const auto& cell : grid_->local_cells)
24,454,199✔
1340
    {
1341
      const auto& cell_mapping = sdm.GetCellMapping(cell);
24,368,939✔
1342
      const size_t num_nodes = cell_mapping.GetNumNodes();
24,368,939✔
1343

1344
      for (size_t i = 0; i < num_nodes; ++i)
164,690,755✔
1345
      {
1346
        const auto imapA = sdm.MapDOFLocal(cell, i, phi_uk_man, m, g);
140,321,816✔
1347
        const auto imapB = sdm.MapDOFLocal(cell, i);
140,321,816✔
1348

1349
        data_vector_local[imapB] = phi_new_local_[imapA];
140,321,816✔
1350
      } // for node
1351
    } // for cell
1352

1353
    auto& ff_ptr = field_functions_.at(ff_index);
85,260✔
1354
    ff_ptr->UpdateFieldVector(data_vector_local);
85,260✔
1355
  }
85,260✔
1356

1357
  // Update power generation and scalar flux
1358
  if (options_.power_field_function_on)
2,957✔
1359
  {
1360
    std::vector<double> data_vector_power_local(local_node_count_, 0.0);
5✔
1361

1362
    double local_total_power = 0.0;
5✔
1363
    for (const auto& cell : grid_->local_cells)
83,319✔
1364
    {
1365
      const auto& cell_mapping = sdm.GetCellMapping(cell);
83,314✔
1366
      const size_t num_nodes = cell_mapping.GetNumNodes();
83,314✔
1367

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

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

1372
      if (not xs->IsFissionable())
83,314✔
1373
        continue;
56,360✔
1374

1375
      for (size_t i = 0; i < num_nodes; ++i)
134,670✔
1376
      {
1377
        const auto imapA = sdm.MapDOFLocal(cell, i);
107,716✔
1378
        const auto imapB = sdm.MapDOFLocal(cell, i, phi_uk_man, 0, 0);
107,716✔
1379

1380
        double nodal_power = 0.0;
1381
        for (unsigned int g = 0; g < num_groups_; ++g)
861,128✔
1382
        {
1383
          const double sigma_fg = xs->GetSigmaFission()[g];
753,412✔
1384
          // const double kappa_g = xs->Kappa()[g];
1385
          const double kappa_g = options_.power_default_kappa;
753,412✔
1386

1387
          nodal_power += kappa_g * sigma_fg * phi_new_local_[imapB + g];
753,412✔
1388
        } // for g
1389

1390
        data_vector_power_local[imapA] = nodal_power;
107,716✔
1391
        local_total_power += nodal_power * Vi(i);
107,716✔
1392
      } // for node
1393
    } // for cell
1394

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

1407
    const size_t ff_index = power_gen_fieldfunc_local_handle_;
5✔
1408

1409
    auto& ff_ptr = field_functions_.at(ff_index);
5✔
1410
    ff_ptr->UpdateFieldVector(data_vector_power_local);
5✔
1411

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

1428
void
UNCOV
1429
LBSProblem::SetPhiFromFieldFunctions(PhiSTLOption which_phi,
×
1430
                                     const std::vector<unsigned int>& m_indices,
1431
                                     const std::vector<unsigned int>& g_indices)
1432
{
1433
  CALI_CXX_MARK_SCOPE("LBSProblem::SetPhiFromFieldFunctions");
×
1434

1435
  std::vector<unsigned int> m_ids_to_copy = m_indices;
×
1436
  std::vector<unsigned int> g_ids_to_copy = g_indices;
×
1437
  if (m_indices.empty())
×
UNCOV
1438
    for (unsigned int m = 0; m < num_moments_; ++m)
×
1439
      m_ids_to_copy.push_back(m);
×
1440
  if (g_ids_to_copy.empty())
×
UNCOV
1441
    for (unsigned int g = 0; g < num_groups_; ++g)
×
1442
      g_ids_to_copy.push_back(g);
×
1443

1444
  const auto& sdm = *discretization_;
×
UNCOV
1445
  const auto& phi_uk_man = flux_moments_uk_man_;
×
1446

1447
  for (const auto m : m_ids_to_copy)
×
1448
  {
UNCOV
1449
    for (const auto g : g_ids_to_copy)
×
1450
    {
UNCOV
1451
      const size_t ff_index = phi_field_functions_local_map_.at({g, m});
×
1452
      const auto& ff_ptr = field_functions_.at(ff_index);
×
1453
      const auto& ff_data = ff_ptr->GetLocalFieldVector();
×
1454

1455
      for (const auto& cell : grid_->local_cells)
×
1456
      {
1457
        const auto& cell_mapping = sdm.GetCellMapping(cell);
×
1458
        const size_t num_nodes = cell_mapping.GetNumNodes();
×
1459

1460
        for (size_t i = 0; i < num_nodes; ++i)
×
1461
        {
1462
          const auto imapA = sdm.MapDOFLocal(cell, i);
×
1463
          const auto imapB = sdm.MapDOFLocal(cell, i, phi_uk_man, m, g);
×
1464

UNCOV
1465
          if (which_phi == PhiSTLOption::PHI_OLD)
×
UNCOV
1466
            phi_old_local_[imapB] = ff_data[imapA];
×
UNCOV
1467
          else if (which_phi == PhiSTLOption::PHI_NEW)
×
1468
            phi_new_local_[imapB] = ff_data[imapA];
×
1469
        } // for node
1470
      } // for cell
1471
    } // for g
1472
  } // for m
UNCOV
1473
}
×
1474

1475
LBSProblem::~LBSProblem()
601✔
1476
{
1477
  ResetGPUCarriers();
1478
}
3,005✔
1479

601✔
1480
void
1481
LBSProblem::SetSaveAngularFlux(bool save)
124✔
1482
{
1483
  options_.save_angular_flux = save;
124✔
1484
  if (initialized_)
124✔
1485
    applied_save_angular_flux_ = save;
80✔
1486
}
124✔
1487

1488
void
1489
LBSProblem::ZeroPhi()
76✔
1490
{
1491
  std::fill(phi_old_local_.begin(), phi_old_local_.end(), 0.0);
76✔
1492
  std::fill(phi_new_local_.begin(), phi_new_local_.end(), 0.0);
76✔
1493
}
76✔
1494

1495
void
1496
LBSProblem::CopyPhiNewToOld()
192✔
1497
{
1498
  assert(phi_old_local_.size() == phi_new_local_.size() && "Phi vectors size mismatch.");
192✔
1499
  phi_old_local_ = phi_new_local_;
192✔
1500
}
192✔
1501

1502
void
1503
LBSProblem::SetPhiOldFrom(const std::vector<double>& phi_old)
2,376✔
1504
{
1505
  assert(phi_old.size() == phi_old_local_.size() && "SetPhiOldFrom size mismatch.");
2,376✔
1506
  phi_old_local_ = phi_old;
2,376✔
1507
}
2,376✔
1508

1509
void
UNCOV
1510
LBSProblem::SetPhiNewFrom(const std::vector<double>& phi_new)
×
1511
{
1512
  assert(phi_new.size() == phi_new_local_.size() && "SetPhiNewFrom size mismatch.");
×
UNCOV
1513
  phi_new_local_ = phi_new;
×
1514
}
×
1515

1516
void
UNCOV
1517
LBSProblem::ScalePhiOld(double factor)
×
1518
{
UNCOV
1519
  for (auto& value : phi_old_local_)
×
UNCOV
1520
    value *= factor;
×
UNCOV
1521
}
×
1522

1523
void
1524
LBSProblem::ScalePhiNew(double factor)
8✔
1525
{
1526
  for (auto& value : phi_new_local_)
168,008✔
1527
    value *= factor;
168,000✔
1528
}
8✔
1529

1530
void
1531
LBSProblem::ZeroQMoments()
66,890✔
1532
{
1533
  assert(q_moments_local_.size() == phi_old_local_.size() && "Q moments/Phi size mismatch.");
66,890✔
1534
  std::fill(q_moments_local_.begin(), q_moments_local_.end(), 0.0);
66,890✔
1535
}
66,890✔
1536

1537
void
1538
LBSProblem::ScaleQMoments(double factor)
8,213✔
1539
{
1540
  for (auto& value : q_moments_local_)
490,206,265✔
1541
    value *= factor;
490,198,052✔
1542
}
8,213✔
1543

1544
void
1545
LBSProblem::SetQMomentsFrom(const std::vector<double>& q_moments)
178,422✔
1546
{
1547
  assert(q_moments.size() == q_moments_local_.size() && "SetQMomentsFrom size mismatch.");
178,422✔
1548
  q_moments_local_ = q_moments;
178,422✔
1549
}
178,422✔
1550

1551
void
1552
LBSProblem::ScalePrecursors(double factor)
68✔
1553
{
1554
  for (auto& value : precursor_new_local_)
1,284✔
1555
    value *= factor;
1,216✔
1556
}
68✔
1557

1558
void
1559
LBSProblem::ZeroPrecursors()
844✔
1560
{
1561
  std::fill(precursor_new_local_.begin(), precursor_new_local_.end(), 0.0);
844✔
1562
}
844✔
1563

1564
void
UNCOV
1565
LBSProblem::ZeroExtSrcMoments()
×
1566
{
UNCOV
1567
  std::fill(ext_src_moments_local_.begin(), ext_src_moments_local_.end(), 0.0);
×
1568
}
×
1569

1570
void
UNCOV
1571
LBSProblem::ScaleExtSrcMoments(double factor)
×
1572
{
1573
  for (auto& value : ext_src_moments_local_)
×
UNCOV
1574
    value *= factor;
×
1575
}
×
1576

1577
void
1578
LBSProblem::SetUniformDensities(double density)
×
1579
{
UNCOV
1580
  assert(densities_local_.size() == grid_->local_cells.size() &&
×
1581
         "Densities/local-cells size mismatch.");
UNCOV
1582
  std::fill(densities_local_.begin(), densities_local_.end(), density);
×
1583
}
×
1584

1585
void
UNCOV
1586
LBSProblem::SetDensity(size_t cell_local_id, double density)
×
1587
{
1588
  assert(cell_local_id < densities_local_.size() && "SetDensity cell index out of range.");
×
UNCOV
1589
  densities_local_[cell_local_id] = density;
×
1590
}
×
1591

1592
void
UNCOV
1593
LBSProblem::SetDensitiesFrom(const std::vector<double>& densities)
×
1594
{
UNCOV
1595
  assert(densities.size() == densities_local_.size() && "SetDensitiesFrom size mismatch.");
×
UNCOV
1596
  densities_local_ = densities;
×
UNCOV
1597
}
×
1598

1599
void
1600
LBSProblem::SetAdjoint(bool adjoint)
24✔
1601
{
1602
  OpenSnLogicalErrorIf(
24✔
1603
    not initialized_, GetName() + ": Problem must be fully constructed before calling SetAdjoint.");
1604

1605
  if (adjoint)
24✔
1606
    if (const auto* do_problem = dynamic_cast<const DiscreteOrdinatesProblem*>(this);
20✔
1607
        do_problem and do_problem->IsTimeDependent())
20✔
UNCOV
1608
      OpenSnInvalidArgument(GetName() + ": Time-dependent adjoint problems are not supported.");
×
1609

1610
  options_.adjoint = adjoint;
24✔
1611

1612
  if (adjoint != applied_adjoint_)
24✔
1613
  {
1614
    // Reinitialize materials to obtain the proper forward/adjoint cross sections.
1615
    InitializeMaterials();
24✔
1616

1617
    // Forward and adjoint sources are fundamentally different.
1618
    point_sources_.clear();
24✔
1619
    volumetric_sources_.clear();
24✔
1620
    ClearBoundaries();
24✔
1621

1622
    // Reset all solution vectors.
1623
    ZeroPhi();
24✔
1624
    ZeroPsi();
24✔
1625
    ZeroPrecursors();
24✔
1626

1627
    applied_adjoint_ = adjoint;
24✔
1628
  }
1629
}
24✔
1630

1631
bool
UNCOV
1632
LBSProblem::IsAdjoint() const
×
1633
{
UNCOV
1634
  return options_.adjoint;
×
1635
}
1636

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