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

Open-Sn / opensn / 21196332731

20 Jan 2026 08:32PM UTC coverage: 74.367% (-0.07%) from 74.437%
21196332731

push

github

web-flow
Merge pull request #891 from quocdang1998/angle-aggregation

Removing one level of std::vector in AngleAggregation

125 of 143 new or added lines in 5 files covered. (87.41%)

302 existing lines in 10 files now uncovered.

18745 of 25206 relevant lines covered (74.37%)

67731135.13 hits per line

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

81.23
/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/time_dependent_solver.h"
13
#include "modules/linear_boltzmann_solvers/solvers/steady_state_solver.h"
14
#include "modules/linear_boltzmann_solvers/solvers/nl_keigen_solver.h"
15
#include "modules/linear_boltzmann_solvers/solvers/pi_keigen_solver.h"
16
#include "modules/linear_boltzmann_solvers/lbs_problem/io/lbs_problem_io.h"
17
#include "modules/linear_boltzmann_solvers/lbs_problem/lbs_problem.h"
18
#include "modules/linear_boltzmann_solvers/lbs_problem/lbs_compute.h"
19
#include "modules/solver.h"
20
#include <pybind11/numpy.h>
21
#include <algorithm>
22
#include <cstddef>
23
#include <cstdint>
24
#include <map>
25
#include <memory>
26
#include <string>
27
#include <vector>
28

29
namespace opensn
30
{
31

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

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

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

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

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

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

115
    Wrapper of :cpp:class:`opensn::LBSProblem`.
116
    )"
117
  );
495✔
118
  lbs_problem.def(
495✔
119
    "GetScalarFieldFunctionList",
120
    [](LBSProblem& self, bool only_scalar_flux)
495✔
121
    {
122
      py::list field_function_list_per_group;
267✔
123
      for (std::size_t group = 0; group < self.GetNumGroups(); group++)
16,995✔
124
      {
125
        if (only_scalar_flux)
16,728✔
126
        {
127
          std::size_t ff_index = self.MapPhiFieldFunction(group, 0);
11,060✔
128
          field_function_list_per_group.append(self.GetFieldFunctions()[ff_index]);
11,060✔
129
        }
130
        else
131
        {
132
          py::list field_function_list_per_moment;
5,668✔
133
          for (std::size_t moment = 0; moment < self.GetNumMoments(); moment++)
22,256✔
134
          {
135
            std::size_t ff_index = self.MapPhiFieldFunction(group, moment);
16,588✔
136
            field_function_list_per_moment.append(self.GetFieldFunctions()[ff_index]);
16,588✔
137
          }
138
          field_function_list_per_group.append(field_function_list_per_moment);
5,668✔
139
        }
5,668✔
140
      }
141
      return field_function_list_per_group;
267✔
142
    },
×
143
    R"(
144
    Return field functions grouped by energy group and, optionally, by moment.
145

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

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

156
    Returns
157
    -------
158
    Union[List[pyopensn.fieldfunc.FieldFunctionGridBased], List[List[pyopensn.fieldfunc.FieldFunctionGridBased]]]
159
        The structure of the returned list depends on the `only_scalar_flux` flag.
160

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

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

256
    Parameters
257
    ----------
258
    scalar_flux_iterate : {'old', 'new'}
259
        Specifies which scalar flux vector to use in the calculation.
260
            - 'old': Use the previous scalar flux iterate.
261
            - 'new': Use the current scalar flux iterate.
262

263
    Returns
264
    -------
265
    float
266
        The total fission rate.
267

268
    Raises
269
    ------
270
    ValueError
271
        If `scalar_flux_iterate` is not 'old' or 'new'.
272
    )",
273
    py::arg("scalar_flux_iterate")
495✔
274
  );
275
  lbs_problem.def(
495✔
276
    "WriteFluxMoments",
277
    [](LBSProblem& self, const std::string& file_base)
527✔
278
    {
279
      LBSSolverIO::WriteFluxMoments(self, file_base);
32✔
280
    },
281
    R"(
282
    Write flux moments to file.
283

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

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

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

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

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

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

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

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

458
    Parameters
459
    ----------
460
    clear_volumetric_sources: bool, default=False
461
        If true, all current the volumetric sources of the problem are deleted.
462
    volumetric_sources: List[pyopensn.source.VolumetricSource]
463
        List of new volumetric sources to be added to the problem.
464
    )"
465
  );
466
  lbs_problem.def(
495✔
467
    "SetXSMap",
468
    [](LBSProblem& self, py::kwargs& params)
503✔
469
    {
470
      BlockID2XSMap xs_map;
8✔
471
      for (auto [key, value] : params)
24✔
472
      {
473
        auto c_key = key.cast<std::string>();
8✔
474
        if (c_key == "xs_map")
8✔
475
        {
476
          auto xs_entries = value.cast<py::list>();
8✔
477
          for (auto entry : xs_entries)
24✔
478
          {
479
            InputParameters xs_entry_pars = LBSProblem::GetXSMapEntryBlock();
8✔
480
            xs_entry_pars.AssignParameters(pyobj_to_param_block("", entry.cast<py::dict>()));
8✔
481
            const auto& block_ids =
8✔
482
              xs_entry_pars.GetParam("block_ids").GetVectorValue<unsigned int>();
8✔
483
            auto xs = xs_entry_pars.GetSharedPtrParam<MultiGroupXS>("xs");
8✔
484
            for (const auto& block_id : block_ids)
16✔
485
              xs_map[block_id] = xs;
8✔
486
          }
8✔
487
        }
8✔
488
        else
UNCOV
489
          throw std::runtime_error("Invalid argument provided to SetXSMap.\n");
×
490
      }
8✔
491
      self.SetBlockID2XSMap(xs_map);
8✔
492
    },
8✔
493
    R"(
494
    Replace the block-id to cross-section map.
495

496
    Parameters
497
    ----------
498
    xs_map: List[Dict]
499
        A list of block-id to cross-section mapping dictionaries. Each dictionary supports:
500
          - block_ids: List[int] (required)
501
              Mesh block ids to associate with the cross section.
502
          - xs: pyopensn.xs.MultiGroupXS (required)
503
              Cross section object.
504
    )"
505
  );
506
  lbs_problem.def(
495✔
507
    "SetAdjoint",
UNCOV
508
    [](LBSProblem& self, bool adjoint)
×
509
    {
510
      self.SetAdjoint(adjoint);
12✔
511
    }
512
  );
513

514
  // discrete ordinate solver
515
  auto do_problem = py::class_<DiscreteOrdinatesProblem, std::shared_ptr<DiscreteOrdinatesProblem>,
495✔
516
                               LBSProblem>(
517
    slv,
518
    "DiscreteOrdinatesProblem",
519
    R"(
520
    Base class for discrete ordinates problems in Cartesian geometry.
521

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

526
    Wrapper of :cpp:class:`opensn::DiscreteOrdinatesProblem`.
527
    )"
528
  );
495✔
529
  do_problem.def(
990✔
530
    py::init(
495✔
531
      [](py::kwargs& params)
362✔
532
      {
533
        return DiscreteOrdinatesProblem::Create(kwargs_to_param_block(params));
362✔
534
      }
535
    ),
536
    R"(
537
    Construct a discrete ordinates problem with Cartesian geometry.
538

539
    Parameters
540
    ----------
541
    mesh : MeshContinuum
542
        The spatial mesh.
543
    num_groups : int
544
        The total number of energy groups.
545
    groupsets : List[Dict], default=[]
546
        A list of input parameter blocks, each block provides the iterative properties for a
547
        groupset. Each dictionary supports:
548
          - groups_from_to: List[int] (required)
549
              Two-entry list with the first and last group id for the groupset, e.g. ``[0, 3]``.
550
          - angular_quadrature: pyopensn.aquad.AngularQuadrature, optional
551
              Handle to an angular quadrature.
552
          - angle_aggregation_type: {'polar', 'single', 'azimuthal'}, default='polar'
553
              Angle aggregation method to use during sweeping.
554
          - angle_aggregation_num_subsets: int, default=1
555
              Number of angle subsets used for aggregation.
556
          - inner_linear_method: {'classic_richardson', 'petsc_richardson',
557
            'petsc_gmres', 'petsc_bicgstab'}, default='petsc_richardson'
558
              Iterative method used for inner linear solves.
559
          - l_abs_tol: float, default=1.0e-6
560
              Inner linear solver absolute residual tolerance.
561
          - l_max_its: int, default=200
562
              Inner linear solver maximum iterations.
563
          - gmres_restart_interval: int, default=30
564
              GMRES restart interval, if GMRES is used.
565
          - allow_cycles: bool, default=True
566
              Whether cyclic dependencies are allowed in sweeps.
567
          - apply_wgdsa: bool, default=False
568
              Enable within-group DSA for this groupset.
569
          - wgdsa_l_abs_tol: float, default=1.0e-4
570
              WGDSA linear absolute tolerance.
571
          - wgdsa_l_max_its: int, default=30
572
              WGDSA maximum iterations.
573
          - wgdsa_verbose: bool, default=False
574
              Verbose WGDSA output.
575
          - wgdsa_petsc_options: str, default=''
576
              PETSc options string for the WGDSA solver.
577
          - apply_tgdsa: bool, default=False
578
              Enable two-grid DSA for this groupset.
579
          - tgdsa_l_abs_tol: float, default=1.0e-4
580
              TGDSA linear absolute tolerance.
581
          - tgdsa_l_max_its: int, default=30
582
              TGDSA maximum iterations.
583
          - tgdsa_verbose: bool, default=False
584
              Verbose TGDSA output.
585
          - tgdsa_petsc_options: str, default=''
586
              PETSc options string for the TGDSA solver.
587
    xs_map : List[Dict], default=[]
588
        A list of mappings from block ids to cross-section definitions. Each dictionary supports:
589
          - block_ids: List[int] (required)
590
              Mesh block IDs to associate with the cross section.
591
          - xs: pyopensn.xs.MultiGroupXS (required)
592
              Cross-section object to assign to the specified blocks.
593
    boundary_conditions: List[Dict], default=[]
594
        A list containing tables for each boundary specification. Each dictionary supports:
595
          - name: str (required)
596
              Boundary name that identifies the specific boundary.
597
          - type: {'vacuum', 'isotropic', 'reflecting', 'arbitrary'} (required)
598
              Boundary type specification.
599
          - group_strength: List[float], optional
600
              Required when ``type='isotropic'``. Isotropic strength per group.
601
          - function: AngularFluxFunction, optional
602
              Required when ``type='arbitrary'``. Callable that returns incoming angular flux.
603
    point_sources: List[pyopensn.source.PointSource], default=[]
604
        A list of point sources.
605
    volumetric_sources: List[pyopensn.source.VolumetricSource], default=[]
606
        A list of volumetric sources.
607
    options : Dict, default={}
608
        A block of optional configuration parameters. Each dictionary supports the same keys as
609
        :meth:`LBSProblem.SetOptions`, including:
610
          - max_mpi_message_size: int, default=32768
611
          - restart_writes_enabled: bool, default=False
612
          - write_delayed_psi_to_restart: bool, default=True
613
          - read_restart_path: str, default=''
614
          - write_restart_path: str, default=''
615
          - write_restart_time_interval: int, default=0
616
          - use_precursors: bool, default=False
617
          - use_source_moments: bool, default=False
618
          - save_angular_flux: bool, default=False
619
          - verbose_inner_iterations: bool, default=True
620
          - verbose_outer_iterations: bool, default=True
621
          - max_ags_iterations: int, default=100
622
          - ags_tolerance: float, default=1.0e-6
623
          - ags_convergence_check: {'l2', 'pointwise'}, default='l2'
624
          - verbose_ags_iterations: bool, default=True
625
          - power_field_function_on: bool, default=False
626
          - power_default_kappa: float, default=3.20435e-11
627
          - power_normalization: float, default=-1.0
628
          - field_function_prefix_option: {'prefix', 'solver_name'}, default='prefix'
629
          - field_function_prefix: str, default=''
630
    sweep_type : str, default="AAH"
631
        The sweep type to use. Must be one of `AAH` or `CBC`. Defaults to `AAH`.
632
    use_gpus : bool, default=False
633
        A flag specifying whether GPU acceleration is used for the sweep. Currently, only ``AAH`` is
634
        supported.
635
    time_dependent : bool, default=False
636
        Enable time-dependent sweeps. Currently only supported with ``sweep_type="AAH"``.
637
    )"
638
  );
639
  do_problem.def(
495✔
640
    "SetOptions",
641
    [](DiscreteOrdinatesProblem& self, py::kwargs& params)
507✔
642
    {
643
      InputParameters input = DiscreteOrdinatesProblem::GetOptionsBlock();
12✔
644
      input.AssignParameters(kwargs_to_param_block(params));
12✔
645
      self.SetOptions(input);
12✔
646
    },
12✔
647
    R"(
648
    Set problem options from a large list of parameters.
649

650
    Parameters
651
    ----------
652
    adjoint: bool, default=False
653
        Flag for toggling whether the solver is in adjoint mode.
654
    )"
655
  );
656
  do_problem.def(
495✔
657
    "SetBoundaryOptions",
658
    [](DiscreteOrdinatesProblem& self, py::kwargs& params)
495✔
659
    {
UNCOV
660
      for (auto [key, value] : params)
×
661
      {
UNCOV
662
        auto c_key = key.cast<std::string>();
×
UNCOV
663
        if (c_key == "clear_boundary_conditions")
×
UNCOV
664
          self.ClearBoundaries();
×
UNCOV
665
        else if (c_key == "boundary_conditions")
×
666
        {
UNCOV
667
          auto boundaries = value.cast<py::list>();
×
UNCOV
668
          for (auto boundary : boundaries)
×
669
          {
UNCOV
670
            InputParameters input = DiscreteOrdinatesProblem::GetBoundaryOptionsBlock();
×
UNCOV
671
            input.AssignParameters(pyobj_to_param_block("", boundary.cast<py::dict>()));
×
672
            self.SetBoundaryOptions(input);
×
UNCOV
673
          }
×
UNCOV
674
        }
×
675
        else
UNCOV
676
          throw std::runtime_error("Invalid argument provided to SetBoundaryOptions.\n");
×
UNCOV
677
      }
×
UNCOV
678
    },
×
679
    R"(
680
    Set or clear boundary conditions.
681

682
    Parameters
683
    ----------
684
    clear_boundary_conditions: bool, default=False
685
        If true, all current boundary conditions are deleted.
686
    boundary_conditions: List[Dict]
687
        A list of boundary condition dictionaries. Each dictionary supports:
688
          - name: str (required)
689
              Boundary name that identifies the specific boundary.
690
          - type: {'vacuum', 'isotropic', 'reflecting', 'arbitrary'} (required)
691
              Boundary type specification.
692
          - group_strength: List[float], optional
693
              Required when ``type='isotropic'``. Isotropic strength per group.
694
          - function: AngularFluxFunction, optional
695
              Required when ``type='arbitrary'``. Callable that returns incoming angular flux.
696
    )"
697
  );
