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

Open-Sn / opensn / 23371558143

20 Mar 2026 05:40PM UTC coverage: 73.918% (-0.5%) from 74.407%
23371558143

push

github

web-flow
Merge pull request #980 from wdhawkins/transient_fix

Transient solver should always compute t^(n+1)

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

304 existing lines in 15 files now uncovered.

20502 of 27736 relevant lines covered (73.92%)

66612898.8 hits per line

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

97.27
/python/lib/aquad.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/math/quadratures/angular/angular_quadrature.h"
6
#include "framework/math/quadratures/angular/curvilinear_product_quadrature.h"
7
#include "framework/math/quadratures/angular/product_quadrature.h"
8
#include "framework/math/quadratures/angular/triangular_quadrature.h"
9
#include "framework/math/quadratures/angular/sldfe_sq_quadrature.h"
10
#include "framework/math/quadratures/angular/lebedev_quadrature.h"
11
#include <pybind11/stl.h>
12
#include <pybind11/numpy.h>
13
#include <memory>
14
#include <stdexcept>
15

16
namespace opensn
17
{
18

19
// Dictionary for Sn Scattering Source Representation
20
static std::map<std::string, OperatorConstructionMethod> op_cons_type_map{
21
  {"standard", OperatorConstructionMethod::STANDARD},
22
  {"galerkin_one", OperatorConstructionMethod::GALERKIN_ONE},
23
  {"galerkin_three", OperatorConstructionMethod::GALERKIN_THREE}};
24

25
static unsigned int
26
GetScatteringOrder(py::kwargs& params)
598✔
27
{
28
  std::string method_str = "standard";
598✔
29
  if (params.contains("operator_method"))
598✔
30
    method_str = py::str(params["operator_method"]).cast<std::string>();
18✔
31
  if (op_cons_type_map.at(method_str) == OperatorConstructionMethod::GALERKIN_ONE)
598✔
32
    return pop_cast(params, "scattering_order", py::int_(0)).cast<unsigned int>();
3✔
33
  else
34
    return pop_cast(params, "scattering_order").cast<unsigned int>();
595✔
35
}
598✔
36

37
// Wrap quadrature point
38
void
39
WrapQuadraturePointPhiTheta(py::module& aquad)
681✔
40
{
41
  // clang-format off
42
  py::class_<QuadraturePointPhiTheta> quad_pt_phi_theta(aquad,
681✔
43
    "QuadraturePointPhiTheta",
44
    R"(
45
    Angular quadrature point.
46

47
    Wrapper of :cpp:class:`opensn::QuadraturePointPhiTheta`.
48
    )"
49
  );
681✔
50
  quad_pt_phi_theta.def_readonly(
681✔
51
    "phi",
52
    &QuadraturePointPhiTheta::phi,
53
    "Azimuthal angle."
54
  );
55
  quad_pt_phi_theta.def_readonly(
681✔
56
    "theta",
57
    &QuadraturePointPhiTheta::theta,
58
    "Polar angle."
59
  );
60
  quad_pt_phi_theta.def(
681✔
61
    "__repr__",
62
    [](QuadraturePointPhiTheta& self)
681✔
63
    {
UNCOV
64
      std::ostringstream os;
×
UNCOV
65
      os << "QuadraturePointPhiTheta(phi=" << self.phi << ", theta=" << self.theta << ")";
×
UNCOV
66
      return os.str();
×
UNCOV
67
    }
×
68
  );
69
  // clang-format on
70
}
681✔
71

72
// Wrap harmonic indices
73
static void
74
WrapHarmonicIndices(py::module& aquad)
72✔
75
{
76
  py::class_<AngularQuadrature::HarmonicIndices> harmonic_indices(aquad, "HarmonicIndices");
72✔
77
  harmonic_indices.def_readonly("ell", &AngularQuadrature::HarmonicIndices::ell);
72✔
78
  harmonic_indices.def_readonly("m", &AngularQuadrature::HarmonicIndices::m);
72✔
79
}
72✔
80

81
// Wrap angular quadrature
82
void
83
WrapQuadrature(py::module& aquad)
681✔
84
{
85
  // clang-format off
86
  // angular quadrature
87
  auto angular_quadrature = py::class_<AngularQuadrature, std::shared_ptr<AngularQuadrature>>(
681✔
88
    aquad,
89
    "AngularQuadrature",
90
    R"(
91
    Angular quadrature.
92

93
    Wrapper of :cpp:class:`opensn::AngularQuadrature`.
94
    )"
95
  );
681✔
96
  angular_quadrature.def_readonly(
681✔
97
    "abscissae",
98
    &AngularQuadrature::abscissae,
99
    "Vector of polar and azimuthal angles."
100
  );
