Skip to content

Commit 42a21c0

Browse files
mikofskichetan201
authored andcommitted
BUG: pvconst is not a singleton, inconsistent, doesn't update (GH38) (#62)
* 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__ * closes #38 consistent pvconst behavior * use duck typing to check pvstrs in PVsystem() for list, object or none * set pvconst from pvstrs if given * set numbstrs from pvstrs if given * set numbermods form pvstrs.pvmods if given * check that pvconst and pvmods are consistent * add tests * apply same changes in pvsystem from last commit to pvstr * change default pvconst arg to None * use duck typing to check if pvmods is a list, an object, or None * relax requirement that all strings have same number of modules * change pvsys.numberMods to a list of number of modules in each string * add test to check pvstring * check that all pvconst are the same for all modules * add docstring for members * also test that pvsys.numberMods is now a list * each item in list is number of modules in corresponding string * use ducktyping to determine if pvcells is list or obj or None * add missing blank lines and wrap long lines per pep8 * add pvcell object to pvcells docstring arg type * add tests in test_module to check that pvconst is the same for module and all cells * add test for update method * replace npts attr with a property * add new private _npts attribute, return for npts in getter * set npts, pts, negpts, Imod_pts, and Imod_negpts in setter * add test to show that changing npts changes pts, negpts, Imod_pts, and Imod_negpts * add pvsystem update method * make system calculation DRY, use everywhere calcSystem and calcMPP_IscVocFFeff are called back to back to set Isys, Vsys, Psys, etc. * also makes it explicity to recalc the system after a change, like change pvconst.npts
1 parent c408b1e commit 42a21c0

File tree

9 files changed

+234
-61
lines changed

9 files changed

+234
-61
lines changed

pvmismatch/pvmismatch_lib/pvconstants.py

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -74,23 +74,39 @@ class PVconstants(object):
7474
T0 = 298.15 #: [K] reference temperature
7575

7676
def __init__(self, npts=NPTS):
77-
# set number of points in IV curve(s)
78-
self.npts = npts #: number of points in IV curves
79-
# point spacing from 0 to 1, used for Vcell, Vmod, Vsys and Istring
80-
# decrease point spacing as voltage approaches Voc by using logspace
81-
pts = (11. - np.logspace(np.log10(11.), 0., self.npts)) / 10.
82-
pts[0] = 0. # first point must be exactly zero
83-
self.pts = pts.reshape((self.npts, 1))
77+
self._npts = None
78+
self.pts = None
8479
"""array of points with decreasing spacing from 0 to 1"""
85-
negpts = (11. - np.logspace(np.log10(11. - 1. / float(self.npts)),
86-
0., self.npts)) / 10.
87-
negpts = negpts.reshape((self.npts, 1))
88-
self.Imod_negpts = 1 + 1. / float(self.npts) / 10. - negpts
80+
self.Imod_negpts = None
8981
"""array of points with decreasing spacing from 1 to just less than but
9082
not including zero"""
91-
self.negpts = np.flipud(negpts) # reverse the order
83+
self.negpts = None
9284
"""array of points with increasing spacing from 1 to just less than but
9385
not including zero"""
86+
self.Imod_pts = None
87+
"""array of points with increasing spacing from 0 to 1"""
88+
# call property setter
89+
self.npts = npts #: number of points in IV curves
90+
91+
@property
92+
def npts(self):
93+
"""number of points in IV curves"""
94+
return self._npts
95+
96+
@npts.setter
97+
def npts(self, npts):
98+
# set number of points in IV curve(s)
99+
self._npts = npts # number of points in IV curves
100+
# point spacing from 0 to 1, used for Vcell, Vmod, Vsys and Istring
101+
# decrease point spacing as voltage approaches Voc by using logspace
102+
pts = (11. - np.logspace(np.log10(11.), 0., self._npts)) / 10.
103+
pts[0] = 0. # first point must be exactly zero
104+
self.pts = pts.reshape((self._npts, 1))
105+
negpts = (11. - np.logspace(np.log10(11. - 1. / float(self._npts)),
106+
0., self._npts)) / 10.
107+
negpts = negpts.reshape((self._npts, 1))
108+
self.Imod_negpts = 1 + 1. / float(self._npts) / 10. - negpts
109+
self.negpts = np.flipud(negpts) # reverse the order
94110
# shift and concatenate pvconst.negpts and pvconst.pts
95111
# so that tight spacing is around MPP and RBD
96112
self.Imod_pts = 1 - np.flipud(self.pts)