698
  do_problem.def(
495✔
699
    "GetPsi",
700
    [](DiscreteOrdinatesProblem& self)
503✔
701
    {
702
      const auto& psi = self.GetPsiNewLocal();
8✔
703
      py::list psi_list;
8✔
704
      for (const auto& vec : psi)
16✔
705
      {
706
        auto array = py::array_t<double>(static_cast<py::ssize_t>(vec.size()),
8✔
707
                                         vec.data(),
708
                                         py::cast(self));
8✔
709
        psi_list.append(array);
8✔
710
      }
8✔
711
      return psi_list;
8✔
UNCOV
712
    },
×
713
    R"(
714
    Return psi as a list of NumPy arrays (float64), using zero-copy views into the
715
    underlying data.
716
    )"
717
  );
718
  do_problem.def(
495✔
719
    "ComputeBalance",
720
    [](DiscreteOrdinatesProblem& self)
522✔
721
    {
722
      ComputeBalance(self);
27✔
723
    },
724
    R"(
725
    Compute and print particle balance for the problem.
726
    )"
727
  );
728
  do_problem.def(
495✔
729
    "ComputeLeakage",
730
    [](DiscreteOrdinatesProblem& self, py::list bnd_names)
526✔
731
    {
732
      auto grid = self.GetGrid();
31✔
733
      // get the supported boundaries
734
      std::map<std::string, std::uint64_t> allowed_bd_names = grid->GetBoundaryNameMap();
31✔
735
      std::map<std::uint64_t, std::string> allowed_bd_ids = grid->GetBoundaryIDMap();
31✔
736
      // get the boundaries to parse, preserving user order
737
      std::vector<std::uint64_t> bndry_ids;
31✔
738
      if (bnd_names.size() > 1)
31✔
739
      {
740
        for (py::handle name : bnd_names)
144✔
741
        {
742
          auto sname = name.cast<std::string>();
88✔
743
          bndry_ids.push_back(allowed_bd_names.at(sname));
88✔
744
        }
88✔
745
      }
746
      else
747
      {
748
        bndry_ids = self.GetGrid()->GetUniqueBoundaryIDs();
9✔
749
      }
750
      // compute the leakage
751
      std::map<std::uint64_t, std::vector<double>> leakage = ComputeLeakage(self, bndry_ids);
31✔
752
      // convert result to native Python
753
      py::dict result;
31✔
754
      for (const auto& bndry_id : bndry_ids)
125✔
755
      {
756
        const auto it = leakage.find(bndry_id);
94✔
757
        if (it == leakage.end())
94✔
UNCOV
758
          continue;
×
759
        // construct numpy array and copy contents
760
        const auto& grp_wise_leakage = it->second;
94✔
761
        py::array_t<double> np_vector(py::ssize_t(grp_wise_leakage.size()));
94✔
762
        auto buffer = np_vector.request();
94✔
763
        auto *np_vector_data = static_cast<double*>(buffer.ptr);
94✔
764
        std::copy(grp_wise_leakage.begin(), grp_wise_leakage.end(), np_vector_data);
94✔
765
        const std::string& name = allowed_bd_ids.at(bndry_id);
94✔
766
        result[py::str(name)] = std::move(np_vector);
188✔
767
      }
94✔
768

769
      return result;
31✔
770
    },
62✔
771
    R"(
772
    Compute leakage for the problem.
773

774
    Parameters
775
    ----------
776
    bnd_names : List[str]
777
        A list of boundary names for which leakage should be computed.
778

779
    Returns
780
    -------
781
    Dict[str, numpy.ndarray]
782
        A dictionary mapping boundary names to group-wise leakage vectors.
783
        Each array contains the outgoing angular flux (per group) integrated over
784
        the corresponding boundary surface.
785

786
    Raises
787
    ------
788
    RuntimeError
789
        If `save_angular_flux` option was not enabled during problem setup.
790

791
    ValueError
792
        If one or more boundary ids are not present on the current mesh.
793
    )",
794
    py::arg("bnd_names")
495✔
795
  );
796

797
  // discrete ordinates curvilinear problem
798
  auto do_curvilinear_problem = py::class_<DiscreteOrdinatesCurvilinearProblem,
495✔
799
                                           std::shared_ptr<DiscreteOrdinatesCurvilinearProblem>,
800
                                           DiscreteOrdinatesProblem>(
801
    slv,
802
    "DiscreteOrdinatesCurvilinearProblem",
803
    R"(
804
    Base class for discrete ordinates problems in curvilinear geometry.
805

806
    Wrapper of :cpp:class:`opensn::DiscreteOrdinatesCurvilinearProblem`.
807
    )"
808
  );
