pvlib 0.10.4__py3-none-any.whl → 0.11.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 +1 -0
- pvlib/albedo.py +168 -0
- pvlib/bifacial/utils.py +2 -1
- pvlib/data/ASTMG173.csv +2004 -0
- pvlib/iam.py +28 -28
- pvlib/iotools/__init__.py +0 -1
- pvlib/iotools/midc.py +15 -10
- pvlib/iotools/psm3.py +10 -25
- pvlib/iotools/srml.py +1 -61
- pvlib/iotools/surfrad.py +1 -1
- pvlib/irradiance.py +133 -95
- pvlib/location.py +16 -6
- pvlib/modelchain.py +2 -165
- pvlib/pvarray.py +7 -5
- pvlib/pvsystem.py +75 -106
- pvlib/scaling.py +4 -2
- pvlib/shading.py +350 -0
- pvlib/singlediode.py +37 -9
- pvlib/snow.py +3 -1
- pvlib/spectrum/__init__.py +5 -0
- pvlib/spectrum/mismatch.py +573 -43
- pvlib/spectrum/spectrl2.py +8 -8
- pvlib/tests/bifacial/test_utils.py +6 -5
- pvlib/tests/iotools/test_psm3.py +0 -18
- pvlib/tests/iotools/test_srml.py +1 -43
- pvlib/tests/test_albedo.py +84 -0
- pvlib/tests/test_inverter.py +2 -2
- pvlib/tests/test_irradiance.py +35 -2
- pvlib/tests/test_location.py +26 -18
- pvlib/tests/test_modelchain.py +0 -57
- pvlib/tests/test_pvsystem.py +73 -128
- pvlib/tests/test_shading.py +167 -1
- pvlib/tests/test_singlediode.py +68 -29
- pvlib/tests/test_spectrum.py +283 -22
- pvlib/tests/test_temperature.py +7 -7
- pvlib/tests/test_tools.py +24 -0
- pvlib/tests/test_transformer.py +60 -0
- pvlib/tools.py +27 -0
- pvlib/transformer.py +117 -0
- pvlib/version.py +1 -5
- {pvlib-0.10.4.dist-info → pvlib-0.11.0.dist-info}/METADATA +3 -4
- {pvlib-0.10.4.dist-info → pvlib-0.11.0.dist-info}/RECORD +46 -42
- {pvlib-0.10.4.dist-info → pvlib-0.11.0.dist-info}/WHEEL +1 -1
- pvlib/data/astm_g173_am15g.csv +0 -2003
- {pvlib-0.10.4.dist-info → pvlib-0.11.0.dist-info}/AUTHORS.md +0 -0
- {pvlib-0.10.4.dist-info → pvlib-0.11.0.dist-info}/LICENSE +0 -0
- {pvlib-0.10.4.dist-info → pvlib-0.11.0.dist-info}/top_level.txt +0 -0
pvlib/tests/test_pvsystem.py
CHANGED
|
@@ -6,8 +6,7 @@ from numpy import nan, array
|
|
|
6
6
|
import pandas as pd
|
|
7
7
|
|
|
8
8
|
import pytest
|
|
9
|
-
from .conftest import
|
|
10
|
-
assert_series_equal, assert_frame_equal, fail_on_pvlib_version)
|
|
9
|
+
from .conftest import assert_series_equal, assert_frame_equal
|
|
11
10
|
from numpy.testing import assert_allclose
|
|
12
11
|
import unittest.mock as mock
|
|
13
12
|
|
|
@@ -103,82 +102,57 @@ def test_PVSystem_get_iam_invalid(sapm_module_params, mocker):
|
|
|
103
102
|
system.get_iam(45, iam_model='not_a_model')
|
|
104
103
|
|
|
105
104
|
|
|
106
|
-
def
|
|
105
|
+
def test_retrieve_sam_raises_exceptions():
|
|
107
106
|
"""
|
|
108
|
-
Raise an exception if
|
|
107
|
+
Raise an exception if an invalid parameter is provided to `retrieve_sam()`.
|
|
109
108
|
"""
|
|
110
|
-
with pytest.raises(ValueError
|
|
109
|
+
with pytest.raises(ValueError, match="Please provide either"):
|
|
111
110
|
pvsystem.retrieve_sam()
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
'
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
'
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
'
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
'
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
111
|
+
with pytest.raises(ValueError, match="Please provide either.*, not both."):
|
|
112
|
+
pvsystem.retrieve_sam(name="this_surely_wont_work", path="wont_work")
|
|
113
|
+
with pytest.raises(KeyError, match="Invalid name"):
|
|
114
|
+
pvsystem.retrieve_sam(name="this_surely_wont_work")
|
|
115
|
+
with pytest.raises(FileNotFoundError):
|
|
116
|
+
pvsystem.retrieve_sam(path="this_surely_wont_work.csv")
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def test_retrieve_sam_databases():
|
|
120
|
+
"""Test the expected keys are retrieved from each database."""
|
|
121
|
+
keys_per_database = {
|
|
122
|
+
"cecmod": {'Technology', 'Bifacial', 'STC', 'PTC', 'A_c', 'Length',
|
|
123
|
+
'Width', 'N_s', 'I_sc_ref', 'V_oc_ref', 'I_mp_ref',
|
|
124
|
+
'V_mp_ref', 'alpha_sc', 'beta_oc', 'T_NOCT', 'a_ref',
|
|
125
|
+
'I_L_ref', 'I_o_ref', 'R_s', 'R_sh_ref', 'Adjust',
|
|
126
|
+
'gamma_r', 'BIPV', 'Version', 'Date'},
|
|
127
|
+
"sandiamod": {'Vintage', 'Area', 'Material', 'Cells_in_Series',
|
|
128
|
+
'Parallel_Strings', 'Isco', 'Voco', 'Impo', 'Vmpo',
|
|
129
|
+
'Aisc', 'Aimp', 'C0', 'C1', 'Bvoco', 'Mbvoc', 'Bvmpo',
|
|
130
|
+
'Mbvmp', 'N', 'C2', 'C3', 'A0', 'A1', 'A2', 'A3', 'A4',
|
|
131
|
+
'B0', 'B1', 'B2', 'B3', 'B4', 'B5', 'DTC', 'FD', 'A',
|
|
132
|
+
'B', 'C4', 'C5', 'IXO', 'IXXO', 'C6', 'C7', 'Notes'},
|
|
133
|
+
"adrinverter": {'Manufacturer', 'Model', 'Source', 'Vac', 'Vintage',
|
|
134
|
+
'Pacmax', 'Pnom', 'Vnom', 'Vmin', 'Vmax',
|
|
135
|
+
'ADRCoefficients', 'Pnt', 'Vdcmax', 'Idcmax',
|
|
136
|
+
'MPPTLow', 'MPPTHi', 'TambLow', 'TambHi', 'Weight',
|
|
137
|
+
'PacFitErrMax', 'YearOfData'},
|
|
138
|
+
"cecinverter": {'Vac', 'Paco', 'Pdco', 'Vdco', 'Pso', 'C0', 'C1', 'C2',
|
|
139
|
+
'C3', 'Pnt', 'Vdcmax', 'Idcmax', 'Mppt_low',
|
|
140
|
+
'Mppt_high', 'CEC_Date', 'CEC_Type'}
|
|
141
|
+
} # fmt: skip
|
|
142
|
+
item_per_database = {
|
|
143
|
+
"cecmod": "Itek_Energy_LLC_iT_300_HE",
|
|
144
|
+
"sandiamod": "Canadian_Solar_CS6X_300M__2013_",
|
|
145
|
+
"adrinverter": "Sainty_Solar__SSI_4K4U_240V__CEC_2011_",
|
|
146
|
+
"cecinverter": "ABB__PVI_3_0_OUTD_S_US__208V_",
|
|
147
|
+
}
|
|
148
|
+
# duplicate the cecinverter items for sandiainverter, for backwards compat
|
|
149
|
+
keys_per_database["sandiainverter"] = keys_per_database["cecinverter"]
|
|
150
|
+
item_per_database["sandiainverter"] = item_per_database["cecinverter"]
|
|
153
151
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
expected keys for that inverter.
|
|
159
|
-
"""
|
|
160
|
-
data = pvsystem.retrieve_sam('cecinverter')
|
|
161
|
-
keys = [
|
|
162
|
-
'Vac',
|
|
163
|
-
'Paco',
|
|
164
|
-
'Pdco',
|
|
165
|
-
'Vdco',
|
|
166
|
-
'Pso',
|
|
167
|
-
'C0',
|
|
168
|
-
'C1',
|
|
169
|
-
'C2',
|
|
170
|
-
'C3',
|
|
171
|
-
'Pnt',
|
|
172
|
-
'Vdcmax',
|
|
173
|
-
'Idcmax',
|
|
174
|
-
'Mppt_low',
|
|
175
|
-
'Mppt_high',
|
|
176
|
-
'CEC_Date',
|
|
177
|
-
'CEC_Type',
|
|
178
|
-
]
|
|
179
|
-
inverter = 'Yaskawa_Solectria_Solar__PVI_5300_208__208V_'
|
|
180
|
-
assert inverter in data
|
|
181
|
-
assert set(data[inverter].keys()) == set(keys)
|
|
152
|
+
for database in keys_per_database.keys():
|
|
153
|
+
data = pvsystem.retrieve_sam(database)
|
|
154
|
+
assert set(data.index) == keys_per_database[database]
|
|
155
|
+
assert item_per_database[database] in data.columns
|
|
182
156
|
|
|
183
157
|
|
|
184
158
|
def test_sapm(sapm_module_params):
|
|
@@ -191,16 +165,14 @@ def test_sapm(sapm_module_params):
|
|
|
191
165
|
out = pvsystem.sapm(effective_irradiance, temp_cell, sapm_module_params)
|
|
192
166
|
|
|
193
167
|
expected = pd.DataFrame(np.array(
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
[ nan, nan, nan, nan,
|
|
203
|
-
nan, nan, nan]]),
|
|
168
|
+
[[-5.0608322, -4.65037767, np.nan, np.nan, np.nan,
|
|
169
|
+
-4.91119927, -4.16721569],
|
|
170
|
+
[2.545575, 2.28773882, 56.86182059, 47.21121608, 108.00693168,
|
|
171
|
+
2.48357383, 1.71782772],
|
|
172
|
+
[5.65584763, 5.01709903, 54.1943277, 42.51861718, 213.32011294,
|
|
173
|
+
5.52987899, 3.46796463],
|
|
174
|
+
[np.nan, np.nan, np.nan, np.nan, np.nan, np.nan, np.nan],
|
|
175
|
+
[np.nan, np.nan, np.nan, np.nan, np.nan, np.nan, np.nan]]),
|
|
204
176
|
columns=['i_sc', 'i_mp', 'v_oc', 'v_mp', 'p_mp', 'i_x', 'i_xx'],
|
|
205
177
|
index=times)
|
|
206
178
|
|
|
@@ -209,13 +181,13 @@ def test_sapm(sapm_module_params):
|
|
|
209
181
|
out = pvsystem.sapm(1000, 25, sapm_module_params)
|
|
210
182
|
|
|
211
183
|
expected = OrderedDict()
|
|
212
|
-
expected['i_sc'] =
|
|
213
|
-
expected['i_mp'] =
|
|
214
|
-
expected['v_oc'] =
|
|
215
|
-
expected['v_mp'] =
|
|
216
|
-
expected['p_mp'] =
|
|
217
|
-
expected['i_x'] =
|
|
218
|
-
expected['i_xx'] =
|
|
184
|
+
expected['i_sc'] = sapm_module_params['Isco']
|
|
185
|
+
expected['i_mp'] = sapm_module_params['Impo']
|
|
186
|
+
expected['v_oc'] = sapm_module_params['Voco']
|
|
187
|
+
expected['v_mp'] = sapm_module_params['Vmpo']
|
|
188
|
+
expected['p_mp'] = sapm_module_params['Impo'] * sapm_module_params['Vmpo']
|
|
189
|
+
expected['i_x'] = sapm_module_params['IXO']
|
|
190
|
+
expected['i_xx'] = sapm_module_params['IXXO']
|
|
219
191
|
|
|
220
192
|
for k, v in expected.items():
|
|
221
193
|
assert_allclose(out[k], v, atol=1e-4)
|
|
@@ -233,7 +205,7 @@ def test_PVSystem_sapm(sapm_module_params, mocker):
|
|
|
233
205
|
out = system.sapm(effective_irradiance, temp_cell)
|
|
234
206
|
pvsystem.sapm.assert_called_once_with(effective_irradiance, temp_cell,
|
|
235
207
|
sapm_module_params)
|
|
236
|
-
assert_allclose(out['p_mp'], 100,
|
|
208
|
+
assert_allclose(out['p_mp'], 100, 10)
|
|
237
209
|
|
|
238
210
|
|
|
239
211
|
def test_PVSystem_multi_array_sapm(sapm_module_params):
|
|
@@ -514,7 +486,7 @@ def test_PVSystem_faiman_celltemp(mocker):
|
|
|
514
486
|
winds = 1
|
|
515
487
|
out = system.get_cell_temperature(irrads, temps, winds, model='faiman')
|
|
516
488
|
temperature.faiman.assert_called_once_with(irrads, temps, winds, u0, u1)
|
|
517
|
-
assert_allclose(out, 56.4, atol=1)
|
|
489
|
+
assert_allclose(out, 56.4, atol=1e-1)
|
|
518
490
|
|
|
519
491
|
|
|
520
492
|
def test_PVSystem_noct_celltemp(mocker):
|
|
@@ -1089,7 +1061,7 @@ def test_PVSystem_calcparams_desoto(cec_module_params, mocker):
|
|
|
1089
1061
|
dEgdT=module_parameters['dEgdT']
|
|
1090
1062
|
)
|
|
1091
1063
|
|
|
1092
|
-
assert_allclose(IL, np.array([0.0, 6.036]), atol=1)
|
|
1064
|
+
assert_allclose(IL, np.array([0.0, 6.036]), atol=1e-1)
|
|
1093
1065
|
assert_allclose(I0, np.array([2.0e-9, 2.0e-9]), atol=1.0e-9)
|
|
1094
1066
|
assert_allclose(Rs, np.array([0.1, 0.1]), atol=0.1)
|
|
1095
1067
|
assert_allclose(Rsh, np.array([np.inf, 20]), atol=1)
|
|
@@ -1595,27 +1567,21 @@ def test_singlediode_floats():
|
|
|
1595
1567
|
assert_allclose(v, expected[k], atol=1e-6)
|
|
1596
1568
|
|
|
1597
1569
|
|
|
1598
|
-
def
|
|
1599
|
-
|
|
1600
|
-
out = pvsystem.singlediode(7., 6e-7, .1, 20., .5, ivcurve_pnts=3,
|
|
1601
|
-
method='lambertw')
|
|
1570
|
+
def test_singlediode_floats_expected():
|
|
1571
|
+
out = pvsystem.singlediode(7., 6e-7, .1, 20., .5, method='lambertw')
|
|
1602
1572
|
expected = {'i_xx': 4.264060478,
|
|
1603
1573
|
'i_mp': 6.136267360,
|
|
1604
1574
|
'v_oc': 8.106300147,
|
|
1605
1575
|
'p_mp': 38.19421055,
|
|
1606
1576
|
'i_x': 6.7558815684,
|
|
1607
1577
|
'i_sc': 6.965172322,
|
|
1608
|
-
'v_mp': 6.224339375
|
|
1609
|
-
'i': np.array([
|
|
1610
|
-
6.965172322, 6.755881568, 2.664535259e-14]),
|
|
1611
|
-
'v': np.array([
|
|
1612
|
-
0., 4.053150073, 8.106300147])}
|
|
1578
|
+
'v_mp': 6.224339375}
|
|
1613
1579
|
assert isinstance(out, dict)
|
|
1614
1580
|
for k, v in out.items():
|
|
1615
1581
|
assert_allclose(v, expected[k], atol=1e-6)
|
|
1616
1582
|
|
|
1617
1583
|
|
|
1618
|
-
def
|
|
1584
|
+
def test_singlediode_series_expected(cec_module_params):
|
|
1619
1585
|
times = pd.date_range(start='2015-06-01', periods=3, freq='6h')
|
|
1620
1586
|
effective_irradiance = pd.Series([0.0, 400.0, 800.0], index=times)
|
|
1621
1587
|
IL, I0, Rs, Rsh, nNsVth = pvsystem.calcparams_desoto(
|
|
@@ -1630,9 +1596,7 @@ def test_singlediode_series_ivcurve(cec_module_params):
|
|
|
1630
1596
|
EgRef=1.121,
|
|
1631
1597
|
dEgdT=-0.0002677)
|
|
1632
1598
|
|
|
1633
|
-
|
|
1634
|
-
out = pvsystem.singlediode(IL, I0, Rs, Rsh, nNsVth, ivcurve_pnts=3,
|
|
1635
|
-
method='lambertw')
|
|
1599
|
+
out = pvsystem.singlediode(IL, I0, Rs, Rsh, nNsVth, method='lambertw')
|
|
1636
1600
|
|
|
1637
1601
|
expected = OrderedDict([('i_sc', array([0., 3.01079860, 6.00726296])),
|
|
1638
1602
|
('v_oc', array([0., 9.96959733, 10.29603253])),
|
|
@@ -1640,41 +1604,22 @@ def test_singlediode_series_ivcurve(cec_module_params):
|
|
|
1640
1604
|
('v_mp', array([0., 8.321092255, 8.409413795])),
|
|
1641
1605
|
('p_mp', array([0., 22.10320053, 44.49021934])),
|
|
1642
1606
|
('i_x', array([0., 2.884132006, 5.746202281])),
|
|
1643
|
-
('i_xx', array([0., 2.052691562, 3.909673879]))
|
|
1644
|
-
('v', array([[0., 0., 0.],
|
|
1645
|
-
[0., 4.984798663, 9.969597327],
|
|
1646
|
-
[0., 5.148016266, 10.29603253]])),
|
|
1647
|
-
('i', array([[0., 0., 0.],
|
|
1648
|
-
[3.0107985972, 2.8841320056, 0.],
|
|
1649
|
-
[6.0072629615, 5.7462022810, 0.]]))])
|
|
1607
|
+
('i_xx', array([0., 2.052691562, 3.909673879]))])
|
|
1650
1608
|
|
|
1651
1609
|
for k, v in out.items():
|
|
1652
1610
|
assert_allclose(v, expected[k], atol=1e-2)
|
|
1653
1611
|
|
|
1654
|
-
|
|
1655
|
-
out = pvsystem.singlediode(IL, I0, Rs, Rsh, nNsVth, ivcurve_pnts=3)
|
|
1612
|
+
out = pvsystem.singlediode(IL, I0, Rs, Rsh, nNsVth)
|
|
1656
1613
|
|
|
1657
1614
|
expected['i_mp'] = pvsystem.i_from_v(out['v_mp'], IL, I0, Rs, Rsh, nNsVth,
|
|
1658
1615
|
method='lambertw')
|
|
1659
1616
|
expected['v_mp'] = pvsystem.v_from_i(out['i_mp'], IL, I0, Rs, Rsh, nNsVth,
|
|
1660
1617
|
method='lambertw')
|
|
1661
|
-
expected['i'] = pvsystem.i_from_v(out['v'].T, IL, I0, Rs, Rsh, nNsVth,
|
|
1662
|
-
method='lambertw').T
|
|
1663
|
-
expected['v'] = pvsystem.v_from_i(out['i'].T, IL, I0, Rs, Rsh, nNsVth,
|
|
1664
|
-
method='lambertw').T
|
|
1665
1618
|
|
|
1666
1619
|
for k, v in out.items():
|
|
1667
1620
|
assert_allclose(v, expected[k], atol=1e-6)
|
|
1668
1621
|
|
|
1669
1622
|
|
|
1670
|
-
@fail_on_pvlib_version('0.11')
|
|
1671
|
-
@pytest.mark.parametrize('method', ['lambertw', 'brentq', 'newton'])
|
|
1672
|
-
def test_singlediode_ivcurvepnts_deprecation_warning(method):
|
|
1673
|
-
with pytest.warns(pvlibDeprecationWarning, match='ivcurve_pnts'):
|
|
1674
|
-
pvsystem.singlediode(7., 6e-7, .1, 20., .5, ivcurve_pnts=3,
|
|
1675
|
-
method=method)
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
1623
|
def test_scale_voltage_current_power():
|
|
1679
1624
|
data = pd.DataFrame(
|
|
1680
1625
|
np.array([[2, 1.5, 10, 8, 12, 0.5, 1.5]]),
|
pvlib/tests/test_shading.py
CHANGED
|
@@ -2,11 +2,12 @@ import numpy as np
|
|
|
2
2
|
import pandas as pd
|
|
3
3
|
|
|
4
4
|
from pandas.testing import assert_series_equal
|
|
5
|
-
from numpy.testing import assert_allclose
|
|
5
|
+
from numpy.testing import assert_allclose, assert_approx_equal
|
|
6
6
|
import pytest
|
|
7
7
|
from datetime import timezone, timedelta
|
|
8
8
|
|
|
9
9
|
from pvlib import shading
|
|
10
|
+
from pvlib.tools import atand
|
|
10
11
|
|
|
11
12
|
|
|
12
13
|
@pytest.fixture
|
|
@@ -223,3 +224,168 @@ def test_projected_solar_zenith_angle_datatypes(
|
|
|
223
224
|
)
|
|
224
225
|
psz = psz_func(sun_apparent_zenith, axis_azimuth, axis_tilt, axis_azimuth)
|
|
225
226
|
assert isinstance(psz, cast_type)
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
@pytest.fixture
|
|
230
|
+
def sf1d_premises_and_expected():
|
|
231
|
+
"""Data comprised of solar position, rows parameters and terrain slope
|
|
232
|
+
with respective shade fractions (sf). Returns a 2-tuple with the premises
|
|
233
|
+
to be used directly in shaded_fraction1d(...) in the first element and
|
|
234
|
+
the expected shaded fractions in the second element.
|
|
235
|
+
See [1] in shaded_fraction1d()
|
|
236
|
+
Test data sourced from http://doi.org/10.5281/zenodo.10513987
|
|
237
|
+
"""
|
|
238
|
+
test_data = pd.DataFrame(
|
|
239
|
+
columns=["x_L", "z_L", "theta_L", "x_R", "z_R", "theta_R", "z_0", "l",
|
|
240
|
+
"theta_s", "f_s"],
|
|
241
|
+
data=(
|
|
242
|
+
(1, 0.2, 50, 0, 0, 25, 0, 0.5, 80, 1),
|
|
243
|
+
(1, 0.1, 50, 0, 0, 25, 0.05, 0.5, 80, 0.937191),
|
|
244
|
+
(1, 0, 50, 0, 0.1, 25, 0, 0.5, 80, 0.30605),
|
|
245
|
+
(1, 0, 50, 0, 0.2, 25, 0, 0.5, 80, 0),
|
|
246
|
+
(1, 0.2, -25, 0, 0, -50, 0, 0.5, -80, 0),
|
|
247
|
+
(1, 0.1, -25, 0, 0, -50, 0, 0.5, -80, 0.30605),
|
|
248
|
+
(1, 0, -25, 0, 0.1, -50, 0.1, 0.5, -80, 0.881549),
|
|
249
|
+
(1, 0, -25, 0, 0.2, -50, 0, 0.5, -80, 1),
|
|
250
|
+
(1, 0.2, 5, 0, 0, 25, 0.05, 0.5, 80, 0.832499),
|
|
251
|
+
(1, 0.2, -25, 0, 0, 25, 0.05, 0.5, 80, 0.832499),
|
|
252
|
+
(1, 0.2, 5, 0, 0, -45, 0.05, 0.5, 80, 0.832499),
|
|
253
|
+
(1, 0.2, -25, 0, 0, -45, 0.05, 0.5, 80, 0.832499),
|
|
254
|
+
(1, 0, -25, 0, 0.2, 25, 0.05, 0.5, -80, 0.832499),
|
|
255
|
+
(1, 0, -25, 0, 0.2, -5, 0.05, 0.5, -80, 0.832499),
|
|
256
|
+
(1, 0, 45, 0, 0.2, 25, 0.05, 0.5, -80, 0.832499),
|
|
257
|
+
(1, 0, 45, 0, 0.2, -5, 0.05, 0.5, -80, 0.832499),
|
|
258
|
+
),
|
|
259
|
+
) # fmt: skip
|
|
260
|
+
|
|
261
|
+
test_data["cross_axis_slope"] = atand(
|
|
262
|
+
(test_data["z_R"] - test_data["z_L"])
|
|
263
|
+
/ (test_data["x_L"] - test_data["x_R"])
|
|
264
|
+
)
|
|
265
|
+
test_data["pitch"] = test_data["x_L"] - test_data["x_R"]
|
|
266
|
+
# switch Left/Right rows if needed to make the right one the shaded
|
|
267
|
+
where_switch = test_data["theta_s"] >= 0
|
|
268
|
+
test_data["theta_L"], test_data["theta_R"] = np.where(
|
|
269
|
+
where_switch,
|
|
270
|
+
(test_data["theta_L"], test_data["theta_R"]),
|
|
271
|
+
(test_data["theta_R"], test_data["theta_L"]),
|
|
272
|
+
)
|
|
273
|
+
test_data.rename(
|
|
274
|
+
columns={
|
|
275
|
+
"theta_L": "shading_row_rotation",
|
|
276
|
+
"theta_R": "shaded_row_rotation",
|
|
277
|
+
"z_0": "surface_to_axis_offset",
|
|
278
|
+
"l": "collector_width",
|
|
279
|
+
"theta_s": "solar_zenith", # for the projected solar zenith angle
|
|
280
|
+
"f_s": "shaded_fraction",
|
|
281
|
+
},
|
|
282
|
+
inplace=True,
|
|
283
|
+
)
|
|
284
|
+
test_data.drop(columns=["x_L", "z_L", "x_R", "z_R"], inplace=True)
|
|
285
|
+
# for the projected solar zenith angle
|
|
286
|
+
# this returns the same psz angle as test_data["solar_zenith"]
|
|
287
|
+
test_data["solar_azimuth"], test_data["axis_azimuth"] = 180, 90
|
|
288
|
+
|
|
289
|
+
# return 1st: premises dataframe first and 2nd: shaded fraction series
|
|
290
|
+
return (
|
|
291
|
+
test_data.drop(columns=["shaded_fraction"]),
|
|
292
|
+
test_data["shaded_fraction"],
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def test_shaded_fraction1d(sf1d_premises_and_expected):
|
|
297
|
+
"""Tests shaded_fraction1d"""
|
|
298
|
+
# unwrap sf_premises_and_expected values premises and expected results
|
|
299
|
+
premises, expected_sf_array = sf1d_premises_and_expected
|
|
300
|
+
# test scalar input
|
|
301
|
+
expected_result = expected_sf_array.iloc[0]
|
|
302
|
+
sf = shading.shaded_fraction1d(**premises.iloc[0])
|
|
303
|
+
assert_approx_equal(sf, expected_result)
|
|
304
|
+
assert isinstance(sf, float)
|
|
305
|
+
|
|
306
|
+
# test Series inputs
|
|
307
|
+
sf_vec = shading.shaded_fraction1d(**premises)
|
|
308
|
+
assert_allclose(sf_vec, expected_sf_array, atol=1e-6)
|
|
309
|
+
assert isinstance(sf_vec, pd.Series)
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def test_shaded_fraction1d_unprovided_shading_row_rotation():
|
|
313
|
+
"""Tests shaded_fraction1d without providing shading_row_rotation"""
|
|
314
|
+
test_data = pd.DataFrame(
|
|
315
|
+
columns=[
|
|
316
|
+
"shaded_row_rotation", "surface_to_axis_offset", "collector_width",
|
|
317
|
+
"solar_zenith", "cross_axis_slope", "pitch", "solar_azimuth",
|
|
318
|
+
"axis_azimuth", "expected_sf",
|
|
319
|
+
],
|
|
320
|
+
data=[
|
|
321
|
+
(30, 0, 5.7735, 60, 0, 5, 90, 180, 0),
|
|
322
|
+
(30, 0, 5.7735, 79, 0, 5, 90, 180, 0.5),
|
|
323
|
+
(30, 0, 5.7735, 90, 0, 5, 90, 180, 1),
|
|
324
|
+
],
|
|
325
|
+
) # fmt: skip
|
|
326
|
+
expected_sf = test_data["expected_sf"]
|
|
327
|
+
premises = test_data.drop(columns=["expected_sf"])
|
|
328
|
+
sf = shading.shaded_fraction1d(**premises)
|
|
329
|
+
assert_allclose(sf, expected_sf, atol=1e-2)
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
@pytest.fixture
|
|
333
|
+
def direct_martinez_Table2():
|
|
334
|
+
"""
|
|
335
|
+
Original data used in [1] (see pvlib.shading.direct_martinez) to validate
|
|
336
|
+
the model. Some of the data is provided in Table 2.
|
|
337
|
+
Returns tuple with (input: pandas.DataFrame, output: pandas.Series)
|
|
338
|
+
Output is power loss: 1 - (P_shaded / P_unshaded)
|
|
339
|
+
"""
|
|
340
|
+
test_data = pd.DataFrame(
|
|
341
|
+
columns=[
|
|
342
|
+
"F_GS-H",
|
|
343
|
+
"F_GS-V",
|
|
344
|
+
"shaded_blocks",
|
|
345
|
+
"poa_direct",
|
|
346
|
+
"poa_diffuse",
|
|
347
|
+
"power_loss_model",
|
|
348
|
+
],
|
|
349
|
+
data=[
|
|
350
|
+
# F-H, F-V, Nsb, direct, diffuse, power_loss
|
|
351
|
+
# original data sourced from researchers
|
|
352
|
+
[1.00, 0.09, 16, 846.59, 59.42, 0.8844],
|
|
353
|
+
[1.00, 0.18, 16, 841.85, 59.69, 0.8888],
|
|
354
|
+
[1.00, 0.36, 16, 843.38, 59.22, 0.8994],
|
|
355
|
+
[0.04, 0.64, 1, 851.90, 59.40, 0.0783],
|
|
356
|
+
[0.17, 0.45, 3, 862.86, 58.40, 0.2237],
|
|
357
|
+
[0.29, 0.27, 5, 864.14, 58.11, 0.3282],
|
|
358
|
+
[0.50, 0.09, 8, 863.23, 58.31, 0.4634],
|
|
359
|
+
[0.13, 1.00, 2, 870.14, 58.02, 0.2137],
|
|
360
|
+
[0.25, 1.00, 4, 876.57, 57.98, 0.4000],
|
|
361
|
+
[0.38, 1.00, 6, 866.86, 58.89, 0.5577],
|
|
362
|
+
[0.50, 1.00, 8, 874.58, 58.44, 0.6892],
|
|
363
|
+
[0.58, 0.82, 10, 876.80, 58.16, 0.7359],
|
|
364
|
+
[0.75, 0.73, 12, 866.89, 58.73, 0.8113],
|
|
365
|
+
[0.92, 0.64, 15, 861.48, 59.66, 0.8894],
|
|
366
|
+
# custom edge cases
|
|
367
|
+
[0.00, 0.00, 0, 800.00, 50.00, 0.0000],
|
|
368
|
+
[1.00, 1.00, 16, 900.00, 00.00, 1.0000],
|
|
369
|
+
[0.00, 1.00, 16, 000.00, 00.00, np.nan],
|
|
370
|
+
[1.00, 0.00, 0, 000.00, 00.00, np.nan],
|
|
371
|
+
[1.00, 0.00, 0, -50.00, 50.00, np.nan], # zero poa_global
|
|
372
|
+
[1.00, 0.00, 0, 50.00, -50.00, np.nan], # zero poa_global
|
|
373
|
+
]
|
|
374
|
+
) # fmt: skip
|
|
375
|
+
test_data["total_blocks"] = 16 # total blocks is 16 for all cases
|
|
376
|
+
test_data["shaded_fraction"] = test_data["F_GS-H"] * test_data["F_GS-V"]
|
|
377
|
+
test_data["poa_global"] = (
|
|
378
|
+
test_data["poa_direct"] + test_data["poa_diffuse"]
|
|
379
|
+
)
|
|
380
|
+
test_data = test_data.drop(columns=["F_GS-H", "F_GS-V", "poa_diffuse"])
|
|
381
|
+
return (
|
|
382
|
+
test_data.drop(columns="power_loss_model"),
|
|
383
|
+
test_data["power_loss_model"],
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
def test_direct_martinez(direct_martinez_Table2):
|
|
388
|
+
"""Tests pvlib.shading.direct_martinez"""
|
|
389
|
+
test_data, power_losses_expected = direct_martinez_Table2
|
|
390
|
+
power_losses = shading.direct_martinez(**test_data)
|
|
391
|
+
assert_allclose(power_losses, power_losses_expected, atol=5e-3)
|
pvlib/tests/test_singlediode.py
CHANGED
|
@@ -8,8 +8,8 @@ import scipy
|
|
|
8
8
|
from pvlib import pvsystem
|
|
9
9
|
from pvlib.singlediode import (bishop88_mpp, estimate_voc, VOLTAGE_BUILTIN,
|
|
10
10
|
bishop88, bishop88_i_from_v, bishop88_v_from_i)
|
|
11
|
-
from pvlib._deprecation import pvlibDeprecationWarning
|
|
12
11
|
import pytest
|
|
12
|
+
from numpy.testing import assert_array_equal
|
|
13
13
|
from .conftest import DATA_DIR
|
|
14
14
|
|
|
15
15
|
POA = 888
|
|
@@ -167,35 +167,24 @@ def test_singlediode_precision(method, precise_iv_curves):
|
|
|
167
167
|
assert np.allclose(pc['i_xx'], outs['i_xx'], atol=1e-6, rtol=0)
|
|
168
168
|
|
|
169
169
|
|
|
170
|
-
def test_singlediode_lambert_negative_voc():
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
x = np.array([0., 1.480501e-11, 0.178, 8000., 1.797559])
|
|
174
|
-
outs = pvsystem.singlediode(*x, method='lambertw')
|
|
175
|
-
assert outs['v_oc'] == 0
|
|
176
|
-
|
|
177
|
-
# Testing for an array
|
|
178
|
-
x = np.array([x, x]).T
|
|
179
|
-
outs = pvsystem.singlediode(*x, method='lambertw')
|
|
180
|
-
assert np.array_equal(outs['v_oc'], [0, 0])
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
@pytest.mark.parametrize('method', ['lambertw'])
|
|
184
|
-
def test_ivcurve_pnts_precision(method, precise_iv_curves):
|
|
185
|
-
"""
|
|
186
|
-
Tests the accuracy of the IV curve points calcuated by singlediode. Only
|
|
187
|
-
methods of singlediode that linearly spaced points are tested.
|
|
170
|
+
def test_singlediode_lambert_negative_voc(mocker):
|
|
171
|
+
"""Tests approximation to zero of v_oc when it is negative and small.
|
|
172
|
+
See singlediode.py:_lambertw > comment 'Set small elements <0 in v_oc to 0'
|
|
188
173
|
"""
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
174
|
+
# Next values should result in a negative v_oc out of `_lambertw_v_from_i`
|
|
175
|
+
# however, we can't ensure that the output belongs to (-1e-12, 0), so we
|
|
176
|
+
# mock it. It depends on the platform and Python distro. See issue #2000.
|
|
177
|
+
patcher = mocker.patch("pvlib.singlediode._lambertw_v_from_i")
|
|
178
|
+
x = np.array([0.0, 1.480501e-11, 0.178, 8000.0, 1.797559])
|
|
179
|
+
patcher.return_value = -9.999e-13
|
|
180
|
+
outs = pvsystem.singlediode(*x, method="lambertw")
|
|
181
|
+
assert outs["v_oc"] == 0
|
|
192
182
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
assert np.allclose(pc_v, outs['v'], atol=1e-10, rtol=0)
|
|
183
|
+
# Testing for an array
|
|
184
|
+
patcher.return_value = np.array([-9.999e-13, -1.001e-13])
|
|
185
|
+
x = np.array([x, x]).T
|
|
186
|
+
outs = pvsystem.singlediode(*x, method="lambertw")
|
|
187
|
+
assert_array_equal(outs["v_oc"], [0, 0])
|
|
199
188
|
|
|
200
189
|
|
|
201
190
|
@pytest.mark.parametrize('method', ['lambertw', 'brentq', 'newton'])
|
|
@@ -347,7 +336,7 @@ def test_pvsyst_recombination_loss(method, poa, temp_cell, expected, tol):
|
|
|
347
336
|
# other conditions with breakdown model on and recombination model off
|
|
348
337
|
(
|
|
349
338
|
(1.e-4, -5.5, 3.28),
|
|
350
|
-
(0., np.
|
|
339
|
+
(0., np.inf),
|
|
351
340
|
POA,
|
|
352
341
|
TCELL,
|
|
353
342
|
{
|
|
@@ -567,3 +556,53 @@ def test_bishop88_pdSeries_len_one(method, bishop88_arguments):
|
|
|
567
556
|
bishop88_i_from_v(pd.Series([0]), **bishop88_arguments, method=method)
|
|
568
557
|
bishop88_v_from_i(pd.Series([0]), **bishop88_arguments, method=method)
|
|
569
558
|
bishop88_mpp(**bishop88_arguments, method=method)
|
|
559
|
+
|
|
560
|
+
|
|
561
|
+
def _sde_check_solution(i, v, il, io, rs, rsh, a, d2mutau=0., NsVbi=np.inf):
|
|
562
|
+
vd = v + rs * i
|
|
563
|
+
return il - io*np.expm1(vd/a) - vd/rsh - il*d2mutau/(NsVbi - vd) - i
|
|
564
|
+
|
|
565
|
+
|
|
566
|
+
@pytest.mark.parametrize('method', ['newton', 'brentq'])
|
|
567
|
+
def test_bishop88_init_cond(method):
|
|
568
|
+
# GH 2013
|
|
569
|
+
p = {'alpha_sc': 0.0012256,
|
|
570
|
+
'gamma_ref': 1.2916241612804187,
|
|
571
|
+
'mu_gamma': 0.00047308959960937403,
|
|
572
|
+
'I_L_ref': 3.068717040806731,
|
|
573
|
+
'I_o_ref': 2.2691248021217617e-11,
|
|
574
|
+
'R_sh_ref': 7000,
|
|
575
|
+
'R_sh_0': 7000,
|
|
576
|
+
'R_s': 4.602,
|
|
577
|
+
'cells_in_series': 268,
|
|
578
|
+
'R_sh_exp': 5.5,
|
|
579
|
+
'EgRef': 1.5}
|
|
580
|
+
NsVbi = 268 * 0.9
|
|
581
|
+
d2mutau = 1.4
|
|
582
|
+
irrad = np.arange(20, 1100, 20)
|
|
583
|
+
tc = np.arange(-25, 74, 1)
|
|
584
|
+
weather = np.array(np.meshgrid(irrad, tc)).T.reshape(-1, 2)
|
|
585
|
+
# with the above parameters and weather conditions, a few combinations
|
|
586
|
+
# result in voc_est > NsVbi, which causes failure of brentq and newton
|
|
587
|
+
# when the recombination parameters NsVbi and d2mutau are used.
|
|
588
|
+
sde_params = pvsystem.calcparams_pvsyst(weather[:, 0], weather[:, 1], **p)
|
|
589
|
+
# test _mpp
|
|
590
|
+
result = bishop88_mpp(*sde_params, d2mutau=d2mutau, NsVbi=NsVbi)
|
|
591
|
+
imp, vmp, pmp = result
|
|
592
|
+
err = np.abs(_sde_check_solution(
|
|
593
|
+
imp, vmp, sde_params[0], sde_params[1], sde_params[2], sde_params[3],
|
|
594
|
+
sde_params[4], d2mutau=d2mutau, NsVbi=NsVbi))
|
|
595
|
+
bad_results = np.isnan(pmp) | (pmp < 0) | (err > 0.00001) # 0.01mA error
|
|
596
|
+
assert not bad_results.any()
|
|
597
|
+
# test v_from_i
|
|
598
|
+
vmp2 = bishop88_v_from_i(imp, *sde_params, d2mutau=d2mutau, NsVbi=NsVbi)
|
|
599
|
+
err = np.abs(_sde_check_solution(imp, vmp2, *sde_params, d2mutau=d2mutau,
|
|
600
|
+
NsVbi=NsVbi))
|
|
601
|
+
bad_results = np.isnan(vmp2) | (vmp2 < 0) | (err > 0.00001)
|
|
602
|
+
assert not bad_results.any()
|
|
603
|
+
# test v_from_i
|
|
604
|
+
imp2 = bishop88_i_from_v(vmp, *sde_params, d2mutau=d2mutau, NsVbi=NsVbi)
|
|
605
|
+
err = np.abs(_sde_check_solution(imp2, vmp, *sde_params, d2mutau=d2mutau,
|
|
606
|
+
NsVbi=NsVbi))
|
|
607
|
+
bad_results = np.isnan(imp2) | (imp2 < 0) | (err > 0.00001)
|
|
608
|
+
assert not bad_results.any()
|