pvmismatch/pvmismatch_lib/pvmodule.py

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ def standard_cellpos_pat(nrows, ncols_per_substr):
5757
cellpos.append(newsubstr)
5858
return cellpos
5959

60+
6061
# standard cell positions presets
6162
STD24 = standard_cellpos_pat(1, [1] * 24)
6263
STD72 = standard_cellpos_pat(12, [2, 2, 2])
@@ -122,6 +123,7 @@ def crosstied_cellpos_pat(nrows_per_substrs, ncols, partial=False):
122123
Substrings have 27, 28 and 27 rows of cells per diode
123124
"""
124125

126+
125127
def combine_parallel_circuits(IVprev_cols, pvconst):
126128
"""
127129
Combine crosstied circuits in a substring
@@ -151,33 +153,50 @@ def combine_parallel_circuits(IVprev_cols, pvconst):
151153
Irows, Vrows, Isc_rows.mean(), Imax_rows.max()
152154
)
153155

156+
154157
class PVmodule(object):
155158
"""
156159
A Class for PV modules.
157160
158161
:param cell_pos: cell position pattern
159162
:type cell_pos: dict
160163
:param pvcells: list of :class:`~pvmismatch.pvmismatch_lib.pvcell.PVcell`
161-
:type pvcells: list
164+
:type pvcells: list, :class:`~pvmismatch.pvmismatch_lib.pvcell.PVcell`
162165
:param pvconst: An object with common parameters and constants.
163166
:type pvconst: :class:`~pvmismatch.pvmismatch_lib.pvconstants.PVconstants`
164167
:param Vbypass: bypass diode trigger voltage [V]
165168
:param cellArea: cell area [cm^2]
166169
"""
167-
def __init__(self, cell_pos=STD96, pvcells=None, pvconst=PVconstants(),
170+
def __init__(self, cell_pos=STD96, pvcells=None, pvconst=None,
168171
Vbypass=VBYPASS, cellArea=CELLAREA):
169172
# TODO: check cell position pattern
170173
self.cell_pos = cell_pos #: cell position pattern dictionary
171174
self.numberCells = sum([len(c) for s in self.cell_pos for c in s])
172175
"""number of cells in the module"""
176+
# is pvcells a list?
177+
try:
178+
pvc0 = pvcells[0]
179+
except TypeError:
180+
# is pvcells an object?
181+
try:
182+
pvconst = pvcells.pvconst
183+
except AttributeError:
184+
# try to use the pvconst arg or create one if none
185+
if not pvconst:
186+
pvconst = PVconstants()
187+
# create pvcell
188+
pvcells = PVcell(pvconst=pvconst)
189+
# expand pvcells to list
190+
pvcells = [pvcells] * self.numberCells
191+
else:
192+
pvconst = pvc0.pvconst
193+
for p in pvcells:
194+
if p.pvconst is not pvconst:
195+
raise Exception('PVconstant must be the same for all cells')
173196
self.pvconst = pvconst #: configuration constants
174197
self.Vbypass = Vbypass #: [V] trigger voltage of bypass diode
175198
self.cellArea = cellArea #: [cm^2] cell area
176-
if pvcells is None:
177-
pvcells = PVcell(pvconst=self.pvconst)
178-
# expand pvcells to list
179-
if isinstance(pvcells, PVcell):
180-
pvcells = [pvcells] * self.numberCells
199+
# check cell position pattern matches list of cells
181200
if len(pvcells) != self.numberCells:
182201
# TODO: use pvexception
183202
raise Exception(
@@ -291,8 +310,10 @@ def setSuns(self, Ee, cells=None):
291310
raise Exception("Input irradiance value (Ee) for each cell!")
292311
self.Imod, self.Vmod, self.Pmod, self.Isubstr, self.Vsubstr = self.calcMod()
293312

294-
# TODO setTemps is a nearly identical copy of setSuns. The DRY principle says that we should not be copying code.
295-
# TODO Replace both setSuns() and setTemps() with a single method for updating cell parameters that works for all params
313+
# TODO setTemps is a nearly identical copy of setSuns. The DRY principle
314+
# says that we should not be copying code.
315+
# TODO Replace both setSuns() and setTemps() with a single method for
316+
# updating cell parameters that works for all params
296317

297318
def setTemps(self, Tc, cells=None):
298319
"""

pvmismatch/pvmismatch_lib/pvstring.py

Lines changed: 26 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -26,26 +26,32 @@ class PVstring(object):
2626
:param pvconst: a configuration constants object
2727
"""
2828
def __init__(self, numberMods=NUMBERMODS, pvmods=None,
29-
pvconst=PVconstants()):
30-
self.pvconst = pvconst
31-
self.numberMods = numberMods
32-
if pvmods is None:
33-
pvmods = PVmodule(pvconst=self.pvconst)
34-
# expand pvmods to list
35-
if isinstance(pvmods, PVmodule):
36-
pvmods = [pvmods] * self.numberMods
37-
if len(pvmods) != self.numberMods:
38-
# TODO: use pvmismatch exceptions
39-
raise Exception("Number of modules doesn't match.")
40-
# check that pvconst if given, is the same for all cells
41-
# don't assign pvcell.pvconst here since it triggers a recalc
42-
for p in pvmods:
43-
for c in p.pvcells:
44-
if c.pvconst is not self.pvconst:
45-
raise Exception('PVconstant must be the same for all cells')
46-
if p.pvconst is not self.pvconst:
47-
raise Exception('PVconstant must be the same for all cells')
48-
self.pvmods = pvmods
29+
pvconst=None):
30+
# is pvmods a list?
31+
try:
32+
pvmod0 = pvmods[0]
33+
except TypeError:
34+
# is pvmods an object?
35+
try:
36+
pvconst = pvmods.pvcons
37+
except AttributeError:
38+
# try to use the pvconst arg or create one if none
39+
if not pvconst:
40+
pvconst = PVconstants()
41+
# create pvmod
42+
pvmods = PVmodule(pvconst=pvconst)
43+
# expand pvmods to list
44+
pvmods = [pvmods] * numberMods
45+
else:
46+
pvconst = pvmod0.pvconst
47+
numberMods = len(pvmods)
48+
for p in pvmods:
49+
if p.pvconst is not pvconst:
50+
raise Exception('pvconst must be the same for all modules')
51+
self.pvconst = pvconst #: ``PVconstants`` used in ``PVstring``
52+
self.numberMods = numberMods #: number of module in string
53+
self.pvmods = pvmods #: list of ``PVModule`` in ``PVstring``
54+
# calculate string
4955
self.Istring, self.Vstring, self.Pstring = self.calcString()
5056

5157
# TODO: use __getattr__ to check for updates to pvcells

pvmismatch/pvmismatch_lib/pvsystem.py

Lines changed: 39 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -27,21 +27,42 @@ class PVsystem(object):
2727
:param numberMods: number of modules per string
2828
:param pvmods: list of modules, a ``PVmodule`` object or None
2929
"""
30-
def __init__(self, pvconst=PVconstants(), numberStrs=NUMBERSTRS,
30+
def __init__(self, pvconst=None, numberStrs=NUMBERSTRS,
3131
pvstrs=None, numberMods=NUMBERMODS, pvmods=None):
32-
self.pvconst = pvconst
33-
self.numberStrs = numberStrs
34-
self.numberMods = numberMods
35-
if pvstrs is None:
36-
pvstrs = PVstring(numberMods=self.numberMods, pvmods=pvmods,
37-
pvconst=self.pvconst)
38-
# expand pvstrs to list
39-
if isinstance(pvstrs, PVstring):
40-
pvstrs = [pvstrs] * self.numberStrs
41-
if len(pvstrs) != self.numberStrs:
42-
# TODO: use pvmismatch excecptions
43-
raise Exception("Number of strings don't match.")
44-
self.pvstrs = pvstrs
32+
# is pvstrs a list?
33+
try:
34+
pvstr0 = pvstrs[0]
35+
except TypeError:
36+
# is pvstrs a PVstring object?
37+
try:
38+
pvconst = pvstrs.pvconst
39+
except AttributeError:
40+
# try to use the pvconst arg or create one if none
41+
if not pvconst:
42+
pvconst = PVconstants()
43+
# create a pvstring
44+
pvstrs = PVstring(numberMods=numberMods, pvmods=pvmods,
45+
pvconst=pvconst)
46+
# expand pvstrs to list
47+
pvstrs = [pvstrs] * numberStrs
48+
numberMods = [numberMods] * numberStrs
49+
else:
50+
pvconst = pvstr0.pvconst
51+
numberStrs = len(pvstrs)
52+
numberMods = []
53+
for p in pvstrs:
54+
if p.pvconst is not pvconst:
55+
raise Exception('pvconst must be the same for all strings')
56+
numberMods.append(len(p.pvmods))
57+
self.pvconst = pvconst #: ``PVconstants`` used in ``PVsystem``
58+
self.numberStrs = numberStrs #: number strings in the system
59+
self.numberMods = numberMods #: list of number of modules per string
60+
self.pvstrs = pvstrs #: list of ``PVstring`` in system
61+
# calculate pvsystem
62+
self.update()
63+
64+
def update(self):
65+
"""Update system calculations."""
4566
self.Isys, self.Vsys, self.Psys = self.calcSystem()
4667
(self.Imp, self.Vmp, self.Pmp,
4768
self.Isc, self.Voc, self.FF, self.eff) = self.calcMPP_IscVocFFeff()
@@ -124,9 +145,8 @@ def setSuns(self, Ee):
124145
pvstr = int(pvstr)
125146
self.pvstrs[pvstr] = copy(self.pvstrs[pvstr])
126147
self.pvstrs[pvstr].setSuns(pvmod_Ee)
127-
self.Isys, self.Vsys, self.Psys = self.calcSystem()
128-
(self.Imp, self.Vmp, self.Pmp,
129-
self.Isc, self.Voc, self.FF, self.eff) = self.calcMPP_IscVocFFeff()
148+
# calculate pvsystem
149+
self.update()
130150

131151
def setTemps(self, Tc):
132152
"""
@@ -162,9 +182,8 @@ def setTemps(self, Tc):
162182
pvstr = int(pvstr)
163183
self.pvstrs[pvstr] = copy(self.pvstrs[pvstr])
164184
self.pvstrs[pvstr].setTemps(pvmod_Tc)
165-
self.Isys, self.Vsys, self.Psys = self.calcSystem()
166-
(self.Imp, self.Vmp, self.Pmp,
167-
self.Isc, self.Voc, self.FF, self.eff) = self.calcMPP_IscVocFFeff()
185+
# calculate pvsystem
186+
self.update()
168187

169188
def plotSys(self, sysPlot=None):
170189
"""

pvmismatch/tests/test_pvcell.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@ def test_pvcell_calc_rbd():
7171

7272

7373
def test_pvcell_calc_now_flag():
74+
"""
75+
Test ``_calc_now`` turns off recalc in ``__setattr__``.
76+
"""
7477
pvc = PVcell()
7578
itest, vtest, ptest = pvc.Icell, pvc.Vcell, pvc.Pcell
7679
pvc._calc_now = False
@@ -85,5 +88,18 @@ def test_pvcell_calc_now_flag():
8588
assert np.allclose(pcell, pvc.Pcell)
8689

8790

91+
def test_update():
92+
pvc = PVcell()
93+
Rs = pvc.Rs
94+
itest = pvc.Icell[170]
95+
pvc.update(Rs=0.001)
96+
assert np.isclose(pvc.Icell[170], 5.79691674)
97+
pvc._calc_now = False
98+
pvc.Rs = Rs
99+
pvc.update() # resets _calc_now to True
100+
assert np.isclose(pvc.Icell[170], itest)
101+
assert pvc._calc_now
102+
103+
88104
if __name__ == "__main__":
89105
test_calc_series()
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
from pvmismatch import *
2+
3+
4+
def test_pvconst_npts_setter():
5+
"""Test pvconst property and setter methods"""
6+
pvconst = pvconstants.PVconstants()
7+
assert pvconst.npts == pvconstants.NPTS
8+
assert len(pvconst.pts) == pvconst.npts
9+
assert pvconst.pts[0] == 0
10+
assert pvconst.pts[-1] == 1
11+
assert len(pvconst.negpts) == pvconst.npts
12+
assert pvconst.negpts[0] == 1
13+
assert pvconst.negpts[-1] > 0
14+
pvconst.npts = 1001
15+
assert pvconst.npts == 1001
16+
assert len(pvconst.pts) == pvconst.npts
17+
assert pvconst.pts[0] == 0
18+
assert pvconst.pts[-1] == 1
19+
assert len(pvconst.negpts) == pvconst.npts
20+
assert pvconst.negpts[0] == 1
21+
assert pvconst.negpts[-1] > 0

pvmismatch/tests/test_pvmodule.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
from nose.tools import ok_
66
from pvmismatch.pvmismatch_lib.pvmodule import PVmodule, TCT492, PCT492
7+
from pvmismatch.pvmismatch_lib.pvcell import PVcell
78
import numpy as np
89
from copy import copy
910

@@ -40,6 +41,30 @@ def test_calc_pct_bridges():
4041
pvmod = PVmodule(cell_pos=pct492_bridges)
4142
return pvmod
4243

44+
45+
def check_same_pvconst_and_lengths(pvmod):
46+
assert len(pvmod.pvcells) == 96
47+
for p in pvmod.pvcells:
48+
assert p.pvconst is pvmod.pvconst
49+
50+
51+
def test_pvmodule_with_pvcells_list():
52+
pvcells = [PVcell()] * 96
53+
pvmod = PVmodule(pvcells=pvcells)
54+
check_same_pvconst_and_lengths(pvmod)
55+
56+
57+
def test_pvmodule_with_pvcells_obj():
58+
pvcells = PVcell()
59+
pvmod = PVmodule(pvcells=pvcells)
60+
check_same_pvconst_and_lengths(pvmod)
61+
62+
63+
def test_pvmodule_with_no_pvcells():
64+
pvmod = PVmodule()
65+
check_same_pvconst_and_lengths(pvmod)
66+
67+
4368
if __name__ == "__main__":
4469
test_calc_mod()
4570
test_calc_tct_mod()

pvmismatch/tests/test_pvstring.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
from pvmismatch import *
2+
3+
4+
def check_same_pvconst_and_lengths(pvstr):
5+
assert len(pvstr.pvmods) == pvstring.NUMBERMODS
6+
for p in pvstr.pvmods:
7+
assert p.pvconst is pvstr.pvconst
8+
9+
10+
def test_pvstring_with_pvmods_list():
11+
pvmods = [pvmodule.PVmodule()] * pvstring.NUMBERMODS
12+
pvstr = pvstring.PVstring(pvmods=pvmods)
13+
check_same_pvconst_and_lengths(pvstr)
14+
15+
16+
def test_pvstring_with_pvmods_obj():
17+
pvmods = pvmodule.PVmodule()
18+
pvstr = pvstring.PVstring(pvmods=pvmods)
19+
check_same_pvconst_and_lengths(pvstr)
20+
21+
22+
def test_pvstring_with_no_pvmods():
23+
pvstr = pvstring.PVstring()
24+
check_same_pvconst_and_lengths(pvstr)

0 commit comments

Comments
 (0)