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

Open-Sn / opensn / 16766938693

05 Aug 2025 03:26PM UTC coverage: 73.386% (+0.1%) from 73.282%
16766938693

push

github

web-flow
Merge pull request #693 from andrsd/move-bnds

Moving sweep boundaries into `DiscreteOrdinatesProblem`

247 of 311 new or added lines in 19 files covered. (79.42%)

625 existing lines in 49 files now uncovered.

18320 of 24964 relevant lines covered (73.39%)

43106214.05 hits per line

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

86.39
/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/event_system/physics_event_publisher.h"
7
#include "framework/field_functions/field_function_grid_based.h"
8
#include "framework/physics/solver.h"
9
#include "modules/linear_boltzmann_solvers/discrete_ordinates_curvilinear_problem/discrete_ordinates_curvilinear_problem.h"
10
#include "modules/linear_boltzmann_solvers/discrete_ordinates_problem/discrete_ordinates_problem.h"
11
#include "modules/linear_boltzmann_solvers/solvers/steady_state_solver.h"
12
#include "modules/linear_boltzmann_solvers/solvers/nl_keigen_solver.h"
13
#include "modules/linear_boltzmann_solvers/solvers/pi_keigen_solver.h"
14
#include "modules/linear_boltzmann_solvers/solvers/pi_keigen_scdsa_solver.h"
15
#include "modules/linear_boltzmann_solvers/solvers/pi_keigen_smm_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
    {
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) {
×
UNCOV
53
        ff_list.append(ff);
×
54
      }
55
      return ff_list;
×
UNCOV
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 & self)
640✔
87
    {
88
      PhysicsEventPublisher::GetInstance().SolverInitialize(self);
260✔
89
    },
90
    "Initialize the solver."
91
  );
92
  solver.def(
380✔
93
    "Execute",
94
    [](Solver & self)
648✔
95
    {
96
      PhysicsEventPublisher::GetInstance().SolverExecute(self);
268✔
97
    },
98
    "Execute the solver."
99
  );
100
  solver.def(
380✔
101
    "Step",
102
    [](Solver & self)
380✔
103
    {
UNCOV
104
      PhysicsEventPublisher::GetInstance().SolverStep(self);
×
105
    },
106
    "Step the solver."
107
  );
108
  solver.def(
380✔
109
    "Advance",
110
    [](Solver & self)
380✔
111
    {
UNCOV
112
      PhysicsEventPublisher::GetInstance().SolverAdvance(self);
×
113
    },
114
    "Advance time values function."
115
  );
116
  // clang-format on
117
}
380✔
118

119
// Wrap LBS solver
120
void
121
WrapLBS(py::module& slv)
380✔
122
{
123
  // clang-format off
124
  // LBS problem
125
  auto lbs_problem = py::class_<LBSProblem, std::shared_ptr<LBSProblem>, Problem>(
380✔
126
    slv,
127
    "LBSProblem",
128
    R"(
129
    Base class for all linear Boltzmann problems.
130

131
    Wrapper of :cpp:class:`opensn::LBSProblem`.
132
    )"
133
  );
380✔
134
  lbs_problem.def(
380✔
135
    "GetScalarFieldFunctionList",
136
    [](LBSProblem& self, bool only_scalar_flux)
380✔
137
    {
138
      py::list field_function_list_per_group;
160✔
139
      for (std::size_t group = 0; group < self.GetNumGroups(); group++)
11,658✔
140
      {
141
        if (only_scalar_flux)
11,498✔
142
        {
143
          std::size_t ff_index = self.MapPhiFieldFunction(group, 0);
8,246✔
144
          field_function_list_per_group.append(self.GetFieldFunctions()[ff_index]);
8,246✔
145
        }
146
        else
147
        {
148
          py::list field_function_list_per_moment;
3,252✔
149
          for (std::size_t moment = 0; moment < self.GetNumMoments(); moment++)
15,828✔
150
          {
151
            std::size_t ff_index = self.MapPhiFieldFunction(group, moment);
12,576✔
152
            field_function_list_per_moment.append(self.GetFieldFunctions()[ff_index]);
12,576✔
153
          }
154
          field_function_list_per_group.append(field_function_list_per_moment);
3,252✔
155
        }
3,252✔
156
      }
157
      return field_function_list_per_group;
160✔
UNCOV
158
    },
×
159
    R"(
160
    Return field functions grouped by energy group and, optionally, by moment.
161

162
    Parameters
163
    ----------
164
    only_scalar_flux : bool, default=True
165
        If True, returns only the zeroth moment (scalar flux) field function for each group.
166
        The result is a flat list of field functions, one per group.
167

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

172
    Returns
173
    -------
174
    Union[List[pyopensn.fieldfunc.FieldFunctionGridBased], List[List[pyopensn.fieldfunc.FieldFunctionGridBased]]]
175
        The structure of the returned list depends on the `only_scalar_flux` flag.
176

177
    Notes
178
    -----
179
    The moment index varies more rapidly than the group index when `only_scalar_flux` is False.
180
    )",
