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

Open-Sn / opensn / 18928565313

22 Oct 2025 07:55PM UTC coverage: 74.771%. Remained the same
18928565313

push

github

web-flow
Merge pull request #807 from wdhawkins/clang-tidy-init-variables

Fixing clang-tidy init-variables warnings

132 of 179 new or added lines in 52 files covered. (73.74%)

184 existing lines in 3 files now uncovered.

18203 of 24345 relevant lines covered (74.77%)

53868061.69 hits per line

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

77.64
/python/lib/solver.cc
1
// SPDX-FileCopyrightText: 2025 The OpenSn Authors <https://open-sn.github.io/opensn/>
2
// SPDX-License-Identifier: MIT
3

4
#include "python/lib/py_wrappers.h"
5
#include "framework/runtime.h"
6
#include "framework/field_functions/field_function_grid_based.h"
7
#include "modules/linear_boltzmann_solvers/discrete_ordinates_problem/acceleration/discrete_ordinates_keigen_acceleration.h"
8
#include "modules/linear_boltzmann_solvers/discrete_ordinates_problem/acceleration/scdsa_acceleration.h"
9
#include "modules/linear_boltzmann_solvers/discrete_ordinates_problem/acceleration/smm_acceleration.h"
10
#include "modules/linear_boltzmann_solvers/discrete_ordinates_curvilinear_problem/discrete_ordinates_curvilinear_problem.h"
11
#include "modules/linear_boltzmann_solvers/discrete_ordinates_problem/discrete_ordinates_problem.h"
12
#include "modules/linear_boltzmann_solvers/solvers/steady_state_solver.h"
13
#include "modules/linear_boltzmann_solvers/solvers/nl_keigen_solver.h"
14
#include "modules/linear_boltzmann_solvers/solvers/pi_keigen_solver.h"
15
#include "modules/linear_boltzmann_solvers/lbs_problem/io/lbs_problem_io.h"
16
#include "modules/linear_boltzmann_solvers/lbs_problem/lbs_problem.h"
17
#include "modules/linear_boltzmann_solvers/lbs_problem/lbs_compute.h"
18
#include "modules/solver.h"
19
#include <pybind11/numpy.h>
20
#include <algorithm>
21
#include <cstddef>
22
#include <cstdint>
23
#include <map>
24
#include <memory>
25
#include <string>
26
#include <vector>
27

28
namespace opensn
29
{
30

31
// Wrap problem
32
void
33
WrapProblem(py::module& slv)
424✔
34
{
35
  // clang-format off
36
  // problem base
37
  auto problem = py::class_<Problem, std::shared_ptr<Problem>>(
424✔
38
    slv,
39
    "Problem",
40
    R"(
41
    Base class for all problems.
42

43
    Wrapper of :cpp:class:`opensn::Problem`.
44
    )"
45
  );
424✔
46
  problem.def(
424✔
47
    "GetFieldFunctions",
48
    [](Problem& self)
424✔
49
    {
50
      py::list ff_list;
×
51
      std::vector<std::shared_ptr<FieldFunctionGridBased>>& cpp_ff_list = self.GetFieldFunctions();
×
52
      for (std::shared_ptr<FieldFunctionGridBased>& ff : cpp_ff_list) {
×
53
        ff_list.append(ff);
×
54
      }
55
      return ff_list;
×
56
    },
×
57
    R"(
58
    Get the list of field functions.
59

60
    Returns
61
    -------
62
    List[pyopensn.fieldfunc.FieldFunctionGridBased]
63
        List of grid-based field functions representing solution data such as scalar fluxes.
64
    )"
65
  );
66
  // clang-format on
67
}
424✔
68

69
// Wrap solver
70
void
71
WrapSolver(py::module& slv)
424✔
72
{
73
  // clang-format off
74
  // solver base
75
  auto solver = py::class_<Solver, std::shared_ptr<Solver> >(
424✔
76
    slv,
77
    "Solver",
78
    R"(
79
    Base class for all solvers.
80

81
    Wrapper of :cpp:class:`opensn::Solver`.
82
    )"
83
  );
424✔
84
  solver.def(
424✔
85
    "Initialize",
86
    &Solver::Initialize,
424✔
87
    "Initialize the solver."
88
  );
89
  solver.def(
424✔
90
    "Execute",
91
    &Solver::Execute,
424✔
92
    "Execute the solver."
93
  );
94
  solver.def(
424✔
95
    "Step",
96
    &Solver::Step,
424✔
97
    "Step the solver."
98
  );
99
  solver.def(
424✔
100
    "Advance",
101
    &Solver::Advance,
424✔
102
    "Advance time values function."
103
  );
104
  // clang-format on
105
}
424✔
106

107
// Wrap LBS solver
108
void
109
WrapLBS(py::module& slv)
424✔
110
{
111
  // clang-format off
112
  // LBS problem
113
  auto lbs_problem = py::class_<LBSProblem, std::shared_ptr<LBSProblem>, Problem>(
424✔
114
    slv,
115
    "LBSProblem",
116
    R"(
117
    Base class for all linear Boltzmann problems.
118

119
    Wrapper of :cpp:class:`opensn::LBSProblem`.
120
    )"
121
  );
