Three-layer PSC device with graded interfaces & Ohmic contacts (1D).
Simulating a three layer PSC device Ti02| MAPI | spiro-OMeTAD without mobile ions. The simulations are performed out of equilibrium, stationary and with two junctions between perovskite layer and transport layers, to which we refer as graded interfaces. Hence, a graded flux discretization with space dependent band-edge energies and density of states is tested here. The difference here is that we adjusted the order of indexing the quasi Fermi potentials.
The parameters are based on the default parameter set of Ionmonger (with minor adjustments), such that we can likewise compare with the software Driftfusion, see https://github.com/barnesgroupICL/Driftfusion/blob/Methods-IonMonger-Comparison/Inputfiles/IonMongerdefault_bulk.csv
module Ex105_PSC_gradedFlux
using ChargeTransport
using ExtendableGrids
using PyPlot
# function for grading the physical parameters
function grading_parameter!(physicalParameter, coord, regionTransportLayers, regionJunctions, h, heightLayers, lengthLayers, values)
for ireg in regionTransportLayers
xcoord = lengthLayers[ireg]:lengthLayers[ireg+1]
physicalParameter[xcoord] .= values[ireg]
end
for ireg in regionJunctions
xcoord = lengthLayers[ireg]:lengthLayers[ireg+1]
left = lengthLayers[ireg]-3
junction = h[ireg]
right = lengthLayers[ireg+2]-3
gradient = ( physicalParameter[right] - physicalParameter[left] ) / junction
for index in xcoord
physicalParameter[index] = physicalParameter[left] + (coord[index] - heightLayers[ireg-1]) * gradient
end
end
return physicalParameter
end
you can also use other Plotters, if you add them to the example file
function main(;n = 2, Plotter = PyPlot, plotting = false, verbose = false, test = false, unknown_storage=:sparse)
if plotting
Plotter.close("all")
end
################################################################################
if test == false
println("Set up grid and regions")
end
################################################################################
# region numbers
regionDonor = 1 # n doped region
regionJunction1 = 2
regionIntrinsic = 3 # intrinsic region
regionJunction2 = 4
regionAcceptor = 5 # p doped region
regions = [regionDonor, regionJunction1, regionIntrinsic, regionJunction2, regionAcceptor]
regionTransportLayers = [regionDonor, regionIntrinsic, regionAcceptor]
regionJunctions = [regionJunction1, regionJunction2]
numberOfRegions = length(regions)
# boundary region numbers
bregionDonor = 1
bregionAcceptor = 2
bregionDJ1 = 3
bregionJ1I = 4
bregionIJ2 = 5
bregionJ2A = 6
# grid
h_ndoping = 9.90e-6 * cm
h_junction1 = 1.0e-7 * cm
h_intrinsic = 4.00e-5 * cm
h_junction2 = 1.0e-7 * cm
h_pdoping = 1.99e-5 * cm
h_total = h_ndoping + h_junction1 + h_intrinsic + h_junction2 + h_pdoping
h = [h_ndoping, h_junction1, h_intrinsic, h_junction2, h_pdoping]
heightLayers = [h_ndoping,
h_ndoping + h_junction1,
h_ndoping + h_junction1 + h_intrinsic,
h_ndoping + h_junction1 + h_intrinsic + h_junction2,
h_ndoping + h_junction1 + h_intrinsic + h_junction2 + h_pdoping]
refinementfactor = 2^(n-1)
coord_ndoping = collect(range(0.0, stop = h_ndoping, length = 4 * refinementfactor))
length_n = length(coord_ndoping)
coord_junction1 = collect(range(h_ndoping,
stop = h_ndoping + h_junction1,
length = 3 * refinementfactor))
coord_intrinsic = collect(range(h_ndoping + h_junction1,
stop = (h_ndoping + h_junction1 + h_intrinsic),
length = 10 * refinementfactor))
coord_junction2 = collect(range(h_ndoping + h_junction1 + h_intrinsic,
stop = (h_ndoping + h_junction1 + h_intrinsic + h_junction2),
length = 3 * refinementfactor))
coord_pdoping = collect(range((h_ndoping + h_junction1 + h_intrinsic + h_junction2),
stop = (h_ndoping + h_junction1 + h_intrinsic + h_junction2 + h_pdoping),
length = 4 * refinementfactor))
coord = glue(coord_ndoping, coord_junction1)
length_j1 = length(coord)
coord = glue(coord, coord_intrinsic)
length_i = length(coord)
coord = glue(coord, coord_junction2)
length_j2 = length(coord)
coord = glue(coord, coord_pdoping)
grid = simplexgrid(coord)
numberOfNodes = length(coord)
lengthLayers = [1, length_n, length_j1, length_i, length_j2, numberOfNodes]
# set different regions in grid
cellmask!(grid, [0.0 * μm], [heightLayers[1]], regionDonor) # n-doped region = 1
cellmask!(grid, [heightLayers[1]], [heightLayers[2]], regionJunction1) # first junction = 2
cellmask!(grid, [heightLayers[2]], [heightLayers[3]], regionIntrinsic) # intrinsic region = 3
cellmask!(grid, [heightLayers[3]], [heightLayers[4]], regionJunction2) # sec. junction = 4
cellmask!(grid, [heightLayers[4]], [heightLayers[5]], regionAcceptor) # p-doped region = 5
inner interfaces
bfacemask!(grid, [heightLayers[1]], [heightLayers[1]], bregionDJ1)
bfacemask!(grid, [heightLayers[2]], [heightLayers[2]], bregionJ1I)
bfacemask!(grid, [heightLayers[3]], [heightLayers[3]], bregionIJ2)
bfacemask!(grid, [heightLayers[4]], [heightLayers[4]], bregionJ2A)
if plotting
gridplot(grid, Plotter = Plotter, legend=:lt)
Plotter.title("Grid")
end
if test == false
println("*** done\n")
end
################################################################################
if test == false
println("Define physical parameters and model")
end
################################################################################
# set indices of the quasi Fermi potentials
iphin = 2 # electron quasi Fermi potential
iphip = 1 # hole quasi Fermi potential
numberOfCarriers = 2
# temperature
T = 300.0 * K
# band edge energies
Ec_d = -4.0 * eV
Ev_d = -6.0 * eV
Ec_i = -3.7 * eV
Ev_i = -5.4 * eV
Ec_a = -3.1 * eV
Ev_a = -5.1 * eV
# these parameters at the junctions for E_\alpha and N_\alpha will be overwritten.
Ec_j1 = Ec_d; Ec_j2 = Ec_i
Ev_j1 = Ev_d; Ev_j2 = Ev_i
EC = [Ec_d, Ec_j1, Ec_i, Ec_j2, Ec_a]
EV = [Ev_d, Ev_j1, Ev_i, Ev_j2, Ev_a]
# effective densities of state
Nc_d = 5.0e19 / (cm^3)
Nv_d = 5.0e19 / (cm^3)
Nc_i = 8.1e18 / (cm^3)
Nv_i = 5.8e18 / (cm^3)
Nc_a = 5.0e19 / (cm^3)
Nv_a = 5.0e19 / (cm^3)
Nc_j1 = Nc_d; Nc_j2 = Nc_i
Nv_j1 = Nv_d; Nv_j2 = Nv_i
NC = [Nc_d, Nc_j1, Nc_i, Nc_j2, Nc_a]
NV = [Nv_d, Nv_j1, Nv_i, Nv_j2, Nv_a]
# mobilities
μn_d = 3.89 * (cm^2) / (V * s)
μp_d = 3.89 * (cm^2) / (V * s)
μn_i = 6.62e1 * (cm^2) / (V * s)
μp_i = 6.62e1 * (cm^2) / (V * s)
μn_a = 3.89e-1 * (cm^2) / (V * s)
μp_a = 3.89e-1 * (cm^2) / (V * s)
μn_j1 = μn_d; μn_j2 = μn_i
μp_j1 = μp_d; μp_j2 = μp_i
μn = [μn_d, μn_j1, μn_i, μn_j2, μn_a]
μp = [μp_d, μp_j1, μp_i, μp_j2, μp_a]
# relative dielectric permittivity
ε_d = 10.0 * 1.0
ε_i = 24.1 * 1.0
ε_a = 3.0 * 1.0
ε_j1 = ε_d; ε_j2 = ε_a
ε = [ε_d, ε_j1, ε_i, ε_j2, ε_a]
# radiative recombination
r0_d = 0.0e+0 * cm^3 / s
r0_i = 1.0e-12 * cm^3 / s
r0_a = 0.0e+0 * cm^3 / s
r0_j1 = r0_i; r0_j2 = r0_i
r0 = [r0_d, r0_j1, r0_i, r0_j2, r0_a]
# life times and trap densities
τn_d = 1.0e100 * s
τp_d = 1.0e100 * s
τn_i = 3.0e-10 * s
τp_i = 3.0e-8 * s
τn_a = τn_d
τp_a = τp_d
τn_j1 = τn_i; τn_j2 = τn_a
τp_j1 = τp_i; τp_j2 = τp_a
τn = [τn_d, τn_j1, τn_i, τn_j2, τn_a]
τp = [τp_d, τp_j1, τp_i, τp_j2, τp_a]
# SRH trap energies (needed for calculation of trap_density! (SRH))
Eid = -5.0 * eV Eii = -4.55 * eV Ei_a = -4.1 * eV
Eij1 = Eid; Eij2 = Eii
EI = [Eid, Eij1, Eii, Eij2, Ei_a]
# reference densities
nτ_d = 7.94e8 /m^3
pτ_d = 7.94e8 /m^3
nτ_i = 4.26e10 /m^3
pτ_i = 3.05e10 /m^3
nτ_a = nτ_d
pτ_a = pτ_d
nτ_j1 = nτ_i; nτ_j2 = nτ_a
pτ_j1 = pτ_i; pτ_j2 = pτ_a
nτ = [nτ_d, nτ_j1, nτ_i, nτ_j2, nτ_a]
pτ = [pτ_d, pτ_j1, pτ_i, pτ_j2, pτ_a]
# Auger recombination
Auger = 0.0
# doping (doping values are from Driftfusion)
Nd = 1.03e18 / (cm^3)
Na = 1.03e18 / (cm^3)
Ni_acceptor = 8.32e7 / (cm^3)
# contact voltage
voltageAcceptor = 1.2 * V
if test == false
println("*** done\n")
end
################################################################################
if test == false
println("Define System and fill in information about model")
end
################################################################################
# Initialize Data instance and fill in predefined data
data = Data(grid, numberOfCarriers)
# Possible choices: Stationary, Transient
data.modelType = Stationary
# Possible choices: Boltzmann, FermiDiracOneHalfBednarczyk, FermiDiracOneHalfTeSCA,
# FermiDiracMinusOne, Blakemore
data.F .= Boltzmann
data.bulkRecombination = set_bulk_recombination(;iphin = iphin, iphip = iphip,
bulk_recomb_Auger = false,
bulk_recomb_radiative = true,
bulk_recomb_SRH = true)
# Possible choices: OhmicContact, SchottkyContact (outer boundary) and InterfaceNone,
# InterfaceRecombination (inner boundary).
data.boundaryType[bregionDonor] = OhmicContact
data.boundaryType[bregionAcceptor] = OhmicContact
# Choose flux discretization scheme: ScharfetterGummel, ScharfetterGummelGraded,
# ExcessChemicalPotential, ExcessChemicalPotentialGraded, DiffusionEnhanced, GeneralizedSG
data.fluxApproximation .= ScharfetterGummelGraded
################################################################################
if test == false
println("Define Params and fill in physical parameters")
end
################################################################################
# for region dependent parameters
params = Params(grid, numberOfCarriers)
# for space dependent parameters
paramsnodal = ParamsNodal(grid, numberOfCarriers)
params.temperature = T
params.UT = (kB * params.temperature) / q
params.chargeNumbers[iphin] = -1
params.chargeNumbers[iphip] = 1
# nodal band-edge energies
paramsnodal.bandEdgeEnergy[iphin, :] = grading_parameter!(paramsnodal.bandEdgeEnergy[iphin, :],
coord, regionTransportLayers, regionJunctions, h,
heightLayers, lengthLayers, EC)
paramsnodal.bandEdgeEnergy[iphip, :] = grading_parameter!(paramsnodal.bandEdgeEnergy[iphip, :],
coord, regionTransportLayers, regionJunctions, h,
heightLayers, lengthLayers, EV)
# nodal effective density of states
paramsnodal.densityOfStates[iphin, :] = grading_parameter!(paramsnodal.densityOfStates[iphin, :],
coord, regionTransportLayers, regionJunctions, h,
heightLayers, lengthLayers, NC)
paramsnodal.densityOfStates[iphip, :] = grading_parameter!(paramsnodal.densityOfStates[iphip, :],
coord, regionTransportLayers, regionJunctions, h,
heightLayers, lengthLayers, NV)
for ireg in 1:numberOfRegions ## region dependent data
# mobility
params.mobility[iphin, ireg] = μn[ireg]
params.mobility[iphip, ireg] = μp[ireg]
params.dielectricConstant[ireg] = ε[ireg] * ε0
# recombination parameters
params.recombinationRadiative[ireg] = r0[ireg]
params.recombinationSRHLifetime[iphin, ireg] = τn[ireg]
params.recombinationSRHLifetime[iphip, ireg] = τp[ireg]
params.recombinationSRHTrapDensity[iphin, ireg] = nτ[ireg]
params.recombinationSRHTrapDensity[iphip, ireg] = pτ[ireg]
params.recombinationAuger[iphin, ireg] = Auger
params.recombinationAuger[iphip, ireg] = Auger
end
# doping
params.doping[iphin, regionDonor] = Nd
params.doping[iphip, regionIntrinsic] = Ni_acceptor
params.doping[iphip, regionAcceptor] = Na
data.params = params
data.paramsnodal = paramsnodal
ctsys = System(grid, data, unknown_storage=unknown_storage)
# print data
if test == false
show_params(ctsys)
end
if test == false
println("*** done\n")
end
################################################################################
if test == false
println("Define control parameters for Solver")
end
################################################################################
control = SolverControl()
control.verbose = verbose
control.maxiters = 200
control.abstol = 1.0e-13
control.reltol = 1.0e-13
control.tol_round = 1.0e-13
control.damp_initial = 0.5
control.damp_growth = 1.61 # >= 1
control.max_round = 5
if test == false
println("*** done\n")
end
################################################################################
if test == false
println("Compute solution in thermodynamic equilibrium")
end
################################################################################
solution = equilibrium_solve!(ctsys, control = control)
inival = solution
if plotting
label_solution, label_density, label_energy = set_plotting_labels(data)
Plotter.figure()
plot_energies(Plotter, ctsys, solution, "Equilibrium", label_energy)
Plotter.figure()
plot_densities(Plotter, ctsys, solution, "Equilibrium", label_density)
Plotter.figure()
plot_solution(Plotter, ctsys, solution, "Equilibrium", label_solution)
end
if test == false
println("*** done\n")
end
################################################################################
if test == false
println("Bias loop")
end
################################################################################
maxBias = voltageAcceptor # bias goes until the given voltage at acceptor boundary
biasValues = range(0, stop = maxBias, length = 13)
for Δu in biasValues
if test == false
println("Bias value: Δu = $(Δu) V")
end
# set non equilibrium boundary conditions
set_contact!(ctsys, bregionAcceptor, Δu = Δu)
solution = solve(ctsys, inival = inival, control = control)
inival = solution
end # bias loop
# plotting
if plotting
Plotter.figure()
plot_energies(Plotter, ctsys, solution, "Applied voltage Δu = $maxBias", label_energy)
Plotter.figure()
plot_densities(Plotter, ctsys, solution, "Applied voltage Δu = $maxBias", label_density)
Plotter.figure()
plot_solution(Plotter, ctsys, solution, "Applied voltage Δu = $maxBias", label_solution)
end
if test == false
println("*** done\n")
end
testval = solution[data.index_psi, 20]
return testval
end # main
function test()
testval = -3.982748467515117
main(test = true, unknown_storage=:dense) ≈ testval && main(test = true, unknown_storage=:sparse) ≈ testval
end
if test == false
println("This message should show when this module is successfully recompiled.")
end
end # module
This page was generated using Literate.jl.