Skip to content

implement PVWatts #195

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 7 commits into from
Jun 27, 2016
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
2 changes: 2 additions & 0 deletions docs/sphinx/source/whatsnew/v0.4.0.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Enhancements

* Adds the First Solar spectral correction model. (:issue:`115`)
* Adds the Gueymard 1994 integrated precipitable water model. (:issue:`115`)
* Adds the PVWatts DC, AC, and system losses model. (:issue:`195`)


Bug fixes
Expand All @@ -28,6 +29,7 @@ Bug fixes
Documentation
~~~~~~~~~~~~~

* Added new terms to the variables documentation. (:issue:`195`)


Other
Expand Down
8 changes: 8 additions & 0 deletions pvlib/data/variables_style_rules.csv
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ poa_direct;direct/beam irradiation in plane
poa_diffuse;total diffuse irradiation in plane. sum of ground and sky diffuse.
poa_global;global irradiation in plane. sum of diffuse and beam projection.
poa_sky_diffuse;diffuse irradiation in plane from scattered light in the atmosphere (without ground reflected irradiation)
g_poa_effective;broadband plane of array effective irradiance.
surface_tilt;tilt angle of the surface
surface_azimuth;azimuth angle of the surface
solar_zenith;zenith angle of the sun in degrees
Expand All @@ -36,3 +37,10 @@ saturation_current;diode saturation current
resistance_series;series resistance
resistance_shunt;shunt resistance
transposition_factor; the gain ratio of the radiation on inclined plane to global horizontal irradiation: :math:`\frac{poa\_global}{ghi}`
pdc0; nameplate DC rating
pdc, dc; dc power
gamma_pdc; module temperature coefficient. Typically in units of 1/C.
pac, ac; ac powe.
eta_inv; inverter efficiency
eta_inv_ref; reference inverter efficiency
eta_inv_nom; nominal inverter efficiency
180 changes: 180 additions & 0 deletions pvlib/pvsystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,40 @@ def scale_voltage_current_power(self, data):
voltage=self.modules_per_string,
current=self.strings_per_inverter)

def pvwatts_dc(self, g_poa_effective, temp_cell):
"""
Calcuates DC power according to the PVWatts model using
:py:func:`pvwatts_dc`, `self.module_parameters['pdc0']`, and
`self.module_parameters['gamma_pdc']`.

See :py:func:`pvwatts_dc` for details.
"""
return pvwatts_dc(g_poa_effective, temp_cell,
self.module_parameters['pdc0'],
self.module_parameters['gamma_pdc'])

def pvwatts_losses(self, **kwargs):
"""
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.

See :py:func:`pvwatts_losses` for details.
"""
return pvwatts_losses(**kwargs)

def pvwatts_ac(self, pdc):
"""
Calculates AC power according to the PVWatts model using
:py:func:`pvwatts_ac`, `self.module_parameters['pdc0']`, and
`eta_inv_nom=self.inverter_parameters['eta_inv_nom']`.

See :py:func:`pvwatts_ac` for details.
"""
return pvwatts_ac(pdc, self.module_parameters['pdc0'],
eta_inv_nom=self.inverter_parameters['eta_inv_nom'])

def localize(self, location=None, latitude=None, longitude=None,
**kwargs):
"""Creates a LocalizedPVSystem object using this object
Expand Down Expand Up @@ -1811,3 +1845,149 @@ def scale_voltage_current_power(data, voltage=1, current=1):
data['p_mp'] *= voltage * current

return data


def pvwatts_dc(g_poa_effective, temp_cell, pdc0, gamma_pdc, temp_ref=25.):
r"""
Implements NREL's PVWatts DC power model [1]_:

.. math::

P_{dc} = \frac{G_{poa eff}}{1000} P_{dc0} ( 1 + \gamma_{pdc} (T_{cell} - T_{ref}))

Parameters
----------
g_poa_effective: numeric
Irradiance transmitted to the PV cells in units of W/m**2. To be
fully consistent with PVWatts, the user must have already
applied angle of incidence losses, but not soiling, spectral,
etc.
temp_cell: numeric
Cell temperature in degrees C.
pdc0: numeric
Nameplate DC rating.
gamma_pdc: numeric
The temperature coefficient in units of 1/C. Typically -0.002 to
-0.005 per degree C.
temp_ref: numeric
Cell reference temperature. PVWatts defines it to be 25 C and
is included here for flexibility.

