Skip to content

Latest commit

 

History

History
executable file
·
345 lines (235 loc) · 20 KB

DriftDiffusionUtilities.rst

File metadata and controls

executable file
·
345 lines (235 loc) · 20 KB

Drift Diffusion Utilities

This module is the interface between Python and Fortran. It takes a structure created with the tools in Device Structure <DeviceStructure> and dumps all that information into the Fortran variables. Then, it runs the selected calculation. This process is completely transparent for the user who only needs to use the higher level methods in the solar cell solver <solving_solar_cells>.

At the end of this page there is a detailed description of the format of all the functions within this module, but here we focus in the more practical aspect, including also examples of usage.

The following example shows the result of calculating a solar cell under short circuit conditions, comparing the resulting band structure. Note that neither equilibrium_pdd nor short_circuit_pdd are called directly, but they are accessed internally by solar_cell_solver.

import matplotlib.pyplot as plt

from solcore import material
from solcore.structure import Layer, Junction
from solcore.solar_cell import SolarCell
from solcore.solar_cell_solver import solar_cell_solver

T = 298

# First, we create the materials, overriding any default property we want, such as the doping or the absorption coefficient
window = material('AlGaAs')(T=T, Na=1e24, Al=0.8)
p_GaAs = material('GaAs')(T=T, Na=1e24)
i_GaAs = material('GaAs')(T=T)
n_GaAs = material('GaAs')(T=T, Nd=1e23)
bsf = material('AlGaAs')(T=T, Nd=1e24, Al=0.4)

# We put everything together in a Junction.
MyJunction = Junction([ Layer(width=30e-9, material=window, role="Window"),
                        Layer(width=400e-9, material=p_GaAs, role="Emitter"),
                        Layer(width=400e-9, material=i_GaAs, role="Intrinsic"),
                        Layer(width=2000e-9, material=n_GaAs, role="Base"),
                        Layer(width=200e-9, material=bsf, role="BSF")],
                        sn=1e3, sp=1e3, T=T, kind='PDD')

my_solar_cell = SolarCell([MyJunction], T=T, R_series=0, substrate=n_GaAs)

# We solve the short circuit problem
solar_cell_solver(my_solar_cell, 'short_circuit')

# We can plot the electron and hole densities in short circuit...
zz = my_solar_cell[0].short_circuit_data.Bandstructure['x'] * 1e9
n = my_solar_cell[0].short_circuit_data.Bandstructure['n']
p = my_solar_cell[0].short_circuit_data.Bandstructure['p']
plt.semilogy(zz, n, 'b', label='e @ short circuit')
plt.semilogy(zz, p, 'r', label='h @ short circuit')

# ... and equilibrium
zz = my_solar_cell[0].equilibrium_data.Bandstructure['x'] * 1e9
n = my_solar_cell[0].equilibrium_data.Bandstructure['n']
p = my_solar_cell[0].equilibrium_data.Bandstructure['p']
plt.semilogy(zz, n, 'b--', label='e @ equilibrium')
plt.semilogy(zz, p, 'r--', label='h @ equilibrium')

plt.xlabel('Position (nm)')
plt.ylabel('Carrier density (m$^{-3}$)')
plt.legend()
plt.show()

The result of the above calculation is this:

image

In the following example, we use the same solar cell described above and calculate the dark IV curve, plotting the different contributions to the current.

import matplotlib.pyplot as plt

from solcore import material
from solcore.structure import Layer, Junction
from solcore.solar_cell import SolarCell
from solcore.solar_cell_solver import solar_cell_solver

T = 298

substrate = material('GaAs')(T=T)

# First, we create the materials, overriding any default property we want, such as the doping or the absorption coefficient
window = material('AlGaAs')(T=T, Na=1e24, Al=0.8)
p_GaAs = material('GaAs')(T=T, Na=1e24)
i_GaAs = material('GaAs')(T=T)
n_GaAs = material('GaAs')(T=T, Nd=1e23)
bsf = material('AlGaAs')(T=T, Nd=1e24, Al=0.4)

# We put everything together in a Junction. We include the surface recombination velocities,
# sn and sp, although they are not necessary in this case.
MyJunction = Junction([Layer(width=30e-9, material=window, role="Window"),
                       Layer(width=400e-9, material=p_GaAs, role="Emitter"),
                       Layer(width=400e-9, material=i_GaAs, role="Intrinsic"),
                       Layer(width=2000e-9, material=n_GaAs, role="Base"),
                       Layer(width=200e-9, material=bsf, role="BSF")],
                      sn=1e3, sp=1e3, T=T, kind='PDD')

