pvlib 0.11.2__py3-none-any.whl → 0.12.1a1__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 (147) hide show
  1. pvlib/__init__.py +1 -0
  2. pvlib/atmosphere.py +40 -40
  3. pvlib/bifacial/infinite_sheds.py +4 -3
  4. pvlib/bifacial/utils.py +2 -1
  5. pvlib/iotools/__init__.py +6 -0
  6. pvlib/iotools/psm3.py +1 -1
  7. pvlib/iotools/psm4.py +819 -0
  8. pvlib/iotools/pvgis.py +10 -2
  9. pvlib/iotools/tmy.py +3 -69
  10. pvlib/irradiance.py +38 -15
  11. pvlib/ivtools/sdm/__init__.py +20 -0
  12. pvlib/ivtools/sdm/_fit_desoto_pvsyst_sandia.py +585 -0
  13. pvlib/ivtools/sdm/cec.py +93 -0
  14. pvlib/ivtools/sdm/desoto.py +401 -0
  15. pvlib/ivtools/sdm/pvsyst.py +630 -0
  16. pvlib/location.py +73 -33
  17. pvlib/modelchain.py +19 -36
  18. pvlib/pvsystem.py +114 -65
  19. pvlib/snow.py +64 -28
  20. pvlib/spectrum/__init__.py +0 -1
  21. pvlib/spectrum/irradiance.py +2 -64
  22. pvlib/spectrum/mismatch.py +3 -3
  23. pvlib/tools.py +6 -5
  24. {pvlib-0.11.2.dist-info → pvlib-0.12.1a1.dist-info}/METADATA +6 -5
  25. pvlib-0.12.1a1.dist-info/RECORD +80 -0
  26. {pvlib-0.11.2.dist-info → pvlib-0.12.1a1.dist-info}/WHEEL +1 -1
  27. pvlib/data/BIRD_08_16_2012.csv +0 -8761
  28. pvlib/data/BIRD_08_16_2012_patm.csv +0 -8761
  29. pvlib/data/Burlington, United States SolarAnywhere Time Series 2021 Lat_44_465 Lon_-73_205 TMY3 format.csv +0 -8762
  30. pvlib/data/Burlington, United States SolarAnywhere Time Series 20210101 to 20210103 Lat_44_4675 Lon_-73_2075 SA format.csv +0 -578
  31. pvlib/data/Burlington, United States SolarAnywhere Typical GHI Year Lat_44_465 Lon_-73_205 SA format.csv +0 -74
  32. pvlib/data/CPS SCH275KTL-DO-US-800-250kW_275kVA_1.OND +0 -146
  33. pvlib/data/CRNS0101-05-2019-AZ_Tucson_11_W.txt +0 -4
  34. pvlib/data/CRN_with_problems.txt +0 -3
  35. pvlib/data/ET-M772BH550GL.PAN +0 -75
  36. pvlib/data/NLD_Amsterdam062400_IWEC.epw +0 -8768
  37. pvlib/data/PVsyst_demo.csv +0 -10757
  38. pvlib/data/PVsyst_demo_model.csv +0 -3588
  39. pvlib/data/SRML-day-EUPO1801.txt +0 -1441
  40. pvlib/data/abq19056.dat +0 -6
  41. pvlib/data/bishop88_numerical_precision.csv +0 -101
  42. pvlib/data/bsrn-lr0100-pay0616.dat +0 -86901
  43. pvlib/data/bsrn-pay0616.dat.gz +0 -0
  44. pvlib/data/cams_mcclear_1min_verbose.csv +0 -60
  45. pvlib/data/cams_mcclear_monthly.csv +0 -42
  46. pvlib/data/cams_radiation_1min_verbose.csv +0 -72
  47. pvlib/data/cams_radiation_monthly.csv +0 -47
  48. pvlib/data/detect_clearsky_data.csv +0 -35
  49. pvlib/data/detect_clearsky_threshold_data.csv +0 -126
  50. pvlib/data/greensboro_kimber_soil_manwash.dat +0 -8761
  51. pvlib/data/greensboro_kimber_soil_nowash.dat +0 -8761
  52. pvlib/data/inverter_fit_snl_meas.csv +0 -127
  53. pvlib/data/inverter_fit_snl_sim.csv +0 -19
  54. pvlib/data/ivtools_numdiff.csv +0 -52
  55. pvlib/data/midc_20181014.txt +0 -1441
  56. pvlib/data/midc_raw_20181018.txt +0 -1441
  57. pvlib/data/midc_raw_short_header_20191115.txt +0 -1441
  58. pvlib/data/msn19056.dat +0 -6
  59. pvlib/data/precise_iv_curves1.json +0 -10251
  60. pvlib/data/precise_iv_curves2.json +0 -10251
  61. pvlib/data/precise_iv_curves_parameter_sets1.csv +0 -33
  62. pvlib/data/precise_iv_curves_parameter_sets2.csv +0 -33
  63. pvlib/data/pvgis_hourly_Timeseries_45.000_8.000_SA2_10kWp_CIS_5_2a_2013_2014.json +0 -1
  64. pvlib/data/pvgis_hourly_Timeseries_45.000_8.000_SA_30deg_0deg_2016_2016.csv +0 -35
  65. pvlib/data/pvgis_tmy_meta.json +0 -32
  66. pvlib/data/pvgis_tmy_test.csv +0 -8761
  67. pvlib/data/pvwatts_8760_rackmount.csv +0 -8779
  68. pvlib/data/pvwatts_8760_roofmount.csv +0 -8779
  69. pvlib/data/singleaxis_tracker_wslope.csv +0 -8761
  70. pvlib/data/spectrl2_example_spectra.csv +0 -123
  71. pvlib/data/surfrad-slv16001.dat +0 -1442
  72. pvlib/data/test_psm3_2017.csv +0 -17521
  73. pvlib/data/test_psm3_2019_5min.csv +0 -289
  74. pvlib/data/test_psm3_tmy-2017.csv +0 -8761
  75. pvlib/data/test_read_psm3.csv +0 -17523
  76. pvlib/data/test_read_pvgis_horizon.csv +0 -49
  77. pvlib/data/tmy_45.000_8.000_2005_2023.csv +0 -8789
  78. pvlib/data/tmy_45.000_8.000_2005_2023.epw +0 -8768
  79. pvlib/data/tmy_45.000_8.000_2005_2023.json +0 -1
  80. pvlib/data/tmy_45.000_8.000_2005_2023.txt +0 -8761
  81. pvlib/data/tmy_45.000_8.000_userhorizon.json +0 -1
  82. pvlib/ivtools/sdm.py +0 -1379
  83. pvlib/spa_c_files/README.md +0 -81
  84. pvlib/spa_c_files/cspa_py.pxd +0 -43
  85. pvlib/spa_c_files/spa_py.pyx +0 -30
  86. pvlib/tests/__init__.py +0 -0
  87. pvlib/tests/bifacial/__init__.py +0 -0
  88. pvlib/tests/bifacial/test_infinite_sheds.py +0 -317
  89. pvlib/tests/bifacial/test_losses_models.py +0 -54
  90. pvlib/tests/bifacial/test_pvfactors.py +0 -82
  91. pvlib/tests/bifacial/test_utils.py +0 -192
  92. pvlib/tests/conftest.py +0 -476
  93. pvlib/tests/iotools/__init__.py +0 -0
  94. pvlib/tests/iotools/test_acis.py +0 -213
  95. pvlib/tests/iotools/test_bsrn.py +0 -131
  96. pvlib/tests/iotools/test_crn.py +0 -95
  97. pvlib/tests/iotools/test_epw.py +0 -23
  98. pvlib/tests/iotools/test_midc.py +0 -89
  99. pvlib/tests/iotools/test_panond.py +0 -32
  100. pvlib/tests/iotools/test_psm3.py +0 -198
  101. pvlib/tests/iotools/test_pvgis.py +0 -644
  102. pvlib/tests/iotools/test_sodapro.py +0 -298
  103. pvlib/tests/iotools/test_solaranywhere.py +0 -287
  104. pvlib/tests/iotools/test_solargis.py +0 -68
  105. pvlib/tests/iotools/test_solcast.py +0 -324
  106. pvlib/tests/iotools/test_solrad.py +0 -152
  107. pvlib/tests/iotools/test_srml.py +0 -124
  108. pvlib/tests/iotools/test_surfrad.py +0 -75
  109. pvlib/tests/iotools/test_tmy.py +0 -133
  110. pvlib/tests/ivtools/__init__.py +0 -0
  111. pvlib/tests/ivtools/test_sde.py +0 -230
  112. pvlib/tests/ivtools/test_sdm.py +0 -429
  113. pvlib/tests/ivtools/test_utils.py +0 -173
  114. pvlib/tests/spectrum/__init__.py +0 -0
  115. pvlib/tests/spectrum/conftest.py +0 -40
  116. pvlib/tests/spectrum/test_irradiance.py +0 -138
  117. pvlib/tests/spectrum/test_mismatch.py +0 -304
  118. pvlib/tests/spectrum/test_response.py +0 -124
  119. pvlib/tests/spectrum/test_spectrl2.py +0 -72
  120. pvlib/tests/test__deprecation.py +0 -97
  121. pvlib/tests/test_albedo.py +0 -84
  122. pvlib/tests/test_atmosphere.py +0 -351
  123. pvlib/tests/test_clearsky.py +0 -884
  124. pvlib/tests/test_conftest.py +0 -37
  125. pvlib/tests/test_iam.py +0 -555
  126. pvlib/tests/test_inverter.py +0 -213
  127. pvlib/tests/test_irradiance.py +0 -1487
  128. pvlib/tests/test_location.py +0 -356
  129. pvlib/tests/test_modelchain.py +0 -2020
  130. pvlib/tests/test_numerical_precision.py +0 -124
  131. pvlib/tests/test_pvarray.py +0 -71
  132. pvlib/tests/test_pvsystem.py +0 -2511
  133. pvlib/tests/test_scaling.py +0 -207
  134. pvlib/tests/test_shading.py +0 -391
  135. pvlib/tests/test_singlediode.py +0 -608
  136. pvlib/tests/test_snow.py +0 -212
  137. pvlib/tests/test_soiling.py +0 -230
  138. pvlib/tests/test_solarposition.py +0 -966
  139. pvlib/tests/test_spa.py +0 -454
  140. pvlib/tests/test_temperature.py +0 -470
  141. pvlib/tests/test_tools.py +0 -146
  142. pvlib/tests/test_tracking.py +0 -474
  143. pvlib/tests/test_transformer.py +0 -60
  144. pvlib-0.11.2.dist-info/RECORD +0 -191
  145. {pvlib-0.11.2.dist-info → pvlib-0.12.1a1.dist-info/licenses}/AUTHORS.md +0 -0
  146. {pvlib-0.11.2.dist-info → pvlib-0.12.1a1.dist-info/licenses}/LICENSE +0 -0
  147. {pvlib-0.11.2.dist-info → pvlib-0.12.1a1.dist-info}/top_level.txt +0 -0
