Skip to content

Commit 675454f

Browse files
authored
refactor ineichen function (#199)
* refactor ineichen. good riddance * fix nans, fix tests * more cleanup from ineichen refactor * reorder kwargs. pep8 * update docs
1 parent 62cf0ae commit 675454f

File tree

9 files changed

+345
-272
lines changed

9 files changed

+345
-272
lines changed

docs/sphinx/source/package_overview.rst

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ configuration at a handful of sites listed below.
5656
5757
# get the module and inverter specifications from SAM
5858
sandia_modules = pvlib.pvsystem.retrieve_sam('SandiaMod')
59-
sapm_inverters = pvlib.pvsystem.retrieve_sam('sandiainverter')
59+
sapm_inverters = pvlib.pvsystem.retrieve_sam('cecinverter')
6060
module = sandia_modules['Canadian_Solar_CS5P_220M___2009_']
6161
inverter = sapm_inverters['ABB__MICRO_0_25_I_OUTD_US_208_208V__CEC_2014_']
6262
@@ -80,17 +80,19 @@ to accomplish our system modeling goal:
8080
'surface_azimuth': 180}
8181
8282
energies = {}
83-
# localize datetime indices (pvlib>=0.3.0)
83+
8484
for latitude, longitude, name, altitude, timezone in coordinates:
8585
times = naive_times.tz_localize(timezone)
8686
system['surface_tilt'] = latitude
87-
cs = pvlib.clearsky.ineichen(times, latitude, longitude, altitude=altitude)
8887
solpos = pvlib.solarposition.get_solarposition(times, latitude, longitude)
8988
dni_extra = pvlib.irradiance.extraradiation(times)
9089
dni_extra = pd.Series(dni_extra, index=times)
9190
airmass = pvlib.atmosphere.relativeairmass(solpos['apparent_zenith'])
9291
pressure = pvlib.atmosphere.alt2pres(altitude)
9392
am_abs = pvlib.atmosphere.absoluteairmass(airmass, pressure)
93+
tl = pvlib.clearsky.lookup_linke_turbidity(times, latitude, longitude)
94+
cs = pvlib.clearsky.ineichen(solpos['apparent_zenith', am_abs, tl,
95+
dni_extra=dni_extra, altitude=altitude)
9496
aoi = pvlib.irradiance.aoi(system['surface_tilt'], system['surface_azimuth'],
9597
solpos['apparent_zenith'], solpos['azimuth'])
9698
total_irrad = pvlib.irradiance.total_irrad(system['surface_tilt'],

docs/sphinx/source/whatsnew/v0.4.0.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ API Changes
1313

1414
* Remove unneeded module argument from singlediode function. (:issue:`200`)
1515
* In ``pvlib.irradiance.perez``, renamed argument ``modelt`` to ``model``.
16-
(:issue:`196`)
16+
(:issue:`196`)
1717

1818

1919
Enhancements

pvlib/clearsky.py

Lines changed: 105 additions & 146 deletions
Original file line numberDiff line numberDiff line change
@@ -5,84 +5,63 @@
55

66
from __future__ import division
77

8-
import logging
9-
logger = logging.getLogger('pvlib')
10-
118
import os
129
from collections import OrderedDict
1310

1411
import numpy as np
1512
import pandas as pd
1613

1714
from pvlib import tools
18-
from pvlib import irradiance
19-
from pvlib import atmosphere
20-
from pvlib import solarposition
2115

2216

23-
def ineichen(time, latitude, longitude, altitude=0, linke_turbidity=None,
24-
solarposition_method='nrel_numpy', zenith_data=None,
25-
airmass_model='young1994', airmass_data=None,
26-
interp_turbidity=True):
17+
def ineichen(apparent_zenith, airmass_absolute, linke_turbidity,
18+
altitude=0, dni_extra=1364.):
2719
'''
28-
Determine clear sky GHI, DNI, and DHI from Ineichen/Perez model
20+
Determine clear sky GHI, DNI, and DHI from Ineichen/Perez model.
2921
30-
Implements the Ineichen and Perez clear sky model for global horizontal
31-
irradiance (GHI), direct normal irradiance (DNI), and calculates
32-
the clear-sky diffuse horizontal (DHI) component as the difference
33-
between GHI and DNI*cos(zenith) as presented in [1, 2]. A report on clear
34-
sky models found the Ineichen/Perez model to have excellent performance
35-
with a minimal input data set [3].
22+
Implements the Ineichen and Perez clear sky model for global
23+
horizontal irradiance (GHI), direct normal irradiance (DNI), and
24+
calculates the clear-sky diffuse horizontal (DHI) component as the
25+
difference between GHI and DNI*cos(zenith) as presented in [1, 2]. A
26+
report on clear sky models found the Ineichen/Perez model to have
27+
excellent performance with a minimal input data set [3].
3628
37-
Default values for montly Linke turbidity provided by SoDa [4, 5].
29+
Default values for monthly Linke turbidity provided by SoDa [4, 5].
3830
3931
Parameters
4032
-----------
41-
time : pandas.DatetimeIndex
42-
43-
latitude : float
44-
45-
longitude : float
46-
47-
altitude : float
48-
49-
linke_turbidity : None or float
50-
If None, uses ``LinkeTurbidities.mat`` lookup table.
33+
apparent_zenith: numeric
34+
Refraction corrected solar zenith angle in degrees.
5135
52-
solarposition_method : string
53-
Sets the solar position algorithm.
54-
See solarposition.get_solarposition()
36+
airmass_absolute: numeric
37+
Pressure corrected airmass.
5538
56-
zenith_data : None or Series
57-
If None, ephemeris data will be calculated using ``solarposition_method``.
39+
linke_turbidity: numeric
40+
Linke Turbidity.
5841
59-
airmass_model : string
60-
See pvlib.airmass.relativeairmass().
42+
altitude: numeric
43+
Altitude above sea level in meters.
6144
62-
airmass_data : None or Series
63-
If None, absolute air mass data will be calculated using
64-
``airmass_model`` and location.alitude.
65-
66-
interp_turbidity : bool
67-
If ``True``, interpolates the monthly Linke turbidity values
68-
found in ``LinkeTurbidities.mat`` to daily values.
45+
dni_extra: numeric
46+
Extraterrestrial irradiance. The units of ``dni_extra``
47+
determine the units of the output.
6948
7049
Returns
71-
--------
72-
DataFrame with the following columns: ``ghi, dni, dhi``.
50+
-------
51+
clearsky : DataFrame (if Series input) or OrderedDict of arrays
52+
DataFrame/OrderedDict contains the columns/keys
53+
``'dhi', 'dni', 'ghi'``.
7354
74-
Notes
75-
-----
76-
If you are using this function
77-
in a loop, it may be faster to load LinkeTurbidities.mat outside of
78-
the loop and feed it in as a keyword argument, rather than
79-
having the function open and process the file each time it is called.
55+
See also
56+
--------
57+
lookup_linke_turbidity
58+
pvlib.location.Location.get_clearsky
8059
8160
References
8261
----------
83-
8462
[1] P. Ineichen and R. Perez, "A New airmass independent formulation for
85-
the Linke turbidity coefficient", Solar Energy, vol 73, pp. 151-157, 2002.
63+
the Linke turbidity coefficient", Solar Energy, vol 73, pp. 151-157,
64+
2002.
8665
8766
[2] R. Perez et. al., "A New Operational Model for Satellite-Derived
8867
Irradiances: Description and Validation", Solar Energy, vol 73, pp.
@@ -98,97 +77,76 @@ def ineichen(time, latitude, longitude, altitude=0, linke_turbidity=None,
9877
[5] J. Remund, et. al., "Worldwide Linke Turbidity Information", Proc.
9978
ISES Solar World Congress, June 2003. Goteborg, Sweden.
10079
'''
101-
# Initial implementation of this algorithm by Matthew Reno.
102-
# Ported to python by Rob Andrews
103-
# Added functionality by Will Holmgren (@wholmgren)
104-
105-
I0 = irradiance.extraradiation(time.dayofyear)
106-
107-
if zenith_data is None:
108-
ephem_data = solarposition.get_solarposition(time,
109-
latitude=latitude,
110-
longitude=longitude,
111-
altitude=altitude,
112-
method=solarposition_method)
113-
time = ephem_data.index # fixes issue with time possibly not being tz-aware
114-
try:
115-
ApparentZenith = ephem_data['apparent_zenith']
116-
except KeyError:
117-
ApparentZenith = ephem_data['zenith']
118-
logger.warning('could not find apparent_zenith. using zenith')
119-
else:
120-
ApparentZenith = zenith_data
121-
#ApparentZenith[ApparentZenith >= 90] = 90 # can cause problems in edge cases
122-
123-
124-
if linke_turbidity is None:
125-
TL = lookup_linke_turbidity(time, latitude, longitude,
126-
interp_turbidity=interp_turbidity)
127-
else:
128-
TL = linke_turbidity
129-
130-
# Get the absolute airmass assuming standard local pressure (per
131-
# alt2pres) using Kasten and Young's 1989 formula for airmass.
13280

133-
if airmass_data is None:
134-
AMabsolute = atmosphere.absoluteairmass(airmass_relative=atmosphere.relativeairmass(ApparentZenith, airmass_model),
135-
pressure=atmosphere.alt2pres(altitude))
136-
else:
137-
AMabsolute = airmass_data
81+
# Dan's note on the TL correction: By my reading of the publication
82+
# on pages 151-157, Ineichen and Perez introduce (among other
83+
# things) three things. 1) Beam model in eqn. 8, 2) new turbidity
84+
# factor in eqn 9 and appendix A, and 3) Global horizontal model in
85+
# eqn. 11. They do NOT appear to use the new turbidity factor (item
86+
# 2 above) in either the beam or GHI models. The phrasing of
87+
# appendix A seems as if there are two separate corrections, the
88+
# first correction is used to correct the beam/GHI models, and the
89+
# second correction is used to correct the revised turibidity
90+
# factor. In my estimation, there is no need to correct the
91+
# turbidity factor used in the beam/GHI models.
92+
93+
# Create the corrected TL for TL < 2
94+
# TLcorr = TL;
95+
# TLcorr(TL < 2) = TLcorr(TL < 2) - 0.25 .* (2-TLcorr(TL < 2)) .^ (0.5);
96+
97+
# This equation is found in Solar Energy 73, pg 311. Full ref: Perez
98+
# et. al., Vol. 73, pp. 307-317 (2002). It is slightly different
99+
# than the equation given in Solar Energy 73, pg 156. We used the
100+
# equation from pg 311 because of the existence of known typos in
101+
# the pg 156 publication (notably the fh2-(TL-1) should be fh2 *
102+
# (TL-1)).
103+
104+
# The NaN handling is a little subtle. The AM input is likely to
105+
# have NaNs that we'll want to map to 0s in the output. However, we
106+
# want NaNs in other inputs to propagate through to the output. This
107+
# is accomplished by judicious use and placement of np.maximum,
108+
# np.minimum, and np.fmax
109+
110+
# use max so that nighttime values will result in 0s instead of
111+
# negatives. propagates nans.
112+
cos_zenith = np.maximum(tools.cosd(apparent_zenith), 0)
113+
114+
tl = linke_turbidity
138115

139116
fh1 = np.exp(-altitude/8000.)
140117
fh2 = np.exp(-altitude/1250.)
141118
cg1 = 5.09e-05 * altitude + 0.868
142119
cg2 = 3.92e-05 * altitude + 0.0387
143-
logger.debug('fh1=%s, fh2=%s, cg1=%s, cg2=%s', fh1, fh2, cg1, cg2)
144-
145-
# Dan's note on the TL correction: By my reading of the publication on
146-
# pages 151-157, Ineichen and Perez introduce (among other things) three
147-
# things. 1) Beam model in eqn. 8, 2) new turbidity factor in eqn 9 and
148-
# appendix A, and 3) Global horizontal model in eqn. 11. They do NOT appear
149-
# to use the new turbidity factor (item 2 above) in either the beam or GHI
150-
# models. The phrasing of appendix A seems as if there are two separate
151-
# corrections, the first correction is used to correct the beam/GHI models,
152-
# and the second correction is used to correct the revised turibidity
153-
# factor. In my estimation, there is no need to correct the turbidity
154-
# factor used in the beam/GHI models.
155-
156-
# Create the corrected TL for TL < 2
157-
# TLcorr = TL;
158-
# TLcorr(TL < 2) = TLcorr(TL < 2) - 0.25 .* (2-TLcorr(TL < 2)) .^ (0.5);
159-
160-
# This equation is found in Solar Energy 73, pg 311.
161-
# Full ref: Perez et. al., Vol. 73, pp. 307-317 (2002).
162-
# It is slightly different than the equation given in Solar Energy 73, pg 156.
163-
# We used the equation from pg 311 because of the existence of known typos
164-
# in the pg 156 publication (notably the fh2-(TL-1) should be fh2 * (TL-1)).
165-
166-
cos_zenith = tools.cosd(ApparentZenith)
167-
168-
clearsky_GHI = ( cg1 * I0 * cos_zenith *
169-
np.exp(-cg2*AMabsolute*(fh1 + fh2*(TL - 1))) *
170-
np.exp(0.01*AMabsolute**1.8) )
171-
clearsky_GHI[clearsky_GHI < 0] = 0
172-
173-
# BncI == "normal beam clear sky radiation"
120+
121+
ghi = (np.exp(-cg2*airmass_absolute*(fh1 + fh2*(tl - 1))) *
122+
np.exp(0.01*airmass_absolute**1.8))
123+
# use fmax to map airmass nans to 0s. multiply and divide by tl to
124+
# reinsert tl nans
125+
ghi = cg1 * dni_extra * cos_zenith * tl / tl * np.fmax(ghi, 0)
126+
127+
# BncI = "normal beam clear sky radiation"
174128
b = 0.664 + 0.163/fh1
175-
BncI = b * I0 * np.exp( -0.09 * AMabsolute * (TL - 1) )
176-
logger.debug('b=%s', b)
129+
bnci = b * np.exp(-0.09 * airmass_absolute * (tl - 1))
130+
bnci = dni_extra * np.fmax(bnci, 0)
177131

178132
# "empirical correction" SE 73, 157 & SE 73, 312.
179-
BncI_2 = ( clearsky_GHI *
180-
( 1 - (0.1 - 0.2*np.exp(-TL))/(0.1 + 0.882/fh1) ) /
181-
cos_zenith )
133+
bnci_2 = ((1 - (0.1 - 0.2*np.exp(-tl))/(0.1 + 0.882/fh1)) /
134+
cos_zenith)
135+
bnci_2 = ghi * np.fmin(np.fmax(bnci_2, 0), 1e20)
182136

183-
clearsky_DNI = np.minimum(BncI, BncI_2)
137+
dni = np.minimum(bnci, bnci_2)
184138

185-
clearsky_DHI = clearsky_GHI - clearsky_DNI*cos_zenith
139+
dhi = ghi - dni*cos_zenith
140+
141+
irrads = OrderedDict()
142+
irrads['ghi'] = ghi
143+
irrads['dni'] = dni
144+
irrads['dhi'] = dhi
186145

187-
df_out = pd.DataFrame({'ghi':clearsky_GHI, 'dni':clearsky_DNI,
188-
'dhi':clearsky_DHI})
189-
df_out.fillna(0, inplace=True)
146+
if isinstance(dni, pd.Series):
147+
irrads = pd.DataFrame.from_dict(irrads)
190148

191-
return df_out
149+
return irrads
192150

193151

194152
def lookup_linke_turbidity(time, latitude, longitude, filepath=None,
@@ -242,14 +200,17 @@ def lookup_linke_turbidity(time, latitude, longitude, filepath=None,
242200
mat = scipy.io.loadmat(filepath)
243201
linke_turbidity_table = mat['LinkeTurbidity']
244202

245-
latitude_index = np.around(_linearly_scale(latitude, 90, -90, 1, 2160)).astype(np.int64)
246-
longitude_index = np.around(_linearly_scale(longitude, -180, 180, 1, 4320)).astype(np.int64)
203+
latitude_index = (
204+
np.around(_linearly_scale(latitude, 90, -90, 1, 2160))
205+
.astype(np.int64))
206+
longitude_index = (
207+
np.around(_linearly_scale(longitude, -180, 180, 1, 4320))
208+
.astype(np.int64))
247209

248210
g = linke_turbidity_table[latitude_index][longitude_index]
249211

250212
if interp_turbidity:
251-
logger.info('interpolating turbidity to the day')
252-
# Cata covers 1 year.
213+
# Data covers 1 year.
253214
# Assume that data corresponds to the value at
254215
# the middle of each month.
255216
# This means that we need to add previous Dec and next Jan
@@ -262,10 +223,9 @@ def lookup_linke_turbidity(time, latitude, longitude, filepath=None,
262223
linke_turbidity = pd.Series(np.interp(time.dayofyear, days, g2),
263224
index=time)
264225
else:
265-
logger.info('using monthly turbidity')
266-
apply_month = lambda x: g[x[0]-1]
267226
linke_turbidity = pd.DataFrame(time.month, index=time)
268-
linke_turbidity = linke_turbidity.apply(apply_month, axis=1)
227+
# apply monthly data
228+
linke_turbidity = linke_turbidity.apply(lambda x: g[x[0]-1], axis=1)
269229

270230
linke_turbidity /= 20.
271231

@@ -312,11 +272,11 @@ def haurwitz(apparent_zenith):
312272

313273
cos_zenith = tools.cosd(apparent_zenith)
314274

315-
clearsky_GHI = 1098.0 * cos_zenith * np.exp(-0.059/cos_zenith)
275+
clearsky_ghi = 1098.0 * cos_zenith * np.exp(-0.059/cos_zenith)
316276

317-
clearsky_GHI[clearsky_GHI < 0] = 0
277+
clearsky_ghi[clearsky_ghi < 0] = 0
318278

319-
df_out = pd.DataFrame({'ghi':clearsky_GHI})
279+
df_out = pd.DataFrame({'ghi': clearsky_ghi})
320280

321281
return df_out
322282

@@ -326,8 +286,8 @@ def _linearly_scale(inputmatrix, inputmin, inputmax, outputmin, outputmax):
326286

327287
inputrange = inputmax - inputmin
328288
outputrange = outputmax - outputmin
329-
OutputMatrix = (inputmatrix-inputmin) * outputrange/inputrange + outputmin
330-
return OutputMatrix
289+
outputmatrix = (inputmatrix-inputmin) * outputrange/inputrange + outputmin
290+
return outputmatrix
331291

332292

333293
def simplified_solis(apparent_elevation, aod700=0.1, precipitable_water=1.,
@@ -360,16 +320,15 @@ def simplified_solis(apparent_elevation, aod700=0.1, precipitable_water=1.,
360320
or 101325 and 41000 Pascals.
361321
362322
dni_extra: numeric
363-
Extraterrestrial irradiance.
323+
Extraterrestrial irradiance. The units of ``dni_extra``
324+
determine the units of the output.
364325
365326
Returns
366-
--------
327+
-------
367328
clearsky : DataFrame (if Series input) or OrderedDict of arrays
368329
DataFrame/OrderedDict contains the columns/keys
369330
``'dhi', 'dni', 'ghi'``.
370331
371-
The units of ``dni_extra`` determine the units of the output.
372-
373332
References
374333
----------
375334
.. [1] P. Ineichen, "A broadband simplified version of the

0 commit comments

Comments
 (0)