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

Open-Sn / opensn / 18300593117

06 Oct 2025 10:47PM UTC coverage: 74.862% (-0.2%) from 75.031%
18300593117

push

github

web-flow
Merge pull request #759 from wdhawkins/performance

Sweep performance optimizations

294 of 302 new or added lines in 15 files covered. (97.35%)

334 existing lines in 80 files now uncovered.

17788 of 23761 relevant lines covered (74.86%)

61852783.95 hits per line

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

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

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

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

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

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

119
    Wrapper of :cpp:class:`opensn::LBSProblem`.
120
    )"
121
  );
416✔
122
  lbs_problem.def(
416✔
123
    "GetScalarFieldFunctionList",
UNCOV
124
    [](LBSProblem& self, bool only_scalar_flux)
×
125
    {
126
      py::list field_function_list_per_group;
196✔
127
      for (std::size_t group = 0; group < self.GetNumGroups(); group++)
13,362✔
128
      {
129
        if (only_scalar_flux)
13,166✔
130
        {
131
          std::size_t ff_index = self.MapPhiFieldFunction(group, 0);
8,254✔
132
          field_function_list_per_group.append(self.GetFieldFunctions()[ff_index]);
8,254✔
133
        }
134
        else
135
        {
136
          py::list field_function_list_per_moment;
4,912✔
137
          for (std::size_t moment = 0; moment < self.GetNumMoments(); moment++)
19,148✔
138
          {
139
            std::size_t ff_index = self.MapPhiFieldFunction(group, moment);
14,236✔
140
            field_function_list_per_moment.append(self.GetFieldFunctions()[ff_index]);
14,236✔
141
          }
142
          field_function_list_per_group.append(field_function_list_per_moment);
4,912✔
143
        }
4,912✔
144
      }
145
      return field_function_list_per_group;
196✔
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
832✔
170
  );
171
  lbs_problem.def(
416✔
172
    "GetPowerFieldFunction",
173
    &LBSProblem::GetPowerFieldFunction,
416✔
174
    R"(
175
    Returns the power generation field function, if enabled.
176
    )"
177
  );
178
  lbs_problem.def(
416✔
179
    "SetOptions",
UNCOV
180
    [](LBSProblem& self, py::kwargs& params)
×
181
    {
182
      InputParameters input = LBSProblem::GetOptionsBlock();
×
183
      input.AssignParameters(kwargs_to_param_block(params));
×
184
      self.SetOptions(input);
×
185
    },
×
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
    verbose_inner_iterations: bool, default=True
210
        Flag to control verbosity of inner iterations.
211
    verbose_outer_iterations: bool, default=True
212
        Flag to control verbosity of across-groupset iterations.
213
    max_ags_iterations: int, default=100
214
        Maximum number of across-groupset iterations.
215
    ags_tolerance: float, default=1.0e-6
216
        Across-groupset iterations tolerance.
217
    ags_convergence_check: {'l2', 'pointwise'}, default='l2'
218
        Type of convergence check for AGS iterations.
219
    verbose_ags_iterations: bool, default=True
220
        Flag to control verbosity of across-groupset iterations.
221
    power_field_function_on: bool, default=False
222
        Flag to control the creation of the power generation field function. If set to ``True``, a
223
        field function will be created with the general name ``<solver_name>_power_generation``.
224
    power_default_kappa: float, default=3.20435e-11
225
        Default ``kappa`` value (Energy released per fission) to use for power generation when cross
226
        sections do not have ``kappa`` values. Default corresponds to 200 MeV per fission.
227
    power_normalization: float, default=-1.0
228
        Power normalization factor to use. Supply a negative or zero number to turn this off.
229
    field_function_prefix_option: {'prefix', 'solver_name'}, default='prefix'
230
        Prefix option on field function names. If unset, flux field functions will be exported as
231
        ``phi_gXXX_mYYY``, where ``XXX`` is the zero-padded 3-digit group number and ``YYY`` is the
232
        zero-padded 3-digit moment.
233
    field_function_prefix: str, default=''
234
        Prefix to use on all field functions. By default, this is empty. If specified, flux moments
235
        are exported as ``prefix_phi_gXXX_mYYY``.
236
    )"
237
  );