181
    py::arg("only_scalar_flux") = true
380✔
182
  );
183
  lbs_problem.def(
380✔
184
    "GetPowerFieldFunction",
185
    &LBSProblem::GetPowerFieldFunction,
380✔
186
    R"(
187
    Returns the power generation field function, if enabled.
188
    )"
189
  );
190
  lbs_problem.def(
380✔
191
    "SetOptions",
192
    [](LBSProblem& self, py::kwargs& params)
400✔
193
    {
194
      InputParameters input = LBSProblem::GetOptionsBlock();
20✔
195
      input.AssignParameters(kwargs_to_param_block(params));
20✔
196
      self.SetOptions(input);
20✔
197
    },
20✔
198
    R"(
199
    Set problem options from a large list of parameters.
200

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

291
    Parameters
292
    ----------
293
    scalar_flux_iterate : {'old', 'new'}
294
        Specifies which scalar flux vector to use in the calculation.
295
            - 'old': Use the previous scalar flux iterate.
296
            - 'new': Use the current scalar flux iterate.
297

298
    Returns
299
    -------
300
    float
301
        The total fission rate.
302

303
    Raises
304
    ------
305
    ValueError
306
        If `scalar_flux_iterate` is not 'old' or 'new'.
307
    )",
308
    py::arg("scalar_flux_iterate")
380✔
309
  );
310
  lbs_problem.def(
380✔
311
    "WriteFluxMoments",
312
    [](LBSProblem& self, const std::string& file_base)
392✔
313
    {
314
      LBSSolverIO::WriteFluxMoments(self, file_base);
12✔
315
    },
316
    R"(
317
    Write flux moments to file.
318

319
    Parameters
320
    ----------
321
    file_base: str
322
        File basename.
323
    )",
324
    py::arg("file_base")
380✔
325
  );
326
  lbs_problem.def(
380✔
327
    "CreateAndWriteSourceMoments",
328
    [](LBSProblem& self, const std::string& file_base)
384✔
329
    {
330
      std::vector<double> source_moments = self.MakeSourceMomentsFromPhi();
4✔
331
      LBSSolverIO::WriteFluxMoments(self, file_base, source_moments);
4✔
332
    },
4✔
333
    R"(
334
    Write source moments from latest flux iterate to file.
335

336
    Parameters
337
    ----------
338
    file_base: str
339
        File basename.
340
    )",
341
    py::arg("file_base")
380✔
342
  );
343
  lbs_problem.def(
380✔
344
    "ReadFluxMomentsAndMakeSourceMoments",
345
    [](LBSProblem& self, const std::string& file_base, bool single_file_flag)
380✔
346
    {
347
      LBSSolverIO::ReadFluxMoments(self, file_base, single_file_flag, self.GetExtSrcMomentsLocal());
×
348
      log.Log() << "Making source moments from flux file.";
×
349
      std::vector<double>& temp_phi = self.GetPhiOldLocal();
×
350
      self.GetPhiOldLocal() = self.GetExtSrcMomentsLocal();
×
351
      self.GetExtSrcMomentsLocal() = self.MakeSourceMomentsFromPhi();
×
352
      self.GetPhiOldLocal() = temp_phi;
×
UNCOV
353
    },
×
354
    R"(
355
    Read flux moments and compute corresponding source moments.
356

357
    Parameters
358
    ----------
359
    file_base: str
360
        File basename.
361
    single_file_flag: bool
362
        True if all flux moments are in a single file.
363
    )",
364
    py::arg("file_base"),
760✔
365
    py::arg("single_file_flag")
380✔
366
  );
367
  lbs_problem.def(
380✔
368
    "ReadSourceMoments",
369
    [](LBSProblem& self, const std::string& file_base, bool single_file_flag)
384✔
370
    {
371
      LBSSolverIO::ReadFluxMoments(self, file_base, single_file_flag, self.GetExtSrcMomentsLocal());
4✔
372
    },
4✔
373
    R"(
374
    Read source moments from file.
375

376
    Parameters
377
    ----------
378
    file_base: str
379
        File basename.
380
    single_file_flag: bool
381
        True if all source moments are in a single file.
382
    )",
