Skip to content

Commit

Permalink
BUG: fix contrib.gen_coeffs Isat2 temperature dependence (#89)
Browse files Browse the repository at this point in the history
* BUG: fix contrib.gen_coeffs Isat2 temperature dependence

* in residual, assume "isat2" in x is at STC, ie "isat2_t0" and calculate
isat2 using `diode.isat_t()`
* also fix all documentation in gen_coeffs to render okay
* make example a callable script, allow user to specify whether they want
to fit just STC or all IEC61853, show generated coeffs, and save figure
* futurize Bennet's module mismatch simulator for py36
* add contrib, gen_coeffs, and module mismatch simulator to docs in new
api section called contrib
* add example output figures
* fix intersphinx, must use https, leave off trailing slash
* don't ignore git conflict files, so they can be removed without hiding
* ignore the new .pytest_cache folder, move python3 __pycache__ folder to
#build
* fix underline in README.rst, should be as long as the title

* fix NoneType has no method upper()

* scale STC values by cells in series and parallel

in RMSE relative to STC

* fix RMSE, show Isc, Voc on plots
  • Loading branch information
mikofski authored and chetan201 committed Sep 28, 2018
1 parent b5b7e09 commit e733ebb
Show file tree
Hide file tree
Showing 10 changed files with 198 additions and 95 deletions.
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@ pvmismatch.sublime-workspace

# files
*.pyc
*.orig

# build
dist/
pvmismatch.egg-info/
build/
Makefile
version.py
__pycache__/

# docs
!docs/_templates/
Expand All @@ -28,7 +28,7 @@ testPV/
benchmark_*/
.coverage
.cache/
__pycache__/
.pytest_cache/

# virtualenv
venv/
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,6 @@ history is also on `GitHub <https://github.com/SunPower/releases/>`__.
:target: https://travis-ci.org/SunPower/PVMismatch

Other Projects that use PVMismatch
-----
----------------------------------
System level mismatch loss calculator using PVMismatch tool (STC and Annual energy loss)
https://github.com/SunPower/MismatchLossStudy
41 changes: 27 additions & 14 deletions pvmismatch/contrib/gen_coeffs/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,25 +11,37 @@
# IEC 61853 test matrix
TC_C = [15.0, 25.0, 50.0, 75.0]
IRR_W_M2 = [100.0, 200.0, 400.0, 600.0, 800.0, 1000.0, 1100.0]
TEST_MAT = np.meshgrid(TC_C, IRR_W_M2)
TEST_MAT = np.meshgrid(TC_C, IRR_W_M2) #: IEC61853 test matrix

def gen_iec_61853_from_sapm(pvmodule):
"""
Generate an IEC 61853 test from Sandia Array Performance Model (sapm).
:param pvmodule: PV module to be tested
:type pvmodule: dict
:param dict pvmodule: PV module to be tested
:returns: a pandas dataframe with columns ``i_mp``, ``v_mp``, ``i_sc``, and
``v_oc`` and rows corresponding to the IEC61853 test conditions
Module is a dictionary according to :def:`pvlib.pvsystem.sapm`.
Module is a dictionary according to ``pvlib.pvsystem.sapm``.
"""
tc, irr = TEST_MAT
return sapm(irr / 1000.0, tc, pvmodule)


def gen_two_diode(isc, voc, imp, vmp, nseries, nparallel,
tc, x0 = None, *args, **kwargs):
tc, x0=None, *args, **kwargs):
"""
Generate two-diode model parameters for ``pvcell`` given
Generate two-diode model parameters for ``pvcell`` given.
:param numeric isc: short circuit current [A]
:param numeric voc: open circuit voltage [V]
:param numeric imp: max power current [A]
:param numeric vmp: max power voltage [V]
:param int nseries: number of cells in series
:param int nparallel: number of parallel substrings in PV module
:param numeric tc: cell temperature [C]
:param x0: optional list of initial guesses, default is ``None``
:returns: tuple ``(isat1, isat2, rs, rsh)`` of generated coefficients and
the solver output
"""
isc_cell = isc / nparallel
voc_cell = voc / nseries
Expand Down Expand Up @@ -78,17 +90,17 @@ def gen_sapm(iec_61853):
return isc0, alpha_isc