238
  lbs_problem.def(
416✔
239
    "ComputeFissionRate",
UNCOV
240
    [](LBSProblem& self, const std::string& scalar_flux_iterate)
×
241
    {
242
      const std::vector<double>* phi_ptr;
243
      if (scalar_flux_iterate == "old")
×
244
      {
245
        phi_ptr = &self.GetPhiOldLocal();
×
246
      }
247
      else if (scalar_flux_iterate == "new")
×
248
      {
249
        phi_ptr = &self.GetPhiNewLocal();
×
250
      }
251
      else
252
      {
253
        throw std::invalid_argument("Unknown scalar_flux_iterate value: \"" + scalar_flux_iterate + "\".");
×
254
      }
255
      return ComputeFissionRate(self, *phi_ptr);
×
256
    },
257
    R"(
258
    Computes the total fission rate.
259

260
    Parameters
261
    ----------
262
    scalar_flux_iterate : {'old', 'new'}
263
        Specifies which scalar flux vector to use in the calculation.
264
            - 'old': Use the previous scalar flux iterate.
265
            - 'new': Use the current scalar flux iterate.
266

267
    Returns
268
    -------
269
    float
270
        The total fission rate.
271

272
    Raises
273
    ------
274
    ValueError
275
        If `scalar_flux_iterate` is not 'old' or 'new'.
276
    )",
277
    py::arg("scalar_flux_iterate")
416✔
278
  );
279
  lbs_problem.def(
416✔
280
    "WriteFluxMoments",
281
    [](LBSProblem& self, const std::string& file_base)
28✔
282
    {
283
      LBSSolverIO::WriteFluxMoments(self, file_base);
28✔
284
    },
28✔
285
    R"(
286
    Write flux moments to file.
287

288
    Parameters
289
    ----------
290
    file_base: str
291
        File basename.
292
    )",
293
    py::arg("file_base")
416✔
294
  );
295
  lbs_problem.def(
416✔
296
    "CreateAndWriteSourceMoments",
UNCOV
297
    [](LBSProblem& self, const std::string& file_base)
×
298
    {
299
      std::vector<double> source_moments = self.MakeSourceMomentsFromPhi();
4✔
300
      LBSSolverIO::WriteFluxMoments(self, file_base, source_moments);
4✔
301
    },
4✔
302
    R"(
303
    Write source moments from latest flux iterate to file.
304

305
    Parameters
306
    ----------
307
    file_base: str
308
        File basename.
309
    )",
310
    py::arg("file_base")
416✔
311
  );
312
  lbs_problem.def(
416✔
313
    "ReadFluxMomentsAndMakeSourceMoments",
UNCOV
314
    [](LBSProblem& self, const std::string& file_base, bool single_file_flag)
×
315
    {
316
      LBSSolverIO::ReadFluxMoments(self, file_base, single_file_flag, self.GetExtSrcMomentsLocal());
×
317
      log.Log() << "Making source moments from flux file.";
×
318
      std::vector<double>& temp_phi = self.GetPhiOldLocal();
×
319
      self.GetPhiOldLocal() = self.GetExtSrcMomentsLocal();
×
320
      self.GetExtSrcMomentsLocal() = self.MakeSourceMomentsFromPhi();
×
321
      self.GetPhiOldLocal() = temp_phi;
×
322
    },
×
323
    R"(
324
    Read flux moments and compute corresponding source moments.
325

326
    Parameters
327
    ----------
328
    file_base: str
329
        File basename.
330
    single_file_flag: bool
331
        True if all flux moments are in a single file.
332
    )",
333
    py::arg("file_base"),
416✔
334
    py::arg("single_file_flag")
416✔
335
  );
336
  lbs_problem.def(
416✔
337
    "ReadSourceMoments",
UNCOV
338
    [](LBSProblem& self, const std::string& file_base, bool single_file_flag)
×
339
    {
340
      LBSSolverIO::ReadFluxMoments(self, file_base, single_file_flag, self.GetExtSrcMomentsLocal());
4✔
341
    },
4✔
342
    R"(
343
    Read source moments from file.
344

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

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

383
    Parameters
384
    ----------
385
    file_base: str
386
        File basename.
387
    )",
388
    py::arg("file_base")
416✔
389
  );
390
  lbs_problem.def(
416✔
391
    "ReadAngularFluxes",
392
    [](DiscreteOrdinatesProblem& self, const std::string& file_base)
4✔
393
    {
394
      LBSSolverIO::ReadAngularFluxes(self, file_base);
4✔
395
    },
4✔
396
    R"(
397
    Read angular fluxes from file.
398

399
    Parameters
400
    ----------
401
    file_base: str
402
        File basename.
403
    )",