pvlib/location.py CHANGED
@@ -6,6 +6,7 @@ This module contains the Location class.
6
6
 
7
7
  import pathlib
8
8
  import datetime
9
+ import zoneinfo
9
10
 
10
11
  import pandas as pd
11
12
  import pytz
@@ -18,13 +19,16 @@ from pvlib.tools import _degrees_to_index
18
19
  class Location:
19
20
  """
20
21
  Location objects are convenient containers for latitude, longitude,
21
- timezone, and altitude data associated with a particular
22
- geographic location. You can also assign a name to a location object.
22
+ time zone, and altitude data associated with a particular geographic
23
+ location. You can also assign a name to a location object.
23
24
 
24
- Location objects have two timezone attributes:
25
+ Location objects have two time-zone attributes:
25
26
 
26
- * ``tz`` is a IANA timezone string.
27
- * ``pytz`` is a pytz timezone object.
27
+ * ``tz`` is an IANA time-zone string.
28
+ * ``pytz`` is a pytz-based time-zone object (read only).
29
+
30
+ The read-only ``pytz`` attribute will stay in sync with any changes made
31
+ using ``tz``.
28
32
 
29
33
  Location objects support the print method.
30
34
 
@@ -38,12 +42,16 @@ class Location:
38
42
  Positive is east of the prime meridian.
39
43
  Use decimal degrees notation.
40
44
 
41
- tz : str, int, float, or pytz.timezone, default 'UTC'.
42
- See
43
- http://en.wikipedia.org/wiki/List_of_tz_database_time_zones
44
- for a list of valid time zones.
45
- pytz.timezone objects will be converted to strings.
46
- ints and floats must be in hours from UTC.
45
+ tz : time zone as str, int, float, or datetime.tzinfo, default 'UTC'.
46
+ See http://en.wikipedia.org/wiki/List_of_tz_database_time_zones for a
47
+ list of valid name strings. An `int` or `float` must be a whole-number
48
+ hour offsets from UTC that can be converted to the IANA-supported
49
+ 'Etc/GMT-N' format. (Note the limited range of the offset N and its
50
+ sign-change convention.) Time zones from the pytz and zoneinfo packages
51
+ may also be passed here, as they are subclasses of datetime.tzinfo.
52
+
53
+ The `tz` attribute is represented as a valid IANA time zone name
54
+ string.
47
55
 
48
56
  altitude : float, optional
49
57
  Altitude from sea level in meters.
@@ -54,43 +62,75 @@ class Location:
54
62
  name : string, optional
55
63
  Sets the name attribute of the Location object.
56
64
 
65
+ Raises
66
+ ------
67
+ ValueError
68
+ when the time zone ``tz`` cannot be converted.
69
+
70
+ zoneinfo.ZoneInfoNotFoundError
71
+ when the time zone ``tz`` is not recognizable as an IANA time zone by
72
+ the ``zoneinfo.ZoneInfo`` initializer used for internal time-zone
73
+ representation.
74
+
57
75
  See also
58
76
  --------
59
77
  pvlib.pvsystem.PVSystem
60
78
  """
