From edde9ebe1f70c57b7c0c3dcec8fedaaac754c233 Mon Sep 17 00:00:00 2001 From: Will Holmgren Date: Fri, 10 Jun 2016 19:19:47 -0700 Subject: [PATCH 1/7] implement v_from_i --- pvlib/pvsystem.py | 79 ++++++++++++++++++++++++++++++++++--- pvlib/test/test_pvsystem.py | 9 ++++- 2 files changed, 80 insertions(+), 8 deletions(-) diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index a530b39f5c..78d0d4d6e4 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -139,7 +139,7 @@ def __init__(self, # needed for tying together Location and PVSystem in LocalizedPVSystem super(PVSystem, self).__init__(**kwargs) - + def __repr__(self): return ('PVSystem with tilt:' + str(self.surface_tilt) + ' and azimuth: ' + str(self.surface_azimuth) + @@ -443,7 +443,7 @@ def __init__(self, pvsystem=None, location=None, **kwargs): # get and combine attributes from the pvsystem and/or location # with the rest of the kwargs - + if pvsystem is not None: pv_dict = pvsystem.__dict__ else: @@ -459,7 +459,7 @@ def __init__(self, pvsystem=None, location=None, **kwargs): list(kwargs.items())) super(LocalizedPVSystem, self).__init__(**new_kwargs) - + def __repr__(self): return ('LocalizedPVSystem with tilt:' + str(self.surface_tilt) + ' and azimuth: ' + str(self.surface_azimuth) + @@ -1395,15 +1395,16 @@ def singlediode(module, photocurrent, saturation_current, i_sc = i_from_v(resistance_shunt, resistance_series, nNsVth, 0.01, saturation_current, photocurrent) + # Find open circuit voltage using Lambert W + v_oc = v_from_i(resistance_shunt, resistance_series, nNsVth, 0.0, + saturation_current, photocurrent) + params = {'r_sh': resistance_shunt, 'r_s': resistance_series, 'nNsVth': nNsVth, 'i_0': saturation_current, 'i_l': photocurrent} - __, v_oc = _golden_sect_DataFrame(params, 0, module['V_oc_ref']*1.6, - _v_oc_optfcn) - p_mp, v_mp = _golden_sect_DataFrame(params, 0, module['V_oc_ref']*1.14, _pwr_optfcn) @@ -1543,6 +1544,72 @@ def _v_oc_optfcn(df, loc): return I +def v_from_i(resistance_shunt, resistance_series, nNsVth, current, + saturation_current, photocurrent): + ''' + Calculates current from voltage per Eq 3 Jain and Kapoor 2004 [1]. + + Parameters + ---------- + resistance_shunt : float or Series + Shunt resistance in ohms under desired IV curve conditions. + Often abbreviated ``Rsh``. + + resistance_series : float or Series + Series resistance in ohms under desired IV curve conditions. + Often abbreviated ``Rs``. + + nNsVth : float or Series + The product of three components. 1) The usual diode ideal factor + (n), 2) the number of cells in series (Ns), and 3) the cell + thermal voltage under the desired IV curve conditions (Vth). The + thermal voltage of the cell (in volts) may be calculated as + ``k*temp_cell/q``, where k is Boltzmann's constant (J/K), + temp_cell is the temperature of the p-n junction in Kelvin, and + q is the charge of an electron (coulombs). + + current : float or Series + The current in amperes under desired IV curve conditions. + + saturation_current : float or Series + Diode saturation current in amperes under desired IV curve + conditions. Often abbreviated ``I_0``. + + photocurrent : float or Series + Light-generated current (photocurrent) in amperes under desired + IV curve conditions. Often abbreviated ``I_L``. + + Returns + ------- + current : np.array + + References + ---------- + [1] A. Jain, A. Kapoor, "Exact analytical solutions of the + parameters of real solar cells using Lambert W-function", Solar + Energy Materials and Solar Cells, 81 (2004) 269-277. + ''' + try: + from scipy.special import lambertw + except ImportError: + raise ImportError('This function requires scipy') + + Rsh = resistance_shunt + Rs = resistance_series + I0 = saturation_current + IL = photocurrent + I = current + + argW = I0 * Rsh / nNsVth * np.exp(Rsh *(-I + IL + I0) / nNsVth) + lambertwterm = lambertw(argW) + + # Eqn. 3 in Jain and Kapoor, 2004 + + V = -I*(Rs + Rsh) + IL*Rsh - nNsVth*lambertwterm + I0*Rsh + + return V.real + + def i_from_v(resistance_shunt, resistance_series, nNsVth, voltage, saturation_current, photocurrent): ''' diff --git a/pvlib/test/test_pvsystem.py b/pvlib/test/test_pvsystem.py index 3b58e0b895..22f35ad942 100644 --- a/pvlib/test/test_pvsystem.py +++ b/pvlib/test/test_pvsystem.py @@ -229,6 +229,11 @@ def test_PVSystem_calcparams_desoto(): assert_almost_equals(nNsVth, 0.473) +def test_v_from_i(): + output = pvsystem.v_from_i(20, .1, .5, 3, 6e-7, 7) + assert_almost_equals(-299.746389916, output, 5) + + def test_i_from_v(): output = pvsystem.i_from_v(20, .1, .5, 40, 6e-7, 7) assert_almost_equals(-299.746389916, output, 5) @@ -478,7 +483,7 @@ def test_PVSystem___repr__(): assert system.__repr__()==('PVSystem with tilt:0 and azimuth:'+ ' 180 with Module: blah and Inverter: blarg') - + def test_PVSystem_localize___repr__(): system = pvsystem.PVSystem(module='blah', inverter='blarg') @@ -504,7 +509,7 @@ def test_LocalizedPVSystem_creation(): assert localized_system.latitude == 32 assert localized_system.longitude == -111 - + def test_LocalizedPVSystem___repr__(): localized_system = pvsystem.LocalizedPVSystem(latitude=32, longitude=-111, From 73d3341e566f72bd6417d9aa2fc9377d39e889b1 Mon Sep 17 00:00:00 2001 From: Will Holmgren Date: Sat, 11 Jun 2016 13:59:27 -0700 Subject: [PATCH 2/7] add and use v_from_i, just add _dpower_dcurrent --- pvlib/pvsystem.py | 70 +++++++++++++++++++++++++++++++++++++ pvlib/test/test_pvsystem.py | 14 ++++---- 2 files changed, 77 insertions(+), 7 deletions(-) diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index 78d0d4d6e4..c0021e9bdf 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -1405,6 +1405,10 @@ def singlediode(module, photocurrent, saturation_current, 'i_0': saturation_current, 'i_l': photocurrent} + # to do: + # replace with a call to scipy.optimize or minimize(_dpower_dcurrent) + # and then calculate v_mp, and then calculate p_mp = i_mp*v_mp + # might need a partial(_dpower_dcurrent) for all params but I p_mp, v_mp = _golden_sect_DataFrame(params, 0, module['V_oc_ref']*1.14, _pwr_optfcn) @@ -1677,6 +1681,72 @@ def i_from_v(resistance_shunt, resistance_series, nNsVth, voltage, return I.real +def _dpower_dcurrent(resistance_shunt, resistance_series, nNsVth, current, + saturation_current, photocurrent): + ''' + Calculates the partial derivative of power with respect to current + per Eq 10 Jain and Kapoor 2004 [1]. + + Parameters + ---------- + resistance_shunt : float or Series + Shunt resistance in ohms under desired IV curve conditions. + Often abbreviated ``Rsh``. + + resistance_series : float or Series + Series resistance in ohms under desired IV curve conditions. + Often abbreviated ``Rs``. + + nNsVth : float or Series + The product of three components. 1) The usual diode ideal factor + (n), 2) the number of cells in series (Ns), and 3) the cell + thermal voltage under the desired IV curve conditions (Vth). The + thermal voltage of the cell (in volts) may be calculated as + ``k*temp_cell/q``, where k is Boltzmann's constant (J/K), + temp_cell is the temperature of the p-n junction in Kelvin, and + q is the charge of an electron (coulombs). + + current : float or Series + The current in amperes under desired IV curve conditions. + + saturation_current : float or Series + Diode saturation current in amperes under desired IV curve + conditions. Often abbreviated ``I_0``. + + photocurrent : float or Series + Light-generated current (photocurrent) in amperes under desired + IV curve conditions. Often abbreviated ``I_L``. + + Returns + ------- + current : np.array + + References + ---------- + [1] A. Jain, A. Kapoor, "Exact analytical solutions of the + parameters of real solar cells using Lambert W-function", Solar + Energy Materials and Solar Cells, 81 (2004) 269-277. + ''' + + # consider using numpy's out parameters, numexpr, or numba to + # make this faster + + Rsh = resistance_shunt + Rs = resistance_series + I0 = saturation_current + IL = photocurrent + I = current + + argW = I0 * Rsh / nNsVth * np.exp(Rsh *(-I + IL + I0) / nNsVth) + z = lambertw(argW) + + # Eqn. 10 in Jain and Kapoor, 2004 + + dpdi = (Iph + I0 - 2*I)*Rsh - 2*I*Rs - nNsVth*z + I*Rsh*z/(1+z) + + return dpdi.real + + def snlinverter(inverter, v_dc, p_dc): ''' Converts DC power and voltage to AC power using Sandia's diff --git a/pvlib/test/test_pvsystem.py b/pvlib/test/test_pvsystem.py index 22f35ad942..ec5756e94b 100644 --- a/pvlib/test/test_pvsystem.py +++ b/pvlib/test/test_pvsystem.py @@ -231,7 +231,7 @@ def test_PVSystem_calcparams_desoto(): def test_v_from_i(): output = pvsystem.v_from_i(20, .1, .5, 3, 6e-7, 7) - assert_almost_equals(-299.746389916, output, 5) + assert_almost_equals(7.5049875193450521, output, 5) def test_i_from_v(): @@ -268,16 +268,16 @@ def test_singlediode_floats(): module = 'Example_Module' module_parameters = sam_data['cecmod'][module] out = pvsystem.singlediode(module_parameters, 7, 6e-7, .1, 20, .5) - expected = {'i_xx': 4.2549732697234193, + expected = {'i_xx': 4.2685798754011426, 'i_mp': 6.1390251797935704, - 'v_oc': 8.1147298764528042, + 'v_oc': 8.1063001465863085, 'p_mp': 38.194165464983037, 'i_x': 6.7556075876880621, 'i_sc': 6.9646747613963198, 'v_mp': 6.221535886625464} assert isinstance(out, dict) for k, v in out.items(): - assert_almost_equals(expected[k], v, 5) + yield assert_almost_equals, expected[k], v, 3 def test_PVSystem_singlediode_floats(): @@ -286,16 +286,16 @@ def test_PVSystem_singlediode_floats(): system = pvsystem.PVSystem(module=module, module_parameters=module_parameters) out = system.singlediode(7, 6e-7, .1, 20, .5) - expected = {'i_xx': 4.2549732697234193, + expected = {'i_xx': 4.2685798754011426, 'i_mp': 6.1390251797935704, - 'v_oc': 8.1147298764528042, + 'v_oc': 8.1063001465863085, 'p_mp': 38.194165464983037, 'i_x': 6.7556075876880621, 'i_sc': 6.9646747613963198, 'v_mp': 6.221535886625464} assert isinstance(out, dict) for k, v in out.items(): - assert_almost_equals(expected[k], v, 5) + yield assert_almost_equals, expected[k], v, 3 def test_scale_voltage_current_power(): From a5eaa755d4a3e4775a47126bc66aeff97465081a Mon Sep 17 00:00:00 2001 From: Will Holmgren Date: Sun, 12 Jun 2016 18:13:53 -0700 Subject: [PATCH 3/7] remove _dpower_dcurrent --- pvlib/pvsystem.py | 70 ----------------------------------------------- 1 file changed, 70 deletions(-) diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index c0021e9bdf..78d0d4d6e4 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -1405,10 +1405,6 @@ def singlediode(module, photocurrent, saturation_current, 'i_0': saturation_current, 'i_l': photocurrent} - # to do: - # replace with a call to scipy.optimize or minimize(_dpower_dcurrent) - # and then calculate v_mp, and then calculate p_mp = i_mp*v_mp - # might need a partial(_dpower_dcurrent) for all params but I p_mp, v_mp = _golden_sect_DataFrame(params, 0, module['V_oc_ref']*1.14, _pwr_optfcn) @@ -1681,72 +1677,6 @@ def i_from_v(resistance_shunt, resistance_series, nNsVth, voltage, return I.real -def _dpower_dcurrent(resistance_shunt, resistance_series, nNsVth, current, - saturation_current, photocurrent): - ''' - Calculates the partial derivative of power with respect to current - per Eq 10 Jain and Kapoor 2004 [1]. - - Parameters - ---------- - resistance_shunt : float or Series - Shunt resistance in ohms under desired IV curve conditions. - Often abbreviated ``Rsh``. - - resistance_series : float or Series - Series resistance in ohms under desired IV curve conditions. - Often abbreviated ``Rs``. - - nNsVth : float or Series - The product of three components. 1) The usual diode ideal factor - (n), 2) the number of cells in series (Ns), and 3) the cell - thermal voltage under the desired IV curve conditions (Vth). The - thermal voltage of the cell (in volts) may be calculated as - ``k*temp_cell/q``, where k is Boltzmann's constant (J/K), - temp_cell is the temperature of the p-n junction in Kelvin, and - q is the charge of an electron (coulombs). - - current : float or Series - The current in amperes under desired IV curve conditions. - - saturation_current : float or Series - Diode saturation current in amperes under desired IV curve - conditions. Often abbreviated ``I_0``. - - photocurrent : float or Series - Light-generated current (photocurrent) in amperes under desired - IV curve conditions. Often abbreviated ``I_L``. - - Returns - ------- - current : np.array - - References - ---------- - [1] A. Jain, A. Kapoor, "Exact analytical solutions of the - parameters of real solar cells using Lambert W-function", Solar - Energy Materials and Solar Cells, 81 (2004) 269-277. - ''' - - # consider using numpy's out parameters, numexpr, or numba to - # make this faster - - Rsh = resistance_shunt - Rs = resistance_series - I0 = saturation_current - IL = photocurrent - I = current - - argW = I0 * Rsh / nNsVth * np.exp(Rsh *(-I + IL + I0) / nNsVth) - z = lambertw(argW) - - # Eqn. 10 in Jain and Kapoor, 2004 - - dpdi = (Iph + I0 - 2*I)*Rsh - 2*I*Rs - nNsVth*z + I*Rsh*z/(1+z) - - return dpdi.real - - def snlinverter(inverter, v_dc, p_dc): ''' Converts DC power and voltage to AC power using Sandia's From e6dea7425a82f369cc58d912cdd1574073b378f8 Mon Sep 17 00:00:00 2001 From: Will Holmgren Date: Sun, 12 Jun 2016 18:17:30 -0700 Subject: [PATCH 4/7] add notes to whatsnew --- docs/sphinx/source/whatsnew/v0.3.3.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/sphinx/source/whatsnew/v0.3.3.txt b/docs/sphinx/source/whatsnew/v0.3.3.txt index f390d1ac13..88ae44ae95 100644 --- a/docs/sphinx/source/whatsnew/v0.3.3.txt +++ b/docs/sphinx/source/whatsnew/v0.3.3.txt @@ -24,6 +24,10 @@ Enhancements (:issue:`169`) * Add ``__repr__`` method to PVSystem, LocalizedPVSystem, ModelChain, SingleAxisTracker, Location. (:issue:`142`) +* Add ``v_from_i`` function for solving the single diode model. + (:issue:`190`) +* Improve speed of ``singlediode`` function by using ``v_from_i`` to + determine ``v_oc``. Speed is ~2x faster. (:issue:`190`) Bug fixes From 55d5c58fd27f8c0b0179d3ec38744c709e25320c Mon Sep 17 00:00:00 2001 From: Will Holmgren Date: Sun, 12 Jun 2016 18:28:57 -0700 Subject: [PATCH 5/7] remove _v_oc_optfcn --- pvlib/pvsystem.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index 78d0d4d6e4..db62d698a1 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -1535,15 +1535,6 @@ def _pwr_optfcn(df, loc): return I*df[loc] -def _v_oc_optfcn(df, loc): - ''' - Function to find the open circuit voltage from ``i_from_v``. - ''' - I = -abs(i_from_v(df['r_sh'], df['r_s'], df['nNsVth'], - df[loc], df['i_0'], df['i_l'])) - return I - - def v_from_i(resistance_shunt, resistance_series, nNsVth, current, saturation_current, photocurrent): ''' From d1b1ae16df4de08afd7b9149a9cd183840944223 Mon Sep 17 00:00:00 2001 From: Will Holmgren Date: Sun, 12 Jun 2016 18:33:42 -0700 Subject: [PATCH 6/7] fix doc typo --- pvlib/pvsystem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index db62d698a1..be0310689c 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -1538,7 +1538,7 @@ def _pwr_optfcn(df, loc): def v_from_i(resistance_shunt, resistance_series, nNsVth, current, saturation_current, photocurrent): ''' - Calculates current from voltage per Eq 3 Jain and Kapoor 2004 [1]. + Calculates voltage from current per Eq 3 Jain and Kapoor 2004 [1]. Parameters ---------- From 8a2d5433d96731b5f85ccd2a5acb39088d036a3f Mon Sep 17 00:00:00 2001 From: Will Holmgren Date: Mon, 13 Jun 2016 13:11:24 -0700 Subject: [PATCH 7/7] add a v_from_i test representative of a cdte voc --- pvlib/test/test_pvsystem.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pvlib/test/test_pvsystem.py b/pvlib/test/test_pvsystem.py index ec5756e94b..6ae13a0cb6 100644 --- a/pvlib/test/test_pvsystem.py +++ b/pvlib/test/test_pvsystem.py @@ -234,6 +234,11 @@ def test_v_from_i(): assert_almost_equals(7.5049875193450521, output, 5) +def test_v_from_i_big(): + output = pvsystem.v_from_i(500, 10, 4.06, 0, 6e-10, 1.2) + assert_almost_equals(86.320000493521079, output, 5) + + def test_i_from_v(): output = pvsystem.i_from_v(20, .1, .5, 40, 6e-7, 7) assert_almost_equals(-299.746389916, output, 5)