424✔
122
  lbs_problem.def(
424✔
123
    "GetScalarFieldFunctionList",
124
    [](LBSProblem& self, bool only_scalar_flux)
424✔
125
    {
126
      py::list field_function_list_per_group;
200✔
127
      for (std::size_t group = 0; group < self.GetNumGroups(); group++)
13,450✔
128
      {
129
        if (only_scalar_flux)
13,250✔
130
        {
131
          std::size_t ff_index = self.MapPhiFieldFunction(group, 0);
8,254✔
132
          field_function_list_per_group.append(self.GetFieldFunctions()[ff_index]);
8,254✔
133
        }
134
        else
135
        {
136
          py::list field_function_list_per_moment;
4,996✔
137
          for (std::size_t moment = 0; moment < self.GetNumMoments(); moment++)
19,568✔
138
          {
139
            std::size_t ff_index = self.MapPhiFieldFunction(group, moment);
14,572✔
140
            field_function_list_per_moment.append(self.GetFieldFunctions()[ff_index]);
14,572✔
141
          }
142
          field_function_list_per_group.append(field_function_list_per_moment);
4,996✔
143
        }
4,996✔
144
      }
145
      return field_function_list_per_group;
200✔
146
    },
×
147
    R"(
148
    Return field functions grouped by energy group and, optionally, by moment.
149

150
    Parameters
151
    ----------
152
    only_scalar_flux : bool, default=True
153
        If True, returns only the zeroth moment (scalar flux) field function for each group.
154
        The result is a flat list of field functions, one per group.
155

156
        If False, returns all moment field functions for each group.
157
        The result is a nested list where each entry corresponds to a group and contains
158
        a list of field functions for all moments (e.g., scalar flux, higher-order moments).
159

160
    Returns
161
    -------
162
    Union[List[pyopensn.fieldfunc.FieldFunctionGridBased], List[List[pyopensn.fieldfunc.FieldFunctionGridBased]]]
163
        The structure of the returned list depends on the `only_scalar_flux` flag.
164

165
    Notes
166
    -----
167
    The moment index varies more rapidly than the group index when `only_scalar_flux` is False.
168
    )",
169
    py::arg("only_scalar_flux") = true
424✔
170
  );
171
  lbs_problem.def(
424✔
172
    "GetPowerFieldFunction",
173
    &LBSProblem::GetPowerFieldFunction,
424✔
174
    R"(
175
    Returns the power generation field function, if enabled.
176
    )"
177
  );
178
  lbs_problem.def(
424✔
179
    "SetOptions",
180
    [](LBSProblem& self, py::kwargs& params)
424✔
181
    {
182
      InputParameters input = LBSProblem::GetOptionsBlock();
×
183
      input.AssignParameters(kwargs_to_param_block(params));
×
184
      self.SetOptions(input);
×
185
    },
×
186
    R"(
187
    Set problem options from a large list of parameters.
188

189
    Parameters
190
    ----------
191
    max_mpi_message_size: int default=32768
192
        The maximum MPI message size used during sweeps.
193
    restart_writes_enabled: bool, default=False
194
        Flag that controls writing of restart dumps.
195
    write_delayed_psi_to_restart: bool, default=True
196
        Flag that controls writing of delayed angular fluxes to restarts.
197
    read_restart_path: str, default=''
198
        Full path for reading restart dumps including file basename.
199
    write_restart_path: str, default=''
200
        Full path for writing restart dumps including file basename.
201
    write_restart_time_interval: int, default=0
202
        Time interval in seconds at which restart data is to be written.
203
    use_precursors: bool, default=False
204
        Flag for using delayed neutron precursors.
205
    use_source_moments: bool, default=False
206
        Flag for ignoring fixed sources and selectively using source moments obtained elsewhere.
207
    save_angular_flux: bool, default=False
208
        Flag indicating whether angular fluxes are to be stored or not.
209
    verbose_inner_iterations: bool, default=True
210
        Flag to control verbosity of inner iterations.
211
    verbose_outer_iterations: bool, default=True
212
        Flag to control verbosity of across-groupset iterations.
213
    max_ags_iterations: int, default=100
214
        Maximum number of across-groupset iterations.
215
    ags_tolerance: float, default=1.0e-6
216
        Across-groupset iterations tolerance.
217
    ags_convergence_check: {'l2', 'pointwise'}, default='l2'
218
        Type of convergence check for AGS iterations.
219
    verbose_ags_iterations: bool, default=True
220
        Flag to control verbosity of across-groupset iterations.
221
    power_field_function_on: bool, default=False
222
        Flag to control the creation of the power generation field function. If set to ``True``, a
223
        field function will be created with the general name ``<solver_name>_power_generation``.
224
    power_default_kappa: float, default=3.20435e-11
225
        Default ``kappa`` value (Energy released per fission) to use for power generation when cross
226
        sections do not have ``kappa`` values. Default corresponds to 200 MeV per fission.
227
    power_normalization: float, default=-1.0
228
        Power normalization factor to use. Supply a negative or zero number to turn this off.
229
    field_function_prefix_option: {'prefix', 'solver_name'}, default='prefix'
230
        Prefix option on field function names. If unset, flux field functions will be exported as
231
        ``phi_gXXX_mYYY``, where ``XXX`` is the zero-padded 3-digit group number and ``YYY`` is the
232
        zero-padded 3-digit moment.
233
    field_function_prefix: str, default=''
234
        Prefix to use on all field functions. By default, this is empty. If specified, flux moments
235
        are exported as ``prefix_phi_gXXX_mYYY``.
236
    )"