def residual_two_diode(x, isc, voc, imp, vmp, tc):
"""
Objective function to solve 2-diode model.
:param x: parameters isat1, isat2, rs and rsh
:param isc: short circuit current [A] at tc [C]
:param voc: open circuit voltage [V] at tc [C]
:param imp: max power current [A] at tc [C]
:param vmp: max power voltage [V] at tc [C]
:param x: parameters ``isat1``, ``isat2``, ``rs``, and ``rsh``
:param isc: short circuit current [A] at ``tc`` [C]
:param voc: open circuit voltage [V] at ``tc`` [C]
:param imp: max power current [A] at ``tc`` [C]
:param vmp: max power voltage [V] at ``tc`` [C]
:param tc: cell temperature [C]
:return: norm of the residuals its sensitivity
:returns: norm of the residuals and the Jacobian matrix
"""
# Constants
q = diode.QE # [C/electron] elementary electric charge
Expand All @@ -99,11 +111,12 @@ def residual_two_diode(x, isc, voc, imp, vmp, tc):
vt = kb * tck / q # [V] thermal voltage
# Rescale Variables
isat1_t0 = np.exp(x[0])
isat2 = np.exp(x[1])
isat2_t0 = np.exp(x[1])
rs = x[2] ** 2.0
rsh = x[3] ** 2.0
# first diode saturation current
isat1 = diode.isat_t(tc, isat1_t0)
isat2 = diode.isat_t(tc, isat2_t0)
# Short Circuit
vd_isc, _ = diode.fvd(vc=0.0, ic=isc, rs=rs)
id1_isc, _ = diode.fid(isat=isat1, vd=vd_isc, m=1.0, vt=vt)
Expand Down
144 changes: 90 additions & 54 deletions pvmismatch/contrib/gen_coeffs/example.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
#! /usr/bin/env python
# -*- coding: utf-8 -*-

"""
Generate coefficients example.
"""

from __future__ import (
absolute_import, division, print_function, unicode_literals)
import os
import sys
from pvmismatch.contrib import gen_coeffs
from pvmismatch import *
from matplotlib import pyplot as plt
Expand All @@ -22,60 +29,89 @@

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)
)
isat1, isat2, rs, rsh = x

pvc = pvcell.PVcell(
Rs=rs, Rsh=rsh, Isat1_T0=isat1, Isat2_T0=isat2,
Isc0_T0=ISC0/NP, alpha_Isc=AISC
)
f1 = plt.figure()
isc0, alpha_isc = gen_coeffs.gen_sapm(iec61853)
assert np.isclose(isc0, ISC0)
assert np.isclose(alpha_isc, AISC)

