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

Open-Sn / opensn / 25593189200

09 May 2026 03:58AM UTC coverage: 75.687% (+0.02%) from 75.667%
25593189200

push

github

web-flow
Merge pull request #1042 from wdhawkins/ang_quadrature_refactor

Refactoring angular quadrature classes.

230 of 278 new or added lines in 27 files covered. (82.73%)

11 existing lines in 4 files now uncovered.

22258 of 29408 relevant lines covered (75.69%)

64631828.08 hits per line

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

91.7
/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
namespace
21
{
22

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

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

41
// Wrap harmonic indices
42
void
43
WrapHarmonicIndices(py::module& aquad)
72✔
44
{
45
  py::class_<AngularQuadrature::HarmonicIndices> harmonic_indices(aquad, "HarmonicIndices");
72✔
46
  harmonic_indices.def_readonly("ell", &AngularQuadrature::HarmonicIndices::ell);
72✔
47
  harmonic_indices.def_readonly("m", &AngularQuadrature::HarmonicIndices::m);
72✔
48
}
72✔
49

50
} // namespace
51

52
// Wrap quadrature point
53
void
54
WrapQuadraturePointPhiTheta(py::module& aquad)
722✔
55
{
56
  // clang-format off
57
  py::class_<QuadraturePointPhiTheta> quad_pt_phi_theta(aquad,
722✔
58
    "QuadraturePointPhiTheta",
59
    R"(
60
    Angular quadrature point.
61

62
    Wrapper of :cpp:class:`opensn::QuadraturePointPhiTheta`.
63
    )"
64
  );
722✔
65
  quad_pt_phi_theta.def_readonly(
722✔
66
    "phi",
67
    &QuadraturePointPhiTheta::phi,
68
    "Azimuthal angle."
69
  );
70
  quad_pt_phi_theta.def_readonly(
722✔
71
    "theta",
72
    &QuadraturePointPhiTheta::theta,
73
    "Polar angle."
74
  );
75
  quad_pt_phi_theta.def(
722✔
76
    "__repr__",
77
    [](QuadraturePointPhiTheta& self)
722✔
78
    {
79
      std::ostringstream os;
×
80
      os << "QuadraturePointPhiTheta(phi=" << self.phi << ", theta=" << self.theta << ")";
×
81
      return os.str();
×
82
    }
×
83
  );
84
  // clang-format on
85
}
722✔
86

87
// Wrap angular quadrature
88
void
89
WrapQuadrature(py::module& aquad)
722✔
90
{
91
  // clang-format off
92
  // angular quadrature
93
  auto angular_quadrature = py::class_<AngularQuadrature, std::shared_ptr<AngularQuadrature>>(
722✔
94
    aquad,
95
    "AngularQuadrature",
96
    R"(
97
    Angular quadrature.
98

99
    Wrapper of :cpp:class:`opensn::AngularQuadrature`.
100
    )"
101
  );
722✔
102
  angular_quadrature.def_property_readonly(
722✔
103
    "abscissae",
104
    [](const AngularQuadrature& self) -> const std::vector<QuadraturePointPhiTheta>&
722✔
105
    {
NEW
106
      return self.GetAbscissae();
×
107
    },
108
    "Vector of polar and azimuthal angles."
109
  );
110
  angular_quadrature.def_property_readonly(
722✔
111
    "weights",
112
    [](const AngularQuadrature& self) -> const std::vector<double>&
734✔
113
    {
114
      return self.GetWeights();
12✔
115
    },
116
    "Quadrature weights."
117
  );
118
  angular_quadrature.def_property_readonly(
722✔
119
    "omegas",
120
    [](const AngularQuadrature& self) -> const std::vector<Vector3>&
4,110✔
121
    {
122
      return self.GetOmegas();
3,388✔
123
    },
124
    "Vector of direction vectors."
125
  );
126
  angular_quadrature.def(
722✔
127
    "GetDiscreteToMomentOperator",
128
    [](const AngularQuadrature& self) {
731✔
129
      const auto& op = self.GetDiscreteToMomentOperator();
9✔
130
      if (op.empty()) {
9✔
131
        return py::array_t<double>();
×
132
      }
133

134
      const auto dims = op.dimension();
9✔
135
      const auto num_rows = dims[0];
9✔
136
      const auto num_cols = dims[1];
9✔
137

138
      // Create numpy array with shape [num_rows, num_cols]
139
      py::array_t<double> result = py::array_t<double>(
9✔
140
        {num_rows, num_cols},  // shape
141
        {sizeof(double) * num_cols, sizeof(double)}  // strides (row-major)
9✔
142
      );
18✔
143

144
      py::buffer_info buf = result.request();
9✔
145
      auto* ptr = static_cast<double*>(buf.ptr);
9✔
146

147
      std::copy_n(op.data(), op.size(), ptr);
9✔
148

149
      return result;
9✔
150
    },
9✔
151
    "Get the discrete-to-moment operator as a numpy array."
152
  );