495✔
809
  do_curvilinear_problem.def(
990✔
810
    py::init(
495✔
811
      [](py::kwargs& params)
8✔
812
      {
813
        return DiscreteOrdinatesCurvilinearProblem::Create(kwargs_to_param_block(params));
8✔
814
      }
815
    ),
816
    R"(
817
    Construct a discrete ordinates problem for curvilinear geometry.
818

819
    Warnings
820
    --------
821
       DiscreteOrdinatesCurvilinearProblem is **experimental** and should be used with caution!
822

823
    Parameters
824
    ----------
825
    mesh : MeshContinuum
826
        The spatial mesh.
827
    coord_system : int
828
        Coordinate system to use. Must be set to 2 (cylindrical coordinates).
829
    num_groups : int
830
        The total number of energy groups.
831
    groupsets : list of dict
832
        A list of input parameter blocks, each block provides the iterative properties for a
833
        groupset. Each dictionary supports:
834
          - groups_from_to: List[int] (required)
835
              Two-entry list with the first and last group id for the groupset, e.g. ``[0, 3]``.
836
          - angular_quadrature: pyopensn.aquad.AngularQuadrature, optional
837
              Handle to an angular quadrature.
838
          - angle_aggregation_type: {'polar', 'single', 'azimuthal'}, default='polar'
839
              Angle aggregation method to use during sweeping.
840
          - angle_aggregation_num_subsets: int, default=1
841
              Number of angle subsets used for aggregation.
842
          - inner_linear_method: {'classic_richardson', 'petsc_richardson',
843
            'petsc_gmres', 'petsc_bicgstab'}, default='petsc_richardson'
844
              Iterative method used for inner linear solves.
845
          - l_abs_tol: float, default=1.0e-6
846
              Inner linear solver absolute residual tolerance.
847
          - l_max_its: int, default=200
848
              Inner linear solver maximum iterations.
849
          - gmres_restart_interval: int, default=30
850
              GMRES restart interval, if GMRES is used.
851
          - allow_cycles: bool, default=True
852
              Whether cyclic dependencies are allowed in sweeps.
853
          - apply_wgdsa: bool, default=False
854
              Enable within-group DSA for this groupset.
855
          - wgdsa_l_abs_tol: float, default=1.0e-4
856
              WGDSA linear absolute tolerance.
857
          - wgdsa_l_max_its: int, default=30
858
              WGDSA maximum iterations.
859
          - wgdsa_verbose: bool, default=False
860
              Verbose WGDSA output.
861
          - wgdsa_petsc_options: str, default=''
862
              PETSc options string for the WGDSA solver.
863
          - apply_tgdsa: bool, default=False
864
              Enable two-grid DSA for this groupset.
865
          - tgdsa_l_abs_tol: float, default=1.0e-4
866
              TGDSA linear absolute tolerance.
867
          - tgdsa_l_max_its: int, default=30
868
              TGDSA maximum iterations.
869
          - tgdsa_verbose: bool, default=False
870
              Verbose TGDSA output.
871
          - tgdsa_petsc_options: str, default=''
872
    xs_map : list of dict
873
        A list of mappings from block ids to cross-section definitions. Each dictionary supports:
874
          - block_ids: List[int] (required)
875
              Mesh block IDs to associate with the cross section.
876
          - xs: pyopensn.xs.MultiGroupXS (required)
877
              Cross-section object to assign to the specified blocks.
878
    boundary_conditions: List[Dict], default=[]
879
        A list containing tables for each boundary specification. Each dictionary supports:
880
          - name: str (required)
881
              Boundary name that identifies the specific boundary.
882
          - type: {'vacuum', 'isotropic', 'reflecting', 'arbitrary'} (required)
883
              Boundary type specification.
884
          - group_strength: List[float], optional
885
              Required when ``type='isotropic'``. Isotropic strength per group.
886
          - function: AngularFluxFunction, optional
887
              Required when ``type='arbitrary'``. Callable that returns incoming angular flux.
888
    point_sources: List[pyopensn.source.PointSource], default=[]
889
        A list of point sources.
890
    volumetric_sources: List[pyopensn.source.VolumetricSource], default=[]
891
        A list of volumetric sources.
892
    options : dict, optional
893
        A block of optional configuration parameters. Each dictionary supports the same keys as
894
        :meth:`LBSProblem.SetOptions`, including:
895
          - max_mpi_message_size: int, default=32768
896
          - restart_writes_enabled: bool, default=False
897
          - write_delayed_psi_to_restart: bool, default=True
898
          - read_restart_path: str, default=''
899
          - write_restart_path: str, default=''
900
          - write_restart_time_interval: int, default=0
901
          - use_precursors: bool, default=False
902
          - use_source_moments: bool, default=False
903
          - save_angular_flux: bool, default=False
904
          - verbose_inner_iterations: bool, default=True
905
          - verbose_outer_iterations: bool, default=True
906
          - max_ags_iterations: int, default=100
907
          - ags_tolerance: float, default=1.0e-6
908
          - ags_convergence_check: {'l2', 'pointwise'}, default='l2'
909
          - verbose_ags_iterations: bool, default=True
910
          - power_field_function_on: bool, default=False
911
          - power_default_kappa: float, default=3.20435e-11
912
          - power_normalization: float, default=-1.0
913
          - field_function_prefix_option: {'prefix', 'solver_name'}, default='prefix'
914
          - field_function_prefix: str, default=''
915
    sweep_type : str, optional
916
        The sweep type to use. Must be one of `AAH` or `CBC`. Defaults to `AAH`.
917
    time_dependent : bool, default=False
918
        Enable time-dependent sweeps. Currently only supported with ``sweep_type="AAH"``.
919
    )"
920
  );
921
}
495✔
922

923
// Wrap steady-state solver
924
void
925
WrapSteadyState(py::module& slv)
495✔
926
{
927
  // clang-format off
928
  // steady state solver
929
  auto steady_state_solver = py::class_<SteadyStateSourceSolver, std::shared_ptr<SteadyStateSourceSolver>,
495✔
930
                                        Solver>(
931
    slv,
932
    "SteadyStateSourceSolver",
933
    R"(
934
    Steady state solver.
935

936
    Wrapper of :cpp:class:`opensn::SteadyStateSourceSolver`.
937
    )"
938
  );