61
79
 
62
- def __init__(self, latitude, longitude, tz='UTC', altitude=None,
63
- name=None):
64
-
80
+ def __init__(
81
+ self, latitude, longitude, tz='UTC', altitude=None, name=None
82
+ ):
65
83
  self.latitude = latitude
66
84
  self.longitude = longitude
67
-
68
- if isinstance(tz, str):
69
- self.tz = tz
70
- self.pytz = pytz.timezone(tz)
71
- elif isinstance(tz, datetime.timezone):
72
- self.tz = 'UTC'
73
- self.pytz = pytz.UTC
74
- elif isinstance(tz, datetime.tzinfo):
75
- self.tz = tz.zone
76
- self.pytz = tz
77
- elif isinstance(tz, (int, float)):
78
- self.tz = tz
79
- self.pytz = pytz.FixedOffset(tz*60)
80
- else:
81
- raise TypeError('Invalid tz specification')
85
+ self.tz = tz
82
86
 
83
87
  if altitude is None:
84
88
  altitude = lookup_altitude(latitude, longitude)
85
89
 
86
90
  self.altitude = altitude
87
-
88
91
  self.name = name
89
92
 
90
93
  def __repr__(self):
91
94
  attrs = ['name', 'latitude', 'longitude', 'altitude', 'tz']