404
    py::arg("file_base")
416✔
405
  );
406
  lbs_problem.def(
416✔
407
    "SetPointSources",
UNCOV
408
    [](LBSProblem& self, py::kwargs& params)
×
409
    {
410
      for (auto [key, value] : params)
×
411
      {
412
        auto c_key = key.cast<std::string>();
×
413
        if (c_key == "clear_point_sources")
×
414
          self.ClearPointSources();
×
415
        else if (c_key == "point_sources")
×
416
        {
417
          auto sources = value.cast<py::list>();
×
418
          for (auto source : sources)
×
419
          {
420
            self.AddPointSource(source.cast<std::shared_ptr<PointSource>>());
×
421
          }
422
        }
×
423
        else
424
          throw std::runtime_error("Invalid argument provided to SetPointSources.\n");
×
425
      }
×
426
    },
×
427
    R"(
428
    Set or clear point sources.
429

430
    Parameters
431
    ----------
432
    clear_point_sources: bool, default=False
433
        If true, all current the point sources of the problem are deleted.
434
    point_sources: List[pyopensn.source.PointSource]
435
        List of new point sources to be added to the problem.
436
    )"
437
  );
438
  lbs_problem.def(
416✔
439
    "SetVolumetricSources",
UNCOV
440
    [](LBSProblem& self, py::kwargs& params)
×
441
    {
442
      for (auto [key, value] : params)
60✔
443
      {
444
        auto c_key = key.cast<std::string>();
20✔
445
        if (c_key == "clear_volumetric_sources")
20✔
446
          self.ClearVolumetricSources();
4✔
447
        else if (c_key == "volumetric_sources")
16✔
448
        {
449
          auto sources = value.cast<py::list>();
16✔
450
          for (auto source : sources)
32✔
451
          {
452
            self.AddVolumetricSource(source.cast<std::shared_ptr<VolumetricSource>>());
16✔
453
          }
454
        }
16✔
455
        else
456
          throw std::runtime_error("Invalid argument provided to SetVolumetricSources.\n");
×
457
      }
20✔
458
    },
20✔
459
    R"(
460
    Set or clear volumetric sources.
461

462
    Parameters
463
    ----------
464
    clear_volumetric_sources: bool, default=False
465
        If true, all current the volumetric sources of the problem are deleted.
466
    volumetric_sources: List[pyopensn.source.VolumetricSource]
467
        List of new volumetric sources to be added to the problem.
468
    )"
469
  );
470
  lbs_problem.def(
416✔
471
    "SetBoundaryOptions",
472
    [](LBSProblem& self, py::kwargs& params)
×
473
    {
474
      for (auto [key, value] : params)
×
475
      {
476
        auto c_key = key.cast<std::string>();
×
477
        if (c_key == "clear_boundary_conditions")
×
478
          self.ClearBoundaries();
×
479
        else if (c_key == "boundary_conditions")
×
480
        {
481
          auto boundaries = value.cast<py::list>();
×
482
          for (auto boundary : boundaries)
×
483
          {
484
            InputParameters input = LBSProblem::GetBoundaryOptionsBlock();
×
485
            input.AssignParameters(pyobj_to_param_block("", boundary.cast<py::dict>()));
×
486
            self.SetBoundaryOptions(input);
×
487
          }
×
488
        }
×
489
        else
490
          throw std::runtime_error("Invalid argument provided to SetBoundaryOptions.\n");
×
491
      }
×
492
    }
×
493
  );
494

495
  // discrete ordinate solver
496
  auto do_problem = py::class_<DiscreteOrdinatesProblem, std::shared_ptr<DiscreteOrdinatesProblem>,
497
                               LBSProblem>(
498
    slv,
499
    "DiscreteOrdinatesProblem",
500
    R"(
501
    Base class for discrete ordinates problems in Cartesian geometry.
502

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

507
    Wrapper of :cpp:class:`opensn::DiscreteOrdinatesProblem`.
508
    )"
509
  );