153
  angular_quadrature.def(
722✔
154
    "GetMomentToDiscreteOperator",
155
    [](const AngularQuadrature& self) {
731✔
156
      const auto& op = self.GetMomentToDiscreteOperator();
9✔
157
      if (op.empty()) {
9✔
158
        return py::array_t<double>();
×
159
      }
160

161
      const auto dims = op.dimension();
9✔
162
      const auto num_rows = dims[0];
9✔
163
      const auto num_cols = dims[1];
9✔
164

165
      // Create numpy array with shape [num_rows, num_cols]
166
      py::array_t<double> result = py::array_t<double>(
9✔
167
        {num_rows, num_cols},  // shape
168
        {sizeof(double) * num_cols, sizeof(double)}  // strides (row-major)
9✔
169
      );
18✔
170

171
      py::buffer_info buf = result.request();
9✔
172
      auto* ptr = static_cast<double*>(buf.ptr);
9✔
173

174
      std::copy_n(op.data(), op.size(), ptr);
9✔
175

176
      return result;
9✔
177
    },
9✔
178
    "Get the moment-to-discrete operator as a numpy array."
179
  );
180
  angular_quadrature.def(
722✔
181
    "GetMomentToHarmonicsIndexMap",
182
    &AngularQuadrature::GetMomentToHarmonicsIndexMap,
1,444✔
183
    py::return_value_policy::reference_internal
722✔
184
  );
185
  angular_quadrature.def("GetDimension", &AngularQuadrature::GetDimension);
722✔
186
  angular_quadrature.def("GetScatteringOrder", &AngularQuadrature::GetScatteringOrder);
722✔
187
  angular_quadrature.def("GetNumMoments", &AngularQuadrature::GetNumMoments);
722✔
188
  angular_quadrature.def("GetNumAngles", &AngularQuadrature::GetNumAngles);
722✔
189
  angular_quadrature.def("GetAbscissa", &AngularQuadrature::GetAbscissa, py::return_value_policy::reference_internal);
722✔
190
  angular_quadrature.def("GetWeight", &AngularQuadrature::GetWeight);
722✔
191
  angular_quadrature.def("GetOmega", &AngularQuadrature::GetOmega, py::return_value_policy::reference_internal);
722✔
192
  // clang-format on
193
}
722✔
194

195
// Wrap product qudrature
196
void
197
WrapProductQuadrature(py::module& aquad)
722✔
198
{
199
  // clang-format off
200
  // product quadrature
201
  auto product_quadrature = py::class_<ProductQuadrature, std::shared_ptr<ProductQuadrature>,
722✔
202
                                       AngularQuadrature>(
203
    aquad,
204
    "ProductQuadrature",
205
    R"(
206
    Product quadrature.
207

208
    Wrapper of :cpp:class:`opensn::ProductQuadrature`.
209
    )"
210
  );
722✔
211
  product_quadrature.def(
722✔
212
    "GetPolarAngles",
213
    &ProductQuadrature::GetPolarAngles,
1,444✔
214
    py::return_value_policy::reference_internal
722✔
215
  );
216
  product_quadrature.def(
722✔
217
    "GetAzimuthalAngles",
218
    &ProductQuadrature::GetAzimuthalAngles,
1,444✔
219
    py::return_value_policy::reference_internal
722✔
220
  );
221
  product_quadrature.def("GetNumPolarAngles", &ProductQuadrature::GetNumPolarAngles);
722✔
222
  product_quadrature.def("GetNumAzimuthalAngles", &ProductQuadrature::GetNumAzimuthalAngles);
722✔
223

224
  // Gauss-Legendre 1D slab product quadrature
225
  auto angular_quadrature_gl_prod_1d_slab = py::class_<GLProductQuadrature1DSlab,
722✔
226
                                                       std::shared_ptr<GLProductQuadrature1DSlab>,
227
                                                       ProductQuadrature>(
228
    aquad,
229
    "GLProductQuadrature1DSlab",
230
    R"(
231
    Gauss-Legendre quadrature for 1D, slab geometry.
232

233
    Wrapper of :cpp:class:`opensn::GLProductQuadrature1DSlab`.
234
    )"
