Skip to content

coefficient estimation method following DeSoto(2006) #784

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 50 commits into from
Oct 31, 2019
Merged
Show file tree
Hide file tree
Changes from 36 commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
c5287ea
function getparams_desoto added to pvsystem module. This commit is ju…
tylunel Aug 21, 2019
5a39fea
getparams_desoto moved from pvsystem to singlediode.
tylunel Aug 21, 2019
7b24201
- Modification of getparams_from_specs so as it follows better the pr…
tylunel Aug 22, 2019
c16f1d0
- Bug corrected in DeSoto(2006) procedure
tylunel Aug 22, 2019
e7ef571
- test_getparams_from_specs_desoto() finished
tylunel Aug 23, 2019
cdb2a73
Not sure of all changes brought by this commit because of holidays.
tylunel Sep 9, 2019
0cfe13f
- function '_parse_raw_sam_df' modified. The parser engine is now def…
tylunel Sep 10, 2019
0c27829
- ModelChain attribute 'diode_params' transformed from tuple containi…
tylunel Sep 11, 2019
d8653cd
- singlediode.get_params_from_specs_desoto() output changed. 'a_ref' …
tylunel Sep 12, 2019
bbb4580
read_epw changed. A line has been added to convert the precipitable w…
tylunel Sep 23, 2019
6b8fadf
- read_epw changed. If condition added to make the conversion only in…
tylunel Sep 30, 2019
121cb06
* Pull and Merge done with upstream pvlib/pvlib-python: fork up to date
tylunel Oct 2, 2019
47e1be4
- argument diode_params changed from tuple to pd.DataFrame
tylunel Oct 2, 2019
c1b62c4
- get_params_from_specs_desoto removed from singlediode.py
tylunel Oct 2, 2019
4e62d1a
- function fit_sdm_desoto added. Still need to be formatted
tylunel Oct 2, 2019
747ed48
- change on type of self.diode_params removed. Go check on branch dio…
tylunel Oct 2, 2019
9ea2ed8
- Function 'fit_sdm_desoto' cleaned and variables names named as in '…
tylunel Oct 2, 2019
bd39dbd
- all changes made on other files than ivtools.py removed (cleaning f…
tylunel Oct 2, 2019
ae5c8be
- other differences cleaned
tylunel Oct 2, 2019
2d4f8f3
- renaming of one variable and minor documentation modifications
tylunel Oct 2, 2019
485dae6
- Beginning of writting of test_fit_sdm_desoto. Coverage around 90-95…
tylunel Oct 4, 2019
77d88a2
-minor format changes
tylunel Oct 4, 2019
6fcafc7
- changes made according to feedbacks of markcampanelli
tylunel Oct 4, 2019
2d53d5d
- some cleaning on fit_sdm_desoto to make it more readable
tylunel Oct 4, 2019
fdbf5ec
- minor code cleaning
tylunel Oct 4, 2019
0e1b4b3
- check on importation of scipy removed
tylunel Oct 7, 2019
d2d8c45
- minor cleaning
tylunel Oct 7, 2019
a70debb
- attempt to reach 100% coverage
tylunel Oct 8, 2019
e01f262
- description added in docs/sphinx/source/whatsnew/v0.7.0.rst
tylunel Oct 8, 2019
17f8617
- changes made according to cwhanse review. Except alpha_sc and beta_…
tylunel Oct 10, 2019
e14ad40
- minor correction and adaptation of test
tylunel Oct 10, 2019
32049b0
- sign correction on 3rd equation
tylunel Oct 11, 2019
bfe3994
- changes on units of alpha_sc and beta_voc inputs. Now in A/K and V/…
tylunel Oct 16, 2019
7ff8a96
- other line of test added for better coverage
tylunel Oct 16, 2019
7f08d80
- some changes to try feedbacks of cwhanse and markcampanelli, not fi…
tylunel Oct 17, 2019
27ab961
-OptimizeResult added in output
tylunel Oct 18, 2019
79ef920
- includes all feedbacks made on the 21/10, except moving of pv_fct()…
tylunel Oct 21, 2019
5e9cf8d
- pv_fct moved out of the fit_sdm_desoto function and renamed in _sys…
tylunel Oct 21, 2019
c02e74f
- minor modification: Boltzmann k given in specs to avoid import of s…
tylunel Oct 22, 2019
fa41eb8
- cleaning and minor modifications to docstring
tylunel Oct 22, 2019
753d312
- references added to docstring in _system_of_equations
tylunel Oct 22, 2019
b1b405c
- modification for removing last lint error
tylunel Oct 22, 2019
22d613c
- modification for removing last lint error
tylunel Oct 22, 2019
ae19a68
- modification for removing lint error
tylunel Oct 22, 2019
6913a52
- modif for removing of lint error
tylunel Oct 22, 2019
05051e1
- integration of adriesse suggestions
tylunel Oct 28, 2019
d4772c6
Merge branch 'master' into fit_sdm_desoto
cwhanse Oct 29, 2019
d821494
- adding of tylunel to the list of contributors
tylunel Oct 29, 2019
9d23552
Merge branch 'fit_sdm_desoto' of https://github.com/tylunel/pvlib-pyt…
tylunel Oct 29, 2019
5037da6
- adding of mark requires_scipy
tylunel Oct 29, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion docs/sphinx/source/whatsnew/v0.7.0.rst
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.. _whatsnew_0700:
.. _whatsnew_0700:

v0.7.0 (MONTH DAY, YEAR)
------------------------
Expand Down Expand Up @@ -96,6 +96,8 @@ Enhancements
the single diode equation to an IV curve.
* 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.
* Add :py:func:`~pvlib.ivtools.fit_sdm_desoto`, a method to fit the single
diode equation to the typical specifications given in manufacturers datasheets.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably should say “DeSoto single diode model” instead of “single diode equation”.


Bug fixes
~~~~~~~~~
Expand Down
167 changes: 167 additions & 0 deletions pvlib/ivtools.py
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,173 @@ def fit_sde_sandia(voltage, current, v_oc=None, i_sc=None, v_mp_i_mp=None,
v_oc)


def fit_sdm_desoto(v_mp, i_mp, v_oc, i_sc, alpha_sc, beta_voc,
cells_in_series, EgRef=1.121, dEgdT=-0.0002677,
temp_ref=25, irrad_ref=1000):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we add a function parameter root_kwargs which accepts a dictionary of arguments to pass on to root?

"""
Calculates the parameters for the De Soto single diode model using the
procedure described in [1]. This procedure has the advantage of
using common specifications given by manufacturers in the
datasheets of PV modules. The solution is found using the
scipy.optimize.root() function, with the correpsonding default solver
method 'hybr'.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's probably worth mentioning that there are no restrictions on the fit variables, i.e., series resistance could go negative.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if you are touching this text, "correpsonding " is misspelled.

The parameters returned by this function can be used by
pvsystem.calcparams_desoto to calculate the values at different
irradiance and cell temperature.

Parameters
----------
v_mp: float
Module voltage at the maximum-power point at reference conditions [V].
i_mp: float
Module current at the maximum-power point at reference conditions [A].
v_oc: float
Open-circuit voltage at reference conditions [V].
i_sc: float
Short-circuit current at reference conditions [A].
alpha_sc: float
The short-circuit current (i_sc) temperature coefficient of the
module [A/K].
beta_voc: float
The open-circuit voltage (v_oc) temperature coefficient of the
module [V/K].
cells_in_series: float
Number of cell in the module.
EgRef: float, default 1.121 eV - value for silicon
Energy of bandgap of semi-conductor used [eV]
dEgdT: float, default -0.0002677 - value for silicon
Variation of bandgap according to temperature [eV/K]
temp_ref: float, default 25
Reference temperature condition [C]
irrad_ref: float, default 1000
Reference irradiance condition [W/m2]

Returns
-------
Dictionary with the following elements:

* I_L_ref: float
Light-generated current at reference conditions [A]
* I_o_ref: float
Diode saturation current at reference conditions [A]
* R_s: float
Series resistance [ohms]
Note that '_ref' is not mentionned in the name because
this resistance is not sensible to the conditions of test.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't readily understand what this note means. I always thought the missing _ref was merely the legacy of an original oversight. @cwhanse Do you have an insight here?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

R_s is used to remain consistent with SAM variable names. Let's remove the "Note that.." text.

* R_sh_ref: float
Shunt resistance at reference conditions [ohms].
* a_ref: float
Modified ideality factor at reference conditions.
The product of the usual diode ideality factor (n, unitless),
number of cells in series (Ns), and cell thermal voltage at
specified effective irradiance and cell temperature.
* alpha_sc: float
The short-circuit current (i_sc) temperature coefficient of the
module [A/K].
* EgRef: float
Energy of bandgap of semi-conductor used [eV]
* dEgdT: float
Variation of bandgap according to temperature [eV/K]
* irrad_ref: float
Reference irradiance condition [W/m2]
* temp_ref: float
Reference temperature condition [C]
* result: scipy.optimize.OptimizeResult
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Key is actually named optimize_result. Don't lump this in with the rest of parameters, see comment below.

Optimization result of scipy.optimize.root().
See scipy.optimize.OptimizeResult for more details.

References
----------
[1] W. De Soto et al., "Improvement and validation of a model for
photovoltaic array performance", Solar Energy, vol 80, pp. 78-88,
2006.

[2] John A Duffie ,William A Beckman, "Solar Engineering of Thermal
Processes", Wiley, 2013
"""

try:
from scipy.optimize import root
from scipy import constants
except ImportError:
raise ImportError("The fit_sdm_desoto function requires scipy.")

# Constants
k = constants.value('Boltzmann constant in eV/K')
Tref = temp_ref + 273.15 # [K]

def pv_fct(params, specs):
"""Evaluates the systems of equations used to solve for the single
diode equation parameters.
To avoid the confusion in names with variables of container
function the '_' of the variables were removed.
"""
# six input known variables
Isc, Voc, Imp, Vmp, betaoc, alphasc = specs

# five parameters vector to find
IL, Io, a, Rsh, Rs = params

# five equation vector
y = [0, 0, 0, 0, 0]

# 1st equation - short-circuit - eq(3) in [1]
y[0] = Isc - IL + Io*np.expm1(Isc*Rs/a) + Isc*Rs/Rsh
# 2nd equation - open-circuit Tref - eq(4) in [1]
y[1] = -IL + Io*np.expm1(Voc/a) + Voc/Rsh
# 3rd equation - Imp & Vmp - eq(5) in [1]
y[2] = Imp - IL + Io*np.expm1((Vmp+Imp*Rs)/a) + \
(Vmp+Imp*Rs)/Rsh
# 4th equation - Pmp derivated=0 - eq23.2.6 in [2]
# caution: eq(6) in [1] has a sign error
y[3] = Imp - Vmp * ((Io/a)*np.exp((Vmp+Imp*Rs)/a) + 1.0/Rsh) / \
(1.0 + (Io*Rs/a)*np.exp((Vmp+Imp*Rs)/a) + Rs/Rsh)
# 5th equation - open-circuit T2 - eq (4) at temperature T2 in [1]
T2 = Tref + 2
Voc2 = (T2 - Tref)*betaoc + Voc # eq (7) in [1]
a2 = a*T2/Tref # eq (8) in [1]
IL2 = IL + alphasc*(T2-Tref) # eq (11) in [1]
Eg2 = EgRef*(1 + dEgdT*(T2-Tref)) # eq (10) in [1]
Io2 = Io * (T2/Tref)**3 * np.exp(1/k * (EgRef/Tref-Eg2/T2)) # eq (9)
y[4] = -IL2 + Io2*np.expm1(Voc2/a2) + Voc2/Rsh # eq (4) at T2

return y

# initial guesses of variables for computing convergence:
# Values are taken from [2], p753
Rsh_i = 100.0
a_i = 1.5*k*Tref*cells_in_series
IL_i = i_sc
Io_i = i_sc * np.exp(-v_oc/a_i)
Rs_i = (a_i*np.log1p((IL_i-i_mp)/Io_i) - v_mp)/i_mp
# params_i : initial values vector
params_i = np.array([IL_i, Io_i, a_i, Rsh_i, Rs_i])

# specs of module
specs = np.array([i_sc, v_oc, i_mp, v_mp, beta_voc, alpha_sc])

# computing
result = root(pv_fct, x0=params_i, args=specs)

if result.success:
sdm_params = result.x
else:
raise RuntimeError('Parameter estimation failed:\n' + result.message)

# results
return {'I_L_ref': sdm_params[0],
'I_o_ref': sdm_params[1],
'a_ref': sdm_params[2],
'R_sh_ref': sdm_params[3],
'R_s': sdm_params[4],
'alpha_sc': alpha_sc,
'EgRef': EgRef,
'dEgdT': dEgdT,
'irrad_ref': irrad_ref,
'temp_ref': temp_ref,
'optimize_result': result}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately, including the optimize_result right in this dictionary makes chaining computations more difficult:

result = ivtools.fit_sdm_desoto(cells_in_series=60, v_mp=31.0, i_mp=8.71, v_oc=38.3, i_sc=9.43, alpha_sc=0.005658, beta_voc=-0.13788)
pvlib.pvsystem.calcparams_desoto(1000, 25, **result)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-17-57980407c8d1> in <module>
----> 1 pvlib.pvsystem.calcparams_desoto(1000, 25, **result)

TypeError: calcparams_desoto() got an unexpected keyword argument 'optimize_result'

There are at least a few options here:

  1. Ignore my suggestion to include the optimize_result.
  2. Take my original suggestion of making optimize_result the second element in a returned tuple.
    return {'I_L_ref': sdm_params[0], 'I_o_ref': sdm_params[1], 'a_ref': sdm_params[2], 'R_sh_ref': sdm_params[3],
                'R_s': sdm_params[4], 'alpha_sc': alpha_sc, 'EgRef': EgRef, 'dEgdT': dEgdT, 'irrad_ref': irrad_ref,
                'temp_ref': temp_ref}, result
  1. Make the returned dictionary have a nested key for the parameters, such as:
    return {
                'params': {
                                'I_L_ref': sdm_params[0],
                                'I_o_ref': sdm_params[1],
                                'a_ref': sdm_params[2],
                                'R_sh_ref': sdm_params[3],
                                'R_s': sdm_params[4],
                                'alpha_sc': alpha_sc,
                                'EgRef': EgRef,
                                'dEgdT': dEgdT,
                                'irrad_ref': irrad_ref,
                                'temp_ref': temp_ref
                               },
                'optimize_result': result
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I apologize, I overlooked this change. If we are returning the result object, return as 2nd element in a tuple. optimize_result would be a better variable name.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I made this change too quickly.
@markcampanelli I think your view on the package is more complete than mine, so I will include the optimize_result as you suggested. (after all, the goal of this pull request is also to learn from experienced users/contributors of pvlib-python)
@cwhanse Why do you prefer to return it in a tuple? Intuitively I would have use the dict (3rd option of markcampanelli) for improving the readability in the later use of the output.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so that it can be omitted if not of interest, e.g., params, _ = fit_sdm_desoto(

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like option 2. I don't like option 3.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for chiming in @cwhanse and @wholmgren. I'm good with option 2. Interestingly, @tylunel, I use option 3 in my own APIs (I always return a single dictionary, nested if necessary), but for some reason I guessed that option 2 would be more preferable than option 3 in pvlib ;).



def _find_mp(voltage, current):
"""
Finds voltage and current at maximum power point.
Expand Down
32 changes: 32 additions & 0 deletions pvlib/test/test_ivtools.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@ def get_cec_params_cansol_cs5p_220p():
'R_sh_ref': 837.51, 'R_s': 1.004, 'Adjust': 2.3}}


@pytest.fixture
def get_test_specs_params():
"""Specifications of module Kyocera KU270-6MCA"""
return {'v_mp': 31.0, 'i_mp': 8.71, 'v_oc': 38.3,
'i_sc': 9.43, 'alpha_sc': 0.005658, 'beta_voc': -0.13788}


@requires_scipy
def test_fit_sde_sandia(get_test_iv_params, get_bad_iv_curves):
test_params = get_test_iv_params
Expand Down Expand Up @@ -102,6 +109,31 @@ def test_fit_sdm_cec_sam(get_cec_params_cansol_cs5p_220p):
cells_in_series=1, temp_ref=25)


@requires_scipy
def test_fit_sdm_desoto(get_test_specs_params):
result = ivtools.fit_sdm_desoto(cells_in_series=60,
Copy link
Contributor

@markcampanelli markcampanelli Oct 21, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

move cells_in_series to the get_test_specs_params fixture.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cells_in_series is not in the get_test_specs_params fixture because it changes at line 129 of the test in order to cause the RuntimeError. I feel like it would be more complicated to add it the fixture and to change it later to have the RuntimeError. What do you think ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer to see it added to the fixture and then modified in the function that causes the RuntimeError. That way the fixture returns a complete, consistent specification.

**get_test_specs_params)
result_expected = {'I_L_ref': 9.452324509050774,
'I_o_ref': 3.2246097466679494e-10,
'a_ref': 1.5912875522463978,
'R_sh_ref': 125.79869968976132,
'R_s': 0.2978148476600744,
'alpha_sc': 0.005658,
'EgRef': 1.121,
'dEgdT': -0.0002677,
'irrad_ref': 1000,
'temp_ref': 25}
del result['optimize_result']
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having to delete this entry here suggests that it shouldn't be lumped in with the rest of the parameters.

assert np.allclose(pd.Series(result), pd.Series(result_expected),
rtol=1e-4)
with pytest.raises(RuntimeError) as exc:
ivtools.fit_sdm_desoto(cells_in_series=10,
v_mp=31.0, i_mp=8.71, v_oc=38.3,
i_sc=9.43, alpha_sc=0.06,
beta_voc=-0.36)
assert ('Parameter estimation failed') in str(exc.value)


@pytest.fixture
def get_bad_iv_curves():
# v1, i1 produces a bad value for I0_voc
Expand Down