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

Open-Sn / opensn / 24437745168

15 Apr 2026 03:39AM UTC coverage: 74.813% (-0.2%) from 75.028%
24437745168

push

github

web-flow
Merge pull request #955 from wdhawkins/cepxs

Add support for CEPXS cross sections

15 of 212 new or added lines in 6 files covered. (7.08%)

319 existing lines in 10 files now uncovered.

21208 of 28348 relevant lines covered (74.81%)

65820875.2 hits per line

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

90.91
/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 <algorithm>
14
#include <memory>
15
#include <stdexcept>
16

17
namespace opensn
18
{
19

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

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

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

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

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

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

94
    Wrapper of :cpp:class:`opensn::AngularQuadrature`.
95
    )"
96
  );
665✔
97
  angular_quadrature.def_readonly(
665✔
98
    "abscissae",
99
    &AngularQuadrature::abscissae,
100
    "Vector of polar and azimuthal angles."
101
  );
102
  angular_quadrature.def_readonly(
665✔
103
    "weights",
104
    &AngularQuadrature::weights,
105
    "Quadrature weights."
106
  );
107
  angular_quadrature.def_readonly(
665✔
108
    "omegas",
109
    &AngularQuadrature::omegas,
110
    "Vector of direction vectors."
111
  );
112
  angular_quadrature.def(
665✔
113
    "GetDiscreteToMomentOperator",
114
    [](const AngularQuadrature& self) {
674✔
115
      const auto& op = self.GetDiscreteToMomentOperator();
9✔
116
      if (op.empty()) {
9✔
117
        return py::array_t<double>();
×
118
      }
119

120
      const auto dims = op.dimension();
9✔
121
      const auto num_rows = dims[0];
9✔
122
      const auto num_cols = dims[1];
9✔
123

124
      // Create numpy array with shape [num_rows, num_cols]
125
      py::array_t<double> result = py::array_t<double>(
9✔
126
        {num_rows, num_cols},  // shape
127
        {sizeof(double) * num_cols, sizeof(double)}  // strides (row-major)
9✔
128
      );
18✔
129

130
      py::buffer_info buf = result.request();
9✔
131
      auto* ptr = static_cast<double*>(buf.ptr);
9✔
132

133
      std::copy_n(op.data(), op.size(), ptr);
9✔
134

135
      return result;
9✔
136
    },
9✔
137
    "Get the discrete-to-moment operator as a numpy array."
138
  );
139
  angular_quadrature.def(
665✔
140
    "GetMomentToDiscreteOperator",
141
    [](const AngularQuadrature& self) {
674✔
142
      const auto& op = self.GetMomentToDiscreteOperator();
9✔
143
      if (op.empty()) {
9✔
144
        return py::array_t<double>();
×
145
      }
146

147
      const auto dims = op.dimension();
9✔
148
      const auto num_rows = dims[0];
9✔
149
      const auto num_cols = dims[1];
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
      std::copy_n(op.data(), op.size(), ptr);
9✔
161

162
      return result;
9✔
163
    },
9✔
164
    "Get the moment-to-discrete operator as a numpy array."
165
  );
166
  angular_quadrature.def(
665✔
167
    "GetMomentToHarmonicsIndexMap",
168
    &AngularQuadrature::GetMomentToHarmonicsIndexMap,
1,330✔
169
    py::return_value_policy::reference_internal
665✔
170
  );
171
  // clang-format on
172
}
665✔
173

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

187
    Wrapper of :cpp:class:`opensn::ProductQuadrature`.
188
    )"
189
  );
665✔
190

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

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

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

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

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

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

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

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

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

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

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

337
    Wrapper of :cpp:class:`opensn::TriangularQuadrature`.
338
    )"
339
  );
665✔
340

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

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

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

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

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

396
    Only includes points in the upper hemisphere (z >= 0).
397

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

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

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

447
    Wrapper of :cpp:class:`opensn::CurvilinearProductQuadrature`.
448
    )"
449
  );
665✔
450

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

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

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

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

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

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

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

567
    Parameters
568
    ----------
569
    file_base: str
570
        File base name.
571
    )",
572
    py::arg("file_base")
665✔
573
  );
574

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

584
    This quadrature is created from the 3D SLDFEsq set by removing directions with negative
585
    xi.
586

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

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

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

644
    Parameters
645
    ----------
646
    file_base: str
647
        File base name.
648
    )",
649
    py::arg("file_base")
665✔
650
  );
651
  // clang-format on
652
}
665✔
653

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

668
    This quadrature provides high-order accuracy for spherical integration with
669
    symmetric distribution of points on the sphere.
670

671
    Wrapper of :cpp:class:`opensn::LebedevQuadrature3DXYZ`.
672
    )"
673
  );
665✔
674

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

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

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

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

717
    Wrapper of :cpp:class:`opensn::LebedevQuadrature2DXY`.
718
    )"
719
  );
665✔
720

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

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

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

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