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

Open-Sn / opensn / 17601663936

08 Sep 2025 10:28PM UTC coverage: 74.731%. Remained the same
17601663936

push

github

web-flow
Merge pull request #723 from wdhawkins/boundary_conditions

Adding boundary conditions as an optional solver input parameter

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

111 existing lines in 3 files now uncovered.

17718 of 23709 relevant lines covered (74.73%)

44876435.05 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 "modules/linear_boltzmann_solvers/discrete_ordinates_problem/acceleration/discrete_ordinates_keigen_acceleration.h"
8
#include "modules/linear_boltzmann_solvers/discrete_ordinates_problem/acceleration/scdsa_acceleration.h"
9
#include "modules/linear_boltzmann_solvers/discrete_ordinates_problem/acceleration/smm_acceleration.h"
10
#include "modules/linear_boltzmann_solvers/discrete_ordinates_curvilinear_problem/discrete_ordinates_curvilinear_problem.h"
11
#include "modules/linear_boltzmann_solvers/discrete_ordinates_problem/discrete_ordinates_problem.h"
12
#include "modules/linear_boltzmann_solvers/solvers/steady_state_solver.h"
13
#include "modules/linear_boltzmann_solvers/solvers/nl_keigen_solver.h"
14
#include "modules/linear_boltzmann_solvers/solvers/pi_keigen_solver.h"
15
#include "modules/linear_boltzmann_solvers/lbs_problem/io/lbs_problem_io.h"
16
#include "modules/linear_boltzmann_solvers/lbs_problem/lbs_problem.h"
17
#include "modules/linear_boltzmann_solvers/lbs_problem/lbs_compute.h"
18
#include "modules/solver.h"
19
#include <pybind11/numpy.h>
20
#include <algorithm>
21
#include <cstddef>
22
#include <cstdint>
23
#include <map>
24
#include <memory>
25
#include <string>
26
#include <vector>
27

28
namespace opensn
29
{
30

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

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

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

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

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

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

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

275
    Parameters
276
    ----------
277
    scalar_flux_iterate : {'old', 'new'}
278
        Specifies which scalar flux vector to use in the calculation.
279
            - 'old': Use the previous scalar flux iterate.
280
            - 'new': Use the current scalar flux iterate.
281

282
    Returns
283
    -------
284
    float
285
        The total fission rate.
286

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

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

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

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

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

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

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

414
    Parameters
415
    ----------
416
    file_base: str
417
        File basename.
418
    )",
419
    py::arg("file_base")
384✔
420
  );
421

422
  // discrete ordinate solver
423
  auto do_problem = py::class_<DiscreteOrdinatesProblem, std::shared_ptr<DiscreteOrdinatesProblem>,
384✔
424
                               LBSProblem>(
425
    slv,
426
    "DiscreteOrdinatesProblem",
427
    R"(
428
    Base class for discrete ordinates problems in Cartesian geometry.
429

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

434
    Wrapper of :cpp:class:`opensn::DiscreteOrdinatesProblem`.
435
    )"
436
  );
384✔
437
  do_problem.def(
768✔
438
    py::init(
384✔
439
      [](py::kwargs& params)
255✔
440
      {
441
        return DiscreteOrdinatesProblem::Create(kwargs_to_param_block(params));
255✔
442
      }
443
    ),
444
    R"(
445
    Construct a discrete ordinates problem with Cartesian geometry.
446

447
    Parameters
448
    ----------
449
    mesh : MeshContinuum
450
        The spatial mesh.
451
    num_groups : int
452
        The total number of energy groups.
453
    groupsets : List[Dict], default=[]
454
        A list of input parameter blocks, each block provides the iterative properties for a
455
        groupset.
456
    xs_map : List[Dict], default=[]
457
        A list of mappings from block ids to cross-section definitions.
458
    scattering_order: int, default=0
459
        The level of harmonic expansion for the scattering source.
460
    boundary_conditions: List[Dict], default=[]
461
        A list containing tables for each boundary specification.
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(
384✔
472
    "ComputeBalance",
473
    [](DiscreteOrdinatesProblem& self)
403✔
474
    {
475
      ComputeBalance(self);
19✔
476
    },
477
    R"(
478
    Compute and print particle balance for the problem.
479
    )"
480
  );
481
  do_problem.def(
384✔
482
    "ComputeLeakage",
483
    [](DiscreteOrdinatesProblem& self, py::list bnd_names)
403✔
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
        {
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
        auto 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")
384✔
539
  );
540

541
  // discrete ordinates curvilinear problem
542
  auto do_curvilinear_problem = py::class_<DiscreteOrdinatesCurvilinearProblem,
384✔
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
  );
384✔
553
  do_curvilinear_problem.def(
768✔
554
    py::init(
384✔
555
      [](py::kwargs& params)
8✔
556
      {
557
        return DiscreteOrdinatesCurvilinearProblem::Create(kwargs_to_param_block(params));
8✔
558
      }
559
    ),
560
    R"(
561
    Construct a discrete ordinates problem for curvilinear geometry.
562

563
    Warnings
564
    --------
565
       DiscreteOrdinatesCurvilinearProblem is **experimental** and should be used with caution!
566

567
    Parameters
568
    ----------
569
    mesh : MeshContinuum
570
        The spatial mesh.
571
    coord_system : int
572
        Coordinate system to use. Must be set to 2 (cylindrical coordinates).
573
    num_groups : int
574
        The total number of energy groups.
575
    groupsets : list of dict
576
        A list of input parameter blocks, each block provides the iterative properties for a
577
        groupset.
578
    xs_map : list of dict
579
        A list of mappings from block ids to cross-section definitions.
580
    scattering_order: int, default=0
581
        The level of harmonic expansion for the scattering source.
582
    boundary_conditions: List[Dict], default=[]
583
        A list containing tables for each boundary specification.
584
    options : dict, optional
585
        A block of optional configuration parameters. See `SetOptions` for available settings.
586
    sweep_type : str, optional
587
        The sweep type to use. Must be one of `AAH` or `CBC`. Defaults to `AAH`.
588
    )"
