14#define PY_ARRAY_UNIQUE_SYMBOL PLASK_OPTICAL_SLAB_ARRAY_API
15#define NO_IMPORT_ARRAY
20namespace plask {
namespace optical {
namespace modal {
namespace python {
23 return "Fourier2D.compute_reflectivity";
27 return "Fourier2D.compute_transmittivity";
32 int dim = 2, strid = 2;
33 if (solver.separated()) strid = dim = 1;
38 return py::object(py::handle<>(
arr));
46 default:
return "none";
50static py::object FourierSolver2D_getMirrors(
const FourierSolver2D& self) {
51 if (!self.
mirrors)
return py::object();
55static void FourierSolver2D_setMirrors(
FourierSolver2D& self, py::object value) {
60 double v = py::extract<double>(value);
62 }
catch (py::error_already_set&) {
65 if (py::len(value) != 2)
throw py::error_already_set();
67 std::make_pair<double, double>(
double(py::extract<double>(value[0])),
double(py::extract<double>(value[1]))));
68 }
catch (py::error_already_set&) {
69 throw ValueError(
"none, float, or tuple of two floats required");
75static py::object FourierSolver2D_getDeterminant(py::tuple
args, py::dict
kwargs) {
76 if (py::len(
args) != 1)
77 throw TypeError(
u8"get_determinant() takes exactly one non-keyword argument ({0} given)", py::len(
args));
81 enum What {
WHAT_NOTHING = 0, WHAT_WAVELENGTH, WHAT_K0, WHAT_NEFF, WHAT_KTRAN, WHAT_BETA };
85 plask::optional<dcomplex> k0, neff, ktran, beta;
89 py::stl_input_iterator<std::string> begin(
kwargs),
end;
90 for (
auto i = begin;
i !=
end; ++
i) {
92 if (what == WHAT_K0 || k0)
throw BadInput(self->
getId(),
u8"'lam' and 'k0' are mutually exclusive");
94 if (what)
throw TypeError(
u8"only one key may be an array");
95 what = WHAT_WAVELENGTH;
98 k0.reset(2
e3 *
PI / py::extract<dcomplex>(
kwargs[*i])());
99 }
else if (*i ==
"k0") {
100 if (what == WHAT_WAVELENGTH || k0)
throw BadInput(self->
getId(),
u8"'lam' and 'k0' are mutually exclusive");
102 if (what)
throw TypeError(
u8"only one key may be an array");
106 k0.reset(py::extract<dcomplex>(
kwargs[*i]));
107 }
else if (*i ==
"neff") {
108 if (what == WHAT_BETA || beta)
throw BadInput(self->
getId(),
u8"'neff' and '{}' are mutually exclusive",
_beta);
110 if (expansion->separated())
111 throw Exception(
"{0}: Cannot get determinant for effective index array with polarization separation",
113 if (what)
throw TypeError(
u8"only one key may be an array");
117 neff.reset(py::extract<dcomplex>(
kwargs[*i]));
118 if (expansion->separated() && *neff != 0.)
119 throw Exception(
"{0}: Effective index must be 0 with polarization separation",
122 }
else if (*i ==
"ktran" || *i ==
"kt" || *i ==
"k" +
axes->getNameForTran()) {
123 if (what == WHAT_KTRAN || ktran)
throw BadInput(self->
getId(),
u8"'{}' was already specified as '{}'", *i,
_ktran);
126 if (expansion->symmetric())
127 throw Exception(
"{0}: Cannot get determinant for transverse wavevector array with symmetry", self->
getId());
128 if (what)
throw TypeError(
u8"only one key may be an array");
132 ktran.reset(py::extract<dcomplex>(
kwargs[*i]));
133 if (expansion->symmetric() && *ktran != 0.)
134 throw Exception(
"{0}: Transverse wavevector must be 0 with symmetry",
137 }
else if (*i ==
"beta" || *i ==
"klong" || *i ==
"kl" || *i ==
"k"+
axes->getNameForLong()) {
138 if (what == WHAT_BETA || beta)
throw BadInput(self->
getId(),
u8"'{}' was already specified as '{}'", *i,
_beta);
140 if (what == WHAT_NEFF || neff)
throw BadInput(self->
getId(),
u8"'{}' and 'neff' are mutually exclusive", *i);
142 if (expansion->separated())
143 throw Exception(
"{0}: Cannot get determinant for longitudinal wavevector array with polarization separation",
145 if (what)
throw TypeError(
u8"only one key may be an array");
149 beta.reset(py::extract<dcomplex>(
kwargs[*i]));
150 if (expansion->separated() && *beta != 0.)
151 throw Exception(
"{0}: Longitudinal wavevector must be 0 with polarization separation",
155 throw TypeError(
u8"get_determinant() got unexpected keyword argument '{0}'", *i);
158 self->Solver::initCalculation();
160 if (k0) expansion->
setK0(*k0);
161 else expansion->setK0(self->
getK0());
162 if (neff) {
if (what != WHAT_WAVELENGTH && what != WHAT_K0) expansion->setBeta(*neff * expansion->k0); }
163 else if (beta) expansion->setBeta(*beta);
164 else expansion->setBeta(self->
getBeta());
165 if (ktran) expansion->setKtran(*ktran);
166 else expansion->setKtran(self->
getKtran());
167 expansion->setLam0(self->
getLam0());
173 case WHAT_WAVELENGTH:
175 [self, neff](dcomplex x) -> dcomplex {
181 "Fourier2D.get_determinant",
185 [self, neff](dcomplex x) -> dcomplex {
191 "Fourier2D.get_determinant",
195 [self](dcomplex x) -> dcomplex {
200 "Fourier2D.get_determinant",
204 [self](dcomplex x) -> dcomplex {
209 "Fourier2D.get_determinant",
213 [self](dcomplex x) -> dcomplex {
218 "Fourier2D.get_determinant",
224static size_t FourierSolver2D_setMode(py::tuple
args, py::dict
kwargs) {
225 if (py::len(
args) != 1)
throw TypeError(
u8"set_mode() takes exactly one non-keyword argument ({0} given)", py::len(
args));
230 plask::optional<dcomplex> k0, neff, ktran;
231 py::stl_input_iterator<std::string> begin(
kwargs),
end;
232 for (
auto i = begin;
i !=
end; ++
i) {
233 if (*i ==
"lam" || *i ==
"wavelength") {
234 if (k0)
throw BadInput(self->
getId(),
u8"'lam' and 'k0' are mutually exclusive");
235 k0.reset(2
e3 *
PI / py::extract<dcomplex>(
kwargs[*i])());
236 }
else if (*i ==
"k0") {
237 if (k0)
throw BadInput(self->
getId(),
u8"'lam' and 'k0' are mutually exclusive");
238 k0.reset(py::extract<dcomplex>(
kwargs[*i]));
239 }
else if (*i ==
"neff") {
240 neff.reset(py::extract<dcomplex>(
kwargs[*i]));
241 }
else if (*i ==
"ktran" || *i ==
"kt" || *i ==
"k" +
axes->getNameForTran()) {
242 ktran.reset(py::extract<dcomplex>(
kwargs[*i]));
244 throw TypeError(
u8"set_mode() got unexpected keyword argument '{0}'", *i);
247 self->Solver::initCalculation();
250 expansion->
setK0(*k0);
252 expansion->setK0(self->
getK0());
254 expansion->setBeta(*neff * expansion->k0);
256 expansion->setBeta(self->
getBeta());
258 expansion->setKtran(*ktran);
260 expansion->setKtran(self->
getKtran());
261 expansion->setLam0(self->
getLam0());
268static size_t FourierSolver2D_findMode(py::tuple
args, py::dict
kwargs) {
269 if (py::len(
args) != 1)
throw TypeError(
u8"find_mode() takes exactly one non-keyword argument ({0} given)", py::len(
args));
272 if (py::len(
kwargs) != 1)
throw TypeError(
u8"find_mode() takes exactly one keyword argument ({0} given)", py::len(
kwargs));
273 std::string key = py::extract<std::string>(
kwargs.keys()[0]);
274 dcomplex value = py::extract<dcomplex>(
kwargs[key]);
278 if (key ==
"lam" || key ==
"wavelength")
280 else if (key ==
"k0")
282 else if (key ==
"neff")
284 else if (key ==
"ktran" || key ==
"kt" || key ==
"k" +
axes->getNameForTran())
286 else if (key ==
"beta" || key ==
"klong" || key ==
"kl" || key ==
"k" +
axes->getNameForTran())
289 throw TypeError(
u8"find_mode() got unexpected keyword argument '{0}'", key);
298 if (name ==
"k" +
axes->getNameForLong())
return py::object(
mode.beta);
299 if (name ==
"k" +
axes->getNameForTran())
return py::object(
mode.ktran);
310 default:
pol =
"none";
316 default:
sym =
"none";
318 return format(
u8"<lam: {:.2f}nm, neff: {}, ktran: {}/um, polarization: {}, symmetry: {}, power: {:.2g} mW>",
328 default:
pol =
"None";
334 default:
sym =
"None";
336 return format(
u8"Fourier2D.Mode(lam={0}, neff={1}, ktran={2}, polarization={3}, symmetry={4}, power={5})",
340static py::object FourierSolver2D_getFieldVectorE(
FourierSolver2D& self,
int num,
double z) {
341 if (num < 0) num +=
int(self.
modes.size());
342 if (std::size_t(num) >= self.
modes.size())
throw IndexError(
u8"bad mode number {:d}", num);
346static py::object FourierSolver2D_getFieldVectorH(
FourierSolver2D& self,
int num,
double z) {
347 if (num < 0) num +=
int(self.
modes.size());
348 if (std::size_t(num) >= self.
modes.size())
throw IndexError(
u8"bad mode number {:d}", num);
363static py::object FourierSolver2D_incidentGaussian(
FourierSolver2D& self,
388 std::string
repr = py::extract<std::string>(obj);
391 else if (
repr ==
"Etran" ||
repr ==
"Et" ||
repr ==
"E"+axes->getNameForTran() ||
392 repr ==
"Hlong" ||
repr ==
"Hl" ||
repr ==
"H"+axes->getNameForLong() ||
repr ==
"TM")
394 else if (
repr ==
"Elong" ||
repr ==
"El" ||
repr ==
"E"+axes->getNameForLong() ||
395 repr ==
"Htran" ||
repr ==
"Ht" ||
repr ==
"H"+axes->getNameForTran() ||
repr ==
"TE")
398 throw py::error_already_set();
399 }
catch (py::error_already_set&) {
400 throw ValueError(
"wrong component specification.");
412 py::object wavelength,
420 py::object wavelength,
441 u8"Optical Solver using Fourier expansion in 2D.\n\n"
442 u8"It calculates optical modes and optical field distribution using Fourier modal method\n"
443 u8"and reflection transfer in two-dimensional Cartesian space.")
445 PROVIDER(outNeff,
"Effective index of the last computed mode.");
446 RW_PROPERTY(size, getSize, setSize,
"Orthogonal expansion size.");
447 RW_PROPERTY(symmetry, getSymmetry, setSymmetry,
"Mode symmetry.");
450 u8"Wavelength of the light (nm).\n\n"
451 u8"Use this property only if you are looking for anything else than\n"
452 u8"the wavelength, e.g. the effective index of lateral wavevector.\n");
455 u8"Normalized frequency of the light (1/µm).\n\n"
456 u8"Use this property only if you are looking for anything else than\n"
457 u8"the wavelength,e.g. the effective index of lateral wavevector.\n");
459 u8"Longitudinal propagation constant of the light (1/µm).\n\n"
460 u8"Use this property only if you are looking for anything else than\n"
461 u8"the longitudinal component of the propagation vector and the effective index.\n");
463 u8"Alias for :attr:`klong`\n");
465 u8"Transverse propagation constant of the light (1/µm).\n\n"
466 u8"Use this property only if you are looking for anything else than\n"
467 u8"the transverse component of the propagation vector.\n");
468 RW_FIELD(refine,
"Number of refinement points for refractive index averaging.");
469 solver.add_property(
"ft", &__Class__::getFourierType, &__Class__::setFourierType,
470 u8"Type of the Fourier transform. Analytic transform is faster and more precise,\n"
471 u8"however it ignores temperature and gain distributions.\n");
472 solver.add_property(
"dct", &__Class__::getDCT, &__Class__::setDCT,
473 "Type of discrete cosine transform for symmetric expansion.");
475 "Direction of the useful light emission.\n\n"
476 u8"Necessary for the over-threshold model to correctly compute the output power.\n"
477 u8"Currently the fields are normalized only if this parameter is set to\n"
478 u8"``top`` or ``bottom``. Otherwise, it is ``undefined`` (default) and the fields\n"
479 u8"are not normalized.");
480 solver.def(
"get_determinant", py::raw_function(FourierSolver2D_getDeterminant),
481 u8"Compute discontinuity matrix determinant.\n\n"
482 u8"Arguments can be given through keywords only.\n\n"
484 u8" lam (complex): Wavelength (nm).\n"
485 u8" k0 (complex): Normalized frequency (1/µm).\n"
486 u8" neff (complex): Longitudinal effective index.\n"
487 u8" ktran (complex): Transverse wavevector (1/µm).\n");
488 solver.def(
"find_mode", py::raw_function(FourierSolver2D_findMode),
489 u8"Compute the mode near the specified effective index.\n\n"
490 u8"Only one of the following arguments can be given through a keyword.\n"
491 u8"It is the starting point for search of the specified parameter.\n\n"
493 u8" lam (complex): Wavelength (nm).\n"
494 u8" k0 (complex): Normalized frequency (1/µm).\n"
495 u8" neff (complex): Longitudinal effective index.\n"
496 u8" ktran (complex): Transverse wavevector (1/µm).\n");
497 solver.def(
"set_mode", py::raw_function(FourierSolver2D_setMode),
498 u8"Set the mode for specified parameters.\n\n"
499 u8"This method should be used if you have found a mode manually and want to insert\n"
500 u8"it into the solver in order to determine the fields. Calling this will raise an\n"
501 u8"exception if the determinant for the specified parameters is too large.\n\n"
502 u8"Arguments can be given through keywords only.\n\n"
504 u8" lam (complex): Wavelength (nm).\n"
505 u8" k0 (complex): Normalized frequency (1/µm).\n"
506 u8" neff (complex): Longitudinal effective index.\n"
507 u8" ktran (complex): Transverse wavevector (1/µm).\n");
509 (py::arg(
"lam"),
"side",
"polarization"));
512 u8"Compute reflection coefficient on planar incidence [%].\n\n"
514 u8" lam (float or array of floats): Incident light wavelength (nm).\n"
515 u8" side (`top` or `bottom`): Side of the structure where the incident light is\n"
517 u8" polarization: Specification of the incident light polarization.\n"
518 u8" It should be a string of the form 'E\\ *#*\\ ', where *#* is the axis\n"
519 u8" name of the non-vanishing electric field component.\n"
520 u8" idx: Eigenmode number.\n"
521 u8" coeffs: expansion coefficients of the incident vector.\n");
523 (py::arg(
"lam"),
"side",
"polarization"));
526 u8"Compute transmission coefficient on planar incidence [%].\n\n"
528 u8" lam (float or array of floats): Incident light wavelength (nm).\n"
529 u8" side (`top` or `bottom`): Side of the structure where the incident light is\n"
531 u8" polarization: Specification of the incident light polarization.\n"
532 u8" It should be a string of the form 'E\\ *#*\\ ', where *#* is the axis name\n"
533 u8" of the non-vanishing electric field component.\n"
534 u8" idx: Eigenmode number.\n"
535 u8" coeffs: expansion coefficients of the incident vector.\n");
536 solver.add_property(
"mirrors", FourierSolver2D_getMirrors, FourierSolver2D_setMirrors,
537 u8"Mirror reflectivities. If None then they are automatically estimated from the\n"
538 u8"Fresnel equations.");
543 (py::arg(
"side"),
"polarization"));
545 (py::arg(
"side"),
"idx"));
547 (py::arg(
"side"),
"coeffs"),
548 u8"Access to the reflected field.\n\n"
550 u8" side (`top` or `bottom`): Side of the structure where the incident light is\n"
552 u8" polarization: Specification of the incident light polarization.\n"
553 u8" It should be a string of the form 'E\\ *#*\\ ', where *#* is the axis name\n"
554 u8" of the non-vanishing electric field component.\n"
555 u8" idx: Eigenmode number.\n"
556 u8" coeffs: expansion coefficients of the incident vector.\n\n"
557 u8":rtype: Fourier2D.Scattering\n");
558 solver.def(
"get_raw_E", FourierSolver2D_getFieldVectorE, (py::arg(
"num"),
"level"),
559 u8"Get Fourier expansion coefficients for the electric field.\n\n"
560 u8"This is a low-level function returning $E_l$ and/or $E_t$ Fourier\n"
561 u8"expansion coefficients. Please refer to the detailed solver description for their\n"
562 u8"interpretation.\n\n"
564 u8" num (int): Computed mode number.\n"
565 u8" level (float): Vertical level at which the coefficients are computed.\n\n"
566 u8":rtype: numpy.ndarray\n");
567 solver.def(
"get_raw_H", FourierSolver2D_getFieldVectorH, (py::arg(
"num"),
"level"),
568 u8"Get Fourier expansion coefficients for the magnetic field.\n\n"
569 u8"This is a low-level function returning $H_l$ and/or $H_t$ Fourier\n"
570 u8"expansion coefficients. Please refer to the detailed solver description for their\n"
571 u8"interpretation.\n\n"
573 u8" num (int): Computed mode number.\n"
574 u8" level (float): Vertical level at which the coefficients are computed.\n\n"
575 u8":rtype: numpy.ndarray\n");
577 u8"Get eignemodes for a layer at specified level.\n\n"
578 u8"This is a low-level function to access diagonalized eigenmodes for a specific\n"
579 u8"layer. Please refer to the detailed solver description for the interpretation\n"
580 u8"of the returned values.\n\n"
582 u8" level (float): Vertical level at which the coefficients are computed.\n\n"
583 u8":rtype: :class:`~optical.modal.Fourier2D.Eigenmodes`\n",
584 py::with_custodian_and_ward_postcall<0, 1>());
585 solver.def(
"gaussian", &FourierSolver2D_incidentGaussian, (py::arg(
"side"),
"polarization",
"sigma", py::arg(
"center") = 0.),
586 u8"Create coefficients vector with Gaussian profile.\n\n"
587 u8"This method is intended to use for :py:meth:`scattering` method.\n\n"
589 u8" side (`top` or `bottom`): Side of the structure where the incident light is\n"
591 u8" polarization: Specification of the incident light polarization.\n"
592 u8" It should be a string of the form 'E\\ *#*\\ ', where *#* is the axis name\n"
593 u8" of the non-vanishing electric field component.\n"
594 u8" sigma (float): Gaussian standard deviation (µm).\n"
595 u8" center (float): Position of the beam center (µm).\n\n"
597 u8" >>> scattered = fourier.scattering('top', \n"
598 u8" ... fourier.gaussian('top', 'Ex', 0.2))\n");
599 solver.def(
"scattering_gaussian", &FourierSolver2D_scatteringGaussian,
600 (py::arg(
"side"),
"polarization",
"sigma", py::arg(
"center") = 0.),
601 u8"Helper function to Access reflected fields for access incidence.\n\n"
602 u8"This method is equivalent to calling:\n\n"
603 u8" >>> fourier.scattering(side,\n"
604 u8" ... fourier.gaussian(side, polarization, sigma, center))\n\n"
606 u8" side (`top` or `bottom`): Side of the structure where the incident light is\n"
608 u8" polarization: Specification of the incident light polarization.\n"
609 u8" It should be a string of the form 'E\\ *#*\\ ', where *#* is the axis name\n"
610 u8" of the non-vanishing electric field component.\n"
611 u8" sigma (float): Gaussian standard deviation (µm).\n"
612 u8" center (float): Position of the beam center (µm).\n\n");
614 py::scope
scope = solver;
618 py::class_<FourierSolver2D::Mode>(
"Mode",
u8"Detailed information about the mode.", py::no_init)
625 .add_property(
"neff", &FourierSolver2D_Mode_Neff,
u8"Mode longitudinal effective index (-).")
628 .def(
"__str__", &FourierSolver2D_Mode_str)
629 .def(
"__repr__", &FourierSolver2D_Mode_repr)
630 .def(
"__getattr__", &FourierSolver2D_Mode__getattr__);