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

Open-Sn / opensn / 19810548924

30 Nov 2025 10:56PM UTC coverage: 74.144% (+0.005%) from 74.139%
19810548924

push

github

web-flow
Merge pull request #839 from wdhawkins/leakage_fixes

Bug fixes for leakage calculations

72 of 89 new or added lines in 2 files covered. (80.9%)

107 existing lines in 3 files now uncovered.

18309 of 24694 relevant lines covered (74.14%)

58540998.23 hits per line

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

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

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

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

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

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

119
    Wrapper of :cpp:class:`opensn::LBSProblem`.
120
    )"
121
  );
451✔
122
  lbs_problem.def(
451✔
123
    "GetScalarFieldFunctionList",
124
    [](LBSProblem& self, bool only_scalar_flux)
451✔
125
    {
126
      py::list field_function_list_per_group;
215✔
127
      for (std::size_t group = 0; group < self.GetNumGroups(); group++)
14,755✔
128
      {
129
        if (only_scalar_flux)
14,540✔
130
        {
131
          std::size_t ff_index = self.MapPhiFieldFunction(group, 0);
8,872✔
132
          field_function_list_per_group.append(self.GetFieldFunctions()[ff_index]);
8,872✔
133
        }
134
        else
135
        {
136
          py::list field_function_list_per_moment;
5,668✔
137
          for (std::size_t moment = 0; moment < self.GetNumMoments(); moment++)
22,256✔
138
          {
139
            std::size_t ff_index = self.MapPhiFieldFunction(group, moment);
16,588✔
140
            field_function_list_per_moment.append(self.GetFieldFunctions()[ff_index]);
16,588✔
141
          }
142
          field_function_list_per_group.append(field_function_list_per_moment);
5,668✔
143
        }
5,668✔
144
      }
145
      return field_function_list_per_group;
215✔
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
451✔
170
  );
171
  lbs_problem.def(
451✔
172
    "GetPowerFieldFunction",
173
    &LBSProblem::GetPowerFieldFunction,
451✔
174
    R"(
175
    Returns the power generation field function, if enabled.
176
    )"
177
  );
