Skip to content

Commit 5a87573

Browse files
authored
Merge pull request #51 from mikofski/pvlib_updates
[Python PVLIB-0.4.0](https://github.com/pvlib/pvlib-python/releases/tag/v0.4.0) updates * new effective irradiance, Linke turbidity lookup and other methods * fix return units bug * needs more testing
2 parents e5c54a9 + 0f55eba commit 5a87573

11 files changed

Lines changed: 209 additions & 107 deletions

File tree

carousel/core/formulas.py

Lines changed: 53 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -127,21 +127,14 @@ def import_formulas(self):
127127
else:
128128
# autodetect formulas assuming first letter is f
129129
formulas = {f: getattr(mod, f) for f in dir(mod) if f[:2] == 'f_'}
130+
if not len(formulas):
131+
for f in dir(mod):
132+
mod_attr = getattr(mod, f)
133+
if inspect.isfunction(mod_attr):
134+
formulas[f] = mod_attr
130135
return formulas
131136

132137

133-
# methods for auto detecting functions in module
134-
# do it using types.FunctionType
135-
# import types
136-
# f = {f: getattr(mod, f)
137-
# for f in mod_attr
138-
# if isinstance(getattr(mod, f), types.FunctionType)}
139-
# do it using inspect.isfunction()
140-
# import inspect
141-
# f = {f: getattr(mod, f)
142-
# for f in mod_attr if inspect.isfunction(getattr(mod, f))}
143-
144-
145138
class NumericalExpressionImporter(FormulaImporter):
146139
"""
147140
Import formulas from numerical expressions using Python Numexpr.
@@ -215,48 +208,57 @@ def __init__(self):
215208
self.units = {}
216209
#: constant arguments that are not included in covariance calculation
217210
self.isconstant = {}
211+
# sequence of formulas, don't propagate uncertainty or units
212+
for f in self.formulas:
213+
self.islinear[f] = True
214+
self.args[f] = inspect.getargspec(self.formulas[f]).args
218215
formula_param = self.parameters.get('formulas') # formulas key
216+
# if formulas is a list or if it can't be iterated as a dictionary
217+
# then log warning and return
219218
try:
220-
# formula dictionary
221-
for k, v in formula_param.iteritems():
222-
if not v:
223-
# skip formula if attributes are null or empty
224-
continue
225-
# get islinear formula attribute
226-
self.islinear[k] = v.get('islinear', True)
227-
# get positional arguments
228-
self.args[k] = v.get('args')
229-
if self.args[k] is None:
230-
# use inspect if args not specified
231-
self.args[k] = inspect.getargspec(self.formulas[k]).args
232-
# get constant arguments to exclude from covariance
233-
self.isconstant[k] = v.get('isconstant')
219+
formula_param_generator = formula_param.iteritems()
220+
except AttributeError as err:
221+
LOGGER.warning('Attribute Error: %s', err.message)
222+
return
223+
# formula dictionary
224+
for k, v in formula_param_generator:
225+
if not v:
226+
# skip formula if attributes are null or empty
227+
continue
228+
# get islinear formula attribute
229+
is_linear = v.get('islinear')
230+
if is_linear is not None:
231+
self.islinear[k] = is_linear
232+
# get positional arguments
233+
f_args = v.get('args')
234+
if f_args is not None:
235+
self.args[k] = f_args
236+
# get constant arguments to exclude from covariance
237+
self.isconstant[k] = v.get('isconstant')
238+
if self.isconstant[k] is not None:
239+
argn = [n for n, a in enumerate(self.args[k]) if a not in
240+
self.isconstant[k]]
241+
LOGGER.debug('%s arg nums: %r', k, argn)
242+
self.formulas[k] = unc_wrapper_args(*argn)(self.formulas[k])
243+
# get units of returns and arguments
244+
self.units[k] = v.get('units')
245+
if self.units[k] is not None:
246+
# append units for covariance and Jacobian if all args
247+
# constant and more than one return output
234248
if self.isconstant[k] is not None:
235-
argn = [n for n, a in enumerate(self.args[k]) if a not in
236-
self.isconstant[k]]
237-
LOGGER.debug('%s arg nums: %r', k, argn)
238-
self.formulas[k] = unc_wrapper_args(*argn)(self.formulas[k])
239-
# get units of returns and arguments
240-
self.units[k] = v.get('units')
241-
if self.units[k] is not None:
242-
# append units for covariance and Jacobian if all args
243-
# constant and more than one return output
244-
if self.isconstant[k] is not None:
245-
if isinstance(self.units[k][0], basestring):
246-
self.units[k][0] = [self.units[k][0]]
247-
try:
248-
self.units[k][0] += [None, None]
249-
except TypeError:
250-
self.units[k][0] += (None, None)
251-
# wrap function with Pint's unit wrapper
252-
self.formulas[k] = UREG.wraps(*self.units[k])(
253-
self.formulas[k]
254-
)
255-
except TypeError:
256-
# sequence of formulas, don't propagate uncertainty or units
257-
for f in self.formulas:
258-
self.islinear[f] = True
259-
self.args[f] = inspect.getargspec(self.formulas[f]).args
249+
# check if retval units is a string or None before adding
250+
# extra units for Jacobian and covariance
251+
ret_units = self.units[k][0]
252+
if isinstance(ret_units, basestring) or ret_units is None:
253+
self.units[k][0] = [ret_units]
254+
try:
255+
self.units[k][0] += [None, None]
256+
except TypeError:
257+
self.units[k][0] += (None, None)
258+
# wrap function with Pint's unit wrapper
259+
self.formulas[k] = UREG.wraps(*self.units[k])(
260+
self.formulas[k]
261+
)
260262

261263
def __getitem__(self, item):
262264
return self.formulas[item]

examples/PVPower/calculations/irradiance.json

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,6 @@
1010
},
1111
"returns": ["timestamps"]
1212
},
13-
{
14-
"formula": "f_clearsky",
15-
"args": {
16-
"data": {
17-
"latitude": "latitude", "longitude": "longitude",
18-
"altitude": "elevation"
19-
},
20-
"outputs": {"times": "timestamps"}
21-
},
22-
"returns": ["dni", "ghi", "dhi"]
23-
},
2413
{
2514
"formula": "f_solpos",
2615
"args": {
@@ -59,6 +48,27 @@
5948
},
6049
"returns": ["am_abs"]
6150
},
51+
{
52+
"formula": "f_linketurbidity",
53+
"args": {
54+
"data": {
55+
"latitude": "latitude", "longitude": "longitude"
56+
},
57+
"outputs": {"times": "timestamps"}
58+
},
59+
"returns": ["tl"]
60+
},
61+
{
62+
"formula": "f_clearsky",
63+
"args": {
64+
"data": {"altitude": "elevation"},
65+
"outputs": {
66+
"solar_zenith": "solar_zenith", "am_abs": "am_abs", "tl": "tl",
67+
"dni_extra": "extraterrestrial"
68+
}
69+
},
70+
"returns": ["dni", "ghi", "dhi"]
71+
},
6272
{
6373
"formula": "f_total_irrad",
6474
"args": {

examples/PVPower/calculations/performance.json

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,12 @@
44
{
55
"formula": "f_aoi",
66
"args": {
7-
"data": {"surface_tilt": "latitude", "surface_azimuth": "surface_azimuth"},
8-
"outputs": {"solar_zenith": "solar_zenith", "solar_azimuth": "solar_azimuth"}
7+
"data": {
8+
"surface_tilt": "latitude", "surface_azimuth": "surface_azimuth"
9+
},
10+
"outputs": {
11+
"solar_zenith": "solar_zenith", "solar_azimuth": "solar_azimuth"
12+
}
913
},
1014
"returns": ["aoi"]
1115
},
@@ -17,17 +21,26 @@
1721
},
1822
"returns": ["Tcell", "Tmod"]
1923
},
24+
{
25+
"formula": "f_effective_irradiance",
26+
"args": {
27+
"data": {"module": "module"},
28+
"outputs": {
29+
"poa_direct": "poa_direct", "poa_diffuse": "poa_diffuse",
30+
"am_abs": "am_abs", "aoi": "aoi"
31+
}
32+
},
33+
"returns": ["Ee"]
34+
},
2035
{
2136
"formula": "f_dc_power",
2237
"args": {
2338
"data": {"module": "module"},
2439
"outputs": {
25-
"times": "timestamps", "poa_direct": "poa_direct",
26-
"poa_diffuse": "poa_diffuse", "cell_temp": "Tcell", "am_abs":
27-
"am_abs", "aoi": "aoi"
40+
"effective_irradiance": "Ee", "cell_temp": "Tcell"
2841
}
2942
},
30-
"returns": ["Isc", "Imp", "Voc", "Vmp", "Pmp", "Ee"]
43+
"returns": ["Isc", "Imp", "Voc", "Vmp", "Pmp"]
3144
},
3245
{
3346
"formula": "f_ac_power",

examples/PVPower/formulas/irradiance.json

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,22 @@
22
"module": ".irradiance",
33
"package": "formulas",
44
"formulas": {
5-
"f_clearsky": {
6-
"args": ["times", "latitude", "longitude", "altitude"],
7-
"units": [["W/m**2", "W/m**2", "W/m**2"], [null, "deg", "deg", "m"]],
5+
"f_linketurbidity": {
6+
"args": ["times", "latitude", "longitude"],
7+
"units": ["dimensionless", [null, "deg", "deg"]],
88
"isconstant": ["times"]
99
},
10+
"f_clearsky": {
11+
"args": ["solar_zenith", "am_abs", "tl", "dni_extra", "altitude"],
12+
"units": [
13+
["W/m**2", "W/m**2", "W/m**2"],
14+
["deg", "dimensionless", "dimensionless", "W/m**2", "m"]
15+
],
16+
"isconstant": ["dni_extra"]
17+
},
1018
"f_solpos": {
1119
"args": ["times", "latitude", "longitude"],
12-
"units": [["deg", "deg"], [null, "deg", "deg"]],
20+
"units": [["degree", "degree"], [null, "degree", "degree"]],
1321
"isconstant": ["times"]
1422
},
1523
"f_dni_extra": {"args": ["times"], "units": ["W/m**2", [null]]},

examples/PVPower/formulas/irradiance.py

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,37 @@
66

77
import pvlib
88
import pandas as pd
9-
import numpy as np
109

1110

12-
def f_clearsky(times, latitude, longitude, altitude):
11+
def f_linketurbidity(times, latitude, longitude):
1312
times = pd.DatetimeIndex(times)
1413
# latitude and longitude must be scalar or else linke turbidity lookup fails
1514
latitude, longitude = latitude.item(), longitude.item()
16-
cs = pvlib.clearsky.ineichen(times, latitude, longitude, altitude)
15+
tl = pvlib.clearsky.lookup_linke_turbidity(times, latitude, longitude)
16+
return tl.values.reshape(1, -1)
17+
18+
19+
def f_clearsky(solar_zenith, am_abs, tl, dni_extra, altitude):
20+
cs = pvlib.clearsky.ineichen(solar_zenith, am_abs, tl, dni_extra, altitude)
1721
return cs['dni'].values, cs['ghi'].values, cs['dhi'].values
1822

1923

2024
def f_solpos(times, latitude, longitude):
21-
times = pd.DatetimeIndex(times)
25+
"""
26+
Calculate solar position for lat/long at times.
27+
28+
:param times: Python :class:`datetime.datetime` object.
29+
:type times: list
30+
:param latitude: latitude [degrees]
31+
:type latitude: float
32+
:param longitude: longitude [degrees]
33+
:type longitude: float
34+
:returns: apparent zenith, azimuth
35+
"""
36+
# pvlib converts Python datetime objects to pandas DatetimeIndex
2237
solpos = pvlib.solarposition.get_solarposition(times, latitude, longitude)
38+
# solpos is a pandas DataFrame, so unpack the desired values
39+
# return shape is (2, NOBS), so unc_wrapper sees 2 dependent variables
2340
return solpos['apparent_zenith'].values, solpos['azimuth'].values
2441

2542

examples/PVPower/formulas/performance.json

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,17 @@
88
},
99
"f_dc_power": {
1010
"args": [
11-
"times", "module", "poa_direct", "poa_diffuse", "cell_temp", "am_abs",
12-
"aoi"
11+
"effective_irradiance", "cell_temp", "module"
1312
],
1413
"units": [
15-
["A", "A", "V", "V", "W", "suns"],
16-
[null, null, "W/m**2", "W/m**2", "degC", "dimensionless", "deg"]
14+
["A", "A", "V", "V", "W"],
15+
["suns", "degC", null]
1716
]
1817
},
18+
"f_effective_irradiance": {
19+
"args": ["poa_direct", "poa_diffuse", "am_abs", "aoi", "module"],
20+
"units": ["suns", ["W/m**2", "W/m**2", "dimensionless", "deg", null]]
21+
},
1922
"f_cell_temp": {
2023
"args": ["poa_global", "wind_speed", "air_temp"],
2124
"units": [["degC", "degC"], ["W/m**2", "m/s", "degC"]]

examples/PVPower/formulas/performance.py

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
"""
66

77
import pvlib
8-
import pandas as pd
98

109

1110
def f_ac_power(inverter, v_mp, p_mp):
@@ -17,34 +16,37 @@ def f_ac_power(inverter, v_mp, p_mp):
1716
:param p_mp:
1817
:return: AC power [W]
1918
"""
20-
return pvlib.pvsystem.snlinverter(inverter, v_mp, p_mp).values
19+
return pvlib.pvsystem.snlinverter(v_mp, p_mp, inverter).flatten()
2120

2221

23-
def f_dc_power(times, module, poa_direct, poa_diffuse, cell_temp, am_abs, aoi):
22+
def f_dc_power(effective_irradiance, cell_temp, module):
2423
"""
25-
Calculate DC power
24+
Calculate DC power using Sandia Performance model
2625
27-
:param times: timestamps
26+
:param effective_irradiance: effective irradiance [suns]
27+
:param cell_temp: PV cell temperature [degC]
2828
:param module: PV module dictionary or pandas data frame
29+
:returns: i_sc, i_mp, v_oc, v_mp, p_mp
30+
"""
31+
dc = pvlib.pvsystem.sapm(effective_irradiance, cell_temp, module)
32+
fields = ('i_sc', 'i_mp', 'v_oc', 'v_mp', 'p_mp')
33+
return tuple(dc[field] for field in fields)
34+
35+
36+
def f_effective_irradiance(poa_direct, poa_diffuse, am_abs, aoi, module):
37+
"""
38+
Calculate effective irradiance for Sandia Performance model
39+
2940
:param poa_direct: plane of array direct irradiance [W/m**2]
3041
:param poa_diffuse: plane of array diffuse irradiance [W/m**2]
31-
:param cell_temp: PV cell temperature [degC]
3242
:param am_abs: absolute air mass [dimensionless]
3343
:param aoi: angle of incidence [degrees]
34-
:return: short circuit current (Isc) [A], max. power current (Imp) [A],
35-
open circuit voltage (Voc) [V], max. power voltage (Vmp) [V],
36-
max. power (Pmp) [W], effective irradiance (Ee) [suns]
44+
:param module: PV module dictionary or pandas data frame
45+
:return: effective irradiance (Ee) [suns]
3746
"""
38-
poa_direct = pd.Series(poa_direct, index=times)
39-
poa_diffuse = pd.Series(poa_diffuse, index=times)
40-
cell_temp = pd.Series(cell_temp, index=times)
41-
am_abs = pd.Series(am_abs, index=times)
42-
aoi = pd.Series(aoi, index=times)
43-
dc = pvlib.pvsystem.sapm(
44-
module, poa_direct, poa_diffuse, cell_temp, am_abs, aoi
45-
)
46-
fields = ('i_sc', 'i_mp', 'v_oc', 'v_mp', 'p_mp', 'effective_irradiance')
47-
return tuple(dc[field].values for field in fields)
47+
Ee = pvlib.pvsystem.sapm_effective_irradiance(poa_direct, poa_diffuse,
48+
am_abs, aoi, module)
49+
return Ee.reshape(1, -1)
4850

4951

5052
def f_cell_temp(poa_global, wind_speed, air_temp):

examples/PVPower/outputs/irradiance.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
{
2+
"tl": {
3+
"isconstant": true, "timeseries": "timestamps", "units": "dimensionless",
4+
"size": 8761
5+
},
26
"poa_global": {
37
"isconstant": true, "timeseries": "timestamps", "units": "W/m**2",
48
"size": 8761

examples/PVPower/pvpower/sandia_perfmod_newstyle.py

Whitespace-only changes.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
11
"""
22
Tests for pvpower demo
33
"""
4+
from pvpower import PROJ_PATH
5+
import os
6+
7+
MODEL_PATH = os.path.join(PROJ_PATH, 'models')

0 commit comments

Comments
 (0)