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

Open-Sn / opensn / 17874334706

19 Sep 2025 06:40PM UTC coverage: 74.578% (-0.2%) from 74.728%
17874334706

push

github

web-flow
Merge pull request #763 from andrsd/cmake-lib-names

cmake: correcting file names for the opensn library files

17734 of 23779 relevant lines covered (74.58%)

44077127.1 hits per line

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

77.22
/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
  lbs_problem.def(
384✔
422
    "SetPointSources",
423
    [](LBSProblem& self, py::kwargs& params)
384✔
424
    {
425
      for (auto [key, value] : params)
×
426
      {
427
        auto c_key = key.cast<std::string>();
×
428
        if (c_key == "clear_point_sources")
×
429
          self.ClearPointSources();
×
430
        else if (c_key == "point_sources")
×
431
        {
432
          auto sources = value.cast<py::list>();
×
433
          for (auto source : sources)
×
434
          {
435
            self.AddPointSource(source.cast<std::shared_ptr<PointSource>>());
×
436
          }
437
        }
×
438
        else
439
          throw std::runtime_error("Invalid argument provided to SetPointSources.\n");
×
440
      }
×
441
    },
×
442
    R"(
443
    Set or clear point sources.
444

445
    Parameters
446
    ----------
447
    clear_point_sources: bool, default=False
448
        If true, all current the point sources of the problem are deleted.
449
    point_sources: List[pyopensn.source.PointSource]
450
        List of new point sources to be added to the problem.
451
    )"
452
  );
453
  lbs_problem.def(
384✔
454
    "SetVolumetricSources",
455
    [](LBSProblem& self, py::kwargs& params)
396✔
456
    {
457
      for (auto [key, value] : params)
36✔
458
      {
459
        auto c_key = key.cast<std::string>();
12✔
460
        if (c_key == "clear_volumetric_sources")
12✔
461
          self.ClearVolumetricSources();
×
462
        else if (c_key == "volumetric_sources")
12✔
463
        {
464
          auto sources = value.cast<py::list>();
12✔
465
          for (auto source : sources)
36✔
466
          {
467
            self.AddVolumetricSource(source.cast<std::shared_ptr<VolumetricSource>>());
24✔
468
          }
469
        }
12✔
470
        else
471
          throw std::runtime_error("Invalid argument provided to SetVolumetricSources.\n");
×
472
      }
12✔
473
    },
12✔
474
    R"(
475
    Set or clear volumetric sources.
476

477
    Parameters
478
    ----------
479
    clear_volumetric_sources: bool, default=False
480
        If true, all current the volumetric sources of the problem are deleted.
481
    volumetric_sources: List[pyopensn.source.VolumetricSource]
482
        List of new volumetric sources to be added to the problem.
483
    )"
484
  );
485
  lbs_problem.def(
384✔
486
    "SetBoundaryOptions",
487
    [](LBSProblem& self, py::kwargs& params)
×
488
    {
489
      for (auto [key, value] : params)
×
490
      {
491
        auto c_key = key.cast<std::string>();
×
492
        if (c_key == "clear_boundary_conditions")
×
493
          self.ClearBoundaries();
×
494
        else if (c_key == "boundary_conditions")
×
495
        {
496
          auto boundaries = value.cast<py::list>();
×
497
          for (auto boundary : boundaries)
×
498
          {
499
            InputParameters input = LBSProblem::GetBoundaryOptionsBlock();
×
500
            input.AssignParameters(pyobj_to_param_block("", boundary.cast<py::dict>()));
×
501
            self.SetBoundaryOptions(input);
×
502
          }
×
503
        }
×
504
        else
505
          throw std::runtime_error("Invalid argument provided to SetBoundaryOptions.\n");
×
506
      }
×
507
    }
×
508
  );
509

510
  // discrete ordinate solver
511
  auto do_problem = py::class_<DiscreteOrdinatesProblem, std::shared_ptr<DiscreteOrdinatesProblem>,
384✔
512
                               LBSProblem>(
513
    slv,
514
    "DiscreteOrdinatesProblem",
515
    R"(
516
    Base class for discrete ordinates problems in Cartesian geometry.
517

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

522
    Wrapper of :cpp:class:`opensn::DiscreteOrdinatesProblem`.
523
    )"
524
  );