383
    py::arg("file_base"),
760✔
384
    py::arg("single_file_flag")
380✔
385
  );
386
  lbs_problem.def(
380✔
387
    "ReadFluxMoments",
388
    [](LBSProblem& self, const std::string& file_base, bool single_file_flag)
380✔
389
    {
UNCOV
390
      LBSSolverIO::ReadFluxMoments(self, file_base, single_file_flag);
×
391
    },
392
    R"(
393
    Read flux moment data.
394

395
    Parameters
396
    ----------
397
    file_base: str
398
        File basename.
399
    single_file_flag: bool
400
        True if all flux moments are in a single file.
401
    )",
402
    py::arg("file_base"),
760✔
403
    py::arg("single_file_flag")
380✔
404
  );
405
  lbs_problem.def(
380✔
406
    "WriteAngularFluxes",
407
    [](LBSProblem& self, const std::string& file_base)
384✔
408
    {
409
      LBSSolverIO::WriteAngularFluxes(self, file_base);
4✔
410
    },
411
    R"(
412
    Write angular flux data to file.
413

414
    Parameters
415
    ----------
416
    file_base: str
417
        File basename.
418
    )",
419
    py::arg("file_base")
380✔
420
  );
421
  lbs_problem.def(
380✔
422
    "ReadAngularFluxes",
423
    [](LBSProblem& self, const std::string& file_base)
384✔
424
    {
425
      LBSSolverIO::ReadAngularFluxes(self, file_base);
4✔
426
    },
427
    R"(
428
    Read angular fluxes from file.
429

430
    Parameters
431
    ----------
432
    file_base: str
433
        File basename.
434
    )",
435
    py::arg("file_base")
380✔
436
  );
437

438
  // discrete ordinate solver
439
  auto do_problem = py::class_<DiscreteOrdinatesProblem, std::shared_ptr<DiscreteOrdinatesProblem>,
380✔
440
                               LBSProblem>(
441
    slv,
442
    "DiscreteOrdinatesProblem",
443
    R"(
444
    Base class for discrete ordinates problems in Cartesian geometry.
445

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

450
    Wrapper of :cpp:class:`opensn::DiscreteOrdinatesProblem`.
451
    )"
452
  );
380✔
453
  do_problem.def(
760✔
454
    py::init(
380✔
455
      [](py::kwargs& params)
252✔
456
      {
457
        return DiscreteOrdinatesProblem::Create(kwargs_to_param_block(params));
252✔
458
      }
459
    ),
460
    R"(
461
    Construct a discrete ordinates problem with Cartesian geometry.
462

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

530
    Parameters
531
    ----------
532
    bnd_names : List[str]
533
        A list of boundary names for which leakage should be computed.
534

535
    Returns
536
    -------
537
    Dict[str, numpy.ndarray]
538
        A dictionary mapping boundary names to group-wise leakage vectors.
539
        Each array contains the outgoing angular flux (per group) integrated over
540
        the corresponding boundary surface.
541

542
    Raises
543
    ------
544
    RuntimeError
545
        If `save_angular_flux` option was not enabled during problem setup.
546

547
    ValueError
548
        If one or more boundary ids are not present on the current mesh.
549
    )",
550
    py::arg("bnd_names")
380✔
551
  );
552

553
  // discrete ordinates curvilinear problem
554
  auto do_curvilinear_problem = py::class_<DiscreteOrdinatesCurvilinearProblem,
380✔
555
                                           std::shared_ptr<DiscreteOrdinatesCurvilinearProblem>,
556
                                           DiscreteOrdinatesProblem>(
557
    slv,
558
    "DiscreteOrdinatesCurvilinearProblem",
559
    R"(
560
    Base class for discrete ordinates problems in curvilinear geometry.
561

562
    Wrapper of :cpp:class:`opensn::DiscreteOrdinatesCurvilinearProblem`.
563
    )"
564
  );