95
+ # Use None as getattr default in case __repr__ is called during
96
+ # initialization before all attributes have been assigned.
92
97
  return ('Location: \n ' + '\n '.join(
93
- f'{attr}: {getattr(self, attr)}' for attr in attrs))
98
+ f'{attr}: {getattr(self, attr, None)}' for attr in attrs))
99
+
100
+ @property
101
+ def tz(self):
102
+ """The location's IANA time-zone string."""
103
+ return str(self._zoneinfo)
104
+
105
+ @tz.setter
106
+ def tz(self, tz_):
107
+ # self._zoneinfo holds single source of time-zone truth as IANA name.
108
+ if isinstance(tz_, str):
109
+ self._zoneinfo = zoneinfo.ZoneInfo(tz_)
110
+ elif isinstance(tz_, int):
111
+ self._zoneinfo = zoneinfo.ZoneInfo(f"Etc/GMT{-tz_:+d}")
112
+ elif isinstance(tz_, float):
113
+ if tz_ % 1 != 0:
114
+ raise TypeError(
115
+ "Floating-point tz has non-zero fractional part: "
116
+ f"{tz_}. Only whole-number offsets are supported."
117
+ )
118
+
119
+ self._zoneinfo = zoneinfo.ZoneInfo(f"Etc/GMT{-int(tz_):+d}")
120
+ elif isinstance(tz_, datetime.tzinfo):
121
+ # Includes time zones generated by pytz and zoneinfo packages.
122
+ self._zoneinfo = zoneinfo.ZoneInfo(str(tz_))
123
+ else:
124
+ raise TypeError(
125
+ f"invalid tz specification: {tz_}, must be an IANA time zone "
126
+ "string, a whole-number int/float UTC offset, or a "
127
+ "datetime.tzinfo object (including subclasses)"
128
+ )
129
+
130
+ @property
131
+ def pytz(self):
132
+ """The location's pytz time zone (read only)."""
133
+ return pytz.timezone(str(self._zoneinfo))
94
134
 
95
135
  @classmethod
96
136
  def from_tmy(cls, tmy_metadata, tmy_data=None, **kwargs):
@@ -328,7 +368,7 @@ class Location:
328
368
 
329
369
  return airmass
330
370
 