101
  angular_quadrature.def_readonly(
681✔
102
    "weights",
103
    &AngularQuadrature::weights,
104
    "Quadrature weights."
105
  );
106
  angular_quadrature.def_readonly(
681✔
107
    "omegas",
108
    &AngularQuadrature::omegas,
109
    "Vector of direction vectors."
110
  );
111
  angular_quadrature.def(
681✔
112
    "GetDiscreteToMomentOperator",
113
    [](const AngularQuadrature& self) {
690✔
114
      const auto& op = self.GetDiscreteToMomentOperator();
9✔
115
      if (op.empty()) {
9✔
UNCOV
116
        return py::array_t<double>();
×
117
      }
118
      
119
      size_t num_rows = op.size();
9✔
120
      size_t num_cols = op[0].size();
9✔
121
      
122
      // Create numpy array with shape [num_rows, num_cols]
123
      py::array_t<double> result = py::array_t<double>(
9✔
124
        {num_rows, num_cols},  // shape
125
        {sizeof(double) * num_cols, sizeof(double)}  // strides (row-major)
9✔
126
      );
18✔
127
      
128
      py::buffer_info buf = result.request();
9✔
129
      auto* ptr = static_cast<double*>(buf.ptr);
9✔
130
      
131
      // Copy data row by row
132
      for (size_t i = 0; i < num_rows; ++i) {
1,353✔
133
        std::copy(op[i].begin(), op[i].end(), ptr + i * num_cols);
1,344✔
134
      }
135
      
136
      return result;
9✔
137
    },
9✔
138
    "Get the discrete-to-moment operator as a numpy array."
139
  );
140
  angular_quadrature.def(
681✔
141
    "GetMomentToDiscreteOperator",
142
    [](const AngularQuadrature& self) {
690✔
143
      const auto& op = self.GetMomentToDiscreteOperator();
9✔
144
      if (op.empty()) {
9✔
UNCOV
145
        return py::array_t<double>();
×
146
      }
147
      
148
      size_t num_rows = op.size();
9✔
149
      size_t num_cols = op[0].size();
9✔
150
      
151
      // Create numpy array with shape [num_rows, num_cols]
152
      py::array_t<double> result = py::array_t<double>(
9✔
153
        {num_rows, num_cols},  // shape
154
        {sizeof(double) * num_cols, sizeof(double)}  // strides (row-major)
9✔
155
      );
18✔
156
      
157
      py::buffer_info buf = result.request();
9✔
158
      auto* ptr = static_cast<double*>(buf.ptr);
9✔
159
      
160
      // Copy data row by row
161
      for (size_t i = 0; i < num_rows; ++i) {
1,353✔
162
        std::copy(op[i].begin(), op[i].end(), ptr + i * num_cols);
1,344✔
163
      }
164
      
165
      return result;
9✔
166
    },
9✔
167
    "Get the moment-to-discrete operator as a numpy array."
168
  );
169
  angular_quadrature.def(
681✔
170
    "GetMomentToHarmonicsIndexMap",
171
    &AngularQuadrature::GetMomentToHarmonicsIndexMap,
1,362✔
172
    py::return_value_policy::reference_internal
681✔
173
  );
174
  // clang-format on
175
}
681✔
176

177
// Wrap product qudrature
178
void
179
WrapProductQuadrature(py::module& aquad)
681✔
180
{
181
  // clang-format off
182
  // product quadrature
183
  auto product_quadrature = py::class_<ProductQuadrature, std::shared_ptr<ProductQuadrature>,
681✔
184
                                       AngularQuadrature>(
185
    aquad,
186
    "ProductQuadrature",
187
    R"(
188
    Product quadrature.
189

190
    Wrapper of :cpp:class:`opensn::ProductQuadrature`.
191
    )"
192
  );
681✔
193

194
  // Gauss-Legendre 1D slab product quadrature
195
  auto angular_quadrature_gl_prod_1d_slab = py::class_<GLProductQuadrature1DSlab,
681✔
196
                                                       std::shared_ptr<GLProductQuadrature1DSlab>,
197
                                                       ProductQuadrature>(
198
    aquad,
199
    "GLProductQuadrature1DSlab",
200
    R"(
201
    Gauss-Legendre quadrature for 1D, slab geometry.
202

203
    Wrapper of :cpp:class:`opensn::GLProductQuadrature1DSlab`.
204
    )"
205
  );