237
  );
238
  lbs_problem.def(
424✔
239
    "ComputeFissionRate",
240
    [](LBSProblem& self, const std::string& scalar_flux_iterate)
424✔
241
    {
NEW
242
      const std::vector<double>* phi_ptr = nullptr;
×
243
      if (scalar_flux_iterate == "old")
×
244
      {
245
        phi_ptr = &self.GetPhiOldLocal();
×
246
      }
247
      else if (scalar_flux_iterate == "new")
×
248
      {
249
        phi_ptr = &self.GetPhiNewLocal();
×
250
      }
251
      else
252
      {
253
        throw std::invalid_argument("Unknown scalar_flux_iterate value: \"" + scalar_flux_iterate + "\".");
×
254
      }
255
      return ComputeFissionRate(self, *phi_ptr);
×
256
    },
257
    R"(
258
    Computes the total fission rate.
259

260
    Parameters
261
    ----------
262
    scalar_flux_iterate : {'old', 'new'}
263
        Specifies which scalar flux vector to use in the calculation.
264
            - 'old': Use the previous scalar flux iterate.
265
            - 'new': Use the current scalar flux iterate.
266

267
    Returns
268
    -------
269
    float
270
        The total fission rate.
271

272
    Raises
273
    ------
274
    ValueError
275
        If `scalar_flux_iterate` is not 'old' or 'new'.
276
    )",
277
    py::arg("scalar_flux_iterate")
424✔
278
  );
279
  lbs_problem.def(
424✔
280
    "WriteFluxMoments",
281
    [](LBSProblem& self, const std::string& file_base)
452✔
282
    {
283
      LBSSolverIO::WriteFluxMoments(self, file_base);
28✔
284
    },
285
    R"(
286
    Write flux moments to file.
287

288
    Parameters
289
    ----------
290
    file_base: str
291
        File basename.
292
    )",
293
    py::arg("file_base")
424✔
294
  );
295
  lbs_problem.def(
424✔
296
    "CreateAndWriteSourceMoments",
297
    [](LBSProblem& self, const std::string& file_base)
428✔
298
    {
299
      std::vector<double> source_moments = self.MakeSourceMomentsFromPhi();
4✔
300
      LBSSolverIO::WriteFluxMoments(self, file_base, source_moments);
4✔
301
    },
4✔
302
    R"(
303
    Write source moments from latest flux iterate to file.
304

305
    Parameters
306
    ----------
307
    file_base: str
308
        File basename.
309
    )",
310
    py::arg("file_base")
424✔
311
  );
312
  lbs_problem.def(
424✔
313
    "ReadFluxMomentsAndMakeSourceMoments",
314
    [](LBSProblem& self, const std::string& file_base, bool single_file_flag)
424✔
315
    {
316
      LBSSolverIO::ReadFluxMoments(self, file_base, single_file_flag, self.GetExtSrcMomentsLocal());
×
317
      log.Log() << "Making source moments from flux file.";
×
318
      std::vector<double>& temp_phi = self.GetPhiOldLocal();
×
319
      self.GetPhiOldLocal() = self.GetExtSrcMomentsLocal();
×
320
      self.GetExtSrcMomentsLocal() = self.MakeSourceMomentsFromPhi();
×
321
      self.GetPhiOldLocal() = temp_phi;
×
322
    },
×
323
    R"(
324
    Read flux moments and compute corresponding source moments.
325

326
    Parameters
327
    ----------
328
    file_base: str
329
        File basename.
330
    single_file_flag: bool
331
        True if all flux moments are in a single file.
332
    )",
333
    py::arg("file_base"),
848✔
334
    py::arg("single_file_flag")
424✔
335
  );
336
  lbs_problem.def(
424✔
337
    "ReadSourceMoments",
338
    [](LBSProblem& self, const std::string& file_base, bool single_file_flag)
428✔
339
    {
340
      LBSSolverIO::ReadFluxMoments(self, file_base, single_file_flag, self.GetExtSrcMomentsLocal());
4✔
341
    },
4✔
342
    R"(
343
    Read source moments from file.
344

345
    Parameters
346
    ----------
347
    file_base: str
348
        File basename.
349
    single_file_flag: bool
350
        True if all source moments are in a single file.
351
    )",
