1414
1515namespace py = pybind11;
1616
17+ namespace {
18+ std::vector<std::vector<complex128>> cast_py_to_sequential_angular_weights (
19+ const py::object& angular_weights,
20+ const std::vector<unsigned >& sampling,
21+ const size_t target_size
22+ ) {
23+ std::vector<std::vector<complex128>> weights_by_detector;
24+
25+ if (angular_weights.is_none ()) {
26+ return weights_by_detector;
27+ }
28+
29+ if (py::isinstance<py::array>(angular_weights)) {
30+ py::array_t <std::complex <double >, py::array::c_style | py::array::forcecast> array =
31+ angular_weights.cast <py::array_t <std::complex <double >, py::array::c_style | py::array::forcecast>>();
32+
33+ if (array.ndim () == 1 ) {
34+ const size_t sampling_size = static_cast <size_t >(array.shape (0 ));
35+
36+ for (const unsigned detector_sampling : sampling) {
37+ if (sampling_size != detector_sampling) {
38+ throw py::value_error (
39+ " One-dimensional angular_weights arrays require a uniform detector sampling."
40+ );
41+ }
42+ }
43+
44+ std::vector<complex128> shared_weight_vector;
45+ vector_assign_from_numpy (shared_weight_vector, array);
46+ weights_by_detector.assign (target_size, shared_weight_vector);
47+ return weights_by_detector;
48+ }
49+
50+ if (array.ndim () == 2 ) {
51+ if (static_cast <size_t >(array.shape (0 )) != target_size) {
52+ throw py::value_error (
53+ " Two-dimensional angular_weights arrays must have shape (target_size, sampling)."
54+ );
55+ }
56+
57+ const size_t sampling_size = static_cast <size_t >(array.shape (1 ));
58+ for (const unsigned detector_sampling : sampling) {
59+ if (sampling_size != detector_sampling) {
60+ throw py::value_error (
61+ " Two-dimensional angular_weights arrays require a uniform sampling shared by all detectors."
62+ );
63+ }
64+ }
65+
66+ auto unchecked = array.unchecked <2 >();
67+ weights_by_detector.resize (target_size);
68+
69+ for (size_t detector_index = 0 ; detector_index < target_size; ++detector_index) {
70+ weights_by_detector[detector_index].resize (sampling_size);
71+
72+ for (size_t sample_index = 0 ; sample_index < sampling_size; ++sample_index) {
73+ weights_by_detector[detector_index][sample_index] = unchecked (
74+ static_cast <py::ssize_t >(detector_index),
75+ static_cast <py::ssize_t >(sample_index)
76+ );
77+ }
78+ }
79+
80+ return weights_by_detector;
81+ }
82+
83+ throw py::value_error (" angular_weights must be one- or two-dimensional." );
84+ }
85+
86+ py::sequence sequence = py::reinterpret_borrow<py::sequence>(angular_weights);
87+
88+ if (static_cast <size_t >(py::len (sequence)) != target_size) {
89+ throw py::value_error (
90+ " Sequence angular_weights inputs must provide one weight vector per sequential detector."
91+ );
92+ }
93+
94+ weights_by_detector.resize (target_size);
95+
96+ for (size_t detector_index = 0 ; detector_index < target_size; ++detector_index) {
97+ py::array_t <std::complex <double >, py::array::c_style | py::array::forcecast> vector =
98+ py::cast<py::array_t <std::complex <double >, py::array::c_style | py::array::forcecast>>(sequence[detector_index]);
99+
100+ std::vector<complex128> cast_weights;
101+ vector_assign_from_numpy (cast_weights, vector);
102+
103+ if (cast_weights.size () != sampling[detector_index]) {
104+ throw py::value_error (
105+ " Each angular weight vector must match the detector sampling at the same sequential index."
106+ );
107+ }
108+
109+ weights_by_detector[detector_index] = std::move (cast_weights);
110+ }
111+
112+ return weights_by_detector;
113+ }
114+ }
115+
17116
18117PYBIND11_MODULE (detector_set, module ) {
19118 py::object ureg = get_shared_ureg ();
@@ -109,6 +208,7 @@ PYBIND11_MODULE(detector_set, module) {
109208 Casting::cast_py_to_vector<double >(gamma_offset, " radian" ),
110209 Casting::Polarization::cast_py_to_polarization_set (polarization_filter),
111210 Casting::Material::create_material_set_from_pyobject<MediumSet, double , BaseMedium, ConstantMedium>(medium, " medium" ),
211+ std::vector<std::vector<complex128>>{},
112212 false
113213 );
114214 }
@@ -243,7 +343,8 @@ PYBIND11_MODULE(detector_set, module) {
243343 const py::object& sampling,
244344 const py::object& cache_numerical_aperture,
245345 const py::object& polarization_filter,
246- const py::object& medium
346+ const py::object& medium,
347+ const py::object& angular_weights
247348 ) {
248349 std::vector<unsigned > sampling_value =
249350 Casting::cast_py_to_broadcasted_vector<unsigned >(
@@ -295,6 +396,13 @@ PYBIND11_MODULE(detector_set, module) {
295396 target_size
296397 );
297398
399+ std::vector<std::vector<complex128>> angular_weights_value =
400+ cast_py_to_sequential_angular_weights (
401+ angular_weights,
402+ sampling_value,
403+ target_size
404+ );
405+
298406 return std::make_shared<PhotodiodeSet>(
299407 sampling_value,
300408 numerical_aperture_value,
@@ -303,6 +411,7 @@ PYBIND11_MODULE(detector_set, module) {
303411 gamma_offset_value,
304412 polarization_filter_set,
305413 medium_set,
414+ angular_weights_value,
306415 true
307416 );
308417 },
@@ -314,6 +423,7 @@ PYBIND11_MODULE(detector_set, module) {
314423 py::arg (" cache_numerical_aperture" ) = py::float_ (0.0 ),
315424 py::arg (" polarization_filter" ) = PolarizationState (),
316425 py::arg (" medium" ) = ConstantMedium (1.0 ),
426+ py::arg (" angular_weights" ) = py::none (),
317427 R"pdoc(
318428 Build a sequential photodiode detector set.
319429
@@ -344,6 +454,12 @@ PYBIND11_MODULE(detector_set, module) {
344454 Surrounding medium. This can be provided either as medium
345455 objects or as refractive index values. Default is
346456 ``ConstantMedium(1.0)``.
457+ angular_weights : array-like, optional
458+ Explicit complex angular weight vectors for each sequential
459+ detector. This can be a single one-dimensional array shared
460+ by all detectors, a two-dimensional array of shape
461+ ``(target_size, sampling)``, or a list of one-dimensional
462+ arrays when detector sampling varies by sequential index.
347463
348464 Returns
349465 -------
0 commit comments