235
  );
722✔
236
  angular_quadrature_gl_prod_1d_slab.def(
1,444✔
237
    py::init(
722✔
238
      [](py::kwargs& params)
203✔
239
      {
240
        auto scattering_order = GetScatteringOrder(params);
203✔
241
        static const std::vector<std::string> required_keys = {"n_polar"};
763✔
242
        const std::vector<std::pair<std::string, py::object>> optional_keys = {{"operator_method", py::str("standard")}, {"verbose", py::bool_(false)}};
1,218✔
243
        auto [n_polar, method_str, verbose] = extract_args_tuple<unsigned int, std::string, bool>(params, required_keys, optional_keys);
203✔
244
        return std::make_shared<GLProductQuadrature1DSlab>(n_polar, scattering_order, verbose, op_cons_type_map.at(method_str));
406✔
245
      }
609✔
246
    ),
247
    R"(
248
    Construct a Gauss-Legendre product quadrature for 1D, slab geometry.
249

250
    Parameters
251
    ----------
252
    n_polar: int
253
        Number of polar angles.
254
    scattering_order: int
255
        Maximum scattering order supported by the angular quadrature.
256
        Optional when ``operator_method='galerkin_one'``, in which case the scattering order
257
        is automatically determined so that the number of moments equals the number of angles.
258
    operator_method: {'standard', 'galerkin_one', 'galerkin_three'}, default='standard'
259
        Method used to construct the discrete-to-moment and moment-to-discrete operators.
260
    verbose: bool, default=False
261
        Verbosity.
262
    )"
263
  );
264

265
  // Gauss-Legendre-Chebyshev 2D XY product quadrature
266
  auto angular_quadrature_glc_prod_2d_xy = py::class_<GLCProductQuadrature2DXY,
722✔
267
                                                      std::shared_ptr<GLCProductQuadrature2DXY>,
268
                                                      ProductQuadrature>(
269
    aquad,
270
    "GLCProductQuadrature2DXY",
271
    R"(
272
    Gauss-Legendre-Chebyshev quadrature for 2D, XY geometry.
273

274
    Wrapper of :cpp:class:`opensn::GLCProductQuadrature2DXY`.
275
    )"
276
  );
722✔
277
  angular_quadrature_glc_prod_2d_xy.def(
1,444✔
278
    py::init(
722✔
279
      [](py::kwargs& params)
191✔
280
      {
281
        auto scattering_order = GetScatteringOrder(params);
191✔
282
        static const std::vector<std::string> required_keys = {"n_polar", "n_azimuthal"};
378✔
283
        const std::vector<std::pair<std::string, py::object>> optional_keys = {{"operator_method", py::str("standard")}, {"verbose", py::bool_(false)}};
1,146✔
284
        auto [n_polar, n_azimuthal, method_str, verbose] = extract_args_tuple<unsigned int, unsigned int, std::string, bool>(params, required_keys, optional_keys);
191✔
285
        return std::make_shared<GLCProductQuadrature2DXY>(n_polar, n_azimuthal, scattering_order, verbose, op_cons_type_map.at(method_str));
382✔
286
      }
573✔
287
    ),
288
    R"(
289
    Construct a Gauss-Legendre-Chebyshev product quadrature for 2D, XY geometry.
290

291
    Parameters
292
    ----------
293
    n_polar: int
294
        Number of polar angles.
295
    n_azimuthal: int
296
        Number of azimuthal angles.
297
    scattering_order: int
298
        Maximum scattering order supported by the angular quadrature.
299
        Optional when ``operator_method='galerkin_one'``, in which case the scattering order
300
        is automatically determined so that the number of moments equals the number of angles.
301
    operator_method: {'standard', 'galerkin_one', 'galerkin_three'}, default='standard'
302
        Method used to construct the discrete-to-moment and moment-to-discrete operators.
303
    verbose: bool, default=False
304
        Verbosity.
305
    )"
306
  );
307

308
  // Gauss-Legendre-Chebyshev 3D XYZ product quadrature
309
  auto angular_quadrature_glc_prod_3d_xyz = py::class_<GLCProductQuadrature3DXYZ,
722✔
310
                                                       std::shared_ptr<GLCProductQuadrature3DXYZ>,
