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

Open-Sn / opensn / 16821314466

07 Aug 2025 04:57PM UTC coverage: 74.527% (+1.0%) from 73.488%
16821314466

push

github

web-flow
Merge pull request #708 from wdhawkins/curvilinear_warning

Adding experimental warning to curvilinear solver

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

250 existing lines in 17 files now uncovered.

17543 of 23539 relevant lines covered (74.53%)

44793362.81 hits per line

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

87.5
/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 "framework/physics/solver.h"
8
#include "modules/linear_boltzmann_solvers/discrete_ordinates_problem/acceleration/discrete_ordinates_keigen_acceleration.h"
9
#include "modules/linear_boltzmann_solvers/discrete_ordinates_problem/acceleration/scdsa_acceleration.h"
10
#include "modules/linear_boltzmann_solvers/discrete_ordinates_problem/acceleration/smm_acceleration.h"
11
#include "modules/linear_boltzmann_solvers/discrete_ordinates_curvilinear_problem/discrete_ordinates_curvilinear_problem.h"
12
#include "modules/linear_boltzmann_solvers/discrete_ordinates_problem/discrete_ordinates_problem.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 <pybind11/numpy.h>
20
#include <algorithm>
21
#include <cstddef>
22
#include <cstdint>
23
#include <map>
24
#include <memory>
25
#include <string>
26
#include <vector>
27

28
namespace opensn
29
{
30

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

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

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

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

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

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

119
    Wrapper of :cpp:class:`opensn::LBSProblem`.
120
    )"
121
  );
380✔
122
  lbs_problem.def(
380✔
123
    "GetScalarFieldFunctionList",
124
    [](LBSProblem& self, bool only_scalar_flux)
380✔
125
    {
126
      py::list field_function_list_per_group;
160✔
127
      for (std::size_t group = 0; group < self.GetNumGroups(); group++)
11,658✔
128
      {
129
        if (only_scalar_flux)
11,498✔
130
        {
131
          std::size_t ff_index = self.MapPhiFieldFunction(group, 0);
8,246✔
132
          field_function_list_per_group.append(self.GetFieldFunctions()[ff_index]);
8,246✔
133
        }
134
        else
135
        {
136
          py::list field_function_list_per_moment;
3,252✔
137
          for (std::size_t moment = 0; moment < self.GetNumMoments(); moment++)
15,828✔
138
          {
139
            std::size_t ff_index = self.MapPhiFieldFunction(group, moment);
12,576✔
140
            field_function_list_per_moment.append(self.GetFieldFunctions()[ff_index]);
12,576✔
141
          }
142
          field_function_list_per_group.append(field_function_list_per_moment);
3,252✔
143
        }
3,252✔
144
      }
145
      return field_function_list_per_group;
160✔
UNCOV
146
    },
×
147
    R"(
148
    Return field functions grouped by energy group and, optionally, by moment.
149

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

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

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

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

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

279
    Parameters
280
    ----------
281
    scalar_flux_iterate : {'old', 'new'}
282
        Specifies which scalar flux vector to use in the calculation.
283
            - 'old': Use the previous scalar flux iterate.
284
            - 'new': Use the current scalar flux iterate.
285

286
    Returns
287
    -------
288
    float
289
        The total fission rate.
290

291
    Raises
292
    ------
293
    ValueError
294
        If `scalar_flux_iterate` is not 'old' or 'new'.
295
    )",
296
    py::arg("scalar_flux_iterate")
380✔
297
  );
298
  lbs_problem.def(
380✔
299
    "WriteFluxMoments",
300
    [](LBSProblem& self, const std::string& file_base)
392✔
301
    {
302
      LBSSolverIO::WriteFluxMoments(self, file_base);
12✔
303
    },
304
    R"(
305
    Write flux moments to file.
306

307
    Parameters
308
    ----------
309
    file_base: str
310
        File basename.
311
    )",
312
    py::arg("file_base")
380✔
313
  );