352
    py::arg("file_base"),
848✔
353
    py::arg("single_file_flag")
424✔
354
  );
355
  lbs_problem.def(
424✔
356
    "ReadFluxMoments",
357
    [](LBSProblem& self, const std::string& file_base, bool single_file_flag)
424✔
358
    {
359
      LBSSolverIO::ReadFluxMoments(self, file_base, single_file_flag);
×
360
    },
361
    R"(
362
    Read flux moment data.
363

364
    Parameters
365
    ----------
366
    file_base: str
367
        File basename.
368
    single_file_flag: bool
369
        True if all flux moments are in a single file.
370
    )",
371
    py::arg("file_base"),
848✔
372
    py::arg("single_file_flag")
424✔
373
  );
374
  lbs_problem.def(
424✔
375
    "WriteAngularFluxes",
376
    [](DiscreteOrdinatesProblem& self, const std::string& file_base)
428✔
377
    {
378
      LBSSolverIO::WriteAngularFluxes(self, file_base);
4✔
379
    },
380
    R"(
381
    Write angular flux data to file.
382

383
    Parameters
384
    ----------
385
    file_base: str
386
        File basename.
387
    )",
388
    py::arg("file_base")
424✔
389
  );
390
  lbs_problem.def(
424✔
391
    "ReadAngularFluxes",
392
    [](DiscreteOrdinatesProblem& self, const std::string& file_base)
428✔
393
    {
394
      LBSSolverIO::ReadAngularFluxes(self, file_base);
4✔
395
    },
396
    R"(
397
    Read angular fluxes from file.
398

399
    Parameters
400
    ----------
401
    file_base: str
402
        File basename.
403
    )",
404
    py::arg("file_base")
424✔
405
  );
406
  lbs_problem.def(
424✔
407
    "SetPointSources",
408
    [](LBSProblem& self, py::kwargs& params)
424✔
409
    {
410
      for (auto [key, value] : params)
×
411
      {
412
        auto c_key = key.cast<std::string>();
×
413
        if (c_key == "clear_point_sources")
×
414
          self.ClearPointSources();
×
415
        else if (c_key == "point_sources")
×
416
        {
417
          auto sources = value.cast<py::list>();
×
418
          for (auto source : sources)
×
419
          {
420
            self.AddPointSource(source.cast<std::shared_ptr<PointSource>>());
×
421
          }
422
        }
×
423
        else
424
          throw std::runtime_error("Invalid argument provided to SetPointSources.\n");
×
425
      }
×
426
    },
×
427
    R"(
428
    Set or clear point sources.
429

430
    Parameters
431
    ----------
432
    clear_point_sources: bool, default=False
433
        If true, all current the point sources of the problem are deleted.
434
    point_sources: List[pyopensn.source.PointSource]
435
        List of new point sources to be added to the problem.
436
    )"
437
  );
438
  lbs_problem.def(
424✔
439
    "SetVolumetricSources",
440
    [](LBSProblem& self, py::kwargs& params)
444✔
441
    {
442
      for (auto [key, value] : params)
60✔
443
      {
444
        auto c_key = key.cast<std::string>();
20✔
445
        if (c_key == "clear_volumetric_sources")
20✔
446
          self.ClearVolumetricSources();
4✔
447
        else if (c_key == "volumetric_sources")
16✔
448
        {
449
          auto sources = value.cast<py::list>();
16✔
450
          for (auto source : sources)
48✔
451
          {
452
            self.AddVolumetricSource(source.cast<std::shared_ptr<VolumetricSource>>());
32✔
453
          }
454
        }
16✔
455
        else
456
          throw std::runtime_error("Invalid argument provided to SetVolumetricSources.\n");
×
457
      }
20✔
458
    },
20✔
459
    R"(
460
    Set or clear volumetric sources.
461

462
    Parameters
463
    ----------
464
    clear_volumetric_sources: bool, default=False
465
        If true, all current the volumetric sources of the problem are deleted.
466
    volumetric_sources: List[pyopensn.source.VolumetricSource]
467
        List of new volumetric sources to be added to the problem.
468
    )"
469
  );
470
  lbs_problem.def(
424✔
471
    "SetBoundaryOptions",
472
    [](LBSProblem& self, py::kwargs& params)
424✔
473
    {
474
      for (auto [key, value] : params)
×
475
      {
476
        auto c_key = key.cast<std::string>();
×
477
        if (c_key == "clear_boundary_conditions")
×
478
          self.ClearBoundaries();
×
479
        else if (c_key == "boundary_conditions")
×
480
        {
481
          auto boundaries = value.cast<py::list>();
×
482
          for (auto boundary : boundaries)
×
483
          {
484
            InputParameters input = LBSProblem::GetBoundaryOptionsBlock();
×
485
            input.AssignParameters(pyobj_to_param_block("", boundary.cast<py::dict>()));
×
486
            self.SetBoundaryOptions(input);
×
487
          }
×
488
        }
×
489
        else
490
          throw std::runtime_error("Invalid argument provided to SetBoundaryOptions.\n");
×
491
      }
×
492
    }
×
493
  );
