From a296384d2f4ee9b4370bc6d34904cb19d346ee65 Mon Sep 17 00:00:00 2001 From: Mark Mikofski Date: Tue, 16 Jan 2018 22:34:26 -0800 Subject: [PATCH 1/5] 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 --- pvmismatch/pvmismatch_lib/pvcell.py | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/pvmismatch/pvmismatch_lib/pvcell.py b/pvmismatch/pvmismatch_lib/pvcell.py index 48133f1..78e5e1e 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,29 @@ 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) + # 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 for k, v in iteritems(kwargs): setattr(self, k, v) + self._calc_now = False # recalculate @property def Vt(self): From c173cfa92df9f258b14aeaa92757430dd2fa466b Mon Sep 17 00:00:00 2001 From: Mark Mikofski Date: Tue, 16 Jan 2018 23:21:02 -0800 Subject: [PATCH 2/5] don't set pvcell.pvconst in pvstring * just raise an exception if they don't match for now * remove comments about "deepcopy" everywhere --- pvmismatch/pvmismatch_lib/pvmodule.py | 4 +--- pvmismatch/pvmismatch_lib/pvstring.py | 15 +++++++++------ pvmismatch/pvmismatch_lib/pvsystem.py | 2 +- 3 files changed, 11 insertions(+), 10 deletions(-) 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: From abe39e6b807ef1525451946a4644caf95545a8a8 Mon Sep 17 00:00:00 2001 From: Mark Mikofski Date: Wed, 17 Jan 2018 13:47:49 -0800 Subject: [PATCH 3/5] oops, recalculate means _calc_now = True, duh! --- pvmismatch/pvmismatch_lib/pvcell.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pvmismatch/pvmismatch_lib/pvcell.py b/pvmismatch/pvmismatch_lib/pvcell.py index 78e5e1e..4529c7e 100644 --- a/pvmismatch/pvmismatch_lib/pvcell.py +++ b/pvmismatch/pvmismatch_lib/pvcell.py @@ -105,7 +105,7 @@ def update(self, **kwargs): self._calc_now = False for k, v in iteritems(kwargs): setattr(self, k, v) - self._calc_now = False # recalculate + self._calc_now = True # recalculate @property def Vt(self): @@ -283,4 +283,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 From 6e72aa47686a55705a921de31eb23a1ece99385d Mon Sep 17 00:00:00 2001 From: Mark Mikofski Date: Wed, 17 Jan 2018 13:55:13 -0800 Subject: [PATCH 4/5] remove commented legacy code in __setattr__, add comment in update re checking for floats --- pvmismatch/pvmismatch_lib/pvcell.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pvmismatch/pvmismatch_lib/pvcell.py b/pvmismatch/pvmismatch_lib/pvcell.py index 4529c7e..4cf6427 100644 --- a/pvmismatch/pvmismatch_lib/pvcell.py +++ b/pvmismatch/pvmismatch_lib/pvcell.py @@ -92,9 +92,6 @@ def __setattr__(self, key, value): # 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): @@ -103,6 +100,8 @@ def update(self, **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 From 1b674e120cee24191fe618d37a0bfa48555d5e79 Mon Sep 17 00:00:00 2001 From: Mark Mikofski Date: Sat, 20 Jan 2018 00:42:00 -0800 Subject: [PATCH 5/5] 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/tests/test_pvcell.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) 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()