Returns
-------
pdc: numeric
DC power.

References
----------
.. [1] A. P. Dobos, "PVWatts Version 5 Manual"
http://pvwatts.nrel.gov/downloads/pvwattsv5.pdf
(2014).
"""

pdc = (g_poa_effective * 0.001 * pdc0 *
(1 + gamma_pdc * (temp_cell - temp_ref)))

return pdc


def pvwatts_losses(soiling=2, shading=3, snow=0, mismatch=2, wiring=2,
connections=0.5, lid=1.5, nameplate_rating=1, age=0,
availability=3):
r"""
Implements NREL's PVWatts system loss model [1]_:

.. math::

L_{total}(\%) = 100 [ 1 - \Pi_i ( 1 - \frac{L_i}{100} ) ]

All parameters must be in units of %. Parameters may be
array-like, though all array sizes must match.

Parameters
----------
soiling: numeric
shading: numeric
snow: numeric
mismatch: numeric
wiring: numeric
connections: numeric
lid: numeric
Light induced degradation
nameplate_rating: numeric
age: numeric
availability: numeric

Returns
-------
losses: numeric
System losses in units of %.

References
----------
.. [1] A. P. Dobos, "PVWatts Version 5 Manual"
http://pvwatts.nrel.gov/downloads/pvwattsv5.pdf
(2014).
"""

params = [soiling, shading, snow, mismatch, wiring, connections, lid,
nameplate_rating, age, availability]

# manually looping over params allows for numpy/pandas to handle any
# array-like broadcasting that might be necessary.
perf = 1
for param in params:
perf *= 1 - param/100

losses = (1 - perf) * 100.

return losses


def pvwatts_ac(pdc, pdc0, eta_inv_nom=0.96, eta_inv_ref=0.9637):
r"""
Implements NREL's PVWatts inverter model [1]_.

.. math::

\eta = \frac{\eta_{nom}}{\eta_{ref}} (-0.0162\zeta - \frac{0.0059}{\zeta} + 0.9858)

.. math::

P_{ac} = \min(\eta P_{dc}, P_{ac0})

where :math:`\zeta=P_{dc}/P_{dc0}` and :math:`P_{dc0}=P_{ac0}/\eta_{nom}`.

Parameters
----------
pdc: numeric
DC power.
pdc0: numeric
Nameplate DC rating.
eta_inv_nom: numeric
Nominal inverter efficiency.
eta_inv_ref: numeric
Reference inverter efficiency. PVWatts defines it to be 0.9637
and is included here for flexibility.

Returns
-------
pac: numeric
AC power.