331
- def get_sun_rise_set_transit(self, times, method='pyephem', **kwargs):
371
+ def get_sun_rise_set_transit(self, times, method='spa', **kwargs):
332
372
  """
333
373
  Calculate sunrise, sunset and transit times.
334
374
 
@@ -336,7 +376,7 @@ class Location:
336
376
  ----------
337
377
  times : DatetimeIndex
338
378
  Must be localized to the Location
339
- method : str, default 'pyephem'
379
+ method : str, default 'spa'
340
380
  'pyephem', 'spa', or 'geometric'
341
381
 
342
382
  kwargs :
pvlib/modelchain.py CHANGED
@@ -29,7 +29,7 @@ POA_KEYS = ('poa_global', 'poa_direct', 'poa_diffuse')
29
29
 
30
30
  # Optional keys to communicate temperature data. If provided,
31
31
  # 'cell_temperature' overrides ModelChain.temperature_model and sets
32
- # ModelChain.cell_temperature to the data. If 'module_temperature' is provdied,
32
+ # ModelChain.cell_temperature to the data. If 'module_temperature' is provided,
33
33
  # overrides ModelChain.temperature_model with
34
34
  # pvlib.temperature.sapm_celL_from_module
35
35
  TEMPERATURE_KEYS = ('module_temperature', 'cell_temperature')
@@ -253,7 +253,7 @@ class ModelChainResult:
253
253
  def _head(obj):
254
254
  try:
255
255
  return obj[:3]
256
- except:
256
+ except Exception:
257
257
  return obj
258
258
 
259
259
  if type(self.dc) is tuple:
@@ -269,7 +269,7 @@ class ModelChainResult:
269
269
  '\n')
270
270
  lines = []
271
271
  for attr in mc_attrs:
272
- if not (attr.startswith('_') or attr=='times'):
272
+ if not (attr.startswith('_') or attr == 'times'):
273
273
  lines.append(f' {attr}: ' + _mcr_repr(getattr(self, attr)))
274
274
  desc4 = '\n'.join(lines)
275
275
  return (desc1 + desc2 + desc3 + desc4)
@@ -330,12 +330,15 @@ class ModelChain:
330
330
  'interp' and 'no_loss'. The ModelChain instance will be passed as the
331
331
  first argument to a user-defined function.
332
332
 
333
- spectral_model : str, or function, optional
334
- If not specified, the model will be inferred from the parameters that
335
- are common to all of system.arrays[i].module_parameters.
336
- Valid strings are 'sapm', 'first_solar', 'no_loss'.
333
+ spectral_model : str or function, optional
334
+ Valid strings are:
335
+
336
+ - ``'sapm'``
337
+ - ``'first_solar'``
338
+ - ``'no_loss'``
339
+
337
340
  The ModelChain instance will be passed as the first argument to
338
- a user-defined function.
341
+ a user-defined function. If not specified, ``'no_loss'`` is assumed.
339
342
 
340
343
  temperature_model : str or function, optional
341
344
  Valid strings are: 'sapm', 'pvsyst', 'faiman', 'fuentes', 'noct_sam'.
@@ -386,7 +389,6 @@ class ModelChain:
386
389
 
387
390
  self.results = ModelChainResult()
388
391
 
389
-
390
392
  @classmethod
391
393
  def with_pvwatts(cls, system, location,
392
394
  clearsky_model='ineichen',
@@ -609,7 +611,7 @@ class ModelChain:
609
611
  """Infer DC power model from Array module parameters."""
610
612
  params = _common_keys(
611
613
  tuple(array.module_parameters for array in self.system.arrays))
612
- if {'A0', 'A1', 'C7'} <= params:
614
+ if {'A0', 'A1', 'C3'} <= params:
613
615
  return self.sapm, 'sapm'
614
616
  elif {'a_ref', 'I_L_ref', 'I_o_ref', 'R_sh_ref', 'R_s',
615
617
  'Adjust'} <= params:
@@ -855,9 +857,7 @@ class ModelChain:
855
857
 
856
858
  @spectral_model.setter
857
859
  def spectral_model(self, model):
858
- if model is None:
859
- self._spectral_model = self.infer_spectral_model()
860
- elif isinstance(model, str):
860
+ if isinstance(model, str):
861
861
  model = model.lower()
862
862
  if model == 'first_solar':
863
863
  self._spectral_model = self.first_solar_spectral_loss
@@ -867,30 +867,12 @@ class ModelChain:
867
867
  self._spectral_model = self.no_spectral_loss
868
868
  else:
869
869
  raise ValueError(model + ' is not a valid spectral loss model')
870
- else:
870
+ elif model is None:
871
+ # not setting a model is equivalent to setting no_loss
872
+ self._spectral_model = self.no_spectral_loss
873
+ else: # assume model is callable with 1st argument = the MC instance
871
874
  self._spectral_model = partial(model, self)
872
875
 
873
- def infer_spectral_model(self):
874
- """Infer spectral model from system attributes."""
875
- module_parameters = tuple(
876
- array.module_parameters for array in self.system.arrays)
877
- params = _common_keys(module_parameters)
878
- if {'A4', 'A3', 'A2', 'A1', 'A0'} <= params:
879
- return self.sapm_spectral_loss
880
- elif ((('Technology' in params or
881
- 'Material' in params) and
882
- (self.system._infer_cell_type() is not None)) or
883
- 'first_solar_spectral_coefficients' in params):
884
- return self.first_solar_spectral_loss
885
- else:
886
- raise ValueError('could not infer spectral model from '
887
- 'system.arrays[i].module_parameters. Check that '
888
- 'the module_parameters for all Arrays in '
889
- 'system.arrays contain valid '
890
- 'first_solar_spectral_coefficients, a valid '
891
- 'Material or Technology value, or set '
892
- 'spectral_model="no_loss".')
893
-
894
876
  def first_solar_spectral_loss(self):
895
877
  self.results.spectral_modifier = self.system.first_solar_spectral_loss(
896
878
  _tuple_from_dfs(self.results.weather, 'precipitable_water'),
@@ -1570,7 +1552,7 @@ class ModelChain:
1570
1552
  ----------
1571
1553
  data : DataFrame
1572
1554
  May contain columns ``'cell_temperature'`` or
1573
- ``'module_temperaure'``.
1555
+ ``'module_temperature'``.
1574
1556
 
1575
1557
  Returns
1576
1558
  -------
@@ -1679,6 +1661,7 @@ class ModelChain:
1679
1661
  self.prepare_inputs(weather)
1680
1662
  self.aoi_model()
1681
1663
  self.spectral_model()
1664
+
1682
1665
  self.effective_irradiance_model()
1683
1666
 
1684
1667
  self._run_from_effective_irrad(weather)
pvlib/pvsystem.py CHANGED
@@ -17,8 +17,6 @@ from dataclasses import dataclass
17
17
  from abc import ABC, abstractmethod
18
18
  from typing import Optional, Union
19
19
 
20
- from pvlib._deprecation import deprecated
21
-
22
20
  import pvlib # used to avoid albedo name collision in the Array class
23
21
  from pvlib import (atmosphere, iam, inverter, irradiance,
24
22
  singlediode as _singlediode, spectrum, temperature)
@@ -29,11 +27,10 @@ import pvlib.tools as tools
29
27
  # a dict of required parameter names for each DC power model
30
28
  _DC_MODEL_PARAMS = {
31
29
  'sapm': {
32
- 'A0', 'A1', 'A2', 'A3', 'A4', 'B0', 'B1', 'B2', 'B3',
33
- 'B4', 'B5', 'C0', 'C1', 'C2', 'C3', 'C4', 'C5', 'C6',
34
- 'C7', 'Isco', 'Impo', 'Voco', 'Vmpo', 'Aisc', 'Aimp', 'Bvoco',
35
- 'Mbvoc', 'Bvmpo', 'Mbvmp', 'N', 'Cells_in_Series',
36
- 'IXO', 'IXXO', 'FD'},
30
+ # i_x and i_xx params (IXO, IXXO, C4-C7) not required
31
+ 'C0', 'C1', 'C2', 'C3',
32
+ 'Isco', 'Impo', 'Voco', 'Vmpo', 'Aisc', 'Aimp', 'Bvoco',
33
+ 'Mbvoc', 'Bvmpo', 'Mbvmp', 'N', 'Cells_in_Series'},
37
34
  'desoto': {
38
35
  'alpha_sc', 'a_ref', 'I_L_ref', 'I_o_ref',
39
36
  'R_sh_ref', 'R_s'},
@@ -1710,6 +1707,8 @@ def calcparams_desoto(effective_irradiance, temp_cell,
1710
1707
  Rs = R_s
1711
1708
 
1712
1709
  numeric_args = (effective_irradiance, temp_cell)
1710
+ # IL: photocurrent, I0: saturation_current, Rs: resistance_series,
1711
+ # Rsh: resistance_shunt
1713
1712
  out = (IL, I0, Rs, Rsh, nNsVth)
1714
1713
 
1715
1714
  if all(map(np.isscalar, numeric_args)):
@@ -1947,35 +1946,23 @@ def calcparams_pvsyst(effective_irradiance, temp_cell,
1947
1946
 
1948
1947
  '''
1949
1948
 
1950
- # Boltzmann constant in J/K
1951
- k = constants.k
1952
-
1953
- # elementary charge in coulomb
1954
- q = constants.e
1949
+ gamma = _pvsyst_gamma(temp_cell, gamma_ref, mu_gamma, temp_ref)
1955
1950
 
1956
- # reference temperature
1957
- Tref_K = temp_ref + 273.15
1958
- Tcell_K = temp_cell + 273.15
1959
-
1960
- gamma = gamma_ref + mu_gamma * (Tcell_K - Tref_K)
1961
- nNsVth = gamma * k / q * cells_in_series * Tcell_K
1951
+ nNsVth = _pvsyst_nNsVth(temp_cell, gamma, cells_in_series)
1962
1952
 
1963
- IL = effective_irradiance / irrad_ref * \
1964
- (I_L_ref + alpha_sc * (Tcell_K - Tref_K))
1953
+ IL = _pvsyst_IL(effective_irradiance, temp_cell, I_L_ref, alpha_sc,
1954
+ irrad_ref, temp_ref)
1965
1955
 
1966
- I0 = I_o_ref * ((Tcell_K / Tref_K) ** 3) * \
1967
- (np.exp((q * EgRef) / (k * gamma) * (1 / Tref_K - 1 / Tcell_K)))
1956
+ I0 = _pvsyst_Io(temp_cell, gamma, I_o_ref, EgRef, temp_ref)
1968
1957
 
1969
- Rsh_tmp = \
1970
- (R_sh_ref - R_sh_0 * np.exp(-R_sh_exp)) / (1.0 - np.exp(-R_sh_exp))
1971
- Rsh_base = np.maximum(0.0, Rsh_tmp)
1972
-
1973
- Rsh = Rsh_base + (R_sh_0 - Rsh_base) * \
1974
- np.exp(-R_sh_exp * effective_irradiance / irrad_ref)
1958
+ Rsh = _pvsyst_Rsh(effective_irradiance, R_sh_ref, R_sh_0, R_sh_exp,
1959
+ irrad_ref)
1975
1960
 
1976
1961
  Rs = R_s
1977
1962
 
1978
1963
  numeric_args = (effective_irradiance, temp_cell)
1964
+ # IL: photocurrent, I0: saturation_current, Rs: resistance_series,
1965
+ # Rsh: resistance_shunt
1979
1966
  out = (IL, I0, Rs, Rsh, nNsVth)
1980
1967
 
1981
1968
  if all(map(np.isscalar, numeric_args)):
@@ -1989,6 +1976,54 @@ def calcparams_pvsyst(effective_irradiance, temp_cell,
1989
1976
  return tuple(pd.Series(a, index=index).rename(None) for a in out)
1990
1977
 
1991
1978
 
1979
+ def _pvsyst_Rsh(effective_irradiance, R_sh_ref, R_sh_0, R_sh_exp=5.5,
1980
+ irrad_ref=1000):
1981
+ Rsh_tmp = \
1982
+ (R_sh_ref - R_sh_0 * np.exp(-R_sh_exp)) / (1.0 - np.exp(-R_sh_exp))
1983
+ Rsh_base = np.maximum(0.0, Rsh_tmp)
1984
+
1985
+ Rsh = Rsh_base + (R_sh_0 - Rsh_base) * \
1986
+ np.exp(-R_sh_exp * effective_irradiance / irrad_ref)
1987
+
1988
+ return Rsh
1989
+
1990
+
1991
+ def _pvsyst_IL(effective_irradiance, temp_cell, I_L_ref, alpha_sc,
1992
+ irrad_ref=1000, temp_ref=25):
1993
+ Tref_K = temp_ref + 273.15
1994
+ Tcell_K = temp_cell + 273.15
1995
+ IL = effective_irradiance / irrad_ref * \
1996
+ (I_L_ref + alpha_sc * (Tcell_K - Tref_K))
1997
+ return IL
1998
+
1999
+
2000
+ def _pvsyst_Io(temp_cell, gamma, I_o_ref, EgRef, temp_ref=25):
2001
+ k = constants.k # Boltzmann constant in J/K
2002
+ q = constants.e # elementary charge in coulomb
2003
+
2004
+ Tref_K = temp_ref + 273.15
2005
+ Tcell_K = temp_cell + 273.15
2006
+
2007
+ Io = I_o_ref * ((Tcell_K / Tref_K) ** 3) * \
2008
+ (np.exp((q * EgRef) / (k * gamma) * (1 / Tref_K - 1 / Tcell_K)))
2009
+
2010
+ return Io
2011
+
2012
+
2013
+ def _pvsyst_gamma(temp_cell, gamma_ref, mu_gamma, temp_ref=25):
2014
+ gamma = gamma_ref + mu_gamma * (temp_cell - temp_ref)
2015
+ return gamma
2016
+
2017
+
2018
+ def _pvsyst_nNsVth(temp_cell, gamma, cells_in_series):
2019
+ k = constants.k # Boltzmann constant in J/K
2020
+ q = constants.e # elementary charge in coulomb
2021
+ Tcell_K = temp_cell + 273.15
2022
+
2023
+ nNsVth = gamma * k / q * cells_in_series * Tcell_K
2024
+ return nNsVth
2025
+
2026
+
1992
2027
  def retrieve_sam(name=None, path=None):
1993
2028
  """
