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

Open-Sn / opensn / 21308346735

23 Jan 2026 06:13PM UTC coverage: 74.265% (-0.1%) from 74.361%
21308346735

push

github

web-flow
Merge pull request #907 from andrsd/py-ctor-update

Updating python binders for quadratures

5 of 5 new or added lines in 1 file covered. (100.0%)

118 existing lines in 7 files now uncovered.

18783 of 25292 relevant lines covered (74.26%)

67392416.31 hits per line

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

81.05
/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)
499✔
35
{
36
  // clang-format off
37
  // problem base
38
  auto problem = py::class_<Problem, std::shared_ptr<Problem>>(
499✔
39
    slv,
40
    "Problem",
41
    R"(
42
    Base class for all problems.
43

44
    Wrapper of :cpp:class:`opensn::Problem`.
45
    )"
46
  );
499✔
47
  problem.def(
499✔
48
    "GetFieldFunctions",
49
    [](Problem& self)
499✔
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
}
499✔
69

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

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

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

115
    Wrapper of :cpp:class:`opensn::LBSProblem`.
116
    )"
117
  );
499✔
118
  lbs_problem.def(
499✔
119
    "GetScalarFieldFunctionList",
120
    [](LBSProblem& self, bool only_scalar_flux)
499✔
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
499✔
166
  );
167
  lbs_problem.def(
499✔
168
    "GetPowerFieldFunction",
169
    &LBSProblem::GetPowerFieldFunction,
499✔
170
    R"(
171
    Returns the power generation field function, if enabled.
172
    )"
173
  );
