From f4751203bbf194bbf55a970df0ef02e31f45f6b7 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Fri, 3 May 2019 11:46:24 -0600 Subject: [PATCH 01/68] initial commit fit_cec_using_sam --- pvlib/ivtools/PySSC.py | 248 ++++++++++++++++++++++++ pvlib/ivtools/fit_cec_model_with_sam.py | 103 ++++++++++ 2 files changed, 351 insertions(+) create mode 100644 pvlib/ivtools/PySSC.py create mode 100644 pvlib/ivtools/fit_cec_model_with_sam.py diff --git a/pvlib/ivtools/PySSC.py b/pvlib/ivtools/PySSC.py new file mode 100644 index 0000000000..c638a11b74 --- /dev/null +++ b/pvlib/ivtools/PySSC.py @@ -0,0 +1,248 @@ +#Created with SAM version 2018.11.11 +# Modified by C. W. Hansen for PEP8 compliance +import sys +import os +import ctypes +ctypes.c_number = ctypes.c_float # must be c_double or c_float depending on how defined in sscapi.h +class PySSC(): + def __init__(self, sam_dir): + if sys.platform in ['win32', 'cygwin']: + self.pdll = ctypes.CDLL(os.path.join(sam_dir, "ssc.dll")) + elif sys.platform == 'darwin': + self.pdll = ctypes.CDLL(os.path.join(sam_dir, "ssc.dylib")) + elif sys.platform == 'linux2': + self.pdll = ctypes.CDLL(os.path.join(sam_dir, "ssc.so")) # instead of relative path, require user to have on LD_LIBRARY_PATH + else: + print ('Platform not supported ', sys.platform) + INVALID=0 + STRING=1 + NUMBER=2 + ARRAY=3 + MATRIX=4 + INPUT=1 + OUTPUT=2 + INOUT=3 + + def version(self): + self.pdll.ssc_version.restype = ctypes.c_int + return self.pdll.ssc_version() + + def build_info(self): + self.pdll.ssc_build_info.restype = ctypes.c_char_p + return self.pdll.ssc_build_info() + + def data_create(self): + self.pdll.ssc_data_create.restype = ctypes.c_void_p + return self.pdll.ssc_data_create() + + def data_free(self, p_data): + self.pdll.ssc_data_free(ctypes.c_void_p(p_data)) + + def data_clear(self, p_data): + self.pdll.ssc_data_clear(ctypes.c_void_p(p_data)) + + def data_unassign(self, p_data, name): + self.pdll.ssc_data_unassign(ctypes.c_void_p(p_data), + ctypes.c_char_p(name)) + + def data_query(self, p_data, name): + self.pdll.ssc_data_query.restype = ctypes.c_int + return self.pdll.ssc_data_query(ctypes.c_void_p(p_data), + ctypes.c_char_p(name)) + + def data_first(self, p_data): + self.pdll.ssc_data_first.restype = ctypes.c_char_p + return self.pdll.ssc_data_first(ctypes.c_void_p(p_data)) + + def data_next(self, p_data): + self.pdll.ssc_data_next.restype = ctypes.c_char_p + return self.pdll.ssc_data_next(ctypes.c_void_p(p_data)) + + def data_set_string(self, p_data, name, value): + self.pdll.ssc_data_set_string(ctypes.c_void_p(p_data), + ctypes.c_char_p(name), + ctypes.c_char_p(value)) + + def data_set_number(self, p_data, name, value): + self.pdll.ssc_data_set_number(ctypes.c_void_p(p_data), + ctypes.c_char_p(name), + ctypes.c_number(value)) + + def data_set_array(self,p_data,name,parr): + count = len(parr) + arr = (ctypes.c_number * count)() + arr[:] = parr # set all at once instead of looping + return self.pdll.ssc_data_set_array(ctypes.c_void_p(p_data), + ctypes.c_char_p(name), + ctypes.pointer(arr), + ctypes.c_int(count)) + + def data_set_array_from_csv(self, p_data, name, fn): + f = open(fn, 'rb'); + data = []; + for line in f : + data.extend([n for n in map(float, line.split(b','))]) + f.close() + return self.data_set_array(p_data, name, data) + + def data_set_matrix(self,p_data,name,mat): + nrows = len(mat) + ncols = len(mat[0]) + size = nrows * ncols + arr = (ctypes.c_number * size)() + idx=0 + for r in range(nrows): + for c in range(ncols): + arr[idx] = ctypes.c_number(mat[r][c]) + idx += 1 + return self.pdll.ssc_data_set_matrix(ctypes.c_void_p(p_data), + ctypes.c_char_p(name), + ctypes.pointer(arr), + ctypes.c_int(nrows), + ctypes.c_int(ncols)) + + def data_set_matrix_from_csv(self, p_data, name, fn): + f = open(fn, 'rb'); + data = []; + for line in f : + lst = ([n for n in map(float, line.split(b','))]) + data.append(lst); + f.close(); + return self.data_set_matrix(p_data, name, data); + + def data_set_table(self,p_data,name,tab): + return self.pdll.ssc_data_set_table(ctypes.c_void_p(p_data), + ctypes.c_char_p(name), + ctypes.c_void_p(tab)); + + def data_get_string(self, p_data, name): + self.pdll.ssc_data_get_string.restype = ctypes.c_char_p + return self.pdll.ssc_data_get_string(ctypes.c_void_p(p_data), + ctypes.c_char_p(name)) + + def data_get_number(self, p_data, name): + val = ctypes.c_number(0) + self.pdll.ssc_data_get_number(ctypes.c_void_p(p_data), + ctypes.c_char_p(name), + ctypes.byref(val)) + return val.value + + def data_get_array(self,p_data,name): + count = ctypes.c_int() + self.pdll.ssc_data_get_array.restype = ctypes.POINTER(ctypes.c_number) + parr = self.pdll.ssc_data_get_array(ctypes.c_void_p(p_data), + ctypes.c_char_p(name), + ctypes.byref(count)) + arr = parr[0:count.value] # extract all at once + return arr + + def data_get_matrix(self,p_data,name): + nrows = ctypes.c_int() + ncols = ctypes.c_int() + self.pdll.ssc_data_get_matrix.restype = ctypes.POINTER(ctypes.c_number) + parr = self.pdll.ssc_data_get_matrix(ctypes.c_void_p(p_data), + ctypes.c_char_p(name), + ctypes.byref(nrows), + ctypes.byref(ncols)) + idx = 0 + mat = [] + for r in range(nrows.value): + row = [] + for c in range(ncols.value): + row.append( float(parr[idx]) ) + idx = idx + 1 + mat.append(row) + return mat + # don't call data_free() on the result, it's an internal + # pointer inside SSC + + def data_get_table(self,p_data,name): + return self.pdll.ssc_data_get_table(ctypes.c_void_p(p_data), name); + + def module_entry(self,index): + self.pdll.ssc_module_entry.restype = ctypes.c_void_p + return self.pdll.ssc_module_entry(ctypes.c_int(index)) + + def entry_name(self,p_entry): + self.pdll.ssc_entry_name.restype = ctypes.c_char_p + return self.pdll.ssc_entry_name(ctypes.c_void_p(p_entry)) + + def entry_description(self,p_entry): + self.pdll.ssc_entry_description.restype = ctypes.c_char_p + return self.pdll.ssc_entry_description(ctypes.c_void_p(p_entry)) + + def entry_version(self,p_entry): + self.pdll.ssc_entry_version.restype = ctypes.c_int + return self.pdll.ssc_entry_version(ctypes.c_void_p(p_entry)) + + def module_create(self,name): + self.pdll.ssc_module_create.restype = ctypes.c_void_p + return self.pdll.ssc_module_create(ctypes.c_char_p(name)) + + def module_free(self,p_mod): + self.pdll.ssc_module_free(ctypes.c_void_p(p_mod)) + + def module_var_info(self,p_mod,index): + self.pdll.ssc_module_var_info.restype = ctypes.c_void_p + return self.pdll.ssc_module_var_info(ctypes.c_void_p(p_mod), + ctypes.c_int(index)) + + def info_var_type(self, p_inf): + return self.pdll.ssc_info_var_type(ctypes.c_void_p(p_inf)) + + def info_data_type(self, p_inf): + return self.pdll.ssc_info_data_type(ctypes.c_void_p(p_inf)) + + def info_name(self, p_inf): + self.pdll.ssc_info_name.restype = ctypes.c_char_p + return self.pdll.ssc_info_name(ctypes.c_void_p(p_inf)) + + def info_label(self, p_inf): + self.pdll.ssc_info_label.restype = ctypes.c_char_p + return self.pdll.ssc_info_label(ctypes.c_void_p(p_inf)) + + def info_units(self, p_inf): + self.pdll.ssc_info_units.restype = ctypes.c_char_p + return self.pdll.ssc_info_units(ctypes.c_void_p(p_inf)) + + def info_meta(self, p_inf): + self.pdll.ssc_info_meta.restype = ctypes.c_char_p + return self.pdll.ssc_info_meta(ctypes.c_void_p(p_inf)) + + def info_group(self, p_inf): + self.pdll.ssc_info_group.restype = ctypes.c_char_p + return self.pdll.ssc_info_group(ctypes.c_void_p(p_inf)) + + def info_uihint(self, p_inf): + self.pdll.ssc_info_uihint.restype = ctypes.c_char_p + return self.pdll.ssc_info_uihint(ctypes.c_void_p(p_inf)) + + def info_required(self, p_inf): + self.pdll.ssc_info_required.restype = ctypes.c_char_p + return self.pdll.ssc_info_required(ctypes.c_void_p(p_inf)) + + def info_constraints(self, p_inf): + self.pdll.ssc_info_constraints.restype = ctypes.c_char_p + return self.pdll.ssc_info_constraints(ctypes.c_void_p(p_inf)) + + def module_exec(self, p_mod, p_data): + self.pdll.ssc_module_exec.restype = ctypes.c_int + return self.pdll.ssc_module_exec(ctypes.c_void_p(p_mod), + ctypes.c_void_p(p_data)) + + def module_exec_simple_no_thread(self, modname, data): + self.pdll.ssc_module_exec_simple_nothread.restype = ctypes.c_char_p; + return self.pdll.ssc_module_exec_simple_nothread( + ctypes.c_char_p(modname), ctypes.c_void_p(data)); + + def module_log(self, p_mod, index): + log_type = ctypes.c_int() + time = ctypes.c_float() + self.pdll.ssc_module_log.restype = ctypes.c_char_p + return self.pdll.ssc_module_log(ctypes.c_void_p(p_mod), + ctypes.c_int(index), + ctypes.byref(log_type), + ctypes.byref(time)) + + def module_exec_set_print(self, prn): + return self.pdll.ssc_module_exec_set_print(ctypes.c_int(prn)); diff --git a/pvlib/ivtools/fit_cec_model_with_sam.py b/pvlib/ivtools/fit_cec_model_with_sam.py new file mode 100644 index 0000000000..541c822398 --- /dev/null +++ b/pvlib/ivtools/fit_cec_model_with_sam.py @@ -0,0 +1,103 @@ +# -*- coding: utf-8 -*- +""" +Created on Thu May 2 12:42:24 2019 + +@author: cwhanse +""" + +from PySSC import PySSC + +def fit_cec_model_with_sam(sam_dir, celltype, Vmp, Imp, Voc, Isc, alpha_sc, + beta_voc, gamma_pmp, cells_in_series, temp_ref=25): + ''' + Estimates parameters for the CEC single diode model using SAM SDK. + + Parameters + ---------- + sam_dir : str + Full path to folder containing the SAM file ssc.dll + celltype : str + Value is one of 'monoSi', 'multiSi', 'polySi', 'cis', 'cigs', 'cdte', + 'amorphous' + Vmp : float + Voltage at maximum power point at standard test condition (STC) + Imp : float + Current at maximum power point at STC + Voc : float + Open circuit voltage at STC + Isc : float + Short circuit current at STC + alpha_sc : float + Temperature coefficient of short circuit current at STC, A/C + beta_voc : float + Temperature coefficient of open circuit voltage at STC, V/C + gamma_pmp : float + Temperature coefficient of power at maximum point point at STC, %/C + cells_in_series : int + Number of cells in series + temp_ref : float, default 25 + Reference temperature condition + + Returns + ------- + a_ref : float + The product of the usual diode ideality factor (n, unitless), + number of cells in series (Ns), and cell thermal voltage at reference + conditions, in units of V. + + I_L_ref : float + The light-generated current (or photocurrent) at reference conditions, + in amperes. + + I_o_ref : float + The dark or diode reverse saturation current at reference conditions, + in amperes. + + R_sh_ref : float + The shunt resistance at reference conditions, in ohms. + + R_s : float + The series resistance at reference conditions, in ohms. + + Adjust : float + The adjustment to the temperature coefficient for short circuit + current, in percent + ''' + + try: + ssc = PySSC(sam_dir) + except Exception as e: + raise(e) + + data = ssc.data_create() + + ssc.data_set_string(data, b'celltype', celltype.encode('utf-8')) + ssc.data_set_number(data, b'Vmp', Vmp) + ssc.data_set_number(data, b'Imp', Imp) + ssc.data_set_number(data, b'Voc', Voc) + ssc.data_set_number(data, b'Isc', Isc) + ssc.data_set_number(data, b'alpha_isc', alpha_sc) + ssc.data_set_number(data, b'beta_voc', beta_voc) + ssc.data_set_number(data, b'gamma_pmp', gamma_pmp) + ssc.data_set_number(data, b'Nser', cells_in_series) + ssc.data_set_number(data, b'Tref', temp_ref) + + solver = ssc.module_create(b'6parsolve') + ssc.module_exec_set_print(0) + if ssc.module_exec(solver, data) == 0: + print('IV curve fit error') + idx = 1 + msg = ssc.module_log(solver, 0) + while (msg != None): + print(' : ' + msg.decode("utf - 8")) + msg = ssc.module_log(solver, idx) + idx = idx + 1 + ssc.module_free(solver) + a_ref = ssc.data_get_number(data, b'a') + I_L_ref = ssc.data_get_number(data, b'Il') + I_o_ref = ssc.data_get_number(data, b'Io') + R_s = ssc.data_get_number(data, b'Rs') + R_sh_ref = ssc.data_get_number(data, b'Rsh') + Adjust = ssc.data_get_number(data, b'Adj') + + return a_ref, I_L_ref, I_o_ref, R_sh_ref, R_s, Adjust From 7454e4dffc17878b696375486a06029c2490c854 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Fri, 3 May 2019 14:56:21 -0600 Subject: [PATCH 02/68] outline ivcurves --- pvlib/ivtools/ivcurves.py | 64 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 pvlib/ivtools/ivcurves.py diff --git a/pvlib/ivtools/ivcurves.py b/pvlib/ivtools/ivcurves.py new file mode 100644 index 0000000000..50fe36b972 --- /dev/null +++ b/pvlib/ivtools/ivcurves.py @@ -0,0 +1,64 @@ +# -*- coding: utf-8 -*- +""" +Created on Fri May 3 14:16:24 2019 + +@author: cwhanse +""" + +import numpy as np + + +class IVCurves(): + """ + Contains IV curves and methods for fitting models to the curves. + """ + + def __init__(self, data): + IVCurves.ivdata = data + IVCurves.voc = None + + def __repr(self): + pass + + def __print__(self): + pass + + def fit(): + """ + Fit a model to IV curve data. + """ + pass + + +class _IVCurve(): + """ + Contains a single IV curve + """ + + def __init__(self, V, I, Ee, Tc, Voc=None, Isc=None, Vmp=None, Imp=None): + self.V = V + self.I = I + self.Ee = Ee + self.Tc = Tc + if Voc is None: + self.Voc = V[-1] + if Isc is None: + self.Isc = I[0] + if Vmp is None: + self.Vmp, self.Imp = find_max_power(V, I) + + +def find_max_power(V, I): + """ Finds V, I pair where V*I is maximum + + Parameters + ---------- + V : numeric + I : numeric + + Returns + ------- + Vmax, Imax : values from V and I where V*I is maximum + """ + idx = np.argmax(V * I) + return V[idx], I[idx] From 9c5e1a9c99b948b8bcef32e50a3325d2c91142bd Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Mon, 6 May 2019 15:36:14 -0600 Subject: [PATCH 03/68] use nrel-pysam --- pvlib/ivtools/PySSC.py | 248 ------------------------ pvlib/ivtools/fit_cec_model_with_sam.py | 4 +- 2 files changed, 2 insertions(+), 250 deletions(-) delete mode 100644 pvlib/ivtools/PySSC.py diff --git a/pvlib/ivtools/PySSC.py b/pvlib/ivtools/PySSC.py deleted file mode 100644 index c638a11b74..0000000000 --- a/pvlib/ivtools/PySSC.py +++ /dev/null @@ -1,248 +0,0 @@ -#Created with SAM version 2018.11.11 -# Modified by C. W. Hansen for PEP8 compliance -import sys -import os -import ctypes -ctypes.c_number = ctypes.c_float # must be c_double or c_float depending on how defined in sscapi.h -class PySSC(): - def __init__(self, sam_dir): - if sys.platform in ['win32', 'cygwin']: - self.pdll = ctypes.CDLL(os.path.join(sam_dir, "ssc.dll")) - elif sys.platform == 'darwin': - self.pdll = ctypes.CDLL(os.path.join(sam_dir, "ssc.dylib")) - elif sys.platform == 'linux2': - self.pdll = ctypes.CDLL(os.path.join(sam_dir, "ssc.so")) # instead of relative path, require user to have on LD_LIBRARY_PATH - else: - print ('Platform not supported ', sys.platform) - INVALID=0 - STRING=1 - NUMBER=2 - ARRAY=3 - MATRIX=4 - INPUT=1 - OUTPUT=2 - INOUT=3 - - def version(self): - self.pdll.ssc_version.restype = ctypes.c_int - return self.pdll.ssc_version() - - def build_info(self): - self.pdll.ssc_build_info.restype = ctypes.c_char_p - return self.pdll.ssc_build_info() - - def data_create(self): - self.pdll.ssc_data_create.restype = ctypes.c_void_p - return self.pdll.ssc_data_create() - - def data_free(self, p_data): - self.pdll.ssc_data_free(ctypes.c_void_p(p_data)) - - def data_clear(self, p_data): - self.pdll.ssc_data_clear(ctypes.c_void_p(p_data)) - - def data_unassign(self, p_data, name): - self.pdll.ssc_data_unassign(ctypes.c_void_p(p_data), - ctypes.c_char_p(name)) - - def data_query(self, p_data, name): - self.pdll.ssc_data_query.restype = ctypes.c_int - return self.pdll.ssc_data_query(ctypes.c_void_p(p_data), - ctypes.c_char_p(name)) - - def data_first(self, p_data): - self.pdll.ssc_data_first.restype = ctypes.c_char_p - return self.pdll.ssc_data_first(ctypes.c_void_p(p_data)) - - def data_next(self, p_data): - self.pdll.ssc_data_next.restype = ctypes.c_char_p - return self.pdll.ssc_data_next(ctypes.c_void_p(p_data)) - - def data_set_string(self, p_data, name, value): - self.pdll.ssc_data_set_string(ctypes.c_void_p(p_data), - ctypes.c_char_p(name), - ctypes.c_char_p(value)) - - def data_set_number(self, p_data, name, value): - self.pdll.ssc_data_set_number(ctypes.c_void_p(p_data), - ctypes.c_char_p(name), - ctypes.c_number(value)) - - def data_set_array(self,p_data,name,parr): - count = len(parr) - arr = (ctypes.c_number * count)() - arr[:] = parr # set all at once instead of looping - return self.pdll.ssc_data_set_array(ctypes.c_void_p(p_data), - ctypes.c_char_p(name), - ctypes.pointer(arr), - ctypes.c_int(count)) - - def data_set_array_from_csv(self, p_data, name, fn): - f = open(fn, 'rb'); - data = []; - for line in f : - data.extend([n for n in map(float, line.split(b','))]) - f.close() - return self.data_set_array(p_data, name, data) - - def data_set_matrix(self,p_data,name,mat): - nrows = len(mat) - ncols = len(mat[0]) - size = nrows * ncols - arr = (ctypes.c_number * size)() - idx=0 - for r in range(nrows): - for c in range(ncols): - arr[idx] = ctypes.c_number(mat[r][c]) - idx += 1 - return self.pdll.ssc_data_set_matrix(ctypes.c_void_p(p_data), - ctypes.c_char_p(name), - ctypes.pointer(arr), - ctypes.c_int(nrows), - ctypes.c_int(ncols)) - - def data_set_matrix_from_csv(self, p_data, name, fn): - f = open(fn, 'rb'); - data = []; - for line in f : - lst = ([n for n in map(float, line.split(b','))]) - data.append(lst); - f.close(); - return self.data_set_matrix(p_data, name, data); - - def data_set_table(self,p_data,name,tab): - return self.pdll.ssc_data_set_table(ctypes.c_void_p(p_data), - ctypes.c_char_p(name), - ctypes.c_void_p(tab)); - - def data_get_string(self, p_data, name): - self.pdll.ssc_data_get_string.restype = ctypes.c_char_p - return self.pdll.ssc_data_get_string(ctypes.c_void_p(p_data), - ctypes.c_char_p(name)) - - def data_get_number(self, p_data, name): - val = ctypes.c_number(0) - self.pdll.ssc_data_get_number(ctypes.c_void_p(p_data), - ctypes.c_char_p(name), - ctypes.byref(val)) - return val.value - - def data_get_array(self,p_data,name): - count = ctypes.c_int() - self.pdll.ssc_data_get_array.restype = ctypes.POINTER(ctypes.c_number) - parr = self.pdll.ssc_data_get_array(ctypes.c_void_p(p_data), - ctypes.c_char_p(name), - ctypes.byref(count)) - arr = parr[0:count.value] # extract all at once - return arr - - def data_get_matrix(self,p_data,name): - nrows = ctypes.c_int() - ncols = ctypes.c_int() - self.pdll.ssc_data_get_matrix.restype = ctypes.POINTER(ctypes.c_number) - parr = self.pdll.ssc_data_get_matrix(ctypes.c_void_p(p_data), - ctypes.c_char_p(name), - ctypes.byref(nrows), - ctypes.byref(ncols)) - idx = 0 - mat = [] - for r in range(nrows.value): - row = [] - for c in range(ncols.value): - row.append( float(parr[idx]) ) - idx = idx + 1 - mat.append(row) - return mat - # don't call data_free() on the result, it's an internal - # pointer inside SSC - - def data_get_table(self,p_data,name): - return self.pdll.ssc_data_get_table(ctypes.c_void_p(p_data), name); - - def module_entry(self,index): - self.pdll.ssc_module_entry.restype = ctypes.c_void_p - return self.pdll.ssc_module_entry(ctypes.c_int(index)) - - def entry_name(self,p_entry): - self.pdll.ssc_entry_name.restype = ctypes.c_char_p - return self.pdll.ssc_entry_name(ctypes.c_void_p(p_entry)) - - def entry_description(self,p_entry): - self.pdll.ssc_entry_description.restype = ctypes.c_char_p - return self.pdll.ssc_entry_description(ctypes.c_void_p(p_entry)) - - def entry_version(self,p_entry): - self.pdll.ssc_entry_version.restype = ctypes.c_int - return self.pdll.ssc_entry_version(ctypes.c_void_p(p_entry)) - - def module_create(self,name): - self.pdll.ssc_module_create.restype = ctypes.c_void_p - return self.pdll.ssc_module_create(ctypes.c_char_p(name)) - - def module_free(self,p_mod): - self.pdll.ssc_module_free(ctypes.c_void_p(p_mod)) - - def module_var_info(self,p_mod,index): - self.pdll.ssc_module_var_info.restype = ctypes.c_void_p - return self.pdll.ssc_module_var_info(ctypes.c_void_p(p_mod), - ctypes.c_int(index)) - - def info_var_type(self, p_inf): - return self.pdll.ssc_info_var_type(ctypes.c_void_p(p_inf)) - - def info_data_type(self, p_inf): - return self.pdll.ssc_info_data_type(ctypes.c_void_p(p_inf)) - - def info_name(self, p_inf): - self.pdll.ssc_info_name.restype = ctypes.c_char_p - return self.pdll.ssc_info_name(ctypes.c_void_p(p_inf)) - - def info_label(self, p_inf): - self.pdll.ssc_info_label.restype = ctypes.c_char_p - return self.pdll.ssc_info_label(ctypes.c_void_p(p_inf)) - - def info_units(self, p_inf): - self.pdll.ssc_info_units.restype = ctypes.c_char_p - return self.pdll.ssc_info_units(ctypes.c_void_p(p_inf)) - - def info_meta(self, p_inf): - self.pdll.ssc_info_meta.restype = ctypes.c_char_p - return self.pdll.ssc_info_meta(ctypes.c_void_p(p_inf)) - - def info_group(self, p_inf): - self.pdll.ssc_info_group.restype = ctypes.c_char_p - return self.pdll.ssc_info_group(ctypes.c_void_p(p_inf)) - - def info_uihint(self, p_inf): - self.pdll.ssc_info_uihint.restype = ctypes.c_char_p - return self.pdll.ssc_info_uihint(ctypes.c_void_p(p_inf)) - - def info_required(self, p_inf): - self.pdll.ssc_info_required.restype = ctypes.c_char_p - return self.pdll.ssc_info_required(ctypes.c_void_p(p_inf)) - - def info_constraints(self, p_inf): - self.pdll.ssc_info_constraints.restype = ctypes.c_char_p - return self.pdll.ssc_info_constraints(ctypes.c_void_p(p_inf)) - - def module_exec(self, p_mod, p_data): - self.pdll.ssc_module_exec.restype = ctypes.c_int - return self.pdll.ssc_module_exec(ctypes.c_void_p(p_mod), - ctypes.c_void_p(p_data)) - - def module_exec_simple_no_thread(self, modname, data): - self.pdll.ssc_module_exec_simple_nothread.restype = ctypes.c_char_p; - return self.pdll.ssc_module_exec_simple_nothread( - ctypes.c_char_p(modname), ctypes.c_void_p(data)); - - def module_log(self, p_mod, index): - log_type = ctypes.c_int() - time = ctypes.c_float() - self.pdll.ssc_module_log.restype = ctypes.c_char_p - return self.pdll.ssc_module_log(ctypes.c_void_p(p_mod), - ctypes.c_int(index), - ctypes.byref(log_type), - ctypes.byref(time)) - - def module_exec_set_print(self, prn): - return self.pdll.ssc_module_exec_set_print(ctypes.c_int(prn)); diff --git a/pvlib/ivtools/fit_cec_model_with_sam.py b/pvlib/ivtools/fit_cec_model_with_sam.py index 541c822398..4613996591 100644 --- a/pvlib/ivtools/fit_cec_model_with_sam.py +++ b/pvlib/ivtools/fit_cec_model_with_sam.py @@ -5,7 +5,7 @@ @author: cwhanse """ -from PySSC import PySSC +from PySAM.PySSC import PySSC def fit_cec_model_with_sam(sam_dir, celltype, Vmp, Imp, Voc, Isc, alpha_sc, beta_voc, gamma_pmp, cells_in_series, temp_ref=25): @@ -65,7 +65,7 @@ def fit_cec_model_with_sam(sam_dir, celltype, Vmp, Imp, Voc, Isc, alpha_sc, ''' try: - ssc = PySSC(sam_dir) + ssc = PySSC.PySSC() except Exception as e: raise(e) From 593f3555b2b90327d73cc7bde81776897259ba48 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Tue, 7 May 2019 08:35:02 -0600 Subject: [PATCH 04/68] remove sam_dir --- pvlib/ivtools/fit_cec_model_with_sam.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/pvlib/ivtools/fit_cec_model_with_sam.py b/pvlib/ivtools/fit_cec_model_with_sam.py index 4613996591..b474c3e77c 100644 --- a/pvlib/ivtools/fit_cec_model_with_sam.py +++ b/pvlib/ivtools/fit_cec_model_with_sam.py @@ -7,15 +7,13 @@ from PySAM.PySSC import PySSC -def fit_cec_model_with_sam(sam_dir, celltype, Vmp, Imp, Voc, Isc, alpha_sc, - beta_voc, gamma_pmp, cells_in_series, temp_ref=25): +def fit_cec_model_with_sam(celltype, Vmp, Imp, Voc, Isc, alpha_sc, beta_voc, + gamma_pmp, cells_in_series, temp_ref=25): ''' - Estimates parameters for the CEC single diode model using SAM SDK. + Estimates parameters for the CEC single diode model using the SAM SDK. Parameters ---------- - sam_dir : str - Full path to folder containing the SAM file ssc.dll celltype : str Value is one of 'monoSi', 'multiSi', 'polySi', 'cis', 'cigs', 'cdte', 'amorphous' From 4ab1fed955f60b6f49b149142c17c4ac94ad2bc7 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Tue, 7 May 2019 08:56:00 -0600 Subject: [PATCH 05/68] add Sandia single curve fit --- pvlib/ivtools/fit_sde_sandia.py | 118 ++++++++++++++++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 pvlib/ivtools/fit_sde_sandia.py diff --git a/pvlib/ivtools/fit_sde_sandia.py b/pvlib/ivtools/fit_sde_sandia.py new file mode 100644 index 0000000000..b16c7a14ca --- /dev/null +++ b/pvlib/ivtools/fit_sde_sandia.py @@ -0,0 +1,118 @@ +# -*- coding: utf-8 -*- +""" +Created on Fri Mar 29 10:34:10 2019 + +@author: cwhanse +""" + +import numpy as np + + +def fit_sde_sandia(V, I, Voc, Isc, Vmp, Imp, vlim=0.2, ilim=0.1): + """ Fits the single diode equation to an IV curve. + + If fitting fails, returns NaN in each parameter. + + Parameters + ---------- + V : numeric + Voltage at each point on the IV curve, from 0 to Voc + + I : numeric + Current at each point on the IV curve, from Isc to 0 + + Voc : float + Open circuit voltage + + Isc : float + Short circuit current + + Vmp : float + Voltage at maximum power point + + Imp : float + Current at maximum power point + + vlim : float, default 0.2 + defines linear portion of IV curve i.e. V <= vlim * Voc + + ilim : float, default 0.1 + defines exponential portion of IV curve i.e. I > ilim * Isc + + Returns + ------- + IL : float + photocurrent, A + + I0 : float + dark (saturation) current, A + + Rs : float + series resistance, ohm + + Rsh : float + shunt (parallel) resistance, ohm + + nNsVth : float + product of diode (ideality) factor n (unitless) x number of + cells in series Ns (unitless) x cell thermal voltage Vth (V), V + + References + ---------- + [1] C. B. Jones, C. W. Hansen, Single Diode Parameter Extraction from + In-Field Photovoltaic I-V Curves on a Single Board Computer, 46th IEEE + Photovoltaic Specialist Conference, Chicago, IL, 2019 + """ + # Find intercept and slope of linear portion of IV curve. + # Start with V < vlim * Voc, extend by adding points until slope is + # acceptable + beta = [np.nan for i in range(5)] + beta[0] = np.nan + beta[1] = np.nan + idx = len(V <= vlim * Voc) + while np.isnan(beta[1]) and (idx<=len(V)): + try: + p = np.polyfit(V[:idx], I[:idx], deg=1) + if p[1] < 0: + beta[0] = p[0] + beta[1] = -p[1] # sign change to get positive parameter value + except: + pass + if np.isnan(beta[1]): + idx += 1 + + if not np.isnan(beta[0]): + # Find parameters from exponential portion of IV curve + Y = beta[0] - beta[1] * V - I + X = np.array([V, I]) + idx = len(Y <= ilim * Isc) + try: + p = np.linalg.lstsq(X, Y) + beta[3] = p[1] + beta[4] = p[2] + except: + pass + + if not any([np.isnan(beta[i]) for i in [0, 1, 3, 4]]): + # calculate parameters + nNsVth = 1.0 / beta[3] + Rs = beta[4] / beta[3] + Gp = beta[1] / (1.0 - Rs * beta[1]) + Rsh = 1.0 / Gp + IL = (1 + Gp * Rs) * beta[0] + # calculate I0 + I0_Vmp = _calc_I0(IL, Imp, Vmp, Gp, Rs, beta[3]) + I0_Voc = _calc_I0(IL, 0, Voc, Gp, Rs, beta[3]) + if (I0_Vmp > 0) and (I0_Voc > 0): + I0 = 0.5 * (I0_Vmp + I0_Voc) + elif (I0_Vmp > 0): + I0 = I0_Vmp + elif (I0_Voc > 0): + I0 = I0_Voc + else: + I0 = np.nan + return IL, I0, Rs, Rsh, nNsVth + + +def _calc_I0(IL, I, V, Gp, Rs, beta3): + return (IL - I - Gp * V - Gp * Rs * I) / np.exp(beta3 * (V + Rs * I)) \ No newline at end of file From 8a8890f87cf80501db1ff8bfcc77a8161275de72 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Thu, 9 May 2019 13:05:57 -0600 Subject: [PATCH 06/68] complete function --- pvlib/ivtools/fit_sde_sandia.py | 39 ++++++++++++++++++++++----------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/pvlib/ivtools/fit_sde_sandia.py b/pvlib/ivtools/fit_sde_sandia.py index b16c7a14ca..e889e1e565 100644 --- a/pvlib/ivtools/fit_sde_sandia.py +++ b/pvlib/ivtools/fit_sde_sandia.py @@ -67,15 +67,16 @@ def fit_sde_sandia(V, I, Voc, Isc, Vmp, Imp, vlim=0.2, ilim=0.1): # Start with V < vlim * Voc, extend by adding points until slope is # acceptable beta = [np.nan for i in range(5)] - beta[0] = np.nan - beta[1] = np.nan - idx = len(V <= vlim * Voc) + # get index of largest voltage less than/equal to limit + idx = _max_index(V, vlim * Voc) while np.isnan(beta[1]) and (idx<=len(V)): try: - p = np.polyfit(V[:idx], I[:idx], deg=1) - if p[1] < 0: - beta[0] = p[0] - beta[1] = -p[1] # sign change to get positive parameter value + coef = np.polyfit(V[:idx], I[:idx], deg=1) + if coef[0] < 0: + # intercept term + beta[0] = coef[1].item() + # sign change of slope to get positive parameter value + beta[1] = -coef[0].item() except: pass if np.isnan(beta[1]): @@ -84,12 +85,13 @@ def fit_sde_sandia(V, I, Voc, Isc, Vmp, Imp, vlim=0.2, ilim=0.1): if not np.isnan(beta[0]): # Find parameters from exponential portion of IV curve Y = beta[0] - beta[1] * V - I - X = np.array([V, I]) - idx = len(Y <= ilim * Isc) + X = np.array([np.ones_like(V), V, I]).T + idx = _min_index(Y, ilim * Isc) try: - p = np.linalg.lstsq(X, Y) - beta[3] = p[1] - beta[4] = p[2] + result = np.linalg.lstsq(X[idx:,], np.log(Y[idx:])) + coef = result[0] + beta[3] = coef[1].item() + beta[4] = coef[2].item() except: pass @@ -111,8 +113,19 @@ def fit_sde_sandia(V, I, Voc, Isc, Vmp, Imp, vlim=0.2, ilim=0.1): I0 = I0_Voc else: I0 = np.nan + else: + IL = I0 = Rs = Rsh = nNsVth = np.nan + return IL, I0, Rs, Rsh, nNsVth def _calc_I0(IL, I, V, Gp, Rs, beta3): - return (IL - I - Gp * V - Gp * Rs * I) / np.exp(beta3 * (V + Rs * I)) \ No newline at end of file + return (IL - I - Gp * V - Gp * Rs * I) / np.exp(beta3 * (V + Rs * I)) + +def _max_index(x, xlim): + """ Finds maximum index of value of x <= xlim """ + return int(np.argwhere(x <= xlim)[-1]) + +def _min_index(x, xlim): + """ Finds minimum index of value of x > xlim """ + return int(np.argwhere(x > xlim)[0]) From b72b0b4ebe4154e2d12165c83e8930a3cbc1b3af Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Fri, 10 May 2019 09:25:16 -0600 Subject: [PATCH 07/68] add test, move code to ivtools.py --- pvlib/ivtools.py | 228 ++++++++++++++++++++++++++++++++ pvlib/ivtools/fit_sde_sandia.py | 8 +- pvlib/test/test_ivtools.py | 30 +++++ 3 files changed, 262 insertions(+), 4 deletions(-) create mode 100644 pvlib/ivtools.py create mode 100644 pvlib/test/test_ivtools.py diff --git a/pvlib/ivtools.py b/pvlib/ivtools.py new file mode 100644 index 0000000000..4c6d275def --- /dev/null +++ b/pvlib/ivtools.py @@ -0,0 +1,228 @@ +# -*- coding: utf-8 -*- +""" +Created on Fri Mar 29 10:34:10 2019 + +@author: cwhanse +""" + +import numpy as np +from PySAM import PySSC + + +def fit_cec_with_sam(celltype, Vmp, Imp, Voc, Isc, alpha_sc, beta_voc, + gamma_pmp, cells_in_series, temp_ref=25): + ''' + Estimates parameters for the CEC single diode model using the SAM SDK. + + Parameters + ---------- + celltype : str + Value is one of 'monoSi', 'multiSi', 'polySi', 'cis', 'cigs', 'cdte', + 'amorphous' + Vmp : float + Voltage at maximum power point at standard test condition (STC) + Imp : float + Current at maximum power point at STC + Voc : float + Open circuit voltage at STC + Isc : float + Short circuit current at STC + alpha_sc : float + Temperature coefficient of short circuit current at STC, A/C + beta_voc : float + Temperature coefficient of open circuit voltage at STC, V/C + gamma_pmp : float + Temperature coefficient of power at maximum point point at STC, %/C + cells_in_series : int + Number of cells in series + temp_ref : float, default 25 + Reference temperature condition + + Returns + ------- + a_ref : float + The product of the usual diode ideality factor (n, unitless), + number of cells in series (Ns), and cell thermal voltage at reference + conditions, in units of V. + + I_L_ref : float + The light-generated current (or photocurrent) at reference conditions, + in amperes. + + I_o_ref : float + The dark or diode reverse saturation current at reference conditions, + in amperes. + + R_sh_ref : float + The shunt resistance at reference conditions, in ohms. + + R_s : float + The series resistance at reference conditions, in ohms. + + Adjust : float + The adjustment to the temperature coefficient for short circuit + current, in percent + ''' + + try: + ssc = PySSC.PySSC() + except Exception as e: + raise(e) + + data = ssc.data_create() + + ssc.data_set_string(data, b'celltype', celltype.encode('utf-8')) + ssc.data_set_number(data, b'Vmp', Vmp) + ssc.data_set_number(data, b'Imp', Imp) + ssc.data_set_number(data, b'Voc', Voc) + ssc.data_set_number(data, b'Isc', Isc) + ssc.data_set_number(data, b'alpha_isc', alpha_sc) + ssc.data_set_number(data, b'beta_voc', beta_voc) + ssc.data_set_number(data, b'gamma_pmp', gamma_pmp) + ssc.data_set_number(data, b'Nser', cells_in_series) + ssc.data_set_number(data, b'Tref', temp_ref) + + solver = ssc.module_create(b'6parsolve') + ssc.module_exec_set_print(0) + if ssc.module_exec(solver, data) == 0: + print('IV curve fit error') + idx = 1 + msg = ssc.module_log(solver, 0) + while (msg != None): + print(' : ' + msg.decode("utf - 8")) + msg = ssc.module_log(solver, idx) + idx = idx + 1 + ssc.module_free(solver) + a_ref = ssc.data_get_number(data, b'a') + I_L_ref = ssc.data_get_number(data, b'Il') + I_o_ref = ssc.data_get_number(data, b'Io') + R_s = ssc.data_get_number(data, b'Rs') + R_sh_ref = ssc.data_get_number(data, b'Rsh') + Adjust = ssc.data_get_number(data, b'Adj') + + return I_L_ref, I_o_ref, R_sh_ref, R_s, a_ref, Adjust + + +def fit_sde_sandia(V, I, Voc, Isc, Vmp, Imp, vlim=0.2, ilim=0.1): + """ Fits the single diode equation to an IV curve. + + If fitting fails, returns NaN in each parameter. + + Parameters + ---------- + V : numeric + Voltage at each point on the IV curve, from 0 to Voc + + I : numeric + Current at each point on the IV curve, from Isc to 0 + + Voc : float + Open circuit voltage + + Isc : float + Short circuit current + + Vmp : float + Voltage at maximum power point + + Imp : float + Current at maximum power point + + vlim : float, default 0.2 + defines linear portion of IV curve i.e. V <= vlim * Voc + + ilim : float, default 0.1 + defines exponential portion of IV curve i.e. I > ilim * Isc + + Returns + ------- + IL : float + photocurrent, A + + I0 : float + dark (saturation) current, A + + Rsh : float + shunt (parallel) resistance, ohm + + Rs : float + series resistance, ohm + + nNsVth : float + product of diode (ideality) factor n (unitless) x number of + cells in series Ns (unitless) x cell thermal voltage Vth (V), V + + References + ---------- + [1] C. B. Jones, C. W. Hansen, Single Diode Parameter Extraction from + In-Field Photovoltaic I-V Curves on a Single Board Computer, 46th IEEE + Photovoltaic Specialist Conference, Chicago, IL, 2019 + """ + # Find intercept and slope of linear portion of IV curve. + # Start with V < vlim * Voc, extend by adding points until slope is + # acceptable + beta = [np.nan for i in range(5)] + # get index of largest voltage less than/equal to limit + idx = _max_index(V, vlim * Voc) + while np.isnan(beta[1]) and (idx<=len(V)): + try: + coef = np.polyfit(V[:idx], I[:idx], deg=1) + if coef[0] < 0: + # intercept term + beta[0] = coef[1].item() + # sign change of slope to get positive parameter value + beta[1] = -coef[0].item() + except: + pass + if np.isnan(beta[1]): + idx += 1 + + if not np.isnan(beta[0]): + # Find parameters from exponential portion of IV curve + Y = beta[0] - beta[1] * V - I + X = np.array([np.ones_like(V), V, I]).T + idx = _min_index(Y, ilim * Isc) + try: + result = np.linalg.lstsq(X[idx:,], np.log(Y[idx:])) + coef = result[0] + beta[3] = coef[1].item() + beta[4] = coef[2].item() + except: + pass + + if not any([np.isnan(beta[i]) for i in [0, 1, 3, 4]]): + # calculate parameters + nNsVth = 1.0 / beta[3] + Rs = beta[4] / beta[3] + Gp = beta[1] / (1.0 - Rs * beta[1]) + Rsh = 1.0 / Gp + IL = (1 + Gp * Rs) * beta[0] + # calculate I0 + I0_Vmp = _calc_I0(IL, Imp, Vmp, Gp, Rs, beta[3]) + I0_Voc = _calc_I0(IL, 0, Voc, Gp, Rs, beta[3]) + if (I0_Vmp > 0) and (I0_Voc > 0): + I0 = 0.5 * (I0_Vmp + I0_Voc) + elif (I0_Vmp > 0): + I0 = I0_Vmp + elif (I0_Voc > 0): + I0 = I0_Voc + else: + I0 = np.nan + else: + IL = I0 = Rs = Rsh = nNsVth = np.nan + + return IL, I0, Rsh, Rs, nNsVth + + +def _calc_I0(IL, I, V, Gp, Rs, beta3): + return (IL - I - Gp * V - Gp * Rs * I) / np.exp(beta3 * (V + Rs * I)) + + +def _max_index(x, xlim): + """ Finds maximum index of value of x <= xlim """ + return int(np.argwhere(x <= xlim)[-1]) + + +def _min_index(x, xlim): + """ Finds minimum index of value of x > xlim """ + return int(np.argwhere(x > xlim)[0]) diff --git a/pvlib/ivtools/fit_sde_sandia.py b/pvlib/ivtools/fit_sde_sandia.py index e889e1e565..9515c954e6 100644 --- a/pvlib/ivtools/fit_sde_sandia.py +++ b/pvlib/ivtools/fit_sde_sandia.py @@ -47,12 +47,12 @@ def fit_sde_sandia(V, I, Voc, Isc, Vmp, Imp, vlim=0.2, ilim=0.1): I0 : float dark (saturation) current, A - Rs : float - series resistance, ohm - Rsh : float shunt (parallel) resistance, ohm + Rs : float + series resistance, ohm + nNsVth : float product of diode (ideality) factor n (unitless) x number of cells in series Ns (unitless) x cell thermal voltage Vth (V), V @@ -116,7 +116,7 @@ def fit_sde_sandia(V, I, Voc, Isc, Vmp, Imp, vlim=0.2, ilim=0.1): else: IL = I0 = Rs = Rsh = nNsVth = np.nan - return IL, I0, Rs, Rsh, nNsVth + return IL, I0, Rsh, Rs, nNsVth def _calc_I0(IL, I, V, Gp, Rs, beta3): diff --git a/pvlib/test/test_ivtools.py b/pvlib/test/test_ivtools.py new file mode 100644 index 0000000000..71397e970a --- /dev/null +++ b/pvlib/test/test_ivtools.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +""" +Created on Thu May 9 10:51:15 2019 + +@author: cwhanse +""" + +import numpy as np +from pvlib import pvsystem +from pvlib.ivtools import fit_sde_sandia + + +def get_test_iv_params(): + return {'IL': 8.0, 'I0': 5e-10, 'Rsh': 1000, 'Rs': 0.2, 'nNsVth': 1.61864} + + +def test_fit_sde_sandia(): + test_params = get_test_iv_params() + testcurve = pvsystem.singlediode(photocurrent=test_params['IL'], + saturation_current=test_params['I0'], + resistance_shunt=test_params['Rsh'], + resistance_series=test_params['Rs'], + nNsVth=test_params['nNsVth'], + ivcurve_pnts=300) + expected = tuple(test_params[k] for k in ['IL', 'I0', 'Rsh', 'Rs', + 'nNsVth']) + result = fit_sde_sandia(V=testcurve['v'], I=testcurve['i'], + Voc=testcurve['v_oc'], Isc=testcurve['i_sc'], + Vmp=testcurve['v_mp'], Imp=testcurve['i_mp']) + assert np.allclose(result, expected, rtol=5e-5) From 921aea89c77d25462dfdee9e35acd0d58140b30b Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Fri, 10 May 2019 09:25:40 -0600 Subject: [PATCH 08/68] remove single files --- pvlib/ivtools/fit_cec_model_with_sam.py | 101 ------------------ pvlib/ivtools/fit_sde_sandia.py | 131 ------------------------ 2 files changed, 232 deletions(-) delete mode 100644 pvlib/ivtools/fit_cec_model_with_sam.py delete mode 100644 pvlib/ivtools/fit_sde_sandia.py diff --git a/pvlib/ivtools/fit_cec_model_with_sam.py b/pvlib/ivtools/fit_cec_model_with_sam.py deleted file mode 100644 index b474c3e77c..0000000000 --- a/pvlib/ivtools/fit_cec_model_with_sam.py +++ /dev/null @@ -1,101 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Created on Thu May 2 12:42:24 2019 - -@author: cwhanse -""" - -from PySAM.PySSC import PySSC - -def fit_cec_model_with_sam(celltype, Vmp, Imp, Voc, Isc, alpha_sc, beta_voc, - gamma_pmp, cells_in_series, temp_ref=25): - ''' - Estimates parameters for the CEC single diode model using the SAM SDK. - - Parameters - ---------- - celltype : str - Value is one of 'monoSi', 'multiSi', 'polySi', 'cis', 'cigs', 'cdte', - 'amorphous' - Vmp : float - Voltage at maximum power point at standard test condition (STC) - Imp : float - Current at maximum power point at STC - Voc : float - Open circuit voltage at STC - Isc : float - Short circuit current at STC - alpha_sc : float - Temperature coefficient of short circuit current at STC, A/C - beta_voc : float - Temperature coefficient of open circuit voltage at STC, V/C - gamma_pmp : float - Temperature coefficient of power at maximum point point at STC, %/C - cells_in_series : int - Number of cells in series - temp_ref : float, default 25 - Reference temperature condition - - Returns - ------- - a_ref : float - The product of the usual diode ideality factor (n, unitless), - number of cells in series (Ns), and cell thermal voltage at reference - conditions, in units of V. - - I_L_ref : float - The light-generated current (or photocurrent) at reference conditions, - in amperes. - - I_o_ref : float - The dark or diode reverse saturation current at reference conditions, - in amperes. - - R_sh_ref : float - The shunt resistance at reference conditions, in ohms. - - R_s : float - The series resistance at reference conditions, in ohms. - - Adjust : float - The adjustment to the temperature coefficient for short circuit - current, in percent - ''' - - try: - ssc = PySSC.PySSC() - except Exception as e: - raise(e) - - data = ssc.data_create() - - ssc.data_set_string(data, b'celltype', celltype.encode('utf-8')) - ssc.data_set_number(data, b'Vmp', Vmp) - ssc.data_set_number(data, b'Imp', Imp) - ssc.data_set_number(data, b'Voc', Voc) - ssc.data_set_number(data, b'Isc', Isc) - ssc.data_set_number(data, b'alpha_isc', alpha_sc) - ssc.data_set_number(data, b'beta_voc', beta_voc) - ssc.data_set_number(data, b'gamma_pmp', gamma_pmp) - ssc.data_set_number(data, b'Nser', cells_in_series) - ssc.data_set_number(data, b'Tref', temp_ref) - - solver = ssc.module_create(b'6parsolve') - ssc.module_exec_set_print(0) - if ssc.module_exec(solver, data) == 0: - print('IV curve fit error') - idx = 1 - msg = ssc.module_log(solver, 0) - while (msg != None): - print(' : ' + msg.decode("utf - 8")) - msg = ssc.module_log(solver, idx) - idx = idx + 1 - ssc.module_free(solver) - a_ref = ssc.data_get_number(data, b'a') - I_L_ref = ssc.data_get_number(data, b'Il') - I_o_ref = ssc.data_get_number(data, b'Io') - R_s = ssc.data_get_number(data, b'Rs') - R_sh_ref = ssc.data_get_number(data, b'Rsh') - Adjust = ssc.data_get_number(data, b'Adj') - - return a_ref, I_L_ref, I_o_ref, R_sh_ref, R_s, Adjust diff --git a/pvlib/ivtools/fit_sde_sandia.py b/pvlib/ivtools/fit_sde_sandia.py deleted file mode 100644 index 9515c954e6..0000000000 --- a/pvlib/ivtools/fit_sde_sandia.py +++ /dev/null @@ -1,131 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Created on Fri Mar 29 10:34:10 2019 - -@author: cwhanse -""" - -import numpy as np - - -def fit_sde_sandia(V, I, Voc, Isc, Vmp, Imp, vlim=0.2, ilim=0.1): - """ Fits the single diode equation to an IV curve. - - If fitting fails, returns NaN in each parameter. - - Parameters - ---------- - V : numeric - Voltage at each point on the IV curve, from 0 to Voc - - I : numeric - Current at each point on the IV curve, from Isc to 0 - - Voc : float - Open circuit voltage - - Isc : float - Short circuit current - - Vmp : float - Voltage at maximum power point - - Imp : float - Current at maximum power point - - vlim : float, default 0.2 - defines linear portion of IV curve i.e. V <= vlim * Voc - - ilim : float, default 0.1 - defines exponential portion of IV curve i.e. I > ilim * Isc - - Returns - ------- - IL : float - photocurrent, A - - I0 : float - dark (saturation) current, A - - Rsh : float - shunt (parallel) resistance, ohm - - Rs : float - series resistance, ohm - - nNsVth : float - product of diode (ideality) factor n (unitless) x number of - cells in series Ns (unitless) x cell thermal voltage Vth (V), V - - References - ---------- - [1] C. B. Jones, C. W. Hansen, Single Diode Parameter Extraction from - In-Field Photovoltaic I-V Curves on a Single Board Computer, 46th IEEE - Photovoltaic Specialist Conference, Chicago, IL, 2019 - """ - # Find intercept and slope of linear portion of IV curve. - # Start with V < vlim * Voc, extend by adding points until slope is - # acceptable - beta = [np.nan for i in range(5)] - # get index of largest voltage less than/equal to limit - idx = _max_index(V, vlim * Voc) - while np.isnan(beta[1]) and (idx<=len(V)): - try: - coef = np.polyfit(V[:idx], I[:idx], deg=1) - if coef[0] < 0: - # intercept term - beta[0] = coef[1].item() - # sign change of slope to get positive parameter value - beta[1] = -coef[0].item() - except: - pass - if np.isnan(beta[1]): - idx += 1 - - if not np.isnan(beta[0]): - # Find parameters from exponential portion of IV curve - Y = beta[0] - beta[1] * V - I - X = np.array([np.ones_like(V), V, I]).T - idx = _min_index(Y, ilim * Isc) - try: - result = np.linalg.lstsq(X[idx:,], np.log(Y[idx:])) - coef = result[0] - beta[3] = coef[1].item() - beta[4] = coef[2].item() - except: - pass - - if not any([np.isnan(beta[i]) for i in [0, 1, 3, 4]]): - # calculate parameters - nNsVth = 1.0 / beta[3] - Rs = beta[4] / beta[3] - Gp = beta[1] / (1.0 - Rs * beta[1]) - Rsh = 1.0 / Gp - IL = (1 + Gp * Rs) * beta[0] - # calculate I0 - I0_Vmp = _calc_I0(IL, Imp, Vmp, Gp, Rs, beta[3]) - I0_Voc = _calc_I0(IL, 0, Voc, Gp, Rs, beta[3]) - if (I0_Vmp > 0) and (I0_Voc > 0): - I0 = 0.5 * (I0_Vmp + I0_Voc) - elif (I0_Vmp > 0): - I0 = I0_Vmp - elif (I0_Voc > 0): - I0 = I0_Voc - else: - I0 = np.nan - else: - IL = I0 = Rs = Rsh = nNsVth = np.nan - - return IL, I0, Rsh, Rs, nNsVth - - -def _calc_I0(IL, I, V, Gp, Rs, beta3): - return (IL - I - Gp * V - Gp * Rs * I) / np.exp(beta3 * (V + Rs * I)) - -def _max_index(x, xlim): - """ Finds maximum index of value of x <= xlim """ - return int(np.argwhere(x <= xlim)[-1]) - -def _min_index(x, xlim): - """ Finds minimum index of value of x > xlim """ - return int(np.argwhere(x > xlim)[0]) From eaee814955f1de0cbcda584f465fcf8ce30372bc Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Tue, 14 May 2019 13:15:07 -0600 Subject: [PATCH 09/68] change PySCC usage to wrapper --- pvlib/ivtools.py | 47 ++++++++++++----------------------------------- 1 file changed, 12 insertions(+), 35 deletions(-) diff --git a/pvlib/ivtools.py b/pvlib/ivtools.py index 4c6d275def..c9c62edf0d 100644 --- a/pvlib/ivtools.py +++ b/pvlib/ivtools.py @@ -64,41 +64,18 @@ def fit_cec_with_sam(celltype, Vmp, Imp, Voc, Isc, alpha_sc, beta_voc, current, in percent ''' - try: - ssc = PySSC.PySSC() - except Exception as e: - raise(e) - - data = ssc.data_create() - - ssc.data_set_string(data, b'celltype', celltype.encode('utf-8')) - ssc.data_set_number(data, b'Vmp', Vmp) - ssc.data_set_number(data, b'Imp', Imp) - ssc.data_set_number(data, b'Voc', Voc) - ssc.data_set_number(data, b'Isc', Isc) - ssc.data_set_number(data, b'alpha_isc', alpha_sc) - ssc.data_set_number(data, b'beta_voc', beta_voc) - ssc.data_set_number(data, b'gamma_pmp', gamma_pmp) - ssc.data_set_number(data, b'Nser', cells_in_series) - ssc.data_set_number(data, b'Tref', temp_ref) - - solver = ssc.module_create(b'6parsolve') - ssc.module_exec_set_print(0) - if ssc.module_exec(solver, data) == 0: - print('IV curve fit error') - idx = 1 - msg = ssc.module_log(solver, 0) - while (msg != None): - print(' : ' + msg.decode("utf - 8")) - msg = ssc.module_log(solver, idx) - idx = idx + 1 - ssc.module_free(solver) - a_ref = ssc.data_get_number(data, b'a') - I_L_ref = ssc.data_get_number(data, b'Il') - I_o_ref = ssc.data_get_number(data, b'Io') - R_s = ssc.data_get_number(data, b'Rs') - R_sh_ref = ssc.data_get_number(data, b'Rsh') - Adjust = ssc.data_get_number(data, b'Adj') + datadict = {"tech_model": '6parsolve' ,'celltype': celltype, 'Vmp': Vmp, + 'Imp': Imp, 'Voc': Voc, 'Isc': Isc, 'alpha_isc': alpha_sc, + 'beta_voc': beta_voc, 'gamma_pmp': gamma_pmp, + 'Nser': cells_in_series, 'Tref': temp_ref} + + result = PySSC.ssc_sim_from_dict(datadict) + a_ref = result['a'] + I_L_ref = result['Il'] + I_o_ref = result['Io'] + R_s = result['Rs'] + R_sh_ref = result['Rsh'] + Adjust = result['Adj'] return I_L_ref, I_o_ref, R_sh_ref, R_s, a_ref, Adjust From 4744bdeb0de34ea82ef3e7340e158173c6fd8485 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Tue, 14 May 2019 13:19:15 -0600 Subject: [PATCH 10/68] add nrel-pysam to ci/requirements --- ci/requirements-py27.yml | 1 + ci/requirements-py35.yml | 1 + ci/requirements-py36.yml | 1 + ci/requirements-py37.yml | 1 + 4 files changed, 4 insertions(+) diff --git a/ci/requirements-py27.yml b/ci/requirements-py27.yml index 12dde97000..6aa3630d97 100644 --- a/ci/requirements-py27.yml +++ b/ci/requirements-py27.yml @@ -21,3 +21,4 @@ dependencies: - pytest-mock - pytest-timeout - pvfactors==0.1.5 + - nrel-pysam \ No newline at end of file diff --git a/ci/requirements-py35.yml b/ci/requirements-py35.yml index fdbde57f58..932029f705 100644 --- a/ci/requirements-py35.yml +++ b/ci/requirements-py35.yml @@ -21,3 +21,4 @@ dependencies: - pytest-mock - pytest-timeout - pvfactors==0.1.5 + - nrel-pysam \ No newline at end of file diff --git a/ci/requirements-py36.yml b/ci/requirements-py36.yml index dfcff39773..cd78b72599 100644 --- a/ci/requirements-py36.yml +++ b/ci/requirements-py36.yml @@ -20,3 +20,4 @@ dependencies: - coveralls - pytest-mock - pvfactors==0.1.5 + - nrel-pysam diff --git a/ci/requirements-py37.yml b/ci/requirements-py37.yml index 527bf91142..c389dc64ab 100644 --- a/ci/requirements-py37.yml +++ b/ci/requirements-py37.yml @@ -20,3 +20,4 @@ dependencies: - coveralls - pytest-mock - pvfactors==0.1.5 + - nrel-pysam \ No newline at end of file From aae2731233f9eced4450479ec5a619d4fe3e98b3 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Tue, 14 May 2019 13:41:31 -0600 Subject: [PATCH 11/68] stickler, add pysam version in ci/requirements --- ci/requirements-py36.yml | 2 +- pvlib/ivtools.py | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/ci/requirements-py36.yml b/ci/requirements-py36.yml index cd78b72599..5a83aa64f8 100644 --- a/ci/requirements-py36.yml +++ b/ci/requirements-py36.yml @@ -20,4 +20,4 @@ dependencies: - coveralls - pytest-mock - pvfactors==0.1.5 - - nrel-pysam + - nrel-pysam==1.2.1 diff --git a/pvlib/ivtools.py b/pvlib/ivtools.py index c9c62edf0d..c6042f2f32 100644 --- a/pvlib/ivtools.py +++ b/pvlib/ivtools.py @@ -64,7 +64,7 @@ def fit_cec_with_sam(celltype, Vmp, Imp, Voc, Isc, alpha_sc, beta_voc, current, in percent ''' - datadict = {"tech_model": '6parsolve' ,'celltype': celltype, 'Vmp': Vmp, + datadict = {"tech_model": '6parsolve', 'celltype': celltype, 'Vmp': Vmp, 'Imp': Imp, 'Voc': Voc, 'Isc': Isc, 'alpha_isc': alpha_sc, 'beta_voc': beta_voc, 'gamma_pmp': gamma_pmp, 'Nser': cells_in_series, 'Tref': temp_ref} @@ -141,7 +141,7 @@ def fit_sde_sandia(V, I, Voc, Isc, Vmp, Imp, vlim=0.2, ilim=0.1): beta = [np.nan for i in range(5)] # get index of largest voltage less than/equal to limit idx = _max_index(V, vlim * Voc) - while np.isnan(beta[1]) and (idx<=len(V)): + while np.isnan(beta[1]) and (idx <= len(V)): try: coef = np.polyfit(V[:idx], I[:idx], deg=1) if coef[0] < 0: @@ -149,8 +149,8 @@ def fit_sde_sandia(V, I, Voc, Isc, Vmp, Imp, vlim=0.2, ilim=0.1): beta[0] = coef[1].item() # sign change of slope to get positive parameter value beta[1] = -coef[0].item() - except: - pass + except Exception as e: + raise(e) if np.isnan(beta[1]): idx += 1 @@ -160,12 +160,12 @@ def fit_sde_sandia(V, I, Voc, Isc, Vmp, Imp, vlim=0.2, ilim=0.1): X = np.array([np.ones_like(V), V, I]).T idx = _min_index(Y, ilim * Isc) try: - result = np.linalg.lstsq(X[idx:,], np.log(Y[idx:])) + result = np.linalg.lstsq(X[idx:, ], np.log(Y[idx:])) coef = result[0] beta[3] = coef[1].item() beta[4] = coef[2].item() - except: - pass + except Exception as e: + raise(e) if not any([np.isnan(beta[i]) for i in [0, 1, 3, 4]]): # calculate parameters From e1697b0fe1601833487ba28baf38b5102eb6a811 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Tue, 14 May 2019 14:13:28 -0600 Subject: [PATCH 12/68] does case matter? --- ci/requirements-py36.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/requirements-py36.yml b/ci/requirements-py36.yml index 5a83aa64f8..fcaa83d919 100644 --- a/ci/requirements-py36.yml +++ b/ci/requirements-py36.yml @@ -20,4 +20,4 @@ dependencies: - coveralls - pytest-mock - pvfactors==0.1.5 - - nrel-pysam==1.2.1 + - NREL-PySAM==1.2.1 From e779c7a1e56b47be63c815820686c767812fe393 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Tue, 14 May 2019 15:25:01 -0600 Subject: [PATCH 13/68] remove pysam for py27, move import pysam to try/except --- ci/requirements-py27.yml | 3 +-- pvlib/ivtools.py | 9 ++++++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/ci/requirements-py27.yml b/ci/requirements-py27.yml index 6aa3630d97..0b2565b739 100644 --- a/ci/requirements-py27.yml +++ b/ci/requirements-py27.yml @@ -20,5 +20,4 @@ dependencies: - coveralls - pytest-mock - pytest-timeout - - pvfactors==0.1.5 - - nrel-pysam \ No newline at end of file + - pvfactors==0.1.5 \ No newline at end of file diff --git a/pvlib/ivtools.py b/pvlib/ivtools.py index c6042f2f32..e94b67f8e3 100644 --- a/pvlib/ivtools.py +++ b/pvlib/ivtools.py @@ -6,7 +6,6 @@ """ import numpy as np -from PySAM import PySSC def fit_cec_with_sam(celltype, Vmp, Imp, Voc, Isc, alpha_sc, beta_voc, @@ -62,8 +61,16 @@ def fit_cec_with_sam(celltype, Vmp, Imp, Voc, Isc, alpha_sc, beta_voc, Adjust : float The adjustment to the temperature coefficient for short circuit current, in percent + + Raises: + ImportError if NREL-PySAM is not installed ''' + try: + from PySAM import PySSC + except ImportError as e: + raise(e) + datadict = {"tech_model": '6parsolve', 'celltype': celltype, 'Vmp': Vmp, 'Imp': Imp, 'Voc': Voc, 'Isc': Isc, 'alpha_isc': alpha_sc, 'beta_voc': beta_voc, 'gamma_pmp': gamma_pmp, From b0f0a9d44f39505202654e1d916bf5158a714cd8 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Tue, 14 May 2019 15:40:05 -0600 Subject: [PATCH 14/68] add requires_scipy --- pvlib/test/test_ivtools.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pvlib/test/test_ivtools.py b/pvlib/test/test_ivtools.py index 71397e970a..27aceadf89 100644 --- a/pvlib/test/test_ivtools.py +++ b/pvlib/test/test_ivtools.py @@ -8,12 +8,14 @@ import numpy as np from pvlib import pvsystem from pvlib.ivtools import fit_sde_sandia +from conftest import requires_scipy def get_test_iv_params(): return {'IL': 8.0, 'I0': 5e-10, 'Rsh': 1000, 'Rs': 0.2, 'nNsVth': 1.61864} +@requires_scipy def test_fit_sde_sandia(): test_params = get_test_iv_params() testcurve = pvsystem.singlediode(photocurrent=test_params['IL'], From df331137ccee6c353ae5fa537b781f39d078f8e8 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Tue, 14 May 2019 16:13:31 -0600 Subject: [PATCH 15/68] rename variables for style guide --- pvlib/ivtools.py | 90 +++++++++++++++++++------------------- pvlib/test/test_ivtools.py | 6 +-- 2 files changed, 49 insertions(+), 47 deletions(-) diff --git a/pvlib/ivtools.py b/pvlib/ivtools.py index e94b67f8e3..5de28e6da8 100644 --- a/pvlib/ivtools.py +++ b/pvlib/ivtools.py @@ -8,7 +8,7 @@ import numpy as np -def fit_cec_with_sam(celltype, Vmp, Imp, Voc, Isc, alpha_sc, beta_voc, +def fit_cec_with_sam(celltype, v_mp, i_mp, v_oc, i_sc, alpha_sc, beta_voc, gamma_pmp, cells_in_series, temp_ref=25): ''' Estimates parameters for the CEC single diode model using the SAM SDK. @@ -18,13 +18,13 @@ def fit_cec_with_sam(celltype, Vmp, Imp, Voc, Isc, alpha_sc, beta_voc, celltype : str Value is one of 'monoSi', 'multiSi', 'polySi', 'cis', 'cigs', 'cdte', 'amorphous' - Vmp : float + v_mp : float Voltage at maximum power point at standard test condition (STC) - Imp : float + i_mp : float Current at maximum power point at STC Voc : float Open circuit voltage at STC - Isc : float + i_sc : float Short circuit current at STC alpha_sc : float Temperature coefficient of short circuit current at STC, A/C @@ -71,8 +71,8 @@ def fit_cec_with_sam(celltype, Vmp, Imp, Voc, Isc, alpha_sc, beta_voc, except ImportError as e: raise(e) - datadict = {"tech_model": '6parsolve', 'celltype': celltype, 'Vmp': Vmp, - 'Imp': Imp, 'Voc': Voc, 'Isc': Isc, 'alpha_isc': alpha_sc, + datadict = {"tech_model": '6parsolve', 'celltype': celltype, 'Vmp': v_mp, + 'Imp': i_mp, 'Voc': v_oc, 'Isc': i_sc, 'alpha_isc': alpha_sc, 'beta_voc': beta_voc, 'gamma_pmp': gamma_pmp, 'Nser': cells_in_series, 'Tref': temp_ref} @@ -87,54 +87,56 @@ def fit_cec_with_sam(celltype, Vmp, Imp, Voc, Isc, alpha_sc, beta_voc, return I_L_ref, I_o_ref, R_sh_ref, R_s, a_ref, Adjust -def fit_sde_sandia(V, I, Voc, Isc, Vmp, Imp, vlim=0.2, ilim=0.1): +def fit_sde_sandia(v, i, v_oc, i_sc, v_mp, i_mp, vlim=0.2, ilim=0.1): """ Fits the single diode equation to an IV curve. If fitting fails, returns NaN in each parameter. Parameters ---------- - V : numeric - Voltage at each point on the IV curve, from 0 to Voc + v : numeric + Voltage at each point on the IV curve, from 0 to v_oc - I : numeric - Current at each point on the IV curve, from Isc to 0 + i : numeric + Current at each point on the IV curve, from i_sc to 0 - Voc : float + v_oc : float Open circuit voltage - Isc : float + i_sc : float Short circuit current - Vmp : float + v_mp : float Voltage at maximum power point - Imp : float + i_mp : float Current at maximum power point vlim : float, default 0.2 - defines linear portion of IV curve i.e. V <= vlim * Voc + defines linear portion of IV curve i.e. V <= vlim * v_oc ilim : float, default 0.1 - defines exponential portion of IV curve i.e. I > ilim * Isc + defines exponential portion of IV curve i.e. I > ilim * i_sc Returns ------- - IL : float - photocurrent, A + tuple of the following elements: + + photocurrent : float + photocurrent, A - I0 : float - dark (saturation) current, A + saturation_current : float + dark (saturation) current, A - Rsh : float - shunt (parallel) resistance, ohm + resistance_shunt : float + shunt (parallel) resistance, ohm - Rs : float - series resistance, ohm + resistance_series : float + series resistance, ohm - nNsVth : float - product of diode (ideality) factor n (unitless) x number of - cells in series Ns (unitless) x cell thermal voltage Vth (V), V + nNsVth : float + product of diode (ideality) factor n (unitless) x number of + cells in series Ns (unitless) x cell thermal voltage Vth (V), V References ---------- @@ -143,14 +145,14 @@ def fit_sde_sandia(V, I, Voc, Isc, Vmp, Imp, vlim=0.2, ilim=0.1): Photovoltaic Specialist Conference, Chicago, IL, 2019 """ # Find intercept and slope of linear portion of IV curve. - # Start with V < vlim * Voc, extend by adding points until slope is + # Start with V < vlim * v_oc, extend by adding points until slope is # acceptable beta = [np.nan for i in range(5)] # get index of largest voltage less than/equal to limit - idx = _max_index(V, vlim * Voc) - while np.isnan(beta[1]) and (idx <= len(V)): + idx = _max_index(v, vlim * v_oc) + while np.isnan(beta[1]) and (idx <= len(v)): try: - coef = np.polyfit(V[:idx], I[:idx], deg=1) + coef = np.polyfit(v[:idx], i[:idx], deg=1) if coef[0] < 0: # intercept term beta[0] = coef[1].item() @@ -163,11 +165,11 @@ def fit_sde_sandia(V, I, Voc, Isc, Vmp, Imp, vlim=0.2, ilim=0.1): if not np.isnan(beta[0]): # Find parameters from exponential portion of IV curve - Y = beta[0] - beta[1] * V - I - X = np.array([np.ones_like(V), V, I]).T - idx = _min_index(Y, ilim * Isc) + y = beta[0] - beta[1] * v - i + x = np.array([np.ones_like(v), v, i]).T + idx = _min_index(y, ilim * i_sc) try: - result = np.linalg.lstsq(X[idx:, ], np.log(Y[idx:])) + result = np.linalg.lstsq(x[idx:, ], np.log(y[idx:])) coef = result[0] beta[3] = coef[1].item() beta[4] = coef[2].item() @@ -182,14 +184,14 @@ def fit_sde_sandia(V, I, Voc, Isc, Vmp, Imp, vlim=0.2, ilim=0.1): Rsh = 1.0 / Gp IL = (1 + Gp * Rs) * beta[0] # calculate I0 - I0_Vmp = _calc_I0(IL, Imp, Vmp, Gp, Rs, beta[3]) - I0_Voc = _calc_I0(IL, 0, Voc, Gp, Rs, beta[3]) - if (I0_Vmp > 0) and (I0_Voc > 0): - I0 = 0.5 * (I0_Vmp + I0_Voc) - elif (I0_Vmp > 0): - I0 = I0_Vmp - elif (I0_Voc > 0): - I0 = I0_Voc + I0_v_mp = _calc_I0(IL, i_mp, v_mp, Gp, Rs, beta[3]) + I0_v_oc = _calc_I0(IL, 0, v_oc, Gp, Rs, beta[3]) + if (I0_v_mp > 0) and (I0_v_oc > 0): + I0 = 0.5 * (I0_v_mp + I0_v_oc) + elif (I0_v_mp > 0): + I0 = I0_v_mp + elif (I0_v_oc > 0): + I0 = I0_v_oc else: I0 = np.nan else: diff --git a/pvlib/test/test_ivtools.py b/pvlib/test/test_ivtools.py index 27aceadf89..d2e073d310 100644 --- a/pvlib/test/test_ivtools.py +++ b/pvlib/test/test_ivtools.py @@ -26,7 +26,7 @@ def test_fit_sde_sandia(): ivcurve_pnts=300) expected = tuple(test_params[k] for k in ['IL', 'I0', 'Rsh', 'Rs', 'nNsVth']) - result = fit_sde_sandia(V=testcurve['v'], I=testcurve['i'], - Voc=testcurve['v_oc'], Isc=testcurve['i_sc'], - Vmp=testcurve['v_mp'], Imp=testcurve['i_mp']) + result = fit_sde_sandia(v=testcurve['v'], i=testcurve['i'], + v_oc=testcurve['v_oc'], i_sc=testcurve['i_sc'], + v_mp=testcurve['v_mp'], i_mp=testcurve['i_mp']) assert np.allclose(result, expected, rtol=5e-5) From b64cf57e5cce5463212b8e9ccabcb2606a005357 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Fri, 17 May 2019 21:51:44 -0400 Subject: [PATCH 16/68] add test for fit_cec_with_sam --- pvlib/ivtools.py | 3 +- pvlib/test/test_ivtools.py | 64 +++++++++++++++++++++++++++++++++++--- 2 files changed, 62 insertions(+), 5 deletions(-) diff --git a/pvlib/ivtools.py b/pvlib/ivtools.py index 5de28e6da8..22ab9e554d 100644 --- a/pvlib/ivtools.py +++ b/pvlib/ivtools.py @@ -71,7 +71,8 @@ def fit_cec_with_sam(celltype, v_mp, i_mp, v_oc, i_sc, alpha_sc, beta_voc, except ImportError as e: raise(e) - datadict = {"tech_model": '6parsolve', 'celltype': celltype, 'Vmp': v_mp, + datadict = {'tech_model': '6parsolve', 'financial_model': 'none', + 'celltype': celltype, 'Vmp': v_mp, 'Imp': i_mp, 'Voc': v_oc, 'Isc': i_sc, 'alpha_isc': alpha_sc, 'beta_voc': beta_voc, 'gamma_pmp': gamma_pmp, 'Nser': cells_in_series, 'Tref': temp_ref} diff --git a/pvlib/test/test_ivtools.py b/pvlib/test/test_ivtools.py index d2e073d310..259daf6320 100644 --- a/pvlib/test/test_ivtools.py +++ b/pvlib/test/test_ivtools.py @@ -6,8 +6,10 @@ """ import numpy as np +import pandas as pd +import pytest from pvlib import pvsystem -from pvlib.ivtools import fit_sde_sandia +from pvlib import ivtools from conftest import requires_scipy @@ -15,6 +17,27 @@ def get_test_iv_params(): return {'IL': 8.0, 'I0': 5e-10, 'Rsh': 1000, 'Rs': 0.2, 'nNsVth': 1.61864} +def get_cec_params_cansol_cs5p_220p(): + return {'V_mp_ref': 46.6, 'I_mp_ref': 4.73, 'V_oc_ref': 58.3, + 'I_sc_ref': 5.05, 'alpha_isc': 0.000495, 'beta_voc': -0.003372, + 'gamma_pmp': -0.43, 'cells_in_series': 96} + + +@pytest.fixture() +def sam_data(): + data = {} + data['cecmod'] = pvsystem.retrieve_sam('cecmod') + return data + + +@pytest.fixture() +def cec_module_parameters(sam_data): + modules = sam_data['cecmod'] + module = "Canadian_Solar_CS5P_220P" + module_parameters = modules[module] + return module_parameters + + @requires_scipy def test_fit_sde_sandia(): test_params = get_test_iv_params() @@ -26,7 +49,40 @@ def test_fit_sde_sandia(): ivcurve_pnts=300) expected = tuple(test_params[k] for k in ['IL', 'I0', 'Rsh', 'Rs', 'nNsVth']) - result = fit_sde_sandia(v=testcurve['v'], i=testcurve['i'], - v_oc=testcurve['v_oc'], i_sc=testcurve['i_sc'], - v_mp=testcurve['v_mp'], i_mp=testcurve['i_mp']) + result = ivtools.fit_sde_sandia(v=testcurve['v'], i=testcurve['i'], + v_oc=testcurve['v_oc'], i_sc=testcurve['i_sc'], v_mp=testcurve['v_mp'], + i_mp=testcurve['i_mp']) assert np.allclose(result, expected, rtol=5e-5) + + +def test_fit_cec_with_SAM(): + sam_parameters = cec_module_parameters(sam_data) + cec_list_data = get_cec_params_cansol_cs5p_220p() + # convert from %/C to A/C and V/C + alpha_sc = cec_list_data['alpha_isc'] * cec_list_data['I_sc_ref'] + beta_oc = cec_list_data['beta_voc'] * cec_list_data['V_oc_ref'] + + I_L_ref, I_o_ref, R_sh_ref, R_s, a_ref, Adjust = \ + ivtools.fit_cec_with_sam(celltype='polySi', + v_mp=cec_list_data['V_mp_ref'], i_mp=cec_list_data['I_mp_ref'], + v_oc=cec_list_data['V_oc_ref'], i_sc=cec_list_data['I_sc_ref'], + alpha_sc=alpha_sc, beta_voc=beta_oc, + gamma_pmp=cec_list_data['gamma_pmp'], + cells_in_series=cec_list_data['cells_in_series'], temp_ref=25) + modeled = pd.Series(index=sam_parameters.index, data=cec_list_data) + modeled['a_ref'] = a_ref + modeled['I_L_ref'] = I_L_ref + modeled['I_o_ref'] = I_o_ref + modeled['R_sh_ref'] = R_sh_ref + modeled['R_s'] = R_s + modeled['Adjust'] = Adjust + modeled['alpha_sc'] = alpha_sc + modeled['beta_oc'] = beta_oc + modeled['gamma_r'] = cec_list_data['gamma_pmp'] + modeled['N_s'] = cec_list_data['cells_in_series'] + modeled = modeled.dropna() + expected = pd.Series(index=modeled.index, data=np.nan) + for k in modeled.keys(): + expected[k] = sam_parameters[k] + assert np.isclose(modeled.values, expected.values, rtol=1e-2) + From c8004ff543bafaa3c090d49c2846fffa9f4942d1 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Fri, 17 May 2019 22:03:31 -0400 Subject: [PATCH 17/68] fix conflict in requirements --- ci/requirements-py27.yml | 4 ---- ci/requirements-py35.yml | 4 ---- ci/requirements-py36.yml | 7 ++----- ci/requirements-py37.yml | 4 ---- 4 files changed, 2 insertions(+), 17 deletions(-) diff --git a/ci/requirements-py27.yml b/ci/requirements-py27.yml index 63ef28e5fb..d69a1b8ad8 100644 --- a/ci/requirements-py27.yml +++ b/ci/requirements-py27.yml @@ -20,8 +20,4 @@ dependencies: - coveralls - pytest-mock - pytest-timeout -<<<<<<< HEAD - - pvfactors==0.1.5 -======= - pvfactors==1.0.1 ->>>>>>> f38fe07f786b49b3b21f63bbc8bcec18bb42af7c diff --git a/ci/requirements-py35.yml b/ci/requirements-py35.yml index ffb7f9f007..802d153083 100644 --- a/ci/requirements-py35.yml +++ b/ci/requirements-py35.yml @@ -20,9 +20,5 @@ dependencies: - coveralls - pytest-mock - pytest-timeout -<<<<<<< HEAD - - pvfactors==0.1.5 - nrel-pysam -======= - pvfactors==1.0.1 ->>>>>>> f38fe07f786b49b3b21f63bbc8bcec18bb42af7c diff --git a/ci/requirements-py36.yml b/ci/requirements-py36.yml index 592d70cb10..1d1a6dfb33 100644 --- a/ci/requirements-py36.yml +++ b/ci/requirements-py36.yml @@ -19,10 +19,7 @@ dependencies: - pip: - coveralls - pytest-mock -<<<<<<< HEAD - - pvfactors==0.1.5 - - NREL-PySAM==1.2.1 -======= + - nrel-pysam==1.2.1 - pytest-timeout - pvfactors==1.0.1 ->>>>>>> f38fe07f786b49b3b21f63bbc8bcec18bb42af7c +s \ No newline at end of file diff --git a/ci/requirements-py37.yml b/ci/requirements-py37.yml index 2d12abb53b..4d4e94e922 100644 --- a/ci/requirements-py37.yml +++ b/ci/requirements-py37.yml @@ -19,10 +19,6 @@ dependencies: - pip: - coveralls - pytest-mock -<<<<<<< HEAD - - pvfactors==0.1.5 - nrel-pysam -======= - pytest-timeout - pvfactors==1.0.1 ->>>>>>> f38fe07f786b49b3b21f63bbc8bcec18bb42af7c From 586a1343df39e4dc79b980d5cf273ea4e7ec1fb0 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Fri, 17 May 2019 22:23:23 -0400 Subject: [PATCH 18/68] fix typo, raise syntax --- ci/requirements-py36.yml | 1 - pvlib/ivtools.py | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/ci/requirements-py36.yml b/ci/requirements-py36.yml index 1d1a6dfb33..d0dd9a2844 100644 --- a/ci/requirements-py36.yml +++ b/ci/requirements-py36.yml @@ -22,4 +22,3 @@ dependencies: - nrel-pysam==1.2.1 - pytest-timeout - pvfactors==1.0.1 -s \ No newline at end of file diff --git a/pvlib/ivtools.py b/pvlib/ivtools.py index 22ab9e554d..6e04d2a113 100644 --- a/pvlib/ivtools.py +++ b/pvlib/ivtools.py @@ -160,7 +160,7 @@ def fit_sde_sandia(v, i, v_oc, i_sc, v_mp, i_mp, vlim=0.2, ilim=0.1): # sign change of slope to get positive parameter value beta[1] = -coef[0].item() except Exception as e: - raise(e) + raise e if np.isnan(beta[1]): idx += 1 @@ -175,7 +175,7 @@ def fit_sde_sandia(v, i, v_oc, i_sc, v_mp, i_mp, vlim=0.2, ilim=0.1): beta[3] = coef[1].item() beta[4] = coef[2].item() except Exception as e: - raise(e) + raise e if not any([np.isnan(beta[i]) for i in [0, 1, 3, 4]]): # calculate parameters From 6d6733d0bf31533f24734b4c31a6b46394879180 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Fri, 17 May 2019 22:30:14 -0400 Subject: [PATCH 19/68] remove ivtools dir --- pvlib/ivtools/ivcurves.py | 64 --------------------------------------- 1 file changed, 64 deletions(-) delete mode 100644 pvlib/ivtools/ivcurves.py diff --git a/pvlib/ivtools/ivcurves.py b/pvlib/ivtools/ivcurves.py deleted file mode 100644 index 50fe36b972..0000000000 --- a/pvlib/ivtools/ivcurves.py +++ /dev/null @@ -1,64 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Created on Fri May 3 14:16:24 2019 - -@author: cwhanse -""" - -import numpy as np - - -class IVCurves(): - """ - Contains IV curves and methods for fitting models to the curves. - """ - - def __init__(self, data): - IVCurves.ivdata = data - IVCurves.voc = None - - def __repr(self): - pass - - def __print__(self): - pass - - def fit(): - """ - Fit a model to IV curve data. - """ - pass - - -class _IVCurve(): - """ - Contains a single IV curve - """ - - def __init__(self, V, I, Ee, Tc, Voc=None, Isc=None, Vmp=None, Imp=None): - self.V = V - self.I = I - self.Ee = Ee - self.Tc = Tc - if Voc is None: - self.Voc = V[-1] - if Isc is None: - self.Isc = I[0] - if Vmp is None: - self.Vmp, self.Imp = find_max_power(V, I) - - -def find_max_power(V, I): - """ Finds V, I pair where V*I is maximum - - Parameters - ---------- - V : numeric - I : numeric - - Returns - ------- - Vmax, Imax : values from V and I where V*I is maximum - """ - idx = np.argmax(V * I) - return V[idx], I[idx] From 98af4ffc3b939f1d44d448d2a20888f3e2ae9756 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Sat, 18 May 2019 12:40:57 -0600 Subject: [PATCH 20/68] fix pytest fixture usage --- pvlib/test/test_ivtools.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pvlib/test/test_ivtools.py b/pvlib/test/test_ivtools.py index 259daf6320..dc8895e183 100644 --- a/pvlib/test/test_ivtools.py +++ b/pvlib/test/test_ivtools.py @@ -55,8 +55,8 @@ def test_fit_sde_sandia(): assert np.allclose(result, expected, rtol=5e-5) -def test_fit_cec_with_SAM(): - sam_parameters = cec_module_parameters(sam_data) +def test_fit_cec_with_SAM(cec_module_parameters): + sam_parameters = cec_module_parameters cec_list_data = get_cec_params_cansol_cs5p_220p() # convert from %/C to A/C and V/C alpha_sc = cec_list_data['alpha_isc'] * cec_list_data['I_sc_ref'] From 26c8f38a2ad1a60d0dcb9cd01ce66a99e3c83561 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Sat, 18 May 2019 13:02:25 -0600 Subject: [PATCH 21/68] linter, fix np.allclose --- pvlib/test/test_ivtools.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/pvlib/test/test_ivtools.py b/pvlib/test/test_ivtools.py index dc8895e183..ef6974b8a8 100644 --- a/pvlib/test/test_ivtools.py +++ b/pvlib/test/test_ivtools.py @@ -50,8 +50,10 @@ def test_fit_sde_sandia(): expected = tuple(test_params[k] for k in ['IL', 'I0', 'Rsh', 'Rs', 'nNsVth']) result = ivtools.fit_sde_sandia(v=testcurve['v'], i=testcurve['i'], - v_oc=testcurve['v_oc'], i_sc=testcurve['i_sc'], v_mp=testcurve['v_mp'], - i_mp=testcurve['i_mp']) + v_oc=testcurve['v_oc'], + i_sc=testcurve['i_sc'], + v_mp=testcurve['v_mp'], + i_mp=testcurve['i_mp']) assert np.allclose(result, expected, rtol=5e-5) @@ -84,5 +86,4 @@ def test_fit_cec_with_SAM(cec_module_parameters): expected = pd.Series(index=modeled.index, data=np.nan) for k in modeled.keys(): expected[k] = sam_parameters[k] - assert np.isclose(modeled.values, expected.values, rtol=1e-2) - + assert np.allclose(modeled.values, expected.values, rtol=1e-2) From 1deb1484d440deda8b2811e2b81ffb57501d5b27 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Sat, 18 May 2019 14:22:59 -0600 Subject: [PATCH 22/68] relax test condition --- pvlib/test/test_ivtools.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/pvlib/test/test_ivtools.py b/pvlib/test/test_ivtools.py index ef6974b8a8..6e70f92849 100644 --- a/pvlib/test/test_ivtools.py +++ b/pvlib/test/test_ivtools.py @@ -25,14 +25,12 @@ def get_cec_params_cansol_cs5p_220p(): @pytest.fixture() def sam_data(): - data = {} - data['cecmod'] = pvsystem.retrieve_sam('cecmod') - return data + return pvsystem.retrieve_sam('cecmod') @pytest.fixture() def cec_module_parameters(sam_data): - modules = sam_data['cecmod'] + modules = sam_data module = "Canadian_Solar_CS5P_220P" module_parameters = modules[module] return module_parameters @@ -57,7 +55,7 @@ def test_fit_sde_sandia(): assert np.allclose(result, expected, rtol=5e-5) -def test_fit_cec_with_SAM(cec_module_parameters): +def test_fit_cec_with_sam(cec_module_parameters): sam_parameters = cec_module_parameters cec_list_data = get_cec_params_cansol_cs5p_220p() # convert from %/C to A/C and V/C @@ -86,4 +84,4 @@ def test_fit_cec_with_SAM(cec_module_parameters): expected = pd.Series(index=modeled.index, data=np.nan) for k in modeled.keys(): expected[k] = sam_parameters[k] - assert np.allclose(modeled.values, expected.values, rtol=1e-2) + assert np.allclose(modeled.values, expected.values, rtol=5e-2) From 83086356476b67d7ea7e72790d5702a590a62dce Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Fri, 24 May 2019 16:38:07 -0600 Subject: [PATCH 23/68] remove try/except, add to docstring, use helper functions --- pvlib/ivtools.py | 152 ++++++++++++++++++++++++++++++----------------- 1 file changed, 98 insertions(+), 54 deletions(-) diff --git a/pvlib/ivtools.py b/pvlib/ivtools.py index 6e04d2a113..6ea4f4cdc8 100644 --- a/pvlib/ivtools.py +++ b/pvlib/ivtools.py @@ -139,54 +139,113 @@ def fit_sde_sandia(v, i, v_oc, i_sc, v_mp, i_mp, vlim=0.2, ilim=0.1): product of diode (ideality) factor n (unitless) x number of cells in series Ns (unitless) x cell thermal voltage Vth (V), V + Notes + ----- + :py:func:`fit_sde_sandia` obtains values for the five parameters for the + single diode equation [1] + + .. math:: + + I = IL - I0*[exp((V+I*Rs)/(nNsVth))-1] - (V + I*Rs)/Rsh + + :py:func:`pvsystem.singlediode` for definition of the parameters. + + The fitting method proceeds in four steps: + 1) simplify the single diode equation + + .. math:: + + I = IL - I0*exp((V+I*Rs)/(nNsVth)) - (V + I*Rs)/Rsh + + 2) replace Rsh = 1/Gp and re-arrange + + .. math:: + + I = IL/(1+Gp*Rs) - (Gp*V)/(1+Gp*Rs) - I0/(1+Gp*Rs)*exp((V+I*Rs)/(nNsVth)) + + 3) fit the linear portion of the IV curve V <= vlim * v_oc + + .. math:: + + I ~ IL/(1+Gp*Rs) - (Gp*V)/(1+Gp*Rs) = beta0 + beta1*V + + 4) fit the exponential portion of the IV curve I > ilim * i_sc + + .. math:: + + log(beta0 - beta1*V - I) ~ log((I0)/(1+Gp*Rs)) + (V)/(nNsVth) + + (Rs*I)/(nNsVth) = beta2 + beta3*V + beta4*I + + Values for ``IL, I0, Rs, Rsh,`` and ``nNsVth`` are calculated from the + regression coefficents beta0, beta1, beta3 and beta4. + References ---------- - [1] C. B. Jones, C. W. Hansen, Single Diode Parameter Extraction from + [1] S.R. Wenham, M.A. Green, M.E. Watt, "Applied Photovoltaics" ISBN + 0 86758 909 4 + [2] C. B. Jones, C. W. Hansen, Single Diode Parameter Extraction from In-Field Photovoltaic I-V Curves on a Single Board Computer, 46th IEEE Photovoltaic Specialist Conference, Chicago, IL, 2019 """ - # Find intercept and slope of linear portion of IV curve. - # Start with V < vlim * v_oc, extend by adding points until slope is + + # Find beta0 and beta1 from linear portion of the IV curve + beta0, beta1 = _find_beta0_beta1(v, i, vlim, v_oc) + + if not np.isnan(beta0): + # Find beta3 and beta4 from exponential portion of IV curve + y = beta0 - beta1 * v - i + x = np.array([np.ones_like(v), v, i]).T + beta3, beta4 = _find_beta3_beta4(y, x, ilim, i_sc) + + # calculate single diode parameters from regression coefficients + IL, I0, Rsh, Rs, nNsVth = _calculate_sde_parameters(beta0, beta1, beta3, + beta4, v_mp, i_mp, v_oc) + + return IL, I0, Rsh, Rs, nNsVth + + +def _calc_I0(IL, I, V, Gp, Rs, beta3): + return (IL - I - Gp * V - Gp * Rs * I) / np.exp(beta3 * (V + Rs * I)) + + +def _find_beta0_beta1(v, i, vlim, v_oc): + # Get intercept and slope of linear portion of IV curve. + # Start with V =< vlim * v_oc, extend by adding points until slope is # acceptable - beta = [np.nan for i in range(5)] - # get index of largest voltage less than/equal to limit - idx = _max_index(v, vlim * v_oc) - while np.isnan(beta[1]) and (idx <= len(v)): - try: - coef = np.polyfit(v[:idx], i[:idx], deg=1) - if coef[0] < 0: - # intercept term - beta[0] = coef[1].item() - # sign change of slope to get positive parameter value - beta[1] = -coef[0].item() - except Exception as e: - raise e - if np.isnan(beta[1]): + beta0 = np.nan + beta1 = np.nan + idx = np.searchsorted(v, vlim * v_oc) + while idx <= len(v): + coef = np.polyfit(v[:idx], i[:idx], deg=1) + if coef[0] < 0: + # intercept term + beta0 = coef[1].item() + # sign change of slope to get positive parameter value + beta1 = -coef[0].item() + else: idx += 1 + return beta0, beta1 - if not np.isnan(beta[0]): - # Find parameters from exponential portion of IV curve - y = beta[0] - beta[1] * v - i - x = np.array([np.ones_like(v), v, i]).T - idx = _min_index(y, ilim * i_sc) - try: - result = np.linalg.lstsq(x[idx:, ], np.log(y[idx:])) - coef = result[0] - beta[3] = coef[1].item() - beta[4] = coef[2].item() - except Exception as e: - raise e - - if not any([np.isnan(beta[i]) for i in [0, 1, 3, 4]]): - # calculate parameters - nNsVth = 1.0 / beta[3] - Rs = beta[4] / beta[3] - Gp = beta[1] / (1.0 - Rs * beta[1]) + +def _find_beta3_beta4(y, x, ilim, i_sc): + idx = np.searchsorted(y, ilim * i_sc) - 1 + result = np.linalg.lstsq(x[idx:, ], np.log(y[idx:])) + coef = result[0] + beta3 = coef[1].item() + beta4 = coef[2].item() + return beta3, beta4 + + +def _calculate_sde_parameters(beta0, beta1, beta3, beta4, v_mp, i_mp, v_oc): + if not any(np.isnan([beta0, beta1, beta3, beta4])): + nNsVth = 1.0 / beta3 + Rs = beta4 / beta3 + Gp = beta1 / (1.0 - Rs * beta1) Rsh = 1.0 / Gp - IL = (1 + Gp * Rs) * beta[0] + IL = (1 + Gp * Rs) * beta0 # calculate I0 - I0_v_mp = _calc_I0(IL, i_mp, v_mp, Gp, Rs, beta[3]) - I0_v_oc = _calc_I0(IL, 0, v_oc, Gp, Rs, beta[3]) + I0_v_mp = _calc_I0(IL, i_mp, v_mp, Gp, Rs, beta3) + I0_v_oc = _calc_I0(IL, 0, v_oc, Gp, Rs, beta3) if (I0_v_mp > 0) and (I0_v_oc > 0): I0 = 0.5 * (I0_v_mp + I0_v_oc) elif (I0_v_mp > 0): @@ -196,20 +255,5 @@ def fit_sde_sandia(v, i, v_oc, i_sc, v_mp, i_mp, vlim=0.2, ilim=0.1): else: I0 = np.nan else: - IL = I0 = Rs = Rsh = nNsVth = np.nan - + IL = I0 = Rsh = Rs = nNsVth = np.nan return IL, I0, Rsh, Rs, nNsVth - - -def _calc_I0(IL, I, V, Gp, Rs, beta3): - return (IL - I - Gp * V - Gp * Rs * I) / np.exp(beta3 * (V + Rs * I)) - - -def _max_index(x, xlim): - """ Finds maximum index of value of x <= xlim """ - return int(np.argwhere(x <= xlim)[-1]) - - -def _min_index(x, xlim): - """ Finds minimum index of value of x > xlim """ - return int(np.argwhere(x > xlim)[0]) From 632234e6ffcb92347007822f13e4c271ac30881a Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Mon, 27 May 2019 20:50:37 -0600 Subject: [PATCH 24/68] add requires_pysam --- pvlib/ivtools.py | 8 +++++--- pvlib/test/conftest.py | 12 ++++++++++++ pvlib/test/test_ivtools.py | 1 + 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/pvlib/ivtools.py b/pvlib/ivtools.py index 6ea4f4cdc8..5386682c56 100644 --- a/pvlib/ivtools.py +++ b/pvlib/ivtools.py @@ -161,7 +161,8 @@ def fit_sde_sandia(v, i, v_oc, i_sc, v_mp, i_mp, vlim=0.2, ilim=0.1): .. math:: - I = IL/(1+Gp*Rs) - (Gp*V)/(1+Gp*Rs) - I0/(1+Gp*Rs)*exp((V+I*Rs)/(nNsVth)) + I = IL/(1+Gp*Rs) - (Gp*V)/(1+Gp*Rs) - + I0/(1+Gp*Rs)*exp((V+I*Rs)/(nNsVth)) 3) fit the linear portion of the IV curve V <= vlim * v_oc @@ -173,7 +174,7 @@ def fit_sde_sandia(v, i, v_oc, i_sc, v_mp, i_mp, vlim=0.2, ilim=0.1): .. math:: - log(beta0 - beta1*V - I) ~ log((I0)/(1+Gp*Rs)) + (V)/(nNsVth) + + log(beta0 - beta1*V - I) ~ log((I0)/(1+Gp*Rs)) + (V)/(nNsVth) + (Rs*I)/(nNsVth) = beta2 + beta3*V + beta4*I Values for ``IL, I0, Rs, Rsh,`` and ``nNsVth`` are calculated from the @@ -199,7 +200,8 @@ def fit_sde_sandia(v, i, v_oc, i_sc, v_mp, i_mp, vlim=0.2, ilim=0.1): # calculate single diode parameters from regression coefficients IL, I0, Rsh, Rs, nNsVth = _calculate_sde_parameters(beta0, beta1, beta3, - beta4, v_mp, i_mp, v_oc) + beta4, v_mp, i_mp, + v_oc) return IL, I0, Rsh, Rs, nNsVth diff --git a/pvlib/test/conftest.py b/pvlib/test/conftest.py index e91adb8689..1ef6318732 100644 --- a/pvlib/test/conftest.py +++ b/pvlib/test/conftest.py @@ -110,6 +110,18 @@ def has_spa_c(): requires_spa_c = pytest.mark.skipif(not has_spa_c(), reason="requires spa_c") +def has_pysam(): + try: + import PySAM + except ImportError: + return False + else: + return True + + +requires_pysam = pytest.mark.skipif(not has_pysam(), reason="requires PySAM") + + def has_numba(): try: import numba diff --git a/pvlib/test/test_ivtools.py b/pvlib/test/test_ivtools.py index 6e70f92849..80133a5ee3 100644 --- a/pvlib/test/test_ivtools.py +++ b/pvlib/test/test_ivtools.py @@ -55,6 +55,7 @@ def test_fit_sde_sandia(): assert np.allclose(result, expected, rtol=5e-5) +@requires_pysam def test_fit_cec_with_sam(cec_module_parameters): sam_parameters = cec_module_parameters cec_list_data = get_cec_params_cansol_cs5p_220p() From d975f32959ae66a5adfc74128b0c35292d35a37b Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Mon, 27 May 2019 21:09:22 -0600 Subject: [PATCH 25/68] fix import --- pvlib/test/test_ivtools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pvlib/test/test_ivtools.py b/pvlib/test/test_ivtools.py index 80133a5ee3..014341f14b 100644 --- a/pvlib/test/test_ivtools.py +++ b/pvlib/test/test_ivtools.py @@ -10,7 +10,7 @@ import pytest from pvlib import pvsystem from pvlib import ivtools -from conftest import requires_scipy +from conftest import requires_scipy, requires_pysam def get_test_iv_params(): From 37a362daaa294ff2ab2258c1abbf91a02ee1db2a Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Tue, 28 May 2019 07:49:41 -0600 Subject: [PATCH 26/68] fix infinite loop --- pvlib/ivtools.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pvlib/ivtools.py b/pvlib/ivtools.py index 5386682c56..089c9e4ce1 100644 --- a/pvlib/ivtools.py +++ b/pvlib/ivtools.py @@ -224,6 +224,7 @@ def _find_beta0_beta1(v, i, vlim, v_oc): beta0 = coef[1].item() # sign change of slope to get positive parameter value beta1 = -coef[0].item() + idx = len(v) + 1 # to exit else: idx += 1 return beta0, beta1 From c9564864fe407aba3f7bac1a9bebfb1d502887e5 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Tue, 28 May 2019 09:24:33 -0600 Subject: [PATCH 27/68] update whatsnew, api.rst --- docs/sphinx/source/api.rst | 8 ++++++++ docs/sphinx/source/whatsnew/v0.7.0.rst | 22 ++++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/docs/sphinx/source/api.rst b/docs/sphinx/source/api.rst index 45ef9ec50a..96d43778b0 100644 --- a/docs/sphinx/source/api.rst +++ b/docs/sphinx/source/api.rst @@ -282,6 +282,14 @@ PVsyst model pvsystem.pvsyst_celltemp +Functions for fitting PV models +------------------------------- +.. autosummary:: + :toctree: generated/ + + ivtools.fit_sde_sandia + ivtools.fit_cec_with_sam + Other ----- diff --git a/docs/sphinx/source/whatsnew/v0.7.0.rst b/docs/sphinx/source/whatsnew/v0.7.0.rst index cf2adfc9e6..75204d12e6 100644 --- a/docs/sphinx/source/whatsnew/v0.7.0.rst +++ b/docs/sphinx/source/whatsnew/v0.7.0.rst @@ -8,8 +8,30 @@ recommend all users of v0.6.3 upgrade to this release. **Python 2.7 support ended on June 1, 2019**. (:issue:`501`) +API Changes +~~~~~~~~~~~ + + +Enhancements +~~~~~~~~~~~~ +* Add `ivtools` module to contain functions for IV model fitting. +* Add :py:func:`~pvlib.ivtools.fit_sde_sandia`, a simple method to fit the + single diode equation to an IV curve. +* Add :py:func:`~pvlib.ivtools.fit_cec_with_sam`, a wrapper to access the + model fitting function '6parsolve' from NREL's System Advisor Model. + + +Bug fixes +~~~~~~~~~ + + +Testing +~~~~~~~ + Contributors ~~~~~~~~~~~~ * Mark Campanellli (:ghuser:`markcampanelli`) * Will Holmgren (:ghuser:`wholmgren`) +* Cliff Hansen (:ghuser:`cwhanse`) + From bbd5720d00cd629b575769477bd39aba62764a8c Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Tue, 28 May 2019 09:30:18 -0600 Subject: [PATCH 28/68] fix lint errors --- pvlib/ivtools.py | 2 +- pvlib/test/conftest.py | 20 ++++++++------------ 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/pvlib/ivtools.py b/pvlib/ivtools.py index 089c9e4ce1..34c69aa36a 100644 --- a/pvlib/ivtools.py +++ b/pvlib/ivtools.py @@ -224,7 +224,7 @@ def _find_beta0_beta1(v, i, vlim, v_oc): beta0 = coef[1].item() # sign change of slope to get positive parameter value beta1 = -coef[0].item() - idx = len(v) + 1 # to exit + idx = len(v) + 1 # to exit else: idx += 1 return beta0, beta1 diff --git a/pvlib/test/conftest.py b/pvlib/test/conftest.py index 1ef6318732..639a06ae47 100644 --- a/pvlib/test/conftest.py +++ b/pvlib/test/conftest.py @@ -110,18 +110,6 @@ def has_spa_c(): requires_spa_c = pytest.mark.skipif(not has_spa_c(), reason="requires spa_c") -def has_pysam(): - try: - import PySAM - except ImportError: - return False - else: - return True - - -requires_pysam = pytest.mark.skipif(not has_pysam(), reason="requires PySAM") - - def has_numba(): try: import numba @@ -163,3 +151,11 @@ def has_numba(): requires_pvfactors = pytest.mark.skipif(not has_pvfactors, reason='requires pvfactors') + +try: + import PySAM + has_pysam = True +except ImportError: + has_pysam = False + +requires_pysam = pytest.mark.skipif(not has_pysam, reason="requires PySAM") From 01f0dba7990b67b126f5f30a59a7d97049866236 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Tue, 28 May 2019 09:56:41 -0600 Subject: [PATCH 29/68] lint --- pvlib/test/conftest.py | 2 +- pvlib/test/test_ivtools.py | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pvlib/test/conftest.py b/pvlib/test/conftest.py index 639a06ae47..eac3497499 100644 --- a/pvlib/test/conftest.py +++ b/pvlib/test/conftest.py @@ -153,7 +153,7 @@ def has_numba(): reason='requires pvfactors') try: - import PySAM + import PySAM # noqa: F401 has_pysam = True except ImportError: has_pysam = False diff --git a/pvlib/test/test_ivtools.py b/pvlib/test/test_ivtools.py index 014341f14b..49a7fc5732 100644 --- a/pvlib/test/test_ivtools.py +++ b/pvlib/test/test_ivtools.py @@ -64,11 +64,11 @@ def test_fit_cec_with_sam(cec_module_parameters): beta_oc = cec_list_data['beta_voc'] * cec_list_data['V_oc_ref'] I_L_ref, I_o_ref, R_sh_ref, R_s, a_ref, Adjust = \ - ivtools.fit_cec_with_sam(celltype='polySi', - v_mp=cec_list_data['V_mp_ref'], i_mp=cec_list_data['I_mp_ref'], - v_oc=cec_list_data['V_oc_ref'], i_sc=cec_list_data['I_sc_ref'], - alpha_sc=alpha_sc, beta_voc=beta_oc, - gamma_pmp=cec_list_data['gamma_pmp'], + ivtools.fit_cec_with_sam( + celltype='polySi', v_mp=cec_list_data['V_mp_ref'], + i_mp=cec_list_data['I_mp_ref'], v_oc=cec_list_data['V_oc_ref'], + i_sc=cec_list_data['I_sc_ref'], alpha_sc=alpha_sc, + beta_voc=beta_oc, gamma_pmp=cec_list_data['gamma_pmp'], cells_in_series=cec_list_data['cells_in_series'], temp_ref=25) modeled = pd.Series(index=sam_parameters.index, data=cec_list_data) modeled['a_ref'] = a_ref From a32bb35ac61c21c522b3bd65f1314404af2bcaac Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Wed, 12 Jun 2019 08:17:54 -0600 Subject: [PATCH 30/68] resolve comments --- ci/requirements-py35.yml | 3 --- ci/requirements-py36.yml | 3 --- ci/requirements-py37.yml | 3 --- pvlib/ivtools.py | 4 ++-- setup.py | 2 +- 5 files changed, 3 insertions(+), 12 deletions(-) diff --git a/ci/requirements-py35.yml b/ci/requirements-py35.yml index ddfc54a189..5fc0077991 100644 --- a/ci/requirements-py35.yml +++ b/ci/requirements-py35.yml @@ -24,8 +24,5 @@ dependencies: - shapely # pvfactors dependency - siphon # conda-forge - pip: - - coveralls - - pytest-mock - - pytest-timeout - nrel-pysam - pvfactors==1.0.1 diff --git a/ci/requirements-py36.yml b/ci/requirements-py36.yml index d49c5b605f..e415f67c7d 100644 --- a/ci/requirements-py36.yml +++ b/ci/requirements-py36.yml @@ -24,8 +24,5 @@ dependencies: - shapely # pvfactors dependency - siphon # conda-forge - pip: - - coveralls - - pytest-mock - nrel-pysam - - pytest-timeout - pvfactors==1.0.1 diff --git a/ci/requirements-py37.yml b/ci/requirements-py37.yml index 43bb5db388..1a6809fba6 100644 --- a/ci/requirements-py37.yml +++ b/ci/requirements-py37.yml @@ -24,8 +24,5 @@ dependencies: - shapely # pvfactors dependency - siphon # conda-forge - pip: - - coveralls - - pytest-mock - nrel-pysam - - pytest-timeout - pvfactors==1.0.1 diff --git a/pvlib/ivtools.py b/pvlib/ivtools.py index 34c69aa36a..5a7c27c9d8 100644 --- a/pvlib/ivtools.py +++ b/pvlib/ivtools.py @@ -213,7 +213,7 @@ def _calc_I0(IL, I, V, Gp, Rs, beta3): def _find_beta0_beta1(v, i, vlim, v_oc): # Get intercept and slope of linear portion of IV curve. # Start with V =< vlim * v_oc, extend by adding points until slope is - # acceptable + # negative (downward). beta0 = np.nan beta1 = np.nan idx = np.searchsorted(v, vlim * v_oc) @@ -224,7 +224,7 @@ def _find_beta0_beta1(v, i, vlim, v_oc): beta0 = coef[1].item() # sign change of slope to get positive parameter value beta1 = -coef[0].item() - idx = len(v) + 1 # to exit + break else: idx += 1 return beta0, beta1 diff --git a/setup.py b/setup.py index a4b41e6c00..9efb411888 100755 --- a/setup.py +++ b/setup.py @@ -45,7 +45,7 @@ 'pytest-timeout'] EXTRAS_REQUIRE = { 'optional': ['ephem', 'cython', 'netcdf4', 'numba', 'pvfactors', 'scipy', - 'siphon', 'tables'], + 'siphon', 'tables', 'nrel-pysam'], 'doc': ['ipython', 'matplotlib', 'sphinx', 'sphinx_rtd_theme'], 'test': TESTS_REQUIRE } From 5779b1ede945e1e0cbfba96386eae3ec80d15fd0 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Tue, 18 Jun 2019 10:39:18 -0500 Subject: [PATCH 31/68] comment response --- pvlib/ivtools.py | 32 +++++++++++++++++++------------- setup.py | 4 ++-- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/pvlib/ivtools.py b/pvlib/ivtools.py index 5a7c27c9d8..56cfeca4f5 100644 --- a/pvlib/ivtools.py +++ b/pvlib/ivtools.py @@ -8,10 +8,10 @@ import numpy as np -def fit_cec_with_sam(celltype, v_mp, i_mp, v_oc, i_sc, alpha_sc, beta_voc, - gamma_pmp, cells_in_series, temp_ref=25): +def fit_cec_sam(celltype, v_mp, i_mp, v_oc, i_sc, alpha_sc, beta_voc, + gamma_pmp, cells_in_series, temp_ref=25): ''' - Estimates parameters for the CEC single diode model using the SAM SDK. + Estimates parameters for the CEC single diode model [1] using the SAM SDK. Parameters ---------- @@ -19,22 +19,22 @@ def fit_cec_with_sam(celltype, v_mp, i_mp, v_oc, i_sc, alpha_sc, beta_voc, Value is one of 'monoSi', 'multiSi', 'polySi', 'cis', 'cigs', 'cdte', 'amorphous' v_mp : float - Voltage at maximum power point at standard test condition (STC) + Voltage at maximum power point at standard test condition (STC) [V] i_mp : float - Current at maximum power point at STC - Voc : float - Open circuit voltage at STC + Current at maximum power point at STC [A] + v_oc : float + Open circuit voltage at STC [V] i_sc : float - Short circuit current at STC + Short circuit current at STC [A] alpha_sc : float - Temperature coefficient of short circuit current at STC, A/C + Temperature coefficient of short circuit current at STC [A/C] beta_voc : float - Temperature coefficient of open circuit voltage at STC, V/C + Temperature coefficient of open circuit voltage at STC [V/C] gamma_pmp : float - Temperature coefficient of power at maximum point point at STC, %/C + Temperature coefficient of power at maximum point point at STC [%/C] cells_in_series : int Number of cells in series - temp_ref : float, default 25 + temp_ref : float, default 25C Reference temperature condition Returns @@ -60,10 +60,16 @@ def fit_cec_with_sam(celltype, v_mp, i_mp, v_oc, i_sc, alpha_sc, beta_voc, Adjust : float The adjustment to the temperature coefficient for short circuit - current, in percent + current, in percent. Raises: ImportError if NREL-PySAM is not installed + + References + ---------- + [1] A. Dobos, "An Improved Coefficient Calculator for the California + Energy Commission 6 Parameter Photovoltaic Module Model", Journal of + Solar Energy Engineering, vol 134, 2012. ''' try: diff --git a/setup.py b/setup.py index 9efb411888..1427206cb2 100755 --- a/setup.py +++ b/setup.py @@ -44,8 +44,8 @@ TESTS_REQUIRE = ['nose', 'pytest', 'pytest-cov', 'pytest-mock', 'pytest-timeout'] EXTRAS_REQUIRE = { - 'optional': ['ephem', 'cython', 'netcdf4', 'numba', 'pvfactors', 'scipy', - 'siphon', 'tables', 'nrel-pysam'], + 'optional': ['ephem', 'cython', 'netcdf4', 'nrel-pysam', 'numba', + 'pvfactors', 'scipy', 'siphon', 'tables'], 'doc': ['ipython', 'matplotlib', 'sphinx', 'sphinx_rtd_theme'], 'test': TESTS_REQUIRE } From 0f459259905f9601c35f30065036a9c1f84dc644 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Tue, 18 Jun 2019 10:55:23 -0500 Subject: [PATCH 32/68] fix function name in test --- pvlib/test/test_ivtools.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pvlib/test/test_ivtools.py b/pvlib/test/test_ivtools.py index 49a7fc5732..0219439a78 100644 --- a/pvlib/test/test_ivtools.py +++ b/pvlib/test/test_ivtools.py @@ -56,7 +56,7 @@ def test_fit_sde_sandia(): @requires_pysam -def test_fit_cec_with_sam(cec_module_parameters): +def test_fit_cec_sam(cec_module_parameters): sam_parameters = cec_module_parameters cec_list_data = get_cec_params_cansol_cs5p_220p() # convert from %/C to A/C and V/C @@ -64,7 +64,7 @@ def test_fit_cec_with_sam(cec_module_parameters): beta_oc = cec_list_data['beta_voc'] * cec_list_data['V_oc_ref'] I_L_ref, I_o_ref, R_sh_ref, R_s, a_ref, Adjust = \ - ivtools.fit_cec_with_sam( + ivtools.fit_cec_sam( celltype='polySi', v_mp=cec_list_data['V_mp_ref'], i_mp=cec_list_data['I_mp_ref'], v_oc=cec_list_data['V_oc_ref'], i_sc=cec_list_data['I_sc_ref'], alpha_sc=alpha_sc, From 58070da8c94d9b9dd846972f7f98fc6738c8b482 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Wed, 19 Jun 2019 22:09:37 -0500 Subject: [PATCH 33/68] more comment responses --- pvlib/ivtools.py | 84 ++++++++++++++++++++++++-------------- pvlib/test/test_ivtools.py | 13 ++---- 2 files changed, 58 insertions(+), 39 deletions(-) diff --git a/pvlib/ivtools.py b/pvlib/ivtools.py index 56cfeca4f5..429a80966d 100644 --- a/pvlib/ivtools.py +++ b/pvlib/ivtools.py @@ -19,19 +19,19 @@ def fit_cec_sam(celltype, v_mp, i_mp, v_oc, i_sc, alpha_sc, beta_voc, Value is one of 'monoSi', 'multiSi', 'polySi', 'cis', 'cigs', 'cdte', 'amorphous' v_mp : float - Voltage at maximum power point at standard test condition (STC) [V] + Voltage at maximum power point [V] i_mp : float - Current at maximum power point at STC [A] + Current at maximum power point [A] v_oc : float - Open circuit voltage at STC [V] + Open circuit voltage [V] i_sc : float - Short circuit current at STC [A] + Short circuit current [A] alpha_sc : float - Temperature coefficient of short circuit current at STC [A/C] + Temperature coefficient of short circuit current [A/C] beta_voc : float - Temperature coefficient of open circuit voltage at STC [V/C] + Temperature coefficient of open circuit voltage [V/C] gamma_pmp : float - Temperature coefficient of power at maximum point point at STC [%/C] + Temperature coefficient of power at maximum point point [%/C] cells_in_series : int Number of cells in series temp_ref : float, default 25C @@ -40,17 +40,17 @@ def fit_cec_sam(celltype, v_mp, i_mp, v_oc, i_sc, alpha_sc, beta_voc, Returns ------- a_ref : float - The product of the usual diode ideality factor (n, unitless), - number of cells in series (Ns), and cell thermal voltage at reference - conditions, in units of V. + The product of the usual diode ideality factor ``n`` (unitless), + number of cells in series ``Ns``, and cell thermal voltage at + reference conditions [V] I_L_ref : float The light-generated current (or photocurrent) at reference conditions, - in amperes. + [A] I_o_ref : float - The dark or diode reverse saturation current at reference conditions, - in amperes. + The dark or diode reverse saturation current at reference conditions + [A] R_sh_ref : float The shunt resistance at reference conditions, in ohms. @@ -65,6 +65,15 @@ def fit_cec_sam(celltype, v_mp, i_mp, v_oc, i_sc, alpha_sc, beta_voc, Raises: ImportError if NREL-PySAM is not installed + Notes + ----- + Inputs v_mp, v_oc, i_mp and i_sc are assumed to be from a single IV curve + at constant irradiance and cell temperature. Irradiance is not explicitly + required for the fitting procedure, but the irradiance and the specified + temperature ``Tref`` are implicitly the reference condition for the + output parameters ``I_L_ref``, ``I_o_ref``, ``R_sh_ref``, ``R_s`` and + ``Adjust``. + References ---------- [1] A. Dobos, "An Improved Coefficient Calculator for the California @@ -75,7 +84,8 @@ def fit_cec_sam(celltype, v_mp, i_mp, v_oc, i_sc, alpha_sc, beta_voc, try: from PySAM import PySSC except ImportError as e: - raise(e) + raise("Requires NREL's PySAM package at " + "https://pypi.org/project/NREL-PySAM/.") from e datadict = {'tech_model': '6parsolve', 'financial_model': 'none', 'celltype': celltype, 'Vmp': v_mp, @@ -102,38 +112,40 @@ def fit_sde_sandia(v, i, v_oc, i_sc, v_mp, i_mp, vlim=0.2, ilim=0.1): Parameters ---------- v : numeric - Voltage at each point on the IV curve, from 0 to v_oc + Voltage at each point on the IV curve, increasing from 0 to v_oc + inclusive [V] i : numeric - Current at each point on the IV curve, from i_sc to 0 + Current at each point on the IV curve, decreasing from i_sc to 0 [A] v_oc : float - Open circuit voltage + Open circuit voltage [V] i_sc : float - Short circuit current + Short circuit current [A] v_mp : float - Voltage at maximum power point + Voltage at maximum power point [V] i_mp : float - Current at maximum power point + Current at maximum power point [V] vlim : float, default 0.2 - defines linear portion of IV curve i.e. V <= vlim * v_oc + defines linear portion of IV curve i.e. V <= vlim * v_oc [V] ilim : float, default 0.1 - defines exponential portion of IV curve i.e. I > ilim * i_sc + defines exponential portion of IV curve, approximately defined by + I < (1 - ilim) * i_sc [A] Returns ------- tuple of the following elements: photocurrent : float - photocurrent, A + photocurrent [A] saturation_current : float - dark (saturation) current, A + dark (saturation) current [A] resistance_shunt : float shunt (parallel) resistance, ohm @@ -142,8 +154,8 @@ def fit_sde_sandia(v, i, v_oc, i_sc, v_mp, i_mp, vlim=0.2, ilim=0.1): series resistance, ohm nNsVth : float - product of diode (ideality) factor n (unitless) x number of - cells in series Ns (unitless) x cell thermal voltage Vth (V), V + product of thermal voltage ``Vth`` [V], diode ideality factor + ``n``, and number of series cells ``Ns`` Notes ----- @@ -156,7 +168,7 @@ def fit_sde_sandia(v, i, v_oc, i_sc, v_mp, i_mp, vlim=0.2, ilim=0.1): :py:func:`pvsystem.singlediode` for definition of the parameters. - The fitting method proceeds in four steps: + The fitting method [2] proceeds in four steps: 1) simplify the single diode equation .. math:: @@ -176,16 +188,26 @@ def fit_sde_sandia(v, i, v_oc, i_sc, v_mp, i_mp, vlim=0.2, ilim=0.1): I ~ IL/(1+Gp*Rs) - (Gp*V)/(1+Gp*Rs) = beta0 + beta1*V - 4) fit the exponential portion of the IV curve I > ilim * i_sc + 4) fit the exponential portion of the IV curve .. math:: + beta0 + beta*V - I > ilim * i_sc + log(beta0 - beta1*V - I) ~ log((I0)/(1+Gp*Rs)) + (V)/(nNsVth) + (Rs*I)/(nNsVth) = beta2 + beta3*V + beta4*I Values for ``IL, I0, Rs, Rsh,`` and ``nNsVth`` are calculated from the regression coefficents beta0, beta1, beta3 and beta4. + Returns `nan` for each parameter if the fitting is not successful. If `nan` + is returned one likely cause is the input IV curve having too few points. + It is recommend that the input IV curve contain 100 or more points, and + more points increase the likelihood of extracting reasonable + parameters. + + case, providing more IV + References ---------- [1] S.R. Wenham, M.A. Green, M.E. Watt, "Applied Photovoltaics" ISBN @@ -199,7 +221,9 @@ def fit_sde_sandia(v, i, v_oc, i_sc, v_mp, i_mp, vlim=0.2, ilim=0.1): beta0, beta1 = _find_beta0_beta1(v, i, vlim, v_oc) if not np.isnan(beta0): - # Find beta3 and beta4 from exponential portion of IV curve + # Subtract the IV curve from the linear fit. Select points where + # beta0 + beta*V - I > ilim * i_sc + # in order to find beta3 and beta4 from exponential portion of IV curve y = beta0 - beta1 * v - i x = np.array([np.ones_like(v), v, i]).T beta3, beta4 = _find_beta3_beta4(y, x, ilim, i_sc) @@ -238,7 +262,7 @@ def _find_beta0_beta1(v, i, vlim, v_oc): def _find_beta3_beta4(y, x, ilim, i_sc): idx = np.searchsorted(y, ilim * i_sc) - 1 - result = np.linalg.lstsq(x[idx:, ], np.log(y[idx:])) + result = np.linalg.lstsq(x[idx:, ], np.log(y[idx:]), rcond=None) coef = result[0] beta3 = coef[1].item() beta4 = coef[2].item() diff --git a/pvlib/test/test_ivtools.py b/pvlib/test/test_ivtools.py index 0219439a78..be2e4eadc9 100644 --- a/pvlib/test/test_ivtools.py +++ b/pvlib/test/test_ivtools.py @@ -19,7 +19,7 @@ def get_test_iv_params(): def get_cec_params_cansol_cs5p_220p(): return {'V_mp_ref': 46.6, 'I_mp_ref': 4.73, 'V_oc_ref': 58.3, - 'I_sc_ref': 5.05, 'alpha_isc': 0.000495, 'beta_voc': -0.003372, + 'I_sc_ref': 5.05, 'alpha_sc': 0.0025, 'beta_voc': -0.19659, 'gamma_pmp': -0.43, 'cells_in_series': 96} @@ -59,16 +59,13 @@ def test_fit_sde_sandia(): def test_fit_cec_sam(cec_module_parameters): sam_parameters = cec_module_parameters cec_list_data = get_cec_params_cansol_cs5p_220p() - # convert from %/C to A/C and V/C - alpha_sc = cec_list_data['alpha_isc'] * cec_list_data['I_sc_ref'] - beta_oc = cec_list_data['beta_voc'] * cec_list_data['V_oc_ref'] - I_L_ref, I_o_ref, R_sh_ref, R_s, a_ref, Adjust = \ ivtools.fit_cec_sam( celltype='polySi', v_mp=cec_list_data['V_mp_ref'], i_mp=cec_list_data['I_mp_ref'], v_oc=cec_list_data['V_oc_ref'], - i_sc=cec_list_data['I_sc_ref'], alpha_sc=alpha_sc, - beta_voc=beta_oc, gamma_pmp=cec_list_data['gamma_pmp'], + i_sc=cec_list_data['I_sc_ref'], alpha_sc=cec_list_data['alpha_sc'], + beta_voc=cec_list_data['beta_voc'], + gamma_pmp=cec_list_data['gamma_pmp'], cells_in_series=cec_list_data['cells_in_series'], temp_ref=25) modeled = pd.Series(index=sam_parameters.index, data=cec_list_data) modeled['a_ref'] = a_ref @@ -77,8 +74,6 @@ def test_fit_cec_sam(cec_module_parameters): modeled['R_sh_ref'] = R_sh_ref modeled['R_s'] = R_s modeled['Adjust'] = Adjust - modeled['alpha_sc'] = alpha_sc - modeled['beta_oc'] = beta_oc modeled['gamma_r'] = cec_list_data['gamma_pmp'] modeled['N_s'] = cec_list_data['cells_in_series'] modeled = modeled.dropna() From 9bf7566c5637170f511f7807b1ac1d1919679949 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Fri, 21 Jun 2019 15:36:48 -0600 Subject: [PATCH 34/68] add nan output to fit_cec_sam, comment responses --- docs/sphinx/source/api.rst | 2 +- docs/sphinx/source/whatsnew/v0.7.0.rst | 2 +- pvlib/ivtools.py | 86 +++++++++++++++----------- pvlib/test/test_ivtools.py | 9 ++- 4 files changed, 59 insertions(+), 40 deletions(-) diff --git a/docs/sphinx/source/api.rst b/docs/sphinx/source/api.rst index 96d43778b0..8563bb65ac 100644 --- a/docs/sphinx/source/api.rst +++ b/docs/sphinx/source/api.rst @@ -288,7 +288,7 @@ Functions for fitting PV models :toctree: generated/ ivtools.fit_sde_sandia - ivtools.fit_cec_with_sam + ivtools.fit_cec_sam Other ----- diff --git a/docs/sphinx/source/whatsnew/v0.7.0.rst b/docs/sphinx/source/whatsnew/v0.7.0.rst index 75204d12e6..0743a0fccb 100644 --- a/docs/sphinx/source/whatsnew/v0.7.0.rst +++ b/docs/sphinx/source/whatsnew/v0.7.0.rst @@ -17,7 +17,7 @@ Enhancements * Add `ivtools` module to contain functions for IV model fitting. * Add :py:func:`~pvlib.ivtools.fit_sde_sandia`, a simple method to fit the single diode equation to an IV curve. -* Add :py:func:`~pvlib.ivtools.fit_cec_with_sam`, a wrapper to access the +* Add :py:func:`~pvlib.ivtools.fit_cec_sam`, a wrapper to access the model fitting function '6parsolve' from NREL's System Advisor Model. diff --git a/pvlib/ivtools.py b/pvlib/ivtools.py index 429a80966d..f9763f538f 100644 --- a/pvlib/ivtools.py +++ b/pvlib/ivtools.py @@ -39,40 +39,44 @@ def fit_cec_sam(celltype, v_mp, i_mp, v_oc, i_sc, alpha_sc, beta_voc, Returns ------- - a_ref : float - The product of the usual diode ideality factor ``n`` (unitless), - number of cells in series ``Ns``, and cell thermal voltage at - reference conditions [V] + tuple of the following elements: + + a_ref : float + The product of the usual diode ideality factor ``n`` (unitless), + number of cells in series ``Ns``, and cell thermal voltage at + reference conditions [V] - I_L_ref : float - The light-generated current (or photocurrent) at reference conditions, - [A] + I_L_ref : float + The light-generated current (or photocurrent) at reference + conditions [A] - I_o_ref : float - The dark or diode reverse saturation current at reference conditions - [A] + I_o_ref : float + The dark or diode reverse saturation current at reference + conditions [A] - R_sh_ref : float - The shunt resistance at reference conditions, in ohms. + R_sh_ref : float + The shunt resistance at reference conditions, in ohms. - R_s : float - The series resistance at reference conditions, in ohms. + R_s : float + The series resistance at reference conditions, in ohms. - Adjust : float - The adjustment to the temperature coefficient for short circuit - current, in percent. + Adjust : float + The adjustment to the temperature coefficient for short circuit + current, in percent. Raises: ImportError if NREL-PySAM is not installed Notes ----- - Inputs v_mp, v_oc, i_mp and i_sc are assumed to be from a single IV curve - at constant irradiance and cell temperature. Irradiance is not explicitly - required for the fitting procedure, but the irradiance and the specified - temperature ``Tref`` are implicitly the reference condition for the - output parameters ``I_L_ref``, ``I_o_ref``, ``R_sh_ref``, ``R_s`` and - ``Adjust``. + Inputs ``v_mp``, ``v_oc``, ``i_mp`` and ``i_sc`` are assumed to be from a + single IV curve at constant irradiance and cell temperature. Irradiance is + not explicitly used by the fitting procedure. The irradiance level at which + the input IV curve is determined and the specified cell temperature + ``Tref`` are the reference conditions for the output parameters + ``I_L_ref``, ``I_o_ref``, ``R_sh_ref``, ``R_s`` and ``Adjust``. + + If the fitting fails, returns NaN in each parameter. References ---------- @@ -94,13 +98,20 @@ def fit_cec_sam(celltype, v_mp, i_mp, v_oc, i_sc, alpha_sc, beta_voc, 'Nser': cells_in_series, 'Tref': temp_ref} result = PySSC.ssc_sim_from_dict(datadict) - a_ref = result['a'] - I_L_ref = result['Il'] - I_o_ref = result['Io'] - R_s = result['Rs'] - R_sh_ref = result['Rsh'] - Adjust = result['Adj'] - + if result['cmod_success'] == 1: + a_ref = result['a'] + I_L_ref = result['Il'] + I_o_ref = result['Io'] + R_s = result['Rs'] + R_sh_ref = result['Rsh'] + Adjust = result['Adj'] + else: + a_ref = np.nan + I_L_ref = np.nan + I_o_ref = np.nan + R_s = np.nan + R_sh_ref = np.nan + Adjust = np.nan return I_L_ref, I_o_ref, R_sh_ref, R_s, a_ref, Adjust @@ -159,8 +170,11 @@ def fit_sde_sandia(v, i, v_oc, i_sc, v_mp, i_mp, vlim=0.2, ilim=0.1): Notes ----- + Inputs ``v``, ``i``, ``v_mp``, ``v_oc``, ``i_mp`` and ``i_sc`` are assumed + to be from a single IV curve at constant irradiance and cell temperature. + :py:func:`fit_sde_sandia` obtains values for the five parameters for the - single diode equation [1] + single diode equation [1]: .. math:: @@ -200,14 +214,12 @@ def fit_sde_sandia(v, i, v_oc, i_sc, v_mp, i_mp, vlim=0.2, ilim=0.1): Values for ``IL, I0, Rs, Rsh,`` and ``nNsVth`` are calculated from the regression coefficents beta0, beta1, beta3 and beta4. - Returns `nan` for each parameter if the fitting is not successful. If `nan` - is returned one likely cause is the input IV curve having too few points. - It is recommend that the input IV curve contain 100 or more points, and - more points increase the likelihood of extracting reasonable + Returns ``NaN`` for each parameter if the fitting is not successful. If + ``NaN`` is returned one likely cause is the input IV curve having too few + points. It is recommend that the input IV curve contain 100 or more points, + and more points increase the likelihood of extracting reasonable parameters. - case, providing more IV - References ---------- [1] S.R. Wenham, M.A. Green, M.E. Watt, "Applied Photovoltaics" ISBN diff --git a/pvlib/test/test_ivtools.py b/pvlib/test/test_ivtools.py index be2e4eadc9..2d737f9c5c 100644 --- a/pvlib/test/test_ivtools.py +++ b/pvlib/test/test_ivtools.py @@ -10,7 +10,7 @@ import pytest from pvlib import pvsystem from pvlib import ivtools -from conftest import requires_scipy, requires_pysam +from pvlib.test.conftest import requires_scipy, requires_pysam def get_test_iv_params(): @@ -81,3 +81,10 @@ def test_fit_cec_sam(cec_module_parameters): for k in modeled.keys(): expected[k] = sam_parameters[k] assert np.allclose(modeled.values, expected.values, rtol=5e-2) + # test for fitting failure + I_L_ref, I_o_ref, R_sh_ref, R_s, a_ref, Adjust = \ + ivtools.fit_cec_sam( + celltype='polySi', v_mp=0.45, i_mp=5.25, v_oc=0.55, i_sc=5.5, + alpha_sc=0.00275, beta_voc = 0.00275, gamma_pmp=0.0055, + cells_in_series=1, temp_ref=25) + assert all(np.isnan([I_L_ref, I_o_ref, R_sh_ref, R_s, a_ref, Adjust])) From 723b45465d03be8a9b62159052d31309e0661569 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Fri, 21 Jun 2019 15:41:07 -0600 Subject: [PATCH 35/68] lint --- pvlib/test/test_ivtools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pvlib/test/test_ivtools.py b/pvlib/test/test_ivtools.py index 2d737f9c5c..c2b25c4444 100644 --- a/pvlib/test/test_ivtools.py +++ b/pvlib/test/test_ivtools.py @@ -85,6 +85,6 @@ def test_fit_cec_sam(cec_module_parameters): I_L_ref, I_o_ref, R_sh_ref, R_s, a_ref, Adjust = \ ivtools.fit_cec_sam( celltype='polySi', v_mp=0.45, i_mp=5.25, v_oc=0.55, i_sc=5.5, - alpha_sc=0.00275, beta_voc = 0.00275, gamma_pmp=0.0055, + alpha_sc=0.00275, beta_voc=0.00275, gamma_pmp=0.0055, cells_in_series=1, temp_ref=25) assert all(np.isnan([I_L_ref, I_o_ref, R_sh_ref, R_s, a_ref, Adjust])) From ad9bc2de8c0599b3c8a054cd8207dd150c6ecaf6 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Mon, 24 Jun 2019 14:43:45 -0600 Subject: [PATCH 36/68] consistent return nan, v_oc arg etc. defaults to None, edit description --- pvlib/ivtools.py | 97 ++++++++++++++++++++++++++------------ pvlib/test/test_ivtools.py | 23 ++++++--- 2 files changed, 84 insertions(+), 36 deletions(-) diff --git a/pvlib/ivtools.py b/pvlib/ivtools.py index f9763f538f..dc7786876a 100644 --- a/pvlib/ivtools.py +++ b/pvlib/ivtools.py @@ -115,31 +115,32 @@ def fit_cec_sam(celltype, v_mp, i_mp, v_oc, i_sc, alpha_sc, beta_voc, return I_L_ref, I_o_ref, R_sh_ref, R_s, a_ref, Adjust -def fit_sde_sandia(v, i, v_oc, i_sc, v_mp, i_mp, vlim=0.2, ilim=0.1): +def fit_sde_sandia(v, i, v_oc=None, i_sc=None, mp=None, vlim=0.2, ilim=0.1): """ Fits the single diode equation to an IV curve. If fitting fails, returns NaN in each parameter. Parameters ---------- - v : numeric - Voltage at each point on the IV curve, increasing from 0 to v_oc - inclusive [V] + v : ndarray + 1D array containing voltage at each point on the IV curve, increasing + from 0 to v_oc inclusive, of `float` type [V] - i : numeric - Current at each point on the IV curve, decreasing from i_sc to 0 [A] + i : ndarray + 1D array containing current at each point on the IV curve, decreasing + from i_sc to 0 inclusive, of `float` type [A] - v_oc : float - Open circuit voltage [V] + v_oc : float, default None + Open circuit voltage [V]. If not provided, the last value in input + ``v`` is taken as ``v_oc``. - i_sc : float - Short circuit current [A] + i_sc : float, default None + Short circuit current [A]. If not provided, the first value in input + ``i`` is taken as ``i_sc``. - v_mp : float - Voltage at maximum power point [V] - - i_mp : float - Current at maximum power point [V] + mp : tuple of float, default None + Voltage, current at maximum power point in units of [V], [A]. + If not provided, values are found from inputs ``v`` and ``i``. vlim : float, default 0.2 defines linear portion of IV curve i.e. V <= vlim * v_oc [V] @@ -182,37 +183,37 @@ def fit_sde_sandia(v, i, v_oc, i_sc, v_mp, i_mp, vlim=0.2, ilim=0.1): :py:func:`pvsystem.singlediode` for definition of the parameters. - The fitting method [2] proceeds in four steps: - 1) simplify the single diode equation + The extraction method [2] proceeds in four steps: + 1) In the single diode equation, replace Rsh = 1/Gp and re-arrange .. math:: - I = IL - I0*exp((V+I*Rs)/(nNsVth)) - (V + I*Rs)/Rsh - - 2) replace Rsh = 1/Gp and re-arrange + I = IL - I0*exp((V+I*Rs)/(nNsVth) - 1) - (V + I*Rs)/Rsh .. math:: I = IL/(1+Gp*Rs) - (Gp*V)/(1+Gp*Rs) - - I0/(1+Gp*Rs)*exp((V+I*Rs)/(nNsVth)) + I0/(1+Gp*Rs)*exp((V+I*Rs)/(nNsVth) - 1) - 3) fit the linear portion of the IV curve V <= vlim * v_oc + 2) fit the linear portion of the IV curve defined as V <= vlim * v_oc, + where I0/(1+Gp*Rs)*exp((V+I*Rs)/(nNsVth) - 1) ≈ 0 .. math:: I ~ IL/(1+Gp*Rs) - (Gp*V)/(1+Gp*Rs) = beta0 + beta1*V - 4) fit the exponential portion of the IV curve + 3) fit the exponential portion of the IV curve defined by + beta0 + beta1*V - I > ilim * i_sc, where + exp((V+I*Rs)/(nNsVth)) >> 1 so that + exp((V+I*Rs)/(nNsVth)) - 1 ≈ exp((V+I*Rs)/(nNsVth)) .. math:: - beta0 + beta*V - I > ilim * i_sc - log(beta0 - beta1*V - I) ~ log((I0)/(1+Gp*Rs)) + (V)/(nNsVth) + (Rs*I)/(nNsVth) = beta2 + beta3*V + beta4*I - Values for ``IL, I0, Rs, Rsh,`` and ``nNsVth`` are calculated from the - regression coefficents beta0, beta1, beta3 and beta4. + 4) calculate values for ``IL, I0, Rs, Rsh,`` and ``nNsVth`` from the + regression coefficents beta0, beta1, beta3 and beta4. Returns ``NaN`` for each parameter if the fitting is not successful. If ``NaN`` is returned one likely cause is the input IV curve having too few @@ -229,6 +230,16 @@ def fit_sde_sandia(v, i, v_oc, i_sc, v_mp, i_mp, vlim=0.2, ilim=0.1): Photovoltaic Specialist Conference, Chicago, IL, 2019 """ + # If not provided, extract v_oc, i_sc, v_mp and i_mp from the IV curve data + if v_oc is None: + v_oc = v[-1] + if i_sc is None: + i_sc = i[0] + if mp is not None: + v_mp, i_mp = mp + else: + v_mp, i_mp = _find_mp(v, i) + # Find beta0 and beta1 from linear portion of the IV curve beta0, beta1 = _find_beta0_beta1(v, i, vlim, v_oc) @@ -248,6 +259,30 @@ def fit_sde_sandia(v, i, v_oc, i_sc, v_mp, i_mp, vlim=0.2, ilim=0.1): return IL, I0, Rsh, Rs, nNsVth +def _find_mp(v, i): + """ + Finds voltage and current at maximum power point. + + Parameters + ---------- + v : ndarray + 1D array containing voltage at each point on the IV curve, increasing + from 0 to v_oc inclusive, of `float` type [V] + + i : ndarray + 1D array containing current at each point on the IV curve, decreasing + from i_sc to 0 inclusive, of `float` type [A] + + Returns + ------- + v, i : tuple + voltage ``v`` and current ``i`` at the maximum power point [V], [A] + """ + p = v * i + idx = np.argmax(p) + return v[idx], i[idx] + + def _calc_I0(IL, I, V, Gp, Rs, beta3): return (IL - I - Gp * V - Gp * Rs * I) / np.exp(beta3 * (V + Rs * I)) @@ -282,7 +317,9 @@ def _find_beta3_beta4(y, x, ilim, i_sc): def _calculate_sde_parameters(beta0, beta1, beta3, beta4, v_mp, i_mp, v_oc): - if not any(np.isnan([beta0, beta1, beta3, beta4])): + if any(np.isnan([beta0, beta1, beta3, beta4])): + failed = True + else: nNsVth = 1.0 / beta3 Rs = beta4 / beta3 Gp = beta1 / (1.0 - Rs * beta1) @@ -298,7 +335,7 @@ def _calculate_sde_parameters(beta0, beta1, beta3, beta4, v_mp, i_mp, v_oc): elif (I0_v_oc > 0): I0 = I0_v_oc else: - I0 = np.nan - else: + failed = True + if failed: IL = I0 = Rsh = Rs = nNsVth = np.nan return IL, I0, Rsh, Rs, nNsVth diff --git a/pvlib/test/test_ivtools.py b/pvlib/test/test_ivtools.py index c2b25c4444..5bda28b935 100644 --- a/pvlib/test/test_ivtools.py +++ b/pvlib/test/test_ivtools.py @@ -13,10 +13,12 @@ from pvlib.test.conftest import requires_scipy, requires_pysam +@pytest.fixture def get_test_iv_params(): return {'IL': 8.0, 'I0': 5e-10, 'Rsh': 1000, 'Rs': 0.2, 'nNsVth': 1.61864} +@pytest.fixture def get_cec_params_cansol_cs5p_220p(): return {'V_mp_ref': 46.6, 'I_mp_ref': 4.73, 'V_oc_ref': 58.3, 'I_sc_ref': 5.05, 'alpha_sc': 0.0025, 'beta_voc': -0.19659, @@ -37,8 +39,8 @@ def cec_module_parameters(sam_data): @requires_scipy -def test_fit_sde_sandia(): - test_params = get_test_iv_params() +def test_fit_sde_sandia(get_test_iv_params): + test_params = get_test_iv_params testcurve = pvsystem.singlediode(photocurrent=test_params['IL'], saturation_current=test_params['I0'], resistance_shunt=test_params['Rsh'], @@ -47,18 +49,27 @@ def test_fit_sde_sandia(): ivcurve_pnts=300) expected = tuple(test_params[k] for k in ['IL', 'I0', 'Rsh', 'Rs', 'nNsVth']) + result = ivtools.fit_sde_sandia(v=testcurve['v'], i=testcurve['i']) + assert np.allclose(result, expected, rtol=5e-5) + result = ivtools.fit_sde_sandia(v=testcurve['v'], i=testcurve['i'], + v_oc=testcurve['v_oc'], + i_sc=testcurve['i_sc']) + assert np.allclose(result, expected, rtol=5e-5) result = ivtools.fit_sde_sandia(v=testcurve['v'], i=testcurve['i'], v_oc=testcurve['v_oc'], i_sc=testcurve['i_sc'], - v_mp=testcurve['v_mp'], - i_mp=testcurve['i_mp']) + mp=(testcurve['v_mp'], + testcurve['i_mp'])) + assert np.allclose(result, expected, rtol=5e-5) + result = ivtools.fit_sde_sandia(v=testcurve['v'], i=testcurve['i'], + vlim=0.1) assert np.allclose(result, expected, rtol=5e-5) @requires_pysam -def test_fit_cec_sam(cec_module_parameters): +def test_fit_cec_sam(cec_module_parameters, get_cec_params_cansol_cs5p_220p): sam_parameters = cec_module_parameters - cec_list_data = get_cec_params_cansol_cs5p_220p() + cec_list_data = get_cec_params_cansol_cs5p_220p I_L_ref, I_o_ref, R_sh_ref, R_s, a_ref, Adjust = \ ivtools.fit_cec_sam( celltype='polySi', v_mp=cec_list_data['V_mp_ref'], From 6d5ff04a145f9873b18cbc6b58cc57489f7e9c48 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Mon, 24 Jun 2019 14:58:18 -0600 Subject: [PATCH 37/68] initialize failed --- pvlib/ivtools.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pvlib/ivtools.py b/pvlib/ivtools.py index dc7786876a..95309c8384 100644 --- a/pvlib/ivtools.py +++ b/pvlib/ivtools.py @@ -317,6 +317,7 @@ def _find_beta3_beta4(y, x, ilim, i_sc): def _calculate_sde_parameters(beta0, beta1, beta3, beta4, v_mp, i_mp, v_oc): + failed = False if any(np.isnan([beta0, beta1, beta3, beta4])): failed = True else: From d5061408113fb5b0f3927d7e90a8394243393282 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Mon, 24 Jun 2019 15:33:25 -0600 Subject: [PATCH 38/68] lint --- pvlib/ivtools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pvlib/ivtools.py b/pvlib/ivtools.py index 95309c8384..459d6cb679 100644 --- a/pvlib/ivtools.py +++ b/pvlib/ivtools.py @@ -203,7 +203,7 @@ def fit_sde_sandia(v, i, v_oc=None, i_sc=None, mp=None, vlim=0.2, ilim=0.1): I ~ IL/(1+Gp*Rs) - (Gp*V)/(1+Gp*Rs) = beta0 + beta1*V 3) fit the exponential portion of the IV curve defined by - beta0 + beta1*V - I > ilim * i_sc, where + beta0 + beta1*V - I > ilim * i_sc, where exp((V+I*Rs)/(nNsVth)) >> 1 so that exp((V+I*Rs)/(nNsVth)) - 1 ≈ exp((V+I*Rs)/(nNsVth)) From 2517b33b9f2e33284e6153dca21141b7a89cd583 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Tue, 25 Jun 2019 12:22:59 -0600 Subject: [PATCH 39/68] implement exception for failed fitting --- pvlib/ivtools.py | 49 ++++++++++++++++---------------------- pvlib/test/test_ivtools.py | 12 +++++----- 2 files changed, 26 insertions(+), 35 deletions(-) diff --git a/pvlib/ivtools.py b/pvlib/ivtools.py index 459d6cb679..4a5d8dd665 100644 --- a/pvlib/ivtools.py +++ b/pvlib/ivtools.py @@ -65,7 +65,8 @@ def fit_cec_sam(celltype, v_mp, i_mp, v_oc, i_sc, alpha_sc, beta_voc, current, in percent. Raises: - ImportError if NREL-PySAM is not installed + ImportError if NREL-PySAM is not installed. + RuntimeError if parameter extraction is not successful. Notes ----- @@ -76,8 +77,6 @@ def fit_cec_sam(celltype, v_mp, i_mp, v_oc, i_sc, alpha_sc, beta_voc, ``Tref`` are the reference conditions for the output parameters ``I_L_ref``, ``I_o_ref``, ``R_sh_ref``, ``R_s`` and ``Adjust``. - If the fitting fails, returns NaN in each parameter. - References ---------- [1] A. Dobos, "An Improved Coefficient Calculator for the California @@ -99,27 +98,15 @@ def fit_cec_sam(celltype, v_mp, i_mp, v_oc, i_sc, alpha_sc, beta_voc, result = PySSC.ssc_sim_from_dict(datadict) if result['cmod_success'] == 1: - a_ref = result['a'] - I_L_ref = result['Il'] - I_o_ref = result['Io'] - R_s = result['Rs'] - R_sh_ref = result['Rsh'] - Adjust = result['Adj'] + return tuple([result[k] for k in ['Il', 'Io', 'Rsh', 'Rs', 'a', + 'Adj']]) else: - a_ref = np.nan - I_L_ref = np.nan - I_o_ref = np.nan - R_s = np.nan - R_sh_ref = np.nan - Adjust = np.nan - return I_L_ref, I_o_ref, R_sh_ref, R_s, a_ref, Adjust + raise RuntimeError('Parameter estimation failed') def fit_sde_sandia(v, i, v_oc=None, i_sc=None, mp=None, vlim=0.2, ilim=0.1): """ Fits the single diode equation to an IV curve. - If fitting fails, returns NaN in each parameter. - Parameters ---------- v : ndarray @@ -169,6 +156,9 @@ def fit_sde_sandia(v, i, v_oc=None, i_sc=None, mp=None, vlim=0.2, ilim=0.1): product of thermal voltage ``Vth`` [V], diode ideality factor ``n``, and number of series cells ``Ns`` + Raises: + RuntimeError if parameter extraction is not successful. + Notes ----- Inputs ``v``, ``i``, ``v_mp``, ``v_oc``, ``i_mp`` and ``i_sc`` are assumed @@ -215,11 +205,6 @@ def fit_sde_sandia(v, i, v_oc=None, i_sc=None, mp=None, vlim=0.2, ilim=0.1): 4) calculate values for ``IL, I0, Rs, Rsh,`` and ``nNsVth`` from the regression coefficents beta0, beta1, beta3 and beta4. - Returns ``NaN`` for each parameter if the fitting is not successful. If - ``NaN`` is returned one likely cause is the input IV curve having too few - points. It is recommend that the input IV curve contain 100 or more points, - and more points increase the likelihood of extracting reasonable - parameters. References ---------- @@ -252,11 +237,15 @@ def fit_sde_sandia(v, i, v_oc=None, i_sc=None, mp=None, vlim=0.2, ilim=0.1): beta3, beta4 = _find_beta3_beta4(y, x, ilim, i_sc) # calculate single diode parameters from regression coefficients - IL, I0, Rsh, Rs, nNsVth = _calculate_sde_parameters(beta0, beta1, beta3, - beta4, v_mp, i_mp, - v_oc) + result = _calculate_sde_parameters(beta0, beta1, beta3, beta4, v_mp, i_mp, + v_oc) - return IL, I0, Rsh, Rs, nNsVth + if result is None: + raise RuntimeError("Parameter extraction failed. Try increasing the" + "number of data points in the IV curve") + else: + IL, I0, Rsh, Rs, nNsVth = result + return IL, I0, Rsh, Rs, nNsVth def _find_mp(v, i): @@ -338,5 +327,7 @@ def _calculate_sde_parameters(beta0, beta1, beta3, beta4, v_mp, i_mp, v_oc): else: failed = True if failed: - IL = I0 = Rsh = Rs = nNsVth = np.nan - return IL, I0, Rsh, Rs, nNsVth + result = None + else: + result = (IL, I0, Rsh, Rs, nNsVth) + return result diff --git a/pvlib/test/test_ivtools.py b/pvlib/test/test_ivtools.py index 5bda28b935..370f2d0476 100644 --- a/pvlib/test/test_ivtools.py +++ b/pvlib/test/test_ivtools.py @@ -93,9 +93,9 @@ def test_fit_cec_sam(cec_module_parameters, get_cec_params_cansol_cs5p_220p): expected[k] = sam_parameters[k] assert np.allclose(modeled.values, expected.values, rtol=5e-2) # test for fitting failure - I_L_ref, I_o_ref, R_sh_ref, R_s, a_ref, Adjust = \ - ivtools.fit_cec_sam( - celltype='polySi', v_mp=0.45, i_mp=5.25, v_oc=0.55, i_sc=5.5, - alpha_sc=0.00275, beta_voc=0.00275, gamma_pmp=0.0055, - cells_in_series=1, temp_ref=25) - assert all(np.isnan([I_L_ref, I_o_ref, R_sh_ref, R_s, a_ref, Adjust])) + with pytest.raises(RuntimeError): + I_L_ref, I_o_ref, R_sh_ref, R_s, a_ref, Adjust = \ + ivtools.fit_cec_sam( + celltype='polySi', v_mp=0.45, i_mp=5.25, v_oc=0.55, i_sc=5.5, + alpha_sc=0.00275, beta_voc=0.00275, gamma_pmp=0.0055, + cells_in_series=1, temp_ref=25) From 320ebf92abcc4add1d82f4f52bc007098fa4326c Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Thu, 27 Jun 2019 10:48:29 -0600 Subject: [PATCH 40/68] remove requirement for sorted i --- pvlib/ivtools.py | 39 ++++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/pvlib/ivtools.py b/pvlib/ivtools.py index 4a5d8dd665..82d15c404d 100644 --- a/pvlib/ivtools.py +++ b/pvlib/ivtools.py @@ -110,30 +110,32 @@ def fit_sde_sandia(v, i, v_oc=None, i_sc=None, mp=None, vlim=0.2, ilim=0.1): Parameters ---------- v : ndarray - 1D array containing voltage at each point on the IV curve, increasing - from 0 to v_oc inclusive, of `float` type [V] + 1D array of `float` type containing voltage at each point on the IV + curve, increasing from 0 to v_oc inclusive [V] i : ndarray - 1D array containing current at each point on the IV curve, decreasing - from i_sc to 0 inclusive, of `float` type [A] + 1D array of `float` type containing current at each point on the IV + curve, from i_sc to 0 inclusive, of `float` type [A] v_oc : float, default None - Open circuit voltage [V]. If not provided, the last value in input - ``v`` is taken as ``v_oc``. + Open circuit voltage [V]. If not provided, ``v_oc`` is taken as + ``v[-1]``. i_sc : float, default None - Short circuit current [A]. If not provided, the first value in input - ``i`` is taken as ``i_sc``. + Short circuit current [A]. If not provided, ``i_sc`` is taken as + ``i[0]``. mp : tuple of float, default None Voltage, current at maximum power point in units of [V], [A]. If not provided, values are found from inputs ``v`` and ``i``. vlim : float, default 0.2 - defines linear portion of IV curve i.e. V <= vlim * v_oc [V] + defines portion of IV curve where the exponential term in the single + diode equation can be neglected, i.e. V <= vlim * v_oc [V] ilim : float, default 0.1 - defines exponential portion of IV curve, approximately defined by + defines portion of the IV curve where the exponential term in the + single diode equation is signficant, approximately defined by I < (1 - ilim) * i_sc [A] Returns @@ -229,12 +231,7 @@ def fit_sde_sandia(v, i, v_oc=None, i_sc=None, mp=None, vlim=0.2, ilim=0.1): beta0, beta1 = _find_beta0_beta1(v, i, vlim, v_oc) if not np.isnan(beta0): - # Subtract the IV curve from the linear fit. Select points where - # beta0 + beta*V - I > ilim * i_sc - # in order to find beta3 and beta4 from exponential portion of IV curve - y = beta0 - beta1 * v - i - x = np.array([np.ones_like(v), v, i]).T - beta3, beta4 = _find_beta3_beta4(y, x, ilim, i_sc) + beta3, beta4 = _find_beta3_beta4(v, i, beta0, beta1, ilim, i_sc) # calculate single diode parameters from regression coefficients result = _calculate_sde_parameters(beta0, beta1, beta3, beta4, v_mp, i_mp, @@ -296,9 +293,13 @@ def _find_beta0_beta1(v, i, vlim, v_oc): return beta0, beta1 -def _find_beta3_beta4(y, x, ilim, i_sc): - idx = np.searchsorted(y, ilim * i_sc) - 1 - result = np.linalg.lstsq(x[idx:, ], np.log(y[idx:]), rcond=None) +def _find_beta3_beta4(v, i, beta0, beta1, ilim, i_sc): + # Subtract the IV curve from the linear fit. + y = beta0 - beta1 * v - i + x = np.array([np.ones_like(v), v, i]).T + # Select points where y > ilim * i_sc to regress log(y) onto x + result = np.linalg.lstsq(x[y > ilim * i_sc], np.log(y[y > ilim * i_sc]), + rcond=None) coef = result[0] beta3 = coef[1].item() beta4 = coef[2].item() From d66701c1939d384deab26a8f88b0e1e8c2772b83 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Thu, 11 Jul 2019 18:15:37 -0600 Subject: [PATCH 41/68] update internal variable name for consistency --- pvlib/ivtools.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pvlib/ivtools.py b/pvlib/ivtools.py index f9763f538f..df1b98c2b8 100644 --- a/pvlib/ivtools.py +++ b/pvlib/ivtools.py @@ -289,14 +289,14 @@ def _calculate_sde_parameters(beta0, beta1, beta3, beta4, v_mp, i_mp, v_oc): Rsh = 1.0 / Gp IL = (1 + Gp * Rs) * beta0 # calculate I0 - I0_v_mp = _calc_I0(IL, i_mp, v_mp, Gp, Rs, beta3) - I0_v_oc = _calc_I0(IL, 0, v_oc, Gp, Rs, beta3) - if (I0_v_mp > 0) and (I0_v_oc > 0): - I0 = 0.5 * (I0_v_mp + I0_v_oc) - elif (I0_v_mp > 0): - I0 = I0_v_mp - elif (I0_v_oc > 0): - I0 = I0_v_oc + I0_vmp = _calc_I0(IL, i_mp, v_mp, Gp, Rs, beta3) + I0_voc = _calc_I0(IL, 0, v_oc, Gp, Rs, beta3) + if (I0_vmp > 0) and (I0_voc > 0): + I0 = 0.5 * (I0_vmp + I0_voc) + elif (I0_vmp > 0): + I0 = I0_vmp + elif (I0_voc > 0): + I0 = I0_voc else: I0 = np.nan else: From 03287559350c35e556a4f5efbf7a7a1a564befbc Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Mon, 15 Jul 2019 20:56:30 -0600 Subject: [PATCH 42/68] add to __init__.py --- pvlib/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pvlib/__init__.py b/pvlib/__init__.py index 238d481615..d2572d1529 100644 --- a/pvlib/__init__.py +++ b/pvlib/__init__.py @@ -7,6 +7,7 @@ from pvlib import location from pvlib import solarposition from pvlib import iotools +from pvlib import ivtools from pvlib import tracking from pvlib import pvsystem from pvlib import spa From cff625b6bfcba8a16888632fce910435a4ef5a01 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Tue, 16 Jul 2019 09:48:41 -0600 Subject: [PATCH 43/68] code style changes --- pvlib/ivtools.py | 97 ++++++++++++++++++++++-------------------------- 1 file changed, 45 insertions(+), 52 deletions(-) diff --git a/pvlib/ivtools.py b/pvlib/ivtools.py index 6b148fa275..546b877f38 100644 --- a/pvlib/ivtools.py +++ b/pvlib/ivtools.py @@ -35,17 +35,12 @@ def fit_cec_sam(celltype, v_mp, i_mp, v_oc, i_sc, alpha_sc, beta_voc, cells_in_series : int Number of cells in series temp_ref : float, default 25C - Reference temperature condition + Reference temperature condition [C] Returns ------- tuple of the following elements: - a_ref : float - The product of the usual diode ideality factor ``n`` (unitless), - number of cells in series ``Ns``, and cell thermal voltage at - reference conditions [V] - I_L_ref : float The light-generated current (or photocurrent) at reference conditions [A] @@ -60,6 +55,11 @@ def fit_cec_sam(celltype, v_mp, i_mp, v_oc, i_sc, alpha_sc, beta_voc, R_s : float The series resistance at reference conditions, in ohms. + a_ref : float + The product of the usual diode ideality factor ``n`` (unitless), + number of cells in series ``Ns``, and cell thermal voltage at + reference conditions [V] + Adjust : float The adjustment to the temperature coefficient for short circuit current, in percent. @@ -115,7 +115,7 @@ def fit_sde_sandia(v, i, v_oc=None, i_sc=None, mp=None, vlim=0.2, ilim=0.1): i : ndarray 1D array of `float` type containing current at each point on the IV - curve, from i_sc to 0 inclusive, of `float` type [A] + curve, from i_sc to 0 inclusive [A] v_oc : float, default None Open circuit voltage [V]. If not provided, ``v_oc`` is taken as @@ -163,7 +163,7 @@ def fit_sde_sandia(v, i, v_oc=None, i_sc=None, mp=None, vlim=0.2, ilim=0.1): Notes ----- - Inputs ``v``, ``i``, ``v_mp``, ``v_oc``, ``i_mp`` and ``i_sc`` are assumed + Inputs ``v``, ``i``, ``v_oc``, ``i_sc`` and ``mp`` are assumed to be from a single IV curve at constant irradiance and cell temperature. :py:func:`fit_sde_sandia` obtains values for the five parameters for the @@ -178,10 +178,6 @@ def fit_sde_sandia(v, i, v_oc=None, i_sc=None, mp=None, vlim=0.2, ilim=0.1): The extraction method [2] proceeds in four steps: 1) In the single diode equation, replace Rsh = 1/Gp and re-arrange - .. math:: - - I = IL - I0*exp((V+I*Rs)/(nNsVth) - 1) - (V + I*Rs)/Rsh - .. math:: I = IL/(1+Gp*Rs) - (Gp*V)/(1+Gp*Rs) - @@ -230,19 +226,20 @@ def fit_sde_sandia(v, i, v_oc=None, i_sc=None, mp=None, vlim=0.2, ilim=0.1): # Find beta0 and beta1 from linear portion of the IV curve beta0, beta1 = _find_beta0_beta1(v, i, vlim, v_oc) - if not np.isnan(beta0): + if not np.isnan(beta0): # linear fit successful beta3, beta4 = _find_beta3_beta4(v, i, beta0, beta1, ilim, i_sc) - # calculate single diode parameters from regression coefficients - result = _calculate_sde_parameters(beta0, beta1, beta3, beta4, v_mp, i_mp, - v_oc) - - if result is None: - raise RuntimeError("Parameter extraction failed. Try increasing the" - "number of data points in the IV curve") + if any(np.isnan([beta0, beta1, beta3, beta4])): + raise RuntimeError("Parameter extraction failed", + " beta0: {}, beta1: {}, beta3: {}, beta4: {}" + .format(beta0, beta1, beta3, beta4)) + return (np.nan, np.nan, np.nan, np.nan) else: - IL, I0, Rsh, Rs, nNsVth = result - return IL, I0, Rsh, Rs, nNsVth + # calculate single diode parameters from regression coefficients + result = _calculate_sde_parameters(beta0, beta1, beta3, beta4, v_mp, + i_mp, v_oc) + + return result def _find_mp(v, i): @@ -279,8 +276,8 @@ def _find_beta0_beta1(v, i, vlim, v_oc): # negative (downward). beta0 = np.nan beta1 = np.nan - idx = np.searchsorted(v, vlim * v_oc) - while idx <= len(v): + first_idx = np.searchsorted(v, vlim * v_oc) + for idx in range(first_idx, len(v)): coef = np.polyfit(v[:idx], i[:idx], deg=1) if coef[0] < 0: # intercept term @@ -288,8 +285,6 @@ def _find_beta0_beta1(v, i, vlim, v_oc): # sign change of slope to get positive parameter value beta1 = -coef[0].item() break - else: - idx += 1 return beta0, beta1 @@ -298,7 +293,8 @@ def _find_beta3_beta4(v, i, beta0, beta1, ilim, i_sc): y = beta0 - beta1 * v - i x = np.array([np.ones_like(v), v, i]).T # Select points where y > ilim * i_sc to regress log(y) onto x - result = np.linalg.lstsq(x[y > ilim * i_sc], np.log(y[y > ilim * i_sc]), + idx = (y > ilim * i_sc) + result = np.linalg.lstsq(x[idx], np.log(y[idx]), rcond=None) coef = result[0] beta3 = coef[1].item() @@ -307,28 +303,25 @@ def _find_beta3_beta4(v, i, beta0, beta1, ilim, i_sc): def _calculate_sde_parameters(beta0, beta1, beta3, beta4, v_mp, i_mp, v_oc): - failed = False - if any(np.isnan([beta0, beta1, beta3, beta4])): - failed = True - else: - nNsVth = 1.0 / beta3 - Rs = beta4 / beta3 - Gp = beta1 / (1.0 - Rs * beta1) - Rsh = 1.0 / Gp - IL = (1 + Gp * Rs) * beta0 - # calculate I0 - I0_vmp = _calc_I0(IL, i_mp, v_mp, Gp, Rs, beta3) - I0_voc = _calc_I0(IL, 0, v_oc, Gp, Rs, beta3) - if (I0_vmp > 0) and (I0_voc > 0): - I0 = 0.5 * (I0_vmp + I0_voc) - elif (I0_vmp > 0): - I0 = I0_vmp - elif (I0_voc > 0): - I0 = I0_voc - else: - failed = True - if failed: - result = None - else: - result = (IL, I0, Rsh, Rs, nNsVth) - return result + nNsVth = 1.0 / beta3 + Rs = beta4 / beta3 + Gp = beta1 / (1.0 - Rs * beta1) + Rsh = 1.0 / Gp + IL = (1 + Gp * Rs) * beta0 + # calculate I0 + I0_vmp = _calc_I0(IL, i_mp, v_mp, Gp, Rs, beta3) + I0_voc = _calc_I0(IL, 0, v_oc, Gp, Rs, beta3) + if any(np.isnan([I0_vmp, I0_voc])): + raise RuntimeError("Parameter extraction failed: I0 is negative. Try " + "increasing the number of data points in the IV " + "curve") + I0 = np.nan + elif (I0_vmp <= 0) and (I0_voc <= 0): + I0 = np.nan + elif (I0_vmp > 0) and (I0_voc > 0): + I0 = 0.5 * (I0_vmp + I0_voc) + elif (I0_vmp > 0): + I0 = I0_vmp + else: # I0_voc > 0 + I0 = I0_voc + return (IL, I0, Rsh, Rs, nNsVth) From f8a48603f07f02ce2556f240ed3ebad137aa37e3 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Tue, 16 Jul 2019 09:50:43 -0600 Subject: [PATCH 44/68] lint --- pvlib/ivtools.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pvlib/ivtools.py b/pvlib/ivtools.py index 546b877f38..b68ef78ac9 100644 --- a/pvlib/ivtools.py +++ b/pvlib/ivtools.py @@ -226,7 +226,7 @@ def fit_sde_sandia(v, i, v_oc=None, i_sc=None, mp=None, vlim=0.2, ilim=0.1): # Find beta0 and beta1 from linear portion of the IV curve beta0, beta1 = _find_beta0_beta1(v, i, vlim, v_oc) - if not np.isnan(beta0): # linear fit successful + if not np.isnan(beta0): # linear fit successful beta3, beta4 = _find_beta3_beta4(v, i, beta0, beta1, ilim, i_sc) if any(np.isnan([beta0, beta1, beta3, beta4])): @@ -322,6 +322,6 @@ def _calculate_sde_parameters(beta0, beta1, beta3, beta4, v_mp, i_mp, v_oc): I0 = 0.5 * (I0_vmp + I0_voc) elif (I0_vmp > 0): I0 = I0_vmp - else: # I0_voc > 0 + else: # I0_voc > 0 I0 = I0_voc return (IL, I0, Rsh, Rs, nNsVth) From e8be7169ff855ef943f2e7dd1ada5812efed3a5c Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Tue, 16 Jul 2019 10:19:34 -0600 Subject: [PATCH 45/68] adjust error messaging in _calculate_sde_parameters --- pvlib/ivtools.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/pvlib/ivtools.py b/pvlib/ivtools.py index b68ef78ac9..be0870ba1f 100644 --- a/pvlib/ivtools.py +++ b/pvlib/ivtools.py @@ -311,13 +311,8 @@ def _calculate_sde_parameters(beta0, beta1, beta3, beta4, v_mp, i_mp, v_oc): # calculate I0 I0_vmp = _calc_I0(IL, i_mp, v_mp, Gp, Rs, beta3) I0_voc = _calc_I0(IL, 0, v_oc, Gp, Rs, beta3) - if any(np.isnan([I0_vmp, I0_voc])): - raise RuntimeError("Parameter extraction failed: I0 is negative. Try " - "increasing the number of data points in the IV " - "curve") - I0 = np.nan - elif (I0_vmp <= 0) and (I0_voc <= 0): - I0 = np.nan + if any(np.isnan([I0_vmp, I0_voc])) or ((I0_vmp <= 0) and (I0_voc <= 0)): + raise RuntimeError("Parameter extraction failed: I0 is undetermined. ") elif (I0_vmp > 0) and (I0_voc > 0): I0 = 0.5 * (I0_vmp + I0_voc) elif (I0_vmp > 0): From 9af20f7f5d6f010cca7eb3272bf9710da025e4be Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Tue, 16 Jul 2019 13:57:44 -0600 Subject: [PATCH 46/68] edits to units in docstring --- pvlib/ivtools.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pvlib/ivtools.py b/pvlib/ivtools.py index be0870ba1f..97dfaddb2d 100644 --- a/pvlib/ivtools.py +++ b/pvlib/ivtools.py @@ -34,7 +34,7 @@ def fit_cec_sam(celltype, v_mp, i_mp, v_oc, i_sc, alpha_sc, beta_voc, Temperature coefficient of power at maximum point point [%/C] cells_in_series : int Number of cells in series - temp_ref : float, default 25C + temp_ref : float, default 25 Reference temperature condition [C] Returns @@ -149,10 +149,10 @@ def fit_sde_sandia(v, i, v_oc=None, i_sc=None, mp=None, vlim=0.2, ilim=0.1): dark (saturation) current [A] resistance_shunt : float - shunt (parallel) resistance, ohm + shunt (parallel) resistance, in ohms resistance_series : float - series resistance, ohm + series resistance, in ohms nNsVth : float product of thermal voltage ``Vth`` [V], diode ideality factor From ee19ed16f9e3a1555b533b437011e1800387fa49 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Tue, 16 Jul 2019 15:49:30 -0600 Subject: [PATCH 47/68] change error handling in fit_sde_sandia, docstring reformat --- pvlib/ivtools.py | 190 +++++++++++++++++++++++++++-------------------- 1 file changed, 110 insertions(+), 80 deletions(-) diff --git a/pvlib/ivtools.py b/pvlib/ivtools.py index 97dfaddb2d..58f9c4e2eb 100644 --- a/pvlib/ivtools.py +++ b/pvlib/ivtools.py @@ -10,7 +10,7 @@ def fit_cec_sam(celltype, v_mp, i_mp, v_oc, i_sc, alpha_sc, beta_voc, gamma_pmp, cells_in_series, temp_ref=25): - ''' + """ Estimates parameters for the CEC single diode model [1] using the SAM SDK. Parameters @@ -41,31 +41,33 @@ def fit_cec_sam(celltype, v_mp, i_mp, v_oc, i_sc, alpha_sc, beta_voc, ------- tuple of the following elements: - I_L_ref : float + * I_L_ref : float The light-generated current (or photocurrent) at reference conditions [A] - I_o_ref : float + * I_o_ref : float The dark or diode reverse saturation current at reference conditions [A] - R_sh_ref : float + * R_sh_ref : float The shunt resistance at reference conditions, in ohms. - R_s : float + * R_s : float The series resistance at reference conditions, in ohms. - a_ref : float + * a_ref : float The product of the usual diode ideality factor ``n`` (unitless), number of cells in series ``Ns``, and cell thermal voltage at reference conditions [V] - Adjust : float + * Adjust : float The adjustment to the temperature coefficient for short circuit current, in percent. - Raises: + Raises + ------ ImportError if NREL-PySAM is not installed. + RuntimeError if parameter extraction is not successful. Notes @@ -82,7 +84,7 @@ def fit_cec_sam(celltype, v_mp, i_mp, v_oc, i_sc, alpha_sc, beta_voc, [1] A. Dobos, "An Improved Coefficient Calculator for the California Energy Commission 6 Parameter Photovoltaic Module Model", Journal of Solar Energy Engineering, vol 134, 2012. - ''' + """ try: from PySAM import PySSC @@ -104,66 +106,70 @@ def fit_cec_sam(celltype, v_mp, i_mp, v_oc, i_sc, alpha_sc, beta_voc, raise RuntimeError('Parameter estimation failed') -def fit_sde_sandia(v, i, v_oc=None, i_sc=None, mp=None, vlim=0.2, ilim=0.1): - """ Fits the single diode equation to an IV curve. +def fit_sde_sandia(voltage, current, v_oc=None, i_sc=None, v_mp_i_mp=None, + vlim=0.2, ilim=0.1): + """Fits the single diode equation to an IV curve. Parameters ---------- - v : ndarray + voltage : ndarray 1D array of `float` type containing voltage at each point on the IV - curve, increasing from 0 to v_oc inclusive [V] + curve, increasing from 0 to ``v_oc`` inclusive [V] - i : ndarray + current : ndarray 1D array of `float` type containing current at each point on the IV - curve, from i_sc to 0 inclusive [A] + curve, from ``i_sc`` to 0 inclusive [A] v_oc : float, default None - Open circuit voltage [V]. If not provided, ``v_oc`` is taken as - ``v[-1]``. + Open circuit voltage [V]. If not provided, ``v_oc`` is taken as the + last point in the ``voltage`` array. i_sc : float, default None - Short circuit current [A]. If not provided, ``i_sc`` is taken as - ``i[0]``. + Short circuit current [A]. If not provided, ``i_sc`` is taken as the + first point in the ``current`` array. - mp : tuple of float, default None + v_mp_i_mp : tuple of float, default None Voltage, current at maximum power point in units of [V], [A]. - If not provided, values are found from inputs ``v`` and ``i``. + If not provided, the maximum power point is found at the maximum of + ``voltage`` x ``current``. vlim : float, default 0.2 defines portion of IV curve where the exponential term in the single - diode equation can be neglected, i.e. V <= vlim * v_oc [V] + diode equation can be neglected, i.e. + ``voltage`` <= ``vlim`` * ``v_oc`` [V] ilim : float, default 0.1 defines portion of the IV curve where the exponential term in the single diode equation is signficant, approximately defined by - I < (1 - ilim) * i_sc [A] + ``current`` < (1 - ``ilim``) * ``i_sc`` [A] Returns ------- tuple of the following elements: - photocurrent : float + * photocurrent : float photocurrent [A] - saturation_current : float + * saturation_current : float dark (saturation) current [A] - resistance_shunt : float + * resistance_shunt : float shunt (parallel) resistance, in ohms - resistance_series : float + * resistance_series : float series resistance, in ohms - nNsVth : float + * nNsVth : float product of thermal voltage ``Vth`` [V], diode ideality factor ``n``, and number of series cells ``Ns`` - Raises: + Raises + ------ RuntimeError if parameter extraction is not successful. Notes ----- - Inputs ``v``, ``i``, ``v_oc``, ``i_sc`` and ``mp`` are assumed + Inputs ``voltage``, ``current``, ``v_oc``, ``i_sc`` and ``mp`` are assumed to be from a single IV curve at constant irradiance and cell temperature. :py:func:`fit_sde_sandia` obtains values for the five parameters for the @@ -171,36 +177,59 @@ def fit_sde_sandia(v, i, v_oc=None, i_sc=None, mp=None, vlim=0.2, ilim=0.1): .. math:: - I = IL - I0*[exp((V+I*Rs)/(nNsVth))-1] - (V + I*Rs)/Rsh + I = I_{L} - I_{0} \times [\exp \frac{(V + I \times R_{s})}{nNsVth} - 1] + - \frac{V + I \times R_{s}}{R_{sh}} + + See :py:func:`pvsystem.singlediode` for definition of the parameters. - :py:func:`pvsystem.singlediode` for definition of the parameters. + The extraction method [2] proceeds in four steps. + 1. In the single diode equation, replace :math:`R_{sh} = 1/Gp` and + re-arrange - The extraction method [2] proceeds in four steps: - 1) In the single diode equation, replace Rsh = 1/Gp and re-arrange + .. math:: + :wrap: + + I = \frac{I_{L}}{1 + G_{p} \times R_{s}} + - \frac{G_{p} \times V}{1 + G_{p} \times R_{s}} + - \frac{I_{0}}{1 + G_{p} \times R_{s}} \times + [\exp(\frac{V + I \times R_{s}}{nNsVth}) - 1] + + 2. The linear portion of the IV curve is defined as + :math:`V \le vlim \times v_oc`. Over this portion of the IV curve, .. math:: - I = IL/(1+Gp*Rs) - (Gp*V)/(1+Gp*Rs) - - I0/(1+Gp*Rs)*exp((V+I*Rs)/(nNsVth) - 1) + \frac{I_{0}}{1 + G_{p} \times R_{s}} \times + [\exp(\frac{V + I \times R_{s}}{nNsVth}) - 1] \approx 0 - 2) fit the linear portion of the IV curve defined as V <= vlim * v_oc, - where I0/(1+Gp*Rs)*exp((V+I*Rs)/(nNsVth) - 1) ≈ 0 + Fit the linear portion of the IV curve with a line. .. math:: - I ~ IL/(1+Gp*Rs) - (Gp*V)/(1+Gp*Rs) = beta0 + beta1*V + I &\approx \frac{I_{L}}{1 + G_{p} \times R_{s}} + - \frac{G_{p} \times V}{1 + G_{p} \times R_{s}} \\ + &= \beta_{0} + \beta_{1} \times V - 3) fit the exponential portion of the IV curve defined by - beta0 + beta1*V - I > ilim * i_sc, where - exp((V+I*Rs)/(nNsVth)) >> 1 so that - exp((V+I*Rs)/(nNsVth)) - 1 ≈ exp((V+I*Rs)/(nNsVth)) + 3. The exponential portion of the IV curve is defined by + :math:`\beta_{0} + \beta_{1} \times V - I > ilim \times i_sc`. + Over this portion of the curve, :math:`exp((V+I*Rs)/(nNsVth)) >> 1` + so that .. math:: - log(beta0 - beta1*V - I) ~ log((I0)/(1+Gp*Rs)) + (V)/(nNsVth) + - (Rs*I)/(nNsVth) = beta2 + beta3*V + beta4*I + \exp(\frac{V + I \times R_{s}}{nNsVth}) - 1 \approx + \exp(\frac{V + I \times R_{s}}{nNsVth}) + + Fit the exponential portion of the IV curve. - 4) calculate values for ``IL, I0, Rs, Rsh,`` and ``nNsVth`` from the + .. math:: + + \log(\beta_{0} - \beta_{1} \times V - I) &\approx + \log(\frac{I_{0}}{1 + G_{p} \times R_{s}} + \frac{V}{nNsVth} + + \frac{I R_{s}}{nNsVth} \\ + &= \beta_{2} + beta_{3} \times V + \beta_{4} \times I + + 4. Calculate values for ``IL, I0, Rs, Rsh,`` and ``nNsVth`` from the regression coefficents beta0, beta1, beta3 and beta4. @@ -215,55 +244,49 @@ def fit_sde_sandia(v, i, v_oc=None, i_sc=None, mp=None, vlim=0.2, ilim=0.1): # If not provided, extract v_oc, i_sc, v_mp and i_mp from the IV curve data if v_oc is None: - v_oc = v[-1] + v_oc = voltage[-1] if i_sc is None: - i_sc = i[0] - if mp is not None: - v_mp, i_mp = mp + i_sc = current[0] + if v_mp_i_mp is not None: + v_mp, i_mp = v_mp_i_mp else: - v_mp, i_mp = _find_mp(v, i) + v_mp, i_mp = _find_mp(voltage, current) # Find beta0 and beta1 from linear portion of the IV curve - beta0, beta1 = _find_beta0_beta1(v, i, vlim, v_oc) + beta0, beta1 = _find_beta0_beta1(voltage, current, vlim, v_oc) - if not np.isnan(beta0): # linear fit successful - beta3, beta4 = _find_beta3_beta4(v, i, beta0, beta1, ilim, i_sc) + # Find beta3 and beta4 from the exponential portion of the IV curve + beta3, beta4 = _find_beta3_beta4(voltage, current, beta0, beta1, ilim, + i_sc) - if any(np.isnan([beta0, beta1, beta3, beta4])): - raise RuntimeError("Parameter extraction failed", - " beta0: {}, beta1: {}, beta3: {}, beta4: {}" - .format(beta0, beta1, beta3, beta4)) - return (np.nan, np.nan, np.nan, np.nan) - else: - # calculate single diode parameters from regression coefficients - result = _calculate_sde_parameters(beta0, beta1, beta3, beta4, v_mp, - i_mp, v_oc) + # calculate single diode parameters from regression coefficients + return _calculate_sde_parameters(beta0, beta1, beta3, beta4, v_mp, i_mp, + v_oc) - return result - -def _find_mp(v, i): +def _find_mp(voltage, current): """ Finds voltage and current at maximum power point. Parameters ---------- - v : ndarray + voltage : ndarray 1D array containing voltage at each point on the IV curve, increasing from 0 to v_oc inclusive, of `float` type [V] - i : ndarray + current : ndarray 1D array containing current at each point on the IV curve, decreasing from i_sc to 0 inclusive, of `float` type [A] Returns ------- - v, i : tuple - voltage ``v`` and current ``i`` at the maximum power point [V], [A] + v_mp, i_mp : tuple + voltage ``v_mp`` and current ``i_mp`` at the maximum power point [V], + [A] """ - p = v * i + p = voltage * current idx = np.argmax(p) - return v[idx], i[idx] + return voltage[idx], current[idx] def _calc_I0(IL, I, V, Gp, Rs, beta3): @@ -285,21 +308,28 @@ def _find_beta0_beta1(v, i, vlim, v_oc): # sign change of slope to get positive parameter value beta1 = -coef[0].item() break - return beta0, beta1 + if any(np.isnan([beta0, beta1])): + raise RuntimeError("Parameter extraction failed: beta0={}, beta1={}" + .format(beta0, beta1)) + else: + return beta0, beta1 -def _find_beta3_beta4(v, i, beta0, beta1, ilim, i_sc): +def _find_beta3_beta4(voltage, current, beta0, beta1, ilim, i_sc): # Subtract the IV curve from the linear fit. - y = beta0 - beta1 * v - i - x = np.array([np.ones_like(v), v, i]).T + y = beta0 - beta1 * voltage - current + x = np.array([np.ones_like(voltage), voltage, current]).T # Select points where y > ilim * i_sc to regress log(y) onto x idx = (y > ilim * i_sc) - result = np.linalg.lstsq(x[idx], np.log(y[idx]), - rcond=None) + result = np.linalg.lstsq(x[idx], np.log(y[idx]), rcond=None) coef = result[0] beta3 = coef[1].item() beta4 = coef[2].item() - return beta3, beta4 + if any(np.isnan([beta3, beta4])): + raise RuntimeError("Parameter extraction failed: beta3={}, beta4={}" + .format(beta3, beta4)) + else: + return beta3, beta4 def _calculate_sde_parameters(beta0, beta1, beta3, beta4, v_mp, i_mp, v_oc): @@ -312,7 +342,7 @@ def _calculate_sde_parameters(beta0, beta1, beta3, beta4, v_mp, i_mp, v_oc): I0_vmp = _calc_I0(IL, i_mp, v_mp, Gp, Rs, beta3) I0_voc = _calc_I0(IL, 0, v_oc, Gp, Rs, beta3) if any(np.isnan([I0_vmp, I0_voc])) or ((I0_vmp <= 0) and (I0_voc <= 0)): - raise RuntimeError("Parameter extraction failed: I0 is undetermined. ") + raise RuntimeError("Parameter extraction failed: I0 is undetermined.") elif (I0_vmp > 0) and (I0_voc > 0): I0 = 0.5 * (I0_vmp + I0_voc) elif (I0_vmp > 0): From aa47f48068eb6a892afe7c29b392d7c12aaad946 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Tue, 16 Jul 2019 16:13:24 -0600 Subject: [PATCH 48/68] fix tests, docstring edits --- pvlib/ivtools.py | 17 +++++++---------- pvlib/test/test_ivtools.py | 16 ++++++++++------ 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/pvlib/ivtools.py b/pvlib/ivtools.py index 58f9c4e2eb..c4c1d82850 100644 --- a/pvlib/ivtools.py +++ b/pvlib/ivtools.py @@ -108,7 +108,8 @@ def fit_cec_sam(celltype, v_mp, i_mp, v_oc, i_sc, alpha_sc, beta_voc, def fit_sde_sandia(voltage, current, v_oc=None, i_sc=None, v_mp_i_mp=None, vlim=0.2, ilim=0.1): - """Fits the single diode equation to an IV curve. + """ + Fits the single diode equation to an IV curve. Parameters ---------- @@ -136,29 +137,25 @@ def fit_sde_sandia(voltage, current, v_oc=None, i_sc=None, v_mp_i_mp=None, vlim : float, default 0.2 defines portion of IV curve where the exponential term in the single diode equation can be neglected, i.e. - ``voltage`` <= ``vlim`` * ``v_oc`` [V] + ``voltage`` <= ``vlim`` \times ``v_oc`` [V] ilim : float, default 0.1 defines portion of the IV curve where the exponential term in the single diode equation is signficant, approximately defined by - ``current`` < (1 - ``ilim``) * ``i_sc`` [A] + ``current`` < (1 - ``ilim``) \times ``i_sc`` [A] Returns ------- - tuple of the following elements: + A tuple of the following elements: * photocurrent : float photocurrent [A] - * saturation_current : float dark (saturation) current [A] - * resistance_shunt : float shunt (parallel) resistance, in ohms - * resistance_series : float series resistance, in ohms - * nNsVth : float product of thermal voltage ``Vth`` [V], diode ideality factor ``n``, and number of series cells ``Ns`` @@ -177,8 +174,8 @@ def fit_sde_sandia(voltage, current, v_oc=None, i_sc=None, v_mp_i_mp=None, .. math:: - I = I_{L} - I_{0} \times [\exp \frac{(V + I \times R_{s})}{nNsVth} - 1] - - \frac{V + I \times R_{s}}{R_{sh}} + I = I_{L} - I_{0} \times \[ \exp \frac{V + I \times R_{s}}{nNsVth} + - 1 \] - \frac{V + I \times R_{s}}{R_{sh}} See :py:func:`pvsystem.singlediode` for definition of the parameters. diff --git a/pvlib/test/test_ivtools.py b/pvlib/test/test_ivtools.py index 370f2d0476..37b62d5799 100644 --- a/pvlib/test/test_ivtools.py +++ b/pvlib/test/test_ivtools.py @@ -49,19 +49,23 @@ def test_fit_sde_sandia(get_test_iv_params): ivcurve_pnts=300) expected = tuple(test_params[k] for k in ['IL', 'I0', 'Rsh', 'Rs', 'nNsVth']) - result = ivtools.fit_sde_sandia(v=testcurve['v'], i=testcurve['i']) + result = ivtools.fit_sde_sandia(voltage=testcurve['v'], + current=testcurve['i']) assert np.allclose(result, expected, rtol=5e-5) - result = ivtools.fit_sde_sandia(v=testcurve['v'], i=testcurve['i'], + result = ivtools.fit_sde_sandia(voltage=testcurve['v'], + current=testcurve['i'], v_oc=testcurve['v_oc'], i_sc=testcurve['i_sc']) assert np.allclose(result, expected, rtol=5e-5) - result = ivtools.fit_sde_sandia(v=testcurve['v'], i=testcurve['i'], + result = ivtools.fit_sde_sandia(voltage=testcurve['v'], + current=testcurve['i'], v_oc=testcurve['v_oc'], i_sc=testcurve['i_sc'], - mp=(testcurve['v_mp'], - testcurve['i_mp'])) + v_mp_i_mp=(testcurve['v_mp'], + testcurve['i_mp'])) assert np.allclose(result, expected, rtol=5e-5) - result = ivtools.fit_sde_sandia(v=testcurve['v'], i=testcurve['i'], + result = ivtools.fit_sde_sandia(voltage=testcurve['v'], + current=testcurve['i'], vlim=0.1) assert np.allclose(result, expected, rtol=5e-5) From e55330f3a6fc274755f2ab5eea4bcbf41f4fb8f8 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Wed, 17 Jul 2019 10:13:28 -0600 Subject: [PATCH 49/68] test math formatting --- pvlib/ivtools.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pvlib/ivtools.py b/pvlib/ivtools.py index c4c1d82850..bdcb90221a 100644 --- a/pvlib/ivtools.py +++ b/pvlib/ivtools.py @@ -137,12 +137,12 @@ def fit_sde_sandia(voltage, current, v_oc=None, i_sc=None, v_mp_i_mp=None, vlim : float, default 0.2 defines portion of IV curve where the exponential term in the single diode equation can be neglected, i.e. - ``voltage`` <= ``vlim`` \times ``v_oc`` [V] + ``voltage`` <= ``vlim`` x ``v_oc`` [V] ilim : float, default 0.1 defines portion of the IV curve where the exponential term in the single diode equation is signficant, approximately defined by - ``current`` < (1 - ``ilim``) \times ``i_sc`` [A] + ``current`` < (1 - ``ilim``) x ``i_sc`` [A] Returns ------- @@ -174,8 +174,8 @@ def fit_sde_sandia(voltage, current, v_oc=None, i_sc=None, v_mp_i_mp=None, .. math:: - I = I_{L} - I_{0} \times \[ \exp \frac{V + I \times R_{s}}{nNsVth} - - 1 \] - \frac{V + I \times R_{s}}{R_{sh}} + r"I = I_{L} - I_{0} \times \[ \exp \frac{V + I \times R_{s}}{nNsVth}" + "- 1 \] - \frac{V + I \times R_{s}}{R_{sh}}" See :py:func:`pvsystem.singlediode` for definition of the parameters. From fb3be8ca2bd8e7fec4f50e6d4ce3a9731718ee46 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Wed, 17 Jul 2019 10:38:59 -0600 Subject: [PATCH 50/68] more edits to docstring --- pvlib/ivtools.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pvlib/ivtools.py b/pvlib/ivtools.py index bdcb90221a..104370bb5b 100644 --- a/pvlib/ivtools.py +++ b/pvlib/ivtools.py @@ -135,12 +135,12 @@ def fit_sde_sandia(voltage, current, v_oc=None, i_sc=None, v_mp_i_mp=None, ``voltage`` x ``current``. vlim : float, default 0.2 - defines portion of IV curve where the exponential term in the single + Defines portion of IV curve where the exponential term in the single diode equation can be neglected, i.e. ``voltage`` <= ``vlim`` x ``v_oc`` [V] ilim : float, default 0.1 - defines portion of the IV curve where the exponential term in the + Defines portion of the IV curve where the exponential term in the single diode equation is signficant, approximately defined by ``current`` < (1 - ``ilim``) x ``i_sc`` [A] @@ -173,6 +173,7 @@ def fit_sde_sandia(voltage, current, v_oc=None, i_sc=None, v_mp_i_mp=None, single diode equation [1]: .. math:: + :wrap: r"I = I_{L} - I_{0} \times \[ \exp \frac{V + I \times R_{s}}{nNsVth}" "- 1 \] - \frac{V + I \times R_{s}}{R_{sh}}" @@ -180,6 +181,7 @@ def fit_sde_sandia(voltage, current, v_oc=None, i_sc=None, v_mp_i_mp=None, See :py:func:`pvsystem.singlediode` for definition of the parameters. The extraction method [2] proceeds in four steps. + 1. In the single diode equation, replace :math:`R_{sh} = 1/Gp` and re-arrange From efe1cfb787b44a10e7e7469edc048e4dc6730e85 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Wed, 17 Jul 2019 11:06:51 -0600 Subject: [PATCH 51/68] multiline raw string --- pvlib/ivtools.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pvlib/ivtools.py b/pvlib/ivtools.py index 104370bb5b..6616ad41ec 100644 --- a/pvlib/ivtools.py +++ b/pvlib/ivtools.py @@ -175,8 +175,8 @@ def fit_sde_sandia(voltage, current, v_oc=None, i_sc=None, v_mp_i_mp=None, .. math:: :wrap: - r"I = I_{L} - I_{0} \times \[ \exp \frac{V + I \times R_{s}}{nNsVth}" - "- 1 \] - \frac{V + I \times R_{s}}{R_{sh}}" + (r"I = I_{L} - I_{0} \times ( \exp \frac{V + I \times R_{s}}{nNsVth}" + r" - 1 ) - \frac{V + I \times R_{s}}{R_{sh}}") See :py:func:`pvsystem.singlediode` for definition of the parameters. From 8d68c8c2c44db093a8848b1bcb0ab7a3bb58c36f Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Wed, 17 Jul 2019 11:30:55 -0600 Subject: [PATCH 52/68] try another approach --- pvlib/ivtools.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pvlib/ivtools.py b/pvlib/ivtools.py index 6616ad41ec..9bf37dfa2d 100644 --- a/pvlib/ivtools.py +++ b/pvlib/ivtools.py @@ -175,8 +175,8 @@ def fit_sde_sandia(voltage, current, v_oc=None, i_sc=None, v_mp_i_mp=None, .. math:: :wrap: - (r"I = I_{L} - I_{0} \times ( \exp \frac{V + I \times R_{s}}{nNsVth}" - r" - 1 ) - \frac{V + I \times R_{s}}{R_{sh}}") + I = I_{L} - I_{0} ( \exp \frac{V + I R_{s}}{nNsVth} - 1 ) + - \frac{V + I R_{s}}{R_{sh}} See :py:func:`pvsystem.singlediode` for definition of the parameters. From ec6fee5de7e89cabf77b5f22e51301fad8838fe3 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Wed, 17 Jul 2019 13:12:43 -0600 Subject: [PATCH 53/68] move r --- pvlib/ivtools.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pvlib/ivtools.py b/pvlib/ivtools.py index 9bf37dfa2d..9d21e23bad 100644 --- a/pvlib/ivtools.py +++ b/pvlib/ivtools.py @@ -108,7 +108,7 @@ def fit_cec_sam(celltype, v_mp, i_mp, v_oc, i_sc, alpha_sc, beta_voc, def fit_sde_sandia(voltage, current, v_oc=None, i_sc=None, v_mp_i_mp=None, vlim=0.2, ilim=0.1): - """ + r""" Fits the single diode equation to an IV curve. Parameters @@ -223,7 +223,7 @@ def fit_sde_sandia(voltage, current, v_oc=None, i_sc=None, v_mp_i_mp=None, .. math:: - \log(\beta_{0} - \beta_{1} \times V - I) &\approx + \log(\beta_{0} - \beta_{1} \times V - I) &\approx \log(\frac{I_{0}}{1 + G_{p} \times R_{s}} + \frac{V}{nNsVth} + \frac{I R_{s}}{nNsVth} \\ &= \beta_{2} + beta_{3} \times V + \beta_{4} \times I From 0270633f14e88bab3fc5970c9cd9eeddb102dcb3 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Wed, 17 Jul 2019 14:15:00 -0600 Subject: [PATCH 54/68] change function name, docstring edits --- pvlib/ivtools.py | 50 +++++++++++++++++++------------------- pvlib/test/test_ivtools.py | 34 +++++++++++++------------- 2 files changed, 42 insertions(+), 42 deletions(-) diff --git a/pvlib/ivtools.py b/pvlib/ivtools.py index 9d21e23bad..08a25314ab 100644 --- a/pvlib/ivtools.py +++ b/pvlib/ivtools.py @@ -76,8 +76,8 @@ def fit_cec_sam(celltype, v_mp, i_mp, v_oc, i_sc, alpha_sc, beta_voc, single IV curve at constant irradiance and cell temperature. Irradiance is not explicitly used by the fitting procedure. The irradiance level at which the input IV curve is determined and the specified cell temperature - ``Tref`` are the reference conditions for the output parameters - ``I_L_ref``, ``I_o_ref``, ``R_sh_ref``, ``R_s`` and ``Adjust``. + ``temp_ref`` are the reference conditions for the output parameters + ``I_L_ref``, ``I_o_ref``, ``R_sh_ref``, ``R_s``, ``a_ref`` and ``Adjust``. References ---------- @@ -106,8 +106,8 @@ def fit_cec_sam(celltype, v_mp, i_mp, v_oc, i_sc, alpha_sc, beta_voc, raise RuntimeError('Parameter estimation failed') -def fit_sde_sandia(voltage, current, v_oc=None, i_sc=None, v_mp_i_mp=None, - vlim=0.2, ilim=0.1): +def fit_single_diode_sandia(voltage, current, v_oc=None, i_sc=None, + v_mp_i_mp=None, vlim=0.2, ilim=0.1): r""" Fits the single diode equation to an IV curve. @@ -132,7 +132,7 @@ def fit_sde_sandia(voltage, current, v_oc=None, i_sc=None, v_mp_i_mp=None, v_mp_i_mp : tuple of float, default None Voltage, current at maximum power point in units of [V], [A]. If not provided, the maximum power point is found at the maximum of - ``voltage`` x ``current``. + ``voltage`` \times ``current``. vlim : float, default 0.2 Defines portion of IV curve where the exponential term in the single @@ -166,27 +166,26 @@ def fit_sde_sandia(voltage, current, v_oc=None, i_sc=None, v_mp_i_mp=None, Notes ----- - Inputs ``voltage``, ``current``, ``v_oc``, ``i_sc`` and ``mp`` are assumed - to be from a single IV curve at constant irradiance and cell temperature. + Inputs ``voltage``, ``current``, ``v_oc``, ``i_sc`` and ``v_mp_i_mp`` are + assumed to be from a single IV curve at constant irradiance and cell + temperature. - :py:func:`fit_sde_sandia` obtains values for the five parameters for the - single diode equation [1]: + :py:func:`fit_single_diode_sandia` obtains values for the five parameters + for the single diode equation [1]: .. math:: - :wrap: - I = I_{L} - I_{0} ( \exp \frac{V + I R_{s}}{nNsVth} - 1 ) + I = I_{L} - I_{0} (\exp \frac{V + I R_{s}}{nNsVth} - 1) - \frac{V + I R_{s}}{R_{sh}} See :py:func:`pvsystem.singlediode` for definition of the parameters. - The extraction method [2] proceeds in four steps. + The extraction method [2] proceeds in six steps. - 1. In the single diode equation, replace :math:`R_{sh} = 1/Gp` and + 1. In the single diode equation, replace :math:`R_{sh} = 1/G_{p}` and re-arrange .. math:: - :wrap: I = \frac{I_{L}}{1 + G_{p} \times R_{s}} - \frac{G_{p} \times V}{1 + G_{p} \times R_{s}} @@ -201,7 +200,7 @@ def fit_sde_sandia(voltage, current, v_oc=None, i_sc=None, v_mp_i_mp=None, \frac{I_{0}}{1 + G_{p} \times R_{s}} \times [\exp(\frac{V + I \times R_{s}}{nNsVth}) - 1] \approx 0 - Fit the linear portion of the IV curve with a line. + 3. Fit the linear portion of the IV curve with a line. .. math:: @@ -209,7 +208,7 @@ def fit_sde_sandia(voltage, current, v_oc=None, i_sc=None, v_mp_i_mp=None, - \frac{G_{p} \times V}{1 + G_{p} \times R_{s}} \\ &= \beta_{0} + \beta_{1} \times V - 3. The exponential portion of the IV curve is defined by + 4. The exponential portion of the IV curve is defined by :math:`\beta_{0} + \beta_{1} \times V - I > ilim \times i_sc`. Over this portion of the curve, :math:`exp((V+I*Rs)/(nNsVth)) >> 1` so that @@ -219,7 +218,7 @@ def fit_sde_sandia(voltage, current, v_oc=None, i_sc=None, v_mp_i_mp=None, \exp(\frac{V + I \times R_{s}}{nNsVth}) - 1 \approx \exp(\frac{V + I \times R_{s}}{nNsVth}) - Fit the exponential portion of the IV curve. + 5. Fit the exponential portion of the IV curve. .. math:: @@ -228,7 +227,7 @@ def fit_sde_sandia(voltage, current, v_oc=None, i_sc=None, v_mp_i_mp=None, + \frac{I R_{s}}{nNsVth} \\ &= \beta_{2} + beta_{3} \times V + \beta_{4} \times I - 4. Calculate values for ``IL, I0, Rs, Rsh,`` and ``nNsVth`` from the + 6. Calculate values for ``IL, I0, Rs, Rsh,`` and ``nNsVth`` from the regression coefficents beta0, beta1, beta3 and beta4. @@ -259,8 +258,8 @@ def fit_sde_sandia(voltage, current, v_oc=None, i_sc=None, v_mp_i_mp=None, i_sc) # calculate single diode parameters from regression coefficients - return _calculate_sde_parameters(beta0, beta1, beta3, beta4, v_mp, i_mp, - v_oc) + return _calculate_single_diode_parameters(beta0, beta1, beta3, beta4, v_mp, + i_mp, v_oc) def _find_mp(voltage, current): @@ -288,8 +287,8 @@ def _find_mp(voltage, current): return voltage[idx], current[idx] -def _calc_I0(IL, I, V, Gp, Rs, beta3): - return (IL - I - Gp * V - Gp * Rs * I) / np.exp(beta3 * (V + Rs * I)) +def _calc_I0(IL, I, V, Gp, Rs, nNsVth): + return (IL - I - Gp * V - Gp * Rs * I) / np.exp((V + Rs * I) / nNsVth) def _find_beta0_beta1(v, i, vlim, v_oc): @@ -331,15 +330,16 @@ def _find_beta3_beta4(voltage, current, beta0, beta1, ilim, i_sc): return beta3, beta4 -def _calculate_sde_parameters(beta0, beta1, beta3, beta4, v_mp, i_mp, v_oc): +def _calculate_single_diode_parameters(beta0, beta1, beta3, beta4, v_mp, i_mp, + v_oc): nNsVth = 1.0 / beta3 Rs = beta4 / beta3 Gp = beta1 / (1.0 - Rs * beta1) Rsh = 1.0 / Gp IL = (1 + Gp * Rs) * beta0 # calculate I0 - I0_vmp = _calc_I0(IL, i_mp, v_mp, Gp, Rs, beta3) - I0_voc = _calc_I0(IL, 0, v_oc, Gp, Rs, beta3) + I0_vmp = _calc_I0(IL, i_mp, v_mp, Gp, Rs, nNsVth) + I0_voc = _calc_I0(IL, 0, v_oc, Gp, Rs, nNsVth) if any(np.isnan([I0_vmp, I0_voc])) or ((I0_vmp <= 0) and (I0_voc <= 0)): raise RuntimeError("Parameter extraction failed: I0 is undetermined.") elif (I0_vmp > 0) and (I0_voc > 0): diff --git a/pvlib/test/test_ivtools.py b/pvlib/test/test_ivtools.py index 37b62d5799..78f22ddda8 100644 --- a/pvlib/test/test_ivtools.py +++ b/pvlib/test/test_ivtools.py @@ -39,7 +39,7 @@ def cec_module_parameters(sam_data): @requires_scipy -def test_fit_sde_sandia(get_test_iv_params): +def test_fit_single_diode_sandia(get_test_iv_params): test_params = get_test_iv_params testcurve = pvsystem.singlediode(photocurrent=test_params['IL'], saturation_current=test_params['I0'], @@ -49,24 +49,24 @@ def test_fit_sde_sandia(get_test_iv_params): ivcurve_pnts=300) expected = tuple(test_params[k] for k in ['IL', 'I0', 'Rsh', 'Rs', 'nNsVth']) - result = ivtools.fit_sde_sandia(voltage=testcurve['v'], - current=testcurve['i']) + result = ivtools.fit_single_diode_sandia(voltage=testcurve['v'], + current=testcurve['i']) assert np.allclose(result, expected, rtol=5e-5) - result = ivtools.fit_sde_sandia(voltage=testcurve['v'], - current=testcurve['i'], - v_oc=testcurve['v_oc'], - i_sc=testcurve['i_sc']) + result = ivtools.fit_single_diode_sandia(voltage=testcurve['v'], + current=testcurve['i'], + v_oc=testcurve['v_oc'], + i_sc=testcurve['i_sc']) assert np.allclose(result, expected, rtol=5e-5) - result = ivtools.fit_sde_sandia(voltage=testcurve['v'], - current=testcurve['i'], - v_oc=testcurve['v_oc'], - i_sc=testcurve['i_sc'], - v_mp_i_mp=(testcurve['v_mp'], - testcurve['i_mp'])) + result = ivtools.fit_single_diode_sandia(voltage=testcurve['v'], + current=testcurve['i'], + v_oc=testcurve['v_oc'], + i_sc=testcurve['i_sc'], + v_mp_i_mp=(testcurve['v_mp'], + testcurve['i_mp'])) assert np.allclose(result, expected, rtol=5e-5) - result = ivtools.fit_sde_sandia(voltage=testcurve['v'], - current=testcurve['i'], - vlim=0.1) + result = ivtools.fit_single_diode_sandia(voltage=testcurve['v'], + current=testcurve['i'], + vlim=0.1) assert np.allclose(result, expected, rtol=5e-5) @@ -81,7 +81,7 @@ def test_fit_cec_sam(cec_module_parameters, get_cec_params_cansol_cs5p_220p): i_sc=cec_list_data['I_sc_ref'], alpha_sc=cec_list_data['alpha_sc'], beta_voc=cec_list_data['beta_voc'], gamma_pmp=cec_list_data['gamma_pmp'], - cells_in_series=cec_list_data['cells_in_series'], temp_ref=25) + cells_in_series=cec_list_data['cells_in_series']) modeled = pd.Series(index=sam_parameters.index, data=cec_list_data) modeled['a_ref'] = a_ref modeled['I_L_ref'] = I_L_ref From 6a3735e03cc68067a72f7047404d065033f210a3 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Wed, 17 Jul 2019 14:41:59 -0600 Subject: [PATCH 55/68] update function name --- docs/sphinx/source/api.rst | 2 +- docs/sphinx/source/whatsnew/v0.7.0.rst | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/sphinx/source/api.rst b/docs/sphinx/source/api.rst index 8563bb65ac..332bcf78cd 100644 --- a/docs/sphinx/source/api.rst +++ b/docs/sphinx/source/api.rst @@ -287,7 +287,7 @@ Functions for fitting PV models .. autosummary:: :toctree: generated/ - ivtools.fit_sde_sandia + ivtools.fit_single_diode_sandia ivtools.fit_cec_sam Other diff --git a/docs/sphinx/source/whatsnew/v0.7.0.rst b/docs/sphinx/source/whatsnew/v0.7.0.rst index 04d1bfc88d..bf1a55cf12 100644 --- a/docs/sphinx/source/whatsnew/v0.7.0.rst +++ b/docs/sphinx/source/whatsnew/v0.7.0.rst @@ -26,8 +26,8 @@ API Changes Enhancements ~~~~~~~~~~~~ * Add `ivtools` module to contain functions for IV model fitting. -* Add :py:func:`~pvlib.ivtools.fit_sde_sandia`, a simple method to fit the - single diode equation to an IV curve. +* Add :py:func:`~pvlib.ivtools.fit_single_diode_sandia`, a simple method to fit + the single diode equation to an IV curve. * Add :py:func:`~pvlib.ivtools.fit_cec_sam`, a wrapper to access the model fitting function '6parsolve' from NREL's System Advisor Model. From 227d8b5db3e959fb1727e95a3f032749a73c12f0 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Wed, 17 Jul 2019 15:16:22 -0600 Subject: [PATCH 56/68] add math delimiters --- pvlib/ivtools.py | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/pvlib/ivtools.py b/pvlib/ivtools.py index 08a25314ab..0873c4451d 100644 --- a/pvlib/ivtools.py +++ b/pvlib/ivtools.py @@ -187,45 +187,43 @@ def fit_single_diode_sandia(voltage, current, v_oc=None, i_sc=None, .. math:: - I = \frac{I_{L}}{1 + G_{p} \times R_{s}} - - \frac{G_{p} \times V}{1 + G_{p} \times R_{s}} - - \frac{I_{0}}{1 + G_{p} \times R_{s}} \times - [\exp(\frac{V + I \times R_{s}}{nNsVth}) - 1] + `I = \frac{I_{L}}{1 + G_{p} R_{s}} - \frac{G_{p} V}{1 + G_{p} R_{s}} + - \frac{I_{0}}{1 + G_{p} R_{s}} (\exp(\frac{V + I R_{s}}{nNsVth}) - 1)` 2. The linear portion of the IV curve is defined as :math:`V \le vlim \times v_oc`. Over this portion of the IV curve, .. math:: - \frac{I_{0}}{1 + G_{p} \times R_{s}} \times - [\exp(\frac{V + I \times R_{s}}{nNsVth}) - 1] \approx 0 + `\frac{I_{0}}{1 + G_{p} R_{s}} (\exp(\frac{V + I R_{s}}{nNsVth}) - 1) + \approx 0` 3. Fit the linear portion of the IV curve with a line. .. math:: - I &\approx \frac{I_{L}}{1 + G_{p} \times R_{s}} - - \frac{G_{p} \times V}{1 + G_{p} \times R_{s}} \\ - &= \beta_{0} + \beta_{1} \times V + `I &\approx \frac{I_{L}}{1 + G_{p} R_{s}} - \frac{G_{p} V}{1 + G_{p} + R_{s}} \\ + &= \beta_{0} + \beta_{1} V` 4. The exponential portion of the IV curve is defined by :math:`\beta_{0} + \beta_{1} \times V - I > ilim \times i_sc`. - Over this portion of the curve, :math:`exp((V+I*Rs)/(nNsVth)) >> 1` + Over this portion of the curve, :math:`exp((V + IRs)/nNsVth) >> 1` so that .. math:: - \exp(\frac{V + I \times R_{s}}{nNsVth}) - 1 \approx - \exp(\frac{V + I \times R_{s}}{nNsVth}) + `\exp(\frac{V + I R_{s}}{nNsVth}) - 1 \approx + \exp(\frac{V + I R_{s}}{nNsVth})` 5. Fit the exponential portion of the IV curve. .. math:: - \log(\beta_{0} - \beta_{1} \times V - I) &\approx - \log(\frac{I_{0}}{1 + G_{p} \times R_{s}} + \frac{V}{nNsVth} + `\log(\beta_{0} - \beta_{1} V - I) + &\approx \log(\frac{I_{0}}{1 + G_{p} R_{s}} + \frac{V}{nNsVth} + \frac{I R_{s}}{nNsVth} \\ - &= \beta_{2} + beta_{3} \times V + \beta_{4} \times I + &= \beta_{2} + beta_{3} V + \beta_{4} I` 6. Calculate values for ``IL, I0, Rs, Rsh,`` and ``nNsVth`` from the regression coefficents beta0, beta1, beta3 and beta4. From 16ab69f40a176d8bd2eeb537bd807c439583da5f Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Wed, 17 Jul 2019 15:35:45 -0600 Subject: [PATCH 57/68] remove delimiter, use indent --- pvlib/ivtools.py | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/pvlib/ivtools.py b/pvlib/ivtools.py index 0873c4451d..d501985f9f 100644 --- a/pvlib/ivtools.py +++ b/pvlib/ivtools.py @@ -182,51 +182,51 @@ def fit_single_diode_sandia(voltage, current, v_oc=None, i_sc=None, The extraction method [2] proceeds in six steps. - 1. In the single diode equation, replace :math:`R_{sh} = 1/G_{p}` and - re-arrange + 1. In the single diode equation, replace :math:`R_{sh} = 1/G_{p}` and + re-arrange .. math:: - `I = \frac{I_{L}}{1 + G_{p} R_{s}} - \frac{G_{p} V}{1 + G_{p} R_{s}} - - \frac{I_{0}}{1 + G_{p} R_{s}} (\exp(\frac{V + I R_{s}}{nNsVth}) - 1)` + I = \frac{I_{L}}{1 + G_{p} R_{s}} - \frac{G_{p} V}{1 + G_{p} R_{s}} + - \frac{I_{0}}{1 + G_{p} R_{s}} (\exp(\frac{V + I R_{s}}{nNsVth}) - 1) - 2. The linear portion of the IV curve is defined as - :math:`V \le vlim \times v_oc`. Over this portion of the IV curve, + 2. The linear portion of the IV curve is defined as + :math:`V \le vlim \times v_oc`. Over this portion of the IV curve, .. math:: - `\frac{I_{0}}{1 + G_{p} R_{s}} (\exp(\frac{V + I R_{s}}{nNsVth}) - 1) - \approx 0` + \frac{I_{0}}{1 + G_{p} R_{s}} (\exp(\frac{V + I R_{s}}{nNsVth}) - 1) + \approx 0 - 3. Fit the linear portion of the IV curve with a line. + 3. Fit the linear portion of the IV curve with a line. .. math:: - `I &\approx \frac{I_{L}}{1 + G_{p} R_{s}} - \frac{G_{p} V}{1 + G_{p} + I &\approx \frac{I_{L}}{1 + G_{p} R_{s}} - \frac{G_{p} V}{1 + G_{p} R_{s}} \\ - &= \beta_{0} + \beta_{1} V` + &= \beta_{0} + \beta_{1} V - 4. The exponential portion of the IV curve is defined by - :math:`\beta_{0} + \beta_{1} \times V - I > ilim \times i_sc`. - Over this portion of the curve, :math:`exp((V + IRs)/nNsVth) >> 1` - so that + 4. The exponential portion of the IV curve is defined by + :math:`\beta_{0} + \beta_{1} \times V - I > ilim \times i_sc`. + Over this portion of the curve, :math:`exp((V + IRs)/nNsVth) >> 1` + so that .. math:: - `\exp(\frac{V + I R_{s}}{nNsVth}) - 1 \approx - \exp(\frac{V + I R_{s}}{nNsVth})` + \exp(\frac{V + I R_{s}}{nNsVth}) - 1 \approx + \exp(\frac{V + I R_{s}}{nNsVth}) - 5. Fit the exponential portion of the IV curve. + 5. Fit the exponential portion of the IV curve. .. math:: - `\log(\beta_{0} - \beta_{1} V - I) + \log(\beta_{0} - \beta_{1} V - I) &\approx \log(\frac{I_{0}}{1 + G_{p} R_{s}} + \frac{V}{nNsVth} + \frac{I R_{s}}{nNsVth} \\ - &= \beta_{2} + beta_{3} V + \beta_{4} I` + &= \beta_{2} + beta_{3} V + \beta_{4} I - 6. Calculate values for ``IL, I0, Rs, Rsh,`` and ``nNsVth`` from the - regression coefficents beta0, beta1, beta3 and beta4. + 6. Calculate values for ``IL, I0, Rs, Rsh,`` and ``nNsVth`` from the + regression coefficents beta0, beta1, beta3 and beta4. References From 11b8dda194c98db367316ba413f9fe36f576fda2 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Wed, 17 Jul 2019 15:52:49 -0600 Subject: [PATCH 58/68] fix alignment in docstring --- pvlib/ivtools.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/pvlib/ivtools.py b/pvlib/ivtools.py index d501985f9f..e56eb91f2a 100644 --- a/pvlib/ivtools.py +++ b/pvlib/ivtools.py @@ -207,9 +207,9 @@ def fit_single_diode_sandia(voltage, current, v_oc=None, i_sc=None, &= \beta_{0} + \beta_{1} V 4. The exponential portion of the IV curve is defined by - :math:`\beta_{0} + \beta_{1} \times V - I > ilim \times i_sc`. - Over this portion of the curve, :math:`exp((V + IRs)/nNsVth) >> 1` - so that + :math:`\beta_{0} + \beta_{1} \times V - I > ilim \times i_sc`. + Over this portion of the curve, :math:`exp((V + IRs)/nNsVth) >> 1` + so that .. math:: @@ -226,7 +226,8 @@ def fit_single_diode_sandia(voltage, current, v_oc=None, i_sc=None, &= \beta_{2} + beta_{3} V + \beta_{4} I 6. Calculate values for ``IL, I0, Rs, Rsh,`` and ``nNsVth`` from the - regression coefficents beta0, beta1, beta3 and beta4. + regression coefficents :math:`\beta_[0}, \beta_{1}, \beta_{3}` and + :math:\beta_{4}`. References From f84000d076413117c2a28b4cc8775a837359a1cd Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Wed, 17 Jul 2019 18:48:41 -0600 Subject: [PATCH 59/68] some edits to docstring format --- pvlib/ivtools.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pvlib/ivtools.py b/pvlib/ivtools.py index e56eb91f2a..3e0cbd3d8c 100644 --- a/pvlib/ivtools.py +++ b/pvlib/ivtools.py @@ -146,7 +146,7 @@ def fit_single_diode_sandia(voltage, current, v_oc=None, i_sc=None, Returns ------- - A tuple of the following elements: + tuple of the following elements: * photocurrent : float photocurrent [A] @@ -226,8 +226,8 @@ def fit_single_diode_sandia(voltage, current, v_oc=None, i_sc=None, &= \beta_{2} + beta_{3} V + \beta_{4} I 6. Calculate values for ``IL, I0, Rs, Rsh,`` and ``nNsVth`` from the - regression coefficents :math:`\beta_[0}, \beta_{1}, \beta_{3}` and - :math:\beta_{4}`. + regression coefficents :math:`\beta_{0}, \beta_{1}, \beta_{3}` and + :math:`\beta_{4}`. References From 6cd10959a11cdea252a880f73ee422ca508cc23f Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Mon, 22 Jul 2019 19:23:30 -0600 Subject: [PATCH 60/68] change name back to sde --- docs/sphinx/source/whatsnew/v0.7.0.rst | 1 - pvlib/ivtools.py | 22 +++++++-------- pvlib/test/test_ivtools.py | 38 +++++++++++++------------- 3 files changed, 30 insertions(+), 31 deletions(-) diff --git a/docs/sphinx/source/whatsnew/v0.7.0.rst b/docs/sphinx/source/whatsnew/v0.7.0.rst index bf1a55cf12..152d1466ec 100644 --- a/docs/sphinx/source/whatsnew/v0.7.0.rst +++ b/docs/sphinx/source/whatsnew/v0.7.0.rst @@ -45,5 +45,4 @@ Contributors * Mark Campanellli (:ghuser:`markcampanelli`) * Will Holmgren (:ghuser:`wholmgren`) * Cliff Hansen (:ghuser:`cwhanse`) - * Oscar Dowson (:ghuser:`odow`) diff --git a/pvlib/ivtools.py b/pvlib/ivtools.py index 3e0cbd3d8c..8f76a3c1b6 100644 --- a/pvlib/ivtools.py +++ b/pvlib/ivtools.py @@ -8,10 +8,11 @@ import numpy as np -def fit_cec_sam(celltype, v_mp, i_mp, v_oc, i_sc, alpha_sc, beta_voc, - gamma_pmp, cells_in_series, temp_ref=25): +def fit_sdm_cec_sam(celltype, v_mp, i_mp, v_oc, i_sc, alpha_sc, beta_voc, + gamma_pmp, cells_in_series, temp_ref=25): """ - Estimates parameters for the CEC single diode model [1] using the SAM SDK. + Estimates parameters for the CEC single diode model (SDM) using the SAM + SDK. Parameters ---------- @@ -106,10 +107,10 @@ def fit_cec_sam(celltype, v_mp, i_mp, v_oc, i_sc, alpha_sc, beta_voc, raise RuntimeError('Parameter estimation failed') -def fit_single_diode_sandia(voltage, current, v_oc=None, i_sc=None, - v_mp_i_mp=None, vlim=0.2, ilim=0.1): +def fit_sde_sandia(voltage, current, v_oc=None, i_sc=None, v_mp_i_mp=None, + vlim=0.2, ilim=0.1): r""" - Fits the single diode equation to an IV curve. + Fits the single diode equation (SDE) to an IV curve. Parameters ---------- @@ -162,7 +163,7 @@ def fit_single_diode_sandia(voltage, current, v_oc=None, i_sc=None, Raises ------ - RuntimeError if parameter extraction is not successful. + RuntimeError if parameter extraction is not successful. Notes ----- @@ -257,8 +258,8 @@ def fit_single_diode_sandia(voltage, current, v_oc=None, i_sc=None, i_sc) # calculate single diode parameters from regression coefficients - return _calculate_single_diode_parameters(beta0, beta1, beta3, beta4, v_mp, - i_mp, v_oc) + return _calculate_sde_parameters(beta0, beta1, beta3, beta4, v_mp, i_mp, + v_oc) def _find_mp(voltage, current): @@ -329,8 +330,7 @@ def _find_beta3_beta4(voltage, current, beta0, beta1, ilim, i_sc): return beta3, beta4 -def _calculate_single_diode_parameters(beta0, beta1, beta3, beta4, v_mp, i_mp, - v_oc): +def _calculate_sde_parameters(beta0, beta1, beta3, beta4, v_mp, i_mp, v_oc): nNsVth = 1.0 / beta3 Rs = beta4 / beta3 Gp = beta1 / (1.0 - Rs * beta1) diff --git a/pvlib/test/test_ivtools.py b/pvlib/test/test_ivtools.py index 78f22ddda8..64636bea6c 100644 --- a/pvlib/test/test_ivtools.py +++ b/pvlib/test/test_ivtools.py @@ -39,7 +39,7 @@ def cec_module_parameters(sam_data): @requires_scipy -def test_fit_single_diode_sandia(get_test_iv_params): +def test_fit_sde_sandia(get_test_iv_params): test_params = get_test_iv_params testcurve = pvsystem.singlediode(photocurrent=test_params['IL'], saturation_current=test_params['I0'], @@ -49,33 +49,33 @@ def test_fit_single_diode_sandia(get_test_iv_params): ivcurve_pnts=300) expected = tuple(test_params[k] for k in ['IL', 'I0', 'Rsh', 'Rs', 'nNsVth']) - result = ivtools.fit_single_diode_sandia(voltage=testcurve['v'], - current=testcurve['i']) + result = ivtools.fit_sde_sandia(voltage=testcurve['v'], + current=testcurve['i']) assert np.allclose(result, expected, rtol=5e-5) - result = ivtools.fit_single_diode_sandia(voltage=testcurve['v'], - current=testcurve['i'], - v_oc=testcurve['v_oc'], - i_sc=testcurve['i_sc']) + result = ivtools.fit_sde_sandia(voltage=testcurve['v'], + current=testcurve['i'], + v_oc=testcurve['v_oc'], + i_sc=testcurve['i_sc']) assert np.allclose(result, expected, rtol=5e-5) - result = ivtools.fit_single_diode_sandia(voltage=testcurve['v'], - current=testcurve['i'], - v_oc=testcurve['v_oc'], - i_sc=testcurve['i_sc'], - v_mp_i_mp=(testcurve['v_mp'], - testcurve['i_mp'])) + result = ivtools.fit_sde_sandia(voltage=testcurve['v'], + current=testcurve['i'], + v_oc=testcurve['v_oc'], + i_sc=testcurve['i_sc'], + v_mp_i_mp=(testcurve['v_mp'], + testcurve['i_mp'])) assert np.allclose(result, expected, rtol=5e-5) - result = ivtools.fit_single_diode_sandia(voltage=testcurve['v'], - current=testcurve['i'], - vlim=0.1) + result = ivtools.fit_sde_sandia(voltage=testcurve['v'], + current=testcurve['i'], vlim=0.1) assert np.allclose(result, expected, rtol=5e-5) @requires_pysam -def test_fit_cec_sam(cec_module_parameters, get_cec_params_cansol_cs5p_220p): +def test_fit_sdm_cec_sam(cec_module_parameters, + get_cec_params_cansol_cs5p_220p): sam_parameters = cec_module_parameters cec_list_data = get_cec_params_cansol_cs5p_220p I_L_ref, I_o_ref, R_sh_ref, R_s, a_ref, Adjust = \ - ivtools.fit_cec_sam( + ivtools.fit_sdm_cec_sam( celltype='polySi', v_mp=cec_list_data['V_mp_ref'], i_mp=cec_list_data['I_mp_ref'], v_oc=cec_list_data['V_oc_ref'], i_sc=cec_list_data['I_sc_ref'], alpha_sc=cec_list_data['alpha_sc'], @@ -99,7 +99,7 @@ def test_fit_cec_sam(cec_module_parameters, get_cec_params_cansol_cs5p_220p): # test for fitting failure with pytest.raises(RuntimeError): I_L_ref, I_o_ref, R_sh_ref, R_s, a_ref, Adjust = \ - ivtools.fit_cec_sam( + ivtools.fit_sdm_cec_sam( celltype='polySi', v_mp=0.45, i_mp=5.25, v_oc=0.55, i_sc=5.5, alpha_sc=0.00275, beta_voc=0.00275, gamma_pmp=0.0055, cells_in_series=1, temp_ref=25) From dde20b5a1b9f843d56824031aa706e6865c51d5f Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Tue, 23 Jul 2019 14:34:42 -0600 Subject: [PATCH 61/68] add bad IV curves for coverage test --- pvlib/test/test_ivtools.py | 151 ++++++++++++++++++++++++++++++++++++- 1 file changed, 150 insertions(+), 1 deletion(-) diff --git a/pvlib/test/test_ivtools.py b/pvlib/test/test_ivtools.py index 64636bea6c..5b4284b980 100644 --- a/pvlib/test/test_ivtools.py +++ b/pvlib/test/test_ivtools.py @@ -39,7 +39,7 @@ def cec_module_parameters(sam_data): @requires_scipy -def test_fit_sde_sandia(get_test_iv_params): +def test_fit_sde_sandia(get_test_iv_params, get_bad_iv_curves): test_params = get_test_iv_params testcurve = pvsystem.singlediode(photocurrent=test_params['IL'], saturation_current=test_params['I0'], @@ -67,6 +67,16 @@ def test_fit_sde_sandia(get_test_iv_params): result = ivtools.fit_sde_sandia(voltage=testcurve['v'], current=testcurve['i'], vlim=0.1) assert np.allclose(result, expected, rtol=5e-5) + # bad IV curves for coverage of if/then in _calculate_sde_parameters + v1, i1, v2, i2 = get_bad_iv_curves + result = ivtools.fit_sde_sandia(voltage=v1, current=i1) + assert np.allclose(result, (-2.4322856072799985, 8.854688976836396, + -63.56227601452038, 111.18558915546389, + -137.9965046659527)) + result = ivtools.fit_sde_sandia(voltage=v1, current=i1) + assert np.allclose(result, (2.62405311949227, 1.8657963912925288, + 110.35202827739991, -65.652554411442, + 174.49362093001415)) @requires_pysam @@ -103,3 +113,142 @@ def test_fit_sdm_cec_sam(cec_module_parameters, celltype='polySi', v_mp=0.45, i_mp=5.25, v_oc=0.55, i_sc=5.5, alpha_sc=0.00275, beta_voc=0.00275, gamma_pmp=0.0055, cells_in_series=1, temp_ref=25) + +@pytest.fixture +def get_bad_iv_curves(): + # v1, i1 produce a bad value for I0_voc + v1 = np.array([0, 0.338798867469060, 0.677597734938121, 1.01639660240718, + 1.35519546987624, 1.69399433734530, 2.03279320481436, + 2.37159207228342, 2.71039093975248, 3.04918980722154, + 3.38798867469060, 3.72678754215966, 4.06558640962873, + 4.40438527709779, 4.74318414456685, 5.08198301203591, + 5.42078187950497, 5.75958074697403, 6.09837961444309, + 6.43717848191215, 6.77597734938121, 7.11477621685027, + 7.45357508431933, 7.79237395178839, 8.13117281925745, + 8.46997168672651, 8.80877055419557, 9.14756942166463, + 9.48636828913369, 9.82516715660275, 10.1639660240718, + 10.5027648915409, 10.8415637590099, 11.1803626264790, + 11.5191614939481, 11.8579603614171, 12.1967592288862, + 12.5355580963552, 12.8743569638243, 13.2131558312934, + 13.5519546987624, 13.8907535662315, 14.2295524337005, + 14.5683513011696, 14.9071501686387, 15.2459490361077, + 15.5847479035768, 15.9235467710458, 16.2623456385149, + 16.6011445059840, 16.9399433734530, 17.2787422409221, + 17.6175411083911, 17.9563399758602, 18.2951388433293, + 18.6339377107983, 18.9727365782674, 19.3115354457364, + 19.6503343132055, 19.9891331806746, 20.3279320481436, + 20.6667309156127, 21.0055297830817, 21.3443286505508, + 21.6831275180199, 22.0219263854889, 22.3607252529580, + 22.6995241204270, 23.0383229878961, 23.3771218553652, + 23.7159207228342, 24.0547195903033, 24.3935184577724, + 24.7323173252414, 25.0711161927105, 25.4099150601795, + 25.7487139276486, 26.0875127951177, 26.4263116625867, + 26.7651105300558, 27.1039093975248, 27.4427082649939, + 27.7815071324630, 28.1203059999320, 28.4591048674011, + 28.7979037348701, 29.1367026023392, 29.4755014698083, + 29.8143003372773, 30.1530992047464, 30.4918980722154, + 30.8306969396845, 31.1694958071536, 31.5082946746226, + 31.8470935420917, 32.1858924095607, 32.5246912770298, + 32.8634901444989, 33.2022890119679, 33.5410878794370]) + i1 = np.array([3.39430882774470, 2.80864492110761, 3.28358165429196, + 3.41191190551673, 3.11975662808148, 3.35436585834612, + 3.23953272899809, 3.60307083325333, 2.80478101508277, + 2.80505102853845, 3.16918996870373, 3.21088388439857, + 3.46332865310431, 3.09224155015883, 3.17541550741062, + 3.32470179290389, 3.33224664316240, 3.07709000050741, + 2.89141245343405, 3.01365768561537, 3.23265176770231, + 3.32253647634228, 2.97900657569736, 3.31959549243966, + 3.03375461550111, 2.97579298978937, 3.25432831375159, + 2.89178382564454, 3.00341909207567, 3.72637492250097, + 3.28379856976360, 2.96516169245835, 3.25658381110230, + 3.41655911533139, 3.02718097944604, 3.11458376760376, + 3.24617304369762, 3.45935502367636, 3.21557333256913, + 3.27611176482650, 2.86954135732485, 3.32416319254657, + 3.15277467598732, 3.08272557013770, 3.15602202666259, + 3.49432799877150, 3.53863997177632, 3.10602611478455, + 3.05373911151821, 3.09876772570781, 2.97417228624287, + 2.84573593699237, 3.16288578405195, 3.06533173612783, + 3.02118336639575, 3.34374977225502, 2.97255164138821, + 3.19286135682863, 3.10999753817133, 3.26925354620079, + 3.11957809501529, 3.20155017481720, 3.31724984405837, + 3.42879043512927, 3.17933067619240, 3.47777362613969, + 3.20708912539777, 3.48205761174907, 3.16804363684327, + 3.14055472378230, 3.13445657434470, 2.91152696252998, + 3.10984113847427, 2.80443349399489, 3.23146278164875, + 2.94521083406108, 3.17388903141715, 3.05930294897030, + 3.18985234673287, 3.27946609274898, 3.33717523113602, + 2.76394303462702, 3.19375132937510, 2.82628616689450, + 2.85238527394143, 2.82975892599489, 2.79196912313914, + 2.72860792049395, 2.75585977414140, 2.44280222448805, + 2.36052347370628, 2.26785071765738, 2.10868255743462, + 2.06165739407987, 1.90047259509385, 1.39925575828709, + 1.24749015957606, 0.867823806536762, 0.432752457749993, 0]) + # v2, i2 produce a bad value for I0_vmp + v2 = np.array([0, 0.365686097622586, 0.731372195245173, 1.09705829286776, + 1.46274439049035, 1.82843048811293, 2.19411658573552, + 2.55980268335810, 2.92548878098069, 3.29117487860328, + 3.65686097622586, 4.02254707384845, 4.38823317147104, + 4.75391926909362, 5.11960536671621, 5.48529146433880, + 5.85097756196138, 6.21666365958397, 6.58234975720655, + 6.94803585482914, 7.31372195245173, 7.67940805007431, + 8.04509414769690, 8.41078024531949, 8.77646634294207, + 9.14215244056466, 9.50783853818725, 9.87352463580983, + 10.2392107334324, 10.6048968310550, 10.9705829286776, + 11.3362690263002, 11.7019551239228, 12.0676412215454, + 12.4333273191679, 12.7990134167905, 13.1646995144131, + 13.5303856120357, 13.8960717096583, 14.2617578072809, + 14.6274439049035, 14.9931300025260, 15.3588161001486, + 15.7245021977712, 16.0901882953938, 16.4558743930164, + 16.8215604906390, 17.1872465882616, 17.5529326858841, + 17.9186187835067, 18.2843048811293, 18.6499909787519, + 19.0156770763745, 19.3813631739971, 19.7470492716197, + 20.1127353692422, 20.4784214668648, 20.8441075644874, + 21.2097936621100, 21.5754797597326, 21.9411658573552, + 22.3068519549778, 22.6725380526004, 23.0382241502229, + 23.4039102478455, 23.7695963454681, 24.1352824430907, + 24.5009685407133, 24.8666546383359, 25.2323407359585, + 25.5980268335810, 25.9637129312036, 26.3293990288262, + 26.6950851264488, 27.0607712240714, 27.4264573216940, + 27.7921434193166, 28.1578295169392, 28.5235156145617, + 28.8892017121843, 29.2548878098069, 29.6205739074295, + 29.9862600050521, 30.3519461026747, 30.7176322002973, + 31.0833182979198, 31.4490043955424, 31.8146904931650, + 32.1803765907876, 32.5460626884102, 32.9117487860328, + 33.2774348836554, 33.6431209812779, 34.0088070789005, + 34.3744931765231, 34.7401792741457, 35.1058653717683, + 35.4715514693909, 35.8372375670135, 36.2029236646360]) + i2 = np.array([6.49218806928330, 6.49139336899548, 6.17810697175204, + 6.75197816263663, 6.59529074137515, 6.18164578868300, + 6.38709397931910, 6.30685422248427, 6.44640615548925, + 6.88727230397772, 6.42074852785591, 6.46348580823746, + 6.38642309763941, 5.66356277572311, 6.61010381702082, + 6.33288284311125, 6.22475343933610, 6.30651399433833, + 6.44435022944051, 6.43741711131908, 6.03536180208946, + 6.23814639328170, 5.97229140403242, 6.20790000748341, + 6.22933550182341, 6.22992127804882, 6.13400871899299, + 6.83491312449950, 6.07952797245846, 6.35837746415450, + 6.41972128662324, 6.85256717258275, 6.25807797296759, + 6.25124948151766, 6.22229212812413, 6.72249444167406, + 6.41085549981649, 6.75792874870056, 6.22096181559171, + 6.47839564388996, 6.56010208597432, 6.63300966556949, + 6.34617546039339, 6.79812221146153, 6.14486056194136, + 6.14979256889311, 6.16883037644880, 6.57309183229605, + 6.40064681038509, 6.18861448239873, 6.91340138179698, + 5.94164388433788, 6.23638991745862, 6.31898940411710, + 6.45247884556830, 6.58081455524297, 6.64915284801713, + 6.07122119270245, 6.41398258148256, 6.62144271089614, + 6.36377197712687, 6.51487678829345, 6.53418950147730, + 6.18886469125371, 6.26341063475750, 6.83488211680259, + 6.62699397226695, 6.41286837534735, 6.44060085001851, + 6.48114130629288, 6.18607038456406, 6.16923370572396, + 6.64223126283631, 6.07231852289266, 5.79043710204375, + 6.48463886529882, 6.36263392044401, 6.11212476454494, + 6.14573900812925, 6.12568047243240, 6.43836230231577, + 6.02505694060219, 6.13819468942244, 6.22100593815064, + 6.02394682666345, 5.89016573063789, 5.74448527739202, + 5.50415294280017, 5.31883018164157, 4.87476769510305, + 4.74386713755523, 4.60638346931628, 4.06177345572680, + 3.73334482123538, 3.13848311672243, 2.71638862600768, + 2.02963773590165, 1.49291145092070, 0.818343889647352, 0]) + + return v1, i1, v2, i2 From 4c39e2afad8c806740e9303afdb03bc18df2bd52 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Tue, 23 Jul 2019 15:46:56 -0600 Subject: [PATCH 62/68] separate bad IV test, fix test --- pvlib/test/test_ivtools.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/pvlib/test/test_ivtools.py b/pvlib/test/test_ivtools.py index 5b4284b980..be37d403a9 100644 --- a/pvlib/test/test_ivtools.py +++ b/pvlib/test/test_ivtools.py @@ -67,13 +67,17 @@ def test_fit_sde_sandia(get_test_iv_params, get_bad_iv_curves): result = ivtools.fit_sde_sandia(voltage=testcurve['v'], current=testcurve['i'], vlim=0.1) assert np.allclose(result, expected, rtol=5e-5) + + +@requires_scipy +def test_fit_sde_sandia_bad_iv(get_bad_iv_curves): # bad IV curves for coverage of if/then in _calculate_sde_parameters v1, i1, v2, i2 = get_bad_iv_curves result = ivtools.fit_sde_sandia(voltage=v1, current=i1) assert np.allclose(result, (-2.4322856072799985, 8.854688976836396, -63.56227601452038, 111.18558915546389, -137.9965046659527)) - result = ivtools.fit_sde_sandia(voltage=v1, current=i1) + result = ivtools.fit_sde_sandia(voltage=v2, current=i2) assert np.allclose(result, (2.62405311949227, 1.8657963912925288, 110.35202827739991, -65.652554411442, 174.49362093001415)) @@ -116,7 +120,7 @@ def test_fit_sdm_cec_sam(cec_module_parameters, @pytest.fixture def get_bad_iv_curves(): - # v1, i1 produce a bad value for I0_voc + # v1, i1 produces a bad value for I0_voc v1 = np.array([0, 0.338798867469060, 0.677597734938121, 1.01639660240718, 1.35519546987624, 1.69399433734530, 2.03279320481436, 2.37159207228342, 2.71039093975248, 3.04918980722154, @@ -183,7 +187,7 @@ def get_bad_iv_curves(): 2.36052347370628, 2.26785071765738, 2.10868255743462, 2.06165739407987, 1.90047259509385, 1.39925575828709, 1.24749015957606, 0.867823806536762, 0.432752457749993, 0]) - # v2, i2 produce a bad value for I0_vmp + # v2, i2 produces a bad value for I0_vmp v2 = np.array([0, 0.365686097622586, 0.731372195245173, 1.09705829286776, 1.46274439049035, 1.82843048811293, 2.19411658573552, 2.55980268335810, 2.92548878098069, 3.29117487860328, From eab687aafbbb81e8ff913c1f150e4c00bcb84851 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Tue, 23 Jul 2019 15:48:26 -0600 Subject: [PATCH 63/68] lint --- pvlib/test/test_ivtools.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pvlib/test/test_ivtools.py b/pvlib/test/test_ivtools.py index be37d403a9..ddfab9c124 100644 --- a/pvlib/test/test_ivtools.py +++ b/pvlib/test/test_ivtools.py @@ -74,7 +74,7 @@ def test_fit_sde_sandia_bad_iv(get_bad_iv_curves): # bad IV curves for coverage of if/then in _calculate_sde_parameters v1, i1, v2, i2 = get_bad_iv_curves result = ivtools.fit_sde_sandia(voltage=v1, current=i1) - assert np.allclose(result, (-2.4322856072799985, 8.854688976836396, + assert np.allclose(result, (-2.4322856072799985, 8.854688976836396, -63.56227601452038, 111.18558915546389, -137.9965046659527)) result = ivtools.fit_sde_sandia(voltage=v2, current=i2) @@ -118,6 +118,7 @@ def test_fit_sdm_cec_sam(cec_module_parameters, alpha_sc=0.00275, beta_voc=0.00275, gamma_pmp=0.0055, cells_in_series=1, temp_ref=25) + @pytest.fixture def get_bad_iv_curves(): # v1, i1 produces a bad value for I0_voc From c02a1ab58a885f5f074a78c3598b935802d49d4e Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Wed, 24 Jul 2019 10:48:49 -0600 Subject: [PATCH 64/68] update api.rst and whatsnew --- docs/sphinx/source/api.rst | 4 ++-- docs/sphinx/source/whatsnew/v0.7.0.rst | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/sphinx/source/api.rst b/docs/sphinx/source/api.rst index 332bcf78cd..e2b76f1746 100644 --- a/docs/sphinx/source/api.rst +++ b/docs/sphinx/source/api.rst @@ -287,8 +287,8 @@ Functions for fitting PV models .. autosummary:: :toctree: generated/ - ivtools.fit_single_diode_sandia - ivtools.fit_cec_sam + ivtools.fit_sde_sandia + ivtools.fit_sdm_cec_sam Other ----- diff --git a/docs/sphinx/source/whatsnew/v0.7.0.rst b/docs/sphinx/source/whatsnew/v0.7.0.rst index 152d1466ec..218379041c 100644 --- a/docs/sphinx/source/whatsnew/v0.7.0.rst +++ b/docs/sphinx/source/whatsnew/v0.7.0.rst @@ -26,10 +26,10 @@ API Changes Enhancements ~~~~~~~~~~~~ * Add `ivtools` module to contain functions for IV model fitting. -* Add :py:func:`~pvlib.ivtools.fit_single_diode_sandia`, a simple method to fit +* Add :py:func:`~pvlib.ivtools.fit_sde_sandia`, a simple method to fit the single diode equation to an IV curve. -* Add :py:func:`~pvlib.ivtools.fit_cec_sam`, a wrapper to access the - model fitting function '6parsolve' from NREL's System Advisor Model. +* Add :py:func:`~pvlib.ivtools.fit_sdm_cec_sam`, a wrapper for the CEC single + diode model fitting function '6parsolve' from NREL's System Advisor Model. Bug fixes From b8a713b2cc9f6361092213bcc4a2d60432b388ff Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Tue, 10 Sep 2019 10:40:06 -0600 Subject: [PATCH 65/68] hardwire test output rather than read from old CEC file --- pvlib/test/test_ivtools.py | 41 +++++++++++++++----------------------- 1 file changed, 16 insertions(+), 25 deletions(-) diff --git a/pvlib/test/test_ivtools.py b/pvlib/test/test_ivtools.py index ddfab9c124..090150d8da 100644 --- a/pvlib/test/test_ivtools.py +++ b/pvlib/test/test_ivtools.py @@ -20,14 +20,12 @@ def get_test_iv_params(): @pytest.fixture def get_cec_params_cansol_cs5p_220p(): - return {'V_mp_ref': 46.6, 'I_mp_ref': 4.73, 'V_oc_ref': 58.3, - 'I_sc_ref': 5.05, 'alpha_sc': 0.0025, 'beta_voc': -0.19659, - 'gamma_pmp': -0.43, 'cells_in_series': 96} - - -@pytest.fixture() -def sam_data(): - return pvsystem.retrieve_sam('cecmod') + return {'input': {'V_mp_ref': 46.6, 'I_mp_ref': 4.73, 'V_oc_ref': 58.3, + 'I_sc_ref': 5.05, 'alpha_sc': 0.0025, + 'beta_voc': -0.19659, 'gamma_pmp': -0.43, + 'cells_in_series': 96}, + 'output': {'a_ref': 2.3674, 'I_L_ref': 5.056, 'I_o_ref': 1.01e-10, + 'R_sh_ref': 837.51, 'R_s': 1.004, 'Adjust': 2.3}} @pytest.fixture() @@ -84,31 +82,24 @@ def test_fit_sde_sandia_bad_iv(get_bad_iv_curves): @requires_pysam -def test_fit_sdm_cec_sam(cec_module_parameters, - get_cec_params_cansol_cs5p_220p): - sam_parameters = cec_module_parameters - cec_list_data = get_cec_params_cansol_cs5p_220p +def test_fit_sdm_cec_sam(get_cec_params_cansol_cs5p_220p): + input_data = get_cec_params_cansol_cs5p_220p['input'] + expected = get_cec_params_cansol_cs5p_220p['output'] I_L_ref, I_o_ref, R_sh_ref, R_s, a_ref, Adjust = \ ivtools.fit_sdm_cec_sam( - celltype='polySi', v_mp=cec_list_data['V_mp_ref'], - i_mp=cec_list_data['I_mp_ref'], v_oc=cec_list_data['V_oc_ref'], - i_sc=cec_list_data['I_sc_ref'], alpha_sc=cec_list_data['alpha_sc'], - beta_voc=cec_list_data['beta_voc'], - gamma_pmp=cec_list_data['gamma_pmp'], - cells_in_series=cec_list_data['cells_in_series']) - modeled = pd.Series(index=sam_parameters.index, data=cec_list_data) + celltype='polySi', v_mp=input_data['V_mp_ref'], + i_mp=input_data['I_mp_ref'], v_oc=input_data['V_oc_ref'], + i_sc=input_data['I_sc_ref'], alpha_sc=input_data['alpha_sc'], + beta_voc=input_data['beta_voc'], + gamma_pmp=input_data['gamma_pmp'], + cells_in_series=input_data['cells_in_series']) + modeled = pd.Series(index=expected.index, data=np.nan) modeled['a_ref'] = a_ref modeled['I_L_ref'] = I_L_ref modeled['I_o_ref'] = I_o_ref modeled['R_sh_ref'] = R_sh_ref modeled['R_s'] = R_s modeled['Adjust'] = Adjust - modeled['gamma_r'] = cec_list_data['gamma_pmp'] - modeled['N_s'] = cec_list_data['cells_in_series'] - modeled = modeled.dropna() - expected = pd.Series(index=modeled.index, data=np.nan) - for k in modeled.keys(): - expected[k] = sam_parameters[k] assert np.allclose(modeled.values, expected.values, rtol=5e-2) # test for fitting failure with pytest.raises(RuntimeError): From 3aae45a51315534935452d197d4a1352bf6a25dc Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Tue, 10 Sep 2019 11:05:58 -0600 Subject: [PATCH 66/68] test fix --- pvlib/test/test_ivtools.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/pvlib/test/test_ivtools.py b/pvlib/test/test_ivtools.py index 090150d8da..de72fd9557 100644 --- a/pvlib/test/test_ivtools.py +++ b/pvlib/test/test_ivtools.py @@ -28,14 +28,6 @@ def get_cec_params_cansol_cs5p_220p(): 'R_sh_ref': 837.51, 'R_s': 1.004, 'Adjust': 2.3}} -@pytest.fixture() -def cec_module_parameters(sam_data): - modules = sam_data - module = "Canadian_Solar_CS5P_220P" - module_parameters = modules[module] - return module_parameters - - @requires_scipy def test_fit_sde_sandia(get_test_iv_params, get_bad_iv_curves): test_params = get_test_iv_params @@ -93,7 +85,7 @@ def test_fit_sdm_cec_sam(get_cec_params_cansol_cs5p_220p): beta_voc=input_data['beta_voc'], gamma_pmp=input_data['gamma_pmp'], cells_in_series=input_data['cells_in_series']) - modeled = pd.Series(index=expected.index, data=np.nan) + modeled = pd.Series(index=expected.keys(), data=np.nan) modeled['a_ref'] = a_ref modeled['I_L_ref'] = I_L_ref modeled['I_o_ref'] = I_o_ref From 35b90e5d4078dae6c05f122af9bfef5f8c11b140 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Tue, 10 Sep 2019 11:51:41 -0600 Subject: [PATCH 67/68] really fix it this time --- pvlib/test/test_ivtools.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pvlib/test/test_ivtools.py b/pvlib/test/test_ivtools.py index de72fd9557..d4aca050c4 100644 --- a/pvlib/test/test_ivtools.py +++ b/pvlib/test/test_ivtools.py @@ -76,7 +76,6 @@ def test_fit_sde_sandia_bad_iv(get_bad_iv_curves): @requires_pysam def test_fit_sdm_cec_sam(get_cec_params_cansol_cs5p_220p): input_data = get_cec_params_cansol_cs5p_220p['input'] - expected = get_cec_params_cansol_cs5p_220p['output'] I_L_ref, I_o_ref, R_sh_ref, R_s, a_ref, Adjust = \ ivtools.fit_sdm_cec_sam( celltype='polySi', v_mp=input_data['V_mp_ref'], @@ -85,7 +84,8 @@ def test_fit_sdm_cec_sam(get_cec_params_cansol_cs5p_220p): beta_voc=input_data['beta_voc'], gamma_pmp=input_data['gamma_pmp'], cells_in_series=input_data['cells_in_series']) - modeled = pd.Series(index=expected.keys(), data=np.nan) + expected = pd.Series(get_cec_params_cansol_cs5p_220p['output']) + modeled = pd.Series(index=expected.index, data=np.nan) modeled['a_ref'] = a_ref modeled['I_L_ref'] = I_L_ref modeled['I_o_ref'] = I_o_ref From fe89a87b70a55f83b918f0566b7c5116e009d3a5 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Tue, 10 Sep 2019 13:52:31 -0600 Subject: [PATCH 68/68] fix raise statement --- pvlib/ivtools.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pvlib/ivtools.py b/pvlib/ivtools.py index 8f76a3c1b6..353a0b8a9a 100644 --- a/pvlib/ivtools.py +++ b/pvlib/ivtools.py @@ -89,9 +89,9 @@ def fit_sdm_cec_sam(celltype, v_mp, i_mp, v_oc, i_sc, alpha_sc, beta_voc, try: from PySAM import PySSC - except ImportError as e: - raise("Requires NREL's PySAM package at " - "https://pypi.org/project/NREL-PySAM/.") from e + except ImportError: + raise ImportError("Requires NREL's PySAM package at " + "https://pypi.org/project/NREL-PySAM/.") datadict = {'tech_model': '6parsolve', 'financial_model': 'none', 'celltype': celltype, 'Vmp': v_mp,