178
  lbs_problem.def(
451✔
179
    "SetOptions",
180
    [](LBSProblem& self, py::kwargs& params)
451✔
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(
451✔
239
    "ComputeFissionRate",
240
    [](LBSProblem& self, const std::string& scalar_flux_iterate)
451✔
241
    {
242
      const std::vector<double>* phi_ptr = nullptr;
×
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")
451✔
278
  );
279
  lbs_problem.def(
451✔
280
    "WriteFluxMoments",
281
    [](LBSProblem& self, const std::string& file_base)
483✔
282
    {
283
      LBSSolverIO::WriteFluxMoments(self, file_base);
32✔
284
    },
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")
451✔
294
  );
295
  lbs_problem.def(
451✔
296
    "CreateAndWriteSourceMoments",
297
    [](LBSProblem& self, const std::string& file_base)
455✔
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")
451✔
311
  );
312
  lbs_problem.def(
451✔
313
    "ReadFluxMomentsAndMakeSourceMoments",
314
    [](LBSProblem& self, const std::string& file_base, bool single_file_flag)
451✔
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"),
902✔
334
    py::arg("single_file_flag")
451✔
335
  );
336
  lbs_problem.def(
451✔
337
    "ReadSourceMoments",
338
    [](LBSProblem& self, const std::string& file_base, bool single_file_flag)
455✔
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"),
902✔
353
    py::arg("single_file_flag")
451✔
354
  );
355
  lbs_problem.def(
451✔
356
    "ReadFluxMoments",
357
    [](LBSProblem& self, const std::string& file_base, bool single_file_flag)
451✔
358
    {
359
      LBSSolverIO::ReadFluxMoments(self, file_base, single_file_flag);
×
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"),
902✔
372
    py::arg("single_file_flag")
451✔
373
  );
374
  lbs_problem.def(
451✔
375
    "WriteAngularFluxes",
376
    [](DiscreteOrdinatesProblem& self, const std::string& file_base)
455✔
377
    {
378
      LBSSolverIO::WriteAngularFluxes(self, file_base);
4✔
379
    },
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")
451✔
389
  );
390
  lbs_problem.def(
451✔
391
    "ReadAngularFluxes",
392
    [](DiscreteOrdinatesProblem& self, const std::string& file_base)
455✔
393
    {
394
      LBSSolverIO::ReadAngularFluxes(self, file_base);
4✔
395
    },
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")
451✔
405
  );
406
  lbs_problem.def(
451✔
407
    "SetPointSources",
408
    [](LBSProblem& self, py::kwargs& params)
451✔
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(
451✔
439
    "SetVolumetricSources",
440
    [](LBSProblem& self, py::kwargs& params)
471✔
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)
48✔
451
          {
452
            self.AddVolumetricSource(source.cast<std::shared_ptr<VolumetricSource>>());
32✔
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(
451✔
471
    "SetBoundaryOptions",
472
    [](LBSProblem& self, py::kwargs& params)
451✔
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
  lbs_problem.def(
451✔
495
    "SetAdjoint",
496
    [](LBSProblem& self, bool adjoint)
×
497
    {
498
      self.SetAdjoint(adjoint);
12✔
499
    }
500
  );
501

502
  // discrete ordinate solver
503
  auto do_problem = py::class_<DiscreteOrdinatesProblem, std::shared_ptr<DiscreteOrdinatesProblem>,
451✔
504
                               LBSProblem>(
505
    slv,
506
    "DiscreteOrdinatesProblem",
507
    R"(
508
    Base class for discrete ordinates problems in Cartesian geometry.
509

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

514
    Wrapper of :cpp:class:`opensn::DiscreteOrdinatesProblem`.
515
    )"
516
  );
451✔
517
  do_problem.def(
902✔
518
    py::init(
451✔
519
      [](py::kwargs& params)
318✔
520
      {
521
        return DiscreteOrdinatesProblem::Create(kwargs_to_param_block(params));
318✔
522
      }
523
    ),
524
    R"(
525
    Construct a discrete ordinates problem with Cartesian geometry.
526

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

564
    Parameters
565
    ----------
566
    adjoint: bool, default=False
567
        Flag for toggling whether the solver is in adjoint mode.
568
    )"
569
  );
570
  do_problem.def(
451✔
571
    "GetPsi",
572
    [](DiscreteOrdinatesProblem& self)
459✔
573
    {
574
      const auto& psi = self.GetPsiNewLocal();
8✔
575
      py::list psi_list;
8✔
576
      for (const auto& vec : psi)
16✔
577
      {
578
        auto array = py::array_t<double>(static_cast<py::ssize_t>(vec.size()),
8✔
579
                                         vec.data(),
580
                                         py::cast(self));
8✔
581
        psi_list.append(array);
8✔
582
      }
8✔
583
      return psi_list;
8✔
NEW
584
    },
×
585
    R"(
586
    Return psi as a list of NumPy arrays (float64), using zero-copy views into the
587
    underlying data.
588
    )"
589
  );
590
  do_problem.def(
451✔
591
    "ComputeBalance",
592
    [](DiscreteOrdinatesProblem& self)
478✔
593
    {
594
      ComputeBalance(self);
27✔
595
    },
596
    R"(
597
    Compute and print particle balance for the problem.
598
    )"
599
  );
600
  do_problem.def(
451✔
601
    "ComputeLeakage",
602
    [](DiscreteOrdinatesProblem& self, py::list bnd_names)
478✔
603
    {
604
      auto grid = self.GetGrid();
27✔
605
      // get the supported boundaries
606
      std::map<std::string, std::uint64_t> allowed_bd_names = grid->GetBoundaryNameMap();
27✔
607
      std::map<std::uint64_t, std::string> allowed_bd_ids = grid->GetBoundaryIDMap();
27✔
608
      // get the boundaries to parse, preserving user order
609
      std::vector<std::uint64_t> bndry_ids;
27✔
610
      if (bnd_names.size() > 1)
27✔
611
      {
612
        for (py::handle name : bnd_names)
120✔
613
        {
614
          auto sname = name.cast<std::string>();
72✔
615
          bndry_ids.push_back(allowed_bd_names.at(sname));
72✔
616
        }
72✔
617
      }
618
      else
619
      {
620
        bndry_ids = self.GetGrid()->GetUniqueBoundaryIDs();
9✔
621
      }
622
      // compute the leakage
623
      std::map<std::uint64_t, std::vector<double>> leakage = ComputeLeakage(self, bndry_ids);
27✔
624
      // convert result to native Python
625
      py::dict result;
27✔
626
      for (const auto& bndry_id : bndry_ids)
105✔
627
      {
628
        const auto it = leakage.find(bndry_id);
78✔
629
        if (it == leakage.end())
78✔
NEW
630
          continue;
×
631
        // construct numpy array and copy contents
632
        const auto& grp_wise_leakage = it->second;
78✔
633
        py::array_t<double> np_vector(py::ssize_t(grp_wise_leakage.size()));
78✔
634
        auto buffer = np_vector.request();
78✔
635
        auto *np_vector_data = static_cast<double*>(buffer.ptr);
78✔
636
        std::copy(grp_wise_leakage.begin(), grp_wise_leakage.end(), np_vector_data);
78✔
637
        const std::string& name = allowed_bd_ids.at(bndry_id);
78✔
638
        result[py::str(name)] = std::move(np_vector);
156✔
639
      }
78✔
640

641
      return result;
27✔
642
    },
54✔
643
    R"(
644
    Compute leakage for the problem.
645

646
    Parameters
647
    ----------
648
    bnd_names : List[str]
649
        A list of boundary names for which leakage should be computed.
650

651
    Returns
652
    -------
653
    Dict[str, numpy.ndarray]
654
        A dictionary mapping boundary names to group-wise leakage vectors.
655
        Each array contains the outgoing angular flux (per group) integrated over
656
        the corresponding boundary surface.
657

658
    Raises
659
    ------
660
    RuntimeError
661
        If `save_angular_flux` option was not enabled during problem setup.
662

663
    ValueError
664
        If one or more boundary ids are not present on the current mesh.
665
    )",
666
    py::arg("bnd_names")
451✔
667
  );
668

669
  // discrete ordinates curvilinear problem
670
  auto do_curvilinear_problem = py::class_<DiscreteOrdinatesCurvilinearProblem,
451✔
671
                                           std::shared_ptr<DiscreteOrdinatesCurvilinearProblem>,
672
                                           DiscreteOrdinatesProblem>(
673
    slv,
674
    "DiscreteOrdinatesCurvilinearProblem",
675
    R"(
676
    Base class for discrete ordinates problems in curvilinear geometry.
677

678
    Wrapper of :cpp:class:`opensn::DiscreteOrdinatesCurvilinearProblem`.
679
    )"
680
  );