314
  lbs_problem.def(
380✔
315
    "CreateAndWriteSourceMoments",
316
    [](LBSProblem& self, const std::string& file_base)
384✔
317
    {
318
      std::vector<double> source_moments = self.MakeSourceMomentsFromPhi();
4✔
319
      LBSSolverIO::WriteFluxMoments(self, file_base, source_moments);
4✔
320
    },
4✔
321
    R"(
322
    Write source moments from latest flux iterate to file.
323

324
    Parameters
325
    ----------
326
    file_base: str
327
        File basename.
328
    )",
329
    py::arg("file_base")
380✔
330
  );
331
  lbs_problem.def(
380✔
332
    "ReadFluxMomentsAndMakeSourceMoments",
333
    [](LBSProblem& self, const std::string& file_base, bool single_file_flag)
380✔
334
    {
UNCOV
335
      LBSSolverIO::ReadFluxMoments(self, file_base, single_file_flag, self.GetExtSrcMomentsLocal());
×
UNCOV
336
      log.Log() << "Making source moments from flux file.";
×
UNCOV
337
      std::vector<double>& temp_phi = self.GetPhiOldLocal();
×
UNCOV
338
      self.GetPhiOldLocal() = self.GetExtSrcMomentsLocal();
×
UNCOV
339
      self.GetExtSrcMomentsLocal() = self.MakeSourceMomentsFromPhi();
×
UNCOV
340
      self.GetPhiOldLocal() = temp_phi;
×
UNCOV
341
    },
×
342
    R"(
343
    Read flux moments and compute corresponding source moments.
344

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

364
    Parameters
365
    ----------
366
    file_base: str
367
        File basename.
368
    single_file_flag: bool
369
        True if all source moments are in a single file.
370
    )",
371
    py::arg("file_base"),
760✔
372
    py::arg("single_file_flag")
380✔
373
  );
374
  lbs_problem.def(
380✔
375
    "ReadFluxMoments",
376
    [](LBSProblem& self, const std::string& file_base, bool single_file_flag)
380✔
377
    {
UNCOV
378
      LBSSolverIO::ReadFluxMoments(self, file_base, single_file_flag);
×
379
    },
380
    R"(
381
    Read flux moment data.
382

383
    Parameters
384
    ----------
385
    file_base: str
386
        File basename.
387
    single_file_flag: bool
388
        True if all flux moments are in a single file.
389
    )",
390
    py::arg("file_base"),
760✔
391
    py::arg("single_file_flag")
380✔
392
  );
393
  lbs_problem.def(
380✔
394
    "WriteAngularFluxes",
395
    [](LBSProblem& self, const std::string& file_base)
384✔
396
    {
397
      LBSSolverIO::WriteAngularFluxes(self, file_base);
4✔
398
    },
399
    R"(
400
    Write angular flux data to file.
401

402
    Parameters
403
    ----------
404
    file_base: str
405
        File basename.
406
    )",
407
    py::arg("file_base")
380✔
408
  );
409
  lbs_problem.def(
380✔
410
    "ReadAngularFluxes",
411
    [](LBSProblem& self, const std::string& file_base)
384✔
412
    {
413
      LBSSolverIO::ReadAngularFluxes(self, file_base);
4✔
414
    },
415
    R"(
416
    Read angular fluxes from file.
417

418
    Parameters
419
    ----------
420
    file_base: str
421
        File basename.
422
    )",
423
    py::arg("file_base")
380✔
424
  );
425

426
  // discrete ordinate solver
427
  auto do_problem = py::class_<DiscreteOrdinatesProblem, std::shared_ptr<DiscreteOrdinatesProblem>,
380✔
428
                               LBSProblem>(
429
    slv,
430
    "DiscreteOrdinatesProblem",
431
    R"(
432
    Base class for discrete ordinates problems in Cartesian geometry.
433

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

438
    Wrapper of :cpp:class:`opensn::DiscreteOrdinatesProblem`.
439
    )"
440
  );