681✔
206
  angular_quadrature_gl_prod_1d_slab.def(
1,362✔
207
    py::init(
681✔
208
      [](py::kwargs& params)
101✔
209
      {
210
        auto scattering_order = GetScatteringOrder(params);
101✔
211
        static const std::vector<std::string> required_keys = {"n_polar"};
579✔
212
        const std::vector<std::pair<std::string, py::object>> optional_keys = {{"operator_method", py::str("standard")}, {"verbose", py::bool_(false)}};
606✔
213
        auto [n_polar, method_str, verbose] = extract_args_tuple<unsigned int, std::string, bool>(params, required_keys, optional_keys);
101✔
214
        return std::make_shared<GLProductQuadrature1DSlab>(n_polar, scattering_order, verbose, op_cons_type_map.at(method_str));
202✔
215
      }
303✔
216
    ),
217
    R"(
218
    Construct a Gauss-Legendre product quadrature for 1D, slab geometry.
219

220
    Parameters
221
    ----------
222
    n_polar: int
223
        Number of polar angles.
224
    scattering_order: int
225
        Maximum scattering order supported by the angular quadrature.
226
        Optional when ``operator_method='galerkin_one'``, in which case the scattering order
227
        is automatically determined so that the number of moments equals the number of angles.
228
    operator_method: {'standard', 'galerkin_one', 'galerkin_three'}, default='standard'
229
        Method used to construct the discrete-to-moment and moment-to-discrete operators.
230
    verbose: bool, default=False
231
        Verbosity.
232
    )"
233
  );
234

235
  // Gauss-Legendre-Chebyshev 2D XY product quadrature
236
  auto angular_quadrature_glc_prod_2d_xy = py::class_<GLCProductQuadrature2DXY,
681✔
237
                                                      std::shared_ptr<GLCProductQuadrature2DXY>,
238
                                                      ProductQuadrature>(
239
    aquad,
240
    "GLCProductQuadrature2DXY",
241
    R"(
242
    Gauss-Legendre-Chebyshev quadrature for 2D, XY geometry.
243

244
    Wrapper of :cpp:class:`opensn::GLCProductQuadrature2DXY`.
245
    )"
246
  );
681✔
247
  angular_quadrature_glc_prod_2d_xy.def(
1,362✔
248
    py::init(
681✔
249
      [](py::kwargs& params)
157✔
250
      {
251
        auto scattering_order = GetScatteringOrder(params);
157✔
252
        static const std::vector<std::string> required_keys = {"n_polar", "n_azimuthal"};
314✔
253
        const std::vector<std::pair<std::string, py::object>> optional_keys = {{"operator_method", py::str("standard")}, {"verbose", py::bool_(false)}};
942✔
254
        auto [n_polar, n_azimuthal, method_str, verbose] = extract_args_tuple<unsigned int, unsigned int, std::string, bool>(params, required_keys, optional_keys);
157✔
255
        return std::make_shared<GLCProductQuadrature2DXY>(n_polar, n_azimuthal, scattering_order, verbose, op_cons_type_map.at(method_str));
314✔
256
      }
471✔
257
    ),
258
    R"(
259
    Construct a Gauss-Legendre-Chebyshev product quadrature for 2D, XY geometry.
260

261
    Parameters
262
    ----------
263
    n_polar: int
264
        Number of polar angles.
265
    n_azimuthal: int
266
        Number of azimuthal angles.
267
    scattering_order: int
268
        Maximum scattering order supported by the angular quadrature.
269
        Optional when ``operator_method='galerkin_one'``, in which case the scattering order
270
        is automatically determined so that the number of moments equals the number of angles.
271
    operator_method: {'standard', 'galerkin_one', 'galerkin_three'}, default='standard'
272
        Method used to construct the discrete-to-moment and moment-to-discrete operators.
273
    verbose: bool, default=False
274
        Verbosity.
275
    )"
276
  );
277

278
  // Gauss-Legendre-Chebyshev 3D XYZ product quadrature
279
  auto angular_quadrature_glc_prod_3d_xyz = py::class_<GLCProductQuadrature3DXYZ,
681✔
280
                                                       std::shared_ptr<GLCProductQuadrature3DXYZ>,
281
                                                       ProductQuadrature>(
282
    aquad,
283
    "GLCProductQuadrature3DXYZ",
284
    R"(
285
    Gauss-Legendre-Chebyshev quadrature for 3D, XYZ geometry.
286

287
    Wrapper of :cpp:class:`opensn::GLCProductQuadrature3DXYZ`.
288
    )"
289
  );