311
                                                       ProductQuadrature>(
312
    aquad,
313
    "GLCProductQuadrature3DXYZ",
314
    R"(
315
    Gauss-Legendre-Chebyshev quadrature for 3D, XYZ geometry.
316

317
    Wrapper of :cpp:class:`opensn::GLCProductQuadrature3DXYZ`.
318
    )"
319
  );
722✔
320
  angular_quadrature_glc_prod_3d_xyz.def(
1,444✔
321
    py::init(
722✔
322
      [](py::kwargs& params)
306✔
323
      {
324
        auto scattering_order = GetScatteringOrder(params);
306✔
325
        static const std::vector<std::string> required_keys = {"n_polar", "n_azimuthal"};
595✔
326
        const std::vector<std::pair<std::string, py::object>> optional_keys = {{"operator_method", py::str("standard")}, {"verbose", py::bool_(false)}};
1,836✔
327
        auto [n_polar, n_azimuthal, method_str, verbose] = extract_args_tuple<unsigned int, unsigned int, std::string, bool>(params, required_keys, optional_keys);
306✔
328
        return std::make_shared<GLCProductQuadrature3DXYZ>(n_polar, n_azimuthal, scattering_order, verbose, op_cons_type_map.at(method_str));
612✔
329
      }
918✔
330
    ),
331
    R"(
332
    Construct a Gauss-Legendre-Chebyshev product quadrature for 3D, XYZ geometry.
333

334
    Parameters
335
    ----------
336
    n_polar: int
337
        Number of polar angles.
338
    n_azimuthal: int
339
        Number of azimuthal angles.
340
    scattering_order: int
341
        Maximum scattering order supported by the angular quadrature.
342
        Optional when ``operator_method='galerkin_one'``, in which case the scattering order
343
        is automatically determined so that the number of moments equals the number of angles.
344
    operator_method: {'standard', 'galerkin_one', 'galerkin_three'}, default='standard'
345
        Method used to construct the discrete-to-moment and moment-to-discrete operators.
346
    verbose: bool, default=False
347
        Verbosity.
348
    )"
349
  );
350
  // clang-format on
351
}
722✔
352

353
// Wrap triangular quadrature
354
void
355
WrapTriangularQuadrature(py::module& aquad)
722✔
356
{
357
  // clang-format off
358
  // triangular quadrature base class
359
  auto triangular_quadrature = py::class_<TriangularQuadrature, std::shared_ptr<TriangularQuadrature>,
722✔
360
                                          AngularQuadrature>(
361
    aquad,
362
    "TriangularQuadrature",
363
    R"(
364
    Triangular quadrature base class.
365

366
    Unlike product quadratures which have a fixed number of azimuthal angles per polar level,
367
    triangular quadratures have a varying number of azimuthal angles that decreases
368
    as the polar angle moves away from the equatorial plane.
369

370
    Wrapper of :cpp:class:`opensn::TriangularQuadrature`.
371
    )"
372
  );
722✔
373
  triangular_quadrature.def(
722✔
374
    "GetPolarAngles",
375
    &TriangularQuadrature::GetPolarAngles,
1,444✔
376
    py::return_value_policy::reference_internal
722✔
377
  );
378
  triangular_quadrature.def(
722✔
379
    "GetAzimuthalAnglesPerPolarLevel",
380
    &TriangularQuadrature::GetAzimuthalAnglesPerPolarLevel,
1,444✔
381
    py::return_value_policy::reference_internal
722✔
382
  );
383
  triangular_quadrature.def("GetNumPolarAngles", &TriangularQuadrature::GetNumPolarAngles);
722✔
384
  triangular_quadrature.def("GetNumAzimuthalAngles", &TriangularQuadrature::GetNumAzimuthalAngles);
722✔
385

386
  // Triangular GLC 3D XYZ quadrature
387
  auto angular_quadrature_triangular_glc_3d_xyz = py::class_<GLCTriangularQuadrature3DXYZ,
722✔
388
                                                             std::shared_ptr<GLCTriangularQuadrature3DXYZ>,
389
                                                             TriangularQuadrature>(
390
    aquad,
391
    "GLCTriangularQuadrature3DXYZ",
392
    R"(
393
    Triangular Gauss-Legendre-Chebyshev quadrature for 3D, XYZ geometry.
394

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

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

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

432
  // Triangular GLC 2D XY quadrature
433
  auto angular_quadrature_triangular_glc_2d_xy = py::class_<GLCTriangularQuadrature2DXY,
722✔
434
                                                            std::shared_ptr<GLCTriangularQuadrature2DXY>,
435
                                                            TriangularQuadrature>(
436
    aquad,
437
    "GLCTriangularQuadrature2DXY",
438
    R"(
439
    Triangular Gauss-Legendre-Chebyshev quadrature for 2D, XY geometry.
440

441
    Only includes points in the upper hemisphere (z >= 0).
442

443
    Wrapper of :cpp:class:`opensn::GLCTriangularQuadrature2DXY`.
444
    )"
445
  );