380✔
441
  do_problem.def(
760✔
442
    py::init(
380✔
443
      [](py::kwargs& params)
252✔
444
      {
445
        return DiscreteOrdinatesProblem::Create(kwargs_to_param_block(params));
252✔
446
      }
447
    ),
448
    R"(
449
    Construct a discrete ordinates problem with Cartesian geometry.
450

451
    Parameters
452
    ----------
453
    mesh : MeshContinuum
454
        The spatial mesh.
455
    num_groups : int
456
        The total number of energy groups.
457
    groupsets : List[Dict], default=[]
458
        A list of input parameter blocks, each block provides the iterative properties for a
459
        groupset.
460
    xs_map : List[Dict], default=[]
461
        A list of mappings from block ids to cross-section definitions.
462
    options : Dict, default={}
463
        A block of optional configuration parameters. See `SetOptions` for available settings.
464
    sweep_type : str, default="AAH"
465
        The sweep type to use. Must be one of `AAH` or `CBC`. Defaults to `AAH`.
466
    use_gpus : bool, default=False
467
        A flag specifying whether GPU acceleration is used for the sweep. Currently, only ``AAH`` is
468
        supported.
469
    )"
470
  );
471
  do_problem.def(
380✔
472
    "ComputeBalance",
473
    [](DiscreteOrdinatesProblem& self)
399✔
474
    {
475
      ComputeBalance(self);
19✔
476
    },
477
    R"(
478
    Compute and print particle balance for the problem.
479
    )"
480
  );
481
  do_problem.def(
380✔
482
    "ComputeLeakage",
483
    [](DiscreteOrdinatesProblem& self, py::list bnd_names)
399✔
484
    {
485
      // get the supported boundaries
486
      std::map<std::string, std::uint64_t> allowed_bd_names = LBSProblem::supported_boundary_names;
19✔
487
      std::map<std::uint64_t, std::string> allowed_bd_ids = LBSProblem::supported_boundary_ids;
19✔
488
      // get the boundaries to parse
489
      std::vector<std::uint64_t> bndry_ids;
19✔
490
      if (bnd_names.size() > 1)
19✔
491
      {
UNCOV
492
        for (py::handle name : bnd_names)
×
493
        {
UNCOV
494
          bndry_ids.push_back(allowed_bd_names.at(name.cast<std::string>()));
×
495
        }
496
      }
497
      else
498
      {
499
        bndry_ids = self.GetGrid()->GetUniqueBoundaryIDs();
57✔
500
      }
501
      // compute the leakage
502
      std::map<std::uint64_t, std::vector<double>> leakage = ComputeLeakage(self, bndry_ids);
19✔
503
      // convert result to native Python
504
      py::dict result;
19✔
505
      for (const auto& [bndry_id, gr_wise_leakage] : leakage)
57✔
506
      {
507
        py::array_t<double> np_vector = py::array_t<double>(gr_wise_leakage.size());
38✔
508
        py::buffer_info buffer = np_vector.request();
38✔
509
        double* np_vector_data = static_cast<double*>(buffer.ptr);
38✔
510
        std::copy(gr_wise_leakage.begin(), gr_wise_leakage.end(), np_vector_data);
38✔
511
        result[allowed_bd_ids.at(bndry_id).data()] = np_vector;
38✔
512
      }
38✔
513
      return result;
19✔
514
    },
19✔
515
    R"(
516
    Compute leakage for the problem.
517

518
    Parameters
519
    ----------
520
    bnd_names : List[str]
521
        A list of boundary names for which leakage should be computed.
522

523
    Returns
524
    -------
525
    Dict[str, numpy.ndarray]
526
        A dictionary mapping boundary names to group-wise leakage vectors.
527
        Each array contains the outgoing angular flux (per group) integrated over
528
        the corresponding boundary surface.
529

530
    Raises
531
    ------
532
    RuntimeError
533
        If `save_angular_flux` option was not enabled during problem setup.
534

535
    ValueError
536
        If one or more boundary ids are not present on the current mesh.
537
    )",
538
    py::arg("bnd_names")
380✔
539
  );
540

541
  // discrete ordinates curvilinear problem
542
  auto do_curvilinear_problem = py::class_<DiscreteOrdinatesCurvilinearProblem,
380✔
543
                                           std::shared_ptr<DiscreteOrdinatesCurvilinearProblem>,
