How can I use the pvlib modules in pvmismatch?

456 views Asked by At

Is it possible to use the cec modules of pvlib modules in pvmismatch? I tried to make it using the values of the cec modules.

cell=pvcell.PVcell(
Rs=parametersw.R_s/parameters.N_s, 
Rsh= parametersw.R_sh_ref*parameters.N_s,
Isat1_T0=parametersw.I_o_ref,
Isat2_T0=0,
Isc0_T0= parametersw.I_L_ref,
alpha_Isc=parametersw.alpha_sc,
#aRBD=0,
#bRBD=0,
#nRBD=0,
    
Eg=1.121,
Tcell=273.15
              )

and putting this cells in a mismatch module, the values that I receive: voc isc vmp imp, are 3V less than expected.

3

There are 3 answers

0
adr On

The short answer is no, it is not possible. pvmismatch uses the two-diode model and you cannot simple put in the corresponding values from the one-diode model and omit the others.

Having said that, using the same (scaled) resistances might be ok, and you could try tweaking Isat1 and Isat2 to get closer to what you were expecting. This may or may not be good enough for your application.

0
Mark Mikofski On

If you are willing to do a little extra work, you can generate 2-diode model coefficients from an IV curve at STC using the gen_coeffs package in pvmismatch.contrib. See this discussion for an example using a Canadian Solar module from the Sandia array performance model library. All you need are the principle characteristics at STC, a little luck, and some elbow grease and you can do it.

For example, if using a CEC module:

"""
Making 2-diode modules from CEC in PVMismatch
"""

from matplotlib import pyplot as plt
import numpy as np
import pvlib
from pvmismatch import *
from pvmismatch.contrib import gen_coeffs

cecmod = pvlib.pvsystem.retrieve_sam('CECMod')
csmods = cecmod.columns[cecmod.T.index.str.startswith('Canadian')]
len(csmods)  # 409 modules in CEC library!
cs6x_300m = cecmod[csmods[264]]  # CS6X-300M Canadian Solar 300W mono-Si module

args = (
    cs6x_300m.I_sc_ref,
    cs6x_300m.V_oc_ref,
    cs6x_300m.I_mp_ref,
    cs6x_300m.V_mp_ref,
    cs6x_300m.N_s,  # number of series cells
    1,              # number of parallel sub-strings
    25.0)           # cell temperature

# try to solve using default coeffs
x, sol = gen_coeffs.gen_two_diode(*args)

# solver fails, so get the last guess before it quit
def last_guess(sol):
    isat1 = np.exp(sol.x[0])
    isat2 = np.exp(sol.x[1])
    rs = sol.x[2] ** 2.0
    rsh = sol.x[3] ** 2.0
    return isat1, isat2, rs, rsh
x = last_guess(sol)

# the series and shunt resistance solver guess are way off, so reset them
# with something reasonable for a 2-diode model
x, sol = gen_coeffs.gen_two_diode(*args, x0=(x[0], x[1], 0.005, 10.0))

# Hooray, it worked! Note that the 1-diode and 2-diode parametres are so
# different! Anyway, let's make a cell and a module to check the solution.
pvc = pvcell.PVcell(
    Isat1_T0=x[0],
    Isat2_T0=x[1],
    Rs=x[2],
    Rsh=x[3],
    Isc0_T0=cs6x_300m.I_sc_ref,
    alpha_Isc=cs6x_300m.alpha_sc)
np.isclose(pvc.Isc, cs6x_300m.I_sc_ref)  # ha-ha, this exact b/c we used it
# open circuit voltage within 1E-3: (45.01267251639085, 45.0)
np.isclose(pvc.Voc*cs6x_300m.N_s, cs6x_300m.V_oc_ref, rtol=1e-3, atol=1e-3)

# get index max power point
mpp = np.argmax(pvc.Pcell)