174
  lbs_problem.def(
499✔
175
    "SetOptions",
176
    [](LBSProblem& self, py::kwargs& params)
499✔
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(
499✔
235
    "ComputeFissionRate",
236
    [](LBSProblem& self, const std::string& scalar_flux_iterate)
499✔
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")
499✔
274
  );
275
  lbs_problem.def(
499✔
276
    "WriteFluxMoments",
277
    [](LBSProblem& self, const std::string& file_base)
531✔
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")
499✔
290
  );
291
  lbs_problem.def(
499✔
292
    "CreateAndWriteSourceMoments",
293
    [](LBSProblem& self, const std::string& file_base)
503✔
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")
499✔
307
  );
308
  lbs_problem.def(
499✔
309
    "ReadFluxMomentsAndMakeSourceMoments",
310
    [](LBSProblem& self, const std::string& file_base, bool single_file_flag)
499✔
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"),
998✔
330
    py::arg("single_file_flag")
499✔
331
  );
332
  lbs_problem.def(
499✔
333
    "ReadSourceMoments",
334
    [](LBSProblem& self, const std::string& file_base, bool single_file_flag)
503✔
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"),
998✔
349
    py::arg("single_file_flag")
499✔
350
  );
351
  lbs_problem.def(
499✔
352
    "ReadFluxMoments",
353
    [](LBSProblem& self, const std::string& file_base, bool single_file_flag)
499✔
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"),
998✔
368
    py::arg("single_file_flag")
499✔
369
  );
370
  lbs_problem.def(
499✔
371
    "WriteAngularFluxes",
372
    [](DiscreteOrdinatesProblem& self, const std::string& file_base)
503✔
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")
499✔
385
  );
386
  lbs_problem.def(
499✔
387
    "ReadAngularFluxes",
388
    [](DiscreteOrdinatesProblem& self, const std::string& file_base)
503✔
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")
499✔
401
  );
402
  lbs_problem.def(
499✔
403
    "SetPointSources",
404
    [](LBSProblem& self, py::kwargs& params)
499✔
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(
499✔
435
    "SetVolumetricSources",
436
    [](LBSProblem& self, py::kwargs& params)
519✔
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(
499✔
467
    "SetXSMap",
468
    [](LBSProblem& self, py::kwargs& params)
507✔
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
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(
499✔
507
    "SetAdjoint",
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>,
499✔
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
  );
499✔
529
  do_problem.def(
998✔
530
    py::init(
499✔
531
      [](py::kwargs& params)
366✔
532
      {
533
        return DiscreteOrdinatesProblem::Create(kwargs_to_param_block(params));
366✔
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(
499✔
640
    "SetOptions",
641
    [](DiscreteOrdinatesProblem& self, py::kwargs& params)
511✔
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(
499✔
657
    "SetBoundaryOptions",
658
    [](DiscreteOrdinatesProblem& self, py::kwargs& params)
499✔
659
    {
660
      for (auto [key, value] : params)
×
661
      {
662
        auto c_key = key.cast<std::string>();
×
663
        if (c_key == "clear_boundary_conditions")
×
664
          self.ClearBoundaries();
×
665
        else if (c_key == "boundary_conditions")
×
666
        {
667
          auto boundaries = value.cast<py::list>();
×
668
          for (auto boundary : boundaries)
×
669
          {
670
            InputParameters input = DiscreteOrdinatesProblem::GetBoundaryOptionsBlock();
×
671
            input.AssignParameters(pyobj_to_param_block("", boundary.cast<py::dict>()));
×
672
            self.SetBoundaryOptions(input);
×
673
          }
×
674
        }
×
675
        else
676
          throw std::runtime_error("Invalid argument provided to SetBoundaryOptions.\n");
×
677
      }
×
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(
499✔
699
    "GetPsi",
700
    [](DiscreteOrdinatesProblem& self)
507✔
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✔
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(
499✔
719
    "ComputeLeakage",
720
    [](DiscreteOrdinatesProblem& self, py::list bnd_names)
530✔
721
    {
722
      auto grid = self.GetGrid();
31✔
723
      // get the supported boundaries
724
      std::map<std::string, std::uint64_t> allowed_bd_names = grid->GetBoundaryNameMap();
31✔
725
      std::map<std::uint64_t, std::string> allowed_bd_ids = grid->GetBoundaryIDMap();
31✔
726
      // get the boundaries to parse, preserving user order
727
      std::vector<std::uint64_t> bndry_ids;
31✔
728
      if (bnd_names.size() > 1)
31✔
729
      {
730
        for (py::handle name : bnd_names)
144✔
731
        {
732
          auto sname = name.cast<std::string>();
88✔
733
          bndry_ids.push_back(allowed_bd_names.at(sname));
88✔
734
        }
88✔
735
      }
736
      else
737
      {
738
        bndry_ids = self.GetGrid()->GetUniqueBoundaryIDs();
9✔
739
      }
740
      // compute the leakage
741
      std::map<std::uint64_t, std::vector<double>> leakage = ComputeLeakage(self, bndry_ids);
31✔
742
      // convert result to native Python
743
      py::dict result;
31✔
744
      for (const auto& bndry_id : bndry_ids)
125✔
745
      {
746
        const auto it = leakage.find(bndry_id);
94✔
747
        if (it == leakage.end())
94✔
UNCOV
748
          continue;
×
749
        // construct numpy array and copy contents
750
        const auto& grp_wise_leakage = it->second;
94✔
751
        py::array_t<double> np_vector(py::ssize_t(grp_wise_leakage.size()));
94✔
752
        auto buffer = np_vector.request();
94✔
753
        auto *np_vector_data = static_cast<double*>(buffer.ptr);
94✔
754
        std::copy(grp_wise_leakage.begin(), grp_wise_leakage.end(), np_vector_data);
94✔
755
        const std::string& name = allowed_bd_ids.at(bndry_id);
94✔
756
        result[py::str(name)] = std::move(np_vector);
188✔
757
      }
94✔
758

759
      return result;
31✔
760
    },
62✔
761
    R"(
762
    Compute leakage for the problem.
763

764
    Parameters
765
    ----------
766
    bnd_names : List[str]
767
        A list of boundary names for which leakage should be computed.
768

769
    Returns
770
    -------
771
    Dict[str, numpy.ndarray]
772
        A dictionary mapping boundary names to group-wise leakage vectors.
773
        Each array contains the outgoing angular flux (per group) integrated over
774
        the corresponding boundary surface.
775

776
    Raises
777
    ------
778
    RuntimeError
779
        If `save_angular_flux` option was not enabled during problem setup.
780

781
    ValueError
782
        If one or more boundary ids are not present on the current mesh.
783
    )",
784
    py::arg("bnd_names")
499✔
785
  );
786

787
  // discrete ordinates curvilinear problem
788
  auto do_curvilinear_problem = py::class_<DiscreteOrdinatesCurvilinearProblem,
499✔
789
                                           std::shared_ptr<DiscreteOrdinatesCurvilinearProblem>,
790
                                           DiscreteOrdinatesProblem>(
791
    slv,
792
    "DiscreteOrdinatesCurvilinearProblem",
793
    R"(
794
    Base class for discrete ordinates problems in curvilinear geometry.
795

796
    Wrapper of :cpp:class:`opensn::DiscreteOrdinatesCurvilinearProblem`.
797
    )"
798
  );
499✔
799
  do_curvilinear_problem.def(
998✔
800
    py::init(
499✔
801
      [](py::kwargs& params)
8✔
802
      {
803
        return DiscreteOrdinatesCurvilinearProblem::Create(kwargs_to_param_block(params));
8✔
804
      }
805
    ),
806
    R"(
807
    Construct a discrete ordinates problem for curvilinear geometry.
808

809
    Warnings
810
    --------
811
       DiscreteOrdinatesCurvilinearProblem is **experimental** and should be used with caution!
812

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

913
// Wrap steady-state solver
914
void
915
WrapSteadyState(py::module& slv)
499✔
916
{
917
  // clang-format off
918
  // steady state solver
919
  auto steady_state_solver = py::class_<SteadyStateSourceSolver, std::shared_ptr<SteadyStateSourceSolver>,
499✔
920
                                        Solver>(
921
    slv,
922
    "SteadyStateSourceSolver",
923
    R"(
924
    Steady state solver.
925

926
    Wrapper of :cpp:class:`opensn::SteadyStateSourceSolver`.
927
    )"
928
  );
499✔
929
  steady_state_solver.def(
998✔
930
    py::init(
499✔
931
      [](py::kwargs& params)
273✔
932
      {
933
        return SteadyStateSourceSolver::Create(kwargs_to_param_block(params));
273✔
934
      }
935
    ),
936
    R"(
937
    Construct a steady state solver.
938

939
    Parameters
940
    ----------
941
    pyopensn.solver.LBSProblem : LBSProblem
942
        Existing LBSProblem instance.
943
    )"
944
  );
945
  // clang-format on
946
}
499✔
947

948
// Wrap time-dependent solver
949
void
950
WrapTimeDependent(py::module& slv)
499✔
951
{
952
  // clang-format off
953
  auto time_dependent_solver =
499✔
954
    py::class_<TimeDependentSourceSolver, std::shared_ptr<TimeDependentSourceSolver>, Solver>(
955
      slv,
956
      "TimeDependentSourceSolver",
957
      R"(
958
      Time dependent solver.
959

960
      Wrapper of :cpp:class:`opensn::TimeDependentSourceSolver`.
961
      )"
962
    );
499✔
963
  time_dependent_solver.def(
998✔
964
    py::init(
499✔
965
      [](py::kwargs& params)
40✔
966
      {
967
        return TimeDependentSourceSolver::Create(kwargs_to_param_block(params));
40✔
968
      }
969
    ),
970
    R"(
971
    Construct a time dependent solver.
972

973
    Parameters
974
    ----------
975
    pyopensn.solver.LBSProblem : LBSProblem
976
        Existing LBSProblem instance.
977
    dt : float, optional, default=1.0
978
        Time step size used during the simulation.
979
    stop_time : float, optional, default=1.0
980
        Simulation end time.
981
    )"
982
  );
983
  time_dependent_solver.def(
499✔
984
    "Advance",
985
    &TimeDependentSourceSolver::Advance,
499✔
986
    R"(
987
    Advance the solver by a single timestep.
988

989
    This method uses the configured `dt` and `theta` values and will return
990
    immediately if the stop time has already been reached. Calling it
991
    repeatedly allows users to write custom python time loops.
992
    )");
993
  time_dependent_solver.def(
499✔
994
    "SetTimeStep",
995
    &TimeDependentSourceSolver::SetTimeStep,
499✔
996
    R"(
997
    Set the timestep size used by :meth:`Advance`.
998

999
    Parameters
1000
    ----------
1001
    dt : float
1002
        New timestep size.
1003
    )");
1004
  time_dependent_solver.def(
499✔
1005
    "SetTheta",
1006
    &TimeDependentSourceSolver::SetTheta,
499✔
1007
    R"(
1008
    Set the theta parameter used by :meth:`Advance`.
1009

1010
    Parameters
1011
    ----------
1012
    theta : float
1013
        Theta value between 0 and 1.
1014
    )");
1015
  time_dependent_solver.def(
499✔
1016
    "SetPreAdvanceCallback",
1017
    static_cast<void (TimeDependentSourceSolver::*)(std::function<void()>)>(
499✔
1018
      &TimeDependentSourceSolver::SetPreAdvanceCallback),
1019
    R"(
1020
    Register a callback that runs before each call to :meth:`Advance`.
1021

1022
    Parameters
1023
    ----------
1024
    callback : Optional[Callable[[], None]]
1025
        Function invoked before the solver advances a timestep. Pass None to clear.
1026
    )");
1027
  time_dependent_solver.def(
499✔
1028
    "SetPreAdvanceCallback",
1029
    static_cast<void (TimeDependentSourceSolver::*)(std::nullptr_t)>(
499✔
1030
      &TimeDependentSourceSolver::SetPreAdvanceCallback),
1031
    "Clear the PreAdvance callback by passing None.");
1032
  time_dependent_solver.def(
499✔
1033
    "SetPostAdvanceCallback",
1034
    static_cast<void (TimeDependentSourceSolver::*)(std::function<void()>)>(
499✔
1035
      &TimeDependentSourceSolver::SetPostAdvanceCallback),
1036
    R"(
1037
    Register a callback that runs after each call to :meth:`Advance`.
1038

1039
    Parameters
1040
    ----------
1041
    callback : Optional[Callable[[], None]]
1042
        Function invoked after the solver advances a timestep. Pass None to clear.
1043
    )");
1044
  time_dependent_solver.def(
499✔
1045
    "SetPostAdvanceCallback",
1046
    static_cast<void (TimeDependentSourceSolver::*)(std::nullptr_t)>(
499✔
1047
      &TimeDependentSourceSolver::SetPostAdvanceCallback),
1048
    "Clear the PostAdvance callback by passing None.");
1049
  slv.attr("BackwardEuler") = 1.0;
499✔
1050
  slv.attr("CrankNicolson") = 0.5;
998✔
1051
  // clang-format on
1052
}
499✔
1053

1054
// Wrap non-linear k-eigen solver
1055
void
1056
WrapNLKEigen(py::module& slv)
499✔
1057
{
1058
  // clang-format off
1059
  // non-linear k-eigen solver
1060
  auto non_linear_k_eigen_solver = py::class_<NonLinearKEigenSolver, std::shared_ptr<NonLinearKEigenSolver>,
499✔
1061
                                              Solver>(
1062
    slv,
1063
    "NonLinearKEigenSolver",
1064
    R"(
1065
    Non-linear k-eigenvalue solver.
1066

1067
    Wrapper of :cpp:class:`opensn::NonLinearKEigenSolver`.
1068
    )"
1069
  );
499✔
1070
  non_linear_k_eigen_solver.def(
998✔
1071
    py::init(
499✔
1072
      [](py::kwargs& params)
28✔
1073
      {
1074
        return NonLinearKEigenSolver::Create(kwargs_to_param_block(params));
28✔
1075
      }
1076
        ),
1077
    R"(
1078
    Construct a non-linear k-eigenvalue solver.
1079

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

1120
// Wrap power iteration solvers
1121
void
1122
WrapPIteration(py::module& slv)
499✔
1123
{
1124
  // clang-format off
1125
  // power iteration k-eigen solver
1126
  auto pi_k_eigen_solver = py::class_<PowerIterationKEigenSolver, std::shared_ptr<PowerIterationKEigenSolver>,
499✔
1127
                                      Solver>(
1128
    slv,
1129
    "PowerIterationKEigenSolver",
1130
    R"(
1131
    Power iteration k-eigenvalue solver.
1132

1133
    Wrapper of :cpp:class:`opensn::PowerIterationKEigenSolver`.
1134
    )"
1135
  );
499✔
1136
  pi_k_eigen_solver.def(
998✔
1137
    py::init(
499✔
1138
      [](py::kwargs& params)
39✔
1139
      {
1140
        return PowerIterationKEigenSolver::Create(kwargs_to_param_block(params));
39✔
1141
      }
1142
    ),
1143
    R"(
1144
    Construct a power iteration k-eigen solver.
1145

1146
    Parameters
1147
    ----------
1148
    problem: pyopensn.solver.LBSProblem
1149
        Existing DiscreteOrdinatesProblem instance.
1150
    acceleration: pyopensn.solver.DiscreteOrdinatesKEigenAcceleration
1151
        Optional DiscreteOrdinatesKEigenAcceleration instance for acceleration.
1152
    max_iters: int, default = 1000
1153
        Maximum power iterations allowed.
1154
    k_tol: float, default = 1.0e-10
1155
        Tolerance on the k-eigenvalue.
1156
    reset_solution: bool, default=True
1157
        If true, initialize flux moments to 1.0.
1158
    reset_phi0: bool, default=True
1159
        If true, reinitializes scalar fluxes to 1.0.
1160
    )"
1161
  );
1162
  pi_k_eigen_solver.def(
499✔
1163
    "GetEigenvalue",
1164
    &PowerIterationKEigenSolver::GetEigenvalue,
499✔
1165
    R"(
1166
    Return the current k‑eigenvalue.
1167
    )"
1168
  );
1169
  // clang-format on
1170
}
499✔
1171

1172
// Wrap LBS solver
1173
void
1174
WrapDiscreteOrdinatesKEigenAcceleration(py::module& slv)
499✔
1175
{
1176
  // clang-format off
1177
  // discrete ordinates k-eigen acceleration base
1178
  auto acceleration = py::class_<DiscreteOrdinatesKEigenAcceleration,
499✔
1179
                                 std::shared_ptr<DiscreteOrdinatesKEigenAcceleration>>(
1180
    slv,
1181
    "DiscreteOrdinatesKEigenAcceleration",
1182
    R"(
1183
    Base class for discrete ordinates k-eigenvalue acceleration methods.
1184

1185
    Wrapper of :cpp:class:`opensn::DiscreteOrdinatesKEigenAcceleration`.
1186
    )"
1187
  );
499✔
1188
  // SCDSA acceleration
1189
  auto scdsa_acceleration = py::class_<SCDSAAcceleration,
499✔
1190
                                       std::shared_ptr<SCDSAAcceleration>,
1191
                                       DiscreteOrdinatesKEigenAcceleration>(
1192
    slv,
1193
    "SCDSAAcceleration",
1194
    R"(
1195
    Construct an SCDSA accelerator for the power iteration k-eigenvalue solver.
1196

1197
    Wrapper of :cpp:class:`opensn::SCDSAAcceleration`.
1198
    )"
1199
  );
499✔
1200
  scdsa_acceleration.def(
998✔
1201
    py::init(
499✔
1202
      [](py::kwargs& params)
8✔
1203
      {
1204
        return SCDSAAcceleration::Create(kwargs_to_param_block(params));
8✔
1205
      }
1206
    ),
1207
    R"(
1208
    SCDSA acceleration for the power iteration k-eigenvalue solver.
1209

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

1241
    Wrapper of :cpp:class:`opensn::SMMAcceleration`.
1242
    )"
1243
  );
499✔
1244
  smm_acceleration.def(
998✔
1245
    py::init(
499✔
1246
      [](py::kwargs& params)
4✔
1247
      {
1248
        return SMMAcceleration::Create(kwargs_to_param_block(params));
4✔
1249
      }
1250
    ),
1251
    R"(
1252
    SMM acceleration for the power iteration k-eigenvalue solver.
1253

1254
    Warnings
1255
    --------
1256
       SMM acceleration is **experimental** and should be used with caution!
1257
       SMM accleration only supports problems with isotropic scattering.
1258

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

1284
// Wrap the solver components of OpenSn
1285
void
1286
py_solver(py::module& pyopensn)
62✔
1287
{
1288
  py::module slv = pyopensn.def_submodule("solver", "Solver module.");
62✔
1289
  WrapProblem(slv);
62✔
1290
  WrapSolver(slv);
62✔
1291
  WrapLBS(slv);
62✔
1292
  WrapSteadyState(slv);
62✔
1293
  WrapTimeDependent(slv);
62✔
1294
  WrapNLKEigen(slv);
62✔
1295
  WrapDiscreteOrdinatesKEigenAcceleration(slv);
62✔
1296
  WrapPIteration(slv);
62✔
1297
}
62✔
1298

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