Skip to content

Commit e77c678

Browse files
committed
refactor ineichen. good riddance
1 parent f773e2d commit e77c678

File tree

3 files changed

+109
-152
lines changed

3 files changed

+109
-152
lines changed

pvlib/clearsky.py

+67-123
Original file line numberDiff line numberDiff line change
@@ -20,69 +20,45 @@
2020
from pvlib import solarposition
2121

2222

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):
23+
def ineichen(apparent_zenith, airmass_absolute, linke_turbidity,
24+
dni_extra=1364., altitude=0):
2725
'''
28-
Determine clear sky GHI, DNI, and DHI from Ineichen/Perez model
26+
Determine clear sky GHI, DNI, and DHI from Ineichen/Perez model.
2927
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].
28+
Implements the Ineichen and Perez clear sky model for global
29+
horizontal irradiance (GHI), direct normal irradiance (DNI), and
30+
calculates the clear-sky diffuse horizontal (DHI) component as the
31+
difference between GHI and DNI*cos(zenith) as presented in [1, 2]. A
32+
report on clear sky models found the Ineichen/Perez model to have
33+
excellent performance with a minimal input data set [3].
3634
3735
Default values for montly Linke turbidity provided by SoDa [4, 5].
3836
3937
Parameters
4038
-----------
41-
time : pandas.DatetimeIndex
42-
43-
latitude : float
44-
45-
longitude : float
39+
apparent_zenith: numeric
4640
47-
altitude : float
41+
airmass_absolute: numeric
4842
49-
linke_turbidity : None or float
50-
If None, uses ``LinkeTurbidities.mat`` lookup table.
43+
linke_turbidity: numeric
5144
52-
solarposition_method : string
53-
Sets the solar position algorithm.
54-
See solarposition.get_solarposition()
55-
56-
zenith_data : None or Series
57-
If None, ephemeris data will be calculated using ``solarposition_method``.
58-
59-
airmass_model : string
60-
See pvlib.airmass.relativeairmass().
45+
dni_extra: numeric
46+
Extraterrestrial irradiance. The units of ``dni_extra``
47+
determine the units of the output.
6148
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.
49+
altitude: numeric
6950
7051
Returns
7152
--------
72-
DataFrame with the following columns: ``ghi, dni, dhi``.
73-
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.
53+
clearsky : DataFrame (if Series input) or OrderedDict of arrays
54+
DataFrame/OrderedDict contains the columns/keys
55+
``'dhi', 'dni', 'ghi'``.
8056
8157
References
8258
----------
83-
8459
[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.
60+
the Linke turbidity coefficient", Solar Energy, vol 73, pp. 151-157,
61+
2002.
8662
8763
[2] R. Perez et. al., "A New Operational Model for Satellite-Derived
8864
Irradiances: Description and Validation", Solar Energy, vol 73, pp.
@@ -98,97 +74,66 @@ def ineichen(time, latitude, longitude, altitude=0, linke_turbidity=None,
9874
[5] J. Remund, et. al., "Worldwide Linke Turbidity Information", Proc.
9975
ISES Solar World Congress, June 2003. Goteborg, Sweden.
10076
'''
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-
12377

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
78+
# Dan's note on the TL correction: By my reading of the publication
79+
# on pages 151-157, Ineichen and Perez introduce (among other
80+
# things) three things. 1) Beam model in eqn. 8, 2) new turbidity
81+
# factor in eqn 9 and appendix A, and 3) Global horizontal model in
82+
# eqn. 11. They do NOT appear to use the new turbidity factor (item
83+
# 2 above) in either the beam or GHI models. The phrasing of
84+
# appendix A seems as if there are two separate corrections, the
85+
# first correction is used to correct the beam/GHI models, and the
86+
# second correction is used to correct the revised turibidity
87+
# factor. In my estimation, there is no need to correct the
88+
# turbidity factor used in the beam/GHI models.
89+
90+
# Create the corrected TL for TL < 2
91+
# TLcorr = TL;
92+
# TLcorr(TL < 2) = TLcorr(TL < 2) - 0.25 .* (2-TLcorr(TL < 2)) .^ (0.5);
93+
94+
# This equation is found in Solar Energy 73, pg 311. Full ref: Perez
95+
# et. al., Vol. 73, pp. 307-317 (2002). It is slightly different
96+
# than the equation given in Solar Energy 73, pg 156. We used the
97+
# equation from pg 311 because of the existence of known typos in
98+
# the pg 156 publication (notably the fh2-(TL-1) should be fh2 *
99+
# (TL-1)).
129100

130-
# Get the absolute airmass assuming standard local pressure (per
131-
# alt2pres) using Kasten and Young's 1989 formula for airmass.
101+
cos_zenith = tools.cosd(apparent_zenith)
132102

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
103+
tl = linke_turbidity
138104

139105
fh1 = np.exp(-altitude/8000.)
140106
fh2 = np.exp(-altitude/1250.)
141107
cg1 = 5.09e-05 * altitude + 0.868
142108
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
172109

173-
# BncI == "normal beam clear sky radiation"
110+
ghi = (cg1 * dni_extra * cos_zenith *
111+
np.exp(-cg2*airmass_absolute*(fh1 + fh2*(tl - 1))) *
112+
np.exp(0.01*airmass_absolute**1.8))
113+
ghi = np.maximum(ghi, 0)
114+
115+
# BncI = "normal beam clear sky radiation"
174116
b = 0.664 + 0.163/fh1
175-
BncI = b * I0 * np.exp( -0.09 * AMabsolute * (TL - 1) )
176-
logger.debug('b=%s', b)
117+
BncI = b * dni_extra * np.exp(-0.09 * airmass_absolute * (tl - 1))
177118

178119
# "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 )
120+
BncI_2 = (ghi *
121+
(1 - (0.1 - 0.2*np.exp(-tl))/(0.1 + 0.882/fh1)) /
122+
cos_zenith)
182123

183-
clearsky_DNI = np.minimum(BncI, BncI_2)
124+
dni = np.minimum(BncI, BncI_2)
184125

185-
clearsky_DHI = clearsky_GHI - clearsky_DNI*cos_zenith
126+
dhi = ghi - dni*cos_zenith
186127

187-
df_out = pd.DataFrame({'ghi':clearsky_GHI, 'dni':clearsky_DNI,
188-
'dhi':clearsky_DHI})
189-
df_out.fillna(0, inplace=True)
128+
irrads = OrderedDict()
129+
irrads['ghi'] = ghi
130+
irrads['dni'] = dni
131+
irrads['dhi'] = dhi
190132

191-
return df_out
133+
if isinstance(dni, pd.Series):
134+
irrads = pd.DataFrame.from_dict(irrads)
135+
136+
return irrads
192137

193138