1994
2029
  Retrieve latest module and inverter info from a file bundled with pvlib,
@@ -2158,25 +2193,32 @@ def _parse_raw_sam_df(csvdata):
2158
2193
  return df
2159
2194
 
2160
2195
 
2161
- def sapm(effective_irradiance, temp_cell, module):
2196
+ def sapm(effective_irradiance, temp_cell, module, *, temperature_ref=25,
2197
+ irradiance_ref=1000):
2162
2198
  '''
2163
2199
  The Sandia PV Array Performance Model (SAPM) generates 5 points on a
2164
2200
  PV module's I-V curve (Voc, Isc, Ix, Ixx, Vmp/Imp) according to
2165
- SAND2004-3535. Assumes a reference cell temperature of 25 C.
2201
+ SAND2004-3535. Assumes a reference cell temperature of 25°C.
2166
2202
 
2167
2203
  Parameters
2168
2204
  ----------
2169
2205
  effective_irradiance : numeric
2170
2206
  Irradiance reaching the module's cells, after reflections and
2171
- adjustment for spectrum. [W/m2]
2207
+ adjustment for spectrum. [Wm⁻²]
2172
2208
 
2173
2209
  temp_cell : numeric
2174
- Cell temperature [C].
2210
+ Cell temperature [°C].
2175
2211
 
2176
2212
  module : dict-like
2177
2213
  A dict or Series defining the SAPM parameters. See the notes section
2178
2214
  for more details.
2179
2215
 
2216
+ temperature_ref : numeric, optional
2217
+ Reference temperature [°C]
2218
+
2219
+ irradiance_ref : numeric, optional
2220
+ Reference irradiance [Wm⁻²]
2221
+
2180
2222
  Returns
2181
2223
  -------
2182
2224
  A DataFrame with the columns:
@@ -2187,18 +2229,33 @@ def sapm(effective_irradiance, temp_cell, module):
2187
2229
  * v_mp : Voltage at maximum-power point (V)
2188
2230
  * p_mp : Power at maximum-power point (W)
2189
2231
  * i_x : Current at module V = 0.5Voc, defines 4th point on I-V
2190
- curve for modeling curve shape
2232
+ curve for modeling curve shape. Omitted if ``IXO``, ``C4``, and
2233
+ ``C5`` parameters are not supplied.
2191
2234
  * i_xx : Current at module V = 0.5(Voc+Vmp), defines 5th point on
2192
- I-V curve for modeling curve shape
2235
+ I-V curve for modeling curve shape. Omitted if ``IXXO``, ``C6``,
2236
+ and ``C7`` parameters are not supplied.
2193
2237
 
2194
2238
  Notes
2195
2239
  -----
2196
- The SAPM parameters which are required in ``module`` are
2197
- listed in the following table.
2198
-
2199
2240
  The Sandia module database contains parameter values for a limited set
2200
2241
  of modules. The CEC module database does not contain these parameters.
2201
- Both databases can be accessed using :py:func:`retrieve_sam`.
2242
+ Both databases can be accessed using :py:func:`retrieve_sam`. The full list
2243
+ of SAPM parameters is presented in the table below. Those that are required
2244
+ in the ``module`` parameter to run this model are as follows:
2245
+
2246
+ * ``C0``, ``C1``, ``C2``, ``C3``
2247
+ * ``Isco``
2248
+ * ``Impo``
2249
+ * ``Voco``
2250
+ * ``Vmpo``
2251
+ * ``Aisc``
2252
+ * ``Aimp``
2253
+ * ``Bvoco``
2254
+ * ``Mbvoc``
2255
+ * ``Bvmpo``
2256
+ * ``Mbvmp``
2257
+ * ``N``
2258
+ * ``Cells_in_series``
2202
2259
 
2203
2260
  ================ ========================================================
2204
2261
  Key Description
@@ -2214,19 +2271,19 @@ def sapm(effective_irradiance, temp_cell, module):
2214
2271
  Voco Open circuit voltage at reference condition (amps)
2215
2272
  Vmpo Maximum power voltage at reference condition (amps)
2216
2273
  Aisc Short circuit current temperature coefficient at
2217
- reference condition (1/C)
2274
+ reference condition (1C)
2218
2275
  Aimp Maximum power current temperature coefficient at
2219
- reference condition (1/C)
2276
+ reference condition (1C)
2220
2277
  Bvoco Open circuit voltage temperature coefficient at
2221
- reference condition (V/C)
2278
+ reference condition (VC)
2222
2279
  Mbvoc Coefficient providing the irradiance dependence for the
2223
2280
  BetaVoc temperature coefficient at reference irradiance
2224
- (V/C)
2281
+ (VC)
2225
2282
  Bvmpo Maximum power voltage temperature coefficient at
2226
2283
  reference condition
2227
2284
  Mbvmp Coefficient providing the irradiance dependence for the
2228
2285
  BetaVmp temperature coefficient at reference irradiance
2229
- (V/C)
2286
+ (VC)
2230
2287
  N Empirically determined "diode factor" (dimensionless)
2231
2288
  Cells_in_Series Number of cells in series in a module's cell string(s)
2232
2289
  IXO Ix at reference conditions
@@ -2247,16 +2304,11 @@ def sapm(effective_irradiance, temp_cell, module):
2247
2304
  pvlib.temperature.sapm_module
2248
2305
  '''
