pvlib 0.13.0__py3-none-any.whl → 0.13.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.
@@ -267,7 +267,7 @@ def _update_io(voc, iph, io, rs, rsh, nnsvth):
267
267
  .. [1] PVLib MATLAB https://github.com/sandialabs/MATLAB_PV_LIB
268
268
  .. [2] C. Hansen, Parameter Estimation for Single Diode Models of
269
269
  Photovoltaic Modules, Sandia National Laboratories Report SAND2015-2065
270
- .. [3] C. Hansen, Estimation of Parameteres for Single Diode Models using
270
+ .. [3] C. Hansen, Estimation of Parameters for Single Diode Models using
271
271
  Measured IV Curves, Proc. of the 39th IEEE PVSC, June 2013.
272
272
  """
273
273
 
pvlib/ivtools/sdm/cec.py CHANGED
@@ -27,7 +27,7 @@ def fit_cec_sam(celltype, v_mp, i_mp, v_oc, i_sc, alpha_sc, beta_voc,
27
27
  cells_in_series : int
28
28
  Number of cells in series
29
29
  temp_ref : float, default 25
30
- Reference temperature condition [C]
30
+ Reference temperature condition, [°C]
31
31
 
32
32
  Returns
33
33
  -------
@@ -58,7 +58,7 @@ def fit_desoto(v_mp, i_mp, v_oc, i_sc, alpha_sc, beta_voc, cells_in_series,
58
58
  dEgdT: float, default -0.0002677 - value for silicon
59
59
  Variation of bandgap according to temperature. [1/K]
60
60
  temp_ref: float, default 25
61
- Reference temperature condition. [C]
61
+ Reference temperature condition. [°C]
62
62
  irrad_ref: float, default 1000
63
63
  Reference irradiance condition. [Wm⁻²]
64
64
  init_guess: dict, optional
@@ -93,7 +93,7 @@ def fit_desoto(v_mp, i_mp, v_oc, i_sc, alpha_sc, beta_voc, cells_in_series,
93
93
  irrad_ref: float
94
94
  Reference irradiance condition. [Wm⁻²]
95
95
  temp_ref: float
96
- Reference temperature condition. [C]
96
+ Reference temperature condition. [°C]
97
97
 
98
98
  scipy.optimize.OptimizeResult
99
99
  Optimization result of scipy.optimize.root().
@@ -110,7 +110,7 @@ def fit_desoto(v_mp, i_mp, v_oc, i_sc, alpha_sc, beta_voc, cells_in_series,
110
110
  """
111
111
 
112
112
  # Constants
113
- k = constants.value('Boltzmann constant in eV/K') # in eV/K
113
+ k = constants.value('Boltzmann constant in eV/K')
114
114
  Tref = temp_ref + 273.15 # [K]
115
115
 
116
116
  # initial guesses of variables for computing convergence:
@@ -234,7 +234,7 @@ def fit_desoto_sandia(ivcurves, specs, const=None, maxiter=5, eps1=1.e-3):
234
234
  effective irradiance for each IV curve, i.e., POA broadband
235
235
  irradiance adjusted by solar spectrum modifier [W / m^2]
236
236
  tc : array
237
- cell temperature for each IV curve [C]
237
+ cell temperature for each IV curve. [°C]
238
238
  i_sc : array
239
239
  short circuit current for each IV curve [A]
240
240
  v_oc : array
@@ -254,9 +254,9 @@ def fit_desoto_sandia(ivcurves, specs, const=None, maxiter=5, eps1=1.e-3):
254
254
 
255
255
  const : dict
256
256
  E0 : float
257
- effective irradiance at STC, default 1000 [W/m^2]
257
+ effective irradiance at STC, default 1000 [Wm⁻²]
258
258
  T0 : float
259
- cell temperature at STC, default 25 [C]
259
+ cell temperature at STC, default 25°C. [°C]
260
260
  k : float
261
261
  Boltzmann's constant [J/K]
262
262
  q : float
@@ -38,7 +38,7 @@ def fit_pvsyst_sandia(ivcurves, specs, const=None, maxiter=5, eps1=1.e-3):
38
38
  effective irradiance for each IV curve, i.e., POA broadband
39
39
  irradiance adjusted by solar spectrum modifier [W / m^2]
40
40
  tc : array
41
- cell temperature for each IV curve [C]
41
+ cell temperature for each IV curve [°C]
42
42
  i_sc : array
43
43
  short circuit current for each IV curve [A]
44
44
  v_oc : array
