10
10
11
11
import numpy as np
12
12
import pandas as pd
13
+ from scipy .interpolate import splev
13
14
14
15
from pvlib import atmosphere , solarposition , tools
15
16
import pvlib # used to avoid dni name collision in complete_irradiance
@@ -323,6 +324,7 @@ def get_total_irradiance(surface_tilt, surface_azimuth,
323
324
* reindl
324
325
* king
325
326
* perez
327
+ * perez-driesse
326
328
327
329
Parameters
328
330
----------
@@ -351,7 +353,8 @@ def get_total_irradiance(surface_tilt, surface_azimuth,
351
353
the list of accepted values.
352
354
model : str, default 'isotropic'
353
355
Irradiance model. Can be one of ``'isotropic'``, ``'klucher'``,
354
- ``'haydavies'``, ``'reindl'``, ``'king'``, ``'perez'``.
356
+ ``'haydavies'``, ``'reindl'``, ``'king'``, ``'perez'``,
357
+ ``'perez-driesse'``.
355
358
model_perez : str, default 'allsitescomposite1990'
356
359
Used only if ``model='perez'``. See :py:func:`~pvlib.irradiance.perez`.
357
360
@@ -363,13 +366,13 @@ def get_total_irradiance(surface_tilt, surface_azimuth,
363
366
364
367
Notes
365
368
-----
366
- Models ``'haydavies'``, ``'reindl'``, or ``'perez'`` require
367
- ``'dni_extra'``. Values can be calculated using
369
+ Models ``'haydavies'``, ``'reindl'``, ``'perez'`` and ``'perez-driesse'``
370
+ require ``'dni_extra'``. Values can be calculated using
368
371
:py:func:`~pvlib.irradiance.get_extra_radiation`.
369
372
370
- The ``'perez'`` model requires relative airmass (``airmass``) as input. If
371
- ``airmass`` is not provided, it is calculated using the defaults in
372
- :py:func:`~pvlib.atmosphere.get_relative_airmass`.
373
+ The ``'perez'`` and ``'perez-driesse'`` models require relative airmass
374
+ ( ``airmass``) as input. If ``airmass`` is not provided, it is calculated
375
+ using the defaults in :py:func:`~pvlib.atmosphere.get_relative_airmass`.
373
376
"""
374
377
375
378
poa_sky_diffuse = get_sky_diffuse (
@@ -400,6 +403,7 @@ def get_sky_diffuse(surface_tilt, surface_azimuth,
400
403
* reindl
401
404
* king
402
405
* perez
406
+ * perez-driesse
403
407
404
408
Parameters
405
409
----------
@@ -423,7 +427,8 @@ def get_sky_diffuse(surface_tilt, surface_azimuth,
423
427
Relative airmass (not adjusted for pressure). [unitless]
424
428
model : str, default 'isotropic'
425
429
Irradiance model. Can be one of ``'isotropic'``, ``'klucher'``,
426
- ``'haydavies'``, ``'reindl'``, ``'king'``, ``'perez'``.
430
+ ``'haydavies'``, ``'reindl'``, ``'king'``, ``'perez'``,
431
+ ``'perez-driesse'``.
427
432
model_perez : str, default 'allsitescomposite1990'
428
433
Used only if ``model='perez'``. See :py:func:`~pvlib.irradiance.perez`.
429
434
@@ -440,18 +445,19 @@ def get_sky_diffuse(surface_tilt, surface_azimuth,
440
445
441
446
Notes
442
447
-----
443
- Models ``'haydavies'``, ``'reindl'``, and ``'perez``` require 'dni_extra'.
444
- Values can be calculated using
448
+ Models ``'haydavies'``, ``'reindl'``, ``'perez'`` and ``'perez-driesse'``
449
+ require ``'dni_extra'``. Values can be calculated using
445
450
:py:func:`~pvlib.irradiance.get_extra_radiation`.
446
451
447
- The ``'perez'`` model requires relative airmass (``airmass``) as input. If
448
- ``airmass`` is not provided, it is calculated using the defaults in
449
- :py:func:`~pvlib.atmosphere.get_relative_airmass`.
452
+ The ``'perez'`` and ``'perez-driesse'`` models require relative airmass
453
+ ( ``airmass``) as input. If ``airmass`` is not provided, it is calculated
454
+ using the defaults in :py:func:`~pvlib.atmosphere.get_relative_airmass`.
450
455
"""
451
456
452
457
model = model .lower ()
453
458
454
- if (model in {'haydavies' , 'reindl' , 'perez' }) and (dni_extra is None ):
459
+ if dni_extra is None and model in {'haydavies' , 'reindl' ,
460
+ 'perez' , 'perez-driesse' }:
455
461
raise ValueError (f'dni_extra is required for model { model } ' )
456
462
457
463
if model == 'isotropic' :
@@ -473,6 +479,10 @@ def get_sky_diffuse(surface_tilt, surface_azimuth,
473
479
sky = perez (surface_tilt , surface_azimuth , dhi , dni , dni_extra ,
474
480
solar_zenith , solar_azimuth , airmass ,
475
481
model = model_perez )
482
+ elif model == 'perez-driesse' :
483
+ # perez_driesse will calculate its own airmass if needed
484
+ sky = perez_driesse (surface_tilt , surface_azimuth , dhi , dni , dni_extra ,
485
+ solar_zenith , solar_azimuth , airmass )
476
486
else :
477
487
raise ValueError (f'invalid model selection { model } ' )
478
488
@@ -1212,6 +1222,228 @@ def perez(surface_tilt, surface_azimuth, dhi, dni, dni_extra,
1212
1222
return sky_diffuse
1213
1223
1214
1224
1225
+ def _calc_delta (dhi , dni_extra , solar_zenith , airmass = None ):
1226
+ '''
1227
+ Compute the delta parameter, which represents sky dome "brightness"
1228
+ in the Perez and Perez-Driesse models.
1229
+
1230
+ Helper function for perez_driesse transposition.
1231
+ '''
1232
+ if airmass is None :
1233
+ # use the same airmass model as in the original perez work
1234
+ airmass = atmosphere .get_relative_airmass (solar_zenith ,
1235
+ 'kastenyoung1989' )
1236
+
1237
+ max_airmass = atmosphere .get_relative_airmass (90 , 'kastenyoung1989' )
1238
+ airmass = np .where (solar_zenith >= 90 , max_airmass , airmass )
1239
+
1240
+ return dhi / (dni_extra / airmass )
1241
+
1242
+
1243
+ def _calc_zeta (dhi , dni , zenith ):
1244
+ '''
1245
+ Compute the zeta parameter, which represents sky dome "clearness"
1246
+ in the Perez-Driesse model.
1247
+
1248
+ Helper function for perez_driesse transposition.
1249
+ '''
1250
+ dhi = np .asarray (dhi )
1251
+ dni = np .asarray (dni )
1252
+
1253
+ # first calculate what zeta would be without the kappa correction
1254
+ # using eq. 5 and eq. 13
1255
+ with np .errstate (invalid = 'ignore' ):
1256
+ zeta = dni / (dhi + dni )
1257
+
1258
+ zeta = np .where (dhi == 0 , 0.0 , zeta )
1259
+
1260
+ # then apply the kappa correction in a manner analogous to eq. 7
1261
+ kappa = 1.041
1262
+ kterm = kappa * np .radians (zenith ) ** 3
1263
+ zeta = zeta / (1 - kterm * (zeta - 1 ))
1264
+
1265
+ return zeta
1266
+
1267
+
1268
+ def _f (i , j , zeta ):
1269
+ '''
1270
+ Evaluate the quadratic splines corresponding to the
1271
+ allsitescomposite1990 Perez model look-up table.
1272
+
1273
+ Helper function for perez_driesse transposition.
1274
+ '''
1275
+ knots = np .array (
1276
+ [0.000 , 0.000 , 0.000 ,
1277
+ 0.061 , 0.187 , 0.333 , 0.487 , 0.643 , 0.778 , 0.839 ,
1278
+ 1.000 , 1.000 , 1.000 ])
1279
+
1280
+ coefs = np .array (
1281
+ [[- 0.053 , + 0.529 , - 0.028 , - 0.071 , + 0.061 , - 0.019 ],
1282
+ [- 0.008 , + 0.588 , - 0.062 , - 0.060 , + 0.072 , - 0.022 ],
1283
+ [+ 0.131 , + 0.770 , - 0.167 , - 0.026 , + 0.106 , - 0.032 ],
1284
+ [+ 0.328 , + 0.471 , - 0.216 , + 0.069 , - 0.105 , - 0.028 ],
1285
+ [+ 0.557 , + 0.241 , - 0.300 , + 0.086 , - 0.085 , - 0.012 ],
1286
+ [+ 0.861 , - 0.323 , - 0.355 , + 0.240 , - 0.467 , - 0.008 ],
1287
+ [ 1.212 , - 1.239 , - 0.444 , + 0.305 , - 0.797 , + 0.047 ],
1288
+ [ 1.099 , - 1.847 , - 0.365 , + 0.275 , - 1.132 , + 0.124 ],
1289
+ [+ 0.544 , + 0.157 , - 0.213 , + 0.118 , - 1.455 , + 0.292 ],
1290
+ [+ 0.544 , + 0.157 , - 0.213 , + 0.118 , - 1.455 , + 0.292 ],
1291
+ [+ 0.000 , + 0.000 , + 0.000 , + 0.000 , + 0.000 , + 0.000 ],
1292
+ [+ 0.000 , + 0.000 , + 0.000 , + 0.000 , + 0.000 , + 0.000 ],
1293
+ [+ 0.000 , + 0.000 , + 0.000 , + 0.000 , + 0.000 , + 0.000 ]])
1294
+
1295
+ coefs = coefs .T .reshape ((2 , 3 , 13 ))
1296
+
1297
+ tck = (knots , coefs [i - 1 , j - 1 ], 2 )
1298
+
1299
+ return splev (zeta , tck )
1300
+
1301
+
1302
+ def perez_driesse (surface_tilt , surface_azimuth , dhi , dni , dni_extra ,
1303
+ solar_zenith , solar_azimuth , airmass = None ,
1304
+ return_components = False ):
1305
+ '''
1306
+ Determine diffuse irradiance from the sky on a tilted surface using
1307
+ the continuous Perez-Driesse model.
1308
+
1309
+ The Perez-Driesse model [1]_ is a reformulation of the 1990 Perez
1310
+ model [2]_ that provides continuity of the function and of its first
1311
+ derivatives. This is achieved by replacing the look-up table of
1312
+ coefficients with quadratic splines.
1313
+
1314
+ Parameters
1315
+ ----------
1316
+ surface_tilt : numeric
1317
+ Surface tilt angles in decimal degrees. surface_tilt must be >=0
1318
+ and <=180. The tilt angle is defined as degrees from horizontal
1319
+ (e.g. surface facing up = 0, surface facing horizon = 90)
1320
+
1321
+ surface_azimuth : numeric
1322
+ Surface azimuth angles in decimal degrees. surface_azimuth must
1323
+ be >=0 and <=360. The azimuth convention is defined as degrees
1324
+ east of north (e.g. North = 0, South=180 East = 90, West = 270).
1325
+
1326
+ dhi : numeric
1327
+ Diffuse horizontal irradiance in W/m^2. dhi must be >=0.
1328
+
1329
+ dni : numeric
1330
+ Direct normal irradiance in W/m^2. dni must be >=0.
1331
+
1332
+ dni_extra : numeric
1333
+ Extraterrestrial normal irradiance in W/m^2.
1334
+
1335
+ solar_zenith : numeric
1336
+ apparent (refraction-corrected) zenith angles in decimal
1337
+ degrees. solar_zenith must be >=0 and <=180.
1338
+
1339
+ solar_azimuth : numeric
1340
+ Sun azimuth angles in decimal degrees. solar_azimuth must be >=0
1341
+ and <=360. The azimuth convention is defined as degrees east of
1342
+ north (e.g. North = 0, East = 90, West = 270).
1343
+
1344
+ airmass : numeric (optional, default None)
1345
+ Relative (not pressure-corrected) airmass values. If airmass is a
1346
+ DataFrame it must be of the same size as all other DataFrame
1347
+ inputs. The kastenyoung1989 airmass calculation is used internally
1348
+ and is also recommended when pre-calculating airmass because
1349
+ it was used in the original model development.
1350
+
1351
+ return_components: bool (optional, default=False)
1352
+ Flag used to decide whether to return the calculated diffuse components
1353
+ or not.
1354
+
1355
+ Returns
1356
+ --------
1357
+ numeric, OrderedDict, or DataFrame
1358
+ Return type controlled by `return_components` argument.
1359
+ If ``return_components=False``, `sky_diffuse` is returned.
1360
+ If ``return_components=True``, `diffuse_components` is returned.
1361
+
1362
+ sky_diffuse : numeric
1363
+ The sky diffuse component of the solar radiation on a tilted
1364
+ surface.
1365
+
1366
+ diffuse_components : OrderedDict (array input) or DataFrame (Series input)
1367
+ Keys/columns are:
1368
+ * sky_diffuse: Total sky diffuse
1369
+ * isotropic
1370
+ * circumsolar
1371
+ * horizon
1372
+
1373
+ Notes
1374
+ -----
1375
+ The Perez-Driesse model can be considered a plug-in replacement for the
1376
+ 1990 Perez model using the ``'allsitescomposite1990'`` coefficient set.
1377
+ Deviations between the two are very small, as demonstrated in [1]_.
1378
+ Other coefficient sets are not supported because the 1990 set is
1379
+ based on the largest and most diverse set of empirical data.
1380
+
1381
+ References
1382
+ ----------
1383
+ .. [1] A. Driesse, A. Jensen, R. Perez, A Continuous Form of the Perez
1384
+ Diffuse Sky Model for Forward and Reverse Transposition, accepted
1385
+ for publication in the Solar Energy Journal.
1386
+
1387
+ .. [2] Perez, R., Ineichen, P., Seals, R., Michalsky, J., Stewart, R.,
1388
+ 1990. Modeling daylight availability and irradiance components from
1389
+ direct and global irradiance. Solar Energy 44 (5), 271-289.
1390
+
1391
+ See also
1392
+ --------
1393
+ perez
1394
+ isotropic
1395
+ haydavies
1396
+ klucher
1397
+ reindl
1398
+ king
1399
+ '''
1400
+ # Contributed by Anton Driesse (@adriesse), PV Performance Labs. Oct., 2023
1401
+
1402
+ delta = _calc_delta (dhi , dni_extra , solar_zenith , airmass )
1403
+ zeta = _calc_zeta (dhi , dni , solar_zenith )
1404
+
1405
+ z = np .radians (solar_zenith )
1406
+
1407
+ F1 = _f (1 , 1 , zeta ) + _f (1 , 2 , zeta ) * delta + _f (1 , 3 , zeta ) * z
1408
+ F2 = _f (2 , 1 , zeta ) + _f (2 , 2 , zeta ) * delta + _f (2 , 3 , zeta ) * z
1409
+
1410
+ # note the newly recommended upper limit on F1
1411
+ F1 = np .clip (F1 , 0 , 0.9 )
1412
+
1413
+ # lines after this point are identical to the original perez function
1414
+ # with some checks removed
1415
+
1416
+ A = aoi_projection (surface_tilt , surface_azimuth ,
1417
+ solar_zenith , solar_azimuth )
1418
+ A = np .maximum (A , 0 )
1419
+
1420
+ B = tools .cosd (solar_zenith )
1421
+ B = np .maximum (B , tools .cosd (85 ))
1422
+
1423
+ # Calculate Diffuse POA from sky dome
1424
+ term1 = 0.5 * (1 - F1 ) * (1 + tools .cosd (surface_tilt ))
1425
+ term2 = F1 * A / B
1426
+ term3 = F2 * tools .sind (surface_tilt )
1427
+
1428
+ sky_diffuse = np .maximum (dhi * (term1 + term2 + term3 ), 0 )
1429
+
1430
+ if return_components :
1431
+ diffuse_components = OrderedDict ()
1432
+ diffuse_components ['sky_diffuse' ] = sky_diffuse
1433
+
1434
+ # Calculate the different components
1435
+ diffuse_components ['isotropic' ] = dhi * term1
1436
+ diffuse_components ['circumsolar' ] = dhi * term2
1437
+ diffuse_components ['horizon' ] = dhi * term3
1438
+
1439
+ if isinstance (sky_diffuse , pd .Series ):
1440
+ diffuse_components = pd .DataFrame (diffuse_components )
1441
+
1442
+ return diffuse_components
1443
+ else :
1444
+ return sky_diffuse
1445
+
1446
+
1215
1447
def clearsky_index (ghi , clearsky_ghi , max_clearsky_index = 2.0 ):
1216
1448
"""
1217
1449
Calculate the clearsky index.
@@ -2350,6 +2582,8 @@ def erbs_driesse(ghi, zenith, datetime_or_doy=None, dni_extra=None,
2350
2582
orgill_hollands
2351
2583
boland
2352
2584
"""
2585
+ # Contributed by Anton Driesse (@adriesse), PV Performance Labs. Aug., 2023
2586
+
2353
2587
# central polynomial coefficients with float64 precision
2354
2588
p = [+ 12.26911439571261000 ,
2355
2589
- 16.47050842469730700 ,
0 commit comments