PLaSK library
Loading...
Searching...
No Matches
freecarrier_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 */
17#include <cmath>
18#include <plask/python.hpp>
19#include "plask/python_util/ufunc.hpp"
20using namespace plask;
21using namespace plask::python;
22
23#include "../freecarrier2d.hpp"
24#include "../freecarrier3d.hpp"
25using namespace plask::gain::freecarrier;
26
27#ifndef NDEBUG
28
29template <typename SolverT> static py::object FreeCarrier_detEl(SolverT* self, py::object E, size_t reg = 0, size_t well = 0) {
30 self->initCalculation();
31 typename SolverT::ActiveRegionParams params(self, self->regions[reg], self->getT0());
32 std::string name = format_geometry_suffix<typename SolverT::SpaceType>("FreeCarrier{}.det_El");
33 return PARALLEL_UFUNC<double>([self, &params, well](double x) { return self->detEl(x, params, well); }, E, name.c_str(), "E");
34}
35
36template <typename SolverT> static py::object FreeCarrier_detHh(SolverT* self, py::object E, size_t reg = 0, size_t well = 0) {
37 self->initCalculation();
38 typename SolverT::ActiveRegionParams params(self, self->regions[reg], self->getT0());
39 std::string name = format_geometry_suffix<typename SolverT::SpaceType>("FreeCarrier{}.det_Hh");
40 return PARALLEL_UFUNC<double>([self, &params, well](double x) { return self->detHh(x, params, well); }, E, name.c_str(), "E");
41}
42
43template <typename SolverT> static py::object FreeCarrier_detLh(SolverT* self, py::object E, size_t reg = 0, size_t well = 0) {
44 self->initCalculation();
45 typename SolverT::ActiveRegionParams params(self, self->regions[reg], self->getT0());
46 std::string name = format_geometry_suffix<typename SolverT::SpaceType>("FreeCarrier{}.det_Lh");
47 return PARALLEL_UFUNC<double>([self, &params, well](double x) { return self->detLh(x, params, well); }, E, name.c_str(), "E");
48}
49
50template <typename SolverT>
51static py::object FreeCarrierGainSolver_getN(SolverT* self, py::object F, py::object pT, size_t reg = 0) {
52 double T = (pT.is_none()) ? self->getT0() : py::extract<double>(pT);
53 self->initCalculation();
54 typename SolverT::ActiveRegionParams params(self, self->params0[reg], T);
55 std::string name = format_geometry_suffix<typename SolverT::SpaceType>("FreeCarrier{}.getN");
56 return PARALLEL_UFUNC<double>([self, T, reg, &params](double x) { return self->getN(x, T, params); }, F, name.c_str(), "F");
57}
58
59template <typename SolverT> static py::object FreeCarrier_getP(SolverT* self, py::object F, py::object pT, size_t reg = 0) {
60 double T = (pT.is_none()) ? self->getT0() : py::extract<double>(pT);
61 self->initCalculation();
62 typename SolverT::ActiveRegionParams params(self, self->params0[reg], T);
63 std::string name = format_geometry_suffix<typename SolverT::SpaceType>("FreeCarrier{}.getP");
64 return PARALLEL_UFUNC<double>([self, T, reg, &params](double x) { return self->getP(x, T, params); }, F, name.c_str(), "F");
65}
66#endif
67
68template <typename SolverT> static py::object FreeCarrier_getLevels(SolverT& self, py::object PLASK_UNUSED(To)) {
69 static const char* names[3] = {"el", "hh", "lh"};
70
71 // TODO consider temperature
72 self.initCalculation();
73 py::list result;
74 for (size_t reg = 0; reg < self.regions.size(); ++reg) {
75 py::dict info;
76 for (size_t i = 0; i < 3; ++i) {
77 py::list lst;
78 for (const auto& l : self.params0[reg].levels[i]) lst.append(l.E);
79 info[names[i]] = lst;
80 }
81 result.append(info);
82 }
83 return result;
84}
85
86template <typename SolverT> static py::object FreeCarrier_getFermiLevels(SolverT* self, double N, py::object To, int reg) {
87 double T = (To.is_none()) ? self->getT0() : py::extract<double>(To);
88 if (reg < 0) reg += int(self->regions.size());
89 if (reg < 0 || std::size_t(reg) >= self->regions.size()) throw IndexError(u8"{}: Bad active region index", self->getId());
90 self->initCalculation();
91 double Fc{NAN}, Fv{NAN};
92 typename SolverT::ActiveRegionParams params(self, self->params0[reg], T);
93 self->findFermiLevels(Fc, Fv, N, T, params);
94 return py::make_tuple(Fc, Fv);
95}
96
97template <typename SolverT>
98static shared_ptr<typename SolverT::GainSpectrumType> FreeCarrierGetGainSpectrum2(SolverT* solver, double c0, double c1) {
99 return solver->getGainSpectrum(Vec<2>(c0, c1));
100}
101
103 double c0,
104 double c1,
105 double c2) {
106 return solver->getGainSpectrum(Vec<3>(c0, c1, c2));
107}
108
109template <typename SolverT>
110static py::object FreeCarrierGainSpectrum__call__(typename SolverT::GainSpectrumType& self, py::object wavelengths) {
111 // return PARALLEL_UFUNC<double>([&](double x){return self.getGain(x);}, wavelengths, "Spectrum", "lam");
112 try {
113 return py::object(self.getGain(py::extract<double>(wavelengths)));
114 } catch (py::error_already_set&) {
115 PyErr_Clear();
116
118 if (inarr == NULL || PyArray_TYPE(inarr) != NPY_DOUBLE) {
120 throw TypeError(u8"{}: Wavelengths for spectrum must be a scalar float or one-dimensional array of floats",
121 self.solver->getId());
122 }
123 if (PyArray_NDIM(inarr) != 1) {
125 throw TypeError(u8"{}: Wavelengths for spectrum must be a scalar float or one-dimensional array of floats",
126 self.solver->getId());
127 }
128
130 npy_intp dims[] = {size, 2};
132 if (outarr == nullptr) {
134 throw plask::CriticalException(u8"cannot create array for gain");
135 }
136
137 double* indata = static_cast<double*>(PyArray_DATA(inarr));
139
141
142 std::exception_ptr error;
143
145 for (npy_intp i = 0; i < size; ++i) {
146 if (!error) try {
147 outdata[i] = self.getGain(indata[i * instride]);
148 } catch (...) {
149#pragma omp critical
150 error = std::current_exception();
151 }
152 }
153 if (error) {
155 std::rethrow_exception(error);
156 }
157
159
160 return py::object(py::handle<>(outarr));
161 }
162}
163
166
167 {
169 u8"Quantum-well gain using free-carrier approximation for two-dimensional Cartesian geometry.")
170#ifndef NDEBUG
171 solver.def("det_El", &FreeCarrier_detEl<__Class__>, (arg("E"), arg("reg") = 0, arg("well") = 0));
172 solver.def("det_Hh", &FreeCarrier_detHh<__Class__>, (arg("E"), arg("reg") = 0, arg("well") = 0));
173 solver.def("det_Lh", &FreeCarrier_detLh<__Class__>, (arg("E"), arg("reg") = 0, arg("well") = 0));
174 solver.def("getN", &FreeCarrierGainSolver_getN<__Class__>, (arg("F"), arg("T") = py::object(), arg("reg") = 0));
175 solver.def("getP", &FreeCarrier_getP<__Class__>, (arg("F"), arg("T") = py::object(), arg("reg") = 0));
176#endif
177 // RW_FIELD(quick_levels,
178 // "Compute levels only once and simply shift for different temperatures?\n\n"
179 // "Setting this to True strongly increases computation speed, but canis make the results\n"
180 // "less accurate for high temperatures.");
181 solver.def("get_energy_levels", &FreeCarrier_getLevels<__Class__>, arg("T") = py::object(),
182 u8"Get energy levels in quantum wells.\n\n"
183 u8"Compute energy levels in quantum wells for electrons, heavy holes and\n"
184 u8"light holes.\n\n"
185 u8"Args:\n"
186 u8" T (float or ``None``): Temperature to get the levels. If this argument is\n"
187 u8" ``None``, the estimates for temperature :py:attr:`T0`\n"
188 u8" are returned.\n\n"
189 u8"Returns:\n"
190 u8" list: List with dictionaries with keys `el`, `hh`, and `lh` with levels for\n"
191 u8" electrons, heavy holes and light holes. Each list element corresponds\n"
192 u8" to one active region.\n");
193 solver.def("get_fermi_levels", &FreeCarrier_getFermiLevels<__Class__>, (arg("n"), arg("T") = py::object(), arg("reg") = 0),
194 u8"Get quasi Fermi levels.\n\n"
195 u8"Compute quasi-Fermi levels in specified active region.\n\n"
196 u8"Args:\n"
197 u8" n (float): Carriers concentration to determine the levels for\n"
198 u8" (1/cm\\ :sup:`3`\\ ).\n"
199 u8" T (float or ``None``): Temperature to get the levels. If this argument is\n"
200 u8" ``None``, the estimates for temperature :py:attr:`T0`\n"
201 u8" are returned.\n\n"
202 u8" reg (int): Active region number.\n"
203 u8"Returns:\n"
204 u8" tuple: Two-element tuple with quasi-Fermi levels for electrons and holes.\n");
205 RW_PROPERTY(T0, getT0, setT0, "Reference temperature.\n\nIn this temperature levels estimates are computed.");
206 RW_PROPERTY(matrix_element, getMatrixElem, setMatrixElem,
207 u8"Momentum matrix element.\n\n"
208 u8"Value of the squared matrix element in gain computations. If it is not set it\n"
209 u8"is estimated automatically. (float [eV×m0])");
210 RW_PROPERTY(lifetime, getLifeTime, setLifeTime,
211 "Average carriers lifetime.\n\n"
212 "This parameter is used for gain spectrum broadening. (float [ps])");
213 RW_PROPERTY(strained, getStrained, setStrained,
214 u8"Boolean attribute indicating if the solver should consider strain in the active\n"
215 u8"region.\n\n"
216 u8"If set to ``True`` then there must a layer with the role *substrate* in\n"
217 u8"the geometry. The strain is computed by comparing the atomic lattice constants\n"
218 u8"of the substrate and the quantum wells.");
219 RW_PROPERTY(substrate, getSubstrate, setSubstrate,
220 u8"Substrate material.\n\n"
221 u8"Material of the substrate. This is used to compute strain in the active region.\n"
222 u8"If not set, the solver looks for geometry object with the __substrate__ role.\n");
223 RECEIVER(inTemperature, "");
224 RECEIVER(inBandEdges, "");
225 RECEIVER(inCarriersConcentration, "");
226 RECEIVER(inFermiLevels, "");
227 PROVIDER(outGain, "");
228 PROVIDER(outEnergyLevels, "");
229 solver.def("spectrum", &__Class__::getGainSpectrum, py::arg("point"), py::with_custodian_and_ward_postcall<0, 1>(),
230 u8"Get gain spectrum at given point.\n\n"
231 u8"Args:\n"
232 u8" point (vec): Point to get gain at.\n"
233 u8" c0, c1 (float): Coordinates of the point to get gain at.\n\n"
234 u8"Returns:\n"
235 u8" :class:`FreeCarrier2D.Spectrum`: Spectrum object.\n");
236 solver.def("spectrum", FreeCarrierGetGainSpectrum2<__Class__>, (py::arg("c0"), "c1"),
237 py::with_custodian_and_ward_postcall<0, 1>());
238
239 py::scope scope = solver;
240 (void)scope; // don't warn about unused variable scope
241 py::class_<typename __Class__::GainSpectrumType, plask::shared_ptr<typename __Class__::GainSpectrumType>,
242 boost::noncopyable>(
243 "Spectrum", u8"Gain spectrum object. You can call it like a function to get gains for different wavelengths.",
244 py::no_init)
245 .def("__call__", &FreeCarrierGainSpectrum__call__<__Class__>, py::arg("lam"),
246 u8"Get gain at specified wavelength.\n\n"
247 u8"Args:\n"
248 u8" lam (float): Wavelength to get the gain at.\n");
249 }
250
251 {
253 u8"Quantum-well gain using free-carrier approximation for cylindrical geometry.")
254#ifndef NDEBUG
255 solver.def("det_El", &FreeCarrier_detEl<__Class__>, (arg("E"), arg("reg") = 0, arg("well") = 0));
256 solver.def("det_Hh", &FreeCarrier_detHh<__Class__>, (arg("E"), arg("reg") = 0, arg("well") = 0));
257 solver.def("det_Lh", &FreeCarrier_detLh<__Class__>, (arg("E"), arg("reg") = 0, arg("well") = 0));
258 solver.def("getN", &FreeCarrierGainSolver_getN<__Class__>, (arg("F"), arg("T") = py::object(), arg("reg") = 0));
259 solver.def("getP", &FreeCarrier_getP<__Class__>, (arg("F"), arg("T") = py::object(), arg("reg") = 0));
260#endif
261 // RW_FIELD(quick_levels,
262 // "Compute levels only once and simply shift for different temperatures?\n\n"
263 // "Setting this to True strongly increases computation speed, but can make the results\n"
264 // "less accurate for high temperatures.");
265 solver.def("get_energy_levels", &FreeCarrier_getLevels<__Class__>, arg("T") = py::object(),
266 u8"Get energy levels in quantum wells.\n\n"
267 u8"Compute energy levels in quantum wells for electrons, heavy holes and\n"
268 u8"light holes.\n\n"
269 u8"Args:\n"
270 u8" T (float or ``None``): Temperature to get the levels. If this argument is\n"
271 u8" ``None``, the estimates for temperature :py:attr:`T0`\n"
272 u8" are returned.\n\n"
273 u8"Returns:\n"
274 u8" list: List with dictionaries with keys `el`, `hh`, and `lh` with levels for\n"
275 u8" electrons, heavy holes and light holes. Each list element corresponds\n"
276 u8" to one active region.\n");
277 solver.def("get_fermi_levels", &FreeCarrier_getFermiLevels<__Class__>, (arg("n"), arg("T") = py::object(), arg("reg") = 0),
278 u8"Get quasi-Fermi levels.\n\n"
279 u8"Compute quasi-Fermi levels in specified active region.\n\n"
280 u8"Args:\n"
281 u8" n (float): Carriers concentration to determine the levels for\n"
282 u8" (1/cm\\ :sup:`3`\\ ).\n"
283 u8" T (float or ``None``): Temperature to get the levels. If this argument is\n"
284 u8" ``None``, the estimates for temperature :py:attr:`T0`\n"
285 u8" are returned.\n"
286 u8" reg (int): Active region number.\n"
287 u8"Returns:\n"
288 u8" tuple: Two-element tuple with quasi-Fermi levels for electrons and holes.\n");
289 RW_PROPERTY(T0, getT0, setT0, u8"Reference temperature.\n\nIn this temperature levels estimates are computed.");
290 RW_PROPERTY(matrix_element, getMatrixElem, setMatrixElem,
291 u8"Momentum matrix element.\n\n"
292 u8"Value of the squared matrix element in gain computations. If it is not set it\n"
293 u8"is estimated automatically. (float [eV×m0])");
294 RW_PROPERTY(lifetime, getLifeTime, setLifeTime,
295 "Average carriers lifetime.\n\n"
296 "This parameter is used for gain spectrum broadening. (float [ps])");
297 RW_PROPERTY(strained, getStrained, setStrained,
298 u8"Boolean attribute indicating if the solver should consider strain in the active\n"
299 u8"region.\n\n"
300 u8"If set to ``True`` then there must a layer with the role *substrate* in\n"
301 u8"the geometry. The strain is computed by comparing the atomic lattice constants\n"
302 u8"of the substrate and the quantum wells.");
303 RW_PROPERTY(substrate, getSubstrate, setSubstrate,
304 u8"Substrate material.\n\n"
305 u8"Material of the substrate. This is used to compute strain in the active region.\n"
306 u8"If not set, the solver looks for geometry object with the __substrate__ role.\n");
307 RECEIVER(inTemperature, "");
308 RECEIVER(inBandEdges, "");
309 RECEIVER(inCarriersConcentration, "");
310 RECEIVER(inFermiLevels, "");
311 PROVIDER(outGain, "");
312 PROVIDER(outEnergyLevels, "");
313 solver.def("spectrum", &__Class__::getGainSpectrum, py::arg("point"), py::with_custodian_and_ward_postcall<0, 1>(),
314 u8"Get gain spectrum at given point.\n\n"
315 u8"Args:\n"
316 u8" point (vec): Point to get gain at.\n"
317 u8" c0, c1 (float): Coordinates of the point to get gain at.\n\n"
318 u8"Returns:\n"
319 u8" :class:`FreeCarrierCyl.Spectrum`: Spectrum object.\n");
320 solver.def("spectrum", FreeCarrierGetGainSpectrum2<__Class__>, (py::arg("c0"), "c1"),
321 py::with_custodian_and_ward_postcall<0, 1>());
322
323 py::scope scope = solver;
324 (void)scope; // don't warn about unused variable scope
325 py::class_<typename __Class__::GainSpectrumType, plask::shared_ptr<typename __Class__::GainSpectrumType>,
326 boost::noncopyable>(
327 "Spectrum", u8"Gain spectrum object. You can call it like a function to get gains for different wavelengths.",
328 py::no_init)
329 .def("__call__", &FreeCarrierGainSpectrum__call__<__Class__>, py::arg("lam"),
330 u8"Get gain at specified wavelength.\n\n"
331 u8"Args:\n"
332 u8" lam (float): Wavelength to get the gain at.\n");
333 }
334
335 {
336 CLASS(FreeCarrierGainSolver3D, "FreeCarrier3D",
337 u8"Quantum-well gain using free-carrier approximation for three-dimensional Cartesian geometry.")
338#ifndef NDEBUG
339 solver.def("det_El", &FreeCarrier_detEl<__Class__>, (arg("E"), arg("reg") = 0, arg("well") = 0));
340 solver.def("det_Hh", &FreeCarrier_detHh<__Class__>, (arg("E"), arg("reg") = 0, arg("well") = 0));
341 solver.def("det_Lh", &FreeCarrier_detLh<__Class__>, (arg("E"), arg("reg") = 0, arg("well") = 0));
342 solver.def("getN", &FreeCarrierGainSolver_getN<__Class__>, (arg("F"), arg("T") = py::object(), arg("reg") = 0));
343 solver.def("getP", &FreeCarrier_getP<__Class__>, (arg("F"), arg("T") = py::object(), arg("reg") = 0));
344#endif
345 // RW_FIELD(quick_levels,
346 // "Compute levels only once and simply shift for different temperatures?\n\n"
347 // "Setting this to True strongly increases computation speed, but canis make the results\n"
348 // "less accurate for high temperatures.");
349 solver.def("get_energy_levels", &FreeCarrier_getLevels<__Class__>, arg("T") = py::object(),
350 u8"Get energy levels in quantum wells.\n\n"
351 u8"Compute energy levels in quantum wells for electrons, heavy holes and\n"
352 u8"light holes.\n\n"
353 u8"Args:\n"
354 u8" T (float or ``None``): Temperature to get the levels. If this argument is\n"
355 u8" ``None``, the estimates for temperature :py:attr:`T0`\n"
356 u8" are returned.\n\n"
357 u8"Returns:\n"
358 u8" list: List with dictionaries with keys `el`, `hh`, and `lh` with levels for\n"
359 u8" electrons, heavy holes and light holes. Each list element corresponds\n"
360 u8" to one active region.\n");
361 solver.def("get_fermi_levels", &FreeCarrier_getFermiLevels<__Class__>, (arg("n"), arg("T") = py::object(), arg("reg") = 0),
362 u8"Get quasi Fermi levels.\n\n"
363 u8"Compute quasi-Fermi levels in specified active region.\n\n"
364 u8"Args:\n"
365 u8" n (float): Carriers concentration to determine the levels for\n"
366 u8" (1/cm\\ :sup:`3`\\ ).\n"
367 u8" T (float or ``None``): Temperature to get the levels. If this argument is\n"
368 u8" ``None``, the estimates for temperature :py:attr:`T0`\n"
369 u8" are returned.\n\n"
370 u8" reg (int): Active region number.\n"
371 u8"Returns:\n"
372 u8" tuple: Two-element tuple with quasi-Fermi levels for electrons and holes.\n");
373 RW_PROPERTY(T0, getT0, setT0, "Reference temperature.\n\nIn this temperature levels estimates are computed.");
374 RW_PROPERTY(matrix_element, getMatrixElem, setMatrixElem,
375 u8"Momentum matrix element.\n\n"
376 u8"Value of the squared matrix element in gain computations. If it is not set it\n"
377 u8"is estimated automatically. (float [eV×m0])");
378 RW_PROPERTY(lifetime, getLifeTime, setLifeTime,
379 "Average carriers lifetime.\n\n"
380 "This parameter is used for gain spectrum broadening. (float [ps])");
381 RW_PROPERTY(strained, getStrained, setStrained,
382 u8"Boolean attribute indicating if the solver should consider strain in the active\n"
383 u8"region.\n\n"
384 u8"If set to ``True`` then there must a layer with the role *substrate* in\n"
385 u8"the geometry. The strain is computed by comparing the atomic lattice constants\n"
386 u8"of the substrate and the quantum wells.");
387 RECEIVER(inTemperature, "");
388 RECEIVER(inBandEdges, "");
389 RECEIVER(inCarriersConcentration, "");
390 RECEIVER(inFermiLevels, "");
391 PROVIDER(outGain, "");
392 PROVIDER(outEnergyLevels, "");
393 solver.def("spectrum", &__Class__::getGainSpectrum, py::arg("point"), py::with_custodian_and_ward_postcall<0, 1>(),
394 u8"Get gain spectrum at given point.\n\n"
395 u8"Args:\n"
396 u8" point (vec): Point to get gain at.\n"
397 u8" c0, c1 (float): Coordinates of the point to get gain at.\n\n"
398 u8"Returns:\n"
399 u8" :class:`FreeCarrier3D.Spectrum`: Spectrum object.\n");
400 solver.def("spectrum", FreeCarrierGetGainSpectrum3, (py::arg("c0"), "c1", "c2"),
401 py::with_custodian_and_ward_postcall<0, 1>());
402
403 py::scope scope = solver;
404 (void)scope; // don't warn about unused variable scope
405 py::class_<typename __Class__::GainSpectrumType, plask::shared_ptr<typename __Class__::GainSpectrumType>,
406 boost::noncopyable>(
407 "Spectrum", u8"Gain spectrum object. You can call it like a function to get gains for different wavelengths.",
408 py::no_init)
409 .def("__call__", &FreeCarrierGainSpectrum__call__<__Class__>, py::arg("lam"),
410 u8"Get gain at specified wavelength.\n\n"
411 u8"Args:\n"
412 u8" lam (float): Wavelength to get the gain at.\n");
413 }
414}