494
  lbs_problem.def(
424✔
495
    "SetAdjoint",
496
    [](LBSProblem& self, bool adjoint)
×
497
    {
498
      self.SetAdjoint(adjoint);
12✔
499
    }
500
  );
501

502
  // discrete ordinate solver
503
  auto do_problem = py::class_<DiscreteOrdinatesProblem, std::shared_ptr<DiscreteOrdinatesProblem>,
424✔
504
                               LBSProblem>(
505
    slv,
506
    "DiscreteOrdinatesProblem",
507
    R"(
508
    Base class for discrete ordinates problems in Cartesian geometry.
509

510
    This class implements the algorithms necessary to solve a problem using the discrete ordinates method.
511
    When paired with a solver base, the result is a solver instance configured for a specific problem type
512
    (steady-state, transient, adjoint, k-eigenvalue, etc.).
513

514
    Wrapper of :cpp:class:`opensn::DiscreteOrdinatesProblem`.
515
    )"
516
  );
424✔
517
  do_problem.def(
848✔
518
    py::init(
424✔
519
      [](py::kwargs& params)
291✔
520
      {
521
        return DiscreteOrdinatesProblem::Create(kwargs_to_param_block(params));
291✔
522
      }
523
    ),
524
    R"(
525
    Construct a discrete ordinates problem with Cartesian geometry.
526

527
    Parameters
528
    ----------
529
    mesh : MeshContinuum
530
        The spatial mesh.
531
    num_groups : int
532
        The total number of energy groups.
533
    groupsets : List[Dict], default=[]
534
        A list of input parameter blocks, each block provides the iterative properties for a
535
        groupset.
536
    xs_map : List[Dict], default=[]
537
        A list of mappings from block ids to cross-section definitions.
538
    scattering_order: int, default=0
539
        The level of harmonic expansion for the scattering source.
540
    boundary_conditions: List[Dict], default=[]
541
        A list containing tables for each boundary specification.
542
    point_sources: List[pyopensn.source.PointSource], default=[]
543
        A list of point sources.
544
    volumetric_sources: List[pyopensn.source.VolumetricSource], default=[]
545
        A list of volumetric sources.
546
    options : Dict, default={}
547
        A block of optional configuration parameters. See `SetOptions` for available settings.
548
    sweep_type : str, default="AAH"
549
        The sweep type to use. Must be one of `AAH` or `CBC`. Defaults to `AAH`.
550
    use_gpus : bool, default=False
551
        A flag specifying whether GPU acceleration is used for the sweep. Currently, only ``AAH`` is
552
        supported.
553
    )"
554
  );
555
  do_problem.def(
424✔
556
    "SetOptions",
557
    [](DiscreteOrdinatesProblem& self, py::kwargs& params)
436✔
558
    {
559
      InputParameters input = DiscreteOrdinatesProblem::GetOptionsBlock();
12✔
560
      input.AssignParameters(kwargs_to_param_block(params));
12✔
561
      self.SetOptions(input);
12✔
562
    },
12✔
563
    R"(
564
    Set problem options from a large list of parameters.
565

566
    Parameters
567
    ----------
568
    adjoint: bool, default=False
569
        Flag for toggling whether the solver is in adjoint mode.
570
    )"
571
  );
572
  do_problem.def(
424✔
573
    "ComputeBalance",
574
    [](DiscreteOrdinatesProblem& self)
451✔
575
    {
576
      ComputeBalance(self);
27✔
577
    },
578
    R"(
579
    Compute and print particle balance for the problem.
580
    )"
581
  );
