Skip to content

Commit

Permalink
Merging v4 branch before submitting PR (#1)
Browse files Browse the repository at this point in the history
* improve python 3 compatibility in pvsys, strs and mods, require future

* change dict.iter*() to iter*(dic), fix import * in tests

* fix two diode test for SymPy-1.1.1

* fixes SunPower#55
* replace string keys in expected_data with the actual symbol objects,
so instead of {'ic': IC}, use {ic: IC} where ic is the SymPy symbol
object
* replace string substitutions in expressions with the actual SymPy
symbols, so use didv.subs(vc + rs*ic, 'vd') instead of
didv.subs('vc + rs * ic(vc)', 'vd') which is error prone anyway, because
the string could change, how do you know what string to use?
* leave these subs in for now, but they are not necessary, since vd =
vc + ic*rs can evaluate just fine
* when numerically evaluating expressions using evalf(subs=expected_data)
substitute values for the symbol di_dv, NOT the string
"Derivative(ic(vc), vc)" since this doesn't work anymore!
* don't substitute for the string 'ic(vc)', instead use the SymPy
symbols ic, since parentheses don't work in SymPy substitutions or
evaluations anymore, and this is more explicit since ic was defined as
f(vc) when it was initialized!
* freeze versions in requirements so this doesn't an issue again
* ignore .spyproject - the new spyder-IDE project file

Signed-off-by: Mark Mikofski <bwana.marko@yahoo.com>

* update travis to test Py3, update setup with correct install and test reqs, add setup.cfg for universal wheel

* ENH: BUG: add _calc_now flag to determine when cell is recalculated (GH59) (SunPower#61)

* fixes SunPower#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__

* BUG: pvconst is not a singleton, inconsistent, doesn't update (GH38) (SunPower#62)

* fixes SunPower#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 SunPower#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

* Update example.py

Isat2 => Isat2_T0

* reverting changes - didn't mean to commit master

* Isat2fix (SunPower#64)

* Added temperature dependance for Isat2, i.e. Isat2(Tcell)

* test for wide range of Tcell values - added after implementing Isat2 as a function of Tcell

* generated new iv curve

* Isat2 to Isat2_T0

* Update example.py

Isat2 => Isat2_T0

* Update test_diode.py

* Deleting the old IV curve

* updated IV curve test file

* fixed isat2 typos caused by replace

* use entire iec matrix

* Update .travis.yml

added new pipy credentials
  • Loading branch information
rayhickey authored Feb 26, 2018
1 parent 40d7eb4 commit f62cc7e
Show file tree
Hide file tree
Showing 22 changed files with 443 additions and 165 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# ide
.spyderproject
.spyproject
.project
.pydevproject
.settings/
Expand Down
5 changes: 4 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
language: python
python:
- "2.7"
- "3.6"
cache: pip
sudo: required
before_install:
Expand All @@ -18,7 +21,7 @@ deploy:
on:
tags: true
- provider: pypi
user: bwanamarko
user: perfspwr
password: $PYPI_PASSWORD
distributions: "sdist bdist_wheel" # Your distributions here
skip_upload_docs: true
Expand Down
4 changes: 2 additions & 2 deletions pvmismatch/contrib/gen_coeffs/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import numpy as np
from scipy import optimize
from pvmismatch.contrib.gen_coeffs import diode, two_diode
from pvmismatch.pvmismatch_lib.pvcell import ISAT1_T0, ISAT2, RS, RSH
from pvmismatch.pvmismatch_lib.pvcell import ISAT1_T0, ISAT2_T0, RS, RSH

# IEC 61853 test matrix
TC_C = [15.0, 25.0, 50.0, 75.0]
Expand Down Expand Up @@ -37,7 +37,7 @@ def gen_two_diode(isc, voc, imp, vmp, nseries, nparallel,
vmp_cell = vmp / nseries
if x0 is None:
isat1 = ISAT1_T0 # [A]
isat2 = ISAT2
isat2 = ISAT2_T0
rs = RS # [ohms]
rsh = RSH # [ohms]
else:
Expand Down
14 changes: 7 additions & 7 deletions pvmismatch/contrib/gen_coeffs/example.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,16 @@
iec61853 = gen_coeffs.gen_iec_61853_from_sapm(gen_coeffs.PVMODULES[PROD_NAME])
iec61853['i_mp'] = iec61853['p_mp'] / iec61853['v_mp']
#isc0, alpha_isc = gen_coeffs.gen_sapm(iec61853)
x, sol = gen_coeffs.gen_two_diode(ISC0, VOC0, IMP0, VMP0, NS, NP, T0)
#x, sol = gen_coeffs.gen_two_diode(
# iec61853['i_sc'], iec61853['v_oc'], iec61853['i_mp'],
# iec61853['v_mp'], NS, NP, tc=TC, method='lm',
# x0=(2.25e-11, 1.5e-6, 0.004, 10.0)
#)
#x, sol = gen_coeffs.gen_two_diode(ISC0, VOC0, IMP0, VMP0, NS, NP, T0)
x, sol = gen_coeffs.gen_two_diode(
iec61853['i_sc'], iec61853['v_oc'], iec61853['i_mp'],
iec61853['v_mp'], NS, NP, tc=TC, method='lm',
x0=(2.25e-11, 1.5e-6, 0.004, 10.0)
)
isat1, isat2, rs, rsh = x

pvc = pvcell.PVcell(
Rs=rs, Rsh=rsh, Isat1_T0=isat1, Isat2=isat2,
Rs=rs, Rsh=rsh, Isat1_T0=isat1, Isat2_T0=isat2,
Isc0_T0=ISC0/NP, alpha_Isc=AISC
)
f1 = plt.figure()
Expand Down
2 changes: 1 addition & 1 deletion pvmismatch/contrib/gen_coeffs/tests/test_diode.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"""

from pvmismatch.pvmismatch_lib.pvcell import (
RS as RS_2, RSH as RSH_2, ISAT1_T0 as ISAT1_2, ISAT2 as ISAT2_2
RS as RS_2, RSH as RSH_2, ISAT1_T0 as ISAT1_2, ISAT2_T0 as ISAT2_2
)
from pvmismatch.contrib.gen_coeffs import diode
from pvmismatch.contrib.gen_coeffs import PVMODULES
Expand Down
54 changes: 27 additions & 27 deletions pvmismatch/contrib/gen_coeffs/tests/test_two_diode.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,22 +56,22 @@ def test_didv_dpdv_frsh():
'rsh': RSH_2, 'ic': IC, 'vc': VC, 'vt': VT}
fdidv_test, jdidv_test = fdidv(**test_data)
expected_data = {
'isat1': ISAT1_2, 'isat2': ISAT2_2, 'rs': RS_2, 'rsh': RSH_2,
'ic(vc)': IC, 'vc': VC, 'vd': VD_2, 'vt': VT
isat1: ISAT1_2, isat2: ISAT2_2, rs: RS_2, rsh: RSH_2,
ic: IC, vc: VC, 'vd': VD_2, vt: VT
}
didv_simple = didv.subs('vc + ic(vc) * rs', 'vd')
didv_simple = didv.subs(vc + ic * rs, 'vd')
fdidv_expected = np.float(didv_simple.evalf(subs=expected_data))
LOGGER.debug('fdidv test: %g, expected: %g', fdidv_test, fdidv_expected)
assert np.isclose(fdidv_test, fdidv_expected)
# jacobian
d_didv_isat1 = didv.diff(isat1).subs('vc + ic(vc) * rs', 'vd')
d_didv_isat2 = didv.diff(isat2).subs('vc + ic(vc) * rs', 'vd')
d_didv_rs = didv.diff(rs).subs('vc + ic(vc) * rs', 'vd')
d_didv_rsh = didv.diff(rsh).subs('vc + ic(vc) * rs', 'vd')
d_didv_ic = didv.diff(ic).subs('vc + ic(vc) * rs', 'vd')
d_didv_vc = didv.diff(vc).subs('vc + ic(vc) * rs', 'vd')
d_didv_isat1 = didv.diff(isat1).subs(vc + ic * rs, 'vd')
d_didv_isat2 = didv.diff(isat2).subs(vc + ic * rs, 'vd')
d_didv_rs = didv.diff(rs).subs(vc + ic * rs, 'vd')
d_didv_rsh = didv.diff(rsh).subs(vc + ic * rs, 'vd')
d_didv_ic = didv.diff(ic).subs(vc + ic * rs, 'vd')
d_didv_vc = didv.diff(vc).subs(vc + ic * rs, 'vd')
# update expected test data with calculated derivative
expected_data['Derivative(ic(vc), vc)'] = fdidv_expected
expected_data[di_dv] = fdidv_expected
jdidv_expected = np.array([
d_didv_isat1.evalf(subs=expected_data),
d_didv_isat2.evalf(subs=expected_data),
Expand All @@ -88,17 +88,17 @@ def test_didv_dpdv_frsh():
dpdv = didv * vc + ic
# test fdpdv
fdpdv_test, jdpdv_test = fdpdv(**test_data)
dpdv_simple = dpdv.subs('vc + ic(vc) * rs', 'vd')
dpdv_simple = dpdv.subs(vc + ic * rs, 'vd')
fdpdv_expected = np.float(dpdv_simple.evalf(subs=expected_data))
LOGGER.debug('fdpdv test: %g, expected: %g', fdpdv_test, fdpdv_expected)
assert np.isclose(fdpdv_test, fdpdv_expected)
# jacobian
d_dpdv_isat1 = dpdv.diff(isat1).subs('vc + ic(vc) * rs', 'vd')
d_dpdv_isat2 = dpdv.diff(isat2).subs('vc + ic(vc) * rs', 'vd')
d_dpdv_rs = dpdv.diff(rs).subs('vc + ic(vc) * rs', 'vd')
d_dpdv_rsh = dpdv.diff(rsh).subs('vc + ic(vc) * rs', 'vd')
d_dpdv_ic = dpdv.diff(ic).subs('vc + ic(vc) * rs', 'vd')
d_dpdv_vc = dpdv.diff(vc).subs('vc + ic(vc) * rs', 'vd')
d_dpdv_isat1 = dpdv.diff(isat1).subs(vc + ic * rs, 'vd')
d_dpdv_isat2 = dpdv.diff(isat2).subs(vc + ic * rs, 'vd')
d_dpdv_rs = dpdv.diff(rs).subs(vc + ic * rs, 'vd')
d_dpdv_rsh = dpdv.diff(rsh).subs(vc + ic * rs, 'vd')
d_dpdv_ic = dpdv.diff(ic).subs(vc + ic * rs, 'vd')
d_dpdv_vc = dpdv.diff(vc).subs(vc + ic * rs, 'vd')
jdpdv_expected = np.array([
d_dpdv_isat1.evalf(subs=expected_data),
d_dpdv_isat2.evalf(subs=expected_data),
Expand All @@ -117,23 +117,23 @@ def test_didv_dpdv_frsh():
del test_data['ic'], test_data['vc'] # remove Ic, Vc
test_data['isc'] = ISC0 # add Isc
frsh_test, jfrsh_test = fjrsh(**test_data)
frsh_simple = frsh.subs('vc + ic(vc) * rs', 'vd')
frsh_simple = frsh.subs(vc + ic * rs, 'vd')
# update expected test data with calculated derivative
expected_data['ic(vc)'] = ISC0
expected_data['vc'] = 0
expected_data[ic] = ISC0
expected_data[vc] = 0
expected_data['vd'] = ISC0 * RS_2
didv_isc = np.float(didv_simple.evalf(subs=expected_data))
expected_data['Derivative(ic(vc), vc)'] = didv_isc
expected_data[di_dv] = didv_isc
frsh_expected = np.float(frsh_simple.evalf(subs=expected_data))
LOGGER.debug('frsh test: %r, expected: %r', frsh_test, frsh_expected)
assert np.isclose(frsh_test, frsh_expected)
# jacobian
dfrsh_isat1 = frsh.diff(isat1).subs('vc + ic(vc) * rs', 'vd')
dfrsh_isat2 = frsh.diff(isat2).subs('vc + ic(vc) * rs', 'vd')
dfrsh_rs = frsh.diff(rs).subs('vc + ic(vc) * rs', 'vd')
dfrsh_rsh = frsh.diff(rsh).subs('vc + ic(vc) * rs', 'vd')
dfrsh_ic = frsh.diff(ic).subs('vc + ic(vc) * rs', 'vd')
dfrsh_vc = frsh.diff(vc).subs('vc + ic(vc) * rs', 'vd')
dfrsh_isat1 = frsh.diff(isat1).subs(vc + ic * rs, 'vd')
dfrsh_isat2 = frsh.diff(isat2).subs(vc + ic * rs, 'vd')
dfrsh_rs = frsh.diff(rs).subs(vc + ic * rs, 'vd')
dfrsh_rsh = frsh.diff(rsh).subs(vc + ic * rs, 'vd')
dfrsh_ic = frsh.diff(ic).subs(vc + ic * rs, 'vd')
dfrsh_vc = frsh.diff(vc).subs(vc + ic * rs, 'vd')
jfrsh_expected = np.array([
dfrsh_isat1.evalf(subs=expected_data),
dfrsh_isat2.evalf(subs=expected_data),
Expand Down
48 changes: 34 additions & 14 deletions pvmismatch/pvmismatch_lib/pvcell.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
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
Expand All @@ -14,7 +16,7 @@
RS = 0.004267236774264931 # [ohm] series resistance
RSH = 10.01226369025448 # [ohm] shunt resistance
ISAT1_T0 = 2.286188161253440E-11 # [A] diode one saturation current
ISAT2 = 1.117455042372326E-6 # [A] diode two saturation current
ISAT2_T0 = 1.117455042372326E-6 # [A] diode two saturation current
ISC0_T0 = 6.3056 # [A] reference short circuit current
TCELL = 298.15 # [K] cell temperature
ARBD = 1.036748445065697E-4 # reverse breakdown coefficient 1
Expand Down Expand Up @@ -45,15 +47,18 @@ class PVcell(object):
:param pvconst: configuration constants object
:type pvconst: :class:`~pvmismatch.pvmismatch_lib.pvconstants.PVconstants`
"""
def __init__(self, Rs=RS, Rsh=RSH, Isat1_T0=ISAT1_T0, Isat2=ISAT2,

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

def __init__(self, Rs=RS, Rsh=RSH, Isat1_T0=ISAT1_T0, Isat2_T0=ISAT2_T0,
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.Isat2 = Isat2 #: [A] diode two saturation current
self.Isat2_T0 = Isat2_T0 #: [A] diode two saturation current
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
Expand All @@ -67,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 = '<PVcell(Ee=%g[suns], Tcell=%g[K], Isc=%g[A], Voc=%g[V])>'
Expand All @@ -76,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)
for k, v in kwargs.iteritems():
# 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):
Expand Down Expand Up @@ -134,6 +142,18 @@ def Isat1(self):
)
return self.Isat1_T0 * _Tstar * _expTstar # [A] Isat1(Tcell)

@property
def Isat2(self):
"""
Diode two 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 / (2.0 * self.pvconst.k) * _inv_delta_T
)
return self.Isat2_T0 * _Tstar * _expTstar # [A] Isat2(Tcell)

@property
def Isc0(self):
"""
Expand Down Expand Up @@ -274,4 +294,4 @@ def plot(self):
plt.xlim(0, self.Voc)
plt.ylim(0, (self.Isc + 1) * self.Voc)
plt.grid()
return cell_plot
return cell_plot
40 changes: 28 additions & 12 deletions pvmismatch/pvmismatch_lib/pvconstants.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,23 +74,39 @@ class PVconstants(object):
T0 = 298.15 #: [K] reference temperature

def __init__(self, npts=NPTS):
# set number of points in IV curve(s)
self.npts = npts #: number of points in IV curves
# point spacing from 0 to 1, used for Vcell, Vmod, Vsys and Istring
# decrease point spacing as voltage approaches Voc by using logspace
pts = (11. - np.logspace(np.log10(11.), 0., self.npts)) / 10.
pts[0] = 0. # first point must be exactly zero
self.pts = pts.reshape((self.npts, 1))
self._npts = None
self.pts = None
"""array of points with decreasing spacing from 0 to 1"""
negpts = (11. - np.logspace(np.log10(11. - 1. / float(self.npts)),
0., self.npts)) / 10.
negpts = negpts.reshape((self.npts, 1))
self.Imod_negpts = 1 + 1. / float(self.npts) / 10. - negpts
self.Imod_negpts = None
"""array of points with decreasing spacing from 1 to just less than but
not including zero"""
self.negpts = np.flipud(negpts) # reverse the order
self.negpts = None
"""array of points with increasing spacing from 1 to just less than but
not including zero"""
self.Imod_pts = None
"""array of points with increasing spacing from 0 to 1"""
# call property setter
self.npts = npts #: number of points in IV curves

@property
def npts(self):
"""number of points in IV curves"""
return self._npts

@npts.setter
def npts(self, npts):
# set number of points in IV curve(s)
self._npts = npts # number of points in IV curves
# point spacing from 0 to 1, used for Vcell, Vmod, Vsys and Istring
# decrease point spacing as voltage approaches Voc by using logspace
pts = (11. - np.logspace(np.log10(11.), 0., self._npts)) / 10.
pts[0] = 0. # first point must be exactly zero
self.pts = pts.reshape((self._npts, 1))
negpts = (11. - np.logspace(np.log10(11. - 1. / float(self._npts)),
0., self._npts)) / 10.
negpts = negpts.reshape((self._npts, 1))
self.Imod_negpts = 1 + 1. / float(self._npts) / 10. - negpts
self.negpts = np.flipud(negpts) # reverse the order
# shift and concatenate pvconst.negpts and pvconst.pts
# so that tight spacing is around MPP and RBD
self.Imod_pts = 1 - np.flipud(self.pts)
Expand Down
Loading

0 comments on commit f62cc7e

Please sign in to comment.