416✔
510
  do_problem.def(
416✔
511
    py::init(
416✔
UNCOV
512
      [](py::kwargs& params)
×
513
      {
514
        return DiscreteOrdinatesProblem::Create(kwargs_to_param_block(params));
287✔
515
      }
516
    ),
517
    R"(
518
    Construct a discrete ordinates problem with Cartesian geometry.
519

520
    Parameters
521
    ----------
522
    mesh : MeshContinuum
523
        The spatial mesh.
524
    num_groups : int
525
        The total number of energy groups.
526
    groupsets : List[Dict], default=[]
527
        A list of input parameter blocks, each block provides the iterative properties for a
528
        groupset.
529
    xs_map : List[Dict], default=[]
530
        A list of mappings from block ids to cross-section definitions.
531
    scattering_order: int, default=0
532
        The level of harmonic expansion for the scattering source.
533
    boundary_conditions: List[Dict], default=[]
534
        A list containing tables for each boundary specification.
535
    point_sources: List[pyopensn.source.PointSource], default=[]
536
        A list of point sources.
537
    volumetric_sources: List[pyopensn.source.VolumetricSource], default=[]
538
        A list of volumetric sources.
539
    options : Dict, default={}
540
        A block of optional configuration parameters. See `SetOptions` for available settings.
541
    sweep_type : str, default="AAH"
542
        The sweep type to use. Must be one of `AAH` or `CBC`. Defaults to `AAH`.
543
    use_gpus : bool, default=False
544
        A flag specifying whether GPU acceleration is used for the sweep. Currently, only ``AAH`` is
545
        supported.
546
    )"
547
  );
548
  do_problem.def(
416✔
549
    "SetOptions",
UNCOV
550
    [](DiscreteOrdinatesProblem& self, py::kwargs& params)
×
551
    {
552
      InputParameters input = DiscreteOrdinatesProblem::GetOptionsBlock();
24✔
553
      input.AssignParameters(kwargs_to_param_block(params));
24✔
554
      self.SetOptions(input);
24✔
555
    },
24✔
556
    R"(
557
    Set problem options from a large list of parameters.
558

559
    Parameters
560
    ----------
561
    adjoint: bool, default=False
562
        Flag for toggling whether the solver is in adjoint mode.
563
    )"
564
  );
565
  do_problem.def(
416✔
566
    "ComputeBalance",
UNCOV
567
    [](DiscreteOrdinatesProblem& self)
×
568
    {
569
      ComputeBalance(self);
27✔
570
    },
27✔
571
    R"(
572
    Compute and print particle balance for the problem.
573
    )"
574
  );
575
  do_problem.def(
416✔
576
    "ComputeLeakage",
UNCOV
577
    [](DiscreteOrdinatesProblem& self, py::list bnd_names)
×
578
    {
579
      // get the supported boundaries
580
      std::map<std::string, std::uint64_t> allowed_bd_names = LBSProblem::supported_boundary_names;
35✔
581
      std::map<std::uint64_t, std::string> allowed_bd_ids = LBSProblem::supported_boundary_ids;
35✔
582
      // get the boundaries to parse
583
      std::vector<std::uint64_t> bndry_ids;
35✔
584
      if (bnd_names.size() > 1)
35✔
585
      {
586
        for (py::handle name : bnd_names)
64✔
587
        {
588
          bndry_ids.push_back(allowed_bd_names.at(name.cast<std::string>()));
48✔
589
        }
590
      }
591
      else
592
      {
593
        bndry_ids = self.GetGrid()->GetUniqueBoundaryIDs();
19✔
594
      }
595
      // compute the leakage
596
      std::map<std::uint64_t, std::vector<double>> leakage = ComputeLeakage(self, bndry_ids);
35✔
597
      // convert result to native Python
598
      py::dict result;
35✔
599
      for (const auto& [bndry_id, gr_wise_leakage] : leakage)
121✔
600
      {
601
        py::array_t<double> np_vector = py::array_t<double>(gr_wise_leakage.size());
86✔
602
        py::buffer_info buffer = np_vector.request();
86✔
603
        auto *np_vector_data = static_cast<double*>(buffer.ptr);
86✔
604
        std::copy(gr_wise_leakage.begin(), gr_wise_leakage.end(), np_vector_data);
86✔
605
        result[allowed_bd_ids.at(bndry_id).data()] = np_vector;
86✔
606
      }
86✔
607
      return result;
35✔
608
    },
35✔
609
    R"(
610
    Compute leakage for the problem.
611

612
    Parameters
613
    ----------
614
    bnd_names : List[str]
615
        A list of boundary names for which leakage should be computed.
616

617
    Returns
618
    -------
619
    Dict[str, numpy.ndarray]
620
        A dictionary mapping boundary names to group-wise leakage vectors.
621
        Each array contains the outgoing angular flux (per group) integrated over
622
        the corresponding boundary surface.
623

624
    Raises
625
    ------
626
    RuntimeError
627
        If `save_angular_flux` option was not enabled during problem setup.
628

629
    ValueError
630
        If one or more boundary ids are not present on the current mesh.
631
    )",
