Skip to content

Commit c9adf8b

Browse files
authored
better nan handling in singleaxis (#697)
* better nan handling in singleaxis * a couple of comments
1 parent a156e6c commit c9adf8b

File tree

3 files changed

+52
-33
lines changed

3 files changed

+52
-33
lines changed

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

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,11 @@ Bug fixes
4141
* Fixed :py:func:`~pvlib.irradiance.erbs` behavior when zenith is
4242
near 90 degrees. (:issue:`681`)
4343
* :py:func:`~pvlib.irradiance.dni` now referenced in API under
44-
Decomposing and Combining irradiance header. (:issue:`686`)
44+
Decomposing and Combining irradiance header. (:issue:`686`)
45+
* Fixed NaN output from :py:func:`~pvlib.tracking.singleaxis` when sun
46+
near horizon. (:issue:`656`)
47+
* Fixed numpy warnings in :py:func:`~pvlib.tracking.singleaxis` when
48+
comparing NaN values to limits. (:issue:`622`)
4549

4650

4751
Testing
@@ -54,4 +58,5 @@ Contributors
5458
* Will Holmgren (:ghuser:`wholmgren`)
5559
* Roel Loonen (:ghuser:`roelloonen`)
5660
* Todd Hendricks (:ghuser:`tahentx`)
57-
61+
* Kevin Anderson (:ghuser:`kevinsa5`)
62+
* :ghuser:`bentomlinson`

pvlib/test/test_tracking.py

Lines changed: 35 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import datetime
2-
31
import numpy as np
42
from numpy import nan
53
import pandas as pd
@@ -96,10 +94,10 @@ def test_arrays_multi():
9694
apparent_azimuth = np.array([[180, 180], [180, 180]])
9795
# singleaxis should fail for num dim > 1
9896
with pytest.raises(ValueError):
99-
tracker_data = tracking.singleaxis(apparent_zenith, apparent_azimuth,
100-
axis_tilt=0, axis_azimuth=0,
101-
max_angle=90, backtrack=True,
102-
gcr=2.0/7.0)
97+
tracking.singleaxis(apparent_zenith, apparent_azimuth,
98+
axis_tilt=0, axis_azimuth=0,
99+
max_angle=90, backtrack=True,
100+
gcr=2.0/7.0)
103101
# uncomment if we ever get singleaxis to support num dim > 1 arrays
104102
# assert isinstance(tracker_data, dict)
105103
# expect = {'tracker_theta': np.full_like(apparent_zenith, 0),
@@ -121,7 +119,7 @@ def test_azimuth_north_south():
121119

122120
expect = pd.DataFrame({'tracker_theta': -60, 'aoi': 0,
123121
'surface_azimuth': 90, 'surface_tilt': 60},
124-
index=[0], dtype=np.float64)
122+
index=[0], dtype=np.float64)
125123
expect = expect[SINGLEAXIS_COL_ORDER]
126124

127125
assert_frame_equal(expect, tracker_data)
@@ -146,7 +144,7 @@ def test_max_angle():
146144

147145
expect = pd.DataFrame({'aoi': 15, 'surface_azimuth': 90,
148146
'surface_tilt': 45, 'tracker_theta': 45},
149-
index=[0], dtype=np.float64)
147+
index=[0], dtype=np.float64)
150148
expect = expect[SINGLEAXIS_COL_ORDER]
151149

152150
assert_frame_equal(expect, tracker_data)
@@ -163,7 +161,7 @@ def test_backtrack():
163161

164162
expect = pd.DataFrame({'aoi': 0, 'surface_azimuth': 90,
165163
'surface_tilt': 80, 'tracker_theta': 80},
166-
index=[0], dtype=np.float64)
164+
index=[0], dtype=np.float64)
167165
expect = expect[SINGLEAXIS_COL_ORDER]
168166

169167
assert_frame_equal(expect, tracker_data)
@@ -175,7 +173,7 @@ def test_backtrack():
175173