589
  );
590
}
384✔
591

592
// Wrap steady-state solver
593
void
594
WrapSteadyState(py::module& slv)
384✔
595
{
596
  // clang-format off
597
  // steady state solver
598
  auto steady_state_solver = py::class_<SteadyStateSourceSolver, std::shared_ptr<SteadyStateSourceSolver>,
384✔
599
                                        Solver>(
600
    slv,
601
    "SteadyStateSourceSolver",
602
    R"(
603
    Steady state solver.
604

605
    Wrapper of :cpp:class:`opensn::SteadyStateSourceSolver`.
606
    )"
607
  );
384✔
608
  steady_state_solver.def(
768✔
609
    py::init(
384✔
610
      [](py::kwargs& params)
198✔
611
      {
612
        return SteadyStateSourceSolver::Create(kwargs_to_param_block(params));
198✔
613
      }
614
    ),
615
    R"(
616
    Construct a steady state solver.
617

618
    Parameters
619
    ----------
620
    pyopensn.solver.LBSProblem : LBSProblem
621
        Existing LBSProblem instance.
622
    )"
623
  );
624
  // clang-format on
625
}
384✔
626

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

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

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

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

706
    Wrapper of :cpp:class:`opensn::PowerIterationKEigenSolver`.
707
    )"
708
  );
384✔
709
  pi_k_eigen_solver.def(
768✔
710
    py::init(
384✔
711
      [](py::kwargs& params)
35✔
712
      {
713
        return PowerIterationKEigenSolver::Create(kwargs_to_param_block(params));
35✔
714
      }
715
    ),
716
    R"(
717
    Construct a power iteration k-eigen solver.
718

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

745
// Wrap LBS solver
746
void
747
WrapDiscreteOrdinatesKEigenAcceleration(py::module& slv)
384✔
748
{
749
  // clang-format off
750
  // discrete ordinates k-eigen acceleration base
751
  auto acceleration = py::class_<DiscreteOrdinatesKEigenAcceleration,
384✔
752
                                 std::shared_ptr<DiscreteOrdinatesKEigenAcceleration>>(
753
    slv,
754
    "DiscreteOrdinatesKEigenAcceleration",
755
    R"(
756
    Base class for discrete ordinates k-eigenvalue acceleration methods.
757

758
    Wrapper of :cpp:class:`opensn::DiscreteOrdinatesKEigenAcceleration`.
759
    )"
760
  );
384✔
761
  // SCDSA acceleration
762
  auto scdsa_acceleration = py::class_<SCDSAAcceleration,
384✔
763
                                       std::shared_ptr<SCDSAAcceleration>,
764
                                       DiscreteOrdinatesKEigenAcceleration>(
765
    slv,
766
    "SCDSAAcceleration",
767
    R"(
768
    Construct an SCDSA accelerator for the power iteration k-eigenvalue solver.
769

770
    Wrapper of :cpp:class:`opensn::SCDSAAcceleration`.
771
    )"
772
  );
384✔
773
  scdsa_acceleration.def(
768✔
774
    py::init(
384✔
775
      [](py::kwargs& params)
8✔
776
      {
777
        return SCDSAAcceleration::Create(kwargs_to_param_block(params));
8✔
778
      }
779
    ),
780
    R"(
781
    SCDSA acceleration for the power iteration k-eigenvalue solver.
782

783
    Parameters
784
    ----------
785
    problem: pyopensn.solver.LBSProblem
786
        Existing DiscreteOrdinatesProblem instance.
787
    l_abs_tol: float, defauilt=1.0e-10
788
        Absolute residual tolerance.
789
    max_iters: int, default=100
790
        Maximum allowable iterations.
791
    verbose: bool, default=False
792
        If true, enables verbose output.
793
    petsc_options: str, default="ssss"
794
        Additional PETSc options.
795
    pi_max_its: int, default=50
796
        Maximum allowable iterations for inner power iterations.
797
    pi_k_tol: float, default=1.0e-10
798
        k-eigenvalue tolerance for the inner power iterations.
799
    sdm: str, default="pwld"
800
        Spatial discretization method to use for the diffusion solver. Valid choices are:
801
            - 'pwld' : Piecewise Linear Discontinuous
802
            - 'pwlc' : Piecewise Linear Continuous
803
    )"
804
  );
805
  // SMM acceleration
806
  auto smm_acceleration = py::class_<SMMAcceleration,
384✔
807
                                     std::shared_ptr<SMMAcceleration>,
808
                                     DiscreteOrdinatesKEigenAcceleration>(
809
    slv,
810
    "SMMAcceleration",
811
    R"(
812
    Construct an SMM accelerator for the power iteration k-eigenvalue solver.
813

814
    Wrapper of :cpp:class:`opensn::SMMAcceleration`.
815
    )"
816
  );
384✔
817
  smm_acceleration.def(
768✔
818
    py::init(
384✔
819
      [](py::kwargs& params)
4✔
820
      {
821
        return SMMAcceleration::Create(kwargs_to_param_block(params));
4✔
822
      }
823
    ),
824
    R"(
825
    SMM acceleration for the power iteration k-eigenvalue solver.
826

827
    Warnings
828
    --------
829
       SMM acceleration is **experimental** and should be used with caution!
830
       SMM accleration only supports problems with isotropic scattering.
831

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

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

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