544
                                           DiscreteOrdinatesProblem>(
545
    slv,
546
    "DiscreteOrdinatesCurvilinearProblem",
547
    R"(
548
    Base class for discrete ordinates problems in curvilinear geometry.
549

550
    Wrapper of :cpp:class:`opensn::DiscreteOrdinatesCurvilinearProblem`.
551
    )"
552
  );
380✔
553
  do_curvilinear_problem.def(
760✔
554
    py::init(
380✔
555
      [](py::kwargs& params)
8✔
556
      {
557
        return DiscreteOrdinatesCurvilinearProblem::Create(kwargs_to_param_block(params));
8✔
558
      }
559
        ),
560
    R"(
561
    .. warning::
562
       DiscreteOrdinatesCurvilinearProblem is **experimental** and should be used with caution!
563

564
    Construct a discrete ordinates problem for curvilinear geometry.
565

566
    Parameters
567
    ----------
568
    mesh : MeshContinuum
569
        The spatial mesh.
570
    coord_system : int
571
        Coordinate system to use. Must be set to 2 (cylindrical coordinates).
572
    num_groups : int
573
        The total number of energy groups.
574
    groupsets : list of dict
575
        A list of input parameter blocks, each block provides the iterative properties for a
576
        groupset.
577
    xs_map : list of dict
578
        A list of mappings from block ids to cross-section definitions.
579
    options : dict, optional
580
        A block of optional configuration parameters. See `SetOptions` for available settings.
581
    sweep_type : str, optional
582
        The sweep type to use. Must be one of `AAH` or `CBC`. Defaults to `AAH`.
583
    )"
584
  );
585
}
380✔
586

587
// Wrap steady-state solver
588
void
589
WrapSteadyState(py::module& slv)
380✔
590
{
591
  // clang-format off
592
  // steady state solver
593
  auto steady_state_solver = py::class_<SteadyStateSolver, std::shared_ptr<SteadyStateSolver>,
380✔
594
                                        Solver>(
595
    slv,
596
    "SteadyStateSolver",
597
    R"(
598
    Steady state solver.
599

600
    Wrapper of :cpp:class:`opensn::SteadyStateSolver`.
601
    )"
602
  );
380✔
603
  steady_state_solver.def(
760✔
604
    py::init(
380✔
605
      [](py::kwargs& params)
198✔
606
      {
607
        return SteadyStateSolver::Create(kwargs_to_param_block(params));
198✔
608
      }
609
    ),
610
    R"(
611
    Construct a steady state solver.
612

613
    Parameters
614
    ----------
615
    pyopensn.solver.LBSProblem : LBSProblem
616
        Existing LBSProblem instance.
617
    )"
618
  );
619
  // clang-format on
620
}
380✔
621

622
// Wrap non-linear k-eigen solver
623
void
624
WrapNLKEigen(py::module& slv)
380✔
625
{
626
  // clang-format off
627
  // non-linear k-eigen solver
628
  auto non_linear_k_eigen_solver = py::class_<NonLinearKEigenSolver, std::shared_ptr<NonLinearKEigenSolver>,
380✔
629
                                              Solver>(
630
    slv,
631
    "NonLinearKEigenSolver",
632
    R"(
633
    Non-linear k-eigenvalue solver.
634

635
    Wrapper of :cpp:class:`opensn::NonLinearKEigenSolver`.
636
    )"
637
  );
380✔
638
  non_linear_k_eigen_solver.def(
760✔
639
    py::init(
380✔
640
      [](py::kwargs& params)
28✔
641
      {
642
        return NonLinearKEigenSolver::Create(kwargs_to_param_block(params));
28✔
643
      }
644
        ),
645
    R"(
646
    Construct a non-linear k-eigenvalue solver.
647

648
    Parameters
649
    ----------
650
    lbs_problem: pyopensn.solver.LBSProblem
651
        Existing LBSProblem instance.
652
    nl_abs_tol: float, default=1.0e-8
653
        Non-linear absolute tolerance.
654
    nl_rel_tol: float, default=1.0e-8
655
        Non-linear relative tolerance.
656
    nl_sol_tol: float, default=1.0e-50
657
        Non-linear solution tolerance.
658
    nl_max_its: int, default=50
659
        Non-linear algorithm maximum iterations.
660
    l_abs_tol: float, default=1.0e-8
661
        Linear absolute tolerance.
662
    l_rel_tol: float, default=1.0e-8
663
        Linear relative tolerance.
664
    l_div_tol: float, default=1.0e6
665
        Linear divergence tolerance.
666
    l_max_its: int, default=50
667
        Linear algorithm maximum iterations.
668
    l_gmres_restart_intvl: int, default=30
669
        GMRES restart interval.
670
    l_gmres_breakdown_tol: float, default=1.0e6
671
        GMRES breakdown tolerance.
672
    reset_phi0: bool, default=True
673
        If true, reinitializes scalar fluxes to 1.0.
674
    num_initial_power_iterations: int, default=0
675
        Number of initial power iterations before the non-linear solve.
676
    )"