176174
expect = pd.DataFrame({'aoi': 52.5716, 'surface_azimuth': 90,
177175
'surface_tilt': 27.42833, 'tracker_theta': 27.4283},
178-
index=[0], dtype=np.float64)
176+
index=[0], dtype=np.float64)
179177
expect = expect[SINGLEAXIS_COL_ORDER]
180178

181179
assert_frame_equal(expect, tracker_data)
@@ -193,7 +191,7 @@ def test_axis_tilt():
193191
expect = pd.DataFrame({'aoi': 7.286245, 'surface_azimuth': 142.65730,
194192
'surface_tilt': 35.98741,
195193
'tracker_theta': -20.88121},
196-
index=[0], dtype=np.float64)
194+
index=[0], dtype=np.float64)
197195
expect = expect[SINGLEAXIS_COL_ORDER]
198196

199197
assert_frame_equal(expect, tracker_data)
@@ -205,7 +203,7 @@ def test_axis_tilt():
205203

206204
expect = pd.DataFrame({'aoi': 47.6632, 'surface_azimuth': 50.96969,
207205
'surface_tilt': 42.5152, 'tracker_theta': 31.6655},
208-
index=[0], dtype=np.float64)
206+
index=[0], dtype=np.float64)
209207
expect = expect[SINGLEAXIS_COL_ORDER]
210208

211209
assert_frame_equal(expect, tracker_data)
@@ -222,7 +220,7 @@ def test_axis_azimuth():
222220

223221
expect = pd.DataFrame({'aoi': 30, 'surface_azimuth': 180,
224222
'surface_tilt': 0, 'tracker_theta': 0},
225-
index=[0], dtype=np.float64)
223+
index=[0], dtype=np.float64)
226224
expect = expect[SINGLEAXIS_COL_ORDER]
227225

228226
assert_frame_equal(expect, tracker_data)
@@ -237,7 +235,7 @@ def test_axis_azimuth():
237235

238236
expect = pd.DataFrame({'aoi': 0, 'surface_azimuth': 180,
239237
'surface_tilt': 30, 'tracker_theta': 30},
240-
index=[0], dtype=np.float64)
238+
index=[0], dtype=np.float64)
241239
expect = expect[SINGLEAXIS_COL_ORDER]
242240

243241
assert_frame_equal(expect, tracker_data)
@@ -277,6 +275,20 @@ def test_horizon_tilted():
277275
assert_frame_equal(out, expected)
278276

279277

