pvlib 0.9.5__py3-none-any.whl → 0.10.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pvlib/__init__.py +3 -2
- pvlib/atmosphere.py +6 -171
- pvlib/bifacial/infinite_sheds.py +30 -267
- pvlib/bifacial/utils.py +225 -5
- pvlib/data/test_psm3_2017.csv +17521 -17521
- pvlib/data/test_read_psm3.csv +17522 -17522
- pvlib/data/test_read_pvgis_horizon.csv +49 -0
- pvlib/data/variables_style_rules.csv +3 -0
- pvlib/iam.py +17 -4
- pvlib/inverter.py +6 -1
- pvlib/iotools/__init__.py +7 -2
- pvlib/iotools/acis.py +516 -0
- pvlib/iotools/midc.py +4 -4
- pvlib/iotools/psm3.py +32 -31
- pvlib/iotools/pvgis.py +84 -28
- pvlib/iotools/sodapro.py +8 -6
- pvlib/iotools/srml.py +121 -18
- pvlib/iotools/surfrad.py +2 -2
- pvlib/iotools/tmy.py +146 -102
- pvlib/irradiance.py +151 -0
- pvlib/ivtools/sde.py +11 -7
- pvlib/ivtools/sdm.py +16 -10
- pvlib/ivtools/utils.py +6 -6
- pvlib/location.py +3 -2
- pvlib/modelchain.py +67 -70
- pvlib/pvsystem.py +160 -532
- pvlib/shading.py +41 -0
- pvlib/singlediode.py +215 -65
- pvlib/soiling.py +3 -3
- pvlib/spa.py +327 -368
- pvlib/spectrum/__init__.py +8 -2
- pvlib/spectrum/mismatch.py +335 -0
- pvlib/temperature.py +1 -8
- pvlib/tests/bifacial/test_infinite_sheds.py +0 -111
- pvlib/tests/bifacial/test_utils.py +101 -4
- pvlib/tests/conftest.py +0 -31
- pvlib/tests/iotools/test_acis.py +213 -0
- pvlib/tests/iotools/test_midc.py +6 -6
- pvlib/tests/iotools/test_psm3.py +3 -3
- pvlib/tests/iotools/test_pvgis.py +21 -14
- pvlib/tests/iotools/test_sodapro.py +1 -1
- pvlib/tests/iotools/test_srml.py +71 -6
- pvlib/tests/iotools/test_tmy.py +43 -8
- pvlib/tests/ivtools/test_sde.py +19 -17
- pvlib/tests/ivtools/test_sdm.py +9 -4
- pvlib/tests/test_atmosphere.py +6 -62
- pvlib/tests/test_iam.py +12 -0
- pvlib/tests/test_irradiance.py +40 -2
- pvlib/tests/test_location.py +1 -1
- pvlib/tests/test_modelchain.py +33 -76
- pvlib/tests/test_pvsystem.py +366 -201
- pvlib/tests/test_shading.py +28 -0
- pvlib/tests/test_singlediode.py +166 -30
- pvlib/tests/test_soiling.py +8 -7
- pvlib/tests/test_spa.py +6 -7
- pvlib/tests/test_spectrum.py +145 -1
- pvlib/tests/test_temperature.py +0 -7
- pvlib/tests/test_tools.py +25 -0
- pvlib/tests/test_tracking.py +0 -149
- pvlib/tools.py +26 -1
- pvlib/tracking.py +1 -269
- {pvlib-0.9.5.dist-info → pvlib-0.10.0.dist-info}/METADATA +1 -9
- {pvlib-0.9.5.dist-info → pvlib-0.10.0.dist-info}/RECORD +67 -68
- pvlib/forecast.py +0 -1211
- pvlib/iotools/ecmwf_macc.py +0 -312
- pvlib/tests/iotools/test_ecmwf_macc.py +0 -162
- pvlib/tests/test_forecast.py +0 -228
- {pvlib-0.9.5.dist-info → pvlib-0.10.0.dist-info}/AUTHORS.md +0 -0
- {pvlib-0.9.5.dist-info → pvlib-0.10.0.dist-info}/LICENSE +0 -0
- {pvlib-0.9.5.dist-info → pvlib-0.10.0.dist-info}/WHEEL +0 -0
- {pvlib-0.9.5.dist-info → pvlib-0.10.0.dist-info}/top_level.txt +0 -0
pvlib/spectrum/__init__.py
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
1
|
from pvlib.spectrum.spectrl2 import spectrl2 # noqa: F401
|
|
2
|
-
from pvlib.spectrum.mismatch import (
|
|
3
|
-
|
|
2
|
+
from pvlib.spectrum.mismatch import ( # noqa: F401
|
|
3
|
+
calc_spectral_mismatch_field,
|
|
4
|
+
get_am15g,
|
|
5
|
+
get_example_spectral_response,
|
|
6
|
+
spectral_factor_caballero,
|
|
7
|
+
spectral_factor_firstsolar,
|
|
8
|
+
spectral_factor_sapm,
|
|
9
|
+
)
|
pvlib/spectrum/mismatch.py
CHANGED
|
@@ -8,6 +8,8 @@ import pandas as pd
|
|
|
8
8
|
from scipy.interpolate import interp1d
|
|
9
9
|
import os
|
|
10
10
|
|
|
11
|
+
from warnings import warn
|
|
12
|
+
|
|
11
13
|
|
|
12
14
|
def get_example_spectral_response(wavelength=None):
|
|
13
15
|
'''
|
|
@@ -235,3 +237,336 @@ def calc_spectral_mismatch_field(sr, e_sun, e_ref=None):
|
|
|
235
237
|
smm = pd.Series(smm, index=e_sun.index)
|
|
236
238
|
|
|
237
239
|
return smm
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def spectral_factor_firstsolar(precipitable_water, airmass_absolute,
|
|
243
|
+
module_type=None, coefficients=None,
|
|
244
|
+
min_precipitable_water=0.1,
|
|
245
|
+
max_precipitable_water=8):
|
|
246
|
+
r"""
|
|
247
|
+
Spectral mismatch modifier based on precipitable water and absolute
|
|
248
|
+
(pressure-adjusted) airmass.
|
|
249
|
+
|
|
250
|
+
Estimates a spectral mismatch modifier :math:`M` representing the effect on
|
|
251
|
+
module short circuit current of variation in the spectral
|
|
252
|
+
irradiance. :math:`M` is estimated from absolute (pressure currected) air
|
|
253
|
+
mass, :math:`AM_a`, and precipitable water, :math:`Pw`, using the following
|
|
254
|
+
function:
|
|
255
|
+
|
|
256
|
+
.. math::
|
|
257
|
+
|
|
258
|
+
M = c_1 + c_2 AM_a + c_3 Pw + c_4 AM_a^{0.5}
|
|
259
|
+
+ c_5 Pw^{0.5} + c_6 \frac{AM_a} {Pw^{0.5}}
|
|
260
|
+
|
|
261
|
+
Default coefficients are determined for several cell types with
|
|
262
|
+
known quantum efficiency curves, by using the Simple Model of the
|
|
263
|
+
Atmospheric Radiative Transfer of Sunshine (SMARTS) [1]_. Using
|
|
264
|
+
SMARTS, spectrums are simulated with all combinations of AMa and
|
|
265
|
+
Pw where:
|
|
266
|
+
|
|
267
|
+
* :math:`0.5 \textrm{cm} <= Pw <= 5 \textrm{cm}`
|
|
268
|
+
* :math:`1.0 <= AM_a <= 5.0`
|
|
269
|
+
* Spectral range is limited to that of CMP11 (280 nm to 2800 nm)
|
|
270
|
+
* spectrum simulated on a plane normal to the sun
|
|
271
|
+
* All other parameters fixed at G173 standard
|
|
272
|
+
|
|
273
|
+
From these simulated spectra, M is calculated using the known
|
|
274
|
+
quantum efficiency curves. Multiple linear regression is then
|
|
275
|
+
applied to fit Eq. 1 to determine the coefficients for each module.
|
|
276
|
+
|
|
277
|
+
Based on the PVLIB Matlab function ``pvl_FSspeccorr`` by Mitchell
|
|
278
|
+
Lee and Alex Panchula of First Solar, 2016 [2]_.
|
|
279
|
+
|
|
280
|
+
Parameters
|
|
281
|
+
----------
|
|
282
|
+
precipitable_water : numeric
|
|
283
|
+
atmospheric precipitable water. [cm]
|
|
284
|
+
|
|
285
|
+
airmass_absolute : numeric
|
|
286
|
+
absolute (pressure-adjusted) airmass. [unitless]
|
|
287
|
+
|
|
288
|
+
module_type : str, optional
|
|
289
|
+
a string specifying a cell type. Values of 'cdte', 'monosi', 'xsi',
|
|
290
|
+
'multisi', and 'polysi' (can be lower or upper case). If provided,
|
|
291
|
+
module_type selects default coefficients for the following modules:
|
|
292
|
+
|
|
293
|
+
* 'cdte' - First Solar Series 4-2 CdTe module.
|
|
294
|
+
* 'monosi', 'xsi' - First Solar TetraSun module.
|
|
295
|
+
* 'multisi', 'polysi' - anonymous multi-crystalline silicon module.
|
|
296
|
+
* 'cigs' - anonymous copper indium gallium selenide module.
|
|
297
|
+
* 'asi' - anonymous amorphous silicon module.
|
|
298
|
+
|
|
299
|
+
The module used to calculate the spectral correction
|
|
300
|
+
coefficients corresponds to the Multi-crystalline silicon
|
|
301
|
+
Manufacturer 2 Model C from [3]_. The spectral response (SR) of CIGS
|
|
302
|
+
and a-Si modules used to derive coefficients can be found in [4]_
|
|
303
|
+
|
|
304
|
+
coefficients : array-like, optional
|
|
305
|
+
Allows for entry of user-defined spectral correction
|
|
306
|
+
coefficients. Coefficients must be of length 6. Derivation of
|
|
307
|
+
coefficients requires use of SMARTS and PV module quantum
|
|
308
|
+
efficiency curve. Useful for modeling PV module types which are
|
|
309
|
+
not included as defaults, or to fine tune the spectral
|
|
310
|
+
correction to a particular PV module. Note that the parameters for
|
|
311
|
+
modules with very similar quantum efficiency should be similar,
|
|
312
|
+
in most cases limiting the need for module specific coefficients.
|
|
313
|
+
|
|
314
|
+
min_precipitable_water : float, default 0.1
|
|
315
|
+
minimum atmospheric precipitable water. Any ``precipitable_water``
|
|
316
|
+
value lower than ``min_precipitable_water``
|
|
317
|
+
is set to ``min_precipitable_water`` to avoid model divergence. [cm]
|
|
318
|
+
|
|
319
|
+
max_precipitable_water : float, default 8
|
|
320
|
+
maximum atmospheric precipitable water. Any ``precipitable_water``
|
|
321
|
+
value greater than ``max_precipitable_water``
|
|
322
|
+
is set to ``np.nan`` to avoid model divergence. [cm]
|
|
323
|
+
|
|
324
|
+
Returns
|
|
325
|
+
-------
|
|
326
|
+
modifier: array-like
|
|
327
|
+
spectral mismatch factor (unitless) which can be multiplied
|
|
328
|
+
with broadband irradiance reaching a module's cells to estimate
|
|
329
|
+
effective irradiance, i.e., the irradiance that is converted to
|
|
330
|
+
electrical current.
|
|
331
|
+
|
|
332
|
+
References
|
|
333
|
+
----------
|
|
334
|
+
.. [1] Gueymard, Christian. SMARTS2: a simple model of the atmospheric
|
|
335
|
+
radiative transfer of sunshine: algorithms and performance
|
|
336
|
+
assessment. Cocoa, FL: Florida Solar Energy Center, 1995.
|
|
337
|
+
.. [2] Lee, Mitchell, and Panchula, Alex. "Spectral Correction for
|
|
338
|
+
Photovoltaic Module Performance Based on Air Mass and Precipitable
|
|
339
|
+
Water." IEEE Photovoltaic Specialists Conference, Portland, 2016
|
|
340
|
+
.. [3] Marion, William F., et al. User's Manual for Data for Validating
|
|
341
|
+
Models for PV Module Performance. National Renewable Energy
|
|
342
|
+
Laboratory, 2014. http://www.nrel.gov/docs/fy14osti/61610.pdf
|
|
343
|
+
.. [4] Schweiger, M. and Hermann, W, Influence of Spectral Effects
|
|
344
|
+
on Energy Yield of Different PV Modules: Comparison of Pwat and
|
|
345
|
+
MMF Approach, TUV Rheinland Energy GmbH report 21237296.003,
|
|
346
|
+
January 2017
|
|
347
|
+
"""
|
|
348
|
+
|
|
349
|
+
# --- Screen Input Data ---
|
|
350
|
+
|
|
351
|
+
# *** Pw ***
|
|
352
|
+
# Replace Pw Values below 0.1 cm with 0.1 cm to prevent model from
|
|
353
|
+
# diverging"
|
|
354
|
+
pw = np.atleast_1d(precipitable_water)
|
|
355
|
+
pw = pw.astype('float64')
|
|
356
|
+
if np.min(pw) < min_precipitable_water:
|
|
357
|
+
pw = np.maximum(pw, min_precipitable_water)
|
|
358
|
+
warn('Exceptionally low pw values replaced with '
|
|
359
|
+
f'{min_precipitable_water} cm to prevent model divergence')
|
|
360
|
+
|
|
361
|
+
# Warn user about Pw data that is exceptionally high
|
|
362
|
+
if np.max(pw) > max_precipitable_water:
|
|
363
|
+
pw[pw > max_precipitable_water] = np.nan
|
|
364
|
+
warn('Exceptionally high pw values replaced by np.nan: '
|
|
365
|
+
'check input data.')
|
|
366
|
+
|
|
367
|
+
# *** AMa ***
|
|
368
|
+
# Replace Extremely High AM with AM 10 to prevent model divergence
|
|
369
|
+
# AM > 10 will only occur very close to sunset
|
|
370
|
+
if np.max(airmass_absolute) > 10:
|
|
371
|
+
airmass_absolute = np.minimum(airmass_absolute, 10)
|
|
372
|
+
|
|
373
|
+
# Warn user about AMa data that is exceptionally low
|
|
374
|
+
if np.min(airmass_absolute) < 0.58:
|
|
375
|
+
warn('Exceptionally low air mass: ' +
|
|
376
|
+
'model not intended for extra-terrestrial use')
|
|
377
|
+
# pvl_absoluteairmass(1,pvl_alt2pres(4340)) = 0.58 Elevation of
|
|
378
|
+
# Mina Pirquita, Argentian = 4340 m. Highest elevation city with
|
|
379
|
+
# population over 50,000.
|
|
380
|
+
|
|
381
|
+
_coefficients = {}
|
|
382
|
+
_coefficients['cdte'] = (
|
|
383
|
+
0.86273, -0.038948, -0.012506, 0.098871, 0.084658, -0.0042948)
|
|
384
|
+
_coefficients['monosi'] = (
|
|
385
|
+
0.85914, -0.020880, -0.0058853, 0.12029, 0.026814, -0.0017810)
|
|
386
|
+
_coefficients['xsi'] = _coefficients['monosi']
|
|
387
|
+
_coefficients['polysi'] = (
|
|
388
|
+
0.84090, -0.027539, -0.0079224, 0.13570, 0.038024, -0.0021218)
|
|
389
|
+
_coefficients['multisi'] = _coefficients['polysi']
|
|
390
|
+
_coefficients['cigs'] = (
|
|
391
|
+
0.85252, -0.022314, -0.0047216, 0.13666, 0.013342, -0.0008945)
|
|
392
|
+
_coefficients['asi'] = (
|
|
393
|
+
1.12094, -0.047620, -0.0083627, -0.10443, 0.098382, -0.0033818)
|
|
394
|
+
|
|
395
|
+
if module_type is not None and coefficients is None:
|
|
396
|
+
coefficients = _coefficients[module_type.lower()]
|
|
397
|
+
elif module_type is None and coefficients is not None:
|
|
398
|
+
pass
|
|
399
|
+
elif module_type is None and coefficients is None:
|
|
400
|
+
raise TypeError('No valid input provided, both module_type and ' +
|
|
401
|
+
'coefficients are None')
|
|
402
|
+
else:
|
|
403
|
+
raise TypeError('Cannot resolve input, must supply only one of ' +
|
|
404
|
+
'module_type and coefficients')
|
|
405
|
+
|
|
406
|
+
# Evaluate Spectral Shift
|
|
407
|
+
coeff = coefficients
|
|
408
|
+
ama = airmass_absolute
|
|
409
|
+
modifier = (
|
|
410
|
+
coeff[0] + coeff[1]*ama + coeff[2]*pw + coeff[3]*np.sqrt(ama) +
|
|
411
|
+
coeff[4]*np.sqrt(pw) + coeff[5]*ama/np.sqrt(pw))
|
|
412
|
+
|
|
413
|
+
return modifier
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
def spectral_factor_sapm(airmass_absolute, module):
|
|
417
|
+
"""
|
|
418
|
+
Calculates the SAPM spectral loss coefficient, F1.
|
|
419
|
+
|
|
420
|
+
Parameters
|
|
421
|
+
----------
|
|
422
|
+
airmass_absolute : numeric
|
|
423
|
+
Absolute airmass
|
|
424
|
+
|
|
425
|
+
module : dict-like
|
|
426
|
+
A dict, Series, or DataFrame defining the SAPM performance
|
|
427
|
+
parameters. See the :py:func:`sapm` notes section for more
|
|
428
|
+
details.
|
|
429
|
+
|
|
430
|
+
Returns
|
|
431
|
+
-------
|
|
432
|
+
F1 : numeric
|
|
433
|
+
The SAPM spectral loss coefficient.
|
|
434
|
+
|
|
435
|
+
Notes
|
|
436
|
+
-----
|
|
437
|
+
nan airmass values will result in 0 output.
|
|
438
|
+
"""
|
|
439
|
+
|
|
440
|
+
am_coeff = [module['A4'], module['A3'], module['A2'], module['A1'],
|
|
441
|
+
module['A0']]
|
|
442
|
+
|
|
443
|
+
spectral_loss = np.polyval(am_coeff, airmass_absolute)
|
|
444
|
+
|
|
445
|
+
spectral_loss = np.where(np.isnan(spectral_loss), 0, spectral_loss)
|
|
446
|
+
|
|
447
|
+
spectral_loss = np.maximum(0, spectral_loss)
|
|
448
|
+
|
|
449
|
+
if isinstance(airmass_absolute, pd.Series):
|
|
450
|
+
spectral_loss = pd.Series(spectral_loss, airmass_absolute.index)
|
|
451
|
+
|
|
452
|
+
return spectral_loss
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
def spectral_factor_caballero(precipitable_water, airmass_absolute, aod500,
|
|
456
|
+
module_type=None, coefficients=None):
|
|
457
|
+
r"""
|
|
458
|
+
Estimate a technology-specific spectral mismatch modifier from
|
|
459
|
+
airmass, aerosol optical depth, and atmospheric precipitable water,
|
|
460
|
+
using the Caballero model.
|
|
461
|
+
|
|
462
|
+
The model structure was motivated by examining the effect of these three
|
|
463
|
+
atmospheric parameters on simulated irradiance spectra and spectral
|
|
464
|
+
modifiers. However, the coefficient values reported in [1]_ and
|
|
465
|
+
available here via the ``module_type`` parameter were determined
|
|
466
|
+
by fitting the model equations to spectral factors calculated from
|
|
467
|
+
global tilted spectral irradiance measurements taken in the city of
|
|
468
|
+
Jaén, Spain. See [1]_ for details.
|
|
469
|
+
|
|
470
|
+
Parameters
|
|
471
|
+
----------
|
|
472
|
+
precipitable_water : numeric
|
|
473
|
+
atmospheric precipitable water. [cm]
|
|
474
|
+
|
|
475
|
+
airmass_absolute : numeric
|
|
476
|
+
absolute (pressure-adjusted) airmass. [unitless]
|
|
477
|
+
|
|
478
|
+
aod500 : numeric
|
|
479
|
+
atmospheric aerosol optical depth at 500 nm. [unitless]
|
|
480
|
+
|
|
481
|
+
module_type : str, optional
|
|
482
|
+
One of the following PV technology strings from [1]_:
|
|
483
|
+
|
|
484
|
+
* ``'cdte'`` - anonymous CdTe module.
|
|
485
|
+
* ``'monosi'``, - anonymous sc-si module.
|
|
486
|
+
* ``'multisi'``, - anonymous mc-si- module.
|
|
487
|
+
* ``'cigs'`` - anonymous copper indium gallium selenide module.
|
|
488
|
+
* ``'asi'`` - anonymous amorphous silicon module.
|
|
489
|
+
* ``'perovskite'`` - anonymous pervoskite module.
|
|
490
|
+
|
|
491
|
+
coefficients : array-like, optional
|
|
492
|
+
user-defined coefficients, if not using one of the default coefficient
|
|
493
|
+
sets via the ``module_type`` parameter.
|
|
494
|
+
|
|
495
|
+
Returns
|
|
496
|
+
-------
|
|
497
|
+
modifier: numeric
|
|
498
|
+
spectral mismatch factor (unitless) which is multiplied
|
|
499
|
+
with broadband irradiance reaching a module's cells to estimate
|
|
500
|
+
effective irradiance, i.e., the irradiance that is converted to
|
|
501
|
+
electrical current.
|
|
502
|
+
|
|
503
|
+
References
|
|
504
|
+
----------
|
|
505
|
+
.. [1] Caballero, J.A., Fernández, E., Theristis, M.,
|
|
506
|
+
Almonacid, F., and Nofuentes, G. "Spectral Corrections Based on
|
|
507
|
+
Air Mass, Aerosol Optical Depth and Precipitable Water
|
|
508
|
+
for PV Performance Modeling."
|
|
509
|
+
IEEE Journal of Photovoltaics 2018, 8(2), 552-558.
|
|
510
|
+
:doi:`10.1109/jphotov.2017.2787019`
|
|
511
|
+
"""
|
|
512
|
+
|
|
513
|
+
if module_type is None and coefficients is None:
|
|
514
|
+
raise ValueError('Must provide either `module_type` or `coefficients`')
|
|
515
|
+
if module_type is not None and coefficients is not None:
|
|
516
|
+
raise ValueError('Only one of `module_type` and `coefficients` should '
|
|
517
|
+
'be provided')
|
|
518
|
+
|
|
519
|
+
# Experimental coefficients from [1]_.
|
|
520
|
+
# The extra 0/1 coefficients at the end are used to enable/disable
|
|
521
|
+
# terms to match the different equation forms in Table 1.
|
|
522
|
+
_coefficients = {}
|
|
523
|
+
_coefficients['cdte'] = (
|
|
524
|
+
1.0044, 0.0095, -0.0037, 0.0002, 0.0000, -0.0046,
|
|
525
|
+
-0.0182, 0, 0.0095, 0.0068, 0, 1)
|
|
526
|
+
_coefficients['monosi'] = (
|
|
527
|
+
0.9706, 0.0377, -0.0123, 0.0025, -0.0002, 0.0159,
|
|
528
|
+
-0.0165, 0, -0.0016, -0.0027, 1, 0)
|
|
529
|
+
_coefficients['multisi'] = (
|
|
530
|
+
0.9836, 0.0254, -0.0085, 0.0016, -0.0001, 0.0094,
|
|
531
|
+
-0.0132, 0, -0.0002, -0.0011, 1, 0)
|
|
532
|
+
_coefficients['cigs'] = (
|
|
533
|
+
0.9801, 0.0283, -0.0092, 0.0019, -0.0001, 0.0117,
|
|
534
|
+
-0.0126, 0, -0.0011, -0.0019, 1, 0)
|
|
535
|
+
_coefficients['asi'] = (
|
|
536
|
+
1.1060, -0.0848, 0.0302, -0.0076, 0.0006, -0.1283,
|
|
537
|
+
0.0986, -0.0254, 0.0156, 0.0146, 1, 0)
|
|
538
|
+
_coefficients['perovskite'] = (
|
|
539
|
+
1.0637, -0.0491, 0.0180, -0.0047, 0.0004, -0.0773,
|
|
540
|
+
0.0583, -0.0159, 0.01251, 0.0109, 1, 0)
|
|
541
|
+
|
|
542
|
+
if module_type is not None:
|
|
543
|
+
coeff = _coefficients[module_type]
|
|
544
|
+
else:
|
|
545
|
+
coeff = coefficients
|
|
546
|
+
|
|
547
|
+
# Evaluate spectral correction factor
|
|
548
|
+
ama = airmass_absolute
|
|
549
|
+
aod500_ref = 0.084
|
|
550
|
+
pw_ref = 1.4164
|
|
551
|
+
|
|
552
|
+
f_AM = (
|
|
553
|
+
coeff[0]
|
|
554
|
+
+ coeff[1] * ama
|
|
555
|
+
+ coeff[2] * ama**2
|
|
556
|
+
+ coeff[3] * ama**3
|
|
557
|
+
+ coeff[4] * ama**4
|
|
558
|
+
)
|
|
559
|
+
# Eq 6, with Table 1
|
|
560
|
+
f_AOD = (aod500 - aod500_ref) * (
|
|
561
|
+
coeff[5]
|
|
562
|
+
+ coeff[10] * coeff[6] * ama
|
|
563
|
+
+ coeff[11] * coeff[6] * np.log(ama)
|
|
564
|
+
+ coeff[7] * ama**2
|
|
565
|
+
)
|
|
566
|
+
# Eq 7, with Table 1
|
|
567
|
+
f_PW = (precipitable_water - pw_ref) * (
|
|
568
|
+
coeff[8]
|
|
569
|
+
+ coeff[9] * np.log(ama)
|
|
570
|
+
)
|
|
571
|
+
modifier = f_AM + f_AOD + f_PW # Eq 5
|
|
572
|
+
return modifier
|
pvlib/temperature.py
CHANGED
|
@@ -291,7 +291,7 @@ def sapm_cell_from_module(module_temperature, poa_global, deltaT,
|
|
|
291
291
|
|
|
292
292
|
|
|
293
293
|
def pvsyst_cell(poa_global, temp_air, wind_speed=1.0, u_c=29.0, u_v=0.0,
|
|
294
|
-
|
|
294
|
+
module_efficiency=0.1, alpha_absorption=0.9):
|
|
295
295
|
r"""
|
|
296
296
|
Calculate cell temperature using an empirical heat loss factor model
|
|
297
297
|
as implemented in PVsyst.
|
|
@@ -321,8 +321,6 @@ def pvsyst_cell(poa_global, temp_air, wind_speed=1.0, u_c=29.0, u_v=0.0,
|
|
|
321
321
|
in :eq:`pvsyst`.
|
|
322
322
|
:math:`\left[ \frac{\text{W}/\text{m}^2}{\text{C}\ \left( \text{m/s} \right)} \right]`
|
|
323
323
|
|
|
324
|
-
eta_m : numeric, default None (deprecated, use module_efficiency instead)
|
|
325
|
-
|
|
326
324
|
module_efficiency : numeric, default 0.1
|
|
327
325
|
Module external efficiency as a fraction. Parameter :math:`\eta_{m}`
|
|
328
326
|
in :eq:`pvsyst`. Calculate as
|
|
@@ -378,11 +376,6 @@ def pvsyst_cell(poa_global, temp_air, wind_speed=1.0, u_c=29.0, u_v=0.0,
|
|
|
378
376
|
37.93103448275862
|
|
379
377
|
""" # noQA: E501
|
|
380
378
|
|
|
381
|
-
if eta_m:
|
|
382
|
-
warn_deprecated(
|
|
383
|
-
since='v0.9', message='eta_m overwriting module_efficiency',
|
|
384
|
-
name='eta_m', alternative='module_efficiency', removal='v0.10')
|
|
385
|
-
module_efficiency = eta_m
|
|
386
379
|
total_loss_factor = u_c + u_v * wind_speed
|
|
387
380
|
heat_input = poa_global * alpha_absorption * (1 - module_efficiency)
|
|
388
381
|
temp_difference = heat_input / total_loss_factor
|
|
@@ -5,7 +5,6 @@ test infinite sheds
|
|
|
5
5
|
import numpy as np
|
|
6
6
|
import pandas as pd
|
|
7
7
|
from pvlib.bifacial import infinite_sheds
|
|
8
|
-
from pvlib.tools import cosd
|
|
9
8
|
from ..conftest import assert_series_equal
|
|
10
9
|
|
|
11
10
|
import pytest
|
|
@@ -42,116 +41,6 @@ def test_system():
|
|
|
42
41
|
return syst, pts, vfs_ground_sky
|
|
43
42
|
|
|
44
43
|
|
|
45
|
-
@pytest.mark.parametrize("vectorize", [True, False])
|
|
46
|
-
def test__vf_ground_sky_integ(test_system, vectorize):
|
|
47
|
-
ts, pts, vfs_gnd_sky = test_system
|
|
48
|
-
# pass rotation here since max_rows=1 for the hand-solved case in
|
|
49
|
-
# the fixture test_system, which means the ground-to-sky view factor
|
|
50
|
-
# isn't summed over enough rows for symmetry to hold.
|
|
51
|
-
vf_integ = infinite_sheds._vf_ground_sky_integ(
|
|
52
|
-
ts['rotation'], ts['surface_azimuth'],
|
|
53
|
-
ts['gcr'], ts['height'], ts['pitch'],
|
|
54
|
-
max_rows=1, npoints=3, vectorize=vectorize)
|
|
55
|
-
expected_vf_integ = np.trapz(vfs_gnd_sky, pts)
|
|
56
|
-
assert np.isclose(vf_integ, expected_vf_integ, rtol=0.1)
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
def test__vf_row_sky_integ(test_system):
|
|
60
|
-
ts, _, _ = test_system
|
|
61
|
-
gcr = ts['gcr']
|
|
62
|
-
surface_tilt = ts['surface_tilt']
|
|
63
|
-
f_x = np.array([0., 0.5, 1.])
|
|
64
|
-
shaded = []
|
|
65
|
-
noshade = []
|
|
66
|
-
for x in f_x:
|
|
67
|
-
s, ns = infinite_sheds._vf_row_sky_integ(
|
|
68
|
-
x, surface_tilt, gcr, npoints=100)
|
|
69
|
-
shaded.append(s)
|
|
70
|
-
noshade.append(ns)
|
|
71
|
-
|
|
72
|
-
def analytic(gcr, surface_tilt, x):
|
|
73
|
-
c = cosd(surface_tilt)
|
|
74
|
-
a = 1. / gcr
|
|
75
|
-
dx = np.sqrt(a**2 - 2 * a * c * x + x**2)
|
|
76
|
-
return - a * (c**2 - 1) * np.arctanh((x - a * c) / dx) - c * dx
|
|
77
|
-
|
|
78
|
-
expected_shade = 0.5 * (f_x * cosd(surface_tilt)
|
|
79
|
-
- analytic(gcr, surface_tilt, 1 - f_x)
|
|
80
|
-
+ analytic(gcr, surface_tilt, 1.))
|
|
81
|
-
expected_noshade = 0.5 * ((1 - f_x) * cosd(surface_tilt)
|
|
82
|
-
+ analytic(gcr, surface_tilt, 1. - f_x)
|
|
83
|
-
- analytic(gcr, surface_tilt, 0.))
|
|
84
|
-
shaded = np.array(shaded)
|
|
85
|
-
noshade = np.array(noshade)
|
|
86
|
-
assert np.allclose(shaded, expected_shade)
|
|
87
|
-
assert np.allclose(noshade, expected_noshade)
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
def test__poa_sky_diffuse_pv():
|
|
91
|
-
dhi = np.array([np.nan, 0.0, 500.])
|
|
92
|
-
f_x = np.array([0.2, 0.2, 0.5])
|
|
93
|
-
vf_shade_sky_integ = np.array([1.0, 0.5, 0.2])
|
|
94
|
-
vf_noshade_sky_integ = np.array([0.0, 0.5, 0.8])
|
|
95
|
-
poa = infinite_sheds._poa_sky_diffuse_pv(
|
|
96
|
-
f_x, dhi, vf_shade_sky_integ, vf_noshade_sky_integ)
|
|
97
|
-
expected_poa = np.array([np.nan, 0.0, 500 * (0.5 * 0.2 + 0.5 * 0.8)])
|
|
98
|
-
assert np.allclose(poa, expected_poa, equal_nan=True)
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
def test__ground_angle(test_system):
|
|
102
|
-
ts, _, _ = test_system
|
|
103
|
-
x = np.array([0., 0.5, 1.0])
|
|
104
|
-
angles = infinite_sheds._ground_angle(
|
|
105
|
-
x, ts['surface_tilt'], ts['gcr'])
|
|
106
|
-
expected_angles = np.array([0., 5.866738789543952, 9.896090638982903])
|
|
107
|
-
assert np.allclose(angles, expected_angles)
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
def test__ground_angle_zero_gcr():
|
|
111
|
-
surface_tilt = 30.0
|
|
112
|
-
x = np.array([0.0, 0.5, 1.0])
|
|
113
|
-
angles = infinite_sheds._ground_angle(x, surface_tilt, 0)
|
|
114
|
-
expected_angles = np.array([0, 0, 0])
|
|
115
|
-
assert np.allclose(angles, expected_angles)
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
def test__vf_row_ground(test_system):
|
|
119
|
-
ts, _, _ = test_system
|
|
120
|
-
x = np.array([0., 0.5, 1.0])
|
|
121
|
-
sqr3 = np.sqrt(3)
|
|
122
|
-
vfs = infinite_sheds._vf_row_ground(
|
|
123
|
-
x, ts['surface_tilt'], ts['gcr'])
|
|
124
|
-
expected_vfs = np.array([
|
|
125
|
-
0.5 * (1. - sqr3 / 2),
|
|
126
|
-
0.5 * ((4 + sqr3 / 2) / np.sqrt(17 + 4 * sqr3) - sqr3 / 2),
|
|
127
|
-
0.5 * ((4 + sqr3) / np.sqrt(20 + 8 * sqr3) - sqr3 / 2)])
|
|
128
|
-
assert np.allclose(vfs, expected_vfs)
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
def test__vf_row_ground_integ(test_system):
|
|
132
|
-
ts, _, _ = test_system
|
|
133
|
-
gcr = ts['gcr']
|
|
134
|
-
surface_tilt = ts['surface_tilt']
|
|
135
|
-
f_x = np.array([0., 0.5, 1.0])
|
|
136
|
-
shaded, noshade = infinite_sheds._vf_row_ground_integ(
|
|
137
|
-
f_x, surface_tilt, gcr)
|
|
138
|
-
|
|
139
|
-
def analytic(x, surface_tilt, gcr):
|
|
140
|
-
c = cosd(surface_tilt)
|
|
141
|
-
a = 1. / gcr
|
|
142
|
-
dx = np.sqrt(a**2 + 2 * a * c * x + x**2)
|
|
143
|
-
return c * dx - a * (c**2 - 1) * np.arctanh((a * c + x) / dx)
|
|
144
|
-
|
|
145
|
-
expected_shade = 0.5 * (analytic(f_x, surface_tilt, gcr)
|
|
146
|
-
- analytic(0., surface_tilt, gcr)
|
|
147
|
-
- f_x * cosd(surface_tilt))
|
|
148
|
-
expected_noshade = 0.5 * (analytic(1., surface_tilt, gcr)
|
|
149
|
-
- analytic(f_x, surface_tilt, gcr)
|
|
150
|
-
- (1. - f_x) * cosd(surface_tilt))
|
|
151
|
-
assert np.allclose(shaded, expected_shade)
|
|
152
|
-
assert np.allclose(noshade, expected_noshade)
|
|
153
|
-
|
|
154
|
-
|
|
155
44
|
def test__poa_ground_shadows():
|
|
156
45
|
poa_ground, f_gnd_beam, df, vf_gnd_sky = (300., 0.5, 0.5, 0.2)
|
|
157
46
|
result = infinite_sheds._poa_ground_shadows(
|
|
@@ -4,6 +4,8 @@ test bifical.utils
|
|
|
4
4
|
import numpy as np
|
|
5
5
|
import pytest
|
|
6
6
|
from pvlib.bifacial import utils
|
|
7
|
+
from pvlib.shading import masking_angle, ground_angle
|
|
8
|
+
from pvlib.tools import cosd
|
|
7
9
|
|
|
8
10
|
|
|
9
11
|
@pytest.fixture
|
|
@@ -79,10 +81,105 @@ def test__unshaded_ground_fraction(
|
|
|
79
81
|
def test__vf_ground_sky_2d(test_system_fixed_tilt):
|
|
80
82
|
# vector input
|
|
81
83
|
ts, pts, vfs_gnd_sky = test_system_fixed_tilt
|
|
82
|
-
vfs = utils.
|
|
83
|
-
|
|
84
|
+
vfs = utils.vf_ground_sky_2d(ts['rotation'], ts['gcr'], pts,
|
|
85
|
+
ts['pitch'], ts['height'], max_rows=1)
|
|
84
86
|
assert np.allclose(vfs, vfs_gnd_sky, rtol=0.1) # middle point vf is off
|
|
85
87
|
# test with singleton x
|
|
86
|
-
vf = utils.
|
|
87
|
-
|
|
88
|
+
vf = utils.vf_ground_sky_2d(ts['rotation'], ts['gcr'], pts[0],
|
|
89
|
+
ts['pitch'], ts['height'], max_rows=1)
|
|
88
90
|
assert np.isclose(vf, vfs_gnd_sky[0])
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@pytest.mark.parametrize("vectorize", [True, False])
|
|
94
|
+
def test_vf_ground_sky_2d_integ(test_system_fixed_tilt, vectorize):
|
|
95
|
+
ts, pts, vfs_gnd_sky = test_system_fixed_tilt
|
|
96
|
+
# pass rotation here since max_rows=1 for the hand-solved case in
|
|
97
|
+
# the fixture test_system, which means the ground-to-sky view factor
|
|
98
|
+
# isn't summed over enough rows for symmetry to hold.
|
|
99
|
+
vf_integ = utils.vf_ground_sky_2d_integ(
|
|
100
|
+
ts['rotation'], ts['gcr'], ts['height'], ts['pitch'],
|
|
101
|
+
max_rows=1, npoints=3, vectorize=vectorize)
|
|
102
|
+
expected_vf_integ = np.trapz(vfs_gnd_sky, pts, axis=0)
|
|
103
|
+
assert np.isclose(vf_integ, expected_vf_integ, rtol=0.1)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def test_vf_row_sky_2d(test_system_fixed_tilt):
|
|
107
|
+
ts, _, _ = test_system_fixed_tilt
|
|
108
|
+
# with float input, fx at top of row
|
|
109
|
+
vf = utils.vf_row_sky_2d(ts['surface_tilt'], ts['gcr'], 1.)
|
|
110
|
+
expected = 0.5 * (1 + cosd(ts['surface_tilt']))
|
|
111
|
+
assert np.isclose(vf, expected)
|
|
112
|
+
# with array input
|
|
113
|
+
fx = np.array([0., 0.5, 1.])
|
|
114
|
+
vf = utils.vf_row_sky_2d(ts['surface_tilt'], ts['gcr'], fx)
|
|
115
|
+
phi = masking_angle(ts['surface_tilt'], ts['gcr'], fx)
|
|
116
|
+
expected = 0.5 * (1 + cosd(ts['surface_tilt'] + phi))
|
|
117
|
+
assert np.allclose(vf, expected)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def test_vf_row_sky_2d_integ(test_system_fixed_tilt):
|
|
121
|
+
ts, _, _ = test_system_fixed_tilt
|
|
122
|
+
# with float input, check end position
|
|
123
|
+
vf = utils.vf_row_sky_2d_integ(ts['surface_tilt'], ts['gcr'], 1., 1.)
|
|
124
|
+
expected = utils.vf_row_sky_2d(ts['surface_tilt'], ts['gcr'], 1.)
|
|
125
|
+
assert np.isclose(vf, expected)
|
|
126
|
+
# with array input
|
|
127
|
+
fx0 = np.array([0., 0.5])
|
|
128
|
+
fx1 = np.array([0., 0.8])
|
|
129
|
+
vf = utils.vf_row_sky_2d_integ(ts['surface_tilt'], ts['gcr'], fx0, fx1)
|
|
130
|
+
phi = masking_angle(ts['surface_tilt'], ts['gcr'], fx0[0])
|
|
131
|
+
y0 = 0.5 * (1 + cosd(ts['surface_tilt'] + phi))
|
|
132
|
+
x = np.arange(fx0[1], fx1[1], 1e-4)
|
|
133
|
+
phi_y = masking_angle(ts['surface_tilt'], ts['gcr'], x)
|
|
134
|
+
y = 0.5 * (1 + cosd(ts['surface_tilt'] + phi_y))
|
|
135
|
+
y1 = np.trapz(y, x) / (fx1[1] - fx0[1])
|
|
136
|
+
expected = np.array([y0, y1])
|
|
137
|
+
assert np.allclose(vf, expected, rtol=1e-3)
|
|
138
|
+
# with defaults (0, 1)
|
|
139
|
+
vf = utils.vf_row_sky_2d_integ(ts['surface_tilt'], ts['gcr'])
|
|
140
|
+
x = np.arange(0, 1, 1e-4)
|
|
141
|
+
phi_y = masking_angle(ts['surface_tilt'], ts['gcr'], x)
|
|
142
|
+
y = 0.5 * (1 + cosd(ts['surface_tilt'] + phi_y))
|
|
143
|
+
y1 = np.trapz(y, x) / (1 - 0)
|
|
144
|
+
assert np.allclose(vf, y1, rtol=1e-3)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def test_vf_row_ground_2d(test_system_fixed_tilt):
|
|
148
|
+
ts, _, _ = test_system_fixed_tilt
|
|
149
|
+
# with float input, fx at bottom of row
|
|
150
|
+
vf = utils.vf_row_ground_2d(ts['surface_tilt'], ts['gcr'], 0.)
|
|
151
|
+
expected = 0.5 * (1. - cosd(ts['surface_tilt']))
|
|
152
|
+
assert np.isclose(vf, expected)
|
|
153
|
+
# with array input
|
|
154
|
+
fx = np.array([0., 0.5, 1.0])
|
|
155
|
+
vf = utils.vf_row_ground_2d(ts['surface_tilt'], ts['gcr'], fx)
|
|
156
|
+
phi = ground_angle(ts['surface_tilt'], ts['gcr'], fx)
|
|
157
|
+
expected = 0.5 * (1 - cosd(phi - ts['surface_tilt']))
|
|
158
|
+
assert np.allclose(vf, expected)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def test_vf_ground_2d_integ(test_system_fixed_tilt):
|
|
162
|
+
ts, _, _ = test_system_fixed_tilt
|
|
163
|
+
# with float input, check end position
|
|
164
|
+
vf = utils.vf_row_ground_2d_integ(ts['surface_tilt'], ts['gcr'], 0., 0.)
|
|
165
|
+
expected = utils.vf_row_ground_2d(ts['surface_tilt'], ts['gcr'], 0.)
|
|
166
|
+
assert np.isclose(vf, expected)
|
|
167
|
+
# with array input
|
|
168
|
+
fx0 = np.array([0., 0.5])
|
|
169
|
+
fx1 = np.array([0., 0.8])
|
|
170
|
+
vf = utils.vf_row_ground_2d_integ(ts['surface_tilt'], ts['gcr'], fx0, fx1)
|
|
171
|
+
phi = ground_angle(ts['surface_tilt'], ts['gcr'], fx0[0])
|
|
172
|
+
y0 = 0.5 * (1 - cosd(phi - ts['surface_tilt']))
|
|
173
|
+
x = np.arange(fx0[1], fx1[1], 1e-4)
|
|
174
|
+
phi_y = ground_angle(ts['surface_tilt'], ts['gcr'], x)
|
|
175
|
+
y = 0.5 * (1 - cosd(phi_y - ts['surface_tilt']))
|
|
176
|
+
y1 = np.trapz(y, x) / (fx1[1] - fx0[1])
|
|
177
|
+
expected = np.array([y0, y1])
|
|
178
|
+
assert np.allclose(vf, expected, rtol=1e-2)
|
|
179
|
+
# with defaults (0, 1)
|
|
180
|
+
vf = utils.vf_row_ground_2d_integ(ts['surface_tilt'], ts['gcr'], 0, 1)
|
|
181
|
+
x = np.arange(0, 1, 1e-4)
|
|
182
|
+
phi_y = ground_angle(ts['surface_tilt'], ts['gcr'], x)
|
|
183
|
+
y = 0.5 * (1 - cosd(phi_y - ts['surface_tilt']))
|
|
184
|
+
y1 = np.trapz(y, x) / (1 - 0)
|
|
185
|
+
assert np.allclose(vf, y1, rtol=1e-2)
|
pvlib/tests/conftest.py
CHANGED
|
@@ -141,23 +141,6 @@ def has_numba():
|
|
|
141
141
|
|
|
142
142
|
requires_numba = pytest.mark.skipif(not has_numba(), reason="requires numba")
|
|
143
143
|
|
|
144
|
-
try:
|
|
145
|
-
import siphon
|
|
146
|
-
has_siphon = True
|
|
147
|
-
except ImportError:
|
|
148
|
-
has_siphon = False
|
|
149
|
-
|
|
150
|
-
requires_siphon = pytest.mark.skipif(not has_siphon,
|
|
151
|
-
reason='requires siphon')
|
|
152
|
-
|
|
153
|
-
try:
|
|
154
|
-
import netCDF4 # noqa: F401
|
|
155
|
-
has_netCDF4 = True
|
|
156
|
-
except ImportError:
|
|
157
|
-
has_netCDF4 = False
|
|
158
|
-
|
|
159
|
-
requires_netCDF4 = pytest.mark.skipif(not has_netCDF4,
|
|
160
|
-
reason='requires netCDF4')
|
|
161
144
|
|
|
162
145
|
try:
|
|
163
146
|
import pvfactors # noqa: F401
|
|
@@ -178,20 +161,6 @@ except ImportError:
|
|
|
178
161
|
requires_pysam = pytest.mark.skipif(not has_pysam, reason="requires PySAM")
|
|
179
162
|
|
|
180
163
|
|
|
181
|
-
try:
|
|
182
|
-
import cftime # noqa: F401
|
|
183
|
-
|
|
184
|
-
has_recent_cftime = parse_version(cftime.__version__) > parse_version(
|
|
185
|
-
"1.1.0"
|
|
186
|
-
)
|
|
187
|
-
except ImportError:
|
|
188
|
-
has_recent_cftime = False
|
|
189
|
-
|
|
190
|
-
requires_recent_cftime = pytest.mark.skipif(
|
|
191
|
-
not has_recent_cftime, reason="requires cftime > 1.1.0"
|
|
192
|
-
)
|
|
193
|
-
|
|
194
|
-
|
|
195
164
|
@pytest.fixture()
|
|
196
165
|
def golden():
|
|
197
166
|
return Location(39.742476, -105.1786, 'America/Denver', 1830.14)
|