References
----------
.. [1] A. P. Dobos, "PVWatts Version 5 Manual,"
http://pvwatts.nrel.gov/downloads/pvwattsv5.pdf
(2014).
"""

pac0 = eta_inv_nom * pdc0
zeta = pdc / pdc0

eta = eta_inv_nom / eta_inv_ref * (-0.0162*zeta - 0.0059/zeta + 0.9858)

pac = eta * pdc
pac = np.minimum(pac0, pac)

return pac
17 changes: 17 additions & 0 deletions pvlib/test/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import sys
import platform
import pandas as pd
import numpy as np


try:
Expand Down Expand Up @@ -63,3 +64,19 @@ def incompatible_pandas_0131(test):
out = test

return out


def needs_numpy_1_10(test):
"""
Test won't work on numpy 1.10.
"""

major = int(np.__version__.split('.')[0])
minor = int(np.__version__.split('.')[1])

if major == 1 and minor < 10:
out = unittest.skip('needs numpy 1.10')(test)
else:
out = test

return out
108 changes: 108 additions & 0 deletions pvlib/test/test_pvsystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

from nose.tools import assert_equals, assert_almost_equals
from pandas.util.testing import assert_series_equal, assert_frame_equal
from numpy.testing import assert_allclose

from pvlib import tmy
from pvlib import pvsystem
Expand All @@ -17,6 +18,8 @@
from pvlib import solarposition
from pvlib.location import Location

from . import needs_numpy_1_10

latitude = 32.2
longitude = -111
tus = Location(latitude, longitude, 'US/Arizona', 700, 'Tucson')
Expand Down Expand Up @@ -524,3 +527,108 @@ def test_LocalizedPVSystem___repr__():
assert localized_system.__repr__()==('LocalizedPVSystem with tilt:0 and'+
' azimuth: 180 with Module: blah and Inverter: blarg at Latitude: 32 ' +
'and Longitude: -111')


def test_pvwatts_dc_scalars():
expected = 88.65
out = pvsystem.pvwatts_dc(900, 30, 100, -0.003)
assert_allclose(expected, out)


@needs_numpy_1_10
def test_pvwatts_dc_arrays():
irrad_trans = np.array([np.nan, 900, 900])
temp_cell = np.array([30, np.nan, 30])
irrad_trans, temp_cell = np.meshgrid(irrad_trans, temp_cell)
expected = np.array([[ nan, 88.65, 88.65],
[ nan, nan, nan],
[ nan, 88.65, 88.65]])
out = pvsystem.pvwatts_dc(irrad_trans, temp_cell, 100, -0.003)
assert_allclose(expected, out, equal_nan=True)


def test_pvwatts_dc_series():
irrad_trans = pd.Series([np.nan, 900, 900])
temp_cell = pd.Series([30, np.nan, 30])
expected = pd.Series(np.array([ nan, nan, 88.65]))
out = pvsystem.pvwatts_dc(irrad_trans, temp_cell, 100, -0.003)
assert_series_equal(expected, out)


def test_pvwatts_ac_scalars():
expected = 85.58556604752516
out = pvsystem.pvwatts_ac(90, 100, 0.95)
assert_allclose(expected, out)


@needs_numpy_1_10
def test_pvwatts_ac_arrays():
pdc = np.array([[np.nan], [50], [100]])
pdc0 = 100
expected = np.array([[ nan],
[ 47.60843624],
[ 95. ]])
out = pvsystem.pvwatts_ac(pdc, pdc0, 0.95)
assert_allclose(expected, out, equal_nan=True)


def test_pvwatts_ac_series():
pdc = pd.Series([np.nan, 50, 100])
pdc0 = 100
expected = pd.Series(np.array([ nan, 47.608436, 95. ]))
out = pvsystem.pvwatts_ac(pdc, pdc0, 0.95)
assert_series_equal(expected, out)


def test_pvwatts_losses_default():
expected = 14.075660688264469
out = pvsystem.pvwatts_losses()
assert_allclose(expected, out)


@needs_numpy_1_10
def test_pvwatts_losses_arrays():
expected = np.array([nan, 14.934904])
age = np.array([nan, 1])
out = pvsystem.pvwatts_losses(age=age)
assert_allclose(expected, out)


def test_pvwatts_losses_series():
expected = pd.Series([nan, 14.934904])
age = pd.Series([nan, 1])
out = pvsystem.pvwatts_losses(age=age)
assert_series_equal(expected, out)


def make_pvwatts_system():
module_parameters = {'pdc0': 100, 'gamma_pdc': -0.003}
inverter_parameters = {'eta_inv_nom': 0.95}
system = pvsystem.PVSystem(module_parameters=module_parameters,
inverter_parameters=inverter_parameters)
return system


def test_PVSystem_pvwatts_dc():
system = make_pvwatts_system()
irrad_trans = pd.Series([np.nan, 900, 900])
temp_cell = pd.Series([30, np.nan, 30])
expected = pd.Series(np.array([ nan, nan, 88.65]))
out = system.pvwatts_dc(irrad_trans, temp_cell)
assert_series_equal(expected, out)


def test_PVSystem_pvwatts_losses():
system = make_pvwatts_system()
expected = pd.Series([nan, 14.934904])
age = pd.Series([nan, 1])
out = system.pvwatts_losses(age=age)
assert_series_equal(expected, out)


def test_PVSystem_pvwatts_ac():
system = make_pvwatts_system()
pdc = pd.Series([np.nan, 50, 100])
expected = pd.Series(np.array([ nan, 47.608436, 95. ]))
out = system.pvwatts_ac(pdc)
assert_series_equal(expected, out)