pvlib 0.11.0a1__py3-none-any.whl → 0.11.2__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/_deprecation.py +73 -0
- pvlib/atmosphere.py +236 -1
- pvlib/bifacial/__init__.py +4 -4
- pvlib/bifacial/loss_models.py +163 -0
- pvlib/clearsky.py +53 -51
- pvlib/data/pvgis_tmy_meta.json +32 -93
- pvlib/data/pvgis_tmy_test.csv +8761 -0
- pvlib/data/tmy_45.000_8.000_2005_2023.csv +8789 -0
- pvlib/data/tmy_45.000_8.000_2005_2023.epw +8768 -0
- pvlib/data/tmy_45.000_8.000_2005_2023.json +1 -0
- pvlib/data/tmy_45.000_8.000_2005_2023.txt +8761 -0
- pvlib/data/tmy_45.000_8.000_userhorizon.json +1 -1
- pvlib/iam.py +4 -4
- pvlib/iotools/midc.py +1 -1
- pvlib/iotools/pvgis.py +39 -13
- pvlib/irradiance.py +237 -173
- pvlib/ivtools/sdm.py +75 -52
- pvlib/location.py +5 -5
- pvlib/modelchain.py +1 -1
- pvlib/pvsystem.py +134 -86
- pvlib/shading.py +8 -8
- pvlib/singlediode.py +1 -1
- pvlib/solarposition.py +101 -80
- pvlib/spa.py +28 -24
- pvlib/spectrum/__init__.py +9 -4
- pvlib/spectrum/irradiance.py +273 -0
- pvlib/spectrum/mismatch.py +118 -508
- pvlib/spectrum/response.py +280 -0
- pvlib/spectrum/spectrl2.py +18 -17
- pvlib/temperature.py +49 -3
- pvlib/tests/bifacial/test_losses_models.py +54 -0
- pvlib/tests/iotools/test_pvgis.py +58 -12
- pvlib/tests/ivtools/test_sdm.py +23 -1
- pvlib/tests/spectrum/__init__.py +0 -0
- pvlib/tests/spectrum/conftest.py +40 -0
- pvlib/tests/spectrum/test_irradiance.py +138 -0
- pvlib/tests/{test_spectrum.py → spectrum/test_mismatch.py} +32 -306
- pvlib/tests/spectrum/test_response.py +124 -0
- pvlib/tests/spectrum/test_spectrl2.py +72 -0
- pvlib/tests/test__deprecation.py +97 -0
- pvlib/tests/test_atmosphere.py +218 -0
- pvlib/tests/test_clearsky.py +44 -26
- pvlib/tests/test_conftest.py +0 -44
- pvlib/tests/test_irradiance.py +62 -16
- pvlib/tests/test_pvsystem.py +17 -1
- pvlib/tests/test_solarposition.py +117 -36
- pvlib/tests/test_spa.py +30 -1
- pvlib/tools.py +26 -2
- pvlib/tracking.py +53 -47
- {pvlib-0.11.0a1.dist-info → pvlib-0.11.2.dist-info}/METADATA +34 -31
- {pvlib-0.11.0a1.dist-info → pvlib-0.11.2.dist-info}/RECORD +55 -47
- {pvlib-0.11.0a1.dist-info → pvlib-0.11.2.dist-info}/WHEEL +1 -1
- pvlib/data/aod550_tcwv_20121101_test.nc +0 -0
- pvlib/data/pvgis_tmy_test.dat +0 -8761
- pvlib/data/tmy_45.000_8.000_2005_2016.csv +0 -8789
- pvlib/data/tmy_45.000_8.000_2005_2016.epw +0 -8768
- pvlib/data/tmy_45.000_8.000_2005_2016.json +0 -1
- pvlib/data/tmy_45.000_8.000_2005_2016.txt +0 -8761
- pvlib/data/variables_style_rules.csv +0 -55
- {pvlib-0.11.0a1.dist-info → pvlib-0.11.2.dist-info}/AUTHORS.md +0 -0
- {pvlib-0.11.0a1.dist-info → pvlib-0.11.2.dist-info}/LICENSE +0 -0
- {pvlib-0.11.0a1.dist-info → pvlib-0.11.2.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
import pandas as pd
|
|
3
|
+
from numpy.testing import assert_allclose, assert_approx_equal, assert_equal
|
|
4
|
+
import numpy as np
|
|
5
|
+
from pvlib import spectrum
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def test_get_example_spectral_response():
|
|
9
|
+
# test that the sample sr is read and interpolated correctly
|
|
10
|
+
sr = spectrum.get_example_spectral_response()
|
|
11
|
+
assert_equal(len(sr), 185)
|
|
12
|
+
assert_equal(np.sum(sr.index), 136900)
|
|
13
|
+
assert_approx_equal(np.sum(sr), 107.6116)
|
|
14
|
+
|
|
15
|
+
wavelength = [270, 850, 950, 1200, 4001]
|
|
16
|
+
expected = [0.0, 0.92778, 1.0, 0.0, 0.0]
|
|
17
|
+
|
|
18
|
+
sr = spectrum.get_example_spectral_response(wavelength)
|
|
19
|
+
assert_equal(len(sr), len(wavelength))
|
|
20
|
+
assert_allclose(sr, expected, rtol=1e-5)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@pytest.fixture
|
|
24
|
+
def sr_and_eqe_fixture():
|
|
25
|
+
# Just some arbitrary data for testing the conversion functions
|
|
26
|
+
df = pd.DataFrame(
|
|
27
|
+
columns=("wavelength", "quantum_efficiency", "spectral_response"),
|
|
28
|
+
data=[
|
|
29
|
+
# nm, [0,1], A/W
|
|
30
|
+
[300, 0.85, 0.205671370402405],
|
|
31
|
+
[350, 0.86, 0.242772872514211],
|
|
32
|
+
[400, 0.87, 0.280680929019753],
|
|
33
|
+
[450, 0.88, 0.319395539919029],
|
|
34
|
+
[500, 0.89, 0.358916705212040],
|
|
35
|
+
[550, 0.90, 0.399244424898786],
|
|
36
|
+
[600, 0.91, 0.440378698979267],
|
|
37
|
+
[650, 0.92, 0.482319527453483],
|
|
38
|
+
[700, 0.93, 0.525066910321434],
|
|
39
|
+
[750, 0.94, 0.568620847583119],
|
|
40
|
+
[800, 0.95, 0.612981339238540],
|
|
41
|
+
[850, 0.90, 0.617014111207215],
|
|
42
|
+
[900, 0.80, 0.580719163489143],
|
|
43
|
+
[950, 0.70, 0.536358671833723],
|
|
44
|
+
[1000, 0.6, 0.483932636240953],
|
|
45
|
+
[1050, 0.4, 0.338752845368667],
|
|
46
|
+
],
|
|
47
|
+
)
|
|
48
|
+
df.set_index("wavelength", inplace=True)
|
|
49
|
+
return df
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def test_sr_to_qe(sr_and_eqe_fixture):
|
|
53
|
+
# vector type
|
|
54
|
+
qe = spectrum.sr_to_qe(
|
|
55
|
+
sr_and_eqe_fixture["spectral_response"].values,
|
|
56
|
+
sr_and_eqe_fixture.index.values, # wavelength, nm
|
|
57
|
+
)
|
|
58
|
+
assert_allclose(qe, sr_and_eqe_fixture["quantum_efficiency"])
|
|
59
|
+
# pandas series type
|
|
60
|
+
# note: output Series' name should match the input
|
|
61
|
+
qe = spectrum.sr_to_qe(
|
|
62
|
+
sr_and_eqe_fixture["spectral_response"]
|
|
63
|
+
)
|
|
64
|
+
pd.testing.assert_series_equal(
|
|
65
|
+
qe, sr_and_eqe_fixture["quantum_efficiency"],
|
|
66
|
+
check_names=False
|
|
67
|
+
)
|
|
68
|
+
assert qe.name == "spectral_response"
|
|
69
|
+
# series normalization
|
|
70
|
+
qe = spectrum.sr_to_qe(
|
|
71
|
+
sr_and_eqe_fixture["spectral_response"] * 10, normalize=True
|
|
72
|
+
)
|
|
73
|
+
pd.testing.assert_series_equal(
|
|
74
|
+
qe,
|
|
75
|
+
sr_and_eqe_fixture["quantum_efficiency"]
|
|
76
|
+
/ max(sr_and_eqe_fixture["quantum_efficiency"]),
|
|
77
|
+
check_names=False,
|
|
78
|
+
)
|
|
79
|
+
# error on lack of wavelength parameter if no pandas object is provided
|
|
80
|
+
with pytest.raises(TypeError, match="must have an '.index' attribute"):
|
|
81
|
+
_ = spectrum.sr_to_qe(sr_and_eqe_fixture["spectral_response"].values)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def test_qe_to_sr(sr_and_eqe_fixture):
|
|
85
|
+
# vector type
|
|
86
|
+
sr = spectrum.qe_to_sr(
|
|
87
|
+
sr_and_eqe_fixture["quantum_efficiency"].values,
|
|
88
|
+
sr_and_eqe_fixture.index.values, # wavelength, nm
|
|
89
|
+
)
|
|
90
|
+
assert_allclose(sr, sr_and_eqe_fixture["spectral_response"])
|
|
91
|
+
# pandas series type
|
|
92
|
+
# note: output Series' name should match the input
|
|
93
|
+
sr = spectrum.qe_to_sr(
|
|
94
|
+
sr_and_eqe_fixture["quantum_efficiency"]
|
|
95
|
+
)
|
|
96
|
+
pd.testing.assert_series_equal(
|
|
97
|
+
sr, sr_and_eqe_fixture["spectral_response"],
|
|
98
|
+
check_names=False
|
|
99
|
+
)
|
|
100
|
+
assert sr.name == "quantum_efficiency"
|
|
101
|
+
# series normalization
|
|
102
|
+
sr = spectrum.qe_to_sr(
|
|
103
|
+
sr_and_eqe_fixture["quantum_efficiency"] * 10, normalize=True
|
|
104
|
+
)
|
|
105
|
+
pd.testing.assert_series_equal(
|
|
106
|
+
sr,
|
|
107
|
+
sr_and_eqe_fixture["spectral_response"]
|
|
108
|
+
/ max(sr_and_eqe_fixture["spectral_response"]),
|
|
109
|
+
check_names=False,
|
|
110
|
+
)
|
|
111
|
+
# error on lack of wavelength parameter if no pandas object is provided
|
|
112
|
+
with pytest.raises(TypeError, match="must have an '.index' attribute"):
|
|
113
|
+
_ = spectrum.qe_to_sr(
|
|
114
|
+
sr_and_eqe_fixture["quantum_efficiency"].values
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def test_qe_and_sr_reciprocal_conversion(sr_and_eqe_fixture):
|
|
119
|
+
# test that the conversion functions are reciprocal
|
|
120
|
+
qe = spectrum.sr_to_qe(sr_and_eqe_fixture["spectral_response"])
|
|
121
|
+
sr = spectrum.qe_to_sr(qe)
|
|
122
|
+
assert_allclose(sr, sr_and_eqe_fixture["spectral_response"])
|
|
123
|
+
qe = spectrum.sr_to_qe(sr)
|
|
124
|
+
assert_allclose(qe, sr_and_eqe_fixture["quantum_efficiency"])
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
import pandas as pd
|
|
3
|
+
import numpy as np
|
|
4
|
+
from pvlib import spectrum
|
|
5
|
+
from numpy.testing import assert_allclose
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def test_spectrl2(spectrl2_data):
|
|
9
|
+
# compare against output from solar_utils wrapper around NREL spectrl2_2.c
|
|
10
|
+
kwargs, expected = spectrl2_data
|
|
11
|
+
actual = spectrum.spectrl2(**kwargs)
|
|
12
|
+
assert_allclose(expected['wavelength'].values, actual['wavelength'])
|
|
13
|
+
assert_allclose(expected['specdif'].values, actual['dhi'].ravel(),
|
|
14
|
+
atol=7e-5)
|
|
15
|
+
assert_allclose(expected['specdir'].values, actual['dni'].ravel(),
|
|
16
|
+
atol=1.5e-4)
|
|
17
|
+
assert_allclose(expected['specetr'], actual['dni_extra'].ravel(),
|
|
18
|
+
atol=2e-4)
|
|
19
|
+
assert_allclose(expected['specglo'], actual['poa_global'].ravel(),
|
|
20
|
+
atol=1e-4)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def test_spectrl2_array(spectrl2_data):
|
|
24
|
+
# test that supplying arrays instead of scalars works
|
|
25
|
+
kwargs, expected = spectrl2_data
|
|
26
|
+
kwargs = {k: np.array([v, v, v]) for k, v in kwargs.items()}
|
|
27
|
+
actual = spectrum.spectrl2(**kwargs)
|
|
28
|
+
|
|
29
|
+
assert actual['wavelength'].shape == (122,)
|
|
30
|
+
|
|
31
|
+
keys = ['dni_extra', 'dhi', 'dni', 'poa_sky_diffuse', 'poa_ground_diffuse',
|
|
32
|
+
'poa_direct', 'poa_global']
|
|
33
|
+
for key in keys:
|
|
34
|
+
assert actual[key].shape == (122, 3)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def test_spectrl2_series(spectrl2_data):
|
|
38
|
+
# test that supplying Series instead of scalars works
|
|
39
|
+
kwargs, expected = spectrl2_data
|
|
40
|
+
kwargs.pop('dayofyear')
|
|
41
|
+
index = pd.to_datetime(['2020-03-15 10:45:59']*3)
|
|
42
|
+
kwargs = {k: pd.Series([v, v, v], index=index) for k, v in kwargs.items()}
|
|
43
|
+
actual = spectrum.spectrl2(**kwargs)
|
|
44
|
+
|
|
45
|
+
assert actual['wavelength'].shape == (122,)
|
|
46
|
+
|
|
47
|
+
keys = ['dni_extra', 'dhi', 'dni', 'poa_sky_diffuse', 'poa_ground_diffuse',
|
|
48
|
+
'poa_direct', 'poa_global']
|
|
49
|
+
for key in keys:
|
|
50
|
+
assert actual[key].shape == (122, 3)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def test_dayofyear_missing(spectrl2_data):
|
|
54
|
+
# test that not specifying dayofyear with non-pandas inputs raises error
|
|
55
|
+
kwargs, expected = spectrl2_data
|
|
56
|
+
kwargs.pop('dayofyear')
|
|
57
|
+
with pytest.raises(ValueError, match='dayofyear must be specified'):
|
|
58
|
+
_ = spectrum.spectrl2(**kwargs)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def test_aoi_gt_90(spectrl2_data):
|
|
62
|
+
# test that returned irradiance values are non-negative when aoi > 90
|
|
63
|
+
# see GH #1348
|
|
64
|
+
kwargs, _ = spectrl2_data
|
|
65
|
+
kwargs['apparent_zenith'] = 70
|
|
66
|
+
kwargs['aoi'] = 130
|
|
67
|
+
kwargs['surface_tilt'] = 60
|
|
68
|
+
|
|
69
|
+
spectra = spectrum.spectrl2(**kwargs)
|
|
70
|
+
for key in ['poa_direct', 'poa_global']:
|
|
71
|
+
message = f'{key} contains negative values for aoi>90'
|
|
72
|
+
assert np.all(spectra[key] >= 0), message
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Test the _deprecation module.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
from pvlib import _deprecation
|
|
8
|
+
from .conftest import fail_on_pvlib_version
|
|
9
|
+
|
|
10
|
+
import warnings
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@pytest.mark.xfail(strict=True,
|
|
14
|
+
reason='fail_on_pvlib_version should cause test to fail')
|
|
15
|
+
@fail_on_pvlib_version('0.0')
|
|
16
|
+
def test_fail_on_pvlib_version():
|
|
17
|
+
pass # pragma: no cover
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@fail_on_pvlib_version('100000.0')
|
|
21
|
+
def test_fail_on_pvlib_version_pass():
|
|
22
|
+
pass
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@pytest.mark.xfail(strict=True, reason='ensure that the test is called')
|
|
26
|
+
@fail_on_pvlib_version('100000.0')
|
|
27
|
+
def test_fail_on_pvlib_version_fail_in_test():
|
|
28
|
+
raise Exception
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
# set up to test using fixtures with function decorated with
|
|
32
|
+
# conftest.fail_on_pvlib_version
|
|
33
|
+
@pytest.fixture
|
|
34
|
+
def some_data():
|
|
35
|
+
return "some data"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def alt_func(*args):
|
|
39
|
+
return args
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@pytest.fixture
|
|
43
|
+
def deprec_func():
|
|
44
|
+
return _deprecation.deprecated(
|
|
45
|
+
"350.8", alternative="alt_func", name="deprec_func", removal="350.9"
|
|
46
|
+
)(alt_func)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@fail_on_pvlib_version('350.9')
|
|
50
|
+
def test_use_fixture_with_decorator(some_data, deprec_func):
|
|
51
|
+
# test that the correct data is returned by the some_data fixture
|
|
52
|
+
assert some_data == "some data"
|
|
53
|
+
with pytest.warns(_deprecation.pvlibDeprecationWarning):
|
|
54
|
+
# test for custom deprecation warning provided by pvlib
|
|
55
|
+
deprec_func(some_data)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@pytest.fixture
|
|
59
|
+
def renamed_kwarg_func():
|
|
60
|
+
"""Returns a function decorated by renamed_kwarg_warning.
|
|
61
|
+
This function is called 'func' and has a docstring equal to 'docstring'.
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
@_deprecation.renamed_kwarg_warning(
|
|
65
|
+
"0.1.0", "old_kwarg", "new_kwarg", "0.2.0"
|
|
66
|
+
)
|
|
67
|
+
def func(new_kwarg):
|
|
68
|
+
"""docstring"""
|
|
69
|
+
return new_kwarg
|
|
70
|
+
|
|
71
|
+
return func
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def test_renamed_kwarg_warning(renamed_kwarg_func):
|
|
75
|
+
# assert decorated function name and docstring are unchanged
|
|
76
|
+
assert renamed_kwarg_func.__name__ == "func"
|
|
77
|
+
assert renamed_kwarg_func.__doc__ == "docstring"
|
|
78
|
+
|
|
79
|
+
# assert no warning is raised when using the new kwarg
|
|
80
|
+
with warnings.catch_warnings():
|
|
81
|
+
warnings.simplefilter("error")
|
|
82
|
+
assert renamed_kwarg_func(new_kwarg=1) == 1 # as keyword argument
|
|
83
|
+
assert renamed_kwarg_func(1) == 1 # as positional argument
|
|
84
|
+
|
|
85
|
+
# assert a warning is raised when using the old kwarg
|
|
86
|
+
with pytest.warns(Warning, match="Parameter 'old_kwarg' has been renamed"):
|
|
87
|
+
assert renamed_kwarg_func(old_kwarg=1) == 1
|
|
88
|
+
|
|
89
|
+
# assert an error is raised when using both the old and new kwarg
|
|
90
|
+
with pytest.raises(ValueError, match="they refer to the same parameter."):
|
|
91
|
+
renamed_kwarg_func(old_kwarg=1, new_kwarg=2)
|
|
92
|
+
|
|
93
|
+
# assert when not providing any of them
|
|
94
|
+
with pytest.raises(
|
|
95
|
+
TypeError, match="missing 1 required positional argument"
|
|
96
|
+
):
|
|
97
|
+
renamed_kwarg_func()
|
pvlib/tests/test_atmosphere.py
CHANGED
|
@@ -88,6 +88,153 @@ def test_gueymard94_pw():
|
|
|
88
88
|
assert_allclose(pws, expected, atol=0.01)
|
|
89
89
|
|
|
90
90
|
|
|
91
|
+
def test_tdew_to_rh_to_tdew():
|
|
92
|
+
|
|
93
|
+
# dewpoint temp calculated with wmo and aekr coefficients
|
|
94
|
+
dewpoint_original = pd.Series([
|
|
95
|
+
15.0, 20.0, 25.0, 12.0, 8.0
|
|
96
|
+
])
|
|
97
|
+
|
|
98
|
+
temperature_ambient = pd.Series([20.0, 25.0, 30.0, 15.0, 10.0])
|
|
99
|
+
|
|
100
|
+
# Calculate relative humidity using pandas series as input
|
|
101
|
+
relative_humidity = atmosphere.rh_from_tdew(
|
|
102
|
+
temp_air=temperature_ambient,
|
|
103
|
+
temp_dew=dewpoint_original
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
dewpoint_calculated = atmosphere.tdew_from_rh(
|
|
107
|
+
temp_air=temperature_ambient,
|
|
108
|
+
relative_humidity=relative_humidity
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
# test
|
|
112
|
+
pd.testing.assert_series_equal(
|
|
113
|
+
dewpoint_original,
|
|
114
|
+
dewpoint_calculated,
|
|
115
|
+
check_names=False
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def test_rh_from_tdew():
|
|
120
|
+
|
|
121
|
+
dewpoint = pd.Series([
|
|
122
|
+
15.0, 20.0, 25.0, 12.0, 8.0
|
|
123
|
+
])
|
|
124
|
+
|
|
125
|
+
# relative humidity calculated with wmo and aekr coefficients
|
|
126
|
+
relative_humidity_wmo = pd.Series([
|
|
127
|
+
72.95185312581116, 73.81500029087906, 74.6401272083123,
|
|
128
|
+
82.27063889868842, 87.39018119185337
|
|
129
|
+
])
|
|
130
|
+
relative_humidity_aekr = pd.Series([
|
|
131
|
+
72.93876680928582, 73.8025121880607, 74.62820502423823,
|
|
132
|
+
82.26135295757305, 87.38323744820416
|
|
133
|
+
])
|
|
134
|
+
|
|
135
|
+
temperature_ambient = pd.Series([20.0, 25.0, 30.0, 15.0, 10.0])
|
|
136
|
+
|
|
137
|
+
# Calculate relative humidity using pandas series as input
|
|
138
|
+
rh_series = atmosphere.rh_from_tdew(
|
|
139
|
+
temp_air=temperature_ambient,
|
|
140
|
+
temp_dew=dewpoint
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
pd.testing.assert_series_equal(
|
|
144
|
+
rh_series,
|
|
145
|
+
relative_humidity_wmo,
|
|
146
|
+
check_names=False
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
# Calulate relative humidity using pandas series as input
|
|
150
|
+
# with AEKR coefficients
|
|
151
|
+
rh_series_aekr = atmosphere.rh_from_tdew(
|
|
152
|
+
temp_air=temperature_ambient,
|
|
153
|
+
temp_dew=dewpoint,
|
|
154
|
+
coeff=(6.1094, 17.625, 243.04)
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
pd.testing.assert_series_equal(
|
|
158
|
+
rh_series_aekr,
|
|
159
|
+
relative_humidity_aekr,
|
|
160
|
+
check_names=False
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
# Calculate relative humidity using array as input
|
|
164
|
+
rh_array = atmosphere.rh_from_tdew(
|
|
165
|
+
temp_air=temperature_ambient.to_numpy(),
|
|
166
|
+
temp_dew=dewpoint.to_numpy()
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
np.testing.assert_allclose(rh_array, relative_humidity_wmo.to_numpy())
|
|
170
|
+
|
|
171
|
+
# Calculate relative humidity using float as input
|
|
172
|
+
rh_float = atmosphere.rh_from_tdew(
|
|
173
|
+
temp_air=temperature_ambient.iloc[0],
|
|
174
|
+
temp_dew=dewpoint.iloc[0]
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
assert np.isclose(rh_float, relative_humidity_wmo.iloc[0])
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
# Unit tests
|
|
181
|
+
def test_tdew_from_rh():
|
|
182
|
+
|
|
183
|
+
dewpoint = pd.Series([
|
|
184
|
+
15.0, 20.0, 25.0, 12.0, 8.0
|
|
185
|
+
])
|
|
186
|
+
|
|
187
|
+
# relative humidity calculated with wmo and aekr coefficients
|
|
188
|
+
relative_humidity_wmo = pd.Series([
|
|
189
|
+
72.95185312581116, 73.81500029087906, 74.6401272083123,
|
|
190
|
+
82.27063889868842, 87.39018119185337
|
|
191
|
+
])
|
|
192
|
+
relative_humidity_aekr = pd.Series([
|
|
193
|
+
72.93876680928582, 73.8025121880607, 74.62820502423823,
|
|
194
|
+
82.26135295757305, 87.38323744820416
|
|
195
|
+
])
|
|
196
|
+
|
|
197
|
+
temperature_ambient = pd.Series([20.0, 25.0, 30.0, 15.0, 10.0])
|
|
198
|
+
|
|
199
|
+
# test as series
|
|
200
|
+
dewpoint_series = atmosphere.tdew_from_rh(
|
|
201
|
+
temp_air=temperature_ambient,
|
|
202
|
+
relative_humidity=relative_humidity_wmo
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
pd.testing.assert_series_equal(
|
|
206
|
+
dewpoint_series, dewpoint, check_names=False
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
# test as series with AEKR coefficients
|
|
210
|
+
dewpoint_series_aekr = atmosphere.tdew_from_rh(
|
|
211
|
+
temp_air=temperature_ambient,
|
|
212
|
+
relative_humidity=relative_humidity_aekr,
|
|
213
|
+
coeff=(6.1094, 17.625, 243.04)
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
pd.testing.assert_series_equal(
|
|
217
|
+
dewpoint_series_aekr, dewpoint,
|
|
218
|
+
check_names=False
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
# test as numpy array
|
|
222
|
+
dewpoint_array = atmosphere.tdew_from_rh(
|
|
223
|
+
temp_air=temperature_ambient.to_numpy(),
|
|
224
|
+
relative_humidity=relative_humidity_wmo.to_numpy()
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
np.testing.assert_allclose(dewpoint_array, dewpoint.to_numpy())
|
|
228
|
+
|
|
229
|
+
# test as float
|
|
230
|
+
dewpoint_float = atmosphere.tdew_from_rh(
|
|
231
|
+
temp_air=temperature_ambient.iloc[0],
|
|
232
|
+
relative_humidity=relative_humidity_wmo.iloc[0]
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
assert np.isclose(dewpoint_float, dewpoint.iloc[0])
|
|
236
|
+
|
|
237
|
+
|
|
91
238
|
def test_first_solar_spectral_correction_deprecated():
|
|
92
239
|
with pytest.warns(pvlibDeprecationWarning,
|
|
93
240
|
match='Use pvlib.spectrum.spectral_factor_firstsolar'):
|
|
@@ -131,3 +278,74 @@ def test_bird_hulstrom80_aod_bb():
|
|
|
131
278
|
aod380, aod500 = 0.22072480948195175, 0.1614279181106312
|
|
132
279
|
bird_hulstrom = atmosphere.bird_hulstrom80_aod_bb(aod380, aod500)
|
|
133
280
|
assert np.isclose(0.11738229553812768, bird_hulstrom)
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
@pytest.fixture
|
|
284
|
+
def windspeeds_data_powerlaw():
|
|
285
|
+
data = pd.DataFrame(
|
|
286
|
+
index=pd.date_range(start="2015-01-01 00:00", end="2015-01-01 05:00",
|
|
287
|
+
freq="1h"),
|
|
288
|
+
columns=["wind_ref", "height_ref", "height_desired", "wind_calc"],
|
|
289
|
+
data=[
|
|
290
|
+
(10, -2, 5, np.nan),
|
|
291
|
+
(-10, 2, 5, np.nan),
|
|
292
|
+
(5, 4, 5, 5.067393209486324),
|
|
293
|
+
(7, 6, 10, 7.2178684911195905),
|
|
294
|
+
(10, 8, 20, 10.565167835216586),
|
|
295
|
+
(12, 10, 30, 12.817653329393977)
|
|
296
|
+
]
|
|
297
|
+
)
|
|
298
|
+
return data
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def test_windspeed_powerlaw_ndarray(windspeeds_data_powerlaw):
|
|
302
|
+
# test wind speed estimation by passing in surface_type
|
|
303
|
+
result_surface = atmosphere.windspeed_powerlaw(
|
|
304
|
+
windspeeds_data_powerlaw["wind_ref"].to_numpy(),
|
|
305
|
+
windspeeds_data_powerlaw["height_ref"],
|
|
306
|
+
windspeeds_data_powerlaw["height_desired"],
|
|
307
|
+
surface_type='unstable_air_above_open_water_surface')
|
|
308
|
+
assert_allclose(windspeeds_data_powerlaw["wind_calc"].to_numpy(),
|
|
309
|
+
result_surface)
|
|
310
|
+
# test wind speed estimation by passing in the exponent corresponding
|
|
311
|
+
# to the surface_type above
|
|
312
|
+
result_exponent = atmosphere.windspeed_powerlaw(
|
|
313
|
+
windspeeds_data_powerlaw["wind_ref"].to_numpy(),
|
|
314
|
+
windspeeds_data_powerlaw["height_ref"],
|
|
315
|
+
windspeeds_data_powerlaw["height_desired"],
|
|
316
|
+
exponent=0.06)
|
|
317
|
+
assert_allclose(windspeeds_data_powerlaw["wind_calc"].to_numpy(),
|
|
318
|
+
result_exponent)
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def test_windspeed_powerlaw_series(windspeeds_data_powerlaw):
|
|
322
|
+
result = atmosphere.windspeed_powerlaw(
|
|
323
|
+
windspeeds_data_powerlaw["wind_ref"],
|
|
324
|
+
windspeeds_data_powerlaw["height_ref"],
|
|
325
|
+
windspeeds_data_powerlaw["height_desired"],
|
|
326
|
+
surface_type='unstable_air_above_open_water_surface')
|
|
327
|
+
assert_series_equal(windspeeds_data_powerlaw["wind_calc"],
|
|
328
|
+
result, check_names=False)
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
def test_windspeed_powerlaw_invalid():
|
|
332
|
+
with pytest.raises(ValueError, match="Either a 'surface_type' or an "
|
|
333
|
+
"'exponent' parameter must be given"):
|
|
334
|
+
# no exponent or surface_type given
|
|
335
|
+
atmosphere.windspeed_powerlaw(wind_speed_reference=10,
|
|
336
|
+
height_reference=5,
|
|
337
|
+
height_desired=10)
|
|
338
|
+
with pytest.raises(ValueError, match="Either a 'surface_type' or an "
|
|
339
|
+
"'exponent' parameter must be given"):
|
|
340
|
+
# no exponent or surface_type given
|
|
341
|
+
atmosphere.windspeed_powerlaw(wind_speed_reference=10,
|
|
342
|
+
height_reference=5,
|
|
343
|
+
height_desired=10,
|
|
344
|
+
exponent=1.2,
|
|
345
|
+
surface_type="surf")
|
|
346
|
+
with pytest.raises(KeyError, match='not_an_exponent'):
|
|
347
|
+
# invalid surface_type
|
|
348
|
+
atmosphere.windspeed_powerlaw(wind_speed_reference=10,
|
|
349
|
+
height_reference=5,
|
|
350
|
+
height_desired=10,
|
|
351
|
+
surface_type='not_an_exponent')
|
pvlib/tests/test_clearsky.py
CHANGED
|
@@ -674,14 +674,25 @@ def test_detect_clearsky_missing_index(detect_clearsky_data):
|
|
|
674
674
|
|
|
675
675
|
def test_detect_clearsky_not_enough_data(detect_clearsky_data):
|
|
676
676
|
expected, cs = detect_clearsky_data
|
|
677
|
-
with pytest.raises(ValueError, match='
|
|
678
|
-
|
|
677
|
+
with pytest.raises(ValueError, match='times has only'):
|
|
678
|
+
clearsky.detect_clearsky(expected['GHI'], cs['ghi'], window_length=60)
|
|
679
679
|
|
|
680
680
|
|
|
681
|
-
def
|
|
681
|
+
def test_detect_clearsky_window_too_short(detect_clearsky_data):
|
|
682
682
|
expected, cs = detect_clearsky_data
|
|
683
|
-
with pytest.raises(
|
|
684
|
-
clearsky.detect_clearsky(expected['GHI'], cs['ghi'], window_length=
|
|
683
|
+
with pytest.raises(ValueError, match="Samples per window of "):
|
|
684
|
+
clearsky.detect_clearsky(expected['GHI'], cs['ghi'], window_length=2)
|
|
685
|
+
|
|
686
|
+
|
|
687
|
+
@pytest.mark.parametrize("window_length", [5, 10, 15, 20, 25])
|
|
688
|
+
def test_detect_clearsky_optimizer_not_failed(
|
|
689
|
+
detect_clearsky_data, window_length
|
|
690
|
+
):
|
|
691
|
+
expected, cs = detect_clearsky_data
|
|
692
|
+
clear_samples = clearsky.detect_clearsky(
|
|
693
|
+
expected["GHI"], cs["ghi"], window_length=window_length
|
|
694
|
+
)
|
|
695
|
+
assert isinstance(clear_samples, pd.Series)
|
|
685
696
|
|
|
686
697
|
|
|
687
698
|
@pytest.fixture
|
|
@@ -749,6 +760,7 @@ def test_bird():
|
|
|
749
760
|
tz = -7 # test timezone
|
|
750
761
|
gmt_tz = pytz.timezone('Etc/GMT%+d' % -(tz))
|
|
751
762
|
times = times.tz_localize(gmt_tz) # set timezone
|
|
763
|
+
times_utc = times.tz_convert('UTC')
|
|
752
764
|
# match test data from BIRD_08_16_2012.xls
|
|
753
765
|
latitude = 40.
|
|
754
766
|
longitude = -105.
|
|
@@ -759,9 +771,9 @@ def test_bird():
|
|
|
759
771
|
aod_380nm = 0.15
|
|
760
772
|
b_a = 0.85
|
|
761
773
|
alb = 0.2
|
|
762
|
-
eot = solarposition.equation_of_time_spencer71(
|
|
774
|
+
eot = solarposition.equation_of_time_spencer71(times_utc.dayofyear)
|
|
763
775
|
hour_angle = solarposition.hour_angle(times, longitude, eot) - 0.5 * 15.
|
|
764
|
-
declination = solarposition.declination_spencer71(
|
|
776
|
+
declination = solarposition.declination_spencer71(times_utc.dayofyear)
|
|
765
777
|
zenith = solarposition.solar_zenith_analytical(
|
|
766
778
|
np.deg2rad(latitude), np.deg2rad(hour_angle), declination
|
|
767
779
|
)
|
|
@@ -777,32 +789,35 @@ def test_bird():
|
|
|
777
789
|
Eb, Ebh, Gh, Dh = (irrads[_] for _ in field_names)
|
|
778
790
|
data_path = DATA_DIR / 'BIRD_08_16_2012.csv'
|
|
779
791
|
testdata = pd.read_csv(data_path, usecols=range(1, 26), header=1).dropna()
|
|
780
|
-
testdata
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
assert np.allclose(testdata['
|
|
792
|
+
testdata[['DEC', 'EQT']] = testdata[['DEC', 'EQT']].shift(tz)
|
|
793
|
+
testdata = testdata[:tz]
|
|
794
|
+
end = 48 + tz
|
|
795
|
+
testdata.index = times[1:end]
|
|
796
|
+
assert np.allclose(testdata['DEC'], np.rad2deg(declination[1:end]))
|
|
797
|
+
assert np.allclose(testdata['EQT'], eot[1:end], rtol=1e-4)
|
|
798
|
+
assert np.allclose(testdata['Hour Angle'], hour_angle[1:end], rtol=1e-2)
|
|
799
|
+
assert np.allclose(testdata['Zenith Ang'], zenith[1:end], rtol=1e-2)
|
|
785
800
|
dawn = zenith < 88.
|
|
786
801
|
dusk = testdata['Zenith Ang'] < 88.
|
|
787
802
|
am = pd.Series(np.where(dawn, airmass, 0.), index=times).fillna(0.0)
|
|
788
803
|
assert np.allclose(
|
|
789
|
-
testdata['Air Mass'].where(dusk, 0.), am[1:
|
|
804
|
+
testdata['Air Mass'].where(dusk, 0.), am[1:end], rtol=1e-3
|
|
790
805
|
)
|
|
791
806
|
direct_beam = pd.Series(np.where(dawn, Eb, 0.), index=times).fillna(0.)
|
|
792
807
|
assert np.allclose(
|
|
793
|
-
testdata['Direct Beam'].where(dusk, 0.), direct_beam[1:
|
|
808
|
+
testdata['Direct Beam'].where(dusk, 0.), direct_beam[1:end], rtol=1e-3
|
|
794
809
|
)
|
|
795
810
|
direct_horz = pd.Series(np.where(dawn, Ebh, 0.), index=times).fillna(0.)
|
|
796
811
|
assert np.allclose(
|
|
797
|
-
testdata['Direct Hz'].where(dusk, 0.), direct_horz[1:
|
|
812
|
+
testdata['Direct Hz'].where(dusk, 0.), direct_horz[1:end], rtol=1e-3
|
|
798
813
|
)
|
|
799
814
|
global_horz = pd.Series(np.where(dawn, Gh, 0.), index=times).fillna(0.)
|
|
800
815
|
assert np.allclose(
|
|
801
|
-
testdata['Global Hz'].where(dusk, 0.), global_horz[1:
|
|
816
|
+
testdata['Global Hz'].where(dusk, 0.), global_horz[1:end], rtol=1e-3
|
|
802
817
|
)
|
|
803
818
|
diffuse_horz = pd.Series(np.where(dawn, Dh, 0.), index=times).fillna(0.)
|
|
804
819
|
assert np.allclose(
|
|
805
|
-
testdata['Dif Hz'].where(dusk, 0.), diffuse_horz[1:
|
|
820
|
+
testdata['Dif Hz'].where(dusk, 0.), diffuse_horz[1:end], rtol=1e-3
|
|
806
821
|
)
|
|
807
822
|
# repeat test with albedo as a Series
|
|
808
823
|
alb_series = pd.Series(0.2, index=times)
|
|
@@ -813,19 +828,19 @@ def test_bird():
|
|
|
813
828
|
Eb, Ebh, Gh, Dh = (irrads[_] for _ in field_names)
|
|
814
829
|
direct_beam = pd.Series(np.where(dawn, Eb, 0.), index=times).fillna(0.)
|
|
815
830
|
assert np.allclose(
|
|
816
|
-
testdata['Direct Beam'].where(dusk, 0.), direct_beam[1:
|
|
831
|
+
testdata['Direct Beam'].where(dusk, 0.), direct_beam[1:end], rtol=1e-3
|
|
817
832
|
)
|
|
818
833
|
direct_horz = pd.Series(np.where(dawn, Ebh, 0.), index=times).fillna(0.)
|
|
819
834
|
assert np.allclose(
|
|
820
|
-
testdata['Direct Hz'].where(dusk, 0.), direct_horz[1:
|
|
835
|
+
testdata['Direct Hz'].where(dusk, 0.), direct_horz[1:end], rtol=1e-3
|
|
821
836
|
)
|
|
822
837
|
global_horz = pd.Series(np.where(dawn, Gh, 0.), index=times).fillna(0.)
|
|
823
838
|
assert np.allclose(
|
|
824
|
-
testdata['Global Hz'].where(dusk, 0.), global_horz[1:
|
|
839
|
+
testdata['Global Hz'].where(dusk, 0.), global_horz[1:end], rtol=1e-3
|
|
825
840
|
)
|
|
826
841
|
diffuse_horz = pd.Series(np.where(dawn, Dh, 0.), index=times).fillna(0.)
|
|
827
842
|
assert np.allclose(
|
|
828
|
-
testdata['Dif Hz'].where(dusk, 0.), diffuse_horz[1:
|
|
843
|
+
testdata['Dif Hz'].where(dusk, 0.), diffuse_horz[1:end], rtol=1e-3
|
|
829
844
|
)
|
|
830
845
|
|
|
831
846
|
# test keyword parameters
|
|
@@ -835,22 +850,25 @@ def test_bird():
|
|
|
835
850
|
Eb2, Ebh2, Gh2, Dh2 = (irrads2[_] for _ in field_names)
|
|
836
851
|
data_path = DATA_DIR / 'BIRD_08_16_2012_patm.csv'
|
|
837
852
|
testdata2 = pd.read_csv(data_path, usecols=range(1, 26), header=1).dropna()
|
|
838
|
-
testdata2
|
|
853
|
+
testdata2[['DEC', 'EQT']] = testdata2[['DEC', 'EQT']].shift(tz)
|
|
854
|
+
testdata2 = testdata2[:tz]
|
|
855
|
+
testdata2.index = times[1:end]
|
|
839
856
|
direct_beam2 = pd.Series(np.where(dawn, Eb2, 0.), index=times).fillna(0.)
|
|
840
857
|
assert np.allclose(
|
|
841
|
-
testdata2['Direct Beam'].where(dusk, 0.), direct_beam2[1:
|
|
858
|
+
testdata2['Direct Beam'].where(dusk, 0.), direct_beam2[1:end],
|
|
859
|
+
rtol=1e-3
|
|
842
860
|
)
|
|
843
861
|
direct_horz2 = pd.Series(np.where(dawn, Ebh2, 0.), index=times).fillna(0.)
|
|
844
862
|
assert np.allclose(
|
|
845
|
-
testdata2['Direct Hz'].where(dusk, 0.), direct_horz2[1:
|
|
863
|
+
testdata2['Direct Hz'].where(dusk, 0.), direct_horz2[1:end], rtol=1e-3
|
|
846
864
|
)
|
|
847
865
|
global_horz2 = pd.Series(np.where(dawn, Gh2, 0.), index=times).fillna(0.)
|
|
848
866
|
assert np.allclose(
|
|
849
|
-
testdata2['Global Hz'].where(dusk, 0.), global_horz2[1:
|
|
867
|
+
testdata2['Global Hz'].where(dusk, 0.), global_horz2[1:end], rtol=1e-3
|
|
850
868
|
)
|
|
851
869
|
diffuse_horz2 = pd.Series(np.where(dawn, Dh2, 0.), index=times).fillna(0.)
|
|
852
870
|
assert np.allclose(
|
|
853
|
-
testdata2['Dif Hz'].where(dusk, 0.), diffuse_horz2[1:
|
|
871
|
+
testdata2['Dif Hz'].where(dusk, 0.), diffuse_horz2[1:end], rtol=1e-3
|
|
854
872
|
)
|
|
855
873
|
# test scalars just at noon
|
|
856
874
|
# XXX: calculations start at 12am so noon is at index = 12
|