722✔
446
  angular_quadrature_triangular_glc_2d_xy.def(
1,444✔
447
    py::init(
722✔
448
      [](py::kwargs& params)
1✔
449
      {
450
        auto scattering_order = GetScatteringOrder(params);
1✔
451
        static const std::vector<std::string> required_keys = {"n_polar"};
2✔
452
        const std::vector<std::pair<std::string, py::object>> optional_keys = {{"operator_method", py::str("standard")}, {"verbose", py::bool_(false)}};
6✔
453
        auto [n_polar, method_str, verbose] = extract_args_tuple<unsigned int, std::string, bool>(params, required_keys, optional_keys);
1✔
454
        return std::make_shared<GLCTriangularQuadrature2DXY>(n_polar, scattering_order, verbose, op_cons_type_map.at(method_str));
2✔
455
      }
3✔
456
    ),
457
    R"(
458
    Construct a Triangular Gauss-Legendre-Chebyshev quadrature for 2D, XY geometry.
459

460
    Parameters
461
    ----------
462
    n_polar: int
463
        Number of polar angles (only upper hemisphere will be used). The maximum
464
        number of azimuthal angles (at the equator) is automatically computed as 2 * n_polar.
465
    scattering_order: int
466
        Maximum scattering order supported by the angular quadrature.
467
        Optional when ``operator_method='galerkin_one'``, in which case the scattering order
468
        is automatically determined so that the number of moments equals the number of angles.
469
    operator_method: {'standard', 'galerkin_one', 'galerkin_three'}, default='standard'
470
        Method used to construct the discrete-to-moment and moment-to-discrete operators.
471
    verbose: bool, default=False
472
        Verbosity.
473
    )"
474
  );
475
  // clang-format on
476
}
722✔
477

478
// Wrap curvilinear product quadrature
479
void
480
WrapCurvilinearProductQuadrature(py::module& aquad)
722✔
481
{
482
  // clang-format off
483
  // curvilinear product quadrature
484
  auto curvilinear_product_quadrature = py::class_<CurvilinearProductQuadrature,
722✔
485
                                                   std::shared_ptr<CurvilinearProductQuadrature>,
486
                                                   ProductQuadrature>(
487
    aquad,
488
    "CurvilinearProductQuadrature",
489
    R"(
490
    Curvilinear product quadrature.
491

492
    Wrapper of :cpp:class:`opensn::CurvilinearProductQuadrature`.
493
    )"
494
  );
722✔
495

496
  // Gauss-Legendre-Chebyshev 2D RZ curvilinear product quadrature
497
  auto curvilinear_quadrature_glc_2d_rz = py::class_<GLCProductQuadrature2DRZ,
722✔
498
                                                     std::shared_ptr<GLCProductQuadrature2DRZ>,
499
                                                     CurvilinearProductQuadrature>(
500
    aquad,
501
    "GLCProductQuadrature2DRZ",
502
    R"(
503
    Gauss-Legendre-Chebyshev product quadrature for 2D, RZ geometry.
504

505
    Wrapper of :cpp:class:`opensn::GLCProductQuadrature2DRZ`.
506
    )"
507
  );
722✔
508
  curvilinear_quadrature_glc_2d_rz.def(
1,444✔
509
    py::init(
722✔
510
      [](py::kwargs& params)
68✔
511
      {
512
        auto scattering_order = GetScatteringOrder(params);
68✔
513
        static const std::vector<std::string> required_keys = {"n_polar", "n_azimuthal"};
136✔
514
        const std::vector<std::pair<std::string, py::object>> optional_keys = {{"operator_method", py::str("standard")}, {"verbose", py::bool_(false)}};
408✔
515
        auto [n_polar, n_azimuthal, method_str, verbose] = extract_args_tuple<unsigned int, unsigned int, std::string, bool>(params, required_keys, optional_keys);
68✔
516
        return std::make_shared<GLCProductQuadrature2DRZ>(n_polar, n_azimuthal, scattering_order, verbose, op_cons_type_map.at(method_str));
136✔
517
      }
204✔
518
    ),
519
    R"(
520
    Construct a Gauss-Legendre Chebyshev product quadrature for 2D, RZ geometry.
521

522
    Parameters
523
    ----------
524
    n_polar: int
525
        Number of polar angles.
526
    n_azimuthal: int
527
        Number of azimuthal angles.
528
    scattering_order: int
529
        Maximum scattering order supported by the angular quadrature.
530
        Optional when ``operator_method='galerkin_one'``, in which case the scattering order
531
        is automatically determined so that the number of moments equals the number of angles.
532
    operator_method: {'standard', 'galerkin_one', 'galerkin_three'}, default='standard'
533
        Method used to construct the discrete-to-moment and moment-to-discrete operators.
534
    verbose: bool, default=False
535
        Verbosity.
536
    )"