677
  );
678
  non_linear_k_eigen_solver.def(
380✔
679
    "GetEigenvalue",
680
    &NonLinearKEigenSolver::GetEigenvalue,
380✔
681
    R"(
682
    Return the current k‑eigenvalue.
683
    )"
684
  );
685
  // clang-format on
686
}
380✔
687

688
// Wrap power iteration solvers
689
void
690
WrapPIteration(py::module& slv)
380✔
691
{
692
  // clang-format off
693
  // power iteration k-eigen solver
694
  auto pi_k_eigen_solver = py::class_<PowerIterationKEigenSolver, std::shared_ptr<PowerIterationKEigenSolver>,
380✔
695
                                      Solver>(
696
    slv,
697
    "PowerIterationKEigenSolver",
698
    R"(
699
    Power iteration k-eigenvalue solver.
700

701
    Wrapper of :cpp:class:`opensn::PowerIterationKEigenSolver`.
702
    )"
703
  );
380✔
704
  pi_k_eigen_solver.def(
760✔
705
    py::init(
380✔
706
      [](py::kwargs& params)
34✔
707
      {
708
        return PowerIterationKEigenSolver::Create(kwargs_to_param_block(params));
34✔
709
      }
710
    ),
711
    R"(
712
    Construct a power iteration k-eigen solver.
713

714
    Parameters
715
    ----------
716
    problem: pyopensn.solver.LBSProblem
717
        Existing DiscreteOrdinatesProblem instance.
718
    acceleration: pyopensn.solver.DiscreteOrdinatesKEigenAcceleration
719
        Optional DiscreteOrdinatesKEigenAcceleration instance for acceleration.
720
    max_iters: int, default = 1000
721
        Maximum power iterations allowed.
722
    k_tol: float, default = 1.0e-10
723
        Tolerance on the k-eigenvalue.
724
    reset_solution: bool, default=True
725
        If true, initialize flux moments to 1.0.
726
    reset_phi0: bool, default=True
727
        If true, reinitializes scalar fluxes to 1.0.
728
    )"
729
  );
730
  pi_k_eigen_solver.def(
380✔
731
    "GetEigenvalue",
732
    &PowerIterationKEigenSolver::GetEigenvalue,
380✔
733
    R"(
734
    Return the current k‑eigenvalue.
735
    )"
736
  );
737
  // clang-format on
738
}
380✔
739

740
// Wrap LBS solver
741
void
742
WrapDiscreteOrdinatesKEigenAcceleration(py::module& slv)
380✔
743
{
744
  // clang-format off
745
  // DiscreteOrdinatesKEigenAcceleration base
746
  auto acceleration = py::class_<DiscreteOrdinatesKEigenAcceleration,
380✔
747
                                     std::shared_ptr<DiscreteOrdinatesKEigenAcceleration>>(
748
    slv,
749
    "LBSAccelertion",
750
    R"(
751
    Base class for LBS acceleration methods.
752

753
    Wrapper of :cpp:class:`opensn::DiscreteOrdinatesKEigenAcceleration`.
754
    )"
755
  );
380✔
756

757
  // SCDSA acceleration
758
  auto scdsa_acceleration = py::class_<SCDSAAcceleration,
380✔
759
                                       std::shared_ptr<SCDSAAcceleration>,
