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.
Files changed (47) hide show
  1. pvlib/__init__.py +1 -0
  2. pvlib/albedo.py +168 -0
  3. pvlib/bifacial/utils.py +2 -1
  4. pvlib/data/ASTMG173.csv +2004 -0
  5. pvlib/iam.py +28 -28
  6. pvlib/iotools/__init__.py +0 -1
  7. pvlib/iotools/midc.py +15 -10
  8. pvlib/iotools/psm3.py +10 -25
  9. pvlib/iotools/srml.py +1 -61
  10. pvlib/iotools/surfrad.py +1 -1
  11. pvlib/irradiance.py +133 -95
  12. pvlib/location.py +16 -6
  13. pvlib/modelchain.py +2 -165
  14. pvlib/pvarray.py +7 -5
  15. pvlib/pvsystem.py +75 -106
  16. pvlib/scaling.py +4 -2
  17. pvlib/shading.py +350 -0
  18. pvlib/singlediode.py +37 -9
  19. pvlib/snow.py +3 -1
  20. pvlib/spectrum/__init__.py +5 -0
  21. pvlib/spectrum/mismatch.py +573 -43
  22. pvlib/spectrum/spectrl2.py +8 -8
  23. pvlib/tests/bifacial/test_utils.py +6 -5
  24. pvlib/tests/iotools/test_psm3.py +0 -18
  25. pvlib/tests/iotools/test_srml.py +1 -43
  26. pvlib/tests/test_albedo.py +84 -0
  27. pvlib/tests/test_inverter.py +2 -2
  28. pvlib/tests/test_irradiance.py +35 -2
  29. pvlib/tests/test_location.py +26 -18
  30. pvlib/tests/test_modelchain.py +0 -57
  31. pvlib/tests/test_pvsystem.py +73 -128
  32. pvlib/tests/test_shading.py +167 -1
  33. pvlib/tests/test_singlediode.py +68 -29
  34. pvlib/tests/test_spectrum.py +283 -22
  35. pvlib/tests/test_temperature.py +7 -7
  36. pvlib/tests/test_tools.py +24 -0
  37. pvlib/tests/test_transformer.py +60 -0
  38. pvlib/tools.py +27 -0
  39. pvlib/transformer.py +117 -0
  40. pvlib/version.py +1 -5
  41. {pvlib-0.10.4.dist-info → pvlib-0.11.0.dist-info}/METADATA +3 -4
  42. {pvlib-0.10.4.dist-info → pvlib-0.11.0.dist-info}/RECORD +46 -42
  43. {pvlib-0.10.4.dist-info → pvlib-0.11.0.dist-info}/WHEEL +1 -1
  44. pvlib/data/astm_g173_am15g.csv +0 -2003
  45. {pvlib-0.10.4.dist-info → pvlib-0.11.0.dist-info}/AUTHORS.md +0 -0
  46. {pvlib-0.10.4.dist-info → pvlib-0.11.0.dist-info}/LICENSE +0 -0
  47. {pvlib-0.10.4.dist-info → pvlib-0.11.0.dist-info}/top_level.txt +0 -0
@@ -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 test_retrieve_sam_raise_no_parameters():
105
+ def test_retrieve_sam_raises_exceptions():
107
106
  """
108
- Raise an exception if no parameters are provided to `retrieve_sam()`.
107
+ Raise an exception if an invalid parameter is provided to `retrieve_sam()`.
109
108
  """
110
- with pytest.raises(ValueError) as error:
109
+ with pytest.raises(ValueError, match="Please provide either"):
111
110
  pvsystem.retrieve_sam()
112
- assert 'A name or path must be provided!' == str(error.value)
113
-
114
-
115
- def test_retrieve_sam_cecmod():
116
- """
117
- Test the expected data is retrieved from the CEC module database. In
118
- particular, check for a known module in the database and check for the
119
- expected keys for that module.
120
- """
121
- data = pvsystem.retrieve_sam('cecmod')
122
- keys = [
123
- 'BIPV',
124
- 'Date',
125
- 'T_NOCT',
126
- 'A_c',
127
- 'N_s',
128
- 'I_sc_ref',
129
- 'V_oc_ref',
130
- 'I_mp_ref',
131
- 'V_mp_ref',
132
- 'alpha_sc',
133
- 'beta_oc',
134
- 'a_ref',
135
- 'I_L_ref',
136
- 'I_o_ref',
137
- 'R_s',
138
- 'R_sh_ref',
139
- 'Adjust',
140
- 'gamma_r',
141
- 'Version',
142
- 'STC',
143
- 'PTC',
144
- 'Technology',
145
- 'Bifacial',
146
- 'Length',
147
- 'Width',
148
- ]
149
- module = 'Itek_Energy_LLC_iT_300_HE'
150
- assert module in data
151
- assert set(data[module].keys()) == set(keys)
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
- def test_retrieve_sam_cecinverter():
155
- """
156
- Test the expected data is retrieved from the CEC inverter database. In
157
- particular, check for a known inverter in the database and check for the
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
- [[ -5.0608322 , -4.65037767, nan, nan,
195
- nan, -4.91119927, -4.15367716],
196
- [ 2.545575 , 2.28773882, 56.86182059, 47.21121608,
197
- 108.00693168, 2.48357383, 1.71782772],
198
- [ 5.65584763, 5.01709903, 54.1943277 , 42.51861718,
199
- 213.32011294, 5.52987899, 3.48660728],
200
- [ nan, nan, nan, nan,
201
- nan, nan, nan],
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'] = 5.09115
213
- expected['i_mp'] = 4.5462909092579995
214
- expected['v_oc'] = 59.260800000000003
215
- expected['v_mp'] = 48.315600000000003
216
- expected['p_mp'] = 219.65677305534581
217
- expected['i_x'] = 4.9759899999999995
218
- expected['i_xx'] = 3.1880204359100004
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, atol=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 test_singlediode_floats_ivcurve():
1599
- with pytest.warns(pvlibDeprecationWarning, match='ivcurve_pnts'):
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 test_singlediode_series_ivcurve(cec_module_params):
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
- with pytest.warns(pvlibDeprecationWarning, match='ivcurve_pnts'):
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
- with pytest.warns(pvlibDeprecationWarning, match='ivcurve_pnts'):
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]]),
@@ -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)
@@ -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
- # Those values result in a negative v_oc out of `_lambertw_v_from_i`
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
- x, pc = precise_iv_curves
190
- pc_i, pc_v = np.stack(pc['Currents']), np.stack(pc['Voltages'])
191
- ivcurve_pnts = len(pc['Currents'][0])
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
- with pytest.warns(pvlibDeprecationWarning, match='ivcurve_pnts'):
194
- outs = pvsystem.singlediode(method=method, ivcurve_pnts=ivcurve_pnts,
195
- **x)
196
-
197
- assert np.allclose(pc_i, outs['i'], atol=1e-10, rtol=0)
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.Inf),
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()