Skip to content

Commit d7e4a1f

Browse files
authored
add SingleAxisTracker.get_aoi, refactor SingleAxisTracker.get_irradiance, fix ModelChain with SAT (#356)
* add SingleAxisTracker.get_aoi, update ModelChain.prepare_inputs to use it * get tests to pass * update whatsnew * add test, clarify things * flake8
1 parent 16c6eac commit d7e4a1f

File tree

5 files changed

+92
-43
lines changed

5 files changed

+92
-43
lines changed

docs/sphinx/source/whatsnew/v0.4.6.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,22 @@ Bug fixes
1212
(:issue:`330`)
1313
* Fix the `__repr__` method of `ModelChain`, crashing when
1414
`orientation_strategy` is set to `'None'` (:issue:`352`)
15+
* Fix the `ModelChain`'s angle of incidence calculation for
16+
SingleAxisTracker objects (:issue:`351`)
1517

1618

1719
Enhancements
1820
~~~~~~~~~~~~
1921
* Added default values to docstrings of all functions (:issue:`336`)
2022
* Added analytical method that calculates solar azimuth angle (:issue:`291`)
2123

24+
2225
API Changes
2326
~~~~~~~~~~~
2427
* Removed parameter w from _calc_d (:issue:`344`)
28+
* SingleAxisTracker.get_aoi and SingleAxisTracker.get_irradiance
29+
now require surface_zenith and surface_azimuth (:issue:`351`)
30+
2531

2632
Documentation
2733
~~~~~~~~~~~~~

pvlib/modelchain.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -757,9 +757,6 @@ def prepare_inputs(self, times=None, irradiance=None, weather=None):
757757
self.airmass = self.location.get_airmass(
758758
solar_position=self.solar_position, model=self.airmass_model)
759759

760-
self.aoi = self.system.get_aoi(self.solar_position['apparent_zenith'],
761-
self.solar_position['azimuth'])
762-
763760
if not any([x in ['ghi', 'dni', 'dhi'] for x in self.weather.columns]):
764761
self.weather[['ghi', 'dni', 'dhi']] = self.location.get_clearsky(
765762
self.solar_position.index, self.clearsky_model,
@@ -773,7 +770,8 @@ def prepare_inputs(self, times=None, irradiance=None, weather=None):
773770
"Detected data: {0}".format(list(self.weather.columns)))
774771

775772
# PVSystem.get_irradiance and SingleAxisTracker.get_irradiance
776-
# have different method signatures, so use partial to handle
773+
# and PVSystem.get_aoi and SingleAxisTracker.get_aoi
774+
# have different method signatures. Use partial to handle
777775
# the differences.
778776
if isinstance(self.system, SingleAxisTracker):
779777
self.tracking = self.system.singleaxis(
@@ -785,13 +783,17 @@ def prepare_inputs(self, times=None, irradiance=None, weather=None):
785783
self.tracking['surface_azimuth'] = (
786784
self.tracking['surface_azimuth']
787785
.fillna(self.system.axis_azimuth))
786+
self.aoi = self.tracking['aoi']
788787
get_irradiance = partial(
789788
self.system.get_irradiance,
790-
surface_tilt=self.tracking['surface_tilt'],
791-
surface_azimuth=self.tracking['surface_azimuth'],
792-
solar_zenith=self.solar_position['apparent_zenith'],
793-
solar_azimuth=self.solar_position['azimuth'])
789+
self.tracking['surface_tilt'],
790+
self.tracking['surface_azimuth'],
791+
self.solar_position['apparent_zenith'],
792+
self.solar_position['azimuth'])
794793
else:
794+
self.aoi = self.system.get_aoi(
795+
self.solar_position['apparent_zenith'],
796+
self.solar_position['azimuth'])
795797
get_irradiance = partial(
796798
self.system.get_irradiance,
797799
self.solar_position['apparent_zenith'],

pvlib/test/test_modelchain.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ def test_run_model_tracker(system, location):
168168
times = pd.date_range('20160101 1200-0700', periods=2, freq='6H')
169169
ac = mc.run_model(times).ac
170170

171-
expected = pd.Series(np.array([ 122.333764454, -2.00000000e-02]),
171+
expected = pd.Series(np.array([119.067713606, nan]),
172172
index=times)
173173
assert_series_equal(ac, expected, check_less_precise=2)
174174

pvlib/test/test_tracking.py

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@
66

77
import pytest
88
from pandas.util.testing import assert_frame_equal
9+
from numpy.testing import assert_allclose
910

1011
from pvlib.location import Location
11-
from pvlib import solarposition
1212
from pvlib import tracking
1313

1414

@@ -246,6 +246,21 @@ def test_SingleAxisTracker_localize_location():
246246
assert localized_system.longitude == -111
247247

248248

249+
# see test_irradiance for more thorough testing
250+
def test_get_aoi():
251+
system = tracking.SingleAxisTracker(max_angle=90, axis_tilt=30,
252+
axis_azimuth=180, gcr=2.0/7.0,
253+
backtrack=True)
254+
surface_tilt = np.array([30, 0])
255+
surface_azimuth = np.array([90, 270])
256+
solar_zenith = np.array([70, 10])
257+
solar_azimuth = np.array([100, 180])
258+
out = system.get_aoi(surface_tilt, surface_azimuth,
259+
solar_zenith, solar_azimuth)
260+
expected = np.array([40.632115, 10.])
261+
assert_allclose(out, expected, atol=0.000001)
262+
263+
249264
def test_get_irradiance():
250265
system = tracking.SingleAxisTracker(max_angle=90, axis_tilt=30,
251266
axis_azimuth=180, gcr=2.0/7.0,
@@ -260,19 +275,17 @@ def test_get_irradiance():
260275
solar_azimuth = solar_position['azimuth']
261276
tracker_data = system.singleaxis(solar_zenith, solar_azimuth)
262277

263-
irradiance = system.get_irradiance(irrads['dni'],
278+
irradiance = system.get_irradiance(tracker_data['surface_tilt'],
279+
tracker_data['surface_azimuth'],
280+
solar_zenith,
281+
solar_azimuth,
282+
irrads['dni'],
264283
irrads['ghi'],
265-
irrads['dhi'],
266-
solar_zenith=solar_zenith,
267-
solar_azimuth=solar_azimuth,
268-
surface_tilt=tracker_data['surface_tilt'],
269-
surface_azimuth=tracker_data['surface_azimuth'])
284+
irrads['dhi'])
270285

271286
expected = pd.DataFrame(data=np.array(
272-
[[ 961.80070, 815.94490, 145.85580, 135.32820,
273-
10.52757492],
274-
[ nan, nan, nan, nan,
275-
nan]]),
287+
[[961.80070, 815.94490, 145.85580, 135.32820, 10.52757492],
288+
[nan, nan, nan, nan, nan]]),
276289
columns=['poa_global', 'poa_direct',
277290
'poa_diffuse', 'poa_sky_diffuse',
278291
'poa_ground_diffuse'],
@@ -284,7 +297,7 @@ def test_get_irradiance():
284297
def test_SingleAxisTracker___repr__():
285298
system = tracking.SingleAxisTracker(max_angle=45, gcr=.25,
286299
module='blah', inverter='blarg')
287-
expected = 'SingleAxisTracker: \n axis_tilt: 0\n axis_azimuth: 0\n max_angle: 45\n backtrack: True\n gcr: 0.25\n name: None\n surface_tilt: 0\n surface_azimuth: 180\n module: blah\n inverter: blarg\n albedo: 0.25\n racking_model: open_rack_cell_glassback'
300+
expected = 'SingleAxisTracker: \n axis_tilt: 0\n axis_azimuth: 0\n max_angle: 45\n backtrack: True\n gcr: 0.25\n name: None\n surface_tilt: None\n surface_azimuth: None\n module: blah\n inverter: blarg\n albedo: 0.25\n racking_model: open_rack_cell_glassback'
288301
assert system.__repr__() == expected
289302

290303

@@ -295,7 +308,6 @@ def test_LocalizedSingleAxisTracker___repr__():
295308
inverter='blarg',
296309
gcr=0.25)
297310

298-
expected = 'LocalizedSingleAxisTracker: \n axis_tilt: 0\n axis_azimuth: 0\n max_angle: 90\n backtrack: True\n gcr: 0.25\n name: None\n surface_tilt: 0\n surface_azimuth: 180\n module: blah\n inverter: blarg\n albedo: 0.25\n racking_model: open_rack_cell_glassback\n latitude: 32\n longitude: -111\n altitude: 0\n tz: UTC'
311+
expected = 'LocalizedSingleAxisTracker: \n axis_tilt: 0\n axis_azimuth: 0\n max_angle: 90\n backtrack: True\n gcr: 0.25\n name: None\n surface_tilt: None\n surface_azimuth: None\n module: blah\n inverter: blarg\n albedo: 0.25\n racking_model: open_rack_cell_glassback\n latitude: 32\n longitude: -111\n altitude: 0\n tz: UTC'
299312

300313
assert localized_system.__repr__() == expected
301-

pvlib/tracking.py

Lines changed: 49 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
class SingleAxisTracker(PVSystem):
1616
"""
17-
Inherits all of the PV modeling methods from PVSystem.
17+
Inherits the PV modeling methods from :ref:PVSystem:.
1818
1919
axis_tilt : float, default 0
2020
The tilt of the axis of rotation (i.e, the y-axis defined by
@@ -54,6 +54,9 @@ def __init__(self, axis_tilt=0, axis_azimuth=0,
5454
self.backtrack = backtrack
5555
self.gcr = gcr
5656

57+
kwargs['surface_tilt'] = None
58+
kwargs['surface_azimuth'] = None
59+
5760
super(SingleAxisTracker, self).__init__(**kwargs)
5861

5962
def __repr__(self):
@@ -98,20 +101,60 @@ def localize(self, location=None, latitude=None, longitude=None,
98101

99102
return LocalizedSingleAxisTracker(pvsystem=self, location=location)
100103

101-
def get_irradiance(self, dni, ghi, dhi,
104+
def get_aoi(self, surface_tilt, surface_azimuth, solar_zenith,
105+
solar_azimuth):
106+
"""Get the angle of incidence on the system.
107+
108+
For a given set of solar zenith and azimuth angles, the
109+
surface tilt and azimuth parameters are typically determined
110+
by :py:method:`~SingleAxisTracker.singleaxis`. The
111+
:py:method:`~SingleAxisTracker.singleaxis` method also returns
112+
the angle of incidence, so this method is only needed
113+
if using a different tracking algorithm.
114+
115+
Parameters
116+
----------
117+
surface_tilt : numeric
118+
Panel tilt from horizontal.
119+
surface_azimuth : numeric
120+
Panel azimuth from north
121+
solar_zenith : float or Series.
122+
Solar zenith angle.
123+
solar_azimuth : float or Series.
124+
Solar azimuth angle.
125+
126+
Returns
127+
-------
128+
aoi : Series
129+
The angle of incidence in degrees from normal.
130+
"""
131+
132+
aoi = irradiance.aoi(surface_tilt, surface_azimuth,
133+
solar_zenith, solar_azimuth)
134+
return aoi
135+
136+
def get_irradiance(self, surface_tilt, surface_azimuth,
137+
solar_zenith, solar_azimuth, dni, ghi, dhi,
102138
dni_extra=None, airmass=None, model='haydavies',
103139
**kwargs):
104140
"""
105141
Uses the :func:`irradiance.total_irrad` function to calculate
106142
the plane of array irradiance components on a tilted surface
107-
defined by ``self.surface_tilt``, ``self.surface_azimuth``, and
108-
``self.albedo``.
143+
defined by the input data and ``self.albedo``.
144+
145+
For a given set of solar zenith and azimuth angles, the
146+
surface tilt and azimuth parameters are typically determined
147+
by :py:method:`~SingleAxisTracker.singleaxis`.
109148
110149
Parameters
111150
----------
112-
solar_zenith : float or Series.
151+
surface_tilt : numeric
152+
Panel tilt from horizontal.
153+
surface_azimuth : numeric
154+
Panel azimuth from north
155+
solar_zenith : numeric
113156
Solar zenith angle.
114-
solar_azimuth : float or Series.
157+
solar_azimuth : numeric
115158
Solar azimuth angle.
116159
dni : float or Series
117160
Direct Normal Irradiance
@@ -135,23 +178,9 @@ def get_irradiance(self, dni, ghi, dhi,
135178
Column names are: ``total, beam, sky, ground``.
136179
"""
137180

138-
surface_tilt = kwargs.pop('surface_tilt', self.surface_tilt)
139-
surface_azimuth = kwargs.pop('surface_azimuth', self.surface_azimuth)
140-
141-
try:
142-
solar_zenith = kwargs['solar_zenith']
143-
except KeyError:
144-
solar_zenith = self.solar_zenith
145-
146-
try:
147-
solar_azimuth = kwargs['solar_azimuth']
148-
except KeyError:
149-
solar_azimuth = self.solar_azimuth
150-
151181
# not needed for all models, but this is easier
152182
if dni_extra is None:
153183
dni_extra = irradiance.extraradiation(solar_zenith.index)
154-
dni_extra = pd.Series(dni_extra, index=solar_zenith.index)
155184

156185
if airmass is None:
157186
airmass = atmosphere.relativeairmass(solar_zenith)

0 commit comments

Comments
 (0)