278+
def test_low_sun_angles():
279+
# GH 656
280+
result = tracking.singleaxis(
281+
apparent_zenith=80, apparent_azimuth=338, axis_tilt=30,
282+
axis_azimuth=180, max_angle=60, backtrack=True, gcr=0.35)
283+
expected = {
284+
'tracker_theta': np.array([-50.31051385]),
285+
'aoi': np.array([61.35300178]),
286+
'surface_azimuth': np.array([112.53615425]),
287+
'surface_tilt': np.array([56.42233095])}
288+
for k, v in result.items():
289+
assert_allclose(expected[k], v)
290+
291+
280292
def test_SingleAxisTracker_creation():
281293
system = tracking.SingleAxisTracker(max_angle=45,
282294
gcr=.25,
@@ -299,37 +311,35 @@ def test_SingleAxisTracker_tracking():
299311

300312
tracker_data = system.singleaxis(apparent_zenith, apparent_azimuth)
301313

302-
expect = pd.DataFrame({'aoi': 7.286245, 'surface_azimuth': 142.65730 ,
314+
expect = pd.DataFrame({'aoi': 7.286245, 'surface_azimuth': 142.65730,
303315
'surface_tilt': 35.98741,
304316
'tracker_theta': -20.88121},
305-
index=[0], dtype=np.float64)
317+
index=[0], dtype=np.float64)
306318
expect = expect[SINGLEAXIS_COL_ORDER]
307319

308320
assert_frame_equal(expect, tracker_data)
309321

310-
### results calculated using PVsyst
322+
# results calculated using PVsyst
311323
pvsyst_solar_azimuth = 7.1609
312324
pvsyst_solar_height = 27.315
313325
pvsyst_axis_tilt = 20.
314326
pvsyst_axis_azimuth = 20.
315-
pvsyst_system = tracking.SingleAxisTracker(max_angle=60.,
316-
axis_tilt=pvsyst_axis_tilt,
317-
axis_azimuth=180+pvsyst_axis_azimuth,
318-
backtrack=False)
327+
pvsyst_system = tracking.SingleAxisTracker(
328+
max_angle=60., axis_tilt=pvsyst_axis_tilt,
329+
axis_azimuth=180+pvsyst_axis_azimuth, backtrack=False)
319330
# the definition of azimuth is different from PYsyst
320331
apparent_azimuth = pd.Series([180+pvsyst_solar_azimuth])
321332
apparent_zenith = pd.Series([90-pvsyst_solar_height])
322333
tracker_data = pvsyst_system.singleaxis(apparent_zenith, apparent_azimuth)
323-
expect = pd.DataFrame({'aoi': 41.07852 , 'surface_azimuth': 180-18.432,
324-
'surface_tilt': 24.92122 ,
334+
expect = pd.DataFrame({'aoi': 41.07852, 'surface_azimuth': 180-18.432,
335+
'surface_tilt': 24.92122,
325336
'tracker_theta': -15.18391},
326-
index=[0], dtype=np.float64)
337+
index=[0], dtype=np.float64)
327338
expect = expect[SINGLEAXIS_COL_ORDER]
328339

329340
assert_frame_equal(expect, tracker_data)
330341

331342

332-
333343
def test_LocalizedSingleAxisTracker_creation():
334344
localized_system = tracking.LocalizedSingleAxisTracker(latitude=32,
335345
longitude=-111,

pvlib/tracking.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -441,19 +441,22 @@ def singleaxis(apparent_zenith, apparent_azimuth,
441441
# angle convention being used here.
442442
if backtrack:
443443
axes_distance = 1/gcr
444-
temp = np.minimum(axes_distance*cosd(wid), 1)
444+
# clip needed for low angles. GH 656
445+
temp = np.clip(axes_distance*cosd(wid), -1, 1)
445446

446447
# backtrack angle
447448
# (always positive b/c acosd returns values between 0 and 180)
448449
wc = np.degrees(np.arccos(temp))
449450

450451
# Eq 4 applied when wid in QIV (wid < 0 evalulates True), QI
451-
tracker_theta = np.where(wid < 0, wid + wc, wid - wc)
452+
with np.errstate(invalid='ignore'):
453+
# errstate for GH 622
454+
tracker_theta = np.where(wid < 0, wid + wc, wid - wc)
452455
else:
453456
tracker_theta = wid
454457

455-
tracker_theta[tracker_theta > max_angle] = max_angle
456-
tracker_theta[tracker_theta < -max_angle] = -max_angle
458+
tracker_theta = np.minimum(tracker_theta, max_angle)
459+
tracker_theta = np.maximum(tracker_theta, -max_angle)
457460

458461
# calculate panel normal vector in panel-oriented x, y, z coordinates.
459462
# y-axis is axis of tracker rotation. tracker_theta is a compass angle
@@ -560,8 +563,9 @@ def singleaxis(apparent_zenith, apparent_azimuth,
560563
surface_azimuth = 90 - surface_azimuth + axis_azimuth
561564

562565
# 5. Map azimuth into [0,360) domain.
563-
surface_azimuth[surface_azimuth < 0] += 360
564-
surface_azimuth[surface_azimuth >= 360] -= 360
566+
# surface_azimuth[surface_azimuth < 0] += 360
567+
# surface_azimuth[surface_azimuth >= 360] -= 360
568+
surface_azimuth = surface_azimuth % 360
565569

566570
# Calculate surface_tilt
567571
dotproduct = (panel_norm_earth * projected_normal).sum(axis=1)

0 commit comments

Comments
 (0)