# max power voltage within 1E-3: (36.50580418834946, 36.5)
np.isclose(pvc.Vcell[mpp][0]*cs6x_300m.N_s, cs6x_300m.V_mp_ref, rtol=1e-3, atol=1e-3)
# max power current within 1E-3: (8.218687568902466, 8.22)
np.isclose(pvc.Icell[mpp][0], cs6x_300m.I_mp_ref, rtol=1e-3, atol=1e-3)

# use pvlib to get the full IV curve using CEC model
params1stc = pvlib.pvsystem.calcparams_cec(effective_irradiance=1000,
    temp_cell=25.0, alpha_sc=cs6x_300m.alpha_sc, a_ref=cs6x_300m.a_ref,
    I_L_ref=cs6x_300m.I_L_ref, I_o_ref=cs6x_300m.I_o_ref, R_sh_ref=cs6x_300m.R_sh_ref,
    R_s=cs6x_300m.R_s, Adjust=cs6x_300m.Adjust)
iv_params1stc = pvlib.pvsystem.singlediode(*params1stc, ivcurve_pnts=100, method='newton')

# use pvmm to get full IV curve using 2-diode model parameters
pvm = pvmodule.PVmodule(cell_pos=pvmodule.STD72, pvcells=[pvc]*72)

# make some comparison plots
pvm.plotMod()  # plot the pvmm module
plt.tight_layout()

# get axes for IV curve
f, ax = plt.gcf(), plt.gca()
ax0 = f.axes[0]
ax0.plot(iv_params1stc['v'], iv_params1stc['i'], '--')
ax0.plot(0, iv_params1stc['i_sc'], '|k')
ax0.plot(iv_params1stc['v_oc'], 0, '|k')
ax0.plot(iv_params1stc['v_mp'], iv_params1stc['i_mp'], '|k')
ax0.set_ylim([0, 10])

ax0.plot(0, pvm.Isc.mean(), '_k')
ax0.plot(pvm.Voc.sum(), 0, '|k')

mpp = np.argmax(pvm.Pmod)
ax0.plot(pvm.Vcell[mpp], mpp.Icell[mpp], '_k')
ax0.plot(pvm.Vmod[mpp], pvm.Imod[mpp], '_k')

iv_params1stc['p'] = iv_params1stc['v'] * iv_params1stc['i']
ax1.plot(iv_params1stc['v'], iv_params1stc['p'], '--')

ax1.plot(iv_params1stc['v_mp'], iv_params1stc['p_mp'], '|k')
ax1.plot(pvm.Vmod[mpp], pvm.Pmod[mpp], '_k')

you can get very close agreement: CS6X-300M

1
Yan Gang On

You can not define as following: Isat1_T0=parametersw.I_o_ref, Isat2_T0=0,

The reason is that the ideal factor for PVmismatch is n1=1 and n2=2. However, if you want to use one diode model, you must input ideal factor n.

If you still want to use one diode model for PVmismatch, the right method is that you need to rewrite "PVcell" module in this pachage. I try to write it, you can refer.

enter code here
"""
This module contains the :class:`~pvmismatch.pvmismatch_lib.pvcell.PVcell`
object which is used by modules, strings and systems.
"""

from __future__ import absolute_import
from future.utils import iteritems
from pvmismatch.pvmismatch_lib.pvconstants import PVconstants
import numpy as np
from matplotlib import pyplot as plt
from scipy.optimize import newton

# Defaults
RS = 0.0038  # [ohm] series resistance
RSH = 500  # [ohm] shunt resistance
Gamma = 0.97
ISAT1_T0 = 2.76E-11  # [A] diode one saturation current
ISC0_T0 = 9.87  # [A] reference short circuit current
TCELL = 298.15  # [K] cell temperature
ARBD = 2E-3  # reverse breakdown coefficient 1
BRBD = 0.  # reverse breakdown coefficient 2
VRBD_ = -20  # [V] reverse breakdown voltage
NRBD = 3.28  # reverse breakdown exponent
EG = 1.1  # [eV] band gap of cSi
ALPHA_ISC = 0.0005  # [1/K] short circuit current temperature coefficient
EPS = np.finfo(np.float64).eps