451✔
681
  do_curvilinear_problem.def(
902✔
682
    py::init(
451✔
683
      [](py::kwargs& params)
8✔
684
      {
685
        return DiscreteOrdinatesCurvilinearProblem::Create(kwargs_to_param_block(params));
8✔
686
      }
687
    ),
688
    R"(
689
    Construct a discrete ordinates problem for curvilinear geometry.
690

691
    Warnings
692
    --------
693
       DiscreteOrdinatesCurvilinearProblem is **experimental** and should be used with caution!
694

695
    Parameters
696
    ----------
697
    mesh : MeshContinuum
698
        The spatial mesh.
699
    coord_system : int
700
        Coordinate system to use. Must be set to 2 (cylindrical coordinates).
701
    num_groups : int
702
        The total number of energy groups.
703
    groupsets : list of dict
704
        A list of input parameter blocks, each block provides the iterative properties for a
705
        groupset.
706
    xs_map : list of dict
707
        A list of mappings from block ids to cross-section definitions.
708
    boundary_conditions: List[Dict], default=[]
709
        A list containing tables for each boundary specification.
710
    point_sources: List[pyopensn.source.PointSource], default=[]
711
        A list of point sources.
712
    volumetric_sources: List[pyopensn.source.VolumetricSource], default=[]
713
        A list of volumetric sources.
714
    options : dict, optional
715
        A block of optional configuration parameters. See `SetOptions` for available settings.
716
    sweep_type : str, optional
717
        The sweep type to use. Must be one of `AAH` or `CBC`. Defaults to `AAH`.
718
    )"
719
  );
720
}
451✔
721

722
// Wrap steady-state solver
723
void
724
WrapSteadyState(py::module& slv)
451✔
725
{
726
  // clang-format off
727
  // steady state solver
728
  auto steady_state_solver = py::class_<SteadyStateSourceSolver, std::shared_ptr<SteadyStateSourceSolver>,
451✔
729
                                        Solver>(
730
    slv,
731
    "SteadyStateSourceSolver",
732
    R"(
733
    Steady state solver.
734

735
    Wrapper of :cpp:class:`opensn::SteadyStateSourceSolver`.
736
    )"
737
  );
451✔
738
  steady_state_solver.def(
902✔
739
    py::init(
451✔
740
      [](py::kwargs& params)
261✔
741
      {
742
        return SteadyStateSourceSolver::Create(kwargs_to_param_block(params));
261✔
743
      }
744
    ),
745
    R"(
746
    Construct a steady state solver.
747

748
    Parameters
749
    ----------
750
    pyopensn.solver.LBSProblem : LBSProblem
751
        Existing LBSProblem instance.
752
    )"
753
  );
754
  // clang-format on
755
}
451✔
756

757
// Wrap non-linear k-eigen solver
758
void
759
WrapNLKEigen(py::module& slv)
451✔
760
{
761
  // clang-format off
762
  // non-linear k-eigen solver
763
  auto non_linear_k_eigen_solver = py::class_<NonLinearKEigenSolver, std::shared_ptr<NonLinearKEigenSolver>,
451✔
764
                                              Solver>(
765
    slv,
766
    "NonLinearKEigenSolver",
767
    R"(
768
    Non-linear k-eigenvalue solver.
769

770
    Wrapper of :cpp:class:`opensn::NonLinearKEigenSolver`.
771
    )"
772
  );
451✔
773
  non_linear_k_eigen_solver.def(
902✔
774
    py::init(
451✔
775
      [](py::kwargs& params)
28✔
776
      {
777
        return NonLinearKEigenSolver::Create(kwargs_to_param_block(params));
28✔
778
      }
779
        ),
780
    R"(
781
    Construct a non-linear k-eigenvalue solver.
782

783
    Parameters
784
    ----------
785
    lbs_problem: pyopensn.solver.LBSProblem
786
        Existing LBSProblem instance.
787
    nl_abs_tol: float, default=1.0e-8
788
        Non-linear absolute tolerance.
789
    nl_rel_tol: float, default=1.0e-8
790
        Non-linear relative tolerance.
791
    nl_sol_tol: float, default=1.0e-50
792
        Non-linear solution tolerance.
793
    nl_max_its: int, default=50
794
        Non-linear algorithm maximum iterations.
795
    l_abs_tol: float, default=1.0e-8
796
        Linear absolute tolerance.
797
    l_rel_tol: float, default=1.0e-8
798
        Linear relative tolerance.
799
    l_div_tol: float, default=1.0e6
800
        Linear divergence tolerance.
801
    l_max_its: int, default=50
802
        Linear algorithm maximum iterations.
803
    l_gmres_restart_intvl: int, default=30
804
        GMRES restart interval.
805
    l_gmres_breakdown_tol: float, default=1.0e6
806
        GMRES breakdown tolerance.
807
    reset_phi0: bool, default=True
808
        If true, reinitializes scalar fluxes to 1.0.
809
    num_initial_power_iterations: int, default=0
810
        Number of initial power iterations before the non-linear solve.
811
    )"
