Single junction using the Poisson Drift Diffusion solver

This example shows how to use the Poisson Drift Diffusion (PDD) solver to calculate the bandstructure, IV and QE curves of a single junction solar cell, a default GaAs solar cell built-in into Solcore.

We start by importing all the required functions and modules:

In [ ]:
import matplotlib.pyplot as plt
import numpy as np

from solcore.solar_cell import SolarCell, default_GaAs
from solcore.structure import Layer, Junction
from solcore import si
from solcore import material
from solcore.light_source import LightSource
from solcore.solar_cell_solver import solar_cell_solver

Next, we define some variables we will need, including temperature, voltages and the illumination spectrum we will use in the calculation.

In [ ]:
T = 298
substrate = material('GaAs')(T=T)
V = np.linspace(0, 1.2, 60)
wl = np.linspace(350, 1000, 301) * 1e-9
light_source = LightSource(source_type='standard', version='AM1.5g', x=wl, output_units='photon_flux_per_m',
                           concentration=1)

Finally, we create the solar cell itself. Since we only have one junction, we just put that one inside a list as the first input. To include more junctions (or extra layers) include them in the list. The order is important: it goes from the front surface of the cell to the substrate.

In [ ]:
my_solar_cell = SolarCell([default_GaAs(T)], T=T, R_series=0, substrate=substrate)

With the solar cell created, we can start to run different simulations on it starting, for example, by finding the bandstructure under equilibrium and plotting the doping and the electron and hole densities.

We have use the index 0 to access the results of the only junction we have in the structure. If there are more, this will be accesible using index 1, 2, etc. In this case, the offset is not needed, because we only have one junction, but when there are more, it is necessary in order to shift the position to the correct location.

In [ ]:
solar_cell_solver(my_solar_cell, 'equilibrium')

# We can plot the electron and hole densities in equilibrium and at short circuit, both calculated automatically
# before calculating the IV curve
plt.figure(1)
zz = my_solar_cell[0].equilibrium_data.Properties['x'] + my_solar_cell[0].offset
n = my_solar_cell[0].equilibrium_data.Properties['Nd']
p = my_solar_cell[0].equilibrium_data.Properties['Na']
plt.semilogy(zz, n, 'b')
plt.semilogy(zz, p, 'r')

zz = my_solar_cell[0].equilibrium_data.Bandstructure['x'] + my_solar_cell[0].offset
n = my_solar_cell[0].equilibrium_data.Bandstructure['n']
p = my_solar_cell[0].equilibrium_data.Bandstructure['p']
plt.semilogy(zz, n, 'b--')
plt.semilogy(zz, p, 'r--')

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

plt.show()

Next we calculate the IV curve of the same cell. We include several options here to configure the solver, such as the voltages and the spectrum we defined above, and also indicating that we want to solve the optics using a simple Beer-Lambert law (BL). We also ask that the maximum power point should be calculated ('mpp': True).

In [ ]:
solar_cell_solver(
    my_solar_cell,
    "iv",
    user_options={
        "voltages": V,
        "internal_voltages": V,
        "light_iv": True,
        "wavelength": wl,
        "optics_method": "BL",
        "mpp": True,
        "light_source": light_source,
    },
)

plt.figure(2)
plt.plot(V, -my_solar_cell[0].iv(V), "r", label="GaAs")

plt.legend()
plt.xlim(0, 1.2)
plt.ylim(0, 350)
plt.xlabel("Bias (V)")
plt.ylabel("Current (A/m$^2}$)")

plt.text(
    0.2,
    100,
    "Voc = {:4.2f} V\n"
    "Isc = {:4.1f} A/m2\n"
    "FF = {:4.1f} %\n"
    "Pmpp = {:4.1f} W/m2".format(
        my_solar_cell.iv["Voc"],
        my_solar_cell.iv["Isc"],
        my_solar_cell.iv["FF"] * 100,
        my_solar_cell.iv["Pmpp"],
    ),
)

plt.show()

Finally, we calculate the QE curve. Again, we provide some inputs, like the solar spectrum to use. In this case, just for a change, we use the TMM optics solver, which gives a more realistic preformance by taking into account front surface recombination. Since we are changing the method use to calculate absorption, we need to force its recalculation.

We do some fancy plotting to get a nicer representation of where the losses for my solar cell are coming from.

In [ ]:
solar_cell_solver(
    my_solar_cell,
    "qe",
    user_options={
        "wavelength": wl,
        "optics_method": "TMM",
        "light_source": light_source,
        "recalculate_absorption": True,
        "position": 1e-9,
    },
)

wl = wl * 1e9

plt.figure(3)
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.EQE + my_solar_cell[0].qe.EQEsurf, "r")
plt.fill_between(
    wl,
    my_solar_cell[0].qe.EQE + my_solar_cell[0].qe.EQEsurf,
    my_solar_cell[0].qe.EQE,
    facecolor="red",
    alpha=0.5,
    label="Front surface recombination",
)

plt.plot(wl, my_solar_cell[0].qe.EQE + my_solar_cell[0].qe.EQEsurb, "orange")
plt.fill_between(
    wl,
    my_solar_cell[0].qe.EQE + my_solar_cell[0].qe.EQEsurb,
    my_solar_cell[0].qe.EQE,
    facecolor="orange",
    alpha=0.5,
    label="Back surface recombination",
)

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

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

plt.show()
In [ ]: