Skip to content
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

add PVSystem.losses_parameters, add support for custom pvwatts losses parameters in ModelChain #491

Merged
merged 5 commits into from
Aug 1, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 5 additions & 1 deletion docs/sphinx/source/whatsnew/v0.6.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@ v0.6.0 (___, 2018)

API Changes
~~~~~~~~~~~
* pvsystem.calcparams_desoto now requires arguments for each module model parameter.
* pvsystem.calcparams_desoto now requires arguments for each module model
parameter. (:issue:`462`)
* Add losses_parameters attribute to PVSystem objects and remove the kwargs
support from PVSystem.pvwatts_losses. Enables custom losses specification
in ModelChain calculations. (:issue:`484`)
* removed irradiance parameter from ModelChain.run_model and ModelChain.prepare_inputs


Expand Down
55 changes: 32 additions & 23 deletions pvlib/pvsystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,9 @@ class PVSystem(object):
racking_model : None or string, default 'open_rack_cell_glassback'
Used for cell and module temperature calculations.

losses_parameters : None, dict or Series, default None
Losses parameters as defined by PVWatts or other.
Copy link
Member

Choose a reason for hiding this comment

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

Can we list the permissible keywords for the dict here? Also I think it would help to include a comment on how they are used, e.g., Loss parameters (%) are combined into a single loss factor, L = (1 - Soiling) * (1 - Age) * ..., that is applied to AC power

Copy link
Member Author

Choose a reason for hiding this comment

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

I'm not opposed to adding some additional detail to this doc string, but I would like to do it in a model-independent way. pvlib currently only includes the pvwatts losses model, but this dict should be useful to future models. Also note the current text is similar to the module_parameters and inverter_parameters doc strings.


name : None or string, default None

**kwargs
Expand All @@ -119,8 +122,7 @@ def __init__(self,
modules_per_string=1, strings_per_inverter=1,
inverter=None, inverter_parameters=None,
racking_model='open_rack_cell_glassback',
name=None,
**kwargs):
losses_parameters=None, name=None, **kwargs):
Copy link
Member

Choose a reason for hiding this comment

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

Need an entry in the signature describing losses_parameters


self.name = name

Expand Down Expand Up @@ -150,6 +152,11 @@ def __init__(self,
else:
self.inverter_parameters = inverter_parameters

if losses_parameters is None:
self.losses_parameters = {}
else:
self.losses_parameters = losses_parameters

self.racking_model = racking_model

def __repr__(self):
Expand Down Expand Up @@ -304,7 +311,7 @@ def calcparams_desoto(self, effective_irradiance, temp_cell, **kwargs):
kwargs = _build_kwargs(['a_ref', 'I_L_ref', 'I_o_ref', 'R_sh_ref',
'R_s', 'alpha_sc', 'EgRef', 'dEgdT'],
self.module_parameters)

return calcparams_desoto(effective_irradiance, temp_cell, **kwargs)

def sapm(self, effective_irradiance, temp_cell, **kwargs):
Expand Down Expand Up @@ -462,7 +469,7 @@ def first_solar_spectral_loss(self, pw, airmass_absolute):
coefficients = None

return atmosphere.first_solar_spectral_correction(pw,
airmass_absolute,
airmass_absolute,
module_type,
coefficients)

Expand Down Expand Up @@ -597,15 +604,17 @@ def pvwatts_dc(self, g_poa_effective, temp_cell):
self.module_parameters['gamma_pdc'],
**kwargs)

def pvwatts_losses(self, **kwargs):
def pvwatts_losses(self):
"""
Calculates DC power losses according the PVwatts model using
:py:func:`pvwatts_losses`. No attributes are used in this
calculation, but all keyword arguments will be passed to the
function.
:py:func:`pvwatts_losses` and ``self.losses_parameters``.`

See :py:func:`pvwatts_losses` for details.
"""
kwargs = _build_kwargs(['soiling', 'shading', 'snow', 'mismatch',
'wiring', 'connections', 'lid',
'nameplate_rating', 'age', 'availability'],
self.losses_parameters)
return pvwatts_losses(**kwargs)

def pvwatts_ac(self, pdc):
Expand Down Expand Up @@ -946,12 +955,12 @@ def physicaliam(aoi, n=1.526, K=4., L=0.002):


def calcparams_desoto(effective_irradiance, temp_cell,
alpha_sc, a_ref, I_L_ref, I_o_ref, R_sh_ref, R_s,
alpha_sc, a_ref, I_L_ref, I_o_ref, R_sh_ref, R_s,
EgRef=1.121, dEgdT=-0.0002677,
irrad_ref=1000, temp_ref=25):
'''
Calculates five parameter values for the single diode equation at
effective irradiance and cell temperature using the De Soto et al.
Calculates five parameter values for the single diode equation at
effective irradiance and cell temperature using the De Soto et al.
model described in [1]. The five values returned by calcparams_desoto
can be used by singlediode to calculate an IV curve.

Expand All @@ -968,34 +977,34 @@ def calcparams_desoto(effective_irradiance, temp_cell,
module in units of A/C.

a_ref : float
The product of the usual diode ideality factor (n, unitless),
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.

EgRef : float
The energy bandgap at reference temperature in units of eV.
1.121 eV for crystalline silicon. EgRef must be >0. For parameters
1.121 eV for crystalline silicon. EgRef must be >0. For parameters
from the SAM CEC module database, EgRef=1.121 is implicit for all
cell types in the parameter estimation algorithm used by NREL.

dEgdT : float
The temperature dependence of the energy bandgap at reference
conditions in units of 1/K. May be either a scalar value
(e.g. -0.0002677 as in [1]) or a DataFrame (this may be useful if
conditions in units of 1/K. May be either a scalar value
(e.g. -0.0002677 as in [1]) or a DataFrame (this may be useful if
dEgdT is a modeled as a function of temperature). For parameters from
the SAM CEC module database, dEgdT=-0.0002677 is implicit for all cell
types in the parameter estimation algorithm used by NREL.
Expand Down Expand Up @@ -1143,7 +1152,7 @@ def calcparams_desoto(effective_irradiance, temp_cell,

# Boltzmann constant in eV/K
k = 8.617332478e-05

# reference temperature
Tref_K = temp_ref + 273.15
Tcell_K = temp_cell + 273.15
Expand All @@ -1152,9 +1161,9 @@ def calcparams_desoto(effective_irradiance, temp_cell,

nNsVth = a_ref * (Tcell_K / Tref_K)

# In the equation for IL, the single factor effective_irradiance is
# used, in place of the product S*M in [1]. effective_irradiance is
# equivalent to the product of S (irradiance reaching a module's cells) *
# In the equation for IL, the single factor effective_irradiance is
# used, in place of the product S*M in [1]. effective_irradiance is
# equivalent to the product of S (irradiance reaching a module's cells) *
# M (spectral adjustment factor) as described in [1].
IL = effective_irradiance / irrad_ref * \
(I_L_ref + alpha_sc * (Tcell_K - Tref_K))
Expand All @@ -1164,7 +1173,7 @@ def calcparams_desoto(effective_irradiance, temp_cell,
# Rsh = Rsh_ref * (S_ref / S) where S is broadband irradiance reaching
# the module's cells. If desired this model behavior can be duplicated
# by applying reflection and soiling losses to broadband plane of array
# irradiance and not applying a spectral loss modifier, i.e.,
# irradiance and not applying a spectral loss modifier, i.e.,
# spectral_modifier = 1.0.
Rsh = R_sh_ref * (irrad_ref / effective_irradiance)
Rs = R_s
Expand Down
62 changes: 47 additions & 15 deletions pvlib/test/test_modelchain.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import sys

import numpy as np
import pandas as pd
from numpy import nan
Expand Down Expand Up @@ -78,11 +80,19 @@ def pvwatts_dc_pvwatts_ac_system(sam_data):
return system


@pytest.fixture()
@pytest.fixture
def location():
return Location(32.2, -111, altitude=700)


@pytest.fixture
def weather():
times = pd.date_range('20160101 1200-0700', periods=2, freq='6H')
weather = pd.DataFrame({'ghi': [500, 0], 'dni': [800, 0], 'dhi': [100, 0]},
index=times)
return weather


def test_ModelChain_creation(system, location):
mc = ModelChain(system, location)

Expand Down Expand Up @@ -292,7 +302,7 @@ def test_spectral_models(system, location, spectral_model):
columns=['precipitable_water'])
mc = ModelChain(system, location, dc_model='sapm',
aoi_model='no_loss', spectral_model=spectral_model)
spectral_modifier = mc.run_model(times=times,
spectral_modifier = mc.run_model(times=times,
weather=weather).spectral_modifier
assert isinstance(spectral_modifier, (pd.Series, float, int))

Expand All @@ -302,22 +312,44 @@ def constant_losses(mc):
mc.ac *= mc.losses


@requires_scipy
@pytest.mark.parametrize('losses_model, expected', [
('pvwatts', [163.280464174, 0]),
('no_loss', [190.028186986, 0]),
(constant_losses, [171.025368287, 0])
])
def test_losses_models(pvwatts_dc_pvwatts_ac_system, location, losses_model,
expected):
def test_losses_models_pvwatts(pvwatts_dc_pvwatts_ac_system, location, weather,
mocker):
age = 1
pvwatts_dc_pvwatts_ac_system.losses_parameters = dict(age=age)
m = mocker.spy(pvsystem, 'pvwatts_losses')
mc = ModelChain(pvwatts_dc_pvwatts_ac_system, location, dc_model='pvwatts',
aoi_model='no_loss', spectral_model='no_loss',
losses_model=losses_model)
times = pd.date_range('20160101 1200-0700', periods=2, freq='6H')
ac = mc.run_model(times).ac
losses_model='pvwatts')
mc.run_model(weather.index, weather=weather)
assert m.call_count == 1
m.assert_called_with(age=age)
assert isinstance(mc.ac, (pd.Series, pd.DataFrame))
assert not mc.ac.empty

expected = pd.Series(np.array(expected), index=times)
assert_series_equal(ac, expected, check_less_precise=2)

def test_losses_models_ext_def(pvwatts_dc_pvwatts_ac_system, location, weather,
mocker):
m = mocker.spy(sys.modules[__name__], 'constant_losses')
mc = ModelChain(pvwatts_dc_pvwatts_ac_system, location, dc_model='pvwatts',
aoi_model='no_loss', spectral_model='no_loss',
losses_model=constant_losses)
mc.run_model(weather.index, weather=weather)
assert m.call_count == 1
assert isinstance(mc.ac, (pd.Series, pd.DataFrame))
assert mc.losses == 0.9
assert not mc.ac.empty


def test_losses_models_no_loss(pvwatts_dc_pvwatts_ac_system, location, weather,
mocker):
m = mocker.spy(pvsystem, 'pvwatts_losses')
mc = ModelChain(pvwatts_dc_pvwatts_ac_system, location, dc_model='pvwatts',
aoi_model='no_loss', spectral_model='no_loss',
losses_model='no_loss')
assert mc.losses_model == mc.no_extra_losses
mc.run_model(weather.index, weather=weather)
assert m.call_count == 0
assert mc.losses == 1


@pytest.mark.parametrize('model', [
Expand Down
5 changes: 3 additions & 2 deletions pvlib/test/test_pvsystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -1156,9 +1156,10 @@ def test_PVSystem_pvwatts_dc_kwargs(mocker):
def test_PVSystem_pvwatts_losses(mocker):
mocker.spy(pvsystem, 'pvwatts_losses')
system = make_pvwatts_system_defaults()
expected = 15
age = 1
out = system.pvwatts_losses(age=age)
system.losses_parameters = dict(age=age)
expected = 15
out = system.pvwatts_losses()
pvsystem.pvwatts_losses.assert_called_once_with(age=age)
assert out < expected

Expand Down