diff --git a/docs/sphinx/source/whatsnew/v0.4.0.txt b/docs/sphinx/source/whatsnew/v0.4.0.txt index f092a2c178..4a6fe46ad6 100644 --- a/docs/sphinx/source/whatsnew/v0.4.0.txt +++ b/docs/sphinx/source/whatsnew/v0.4.0.txt @@ -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 @@ -28,6 +29,7 @@ Bug fixes Documentation ~~~~~~~~~~~~~ +* Added new terms to the variables documentation. (:issue:`195`) Other diff --git a/pvlib/data/variables_style_rules.csv b/pvlib/data/variables_style_rules.csv index 9db1130a7e..236d5f6ccc 100644 --- a/pvlib/data/variables_style_rules.csv +++ b/pvlib/data/variables_style_rules.csv @@ -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 @@ -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 diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index 58c13d1190..271be5f68c 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -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 @@ -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 diff --git a/pvlib/test/__init__.py b/pvlib/test/__init__.py index 9995464402..1e75f41bcb 100644 --- a/pvlib/test/__init__.py +++ b/pvlib/test/__init__.py @@ -4,6 +4,7 @@ import sys import platform import pandas as pd +import numpy as np try: @@ -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 diff --git a/pvlib/test/test_pvsystem.py b/pvlib/test/test_pvsystem.py index 6ae13a0cb6..a5044316ef 100644 --- a/pvlib/test/test_pvsystem.py +++ b/pvlib/test/test_pvsystem.py @@ -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 @@ -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') @@ -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)