812
  );
813
  non_linear_k_eigen_solver.def(
451✔
814
    "GetEigenvalue",
815
    &NonLinearKEigenSolver::GetEigenvalue,
451✔
816
    R"(
817
    Return the current k‑eigenvalue.
818
    )"
819
  );
820
  // clang-format on
821
}
451✔
822

823
// Wrap power iteration solvers
824
void
825
WrapPIteration(py::module& slv)
451✔
826
{
827
  // clang-format off
828
  // power iteration k-eigen solver
829
  auto pi_k_eigen_solver = py::class_<PowerIterationKEigenSolver, std::shared_ptr<PowerIterationKEigenSolver>,
451✔
830
                                      Solver>(
831
    slv,
832
    "PowerIterationKEigenSolver",
833
    R"(
834
    Power iteration k-eigenvalue solver.
835

836
    Wrapper of :cpp:class:`opensn::PowerIterationKEigenSolver`.
837
    )"
838
  );
451✔
839
  pi_k_eigen_solver.def(
902✔
840
    py::init(
451✔
841
      [](py::kwargs& params)
35✔
842
      {
843
        return PowerIterationKEigenSolver::Create(kwargs_to_param_block(params));
35✔
844
      }
845
    ),
846
    R"(
847
    Construct a power iteration k-eigen solver.
848

849
    Parameters
850
    ----------
851
    problem: pyopensn.solver.LBSProblem
852
        Existing DiscreteOrdinatesProblem instance.
853
    acceleration: pyopensn.solver.DiscreteOrdinatesKEigenAcceleration
854
        Optional DiscreteOrdinatesKEigenAcceleration instance for acceleration.
855
    max_iters: int, default = 1000
856
        Maximum power iterations allowed.
857
    k_tol: float, default = 1.0e-10
858
        Tolerance on the k-eigenvalue.
859
    reset_solution: bool, default=True
860
        If true, initialize flux moments to 1.0.
861
    reset_phi0: bool, default=True
862
        If true, reinitializes scalar fluxes to 1.0.
863
    )"
864
  );
865
  pi_k_eigen_solver.def(
451✔
866
    "GetEigenvalue",
867
    &PowerIterationKEigenSolver::GetEigenvalue,
451✔
868
    R"(
869
    Return the current k‑eigenvalue.
870
    )"
871
  );
872
  // clang-format on
873
}
451✔
874

875
// Wrap LBS solver
876
void
877
WrapDiscreteOrdinatesKEigenAcceleration(py::module& slv)
451✔
878
{
879
  // clang-format off
880
  // discrete ordinates k-eigen acceleration base
881
  auto acceleration = py::class_<DiscreteOrdinatesKEigenAcceleration,
451✔
882
                                 std::shared_ptr<DiscreteOrdinatesKEigenAcceleration>>(
883
    slv,
884
    "DiscreteOrdinatesKEigenAcceleration",
885
    R"(
886
    Base class for discrete ordinates k-eigenvalue acceleration methods.
887

888
    Wrapper of :cpp:class:`opensn::DiscreteOrdinatesKEigenAcceleration`.
889
    )"
890
  );
451✔
891
  // SCDSA acceleration
892
  auto scdsa_acceleration = py::class_<SCDSAAcceleration,
451✔
893
                                       std::shared_ptr<SCDSAAcceleration>,
894
                                       DiscreteOrdinatesKEigenAcceleration>(
895
    slv,
896
    "SCDSAAcceleration",
897
    R"(
898
    Construct an SCDSA accelerator for the power iteration k-eigenvalue solver.
899

900
    Wrapper of :cpp:class:`opensn::SCDSAAcceleration`.
901
    )"
902
  );