384✔
525
  do_problem.def(
768✔
526
    py::init(
384✔
527
      [](py::kwargs& params)
255✔
528
      {
529
        return DiscreteOrdinatesProblem::Create(kwargs_to_param_block(params));
255✔
530
      }
531
    ),
532
    R"(
533
    Construct a discrete ordinates problem with Cartesian geometry.
534

535
    Parameters
536
    ----------
537
    mesh : MeshContinuum
538
        The spatial mesh.
539
    num_groups : int
540
        The total number of energy groups.
541
    groupsets : List[Dict], default=[]
542
        A list of input parameter blocks, each block provides the iterative properties for a
543
        groupset.
544
    xs_map : List[Dict], default=[]
545
        A list of mappings from block ids to cross-section definitions.
546
    scattering_order: int, default=0
547
        The level of harmonic expansion for the scattering source.
548
    boundary_conditions: List[Dict], default=[]
549
        A list containing tables for each boundary specification.
550
    options : Dict, default={}
551
        A block of optional configuration parameters. See `SetOptions` for available settings.
552
    sweep_type : str, default="AAH"
553
        The sweep type to use. Must be one of `AAH` or `CBC`. Defaults to `AAH`.
554
    use_gpus : bool, default=False
555
        A flag specifying whether GPU acceleration is used for the sweep. Currently, only ``AAH`` is
556
        supported.
557
    )"
558
  );
559
  do_problem.def(
384✔
560
    "ComputeBalance",
561
    [](DiscreteOrdinatesProblem& self)
403✔
562
    {
563
      ComputeBalance(self);
19✔
564
    },
565
    R"(
566
    Compute and print particle balance for the problem.
567
    )"
568
  );
569
  do_problem.def(
384✔
570
    "ComputeLeakage",
571
    [](DiscreteOrdinatesProblem& self, py::list bnd_names)
403✔
572
    {
573
      // get the supported boundaries
574
      std::map<std::string, std::uint64_t> allowed_bd_names = LBSProblem::supported_boundary_names;
19✔
575
      std::map<std::uint64_t, std::string> allowed_bd_ids = LBSProblem::supported_boundary_ids;
19✔
576
      // get the boundaries to parse
577
      std::vector<std::uint64_t> bndry_ids;
19✔
578
      if (bnd_names.size() > 1)
19✔
579
      {
580
        for (py::handle name : bnd_names)
×
581
        {
582
          bndry_ids.push_back(allowed_bd_names.at(name.cast<std::string>()));
×
583
        }
584
      }
585
      else
586
      {
587
        bndry_ids = self.GetGrid()->GetUniqueBoundaryIDs();
57✔
588
      }
589
      // compute the leakage
590
      std::map<std::uint64_t, std::vector<double>> leakage = ComputeLeakage(self, bndry_ids);
19✔
591
      // convert result to native Python
592
      py::dict result;
19✔
593
      for (const auto& [bndry_id, gr_wise_leakage] : leakage)
57✔
594
      {
595
        py::array_t<double> np_vector = py::array_t<double>(gr_wise_leakage.size());
38✔
596
        py::buffer_info buffer = np_vector.request();
38✔
597
        auto np_vector_data = static_cast<double*>(buffer.ptr);
38✔
598
        std::copy(gr_wise_leakage.begin(), gr_wise_leakage.end(), np_vector_data);
38✔
599
        result[allowed_bd_ids.at(bndry_id).data()] = np_vector;
38✔
600
      }
38✔
601
      return result;
19✔
602
    },
19✔
603
    R"(
604
    Compute leakage for the problem.
605

606
    Parameters
607
    ----------
608
    bnd_names : List[str]
609
        A list of boundary names for which leakage should be computed.
610

611
    Returns
612
    -------
613
    Dict[str, numpy.ndarray]
614
        A dictionary mapping boundary names to group-wise leakage vectors.
615
        Each array contains the outgoing angular flux (per group) integrated over
616
        the corresponding boundary surface.
617

618
    Raises
619
    ------
620
    RuntimeError
621
        If `save_angular_flux` option was not enabled during problem setup.
622

623
    ValueError
624
        If one or more boundary ids are not present on the current mesh.
625
    )",
626
    py::arg("bnd_names")
384✔
627
  );
628

629
  // discrete ordinates curvilinear problem
630
  auto do_curvilinear_problem = py::class_<DiscreteOrdinatesCurvilinearProblem,