2249
2306
 
2250
- # TODO: someday, change temp_ref and irrad_ref to reference_temperature and
2251
- # reference_irradiance and expose
2252
- temp_ref = 25
2253
- irrad_ref = 1000
2254
-
2255
2307
  q = constants.e # Elementary charge in units of coulombs
2256
2308
  kb = constants.k # Boltzmann's constant in units of J/K
2257
2309
 
2258
2310
  # avoid problem with integer input
2259
- Ee = np.array(effective_irradiance, dtype='float64') / irrad_ref
2311
+ Ee = np.array(effective_irradiance, dtype='float64') / irradiance_ref
2260
2312
 
2261
2313
  # set up masking for 0, positive, and nan inputs
2262
2314
  Ee_gt_0 = np.full_like(Ee, False, dtype='bool')
@@ -2279,31 +2331,34 @@ def sapm(effective_irradiance, temp_cell, module):
2279
2331
  out = OrderedDict()
2280
2332
 
2281
2333
  out['i_sc'] = (
2282
- module['Isco'] * Ee * (1 + module['Aisc']*(temp_cell - temp_ref)))
2334
+ module['Isco'] * Ee * (1 + module['Aisc']*(temp_cell -
2335
+ temperature_ref)))
2283
2336
 
2284
2337
  out['i_mp'] = (
2285
2338
  module['Impo'] * (module['C0']*Ee + module['C1']*(Ee**2)) *
2286
- (1 + module['Aimp']*(temp_cell - temp_ref)))
2339
+ (1 + module['Aimp']*(temp_cell - temperature_ref)))
2287
2340
 
