Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use Hottel's method for bifacial.utils.*_integ functions #1865

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 72 additions & 19 deletions pvlib/bifacial/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@
"""
import numpy as np
from pvlib.tools import sind, cosd, tand

import warnings
from pvlib._deprecation import pvlibDeprecationWarning

def _solar_projection_tangent(solar_zenith, solar_azimuth, surface_azimuth):

Check failure on line 10 in pvlib/bifacial/utils.py

View workflow job for this annotation

GitHub Actions / flake8-linter

E302 expected 2 blank lines, found 1
"""
Tangent of the angle between the zenith vector and the sun vector
projected to the plane defined by the zenith vector and surface_azimuth.
Expand Down Expand Up @@ -172,8 +173,34 @@
return vf


def _dist(p1, p2):
return ((p1[0] - p2[0])**2 + (p1[1] - p2[1])**2)**0.5


def _angle(p1, p2):
return np.arctan2(p2[1] - p1[1], p2[0] - p1[0])


def _obstructed_string_length(p1, p2, ob_left, ob_right):
# string length calculations for Hottel's crossed strings method,
# considering view obstructions from the left and right.
# all inputs are (x, y) points.

# unobstructed length
d = _dist(p1, p2)
# obstructed on the left
d = np.where(_angle(p1, p2) > _angle(p1, ob_left),
_dist(p1, ob_left) + _dist(ob_left, p2),
d)
# obstructed on the right
d = np.where(_angle(p1, p2) < _angle(p1, ob_right),
_dist(p1, ob_right) + _dist(ob_right, p2),
d)
return d


def vf_ground_sky_2d_integ(surface_tilt, gcr, height, pitch, max_rows=10,
npoints=100, vectorize=False):
npoints=None, vectorize=None):
"""
Integrated view factor to the sky from the ground underneath
interior rows of the array.
Expand Down Expand Up @@ -204,23 +231,49 @@
Integration of view factor over the length between adjacent, interior
rows. Shape matches that of ``surface_tilt``. [unitless]
"""
# Abuse vf_ground_sky_2d by supplying surface_tilt in place
# of a signed rotation. This is OK because
# 1) z span the full distance between 2 rows, and
# 2) max_rows is set to be large upstream, and
# 3) _vf_ground_sky_2d considers [-max_rows, +max_rows]
# The VFs to the sky will thus be symmetric around z=0.5
z = np.linspace(0, 1, npoints)
rotation = np.atleast_1d(surface_tilt)
if vectorize:
fz_sky = vf_ground_sky_2d(rotation, gcr, z, pitch, height, max_rows)
else:
fz_sky = np.zeros((npoints, len(rotation)))
for k, r in enumerate(rotation):
vf = vf_ground_sky_2d(r, gcr, z, pitch, height, max_rows)
fz_sky[:, k] = vf[:, 0] # remove spurious rotation dimension
# calculate the integrated view factor for all of the ground between rows
return np.trapz(fz_sky, z, axis=0)
if npoints is not None or vectorize is not None:
msg = (
"The `npoints` and `vectorize` parameters have no effect and will "
"be removed in a future version." # TODO make this better
)
warnings.warn(msg, pvlibDeprecationWarning)

input_is_scalar = np.isscalar(surface_tilt)

collector_width = pitch * gcr
surface_tilt = np.atleast_2d(np.abs(surface_tilt))

# TODO figure out if this range is correct, or if the original code has a bug

Check failure on line 246 in pvlib/bifacial/utils.py

View workflow job for this annotation

GitHub Actions / flake8-linter

E501 line too long (81 > 79 characters)
k = np.arange(-max_rows+1, max_rows+1)[:, np.newaxis]

# primary crossed string points:
# a, b: boundaries of ground segment
# c, d: upper module edges
a = (0, 0)
b = (pitch, 0)
c = ((k+1)*pitch - 0.5 * collector_width * cosd(surface_tilt),
height + 0.5 * collector_width * sind(surface_tilt))
d = (c[0] - pitch, c[1])

# view obstruction points (lower module edges)
obs_left = (d[0] + collector_width * cosd(surface_tilt),
d[1] - collector_width * sind(surface_tilt))
obs_right = (obs_left[0] + pitch, obs_left[1])

# hottel string lengths, considering obstructions
ac = _obstructed_string_length(a, c, obs_left, obs_right)
ad = _obstructed_string_length(a, d, obs_left, obs_right)
bc = _obstructed_string_length(b, c, obs_left, obs_right)
bd = _obstructed_string_length(b, d, obs_left, obs_right)

# crossed string formula for VF
vf_per_slat = np.maximum(0.5 * (1/pitch) * ((ac + bd) - (bc + ad)), 0)
vf_total = np.sum(vf_per_slat, axis=0)

if input_is_scalar:
vf_total = vf_total.item()

return vf_total


def _vf_poly(surface_tilt, gcr, x, delta):
Expand Down
Loading