%matplotlib inline
import matplotlib.pylab as plt
from oedes import *
init_notebook()
import scipy.interpolate
plt.rcParams['axes.formatter.useoffset']=False

"""
Explanation: Modeling of photovoltaic devices
This example demonstrates modeling of photovoltaic devices. Figures of merit, such as fill factor, are extracted from results of the simulation. For illustration, a reference is partially reproduced.
Simulations like those shown above can show possible optimization oportunities for devices. They also can yield ultimate performance of idealized devices.
End of explanation
"""


def absorption(x):
    return 1.5e28

"""
Explanation: Model
We start with popular assumption that light is absorbed uniformly inside the device. The assumption can be justified for thin devices, and for white illumination.
End of explanation
"""


def base_model(L=50e-9,selective=False,**kwargs):
    model = models.BaseModel()
    mesh = fvm.mesh1d(L)
    models.std.bulk_heterojunction(model, mesh, absorption=absorption,selective_contacts=selective,**kwargs)
    model.setUp()
    return model

"""
Explanation: The base model consists in Poisson's equation coupled to the drift-diffusion equations for electrons and holes. Constant mobilities are assumed. Additionally, contacts can be defined as selective (blocking electrons and holes at "invalid" electrode"), or non-selective with local thermal equilibrium is assumed at any electrode for all charge carriers.
End of explanation
"""


def model_Langevin(**kwargs):
    return base_model(langevin_recombination=True,**kwargs)
def model_const(**kwargs):
    return base_model(const_recombination=True,**kwargs)
def model_SRH(**kwargs):
    return base_model(const_recombination=True,srh_recombination=True,**kwargs)

"""
Explanation: The basic model created by function above contains no recombination term, and must be suplemented with it. Complete models are created by functions below, with the following options for the recombination model:
- direct : $R=\beta \left(n p-n_i p_i \right)$
- Langevin: $R=\frac{\mu_n+\mu_p}{\varepsilon} \left(n p-n_i p_i \right)$
- Shockley-Reed-Hall recombination, in parallel with direct recombination
Absorption is assumed to create free electrons and holes directly.
End of explanation
"""


def make_params(barrier=0.3,bandgap=1.2,srh_tau=1e-8):
    params=models.std.bulk_heterojunction_params(barrier=barrier,bandgap=bandgap,Nc=1e27,Nv=1e27)
    srh_trate=1e-8
    params.update({
        'beta':7.23e-17,
        'electron.srh.trate':srh_trate,
        'hole.srh.trate':srh_trate,
        'srh.N0':1./(srh_tau*srh_trate),
        'srh.energy':-bandgap*0.5
    })
    return params

"""
Explanation: Below is a procedure generating default simulation parameters. They are parametrized by the bandgap, by the (symmetric) barrier at the electrodes, and by SRH lifetime. Note that not all parameters are used at the same time, for example SRH parameters are not used by non-SRH models.
End of explanation
"""


def performance(v,J):
    iv=scipy.interpolate.InterpolatedUnivariateSpline(v,J)
    Isc=iv(0.)
    Voc,=iv.roots()
    v=np.linspace(0,Voc)
    Pmax=np.amax(-v*iv(v))
    Ff=-Pmax/(Voc*Isc)
    return dict(Ff=Ff,Voc=Voc,Isc=Isc,Pmax=Pmax)

"""
Explanation: Calculations
The function below takes I_V curve, which should include points V=0 and J=0, and calculates the open circuit voltage, the power at maximum power point, and the fill factor.
End of explanation
"""


mu_values = np.logspace(-10,-2,19)
def mu_sweep(params):
    for mu in mu_values:
        p=dict(params)
        p['electron.mu']=mu
        p['hole.mu']=mu
        yield mu,p
v_sweep = sweep('electrode0.voltage',np.linspace(0.,0.8,40))

"""
Explanation: In the reference, the mobilities of electrons and holes are varied but kept equal. The following shows how such sweep can be implemented.
End of explanation
"""


def Voc_Ff(model,params):
    c=context(model)
    result=[]
    def onemu(mu, cmu):
        for _ in cmu.sweep(cmu.params, v_sweep):
            pass
        v,J=cmu.teval(v_sweep.parameter_name,'J')
        p = performance(v,J)
        return (mu, p['Voc'], p['Ff'])
    result = np.asarray([onemu(*_) for _ in c.sweep(params, mu_sweep)])
    testing.store(result)
    fig,(ax_voc,ax_ff)=plt.subplots(nrows=2,sharex=True)
    ax_voc.plot(result[:,0],result[:,1])
    ax_ff.plot(result[:,0],result[:,2])
    ax_ff.set_xlabel(r'$\mu \mathrm{[m^2 V^{-1} s^{-1}]}$')
    ax_ff.set_xscale('log')
    ax_ff.set_ylabel('FF')
    ax_voc.set_ylabel('$V_{oc}$');    
    return result

params=make_params()

"""
Explanation: Because different models are considered below, a common function is defined here to run the simulation and to plot the result. The function takes model as an argument.
End of explanation
"""


Voc_Ff(model_const(selective=False),params);

"""
Explanation: Results
Direct recombination, non-selective contacts
As seen below, in the case of direct recombination, selective contacts are useful for improving fill factor and open-circuit voltage regardless of mobilities.
End of explanation
"""


Voc_Ff(model_const(selective=True),params);

"""
Explanation: Direct recombination, selective contacts
End of explanation
"""


Voc_Ff(model_Langevin(selective=False),params);

"""
Explanation: Langevin recombination, non-selective contants
If Langevin recombination is assumed, open circuit voltage drops regardless of contact selectivity.
End of explanation
"""


Voc_Ff(model_Langevin(selective=True),params);

"""
Explanation: Langevin recombination, selective contants
End of explanation
"""


Voc_Ff(model_SRH(selective=False),params);

"""
Explanation: SRH recombination, non-selective contacts
The case of SRH recombination resembles the case of direct recombination in its dependence on mobility. This is not surprising, as in both cases the mobility does not enter the recombination term $R$.
End of explanation
"""


Voc_Ff(model_SRH(selective=True),params);

"""
Explanation: SRH recombination, selective contacts
End of explanation
"""