451✔
903
  scdsa_acceleration.def(
902✔
904
    py::init(
451✔
905
      [](py::kwargs& params)
8✔
906
      {
907
        return SCDSAAcceleration::Create(kwargs_to_param_block(params));
8✔
908
      }
909
    ),
910
    R"(
911
    SCDSA acceleration for the power iteration k-eigenvalue solver.
912

913
    Parameters
914
    ----------
915
    problem: pyopensn.solver.LBSProblem
916
        Existing DiscreteOrdinatesProblem instance.
917
    l_abs_tol: float, defauilt=1.0e-10
918
        Absolute residual tolerance.
919
    max_iters: int, default=100
920
        Maximum allowable iterations.
921
    verbose: bool, default=False
922
        If true, enables verbose output.
923
    petsc_options: str, default="ssss"
924
        Additional PETSc options.
925
    pi_max_its: int, default=50
926
        Maximum allowable iterations for inner power iterations.
927
    pi_k_tol: float, default=1.0e-10
928
        k-eigenvalue tolerance for the inner power iterations.
929
    sdm: str, default="pwld"
930
        Spatial discretization method to use for the diffusion solver. Valid choices are:
931
            - 'pwld' : Piecewise Linear Discontinuous
932
            - 'pwlc' : Piecewise Linear Continuous
933
    )"
934
  );
935
  // SMM acceleration
936
  auto smm_acceleration = py::class_<SMMAcceleration,
451✔
937
                                     std::shared_ptr<SMMAcceleration>,
938
                                     DiscreteOrdinatesKEigenAcceleration>(
939
    slv,
940
    "SMMAcceleration",
941
    R"(
942
    Construct an SMM accelerator for the power iteration k-eigenvalue solver.
943

944
    Wrapper of :cpp:class:`opensn::SMMAcceleration`.
945
    )"
946
  );
451✔
947
  smm_acceleration.def(
902✔
948
    py::init(
451✔
949
      [](py::kwargs& params)
4✔
950
      {
951
        return SMMAcceleration::Create(kwargs_to_param_block(params));
4✔
952
      }
953
    ),
954
    R"(
955
    SMM acceleration for the power iteration k-eigenvalue solver.
956

957
    Warnings
958
    --------
959
       SMM acceleration is **experimental** and should be used with caution!
960
       SMM accleration only supports problems with isotropic scattering.
961

962
    Parameters
963
    ----------
964
    problem: pyopensn.solver.LBSProblem
965
        Existing DiscreteOrdinatesProblem instance.
966
    l_abs_tol: float, defauilt=1.0e-10
967
        Absolute residual tolerance.
968
    max_iters: int, default=100
969
        Maximum allowable iterations.
970
    verbose: bool, default=False
971
        If true, enables verbose output.
972
    petsc_options: str, default="ssss"
973
        Additional PETSc options.
974
    pi_max_its: int, default=50
975
        Maximum allowable iterations for inner power iterations.
976
    pi_k_tol: float, default=1.0e-10
977
        k-eigenvalue tolerance for the inner power iterations.
978
    sdm: str, default="pwld"
979
        Spatial discretization method to use for the diffusion solver. Valid choices are:
980
            - 'pwld' : Piecewise Linear Discontinuous
981
            - 'pwlc' : Piecewise Linear Continuous
982
    )"
983
  );
984
  // clang-format on
985
}
451✔
986

987
// Wrap the solver components of OpenSn
988
void
989
py_solver(py::module& pyopensn)
62✔
990
{
991
  py::module slv = pyopensn.def_submodule("solver", "Solver module.");
62✔
992
  WrapProblem(slv);
62✔
993
  WrapSolver(slv);
62✔
994
  WrapLBS(slv);
62✔
995
  WrapSteadyState(slv);
62✔
996
  WrapNLKEigen(slv);
62✔
997
  WrapDiscreteOrdinatesKEigenAcceleration(slv);
62✔
998
  WrapPIteration(slv);
62✔
999
}
62✔
1000

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