384✔
631
                                           std::shared_ptr<DiscreteOrdinatesCurvilinearProblem>,
632
                                           DiscreteOrdinatesProblem>(
633
    slv,
634
    "DiscreteOrdinatesCurvilinearProblem",
635
    R"(
636
    Base class for discrete ordinates problems in curvilinear geometry.
637

638
    Wrapper of :cpp:class:`opensn::DiscreteOrdinatesCurvilinearProblem`.
639
    )"
640
  );
384✔
641
  do_curvilinear_problem.def(
768✔
642
    py::init(
384✔
643
      [](py::kwargs& params)
8✔
644
      {
645
        return DiscreteOrdinatesCurvilinearProblem::Create(kwargs_to_param_block(params));
8✔
646
      }
647
    ),
648
    R"(
649
    Construct a discrete ordinates problem for curvilinear geometry.
650

651
    Warnings
652
    --------
653
       DiscreteOrdinatesCurvilinearProblem is **experimental** and should be used with caution!
654

655
    Parameters
656
    ----------
657
    mesh : MeshContinuum
658
        The spatial mesh.
659
    coord_system : int
660
        Coordinate system to use. Must be set to 2 (cylindrical coordinates).
661
    num_groups : int
662
        The total number of energy groups.
663
    groupsets : list of dict
664
        A list of input parameter blocks, each block provides the iterative properties for a
665
        groupset.
666
    xs_map : list of dict
667
        A list of mappings from block ids to cross-section definitions.
668
    scattering_order: int, default=0
669
        The level of harmonic expansion for the scattering source.
670
    boundary_conditions: List[Dict], default=[]
671
        A list containing tables for each boundary specification.
672
    options : dict, optional
673
        A block of optional configuration parameters. See `SetOptions` for available settings.
674
    sweep_type : str, optional
675
        The sweep type to use. Must be one of `AAH` or `CBC`. Defaults to `AAH`.
676
    )"
677
  );
678
}
384✔
679

680
// Wrap steady-state solver
681
void
682
WrapSteadyState(py::module& slv)
384✔
683
{
684
  // clang-format off
685
  // steady state solver
686
  auto steady_state_solver = py::class_<SteadyStateSourceSolver, std::shared_ptr<SteadyStateSourceSolver>,
384✔
687
                                        Solver>(
688
    slv,
689
    "SteadyStateSourceSolver",
690
    R"(
691
    Steady state solver.
692

693
    Wrapper of :cpp:class:`opensn::SteadyStateSourceSolver`.
694
    )"
695
  );
384✔
696
  steady_state_solver.def(
768✔
697
    py::init(
384✔
698
      [](py::kwargs& params)
198✔
699
      {
700
        return SteadyStateSourceSolver::Create(kwargs_to_param_block(params));
198✔
701
      }
702
    ),
703
    R"(
704
    Construct a steady state solver.
705

706
    Parameters
707
    ----------
708
    pyopensn.solver.LBSProblem : LBSProblem
709
        Existing LBSProblem instance.
710
    )"
711
  );
712
  // clang-format on
713
}
384✔
714

715
// Wrap non-linear k-eigen solver
716
void
717
WrapNLKEigen(py::module& slv)
384✔
718
{
719
  // clang-format off
720
  // non-linear k-eigen solver
721
  auto non_linear_k_eigen_solver = py::class_<NonLinearKEigenSolver, std::shared_ptr<NonLinearKEigenSolver>,
384✔
722
                                              Solver>(
723
    slv,
724
    "NonLinearKEigenSolver",
725
    R"(
726
    Non-linear k-eigenvalue solver.
727

728
    Wrapper of :cpp:class:`opensn::NonLinearKEigenSolver`.
729
    )"
730
  );