632
    py::arg("bnd_names")
416✔
633
  );
634

635
  // discrete ordinates curvilinear problem
636
  auto do_curvilinear_problem = py::class_<DiscreteOrdinatesCurvilinearProblem,
637
                                           std::shared_ptr<DiscreteOrdinatesCurvilinearProblem>,
638
                                           DiscreteOrdinatesProblem>(
639
    slv,
640
    "DiscreteOrdinatesCurvilinearProblem",
641
    R"(
642
    Base class for discrete ordinates problems in curvilinear geometry.
643

644
    Wrapper of :cpp:class:`opensn::DiscreteOrdinatesCurvilinearProblem`.
645
    )"
646
  );
416✔
647
  do_curvilinear_problem.def(
416✔
648
    py::init(
416✔
UNCOV
649
      [](py::kwargs& params)
×
650
      {
651
        return DiscreteOrdinatesCurvilinearProblem::Create(kwargs_to_param_block(params));
8✔
652
      }
653
    ),
654
    R"(
655
    Construct a discrete ordinates problem for curvilinear geometry.
656

657
    Warnings
658
    --------
659
       DiscreteOrdinatesCurvilinearProblem is **experimental** and should be used with caution!
660

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

690
// Wrap steady-state solver
691
void
692
WrapSteadyState(py::module& slv)
416✔
693
{
694
  // clang-format off
695
  // steady state solver
696
  auto steady_state_solver = py::class_<SteadyStateSourceSolver, std::shared_ptr<SteadyStateSourceSolver>,
697
                                        Solver>(
698
    slv,
699
    "SteadyStateSourceSolver",
700
    R"(
701
    Steady state solver.
702

703
    Wrapper of :cpp:class:`opensn::SteadyStateSourceSolver`.
704
    )"
705
  );
416✔
706
  steady_state_solver.def(
416✔
707
    py::init(
416✔
UNCOV
708
      [](py::kwargs& params)
×
709
      {
710
        return SteadyStateSourceSolver::Create(kwargs_to_param_block(params));
230✔
711
      }
712
    ),
713
    R"(
714
    Construct a steady state solver.
715

716
    Parameters
717
    ----------
718
    pyopensn.solver.LBSProblem : LBSProblem
719
        Existing LBSProblem instance.
720
    )"
721
  );
722
  // clang-format on
723
}
416✔
724

725
// Wrap non-linear k-eigen solver
726
void
727
WrapNLKEigen(py::module& slv)
416✔
728
{
729
  // clang-format off
730
  // non-linear k-eigen solver
731
  auto non_linear_k_eigen_solver = py::class_<NonLinearKEigenSolver, std::shared_ptr<NonLinearKEigenSolver>,
732
                                              Solver>(
733
    slv,
734
    "NonLinearKEigenSolver",
735
    R"(
736
    Non-linear k-eigenvalue solver.
737

738
    Wrapper of :cpp:class:`opensn::NonLinearKEigenSolver`.
739
    )"
740
  );