537
  );
538
  // clang-format on
539
}
722✔
540

541
// Wrap SLDFES quadrature
542
void
543
WrapSLDFEsqQuadrature(py::module& aquad)
722✔
544
{
545
  // clang-format off
546
  // Simplified LDFEsq quadrature
547
  auto sldfesq_quadrature_3d_xyz = py::class_<SLDFEsqQuadrature3DXYZ,
722✔
548
                                              std::shared_ptr<SLDFEsqQuadrature3DXYZ>,
549
                                              AngularQuadrature>(
550
    aquad,
551
    "SLDFEsqQuadrature3DXYZ",
552
    R"(
553
    Piecewise-linear finite element quadrature using quadrilaterals.
554

555
    Wrapper of :cpp:class:`opensn::SLDFEsqQuadrature3DXYZ`.
556
    )"
557
  );
722✔
558
  sldfesq_quadrature_3d_xyz.def(
1,444✔
559
    py::init(
722✔
560
      [](py::kwargs& params)
9✔
561
      {
562
        auto scattering_order = GetScatteringOrder(params);
9✔
563
        static const std::vector<std::string> required_keys = {"level"};
15✔
564
        const std::vector<std::pair<std::string, py::object>> optional_keys = {{"operator_method", py::str("standard")}, {"verbose", py::bool_(false)}};
54✔
565
        auto [level, method_str, verbose] = extract_args_tuple<int, std::string, bool>(params, required_keys, optional_keys);
9✔
566
        return std::make_shared<SLDFEsqQuadrature3DXYZ>(level, scattering_order, verbose, op_cons_type_map.at(method_str));
18✔
567
      }
27✔
568
    ),
569
    R"(
570
    Generates uniform spherical quadrilaterals from the subdivision of an inscribed cube.
571

572
    Parameters
573
    ----------
574
    level: int
575
        Number of subdivisions of the inscribed cube.
576
    scattering_order: int
577
        Maximum scattering order supported by the angular quadrature.
578
        Optional when ``operator_method='galerkin_one'``, in which case the scattering order
579
        is automatically determined so that the number of moments equals the number of angles.
580
    operator_method: {'standard', 'galerkin_one', 'galerkin_three'}, default='standard'
581
        Method used to construct the discrete-to-moment and moment-to-discrete operators.
582
    verbose: bool, default=False
583
        Verbosity.
584
    )"
585
  );
586
  sldfesq_quadrature_3d_xyz.def("GetQuadratureOrder", &SLDFEsqQuadrature3DXYZ::GetQuadratureOrder);
722✔
587
  sldfesq_quadrature_3d_xyz.def(
1,444✔
588
    "LocallyRefine",
589
    &SLDFEsqQuadrature3DXYZ::LocallyRefine,
1,444✔
590
    R"(
591
    Locally refines the cells.
592

593
    Parameters
594
    ----------
595
    ref_dir: pyopensn.math.Vector3
596
        Reference direction :math:`\vec{r}`.
597
    cone_size: float
598
        Cone size (in radians) :math:`\theta`.
599
    dir_as_plane_normal: bool, default=False
600
        If true, interpret SQ-splitting as when :math:`|\omega \cdot \vec{r}| < \sin(\theta)`.
601
        Otherwise, SQs will be split if :math:`\omega \cdot \vec{r} > \cos(\theta)`.
602
    )",
603
    py::arg("ref_dir"),
1,444✔
604
    py::arg("cone_size"),
722✔
605
    py::arg("dir_as_plane_normal") = false
722✔
606
  );
607
  sldfesq_quadrature_3d_xyz.def(
722✔
608
    "PrintQuadratureToFile",
609
    &SLDFEsqQuadrature3DXYZ::PrintQuadratureToFile,
1,444✔
610
    R"(
611
    Prints the quadrature to file.
612

613
    Parameters
614
    ----------
615
    file_base: str
616
        File base name.
617
    )",
