pvlib 0.9.5__py3-none-any.whl → 0.10.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pvlib/__init__.py +3 -2
- pvlib/atmosphere.py +6 -171
- pvlib/bifacial/infinite_sheds.py +30 -267
- pvlib/bifacial/utils.py +225 -5
- pvlib/data/test_psm3_2017.csv +17521 -17521
- pvlib/data/test_read_psm3.csv +17522 -17522
- pvlib/data/test_read_pvgis_horizon.csv +49 -0
- pvlib/data/variables_style_rules.csv +3 -0
- pvlib/iam.py +17 -4
- pvlib/inverter.py +6 -1
- pvlib/iotools/__init__.py +7 -2
- pvlib/iotools/acis.py +516 -0
- pvlib/iotools/midc.py +4 -4
- pvlib/iotools/psm3.py +32 -31
- pvlib/iotools/pvgis.py +84 -28
- pvlib/iotools/sodapro.py +8 -6
- pvlib/iotools/srml.py +121 -18
- pvlib/iotools/surfrad.py +2 -2
- pvlib/iotools/tmy.py +146 -102
- pvlib/irradiance.py +151 -0
- pvlib/ivtools/sde.py +11 -7
- pvlib/ivtools/sdm.py +16 -10
- pvlib/ivtools/utils.py +6 -6
- pvlib/location.py +3 -2
- pvlib/modelchain.py +67 -70
- pvlib/pvsystem.py +160 -532
- pvlib/shading.py +41 -0
- pvlib/singlediode.py +215 -65
- pvlib/soiling.py +3 -3
- pvlib/spa.py +327 -368
- pvlib/spectrum/__init__.py +8 -2
- pvlib/spectrum/mismatch.py +335 -0
- pvlib/temperature.py +1 -8
- pvlib/tests/bifacial/test_infinite_sheds.py +0 -111
- pvlib/tests/bifacial/test_utils.py +101 -4
- pvlib/tests/conftest.py +0 -31
- pvlib/tests/iotools/test_acis.py +213 -0
- pvlib/tests/iotools/test_midc.py +6 -6
- pvlib/tests/iotools/test_psm3.py +3 -3
- pvlib/tests/iotools/test_pvgis.py +21 -14
- pvlib/tests/iotools/test_sodapro.py +1 -1
- pvlib/tests/iotools/test_srml.py +71 -6
- pvlib/tests/iotools/test_tmy.py +43 -8
- pvlib/tests/ivtools/test_sde.py +19 -17
- pvlib/tests/ivtools/test_sdm.py +9 -4
- pvlib/tests/test_atmosphere.py +6 -62
- pvlib/tests/test_iam.py +12 -0
- pvlib/tests/test_irradiance.py +40 -2
- pvlib/tests/test_location.py +1 -1
- pvlib/tests/test_modelchain.py +33 -76
- pvlib/tests/test_pvsystem.py +366 -201
- pvlib/tests/test_shading.py +28 -0
- pvlib/tests/test_singlediode.py +166 -30
- pvlib/tests/test_soiling.py +8 -7
- pvlib/tests/test_spa.py +6 -7
- pvlib/tests/test_spectrum.py +145 -1
- pvlib/tests/test_temperature.py +0 -7
- pvlib/tests/test_tools.py +25 -0
- pvlib/tests/test_tracking.py +0 -149
- pvlib/tools.py +26 -1
- pvlib/tracking.py +1 -269
- {pvlib-0.9.5.dist-info → pvlib-0.10.0.dist-info}/METADATA +1 -9
- {pvlib-0.9.5.dist-info → pvlib-0.10.0.dist-info}/RECORD +67 -68
- pvlib/forecast.py +0 -1211
- pvlib/iotools/ecmwf_macc.py +0 -312
- pvlib/tests/iotools/test_ecmwf_macc.py +0 -162
- pvlib/tests/test_forecast.py +0 -228
- {pvlib-0.9.5.dist-info → pvlib-0.10.0.dist-info}/AUTHORS.md +0 -0
- {pvlib-0.9.5.dist-info → pvlib-0.10.0.dist-info}/LICENSE +0 -0
- {pvlib-0.9.5.dist-info → pvlib-0.10.0.dist-info}/WHEEL +0 -0
- {pvlib-0.9.5.dist-info → pvlib-0.10.0.dist-info}/top_level.txt +0 -0
pvlib/iotools/ecmwf_macc.py
DELETED
|
@@ -1,312 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Read data from ECMWF MACC Reanalysis.
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
import threading
|
|
6
|
-
import pandas as pd
|
|
7
|
-
|
|
8
|
-
try:
|
|
9
|
-
import netCDF4
|
|
10
|
-
except ImportError:
|
|
11
|
-
class netCDF4:
|
|
12
|
-
@staticmethod
|
|
13
|
-
def Dataset(*a, **kw):
|
|
14
|
-
raise ImportError(
|
|
15
|
-
'Reading ECMWF data requires netCDF4 to be installed.')
|
|
16
|
-
|
|
17
|
-
try:
|
|
18
|
-
from ecmwfapi import ECMWFDataServer
|
|
19
|
-
except ImportError:
|
|
20
|
-
def ECMWFDataServer(*a, **kw):
|
|
21
|
-
raise ImportError(
|
|
22
|
-
'To download data from ECMWF requires the API client.\nSee https:/'
|
|
23
|
-
'/confluence.ecmwf.int/display/WEBAPI/Access+ECMWF+Public+Datasets'
|
|
24
|
-
)
|
|
25
|
-
|
|
26
|
-
#: map of ECMWF MACC parameter keynames and codes used in API
|
|
27
|
-
PARAMS = {
|
|
28
|
-
"tcwv": "137.128",
|
|
29
|
-
"aod550": "207.210",
|
|
30
|
-
'aod469': '213.210',
|
|
31
|
-
'aod670': '214.210',
|
|
32
|
-
'aod865': '215.210',
|
|
33
|
-
"aod1240": "216.210",
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
def _ecmwf(server, startdate, enddate, params, targetname):
|
|
38
|
-
# see http://apps.ecmwf.int/datasets/data/macc-reanalysis/levtype=sfc/
|
|
39
|
-
server.retrieve({
|
|
40
|
-
"class": "mc",
|
|
41
|
-
"dataset": "macc",
|
|
42
|
-
"date": "%s/to/%s" % (startdate, enddate),
|
|
43
|
-
"expver": "rean",
|
|
44
|
-
"grid": "0.75/0.75",
|
|
45
|
-
"levtype": "sfc",
|
|
46
|
-
"param": params,
|
|
47
|
-
"step": "3/6/9/12/15/18/21/24",
|
|
48
|
-
"stream": "oper",
|
|
49
|
-
"format": "netcdf",
|
|
50
|
-
"time": "00:00:00",
|
|
51
|
-
"type": "fc",
|
|
52
|
-
"target": targetname,
|
|
53
|
-
})
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
def get_ecmwf_macc(filename, params, start, end, lookup_params=True,
|
|
57
|
-
server=None, target=_ecmwf):
|
|
58
|
-
"""
|
|
59
|
-
Download data from ECMWF MACC Reanalysis API.
|
|
60
|
-
|
|
61
|
-
Parameters
|
|
62
|
-
----------
|
|
63
|
-
filename : str
|
|
64
|
-
full path of file where to save data, ``.nc`` appended if not given
|
|
65
|
-
params : str or sequence of str
|
|
66
|
-
keynames of parameter[s] to download
|
|
67
|
-
start : datetime.datetime or datetime.date
|
|
68
|
-
UTC date
|
|
69
|
-
end : datetime.datetime or datetime.date
|
|
70
|
-
UTC date
|
|
71
|
-
lookup_params : bool, default True
|
|
72
|
-
optional flag, if ``False``, then codes are already formatted
|
|
73
|
-
server : ecmwfapi.api.ECMWFDataServer
|
|
74
|
-
optionally provide a server object, default is ``None``
|
|
75
|
-
target : callable
|
|
76
|
-
optional function that calls ``server.retrieve`` to pass to thread
|
|
77
|
-
|
|
78
|
-
Returns
|
|
79
|
-
-------
|
|
80
|
-
t : thread
|
|
81
|
-
a thread object, use it to check status by calling `t.is_alive()`
|
|
82
|
-
|
|
83
|
-
Notes
|
|
84
|
-
-----
|
|
85
|
-
To download data from ECMWF requires the API client and a registration
|
|
86
|
-
key. Please read the documentation in `Access ECMWF Public Datasets
|
|
87
|
-
<https://confluence.ecmwf.int/display/WEBAPI/Access+ECMWF+Public+Datasets>`_.
|
|
88
|
-
Follow the instructions in step 4 and save the ECMWF registration key
|
|
89
|
-
as `$HOME/.ecmwfapirc` or set `ECMWF_API_KEY` as the path to the key.
|
|
90
|
-
|
|
91
|
-
This function returns a daemon thread that runs in the background. Exiting
|
|
92
|
-
Python will kill this thread, however this thread will not block the main
|
|
93
|
-
thread or other threads. This thread will terminate when the file is
|
|
94
|
-
downloaded or if the thread raises an unhandled exception. You may submit
|
|
95
|
-
multiple requests simultaneously to break up large downloads. You can also
|
|
96
|
-
check the status and retrieve downloads online at
|
|
97
|
-
http://apps.ecmwf.int/webmars/joblist/. This is useful if you kill the
|
|
98
|
-
thread. Downloads expire after 24 hours.
|
|
99
|
-
|
|
100
|
-
.. warning:: Your request may be queued online for an hour or more before
|
|
101
|
-
it begins to download
|
|
102
|
-
|
|
103
|
-
Precipitable water :math:`P_{wat}` is equivalent to the total column of
|
|
104
|
-
water vapor (TCWV), but the units given by ECMWF MACC Reanalysis are kg/m^2
|
|
105
|
-
at STP (1-atm, 25-C). Divide by ten to convert to centimeters of
|
|
106
|
-
precipitable water:
|
|
107
|
-
|
|
108
|
-
.. math::
|
|
109
|
-
P_{wat} \\left( \\text{cm} \\right) \
|
|
110
|
-
= TCWV \\left( \\frac{\\text{kg}}{\\text{m}^2} \\right) \
|
|
111
|
-
\\frac{100 \\frac{\\text{cm}}{\\text{m}}} \
|
|
112
|
-
{1000 \\frac{\\text{kg}}{\\text{m}^3}}
|
|
113
|
-
|
|
114
|
-
The keynames available for the ``params`` argument are given by
|
|
115
|
-
:const:`pvlib.iotools.ecmwf_macc.PARAMS` which maps the keys to codes used
|
|
116
|
-
in the API. The following keynames are available:
|
|
117
|
-
|
|
118
|
-
======= =========================================
|
|
119
|
-
keyname description
|
|
120
|
-
======= =========================================
|
|
121
|
-
tcwv total column water vapor in kg/m^2 at STP
|
|
122
|
-
aod550 aerosol optical depth measured at 550-nm
|
|
123
|
-
aod469 aerosol optical depth measured at 469-nm
|
|
124
|
-
aod670 aerosol optical depth measured at 670-nm
|
|
125
|
-
aod865 aerosol optical depth measured at 865-nm
|
|
126
|
-
aod1240 aerosol optical depth measured at 1240-nm
|
|
127
|
-
======= =========================================
|
|
128
|
-
|
|
129
|
-
If ``lookup_params`` is ``False`` then ``params`` must contain the codes
|
|
130
|
-
preformatted according to the ECMWF MACC Reanalysis API. This is useful if
|
|
131
|
-
you want to retrieve codes that are not mapped in
|
|
132
|
-
:const:`pvlib.iotools.ecmwf_macc.PARAMS`.
|
|
133
|
-
|
|
134
|
-
Specify a custom ``target`` function to modify how the ECMWF API function
|
|
135
|
-
``server.retrieve`` is called. The ``target`` function must have the
|
|
136
|
-
following signature in which the parameter definitions are similar to
|
|
137
|
-
:func:`pvlib.iotools.get_ecmwf_macc`. ::
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
target(server, startdate, enddate, params, filename) -> None
|
|
141
|
-
|
|
142
|
-
Examples
|
|
143
|
-
--------
|
|
144
|
-
Retrieve the AOD measured at 550-nm and the total column of water vapor for
|
|
145
|
-
November 1, 2012.
|
|
146
|
-
|
|
147
|
-
>>> from datetime import date
|
|
148
|
-
>>> from pvlib.iotools import get_ecmwf_macc
|
|
149
|
-
>>> filename = 'aod_tcwv_20121101.nc' # .nc extension added if missing
|
|
150
|
-
>>> params = ('aod550', 'tcwv')
|
|
151
|
-
>>> start = end = date(2012, 11, 1)
|
|
152
|
-
>>> t = get_ecmwf_macc(filename, params, start, end)
|
|
153
|
-
>>> t.is_alive()
|
|
154
|
-
True
|
|
155
|
-
|
|
156
|
-
"""
|
|
157
|
-
if not filename.endswith('nc'):
|
|
158
|
-
filename += '.nc'
|
|
159
|
-
if lookup_params:
|
|
160
|
-
try:
|
|
161
|
-
params = '/'.join(PARAMS.get(p) for p in params)
|
|
162
|
-
except TypeError:
|
|
163
|
-
params = PARAMS.get(params)
|
|
164
|
-
startdate = start.strftime('%Y-%m-%d')
|
|
165
|
-
enddate = end.strftime('%Y-%m-%d')
|
|
166
|
-
if not server:
|
|
167
|
-
server = ECMWFDataServer()
|
|
168
|
-
t = threading.Thread(target=target, daemon=True,
|
|
169
|
-
args=(server, startdate, enddate, params, filename))
|
|
170
|
-
t.start()
|
|
171
|
-
return t
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
class ECMWF_MACC(object):
|
|
175
|
-
"""container for ECMWF MACC reanalysis data"""
|
|
176
|
-
|
|
177
|
-
TCWV = 'tcwv' # total column water vapor in kg/m^2 at (1-atm,25-degC)
|
|
178
|
-
|
|
179
|
-
def __init__(self, filename):
|
|
180
|
-
self.data = netCDF4.Dataset(filename)
|
|
181
|
-
# data variables and dimensions
|
|
182
|
-
variables = set(self.data.variables.keys())
|
|
183
|
-
dimensions = set(self.data.dimensions.keys())
|
|
184
|
-
self.keys = tuple(variables - dimensions)
|
|
185
|
-
# size of lat/lon dimensions
|
|
186
|
-
self.lat_size = self.data.dimensions['latitude'].size
|
|
187
|
-
self.lon_size = self.data.dimensions['longitude'].size
|
|
188
|
-
# spatial resolution in degrees
|
|
189
|
-
self.delta_lat = -180.0 / (self.lat_size - 1) # from north to south
|
|
190
|
-
self.delta_lon = 360.0 / self.lon_size # from west to east
|
|
191
|
-
# time resolution in hours
|
|
192
|
-
self.time_size = self.data.dimensions['time'].size
|
|
193
|
-
self.start_time = self.data['time'][0]
|
|
194
|
-
self.end_time = self.data['time'][-1]
|
|
195
|
-
self.time_range = self.end_time - self.start_time
|
|
196
|
-
self.delta_time = self.time_range / (self.time_size - 1)
|
|
197
|
-
|
|
198
|
-
def get_nearest_indices(self, latitude, longitude):
|
|
199
|
-
"""
|
|
200
|
-
Get nearest indices to (latitude, longitude).
|
|
201
|
-
|
|
202
|
-
Parmaeters
|
|
203
|
-
----------
|
|
204
|
-
latitude : float
|
|
205
|
-
Latitude in degrees
|
|
206
|
-
longitude : float
|
|
207
|
-
Longitude in degrees
|
|
208
|
-
|
|
209
|
-
Returns
|
|
210
|
-
-------
|
|
211
|
-
idx_lat : int
|
|
212
|
-
index of nearest latitude
|
|
213
|
-
idx_lon : int
|
|
214
|
-
index of nearest longitude
|
|
215
|
-
"""
|
|
216
|
-
# index of nearest latitude
|
|
217
|
-
idx_lat = int(round((latitude - 90.0) / self.delta_lat))
|
|
218
|
-
# avoid out of bounds latitudes
|
|
219
|
-
if idx_lat < 0:
|
|
220
|
-
idx_lat = 0 # if latitude == 90, north pole
|
|
221
|
-
elif idx_lat > self.lat_size:
|
|
222
|
-
idx_lat = self.lat_size # if latitude == -90, south pole
|
|
223
|
-
# adjust longitude from -180/180 to 0/360
|
|
224
|
-
longitude = longitude % 360.0
|
|
225
|
-
# index of nearest longitude
|
|
226
|
-
idx_lon = int(round(longitude / self.delta_lon)) % self.lon_size
|
|
227
|
-
return idx_lat, idx_lon
|
|
228
|
-
|
|
229
|
-
def interp_data(self, latitude, longitude, utc_time, param):
|
|
230
|
-
"""
|
|
231
|
-
Interpolate ``param`` values to ``utc_time`` using indices nearest to
|
|
232
|
-
(``latitude, longitude``).
|
|
233
|
-
|
|
234
|
-
Parmaeters
|
|
235
|
-
----------
|
|
236
|
-
latitude : float
|
|
237
|
-
Latitude in degrees
|
|
238
|
-
longitude : float
|
|
239
|
-
Longitude in degrees
|
|
240
|
-
utc_time : datetime.datetime or datetime.date
|
|
241
|
-
Naive or UTC date or datetime to interpolate
|
|
242
|
-
param : str
|
|
243
|
-
Name of the parameter to interpolate from the data
|
|
244
|
-
|
|
245
|
-
Returns
|
|
246
|
-
-------
|
|
247
|
-
Interpolated ``param`` value at (``utc_time, latitude, longitude``)
|
|
248
|
-
|
|
249
|
-
Examples
|
|
250
|
-
--------
|
|
251
|
-
Use this to get a single value of a parameter in the data at a specific
|
|
252
|
-
time and set of (latitude, longitude) coordinates.
|
|
253
|
-
|
|
254
|
-
>>> from datetime import datetime
|
|
255
|
-
>>> from pvlib.iotools import ecmwf_macc
|
|
256
|
-
>>> data = ecmwf_macc.ECMWF_MACC('aod_tcwv_20121101.nc')
|
|
257
|
-
>>> dt = datetime(2012, 11, 1, 11, 33, 1)
|
|
258
|
-
>>> data.interp_data(38.2, -122.1, dt, 'aod550')
|
|
259
|
-
"""
|
|
260
|
-
nctime = self.data['time'] # time
|
|
261
|
-
ilat, ilon = self.get_nearest_indices(latitude, longitude)
|
|
262
|
-
# time index before
|
|
263
|
-
before = netCDF4.date2index(utc_time, nctime, select='before')
|
|
264
|
-
fbefore = self.data[param][before, ilat, ilon]
|
|
265
|
-
fafter = self.data[param][before + 1, ilat, ilon]
|
|
266
|
-
dt_num = netCDF4.date2num(utc_time, nctime.units)
|
|
267
|
-
time_ratio = (dt_num - nctime[before]) / self.delta_time
|
|
268
|
-
return fbefore + (fafter - fbefore) * time_ratio
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
def read_ecmwf_macc(filename, latitude, longitude, utc_time_range=None):
|
|
272
|
-
"""
|
|
273
|
-
Read data from ECMWF MACC reanalysis netCDF4 file.
|
|
274
|
-
|
|
275
|
-
Parameters
|
|
276
|
-
----------
|
|
277
|
-
filename : string
|
|
278
|
-
full path to netCDF4 data file.
|
|
279
|
-
latitude : float
|
|
280
|
-
latitude in degrees
|
|
281
|
-
longitude : float
|
|
282
|
-
longitude in degrees
|
|
283
|
-
utc_time_range : sequence of datetime.datetime
|
|
284
|
-
pair of start and end naive or UTC date-times
|
|
285
|
-
|
|
286
|
-
Returns
|
|
287
|
-
-------
|
|
288
|
-
data : pandas.DataFrame
|
|
289
|
-
dataframe for specified range of UTC date-times
|
|
290
|
-
"""
|
|
291
|
-
ecmwf_macc = ECMWF_MACC(filename)
|
|
292
|
-
try:
|
|
293
|
-
ilat, ilon = ecmwf_macc.get_nearest_indices(latitude, longitude)
|
|
294
|
-
nctime = ecmwf_macc.data['time']
|
|
295
|
-
if utc_time_range:
|
|
296
|
-
start_idx = netCDF4.date2index(
|
|
297
|
-
utc_time_range[0], nctime, select='nearest')
|
|
298
|
-
end_idx = netCDF4.date2index(
|
|
299
|
-
utc_time_range[-1], nctime, select='nearest')
|
|
300
|
-
time_slice = slice(start_idx, end_idx + 1)
|
|
301
|
-
else:
|
|
302
|
-
time_slice = slice(0, ecmwf_macc.time_size)
|
|
303
|
-
times = netCDF4.num2date(nctime[time_slice], nctime.units)
|
|
304
|
-
df = {k: ecmwf_macc.data[k][time_slice, ilat, ilon]
|
|
305
|
-
for k in ecmwf_macc.keys}
|
|
306
|
-
if ECMWF_MACC.TCWV in df:
|
|
307
|
-
# convert total column water vapor in kg/m^2 at (1-atm, 25-degC) to
|
|
308
|
-
# precipitable water in cm
|
|
309
|
-
df['precipitable_water'] = df[ECMWF_MACC.TCWV] / 10.0
|
|
310
|
-
finally:
|
|
311
|
-
ecmwf_macc.data.close()
|
|
312
|
-
return pd.DataFrame(df, index=times.astype('datetime64[s]'))
|
|
@@ -1,162 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
tests for :mod:`pvlib.iotools.ecmwf_macc`
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
import os
|
|
6
|
-
import datetime
|
|
7
|
-
import numpy as np
|
|
8
|
-
import pytest
|
|
9
|
-
from ..conftest import requires_netCDF4, DATA_DIR
|
|
10
|
-
from pvlib.iotools import ecmwf_macc
|
|
11
|
-
|
|
12
|
-
TESTDATA = 'aod550_tcwv_20121101_test.nc'
|
|
13
|
-
|
|
14
|
-
# for creating test data
|
|
15
|
-
START = END = datetime.date(2012, 11, 1)
|
|
16
|
-
DATAFILE = 'aod550_tcwv_20121101.nc'
|
|
17
|
-
RESIZE = 4
|
|
18
|
-
LON_BND = (0, 360.0)
|
|
19
|
-
LAT_BND = (90, -90)
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
@pytest.fixture
|
|
23
|
-
def expected_test_data():
|
|
24
|
-
return DATA_DIR / TESTDATA
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
@requires_netCDF4
|
|
28
|
-
def test_get_nearest_indices(expected_test_data):
|
|
29
|
-
"""Test getting indices given latitude, longitude from ECMWF_MACC data."""
|
|
30
|
-
data = ecmwf_macc.ECMWF_MACC(expected_test_data)
|
|
31
|
-
ilat, ilon = data.get_nearest_indices(38, -122)
|
|
32
|
-
assert ilat == 17
|
|
33
|
-
assert ilon == 79
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
@requires_netCDF4
|
|
37
|
-
def test_interp_data(expected_test_data):
|
|
38
|
-
"""Test interpolating UTC time from ECMWF_MACC data."""
|
|
39
|
-
data = ecmwf_macc.ECMWF_MACC(expected_test_data)
|
|
40
|
-
test9am = data.interp_data(
|
|
41
|
-
38, -122, datetime.datetime(2012, 11, 1, 9, 0, 0), 'aod550')
|
|
42
|
-
assert np.isclose(test9am, data.data.variables['aod550'][2, 17, 79])
|
|
43
|
-
test12pm = data.interp_data(
|
|
44
|
-
38, -122, datetime.datetime(2012, 11, 1, 12, 0, 0), 'aod550')
|
|
45
|
-
assert np.isclose(test12pm, data.data.variables['aod550'][3, 17, 79])
|
|
46
|
-
test113301 = data.interp_data(
|
|
47
|
-
38, -122, datetime.datetime(2012, 11, 1, 11, 33, 1), 'aod550')
|
|
48
|
-
expected = test9am + (2 + (33 + 1 / 60) / 60) / 3 * (test12pm - test9am)
|
|
49
|
-
assert np.isclose(test113301, expected) # 0.15515305836696536
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
@requires_netCDF4
|
|
53
|
-
def test_read_ecmwf_macc(expected_test_data):
|
|
54
|
-
"""Test reading ECMWF_MACC data from netCDF4 file."""
|
|
55
|
-
data = ecmwf_macc.read_ecmwf_macc(
|
|
56
|
-
expected_test_data, 38, -122)
|
|
57
|
-
expected_times = [
|
|
58
|
-
1351738800, 1351749600, 1351760400, 1351771200, 1351782000, 1351792800,
|
|
59
|
-
1351803600, 1351814400]
|
|
60
|
-
assert np.allclose(data.index.view(np.int64) // 1000000000, expected_times)
|
|
61
|
-
expected_aod = np.array([
|
|
62
|
-
0.39531226, 0.22371339, 0.18373083, 0.15010143, 0.130809, 0.11172834,
|
|
63
|
-
0.09741255, 0.0921606])
|
|
64
|
-
expected_tcwv = np.array([
|
|
65
|
-
26.56172238, 22.75563109, 19.37884304, 16.19186269, 13.31990346,
|
|
66
|
-
11.65635338, 10.94879802, 10.55725756])
|
|
67
|
-
assert np.allclose(data.aod550.values, expected_aod)
|
|
68
|
-
assert np.allclose(data.tcwv.values, expected_tcwv)
|
|
69
|
-
assert np.allclose(data.precipitable_water.values, expected_tcwv / 10.0)
|
|
70
|
-
datetimes = (datetime.datetime(2012, 11, 1, 9, 0, 0),
|
|
71
|
-
datetime.datetime(2012, 11, 1, 12, 0, 0))
|
|
72
|
-
data_9am_12pm = ecmwf_macc.read_ecmwf_macc(
|
|
73
|
-
expected_test_data, 38, -122, datetimes)
|
|
74
|
-
assert np.allclose(data_9am_12pm.aod550.values, expected_aod[2:4])
|
|
75
|
-
assert np.allclose(data_9am_12pm.tcwv.values, expected_tcwv[2:4])
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
def _create_test_data(datafile=DATAFILE, testfile=TESTDATA, start=START,
|
|
79
|
-
end=END, resize=RESIZE): # pragma: no cover
|
|
80
|
-
"""
|
|
81
|
-
Create test data from downloaded data.
|
|
82
|
-
|
|
83
|
-
Downloaded data from ECMWF for a single day is 3MB. This creates a subset
|
|
84
|
-
of the downloaded data that is only 100kb.
|
|
85
|
-
"""
|
|
86
|
-
|
|
87
|
-
import netCDF4
|
|
88
|
-
|
|
89
|
-
if not os.path.exists(datafile):
|
|
90
|
-
ecmwf_macc.get_ecmwf_macc(datafile, ("aod550", "tcwv"), start, end)
|
|
91
|
-
|
|
92
|
-
data = netCDF4.Dataset(datafile)
|
|
93
|
-
testdata = netCDF4.Dataset(testfile, 'w', format="NETCDF3_64BIT_OFFSET")
|
|
94
|
-
|
|
95
|
-
# attributes
|
|
96
|
-
testdata.Conventions = data.Conventions
|
|
97
|
-
testdata.history = "intentionally blank"
|
|
98
|
-
|
|
99
|
-
# longitude
|
|
100
|
-
lon_name = 'longitude'
|
|
101
|
-
lon_test = data.variables[lon_name][::resize]
|
|
102
|
-
lon_size = lon_test.size
|
|
103
|
-
lon = testdata.createDimension(lon_name, lon_size)
|
|
104
|
-
assert not lon.isunlimited()
|
|
105
|
-
assert lon_test[0] == LON_BND[0]
|
|
106
|
-
assert (LON_BND[-1] - lon_test[-1]) == (LON_BND[-1] / lon_size)
|
|
107
|
-
longitudes = testdata.createVariable(lon_name, "f4", (lon_name,))
|
|
108
|
-
longitudes.units = data.variables[lon_name].units
|
|
109
|
-
longitudes.long_name = lon_name
|
|
110
|
-
longitudes[:] = lon_test
|
|
111
|
-
|
|
112
|
-
# latitude
|
|
113
|
-
lat_name = 'latitude'
|
|
114
|
-
lat_test = data.variables[lat_name][::resize]
|
|
115
|
-
lat = testdata.createDimension(lat_name, lat_test.size)
|
|
116
|
-
assert not lat.isunlimited()
|
|
117
|
-
assert lat_test[0] == LAT_BND[0]
|
|
118
|
-
assert lat_test[-1] == LAT_BND[-1]
|
|
119
|
-
latitudes = testdata.createVariable(lat_name, "f4", (lat_name,))
|
|
120
|
-
latitudes.units = data.variables[lat_name].units
|
|
121
|
-
latitudes.long_name = lat_name
|
|
122
|
-
latitudes[:] = lat_test
|
|
123
|
-
|
|
124
|
-
# time
|
|
125
|
-
time_name = 'time'
|
|
126
|
-
time_test = data.variables[time_name][:]
|
|
127
|
-
time = testdata.createDimension(time_name, None)
|
|
128
|
-
assert time.isunlimited()
|
|
129
|
-
times = testdata.createVariable(time_name, 'i4', (time_name,))
|
|
130
|
-
times.units = data.variables[time_name].units
|
|
131
|
-
times.long_name = time_name
|
|
132
|
-
times.calendar = data.variables[time_name].calendar
|
|
133
|
-
times[:] = time_test
|
|
134
|
-
|
|
135
|
-
# aod
|
|
136
|
-
aod_name = 'aod550'
|
|
137
|
-
aod_vars = data.variables[aod_name]
|
|
138
|
-
aod_dims = (time_name, lat_name, lon_name)
|
|
139
|
-
aod_fill_value = getattr(aod_vars, '_FillValue')
|
|
140
|
-
aods = testdata.createVariable(
|
|
141
|
-
aod_name, 'i2', aod_dims, fill_value=aod_fill_value)
|
|
142
|
-
for attr in aod_vars.ncattrs():
|
|
143
|
-
if attr.startswith('_'):
|
|
144
|
-
continue
|
|
145
|
-
setattr(aods, attr, getattr(aod_vars, attr))
|
|
146
|
-
aods[:] = aod_vars[:, ::resize, ::resize]
|
|
147
|
-
|
|
148
|
-
# tcwv
|
|
149
|
-
tcwv_name = 'tcwv'
|
|
150
|
-
tcwv_vars = data.variables[tcwv_name]
|
|
151
|
-
tcwv_dims = (time_name, lat_name, lon_name)
|
|
152
|
-
tcwv_fill_value = getattr(tcwv_vars, '_FillValue')
|
|
153
|
-
tcwvs = testdata.createVariable(
|
|
154
|
-
tcwv_name, 'i2', tcwv_dims, fill_value=tcwv_fill_value)
|
|
155
|
-
for attr in tcwv_vars.ncattrs():
|
|
156
|
-
if attr.startswith('_'):
|
|
157
|
-
continue
|
|
158
|
-
setattr(tcwvs, attr, getattr(tcwv_vars, attr))
|
|
159
|
-
tcwvs[:] = tcwv_vars[:, ::resize, ::resize]
|
|
160
|
-
|
|
161
|
-
data.close()
|
|
162
|
-
testdata.close()
|
pvlib/tests/test_forecast.py
DELETED
|
@@ -1,228 +0,0 @@
|
|
|
1
|
-
from datetime import datetime, timedelta, timezone
|
|
2
|
-
import warnings
|
|
3
|
-
|
|
4
|
-
import pandas as pd
|
|
5
|
-
|
|
6
|
-
import pytest
|
|
7
|
-
from numpy.testing import assert_allclose
|
|
8
|
-
|
|
9
|
-
from .conftest import (
|
|
10
|
-
requires_siphon,
|
|
11
|
-
has_siphon,
|
|
12
|
-
skip_windows,
|
|
13
|
-
requires_recent_cftime
|
|
14
|
-
)
|
|
15
|
-
from .conftest import RERUNS, RERUNS_DELAY
|
|
16
|
-
|
|
17
|
-
from pvlib._deprecation import pvlibDeprecationWarning
|
|
18
|
-
|
|
19
|
-
pytestmark = pytest.mark.skipif(not has_siphon, reason='requires siphon')
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
if has_siphon:
|
|
23
|
-
with warnings.catch_warnings():
|
|
24
|
-
# don't emit import warning
|
|
25
|
-
warnings.simplefilter("ignore")
|
|
26
|
-
from pvlib.forecast import GFS, HRRR_ESRL, HRRR, NAM, NDFD, RAP
|
|
27
|
-
|
|
28
|
-
# setup times and location to be tested. Tucson, AZ
|
|
29
|
-
_latitude = 32.2
|
|
30
|
-
_longitude = -110.9
|
|
31
|
-
_tz = 'US/Arizona'
|
|
32
|
-
_start = pd.Timestamp.now(tz=_tz)
|
|
33
|
-
_end = _start + pd.Timedelta(days=1)
|
|
34
|
-
_modelclasses = [
|
|
35
|
-
GFS, NAM, HRRR, NDFD, RAP,
|
|
36
|
-
pytest.param(
|
|
37
|
-
HRRR_ESRL, marks=[
|
|
38
|
-
skip_windows,
|
|
39
|
-
pytest.mark.xfail(reason="HRRR_ESRL is unreliable"),
|
|
40
|
-
pytest.mark.timeout(timeout=60),
|
|
41
|
-
pytest.mark.filterwarnings('ignore:.*experimental')])]
|
|
42
|
-
_working_models = []
|
|
43
|
-
_variables = ['temp_air', 'wind_speed', 'total_clouds', 'low_clouds',
|
|
44
|
-
'mid_clouds', 'high_clouds', 'dni', 'dhi', 'ghi']
|
|
45
|
-
_nonnan_variables = ['temp_air', 'wind_speed', 'total_clouds', 'dni',
|
|
46
|
-
'dhi', 'ghi']
|
|
47
|
-
else:
|
|
48
|
-
_modelclasses = []
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
# make a model object for each model class
|
|
52
|
-
# get the data for that model and store it in an
|
|
53
|
-
# attribute for further testing
|
|
54
|
-
@requires_siphon
|
|
55
|
-
@pytest.fixture(scope='module', params=_modelclasses)
|
|
56
|
-
def model(request):
|
|
57
|
-
with pytest.warns(pvlibDeprecationWarning):
|
|
58
|
-
amodel = request.param()
|
|
59
|
-
try:
|
|
60
|
-
raw_data = amodel.get_data(_latitude, _longitude, _start, _end)
|
|
61
|
-
except Exception as e:
|
|
62
|
-
warnings.warn('Exception getting data for {}.\n'
|
|
63
|
-
'latitude, longitude, start, end = {} {} {} {}\n{}'
|
|
64
|
-
.format(amodel, _latitude, _longitude, _start, _end, e))
|
|
65
|
-
raw_data = pd.DataFrame() # raw_data.empty will be used later
|
|
66
|
-
amodel.raw_data = raw_data
|
|
67
|
-
return amodel
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
@requires_siphon
|
|
71
|
-
@requires_recent_cftime
|
|
72
|
-
@pytest.mark.remote_data
|
|
73
|
-
@pytest.mark.flaky(reruns=RERUNS, reruns_delay=RERUNS_DELAY)
|
|
74
|
-
def test_process_data(model):
|
|
75
|
-
for how in ['campbell_norman', 'clearsky_scaling']:
|
|
76
|
-
if model.raw_data.empty:
|
|
77
|
-
warnings.warn('Could not test {} process_data with how={} '
|
|
78
|
-
'because raw_data was empty'.format(model, how))
|
|
79
|
-
continue
|
|
80
|
-
data = model.process_data(model.raw_data, how=how)
|
|
81
|
-
for variable in _nonnan_variables:
|
|
82
|
-
try:
|
|
83
|
-
assert not data[variable].isnull().values.any()
|
|
84
|
-
except AssertionError:
|
|
85
|
-
warnings.warn('{}, {}, data contained null values'
|
|
86
|
-
.format(model, variable))
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
@requires_siphon
|
|
90
|
-
@requires_recent_cftime
|
|
91
|
-
@pytest.mark.remote_data
|
|
92
|
-
@pytest.mark.flaky(reruns=RERUNS, reruns_delay=RERUNS_DELAY)
|
|
93
|
-
def test_bad_kwarg_get_data():
|
|
94
|
-
# For more information on why you would want to pass an unknown keyword
|
|
95
|
-
# argument, see Github issue #745.
|
|
96
|
-
with pytest.warns(pvlibDeprecationWarning):
|
|
97
|
-
amodel = NAM()
|
|
98
|
-
data = amodel.get_data(_latitude, _longitude, _start, _end,
|
|
99
|
-
bad_kwarg=False)
|
|
100
|
-
assert not data.empty
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
@requires_siphon
|
|
104
|
-
@requires_recent_cftime
|
|
105
|
-
@pytest.mark.remote_data
|
|
106
|
-
@pytest.mark.flaky(reruns=RERUNS, reruns_delay=RERUNS_DELAY)
|
|
107
|
-
def test_bad_kwarg_get_processed_data():
|
|
108
|
-
# For more information on why you would want to pass an unknown keyword
|
|
109
|
-
# argument, see Github issue #745.
|
|
110
|
-
with pytest.warns(pvlibDeprecationWarning):
|
|
111
|
-
amodel = NAM()
|
|
112
|
-
data = amodel.get_processed_data(_latitude, _longitude, _start, _end,
|
|
113
|
-
bad_kwarg=False)
|
|
114
|
-
assert not data.empty
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
@requires_siphon
|
|
118
|
-
@requires_recent_cftime
|
|
119
|
-
@pytest.mark.remote_data
|
|
120
|
-
@pytest.mark.flaky(reruns=RERUNS, reruns_delay=RERUNS_DELAY)
|
|
121
|
-
def test_how_kwarg_get_processed_data():
|
|
122
|
-
with pytest.warns(pvlibDeprecationWarning):
|
|
123
|
-
amodel = NAM()
|
|
124
|
-
data = amodel.get_processed_data(_latitude, _longitude, _start, _end,
|
|
125
|
-
how='clearsky_scaling')
|
|
126
|
-
assert not data.empty
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
@requires_siphon
|
|
130
|
-
@requires_recent_cftime
|
|
131
|
-
@pytest.mark.remote_data
|
|
132
|
-
@pytest.mark.flaky(reruns=RERUNS, reruns_delay=RERUNS_DELAY)
|
|
133
|
-
def test_vert_level():
|
|
134
|
-
with pytest.warns(pvlibDeprecationWarning):
|
|
135
|
-
amodel = NAM()
|
|
136
|
-
vert_level = 5000
|
|
137
|
-
amodel.get_processed_data(_latitude, _longitude, _start, _end,
|
|
138
|
-
vert_level=vert_level)
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
@requires_siphon
|
|
142
|
-
@requires_recent_cftime
|
|
143
|
-
@pytest.mark.remote_data
|
|
144
|
-
@pytest.mark.flaky(reruns=RERUNS, reruns_delay=RERUNS_DELAY)
|
|
145
|
-
def test_datetime():
|
|
146
|
-
with pytest.warns(pvlibDeprecationWarning):
|
|
147
|
-
amodel = NAM()
|
|
148
|
-
start = datetime.now(tz=timezone.utc)
|
|
149
|
-
end = start + timedelta(days=1)
|
|
150
|
-
amodel.get_processed_data(_latitude, _longitude, start, end)
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
@requires_siphon
|
|
154
|
-
@requires_recent_cftime
|
|
155
|
-
@pytest.mark.remote_data
|
|
156
|
-
@pytest.mark.flaky(reruns=RERUNS, reruns_delay=RERUNS_DELAY)
|
|
157
|
-
def test_queryvariables():
|
|
158
|
-
with pytest.warns(pvlibDeprecationWarning):
|
|
159
|
-
amodel = GFS()
|
|
160
|
-
new_variables = ['u-component_of_wind_height_above_ground']
|
|
161
|
-
data = amodel.get_data(_latitude, _longitude, _start, _end,
|
|
162
|
-
query_variables=new_variables)
|
|
163
|
-
data['u-component_of_wind_height_above_ground']
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
@requires_siphon
|
|
167
|
-
def test_latest():
|
|
168
|
-
with pytest.warns(pvlibDeprecationWarning):
|
|
169
|
-
GFS(set_type='latest')
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
@requires_siphon
|
|
173
|
-
def test_full():
|
|
174
|
-
with pytest.warns(pvlibDeprecationWarning):
|
|
175
|
-
GFS(set_type='full')
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
def test_temp_convert():
|
|
179
|
-
with pytest.warns(pvlibDeprecationWarning):
|
|
180
|
-
amodel = GFS()
|
|
181
|
-
data = pd.DataFrame({'temp_air': [273.15]})
|
|
182
|
-
data['temp_air'] = amodel.kelvin_to_celsius(data['temp_air'])
|
|
183
|
-
|
|
184
|
-
assert_allclose(data['temp_air'].values, 0.0)
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
# @requires_siphon
|
|
188
|
-
# def test_bounding_box():
|
|
189
|
-
# amodel = GFS()
|
|
190
|
-
# latitude = [31.2,32.2]
|
|
191
|
-
# longitude = [-111.9,-110.9]
|
|
192
|
-
# new_variables = {'temperature':'Temperature_surface'}
|
|
193
|
-
# data = amodel.get_query_data(latitude, longitude, _start, _end,
|
|
194
|
-
# variables=new_variables)
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
def test_set_location():
|
|
198
|
-
with pytest.warns(pvlibDeprecationWarning):
|
|
199
|
-
amodel = GFS()
|
|
200
|
-
latitude, longitude = 32.2, -110.9
|
|
201
|
-
time = 'UTC'
|
|
202
|
-
amodel.set_location(time, latitude, longitude)
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
def test_set_query_time_range_tzfail():
|
|
206
|
-
with pytest.warns(pvlibDeprecationWarning):
|
|
207
|
-
amodel = GFS()
|
|
208
|
-
with pytest.raises(TypeError):
|
|
209
|
-
amodel.set_query_time_range(datetime.now(), datetime.now())
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
def test_cloud_cover_to_transmittance_linear():
|
|
213
|
-
with pytest.warns(pvlibDeprecationWarning):
|
|
214
|
-
amodel = GFS()
|
|
215
|
-
assert_allclose(amodel.cloud_cover_to_transmittance_linear(0), 0.75)
|
|
216
|
-
assert_allclose(amodel.cloud_cover_to_transmittance_linear(100), 0.0)
|
|
217
|
-
assert_allclose(amodel.cloud_cover_to_transmittance_linear(0, 0.5), 0.5)
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
def test_cloud_cover_to_ghi_linear():
|
|
221
|
-
with pytest.warns(pvlibDeprecationWarning):
|
|
222
|
-
amodel = GFS()
|
|
223
|
-
ghi_clear = 1000
|
|
224
|
-
offset = 25
|
|
225
|
-
out = amodel.cloud_cover_to_ghi_linear(0, ghi_clear, offset=offset)
|
|
226
|
-
assert_allclose(out, 1000)
|
|
227
|
-
out = amodel.cloud_cover_to_ghi_linear(100, ghi_clear, offset=offset)
|
|
228
|
-
assert_allclose(out, 250)
|
|
File without changes
|