From 8ead0c9a7272af94d0fc766fd3b403efe7ba53e9 Mon Sep 17 00:00:00 2001 From: Will Holmgren Date: Sun, 13 May 2018 20:43:51 -0700 Subject: [PATCH 01/14] add perez_enhancement option --- pvlib/clearsky.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/pvlib/clearsky.py b/pvlib/clearsky.py index 75f598baeb..4343465d43 100644 --- a/pvlib/clearsky.py +++ b/pvlib/clearsky.py @@ -16,7 +16,7 @@ def ineichen(apparent_zenith, airmass_absolute, linke_turbidity, - altitude=0, dni_extra=1364.): + altitude=0, dni_extra=1364., perez_enhancement=False): ''' Determine clear sky GHI, DNI, and DHI from Ineichen/Perez model. @@ -47,6 +47,12 @@ def ineichen(apparent_zenith, airmass_absolute, linke_turbidity, Extraterrestrial irradiance. The units of ``dni_extra`` determine the units of the output. + perez_enhancement : bool, default False + Controls if the Perez enhancement factor should be applied. + Setting to True may produce spurious results for times when + the Sun is near the horizon and the airmass is high. + See https://github.com/pvlib/pvlib-python/issues/435 + Returns ------- clearsky : DataFrame (if Series input) or OrderedDict of arrays @@ -119,8 +125,12 @@ def ineichen(apparent_zenith, airmass_absolute, linke_turbidity, cg1 = 5.09e-05 * altitude + 0.868 cg2 = 3.92e-05 * altitude + 0.0387 - ghi = (np.exp(-cg2*airmass_absolute*(fh1 + fh2*(tl - 1))) * - np.exp(0.01*airmass_absolute**1.8)) + ghi = np.exp(-cg2*airmass_absolute*(fh1 + fh2*(tl - 1))) + + # https://github.com/pvlib/pvlib-python/issues/435 + if perez_enhancement: + ghi *= np.exp(0.01*airmass_absolute**1.8) + # use fmax to map airmass nans to 0s. multiply and divide by tl to # reinsert tl nans ghi = cg1 * dni_extra * cos_zenith * tl / tl * np.fmax(ghi, 0) From de31833b417d8b1cf077d649d71b5ca506f0dcc7 Mon Sep 17 00:00:00 2001 From: Will Holmgren Date: Sun, 13 May 2018 20:44:13 -0700 Subject: [PATCH 02/14] pass kwargs through location.get_clearsky to ineichen --- pvlib/location.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pvlib/location.py b/pvlib/location.py index e64ce52d53..bd47c27ef9 100644 --- a/pvlib/location.py +++ b/pvlib/location.py @@ -218,7 +218,7 @@ def get_clearsky(self, times, model='ineichen', solar_position=None, cs = clearsky.ineichen(apparent_zenith, airmass_absolute, linke_turbidity, altitude=self.altitude, - dni_extra=dni_extra) + dni_extra=dni_extra, **kwargs) elif model == 'haurwitz': cs = clearsky.haurwitz(apparent_zenith) elif model == 'simplified_solis': From 313aaf9ee078adbe2c256b6208085a3a38caa21a Mon Sep 17 00:00:00 2001 From: Will Holmgren Date: Sun, 24 Jun 2018 17:34:29 -0700 Subject: [PATCH 03/14] hard code test values to avoid test failures --- pvlib/test/test_irradiance.py | 43 +++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/pvlib/test/test_irradiance.py b/pvlib/test/test_irradiance.py index ef9599e7b2..2a9a6aeee7 100644 --- a/pvlib/test/test_irradiance.py +++ b/pvlib/test/test_irradiance.py @@ -24,10 +24,25 @@ # must include night values times = pd.date_range(start='20140624', freq='6H', periods=4, tz=tus.tz) -ephem_data = solarposition.get_solarposition( - times, tus.latitude, tus.longitude, method='nrel_numpy') - -irrad_data = tus.get_clearsky(times, model='ineichen', linke_turbidity=3) +irrad_data = pd.DataFrame(np.array( + [[ 0. , 0. , 0. ], + [ 79.73860422, 316.1949056 , 40.46149818], + [1042.48031487, 939.95469881, 118.45831879], + [ 257.20751138, 646.22886049, 62.03376265]]), + columns=['ghi', 'dni', 'dhi'], index=times) + +ephem_data = pd.DataFrame(np.array( + [[124.0390863 , 124.0390863 , -34.0390863 , -34.0390863 , + 352.69550699, -2.36677158], + [ 82.85457044, 82.97705621, 7.14542956, 7.02294379, + 66.71410338, -2.42072165], + [ 10.56413562, 10.56725766, 79.43586438, 79.43274234, + 144.76567754, -2.47457321], + [ 72.41687122, 72.46903556, 17.58312878, 17.53096444, + 287.04104128, -2.52831909]]), + columns=['apparent_zenith', 'zenith', 'apparent_elevation', 'elevation', + 'azimuth', 'equation_of_time'], + index=times) dni_et = irradiance.extraradiation(times.dayofyear) @@ -64,8 +79,10 @@ def test_extraradiation(input, expected, method): @requires_numba def test_extraradiation_nrel_numba(): - result = irradiance.extraradiation(times, method='nrel', how='numba', numthreads=8) - assert_allclose(result, [1322.332316, 1322.296282, 1322.261205, 1322.227091]) + result = irradiance.extraradiation(times, method='nrel', how='numba', + numthreads=4) + assert_allclose(result, + [1322.332316, 1322.296282, 1322.261205, 1322.227091]) def test_extraradiation_epoch_year(): @@ -265,9 +282,7 @@ def test_globalinplane(): def test_disc_keys(): - clearsky_data = tus.get_clearsky(times, model='ineichen', - linke_turbidity=3) - disc_data = irradiance.disc(clearsky_data['ghi'], ephem_data['zenith'], + disc_data = irradiance.disc(irrad_data['ghi'], ephem_data['zenith'], ephem_data.index) assert 'dni' in disc_data.columns assert 'kt' in disc_data.columns @@ -284,14 +299,6 @@ def test_disc_value(): np.array([830.46, 676.09]), 1) -def test_dirint(): - clearsky_data = tus.get_clearsky(times, model='ineichen', - linke_turbidity=3) - pressure = 93193. - dirint_data = irradiance.dirint(clearsky_data['ghi'], ephem_data['zenith'], - ephem_data.index, pressure=pressure) - - def test_dirint_value(): times = pd.DatetimeIndex(['2014-06-24T12-0700','2014-06-24T18-0700']) ghi = pd.Series([1038.62, 254.53], index=times) @@ -377,8 +384,6 @@ def test_erbs_all_scalar(): @needs_numpy_1_10 def test_dirindex(): - clearsky_data = tus.get_clearsky(times, model='ineichen', - linke_turbidity=3) ghi = pd.Series([0, 0, 1038.62, 254.53], index=times) ghi_clearsky = pd.Series( np.array([0., 79.73860422, 1042.48031487, 257.20751138]), From 517efcc82495dae3c54d220de542fd4c34451933 Mon Sep 17 00:00:00 2001 From: Will Holmgren Date: Mon, 25 Jun 2018 07:47:47 -0700 Subject: [PATCH 04/14] refactor test_irradiance.py input data into fixtures --- pvlib/test/test_irradiance.py | 117 +++++++++++++++++++--------------- 1 file changed, 67 insertions(+), 50 deletions(-) diff --git a/pvlib/test/test_irradiance.py b/pvlib/test/test_irradiance.py index 2a9a6aeee7..18b31c9ff1 100644 --- a/pvlib/test/test_irradiance.py +++ b/pvlib/test/test_irradiance.py @@ -18,35 +18,49 @@ from conftest import (requires_ephem, requires_numba, needs_numpy_1_10, pandas_0_22) -# setup times and location to be tested. -tus = Location(32.2, -111, 'US/Arizona', 700) -# must include night values -times = pd.date_range(start='20140624', freq='6H', periods=4, tz=tus.tz) - -irrad_data = pd.DataFrame(np.array( - [[ 0. , 0. , 0. ], - [ 79.73860422, 316.1949056 , 40.46149818], - [1042.48031487, 939.95469881, 118.45831879], - [ 257.20751138, 646.22886049, 62.03376265]]), - columns=['ghi', 'dni', 'dhi'], index=times) +# fixtures create realistic test input data +# test input data generated at Location(32.2, -111, 'US/Arizona', 700) +# test input data is hard coded to avoid dependencies on other parts of pvlib + + +@pytest.fixture +def times(): + # must include night values + return pd.date_range(start='20140624', freq='6H', periods=4, + tz='US/Arizona') + + +@pytest.fixture +def irrad_data(times): + return pd.DataFrame(np.array( + [[ 0. , 0. , 0. ], + [ 79.73860422, 316.1949056 , 40.46149818], + [1042.48031487, 939.95469881, 118.45831879], + [ 257.20751138, 646.22886049, 62.03376265]]), + columns=['ghi', 'dni', 'dhi'], index=times) + + +@pytest.fixture +def ephem_data(times): + return pd.DataFrame(np.array( + [[124.0390863 , 124.0390863 , -34.0390863 , -34.0390863 , + 352.69550699, -2.36677158], + [ 82.85457044, 82.97705621, 7.14542956, 7.02294379, + 66.71410338, -2.42072165], + [ 10.56413562, 10.56725766, 79.43586438, 79.43274234, + 144.76567754, -2.47457321], + [ 72.41687122, 72.46903556, 17.58312878, 17.53096444, + 287.04104128, -2.52831909]]), + columns=['apparent_zenith', 'zenith', 'apparent_elevation', + 'elevation', 'azimuth', 'equation_of_time'], + index=times) -ephem_data = pd.DataFrame(np.array( - [[124.0390863 , 124.0390863 , -34.0390863 , -34.0390863 , - 352.69550699, -2.36677158], - [ 82.85457044, 82.97705621, 7.14542956, 7.02294379, - 66.71410338, -2.42072165], - [ 10.56413562, 10.56725766, 79.43586438, 79.43274234, - 144.76567754, -2.47457321], - [ 72.41687122, 72.46903556, 17.58312878, 17.53096444, - 287.04104128, -2.52831909]]), - columns=['apparent_zenith', 'zenith', 'apparent_elevation', 'elevation', - 'azimuth', 'equation_of_time'], - index=times) -dni_et = irradiance.extraradiation(times.dayofyear) +@pytest.fixture +def dni_et(times): + return irradiance.extraradiation(times.dayofyear) -ghi = irrad_data['ghi'] # setup for et rad test. put it here for readability @@ -78,7 +92,7 @@ def test_extraradiation(input, expected, method): @requires_numba -def test_extraradiation_nrel_numba(): +def test_extraradiation_nrel_numba(times): result = irradiance.extraradiation(times, method='nrel', how='numba', numthreads=4) assert_allclose(result, @@ -100,23 +114,24 @@ def test_grounddiffuse_simple_float(): assert_allclose(result, 26.32000014911496) -def test_grounddiffuse_simple_series(): - ground_irrad = irradiance.grounddiffuse(40, ghi) +def test_grounddiffuse_simple_series(irrad_data): + ground_irrad = irradiance.grounddiffuse(40, irrad_data['ghi']) assert ground_irrad.name == 'diffuse_ground' -def test_grounddiffuse_albedo_0(): - ground_irrad = irradiance.grounddiffuse(40, ghi, albedo=0) +def test_grounddiffuse_albedo_0(irrad_data): + ground_irrad = irradiance.grounddiffuse(40, irrad_data['ghi'], albedo=0) assert 0 == ground_irrad.all() -def test_grounddiffuse_albedo_invalid_surface(): +def test_grounddiffuse_albedo_invalid_surface(irrad_data): with pytest.raises(KeyError): - irradiance.grounddiffuse(40, ghi, surface_type='invalid') + irradiance.grounddiffuse(40, irrad_data['ghi'], surface_type='invalid') -def test_grounddiffuse_albedo_surface(): - result = irradiance.grounddiffuse(40, ghi, surface_type='sand') +def test_grounddiffuse_albedo_surface(irrad_data): + result = irradiance.grounddiffuse(40, irrad_data['ghi'], + surface_type='sand') assert_allclose(result, [0, 3.731058, 48.778813, 12.035025], atol=1e-4) @@ -125,7 +140,7 @@ def test_isotropic_float(): assert_allclose(result, 88.30222215594891) -def test_isotropic_series(): +def test_isotropic_series(irrad_data): result = irradiance.isotropic(40, irrad_data['dhi']) assert_allclose(result, [0, 35.728402, 104.601328, 54.777191], atol=1e-4) @@ -135,14 +150,14 @@ def test_klucher_series_float(): assert_allclose(result, 88.3022221559) -def test_klucher_series(): +def test_klucher_series(irrad_data, ephem_data): result = irradiance.klucher(40, 180, irrad_data['dhi'], irrad_data['ghi'], ephem_data['apparent_zenith'], ephem_data['azimuth']) assert_allclose(result, [0, 37.446276, 109.209347, 56.965916], atol=1e-4) -def test_haydavies(): +def test_haydavies(irrad_data, ephem_data, dni_et): result = irradiance.haydavies(40, 180, irrad_data['dhi'], irrad_data['dni'], dni_et, ephem_data['apparent_zenith'], @@ -150,7 +165,7 @@ def test_haydavies(): assert_allclose(result, [0, 14.967008, 102.994862, 33.190865], atol=1e-4) -def test_reindl(): +def test_reindl(irrad_data, ephem_data, dni_et): result = irradiance.reindl(40, 180, irrad_data['dhi'], irrad_data['dni'], irrad_data['ghi'], dni_et, ephem_data['apparent_zenith'], @@ -158,13 +173,13 @@ def test_reindl(): assert_allclose(result, [np.nan, 15.730664, 104.131724, 34.166258], atol=1e-4) -def test_king(): +def test_king(irrad_data, ephem_data): result = irradiance.king(40, irrad_data['dhi'], irrad_data['ghi'], ephem_data['apparent_zenith']) assert_allclose(result, [0, 44.629352, 115.182626, 79.719855], atol=1e-4) -def test_perez(): +def test_perez(irrad_data, ephem_data, dni_et): am = atmosphere.relativeairmass(ephem_data['apparent_zenith']) dni = irrad_data['dni'].copy() dni.iloc[2] = np.nan @@ -173,11 +188,11 @@ def test_perez(): ephem_data['azimuth'], am) expected = pd.Series(np.array( [ 0. , 31.46046871, np.nan, 45.45539877]), - index=times) + index=irrad_data.index) assert_series_equal(out, expected, check_less_precise=2) -def test_perez_components(): +def test_perez_components(irrad_data, ephem_data, dni_et): am = atmosphere.relativeairmass(ephem_data['apparent_zenith']) dni = irrad_data['dni'].copy() dni.iloc[2] = np.nan @@ -186,13 +201,13 @@ def test_perez_components(): ephem_data['azimuth'], am, return_components=True) expected = pd.Series(np.array( [ 0. , 31.46046871, np.nan, 45.45539877]), - index=times) + index=irrad_data.index) expected_components = pd.DataFrame( np.array([[ 0. , 26.84138589, np.nan, 31.72696071], [ 0. , 0. , np.nan, 4.47966439], [ 0. , 4.62212181, np.nan, 9.25316454]]).T, columns=['isotropic', 'circumsolar', 'horizon'], - index=times + index=irrad_data.index ) if pandas_0_22(): expected_for_sum = expected.copy() @@ -205,8 +220,9 @@ def test_perez_components(): assert_frame_equal(df_components, expected_components) assert_series_equal(sum_components, expected_for_sum, check_less_precise=2) + @needs_numpy_1_10 -def test_perez_arrays(): +def test_perez_arrays(irrad_data, ephem_data, dni_et): am = atmosphere.relativeairmass(ephem_data['apparent_zenith']) dni = irrad_data['dni'].copy() dni.iloc[2] = np.nan @@ -229,7 +245,7 @@ def test_liujordan(): # klutcher (misspelling) will be removed in 0.3 -def test_total_irrad(): +def test_total_irrad(irrad_data, ephem_data, dni_et): models = ['isotropic', 'klutcher', 'klucher', 'haydavies', 'reindl', 'king', 'perez'] AM = atmosphere.relativeairmass(ephem_data['apparent_zenith']) @@ -268,11 +284,12 @@ def test_total_irrad_scalars(model): assert np.isnan(np.array(list(total.values()))).sum() == 0 -def test_globalinplane(): +def test_globalinplane(irrad_data, ephem_data, dni_et): aoi = irradiance.aoi(40, 180, ephem_data['apparent_zenith'], ephem_data['azimuth']) airmass = atmosphere.relativeairmass(ephem_data['apparent_zenith']) - gr_sand = irradiance.grounddiffuse(40, ghi, surface_type='sand') + gr_sand = irradiance.grounddiffuse(40, irrad_data['ghi'], + surface_type='sand') diff_perez = irradiance.perez( 40, 180, irrad_data['dhi'], irrad_data['dni'], dni_et, ephem_data['apparent_zenith'], ephem_data['azimuth'], airmass) @@ -281,7 +298,7 @@ def test_globalinplane(): poa_ground_diffuse=gr_sand) -def test_disc_keys(): +def test_disc_keys(irrad_data, ephem_data): disc_data = irradiance.disc(irrad_data['ghi'], ephem_data['zenith'], ephem_data.index) assert 'dni' in disc_data.columns @@ -383,7 +400,7 @@ def test_erbs_all_scalar(): @needs_numpy_1_10 -def test_dirindex(): +def test_dirindex(times): ghi = pd.Series([0, 0, 1038.62, 254.53], index=times) ghi_clearsky = pd.Series( np.array([0., 79.73860422, 1042.48031487, 257.20751138]), From 2147813785f1b9ce4f062e19003b02cea4d45f9e Mon Sep 17 00:00:00 2001 From: Will Holmgren Date: Mon, 25 Jun 2018 08:34:48 -0700 Subject: [PATCH 05/14] refactor location tests to avoid tight cs dependence --- pvlib/test/test_irradiance.py | 38 +++++++++--------- pvlib/test/test_location.py | 72 ++++++++++++++++++----------------- 2 files changed, 54 insertions(+), 56 deletions(-) diff --git a/pvlib/test/test_irradiance.py b/pvlib/test/test_irradiance.py index 18b31c9ff1..875be4bdf0 100644 --- a/pvlib/test/test_irradiance.py +++ b/pvlib/test/test_irradiance.py @@ -9,14 +9,10 @@ from pandas.util.testing import assert_frame_equal, assert_series_equal -from pvlib.location import Location -from pvlib import clearsky -from pvlib import solarposition from pvlib import irradiance -from pvlib import atmosphere -from conftest import (requires_ephem, requires_numba, needs_numpy_1_10, - pandas_0_22) +from conftest import (needs_numpy_1_10, pandas_0_22, requires_ephem, + requires_numba) # fixtures create realistic test input data @@ -62,6 +58,10 @@ def dni_et(times): return irradiance.extraradiation(times.dayofyear) +@pytest.fixture +def relative_airmass(times): + return pd.Series([np.nan, 7.58831596, 1.01688136, 3.27930443], times) + # setup for et rad test. put it here for readability timestamp = pd.Timestamp('20161026') @@ -179,26 +179,25 @@ def test_king(irrad_data, ephem_data): assert_allclose(result, [0, 44.629352, 115.182626, 79.719855], atol=1e-4) -def test_perez(irrad_data, ephem_data, dni_et): - am = atmosphere.relativeairmass(ephem_data['apparent_zenith']) +def test_perez(irrad_data, ephem_data, dni_et, relative_airmass): dni = irrad_data['dni'].copy() dni.iloc[2] = np.nan out = irradiance.perez(40, 180, irrad_data['dhi'], dni, dni_et, ephem_data['apparent_zenith'], - ephem_data['azimuth'], am) + ephem_data['azimuth'], relative_airmass) expected = pd.Series(np.array( [ 0. , 31.46046871, np.nan, 45.45539877]), index=irrad_data.index) assert_series_equal(out, expected, check_less_precise=2) -def test_perez_components(irrad_data, ephem_data, dni_et): - am = atmosphere.relativeairmass(ephem_data['apparent_zenith']) +def test_perez_components(irrad_data, ephem_data, dni_et, relative_airmass): dni = irrad_data['dni'].copy() dni.iloc[2] = np.nan out, df_components = irradiance.perez(40, 180, irrad_data['dhi'], dni, dni_et, ephem_data['apparent_zenith'], - ephem_data['azimuth'], am, return_components=True) + ephem_data['azimuth'], relative_airmass, + return_components=True) expected = pd.Series(np.array( [ 0. , 31.46046871, np.nan, 45.45539877]), index=irrad_data.index) @@ -222,13 +221,12 @@ def test_perez_components(irrad_data, ephem_data, dni_et): @needs_numpy_1_10 -def test_perez_arrays(irrad_data, ephem_data, dni_et): - am = atmosphere.relativeairmass(ephem_data['apparent_zenith']) +def test_perez_arrays(irrad_data, ephem_data, dni_et, relative_airmass): dni = irrad_data['dni'].copy() dni.iloc[2] = np.nan out = irradiance.perez(40, 180, irrad_data['dhi'].values, dni.values, dni_et, ephem_data['apparent_zenith'].values, - ephem_data['azimuth'].values, am.values) + ephem_data['azimuth'].values, relative_airmass.values) expected = np.array( [ 0. , 31.46046871, np.nan, 45.45539877]) assert_allclose(out, expected, atol=1e-2) @@ -245,10 +243,9 @@ def test_liujordan(): # klutcher (misspelling) will be removed in 0.3 -def test_total_irrad(irrad_data, ephem_data, dni_et): +def test_total_irrad(irrad_data, ephem_data, dni_et, relative_airmass): models = ['isotropic', 'klutcher', 'klucher', 'haydavies', 'reindl', 'king', 'perez'] - AM = atmosphere.relativeairmass(ephem_data['apparent_zenith']) for model in models: total = irradiance.total_irrad( @@ -256,7 +253,7 @@ def test_total_irrad(irrad_data, ephem_data, dni_et): ephem_data['apparent_zenith'], ephem_data['azimuth'], dni=irrad_data['dni'], ghi=irrad_data['ghi'], dhi=irrad_data['dhi'], - dni_extra=dni_et, airmass=AM, + dni_extra=dni_et, airmass=relative_airmass, model=model, surface_type='urban') @@ -284,15 +281,14 @@ def test_total_irrad_scalars(model): assert np.isnan(np.array(list(total.values()))).sum() == 0 -def test_globalinplane(irrad_data, ephem_data, dni_et): +def test_globalinplane(irrad_data, ephem_data, dni_et, relative_airmass): aoi = irradiance.aoi(40, 180, ephem_data['apparent_zenith'], ephem_data['azimuth']) - airmass = atmosphere.relativeairmass(ephem_data['apparent_zenith']) gr_sand = irradiance.grounddiffuse(40, irrad_data['ghi'], surface_type='sand') diff_perez = irradiance.perez( 40, 180, irrad_data['dhi'], irrad_data['dni'], dni_et, - ephem_data['apparent_zenith'], ephem_data['azimuth'], airmass) + ephem_data['apparent_zenith'], ephem_data['azimuth'], relative_airmass) irradiance.globalinplane( aoi=aoi, dni=irrad_data['dni'], poa_sky_diffuse=diff_perez, poa_ground_diffuse=gr_sand) diff --git a/pvlib/test/test_location.py b/pvlib/test/test_location.py index bc18583b83..b38bb962aa 100644 --- a/pvlib/test/test_location.py +++ b/pvlib/test/test_location.py @@ -1,20 +1,23 @@ import datetime +from unittest.mock import ANY import numpy as np from numpy import nan import pandas as pd -import pytz +from pandas.util.testing import assert_frame_equal, assert_index_equal import pytest + +import pytz from pytz.exceptions import UnknownTimeZoneError -from pandas.util.testing import assert_series_equal, assert_frame_equal +import pvlib from pvlib.location import Location from test_solarposition import expected_solpos + from conftest import requires_scipy -aztz = pytz.timezone('US/Arizona') def test_location_required(): Location(32.2, -111) @@ -24,7 +27,7 @@ def test_location_all(): @pytest.mark.parametrize('tz', [ - aztz, 'America/Phoenix', -7, -7.0, + pytz.timezone('US/Arizona'), 'America/Phoenix', -7, -7.0, ]) def test_location_tz(tz): Location(32.2, -111, tz) @@ -49,11 +52,12 @@ def test_location_print_all(): ' longitude: -111', ' altitude: 700', ' tz: US/Arizona' -]) + ]) assert tus.__str__() == expected_str + def test_location_print_pytz(): - tus = Location(32.2, -111, aztz, 700, 'Tucson') + tus = Location(32.2, -111, pytz.timezone('US/Arizona'), 700, 'Tucson') expected_str = '\n'.join([ 'Location: ', ' name: Tucson', @@ -66,40 +70,38 @@ def test_location_print_pytz(): @requires_scipy -def test_get_clearsky(): +def test_get_clearsky(mocker): tus = Location(32.2, -111, 'US/Arizona', 700, 'Tucson') times = pd.DatetimeIndex(start='20160101T0600-0700', end='20160101T1800-0700', freq='3H') - clearsky = tus.get_clearsky(times) - expected = pd.DataFrame(data=np.array([ - ( 0.0, 0.0, 0.0), - (262.77734276159333, 791.1972825869296, 46.18714900637892), - (616.764693938387, 974.9610353623959, 65.44157429054201), - (419.6512657626518, 901.6234995035793, 54.26016437839348), - ( 0.0, 0.0, 0.0)], - dtype=[('ghi', ' 0 during the day + assert (out.iloc[1:-1, :] > 0).all().all() + assert (out.columns.values == ['ghi', 'dni', 'dhi']).all() + + +def test_get_clearsky_ineichen_supply_linke(mocker): tus = Location(32.2, -111, 'US/Arizona', 700) - times = pd.date_range(start='2014-06-24', end='2014-06-25', freq='3h') - times_localized = times.tz_localize(tus.tz) - expected = pd.DataFrame(np. - array([[ 0. , 0. , 0. ], - [ 0. , 0. , 0. ], - [ 79.73090244, 316.16436502, 40.45759009], - [ 703.43653498, 876.41452667, 95.15798252], - [ 1042.37962396, 939.86391062, 118.44687715], - [ 851.32411813, 909.11186737, 105.36662462], - [ 257.18266827, 646.16644264, 62.02777094], - [ 0. , 0. , 0. ], - [ 0. , 0. , 0. ]]), - columns=['ghi', 'dni', 'dhi'], - index=times_localized) - out = tus.get_clearsky(times_localized, linke_turbidity=3) - assert_frame_equal(expected, out, check_less_precise=2) + times = pd.date_range(start='2014-06-24-0700', end='2014-06-25-0700', + freq='3h') + mocker.spy(pvlib.clearsky, 'ineichen') + out = tus.get_clearsky(times, linke_turbidity=3) + # we only care that the LT is passed in this test + pvlib.clearsky.ineichen.assert_called_once_with(ANY, ANY, 3, ANY, ANY) + assert_index_equal(out.index, times) + # check that values are 0 before sunrise and after sunset + assert out.iloc[0:2, :].sum().sum() == 0 + assert out.iloc[-2:, :].sum().sum() == 0 + # check that values are > 0 during the day + assert (out.iloc[2:-2, :] > 0).all().all() + assert (out.columns.values == ['ghi', 'dni', 'dhi']).all() def test_get_clearsky_haurwitz(): From 7d64c5d0dcfd13679520b1efb347aefb154f0f28 Mon Sep 17 00:00:00 2001 From: Will Holmgren Date: Mon, 25 Jun 2018 21:00:21 -0700 Subject: [PATCH 06/14] mock/refactor all tests except losses --- pvlib/test/test_modelchain.py | 196 +++++++++++++++++++--------------- 1 file changed, 107 insertions(+), 89 deletions(-) diff --git a/pvlib/test/test_modelchain.py b/pvlib/test/test_modelchain.py index de8ba049c9..361e3c0113 100644 --- a/pvlib/test/test_modelchain.py +++ b/pvlib/test/test_modelchain.py @@ -1,3 +1,6 @@ +import sys +from unittest.mock import ANY + import numpy as np import pandas as pd from numpy import nan @@ -78,11 +81,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) @@ -109,7 +120,7 @@ def test_run_model(system, location): expected = pd.Series(np.array([ 183.522449305, -2.00000000e-02]), index=times) - assert_series_equal(ac, expected, check_less_precise=2) + assert_series_equal(ac, expected, check_less_precise=1) def test_run_model_with_irradiance(system, location): @@ -149,132 +160,139 @@ def test_run_model_gueymard_perez(system, location): assert_series_equal(ac, expected) -@requires_scipy -def test_run_model_with_weather(system, location): +def test_run_model_with_weather(system, location, weather, mocker): mc = ModelChain(system, location) - times = pd.date_range('20160101 1200-0700', periods=2, freq='6H') - weather = pd.DataFrame({'wind_speed':5, 'temp_air':10}, index=times) - ac = mc.run_model(times, weather=weather).ac - - expected = pd.Series(np.array([ 201.691634921, -2.00000000e-02]), - index=times) - assert_series_equal(ac, expected, check_less_precise=2) + m = mocker.spy(system, 'sapm_celltemp') + weather2 = pd.DataFrame({'wind_speed':5, 'temp_air':10}, + index=weather.index) + weather = weather.join(weather2) + mc.run_model(weather.index, weather=weather) + assert m.call_count == 1 + # assert_called_once_with cannot be used with sereis, so need to use + # assert_series_equal on call_args + assert_series_equal(m.call_args[0][1], weather2['wind_speed']) # wind + assert_series_equal(m.call_args[0][2], weather2['temp_air']) # temp + assert not mc.ac.empty -@requires_scipy -def test_run_model_tracker(system, location): +def test_run_model_tracker(system, location, weather, mocker): system = SingleAxisTracker(module_parameters=system.module_parameters, inverter_parameters=system.inverter_parameters) + mocker.spy(system, 'singleaxis') mc = ModelChain(system, location) - times = pd.date_range('20160101 1200-0700', periods=2, freq='6H') - ac = mc.run_model(times).ac - - expected = pd.Series(np.array([119.067713606, nan]), - index=times) - assert_series_equal(ac, expected, check_less_precise=2) - - expect = pd.DataFrame(np. - array([[ 54.82513187, 90. , 11.0039221 , 11.0039221 ], - [ nan, 0. , 0. , nan]]), - columns=['aoi', 'surface_azimuth', 'surface_tilt', 'tracker_theta'], - index=times) - expect = expect[['tracker_theta', 'aoi', 'surface_azimuth', 'surface_tilt']] - assert_frame_equal(mc.tracking, expect, check_less_precise=2) + mc.run_model(weather.index, weather=weather) + assert system.singleaxis.call_count == 1 + assert (mc.tracking.columns == ['tracker_theta', 'aoi', 'surface_azimuth', + 'surface_tilt']).all() + assert mc.ac[0] > 0 + assert np.isnan(mc.ac[1]) def poadc(mc): mc.dc = mc.total_irrad['poa_global'] * 0.2 mc.dc.name = None # assert_series_equal will fail without this -@requires_scipy -@pytest.mark.parametrize('dc_model, expected', [ - ('sapm', [181.604438144, -2.00000000e-02]), - ('singlediode', [181.044109596, -2.00000000e-02]), - ('pvwatts', [190.028186986, 0]), - (poadc, [189.183065667, 0]) # user supplied function -]) -def test_dc_models(system, cec_dc_snl_ac_system, pvwatts_dc_pvwatts_ac_system, - location, dc_model, expected): - - dc_systems = {'sapm': system, 'singlediode': cec_dc_snl_ac_system, - 'pvwatts': pvwatts_dc_pvwatts_ac_system, - poadc: pvwatts_dc_pvwatts_ac_system} - - system = dc_systems[dc_model] - - mc = ModelChain(system, location, dc_model=dc_model, - aoi_model='no_loss', spectral_model='no_loss') - times = pd.date_range('20160101 1200-0700', periods=2, freq='6H') - ac = mc.run_model(times).ac - expected = pd.Series(np.array(expected), index=times) - assert_series_equal(ac, expected, check_less_precise=2) - - -@requires_scipy @pytest.mark.parametrize('dc_model', ['sapm', 'singlediode', 'pvwatts_dc']) def test_infer_dc_model(system, cec_dc_snl_ac_system, pvwatts_dc_pvwatts_ac_system, location, dc_model, - mocker): + weather, mocker): dc_systems = {'sapm': system, 'singlediode': cec_dc_snl_ac_system, 'pvwatts_dc': pvwatts_dc_pvwatts_ac_system} system = dc_systems[dc_model] m = mocker.spy(system, dc_model) mc = ModelChain(system, location, aoi_model='no_loss', spectral_model='no_loss') - times = pd.date_range('20160101 1200-0700', periods=2, freq='6H') - mc.run_model(times) + mc.run_model(weather.index, weather=weather) assert m.call_count == 1 assert isinstance(mc.dc, (pd.Series, pd.DataFrame)) +def test_dc_model_user_func(pvwatts_dc_pvwatts_ac_system, location, weather, + mocker): + m = mocker.spy(sys.modules[__name__], 'poadc') + mc = ModelChain(pvwatts_dc_pvwatts_ac_system, location, dc_model=poadc, + aoi_model='no_loss', spectral_model='no_loss') + mc.run_model(weather.index, weather=weather) + assert m.call_count == 1 + assert isinstance(mc.ac, (pd.Series, pd.DataFrame)) + assert not mc.ac.empty + + def acdc(mc): mc.ac = mc.dc -@requires_scipy -@pytest.mark.parametrize('ac_model, expected', [ - ('snlinverter', [181.604438144, -2.00000000e-02]), - ('adrinverter', [np.nan, -25.00000000e-02]), - ('pvwatts', [190.028186986, 0]), - (acdc, [199.845296258, 0]) # user supplied function -]) -def test_ac_models(system, cec_dc_adr_ac_system, pvwatts_dc_pvwatts_ac_system, - location, ac_model, expected): +@pytest.mark.parametrize('ac_model', ['snlinverter', 'adrinverter', + 'pvwatts']) +def test_ac_models(system, cec_dc_adr_ac_system, pvwatts_dc_pvwatts_ac_system, + location, ac_model, weather, mocker): ac_systems = {'snlinverter': system, 'adrinverter': cec_dc_adr_ac_system, - 'pvwatts': pvwatts_dc_pvwatts_ac_system, - acdc: pvwatts_dc_pvwatts_ac_system} - + 'pvwatts': pvwatts_dc_pvwatts_ac_system} system = ac_systems[ac_model] mc = ModelChain(system, location, ac_model=ac_model, aoi_model='no_loss', spectral_model='no_loss') - times = pd.date_range('20160101 1200-0700', periods=2, freq='6H') - ac = mc.run_model(times).ac + if ac_model == 'pvwatts': + ac_model += '_ac' + m = mocker.spy(system, ac_model) + mc.run_model(weather.index, weather=weather) + assert m.call_count == 1 + assert isinstance(mc.ac, pd.Series) + assert not mc.ac.empty + assert mc.ac[1] < 1 - expected = pd.Series(np.array(expected), index=times) - assert_series_equal(ac, expected, check_less_precise=2) + +def test_ac_model_user_func(pvwatts_dc_pvwatts_ac_system, location, weather, + mocker): + m = mocker.spy(sys.modules[__name__], 'acdc') + mc = ModelChain(pvwatts_dc_pvwatts_ac_system, location, ac_model=acdc, + aoi_model='no_loss', spectral_model='no_loss') + mc.run_model(weather.index, weather=weather) + assert m.call_count == 1 + assert_series_equal(mc.ac, mc.dc) + assert not mc.ac.empty def constant_aoi_loss(mc): mc.aoi_modifier = 0.9 -@requires_scipy -@pytest.mark.parametrize('aoi_model, expected', [ - ('sapm', [182.784057666, -2.00000000e-02]), - ('ashrae', [180.825930547, -2.00000000e-02]), - ('physical', [181.453077805, -2.00000000e-02]), - ('no_loss', [181.604438144, -2.00000000e-02]), - (constant_aoi_loss, [164.997043305, -2e-2]) -]) -def test_aoi_models(system, location, aoi_model, expected): + +@pytest.mark.parametrize('aoi_model, method', [ + ('sapm', 'sapm_aoi_loss'), ('ashrae', 'ashraeiam'), + ('physical', 'physicaliam')]) +def test_aoi_models(system, location, aoi_model, method, weather, mocker): mc = ModelChain(system, location, dc_model='sapm', aoi_model=aoi_model, spectral_model='no_loss') - times = pd.date_range('20160101 1200-0700', periods=2, freq='6H') - ac = mc.run_model(times).ac + m = mocker.spy(system, method) + mc.run_model(weather.index, weather=weather) + assert m.call_count == 1 + assert isinstance(mc.ac, pd.Series) + assert not mc.ac.empty + assert mc.ac[0] > 150 and mc.ac[0] < 200 + assert mc.ac[1] < 1 - expected = pd.Series(np.array(expected), index=times) - assert_series_equal(ac, expected, check_less_precise=2) + +def test_aoi_model_no_loss(system, location, weather): + mc = ModelChain(system, location, dc_model='sapm', + aoi_model='no_loss', spectral_model='no_loss') + mc.run_model(weather.index, weather=weather) + assert mc.aoi_modifier == 1.0 + assert not mc.ac.empty + assert mc.ac[0] > 150 and mc.ac[0] < 200 + assert mc.ac[1] < 1 + + +def test_aoi_model_user_func(system, location, weather, mocker): + m = mocker.spy(sys.modules[__name__], 'constant_aoi_loss') + mc = ModelChain(system, location, dc_model='sapm', + aoi_model=constant_aoi_loss, spectral_model='no_loss') + mc.run_model(weather.index, weather=weather) + assert m.call_count == 1 + assert mc.aoi_modifier == 0.9 + assert not mc.ac.empty + assert mc.ac[0] > 140 and mc.ac[0] < 200 + assert mc.ac[1] < 1 def constant_spectral_loss(mc): @@ -292,7 +310,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)) @@ -376,7 +394,7 @@ def test_basic_chain_alt_az(sam_data): expected = pd.Series(np.array([ 115.40352679, -2.00000000e-02]), index=times) - assert_series_equal(ac, expected, check_less_precise=2) + assert_series_equal(ac, expected, check_less_precise=1) @requires_scipy @@ -398,7 +416,7 @@ def test_basic_chain_strategy(sam_data): expected = pd.Series(np.array([ 183.522449305, -2.00000000e-02]), index=times) - assert_series_equal(ac, expected, check_less_precise=2) + assert_series_equal(ac, expected, check_less_precise=1) @requires_scipy @@ -423,7 +441,7 @@ def test_basic_chain_altitude_pressure(sam_data): expected = pd.Series(np.array([ 116.595664887, -2.00000000e-02]), index=times) - assert_series_equal(ac, expected, check_less_precise=2) + assert_series_equal(ac, expected, check_less_precise=1) dc, ac = modelchain.basic_chain(times, latitude, longitude, module_parameters, inverter_parameters, @@ -433,7 +451,7 @@ def test_basic_chain_altitude_pressure(sam_data): expected = pd.Series(np.array([ 116.595664887, -2.00000000e-02]), index=times) - assert_series_equal(ac, expected, check_less_precise=2) + assert_series_equal(ac, expected, check_less_precise=1) @pytest.mark.parametrize('strategy, strategy_str', [ From 91964ab870f6ce96ab1f33239ba912796db31699 Mon Sep 17 00:00:00 2001 From: Will Holmgren Date: Tue, 31 Jul 2018 19:58:39 -0700 Subject: [PATCH 07/14] fix typo in test --- pvlib/test/test_modelchain.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/pvlib/test/test_modelchain.py b/pvlib/test/test_modelchain.py index 1a217bf607..485619cc7d 100644 --- a/pvlib/test/test_modelchain.py +++ b/pvlib/test/test_modelchain.py @@ -163,15 +163,14 @@ def test_run_model_gueymard_perez(system, location): def test_run_model_with_weather(system, location, weather, mocker): mc = ModelChain(system, location) m = mocker.spy(system, 'sapm_celltemp') - weather2 = pd.DataFrame({'wind_speed':5, 'temp_air':10}, - index=weather.index) - weather = weather.join(weather2) + weather['wind_speed'] = 5 + weather['temp_air'] = 10 mc.run_model(weather.index, weather=weather) assert m.call_count == 1 - # assert_called_once_with cannot be used with sereis, so need to use + # assert_called_once_with cannot be used with series, so need to use # assert_series_equal on call_args - assert_series_equal(m.call_args[0][1], weather2['wind_speed']) # wind - assert_series_equal(m.call_args[0][2], weather2['temp_air']) # temp + assert_series_equal(m.call_args[0][1], weather['wind_speed']) # wind + assert_series_equal(m.call_args[0][2], weather['temp_air']) # temp assert not mc.ac.empty From bd697bbeb26216d2087a7e210141158b042977f1 Mon Sep 17 00:00:00 2001 From: Will Holmgren Date: Tue, 31 Jul 2018 20:09:22 -0700 Subject: [PATCH 08/14] update whatsnew --- docs/sphinx/source/whatsnew/v0.6.0.rst | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/docs/sphinx/source/whatsnew/v0.6.0.rst b/docs/sphinx/source/whatsnew/v0.6.0.rst index f3cb70a46a..58eb5648b7 100644 --- a/docs/sphinx/source/whatsnew/v0.6.0.rst +++ b/docs/sphinx/source/whatsnew/v0.6.0.rst @@ -11,6 +11,12 @@ API Changes 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 +* Add ``perez_enhancement`` keyword argument to clearsky.ineichen to control + whether or not the "perez enhancement factor" is applied. The enhancement + factor was always applied until now. Now it is turned off by default. The + enhancement factor can yield unphysical results, especially for latitudes + closer to the poles and especially in the winter months. It may yield + improved results under other conditions. (:issue:`435`) Enhancements @@ -81,8 +87,11 @@ Documentation Testing ~~~~~~~ * Add pytest-mock dependency -* Use pytest-mock to ensure that PVSystem methods call corresponding functions - correctly. Removes implicit dependence on precise return values of functions +* Use pytest-mock to ensure that PVSystem and ModelChain methods call + corresponding functions correctly. Removes implicit dependence on precise + return values of some function/methods. (:issue:`394`) +* Additional test refactoring to limit test result dependence to a single + function per test. (:issue:`394`) * Use pytest-mock to ensure that ModelChain DC model is set up correctly. From 40f33859e5dc48f8de61e6ae84376a8a6d4dbeac Mon Sep 17 00:00:00 2001 From: Will Holmgren Date: Tue, 31 Jul 2018 20:18:57 -0700 Subject: [PATCH 09/14] update test ineichen values for perez_enhancement=False --- pvlib/test/test_clearsky.py | 86 ++++++++++++++++++------------------- 1 file changed, 43 insertions(+), 43 deletions(-) diff --git a/pvlib/test/test_clearsky.py b/pvlib/test/test_clearsky.py index e858cdf0d3..3dbc28b6f2 100644 --- a/pvlib/test/test_clearsky.py +++ b/pvlib/test/test_clearsky.py @@ -28,15 +28,15 @@ def test_ineichen_series(): am = atmosphere.relativeairmass(ephem_data['apparent_zenith']) am = atmosphere.absoluteairmass(am, atmosphere.alt2pres(tus.altitude)) expected = pd.DataFrame(np. - array([[ 0. , 0. , 0. ], - [ 0. , 0. , 0. ], - [ 91.12492792, 321.16092181, 51.17628184], - [ 716.46580533, 888.90147035, 99.5050056 ], - [ 1053.42066043, 953.24925854, 116.32868969], - [ 863.54692781, 922.06124712, 106.95536561], - [ 271.06382274, 655.44925241, 73.05968071], - [ 0. , 0. , 0. ], - [ 0. , 0. , 0. ]]), + array([[ 0. , 0. , 0. ], + [ 0. , 0. , 0. ], + [ 65.49426624, 321.16092181, 25.54562017], + [ 704.6968125 , 888.90147035, 87.73601277], + [1044.1230677 , 953.24925854, 107.03109696], + [ 853.02065704, 922.06124712, 96.42909484], + [ 251.99427693, 655.44925241, 53.9901349 ], + [ 0. , 0. , 0. ], + [ 0. , 0. , 0. ]]), columns=['ghi', 'dni', 'dhi'], index=times_localized) @@ -46,9 +46,9 @@ def test_ineichen_series(): def test_ineichen_scalar_input(): expected = OrderedDict() - expected['ghi'] = 1048.592893113678 + expected['ghi'] = 1038.159219 expected['dni'] = 942.2081860378344 - expected['dhi'] = 120.6989665520498 + expected['dhi'] = 110.26529293612793 out = clearsky.ineichen(10., 1., 3.) for k, v in expected.items(): @@ -74,9 +74,9 @@ def test_ineichen_nans(): expected['dni'] = np.full(length, np.nan) expected['dhi'] = np.full(length, np.nan) - expected['ghi'][length-1] = 1053.205472 - expected['dni'][length-1] = 946.352797 - expected['dhi'][length-1] = 121.2299 + expected['ghi'][length-1] = 1042.72590228 + expected['dni'][length-1] = 946.35279683 + expected['dhi'][length-1] = 110.75033088 out = clearsky.ineichen(apparent_zenith, airmass_absolute, linke_turbidity, dni_extra=dni_extra) @@ -89,43 +89,43 @@ def test_ineichen_arrays(): expected = OrderedDict() expected['ghi'] = (np. - array([[[ 1106.78342709, 1064.7691287 , 1024.34972343], - [ 847.84529406, 815.66047425, 784.69741345], - [ 192.19092519, 184.89521884, 177.87646277]], + array([[[1095.77074798, 1054.17449885, 1014.15727338], + [ 839.40909243, 807.54451692, 776.88954373], + [ 190.27859353, 183.05548067, 176.10656239]], - [[ 959.12310134, 775.2374976 , 626.60692548], - [ 734.73092205, 593.86637713, 480.00875328], - [ 166.54997871, 134.61857872, 108.80915072]], + [[ 773.49041181, 625.19479557, 505.33080493], + [ 592.52803177, 478.92699901, 387.10585505], + [ 134.31520045, 108.56393694, 87.74977339]], - [[ 1026.15144142, 696.85030591, 473.22483724], - [ 786.0776095 , 533.81830453, 362.51125692], - [ 178.18932781, 121.00678573, 82.17463061]]])) + [[ 545.9968869 , 370.78162375, 251.79449885], + [ 418.25788117, 284.03520249, 192.88577665], + [ 94.81136442, 64.38555328, 43.72365587]]])) expected['dni'] = (np. - array([[[ 1024.58284359, 942.20818604, 861.11344424], - [ 1024.58284359, 942.20818604, 861.11344424], - [ 1024.58284359, 942.20818604, 861.11344424]], + array([[[1014.38807396, 942.20818604, 861.11344424], + [1014.38807396, 942.20818604, 861.11344424], + [1014.38807396, 942.20818604, 861.11344424]], - [[ 687.61305142, 419.14891162, 255.50098235], - [ 687.61305142, 419.14891162, 255.50098235], - [ 687.61305142, 419.14891162, 255.50098235]], + [[ 687.61305142, 419.14891162, 255.50098235], + [ 687.61305142, 419.14891162, 255.50098235], + [ 687.61305142, 419.14891162, 255.50098235]], - [[ 458.62196014, 186.46177428, 75.80970012], - [ 458.62196014, 186.46177428, 75.80970012], - [ 458.62196014, 186.46177428, 75.80970012]]])) + [[ 458.62196014, 186.46177428, 75.80970012], + [ 458.62196014, 186.46177428, 75.80970012], + [ 458.62196014, 186.46177428, 75.80970012]]])) expected['dhi'] = (np. - array([[[ 82.20058349, 122.56094266, 163.23627919], - [ 62.96930021, 93.88712907, 125.04624459], - [ 14.27398153, 21.28248435, 28.34568241]], + array([[[ 81.38267402, 111.96631281, 153.04382915], + [ 62.3427452 , 85.77117175, 117.23837487], + [ 14.13195304, 19.44274618, 26.57578203]], - [[ 271.51004993, 356.08858598, 371.10594313], - [ 207.988765 , 272.77968255, 284.28364554], - [ 47.14722539, 61.83413404, 64.44187075]], + [[ 85.87736039, 206.04588395, 249.82982258], + [ 65.78587472, 157.84030442, 191.38074731], + [ 14.91244713, 35.77949226, 43.38249342]], - [[ 567.52948128, 510.38853163, 397.41513712], - [ 434.75280544, 390.98029849, 304.4376574 ], - [ 98.5504602 , 88.62803842, 69.01041434]]])) + [[ 87.37492676, 184.31984947, 175.98479873], + [ 66.93307711, 141.19719644, 134.81217714], + [ 15.17249681, 32.00680597, 30.5594396 ]]])) apparent_zenith = np.linspace(0, 80, 3) airmass_absolute = np.linspace(1, 10, 3) @@ -142,7 +142,7 @@ def test_ineichen_arrays(): def test_ineichen_dni_extra(): expected = pd.DataFrame( - np.array([[ 1053.20547182, 946.35279683, 121.22990042]]), + np.array([[1042.72590228, 946.35279683, 110.75033088]]), columns=['ghi', 'dni', 'dhi']) out = clearsky.ineichen(10, 1, 3, dni_extra=pd.Series(1370)) @@ -151,7 +151,7 @@ def test_ineichen_dni_extra(): def test_ineichen_altitude(): expected = pd.DataFrame( - np.array([[ 1145.64245696, 994.95377835, 165.80426215]]), + np.array([[1134.24312405, 994.95377835, 154.40492924]]), columns=['ghi', 'dni', 'dhi']) out = clearsky.ineichen(10, 1, 3, altitude=pd.Series(2000)) From ec296641678a419d9566cff086f8c1ae4ffe9b5a Mon Sep 17 00:00:00 2001 From: Will Holmgren Date: Tue, 31 Jul 2018 20:34:56 -0700 Subject: [PATCH 10/14] update test_clearsky values --- pvlib/test/test_clearsky.py | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/pvlib/test/test_clearsky.py b/pvlib/test/test_clearsky.py index 3dbc28b6f2..db6cb8966e 100644 --- a/pvlib/test/test_clearsky.py +++ b/pvlib/test/test_clearsky.py @@ -3,6 +3,7 @@ from collections import OrderedDict import numpy as np +from numpy import nan import pandas as pd import pytz @@ -20,13 +21,16 @@ def test_ineichen_series(): - tus = Location(32.2, -111, 'US/Arizona', 700) - times = pd.date_range(start='2014-06-24', end='2014-06-25', freq='3h') - times_localized = times.tz_localize(tus.tz) - ephem_data = solarposition.get_solarposition(times_localized, tus.latitude, - tus.longitude) - am = atmosphere.relativeairmass(ephem_data['apparent_zenith']) - am = atmosphere.absoluteairmass(am, atmosphere.alt2pres(tus.altitude)) + times = pd.date_range(start='2014-06-24', end='2014-06-25', freq='3h', + tz='America/Phoenix') + apparent_zenith = pd.Series(np.array( + [124.0390863, 113.38779941, 82.85457044, 46.0467599, 10.56413562, + 34.86074109, 72.41687122, 105.69538659, 124.05614124]), + index=times) + am = pd.Series(np.array( + [nan, nan, 6.97935524, 1.32355476, 0.93527685, + 1.12008114, 3.01614096, nan, nan]), + index=times) expected = pd.DataFrame(np. array([[ 0. , 0. , 0. ], [ 0. , 0. , 0. ], @@ -38,9 +42,9 @@ def test_ineichen_series(): [ 0. , 0. , 0. ], [ 0. , 0. , 0. ]]), columns=['ghi', 'dni', 'dhi'], - index=times_localized) + index=times) - out = clearsky.ineichen(ephem_data['apparent_zenith'], am, 3) + out = clearsky.ineichen(apparent_zenith, am, 3) assert_frame_equal(expected, out) @@ -526,18 +530,18 @@ def test_detect_clearsky_components(detect_clearsky_data): assert_series_equal(expected['Clear or not'], clear_samples, check_dtype=False, check_names=False) assert isinstance(components, OrderedDict) - assert np.allclose(alpha, 0.95345573579557108) + assert np.allclose(alpha, 0.9633903181941296) @requires_scipy def test_detect_clearsky_iterations(detect_clearsky_data): expected, cs = detect_clearsky_data - alpha = 1.0348 + alpha = 1.0448 with pytest.warns(RuntimeWarning): clear_samples = clearsky.detect_clearsky( expected['GHI'], cs['ghi']*alpha, cs.index, 10, max_iterations=1) - assert (clear_samples[:'2012-04-01 10:39:00'] == True).all() - assert (clear_samples['2012-04-01 10:40:00':] == False).all() + assert (clear_samples[:'2012-04-01 10:41:00'] == True).all() + assert (clear_samples['2012-04-01 10:42:00':] == False).all() clear_samples = clearsky.detect_clearsky( expected['GHI'], cs['ghi']*alpha, cs.index, 10, max_iterations=20) assert_series_equal(expected['Clear or not'], clear_samples, From dcc12242879ef892052de1dd21445f9108d1bd01 Mon Sep 17 00:00:00 2001 From: Will Holmgren Date: Tue, 31 Jul 2018 20:42:45 -0700 Subject: [PATCH 11/14] add test for perez enhancement --- pvlib/test/test_clearsky.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/pvlib/test/test_clearsky.py b/pvlib/test/test_clearsky.py index db6cb8966e..98007b449d 100644 --- a/pvlib/test/test_clearsky.py +++ b/pvlib/test/test_clearsky.py @@ -48,6 +48,34 @@ def test_ineichen_series(): assert_frame_equal(expected, out) +def test_ineichen_series_perez_enhancement(): + times = pd.date_range(start='2014-06-24', end='2014-06-25', freq='3h', + tz='America/Phoenix') + apparent_zenith = pd.Series(np.array( + [124.0390863, 113.38779941, 82.85457044, 46.0467599, 10.56413562, + 34.86074109, 72.41687122, 105.69538659, 124.05614124]), + index=times) + am = pd.Series(np.array( + [nan, nan, 6.97935524, 1.32355476, 0.93527685, + 1.12008114, 3.01614096, nan, nan]), + index=times) + expected = pd.DataFrame(np. + array([[ 0. , 0. , 0. ], + [ 0. , 0. , 0. ], + [ 91.1249279 , 321.16092171, 51.17628184], + [ 716.46580547, 888.9014706 , 99.50500553], + [1053.42066073, 953.24925905, 116.3286895 ], + [ 863.54692748, 922.06124652, 106.9553658 ], + [ 271.06382275, 655.44925213, 73.05968076], + [ 0. , 0. , 0. ], + [ 0. , 0. , 0. ]]), + columns=['ghi', 'dni', 'dhi'], + index=times) + + out = clearsky.ineichen(apparent_zenith, am, 3, perez_enhancement=True) + assert_frame_equal(expected, out) + + def test_ineichen_scalar_input(): expected = OrderedDict() expected['ghi'] = 1038.159219 From bfcbb13b88c368e614416053dc46ee7e677dec9a Mon Sep 17 00:00:00 2001 From: Will Holmgren Date: Wed, 1 Aug 2018 21:25:57 -0700 Subject: [PATCH 12/14] update tests for py2.7-3.5 --- pvlib/test/test_location.py | 10 +++++++--- pvlib/test/test_modelchain.py | 6 +++++- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/pvlib/test/test_location.py b/pvlib/test/test_location.py index b38bb962aa..fd4a48d359 100644 --- a/pvlib/test/test_location.py +++ b/pvlib/test/test_location.py @@ -1,5 +1,9 @@ import datetime -from unittest.mock import ANY +try: + from unittest.mock import ANY +except ImportError: + # python 2 + from mock import ANY import numpy as np from numpy import nan @@ -75,9 +79,9 @@ def test_get_clearsky(mocker): times = pd.DatetimeIndex(start='20160101T0600-0700', end='20160101T1800-0700', freq='3H') - mocker.spy(pvlib.clearsky, 'ineichen') + m = mocker.spy(pvlib.clearsky, 'ineichen') out = tus.get_clearsky(times) - pvlib.clearsky.ineichen.assert_called_once() + assert m.call_count == 1 assert_index_equal(out.index, times) # check that values are 0 before sunrise and after sunset assert out.iloc[0, :].sum().sum() == 0 diff --git a/pvlib/test/test_modelchain.py b/pvlib/test/test_modelchain.py index 485619cc7d..d61bbb1c9c 100644 --- a/pvlib/test/test_modelchain.py +++ b/pvlib/test/test_modelchain.py @@ -1,5 +1,9 @@ import sys -from unittest.mock import ANY +try: + from unittest.mock import ANY +except ImportError: + # python 2 + from mock import ANY import numpy as np import pandas as pd From 84f344f783fb6c808df5a8dde413c96d977e9bd8 Mon Sep 17 00:00:00 2001 From: Will Holmgren Date: Thu, 2 Aug 2018 08:15:45 -0700 Subject: [PATCH 13/14] add requires_scipy for min env test --- pvlib/test/test_modelchain.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/pvlib/test/test_modelchain.py b/pvlib/test/test_modelchain.py index d61bbb1c9c..0dab068713 100644 --- a/pvlib/test/test_modelchain.py +++ b/pvlib/test/test_modelchain.py @@ -196,7 +196,8 @@ def poadc(mc): mc.dc.name = None # assert_series_equal will fail without this -@pytest.mark.parametrize('dc_model', ['sapm', 'singlediode', 'pvwatts_dc']) +@pytest.mark.parametrize('dc_model', [ + 'sapm', pytest.param('singlediode', marks=requires_scipy), 'pvwatts_dc']) def test_infer_dc_model(system, cec_dc_snl_ac_system, pvwatts_dc_pvwatts_ac_system, location, dc_model, weather, mocker): @@ -226,8 +227,9 @@ def acdc(mc): mc.ac = mc.dc -@pytest.mark.parametrize('ac_model', ['snlinverter', 'adrinverter', - 'pvwatts']) +@pytest.mark.parametrize('ac_model', [ + 'snlinverter', pytest.param('adrinverter', marks=requires_scipy), + 'pvwatts']) def test_ac_models(system, cec_dc_adr_ac_system, pvwatts_dc_pvwatts_ac_system, location, ac_model, weather, mocker): ac_systems = {'snlinverter': system, 'adrinverter': cec_dc_adr_ac_system, From c750fff55c6fb0dc06d594ab4c1abaa9ded382d2 Mon Sep 17 00:00:00 2001 From: Will Holmgren Date: Thu, 2 Aug 2018 08:18:06 -0700 Subject: [PATCH 14/14] update comment to point to correct refs --- pvlib/clearsky.py | 25 +++---------------------- 1 file changed, 3 insertions(+), 22 deletions(-) diff --git a/pvlib/clearsky.py b/pvlib/clearsky.py index 4343465d43..8ce301008f 100644 --- a/pvlib/clearsky.py +++ b/pvlib/clearsky.py @@ -85,28 +85,9 @@ def ineichen(apparent_zenith, airmass_absolute, linke_turbidity, ISES Solar World Congress, June 2003. Goteborg, Sweden. ''' - # Dan's note on the TL correction: By my reading of the publication - # on pages 151-157, Ineichen and Perez introduce (among other - # things) three things. 1) Beam model in eqn. 8, 2) new turbidity - # factor in eqn 9 and appendix A, and 3) Global horizontal model in - # eqn. 11. They do NOT appear to use the new turbidity factor (item - # 2 above) in either the beam or GHI models. The phrasing of - # appendix A seems as if there are two separate corrections, the - # first correction is used to correct the beam/GHI models, and the - # second correction is used to correct the revised turibidity - # factor. In my estimation, there is no need to correct the - # turbidity factor used in the beam/GHI models. - - # Create the corrected TL for TL < 2 - # TLcorr = TL; - # TLcorr(TL < 2) = TLcorr(TL < 2) - 0.25 .* (2-TLcorr(TL < 2)) .^ (0.5); - - # This equation is found in Solar Energy 73, pg 311. Full ref: Perez - # et. al., Vol. 73, pp. 307-317 (2002). It is slightly different - # than the equation given in Solar Energy 73, pg 156. We used the - # equation from pg 311 because of the existence of known typos in - # the pg 156 publication (notably the fh2-(TL-1) should be fh2 * - # (TL-1)). + # ghi is calculated using either the equations in [1] by setting + # perez_enhancement=False (default behavior) or using the model + # in [2] by setting perez_enhancement=True. # The NaN handling is a little subtle. The AM input is likely to # have NaNs that we'll want to map to 0s in the output. However, we