582
  do_problem.def(
424✔
583
    "ComputeLeakage",
584
    [](DiscreteOrdinatesProblem& self, py::list bnd_names)
459✔
585
    {
586
      // get the supported boundaries
587
      std::map<std::string, std::uint64_t> allowed_bd_names = LBSProblem::supported_boundary_names;
35✔
588
      std::map<std::uint64_t, std::string> allowed_bd_ids = LBSProblem::supported_boundary_ids;
35✔
589
      // get the boundaries to parse
590
      std::vector<std::uint64_t> bndry_ids;
35✔
591
      if (bnd_names.size() > 1)
35✔
592
      {
593
        for (py::handle name : bnd_names)
80✔
594
        {
595
          bndry_ids.push_back(allowed_bd_names.at(name.cast<std::string>()));
96✔
596
        }
597
      }
598
      else
599
      {
600
        bndry_ids = self.GetGrid()->GetUniqueBoundaryIDs();
57✔
601
      }
602
      // compute the leakage
603
      std::map<std::uint64_t, std::vector<double>> leakage = ComputeLeakage(self, bndry_ids);
35✔
604
      // convert result to native Python
605
      py::dict result;
35✔
606
      for (const auto& [bndry_id, gr_wise_leakage] : leakage)
121✔
607
      {
608
        py::array_t<double> np_vector = py::array_t<double>(static_cast<long>(gr_wise_leakage.size()));
86✔
609
        py::buffer_info buffer = np_vector.request();
86✔
610
        auto *np_vector_data = static_cast<double*>(buffer.ptr);
86✔
611
        std::copy(gr_wise_leakage.begin(), gr_wise_leakage.end(), np_vector_data);
86✔
612
        result[allowed_bd_ids.at(bndry_id).data()] = np_vector;
86✔
613
      }
86✔
614
      return result;
35✔
615
    },
35✔
616
    R"(
617
    Compute leakage for the problem.
618

619
    Parameters
620
    ----------
621
    bnd_names : List[str]
622
        A list of boundary names for which leakage should be computed.
623

624
    Returns
625
    -------
626
    Dict[str, numpy.ndarray]
627
        A dictionary mapping boundary names to group-wise leakage vectors.
628
        Each array contains the outgoing angular flux (per group) integrated over
629
        the corresponding boundary surface.
630

631
    Raises
632
    ------
633
    RuntimeError
634
        If `save_angular_flux` option was not enabled during problem setup.
635

636
    ValueError
637
        If one or more boundary ids are not present on the current mesh.
638
    )",
639
    py::arg("bnd_names")
424✔
640
  );
641

642
  // discrete ordinates curvilinear problem
643
  auto do_curvilinear_problem = py::class_<DiscreteOrdinatesCurvilinearProblem,
424✔
644
                                           std::shared_ptr<DiscreteOrdinatesCurvilinearProblem>,
645
                                           DiscreteOrdinatesProblem>(
646
    slv,
647
    "DiscreteOrdinatesCurvilinearProblem",
648
    R"(
649
    Base class for discrete ordinates problems in curvilinear geometry.
650

651
    Wrapper of :cpp:class:`opensn::DiscreteOrdinatesCurvilinearProblem`.
652
    )"
653
  );
424✔
654
  do_curvilinear_problem.def(
848✔
655
    py::init(
424✔
656
      [](py::kwargs& params)
8✔
657
      {
658
        return DiscreteOrdinatesCurvilinearProblem::Create(kwargs_to_param_block(params));
8✔
659
      }
660
    ),
661
    R"(
662
    Construct a discrete ordinates problem for curvilinear geometry.
663

664
    Warnings
665
    --------
666
       DiscreteOrdinatesCurvilinearProblem is **experimental** and should be used with caution!
667

668
    Parameters
669
    ----------
670
    mesh : MeshContinuum
671
        The spatial mesh.
672
    coord_system : int
673
        Coordinate system to use. Must be set to 2 (cylindrical coordinates).
674
    num_groups : int
675
        The total number of energy groups.
676
    groupsets : list of dict
677
        A list of input parameter blocks, each block provides the iterative properties for a
678
        groupset.
679
    xs_map : list of dict
680
        A list of mappings from block ids to cross-section definitions.
681
    scattering_order: int, default=0
682
        The level of harmonic expansion for the scattering source.
683
    boundary_conditions: List[Dict], default=[]
684
        A list containing tables for each boundary specification.
685
    point_sources: List[pyopensn.source.PointSource], default=[]
686
        A list of point sources.
687
    volumetric_sources: List[pyopensn.source.VolumetricSource], default=[]
688
        A list of volumetric sources.
689
    options : dict, optional
690
        A block of optional configuration parameters. See `SetOptions` for available settings.
691
    sweep_type : str, optional
692
        The sweep type to use. Must be one of `AAH` or `CBC`. Defaults to `AAH`.
693
    )"
694
  );
695
}
424✔
696

697
// Wrap steady-state solver
698
void
699
WrapSteadyState(py::module& slv)
424✔
700
{
701
  // clang-format off
702
  // steady state solver
703
  auto steady_state_solver = py::class_<SteadyStateSourceSolver, std::shared_ptr<SteadyStateSourceSolver>,
424✔
704
                                        Solver>(
705
    slv,
706
    "SteadyStateSourceSolver",
707
    R"(
708
    Steady state solver.
709

710
    Wrapper of :cpp:class:`opensn::SteadyStateSourceSolver`.
711
    )"
712
  );
424✔
713
  steady_state_solver.def(
848✔
714
    py::init(
424✔
715
      [](py::kwargs& params)
234✔
716
      {
717
        return SteadyStateSourceSolver::Create(kwargs_to_param_block(params));
234✔
718
      }
719
    ),
720
    R"(
721
    Construct a steady state solver.
722

723
    Parameters
724
    ----------
725
    pyopensn.solver.LBSProblem : LBSProblem
726
        Existing LBSProblem instance.
727
    )"
728
  );
729
  // clang-format on
730
}
424✔
731

