Describe the bug
pvlib.tracking.calc_surface_orientation regressed in 0.15.1: passing 2-D arrays for tracker_theta (and broadcasting-compatible 2-D axis_tilt / axis_azimuth, e.g. shape (timestamps, sites)) raises a broadcasting ValueError. The same call works in 0.15.0 and earlier.
The cause is the new _unit_normal helper introduced in #2702, which builds its return value with np.column_stack((x, y, z)). np.column_stack only stacks 1-D inputs into columns; for 2-D inputs of shape (T, N) it concatenates along axis=1, producing a (T, 3*N) array instead of the intended (T, N, 3). The subsequent unit_normal[:, 0] / unit_normal[:, 1] then collapse to shape (T,), and the downstream
python surface_azimuth = np.where(surface_tilt == 0., axis_azimuth - 90., surface_azimuth)
fails because the condition and true branch are still (T, N) while the false branch is now (T,).
To Reproduce
import numpy as np
import pvlib
T, N = 4655, 105
tracker_theta = np.zeros((T, N))
axis_tilt = np.zeros((T, N))
axis_azimuth = np.full((T, N), 180.0)
pvlib.tracking.calc_surface_orientation(tracker_theta, axis_tilt, axis_azimuth)
# pvlib 0.15.0: returns dict of (T, N) arrays — works
# pvlib 0.15.1: ValueError: operands could not be broadcast together with shapes (4655,105) (4655,105) (4655,)
Bisected against pip-installed releases on Python 3.11 / numpy 2.4.4 / pandas 3.0.2:
| pvlib |
result |
| 0.11.2 → 0.15.0 |
works |
| 0.15.1 |
broken |
Expected behavior
Return surface_tilt and surface_azimuth arrays with the same shape as the broadcast inputs (the 0.15.0 behavior).
Versions
pvlib.__version__: 0.15.1 (broken), 0.15.0 (works)
pandas.__version__: 3.0.2
numpy.__version__: 2.4.4
- python: 3.11.9
- platform: Windows
Additional context
Regression introduced in #2702 (milestone v0.15.1). Suggested one-line fix in
_unit_normal:
# before
return np.column_stack((x, y, z))
# after
return np.stack((x, y, z), axis=-1)
np.stack(..., axis=-1) preserves the leading dimensions for inputs of any rank and is equivalent to the old behavior for 1-D inputs, so it should be a drop-in replacement.
Hit in production while batch-processing tracker geometry across (timestamps × bays); pinned to pvlib<0.15.1 as a workaround.
Describe the bug
pvlib.tracking.calc_surface_orientationregressed in 0.15.1: passing 2-D arrays fortracker_theta(and broadcasting-compatible 2-Daxis_tilt/axis_azimuth, e.g. shape(timestamps, sites)) raises a broadcastingValueError. The same call works in 0.15.0 and earlier.The cause is the new
_unit_normalhelper introduced in #2702, which builds its return value withnp.column_stack((x, y, z)).np.column_stackonly stacks 1-D inputs into columns; for 2-D inputs of shape(T, N)it concatenates along axis=1, producing a(T, 3*N)array instead of the intended(T, N, 3). The subsequentunit_normal[:, 0]/unit_normal[:, 1]then collapse to shape(T,), and the downstreampython surface_azimuth = np.where(surface_tilt == 0., axis_azimuth - 90., surface_azimuth)fails because the condition and true branch are still
(T, N)while the false branch is now(T,).To Reproduce
Bisected against pip-installed releases on Python 3.11 / numpy 2.4.4 / pandas 3.0.2:
Expected behavior
Return
surface_tiltandsurface_azimutharrays with the same shape as the broadcast inputs (the 0.15.0 behavior).Versions
pvlib.__version__: 0.15.1 (broken), 0.15.0 (works)pandas.__version__: 3.0.2numpy.__version__: 2.4.4Additional context
Regression introduced in #2702 (milestone v0.15.1). Suggested one-line fix in
_unit_normal:np.stack(..., axis=-1)preserves the leading dimensions for inputs of any rank and is equivalent to the old behavior for 1-D inputs, so it should be a drop-in replacement.Hit in production while batch-processing tracker geometry across (timestamps × bays); pinned to
pvlib<0.15.1as a workaround.