From c408b1ecf351151e95398a67b03ad2f64e58d200 Mon Sep 17 00:00:00 2001 From: Mark Mikofski Date: Mon, 5 Feb 2018 11:50:54 -0800 Subject: [PATCH] ENH: BUG: add _calc_now flag to determine when cell is recalculated (GH59) (#61) * fixes #59 * add class attr _calc_now * override _calc_now with instance attribte at the end of constructor * check if _calc_now is True to decide to call self.calcCell() instead of checking for pvconst * use self.__dict__.update() instead of using super(PVcell, self).__setattr__(key, value) * in update, remove TODO, b/c even tho __dict__.update() would bypass __setattr__() then would have to check for floats, and still set _calc_now = True to trigger recalc, so instead, just set _calc_now = False first to turn calculations off, until all attr are set, then recalc * don't set pvcell.pvconst in pvstring * just raise an exception if they don't match for now * remove comments about "deepcopy" everywhere * oops, recalculate means _calc_now = True, duh! * remove commented legacy code in __setattr__, add comment in update re checking for floats * add test for new _calc_now flag * if _calc_now == False, then calcCell() is not called in __setattr__ * if _calc_now == True, then calcCell() is called in __setattr__ --- pvmismatch/pvmismatch_lib/pvcell.py | 26 ++++++++++++++++---------- pvmismatch/pvmismatch_lib/pvmodule.py | 4 +--- pvmismatch/pvmismatch_lib/pvstring.py | 15 +++++++++------ pvmismatch/pvmismatch_lib/pvsystem.py | 2 +- pvmismatch/tests/test_pvcell.py | 17 ++++++++++++++++- 5 files changed, 43 insertions(+), 21 deletions(-) diff --git a/pvmismatch/pvmismatch_lib/pvcell.py b/pvmismatch/pvmismatch_lib/pvcell.py index 48133f1..4cf6427 100644 --- a/pvmismatch/pvmismatch_lib/pvcell.py +++ b/pvmismatch/pvmismatch_lib/pvcell.py @@ -47,6 +47,9 @@ class PVcell(object): :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, Isat2=ISAT2, Isc0_T0=ISC0_T0, aRBD=ARBD, bRBD=BRBD, VRBD=VRBD_, nRBD=NRBD, Eg=EG, alpha_Isc=ALPHA_ISC, @@ -69,6 +72,8 @@ def __init__(self, Rs=RS, Rsh=RSH, Isat1_T0=ISAT1_T0, Isat2=ISAT2, 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] + # set calculation flag + self._calc_now = True # overwrites the class attribute def __str__(self): fmt = '' @@ -78,27 +83,28 @@ def __repr__(self): return str(self) def __setattr__(self, key, value): + # check for floats try: value = np.float64(value) except (TypeError, ValueError): - pass + pass # fail silently if not float, eg: pvconst or _calc_now super(PVcell, self).__setattr__(key, value) - # after all attributes have been initialized, recalculate IV curve - # every time __setattr__() is called - if hasattr(self, 'pvconst'): + # recalculate IV curve + if self._calc_now: Icell, Vcell, Pcell = self.calcCell() - super(PVcell, self).__setattr__('Icell', Icell) - super(PVcell, self).__setattr__('Vcell', Vcell) - super(PVcell, self).__setattr__('Pcell', Pcell) + self.__dict__.update(Icell=Icell, Vcell=Vcell, Pcell=Pcell) def update(self, **kwargs): """ Update user-defined constants. """ - # TODO: use __dict__.update(), check for floats and update IV curve - # self.__dict__.update(kwargs) + # 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): @@ -276,4 +282,4 @@ def plot(self): plt.xlim(0, self.Voc) plt.ylim(0, (self.Isc + 1) * self.Voc) plt.grid() - return cell_plot \ No newline at end of file + return cell_plot diff --git a/pvmismatch/pvmismatch_lib/pvmodule.py b/pvmismatch/pvmismatch_lib/pvmodule.py index 7e96942..1964c04 100644 --- a/pvmismatch/pvmismatch_lib/pvmodule.py +++ b/pvmismatch/pvmismatch_lib/pvmodule.py @@ -174,10 +174,8 @@ def __init__(self, cell_pos=STD96, pvcells=None, pvconst=PVconstants(), self.Vbypass = Vbypass #: [V] trigger voltage of bypass diode self.cellArea = cellArea #: [cm^2] cell area if pvcells is None: - # faster to use copy instead of making each object in a for-loop - # use copy instead of deepcopy to keep same pvconst for all objects - # PVcell.calcCell() creates new np.ndarray if attributes change pvcells = PVcell(pvconst=self.pvconst) + # expand pvcells to list if isinstance(pvcells, PVcell): pvcells = [pvcells] * self.numberCells if len(pvcells) != self.numberCells: diff --git a/pvmismatch/pvmismatch_lib/pvstring.py b/pvmismatch/pvmismatch_lib/pvstring.py index 2d0a16e..c8855aa 100644 --- a/pvmismatch/pvmismatch_lib/pvstring.py +++ b/pvmismatch/pvmismatch_lib/pvstring.py @@ -30,18 +30,21 @@ def __init__(self, numberMods=NUMBERMODS, pvmods=None, self.pvconst = pvconst self.numberMods = numberMods if pvmods is None: - # use deepcopy instead of making each object in for-loop, 2x faster pvmods = PVmodule(pvconst=self.pvconst) + # expand pvmods to list if isinstance(pvmods, PVmodule): pvmods = [pvmods] * self.numberMods - # reset pvconsts in all pvcells and pvmodules - for p in pvmods: - for c in p.pvcells: - c.pvconst = self.pvconst - p.pvconst = self.pvconst if len(pvmods) != self.numberMods: # TODO: use pvmismatch exceptions raise Exception("Number of modules doesn't match.") + # check that pvconst if given, is the same for all cells + # don't assign pvcell.pvconst here since it triggers a recalc + for p in pvmods: + for c in p.pvcells: + if c.pvconst is not self.pvconst: + raise Exception('PVconstant must be the same for all cells') + if p.pvconst is not self.pvconst: + raise Exception('PVconstant must be the same for all cells') self.pvmods = pvmods self.Istring, self.Vstring, self.Pstring = self.calcString() diff --git a/pvmismatch/pvmismatch_lib/pvsystem.py b/pvmismatch/pvmismatch_lib/pvsystem.py index a993395..06d017d 100644 --- a/pvmismatch/pvmismatch_lib/pvsystem.py +++ b/pvmismatch/pvmismatch_lib/pvsystem.py @@ -35,7 +35,7 @@ def __init__(self, pvconst=PVconstants(), numberStrs=NUMBERSTRS, if pvstrs is None: pvstrs = PVstring(numberMods=self.numberMods, pvmods=pvmods, pvconst=self.pvconst) - # use deep copy instead of making each object in a for-loop + # expand pvstrs to list if isinstance(pvstrs, PVstring): pvstrs = [pvstrs] * self.numberStrs if len(pvstrs) != self.numberStrs: diff --git a/pvmismatch/tests/test_pvcell.py b/pvmismatch/tests/test_pvcell.py index ce395a6..652b50f 100644 --- a/pvmismatch/tests/test_pvcell.py +++ b/pvmismatch/tests/test_pvcell.py @@ -69,6 +69,21 @@ def test_pvcell_calc_rbd(): pvc2 = PVcell(bRBD=-0.056) ok_(isinstance(pvc2, PVcell)) - + +def test_pvcell_calc_now_flag(): + pvc = PVcell() + itest, vtest, ptest = pvc.Icell, pvc.Vcell, pvc.Pcell + pvc._calc_now = False + pvc.Rs = 0.001 + assert np.allclose(itest, pvc.Icell) + assert np.allclose(vtest, pvc.Vcell) + assert np.allclose(ptest, pvc.Pcell) + icell, vcell, pcell = pvc.calcCell() + pvc._calc_now = True + assert np.allclose(icell, pvc.Icell) + assert np.allclose(vcell, pvc.Vcell) + assert np.allclose(pcell, pvc.Pcell) + + if __name__ == "__main__": test_calc_series()