for m, _tc in enumerate(gen_coeffs.TC_C):
pvc.Tcell = _tc + 273.15
plt.subplot(2, 2, m+1)
plt.xlim([0, 0.8])
plt.ylim([0, 8])
res_norm = 0
for n, _irr in enumerate(gen_coeffs.IRR_W_M2):
pvc.Ee = _irr / 1000.0
plt.plot(pvc.Vcell, pvc.Icell, '-', pvc.Vcell, pvc.Pcell, ':')
plt.plot(
iec61853['v_mp'][n][m]/NS, iec61853['i_mp'][n][m]/NP, 'x',
iec61853['v_oc'][n][m]/NS, 0.0, 'x',
0.0, iec61853['i_sc'][n][m]/NP, 'x',
iec61853['v_mp'][n][m]/NS, iec61853['p_mp'][n][m]/NS/NP, 'o',
if __name__ == '__main__':
test_cond = 'STC'
if len(sys.argv) > 1:
test_cond = sys.argv[1]
if test_cond.upper() == 'STC':
x, sol = gen_coeffs.gen_two_diode(ISC0, VOC0, IMP0, VMP0, NS, NP, T0)
else:
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)
)
res_norm += (
pvc.calcIcell(iec61853['v_mp'][n][m]/NS)
- iec61853['i_mp'][n][m]/NP
)**2 / (iec61853['i_mp'][n][m]/NP)**2
res_norm += (
pvc.calcVcell(iec61853['i_mp'][n][m]/NP)
- iec61853['v_mp'][n][m]/NS
)**2 / (iec61853['v_mp'][n][m]/NS)**2
res_norm += (
pvc.calcVcell(0.0) - iec61853['v_oc'][n][m]/NS
)**2 / (iec61853['v_oc'][n][m]/NS)**2
res_norm += (
pvc.calcIcell(0.0) - iec61853['i_sc'][n][m]/NP
)**2 / (iec61853['i_sc'][n][m]/NP)**2
rel_diff = (pvc.Pcell.max()*NS*NP - iec61853['p_mp'][n][m]) / PMP0
plt.annotate('$\Delta_{rel}$ = %.2g%%' % (rel_diff*100),
(0.65, iec61853['p_mp'][n][m]/NS/NP))
plt.annotate('norm of residuals = %g' % np.sqrt(res_norm / (7*4)),
(0.5, 7.5))
plt.grid(True)
plt.title(
'PVMismatch Generated Coefficients for %s at Tc = %g' % (PROD_NAME, _tc)
isat1, isat2, rs, rsh = x

pvc = pvcell.PVcell(
Rs=rs, Rsh=rsh, Isat1_T0=isat1, Isat2_T0=isat2,
Isc0_T0=ISC0/NP, alpha_Isc=AISC,
pvconst=pvconstants.PVconstants(npts=1001)
)
plt.xlabel('voltage')
plt.ylabel('current [A]')
plt.xlabel('voltage [V]')
f1.show()
f1 = plt.figure(figsize=(16, 10))

for m, _tc in enumerate(gen_coeffs.TC_C):
pvc.Tcell = _tc + 273.15
plt.subplot(2, 2, m+1)
plt.xlim([0, 0.8])
plt.ylim([0, 8])
res_norm = 0
for n, _irr in enumerate(gen_coeffs.IRR_W_M2):
pvc.Ee = _irr / 1000.0
plt.plot(pvc.Vcell, pvc.Icell, '-', pvc.Vcell, pvc.Pcell, ':')
plt.plot(
iec61853['v_mp'][n][m]/NS, iec61853['i_mp'][n][m]/NP, 'x',
iec61853['v_oc'][n][m]/NS, 0.0, 'x',
0.0, iec61853['i_sc'][n][m]/NP, 'x',
iec61853['v_mp'][n][m]/NS, iec61853['p_mp'][n][m]/NS/NP, 'o',
)
mpp = np.argmax(pvc.Pcell)
res_norm += (
pvc.Icell[mpp] - iec61853['i_mp'][n][m]/NP
)**2 / (IMP0/NP)**2
res_norm += (
pvc.Vcell[mpp] - iec61853['v_mp'][n][m]/NS
)**2 / (VMP0/NS)**2
voc = pvc.calcVcell(0.0)
res_norm += (
voc - iec61853['v_oc'][n][m]/NS
)**2 / (VOC0/NS)**2
isc = pvc.calcIcell(0.0)
res_norm += (
isc - iec61853['i_sc'][n][m]/NP
)**2 / (ISC0/NP)**2
rel_diff = (pvc.Pcell[mpp]*NS*NP - iec61853['p_mp'][n][m]) / PMP0
plt.annotate('$\Delta_{STC}$ = %.2g%%' % (rel_diff*100),
(0.65, iec61853['p_mp'][n][m]/NS/NP))
plt.annotate(
'$E_e$ = %.2g[suns], $V_{oc}$ = %.2g[V}, $I_{sc}$ = %.g[A]' % (_irr/1000, voc, isc),
(0.05, 0.05+iec61853['i_sc'][n][m]/NP))
plt.annotate('STC RMSE = %.2g%%' % (np.sqrt(res_norm / (7*4))*100),
(0.65, 7.5))
plt.annotate('$I_{sat,1}$ = %.4g' % isat1,
(0.65, 7.2))
plt.annotate('$I_{sat,2}$ = %.4g' % isat2,
(0.65, 6.9))
plt.annotate('$R_s$ = %.4g' % rs,
(0.65, 6.6))
plt.annotate('$R_{sh}$ = %.4g' % rsh,
(0.65, 6.3))

plt.grid(True)
plt.title(
'PVMismatch Generated Coefficients for %s at Tc = %g' % (PROD_NAME, _tc)
)
plt.xlabel('voltage')
plt.ylabel('current [A], power [W]')
plt.xlabel('voltage [V]')
plt.tight_layout()
if len(sys.argv) > 2:
test_save_dir = sys.argv[2]
os.makedirs(test_save_dir, exist_ok=True)
f1.savefig(os.path.join(test_save_dir, sys.argv[1]))
else:
f1.show()
50 changes: 29 additions & 21 deletions pvmismatch/contrib/module_mismatch_simulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,33 @@

'''
Created on Mar 29, 2013
This script allows the user to dynamically investigate the IV and PV
characteristics of a single module. The user chooses the modules size--72 or 96
cells. A UI is then generated that allows the user to change the size, location,
cells. A GUI is then generated that allows the user to change the size, location,
and irradiance level of a single "shade rectangle". Outputs include cell,
substring, and module level IV and PV curves as well as a module diagram showing
the shade location, any reverse biased cells, and any active diodes.
@author: bmeyers
'''
# ==============================================================================
# Importing standard libraries
# ==============================================================================

import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
from __future__ import (
absolute_import, division, unicode_literals, print_function)
import json
from functools import partial
from copy import deepcopy
import os
import numpy as np
from scipy.interpolate import interp1d
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
from matplotlib.widgets import Slider
from matplotlib.widgets import Button
import json
from functools import partial
from copy import deepcopy
from past.builtins import raw_input, xrange

# ==============================================================================
# Import PVmismatch items
Expand All @@ -32,8 +38,8 @@
from pvmismatch import PVsystem, PVmodule, PVcell, PVconstants
from pvmismatch.pvmismatch_lib.pvmodule import STD72, STD96, STD128
except ImportError:
print "PVMismatch not found on path! Please use 'pip install -e path/to/pvmismatch'"
print "or 'export PYTHONPATH=path/to/pvmismatch:$PYTHONPATH' first."
print("PVMismatch not found on path! Please use 'pip install -e path/to/pvmismatch'")
print("or 'export PYTHONPATH=path/to/pvmismatch:$PYTHONPATH' first.")
import sys
sys.exit(-1)

Expand Down Expand Up @@ -74,8 +80,8 @@ class ShadeObj(object):
def __init__(self, pershade=90, shd_width=1, shd_height=1, shd_x=4,
shd_y=6, numberCells=96):
modHeight = modheight(numberCells)
module = np.empty([numberCells / modHeight, modHeight], dtype=int)
for n in range(numberCells / modHeight):
module = np.empty([numberCells // modHeight, modHeight], dtype=int)
for n in range(numberCells // modHeight):
if n % 2 == 0:
module[n] = np.arange(n * modHeight, (n + 1) * modHeight, 1)
else:
Expand Down Expand Up @@ -155,8 +161,8 @@ def plotting_calcs(pvmod, ivp=None):
reversebias = [n for n in range(len(ivp.Icell.T))
if -np.interp(-ivp.Imp, -ivp.Icell.T[n], -ivp.Vcell.T[n]) < 0]
boolindx = np.array(reversebias)
module = np.empty([pvmod.numberCells / ivp.modHeight, ivp.modHeight], dtype=int)
for n in range(pvmod.numberCells / ivp.modHeight):
module = np.empty([pvmod.numberCells // ivp.modHeight, ivp.modHeight], dtype=int)
for n in range(pvmod.numberCells // ivp.modHeight):
if n % 2 == 0:
module[n] = np.arange(n * ivp.modHeight, (n + 1) * ivp.modHeight, 1)
else:
Expand Down Expand Up @@ -427,24 +433,26 @@ def full_update(val, output=None, ivp0=None, plotobjs=None):
output['ax12'], output['ax03'], output['ax_4'], output['x'], output['y'])
plt.draw()
t1 = (sw * sh, s_ps.val, ivp0.Pmp, 100 * ivp0.Pmp / Pmp0)
print '{0:^6} {1:^6,.2f} {2:^6,.2f} {3:^7,.2f}'.format(*t1)
print('{0:^6} {1:^6,.2f} {2:^6,.2f} {3:^7,.2f}'.format(*t1))


def set_the_shade(val):
ivp0.shade.insert(0, ivp0.shade[-1])


def save_the_shade(val):
this_dir = os.getcwd()
save_dir = os.path.join(this_dir, 'JSONshade')
os.makedirs(save_dir, exist_ok=True)
dicts = []
for shd in ivp0.shade:
dicts.append({'ps': shd.pershade, 'sw': shd.sw, 'sh': shd.sh,
'sy': shd.sy, 'sx': shd.sx,
'numberCells': pvmod1.numberCells})
filename = raw_input("File name? ")
filename = 'JSONshade/' + filename + '.json'
fo = open(filename, 'w')
shades_json = json.dump(dicts, fo, sort_keys=True, indent=2)
fo.close()
filename = os.path.join(save_dir, filename + '.json')
with open(filename, 'w') as fo:
json.dump(dicts, fo, sort_keys=True, indent=2)


def clear_last_full(val, update=None):
Expand All @@ -465,10 +473,10 @@ def clear_last_full(val, update=None):
plotobjs = PlotObjs()
update = partial(full_update, output=output, ivp0=ivp0, plotobjs=plotobjs)
ClearLast = partial(clear_last_full, update=update)
print "Pmp0: {}".format(Pmp0)
print ""
print '{0:6} {1:^6} {2:^6} {3:^7}'.format('#Cells', '%Shade', 'Pmp', '%ofPmp0')
print '----------------------------'
print("Pmp0: {}".format(Pmp0))
print("")
print('{0:6} {1:^6} {2:^6} {3:^7}'.format('#Cells', '%Shade', 'Pmp', '%ofPmp0'))
print('----------------------------')
ps0 = 90
sw0 = 1
sh0 = 1
Expand Down
Loading

0 comments on commit e733ebb

Please sign in to comment.