PLaSK library
Loading...
Searching...
No Matches
besselcyl-python.cpp
Go to the documentation of this file.
1/*
2 * This file is part of PLaSK (https://plask.app) by Photonics Group at TUL
3 * Copyright (c) 2022 Lodz University of Technology
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, version 3.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 */
14#define PY_ARRAY_UNIQUE_SYMBOL PLASK_OPTICAL_SLAB_ARRAY_API
15#define NO_IMPORT_ARRAY
16
17#include "plask/python_numpy.hpp"
18
19#include "besselcyl-python.hpp"
20#include "modal-python.hpp"
21
22namespace plask { namespace optical { namespace modal { namespace python {
23
24template <> inline const char* solver_compute_reflectivity_name<BesselSolverCyl>() {
25 return "BesselCyl.compute_reflectivity";
26}
27
29 return "BesselCyl.compute_transmittivity";
30}
31
32
33template <>
34py::object Eigenmodes<BesselSolverCyl>::array(const dcomplex* data, size_t N) const {
35 const int dim = 2, strid = 2;
36 npy_intp dims[] = { npy_intp(N / strid), npy_intp(strid) };
37 npy_intp strides[] = { strid * sizeof(dcomplex), sizeof(dcomplex) };
38 PyObject* arr = PyArray_New(&PyArray_Type, dim, dims, NPY_CDOUBLE, strides, (void*)data, 0, 0, NULL);
39 if (arr == nullptr) throw plask::CriticalException("cannot create array");
40 return py::object(py::handle<>(arr));
41}
42
44 return format(u8"<m: {:d}, lam: {}nm, power: {:.2g}mW>", self.m, str(2e3*PI/self.k0, u8"({:.3f}{:+.3g}j)"), self.power);
45}
47 return format(u8"BesselCyl.Mode(m={:d}, lam={}, power={:g})", self.m, str(2e3*PI/self.k0), self.power);
48}
49
50py::object BesselSolverCyl_getDeterminant(py::tuple args, py::dict kwargs) {
51 if (py::len(args) != 1)
52 throw TypeError(u8"get_determinant() takes exactly one non-keyword argument ({0} given)", py::len(args));
53 BesselSolverCyl* self = py::extract<BesselSolverCyl*>(args[0]);
54
55 enum What {
56 WHAT_NOTHING = 0,
57 WHAT_WAVELENGTH,
58 WHAT_K0,
59 };
60 What what = WHAT_NOTHING;
61 py::object array;
62 int m = self->getM();
63
64 plask::optional<dcomplex> k0;
65 py::stl_input_iterator<std::string> begin(kwargs), end;
66 for (auto i = begin; i != end; ++i) {
67 if (*i == "lam") {
68 if (what == WHAT_K0 || k0)
69 throw BadInput(self->getId(), u8"'lam' and 'k0' are mutually exclusive");
70 if (PyArray_Check(py::object(kwargs[*i]).ptr())) {
71 if (what) throw TypeError(u8"only one key may be an array");
72 what = WHAT_WAVELENGTH; array = kwargs[*i];
73 } else
74 k0.reset(2e3*PI / py::extract<dcomplex>(kwargs[*i])());
75 } else if (*i == "k0") {
76 if (what == WHAT_WAVELENGTH || k0)
77 throw BadInput(self->getId(), u8"'lam' and 'k0' are mutually exclusive");
78 if (PyArray_Check(py::object(kwargs[*i]).ptr())) {
79 if (what) throw TypeError(u8"only one key may be an array");
80 what = WHAT_K0; array = kwargs[*i];
81 } else
82 k0.reset(py::extract<dcomplex>(kwargs[*i]));
83 } else if (*i == "m") {
84 m = py::extract<int>(kwargs[*i]);
85 } else
86 throw TypeError(u8"get_determinant() got unexpected keyword argument '{0}'", *i);
87 }
88
89 self->Solver::initCalculation();
90 auto* expansion = self->expansion.get();
91
92 if (k0) expansion->setK0(*k0);
93 expansion->setM(m);
94
95 switch (what) {
96 case WHAT_NOTHING:
97 if (!k0) expansion->setK0(self->getK0());
98 return py::object(self->getDeterminant());
99 case WHAT_WAVELENGTH:
100 return UFUNC<dcomplex>(
101 [self](dcomplex x) -> dcomplex { self->expansion->setK0(2e3*PI / x); return self->getDeterminant(); },
102 array,
103 "BesselCyl.get_determinant",
104 "lam"
105 );
106 case WHAT_K0:
107 return UFUNC<dcomplex>(
108 [self](dcomplex x) -> dcomplex { self->expansion->setK0(x); return self->getDeterminant(); },
109 array,
110 "BesselCyl.get_determinant",
111 "k0"
112 );
113 }
114 return py::object();
115}
116
117static size_t BesselSolverCyl_findMode(BesselSolverCyl& self, dcomplex start, const py::object& pym) {
118 int m;
119 if (pym.is_none()) {
120 m = self.getM();
121 } else {
122 m = py::extract<int>(pym);
123 }
124 return self.findMode(start, m);
125}
126
127static size_t BesselSolverCyl_setMode(BesselSolverCyl* self, dcomplex lam, const py::object& pym) {
128 self->Solver::initCalculation();
129 self->setExpansionDefaults();
130
131 auto* expansion = self->expansion.get();
132 expansion->setK0(2e3*PI / lam);
133
134 if (!pym.is_none()) {
135 expansion->setM(py::extract<int>(pym));
136 }
137
138 return self->setMode();
139}
140
141static py::object BesselSolverCyl_getFieldVectorE(BesselSolverCyl& self, int num, double z) {
142 if (num < 0) num += int(self.modes.size());
143 if (std::size_t(num) >= self.modes.size()) throw IndexError(u8"bad mode number {:d}", num);
144 return arrayFromVec2D<NPY_CDOUBLE>(self.getFieldVectorE(num, z), false, 2);
145}
146
147static py::object BesselSolverCyl_getFieldVectorH(BesselSolverCyl& self, int num, double z) {
148 if (num < 0) num += int(self.modes.size());
149 if (std::size_t(num) >= self.modes.size()) throw IndexError(u8"bad mode number {:d}", num);
150 return arrayFromVec2D<NPY_CDOUBLE>(self.getFieldVectorH(num, z), false, 2);
151}
152
153
154template <>
156 return arrayFromVec2D<NPY_CDOUBLE>(solver->getScatteredFieldVectorE(incident, side, z), false, 2);
157}
158
159template <>
161 return arrayFromVec2D<NPY_CDOUBLE>(solver->getScatteredFieldVectorH(incident, side, z), false, 2);
162}
163
164
165static py::object BesselSolverCyl_getKweights(BesselSolverCyl& self) {
167 return py::object(self.getKweights());
168 return py::object();
169}
170
171static void BesselSolverCyl_setKweights(BesselSolverCyl& self, const py::object& value) {
172 if (value.is_none()) self.clearKweights();
173 else self.setKweights(py::extract<std::vector<double>>(value));
174}
175
176
178{
180 .value("FINITE", BesselSolverCyl::DOMAIN_FINITE)
181 .value("INFINITE", BesselSolverCyl::DOMAIN_INFINITE)
182 ;
183
185 .value("DIRECT", BesselSolverCyl::RULE_DIRECT)
186 .value("COMBINED1", BesselSolverCyl::RULE_COMBINED_1)
187 .value("COMBINED2", BesselSolverCyl::RULE_COMBINED_2)
188 .value("OLD", BesselSolverCyl::RULE_OLD)
189 ;
190
192 .value("UNIFORM", BesselSolverCyl::WAVEVECTORS_UNIFORM)
193 .value("NONUNIFORM", BesselSolverCyl::WAVEVECTORS_NONUNIFORM)
194 .value("NON_UNIFORM", BesselSolverCyl::WAVEVECTORS_NONUNIFORM)
195 .value("LAGUERRE", BesselSolverCyl::WAVEVECTORS_LAGUERRE)
197 ;
198
199 CLASS(BesselSolverCyl, "BesselCyl",
200 u8"Optical Solver using Bessel expansion in cylindrical coordinates.\n\n"
201 u8"It calculates optical modes and optical field distribution using Bessel modal method\n"
202 u8"and reflection transfer in two-dimensional cylindrical space.")
203 export_base(solver);
204// solver.add_property("material_mesh", &__Class__::getMesh, u8"Regular mesh with points in which material is sampled.");
205 PROVIDER(outLoss, "");
206 RW_PROPERTY(domain, getDomain, setDomain, u8"Computational domain ('finite' or 'infinite').");
207 RW_PROPERTY(rule, getRule, setRule,
208 u8"Expansion rule for coefficients matrix. Can be 'direct', 'combined1', 'combined2'\n"
209 u8"or 'old'. Inverse rule is proven to provide the best convergence and\n"
210 u8"should be used in almost every case.\n");
211 RW_PROPERTY(size, getSize, setSize, u8"Orthogonal expansion size.");
212 RW_PROPERTY(kmethod, getKmethod, setKmethod,
213 u8"Method of selecting wavevectors for numerical Hankel transform in infinite\n"
214 u8"domain.");
215 RW_PROPERTY(klist, getKlist, setKlist,
216 u8"A list of wavevectors ranges. If no weights are given, the actual wavevectors\n"
217 u8"used in the computations are the averages of each two adjacent values specified\n"
218 u8"here and the integration weights are the sizes of each interval.\n");
219 solver.add_property("kweights", &BesselSolverCyl_getKweights, &BesselSolverCyl_setKweights,
220 u8"An optional list of relative wavevector weights. The numbers should be relative\n"
221 u8"to the inverse of the structure width.");
222 RW_PROPERTY(kmax, getKmax, setKmax,
223 u8"Maximum wavevector used in the infinite domain relative to the wavelength.\n");
224 RW_PROPERTY(kscale, getKscale, setKscale,
225 u8"Scale factor for wavevectors used in the infinite domain.\n");
226 solver.add_property("lam", &__Class__::getLam, &Solver_setLam<__Class__>,
227 u8"Wavelength of the light (nm).\n");
228 solver.add_property("wavelength", &__Class__::getLam, &Solver_setLam<__Class__>,
229 u8"Alias for :attr:`lam`");
230 solver.add_property("k0", &__Class__::getK0, &Solver_setK0<__Class__>,
231 u8"Normalized frequency of the light (1/µm).\n");
232 solver.add_property("m", &__Class__::getM, &__Class__::setM, "Angular dependence parameter.");
233 solver.def("find_mode", &BesselSolverCyl_findMode,
234 u8"Compute the mode near the specified effective index.\n\n"
235 u8"Only one of the following arguments can be given through a keyword.\n"
236 u8"It is the starting point for search of the specified parameter.\n\n"
237 u8"Args:\n"
238 u8" lam (complex): Starting wavelength (nm).\n"
239 u8" m (int): HE/EH Mode angular number. If ``None``, use :attr:`m` attribute.\n",
240 (arg("lam"), arg("m")=py::object())
241 );
242 solver.def("set_mode", &BesselSolverCyl_setMode,
243 u8"Set the mode for specified parameters.\n\n"
244 u8"This method should be used if you have found a mode manually and want to insert\n"
245 u8"it into the solver in order to determine the fields. Calling this will raise an\n"
246 u8"exception if the determinant for the specified parameters is too large.\n\n"
247 u8"Arguments can be given through keywords only.\n\n"
248 u8"Args:\n"
249 u8" lam (complex): Wavelength (nm).\n"
250 u8" m (int): HE/EH Mode angular number.\n"
251 );
252 RW_FIELD(emission, "Direction of the useful light emission.\n\n"
253 u8"Necessary for the over-threshold model to correctly compute the output power.\n"
254 u8"Currently the fields are normalized only if this parameter is set to\n"
255 u8"``top`` or ``bottom``. Otherwise, it is ``undefined`` (default) and the fields\n"
256 u8"are not normalized.");
257 solver.def("get_determinant", py::raw_function(BesselSolverCyl_getDeterminant),
258 u8"Compute discontinuity matrix determinant.\n\n"
259 u8"Arguments can be given through keywords only.\n\n"
260 u8"Args:\n"
261 u8" lam (complex): Wavelength (nm).\n"
262 u8" k0 (complex): Normalized frequency.\n"
263 u8" m (int): HE/EH Mode angular number.\n"
264 );
265 solver.def("compute_reflectivity", &Solver_computeReflectivity_index<BesselSolverCyl>,
266 (py::arg("lam"), "side", "index"));
267 solver.def("compute_reflectivity", &Solver_computeReflectivity_array<BesselSolverCyl>,
268 (py::arg("lam"), "side", "coeffs"),
269 u8"Compute reflection coefficient on planar incidence [%].\n\n"
270 u8"Args:\n"
271 u8" lam (float or array of floats): Incident light wavelength (nm).\n"
272 u8" side (`top` or `bottom`): Side of the structure where the incident light is\n"
273 u8" present.\n"
274 u8" index: Eigenmode number.\n"
275 u8" coeffs: expansion coefficients of the incident vector.\n");
276 solver.def("compute_transmittivity", &Solver_computeTransmittivity_index<BesselSolverCyl>,
277 (py::arg("lam"), "side", "index"));
278 solver.def("compute_transmittivity", &Solver_computeTransmittivity_array<BesselSolverCyl>,
279 (py::arg("lam"), "side", "coeffs"),
280 u8"Compute transmission coefficient on planar incidence [%].\n\n"
281 u8"Args:\n"
282 u8" lam (float or array of floats): Incident light wavelength (nm).\n"
283 u8" side (`top` or `bottom`): Side of the structure where the incident light is\n"
284 u8" present.\n"
285 u8" index: Eigenmode number.\n"
286 u8" coeffs: expansion coefficients of the incident vector.\n");
287 solver.def("scattering", Scattering<BesselSolverCyl>::from_index, py::with_custodian_and_ward_postcall<0,1>(), (py::arg("side"), "idx"));
288 solver.def("scattering", Scattering<BesselSolverCyl>::from_array, py::with_custodian_and_ward_postcall<0,1>(), (py::arg("side"), "coeffs"),
289 u8"Access to the reflected field.\n\n"
290 u8"Args:\n"
291 u8" side (`top` or `bottom`): Side of the structure where the incident light is\n"
292 u8" present.\n"
293 u8" polarization: Specification of the incident light polarization.\n"
294 u8" It should be a string of the form 'E\\ *#*\\ ', where *#* is the axis name\n"
295 u8" of the non-vanishing electric field component.\n"
296 u8" idx: Eigenmode number.\n"
297 u8" coeffs: expansion coefficients of the incident vector.\n\n"
298 u8":rtype: Fourier2D.Scattering\n"
299 );
300 solver.def("get_raw_E", BesselSolverCyl_getFieldVectorE, (py::arg("num"), "level"),
301 u8"Get Bessel expansion coefficients for the electric field.\n\n"
302 u8"This is a low-level function returning $E_s$ and $E_p$ Bessel\n"
303 u8"expansion coefficients. Please refer to the detailed solver description for their\n"
304 u8"interpretation.\n\n"
305 u8"Args:\n"
306 u8" num (int): Computed mode number.\n"
307 u8" level (float): Vertical lever at which the coefficients are computed.\n\n"
308 u8":rtype: numpy.ndarray\n"
309 );
310 solver.def("get_raw_H", BesselSolverCyl_getFieldVectorH, (py::arg("num"), "level"),
311 u8"Get Bessel expansion coefficients for the magnetic field.\n\n"
312 u8"This is a low-level function returning $H_s$ and $H_p$ Bessel\n"
313 u8"expansion coefficients. Please refer to the detailed solver description for their\n"
314 u8"interpretation.\n\n"
315 u8"Args:\n"
316 u8" num (int): Computed mode number.\n"
317 u8" level (float): Vertical lever at which the coefficients are computed.\n\n"
318 u8":rtype: numpy.ndarray\n"
319 );
320 solver.add_property("pml", py::make_function(&Solver_getPML<BesselSolverCyl>, py::with_custodian_and_ward_postcall<0,1>()),
322 "Side Perfectly Matched Layers boundary conditions.\n\n"
324 );
325 RO_FIELD(modes, "Computed modes.");
326
327 solver.def("layer_eigenmodes", &Eigenmodes<BesselSolverCyl>::fromZ, py::arg("level"),
328 u8"Get eigenmodes for a layer at specified level.\n\n"
329 u8"This is a low-level function to access diagonalized eigenmodes for a specific\n"
330 u8"layer. Please refer to the detailed solver description for the interpretation\n"
331 u8"of the returned values.\n\n"
332 u8"Args:\n"
333 u8" level (float): Vertical level at which the coefficients are computed.\n\n"
334 u8":rtype: :class:`~optical.modal.BesselCyl.Eigenmodes`\n",
335 py::with_custodian_and_ward_postcall<0, 1>()
336 );
337
338#ifndef NDEBUG
339 solver.def("epsV_k", &BesselSolverCyl::epsV_k, py::arg("layer"));
340 solver.def("epsTss", &BesselSolverCyl::epsTss, py::arg("layer"));
341 solver.def("epsTsp", &BesselSolverCyl::epsTsp, py::arg("layer"));
342 solver.def("epsTps", &BesselSolverCyl::epsTps, py::arg("layer"));
343 solver.def("epsTpp", &BesselSolverCyl::epsTpp, py::arg("layer"));
344 solver.def("muV_k", &BesselSolverCyl::muV_k);
345 solver.def("muTss", &BesselSolverCyl::muTss);
346 solver.def("muTsp", &BesselSolverCyl::muTsp);
347 solver.def("muTps", &BesselSolverCyl::muTps);
348 solver.def("muTpp", &BesselSolverCyl::muTpp);
349#endif
350
351 py::scope scope = solver;
352 (void) scope; // don't warn about unused variable scope
353
355 py::class_<BesselSolverCyl::Mode>("Mode", u8"Detailed information about the mode.", py::no_init)
356 .add_property("lam", &getModeWavelength<BesselSolverCyl::Mode>, u8"Mode wavelength (nm).")
357 .add_property("loss", &getModeLoss<BesselSolverCyl::Mode>, u8"Mode loss (1/cm).")
358 .add_property("wavelength", &getModeWavelength<BesselSolverCyl::Mode>, u8"Mode wavelength (nm).")
359 .def_readonly("k0", &BesselSolverCyl::Mode::k0, u8"Mode normalized frequency (1/µm).")
360 .def_readonly("m", &BesselSolverCyl::Mode::m, u8"Angular mode order.")
361 .def_readwrite("power", &BesselSolverCyl::Mode::power, u8"Total power emitted into the mode.")
362 .def("__str__", &BesselSolverCyl_Mode_str)
363 .def("__repr__", &BesselSolverCyl_Mode_repr)
364 ;
365
368}
369
370}}}} // namespace plask::optical::modal::python