380✔
565
  do_curvilinear_problem.def(
760✔
566
    py::init(
380✔
567
      [](py::kwargs& params)
8✔
568
      {
569
        return DiscreteOrdinatesCurvilinearProblem::Create(kwargs_to_param_block(params));
8✔
570
      }
571
        ),
572
    R"(
573
    Construct a discrete ordinates problem for curvilinear geometry.
574

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

596
// Wrap steady-state solver
597
void
598
WrapSteadyState(py::module& slv)
380✔
599
{
600
  // clang-format off
601
  // steady state solver
602
  auto steady_state_solver = py::class_<SteadyStateSolver, std::shared_ptr<SteadyStateSolver>,
380✔
603
                                        Solver>(
604
    slv,
605
    "SteadyStateSolver",
606
    R"(
607
    Steady state solver.
608

609
    Wrapper of :cpp:class:`opensn::SteadyStateSolver`.
610
    )"
611
  );
380✔
612
  steady_state_solver.def(
760✔
613
    py::init(
380✔
614
      [](py::kwargs& params)
198✔
615
      {
616
        return SteadyStateSolver::Create(kwargs_to_param_block(params));
198✔
617
      }
618
    ),
619
    R"(
620
    Construct a steady state solver.
621

622
    Parameters
623
    ----------
624
    pyopensn.solver.LBSProblem : LBSProblem
625
        Existing LBSProblem instance.
626
    )"
627
  );
628
  // clang-format on
629
}
380✔
630

631
// Wrap non-linear k-eigen solver
632
void
633
WrapNLKEigen(py::module& slv)
380✔
634
{
635
  // clang-format off
636
  // non-linear k-eigen solver
637
  auto non_linear_k_eigen_solver = py::class_<NonLinearKEigenSolver, std::shared_ptr<NonLinearKEigenSolver>,
380✔
638
                                              Solver>(
639
    slv,
640
    "NonLinearKEigenSolver",
641
    R"(
642
    Non-linear k-eigenvalue solver.
643

644
    Wrapper of :cpp:class:`opensn::NonLinearKEigenSolver`.
645
    )"
646
  );
380✔
647
  non_linear_k_eigen_solver.def(
760✔
648
    py::init(
380✔
649
      [](py::kwargs& params)
28✔
650
      {
651
        return NonLinearKEigenSolver::Create(kwargs_to_param_block(params));
28✔
652
      }
653
        ),
654
    R"(
655
    Construct a non-linear k-eigenvalue solver.
656

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

697
// Wrap power iteration solvers
698
void
699
WrapPIteration(py::module& slv)
380✔
700
{
701
  // clang-format off
702
  // power iteration k-eigen solver
703
  auto pi_k_eigen_solver = py::class_<PowerIterationKEigenSolver, std::shared_ptr<PowerIterationKEigenSolver>,
380✔
704
                                      Solver>(
705
    slv,
706
    "PowerIterationKEigenSolver",
707
    R"(
708
    Power iteration k-eigenvalue solver.
709

710
    Wrapper of :cpp:class:`opensn::PowerIterationKEigenSolver`.
711
    )"
712
  );
380✔
713
  pi_k_eigen_solver.def(
760✔
714
    py::init(
380✔
715
      [](py::kwargs& params)
22✔
716
      {
717
        return PowerIterationKEigenSolver::Create(kwargs_to_param_block(params));
22✔
718
      }
719
    ),
720
    R"(
721
    Construct a power iteration k-eigen solver.
722

723
    Parameters
724
    ----------
725
    lbs_problem: pyopensn.solver.LBSProblem
726
        Existing LBSProblem instance.
727
    max_iters: int, default = 1000
728
        Maximum power iterations allowed.
729
    k_tol: float, default = 1.0e-10
730
        Tolerance on the k-eigenvalue.
731
    reset_solution: bool, default=True
732
        If true, initialize flux moments to 1.0.
733
    reset_phi0: bool, default=True
734
        If true, reinitializes scalar fluxes to 1.0.
735
    )"
736
  );
737
  pi_k_eigen_solver.def(
380✔
738
    "GetEigenvalue",
739
    &PowerIterationKEigenSolver::GetEigenvalue,
380✔
740
    R"(
741
    Return the current k‑eigenvalue.
742
    )"
743
  );
744

745
  // power iteration k-eigen SCDSA solver
746
  auto pi_k_eigen_scdsa_solver = py::class_<PowerIterationKEigenSCDSASolver,
380✔
747
                                            std::shared_ptr<PowerIterationKEigenSCDSASolver>,
748
                                            PowerIterationKEigenSolver>(
749
    slv,
750
    "PowerIterationKEigenSCDSASolver",
751
    R"(
752
    Power iteration k-eigenvalue solver with SCDSA.
753

754
    Wrapper of :cpp:class:`opensn::PowerIterationKEigenSCDSASolver`.
755
    )"
756
  );
