From 7b3fab5d01aea73fa4fac2abb9114b50dfb78964 Mon Sep 17 00:00:00 2001 From: Kevin Anderson Date: Mon, 12 Jun 2023 12:34:33 -0400 Subject: [PATCH 01/13] privatize scaling.latlon_to_xy --- pvlib/scaling.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pvlib/scaling.py b/pvlib/scaling.py index dca2ca4935..2c6fe9e1fd 100644 --- a/pvlib/scaling.py +++ b/pvlib/scaling.py @@ -146,7 +146,7 @@ def fn(x): return vr -def latlon_to_xy(coordinates): +def _latlon_to_xy(coordinates): """ Convert latitude and longitude in degrees to a coordinate system measured in meters from zero deg latitude, zero deg longitude. From 2d5b043b0f05e42b702a633692e8c2ad5abf2008 Mon Sep 17 00:00:00 2001 From: Kevin Anderson Date: Mon, 12 Jun 2023 12:34:56 -0400 Subject: [PATCH 02/13] privatize iotools.surfrad.format_index --- pvlib/iotools/surfrad.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pvlib/iotools/surfrad.py b/pvlib/iotools/surfrad.py index 7f4d86e46a..d3e40d86a2 100644 --- a/pvlib/iotools/surfrad.py +++ b/pvlib/iotools/surfrad.py @@ -150,7 +150,7 @@ def read_surfrad(filename, map_variables=True): header=None, names=SURFRAD_COLUMNS) file_buffer.close() - data = format_index(data) + data = _format_index(data) missing = data == -9999.9 data = data.where(~missing, np.NaN) @@ -159,7 +159,7 @@ def read_surfrad(filename, map_variables=True): return data, metadata -def format_index(data): +def _format_index(data): """Create UTC localized DatetimeIndex for the dataframe. Parameters From 16a97be15ab662d386243b483c7ecf95dfeb010b Mon Sep 17 00:00:00 2001 From: Kevin Anderson Date: Mon, 12 Jun 2023 12:35:12 -0400 Subject: [PATCH 03/13] privatize iotools.srml.format_index and map_columns --- pvlib/iotools/srml.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pvlib/iotools/srml.py b/pvlib/iotools/srml.py index 397a7bd612..d608622632 100644 --- a/pvlib/iotools/srml.py +++ b/pvlib/iotools/srml.py @@ -60,11 +60,11 @@ def read_srml(filename): `http://solardat.uoregon.edu/ `_ """ tsv_data = pd.read_csv(filename, delimiter='\t') - data = format_index(tsv_data) + data = _format_index(tsv_data) # Drop day of year and time columns data = data[data.columns[2:]] - data = data.rename(columns=map_columns) + data = data.rename(columns=_map_columns) # Quality flag columns are all labeled 0 in the original data. They # appear immediately after their associated variable and are suffixed @@ -90,7 +90,7 @@ def read_srml(filename): return data -def map_columns(col): +def _map_columns(col): """Map data element numbers to pvlib names. Parameters @@ -118,7 +118,7 @@ def map_columns(col): return col -def format_index(df): +def _format_index(df): """Create a datetime index from day of year, and time columns. Parameters From ef11aac7bd13669ac1415a6487e4ba949f4712a3 Mon Sep 17 00:00:00 2001 From: Kevin Anderson Date: Mon, 12 Jun 2023 12:35:26 -0400 Subject: [PATCH 04/13] privatize iotools.midc.format_index and format_index_raw --- pvlib/iotools/midc.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pvlib/iotools/midc.py b/pvlib/iotools/midc.py index 063326b60c..75cc5609a7 100644 --- a/pvlib/iotools/midc.py +++ b/pvlib/iotools/midc.py @@ -102,7 +102,7 @@ } -def format_index(data): +def _format_index(data): """Create DatetimeIndex for the Dataframe localized to the timezone provided as the label of the second (time) column. @@ -126,7 +126,7 @@ def format_index(data): return data -def format_index_raw(data): +def _format_index_raw(data): """Create DatetimeIndex for the Dataframe localized to the timezone provided as the label of the third column. @@ -200,9 +200,9 @@ def read_midc(filename, variable_map={}, raw_data=False, **kwargs): """ data = pd.read_csv(filename, **kwargs) if raw_data: - data = format_index_raw(data) + data = _format_index_raw(data) else: - data = format_index(data) + data = _format_index(data) data = data.rename(columns=variable_map) return data From f02266f2cf755fe17ef6270f731d1ac074928714 Mon Sep 17 00:00:00 2001 From: Kevin Anderson Date: Mon, 12 Jun 2023 13:03:29 -0400 Subject: [PATCH 05/13] move calculate_deltat from pvlib.spa to pvlib.solarposition --- .../sphinx/source/reference/solarposition.rst | 2 +- pvlib/solarposition.py | 157 +++++++++++++++++- pvlib/spa.py | 129 -------------- 3 files changed, 152 insertions(+), 136 deletions(-) diff --git a/docs/sphinx/source/reference/solarposition.rst b/docs/sphinx/source/reference/solarposition.rst index dbe93d5392..f17a9abaec 100644 --- a/docs/sphinx/source/reference/solarposition.rst +++ b/docs/sphinx/source/reference/solarposition.rst @@ -29,7 +29,7 @@ Additional functions for quantities closely related to solar position. solarposition.calc_time solarposition.pyephem_earthsun_distance solarposition.nrel_earthsun_distance - spa.calculate_deltat + solarposition.calculate_deltat Functions for calculating sunrise, sunset and transit times. diff --git a/pvlib/solarposition.py b/pvlib/solarposition.py index cdcacd7ec6..653c04b50b 100644 --- a/pvlib/solarposition.py +++ b/pvlib/solarposition.py @@ -306,7 +306,7 @@ def spa_python(time, latitude, longitude, avg. yearly air temperature in degrees C. delta_t : float, optional, default 67.0 Difference between terrestrial time and UT1. - If delta_t is None, uses spa.calculate_deltat + If delta_t is None, uses :py:func:`calculate_deltat` using time.year and time.month from pandas.DatetimeIndex. For most simulations the default delta_t is sufficient. *Note: delta_t = None will break code using nrel_numba, @@ -370,7 +370,7 @@ def spa_python(time, latitude, longitude, spa = _spa_python_import(how) - delta_t = delta_t or spa.calculate_deltat(time.year, time.month) + delta_t = delta_t or calculate_deltat(time.year, time.month) app_zenith, zenith, app_elevation, elevation, azimuth, eot = \ spa.solar_position(unixtime, lat, lon, elev, pressure, temperature, @@ -412,7 +412,7 @@ def sun_rise_set_transit_spa(times, latitude, longitude, how='numpy', to machine code and run them multithreaded. delta_t : float, optional, default 67.0 Difference between terrestrial time and UT1. - If delta_t is None, uses spa.calculate_deltat + If delta_t is None, uses :py:func:`calculate_deltat` using times.year and times.month from pandas.DatetimeIndex. For most simulations the default delta_t is sufficient. *Note: delta_t = None will break code using nrel_numba, @@ -449,7 +449,7 @@ def sun_rise_set_transit_spa(times, latitude, longitude, how='numpy', spa = _spa_python_import(how) - delta_t = delta_t or spa.calculate_deltat(times.year, times.month) + delta_t = delta_t or calculate_deltat(times.year, times.month) transit, sunrise, sunset = spa.transit_sunrise_sunset( unixtime, lat, lon, delta_t, numthreads) @@ -974,7 +974,7 @@ def nrel_earthsun_distance(time, how='numpy', delta_t=67.0, numthreads=4): delta_t : float, optional, default 67.0 Difference between terrestrial time and UT1. - If delta_t is None, uses spa.calculate_deltat + If delta_t is None, uses :py:func:`calculate_deltat` using time.year and time.month from pandas.DatetimeIndex. For most simulations the default delta_t is sufficient. *Note: delta_t = None will break code using nrel_numba, @@ -1005,7 +1005,7 @@ def nrel_earthsun_distance(time, how='numpy', delta_t=67.0, numthreads=4): spa = _spa_python_import(how) - delta_t = delta_t or spa.calculate_deltat(time.year, time.month) + delta_t = delta_t or calculate_deltat(time.year, time.month) dist = spa.earthsun_distance(unixtime, delta_t, numthreads) @@ -1476,3 +1476,148 @@ def sun_rise_set_transit_geometric(times, latitude, longitude, declination, sunset = _local_times_from_hours_since_midnight(times, sunset_hour) transit = _local_times_from_hours_since_midnight(times, transit_hour) return sunrise, sunset, transit + + +def calculate_deltat(year, month): + """Calculate the difference between Terrestrial Dynamical Time (TD) + and Universal Time (UT). + + Note: This function is not yet compatible for calculations using + Numba. + + Parameters + ---------- + year : numeric + Calendar year for which to calculate the time offset + month : numeric + Calendar month (1-12) for which to calculate the time offset + + Returns + ------- + deltat : numeric + + References + ---------- + .. [1] `NASA GSFC: Polynomial Expressions for Delta T (ΔT) + `_ + """ + + plw = 'Deltat is unknown for years before -1999 and after 3000. ' \ + 'Delta values will be calculated, but the calculations ' \ + 'are not intended to be used for these years.' + + try: + if np.any((year > 3000) | (year < -1999)): + warnings.warn(plw) + except ValueError: + if (year > 3000) | (year < -1999): + warnings.warn(plw) + except TypeError: + return 0 + + y = year + (month - 0.5)/12 + + deltat = np.where(year < -500, + + -20+32*((y-1820)/100)**2, 0) + + deltat = np.where((-500 <= year) & (year < 500), + + 10583.6-1014.41*(y/100) + + 33.78311*(y/100)**2 + - 5.952053*(y/100)**3 + - 0.1798452*(y/100)**4 + + 0.022174192*(y/100)**5 + + 0.0090316521*(y/100)**6, deltat) + + deltat = np.where((500 <= year) & (year < 1600), + + 1574.2-556.01*((y-1000)/100) + + 71.23472*((y-1000)/100)**2 + + 0.319781*((y-1000)/100)**3 + - 0.8503463*((y-1000)/100)**4 + - 0.005050998*((y-1000)/100)**5 + + 0.0083572073*((y-1000)/100)**6, deltat) + + deltat = np.where((1600 <= year) & (year < 1700), + + 120-0.9808*(y-1600) + - 0.01532*(y-1600)**2 + + (y-1600)**3/7129, deltat) + + deltat = np.where((1700 <= year) & (year < 1800), + + 8.83+0.1603*(y-1700) + - 0.0059285*(y-1700)**2 + + 0.00013336*(y-1700)**3 + - (y-1700)**4/1174000, deltat) + + deltat = np.where((1800 <= year) & (year < 1860), + + 13.72-0.332447*(y-1800) + + 0.0068612*(y-1800)**2 + + 0.0041116*(y-1800)**3 + - 0.00037436*(y-1800)**4 + + 0.0000121272*(y-1800)**5 + - 0.0000001699*(y-1800)**6 + + 0.000000000875*(y-1800)**7, deltat) + + deltat = np.where((1860 <= year) & (year < 1900), + + 7.62+0.5737*(y-1860) + - 0.251754*(y-1860)**2 + + 0.01680668*(y-1860)**3 + - 0.0004473624*(y-1860)**4 + + (y-1860)**5/233174, deltat) + + deltat = np.where((1900 <= year) & (year < 1920), + + -2.79+1.494119*(y-1900) + - 0.0598939*(y-1900)**2 + + 0.0061966*(y-1900)**3 + - 0.000197*(y-1900)**4, deltat) + + deltat = np.where((1920 <= year) & (year < 1941), + + 21.20+0.84493*(y-1920) + - 0.076100*(y-1920)**2 + + 0.0020936*(y-1920)**3, deltat) + + deltat = np.where((1941 <= year) & (year < 1961), + + 29.07+0.407*(y-1950) + - (y-1950)**2/233 + + (y-1950)**3/2547, deltat) + + deltat = np.where((1961 <= year) & (year < 1986), + + 45.45+1.067*(y-1975) + - (y-1975)**2/260 + - (y-1975)**3/718, deltat) + + deltat = np.where((1986 <= year) & (year < 2005), + + 63.86+0.3345*(y-2000) + - 0.060374*(y-2000)**2 + + 0.0017275*(y-2000)**3 + + 0.000651814*(y-2000)**4 + + 0.00002373599*(y-2000)**5, deltat) + + deltat = np.where((2005 <= year) & (year < 2050), + + 62.92+0.32217*(y-2000) + + 0.005589*(y-2000)**2, deltat) + + deltat = np.where((2050 <= year) & (year < 2150), + + -20+32*((y-1820)/100)**2 + - 0.5628*(2150-y), deltat) + + deltat = np.where(year >= 2150, + + -20+32*((y-1820)/100)**2, deltat) + + deltat = deltat.item() if np.isscalar(year) & np.isscalar(month)\ + else deltat + + return deltat diff --git a/pvlib/spa.py b/pvlib/spa.py index 763145cbc4..7d355142ec 100644 --- a/pvlib/spa.py +++ b/pvlib/spa.py @@ -1246,132 +1246,3 @@ def earthsun_distance(unixtime, delta_t, numthreads): return R -def calculate_deltat(year, month): - """Calculate the difference between Terrestrial Dynamical Time (TD) - and Universal Time (UT). - - Note: This function is not yet compatible for calculations using - Numba. - - Equations taken from http://eclipse.gsfc.nasa.gov/SEcat5/deltatpoly.html - """ - - plw = 'Deltat is unknown for years before -1999 and after 3000. ' \ - 'Delta values will be calculated, but the calculations ' \ - 'are not intended to be used for these years.' - - try: - if np.any((year > 3000) | (year < -1999)): - warnings.warn(plw) - except ValueError: - if (year > 3000) | (year < -1999): - warnings.warn(plw) - except TypeError: - return 0 - - y = year + (month - 0.5)/12 - - deltat = np.where(year < -500, - - -20+32*((y-1820)/100)**2, 0) - - deltat = np.where((-500 <= year) & (year < 500), - - 10583.6-1014.41*(y/100) - + 33.78311*(y/100)**2 - - 5.952053*(y/100)**3 - - 0.1798452*(y/100)**4 - + 0.022174192*(y/100)**5 - + 0.0090316521*(y/100)**6, deltat) - - deltat = np.where((500 <= year) & (year < 1600), - - 1574.2-556.01*((y-1000)/100) - + 71.23472*((y-1000)/100)**2 - + 0.319781*((y-1000)/100)**3 - - 0.8503463*((y-1000)/100)**4 - - 0.005050998*((y-1000)/100)**5 - + 0.0083572073*((y-1000)/100)**6, deltat) - - deltat = np.where((1600 <= year) & (year < 1700), - - 120-0.9808*(y-1600) - - 0.01532*(y-1600)**2 - + (y-1600)**3/7129, deltat) - - deltat = np.where((1700 <= year) & (year < 1800), - - 8.83+0.1603*(y-1700) - - 0.0059285*(y-1700)**2 - + 0.00013336*(y-1700)**3 - - (y-1700)**4/1174000, deltat) - - deltat = np.where((1800 <= year) & (year < 1860), - - 13.72-0.332447*(y-1800) - + 0.0068612*(y-1800)**2 - + 0.0041116*(y-1800)**3 - - 0.00037436*(y-1800)**4 - + 0.0000121272*(y-1800)**5 - - 0.0000001699*(y-1800)**6 - + 0.000000000875*(y-1800)**7, deltat) - - deltat = np.where((1860 <= year) & (year < 1900), - - 7.62+0.5737*(y-1860) - - 0.251754*(y-1860)**2 - + 0.01680668*(y-1860)**3 - - 0.0004473624*(y-1860)**4 - + (y-1860)**5/233174, deltat) - - deltat = np.where((1900 <= year) & (year < 1920), - - -2.79+1.494119*(y-1900) - - 0.0598939*(y-1900)**2 - + 0.0061966*(y-1900)**3 - - 0.000197*(y-1900)**4, deltat) - - deltat = np.where((1920 <= year) & (year < 1941), - - 21.20+0.84493*(y-1920) - - 0.076100*(y-1920)**2 - + 0.0020936*(y-1920)**3, deltat) - - deltat = np.where((1941 <= year) & (year < 1961), - - 29.07+0.407*(y-1950) - - (y-1950)**2/233 - + (y-1950)**3/2547, deltat) - - deltat = np.where((1961 <= year) & (year < 1986), - - 45.45+1.067*(y-1975) - - (y-1975)**2/260 - - (y-1975)**3/718, deltat) - - deltat = np.where((1986 <= year) & (year < 2005), - - 63.86+0.3345*(y-2000) - - 0.060374*(y-2000)**2 - + 0.0017275*(y-2000)**3 - + 0.000651814*(y-2000)**4 - + 0.00002373599*(y-2000)**5, deltat) - - deltat = np.where((2005 <= year) & (year < 2050), - - 62.92+0.32217*(y-2000) - + 0.005589*(y-2000)**2, deltat) - - deltat = np.where((2050 <= year) & (year < 2150), - - -20+32*((y-1820)/100)**2 - - 0.5628*(2150-y), deltat) - - deltat = np.where(year >= 2150, - - -20+32*((y-1820)/100)**2, deltat) - - deltat = deltat.item() if np.isscalar(year) & np.isscalar(month)\ - else deltat - - return deltat From eb2e23a74ce4bae639ad64f865a99c10cecdfe27 Mon Sep 17 00:00:00 2001 From: Kevin Anderson Date: Mon, 12 Jun 2023 13:15:58 -0400 Subject: [PATCH 06/13] rename `spa.py` to `_spa.py` --- .../sphinx/source/reference/solarposition.rst | 8 ---- pvlib/__init__.py | 1 - pvlib/{spa.py => _spa.py} | 1 - pvlib/solarposition.py | 10 ++--- pvlib/tests/test_solarposition.py | 43 ++++++++++++++++--- pvlib/tests/test_spa.py | 4 +- 6 files changed, 45 insertions(+), 22 deletions(-) rename pvlib/{spa.py => _spa.py} (99%) diff --git a/docs/sphinx/source/reference/solarposition.rst b/docs/sphinx/source/reference/solarposition.rst index f17a9abaec..b3c4c0e351 100644 --- a/docs/sphinx/source/reference/solarposition.rst +++ b/docs/sphinx/source/reference/solarposition.rst @@ -43,14 +43,6 @@ Functions for calculating sunrise, sunset and transit times. solarposition.sun_rise_set_transit_geometric -The spa module contains the implementation of the built-in NREL SPA -algorithm. - -.. autosummary:: - :toctree: generated/ - - spa - Correlations and analytical expressions for low precision solar position calculations. diff --git a/pvlib/__init__.py b/pvlib/__init__.py index dfd5f1dc2a..ba39d0c15a 100644 --- a/pvlib/__init__.py +++ b/pvlib/__init__.py @@ -22,7 +22,6 @@ snow, soiling, solarposition, - spa, temperature, tools, tracking, diff --git a/pvlib/spa.py b/pvlib/_spa.py similarity index 99% rename from pvlib/spa.py rename to pvlib/_spa.py index 7d355142ec..9502514325 100644 --- a/pvlib/spa.py +++ b/pvlib/_spa.py @@ -1245,4 +1245,3 @@ def earthsun_distance(unixtime, delta_t, numthreads): return R - diff --git a/pvlib/solarposition.py b/pvlib/solarposition.py index 653c04b50b..3fd200ba14 100644 --- a/pvlib/solarposition.py +++ b/pvlib/solarposition.py @@ -247,10 +247,10 @@ def spa_c(time, latitude, longitude, pressure=101325, altitude=0, def _spa_python_import(how): """Compile spa.py appropriately""" - from pvlib import spa + from pvlib import _spa # check to see if the spa module was compiled with numba - using_numba = spa.USE_NUMBA + using_numba = _spa.USE_NUMBA if how == 'numpy' and using_numba: # the spa module was compiled to numba code, so we need to @@ -259,19 +259,19 @@ def _spa_python_import(how): # to not compile with numba warnings.warn('Reloading spa to use numpy') os.environ['PVLIB_USE_NUMBA'] = '0' - spa = reload(spa) + _spa = reload(_spa) del os.environ['PVLIB_USE_NUMBA'] elif how == 'numba' and not using_numba: # The spa module was not compiled to numba code, so set # PVLIB_USE_NUMBA so it does compile to numba on reload. warnings.warn('Reloading spa to use numba') os.environ['PVLIB_USE_NUMBA'] = '1' - spa = reload(spa) + _spa = reload(_spa) del os.environ['PVLIB_USE_NUMBA'] elif how != 'numba' and how != 'numpy': raise ValueError("how must be either 'numba' or 'numpy'") - return spa + return _spa def spa_python(time, latitude, longitude, diff --git a/pvlib/tests/test_solarposition.py b/pvlib/tests/test_solarposition.py index 36af6afff0..8d031cc241 100644 --- a/pvlib/tests/test_solarposition.py +++ b/pvlib/tests/test_solarposition.py @@ -10,7 +10,7 @@ import pytest from pvlib.location import Location -from pvlib import solarposition, spa +from pvlib import solarposition, _spa from .conftest import requires_ephem, requires_spa_c, requires_numba @@ -560,11 +560,11 @@ def test_declination(): times = pd.date_range(start="1/1/2015 0:00", end="12/31/2015 23:00", freq="H") atmos_refract = 0.5667 - delta_t = spa.calculate_deltat(times.year, times.month) + delta_t = solarposition.calculate_deltat(times.year, times.month) unixtime = np.array([calendar.timegm(t.timetuple()) for t in times]) - _, _, declination = spa.solar_position(unixtime, 37.8, -122.25, 100, - 1013.25, 25, delta_t, atmos_refract, - sst=True) + _, _, declination = _spa.solar_position( + unixtime, 37.8, -122.25, 100, 1013.25, 25, delta_t, atmos_refract, + sst=True) declination = np.deg2rad(declination) declination_rng = declination.max() - declination.min() declination_1 = solarposition.declination_cooper69(times.dayofyear) @@ -769,3 +769,36 @@ def test_spa_python_numba_physical_dst(expected_solpos, golden): temperature=11, delta_t=67, atmos_refract=0.5667, how='numpy', numthreads=1) + + +def test_calculate_deltat(): + year_array = np.array([-499, 500, 1000, 1500, 1800, 1860, 1900, 1950, + 1970, 1985, 1990, 2000, 2005, 2050, 2150]) + # `month_array` is used with `year_array` in `test_calculate_deltat`. + # Both arrays need to have the same length for the test, hence the duplicates. + month_array = np.array([1, 2, 3, 4, 5, 6, 6, 7, 8, 9, 10, 11, 12, 12, 12]) + dt_actual = 54.413442486 + dt_actual_array = np.array([1.7184831e+04, 5.7088051e+03, 1.5730419e+03, + 1.9801820e+02, 1.3596506e+01, 7.8316585e+00, + -2.1171894e+00, 2.9289261e+01, 4.0824887e+01, + 5.4724581e+01, 5.7426651e+01, 6.4108015e+01, + 6.5038015e+01, 9.4952955e+01, 3.3050693e+02]) + year = 1985 + month = 2 + + mix_year_array = np.full((10), year) + mix_month_array = np.full((10), month) + mix_year_actual = np.full((10), dt_actual) + mix_month_actual = mix_year_actual + + result_mix_year = solarposition.calculate_deltat(mix_year_array, month) + np.testing.assert_almost_equal(mix_year_actual, result_mix_year) + + result_mix_month = solarposition.calculate_deltat(year, mix_month_array) + np.testing.assert_almost_equal(mix_month_actual, result_mix_month) + + result_array = solarposition.calculate_deltat(year_array, month_array) + np.testing.assert_almost_equal(dt_actual_array, result_array, 3) + + result_scalar = solarposition.calculate_deltat(year, month) + np.testing.assert_almost_equal(dt_actual, result_scalar) diff --git a/pvlib/tests/test_spa.py b/pvlib/tests/test_spa.py index 00b30f46d2..c288b04442 100644 --- a/pvlib/tests/test_spa.py +++ b/pvlib/tests/test_spa.py @@ -378,7 +378,7 @@ class NumpySpaTest(unittest.TestCase, SpaBase): @classmethod def setUpClass(self): os.environ['PVLIB_USE_NUMBA'] = '0' - import pvlib.spa as spa + import pvlib._spa as spa spa = reload(spa) self.spa = spa @@ -398,7 +398,7 @@ class NumbaSpaTest(unittest.TestCase, SpaBase): def setUpClass(self): os.environ['PVLIB_USE_NUMBA'] = '1' if numba_version_int >= 17: - import pvlib.spa as spa + import pvlib._spa as spa spa = reload(spa) self.spa = spa From 6ae1482ea04e1f0af6aa71ecda941a39ad5b0b30 Mon Sep 17 00:00:00 2001 From: Kevin Anderson Date: Mon, 12 Jun 2023 13:16:22 -0400 Subject: [PATCH 07/13] update tests --- pvlib/tests/iotools/test_midc.py | 12 ++++++------ pvlib/tests/iotools/test_srml.py | 4 ++-- pvlib/tests/test_scaling.py | 16 ++++++++-------- pvlib/tests/test_spa.py | 31 ------------------------------- 4 files changed, 16 insertions(+), 47 deletions(-) diff --git a/pvlib/tests/iotools/test_midc.py b/pvlib/tests/iotools/test_midc.py index 1c5c38b6a2..96992babba 100644 --- a/pvlib/tests/iotools/test_midc.py +++ b/pvlib/tests/iotools/test_midc.py @@ -27,9 +27,9 @@ def test_mapping(): # '?site=UAT&begin=20181018&end=20181019') -def test_midc_format_index(): +def test_midc__format_index(): data = pd.read_csv(MIDC_TESTFILE) - data = midc.format_index(data) + data = midc._format_index(data) start = pd.Timestamp("20181014 00:00") start = start.tz_localize("MST") end = pd.Timestamp("20181014 23:59") @@ -39,16 +39,16 @@ def test_midc_format_index(): assert data.index[-1] == end -def test_midc_format_index_tz_conversion(): +def test_midc__format_index_tz_conversion(): data = pd.read_csv(MIDC_TESTFILE) data = data.rename(columns={'MST': 'PST'}) - data = midc.format_index(data) + data = midc._format_index(data) assert data.index[0].tz == pytz.timezone('Etc/GMT+8') -def test_midc_format_index_raw(): +def test_midc__format_index_raw(): data = pd.read_csv(MIDC_RAW_TESTFILE) - data = midc.format_index_raw(data) + data = midc._format_index_raw(data) start = pd.Timestamp('20181018 00:00') start = start.tz_localize('MST') end = pd.Timestamp('20181018 23:59') diff --git a/pvlib/tests/iotools/test_srml.py b/pvlib/tests/iotools/test_srml.py index 556bd7111d..14fa299d84 100644 --- a/pvlib/tests/iotools/test_srml.py +++ b/pvlib/tests/iotools/test_srml.py @@ -60,8 +60,8 @@ def test_read_srml_dt_index(url, year, month): ('2001', '2001'), ('2017', 'dni_7') ]) -def test_map_columns(column, expected): - assert srml.map_columns(column) == expected +def test__map_columns(column, expected): + assert srml._map_columns(column) == expected @pytest.mark.remote_data diff --git a/pvlib/tests/test_scaling.py b/pvlib/tests/test_scaling.py index 344e2209b5..136e516331 100644 --- a/pvlib/tests/test_scaling.py +++ b/pvlib/tests/test_scaling.py @@ -99,27 +99,27 @@ def expect_vr(): 2.0726611, 1.5653324, 1.2812714, 1.1389995]) -def test_latlon_to_xy_zero(): +def test__latlon_to_xy_zero(): coord = [0, 0] pos_e = [0, 0] - pos = scaling.latlon_to_xy(coord) + pos = scaling._latlon_to_xy(coord) assert_almost_equal(pos, pos_e, decimal=1) -def test_latlon_to_xy_single(coordinates, positions): +def test__latlon_to_xy_single(coordinates, positions): # Must test against central value, because latlon_to_xy uses the mean coord = coordinates[1] - pos = scaling.latlon_to_xy(coord) + pos = scaling._latlon_to_xy(coord) assert_almost_equal(pos, positions[1], decimal=1) -def test_latlon_to_xy_array(coordinates, positions): - pos = scaling.latlon_to_xy(coordinates) +def test__latlon_to_xy_array(coordinates, positions): + pos = scaling._latlon_to_xy(coordinates) assert_almost_equal(pos, positions, decimal=1) -def test_latlon_to_xy_list(coordinates, positions): - pos = scaling.latlon_to_xy(coordinates.tolist()) +def test__latlon_to_xy_list(coordinates, positions): + pos = scaling._latlon_to_xy(coordinates.tolist()) assert_almost_equal(pos, positions, decimal=1) diff --git a/pvlib/tests/test_spa.py b/pvlib/tests/test_spa.py index c288b04442..21b6fded69 100644 --- a/pvlib/tests/test_spa.py +++ b/pvlib/tests/test_spa.py @@ -76,24 +76,6 @@ theta0 = 90 - e0 Gamma = 14.340241 Phi = 194.340241 -year = 1985 -month = 2 -year_array = np.array([-499, 500, 1000, 1500, 1800, 1860, 1900, 1950, - 1970, 1985, 1990, 2000, 2005, 2050, 2150]) -# `month_array` is used with `year_array` in `test_calculate_deltat`. -# Both arrays need to have the same length for the test, hence the duplicates. -month_array = np.array([1, 2, 3, 4, 5, 6, 6, 7, 8, 9, 10, 11, 12, 12, 12]) -dt_actual = 54.413442486 -dt_actual_array = np.array([1.7184831e+04, 5.7088051e+03, 1.5730419e+03, - 1.9801820e+02, 1.3596506e+01, 7.8316585e+00, - -2.1171894e+00, 2.9289261e+01, 4.0824887e+01, - 5.4724581e+01, 5.7426651e+01, 6.4108015e+01, - 6.5038015e+01, 9.4952955e+01, 3.3050693e+02]) -mix_year_array = np.full((10), year) -mix_month_array = np.full((10), month) -mix_year_actual = np.full((10), dt_actual) -mix_month_actual = mix_year_actual - class SpaBase: """Test functions common to numpy and numba spa""" @@ -359,19 +341,6 @@ def test_earthsun_distance(self): result = self.spa.earthsun_distance(unixtimes, 64.0, 1) assert_almost_equal(R, result, 6) - def test_calculate_deltat(self): - result_mix_year = self.spa.calculate_deltat(mix_year_array, month) - assert_almost_equal(mix_year_actual, result_mix_year) - - result_mix_month = self.spa.calculate_deltat(year, mix_month_array) - assert_almost_equal(mix_month_actual, result_mix_month) - - result_array = self.spa.calculate_deltat(year_array, month_array) - assert_almost_equal(dt_actual_array, result_array, 3) - - result_scalar = self.spa.calculate_deltat(year, month) - assert_almost_equal(dt_actual, result_scalar) - class NumpySpaTest(unittest.TestCase, SpaBase): """Import spa without compiling to numba then run tests""" From 37e6ea488f411a5ba4d7967dd8d45e88a2d47121 Mon Sep 17 00:00:00 2001 From: Kevin Anderson Date: Mon, 12 Jun 2023 13:21:51 -0400 Subject: [PATCH 08/13] whatsnew --- docs/sphinx/source/whatsnew/v0.9.6.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/sphinx/source/whatsnew/v0.9.6.rst b/docs/sphinx/source/whatsnew/v0.9.6.rst index 889f456e0c..6d331eedb0 100644 --- a/docs/sphinx/source/whatsnew/v0.9.6.rst +++ b/docs/sphinx/source/whatsnew/v0.9.6.rst @@ -12,6 +12,14 @@ Breaking Changes (:issue:`1724`, :pull:`1739`) * For consistency with the rest of pvlib, the ``tilt`` parameter is renamed to ``surface_tilt`` in :py:func:`pvlib.soiling.hsu`. (:issue:`1717`, :pull:`1738`) +* The largely undocumented :py:mod:`pvlib.spa` module is now private. Users + are directed to the wrapper functions in :py:mod:`pvlib.solarposition` instead. + Additionally, the :py:func:`~pvlib.solarposition.calculate_deltat` function + has been moved to :py:mod:`pvlib.solarposition`. (:issue:`1756`, :pull:`1769`) +* Several other undocumented functions in :py:mod:`pvlib.iotools.midc`, + :py:mod:`pvlib.iotools.srml`, :py:mod:`pvlib.iotools.surfrad`, and + :py:mod:`pvlib.scaling` are now private as well. (:issue:`1756`, :pull:`1769`) + Deprecations ~~~~~~~~~~~~ From 538191dc17dcb7fbf9708d0c7e4c91909188c758 Mon Sep 17 00:00:00 2001 From: Kevin Anderson Date: Tue, 20 Jun 2023 11:02:42 -0400 Subject: [PATCH 09/13] Revert "rename `spa.py` to `_spa.py`" This reverts commit eb2e23a74ce4bae639ad64f865a99c10cecdfe27. --- .../sphinx/source/reference/solarposition.rst | 8 ++++ pvlib/__init__.py | 1 + pvlib/solarposition.py | 10 ++--- pvlib/{_spa.py => spa.py} | 1 + pvlib/tests/test_solarposition.py | 43 +++---------------- pvlib/tests/test_spa.py | 4 +- 6 files changed, 22 insertions(+), 45 deletions(-) rename pvlib/{_spa.py => spa.py} (99%) diff --git a/docs/sphinx/source/reference/solarposition.rst b/docs/sphinx/source/reference/solarposition.rst index b3c4c0e351..f17a9abaec 100644 --- a/docs/sphinx/source/reference/solarposition.rst +++ b/docs/sphinx/source/reference/solarposition.rst @@ -43,6 +43,14 @@ Functions for calculating sunrise, sunset and transit times. solarposition.sun_rise_set_transit_geometric +The spa module contains the implementation of the built-in NREL SPA +algorithm. + +.. autosummary:: + :toctree: generated/ + + spa + Correlations and analytical expressions for low precision solar position calculations. diff --git a/pvlib/__init__.py b/pvlib/__init__.py index ba39d0c15a..dfd5f1dc2a 100644 --- a/pvlib/__init__.py +++ b/pvlib/__init__.py @@ -22,6 +22,7 @@ snow, soiling, solarposition, + spa, temperature, tools, tracking, diff --git a/pvlib/solarposition.py b/pvlib/solarposition.py index 3fd200ba14..653c04b50b 100644 --- a/pvlib/solarposition.py +++ b/pvlib/solarposition.py @@ -247,10 +247,10 @@ def spa_c(time, latitude, longitude, pressure=101325, altitude=0, def _spa_python_import(how): """Compile spa.py appropriately""" - from pvlib import _spa + from pvlib import spa # check to see if the spa module was compiled with numba - using_numba = _spa.USE_NUMBA + using_numba = spa.USE_NUMBA if how == 'numpy' and using_numba: # the spa module was compiled to numba code, so we need to @@ -259,19 +259,19 @@ def _spa_python_import(how): # to not compile with numba warnings.warn('Reloading spa to use numpy') os.environ['PVLIB_USE_NUMBA'] = '0' - _spa = reload(_spa) + spa = reload(spa) del os.environ['PVLIB_USE_NUMBA'] elif how == 'numba' and not using_numba: # The spa module was not compiled to numba code, so set # PVLIB_USE_NUMBA so it does compile to numba on reload. warnings.warn('Reloading spa to use numba') os.environ['PVLIB_USE_NUMBA'] = '1' - _spa = reload(_spa) + spa = reload(spa) del os.environ['PVLIB_USE_NUMBA'] elif how != 'numba' and how != 'numpy': raise ValueError("how must be either 'numba' or 'numpy'") - return _spa + return spa def spa_python(time, latitude, longitude, diff --git a/pvlib/_spa.py b/pvlib/spa.py similarity index 99% rename from pvlib/_spa.py rename to pvlib/spa.py index 9502514325..7d355142ec 100644 --- a/pvlib/_spa.py +++ b/pvlib/spa.py @@ -1245,3 +1245,4 @@ def earthsun_distance(unixtime, delta_t, numthreads): return R + diff --git a/pvlib/tests/test_solarposition.py b/pvlib/tests/test_solarposition.py index 8d031cc241..36af6afff0 100644 --- a/pvlib/tests/test_solarposition.py +++ b/pvlib/tests/test_solarposition.py @@ -10,7 +10,7 @@ import pytest from pvlib.location import Location -from pvlib import solarposition, _spa +from pvlib import solarposition, spa from .conftest import requires_ephem, requires_spa_c, requires_numba @@ -560,11 +560,11 @@ def test_declination(): times = pd.date_range(start="1/1/2015 0:00", end="12/31/2015 23:00", freq="H") atmos_refract = 0.5667 - delta_t = solarposition.calculate_deltat(times.year, times.month) + delta_t = spa.calculate_deltat(times.year, times.month) unixtime = np.array([calendar.timegm(t.timetuple()) for t in times]) - _, _, declination = _spa.solar_position( - unixtime, 37.8, -122.25, 100, 1013.25, 25, delta_t, atmos_refract, - sst=True) + _, _, declination = spa.solar_position(unixtime, 37.8, -122.25, 100, + 1013.25, 25, delta_t, atmos_refract, + sst=True) declination = np.deg2rad(declination) declination_rng = declination.max() - declination.min() declination_1 = solarposition.declination_cooper69(times.dayofyear) @@ -769,36 +769,3 @@ def test_spa_python_numba_physical_dst(expected_solpos, golden): temperature=11, delta_t=67, atmos_refract=0.5667, how='numpy', numthreads=1) - - -def test_calculate_deltat(): - year_array = np.array([-499, 500, 1000, 1500, 1800, 1860, 1900, 1950, - 1970, 1985, 1990, 2000, 2005, 2050, 2150]) - # `month_array` is used with `year_array` in `test_calculate_deltat`. - # Both arrays need to have the same length for the test, hence the duplicates. - month_array = np.array([1, 2, 3, 4, 5, 6, 6, 7, 8, 9, 10, 11, 12, 12, 12]) - dt_actual = 54.413442486 - dt_actual_array = np.array([1.7184831e+04, 5.7088051e+03, 1.5730419e+03, - 1.9801820e+02, 1.3596506e+01, 7.8316585e+00, - -2.1171894e+00, 2.9289261e+01, 4.0824887e+01, - 5.4724581e+01, 5.7426651e+01, 6.4108015e+01, - 6.5038015e+01, 9.4952955e+01, 3.3050693e+02]) - year = 1985 - month = 2 - - mix_year_array = np.full((10), year) - mix_month_array = np.full((10), month) - mix_year_actual = np.full((10), dt_actual) - mix_month_actual = mix_year_actual - - result_mix_year = solarposition.calculate_deltat(mix_year_array, month) - np.testing.assert_almost_equal(mix_year_actual, result_mix_year) - - result_mix_month = solarposition.calculate_deltat(year, mix_month_array) - np.testing.assert_almost_equal(mix_month_actual, result_mix_month) - - result_array = solarposition.calculate_deltat(year_array, month_array) - np.testing.assert_almost_equal(dt_actual_array, result_array, 3) - - result_scalar = solarposition.calculate_deltat(year, month) - np.testing.assert_almost_equal(dt_actual, result_scalar) diff --git a/pvlib/tests/test_spa.py b/pvlib/tests/test_spa.py index 21b6fded69..de5caa2724 100644 --- a/pvlib/tests/test_spa.py +++ b/pvlib/tests/test_spa.py @@ -347,7 +347,7 @@ class NumpySpaTest(unittest.TestCase, SpaBase): @classmethod def setUpClass(self): os.environ['PVLIB_USE_NUMBA'] = '0' - import pvlib._spa as spa + import pvlib.spa as spa spa = reload(spa) self.spa = spa @@ -367,7 +367,7 @@ class NumbaSpaTest(unittest.TestCase, SpaBase): def setUpClass(self): os.environ['PVLIB_USE_NUMBA'] = '1' if numba_version_int >= 17: - import pvlib._spa as spa + import pvlib.spa as spa spa = reload(spa) self.spa = spa From 9a869464ad2caf6738a8d9a0dcd95657f6e174b9 Mon Sep 17 00:00:00 2001 From: Kevin Anderson Date: Tue, 20 Jun 2023 11:02:50 -0400 Subject: [PATCH 10/13] Revert "move calculate_deltat from pvlib.spa to pvlib.solarposition" This reverts commit f02266f2cf755fe17ef6270f731d1ac074928714. --- .../sphinx/source/reference/solarposition.rst | 2 +- pvlib/solarposition.py | 157 +----------------- pvlib/spa.py | 129 ++++++++++++++ 3 files changed, 136 insertions(+), 152 deletions(-) diff --git a/docs/sphinx/source/reference/solarposition.rst b/docs/sphinx/source/reference/solarposition.rst index f17a9abaec..dbe93d5392 100644 --- a/docs/sphinx/source/reference/solarposition.rst +++ b/docs/sphinx/source/reference/solarposition.rst @@ -29,7 +29,7 @@ Additional functions for quantities closely related to solar position. solarposition.calc_time solarposition.pyephem_earthsun_distance solarposition.nrel_earthsun_distance - solarposition.calculate_deltat + spa.calculate_deltat Functions for calculating sunrise, sunset and transit times. diff --git a/pvlib/solarposition.py b/pvlib/solarposition.py index 653c04b50b..cdcacd7ec6 100644 --- a/pvlib/solarposition.py +++ b/pvlib/solarposition.py @@ -306,7 +306,7 @@ def spa_python(time, latitude, longitude, avg. yearly air temperature in degrees C. delta_t : float, optional, default 67.0 Difference between terrestrial time and UT1. - If delta_t is None, uses :py:func:`calculate_deltat` + If delta_t is None, uses spa.calculate_deltat using time.year and time.month from pandas.DatetimeIndex. For most simulations the default delta_t is sufficient. *Note: delta_t = None will break code using nrel_numba, @@ -370,7 +370,7 @@ def spa_python(time, latitude, longitude, spa = _spa_python_import(how) - delta_t = delta_t or calculate_deltat(time.year, time.month) + delta_t = delta_t or spa.calculate_deltat(time.year, time.month) app_zenith, zenith, app_elevation, elevation, azimuth, eot = \ spa.solar_position(unixtime, lat, lon, elev, pressure, temperature, @@ -412,7 +412,7 @@ def sun_rise_set_transit_spa(times, latitude, longitude, how='numpy', to machine code and run them multithreaded. delta_t : float, optional, default 67.0 Difference between terrestrial time and UT1. - If delta_t is None, uses :py:func:`calculate_deltat` + If delta_t is None, uses spa.calculate_deltat using times.year and times.month from pandas.DatetimeIndex. For most simulations the default delta_t is sufficient. *Note: delta_t = None will break code using nrel_numba, @@ -449,7 +449,7 @@ def sun_rise_set_transit_spa(times, latitude, longitude, how='numpy', spa = _spa_python_import(how) - delta_t = delta_t or calculate_deltat(times.year, times.month) + delta_t = delta_t or spa.calculate_deltat(times.year, times.month) transit, sunrise, sunset = spa.transit_sunrise_sunset( unixtime, lat, lon, delta_t, numthreads) @@ -974,7 +974,7 @@ def nrel_earthsun_distance(time, how='numpy', delta_t=67.0, numthreads=4): delta_t : float, optional, default 67.0 Difference between terrestrial time and UT1. - If delta_t is None, uses :py:func:`calculate_deltat` + If delta_t is None, uses spa.calculate_deltat using time.year and time.month from pandas.DatetimeIndex. For most simulations the default delta_t is sufficient. *Note: delta_t = None will break code using nrel_numba, @@ -1005,7 +1005,7 @@ def nrel_earthsun_distance(time, how='numpy', delta_t=67.0, numthreads=4): spa = _spa_python_import(how) - delta_t = delta_t or calculate_deltat(time.year, time.month) + delta_t = delta_t or spa.calculate_deltat(time.year, time.month) dist = spa.earthsun_distance(unixtime, delta_t, numthreads) @@ -1476,148 +1476,3 @@ def sun_rise_set_transit_geometric(times, latitude, longitude, declination, sunset = _local_times_from_hours_since_midnight(times, sunset_hour) transit = _local_times_from_hours_since_midnight(times, transit_hour) return sunrise, sunset, transit - - -def calculate_deltat(year, month): - """Calculate the difference between Terrestrial Dynamical Time (TD) - and Universal Time (UT). - - Note: This function is not yet compatible for calculations using - Numba. - - Parameters - ---------- - year : numeric - Calendar year for which to calculate the time offset - month : numeric - Calendar month (1-12) for which to calculate the time offset - - Returns - ------- - deltat : numeric - - References - ---------- - .. [1] `NASA GSFC: Polynomial Expressions for Delta T (ΔT) - `_ - """ - - plw = 'Deltat is unknown for years before -1999 and after 3000. ' \ - 'Delta values will be calculated, but the calculations ' \ - 'are not intended to be used for these years.' - - try: - if np.any((year > 3000) | (year < -1999)): - warnings.warn(plw) - except ValueError: - if (year > 3000) | (year < -1999): - warnings.warn(plw) - except TypeError: - return 0 - - y = year + (month - 0.5)/12 - - deltat = np.where(year < -500, - - -20+32*((y-1820)/100)**2, 0) - - deltat = np.where((-500 <= year) & (year < 500), - - 10583.6-1014.41*(y/100) - + 33.78311*(y/100)**2 - - 5.952053*(y/100)**3 - - 0.1798452*(y/100)**4 - + 0.022174192*(y/100)**5 - + 0.0090316521*(y/100)**6, deltat) - - deltat = np.where((500 <= year) & (year < 1600), - - 1574.2-556.01*((y-1000)/100) - + 71.23472*((y-1000)/100)**2 - + 0.319781*((y-1000)/100)**3 - - 0.8503463*((y-1000)/100)**4 - - 0.005050998*((y-1000)/100)**5 - + 0.0083572073*((y-1000)/100)**6, deltat) - - deltat = np.where((1600 <= year) & (year < 1700), - - 120-0.9808*(y-1600) - - 0.01532*(y-1600)**2 - + (y-1600)**3/7129, deltat) - - deltat = np.where((1700 <= year) & (year < 1800), - - 8.83+0.1603*(y-1700) - - 0.0059285*(y-1700)**2 - + 0.00013336*(y-1700)**3 - - (y-1700)**4/1174000, deltat) - - deltat = np.where((1800 <= year) & (year < 1860), - - 13.72-0.332447*(y-1800) - + 0.0068612*(y-1800)**2 - + 0.0041116*(y-1800)**3 - - 0.00037436*(y-1800)**4 - + 0.0000121272*(y-1800)**5 - - 0.0000001699*(y-1800)**6 - + 0.000000000875*(y-1800)**7, deltat) - - deltat = np.where((1860 <= year) & (year < 1900), - - 7.62+0.5737*(y-1860) - - 0.251754*(y-1860)**2 - + 0.01680668*(y-1860)**3 - - 0.0004473624*(y-1860)**4 - + (y-1860)**5/233174, deltat) - - deltat = np.where((1900 <= year) & (year < 1920), - - -2.79+1.494119*(y-1900) - - 0.0598939*(y-1900)**2 - + 0.0061966*(y-1900)**3 - - 0.000197*(y-1900)**4, deltat) - - deltat = np.where((1920 <= year) & (year < 1941), - - 21.20+0.84493*(y-1920) - - 0.076100*(y-1920)**2 - + 0.0020936*(y-1920)**3, deltat) - - deltat = np.where((1941 <= year) & (year < 1961), - - 29.07+0.407*(y-1950) - - (y-1950)**2/233 - + (y-1950)**3/2547, deltat) - - deltat = np.where((1961 <= year) & (year < 1986), - - 45.45+1.067*(y-1975) - - (y-1975)**2/260 - - (y-1975)**3/718, deltat) - - deltat = np.where((1986 <= year) & (year < 2005), - - 63.86+0.3345*(y-2000) - - 0.060374*(y-2000)**2 - + 0.0017275*(y-2000)**3 - + 0.000651814*(y-2000)**4 - + 0.00002373599*(y-2000)**5, deltat) - - deltat = np.where((2005 <= year) & (year < 2050), - - 62.92+0.32217*(y-2000) - + 0.005589*(y-2000)**2, deltat) - - deltat = np.where((2050 <= year) & (year < 2150), - - -20+32*((y-1820)/100)**2 - - 0.5628*(2150-y), deltat) - - deltat = np.where(year >= 2150, - - -20+32*((y-1820)/100)**2, deltat) - - deltat = deltat.item() if np.isscalar(year) & np.isscalar(month)\ - else deltat - - return deltat diff --git a/pvlib/spa.py b/pvlib/spa.py index 7d355142ec..763145cbc4 100644 --- a/pvlib/spa.py +++ b/pvlib/spa.py @@ -1246,3 +1246,132 @@ def earthsun_distance(unixtime, delta_t, numthreads): return R +def calculate_deltat(year, month): + """Calculate the difference between Terrestrial Dynamical Time (TD) + and Universal Time (UT). + + Note: This function is not yet compatible for calculations using + Numba. + + Equations taken from http://eclipse.gsfc.nasa.gov/SEcat5/deltatpoly.html + """ + + plw = 'Deltat is unknown for years before -1999 and after 3000. ' \ + 'Delta values will be calculated, but the calculations ' \ + 'are not intended to be used for these years.' + + try: + if np.any((year > 3000) | (year < -1999)): + warnings.warn(plw) + except ValueError: + if (year > 3000) | (year < -1999): + warnings.warn(plw) + except TypeError: + return 0 + + y = year + (month - 0.5)/12 + + deltat = np.where(year < -500, + + -20+32*((y-1820)/100)**2, 0) + + deltat = np.where((-500 <= year) & (year < 500), + + 10583.6-1014.41*(y/100) + + 33.78311*(y/100)**2 + - 5.952053*(y/100)**3 + - 0.1798452*(y/100)**4 + + 0.022174192*(y/100)**5 + + 0.0090316521*(y/100)**6, deltat) + + deltat = np.where((500 <= year) & (year < 1600), + + 1574.2-556.01*((y-1000)/100) + + 71.23472*((y-1000)/100)**2 + + 0.319781*((y-1000)/100)**3 + - 0.8503463*((y-1000)/100)**4 + - 0.005050998*((y-1000)/100)**5 + + 0.0083572073*((y-1000)/100)**6, deltat) + + deltat = np.where((1600 <= year) & (year < 1700), + + 120-0.9808*(y-1600) + - 0.01532*(y-1600)**2 + + (y-1600)**3/7129, deltat) + + deltat = np.where((1700 <= year) & (year < 1800), + + 8.83+0.1603*(y-1700) + - 0.0059285*(y-1700)**2 + + 0.00013336*(y-1700)**3 + - (y-1700)**4/1174000, deltat) + + deltat = np.where((1800 <= year) & (year < 1860), + + 13.72-0.332447*(y-1800) + + 0.0068612*(y-1800)**2 + + 0.0041116*(y-1800)**3 + - 0.00037436*(y-1800)**4 + + 0.0000121272*(y-1800)**5 + - 0.0000001699*(y-1800)**6 + + 0.000000000875*(y-1800)**7, deltat) + + deltat = np.where((1860 <= year) & (year < 1900), + + 7.62+0.5737*(y-1860) + - 0.251754*(y-1860)**2 + + 0.01680668*(y-1860)**3 + - 0.0004473624*(y-1860)**4 + + (y-1860)**5/233174, deltat) + + deltat = np.where((1900 <= year) & (year < 1920), + + -2.79+1.494119*(y-1900) + - 0.0598939*(y-1900)**2 + + 0.0061966*(y-1900)**3 + - 0.000197*(y-1900)**4, deltat) + + deltat = np.where((1920 <= year) & (year < 1941), + + 21.20+0.84493*(y-1920) + - 0.076100*(y-1920)**2 + + 0.0020936*(y-1920)**3, deltat) + + deltat = np.where((1941 <= year) & (year < 1961), + + 29.07+0.407*(y-1950) + - (y-1950)**2/233 + + (y-1950)**3/2547, deltat) + + deltat = np.where((1961 <= year) & (year < 1986), + + 45.45+1.067*(y-1975) + - (y-1975)**2/260 + - (y-1975)**3/718, deltat) + + deltat = np.where((1986 <= year) & (year < 2005), + + 63.86+0.3345*(y-2000) + - 0.060374*(y-2000)**2 + + 0.0017275*(y-2000)**3 + + 0.000651814*(y-2000)**4 + + 0.00002373599*(y-2000)**5, deltat) + + deltat = np.where((2005 <= year) & (year < 2050), + + 62.92+0.32217*(y-2000) + + 0.005589*(y-2000)**2, deltat) + + deltat = np.where((2050 <= year) & (year < 2150), + + -20+32*((y-1820)/100)**2 + - 0.5628*(2150-y), deltat) + + deltat = np.where(year >= 2150, + + -20+32*((y-1820)/100)**2, deltat) + + deltat = deltat.item() if np.isscalar(year) & np.isscalar(month)\ + else deltat + + return deltat From 2972b959636d964819f91c87c9484374d6e9fc21 Mon Sep 17 00:00:00 2001 From: Kevin Anderson Date: Tue, 20 Jun 2023 11:04:04 -0400 Subject: [PATCH 11/13] remove SPA mentions from whatsnew --- docs/sphinx/source/whatsnew/v0.9.6.rst | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/docs/sphinx/source/whatsnew/v0.9.6.rst b/docs/sphinx/source/whatsnew/v0.9.6.rst index 6d331eedb0..bca89842c8 100644 --- a/docs/sphinx/source/whatsnew/v0.9.6.rst +++ b/docs/sphinx/source/whatsnew/v0.9.6.rst @@ -12,13 +12,9 @@ Breaking Changes (:issue:`1724`, :pull:`1739`) * For consistency with the rest of pvlib, the ``tilt`` parameter is renamed to ``surface_tilt`` in :py:func:`pvlib.soiling.hsu`. (:issue:`1717`, :pull:`1738`) -* The largely undocumented :py:mod:`pvlib.spa` module is now private. Users - are directed to the wrapper functions in :py:mod:`pvlib.solarposition` instead. - Additionally, the :py:func:`~pvlib.solarposition.calculate_deltat` function - has been moved to :py:mod:`pvlib.solarposition`. (:issue:`1756`, :pull:`1769`) -* Several other undocumented functions in :py:mod:`pvlib.iotools.midc`, +* Several undocumented functions in :py:mod:`pvlib.iotools.midc`, :py:mod:`pvlib.iotools.srml`, :py:mod:`pvlib.iotools.surfrad`, and - :py:mod:`pvlib.scaling` are now private as well. (:issue:`1756`, :pull:`1769`) + :py:mod:`pvlib.scaling` are now private. (:issue:`1756`, :pull:`1769`) Deprecations From c4a1adfe972c2ec7ee442c193217b8c36c60f621 Mon Sep 17 00:00:00 2001 From: Kevin Anderson Date: Tue, 20 Jun 2023 11:06:37 -0400 Subject: [PATCH 12/13] put back deltaT test --- pvlib/tests/test_spa.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/pvlib/tests/test_spa.py b/pvlib/tests/test_spa.py index de5caa2724..00b30f46d2 100644 --- a/pvlib/tests/test_spa.py +++ b/pvlib/tests/test_spa.py @@ -76,6 +76,24 @@ theta0 = 90 - e0 Gamma = 14.340241 Phi = 194.340241 +year = 1985 +month = 2 +year_array = np.array([-499, 500, 1000, 1500, 1800, 1860, 1900, 1950, + 1970, 1985, 1990, 2000, 2005, 2050, 2150]) +# `month_array` is used with `year_array` in `test_calculate_deltat`. +# Both arrays need to have the same length for the test, hence the duplicates. +month_array = np.array([1, 2, 3, 4, 5, 6, 6, 7, 8, 9, 10, 11, 12, 12, 12]) +dt_actual = 54.413442486 +dt_actual_array = np.array([1.7184831e+04, 5.7088051e+03, 1.5730419e+03, + 1.9801820e+02, 1.3596506e+01, 7.8316585e+00, + -2.1171894e+00, 2.9289261e+01, 4.0824887e+01, + 5.4724581e+01, 5.7426651e+01, 6.4108015e+01, + 6.5038015e+01, 9.4952955e+01, 3.3050693e+02]) +mix_year_array = np.full((10), year) +mix_month_array = np.full((10), month) +mix_year_actual = np.full((10), dt_actual) +mix_month_actual = mix_year_actual + class SpaBase: """Test functions common to numpy and numba spa""" @@ -341,6 +359,19 @@ def test_earthsun_distance(self): result = self.spa.earthsun_distance(unixtimes, 64.0, 1) assert_almost_equal(R, result, 6) + def test_calculate_deltat(self): + result_mix_year = self.spa.calculate_deltat(mix_year_array, month) + assert_almost_equal(mix_year_actual, result_mix_year) + + result_mix_month = self.spa.calculate_deltat(year, mix_month_array) + assert_almost_equal(mix_month_actual, result_mix_month) + + result_array = self.spa.calculate_deltat(year_array, month_array) + assert_almost_equal(dt_actual_array, result_array, 3) + + result_scalar = self.spa.calculate_deltat(year, month) + assert_almost_equal(dt_actual, result_scalar) + class NumpySpaTest(unittest.TestCase, SpaBase): """Import spa without compiling to numba then run tests""" From a6b28b01dac4103876f5cd7a3af705ce66a7dbde Mon Sep 17 00:00:00 2001 From: Kevin Anderson Date: Fri, 23 Jun 2023 14:55:09 -0400 Subject: [PATCH 13/13] make `scaling.latlon_to_xy` public again, and include in docs --- docs/sphinx/source/reference/scaling.rst | 1 + docs/sphinx/source/whatsnew/v0.9.6.rst | 4 ++-- pvlib/scaling.py | 2 +- pvlib/tests/test_scaling.py | 16 ++++++++-------- 4 files changed, 12 insertions(+), 11 deletions(-) diff --git a/docs/sphinx/source/reference/scaling.rst b/docs/sphinx/source/reference/scaling.rst index 75599282e2..34f1ad3a5c 100644 --- a/docs/sphinx/source/reference/scaling.rst +++ b/docs/sphinx/source/reference/scaling.rst @@ -9,3 +9,4 @@ Methods for manipulating irradiance for temporal or spatial considerations :toctree: generated/ scaling.wvm + scaling.latlon_to_xy diff --git a/docs/sphinx/source/whatsnew/v0.9.6.rst b/docs/sphinx/source/whatsnew/v0.9.6.rst index 8594f41613..e5cb007a06 100644 --- a/docs/sphinx/source/whatsnew/v0.9.6.rst +++ b/docs/sphinx/source/whatsnew/v0.9.6.rst @@ -18,8 +18,8 @@ Breaking Changes * For consistency with the rest of pvlib, the ``tilt`` parameter is renamed to ``surface_tilt`` in :py:func:`pvlib.soiling.hsu`. (:issue:`1717`, :pull:`1738`) * Several undocumented functions in :py:mod:`pvlib.iotools.midc`, - :py:mod:`pvlib.iotools.srml`, :py:mod:`pvlib.iotools.surfrad`, and - :py:mod:`pvlib.scaling` are now private. (:issue:`1756`, :pull:`1769`) + :py:mod:`pvlib.iotools.srml`, and :py:mod:`pvlib.iotools.surfrad` + are now private. (:issue:`1756`, :pull:`1769`) * The following, originally deprecated in :ref:`whatsnew_0900`, is now removed: (:pull:`1770`) - The :py:class:`pvlib.tracking.SingleAxisTracker` class diff --git a/pvlib/scaling.py b/pvlib/scaling.py index 2c6fe9e1fd..dca2ca4935 100644 --- a/pvlib/scaling.py +++ b/pvlib/scaling.py @@ -146,7 +146,7 @@ def fn(x): return vr -def _latlon_to_xy(coordinates): +def latlon_to_xy(coordinates): """ Convert latitude and longitude in degrees to a coordinate system measured in meters from zero deg latitude, zero deg longitude. diff --git a/pvlib/tests/test_scaling.py b/pvlib/tests/test_scaling.py index 136e516331..344e2209b5 100644 --- a/pvlib/tests/test_scaling.py +++ b/pvlib/tests/test_scaling.py @@ -99,27 +99,27 @@ def expect_vr(): 2.0726611, 1.5653324, 1.2812714, 1.1389995]) -def test__latlon_to_xy_zero(): +def test_latlon_to_xy_zero(): coord = [0, 0] pos_e = [0, 0] - pos = scaling._latlon_to_xy(coord) + pos = scaling.latlon_to_xy(coord) assert_almost_equal(pos, pos_e, decimal=1) -def test__latlon_to_xy_single(coordinates, positions): +def test_latlon_to_xy_single(coordinates, positions): # Must test against central value, because latlon_to_xy uses the mean coord = coordinates[1] - pos = scaling._latlon_to_xy(coord) + pos = scaling.latlon_to_xy(coord) assert_almost_equal(pos, positions[1], decimal=1) -def test__latlon_to_xy_array(coordinates, positions): - pos = scaling._latlon_to_xy(coordinates) +def test_latlon_to_xy_array(coordinates, positions): + pos = scaling.latlon_to_xy(coordinates) assert_almost_equal(pos, positions, decimal=1) -def test__latlon_to_xy_list(coordinates, positions): - pos = scaling._latlon_to_xy(coordinates.tolist()) +def test_latlon_to_xy_list(coordinates, positions): + pos = scaling.latlon_to_xy(coordinates.tolist()) assert_almost_equal(pos, positions, decimal=1)