# Copyright 2017 University of Maryland.
#
# This file is part of Sesame. It is subject to the license terms in the file
# LICENSE.rst found in the top-level directory of this distribution.
from scipy.interpolate import InterpolatedUnivariateSpline as spline, interp2d
from .utils import Bresenham, get_indices
from .observables import *
from .defects import defectsF
try:
import matplotlib.pyplot as plt
mpl_enabled = True
try:
from mpl_toolkits import mplot3d
has3d = True
except:
has3d = False
except:
mpl_enabled = False
[docs]class Analyzer():
"""
Object that simplifies the extraction of physical data (densities, currents,
recombination) across the system.
Parameters
----------
sys: Builder
A discretized system.
data: dictionary
Dictionary containing 1D arrays of electron and hole quasi-Fermi levels
and the electrostatic potential across the system. Keys must be 'efn',
'efp', and/or 'v'.
"""
def __init__(self, sys, data):
self.sys = sys
self.v = data['v']
# check for efn
if 'efn' in data.keys():
self.efn = data['efn']
else:
self.efn = 0 * self.v
# check for efp
if 'efp' in data.keys():
self.efp = data['efp']
else:
self.efp = 0 * self.v
# sites of the system
self.sites = np.arange(sys.nx*sys.ny, dtype=int)
[docs] @staticmethod
def line(system, p1, p2):
"""
Compute the path and sites between two points.
Parameters
----------
system: Builder
The discretized system.
p1, p2: array-like (x, y)
Two points defining a line.
Returns
-------
s, sites: numpay arrays
Curvilinear abscissa and sites of the line.
Notes
-----
This method can be used with an instance of the Analyzer():
>>> az = sesame.Analyzer(sys, res)
>>> X, sites = az.line(sys, p1, p2)
or without it:
>>> X, sites = sesame.Analyzer.line(sys, p1, p2)
"""
p1 = (p1[0], p1[1], 0)
p2 = (p2[0], p2[1], 0)
s, x, _, _ = Bresenham(system, p1, p2)
return x, s
[docs] def band_diagram(self, location, fig=None):
"""
Compute the band diagram between two points defining a line. Display a
plot if fig is None.
Parameters
----------
location: array-like ((x1,y1), (x2,y2))
Tuple of two points defining a line over which to compute a band
diagram.
fig: Maplotlib figure
A plot is added to it if given. If not given, a new one is created and
displayed.
"""
p1, p2 = location
if self.sys.dimension == 1:
idx1, _ = get_indices(self.sys, (p1[0],0,0))
idx2, _ = get_indices(self.sys, (p2[0],0,0))
X = self.sys.xpts[idx1:idx2]
sites = np.arange(idx1, idx2, 1, dtype=int)
if self.sys.dimension == 2:
X, sites = self.line(self.sys, p1, p2)
show = False
if fig is None:
fig = plt.figure()
show = True
# add axis to figure
ax = fig.add_subplot(111)
vt = self.sys.scaling.energy
X = X * 1e4 # in um
l1, = ax.plot(X, vt*self.efn[sites], lw=2, color='#2e89cf', ls='--')
l2, = ax.plot(X, vt*self.efp[sites], lw=2, color='#cf392e', ls='--')
l3, = ax.plot(X, -vt * (self.v[sites] + self.sys.bl[sites]), lw=2, color='k', ls='-')
l4, = ax.plot(X, -vt * (self.v[sites] + self.sys.bl[sites] + self.sys.Eg[sites]), lw=2, color='k', ls='-')
fig.legend([l1, l2], [r'$\mathregular{E_{F_n}}$',\
r'$\mathregular{E_{F_p}}$'])
ax.set_xlabel(r'Position [$\mathregular{\mu m}$]')
ax.set_ylabel('Energy [eV]')
if show:
plt.show()
[docs] def electron_density(self, location=None):
"""
Compute the electron density across the system or on a line defined by two points.
Parameters
----------
location: array-like ((x1,y1), (x2,y2))
Tuple of two points defining a line over which to compute the electron
density.
Returns
-------
n: numpy array of floats
See also
--------
hole_density
"""
if location is None:
sites = self.sites
else:
p1, p2 = location
_, sites = self.line(self.sys, p1, p2)
n = get_n(self.sys, self.efn, self.v, sites)
return n
[docs] def hole_density(self, location=None):
"""
Compute the hole density across the system or on a line defined by two points.
Parameters
----------
location: array-like ((x1,y1), (x2,y2))
Tuple of two points defining a line over which to compute the hole
density.
Returns
-------
p: numpy array of floats
See also
--------
electron_density
"""
if location is None:
sites = self.sites
else:
p1, p2 = location
_, sites = self.line(self.sys, p1, p2)
p = get_p(self.sys, self.efp, self.v, sites)
return p
[docs] def bulk_srh_rr(self, location=None):
"""
Compute the bulk Shockley-Read-Hall recombination across the system or
on a line defined by two points.
Parameters
----------
location: array-like ((x1,y1), (x2,y2))
TUple of two points defining a line over which to compute the recombination.
Returns
-------
r: numpy array
An array with the values of recombination.
"""
if location is None:
sites = self.sites
else:
p1, p2 = location
_, sites = self.line(self.sys, p1, p2)
p = get_p(self.sys, self.efp, self.v, sites)
ni2 = self.sys.ni[sites]**2
n1 = self.sys.n1[sites]
p1 = self.sys.p1[sites]
tau_h = self.sys.tau_h[sites]
tau_e = self.sys.tau_e[sites]
n = get_n(self.sys, self.efn, self.v, sites)
p = get_p(self.sys, self.efp, self.v, sites)
r = (n*p - ni2)/(tau_h * (n+n1) + tau_e*(p+p1))
return r
[docs] def auger_rr(self, location=None):
"""
Compute the Auger recombination across the system or on a line defined
by two points.
Parameters
----------
location: array-like ((x1,y1), (x2,y2))
Tuple of two points defining a line over which to compute the recombination.
Returns
-------
r: numpy array
An array with the values of recombination.
"""
if location is None:
sites = self.sites
else:
p1, p2 = location
_, sites = self.line(self.sys, p1, p2)
n = get_n(self.sys, self.efn, self.v, sites)
p = get_p(self.sys, self.efp, self.v, sites)
ni2 = self.sys.ni[sites]**2
r = self.sys.Cn[sites] * n * (n*p - ni2) + self.sys.Cp[sites] * p * (n*p - ni2)
return r
[docs] def radiative_rr(self, location=None):
"""
Compute the radiative recombination across the system or on a line defined by two points.
Parameters
----------
location: array-like ((x1,y1), (x2,y2))
Tuple of two points defining a line over which to compute the recombination.
Returns
-------
r: numpy array
An array with the values of recombination.
"""
if location is None:
sites = self.sites
else:
p1, p2 = location
_, sites = self.line(self.sys, p1, p2)
n = get_n(self.sys, self.efn, self.v, sites)
p = get_p(self.sys, self.efp, self.v, sites)
ni2 = self.sys.ni[sites]**2
r = self.sys.B[sites] * (n*p - ni2)
return r
[docs] def defect_rr(self, defect):
"""
Compute the recombination for all sites of a defect (2D and 3D).
Parameters
----------
defect: named tuple
Container with the properties of a defect. The expected field names
of the named tuple are sites, location, dos, energy, sigma_e,
sigma_h, transition, perp_dl.
Returns
-------
r: numpy array of floats
An array with the values of recombination at each sites.
"""
# Create arrays to pass to defectsF
n = self.electron_density()
p = self.hole_density()
rho = np.zeros_like(n)
r = np.zeros_like(n)
# Update r (and rho but we don't use it)
defectsF(self.sys, [defect], n, p, rho, r=r)
r = np.multiply(r[defect.sites],defect.perp_dl)
return r
[docs] def total_rr(self):
"""
Compute the sum of all the recombination sources for all sites of the
system.
Returns
-------
r: numpy array of floats
An array with the values of the total recombination at each sites.
"""
srh = self.bulk_srh_rr()
radiative = self.radiative_rr()
auger = self.auger_rr()
defects = np.zeros_like(srh)
for defect in self.sys.defects_list:
sites = defect.sites
defects[sites] += self.defect_rr(defect)
return srh + radiative + auger + defects
[docs] def electron_current(self, component='x', location=None):
"""
Compute the electron current either by component (x or y) across the
entire system, or on a line defined by two points.
Parameters
----------
component: string
Current direction ``'x'`` or ``'y'``. By default returns all currents
in the x-direction.
location: array-like ((x1,y1), (x2,y2))
Tuple of two points defining a line over which to compute the electron
current.
Returns
-------
jn: numpy array of floats
"""
if location is not None:
p1, p2 = location
X, sites = self.line(self.sys, p1, p2)
jn = get_jn(self.sys, self.efn, self.v, sites[:-1], sites[1:], X[1:]-X[:-1])
else:
Nx, Ny = self.sys.nx, self.sys.ny
sites = self.sites.reshape(Ny, Nx)
if component == 'x':
sites = sites[:Ny, :Nx-1].flatten()
dx = np.tile(self.sys.dx, Ny)
jn = get_jn(self.sys, self.efn, self.v, sites, sites+1, dx)
if component == 'y':
sites = sites[:Ny-1, :Nx].flatten()
dy = np.repeat(self.sys.dy, Nx)
jn = get_jn(self.sys, self.efn, self.v, sites, sites+Nx, dy)
return jn
[docs] def hole_current(self, component='x', location=None):
"""
Compute the hole current either by component (x or y) across the entire
system, or on a line defined by two points.
Parameters
----------
component: string
Current direction ``'x'`` or ``'y'``. By default returns all currents
in the x-direction.
location: array-like ((x1,y1), (x2,y2))
Tuple of two points defining a line over which to compute the hole
current.
Returns
-------
jp: numpy array of floats
"""
if location is not None:
p1, p2 = location
X, sites = self.line(self.sys, p1, p2)
jp = get_jp(self.sys, self.efp, self.v, sites[:-1], sites[1:], X[1:]-X[:-1])
else:
Nx, Ny = self.sys.nx, self.sys.ny
sites = self.sites.reshape(Ny, Nx)
if component == 'x':
sites = sites[:Ny, :Nx-1].flatten()
dx = np.tile(self.sys.dx, Ny)
jp = get_jp(self.sys, self.efp, self.v, sites, sites+1, dx)
if component == 'y':
sites = sites[:Ny-1, :Nx].flatten()
dy = np.repeat(self.sys.dy, Nx)
jp = get_jp(self.sys, self.efp, self.v, sites, sites+Nx, dy)
return jp
[docs] def electron_current_map(self, cmap='gnuplot', scale=1e4):
"""
Compute a 2D map of the electron current.
Parameters
----------
cmap: Matplotlib color map
Color map used for the plot.
scale: float
Scale to apply to the axes of the plot.
"""
self.current_map(True, cmap, scale)
[docs] def hole_current_map(self, cmap='gnuplot', scale=1e4):
"""
Compute a 2D map of the hole current of a 2D system.
Parameters
----------
cmap: Matplotlib color map
Color map used for the plot.
scale: float
Scale to apply to the axes of the plot.
"""
self.current_map(False, cmap, scale)
def current_map(self, electron, cmap, scale, fig=None):
if not mpl_enabled:
raise RuntimeError("matplotlib was not found, but is required "
"for plotting.")
show = False
if fig is None:
fig = plt.figure()
show = True
# add axis to figure
ax = fig.add_subplot(111)
Lx = self.sys.xpts[-2] * scale
Ly = self.sys.ypts[-2] * scale
x, y = self.sys.xpts[:-1], self.sys.ypts[:-1]
nx, ny = len(x), len(y)
s = np.asarray([i + j*self.sys.nx for j in range(self.sys.ny-1)\
for i in range(self.sys.nx-1)])
dx = np.tile(self.sys.dx, ny)
dy = np.repeat(self.sys.dy[:-1], nx)
if electron:
Jx = get_jn(self.sys, self.efn, self.v, s, s+1, dx)
Jy = get_jn(self.sys, self.efn, self.v, s, s+(nx+1), dy)
title = r'$\mathregular{J_{n}\ [mA\cdot cm^{-2}]}$'
else:
Jx = get_jp(self.sys, self.efp, self.v, s, s+1, dx)
Jy = get_jp(self.sys, self.efp, self.v, s, s+(nx+1), dy)
title = r'$\mathregular{J_{p}\ [mA\cdot cm^{-2}]}$'
Jx = np.reshape(Jx, (ny, nx)) * self.sys.scaling.current * 1e3
Jy = np.reshape(Jy, (ny, nx)) * self.sys.scaling.current * 1e3
jx = interp2d(x*scale, y*scale, Jx, kind='linear')
jy = interp2d(x*scale, y*scale, Jy, kind='linear')
xx, yy = np.linspace(0, Lx, 100), np.linspace(0, Ly, 100)
jnx, jny = jx(xx, yy), jy(xx, yy)
norm = np.sqrt(jnx**2 + jny**2)
y, x = np.mgrid[0:Ly:100j, 0:Lx:100j]
p = ax.pcolor(x, y, norm, cmap=cmap, rasterized=True)
cbar = fig.colorbar(p, ax=ax)
ax.streamplot(x, y, jnx, jny, linewidth=1, color='#a9a9a9', density=2)
ax.set_xlim(xmax=Lx, xmin=0)
ax.set_ylim(ymin=0, ymax=Ly)
ax.set_xlabel(r'x [$\mathregular{\mu m}$]')
ax.set_ylabel(r'y [$\mathregular{\mu m}$]')
ax.set_title(title)
if show:
plt.show()
[docs] def map3D(self, data, cmap='gnuplot', scale=1e-6):
"""
Plot a 3D map of data across the entire system.
Parameters
----------
data: numpy array
One-dimensional array of data with size equal to the size of the system.
cmap: string
Name of the colormap used by Matplolib.
scale: float
Relevant scaling to apply to the axes.
"""
if not mpl_enabled:
raise RuntimeError("matplotlib was not found, but is required "
"for map3D()")
if not has3d:
raise RuntimeError("Installed matplotlib does not support 3d plotting")
xpts, ypts = self.sys.xpts / scale, self.sys.ypts / scale
nx, ny = len(xpts), len(ypts)
data_xy = data.reshape(ny, nx).T
X, Y = np.meshgrid(xpts, ypts)
fig = plt.figure(figsize=(8,6))
ax = fig.add_subplot(1,1,1, projection='3d')
Z = data_xy.T
ax.plot_surface(X, Y, Z, cmap=cmap)
ax.mouse_init(rotate_btn=1, zoom_btn=3)
plt.xlabel('x')
plt.ylabel('y')
plt.show()
[docs] def integrated_bulk_srh_recombination(self):
"""
Integrate the bulk Shockley-Read-Hall recombination over an entire
system.
Returns
-------
JR: float
The integrated bulk recombination.
Warnings
--------
Not implemented in 3D.
"""
return self.integrated_recombination('srh')
[docs] def integrated_auger_recombination(self):
"""
Integrate the Auger recombination over an entire system.
Returns
-------
JR: float
The integrated Auger recombination.
Warnings
--------
Not implemented in 3D.
"""
return self.integrated_recombination('auger')
[docs] def integrated_radiative_recombination(self):
"""
Integrate the radiative recombination over an entire system.
Returns
-------
JR: float
The integrated radiative recombination.
Warnings
--------
Not implemented in 3D.
"""
return self.integrated_recombination('radiative')
def integrated_recombination(self, mec):
# Compute recombination averywhere
if mec == 'srh':
r = self.bulk_srh_rr()
if mec == 'auger':
r = self.auger_rr()
if mec == 'radiative':
r = self.radiative_rr()
# Integrate along x for each y (if any
x = self.sys.xpts / self.sys.scaling.length
if self.sys.ny > 1:
y = self.sys.ypts / self.sys.scaling.length
u = []
for j in range(self.sys.ny):
# List of sites
s = [i + j*self.sys.nx for i in range(self.sys.nx)]
sp = spline(x, r[s])
u.append(sp.integral(x[0], x[-1]))
if self.sys.ny == 1:
JR = u[-1]
if self.sys.ny > 1:
sp = spline(y, u)
JR = sp.integral(y[0], y[-1])
return JR
[docs] def integrated_defect_recombination(self, defect):
"""
Integrate the recombination along a defect in 2D.
Returns
-------
JD: float
The recombination integrated along the line of the defect.
Warnings
--------
Not implemented in 3D.
"""
# Find the path along which to integrate
p1 = defect.location[0]
p2 = defect.location[1]
X, _ = self.line(self.sys, p1, p2)
# interpolate recombination and integrate
r = self.defect_rr(defect)
sp = spline(X, r)
JD = sp.integral(X[0], X[-1])
return JD
[docs] def full_current(self):
"""
Compute the steady state current in 1D and 2D.
Returns
-------
J: float
The integrated full steady state current.
"""
# System number of sites
nx, ny= self.sys.nx, self.sys.ny
# Define the sites between which computing the currents
sites_i = [nx//2 + j*nx for j in range(ny)]
sites_ip1 = [nx//2+1+j*nx for j in range(ny)]
# And the corresponding lattice dimensions
dl = self.sys.dx[self.sys.nx//2]
# Compute the electron and hole currents
jn = get_jn(self.sys, self.efn, self.v, sites_i, sites_ip1, dl)
jp = get_jp(self.sys, self.efp, self.v, sites_i, sites_ip1, dl)
if ny == 1:
j = jn[0] + jp[0]
if ny > 1:
# Interpolate the results and integrate over the y-direction
y = self.sys.ypts / self.sys.scaling.length
j = spline(y, jn+jp).integral(y[0], y[-1])
return j