380✔
757
  pi_k_eigen_scdsa_solver.def(
760✔
758
    py::init(
380✔
759
      [](py::kwargs& params)
8✔
760
      {
761
        return PowerIterationKEigenSCDSASolver::Create(kwargs_to_param_block(params));
8✔
762
      }
763
    ),
764
    R"(
765
    Construct a power iteration k-eigenvalue solver with SCDSA.
766

767
    Parameters
768
    ----------
769
    lbs_problem: pyopensn.solver.LBSProblem
770
        Existing LBSProblem instance.
771
    max_iters: int, default=1000
772
        Maximum power iterations allowed.
773
    k_tol: float, default=1.0e-10
774
        Tolerance on the k-eigenvalue.
775
    reset_solution: bool, default=True
776
        If true, initialize flux moments to 1.0.
777
    reset_phi0: bool, default=True
778
        If true, reinitializes scalar fluxes to 1.0.
779
    accel_pi_max_its : int, default=50
780
        Maximum number of iterations allowed for the inner power iterations used by the
781
        acceleration method.
782
    accel_pi_k_tol : float, default=1.0e-10
783
        Convergence tolerance on the k-eigenvalue within the inner power iterations of the
784
        acceleration method.
785
    accel_pi_verbose : bool, default=False
786
        If True, enables verbose logging output from the acceleration method's inner solver.
787
    diff_accel_diffusion_l_abs_tol : float, default=1.0e-10
788
        Absolute residual tolerance for convergence of the diffusion accelerator.
789
    diff_accel_diffusion_max_iters : int, default=100
790
        Maximum number of iterations allowed for the diffusion accelerator solve.
791
    diff_accel_diffusion_verbose : bool, default=False
792
        If True, enables verbose logging output from the diffusion accelerator.
793
    diff_accel_diffusion_petsc_options : str, default="ssss"
794
        Additional PETSc options passed to the diffusion accelerator linear solver.
795
    diff_accel_sdm : {'pwld', 'pwlc'}, 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
  // power iteration k-eigen SMM solver
803
  auto pi_k_eigen_smm_solver = py::class_<PowerIterationKEigenSMMSolver,
380✔
804
                                          std::shared_ptr<PowerIterationKEigenSMMSolver>,
805
                                          PowerIterationKEigenSolver>(
806
    slv,
807
    "PowerIterationKEigenSMMSolver",
808
    R"(
809
    Power iteration k-eigenvalue solver with SMM acceleration.
810

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

824
    Parameters
825
    ----------
826
    lbs_problem: pyopensn.solver.LBSProblem
827
        Existing LBSProblem instance.
828
    max_iters: int, defauilt=1000
829
        Maximum power iterations allowed.
830
    k_tol: float, default=1.0e-10
831
        Tolerance on the k-eigenvalue.
832
    reset_solution: bool, default=True
833
        If true, initialize flux moments to 1.0.
834
    reset_phi0: bool, default=True
835
        If true, reinitializes scalar fluxes to 1.0.
836
    accel_pi_max_its: int, default=50
837
        Maximum number of power iterations allowed for the second-moment method's diffusion solver.
838
    accel_pi_k_tol: float, default=1.0e-10
839
        Convergence tolerance for the k-eigenvalue in the second-moment method diffusion solver.
840
    accel_pi_verbose: bool, default=False
841
        If True, enables verbose output from the second-moment method diffusion solver.
842
    diff_sdm: {'pwlc', 'pwld'}, default='pwlc'
843
        Spatial discretization method for the second-moment method diffusion system.
844
            - 'pwlc': Piecewise Linear Continuous
845
            - 'pwld': Piecewise Linear Discontinuous
846
    diff_l_abs_tol: float, default=1.0e-10
847
        Absolute residual tolerance for convergence of the diffusion accelerator linear solver.
848
    diff_l_max_its: int, default=100
849
        Maximum number of iterations allowed for the diffusion accelerator linear solver.
850
    diff_petsc_options: str, default=""
851
        Additional PETSc options to pass to the diffusion accelerator solver.
852
    diff_verbose: bool, default=False
853
        If True, enables verbose output from the diffusion accelerator.
854
    )"
855
  );
856
  // clang-format on
857
}
380✔
858

859
// Wrap the solver components of OpenSn
860
void
861
py_solver(py::module& pyopensn)
34✔
862
{
863
  py::module slv = pyopensn.def_submodule("solver", "Solver module.");
34✔
864
  WrapProblem(slv);
34✔
865
  WrapSolver(slv);
34✔
866
  WrapLBS(slv);
34✔
867
  WrapSteadyState(slv);
34✔
868
  WrapNLKEigen(slv);
34✔
869
  WrapPIteration(slv);
34✔
870
}
34✔
871

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