732
// Wrap non-linear k-eigen solver
733
void
734
WrapNLKEigen(py::module& slv)
424✔
735
{
736
  // clang-format off
737
  // non-linear k-eigen solver
738
  auto non_linear_k_eigen_solver = py::class_<NonLinearKEigenSolver, std::shared_ptr<NonLinearKEigenSolver>,
424✔
739
                                              Solver>(
740
    slv,
741
    "NonLinearKEigenSolver",
742
    R"(
743
    Non-linear k-eigenvalue solver.
744

745
    Wrapper of :cpp:class:`opensn::NonLinearKEigenSolver`.
746
    )"
747
  );
424✔
748
  non_linear_k_eigen_solver.def(
848✔
749
    py::init(
424✔
750
      [](py::kwargs& params)
28✔
751
      {
752
        return NonLinearKEigenSolver::Create(kwargs_to_param_block(params));
28✔
753
      }
754
        ),
755
    R"(
756
    Construct a non-linear k-eigenvalue solver.
757

758
    Parameters
759
    ----------
760
    lbs_problem: pyopensn.solver.LBSProblem
761
        Existing LBSProblem instance.
762
    nl_abs_tol: float, default=1.0e-8
763
        Non-linear absolute tolerance.
764
    nl_rel_tol: float, default=1.0e-8
765
        Non-linear relative tolerance.
766
    nl_sol_tol: float, default=1.0e-50
767
        Non-linear solution tolerance.
768
    nl_max_its: int, default=50
769
        Non-linear algorithm maximum iterations.
770
    l_abs_tol: float, default=1.0e-8
771
        Linear absolute tolerance.
772
    l_rel_tol: float, default=1.0e-8
773
        Linear relative tolerance.
774
    l_div_tol: float, default=1.0e6
775
        Linear divergence tolerance.
776
    l_max_its: int, default=50
777
        Linear algorithm maximum iterations.
778
    l_gmres_restart_intvl: int, default=30
779
        GMRES restart interval.
780
    l_gmres_breakdown_tol: float, default=1.0e6
781
        GMRES breakdown tolerance.
782
    reset_phi0: bool, default=True
783
        If true, reinitializes scalar fluxes to 1.0.
784
    num_initial_power_iterations: int, default=0
785
        Number of initial power iterations before the non-linear solve.
786
    )"
787
  );
788
  non_linear_k_eigen_solver.def(
424✔
789
    "GetEigenvalue",
790
    &NonLinearKEigenSolver::GetEigenvalue,
424✔
791
    R"(
792
    Return the current k‑eigenvalue.
793
    )"
794
  );
795
  // clang-format on
796
}
424✔
797

798
// Wrap power iteration solvers
799
void
800
WrapPIteration(py::module& slv)
424✔
801
{
802
  // clang-format off
803
  // power iteration k-eigen solver
804
  auto pi_k_eigen_solver = py::class_<PowerIterationKEigenSolver, std::shared_ptr<PowerIterationKEigenSolver>,
424✔
805
                                      Solver>(
806
    slv,
807
    "PowerIterationKEigenSolver",
808
    R"(
809
    Power iteration k-eigenvalue solver.
810

811
    Wrapper of :cpp:class:`opensn::PowerIterationKEigenSolver`.
812
    )"
813
  );
424✔
814
  pi_k_eigen_solver.def(
848✔
815
    py::init(
424✔
816
      [](py::kwargs& params)
35✔
817
      {
818
        return PowerIterationKEigenSolver::Create(kwargs_to_param_block(params));
35✔
819
      }
820
    ),
821
    R"(
822
    Construct a power iteration k-eigen solver.
823

824
    Parameters
825
    ----------
826
    problem: pyopensn.solver.LBSProblem
827
        Existing DiscreteOrdinatesProblem instance.
828
    acceleration: pyopensn.solver.DiscreteOrdinatesKEigenAcceleration
829
        Optional DiscreteOrdinatesKEigenAcceleration instance for acceleration.
830
    max_iters: int, default = 1000
831
        Maximum power iterations allowed.
832
    k_tol: float, default = 1.0e-10
833
        Tolerance on the k-eigenvalue.
834
    reset_solution: bool, default=True
835
        If true, initialize flux moments to 1.0.
836
    reset_phi0: bool, default=True
837
        If true, reinitializes scalar fluxes to 1.0.
838
    )"
839
  );
840
  pi_k_eigen_solver.def(
424✔
841
    "GetEigenvalue",
842
    &PowerIterationKEigenSolver::GetEigenvalue,
424✔
843
    R"(
844
    Return the current k‑eigenvalue.
845
    )"
846
  );
847
  // clang-format on
848
}
424✔
849