760
                                       DiscreteOrdinatesKEigenAcceleration>(
761
    slv,
762
    "SCDSAAcceleration",
763
    R"(
764
    SCDSA acceleration scheme to be used the power iteration k-eigenvalue solver.
765

766
    Wrapper of :cpp:class:`opensn::SCDSAAcceleration`.
767
    )"
768
  );
380✔
769
  scdsa_acceleration.def(
760✔
770
    py::init(
380✔
771
      [](py::kwargs& params)
8✔
772
      {
773
        return SCDSAAcceleration::Create(kwargs_to_param_block(params));
8✔
774
      }
775
    ),
776
    R"(
777
    Construct a SCDSA acceleration solver for LBS.
778

779
    Parameters
780
    ----------
781
    problem: pyopensn.solver.LBSProblem
782
        Existing DiscreteOrdinatesProblem instance.
783
    l_abs_tol: float, defauilt=1.0e-10
784
        Absolute residual tolerance.
785
    max_iters: int, default=100
786
        Maximum allowable iterations.
787
    verbose: bool, default=False
788
        If true, enables verbose output.
789
    petsc_options: str, default="ssss"
790
        Additional PETSc options.
791
    pi_max_its: int, default=50
792
        Maximum allowable iterations for inner power iterations.
793
    pi_k_tol: float, default=1.0e-10
794
        k-eigenvalue tolerance for the inner power iterations.
795
    sdm: str, default="pwld"
796
        Spatial discretization method to use for the diffusion solver. Valid choices are:
797
            - 'pwld' : Piecewise Linear Discontinuous
798
            - 'pwlc' : Piecewise Linear Continuous
799
    )"
800
  );
801

802
  // SMM acceleration
803
  auto smm_acceleration = py::class_<SMMAcceleration,
380✔
804
                                     std::shared_ptr<SMMAcceleration>,
805
                                     DiscreteOrdinatesKEigenAcceleration>(
806
    slv,
807
    "SMMAcceleration",
808
    R"(
809
    SCDSA acceleration scheme to be used the power iteration k-eigenvalue solver.
810

811
    Wrapper of :cpp:class:`opensn::SMMAcceleration`.
812
    )"
813
  );
380✔
814
  smm_acceleration.def(
760✔
815
    py::init(
380✔
816
      [](py::kwargs& params)
4✔
817
      {
818
        return SMMAcceleration::Create(kwargs_to_param_block(params));
4✔
819
      }
820
    ),
821
    R"(
822
    Construct a SMM acceleration solver for LBS.
823

824
    Parameters
825
    ----------
826
    problem: pyopensn.solver.LBSProblem
827
        Existing DiscreteOrdinatesProblem instance.
828
    l_abs_tol: float, defauilt=1.0e-10
829
        Absolute residual tolerance.
830
    max_iters: int, default=100
831
        Maximum allowable iterations.
832
    verbose: bool, default=False
833
        If true, enables verbose output.
834
    petsc_options: str, default="ssss"
835
        Additional PETSc options.
836
    pi_max_its: int, default=50
837
        Maximum allowable iterations for inner power iterations.
838
    pi_k_tol: float, default=1.0e-10
839
        k-eigenvalue tolerance for the inner power iterations.
840
    sdm: str, default="pwld"
841
        Spatial discretization method to use for the diffusion solver. Valid choices are:
842
            - 'pwld' : Piecewise Linear Discontinuous
843
            - 'pwlc' : Piecewise Linear Continuous
844
    )"
845
  );
846
  // clang-format on
847
}
380✔
848

849
// Wrap the solver components of OpenSn
850
void
851
py_solver(py::module& pyopensn)
34✔
852
{
853
  py::module slv = pyopensn.def_submodule("solver", "Solver module.");
34✔
854
  WrapProblem(slv);
34✔
855
  WrapSolver(slv);
34✔
856
  WrapLBS(slv);
34✔
857
  WrapSteadyState(slv);
34✔
858
  WrapNLKEigen(slv);
34✔
859
  WrapPIteration(slv);
34✔
860
  WrapDiscreteOrdinatesKEigenAcceleration(slv);
34✔
861
}
34✔
862

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