681✔
290
  angular_quadrature_glc_prod_3d_xyz.def(
1,362✔
291
    py::init(
681✔
292
      [](py::kwargs& params)
245✔
293
      {
294
        auto scattering_order = GetScatteringOrder(params);
245✔
295
        static const std::vector<std::string> required_keys = {"n_polar", "n_azimuthal"};
477✔
296
        const std::vector<std::pair<std::string, py::object>> optional_keys = {{"operator_method", py::str("standard")}, {"verbose", py::bool_(false)}};
1,470✔
297
        auto [n_polar, n_azimuthal, method_str, verbose] = extract_args_tuple<unsigned int, unsigned int, std::string, bool>(params, required_keys, optional_keys);
245✔
298
        return std::make_shared<GLCProductQuadrature3DXYZ>(n_polar, n_azimuthal, scattering_order, verbose, op_cons_type_map.at(method_str));
490✔
299
      }
735✔
300
    ),
301
    R"(
302
    Construct a Gauss-Legendre-Chebyshev product quadrature for 3D, XYZ geometry.
303

304
    Parameters
305
    ----------
306
    n_polar: int
307
        Number of polar angles.
308
    n_azimuthal: int
309
        Number of azimuthal angles.
310
    scattering_order: int
311
        Maximum scattering order supported by the angular quadrature.
312
        Optional when ``operator_method='galerkin_one'``, in which case the scattering order
313
        is automatically determined so that the number of moments equals the number of angles.
314
    operator_method: {'standard', 'galerkin_one', 'galerkin_three'}, default='standard'
315
        Method used to construct the discrete-to-moment and moment-to-discrete operators.
316
    verbose: bool, default=False
317
        Verbosity.
318
    )"
319
  );
320
  // clang-format on
321
}
681✔
322

323
// Wrap triangular quadrature
324
void
325
WrapTriangularQuadrature(py::module& aquad)
681✔
326
{
327
  // clang-format off
328
  // triangular quadrature base class
329
  auto triangular_quadrature = py::class_<TriangularQuadrature, std::shared_ptr<TriangularQuadrature>,
681✔
330
                                          AngularQuadrature>(
331
    aquad,
332
    "TriangularQuadrature",
333
    R"(
334
    Triangular quadrature base class.
335

336
    Unlike product quadratures which have a fixed number of azimuthal angles per polar level,
337
    triangular quadratures have a varying number of azimuthal angles that decreases
338
    as the polar angle moves away from the equatorial plane.
339

340
    Wrapper of :cpp:class:`opensn::TriangularQuadrature`.
341
    )"
342
  );
681✔
343

344
  // Triangular GLC 3D XYZ quadrature
345
  auto angular_quadrature_triangular_glc_3d_xyz = py::class_<GLCTriangularQuadrature3DXYZ,
681✔
346
                                                             std::shared_ptr<GLCTriangularQuadrature3DXYZ>,
347
                                                             TriangularQuadrature>(
348
    aquad,
349
    "GLCTriangularQuadrature3DXYZ",
350
    R"(
351
    Triangular Gauss-Legendre-Chebyshev quadrature for 3D, XYZ geometry.
352

353
    For each polar level away from the equator, there is 1 less azimuthal angle
354
    per octant. The maximum number of azimuthal angles (at the equator) is
355
    automatically computed as 2 * n_polar.
356

357
    Wrapper of :cpp:class:`opensn::GLCTriangularQuadrature3DXYZ`.
358
    )"
359
  );
681✔
360
  angular_quadrature_triangular_glc_3d_xyz.def(
1,362✔
361
    py::init(
681✔
362
      [](py::kwargs& params)
1✔
363
      {
364
        auto scattering_order = GetScatteringOrder(params);
1✔
365
        static const std::vector<std::string> required_keys = {"n_polar"};
2✔
366
        const std::vector<std::pair<std::string, py::object>> optional_keys = {{"operator_method", py::str("standard")}, {"verbose", py::bool_(false)}};
6✔
367
        auto [n_polar, method_str, verbose] = extract_args_tuple<unsigned int, std::string, bool>(params, required_keys, optional_keys);
1✔
368
        return std::make_shared<GLCTriangularQuadrature3DXYZ>(n_polar, scattering_order, verbose, op_cons_type_map.at(method_str));
2✔
369
      }
3✔
370
    ),
371
    R"(
372
    Construct a Triangular Gauss-Legendre-Chebyshev quadrature for 3D, XYZ geometry.
373

374
    Parameters
375
    ----------
376
    n_polar: int
377
        Number of polar angles. The maximum number of azimuthal angles (at the equator)
378
        is automatically computed as ``2 * n_polar``.
379
    scattering_order: int
380
        Maximum scattering order supported by the angular quadrature.
381
        Optional when ``operator_method='galerkin_one'``, in which case the scattering order
382
        is automatically determined so that the number of moments equals the number of angles.
383
    operator_method: {'standard', 'galerkin_one', 'galerkin_three'}, default='standard'
384
        Method used to construct the discrete-to-moment and moment-to-discrete operators.
385
    verbose: bool, default=False
386
        Verbosity.
387
    )"