495✔
939
  steady_state_solver.def(
990✔
940
    py::init(
495✔
941
      [](py::kwargs& params)
265✔
942
      {
943
        return SteadyStateSourceSolver::Create(kwargs_to_param_block(params));
265✔
944
      }
945
    ),
946
    R"(
947
    Construct a steady state solver.
948

949
    Parameters
950
    ----------
951
    pyopensn.solver.LBSProblem : LBSProblem
952
        Existing LBSProblem instance.
953
    )"
954
  );
955
  // clang-format on
956
}
495✔
957

958
// Wrap time-dependent solver
959
void
960
WrapTimeDependent(py::module& slv)
495✔
961
{
962
  // clang-format off
963
  auto time_dependent_solver =
495✔
964
    py::class_<TimeDependentSourceSolver, std::shared_ptr<TimeDependentSourceSolver>, Solver>(
965
      slv,
966
      "TimeDependentSourceSolver",
967
      R"(
968
      Time dependent solver.
969

970
      Wrapper of :cpp:class:`opensn::TimeDependentSourceSolver`.
971
      )"
972
    );
495✔
973
  time_dependent_solver.def(
990✔
974
    py::init(
495✔
975
      [](py::kwargs& params)
40✔
976
      {
977
        return TimeDependentSourceSolver::Create(kwargs_to_param_block(params));
40✔
978
      }
979
    ),
980
    R"(
981
    Construct a time dependent solver.
982

983
    Parameters
984
    ----------
985
    pyopensn.solver.LBSProblem : LBSProblem
986
        Existing LBSProblem instance.
987
    dt : float, optional, default=1.0
988
        Time step size used during the simulation.
989
    stop_time : float, optional, default=1.0
990
        Simulation end time.
991
    )"
992
  );
993
  time_dependent_solver.def(
495✔
994
    "Advance",
995
    &TimeDependentSourceSolver::Advance,
495✔
996
    R"(
997
    Advance the solver by a single timestep.
998

999
    This method uses the configured `dt` and `theta` values and will return
1000
    immediately if the stop time has already been reached. Calling it
1001
    repeatedly allows users to write custom python time loops.
1002
    )");
1003
  time_dependent_solver.def(
495✔
1004
    "SetTimeStep",
1005
    &TimeDependentSourceSolver::SetTimeStep,
495✔
1006
    R"(
1007
    Set the timestep size used by :meth:`Advance`.
1008

1009
    Parameters
1010
    ----------
1011
    dt : float
1012
        New timestep size.
1013
    )");
1014
  time_dependent_solver.def(
495✔
1015
    "SetTheta",
1016
    &TimeDependentSourceSolver::SetTheta,
495✔
1017
    R"(
1018
    Set the theta parameter used by :meth:`Advance`.
1019

1020
    Parameters
1021
    ----------
1022
    theta : float
1023
        Theta value between 0 and 1.
1024
    )");
1025
  time_dependent_solver.def(
495✔
1026
    "SetPreAdvanceCallback",
1027
    static_cast<void (TimeDependentSourceSolver::*)(std::function<void()>)>(
495✔
1028
      &TimeDependentSourceSolver::SetPreAdvanceCallback),
1029
    R"(
1030
    Register a callback that runs before each call to :meth:`Advance`.
1031

1032
    Parameters
1033
    ----------
1034
    callback : Optional[Callable[[], None]]
1035
        Function invoked before the solver advances a timestep. Pass None to clear.
1036
    )");
1037
  time_dependent_solver.def(
495✔
1038
    "SetPreAdvanceCallback",
1039
    static_cast<void (TimeDependentSourceSolver::*)(std::nullptr_t)>(
495✔
1040
      &TimeDependentSourceSolver::SetPreAdvanceCallback),
1041
    "Clear the PreAdvance callback by passing None.");
1042
  time_dependent_solver.def(
495✔
1043
    "SetPostAdvanceCallback",
1044
    static_cast<void (TimeDependentSourceSolver::*)(std::function<void()>)>(
495✔
1045
      &TimeDependentSourceSolver::SetPostAdvanceCallback),
1046
    R"(
1047
    Register a callback that runs after each call to :meth:`Advance`.
1048

1049
    Parameters
1050
    ----------
1051
    callback : Optional[Callable[[], None]]
1052
        Function invoked after the solver advances a timestep. Pass None to clear.
1053
    )");
1054
  time_dependent_solver.def(
495✔
1055
    "SetPostAdvanceCallback",
1056
    static_cast<void (TimeDependentSourceSolver::*)(std::nullptr_t)>(
495✔
1057
      &TimeDependentSourceSolver::SetPostAdvanceCallback),
1058
    "Clear the PostAdvance callback by passing None.");
1059
  slv.attr("BackwardEuler") = 1.0;
495✔
1060
  slv.attr("CrankNicolson") = 0.5;
990✔
1061
  // clang-format on
1062
}
495✔
1063

