diff --git a/.gitignore b/.gitignore index 37fc2f7..9fb9c78 100644 --- a/.gitignore +++ b/.gitignore @@ -10,7 +10,6 @@ pvmismatch.sublime-workspace # files *.pyc -*.orig # build dist/ @@ -18,6 +17,7 @@ pvmismatch.egg-info/ build/ Makefile version.py +__pycache__/ # docs !docs/_templates/ @@ -28,7 +28,7 @@ testPV/ benchmark_*/ .coverage .cache/ -__pycache__/ +.pytest_cache/ # virtualenv venv/ diff --git a/README.rst b/README.rst index 9382c3d..461bb22 100644 --- a/README.rst +++ b/README.rst @@ -35,6 +35,6 @@ history is also on `GitHub `__. :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 diff --git a/pvmismatch/contrib/gen_coeffs/__init__.py b/pvmismatch/contrib/gen_coeffs/__init__.py index ea9e616..8cf0b07 100644 --- a/pvmismatch/contrib/gen_coeffs/__init__.py +++ b/pvmismatch/contrib/gen_coeffs/__init__.py @@ -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 @@ -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 @@ -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) diff --git a/pvmismatch/contrib/gen_coeffs/example.py b/pvmismatch/contrib/gen_coeffs/example.py index aa24659..a9cbd27 100644 --- a/pvmismatch/contrib/gen_coeffs/example.py +++ b/pvmismatch/contrib/gen_coeffs/example.py @@ -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 @@ -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() diff --git a/pvmismatch/contrib/module_mismatch_simulator.py b/pvmismatch/contrib/module_mismatch_simulator.py index 2e676c1..ef39e51 100755 --- a/pvmismatch/contrib/module_mismatch_simulator.py +++ b/pvmismatch/contrib/module_mismatch_simulator.py @@ -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 @@ -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) @@ -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: @@ -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: @@ -427,7 +433,7 @@ 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): @@ -435,16 +441,18 @@ def set_the_shade(val): 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): @@ -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 diff --git a/pvmismatch/docs/api/contrib.rst b/pvmismatch/docs/api/contrib.rst new file mode 100644 index 0000000..223496f --- /dev/null +++ b/pvmismatch/docs/api/contrib.rst @@ -0,0 +1,45 @@ +.. _contrib: + +Contributions +============= + +.. automodule:: pvmismatch.contrib + +Generate Coefficients +--------------------- + +.. automodule:: pvmismatch.contrib.gen_coeffs + :members: + +Example +~~~~~~~ +Run the example in :mod:`~pvmismatch.contrib.gen_coeffs`:: + + python pvmismatch/contrib/gen_coeffs/example.py STC ./examples + +This creates a figure ``examples/STC.png`` which calculates the coefficients +using only STC conditions, and displays the power error relative to STC, and +the RMSE of the IV curve relative to ``IMP0``, ``VMP0``, ``ISC0``, and +``VOC0``. + +.. image:: examples/STC.png + +Run the example again with ``IEC61853`` as the first argument:: + + python pvmismatch/contrib/gen_coeffs/example.py IEC61853 ./examples + +This creates a figure ``examples/IEC61853.png`` which calculates the +coefficients using the IEC61853 test conditions, and displays the power error +relative to STC, and the RMSE of the IV curve relative to ``IMP0``, ``VMP0``, +``ISC0``, and ``VOC0``. + +.. image:: examples/IEC61853.png + +Note: You can see that the lack of irradiance correction on shunt resistance +in PVMismatch causes a larger error when the IEC61853 test results are used. + +Module Mismatch Simulator +------------------------- + +.. automodule:: pvmismatch.contrib.module_mismatch_simulator + diff --git a/pvmismatch/docs/api/examples/STC.png b/pvmismatch/docs/api/examples/STC.png new file mode 100644 index 0000000..7024842 Binary files /dev/null and b/pvmismatch/docs/api/examples/STC.png differ diff --git a/pvmismatch/docs/api/examples/iec61853.png b/pvmismatch/docs/api/examples/iec61853.png new file mode 100644 index 0000000..1004031 Binary files /dev/null and b/pvmismatch/docs/api/examples/iec61853.png differ diff --git a/pvmismatch/docs/conf.py b/pvmismatch/docs/conf.py index 5c0a118..2afa5b6 100644 --- a/pvmismatch/docs/conf.py +++ b/pvmismatch/docs/conf.py @@ -266,7 +266,7 @@ # Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = { - 'numpy': ('http://docs.scipy.org/doc/numpy/', None), - 'scipy': ('http://docs.scipy.org/doc/scipy/reference/', None), - 'python': ('http://docs.python.org/', None)} + 'numpy': ('https://docs.scipy.org/doc/numpy', None), + 'scipy': ('https://docs.scipy.org/doc/scipy/reference', None), + 'python': ('https://docs.python.org/3', None)} diff --git a/pvmismatch/docs/index.rst b/pvmismatch/docs/index.rst index 567c696..8e7ebac 100644 --- a/pvmismatch/docs/index.rst +++ b/pvmismatch/docs/index.rst @@ -31,6 +31,7 @@ Contents: api/pvmodule api/pvstring api/pvsystem + api/contrib Indices and tables