388
  );
389

390
  // Triangular GLC 2D XY quadrature
391
  auto angular_quadrature_triangular_glc_2d_xy = py::class_<GLCTriangularQuadrature2DXY,
681✔
392
                                                            std::shared_ptr<GLCTriangularQuadrature2DXY>,
393
                                                            TriangularQuadrature>(
394
    aquad,
395
    "GLCTriangularQuadrature2DXY",
396
    R"(
397
    Triangular Gauss-Legendre-Chebyshev quadrature for 2D, XY geometry.
398

399
    Only includes points in the upper hemisphere (z >= 0).
400

401
    Wrapper of :cpp:class:`opensn::GLCTriangularQuadrature2DXY`.
402
    )"
403
  );
681✔
404
  angular_quadrature_triangular_glc_2d_xy.def(
1,362✔
405
    py::init(
681✔
406
      [](py::kwargs& params)
2✔
407
      {
408
        auto scattering_order = GetScatteringOrder(params);
2✔
409
        static const std::vector<std::string> required_keys = {"n_polar"};
4✔
410
        const std::vector<std::pair<std::string, py::object>> optional_keys = {{"operator_method", py::str("standard")}, {"verbose", py::bool_(false)}};
12✔
411
        auto [n_polar, method_str, verbose] = extract_args_tuple<unsigned int, std::string, bool>(params, required_keys, optional_keys);
2✔
412
        return std::make_shared<GLCTriangularQuadrature2DXY>(n_polar, scattering_order, verbose, op_cons_type_map.at(method_str));
4✔
413
      }
6✔
414
    ),
415
    R"(
416
    Construct a Triangular Gauss-Legendre-Chebyshev quadrature for 2D, XY geometry.
417

418
    Parameters
419
    ----------
420
    n_polar: int
421
        Number of polar angles (only upper hemisphere will be used). The maximum
422
        number of azimuthal angles (at the equator) is automatically computed as 2 * n_polar.
423
    scattering_order: int
424
        Maximum scattering order supported by the angular quadrature.
425
        Optional when ``operator_method='galerkin_one'``, in which case the scattering order
426
        is automatically determined so that the number of moments equals the number of angles.
427
    operator_method: {'standard', 'galerkin_one', 'galerkin_three'}, default='standard'
428
        Method used to construct the discrete-to-moment and moment-to-discrete operators.
429
    verbose: bool, default=False
430
        Verbosity.
431
    )"
432
  );
433
  // clang-format on
434
}
681✔
435

436
// Wrap curvilinear product quadrature
437
void
438
WrapCurvilinearProductQuadrature(py::module& aquad)
681✔
439
{
440
  // clang-format off
441
  // curvilinear product quadrature
442
  auto curvilinear_product_quadrature = py::class_<CurvilinearProductQuadrature,
681✔
443
                                                   std::shared_ptr<CurvilinearProductQuadrature>,
444
                                                   ProductQuadrature>(
445
    aquad,
446
    "CurvilinearProductQuadrature",
447
    R"(
448
    Curvilinear product quadrature.
449

450
    Wrapper of :cpp:class:`opensn::CurvilinearProductQuadrature`.
451
    )"
452
  );
681✔
453

454
  // Gauss-Legendre-Chebyshev 2D RZ curvilinear product quadrature
455
  auto curvilinear_quadrature_glc_2d_rz = py::class_<GLCProductQuadrature2DRZ,
681✔
456
                                                     std::shared_ptr<GLCProductQuadrature2DRZ>,
457
                                                     CurvilinearProductQuadrature>(
458
    aquad,
459
    "GLCProductQuadrature2DRZ",
460
    R"(
461
    Gauss-Legendre-Chebyshev product quadrature for 2D, RZ geometry.
462

463
    Wrapper of :cpp:class:`opensn::GLCProductQuadrature2DRZ`.
464
    )"
465
  );
