pvlib 0.9.4a1__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 +23 -173
- pvlib/bifacial/infinite_sheds.py +88 -277
- pvlib/bifacial/utils.py +270 -28
- pvlib/data/adr-library-cec-inverters-2019-03-05.csv +5009 -0
- pvlib/data/precise_iv_curves1.json +10251 -0
- pvlib/data/precise_iv_curves2.json +10251 -0
- pvlib/data/precise_iv_curves_parameter_sets1.csv +33 -0
- pvlib/data/precise_iv_curves_parameter_sets2.csv +33 -0
- pvlib/data/test_psm3_2017.csv +17521 -17521
- pvlib/data/test_psm3_2019_5min.csv +288 -288
- 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 +207 -51
- 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 +59 -42
- 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 +270 -15
- pvlib/ivtools/sde.py +14 -20
- pvlib/ivtools/sdm.py +31 -20
- pvlib/ivtools/utils.py +127 -6
- pvlib/location.py +3 -2
- pvlib/modelchain.py +67 -70
- pvlib/pvarray.py +225 -0
- pvlib/pvsystem.py +169 -539
- pvlib/shading.py +43 -2
- pvlib/singlediode.py +216 -66
- pvlib/snow.py +36 -15
- 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 +124 -13
- pvlib/tests/bifacial/test_infinite_sheds.py +44 -106
- pvlib/tests/bifacial/test_utils.py +102 -5
- 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 +7 -5
- 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/ivtools/test_utils.py +96 -1
- pvlib/tests/test_atmosphere.py +8 -64
- pvlib/tests/test_clearsky.py +0 -1
- pvlib/tests/test_iam.py +74 -1
- pvlib/tests/test_irradiance.py +56 -2
- pvlib/tests/test_location.py +1 -1
- pvlib/tests/test_modelchain.py +33 -76
- pvlib/tests/test_pvarray.py +46 -0
- pvlib/tests/test_pvsystem.py +366 -201
- pvlib/tests/test_shading.py +35 -0
- pvlib/tests/test_singlediode.py +306 -29
- pvlib/tests/test_snow.py +84 -1
- pvlib/tests/test_soiling.py +8 -7
- pvlib/tests/test_solarposition.py +7 -7
- pvlib/tests/test_spa.py +6 -7
- pvlib/tests/test_spectrum.py +145 -1
- pvlib/tests/test_temperature.py +29 -11
- pvlib/tests/test_tools.py +41 -0
- pvlib/tests/test_tracking.py +0 -149
- pvlib/tools.py +49 -25
- pvlib/tracking.py +1 -269
- pvlib-0.10.0.dist-info/AUTHORS.md +35 -0
- {pvlib-0.9.4a1.dist-info → pvlib-0.10.0.dist-info}/LICENSE +5 -2
- {pvlib-0.9.4a1.dist-info → pvlib-0.10.0.dist-info}/METADATA +3 -13
- {pvlib-0.9.4a1.dist-info → pvlib-0.10.0.dist-info}/RECORD +80 -75
- {pvlib-0.9.4a1.dist-info → pvlib-0.10.0.dist-info}/WHEEL +1 -1
- pvlib/data/adr-library-2013-10-01.csv +0 -1762
- 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.4a1.dist-info/AUTHORS.md +0 -32
- {pvlib-0.9.4a1.dist-info → pvlib-0.10.0.dist-info}/top_level.txt +0 -0
pvlib/tests/test_shading.py
CHANGED
|
@@ -7,6 +7,34 @@ import pytest
|
|
|
7
7
|
from pvlib import shading
|
|
8
8
|
|
|
9
9
|
|
|
10
|
+
@pytest.fixture
|
|
11
|
+
def test_system():
|
|
12
|
+
syst = {'height': 1.0,
|
|
13
|
+
'pitch': 2.,
|
|
14
|
+
'surface_tilt': 30.,
|
|
15
|
+
'surface_azimuth': 180.,
|
|
16
|
+
'rotation': -30.} # rotation of right edge relative to horizontal
|
|
17
|
+
syst['gcr'] = 1.0 / syst['pitch']
|
|
18
|
+
return syst
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def test__ground_angle(test_system):
|
|
22
|
+
ts = test_system
|
|
23
|
+
x = np.array([0., 0.5, 1.0])
|
|
24
|
+
angles = shading.ground_angle(
|
|
25
|
+
ts['surface_tilt'], ts['gcr'], x)
|
|
26
|
+
expected_angles = np.array([0., 5.866738789543952, 9.896090638982903])
|
|
27
|
+
assert np.allclose(angles, expected_angles)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def test__ground_angle_zero_gcr():
|
|
31
|
+
surface_tilt = 30.0
|
|
32
|
+
x = np.array([0.0, 0.5, 1.0])
|
|
33
|
+
angles = shading.ground_angle(surface_tilt, 0, x)
|
|
34
|
+
expected_angles = np.array([0, 0, 0])
|
|
35
|
+
assert np.allclose(angles, expected_angles)
|
|
36
|
+
|
|
37
|
+
|
|
10
38
|
@pytest.fixture
|
|
11
39
|
def surface_tilt():
|
|
12
40
|
idx = pd.date_range('2019-01-01', freq='h', periods=3)
|
|
@@ -45,6 +73,13 @@ def test_masking_angle_scalar(surface_tilt, masking_angle):
|
|
|
45
73
|
assert np.isclose(masking_angle_actual, angle)
|
|
46
74
|
|
|
47
75
|
|
|
76
|
+
def test_masking_angle_zero_gcr(surface_tilt):
|
|
77
|
+
# scalar inputs and outputs, including zero
|
|
78
|
+
for tilt in surface_tilt:
|
|
79
|
+
masking_angle_actual = shading.masking_angle(tilt, 0, 0.25)
|
|
80
|
+
assert np.isclose(masking_angle_actual, 0)
|
|
81
|
+
|
|
82
|
+
|
|
48
83
|
def test_masking_angle_passias_series(surface_tilt, average_masking_angle):
|
|
49
84
|
# pandas series inputs and outputs
|
|
50
85
|
masking_angle_actual = shading.masking_angle_passias(surface_tilt, 0.5)
|
pvlib/tests/test_singlediode.py
CHANGED
|
@@ -3,10 +3,14 @@ testing single-diode methods using JW Bishop 1988
|
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
import numpy as np
|
|
6
|
+
import pandas as pd
|
|
7
|
+
import scipy
|
|
6
8
|
from pvlib import pvsystem
|
|
7
9
|
from pvlib.singlediode import (bishop88_mpp, estimate_voc, VOLTAGE_BUILTIN,
|
|
8
10
|
bishop88, bishop88_i_from_v, bishop88_v_from_i)
|
|
11
|
+
from pvlib._deprecation import pvlibDeprecationWarning
|
|
9
12
|
import pytest
|
|
13
|
+
from .conftest import DATA_DIR
|
|
10
14
|
|
|
11
15
|
POA = 888
|
|
12
16
|
TCELL = 55
|
|
@@ -22,22 +26,16 @@ def test_method_spr_e20_327(method, cec_module_spr_e20_327):
|
|
|
22
26
|
I_L_ref=spr_e20_327['I_L_ref'], I_o_ref=spr_e20_327['I_o_ref'],
|
|
23
27
|
R_sh_ref=spr_e20_327['R_sh_ref'], R_s=spr_e20_327['R_s'],
|
|
24
28
|
EgRef=1.121, dEgdT=-0.0002677)
|
|
25
|
-
il, io, rs, rsh, nnsvt = x
|
|
26
29
|
pvs = pvsystem.singlediode(*x, method='lambertw')
|
|
27
30
|
out = pvsystem.singlediode(*x, method=method)
|
|
28
|
-
|
|
29
|
-
assert np.isclose(pvs['i_sc'],
|
|
30
|
-
assert np.isclose(pvs['v_oc'],
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
assert np.isclose(
|
|
35
|
-
assert np.isclose(
|
|
36
|
-
assert np.isclose(pvs['p_mp'], pmp)
|
|
37
|
-
assert np.isclose(pvs['i_x'], ix)
|
|
38
|
-
pvs_ixx = pvsystem.i_from_v(rsh, rs, nnsvt, (voc + vmp)/2, io, il,
|
|
39
|
-
method='lambertw')
|
|
40
|
-
assert np.isclose(pvs_ixx, ixx)
|
|
31
|
+
|
|
32
|
+
assert np.isclose(pvs['i_sc'], out['i_sc'])
|
|
33
|
+
assert np.isclose(pvs['v_oc'], out['v_oc'])
|
|
34
|
+
assert np.isclose(pvs['i_mp'], out['i_mp'])
|
|
35
|
+
assert np.isclose(pvs['v_mp'], out['v_mp'])
|
|
36
|
+
assert np.isclose(pvs['p_mp'], out['p_mp'])
|
|
37
|
+
assert np.isclose(pvs['i_x'], out['i_x'])
|
|
38
|
+
assert np.isclose(pvs['i_xx'], out['i_xx'])
|
|
41
39
|
|
|
42
40
|
|
|
43
41
|
@pytest.mark.parametrize('method', ['brentq', 'newton'])
|
|
@@ -50,23 +48,170 @@ def test_newton_fs_495(method, cec_module_fs_495):
|
|
|
50
48
|
I_L_ref=fs_495['I_L_ref'], I_o_ref=fs_495['I_o_ref'],
|
|
51
49
|
R_sh_ref=fs_495['R_sh_ref'], R_s=fs_495['R_s'],
|
|
52
50
|
EgRef=1.475, dEgdT=-0.0003)
|
|
53
|
-
il, io, rs, rsh, nnsvt = x
|
|
54
|
-
x += (101, )
|
|
55
51
|
pvs = pvsystem.singlediode(*x, method='lambertw')
|
|
56
52
|
out = pvsystem.singlediode(*x, method=method)
|
|
57
|
-
|
|
58
|
-
assert np.isclose(pvs['i_sc'],
|
|
59
|
-
assert np.isclose(pvs['v_oc'],
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
assert np.isclose(
|
|
64
|
-
assert np.isclose(
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
53
|
+
|
|
54
|
+
assert np.isclose(pvs['i_sc'], out['i_sc'])
|
|
55
|
+
assert np.isclose(pvs['v_oc'], out['v_oc'])
|
|
56
|
+
assert np.isclose(pvs['i_mp'], out['i_mp'])
|
|
57
|
+
assert np.isclose(pvs['v_mp'], out['v_mp'])
|
|
58
|
+
assert np.isclose(pvs['p_mp'], out['p_mp'])
|
|
59
|
+
assert np.isclose(pvs['i_x'], out['i_x'])
|
|
60
|
+
assert np.isclose(pvs['i_xx'], out['i_xx'])
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def build_precise_iv_curve_dataframe(file_csv, file_json):
|
|
64
|
+
"""
|
|
65
|
+
Reads a precise IV curve parameter set CSV and JSON to create a DataFrame.
|
|
66
|
+
The CSV contains the parameters of the single diode equation which are used
|
|
67
|
+
to generate the JSON data. The data are calculated using [1]_ with 40
|
|
68
|
+
decimal digits of precision in order have at least 16 decimal digits of
|
|
69
|
+
precision when they are stored in JSON. The precision is sufficient for the
|
|
70
|
+
difference between the left and right side of the single diode equation to
|
|
71
|
+
be less than :math:`1 \times 10^{-16}` when the numbers from the JSON are
|
|
72
|
+
read as mpmath floats. The code to generate these IV curve data is from
|
|
73
|
+
[2]_. The data and tests that use this function were added in :pull:`1573`.
|
|
74
|
+
|
|
75
|
+
Parameters
|
|
76
|
+
----------
|
|
77
|
+
file_csv: str
|
|
78
|
+
Path to a CSV file of IV curve parameter sets.
|
|
79
|
+
|
|
80
|
+
file_json: str
|
|
81
|
+
Path to a JSON file of precise IV curves.
|
|
82
|
+
|
|
83
|
+
Returns
|
|
84
|
+
-------
|
|
85
|
+
A DataFrame with these columns: ``Index``, ``photocurrent``,
|
|
86
|
+
``saturation_current``, ``resistance_series``, ``resistance_shunt``,
|
|
87
|
+
``n``, ``cells_in_series``, ``Voltages``, ``Currents``,
|
|
88
|
+
``diode_voltage``, ``v_oc``, ``i_sc``, ``v_mp``, ``i_mp``, ``p_mp``,
|
|
89
|
+
``i_x``, ``i_xx`, ``Temperature``, ``Irradiance``, ``Sweep Direction``,
|
|
90
|
+
``Datetime``, ``Boltzmann``, ``Elementary Charge``, and ``Vth``. The
|
|
91
|
+
columns ``Irradiance``, ``Sweep Direction`` are None or empty strings.
|
|
92
|
+
|
|
93
|
+
References
|
|
94
|
+
----------
|
|
95
|
+
.. [1] The mpmath development team. (2023). mpmath: a Python library for
|
|
96
|
+
arbitrary-precision floating-point arithmetic (version 1.2.1).
|
|
97
|
+
`mpmath <mpmath.org>`_
|
|
98
|
+
|
|
99
|
+
.. [2] The ivcurves development team. (2022). Code to generate precise
|
|
100
|
+
solutions to the single diode equation.
|
|
101
|
+
`ivcurves <github.com/cwhanse/ivcurves>`_
|
|
102
|
+
"""
|
|
103
|
+
params = pd.read_csv(file_csv)
|
|
104
|
+
curves_metadata = pd.read_json(file_json)
|
|
105
|
+
curves = pd.DataFrame(curves_metadata['IV Curves'].values.tolist())
|
|
106
|
+
curves['cells_in_series'] = curves_metadata['cells_in_series']
|
|
107
|
+
joined = params.merge(curves, on='Index', how='inner',
|
|
108
|
+
suffixes=(None, '_drop'), validate='one_to_one')
|
|
109
|
+
joined = joined[(c for c in joined.columns if not c.endswith('_drop'))]
|
|
110
|
+
|
|
111
|
+
# parse strings to np.float64
|
|
112
|
+
is_array = ['Currents', 'Voltages', 'diode_voltage']
|
|
113
|
+
joined[is_array] = joined[is_array].applymap(
|
|
114
|
+
lambda a: np.asarray(a, dtype=np.float64)
|
|
115
|
+
)
|
|
116
|
+
is_number = ['v_oc', 'i_sc', 'v_mp', 'i_mp', 'p_mp', 'i_x', 'i_xx',
|
|
117
|
+
'Temperature']
|
|
118
|
+
joined[is_number] = joined[is_number].applymap(np.float64)
|
|
119
|
+
|
|
120
|
+
joined['Boltzmann'] = scipy.constants.Boltzmann
|
|
121
|
+
joined['Elementary Charge'] = scipy.constants.elementary_charge
|
|
122
|
+
joined['Vth'] = (
|
|
123
|
+
joined['Boltzmann'] * joined['Temperature']
|
|
124
|
+
/ joined['Elementary Charge']
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
return joined
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
@pytest.fixture(scope='function', params=[
|
|
131
|
+
{
|
|
132
|
+
'csv': f'{DATA_DIR}/precise_iv_curves_parameter_sets1.csv',
|
|
133
|
+
'json': f'{DATA_DIR}/precise_iv_curves1.json'
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
'csv': f'{DATA_DIR}/precise_iv_curves_parameter_sets2.csv',
|
|
137
|
+
'json': f'{DATA_DIR}/precise_iv_curves2.json'
|
|
138
|
+
}
|
|
139
|
+
], ids=[1, 2])
|
|
140
|
+
def precise_iv_curves(request):
|
|
141
|
+
file_csv, file_json = request.param['csv'], request.param['json']
|
|
142
|
+
pc = build_precise_iv_curve_dataframe(file_csv, file_json)
|
|
143
|
+
params = ['photocurrent', 'saturation_current', 'resistance_series',
|
|
144
|
+
'resistance_shunt']
|
|
145
|
+
singlediode_params = pc.loc[:, params]
|
|
146
|
+
singlediode_params['nNsVth'] = pc['n'] * pc['cells_in_series'] * pc['Vth']
|
|
147
|
+
return singlediode_params, pc
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
@pytest.mark.parametrize('method', ['lambertw', 'brentq', 'newton'])
|
|
151
|
+
def test_singlediode_precision(method, precise_iv_curves):
|
|
152
|
+
"""
|
|
153
|
+
Tests the accuracy of singlediode. ivcurve_pnts is not tested.
|
|
154
|
+
"""
|
|
155
|
+
x, pc = precise_iv_curves
|
|
156
|
+
outs = pvsystem.singlediode(method=method, **x)
|
|
157
|
+
|
|
158
|
+
assert np.allclose(pc['i_sc'], outs['i_sc'], atol=1e-10, rtol=0)
|
|
159
|
+
assert np.allclose(pc['v_oc'], outs['v_oc'], atol=1e-10, rtol=0)
|
|
160
|
+
assert np.allclose(pc['i_mp'], outs['i_mp'], atol=7e-8, rtol=0)
|
|
161
|
+
assert np.allclose(pc['v_mp'], outs['v_mp'], atol=1e-6, rtol=0)
|
|
162
|
+
assert np.allclose(pc['p_mp'], outs['p_mp'], atol=1e-10, rtol=0)
|
|
163
|
+
assert np.allclose(pc['i_x'], outs['i_x'], atol=1e-10, rtol=0)
|
|
164
|
+
|
|
165
|
+
# This test should pass with atol=9e-8 on MacOS and Windows.
|
|
166
|
+
# The atol was lowered to pass on Linux when the vectorized umath module
|
|
167
|
+
# introduced in NumPy 1.22.0 is used.
|
|
168
|
+
assert np.allclose(pc['i_xx'], outs['i_xx'], atol=1e-6, rtol=0)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def test_singlediode_lambert_negative_voc():
|
|
172
|
+
|
|
173
|
+
# Those values result in a negative v_oc out of `_lambertw_v_from_i`
|
|
174
|
+
x = np.array([0., 1.480501e-11, 0.178, 8000., 1.797559])
|
|
175
|
+
outs = pvsystem.singlediode(*x, method='lambertw')
|
|
176
|
+
assert outs['v_oc'] == 0
|
|
177
|
+
|
|
178
|
+
# Testing for an array
|
|
179
|
+
x = np.array([x, x]).T
|
|
180
|
+
outs = pvsystem.singlediode(*x, method='lambertw')
|
|
181
|
+
assert np.array_equal(outs['v_oc'], [0, 0])
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
@pytest.mark.parametrize('method', ['lambertw'])
|
|
185
|
+
def test_ivcurve_pnts_precision(method, precise_iv_curves):
|
|
186
|
+
"""
|
|
187
|
+
Tests the accuracy of the IV curve points calcuated by singlediode. Only
|
|
188
|
+
methods of singlediode that linearly spaced points are tested.
|
|
189
|
+
"""
|
|
190
|
+
x, pc = precise_iv_curves
|
|
191
|
+
pc_i, pc_v = np.stack(pc['Currents']), np.stack(pc['Voltages'])
|
|
192
|
+
ivcurve_pnts = len(pc['Currents'][0])
|
|
193
|
+
|
|
194
|
+
with pytest.warns(pvlibDeprecationWarning, match='ivcurve_pnts'):
|
|
195
|
+
outs = pvsystem.singlediode(method=method, ivcurve_pnts=ivcurve_pnts,
|
|
196
|
+
**x)
|
|
197
|
+
|
|
198
|
+
assert np.allclose(pc_i, outs['i'], atol=1e-10, rtol=0)
|
|
199
|
+
assert np.allclose(pc_v, outs['v'], atol=1e-10, rtol=0)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
@pytest.mark.parametrize('method', ['lambertw', 'brentq', 'newton'])
|
|
203
|
+
def test_v_from_i_i_from_v_precision(method, precise_iv_curves):
|
|
204
|
+
"""
|
|
205
|
+
Tests the accuracy of pvsystem.v_from_i and pvsystem.i_from_v.
|
|
206
|
+
"""
|
|
207
|
+
x, pc = precise_iv_curves
|
|
208
|
+
pc_i, pc_v = pc['Currents'], pc['Voltages']
|
|
209
|
+
for i, v, (_, x_one_curve) in zip(pc_i, pc_v, x.iterrows()):
|
|
210
|
+
out_i = pvsystem.i_from_v(voltage=v, method=method, **x_one_curve)
|
|
211
|
+
out_v = pvsystem.v_from_i(current=i, method=method, **x_one_curve)
|
|
212
|
+
|
|
213
|
+
assert np.allclose(i, out_i, atol=1e-10, rtol=0)
|
|
214
|
+
assert np.allclose(v, out_v, atol=1e-10, rtol=0)
|
|
70
215
|
|
|
71
216
|
|
|
72
217
|
def get_pvsyst_fs_495():
|
|
@@ -280,3 +425,135 @@ def test_pvsyst_breakdown(method, brk_params, recomb_params, poa, temp_cell,
|
|
|
280
425
|
|
|
281
426
|
vsc_88 = bishop88_v_from_i(isc_88, *x, **y, method=method)
|
|
282
427
|
assert np.isclose(vsc_88, 0.0, *tol)
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
@pytest.fixture
|
|
431
|
+
def bishop88_arguments():
|
|
432
|
+
pvsyst_fs_495 = get_pvsyst_fs_495()
|
|
433
|
+
# evaluate PVSyst model with thin-film recombination loss current
|
|
434
|
+
# at reference conditions
|
|
435
|
+
x = pvsystem.calcparams_pvsyst(
|
|
436
|
+
effective_irradiance=pvsyst_fs_495['irrad_ref'],
|
|
437
|
+
temp_cell=pvsyst_fs_495['temp_ref'],
|
|
438
|
+
alpha_sc=pvsyst_fs_495['alpha_sc'],
|
|
439
|
+
gamma_ref=pvsyst_fs_495['gamma_ref'],
|
|
440
|
+
mu_gamma=pvsyst_fs_495['mu_gamma'], I_L_ref=pvsyst_fs_495['I_L_ref'],
|
|
441
|
+
I_o_ref=pvsyst_fs_495['I_o_ref'], R_sh_ref=pvsyst_fs_495['R_sh_ref'],
|
|
442
|
+
R_sh_0=pvsyst_fs_495['R_sh_0'], R_sh_exp=pvsyst_fs_495['R_sh_exp'],
|
|
443
|
+
R_s=pvsyst_fs_495['R_s'],
|
|
444
|
+
cells_in_series=pvsyst_fs_495['cells_in_series'],
|
|
445
|
+
EgRef=pvsyst_fs_495['EgRef']
|
|
446
|
+
)
|
|
447
|
+
y = dict(d2mutau=pvsyst_fs_495['d2mutau'],
|
|
448
|
+
NsVbi=VOLTAGE_BUILTIN*pvsyst_fs_495['cells_in_series'])
|
|
449
|
+
# Convert (*x, **y) in a bishop88_.* call to dict of arguments
|
|
450
|
+
args_dict = {
|
|
451
|
+
'photocurrent': x[0],
|
|
452
|
+
'saturation_current': x[1],
|
|
453
|
+
'resistance_series': x[2],
|
|
454
|
+
'resistance_shunt': x[3],
|
|
455
|
+
'nNsVth': x[4],
|
|
456
|
+
}
|
|
457
|
+
args_dict.update(y)
|
|
458
|
+
return args_dict
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
@pytest.mark.parametrize('method, method_kwargs', [
|
|
462
|
+
('newton', {
|
|
463
|
+
'tol': 1e-8,
|
|
464
|
+
'rtol': 1e-8,
|
|
465
|
+
'maxiter': 30,
|
|
466
|
+
}),
|
|
467
|
+
('brentq', {
|
|
468
|
+
'xtol': 1e-8,
|
|
469
|
+
'rtol': 1e-8,
|
|
470
|
+
'maxiter': 30,
|
|
471
|
+
})
|
|
472
|
+
])
|
|
473
|
+
def test_bishop88_kwargs_transfer(method, method_kwargs, mocker,
|
|
474
|
+
bishop88_arguments):
|
|
475
|
+
"""test method_kwargs modifying optimizer does not break anything"""
|
|
476
|
+
# patch method namespace at singlediode module namespace
|
|
477
|
+
optimizer_mock = mocker.patch('pvlib.singlediode.' + method)
|
|
478
|
+
|
|
479
|
+
# check kwargs passed to bishop_.* are a subset of the call args
|
|
480
|
+
# since they are called with more keyword arguments
|
|
481
|
+
|
|
482
|
+
bishop88_i_from_v(0, **bishop88_arguments, method=method,
|
|
483
|
+
method_kwargs=method_kwargs)
|
|
484
|
+
_, kwargs = optimizer_mock.call_args
|
|
485
|
+
assert method_kwargs.items() <= kwargs.items()
|
|
486
|
+
|
|
487
|
+
bishop88_v_from_i(0, **bishop88_arguments, method=method,
|
|
488
|
+
method_kwargs=method_kwargs)
|
|
489
|
+
_, kwargs = optimizer_mock.call_args
|
|
490
|
+
assert method_kwargs.items() <= kwargs.items()
|
|
491
|
+
|
|
492
|
+
bishop88_mpp(**bishop88_arguments, method=method,
|
|
493
|
+
method_kwargs=method_kwargs)
|
|
494
|
+
_, kwargs = optimizer_mock.call_args
|
|
495
|
+
assert method_kwargs.items() <= kwargs.items()
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
@pytest.mark.parametrize('method, method_kwargs', [
|
|
499
|
+
('newton', {
|
|
500
|
+
'tol': 1e-4,
|
|
501
|
+
'rtol': 1e-4,
|
|
502
|
+
'maxiter': 20,
|
|
503
|
+
'_inexistent_param': "0.01"
|
|
504
|
+
}),
|
|
505
|
+
('brentq', {
|
|
506
|
+
'xtol': 1e-4,
|
|
507
|
+
'rtol': 1e-4,
|
|
508
|
+
'maxiter': 20,
|
|
509
|
+
'_inexistent_param': "0.01"
|
|
510
|
+
})
|
|
511
|
+
])
|
|
512
|
+
def test_bishop88_kwargs_fails(method, method_kwargs, bishop88_arguments):
|
|
513
|
+
"""test invalid method_kwargs passed onto the optimizer fail"""
|
|
514
|
+
|
|
515
|
+
pytest.raises(TypeError, bishop88_i_from_v,
|
|
516
|
+
0, **bishop88_arguments, method=method,
|
|
517
|
+
method_kwargs=method_kwargs)
|
|
518
|
+
|
|
519
|
+
pytest.raises(TypeError, bishop88_v_from_i,
|
|
520
|
+
0, **bishop88_arguments, method=method,
|
|
521
|
+
method_kwargs=method_kwargs)
|
|
522
|
+
|
|
523
|
+
pytest.raises(TypeError, bishop88_mpp,
|
|
524
|
+
**bishop88_arguments, method=method,
|
|
525
|
+
method_kwargs=method_kwargs)
|
|
526
|
+
|
|
527
|
+
|
|
528
|
+
@pytest.mark.parametrize('method', ['newton', 'brentq'])
|
|
529
|
+
def test_bishop88_full_output_kwarg(method, bishop88_arguments):
|
|
530
|
+
"""test call to bishop88_.* with full_output=True return values are ok"""
|
|
531
|
+
method_kwargs = {'full_output': True}
|
|
532
|
+
|
|
533
|
+
ret_val = bishop88_i_from_v(0, **bishop88_arguments, method=method,
|
|
534
|
+
method_kwargs=method_kwargs)
|
|
535
|
+
assert isinstance(ret_val, tuple) # ret_val must be a tuple
|
|
536
|
+
assert len(ret_val) == 2 # of two elements
|
|
537
|
+
assert isinstance(ret_val[0], float) # first one has bishop88 result
|
|
538
|
+
assert isinstance(ret_val[1], tuple) # second is output from optimizer
|
|
539
|
+
# any root finder returns at least 2 elements with full_output=True
|
|
540
|
+
assert len(ret_val[1]) >= 2
|
|
541
|
+
|
|
542
|
+
ret_val = bishop88_v_from_i(0, **bishop88_arguments, method=method,
|
|
543
|
+
method_kwargs=method_kwargs)
|
|
544
|
+
assert isinstance(ret_val, tuple) # ret_val must be a tuple
|
|
545
|
+
assert len(ret_val) == 2 # of two elements
|
|
546
|
+
assert isinstance(ret_val[0], float) # first one has bishop88 result
|
|
547
|
+
assert isinstance(ret_val[1], tuple) # second is output from optimizer
|
|
548
|
+
# any root finder returns at least 2 elements with full_output=True
|
|
549
|
+
assert len(ret_val[1]) >= 2
|
|
550
|
+
|
|
551
|
+
ret_val = bishop88_mpp(**bishop88_arguments, method=method,
|
|
552
|
+
method_kwargs=method_kwargs)
|
|
553
|
+
assert isinstance(ret_val, tuple) # ret_val must be a tuple
|
|
554
|
+
assert len(ret_val) == 2 # of two elements
|
|
555
|
+
assert isinstance(ret_val[0], tuple) # first one has bishop88 result
|
|
556
|
+
assert len(ret_val[0]) == 3 # of three elements (I,V,P)
|
|
557
|
+
assert isinstance(ret_val[1], tuple) # second is output from optimizer
|
|
558
|
+
# any root finder returns at least 2 elements with full_output=True
|
|
559
|
+
assert len(ret_val[1]) >= 2
|
pvlib/tests/test_snow.py
CHANGED
|
@@ -6,6 +6,8 @@ from .conftest import assert_series_equal
|
|
|
6
6
|
from pvlib import snow
|
|
7
7
|
from pvlib.tools import sind
|
|
8
8
|
|
|
9
|
+
import pytest
|
|
10
|
+
|
|
9
11
|
|
|
10
12
|
def test_fully_covered_nrel():
|
|
11
13
|
dt = pd.date_range(start="2019-1-1 12:00:00", end="2019-1-1 18:00:00",
|
|
@@ -108,6 +110,7 @@ def test__townsend_effective_snow():
|
|
|
108
110
|
|
|
109
111
|
|
|
110
112
|
def test_loss_townsend():
|
|
113
|
+
# hand-calculated solution
|
|
111
114
|
snow_total = np.array([25.4, 25.4, 12.7, 2.54, 0, 0, 0, 0, 0, 0, 12.7,
|
|
112
115
|
25.4])
|
|
113
116
|
snow_events = np.array([2, 2, 1, 0, 0, 0, 0, 0, 0, 0, 2, 3])
|
|
@@ -118,6 +121,7 @@ def test_loss_townsend():
|
|
|
118
121
|
poa_global = np.array([350000, 350000, 350000, 350000, 350000, 350000,
|
|
119
122
|
350000, 350000, 350000, 350000, 350000, 350000])
|
|
120
123
|
angle_of_repose = 40
|
|
124
|
+
string_factor = 1.0
|
|
121
125
|
slant_height = 2.54
|
|
122
126
|
lower_edge_height = 0.254
|
|
123
127
|
expected = np.array([0.07696253, 0.07992262, 0.06216201, 0.01715392, 0, 0,
|
|
@@ -125,5 +129,84 @@ def test_loss_townsend():
|
|
|
125
129
|
actual = snow.loss_townsend(snow_total, snow_events, surface_tilt,
|
|
126
130
|
relative_humidity, temp_air,
|
|
127
131
|
poa_global, slant_height,
|
|
128
|
-
lower_edge_height,
|
|
132
|
+
lower_edge_height, string_factor,
|
|
133
|
+
angle_of_repose)
|
|
129
134
|
np.testing.assert_allclose(expected, actual, rtol=1e-05)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
@pytest.mark.parametrize(
|
|
138
|
+
'poa_global,surface_tilt,slant_height,lower_edge_height,string_factor,expected', # noQA: E501
|
|
139
|
+
[
|
|
140
|
+
(np.asarray(
|
|
141
|
+
[60., 80., 100., 125., 175., 225., 225., 210., 175., 125., 90.,
|
|
142
|
+
60.], dtype=float) * 1000.,
|
|
143
|
+
2.,
|
|
144
|
+
79. / 39.37,
|
|
145
|
+
3. / 39.37,
|
|
146
|
+
1.0,
|
|
147
|
+
np.asarray(
|
|
148
|
+
[44, 34, 20, 9, 3, 1, 0, 0, 0, 2, 6, 25], dtype=float)
|
|
149
|
+
),
|
|
150
|
+
(np.asarray(
|
|
151
|
+
[60., 80., 100., 125., 175., 225., 225., 210., 175., 125., 90.,
|
|
152
|
+
60.], dtype=float) * 1000.,
|
|
153
|
+
5.,
|
|
154
|
+
316 / 39.37,
|
|
155
|
+
120. / 39.37,
|
|
156
|
+
0.75,
|
|
157
|
+
np.asarray(
|
|
158
|
+
[22, 16, 9, 4, 1, 0, 0, 0, 0, 1, 2, 12], dtype=float)
|
|
159
|
+
),
|
|
160
|
+
(np.asarray(
|
|
161
|
+
[60., 80., 100., 125., 175., 225., 225., 210., 175., 125., 90.,
|
|
162
|
+
60.], dtype=float) * 1000.,
|
|
163
|
+
23.,
|
|
164
|
+
158 / 39.27,
|
|
165
|
+
12 / 39.37,
|
|
166
|
+
0.75,
|
|
167
|
+
np.asarray(
|
|
168
|
+
[28, 21, 13, 6, 2, 0, 0, 0, 0, 1, 4, 16], dtype=float)
|
|
169
|
+
),
|
|
170
|
+
(np.asarray(
|
|
171
|
+
[80., 100., 125., 150., 225., 300., 300., 275., 225., 150., 115.,
|
|
172
|
+
80.], dtype=float) * 1000.,
|
|
173
|
+
52.,
|
|
174
|
+
39.5 / 39.37,
|
|
175
|
+
34. / 39.37,
|
|
176
|
+
0.75,
|
|
177
|
+
np.asarray(
|
|
178
|
+
[7, 5, 3, 1, 0, 0, 0, 0, 0, 0, 1, 4], dtype=float)
|
|
179
|
+
),
|
|
180
|
+
(np.asarray(
|
|
181
|
+
[80., 100., 125., 150., 225., 300., 300., 275., 225., 150., 115.,
|
|
182
|
+
80.], dtype=float) * 1000.,
|
|
183
|
+
60.,
|
|
184
|
+
39.5 / 39.37,
|
|
185
|
+
25. / 39.37,
|
|
186
|
+
1.,
|
|
187
|
+
np.asarray(
|
|
188
|
+
[7, 5, 3, 1, 0, 0, 0, 0, 0, 0, 1, 3], dtype=float)
|
|
189
|
+
)
|
|
190
|
+
]
|
|
191
|
+
)
|
|
192
|
+
def test_loss_townsend_cases(poa_global, surface_tilt, slant_height,
|
|
193
|
+
lower_edge_height, string_factor, expected):
|
|
194
|
+
# test cases from Townsend, 1/27/2023, addeed by cwh
|
|
195
|
+
# snow_total in inches, convert to cm for pvlib
|
|
196
|
+
snow_total = np.asarray(
|
|
197
|
+
[20, 15, 10, 4, 1.5, 0, 0, 0, 0, 1.5, 4, 15], dtype=float) * 2.54
|
|
198
|
+
# snow events are an average for each month
|
|
199
|
+
snow_events = np.asarray(
|
|
200
|
+
[5, 4.2, 2.8, 1.3, 0.8, 0, 0, 0, 0, 0.5, 1.5, 4.5], dtype=float)
|
|
201
|
+
# air temperature in C
|
|
202
|
+
temp_air = np.asarray(
|
|
203
|
+
[-6., -2., 1., 4., 7., 10., 13., 16., 14., 12., 7., -3.], dtype=float)
|
|
204
|
+
# relative humidity in %
|
|
205
|
+
relative_humidity = np.asarray(
|
|
206
|
+
[78., 80., 75., 65., 60., 55., 55., 55., 50., 55., 60., 70.],
|
|
207
|
+
dtype=float)
|
|
208
|
+
actual = snow.loss_townsend(
|
|
209
|
+
snow_total, snow_events, surface_tilt, relative_humidity, temp_air,
|
|
210
|
+
poa_global, slant_height, lower_edge_height, string_factor)
|
|
211
|
+
actual = np.around(actual * 100)
|
|
212
|
+
assert np.allclose(expected, actual)
|
pvlib/tests/test_soiling.py
CHANGED
|
@@ -92,7 +92,7 @@ def test_hsu_no_cleaning(rainfall_input, expected_output):
|
|
|
92
92
|
tilt = 0.
|
|
93
93
|
expected_no_cleaning = expected_output
|
|
94
94
|
|
|
95
|
-
result = hsu(rainfall=rainfall, cleaning_threshold=10.,
|
|
95
|
+
result = hsu(rainfall=rainfall, cleaning_threshold=10., surface_tilt=tilt,
|
|
96
96
|
pm2_5=pm2_5, pm10=pm10, depo_veloc=depo_veloc,
|
|
97
97
|
rain_accum_period=pd.Timedelta('1h'))
|
|
98
98
|
assert_series_equal(result, expected_no_cleaning)
|
|
@@ -108,7 +108,7 @@ def test_hsu(rainfall_input, expected_output_2):
|
|
|
108
108
|
tilt = 0.
|
|
109
109
|
|
|
110
110
|
# three cleaning events at 4:00-6:00, 8:00-11:00, and 17:00-20:00
|
|
111
|
-
result = hsu(rainfall=rainfall, cleaning_threshold=0.5,
|
|
111
|
+
result = hsu(rainfall=rainfall, cleaning_threshold=0.5, surface_tilt=tilt,
|
|
112
112
|
pm2_5=pm2_5, pm10=pm10, depo_veloc=depo_veloc,
|
|
113
113
|
rain_accum_period=pd.Timedelta('3h'))
|
|
114
114
|
|
|
@@ -120,8 +120,8 @@ def test_hsu_defaults(rainfall_input, expected_output_1):
|
|
|
120
120
|
Test Soiling HSU function with default deposition velocity and default rain
|
|
121
121
|
accumulation period.
|
|
122
122
|
"""
|
|
123
|
-
result = hsu(rainfall=rainfall_input, cleaning_threshold=0.5,
|
|
124
|
-
pm2_5=1.0e-2, pm10=2.0e-2)
|
|
123
|
+
result = hsu(rainfall=rainfall_input, cleaning_threshold=0.5,
|
|
124
|
+
surface_tilt=0.0, pm2_5=1.0e-2, pm10=2.0e-2)
|
|
125
125
|
assert np.allclose(result.values, expected_output_1)
|
|
126
126
|
|
|
127
127
|
|
|
@@ -138,7 +138,7 @@ def test_hsu_variable_time_intervals(rainfall_input, expected_output_3):
|
|
|
138
138
|
rain['new_time'] = rain.index + rain['mins_added']
|
|
139
139
|
rain_var_times = rain.set_index('new_time').iloc[:, 0]
|
|
140
140
|
result = hsu(
|
|
141
|
-
rainfall=rain_var_times, cleaning_threshold=0.5,
|
|
141
|
+
rainfall=rain_var_times, cleaning_threshold=0.5, surface_tilt=50.0,
|
|
142
142
|
pm2_5=1, pm10=2, depo_veloc=depo_veloc,
|
|
143
143
|
rain_accum_period=pd.Timedelta('2h'))
|
|
144
144
|
assert np.allclose(result, expected_output_3)
|
|
@@ -147,8 +147,9 @@ def test_hsu_variable_time_intervals(rainfall_input, expected_output_3):
|
|
|
147
147
|
@pytest.fixture
|
|
148
148
|
def greensboro_rain():
|
|
149
149
|
# get TMY3 data with rain
|
|
150
|
-
greensboro, _ = read_tmy3(DATA_DIR / '723170TYA.CSV', coerce_year=1990
|
|
151
|
-
|
|
150
|
+
greensboro, _ = read_tmy3(DATA_DIR / '723170TYA.CSV', coerce_year=1990,
|
|
151
|
+
map_variables=True)
|
|
152
|
+
return greensboro['Lprecip depth (mm)']
|
|
152
153
|
|
|
153
154
|
|
|
154
155
|
@pytest.fixture
|
|
@@ -163,7 +163,7 @@ def test_sun_rise_set_transit_spa(expected_rise_set_spa, golden):
|
|
|
163
163
|
result_rounded = pd.DataFrame(index=result.index)
|
|
164
164
|
# need to iterate because to_datetime does not accept 2D data
|
|
165
165
|
# the rounding fails on pandas < 0.17
|
|
166
|
-
for col, data in result.
|
|
166
|
+
for col, data in result.items():
|
|
167
167
|
result_rounded[col] = data.dt.round('1s')
|
|
168
168
|
|
|
169
169
|
assert_frame_equal(frame, result_rounded)
|
|
@@ -176,7 +176,7 @@ def test_sun_rise_set_transit_spa(expected_rise_set_spa, golden):
|
|
|
176
176
|
# round to nearest minute
|
|
177
177
|
result_rounded = pd.DataFrame(index=result.index)
|
|
178
178
|
# need to iterate because to_datetime does not accept 2D data
|
|
179
|
-
for col, data in result.
|
|
179
|
+
for col, data in result.items():
|
|
180
180
|
result_rounded[col] = data.dt.round('s').tz_convert('MST')
|
|
181
181
|
|
|
182
182
|
assert_frame_equal(expected_rise_set_spa, result_rounded)
|
|
@@ -191,7 +191,7 @@ def test_sun_rise_set_transit_ephem(expected_rise_set_ephem, golden):
|
|
|
191
191
|
temperature=11, horizon='-0:34')
|
|
192
192
|
# round to nearest minute
|
|
193
193
|
result_rounded = pd.DataFrame(index=result.index)
|
|
194
|
-
for col, data in result.
|
|
194
|
+
for col, data in result.items():
|
|
195
195
|
result_rounded[col] = data.dt.round('min').tz_convert('MST')
|
|
196
196
|
assert_frame_equal(expected_rise_set_ephem, result_rounded)
|
|
197
197
|
|
|
@@ -227,7 +227,7 @@ def test_sun_rise_set_transit_ephem(expected_rise_set_ephem, golden):
|
|
|
227
227
|
horizon='-0:34')
|
|
228
228
|
# round to nearest minute
|
|
229
229
|
result_rounded = pd.DataFrame(index=result.index)
|
|
230
|
-
for col, data in result.
|
|
230
|
+
for col, data in result.items():
|
|
231
231
|
result_rounded[col] = data.dt.round('min').tz_convert('MST')
|
|
232
232
|
assert_frame_equal(expected, result_rounded)
|
|
233
233
|
|
|
@@ -259,14 +259,14 @@ def test_sun_rise_set_transit_ephem(expected_rise_set_ephem, golden):
|
|
|
259
259
|
altitude=golden.altitude, pressure=0, temperature=11, horizon='-0:34')
|
|
260
260
|
# round to nearest minute
|
|
261
261
|
result_rounded = pd.DataFrame(index=result.index)
|
|
262
|
-
for col, data in result.
|
|
262
|
+
for col, data in result.items():
|
|
263
263
|
result_rounded[col] = data.dt.round('min').tz_convert('MST')
|
|
264
264
|
assert_frame_equal(expected, result_rounded)
|
|
265
265
|
|
|
266
266
|
# test with different timezone
|
|
267
267
|
times = times.tz_convert('UTC')
|
|
268
268
|
expected = expected.tz_convert('UTC') # resuse result from previous
|
|
269
|
-
for col, data in expected.
|
|
269
|
+
for col, data in expected.items():
|
|
270
270
|
expected[col] = data.dt.tz_convert('UTC')
|
|
271
271
|
result = solarposition.sun_rise_set_transit_ephem(
|
|
272
272
|
times,
|
|
@@ -274,7 +274,7 @@ def test_sun_rise_set_transit_ephem(expected_rise_set_ephem, golden):
|
|
|
274
274
|
altitude=golden.altitude, pressure=0, temperature=11, horizon='-0:34')
|
|
275
275
|
# round to nearest minute
|
|
276
276
|
result_rounded = pd.DataFrame(index=result.index)
|
|
277
|
-
for col, data in result.
|
|
277
|
+
for col, data in result.items():
|
|
278
278
|
result_rounded[col] = data.dt.round('min').tz_convert(times.tz)
|
|
279
279
|
assert_frame_equal(expected, result_rounded)
|
|
280
280
|
|
pvlib/tests/test_spa.py
CHANGED
|
@@ -154,13 +154,12 @@ class SpaBase:
|
|
|
154
154
|
def test_moon_ascending_longitude(self):
|
|
155
155
|
assert_almost_equal(X4, self.spa.moon_ascending_longitude(JCE), 6)
|
|
156
156
|
|
|
157
|
-
def
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
assert_almost_equal(dEpsilon,
|
|
163
|
-
JCE, X0, X1, X2, X3, X4), 6)
|
|
157
|
+
def test_longitude_obliquity_nutation(self):
|
|
158
|
+
out = np.empty((2,))
|
|
159
|
+
self.spa.longitude_obliquity_nutation(JCE, X0, X1, X2, X3, X4, out)
|
|
160
|
+
_dPsi, _dEpsilon = out[0], out[1]
|
|
161
|
+
assert_almost_equal(dPsi, _dPsi, 6)
|
|
162
|
+
assert_almost_equal(dEpsilon, _dEpsilon, 6)
|
|
164
163
|
|
|
165
164
|
def test_mean_ecliptic_obliquity(self):
|
|
166
165
|
assert_almost_equal(epsilon0, self.spa.mean_ecliptic_obliquity(JME), 6)
|