194139
def lookup_linke_turbidity(time, latitude, longitude, filepath=None,
@@ -360,16 +305,15 @@ def simplified_solis(apparent_elevation, aod700=0.1, precipitable_water=1.,
360305
or 101325 and 41000 Pascals.
361306
362307
dni_extra: numeric
363-
Extraterrestrial irradiance.
308+
Extraterrestrial irradiance. The units of ``dni_extra``
309+
determine the units of the output.
364310
365311
Returns
366312
--------
367313
clearsky : DataFrame (if Series input) or OrderedDict of arrays
368314
DataFrame/OrderedDict contains the columns/keys
369315
``'dhi', 'dni', 'ghi'``.
370316
371-
The units of ``dni_extra`` determine the units of the output.
372-
373317
References
374318
----------
375319
.. [1] P. Ineichen, "A broadband simplified version of the

pvlib/location.py

+39-27
Original file line numberDiff line numberDiff line change
@@ -167,62 +167,74 @@ def get_solarposition(self, times, pressure=None, temperature=12,
167167
**kwargs)
168168

169169

170-
def get_clearsky(self, times, model='ineichen', **kwargs):
170+
def get_clearsky(self, times, model='ineichen', solar_position=None,
171+
dni_extra=None, **kwargs):
171172
"""
172173
Calculate the clear sky estimates of GHI, DNI, and/or DHI
173174
at this location.
174175
175176
Parameters
176177
----------
177-
times : DatetimeIndex
178-
179-
model : str
178+
times: DatetimeIndex
179+
model: str
180180
The clear sky model to use. Must be one of
181181
'ineichen', 'haurwitz', 'simplified_solis'.
182+
solar_position : None or DataFrame
183+
DataFrame with with columns 'apparent_zenith', 'zenith',
184+
'apparent_elevation'.
185+
dni_extra: None or numeric
186+
If None, will be calculated from times.
182187
183188
kwargs passed to the relevant functions. Climatological values
184-
are assumed in many cases. See code for details.
189+
are assumed in many cases. See source code for details!
185190
186191
Returns
187192
-------
188193
clearsky : DataFrame
189194
Column names are: ``ghi, dni, dhi``.
190195
"""
196+
if dni_extra is None:
197+
dni_extra = irradiance.extraradiation(times.dayofyear)
191198

192-
if model == 'ineichen':
193-
cs = clearsky.ineichen(times, latitude=self.latitude,
194-
longitude=self.longitude,
195-
altitude=self.altitude,
196-
**kwargs)
197-
elif model == 'haurwitz':
198-
solpos = self.get_solarposition(times, **kwargs)
199-
cs = clearsky.haurwitz(solpos['apparent_zenith'])
200-
elif model == 'simplified_solis':
199+
try:
200+
pressure = kwargs.pop('pressure')
201+
except KeyError:
202+
pressure = atmosphere.alt2pres(self.altitude)
201203

202-
# these try/excepts define default values that are only
203-
# evaluated if necessary. ineichen does some of this internally
204-
try:
205-
dni_extra = kwargs.pop('dni_extra')
206-
except KeyError:
207-
dni_extra = irradiance.extraradiation(times.dayofyear)
204+
if solar_position is None:
205+
solar_position = self.get_solarposition(times, pressure=pressure,
206+
**kwargs)
207+
208+
apparent_zenith = solar_position['apparent_zenith']
209+
apparent_elevation = solar_position['apparent_elevation']
208210

211+
if model == 'ineichen':
209212
try:
210-
pressure = kwargs.pop('pressure')
213+
linke_turbidity = kwargs.pop('linke_turbidity')
211214
except KeyError:
212-
pressure = atmosphere.alt2pres(self.altitude)
215+
interp_turbidity = kwargs.pop('interp_turbidity', True)
216+
linke_turbidity = clearsky.lookup_linke_turbidity(
217+
times, self.latitude, self.longitude,
218+
interp_turbidity=interp_turbidity)
213219

214220
try:
215-
apparent_elevation = kwargs.pop('apparent_elevation')
221+
airmass_absolute = kwargs.pop('airmass_absolute')
216222
except KeyError:
217-
solpos = self.get_solarposition(
218-
times, pressure=pressure, **kwargs)
219-
apparent_elevation = solpos['apparent_elevation']
223+
airmass_absolute = self.get_airmass(
224+
times, solar_position=solar_position)['airmass_absolute']
220225

226+
cs = clearsky.ineichen(apparent_zenith, airmass_absolute,
227+
linke_turbidity, dni_extra=dni_extra,
228+
altitude=self.altitude)
229+
elif model == 'haurwitz':
230+
cs = clearsky.haurwitz(apparent_zenith)
231+
elif model == 'simplified_solis':
221232
cs = clearsky.simplified_solis(
222233
apparent_elevation, pressure=pressure, dni_extra=dni_extra,
223234
**kwargs)
224235
else:
225-
raise ValueError('{} is not a valid clear sky model'
236+
raise ValueError(('{} is not a valid clear sky model. Must be ' +
237+
'one of ineichen, simplified_solis, haurwitz')
226238
.format(model))
227239

228240
return cs

pvlib/test/test_location.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -102,9 +102,10 @@ def test_get_clearsky_simplified_solis_apparent_elevation():
102102
times = pd.DatetimeIndex(start='20160101T0600-0700',
103103
end='20160101T1800-0700',
104104
freq='3H')
105-
apparent_elevation = pd.Series(80, index=times)
105+
solar_position = {'apparent_elevation': pd.Series(80, index=times),
106+
'apparent_zenith': pd.Series(10, index=times)}
106107
clearsky = tus.get_clearsky(times, model='simplified_solis',
107-
apparent_elevation=apparent_elevation)
108+
solar_position=solar_position)
108109
expected = pd.DataFrame(data=np.
109110
array([[ 131.3124497 , 1001.14754036, 1108.14147919],
110111
[ 131.3124497 , 1001.14754036, 1108.14147919],

0 commit comments

Comments
 (0)