class PVcell(object):
    """
    Class for PV cells.

    :param Rs: series resistance [ohms]
    :param Rsh: shunt resistance [ohms]
    :param Isat1_T0: first saturation diode current at ref temp [A]
    :param Isat2_T0: second saturation diode current [A]
    :param Isc0_T0: short circuit current at ref temp [A]
    :param aRBD: reverse breakdown coefficient 1
    :param bRBD: reverse breakdown coefficient 2
    :param VRBD: reverse breakdown voltage [V]
    :param nRBD: reverse breakdown exponent
    :param Eg: band gap [eV]
    :param alpha_Isc: short circuit current temp coeff [1/K]
    :param Tcell: cell temperature [K]
    :param Ee: incident effective irradiance [suns]
    :param pvconst: configuration constants object
    :type pvconst: :class:`~pvmismatch.pvmismatch_lib.pvconstants.PVconstants`
    """

    _calc_now = False  #: if True ``calcCells()`` is called in ``__setattr__``

    def __init__(self, Rs=RS, Rsh=RSH, Isat1_T0=ISAT1_T0, Gamma=Gamma,
                 Isc0_T0=ISC0_T0, aRBD=ARBD, bRBD=BRBD, VRBD=VRBD_,
                 nRBD=NRBD, Eg=EG, alpha_Isc=ALPHA_ISC,
                 Tcell=TCELL, Ee=1., pvconst=PVconstants()):
        # user inputs
        self.Rs = Rs  #: [ohm] series resistance
        self.Rsh = Rsh  #: [ohm] shunt resistance
        self.Isat1_T0 = Isat1_T0  #: [A] diode one sat. current at T0
        self.Gamma = Gamma  #:  ideal factor
        self.Isc0_T0 = Isc0_T0  #: [A] short circuit current at T0
        self.aRBD = aRBD  #: reverse breakdown coefficient 1
        self.bRBD = bRBD  #: reverse breakdown coefficient 2
        self.VRBD = VRBD  #: [V] reverse breakdown voltage
        self.nRBD = nRBD  #: reverse breakdown exponent
        self.Eg = Eg  #: [eV] band gap of cSi
        self.alpha_Isc = alpha_Isc  #: [1/K] short circuit temp. coeff.
        self.Tcell = Tcell  #: [K] cell temperature
        self.Ee = Ee  #: [suns] incident effective irradiance on cell
        self.pvconst = pvconst  #: configuration constants
        self.Icell = None  #: cell currents on IV curve [A]
        self.Vcell = None  #: cell voltages on IV curve [V]
        self.Pcell = None  #: cell power on IV curve [W]
        self.VocSTC = self._VocSTC()  #: estimated Voc at STC [V]
        # set calculation flag
        self._calc_now = True  # overwrites the class attribute

    def __str__(self):
        fmt = '<PVcell(Ee=%g[suns], Tcell=%g[K], Isc=%g[A], Voc=%g[V])>'
        return fmt % (self.Ee, self.Tcell, self.Isc, self.Voc)

    def __repr__(self):
        return str(self)

    def __setattr__(self, key, value):
        # check for floats
        try:
            value = np.float64(value)
        except (TypeError, ValueError):
            pass  # fail silently if not float, eg: pvconst or _calc_now
        super(PVcell, self).__setattr__(key, value)
        # recalculate IV curve
        if self._calc_now:
            Icell, Vcell, Pcell = self.calcCell()
            self.__dict__.update(Icell=Icell, Vcell=Vcell, Pcell=Pcell)

    def update(self, **kwargs):
        """
        Update user-defined constants.
        """
        # turn off calculation flag until all attributes are updated
        self._calc_now = False
        # don't use __dict__.update() instead use setattr() to go through
        # custom __setattr__() so that numbers are cast to floats
        for k, v in iteritems(kwargs):
            setattr(self, k, v)
        self._calc_now = True  # recalculate

    @property
    def Vt(self):
        """
        Thermal voltage in volts.
        """
        return self.pvconst.k * self.Tcell / self.pvconst.q

    @property
    def Isc(self):
        return self.Ee * self.Isc0

    @property
    def Aph(self):
        """
        Photogenerated current coefficient, non-dimensional.
        """
        # Aph is undefined (0/0) if there is no irradiance
        if self.Isc == 0: return np.nan
        # short current (SC) conditions (Vcell = 0)
        Vdiode_sc = self.Isc * self.Rs  # diode voltage at SC
        Idiode1_sc = self.Isat1 * (np.exp(Vdiode_sc / (self.Gamma*self.Vt)) - 1.)
        Ishunt_sc = Vdiode_sc / self.Rsh  # diode voltage at SC
        # photogenerated current coefficient
        return 1. + (Idiode1_sc + Ishunt_sc) / self.Isc

    @property
    def Isat1(self):
        """
        Diode one saturation current at Tcell in amps.
        """
        _Tstar = self.Tcell ** 3. / self.pvconst.T0 ** 3.  # scaled temperature
        _inv_delta_T = 1. / self.pvconst.T0 - 1. / self.Tcell  # [1/K]
        _expTstar = np.exp(
            self.Eg * self.pvconst.q / (self.pvconst.k * self.Gamma) * _inv_delta_T
        )
        return self.Isat1_T0 * _Tstar * _expTstar  # [A] Isat1(Tcell)

    
    @property
    def Isc0(self):
        """
        Short circuit current at Tcell in amps.
        """
        _delta_T = self.Tcell - self.pvconst.T0  # [K] temperature difference
        return self.Isc0_T0 * (1. + self.alpha_Isc * _delta_T)  # [A] Isc0

    @property
    def Voc(self):
        """
        Estimate open circuit voltage of cells.
        Returns Voc : numpy.ndarray of float, estimated open circuit voltage
        """

        return self.Vt * self.Gamma * np.log((self.Aph * self.Isc)/self.Isat1_T0 + 1)

    def _VocSTC(self):
        """
        Estimate open circuit voltage of cells.
        Returns Voc : numpy.ndarray of float, estimated open circuit voltage
        """
        Vdiode_sc = self.Isc0_T0 * self.Rs  # diode voltage at SC
        Idiode1_sc = self.Isat1_T0 * (np.exp(Vdiode_sc / (self.Gamma*self.Vt)) - 1.)
        Ishunt_sc = Vdiode_sc / self.Rsh  # diode voltage at SC
        # photogenerated current coefficient
        Aph = 1. + (Idiode1_sc + Ishunt_sc) / self.Isc0_T0
        # estimated Voc at STC
        return self.Vt * self.Gamma * np.log((Aph * self.Isc0_T0)/self.Isat1_T0 + 1)

    @property
    def Igen(self):
        """
        Photovoltaic generated light current (AKA IL or Iph)
        Returns Igen : numpy.ndarray of float, PV generated light current [A]

        Photovoltaic generated light current is zero if irradiance is zero.
        """
        if self.Ee == 0: return 0
        return self.Aph * self.Isc

    def calcCell(self):
        """
        Calculate cell I-V curves.
        Returns (Icell, Vcell, Pcell) : tuple of numpy.ndarray of float
        """
        Vreverse = self.VRBD * self.pvconst.negpts
        Vff = self.Voc
        delta_Voc = self.VocSTC - self.Voc
        # to make sure that the max voltage is always in the 4th quadrant, add
        # a third set of points log spaced with decreasing density, from Voc to
        # Voc @ STC unless Voc *is* Voc @ STC, then use an arbitrary voltage at
        # 80% of Voc as an estimate of Vmp assuming a fill factor of 80% and
        # Isc close to Imp, or if Voc > Voc @ STC, then use Voc as the max
        if delta_Voc == 0:
            Vff = 0.8 * self.Voc
            delta_Voc = 0.2 * self.Voc
        elif delta_Voc < 0:
            Vff = self.VocSTC
            delta_Voc = -delta_Voc
        Vquad4 = Vff + delta_Voc * self.pvconst.Vmod_q4pts
        Vforward = Vff * self.pvconst.pts
        Vdiode = np.concatenate((Vreverse, Vforward, Vquad4), axis=0)
        Idiode1 = self.Isat1 * (np.exp(Vdiode / (self.Gamma*self.Vt)) - 1.)
        Ishunt = Vdiode / self.Rsh
        fRBD = 1. - Vdiode / self.VRBD
        # use epsilon = 2.2204460492503131e-16 to avoid "divide by zero"
        fRBD[fRBD == 0] = EPS
        Vdiode_norm = Vdiode / self.Rsh / self.Isc0_T0
        fRBD = self.Isc0_T0 * fRBD ** (-self.nRBD)
        IRBD = (self.aRBD * Vdiode_norm + self.bRBD * Vdiode_norm ** 2) * fRBD 
        Icell = self.Igen - Idiode1 - Ishunt - IRBD
        Vcell = Vdiode - Icell * self.Rs
        Pcell = Icell * Vcell
        return Icell, Vcell, Pcell

    # diode model
    #  *-->--*--->---*--Rs->-Icell--+
    #  ^     |       |              ^
    #  |     |       |              |
    # Igen  Idiode  Ishunt         Vcell
    #  |     |       |              |
    #  |     v       v              v
    #  *--<--*---<---*--<-----------=
    # http://en.wikipedia.org/wiki/Diode_modelling#Shockley_diode_model
    # http://en.wikipedia.org/wiki/Diode#Shockley_diode_equation
    # http://en.wikipedia.org/wiki/William_Shockley

    @staticmethod
    def f_Icell(Icell, Vcell, Igen, Rs, Vt, Isat1, Rsh):
        """
        Objective function for Icell.
        :param Icell: cell current [A]
        :param Vcell: cell voltage [V]
        :param Igen: photogenerated current at Tcell and Ee [A]
        :param Rs: series resistance [ohms]
        :param Vt: thermal voltage [V]
        :param Isat1: first diode saturation current at Tcell [A]
        :param Isat2: second diode saturation current [A]
        :param Rsh: shunt resistance [ohms]
        :return: residual = (Icell - Icell0) [A]
        """
        # arbitrary current condition
        Vdiode = Vcell + Icell * Rs  # diode voltage
        Idiode1 = Isat1 * (np.exp(Vdiode / (Gamma * Vt)) - 1.)  # diode current
        Ishunt = Vdiode / Rsh  # shunt current
        return Igen - Idiode1 - Ishunt - Icell

    def calcIcell(self, Vcell):
        """
        Calculate Icell as a function of Vcell.
        :param Vcell: cell voltage [V]
        :return: Icell
        """
        args = (np.float64(Vcell), self.Igen, self.Rs, self.Vt,
                self.Rsh)
        return newton(self.f_Icell, x0=self.Isc, args=args)

    @staticmethod
    def f_Vcell(Vcell, Icell, Igen, Rs, Vt, Isat1, Rsh):
        return PVcell.f_Icell(Icell, Vcell, Igen, Rs, Vt, Isat1, Rsh)

    def calcVcell(self, Icell):
        """
        Calculate Vcell as a function of Icell.
        :param Icell: cell current [A]
        :return: Vcell
        """
        args = (np.float64(Icell), self.Igen, self.Rs, self.Vt,
                self.Isat1, self.Rsh)
        return newton(self.f_Vcell, x0=self.Voc, args=args)