681✔
466
  curvilinear_quadrature_glc_2d_rz.def(
1,362✔
467
    py::init(
681✔
468
      [](py::kwargs& params)
68✔
469
      {
470
        auto scattering_order = GetScatteringOrder(params);
68✔
471
        static const std::vector<std::string> required_keys = {"n_polar", "n_azimuthal"};
136✔
472
        const std::vector<std::pair<std::string, py::object>> optional_keys = {{"operator_method", py::str("standard")}, {"verbose", py::bool_(false)}};
408✔
473
        auto [n_polar, n_azimuthal, method_str, verbose] = extract_args_tuple<unsigned int, unsigned int, std::string, bool>(params, required_keys, optional_keys);
68✔
474
        return std::make_shared<GLCProductQuadrature2DRZ>(n_polar, n_azimuthal, scattering_order, verbose, op_cons_type_map.at(method_str));
136✔
475
      }
204✔
476
    ),
477
    R"(
478
    Construct a Gauss-Legendre Chebyshev product quadrature for 2D, RZ geometry.
479

480
    Parameters
481
    ----------
482
    n_polar: int
483
        Number of polar angles.
484
    n_azimuthal: int
485
        Number of azimuthal angles.
486
    scattering_order: int
487
        Maximum scattering order supported by the angular quadrature.
488
        Optional when ``operator_method='galerkin_one'``, in which case the scattering order
489
        is automatically determined so that the number of moments equals the number of angles.
490
    operator_method: {'standard', 'galerkin_one', 'galerkin_three'}, default='standard'
491
        Method used to construct the discrete-to-moment and moment-to-discrete operators.
492
    verbose: bool, default=False
493
        Verbosity.
494
    )"
495
  );
496
  // clang-format on
497
}
681✔
498

499
// Wrap SLDFES quadrature
500
void
501
WrapSLDFEsqQuadrature(py::module& aquad)
681✔
502
{
503
  // clang-format off
504
  // Simplified LDFEsq quadrature
505
  auto sldfesq_quadrature_3d_xyz = py::class_<SLDFEsqQuadrature3DXYZ,
681✔
506
                                              std::shared_ptr<SLDFEsqQuadrature3DXYZ>,
507
                                              AngularQuadrature>(
508
    aquad,
509
    "SLDFEsqQuadrature3DXYZ",
510
    R"(
511
    Piecewise-linear finite element quadrature using quadrilaterals.
512

513
    Wrapper of :cpp:class:`opensn::SLDFEsqQuadrature3DXYZ`.
514
    )"
515
  );
681✔
516
  sldfesq_quadrature_3d_xyz.def(
1,362✔
517
    py::init(
681✔
518
      [](py::kwargs& params)
11✔
519
      {
520
        auto scattering_order = GetScatteringOrder(params);
11✔
521
        static const std::vector<std::string> required_keys = {"level"};
18✔
522
        const std::vector<std::pair<std::string, py::object>> optional_keys = {{"operator_method", py::str("standard")}, {"verbose", py::bool_(false)}};
66✔
523
        auto [level, method_str, verbose] = extract_args_tuple<int, std::string, bool>(params, required_keys, optional_keys);
11✔
524
        return std::make_shared<SLDFEsqQuadrature3DXYZ>(level, scattering_order, verbose, op_cons_type_map.at(method_str));
22✔
525
      }
33✔
526
    ),
527
    R"(
528
    Generates uniform spherical quadrilaterals from the subdivision of an inscribed cube.
529

530
    Parameters
531
    ----------
532
    level: int
533
        Number of subdivisions of the inscribed cube.
534
    scattering_order: int
535
        Maximum scattering order supported by the angular quadrature.
536
        Optional when ``operator_method='galerkin_one'``, in which case the scattering order
537
        is automatically determined so that the number of moments equals the number of angles.
538
    operator_method: {'standard', 'galerkin_one', 'galerkin_three'}, default='standard'
539
        Method used to construct the discrete-to-moment and moment-to-discrete operators.
540
    verbose: bool, default=False
541
        Verbosity.
542
    )"
543
  );
544
  sldfesq_quadrature_3d_xyz.def(
1,362✔
545
    "LocallyRefine",
546
    &SLDFEsqQuadrature3DXYZ::LocallyRefine,
1,362✔
547
    R"(
548
    Locally refines the cells.
549

550
    Parameters
551
    ----------
552
    ref_dir: pyopensn.math.Vector3
553
        Reference direction :math:`\vec{r}`.
554
    cone_size: float
555
        Cone size (in radians) :math:`\theta`.
556
    dir_as_plane_normal: bool, default=False
557
        If true, interpret SQ-splitting as when :math:`|\omega \cdot \vec{r}| < \sin(\theta)`.
558
        Otherwise, SQs will be split if :math:`\omega \cdot \vec{r} > \cos(\theta)`.
559
    )",
560
    py::arg("ref_dir"),
1,362✔
561
    py::arg("cone_size"),
681✔
562
    py::arg("dir_as_plane_normal") = false
681✔
563
  );
564
  sldfesq_quadrature_3d_xyz.def(
681✔
565
    "PrintQuadratureToFile",
566
    &SLDFEsqQuadrature3DXYZ::PrintQuadratureToFile,
1,362✔
567
    R"(
568
    Prints the quadrature to file.
569

570
    Parameters
571
    ----------
572
    file_base: str
573
        File base name.
574
    )",