618
    py::arg("file_base")
722✔
619
  );
620

621
  // 2D SLDFEsq quadrature
622
  auto sldfesq_quadrature_2d_xy = py::class_<SLDFEsqQuadrature2DXY,
722✔
623
                                             std::shared_ptr<SLDFEsqQuadrature2DXY>,
624
                                             AngularQuadrature>(
625
    aquad,
626
    "SLDFEsqQuadrature2DXY",
627
    R"(
628
    Two-dimensional variant of the piecewise-linear finite element quadrature.
629

630
    This quadrature is created from the 3D SLDFEsq set by removing directions with negative
631
    xi.
632

633
    Wrapper of :cpp:class:`opensn::SLDFEsqQuadrature2DXY`.
634
    )"
635
  );
722✔
636
  sldfesq_quadrature_2d_xy.def(
1,444✔
637
    py::init(
722✔
638
      [](py::kwargs& params)
4✔
639
      {
640
        auto scattering_order = GetScatteringOrder(params);
4✔
641
        static const std::vector<std::string> required_keys = {"level"};
8✔
642
        const std::vector<std::pair<std::string, py::object>> optional_keys = {{"operator_method", py::str("standard")}, {"verbose", py::bool_(false)}};
24✔
643
        auto [level, method_str, verbose] = extract_args_tuple<int, std::string, bool>(params, required_keys, optional_keys);
4✔
644
        return std::make_shared<SLDFEsqQuadrature2DXY>(level, scattering_order, verbose, op_cons_type_map.at(method_str));
8✔
645
      }
12✔
646
    ),
647
    R"(
648
    Generates a 2D SLDFEsq quadrature by removing directions with negative xi.
649

650
    Parameters
651
    ----------
652
    level: int
653
        Number of subdivisions of the inscribed cube.
654
    scattering_order: int
655
        Maximum scattering order supported by the angular quadrature.
656
        Optional when ``operator_method='galerkin_one'``, in which case the scattering order
657
        is automatically determined so that the number of moments equals the number of angles.
658
    operator_method: {'standard', 'galerkin_one', 'galerkin_three'}, default='standard'
659
        Method used to construct the discrete-to-moment and moment-to-discrete operators.
660
    verbose: bool, default=False
661
        Verbosity.
662
    )"
663
  );
664
  sldfesq_quadrature_2d_xy.def("GetQuadratureOrder", &SLDFEsqQuadrature2DXY::GetQuadratureOrder);
722✔
665
  sldfesq_quadrature_2d_xy.def(
1,444✔
666
    "LocallyRefine",
667
    &SLDFEsqQuadrature2DXY::LocallyRefine,
1,444✔
668
    R"(
669
    Locally refines the cells.
670

671
    Parameters
672
    ----------
673
    ref_dir: pyopensn.math.Vector3
674
        Reference direction :math:`\vec{r}`.
675
    cone_size: float
676
        Cone size (in radians) :math:`\theta`.
677
    dir_as_plane_normal: bool, default=False
678
        If true, interpret SQ-splitting as when :math:`|\omega \cdot \vec{r}| < \sin(\theta)`.
679
        Otherwise, SQs will be split if :math:`\omega \cdot \vec{r} > \cos(\theta)`.
680
    )",
681
    py::arg("ref_dir"),
1,444✔
682
    py::arg("cone_size"),
722✔
683
    py::arg("dir_as_plane_normal") = false
722✔
684
  );
685
  sldfesq_quadrature_2d_xy.def(
722✔
686
    "PrintQuadratureToFile",
687
    &SLDFEsqQuadrature2DXY::PrintQuadratureToFile,
1,444✔
688
    R"(
689
    Prints the quadrature to file.
690

691
    Parameters
692
    ----------
693
    file_base: str
694
        File base name.
695
    )",
696
    py::arg("file_base")
722✔
697
  );
698
  // clang-format on
699
}
722✔
700

701
// Wrap Lebedev quadrature
702
void
703
WrapLebedevQuadrature(py::module& aquad)
722✔
704
{
705
  // clang-format off
706
  // Lebedev 3D XYZ quadrature
707
  auto angular_quadrature_lebedev_3d_xyz = py::class_<LebedevQuadrature3DXYZ,
722✔
708
                                                     std::shared_ptr<LebedevQuadrature3DXYZ>,
709
                                                     AngularQuadrature>(
710
    aquad,
711
    "LebedevQuadrature3DXYZ",
712
    R"(
713
    Lebedev quadrature for 3D, XYZ geometry.
714

715
    This quadrature provides high-order accuracy for spherical integration with
716
    symmetric distribution of points on the sphere.
717

718
    Wrapper of :cpp:class:`opensn::LebedevQuadrature3DXYZ`.
719
    )"
720
  );