384✔
731
  non_linear_k_eigen_solver.def(
768✔
732
    py::init(
384✔
733
      [](py::kwargs& params)
28✔
734
      {
735
        return NonLinearKEigenSolver::Create(kwargs_to_param_block(params));
28✔
736
      }
737
        ),
738
    R"(
739
    Construct a non-linear k-eigenvalue solver.
740

741
    Parameters
742
    ----------
743
    lbs_problem: pyopensn.solver.LBSProblem
744
        Existing LBSProblem instance.
745
    nl_abs_tol: float, default=1.0e-8
746
        Non-linear absolute tolerance.
747
    nl_rel_tol: float, default=1.0e-8
748
        Non-linear relative tolerance.
749
    nl_sol_tol: float, default=1.0e-50
750
        Non-linear solution tolerance.
751
    nl_max_its: int, default=50
752
        Non-linear algorithm maximum iterations.
753
    l_abs_tol: float, default=1.0e-8
754
        Linear absolute tolerance.
755
    l_rel_tol: float, default=1.0e-8
756
        Linear relative tolerance.
757
    l_div_tol: float, default=1.0e6
758
        Linear divergence tolerance.
759
    l_max_its: int, default=50
760
        Linear algorithm maximum iterations.
761
    l_gmres_restart_intvl: int, default=30
762
        GMRES restart interval.
763
    l_gmres_breakdown_tol: float, default=1.0e6
764
        GMRES breakdown tolerance.
765
    reset_phi0: bool, default=True
766
        If true, reinitializes scalar fluxes to 1.0.
767
    num_initial_power_iterations: int, default=0
768
        Number of initial power iterations before the non-linear solve.
769
    )"
770
  );
771
  non_linear_k_eigen_solver.def(
384✔
772
    "GetEigenvalue",
773
    &NonLinearKEigenSolver::GetEigenvalue,
384✔
774
    R"(
775
    Return the current k‑eigenvalue.
776
    )"
777
  );
778
  // clang-format on
779
}
384✔
780

781
// Wrap power iteration solvers
782
void
783
WrapPIteration(py::module& slv)
384✔
784
{
785
  // clang-format off
786
  // power iteration k-eigen solver
787
  auto pi_k_eigen_solver = py::class_<PowerIterationKEigenSolver, std::shared_ptr<PowerIterationKEigenSolver>,
384✔
788
                                      Solver>(
789
    slv,
790
    "PowerIterationKEigenSolver",
791
    R"(
792
    Power iteration k-eigenvalue solver.
793

794
    Wrapper of :cpp:class:`opensn::PowerIterationKEigenSolver`.
795
    )"
796
  );
384✔
797
  pi_k_eigen_solver.def(
768✔
798
    py::init(
384✔
799
      [](py::kwargs& params)
35✔
800
      {
801
        return PowerIterationKEigenSolver::Create(kwargs_to_param_block(params));
35✔
802
      }
803
    ),
804
    R"(
805
    Construct a power iteration k-eigen solver.
806

807
    Parameters
808
    ----------
809
    problem: pyopensn.solver.LBSProblem
810
        Existing DiscreteOrdinatesProblem instance.
811
    acceleration: pyopensn.solver.DiscreteOrdinatesKEigenAcceleration
812
        Optional DiscreteOrdinatesKEigenAcceleration instance for acceleration.
813
    max_iters: int, default = 1000
814
        Maximum power iterations allowed.
815
    k_tol: float, default = 1.0e-10
816
        Tolerance on the k-eigenvalue.
817
    reset_solution: bool, default=True
818
        If true, initialize flux moments to 1.0.
819
    reset_phi0: bool, default=True
820
        If true, reinitializes scalar fluxes to 1.0.
821
    )"
822
  );
823
  pi_k_eigen_solver.def(
384✔
824
    "GetEigenvalue",
825
    &PowerIterationKEigenSolver::GetEigenvalue,
384✔
826
    R"(
827
    Return the current k‑eigenvalue.
828
    )"
829
  );
830
  // clang-format on
831
}
384✔
832

833
// Wrap LBS solver
834
void
835
WrapDiscreteOrdinatesKEigenAcceleration(py::module& slv)
384✔
836
{
837
  // clang-format off
838
  // discrete ordinates k-eigen acceleration base
839
  auto acceleration = py::class_<DiscreteOrdinatesKEigenAcceleration,
384✔
840
                                 std::shared_ptr<DiscreteOrdinatesKEigenAcceleration>>(
841
    slv,
842
    "DiscreteOrdinatesKEigenAcceleration",
843
    R"(
844
    Base class for discrete ordinates k-eigenvalue acceleration methods.
845

846
    Wrapper of :cpp:class:`opensn::DiscreteOrdinatesKEigenAcceleration`.
847
    )"
848
  );
384✔
849
  // SCDSA acceleration
850
  auto scdsa_acceleration = py::class_<SCDSAAcceleration,
384✔
851
                                       std::shared_ptr<SCDSAAcceleration>,