1064
// Wrap non-linear k-eigen solver
1065
void
1066
WrapNLKEigen(py::module& slv)
495✔
1067
{
1068
  // clang-format off
1069
  // non-linear k-eigen solver
1070
  auto non_linear_k_eigen_solver = py::class_<NonLinearKEigenSolver, std::shared_ptr<NonLinearKEigenSolver>,
495✔
1071
                                              Solver>(
1072
    slv,
1073
    "NonLinearKEigenSolver",
1074
    R"(
1075
    Non-linear k-eigenvalue solver.
1076

1077
    Wrapper of :cpp:class:`opensn::NonLinearKEigenSolver`.
1078
    )"
1079
  );
495✔
1080
  non_linear_k_eigen_solver.def(
990✔
1081
    py::init(
495✔
1082
      [](py::kwargs& params)
28✔
1083
      {
1084
        return NonLinearKEigenSolver::Create(kwargs_to_param_block(params));
28✔
1085
      }
1086
        ),
1087
    R"(
1088
    Construct a non-linear k-eigenvalue solver.
1089

1090
    Parameters
1091
    ----------
1092
    lbs_problem: pyopensn.solver.LBSProblem
1093
        Existing LBSProblem instance.
1094
    nl_abs_tol: float, default=1.0e-8
1095
        Non-linear absolute tolerance.
1096
    nl_rel_tol: float, default=1.0e-8
1097
        Non-linear relative tolerance.
1098
    nl_sol_tol: float, default=1.0e-50
1099
        Non-linear solution tolerance.
1100
    nl_max_its: int, default=50
1101
        Non-linear algorithm maximum iterations.
1102
    l_abs_tol: float, default=1.0e-8
1103
        Linear absolute tolerance.
1104
    l_rel_tol: float, default=1.0e-8
1105
        Linear relative tolerance.
1106
    l_div_tol: float, default=1.0e6
1107
        Linear divergence tolerance.
1108
    l_max_its: int, default=50
1109
        Linear algorithm maximum iterations.
1110
    l_gmres_restart_intvl: int, default=30
1111
        GMRES restart interval.
1112
    l_gmres_breakdown_tol: float, default=1.0e6
1113
        GMRES breakdown tolerance.
1114
    reset_phi0: bool, default=True
1115
        If true, reinitializes scalar fluxes to 1.0.
1116
    num_initial_power_iterations: int, default=0
1117
        Number of initial power iterations before the non-linear solve.
1118
    )"
1119
  );
1120
  non_linear_k_eigen_solver.def(
495✔
1121
    "GetEigenvalue",
1122
    &NonLinearKEigenSolver::GetEigenvalue,
495✔
1123
    R"(
1124
    Return the current k‑eigenvalue.
1125
    )"
1126
  );
1127
  // clang-format on
1128
}
495✔
1129

1130
// Wrap power iteration solvers
1131
void
1132
WrapPIteration(py::module& slv)
495✔
1133
{
1134
  // clang-format off
1135
  // power iteration k-eigen solver
1136
  auto pi_k_eigen_solver = py::class_<PowerIterationKEigenSolver, std::shared_ptr<PowerIterationKEigenSolver>,
495✔
1137
                                      Solver>(
1138
    slv,
1139
    "PowerIterationKEigenSolver",
1140
    R"(
1141
    Power iteration k-eigenvalue solver.
1142

1143
    Wrapper of :cpp:class:`opensn::PowerIterationKEigenSolver`.
1144
    )"
1145
  );
495✔
1146
  pi_k_eigen_solver.def(
990✔
1147
    py::init(
495✔
1148
      [](py::kwargs& params)
35✔
1149
      {
1150
        return PowerIterationKEigenSolver::Create(kwargs_to_param_block(params));
35✔
1151
      }
1152
    ),
1153
    R"(
1154
    Construct a power iteration k-eigen solver.
1155

1156
    Parameters
1157
    ----------
1158
    problem: pyopensn.solver.LBSProblem
1159
        Existing DiscreteOrdinatesProblem instance.
1160
    acceleration: pyopensn.solver.DiscreteOrdinatesKEigenAcceleration
1161
        Optional DiscreteOrdinatesKEigenAcceleration instance for acceleration.
1162
    max_iters: int, default = 1000
1163
        Maximum power iterations allowed.
1164
    k_tol: float, default = 1.0e-10
1165
        Tolerance on the k-eigenvalue.
1166
    reset_solution: bool, default=True
1167
        If true, initialize flux moments to 1.0.
1168
    reset_phi0: bool, default=True
1169
        If true, reinitializes scalar fluxes to 1.0.
1170
    )"
1171
  );
1172
  pi_k_eigen_solver.def(
495✔
1173
    "GetEigenvalue",
1174
    &PowerIterationKEigenSolver::GetEigenvalue,
495✔
1175
    R"(
1176
    Return the current k‑eigenvalue.
1177
    )"
1178
  );
1179
  // clang-format on
1180
}
495✔
1181

1182
// Wrap LBS solver
1183
void
1184
WrapDiscreteOrdinatesKEigenAcceleration(py::module& slv)
495✔
1185
{
1186
  // clang-format off
1187
  // discrete ordinates k-eigen acceleration base
1188
  auto acceleration = py::class_<DiscreteOrdinatesKEigenAcceleration,
495✔
1189
                                 std::shared_ptr<DiscreteOrdinatesKEigenAcceleration>>(
1190
    slv,
1191
    "DiscreteOrdinatesKEigenAcceleration",
1192
    R"(
1193
    Base class for discrete ordinates k-eigenvalue acceleration methods.
1194

1195
    Wrapper of :cpp:class:`opensn::DiscreteOrdinatesKEigenAcceleration`.
1196
    )"
1197
  );