575
    py::arg("file_base")
681✔
576
  );
577

578
  // 2D SLDFEsq quadrature
579
  auto sldfesq_quadrature_2d_xy = py::class_<SLDFEsqQuadrature2DXY,
681✔
580
                                             std::shared_ptr<SLDFEsqQuadrature2DXY>,
581
                                             AngularQuadrature>(
582
    aquad,
583
    "SLDFEsqQuadrature2DXY",
584
    R"(
585
    Two-dimensional variant of the piecewise-linear finite element quadrature.
586

587
    This quadrature is created from the 3D SLDFEsq set by removing directions with negative
588
    xi.
589

590
    Wrapper of :cpp:class:`opensn::SLDFEsqQuadrature2DXY`.
591
    )"
592
  );
681✔
593
  sldfesq_quadrature_2d_xy.def(
1,362✔
594
    py::init(
681✔
595
      [](py::kwargs& params)
4✔
596
      {
597
        auto scattering_order = GetScatteringOrder(params);
4✔
598
        static const std::vector<std::string> required_keys = {"level"};
8✔
599
        const std::vector<std::pair<std::string, py::object>> optional_keys = {{"operator_method", py::str("standard")}, {"verbose", py::bool_(false)}};
24✔
600
        auto [level, method_str, verbose] = extract_args_tuple<int, std::string, bool>(params, required_keys, optional_keys);
4✔
601
        return std::make_shared<SLDFEsqQuadrature2DXY>(level, scattering_order, verbose, op_cons_type_map.at(method_str));
8✔
602
      }
12✔
603
    ),
604
    R"(
605
    Generates a 2D SLDFEsq quadrature by removing directions with negative xi.
606

607
    Parameters
608
    ----------
609
    level: int
610
        Number of subdivisions of the inscribed cube.
611
    scattering_order: int
612
        Maximum scattering order supported by the angular quadrature.
613
        Optional when ``operator_method='galerkin_one'``, in which case the scattering order
614
        is automatically determined so that the number of moments equals the number of angles.
615
    operator_method: {'standard', 'galerkin_one', 'galerkin_three'}, default='standard'
616
        Method used to construct the discrete-to-moment and moment-to-discrete operators.
617
    verbose: bool, default=False
618
        Verbosity.
619
    )"
620
  );
621
  sldfesq_quadrature_2d_xy.def(
1,362✔
622
    "LocallyRefine",
623
    &SLDFEsqQuadrature2DXY::LocallyRefine,
1,362✔
624
    R"(
625
    Locally refines the cells.
626

627
    Parameters
628
    ----------
629
    ref_dir: pyopensn.math.Vector3
630
        Reference direction :math:`\vec{r}`.
631
    cone_size: float
632
        Cone size (in radians) :math:`\theta`.
633
    dir_as_plane_normal: bool, default=False
634
        If true, interpret SQ-splitting as when :math:`|\omega \cdot \vec{r}| < \sin(\theta)`.
635
        Otherwise, SQs will be split if :math:`\omega \cdot \vec{r} > \cos(\theta)`.
636
    )",
637
    py::arg("ref_dir"),
1,362✔
638
    py::arg("cone_size"),
681✔
639
    py::arg("dir_as_plane_normal") = false
681✔
640
  );
641
  sldfesq_quadrature_2d_xy.def(
681✔
642
    "PrintQuadratureToFile",
643
    &SLDFEsqQuadrature2DXY::PrintQuadratureToFile,
1,362✔
644
    R"(
645
    Prints the quadrature to file.
646

647
    Parameters
648
    ----------
649
    file_base: str
650
        File base name.
651
    )",
652
    py::arg("file_base")
681✔
653
  );
654
  // clang-format on
655
}
681✔
656

657
// Wrap Lebedev quadrature
658
void
659
WrapLebedevQuadrature(py::module& aquad)
681✔
660
{
661
  // clang-format off
662
  // Lebedev 3D XYZ quadrature
663
  auto angular_quadrature_lebedev_3d_xyz = py::class_<LebedevQuadrature3DXYZ,
681✔
664
                                                     std::shared_ptr<LebedevQuadrature3DXYZ>,
665
                                                     AngularQuadrature>(
666
    aquad,
667
    "LebedevQuadrature3DXYZ",
668
    R"(
669
    Lebedev quadrature for 3D, XYZ geometry.
670

671
    This quadrature provides high-order accuracy for spherical integration with
672
    symmetric distribution of points on the sphere.
673

674
    Wrapper of :cpp:class:`opensn::LebedevQuadrature3DXYZ`.
675
    )"
676
  );
681✔
677

678
  angular_quadrature_lebedev_3d_xyz.def(
1,362✔
679
    py::init(
681✔
680
      [](py::kwargs& params)
7✔
681
      {
682
        auto scattering_order = GetScatteringOrder(params);
7✔
683
        static const std::vector<std::string> required_keys = {"quadrature_order"};
13✔
684
        const std::vector<std::pair<std::string, py::object>> optional_keys = {{"operator_method", py::str("standard")}, {"verbose", py::bool_(false)}};
42✔
685
        auto [quadrature_order, method_str, verbose] = extract_args_tuple<unsigned int, std::string, bool>(params, required_keys, optional_keys);
7✔
686
        return std::make_shared<LebedevQuadrature3DXYZ>(quadrature_order, scattering_order, verbose, op_cons_type_map.at(method_str));
14✔
687
      }
21✔
688
    ),
689
    R"(
690
    Constructs a Lebedev quadrature for 3D, XYZ geometry.
691

692
    Parameters
693
    ----------
694
    quadrature_order: int
695
        The order of the quadrature.
696
    scattering_order: int
697
        Maximum scattering order supported by the angular quadrature.
698
        Optional when ``operator_method='galerkin_one'``, in which case the scattering order
699
        is automatically determined so that the number of moments equals the number of angles.
700
    operator_method: {'standard', 'galerkin_one', 'galerkin_three'}, default='standard'
701
        Method used to construct the discrete-to-moment and moment-to-discrete operators.
702
    verbose: bool, default=False
703
        Whether to print verbose output during initialization.
704
    )"
705
  );
706

707
  // Lebedev 2D XY quadrature
708
  auto angular_quadrature_lebedev_2d_xy = py::class_<LebedevQuadrature2DXY,
681✔
709
                                                     std::shared_ptr<LebedevQuadrature2DXY>,
710
                                                     AngularQuadrature>(
711
    aquad,
712
    "LebedevQuadrature2DXY",
713
    R"(
714
    Lebedev quadrature for 2D, XY geometry.
715

716
    This is a 2D version of the Lebedev quadrature that only includes points
717
    in the upper hemisphere (z >= 0). Points on the equator (z = 0) have their
718
    weights halved since they are shared between hemispheres.
719

720
    Wrapper of :cpp:class:`opensn::LebedevQuadrature2DXY`.
721
    )"
722
  );
681✔
723

724
  angular_quadrature_lebedev_2d_xy.def(
1,362✔
725
    py::init(
681✔
726
      [](py::kwargs& params)
2✔
727
      {
728
        auto scattering_order = GetScatteringOrder(params);
2✔
729
        static const std::vector<std::string> required_keys = {"quadrature_order"};
3✔
730
        const std::vector<std::pair<std::string, py::object>> optional_keys = {{"operator_method", py::str("standard")}, {"verbose", py::bool_(false)}};
12✔
731
        auto [quadrature_order, method_str, verbose] = extract_args_tuple<unsigned int, std::string, bool>(params, required_keys, optional_keys);
2✔
732
        return std::make_shared<LebedevQuadrature2DXY>(quadrature_order, scattering_order, verbose, op_cons_type_map.at(method_str));
4✔
733
      }
6✔
734
    ),
735
    R"(
736
    Constructs a Lebedev quadrature for 2D, XY geometry.
737

738
    Parameters
739
    ----------
740
    quadrature_order: int
741
        The order of the quadrature.
742
    scattering_order: int
743
        Maximum scattering order supported by the angular quadrature.
744
        Optional when ``operator_method='galerkin_one'``, in which case the scattering order
745
        is automatically determined so that the number of moments equals the number of angles.
746
    operator_method: {'standard', 'galerkin_one', 'galerkin_three'}, default='standard'
747
        Method used to construct the discrete-to-moment and moment-to-discrete operators.
748
    verbose: bool, default=False
749
        Whether to print verbose output during initialization.
750
    )"
751
  );
752
  // clang-format on
753
}
681✔
754

755
// Wrap the angular quadrature components of OpenSn
756
void
757
py_aquad(py::module& pyopensn)
72✔
758
{
759
  py::module aquad = pyopensn.def_submodule("aquad", "Angular quadrature module.");
72✔
760
  WrapQuadraturePointPhiTheta(aquad);
72✔
761
  WrapHarmonicIndices(aquad);
72✔
762
  WrapQuadrature(aquad);
72✔
763
  WrapProductQuadrature(aquad);
72✔
764
  WrapTriangularQuadrature(aquad);
72✔
765
  WrapCurvilinearProductQuadrature(aquad);
72✔
766
  WrapSLDFEsqQuadrature(aquad);
72✔
767
  WrapLebedevQuadrature(aquad);
72✔
768
}
72✔
769

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