@@ -56,9 +56,9 @@ def fit_pvsyst_sandia(ivcurves, specs, const=None, maxiter=5, eps1=1.e-3):
56
56
 
57
57
  const : dict
58
58
  E0 : float
59
- effective irradiance at STC, default 1000 [W/m^2]
59
+ effective irradiance at STC, default 1000 [Wm⁻²]
60
60
  T0 : float
61
- cell temperature at STC, default 25 [C]
61
+ cell temperature at STC, default 25°C. [°C]
62
62
  k : float
63
63
  Boltzmann's constant [J/K]
64
64
  q : float
@@ -272,10 +272,10 @@ def pvsyst_temperature_coeff(alpha_sc, gamma_ref, mu_gamma, I_L_ref, I_o_ref,
272
272
  Default of 1.121 eV is for crystalline silicon. Must be positive. [eV]
273
273
 
274
274
  irrad_ref : float, default 1000
275
- Reference irradiance. [W/m^2].
275
+ Reference irradiance. [Wm⁻²].
276
276
 
277
277
  temp_ref : float, default 25
278
- Reference cell temperature. [C]
278
+ Reference cell temperature. [°C]
279
279
 
280
280
 
281
281
  Returns
@@ -327,7 +327,7 @@ def fit_pvsyst_iec61853_sandia_2025(effective_irradiance, temp_cell,
327
327
  effective_irradiance : array
328
328
  Effective irradiance for each test condition [W/m²]
329
329
  temp_cell : array
330
- Cell temperature for each test condition [C]
330
+ Cell temperature for each test condition. [°C]
331
331
  i_sc : array
332
332
  Short circuit current for each test condition [A]
333
333
  v_oc : array
@@ -366,7 +366,7 @@ def fit_pvsyst_iec61853_sandia_2025(effective_irradiance, temp_cell,
366
366
  temperature_tolerance : float, default 1
367
367
  Tolerance for temperature variation around the STC value.
368
368
  The default value corresponds to a +/- 1 degree interval around the STC
369
- value of 25 degrees. [C]
369
+ value of 25°C. [°C]
370
370
 
371
371
  Returns
372
372
  -------
pvlib/modelchain.py CHANGED
@@ -18,6 +18,8 @@ import pvlib.irradiance # avoid name conflict with full import
18
18
  from pvlib.pvsystem import _DC_MODEL_PARAMS
19
19
  from pvlib.tools import _build_kwargs
20
20
 
21
+ from pvlib._deprecation import deprecated
22
+
21
23
  # keys that are used to detect input data and assign data to appropriate
22
24
  # ModelChain attribute
23
25
  # for ModelChain.weather
@@ -31,7 +33,7 @@ POA_KEYS = ('poa_global', 'poa_direct', 'poa_diffuse')
31
33
  # 'cell_temperature' overrides ModelChain.temperature_model and sets
32
34
  # ModelChain.cell_temperature to the data. If 'module_temperature' is provided,
33
35
  # overrides ModelChain.temperature_model with
34
- # pvlib.temperature.sapm_celL_from_module
36
+ # pvlib.temperature.sapm_cell_from_module
35
37
  TEMPERATURE_KEYS = ('module_temperature', 'cell_temperature')
36
38
 
37
39
  DATA_KEYS = WEATHER_KEYS + POA_KEYS + TEMPERATURE_KEYS
@@ -46,7 +48,7 @@ DATA_KEYS = WEATHER_KEYS + POA_KEYS + TEMPERATURE_KEYS
46
48
  # for Flat-Plate Photovoltaic Arrays. SAND85-0330. Albuquerque, NM:
47
49
  # Sandia National Laboratories. Accessed September 3, 2013:
48
50
  # http://prod.sandia.gov/techlib/access-control.cgi/1985/850330.pdf
49
- # pvlib python does not implement that model, so use the SAPM instead.
51
+ # pvlib-python does not implement that model, so it uses the SAPM instead.
50
52
  PVWATTS_CONFIG = dict(
51
53
  dc_model='pvwatts', ac_model='pvwatts', losses_model='pvwatts',
52
54
  transposition_model='perez', aoi_model='physical',
@@ -59,6 +61,13 @@ SAPM_CONFIG = dict(
59
61
  )
60
62
 
61
63
 
64
+ @deprecated(
65
+ since="0.13.1",
66
+ removal="",
67
+ name="pvlib.modelchain.get_orientation",
68
+ alternative=None,
69
+ addendum=None,
70
+ )
62
71
  def get_orientation(strategy, **kwargs):
63
72
  """
64
73
  Determine a PV system's surface tilt and surface azimuth
@@ -84,7 +93,7 @@ def get_orientation(strategy, **kwargs):
84
93
  surface_tilt = 0
85
94
  else:
86
95
  raise ValueError('invalid orientation strategy. strategy must '
87
- 'be one of south_at_latitude, flat,')
96
+ 'be one of south_at_latitude_tilt, flat,')
88
97
 
89
98
  return surface_tilt, surface_azimuth
90
99
 
@@ -162,8 +171,8 @@ class ModelChainResult:
162
171
  # per DC array information
163
172
  total_irrad: Optional[PerArray[pd.DataFrame]] = field(default=None)
164
173
  """ DataFrame (or tuple of DataFrame, one for each array) containing
165
- columns ``'poa_global'``, ``'poa_direct'`` ``'poa_diffuse'``,
166
- ``poa_sky_diffuse'``, ``'poa_ground_diffuse'`` (W/m2); see
174
+ columns ``'poa_global'``, ``'poa_direct'``, ``'poa_diffuse'``,
175
+ ``poa_sky_diffuse'``, and ``'poa_ground_diffuse'`` (Wm⁻²); see
167
176
  :py:func:`~pvlib.irradiance.get_total_irradiance` for details.
168
177
  """
169
178
 
@@ -190,12 +199,12 @@ class ModelChainResult:
190
199
 
191
200
  cell_temperature: Optional[PerArray[pd.Series]] = field(default=None)
192
201
  """Series (or tuple of Series, one for each array) containing cell
193
- temperature (C).
202
+ temperature (°C).
194
203
  """
195
204
 
196
205
  effective_irradiance: Optional[PerArray[pd.Series]] = field(default=None)
197
206
  """Series (or tuple of Series, one for each array) containing effective
198
- irradiance (W/m2) which is total plane-of-array irradiance adjusted for
207
+ irradiance (Wm⁻²) which is total plane-of-array irradiance adjusted for
199
208
  reflections and spectral content.
200
209
  """
201
210
 
@@ -215,12 +224,12 @@ class ModelChainResult:
215
224
 
216
225
  dc_ohmic_losses: Optional[PerArray[pd.Series]] = field(default=None)
217
226
  """Series (or tuple of Series, one for each array) containing DC ohmic
218
- loss (W) calculated by ``ModelChain.dc_ohmic_model``.
227
+ losses (W) calculated by ``ModelChain.dc_ohmic_model``.
219
228
  """
220
229
 
221
230
  # copies of input data, for user convenience
222
231
  weather: Optional[PerArray[pd.DataFrame]] = None
223
- """DataFrame (or tuple of DataFrame, one for each array) contains a
232
+ """DataFrame (or tuple of DataFrame, one for each array) containing a
224
233
  copy of the input weather data.
225
234
  """
226
235
 
@@ -806,7 +815,7 @@ class ModelChain:
806
815
  'system.arrays[i].module_parameters. Check that '
807
816
  'the module_parameters for all Arrays in '
808
817
  'system.arrays contain parameters for the '
809
- 'physical, aoi, ashrae, martin_ruiz or interp '
818
+ 'physical, sapm, ashrae, martin_ruiz or interp '
810
819
  'model; explicitly set the model with the '
811
820
  'aoi_model kwarg; or set aoi_model="no_loss".')
812
821
 
pvlib/pvarray.py CHANGED
@@ -37,7 +37,7 @@ def pvefficiency_adr(effective_irradiance, temp_cell,
37
37
  the reference conditions. [unitless]
38
38
 
39
39
  k_d : numeric, negative
40
- Dark irradiance or diode coefficient which influences the voltage
40
+ "Dark irradiance" or diode coefficient which influences the voltage
41
41
  increase with irradiance. [unitless]
42
42
 
43
43
  tc_d : numeric
@@ -225,24 +225,55 @@ def fit_pvefficiency_adr(effective_irradiance, temp_cell, eta,
225
225
  return popt
226
226
 
227
227
 
228
- def _infer_k_huld(cell_type, pdc0):
228
+ def _infer_k_huld(cell_type, pdc0, k_version):
229
+ r"""
230
+ Get the EU JRC updated coefficients for the Huld model.
231
+
232
+ Parameters
233
+ ----------
234
+ cell_type : str
235
+ Must be one of 'csi', 'cis', or 'cdte'
236
+ pdc0 : numeric
237
+ Power of the modules at reference conditions [W]
238
+ k_version : str
239
+ Either 'pvgis5' or 'pvgis6'.
240
+
241
+ Returns
242
+ -------
243
+ tuple
244
+ The six coefficients (k1-k6) for the Huld model, scaled by pdc0
245
+ """
229
246
  # from PVGIS documentation, "PVGIS data sources & calculation methods",
230
247
  # Section 5.2.3, accessed 12/22/2023
231
248
  # The parameters in PVGIS' documentation are for a version of Huld's
232
249
  # equation that has factored Pdc0 out of the polynomial:
233
250
  # P = G/1000 * Pdc0 * (1 + k1 log(Geff) + ...) so these parameters are
234
251
  # multiplied by pdc0
235
- huld_params = {'csi': (-0.017237, -0.040465, -0.004702, 0.000149,
236
- 0.000170, 0.000005),
237
- 'cis': (-0.005554, -0.038724, -0.003723, -0.000905,
238
- -0.001256, 0.000001),
239
- 'cdte': (-0.046689, -0.072844, -0.002262, 0.000276,
240
- 0.000159, -0.000006)}
252
+ if k_version.lower() == 'pvgis5':
253
+ # coefficients from PVGIS webpage
254
+ huld_params = {'csi': (-0.017237, -0.040465, -0.004702, 0.000149,
255
+ 0.000170, 0.000005),
256
+ 'cis': (-0.005554, -0.038724, -0.003723, -0.000905,
257
+ -0.001256, 0.000001),
258
+ 'cdte': (-0.046689, -0.072844, -0.002262, 0.000276,
259
+ 0.000159, -0.000006)}
260
+ elif k_version.lower() == 'pvgis6':
261
+ # Coefficients from EU JRC paper
262
+ huld_params = {'csi': (-0.0067560, -0.016444, -0.003015, -0.000045,
263
+ -0.000043, 0.0),
264
+ 'cis': (-0.011001, -0.029734, -0.002887, 0.000217,
265
+ -0.000163, 0.0),
266
+ 'cdte': (-0.020644, -0.035136, -0.003406, 0.000073,
267
+ -0.000141, 0.000002)}
268
+ else:
269
+ raise ValueError(f'Invalid k_version={k_version}: must be either '
270
+ '"pvgis5" or "pvgis6"')
241
271
  k = tuple([x*pdc0 for x in huld_params[cell_type.lower()]])
242
272
  return k
243
273
 
244
274
 
245
- def huld(effective_irradiance, temp_mod, pdc0, k=None, cell_type=None):
275
+ def huld(effective_irradiance, temp_mod, pdc0, k=None, cell_type=None,
276
+ k_version='pvgis5'):
246
277
  r"""
247
278
  Power (DC) using the Huld model.
248
279
 
@@ -274,6 +305,11 @@ def huld(effective_irradiance, temp_mod, pdc0, k=None, cell_type=None):
274
305
  cell_type : str, optional
275
306
  If provided, must be one of ``'cSi'``, ``'CIS'``, or ``'CdTe'``.
276
307
  Used to look up default values for ``k`` if ``k`` is not specified.
308
+ k_version : str, optional
309
+ Either ``'pvgis5'`` (default) or ``'pvgis6'``. Selects values
310
+ for ``k`` if ``k`` is not specified. If ``'pvgis5'``, values are
311
+ from PVGIS documentation and are labeled in [2]_ as "current".
312
+ If ``'pvgis6'`` values are from [2]_ labeled as "updated".
277
313
 
278
314
  Returns
279
315
  -------
@@ -328,14 +364,19 @@ def huld(effective_irradiance, temp_mod, pdc0, k=None, cell_type=None):
328
364
 
329
365
  References
330
366
  ----------
331
- .. [1] T. Huld, G. Friesen, A. Skoczek, R. Kenny, T. Sample, M. Field,
332
- E. Dunlop. A power-rating model for crystalline silicon PV modules.
333
- Solar Energy Materials and Solar Cells 95, (2011), pp. 3359-3369.
334
- :doi:`10.1016/j.solmat.2011.07.026`.
367
+ .. [1] T. Huld, G. Friesen, A. Skoczek, R. Kenny, T. Sample, M. Field, and
368
+ E. Dunlop, "A power-rating model for crystalline silicon PV
369
+ modules," Solar Energy Materials and Solar Cells 95, (2011),
370
+ pp. 3359-3369. :doi:`10.1016/j.solmat.2011.07.026`.
371
+ .. [2] A. Chatzipanagi, N. Taylor, I. Suarez, A. Martinez, T. Lyubenova,
372
+ and E. Dunlop, "An Updated Simplified Energy Yield Model for Recent
373
+ Photovoltaic Module Technologies,"
374
+ Progress in Photovoltaics: Research and Applications 33,
375
+ no. 8 (2025): 905–917, :doi:`10.1002/pip.3926`.
335
376
  """
336
377
  if k is None:
337
378
  if cell_type is not None:
338
- k = _infer_k_huld(cell_type, pdc0)
379
+ k = _infer_k_huld(cell_type, pdc0, k_version)
339
380
  else:
340
381
  raise ValueError('Either k or cell_type must be specified')
341
382
 
@@ -346,7 +387,10 @@ def huld(effective_irradiance, temp_mod, pdc0, k=None, cell_type=None):
346
387
  logGprime = np.log(gprime, out=np.zeros_like(gprime),
347
388
  where=np.array(gprime > 0))
348
389
  # Eq. 1 in [1]
349
- pdc = gprime * (pdc0 + k[0] * logGprime + k[1] * logGprime**2 +
350
- k[2] * tprime + k[3] * tprime * logGprime +
351
- k[4] * tprime * logGprime**2 + k[5] * tprime**2)
390
+ pdc = gprime * (
391
+ pdc0 + k[0] * logGprime + k[1] * logGprime**2 +
392
+ k[2] * tprime + k[3] * tprime * logGprime +
393
+ k[4] * tprime * logGprime**2 +
394
+ k[5] * tprime**2
395
+ )
352
396
  return pdc
pvlib/singlediode.py CHANGED
@@ -696,27 +696,34 @@ def _lambertw_v_from_i(current, photocurrent, saturation_current,
696
696
 
697
697
  # Only compute using LambertW if there are cases with Gsh>0
698
698
  if np.any(idx_p):
699
+
700
+ # use only the relevant subset for what follows
701
+ I = I[idx_p]
702
+ IL = IL[idx_p]
703
+ I0 = I0[idx_p]
704
+ Rs = Rs[idx_p]
705
+ Gsh = Gsh[idx_p]
706
+ a = a[idx_p]
707
+
699
708
  # LambertW argument, cannot be float128, may overflow to np.inf
700
709
  # overflow is explicitly handled below, so ignore warnings here
701
710
  with np.errstate(over='ignore'):
702
- argW = (I0[idx_p] / (Gsh[idx_p] * a[idx_p]) *
703
- np.exp((-I[idx_p] + IL[idx_p] + I0[idx_p]) /
704
- (Gsh[idx_p] * a[idx_p])))
711
+ argW = I0 / (Gsh * a) * np.exp((-I + IL + I0) / (Gsh * a))
705
712
 
706
713
  # lambertw typically returns complex value with zero imaginary part
707
714
  # may overflow to np.inf
708
715
  lambertwterm = lambertw(argW).real
709
716
 
710
717
  # Record indices where lambertw input overflowed output
711
- idx_inf = np.logical_not(np.isfinite(lambertwterm))
718
+ idx_inf = np.isinf(lambertwterm)
712
719
 
713
720
  # Only re-compute LambertW if it overflowed
714
721
  if np.any(idx_inf):
715
722
  # Calculate using log(argW) in case argW is really big
716
- logargW = (np.log(I0[idx_p]) - np.log(Gsh[idx_p]) -
717
- np.log(a[idx_p]) +
718
- (-I[idx_p] + IL[idx_p] + I0[idx_p]) /
719
- (Gsh[idx_p] * a[idx_p]))[idx_inf]
723
+ logargW = (np.log(I0[idx_inf]) - np.log(Gsh[idx_inf]) -
724
+ np.log(a[idx_inf]) +
725
+ (-I[idx_inf] + IL[idx_inf] + I0[idx_inf]) /
726
+ (Gsh[idx_inf] * a[idx_inf]))
720
727
 
721
728
  # Three iterations of Newton-Raphson method to solve
722
729
  # w+log(w)=logargW. The initial guess is w=logargW. Where direct
@@ -730,8 +737,7 @@ def _lambertw_v_from_i(current, photocurrent, saturation_current,
730
737
  # Eqn. 3 in Jain and Kapoor, 2004
731
738
  # V = -I*(Rs + Rsh) + IL*Rsh - a*lambertwterm + I0*Rsh
732
739
  # Recast in terms of Gsh=1/Rsh for better numerical stability.
733
- V[idx_p] = (IL[idx_p] + I0[idx_p] - I[idx_p]) / Gsh[idx_p] - \
734
- I[idx_p] * Rs[idx_p] - a[idx_p] * lambertwterm
740
+ V[idx_p] = (IL + I0 - I) / Gsh - I * Rs - a * lambertwterm
735
741
 
736
742
  if output_is_scalar:
737
743
  return V.item()
pvlib/solarposition.py CHANGED
@@ -285,7 +285,7 @@ def spa_python(time, latitude, longitude,
285
285
  Calculate the solar position using a python implementation of the
286
286
  NREL SPA algorithm.
287
287
 
288
- The details of the NREL SPA algorithm are described in [1]_.
288
+ The details of the NREL SPA algorithm are described in [1]_, [2]_.
289
289
 
290
290
  If numba is installed, the functions can be compiled to
291
291
  machine code and the function can be multithreaded.
@@ -328,25 +328,26 @@ def spa_python(time, latitude, longitude,
328
328
  -------
329
329
  DataFrame
330
330
  The DataFrame will have the following columns:
331
- apparent_zenith (degrees),
332
- zenith (degrees),
333
- apparent_elevation (degrees),
334
- elevation (degrees),
335
- azimuth (degrees),
336
- equation_of_time (minutes).
337
331
 
332
+ - apparent_zenith (degrees),
333
+ - zenith (degrees),
334
+ - apparent_elevation (degrees),
335
+ - elevation (degrees),
336
+ - azimuth (degrees),
337
+ - equation_of_time (minutes).
338
338
 
339
339
  References
340
340
  ----------
341
341
  .. [1] I. Reda and A. Andreas, Solar position algorithm for solar
342
342
  radiation applications. Solar Energy, vol. 76, no. 5, pp. 577-589, 2004.
343
+ :doi:`10.1016/j.solener.2003.12.003`.
343
344
 
344
345
  .. [2] I. Reda and A. Andreas, Corrigendum to Solar position algorithm for
345
346
  solar radiation applications. Solar Energy, vol. 81, no. 6, p. 838,
346
- 2007.
347
+ 2007. :doi:`10.1016/j.solener.2007.01.003`.
347
348
 
348
- .. [3] USNO delta T:
349
- https://maia.usno.navy.mil/products/deltaT
349
+ .. [3] `U.S. Naval Observatory, delta T
350
+ <https://maia.usno.navy.mil/products/deltaT>`_
350
351
 
351
352
  See also
352
353
  --------
@@ -239,27 +239,15 @@ def spectral_factor_firstsolar(precipitable_water, airmass_absolute,
239
239
  """
240
240
  pw = np.atleast_1d(precipitable_water)
241
241
  pw = pw.astype('float64')
242
- if np.min(pw) < min_precipitable_water:
243
- pw = np.maximum(pw, min_precipitable_water)
244
- warn('Low precipitable water values replaced with '
245
- f'{min_precipitable_water} cm in the calculation of spectral '
246
- 'mismatch.')
247
-
248
- if np.max(pw) > max_precipitable_water:
249
- pw[pw > max_precipitable_water] = np.nan
250
- warn('High precipitable water values replaced with np.nan in '
251
- 'the calculation of spectral mismatch.')
242
+ pw = np.maximum(pw, min_precipitable_water)
243
+ pw[pw > max_precipitable_water] = np.nan
252
244
 
253
245
  airmass_absolute = np.minimum(airmass_absolute, max_airmass_absolute)
254
-
255
- if np.min(airmass_absolute) < min_airmass_absolute:
256
- airmass_absolute = np.maximum(airmass_absolute, min_airmass_absolute)
257
- warn('Low airmass values replaced with 'f'{min_airmass_absolute} in '
258
- 'the calculation of spectral mismatch.')
259
- # pvlib.atmosphere.get_absolute_airmass(1,
260
- # pvlib.atmosphere.alt2pres(4340)) = 0.58 Elevation of
261
- # Mina Pirquita, Argentian = 4340 m. Highest elevation city with
262
- # population over 50,000.
246
+ # pvlib.atmosphere.get_absolute_airmass(1,
247
+ # pvlib.atmosphere.alt2pres(4340)) = 0.58 Elevation of
248
+ # Mina Pirquita, Argentian = 4340 m. Highest elevation city with
249
+ # population over 50,000.
250
+ airmass_absolute = np.maximum(airmass_absolute, min_airmass_absolute)
263
251
 
264
252
  _coefficients = {}
265
253
  _coefficients['cdte'] = (
pvlib/temperature.py CHANGED
@@ -618,7 +618,7 @@ def faiman_rad(poa_global, temp_air, wind_speed=1.0, ir_down=None,
618
618
  return temp_air + temp_difference
619
619
 
620
620
 
621
- def ross(poa_global, temp_air, noct):
621
+ def ross(poa_global, temp_air, noct=None, k=None):
622
622
  r'''
623
623
  Calculate cell temperature using the Ross model.
624
624
 
@@ -630,14 +630,19 @@ def ross(poa_global, temp_air, noct):
630
630
  Parameters
631
631
  ----------
632
632
  poa_global : numeric
633
- Total incident irradiance. [W/m^2]
633
+ Total incident irradiance. [W/m⁻²]
634
634
 
635
635
  temp_air : numeric
636
636
  Ambient dry bulb temperature. [C]
637
637
 
638
- noct : numeric
638
+ noct : numeric, optional
639
639
  Nominal operating cell temperature [C], determined at conditions of
640
- 800 W/m^2 irradiance, 20 C ambient air temperature and 1 m/s wind.
640
+ 800 W/m⁻² irradiance, 20 C ambient air temperature and 1 m/s wind.
641
+ If ``noct`` is not provided, ``k`` is required.
642
+ k: numeric, optional
643
+ Ross coefficient [Km²W⁻¹], which is an alternative to employing
644
+ NOCT in Ross's equation. If ``k`` is not provided, ``noct`` is
645
+ required.
641
646
 
642
647
  Returns
643
648
  -------
@@ -650,19 +655,62 @@ def ross(poa_global, temp_air, noct):
650
655
 
651
656
  .. math::
652
657
 
653
- T_{C} = T_{a} + \frac{NOCT - 20}{80} S
654
-
655
- where :math:`S` is the plane of array irradiance in :math:`mW/{cm}^2`.
656
- This function expects irradiance in :math:`W/m^2`.
658
+ T_{C} = T_{a} + \frac{NOCT - 20}{80} S = T_{a} + k × S
659
+
660
+ where :math:`S` is the plane of array irradiance in mWcm⁻².
661
+ This function expects irradiance in Wm⁻².
662
+
663
+ Representative values for k are provided in [2]_, covering different types
664
+ of mounting and degrees of back ventialtion. The naming designations,
665
+ however, are adapted from [3]_ to enhance clarity and usability.
666
+
667
+ +--------------------------------------+-----------+
668
+ | Mounting | :math:`k` |
669
+ +======================================+===========+
670
+ | Sloped roof, well ventilated | 0.02 |
671
+ +--------------------------------------+-----------+
672
+ | Free-standing system | 0.0208 |
673
+ +--------------------------------------+-----------+
674
+ | Flat roof, well ventilated | 0.026 |
675
+ +--------------------------------------+-----------+
676
+ | Sloped roof, poorly ventilated | 0.0342 |
677
+ +--------------------------------------+-----------+
678
+ | Facade integrated, semi-ventilated | 0.0455 |
679
+ +--------------------------------------+-----------+
680
+ | Facade integrated, poorly ventilated | 0.0538 |
681
+ +--------------------------------------+-----------+
682
+ | Sloped roof, non-ventilated | 0.0563 |
683
+ +--------------------------------------+-----------+
684
+
685
+ It is also worth noting that the semi-ventilated facade case refers to
686
+ partly transparent compound glass insulation modules, while the non-
687
+ ventilated case corresponds to opaque, insulated PV-cladding elements.
688
+ However, the emphasis in [3]_ appears to be on ventilation conditions
689
+ rather than module construction.
657
690
 
658
691
  References
659
692
  ----------
660
693
  .. [1] Ross, R. G. Jr., (1981). "Design Techniques for Flat-Plate
661
694
  Photovoltaic Arrays". 15th IEEE Photovoltaic Specialist Conference,
662
695
  Orlando, FL.
696
+ .. [2] E. Skoplaki and J. A. Palyvos, "Operating temperature of
697
+ photovoltaic modules: A survey of pertinent correlations," Renewable
698
+ Energy, vol. 34, no. 1, pp. 23–29, Jan. 2009,
699
+ :doi:`10.1016/j.renene.2008.04.009`
700
+ .. [3] T. Nordmann and L. Clavadetscher, "Understanding temperature
701
+ effects on PV system performance," Proceedings of 3rd World Conference
702
+ on Photovoltaic Energy Conversion, May 2003.
663
703
  '''
664
- # factor of 0.1 converts irradiance from W/m2 to mW/cm2
665
- return temp_air + (noct - 20.) / 80. * poa_global * 0.1
704
+ if (noct is None) & (k is None):
705
+ raise ValueError("Either noct or k is required.")
706
+ elif (noct is not None) & (k is not None):
707
+ raise ValueError("Provide only one of noct or k, not both.")
708
+ elif k is None:
709
+ # factor of 0.1 converts irradiance from W/m2 to mW/cm2
710
+ return temp_air + (noct - 20.) / 80. * poa_global * 0.1
711
+ elif noct is None:
712
+ # k assumes irradiance in W.m-2, dismissing 0.1 factor
713
+ return temp_air + k * poa_global
666
714
 
667
715
 
668
716
  def _fuentes_hconv(tave, windmod, tinoct, temp_delta, xlen, tilt,
pvlib/tools.py CHANGED
@@ -562,7 +562,7 @@ def normalize_max2one(a):
562
562
  return res
563
563
 
564
564
 
565
- def _file_context_manager(filename_or_object, mode='r'):
565
+ def _file_context_manager(filename_or_object, mode='r', encoding=None):
566
566
  """
567
567
  Open a filename/path for reading, or pass a file-like object
568
568
  through unchanged.
@@ -584,5 +584,5 @@ def _file_context_manager(filename_or_object, mode='r'):
584
584
  context = contextlib.nullcontext(filename_or_object)
585
585
  else:
586
586
  # otherwise, assume a filename or path
587
- context = open(str(filename_or_object), mode=mode)
587
+ context = open(str(filename_or_object), mode=mode, encoding=encoding)
588
588
  return context
pvlib/tracking.py CHANGED
@@ -4,9 +4,14 @@ import pandas as pd
4
4
  from pvlib.tools import cosd, sind, tand, acosd, asind
5
5
  from pvlib import irradiance
6
6
  from pvlib import shading
7
+ from pvlib._deprecation import renamed_kwarg_warning
7
8
 
8
9
 
9
- def singleaxis(apparent_zenith, apparent_azimuth,
10
+ @renamed_kwarg_warning(
11
+ since='0.13.1',
12
+ old_param_name='apparent_azimuth',
13
+ new_param_name='solar_azimuth')
14
+ def singleaxis(apparent_zenith, solar_azimuth,
10
15
  axis_tilt=0, axis_azimuth=0, max_angle=90,
11
16
  backtrack=True, gcr=2.0/7.0, cross_axis_tilt=0):
12
17
  """
@@ -33,7 +38,7 @@ def singleaxis(apparent_zenith, apparent_azimuth,
33
38
  apparent_zenith : float, 1d array, or Series
34
39
  Solar apparent zenith angles in decimal degrees.
35
40
 
36
- apparent_azimuth : float, 1d array, or Series
41
+ solar_azimuth : float, 1d array, or Series
37
42
  Solar apparent azimuth angles in decimal degrees.
38
43
 
39
44
  axis_tilt : float, default 0
@@ -123,10 +128,10 @@ def singleaxis(apparent_zenith, apparent_azimuth,
123
128
  index = None
124
129
 
125
130
  # convert scalars to arrays
126
- apparent_azimuth = np.atleast_1d(apparent_azimuth)
131
+ solar_azimuth = np.atleast_1d(solar_azimuth)
127
132
  apparent_zenith = np.atleast_1d(apparent_zenith)
128
133
 
129
- if apparent_azimuth.ndim > 1 or apparent_zenith.ndim > 1:
134
+ if solar_azimuth.ndim > 1 or apparent_zenith.ndim > 1:
130
135
  raise ValueError('Input dimensions must not exceed 1')
131
136
 
132
137
  # The ideal tracking angle, omega_ideal, is the rotation to place the sun
@@ -142,7 +147,7 @@ def singleaxis(apparent_zenith, apparent_azimuth,
142
147
  axis_tilt=axis_tilt,
143
148
  axis_azimuth=axis_azimuth,
144
149
  solar_zenith=apparent_zenith,
145
- solar_azimuth=apparent_azimuth,
150
+ solar_azimuth=solar_azimuth,
146
151
  )
147
152
 
148
153
  # filter for sun above panel horizon
@@ -191,7 +196,7 @@ def singleaxis(apparent_zenith, apparent_azimuth,
191
196
  surface_tilt = surface['surface_tilt']
192
197
  surface_azimuth = surface['surface_azimuth']
193
198
  aoi = irradiance.aoi(surface_tilt, surface_azimuth,
194
- apparent_zenith, apparent_azimuth)
199
+ apparent_zenith, solar_azimuth)
195
200
 
196
201
  # Bundle DataFrame for return values and filter for sun below horizon.
197
202
  out = {'tracker_theta': tracker_theta, 'aoi': aoi,