2288
2341
  out['v_oc'] = np.maximum(0, (
2289
2342
  module['Voco'] + cells_in_series * delta * logEe +
2290
- Bvoco*(temp_cell - temp_ref)))
2343
+ Bvoco*(temp_cell - temperature_ref)))
2291
2344
 
2292
2345
  out['v_mp'] = np.maximum(0, (
2293
2346
  module['Vmpo'] +
2294
2347
  module['C2'] * cells_in_series * delta * logEe +
2295
2348
  module['C3'] * cells_in_series * ((delta * logEe) ** 2) +
2296
- Bvmpo*(temp_cell - temp_ref)))
2349
+ Bvmpo*(temp_cell - temperature_ref)))
2297
2350
 
2298
2351
  out['p_mp'] = out['i_mp'] * out['v_mp']
2299
2352
 
2300
- out['i_x'] = (
2301
- module['IXO'] * (module['C4']*Ee + module['C5']*(Ee**2)) *
2302
- (1 + module['Aisc']*(temp_cell - temp_ref)))
2353
+ if 'IXO' in module and 'C4' in module and 'C5' in module:
2354
+ out['i_x'] = (
2355
+ module['IXO'] * (module['C4']*Ee + module['C5']*(Ee**2)) *
2356
+ (1 + module['Aisc']*(temp_cell - temperature_ref)))
2303
2357
 
2304
- out['i_xx'] = (
2305
- module['IXXO'] * (module['C6']*Ee + module['C7']*(Ee**2)) *
2306
- (1 + module['Aimp']*(temp_cell - temp_ref)))
2358
+ if 'IXXO' in module and 'C6' in module and 'C7' in module:
2359
+ out['i_xx'] = (
2360
+ module['IXXO'] * (module['C6']*Ee + module['C7']*(Ee**2)) *
2361
+ (1 + module['Aimp']*(temp_cell - temperature_ref)))
2307
2362
 
2308
2363
  if isinstance(out['i_sc'], pd.Series):
2309
2364
  out = pd.DataFrame(out)
@@ -2311,12 +2366,6 @@ def sapm(effective_irradiance, temp_cell, module):
2311
2366
  return out
2312
2367
 
2313
2368
 
2314
- sapm_spectral_loss = deprecated(
2315
- since='0.10.0',
2316
- alternative='pvlib.spectrum.spectral_factor_sapm'
2317
- )(spectrum.spectral_factor_sapm)
2318
-
2319
-
2320
2369
  def sapm_effective_irradiance(poa_direct, poa_diffuse, airmass_absolute, aoi,
2321
2370
  module):
2322
2371
  r"""