850
// Wrap LBS solver
851
void
852
WrapDiscreteOrdinatesKEigenAcceleration(py::module& slv)
424✔
853
{
854
  // clang-format off
855
  // discrete ordinates k-eigen acceleration base
856
  auto acceleration = py::class_<DiscreteOrdinatesKEigenAcceleration,
424✔
857
                                 std::shared_ptr<DiscreteOrdinatesKEigenAcceleration>>(
858
    slv,
859
    "DiscreteOrdinatesKEigenAcceleration",
860
    R"(
861
    Base class for discrete ordinates k-eigenvalue acceleration methods.
862

863
    Wrapper of :cpp:class:`opensn::DiscreteOrdinatesKEigenAcceleration`.
864
    )"
865
  );
424✔
866
  // SCDSA acceleration
867
  auto scdsa_acceleration = py::class_<SCDSAAcceleration,
424✔
868
                                       std::shared_ptr<SCDSAAcceleration>,
869
                                       DiscreteOrdinatesKEigenAcceleration>(
870
    slv,
871
    "SCDSAAcceleration",
872
    R"(
873
    Construct an SCDSA accelerator for the power iteration k-eigenvalue solver.
874

875
    Wrapper of :cpp:class:`opensn::SCDSAAcceleration`.
876
    )"
877
  );
424✔
878
  scdsa_acceleration.def(
848✔
879
    py::init(
424✔
880
      [](py::kwargs& params)
8✔
881
      {
882
        return SCDSAAcceleration::Create(kwargs_to_param_block(params));
8✔
883
      }
884
    ),
885
    R"(
886
    SCDSA acceleration for the power iteration k-eigenvalue solver.
887

888
    Parameters
889
    ----------
890
    problem: pyopensn.solver.LBSProblem
891
        Existing DiscreteOrdinatesProblem instance.
892
    l_abs_tol: float, defauilt=1.0e-10
893
        Absolute residual tolerance.
894
    max_iters: int, default=100
895
        Maximum allowable iterations.
896
    verbose: bool, default=False
897
        If true, enables verbose output.
898
    petsc_options: str, default="ssss"
899
        Additional PETSc options.
900
    pi_max_its: int, default=50
901
        Maximum allowable iterations for inner power iterations.
902
    pi_k_tol: float, default=1.0e-10
903
        k-eigenvalue tolerance for the inner power iterations.
904
    sdm: str, default="pwld"
905
        Spatial discretization method to use for the diffusion solver. Valid choices are:
906
            - 'pwld' : Piecewise Linear Discontinuous
907
            - 'pwlc' : Piecewise Linear Continuous
908
    )"
909
  );
910
  // SMM acceleration
911
  auto smm_acceleration = py::class_<SMMAcceleration,
424✔
912
                                     std::shared_ptr<SMMAcceleration>,
913
                                     DiscreteOrdinatesKEigenAcceleration>(
914
    slv,
915
    "SMMAcceleration",
916
    R"(
917
    Construct an SMM accelerator for the power iteration k-eigenvalue solver.
918

919
    Wrapper of :cpp:class:`opensn::SMMAcceleration`.
920
    )"
921
  );
424✔
922
  smm_acceleration.def(
848✔
923
    py::init(
424✔
924
      [](py::kwargs& params)
4✔
925
      {
926
        return SMMAcceleration::Create(kwargs_to_param_block(params));
4✔
927
      }
928
    ),
929
    R"(
930
    SMM acceleration for the power iteration k-eigenvalue solver.
931

932
    Warnings
933
    --------
934
       SMM acceleration is **experimental** and should be used with caution!
935
       SMM accleration only supports problems with isotropic scattering.
936

937
    Parameters
938
    ----------
939
    problem: pyopensn.solver.LBSProblem
940
        Existing DiscreteOrdinatesProblem instance.
941
    l_abs_tol: float, defauilt=1.0e-10
942
        Absolute residual tolerance.
943
    max_iters: int, default=100
944
        Maximum allowable iterations.
945
    verbose: bool, default=False
946
        If true, enables verbose output.
947
    petsc_options: str, default="ssss"
948
        Additional PETSc options.
949
    pi_max_its: int, default=50
950
        Maximum allowable iterations for inner power iterations.
951
    pi_k_tol: float, default=1.0e-10
952
        k-eigenvalue tolerance for the inner power iterations.
953
    sdm: str, default="pwld"
954
        Spatial discretization method to use for the diffusion solver. Valid choices are:
955
            - 'pwld' : Piecewise Linear Discontinuous
956
            - 'pwlc' : Piecewise Linear Continuous
957
    )"
958
  );
959
  // clang-format on
960
}
424✔
961

962
// Wrap the solver components of OpenSn
963
void
964
py_solver(py::module& pyopensn)
62✔
965
{
966
  py::module slv = pyopensn.def_submodule("solver", "Solver module.");
62✔
967
  WrapProblem(slv);
62✔
968
  WrapSolver(slv);
62✔
969
  WrapLBS(slv);
62✔
970
  WrapSteadyState(slv);
62✔
971
  WrapNLKEigen(slv);
62✔
972
  WrapDiscreteOrdinatesKEigenAcceleration(slv);
62✔
973
  WrapPIteration(slv);
62✔
974
}
62✔
975

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