my_solar_cell = SolarCell([MyJunction], T=T, R_series=0, substrate=substrate)

# We calculate the IV curve under illumination, using all the default options
solar_cell_solver(my_solar_cell, 'iv')

plt.semilogy(my_solar_cell[0].voltage, abs(my_solar_cell[0].current), 'k', linewidth=4, label='Total')
plt.semilogy(my_solar_cell[0].voltage, abs(my_solar_cell[0].recombination_currents['Jrad']), 'r', label='Jrad')
plt.semilogy(my_solar_cell[0].voltage, abs(my_solar_cell[0].recombination_currents['Jsrh']), 'b', label='Jsrh')
plt.semilogy(my_solar_cell[0].voltage, abs(my_solar_cell[0].recombination_currents['Jsur']), 'g', label='Jsur')

plt.legend()
plt.xlim(-0.5, 1.3)
plt.ylim(1e-10, 1e5)
plt.xlabel('Bias (V)')
plt.ylabel('Current (A/m$^2}$)')

plt.show()

The result of the above calculation is this:

image

In order to get the IV curve under illumination, we simply indicate it with the user_options keyword in the solar_solar_cell solver function, also asking for the parameters under illumination (Voc, Isc, etc.).

solar_cell_solver(my_solar_cell, 'iv', user_options={'light_iv' : True, 'mpp' : True})

plt.plot(my_solar_cell[0].voltage, -my_solar_cell[0].current, 'r', linewidth=2, label='Total')

plt.xlim(-0.5, 1.3)
plt.ylim(0, 350)
plt.xlabel('Bias (V)')
plt.ylabel('Current (A/m$^2}$)')

plt.text(0, 200, 'Voc = {:4.1f} V\n'
                 'Isc = {:4.1f} A/m${^2}$\n'
                 'FF = {:4.1f} %\n'
                 'Pmpp = {:4.1f} W/m${^2}$'.format(my_solar_cell.iv['Voc'], my_solar_cell.iv['Isc'],
                                           my_solar_cell.iv['FF'] * 100, my_solar_cell.iv['Pmpp']))

While the power at maximum power point seems very high (>300 W/m 2 ) let's keep in mind that the default modelling options use the Beer-Lambert law optics method which does not take into account front surface reflection. If that is included (for example using the TMM optics method) Isc will be much lower and so will the power.

image

import matplotlib.pyplot as plt

from solcore import material
from solcore.structure import Layer, Junction
from solcore.solar_cell import SolarCell
from solcore.solar_cell_solver import solar_cell_solver

T = 298

substrate = material('GaAs')(T=T)

# First, we create the materials, overriding any default property we want, such as the doping or the absorption coefficient
window = material('AlGaAs')(T=T, Na=1e24, Al=0.8)
p_GaAs = material('GaAs')(T=T, Na=1e24)
i_GaAs = material('GaAs')(T=T)
n_GaAs = material('GaAs')(T=T, Nd=1e23)
bsf = material('AlGaAs')(T=T, Nd=1e24, Al=0.4)

# We put everything together in a Junction. We include the surface recombination velocities,
# sn and sp, although they are not necessary in this case.
MyJunction = Junction([Layer(width=30e-9, material=window, role="Window"),
                       Layer(width=400e-9, material=p_GaAs, role="Emitter"),
                       Layer(width=400e-9, material=i_GaAs, role="Intrinsic"),
                       Layer(width=2000e-9, material=n_GaAs, role="Base"),
                       Layer(width=200e-9, material=bsf, role="BSF")],
                      sn=1e6, sp=1e6, T=T, kind='PDD')

my_solar_cell = SolarCell([MyJunction], T=T, R_series=0, substrate=substrate)

# We calculate the EQE of the cell, using the TMM optics method.
solar_cell_solver(my_solar_cell, 'qe', user_options={'optics_method': 'TMM'})

wl = my_solar_cell[0].qe_data.wavelengths * 1e9

plt.plot(wl, 1 - my_solar_cell.reflected, 'b')
plt.fill_between(wl, 1 - my_solar_cell.reflected, 1, facecolor='blue', alpha=0.6, label='Reflected')
plt.fill_between(wl, 1 - my_solar_cell.reflected, my_solar_cell.absorbed, facecolor='yellow', alpha=0.5,
                 label='Transmitted')