416✔
741
  non_linear_k_eigen_solver.def(
416✔
742
    py::init(
416✔
UNCOV
743
      [](py::kwargs& params)
×
744
      {
745
        return NonLinearKEigenSolver::Create(kwargs_to_param_block(params));
28✔
746
      }
747
        ),
748
    R"(
749
    Construct a non-linear k-eigenvalue solver.
750

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

791
// Wrap power iteration solvers
792
void
793
WrapPIteration(py::module& slv)
416✔
794
{
795
  // clang-format off
796
  // power iteration k-eigen solver
797
  auto pi_k_eigen_solver = py::class_<PowerIterationKEigenSolver, std::shared_ptr<PowerIterationKEigenSolver>,
798
                                      Solver>(
799
    slv,
800
    "PowerIterationKEigenSolver",
801
    R"(
802
    Power iteration k-eigenvalue solver.
803

804
    Wrapper of :cpp:class:`opensn::PowerIterationKEigenSolver`.
805
    )"
806
  );
416✔
807
  pi_k_eigen_solver.def(
416✔
808
    py::init(
416✔
UNCOV
809
      [](py::kwargs& params)
×
810
      {
811
        return PowerIterationKEigenSolver::Create(kwargs_to_param_block(params));
35✔
812
      }
813
    ),
814
    R"(
815
    Construct a power iteration k-eigen solver.
816

817
    Parameters
818
    ----------
819
    problem: pyopensn.solver.LBSProblem
820
        Existing DiscreteOrdinatesProblem instance.
821
    acceleration: pyopensn.solver.DiscreteOrdinatesKEigenAcceleration
822
        Optional DiscreteOrdinatesKEigenAcceleration instance for acceleration.
823
    max_iters: int, default = 1000
824
        Maximum power iterations allowed.
825
    k_tol: float, default = 1.0e-10
826
        Tolerance on the k-eigenvalue.
827
    reset_solution: bool, default=True
828
        If true, initialize flux moments to 1.0.
829
    reset_phi0: bool, default=True
830
        If true, reinitializes scalar fluxes to 1.0.
831
    )"
832
  );
833
  pi_k_eigen_solver.def(
416✔
834
    "GetEigenvalue",
835
    &PowerIterationKEigenSolver::GetEigenvalue,
416✔
836
    R"(
837
    Return the current k‑eigenvalue.
838
    )"
839
  );
840
  // clang-format on
841
}
416✔
842

843
// Wrap LBS solver
844
void
845
WrapDiscreteOrdinatesKEigenAcceleration(py::module& slv)
416✔
846
{
847
  // clang-format off
848
  // discrete ordinates k-eigen acceleration base
849
  auto acceleration = py::class_<DiscreteOrdinatesKEigenAcceleration,
850
                                 std::shared_ptr<DiscreteOrdinatesKEigenAcceleration>>(
851
    slv,
852
    "DiscreteOrdinatesKEigenAcceleration",
853
    R"(
854
    Base class for discrete ordinates k-eigenvalue acceleration methods.
855

856
    Wrapper of :cpp:class:`opensn::DiscreteOrdinatesKEigenAcceleration`.
857
    )"
858
  );
416✔
859
  // SCDSA acceleration
860
  auto scdsa_acceleration = py::class_<SCDSAAcceleration,
861
                                       std::shared_ptr<SCDSAAcceleration>,
862
                                       DiscreteOrdinatesKEigenAcceleration>(
863
    slv,
864
    "SCDSAAcceleration",
865
    R"(
866
    Construct an SCDSA accelerator for the power iteration k-eigenvalue solver.
867

868
    Wrapper of :cpp:class:`opensn::SCDSAAcceleration`.
869
    )"
870
  );
416✔
871
  scdsa_acceleration.def(
416✔
872
    py::init(
416✔
UNCOV
873
      [](py::kwargs& params)
×
874
      {
875
        return SCDSAAcceleration::Create(kwargs_to_param_block(params));
8✔
876
      }
877
    ),
878
    R"(
879
    SCDSA acceleration for the power iteration k-eigenvalue solver.
880

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

912
    Wrapper of :cpp:class:`opensn::SMMAcceleration`.
913
    )"
914
  );
416✔
915
  smm_acceleration.def(
416✔
916
    py::init(
416✔
UNCOV
917
      [](py::kwargs& params)
×
918
      {
919
        return SMMAcceleration::Create(kwargs_to_param_block(params));
4✔
920
      }
921
    ),
922
    R"(
923
    SMM acceleration for the power iteration k-eigenvalue solver.
924

925
    Warnings
926
    --------
927
       SMM acceleration is **experimental** and should be used with caution!
928
       SMM accleration only supports problems with isotropic scattering.
929

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

955
// Wrap the solver components of OpenSn
956
void
957
py_solver(py::module& pyopensn)
62✔
958
{
959
  py::module slv = pyopensn.def_submodule("solver", "Solver module.");
62✔
960
  WrapProblem(slv);
62✔
961
  WrapSolver(slv);
62✔
962
  WrapLBS(slv);
62✔
963
  WrapSteadyState(slv);
62✔
964
  WrapNLKEigen(slv);
62✔
965
  WrapDiscreteOrdinatesKEigenAcceleration(slv);
62✔
966
  WrapPIteration(slv);
62✔
967
}
62✔
968

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