722✔
721

722
  angular_quadrature_lebedev_3d_xyz.def(
1,444✔
723
    py::init(
722✔
724
      [](py::kwargs& params)
5✔
725
      {
726
        auto scattering_order = GetScatteringOrder(params);
5✔
727
        static const std::vector<std::string> required_keys = {"quadrature_order"};
10✔
728
        const std::vector<std::pair<std::string, py::object>> optional_keys = {{"operator_method", py::str("standard")}, {"verbose", py::bool_(false)}};
30✔
729
        auto [quadrature_order, method_str, verbose] = extract_args_tuple<unsigned int, std::string, bool>(params, required_keys, optional_keys);
5✔
730
        return std::make_shared<LebedevQuadrature3DXYZ>(quadrature_order, scattering_order, verbose, op_cons_type_map.at(method_str));
10✔
731
      }
15✔
732
    ),
733
    R"(
734
    Constructs a Lebedev quadrature for 3D, XYZ geometry.
735

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

752
  // Lebedev 2D XY quadrature
753
  auto angular_quadrature_lebedev_2d_xy = py::class_<LebedevQuadrature2DXY,
722✔
754
                                                     std::shared_ptr<LebedevQuadrature2DXY>,
755
                                                     AngularQuadrature>(
756
    aquad,
757
    "LebedevQuadrature2DXY",
758
    R"(
759
    Lebedev quadrature for 2D, XY geometry.
760

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

765
    Wrapper of :cpp:class:`opensn::LebedevQuadrature2DXY`.
766
    )"
767
  );
722✔
768

769
  angular_quadrature_lebedev_2d_xy.def(
1,444✔
770
    py::init(
722✔
771
      [](py::kwargs& params)
×
772
      {
773
        auto scattering_order = GetScatteringOrder(params);
×
774
        static const std::vector<std::string> required_keys = {"quadrature_order"};
×
775
        const std::vector<std::pair<std::string, py::object>> optional_keys = {{"operator_method", py::str("standard")}, {"verbose", py::bool_(false)}};
×
776
        auto [quadrature_order, method_str, verbose] = extract_args_tuple<unsigned int, std::string, bool>(params, required_keys, optional_keys);
×
777
        return std::make_shared<LebedevQuadrature2DXY>(quadrature_order, scattering_order, verbose, op_cons_type_map.at(method_str));
×
778
      }
×
779
    ),
780
    R"(
781
    Constructs a Lebedev quadrature for 2D, XY geometry.
782

783
    Parameters
784
    ----------
785
    quadrature_order: int
786
        The order of the quadrature.
787
    scattering_order: int
788
        Maximum scattering order supported by the angular quadrature.
789
        Optional when ``operator_method='galerkin_one'``, in which case the scattering order
790
        is automatically determined so that the number of moments equals the number of angles.
791
    operator_method: {'standard', 'galerkin_one', 'galerkin_three'}, default='standard'
792
        Method used to construct the discrete-to-moment and moment-to-discrete operators.
793
    verbose: bool, default=False
794
        Whether to print verbose output during initialization.
795
    )"
796
  );
797
  angular_quadrature_lebedev_2d_xy.def("GetQuadratureOrder", &LebedevQuadrature2DXY::GetQuadratureOrder);
722✔
798
  // clang-format on
799
}
722✔
800

801
// Wrap the angular quadrature components of OpenSn
802
void
803
py_aquad(py::module& pyopensn)
72✔
804
{
805
  py::module aquad = pyopensn.def_submodule("aquad", "Angular quadrature module.");
72✔
806
  WrapQuadraturePointPhiTheta(aquad);
72✔
807
  WrapHarmonicIndices(aquad);
72✔
808
  WrapQuadrature(aquad);
72✔
809
  WrapProductQuadrature(aquad);
72✔
810
  WrapTriangularQuadrature(aquad);
72✔
811
  WrapCurvilinearProductQuadrature(aquad);
72✔
812
  WrapSLDFEsqQuadrature(aquad);
72✔
813
  WrapLebedevQuadrature(aquad);
72✔
814
}
72✔
815

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