# EQE + fraction lost due to recombination in the front surface
plt.plot(wl, my_solar_cell[0].qe_data.EQE + my_solar_cell[0].qe_data.EQEsurf, 'r')
plt.fill_between(wl, my_solar_cell[0].qe_data.EQE + my_solar_cell[0].qe_data.EQEsurf, my_solar_cell[0].qe_data.EQE,
                 facecolor='red', alpha=0.5, label='Front surface recombination')

plt.plot(wl, my_solar_cell[0].qe_data.EQE, 'k', linewidth=4)
plt.fill_between(wl, my_solar_cell[0].qe_data.EQE, 0, facecolor='green', alpha=0.5, label='EQE')

plt.legend()
plt.xlim(300, 950)
plt.ylim(0, 1)
plt.xlabel('Wavelength (nm)')
plt.ylabel('EQE (%/100)')

plt.show()

The result of running the code above is the next figure, where the EQE of the cell is plotted together with the main sources of losses in this particular case: reflection, recombination in the front surface and sub-bandgap light transmission.

image

In principle, EQE + all the internal sources of losses (SRH, surface recombination, etc.) should be equal to the total absorbed light and, in any case, smaller than 1. Sometimes this does not happen and the result is > 1. The reason is the vertical discretization process of the solar cell which is not accurate in situations where there is a fast variation of the absorption and/or the electron generation. This happens at short wavelengths, when absorption is very fast in the first few nanometers, and at longer wavelengths in the presence of oscillations due to interference. There are three tricks to tackle this issue:

  • Increase the number of mesh points in the PDD solver by adjusting the options dealing with the mesh creation (see below and solver-options). This will result in a global increase in mesh points and therefore will make the PDD solver slower.
  • Divide thick layers into thinner ones. This will increase the number of mesh points locally in that region, and therefore good if the one causing problems is identified. This solution is helpful for the case of fast oscillations.
  • Increase the number of points used by the optical solver. By default, this is one point per angstrom, and therefore very dense already, but might help, specially at short wavelengths. This is controlled by the 'position' option (see solver-options).

Setting different aspects of the solver

Set the parameters that control the meshing of the structure. Changing this values might improve convergence in some difficult cases. The absolute maximum number of meshpoints at any time is 6000. The keywords and default values are:

  • meshpoints = -400 : Defines the type of meshing that must be done.
    • meshpoints > 0: The mesh is homogeneous with that many mesh points.
    • meshpoints = 0: The mesh is inhomogeneous with master nodes at the interfaces between layers and denser mesh around them. This density and distribution of the points are controlled by the variables coarse, fine and ultrafine.
    • meshpoints < 0: The exact value does not matter. The mesh is inhomogeneous with master nodes at the interfaces between layers and denser mesh around them. Initially, their density and distribution is controlled by the variables coarse, fine and ultrafine but then this is dynamically modified to increase or reduce their density in a 'smooth' way, wherever the mesh points are needed more. This dynamic remeshing is performed at different stages of the above solvers. Master nodes are not modified.
  • growth_rate = 0.7 : Defines how 'fast' mesh points are increased by the dynamic meshing routine. It should between 0 and 1. Small values produce denser meshes and larger values produce coarser ones.
  • coarse = 20e-9
  • fine = 1e-9
  • ultrafine = 0.2e-9

    Values are in nanometers. Define the structure of the inhomogeneous mesh and the initial dynamic mesh. After defining the master nodes (two nodes separated 0.1 nm per interface), the space between them is divided in equal size elemens smaller or equal than coarse. Then, the coarse elements adjacent to the master nodes are divided in equal size elemens smaller or equal than fine. Finally, the fine elements adjacent to the master nodes are divided in equal size elemens smaller or equal than ultrafine. This structure is static if meshpoints = 0 and evolves if meshpoints < 0 to a smoother configuration.

image

Output dictionary

All virtual experiments described above produce a dictionary as output. A variable can be accessed as: :

output[<primary_key>][<secondary_key]

The total list of primary (columns) and secondary (rows) keys are:

Properties Bandstructure IV QE Optics
x x V wavelengths wavelengths
Xi n J EQE R
Eg p Jrad EQEsrh T
Nd ni Jsrh EQErad -
Na Rho Jaug EQEaug -
Nc Efe Jsur EQEsurf -
Nv Efh Jsc [a] EQEsurb -
- potential Voc [a] - -
- Ec Jmpp [a] - -
- Ev Vmpp [a] - -
- GR FF [a] - -
- G - - -
- Rrad - - -
- Rsrh - - -
- Raug - - -

All functions description

solcore.poisson_drift_diffusion.DriftDiffusionUtilities

References

a

Only available in light IV if IV_info=True