852
                                       DiscreteOrdinatesKEigenAcceleration>(
853
    slv,
854
    "SCDSAAcceleration",
855
    R"(
856
    Construct an SCDSA accelerator for the power iteration k-eigenvalue solver.
857

858
    Wrapper of :cpp:class:`opensn::SCDSAAcceleration`.
859
    )"
860
  );
384✔
861
  scdsa_acceleration.def(
768✔
862
    py::init(
384✔
863
      [](py::kwargs& params)
8✔
864
      {
865
        return SCDSAAcceleration::Create(kwargs_to_param_block(params));
8✔
866
      }
867
    ),
868
    R"(
869
    SCDSA acceleration for the power iteration k-eigenvalue solver.
870

871
    Parameters
872
    ----------
873
    problem: pyopensn.solver.LBSProblem
874
        Existing DiscreteOrdinatesProblem instance.
875
    l_abs_tol: float, defauilt=1.0e-10
876
        Absolute residual tolerance.
877
    max_iters: int, default=100
878
        Maximum allowable iterations.
879
    verbose: bool, default=False
880
        If true, enables verbose output.
881
    petsc_options: str, default="ssss"
882
        Additional PETSc options.
883
    pi_max_its: int, default=50
884
        Maximum allowable iterations for inner power iterations.
885
    pi_k_tol: float, default=1.0e-10
886
        k-eigenvalue tolerance for the inner power iterations.
887
    sdm: str, default="pwld"
888
        Spatial discretization method to use for the diffusion solver. Valid choices are:
889
            - 'pwld' : Piecewise Linear Discontinuous
890
            - 'pwlc' : Piecewise Linear Continuous
891
    )"
892
  );
893
  // SMM acceleration
894
  auto smm_acceleration = py::class_<SMMAcceleration,
384✔
895
                                     std::shared_ptr<SMMAcceleration>,
896
                                     DiscreteOrdinatesKEigenAcceleration>(
897
    slv,
898
    "SMMAcceleration",
899
    R"(
900
    Construct an SMM accelerator for the power iteration k-eigenvalue solver.
901

902
    Wrapper of :cpp:class:`opensn::SMMAcceleration`.
903
    )"
904
  );
384✔
905
  smm_acceleration.def(
768✔
906
    py::init(
384✔
907
      [](py::kwargs& params)
4✔
908
      {
909
        return SMMAcceleration::Create(kwargs_to_param_block(params));
4✔
910
      }
911
    ),
912
    R"(
913
    SMM acceleration for the power iteration k-eigenvalue solver.
914

915
    Warnings
916
    --------
917
       SMM acceleration is **experimental** and should be used with caution!
918
       SMM accleration only supports problems with isotropic scattering.
919

920
    Parameters
921
    ----------
922
    problem: pyopensn.solver.LBSProblem
923
        Existing DiscreteOrdinatesProblem instance.
924
    l_abs_tol: float, defauilt=1.0e-10
925
        Absolute residual tolerance.
926
    max_iters: int, default=100
927
        Maximum allowable iterations.
928
    verbose: bool, default=False
929
        If true, enables verbose output.
930
    petsc_options: str, default="ssss"
931
        Additional PETSc options.
932
    pi_max_its: int, default=50
933
        Maximum allowable iterations for inner power iterations.
934
    pi_k_tol: float, default=1.0e-10
935
        k-eigenvalue tolerance for the inner power iterations.
936
    sdm: str, default="pwld"
937
        Spatial discretization method to use for the diffusion solver. Valid choices are:
938
            - 'pwld' : Piecewise Linear Discontinuous
939
            - 'pwlc' : Piecewise Linear Continuous
940
    )"
941
  );
942
  // clang-format on
943
}
384✔
944

945
// Wrap the solver components of OpenSn
946
void
947
py_solver(py::module& pyopensn)
34✔
948
{
949
  py::module slv = pyopensn.def_submodule("solver", "Solver module.");
34✔
950
  WrapProblem(slv);
34✔
951
  WrapSolver(slv);
34✔
952
  WrapLBS(slv);
34✔
953
  WrapSteadyState(slv);
34✔
954
  WrapNLKEigen(slv);
34✔
955
  WrapDiscreteOrdinatesKEigenAcceleration(slv);
34✔
956
  WrapPIteration(slv);
34✔
957
}
34✔
958

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