495✔
1198
  // SCDSA acceleration
1199
  auto scdsa_acceleration = py::class_<SCDSAAcceleration,
495✔
1200
                                       std::shared_ptr<SCDSAAcceleration>,
1201
                                       DiscreteOrdinatesKEigenAcceleration>(
1202
    slv,
1203
    "SCDSAAcceleration",
1204
    R"(
1205
    Construct an SCDSA accelerator for the power iteration k-eigenvalue solver.
1206

1207
    Wrapper of :cpp:class:`opensn::SCDSAAcceleration`.
1208
    )"
1209
  );
495✔
1210
  scdsa_acceleration.def(
990✔
1211
    py::init(
495✔
1212
      [](py::kwargs& params)
8✔
1213
      {
1214
        return SCDSAAcceleration::Create(kwargs_to_param_block(params));
8✔
1215
      }
1216
    ),
1217
    R"(
1218
    SCDSA acceleration for the power iteration k-eigenvalue solver.
1219

1220
    Parameters
1221
    ----------
1222
    problem: pyopensn.solver.LBSProblem
1223
        Existing DiscreteOrdinatesProblem instance.
1224
    l_abs_tol: float, defauilt=1.0e-10
1225
        Absolute residual tolerance.
1226
    max_iters: int, default=100
1227
        Maximum allowable iterations.
1228
    verbose: bool, default=False
1229
        If true, enables verbose output.
1230
    petsc_options: str, default="ssss"
1231
        Additional PETSc options.
1232
    pi_max_its: int, default=50
1233
        Maximum allowable iterations for inner power iterations.
1234
    pi_k_tol: float, default=1.0e-10
1235
        k-eigenvalue tolerance for the inner power iterations.
1236
    sdm: str, default="pwld"
1237
        Spatial discretization method to use for the diffusion solver. Valid choices are:
1238
            - 'pwld' : Piecewise Linear Discontinuous
1239
            - 'pwlc' : Piecewise Linear Continuous
1240
    )"
1241
  );
1242
  // SMM acceleration
1243
  auto smm_acceleration = py::class_<SMMAcceleration,
495✔
1244
                                     std::shared_ptr<SMMAcceleration>,
1245
                                     DiscreteOrdinatesKEigenAcceleration>(
1246
    slv,
1247
    "SMMAcceleration",
1248
    R"(
1249
    Construct an SMM accelerator for the power iteration k-eigenvalue solver.
1250

1251
    Wrapper of :cpp:class:`opensn::SMMAcceleration`.
1252
    )"
1253
  );
495✔
1254
  smm_acceleration.def(
990✔
1255
    py::init(
495✔
1256
      [](py::kwargs& params)
4✔
1257
      {
1258
        return SMMAcceleration::Create(kwargs_to_param_block(params));
4✔
1259
      }
1260
    ),
1261
    R"(
1262
    SMM acceleration for the power iteration k-eigenvalue solver.
1263

1264
    Warnings
1265
    --------
1266
       SMM acceleration is **experimental** and should be used with caution!
1267
       SMM accleration only supports problems with isotropic scattering.
1268

1269
    Parameters
1270
    ----------
1271
    problem: pyopensn.solver.LBSProblem
1272
        Existing DiscreteOrdinatesProblem instance.
1273
    l_abs_tol: float, defauilt=1.0e-10
1274
        Absolute residual tolerance.
1275
    max_iters: int, default=100
1276
        Maximum allowable iterations.
1277
    verbose: bool, default=False
1278
        If true, enables verbose output.
1279
    petsc_options: str, default="ssss"
1280
        Additional PETSc options.
1281
    pi_max_its: int, default=50
1282
        Maximum allowable iterations for inner power iterations.
1283
    pi_k_tol: float, default=1.0e-10
1284
        k-eigenvalue tolerance for the inner power iterations.
1285
    sdm: str, default="pwld"
1286
        Spatial discretization method to use for the diffusion solver. Valid choices are:
1287
            - 'pwld' : Piecewise Linear Discontinuous
1288
            - 'pwlc' : Piecewise Linear Continuous
1289
    )"
1290
  );
1291
  // clang-format on
1292
}
495✔
1293

1294
// Wrap the solver components of OpenSn
1295
void
1296
py_solver(py::module& pyopensn)
62✔
1297
{
1298
  py::module slv = pyopensn.def_submodule("solver", "Solver module.");
62✔
1299
  WrapProblem(slv);
62✔
1300
  WrapSolver(slv);
62✔
1301
  WrapLBS(slv);
62✔
1302
  WrapSteadyState(slv);
62✔
1303
  WrapTimeDependent(slv);
62✔
1304
  WrapNLKEigen(slv);
62✔
1305
  WrapDiscreteOrdinatesKEigenAcceleration(slv);
62✔
1306
  WrapPIteration(slv);
62✔
1307
}
62✔
1308

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