From 90014aff811cc0ebc0bca0042d448a35263c294c Mon Sep 17 00:00:00 2001 From: Kevin Anderson Date: Mon, 18 Sep 2023 19:09:37 -0400 Subject: [PATCH 01/11] draft of hottel's method in bifacial.utils.*_integ --- pvlib/bifacial/utils.py | 99 +++++++++++++++++++++++++++++------------ 1 file changed, 70 insertions(+), 29 deletions(-) diff --git a/pvlib/bifacial/utils.py b/pvlib/bifacial/utils.py index 8d7f5d5a71..0faa459a90 100644 --- a/pvlib/bifacial/utils.py +++ b/pvlib/bifacial/utils.py @@ -4,7 +4,8 @@ """ 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): """ @@ -173,7 +174,7 @@ def vf_ground_sky_2d(rotation, gcr, x, pitch, height, max_rows=10): 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. @@ -204,23 +205,77 @@ def vf_ground_sky_2d_integ(surface_tilt, gcr, height, pitch, max_rows=10, Integration of view factor over the length between adjacent, interior rows. Shape matches that of ``surface_tilt``. [unitless] """ + if npoints is not None or vectorize is not None: + warnings.warn("the `npoints` and `vectorize` parameters are deprecated", pvlibDeprecationWarning) + # 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) + + + # TODO: compatibility with scalar inputs + # TODO: clean this up + collector_width = pitch * gcr + + base_x1 = 0.5 * collector_width * cosd(surface_tilt)[np.newaxis, :] + base_y1 = 0.5 * collector_width * sind(surface_tilt)[np.newaxis, :] + + k = np.arange(-max_rows, max_rows+1)[:, np.newaxis] + + x1l = k*pitch - base_x1 + x1r = k*pitch + base_x1 + x2l = x1l + pitch + x2r = x1r + pitch + + y1l = y2l = height + base_y1 + y1r = y2r = height - base_y1 + + dx = x1l + dy = y1l + cx = x2l + cy = y2l + ax = 0 + ay = 0 + bx = pitch + by = 0 + + o1x = x1r + o1y = y1r + o2x = x2r + o2y = y2r + + theta_ac = np.arctan2(cy - ay, cx - ax) + theta_ad = np.arctan2(dy - ay, dx - ax) + theta_ao1 = np.arctan2(o1y - ay, o1x - ax) + theta_ao2 = np.arctan2(o2y - ay, o2x - ax) + + ac = ((cx - ax)**2 + (cy - ay)**2)**0.5 + ac = np.where(theta_ac > theta_ao1, ((o1x - ax)**2 + (o1y - ay)**2)**0.5 + ((cx - o1x)**2 + (cy - o1y)**2)**0.5, ac) + ac = np.where(theta_ac < theta_ao2, ((o2x - ax)**2 + (o2y - ay)**2)**0.5 + collector_width, ac) + + ad = ((dx - ax)**2 + (dy - ay)**2)**0.5 + ad = np.where(theta_ad > theta_ao1, ((o1x - ax)**2 + (o1y - ay)**2)**0.5 + collector_width, ad) + ad = np.where(theta_ad < theta_ao2, ((o2x - ax)**2 + (o2y - ay)**2)**0.5 + ((dx - o2x)**2 + (dy - o2y)**2)**0.5, ad) + + theta_bc = np.arctan2(cy - by, cx - bx) + theta_bd = np.arctan2(dy - by, dx - bx) + theta_ao1 = np.arctan2(o1y - by, o1x - bx) + theta_ao2 = np.arctan2(o2y - by, o2x - bx) + + bd = ((dx - bx)**2 + (dy - by)**2)**0.5 + bd = np.where(theta_bd < theta_ao2, ((o2x - bx)**2 + (o2y - by)**2)**0.5 + ((dx - o2x)**2 + (dy - o2y)**2)**0.5, bd) + bd = np.where(theta_bd > theta_ao1, ((o1x - bx)**2 + (o1y - by)**2)**0.5 + collector_width, bd) + + bc = ((cx - bx)**2 + (cy - by)**2)**0.5 + bc = np.where(theta_bc < theta_ao2, ((o2x - bx)**2 + (o2y - by)**2)**0.5 + collector_width, bc) + bc = np.where(theta_bc > theta_ao1, ((o1x - bx)**2 + (o1y - by)**2)**0.5 + ((cx - o1x)**2 + (cy - o1y)**2)**0.5, bc) + + vf = np.sum(np.clip(0.5 * (1/pitch) * ((ac + bd) - (bc + ad)), a_min=0, a_max=None), axis=0) + + return vf def _vf_poly(surface_tilt, gcr, x, delta): @@ -309,14 +364,7 @@ def vf_row_sky_2d_integ(surface_tilt, gcr, x0=0, x1=1): from x0 to x1. [unitless] ''' - u = np.abs(x1 - x0) - p0 = _vf_poly(surface_tilt, gcr, 1 - x0, -1) - p1 = _vf_poly(surface_tilt, gcr, 1 - x1, -1) - with np.errstate(divide='ignore'): - result = np.where(u < 1e-6, - vf_row_sky_2d(surface_tilt, gcr, x0), - 0.5*(1 + 1/u * (p1 - p0)) - ) + result = 0.5 * (1/gcr + 1 - ((1/gcr)**2 - (2/gcr)*cosd(surface_tilt) + 1)**0.5) return result @@ -380,12 +428,5 @@ def vf_row_ground_2d_integ(surface_tilt, gcr, x0=0, x1=1): [unitless] ''' - u = np.abs(x1 - x0) - p0 = _vf_poly(surface_tilt, gcr, x0, 1) - p1 = _vf_poly(surface_tilt, gcr, x1, 1) - with np.errstate(divide='ignore'): - result = np.where(u < 1e-6, - vf_row_ground_2d(surface_tilt, gcr, x0), - 0.5*(1 - 1/u * (p1 - p0)) - ) + result = 0.5 * (1/gcr + 1 - ((1/gcr)**2 + (2/gcr)*cosd(surface_tilt) + 1)**0.5) return result From ce14d890d19e845d261911aa1e807441072f9d2b Mon Sep 17 00:00:00 2001 From: Kevin Anderson Date: Wed, 20 Sep 2023 14:07:49 -0400 Subject: [PATCH 02/11] preliminary cleanup --- pvlib/bifacial/utils.py | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/pvlib/bifacial/utils.py b/pvlib/bifacial/utils.py index 0faa459a90..9c93513659 100644 --- a/pvlib/bifacial/utils.py +++ b/pvlib/bifacial/utils.py @@ -252,13 +252,22 @@ def vf_ground_sky_2d_integ(surface_tilt, gcr, height, pitch, max_rows=10, theta_ao1 = np.arctan2(o1y - ay, o1x - ax) theta_ao2 = np.arctan2(o2y - ay, o2x - ax) + a_o1 = ((o1x - ax)**2 + (o1y - ay)**2)**0.5 + a_o2 = ((o2x - ax)**2 + (o2y - ay)**2)**0.5 + b_o1 = ((o1x - bx)**2 + (o1y - by)**2)**0.5 + b_o2 = ((o2x - bx)**2 + (o2y - by)**2)**0.5 + c_o1 = ((cx - o1x)**2 + (cy - o1y)**2)**0.5 + c_o2 = collector_width + d_o1 = collector_width + d_o2 = ((dx - o2x)**2 + (dy - o2y)**2)**0.5 + ac = ((cx - ax)**2 + (cy - ay)**2)**0.5 - ac = np.where(theta_ac > theta_ao1, ((o1x - ax)**2 + (o1y - ay)**2)**0.5 + ((cx - o1x)**2 + (cy - o1y)**2)**0.5, ac) - ac = np.where(theta_ac < theta_ao2, ((o2x - ax)**2 + (o2y - ay)**2)**0.5 + collector_width, ac) + ac = np.where(theta_ac > theta_ao1, a_o1 + c_o1, ac) + ac = np.where(theta_ac < theta_ao2, a_o2 + c_o2, ac) ad = ((dx - ax)**2 + (dy - ay)**2)**0.5 - ad = np.where(theta_ad > theta_ao1, ((o1x - ax)**2 + (o1y - ay)**2)**0.5 + collector_width, ad) - ad = np.where(theta_ad < theta_ao2, ((o2x - ax)**2 + (o2y - ay)**2)**0.5 + ((dx - o2x)**2 + (dy - o2y)**2)**0.5, ad) + ad = np.where(theta_ad > theta_ao1, a_o1 + d_o1, ad) + ad = np.where(theta_ad < theta_ao2, a_o2 + d_o2, ad) theta_bc = np.arctan2(cy - by, cx - bx) theta_bd = np.arctan2(dy - by, dx - bx) @@ -266,12 +275,12 @@ def vf_ground_sky_2d_integ(surface_tilt, gcr, height, pitch, max_rows=10, theta_ao2 = np.arctan2(o2y - by, o2x - bx) bd = ((dx - bx)**2 + (dy - by)**2)**0.5 - bd = np.where(theta_bd < theta_ao2, ((o2x - bx)**2 + (o2y - by)**2)**0.5 + ((dx - o2x)**2 + (dy - o2y)**2)**0.5, bd) - bd = np.where(theta_bd > theta_ao1, ((o1x - bx)**2 + (o1y - by)**2)**0.5 + collector_width, bd) + bd = np.where(theta_bd < theta_ao2, b_o2 + d_o2, bd) + bd = np.where(theta_bd > theta_ao1, b_o1 + d_o1, bd) bc = ((cx - bx)**2 + (cy - by)**2)**0.5 - bc = np.where(theta_bc < theta_ao2, ((o2x - bx)**2 + (o2y - by)**2)**0.5 + collector_width, bc) - bc = np.where(theta_bc > theta_ao1, ((o1x - bx)**2 + (o1y - by)**2)**0.5 + ((cx - o1x)**2 + (cy - o1y)**2)**0.5, bc) + bc = np.where(theta_bc < theta_ao2, b_o2 + c_o2, bc) + bc = np.where(theta_bc > theta_ao1, b_o1 + c_o1, bc) vf = np.sum(np.clip(0.5 * (1/pitch) * ((ac + bd) - (bc + ad)), a_min=0, a_max=None), axis=0) From c0ddd614f295d1acc0bd9077908553e060c47901 Mon Sep 17 00:00:00 2001 From: Kevin Anderson Date: Wed, 20 Sep 2023 15:21:40 -0400 Subject: [PATCH 03/11] major refactor --- pvlib/bifacial/utils.py | 122 ++++++++++++++++------------------------ 1 file changed, 47 insertions(+), 75 deletions(-) diff --git a/pvlib/bifacial/utils.py b/pvlib/bifacial/utils.py index 9c93513659..47a19e0c79 100644 --- a/pvlib/bifacial/utils.py +++ b/pvlib/bifacial/utils.py @@ -206,85 +206,57 @@ def vf_ground_sky_2d_integ(surface_tilt, gcr, height, pitch, max_rows=10, rows. Shape matches that of ``surface_tilt``. [unitless] """ if npoints is not None or vectorize is not None: - warnings.warn("the `npoints` and `vectorize` parameters are deprecated", pvlibDeprecationWarning) + msg = ( + "The `npoints` and `vectorize` parameters have no effect and will " + "be removed in a future version." + ) + warnings.warn(msg, pvlibDeprecationWarning) - # 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 - - - # TODO: compatibility with scalar inputs - # TODO: clean this up + input_is_scalar = np.isscalar(surface_tilt) collector_width = pitch * gcr - - base_x1 = 0.5 * collector_width * cosd(surface_tilt)[np.newaxis, :] - base_y1 = 0.5 * collector_width * sind(surface_tilt)[np.newaxis, :] - + surface_tilt = np.atleast_2d(surface_tilt) k = np.arange(-max_rows, max_rows+1)[:, np.newaxis] - x1l = k*pitch - base_x1 - x1r = k*pitch + base_x1 - x2l = x1l + pitch - x2r = x1r + pitch - - y1l = y2l = height + base_y1 - y1r = y2r = height - base_y1 - - dx = x1l - dy = y1l - cx = x2l - cy = y2l - ax = 0 - ay = 0 - bx = pitch - by = 0 - - o1x = x1r - o1y = y1r - o2x = x2r - o2y = y2r - - theta_ac = np.arctan2(cy - ay, cx - ax) - theta_ad = np.arctan2(dy - ay, dx - ax) - theta_ao1 = np.arctan2(o1y - ay, o1x - ax) - theta_ao2 = np.arctan2(o2y - ay, o2x - ax) - - a_o1 = ((o1x - ax)**2 + (o1y - ay)**2)**0.5 - a_o2 = ((o2x - ax)**2 + (o2y - ay)**2)**0.5 - b_o1 = ((o1x - bx)**2 + (o1y - by)**2)**0.5 - b_o2 = ((o2x - bx)**2 + (o2y - by)**2)**0.5 - c_o1 = ((cx - o1x)**2 + (cy - o1y)**2)**0.5 - c_o2 = collector_width - d_o1 = collector_width - d_o2 = ((dx - o2x)**2 + (dy - o2y)**2)**0.5 - - ac = ((cx - ax)**2 + (cy - ay)**2)**0.5 - ac = np.where(theta_ac > theta_ao1, a_o1 + c_o1, ac) - ac = np.where(theta_ac < theta_ao2, a_o2 + c_o2, ac) - - ad = ((dx - ax)**2 + (dy - ay)**2)**0.5 - ad = np.where(theta_ad > theta_ao1, a_o1 + d_o1, ad) - ad = np.where(theta_ad < theta_ao2, a_o2 + d_o2, ad) - - theta_bc = np.arctan2(cy - by, cx - bx) - theta_bd = np.arctan2(dy - by, dx - bx) - theta_ao1 = np.arctan2(o1y - by, o1x - bx) - theta_ao2 = np.arctan2(o2y - by, o2x - bx) - - bd = ((dx - bx)**2 + (dy - by)**2)**0.5 - bd = np.where(theta_bd < theta_ao2, b_o2 + d_o2, bd) - bd = np.where(theta_bd > theta_ao1, b_o1 + d_o1, bd) - - bc = ((cx - bx)**2 + (cy - by)**2)**0.5 - bc = np.where(theta_bc < theta_ao2, b_o2 + c_o2, bc) - bc = np.where(theta_bc > theta_ao1, b_o1 + c_o1, bc) - - vf = np.sum(np.clip(0.5 * (1/pitch) * ((ac + bd) - (bc + ad)), a_min=0, a_max=None), axis=0) - - return vf + 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]) + + o1 = (d[0] + collector_width * cosd(surface_tilt), d[1] - collector_width * sind(surface_tilt)) + o2 = (o1[0] + pitch, o1[1]) + + 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): + # 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 + + ac = obstructed_string_length(a, c, o1, o2) + ad = obstructed_string_length(a, d, o1, o2) + bc = obstructed_string_length(b, c, o1, o2) + bd = obstructed_string_length(b, d, o1, o2) + + vf_per_slat = 0.5 * (1/pitch) * ((ac + bd) - (bc + ad)) + vf_total = np.sum(np.clip(vf_per_slat, a_min=0, a_max=None), axis=0) + + if input_is_scalar: + vf_total = vf_total.item() + + return vf_total def _vf_poly(surface_tilt, gcr, x, delta): From b008c900f6adc79cbb7138fbc1d0239845b0dcc0 Mon Sep 17 00:00:00 2001 From: Kevin Anderson Date: Wed, 20 Sep 2023 15:24:27 -0400 Subject: [PATCH 04/11] revert row_sky and row_ground to original definitions the crossed-strings math assumed x0=0, x1=1. I am confident it could be extended to other endpoints, but reverting this for now. --- pvlib/bifacial/utils.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/pvlib/bifacial/utils.py b/pvlib/bifacial/utils.py index 47a19e0c79..fd46012be3 100644 --- a/pvlib/bifacial/utils.py +++ b/pvlib/bifacial/utils.py @@ -345,7 +345,14 @@ def vf_row_sky_2d_integ(surface_tilt, gcr, x0=0, x1=1): from x0 to x1. [unitless] ''' - result = 0.5 * (1/gcr + 1 - ((1/gcr)**2 - (2/gcr)*cosd(surface_tilt) + 1)**0.5) + u = np.abs(x1 - x0) + p0 = _vf_poly(surface_tilt, gcr, 1 - x0, -1) + p1 = _vf_poly(surface_tilt, gcr, 1 - x1, -1) + with np.errstate(divide='ignore'): + result = np.where(u < 1e-6, + vf_row_sky_2d(surface_tilt, gcr, x0), + 0.5*(1 + 1/u * (p1 - p0)) + ) return result @@ -409,5 +416,12 @@ def vf_row_ground_2d_integ(surface_tilt, gcr, x0=0, x1=1): [unitless] ''' - result = 0.5 * (1/gcr + 1 - ((1/gcr)**2 + (2/gcr)*cosd(surface_tilt) + 1)**0.5) + u = np.abs(x1 - x0) + p0 = _vf_poly(surface_tilt, gcr, x0, 1) + p1 = _vf_poly(surface_tilt, gcr, x1, 1) + with np.errstate(divide='ignore'): + result = np.where(u < 1e-6, + vf_row_ground_2d(surface_tilt, gcr, x0), + 0.5*(1 - 1/u * (p1 - p0)) + ) return result From 1bcdc8b24d5dc91d635e536ee4f696a9d7aa241e Mon Sep 17 00:00:00 2001 From: Kevin Anderson Date: Wed, 20 Sep 2023 15:41:09 -0400 Subject: [PATCH 05/11] more cleanup --- pvlib/bifacial/utils.py | 78 +++++++++++++++++++++++++---------------- 1 file changed, 47 insertions(+), 31 deletions(-) diff --git a/pvlib/bifacial/utils.py b/pvlib/bifacial/utils.py index fd46012be3..34a112170c 100644 --- a/pvlib/bifacial/utils.py +++ b/pvlib/bifacial/utils.py @@ -173,6 +173,32 @@ def vf_ground_sky_2d(rotation, gcr, x, pitch, height, max_rows=10): 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=None, vectorize=None): """ @@ -208,50 +234,40 @@ def vf_ground_sky_2d_integ(surface_tilt, gcr, height, pitch, max_rows=10, 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." + "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(surface_tilt) + k = np.arange(-max_rows, 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]) - - o1 = (d[0] + collector_width * cosd(surface_tilt), d[1] - collector_width * sind(surface_tilt)) - o2 = (o1[0] + pitch, o1[1]) - - 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): - # 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 - - ac = obstructed_string_length(a, c, o1, o2) - ad = obstructed_string_length(a, d, o1, o2) - bc = obstructed_string_length(b, c, o1, o2) - bd = obstructed_string_length(b, d, o1, o2) - - vf_per_slat = 0.5 * (1/pitch) * ((ac + bd) - (bc + ad)) - vf_total = np.sum(np.clip(vf_per_slat, a_min=0, a_max=None), axis=0) + + # 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() From 78da47f90e28703b26885fe58575a29f3f52d697 Mon Sep 17 00:00:00 2001 From: Kevin Anderson Date: Wed, 20 Sep 2023 17:31:14 -0400 Subject: [PATCH 06/11] mimic original max_rows behavior (#1867) --- pvlib/bifacial/utils.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pvlib/bifacial/utils.py b/pvlib/bifacial/utils.py index 34a112170c..c8d1f8cb48 100644 --- a/pvlib/bifacial/utils.py +++ b/pvlib/bifacial/utils.py @@ -241,9 +241,10 @@ def vf_ground_sky_2d_integ(surface_tilt, gcr, height, pitch, max_rows=10, input_is_scalar = np.isscalar(surface_tilt) collector_width = pitch * gcr - surface_tilt = np.atleast_2d(surface_tilt) + surface_tilt = np.atleast_2d(np.abs(surface_tilt)) - k = np.arange(-max_rows, max_rows+1)[:, np.newaxis] + # TODO figure out if this range is correct, or if the original code has a bug + k = np.arange(-max_rows+1, max_rows+1)[:, np.newaxis] # primary crossed string points: # a, b: boundaries of ground segment From f20d78e5f7518d3fc4355f5dfa74a9e97a17f9c2 Mon Sep 17 00:00:00 2001 From: Kevin Anderson Date: Tue, 15 Oct 2024 13:30:26 -0400 Subject: [PATCH 07/11] more max_rows behavior matching --- pvlib/bifacial/utils.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pvlib/bifacial/utils.py b/pvlib/bifacial/utils.py index c8d1f8cb48..c98bac877f 100644 --- a/pvlib/bifacial/utils.py +++ b/pvlib/bifacial/utils.py @@ -243,8 +243,9 @@ def vf_ground_sky_2d_integ(surface_tilt, gcr, height, pitch, max_rows=10, 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 - k = np.arange(-max_rows+1, max_rows+1)[:, np.newaxis] + # TODO seems like this should be np.arange(-max_rows, max_rows+1)? + # see GH #1867 + k = np.arange(-max_rows, max_rows)[:, np.newaxis] # primary crossed string points: # a, b: boundaries of ground segment From b55f74d89d446ee420d5f1cfd071a308a18c44bf Mon Sep 17 00:00:00 2001 From: Kevin Anderson Date: Tue, 15 Oct 2024 14:04:45 -0400 Subject: [PATCH 08/11] deprecate npoints and vectorize parameters --- pvlib/bifacial/infinite_sheds.py | 4 +- pvlib/tests/bifacial/test_infinite_sheds.py | 54 ++++++++++++++------- pvlib/tests/bifacial/test_utils.py | 14 ++++-- 3 files changed, 49 insertions(+), 23 deletions(-) diff --git a/pvlib/bifacial/infinite_sheds.py b/pvlib/bifacial/infinite_sheds.py index 7d84d4c3f4..a3aab2f742 100644 --- a/pvlib/bifacial/infinite_sheds.py +++ b/pvlib/bifacial/infinite_sheds.py @@ -182,7 +182,7 @@ def _shaded_fraction(solar_zenith, solar_azimuth, surface_tilt, def get_irradiance_poa(surface_tilt, surface_azimuth, solar_zenith, solar_azimuth, gcr, height, pitch, ghi, dhi, dni, albedo, model='isotropic', dni_extra=None, iam=1.0, - npoints=100, vectorize=False): + npoints=None, vectorize=None): r""" Calculate plane-of-array (POA) irradiance on one side of a row of modules. @@ -379,7 +379,7 @@ def get_irradiance(surface_tilt, surface_azimuth, solar_zenith, solar_azimuth, gcr, height, pitch, ghi, dhi, dni, albedo, model='isotropic', dni_extra=None, iam_front=1.0, iam_back=1.0, bifaciality=0.8, shade_factor=-0.02, - transmission_factor=0, npoints=100, vectorize=False): + transmission_factor=0, npoints=None, vectorize=None): """ Get front and rear irradiance using the infinite sheds model. diff --git a/pvlib/tests/bifacial/test_infinite_sheds.py b/pvlib/tests/bifacial/test_infinite_sheds.py index 017b15f943..4880f3c09a 100644 --- a/pvlib/tests/bifacial/test_infinite_sheds.py +++ b/pvlib/tests/bifacial/test_infinite_sheds.py @@ -7,6 +7,8 @@ from pvlib.bifacial import infinite_sheds from ..conftest import assert_series_equal +from pvlib._deprecation import pvlibDeprecationWarning + import pytest @@ -92,11 +94,10 @@ def test_get_irradiance_poa(): dni = 700 albedo = 0 iam = 1.0 - npoints = 100 res = infinite_sheds.get_irradiance_poa( surface_tilt, surface_azimuth, solar_zenith, solar_azimuth, gcr, height, pitch, ghi, dhi, dni, - albedo, iam=iam, npoints=npoints) + albedo, iam=iam) expected_diffuse = np.array([300.]) expected_direct = np.array([700.]) expected_global = expected_diffuse + expected_direct @@ -118,7 +119,7 @@ def test_get_irradiance_poa(): res = infinite_sheds.get_irradiance_poa( surface_tilt, surface_azimuth, solar_zenith, solar_azimuth, gcr, height, pitch, ghi, dhi, dni, - albedo, iam=iam, npoints=npoints) + albedo, iam=iam) assert np.allclose(res['poa_global'], expected_global) assert np.allclose(res['poa_diffuse'], expected_diffuse) assert np.allclose(res['poa_direct'], expected_direct) @@ -136,7 +137,7 @@ def test_get_irradiance_poa(): res = infinite_sheds.get_irradiance_poa( surface_tilt, surface_azimuth, solar_zenith, solar_azimuth, gcr, height, pitch, ghi, dhi, dni, - albedo, iam=iam, npoints=npoints) + albedo, iam=iam) assert isinstance(res, pd.DataFrame) assert_series_equal(res['poa_global'], expected_global) assert all(k in res.columns for k in [ @@ -152,8 +153,7 @@ def test__backside_tilt(): assert np.allclose(back_az, np.array([0., 330., 90., 180.])) -@pytest.mark.parametrize("vectorize", [True, False]) -def test_get_irradiance(vectorize): +def test_get_irradiance(): # singleton inputs solar_zenith = 0. solar_azimuth = 180. @@ -168,12 +168,10 @@ def test_get_irradiance(vectorize): albedo = 0. iam_front = 1.0 iam_back = 1.0 - npoints = 100 result = infinite_sheds.get_irradiance( surface_tilt, surface_azimuth, solar_zenith, solar_azimuth, gcr, height, pitch, ghi, dhi, dni, albedo, iam_front, iam_back, - bifaciality=0.8, shade_factor=-0.02, transmission_factor=0, - npoints=npoints, vectorize=vectorize) + bifaciality=0.8, shade_factor=-0.02, transmission_factor=0) expected_front_diffuse = np.array([300.]) expected_front_direct = np.array([700.]) expected_front_global = expected_front_diffuse + expected_front_direct @@ -190,12 +188,11 @@ def test_get_irradiance(vectorize): result = infinite_sheds.get_irradiance( surface_tilt, surface_azimuth, solar_zenith, solar_azimuth, gcr, height, pitch, ghi, dhi, dni, albedo, iam_front, iam_back, - bifaciality=0.8, shade_factor=-0.02, transmission_factor=0, - npoints=npoints, vectorize=vectorize) + bifaciality=0.8, shade_factor=-0.02, transmission_factor=0) result_front = infinite_sheds.get_irradiance_poa( surface_tilt, surface_azimuth, solar_zenith, solar_azimuth, gcr, height, pitch, ghi, dhi, dni, - albedo, iam=iam_front, vectorize=vectorize) + albedo, iam=iam_front) assert isinstance(result, pd.DataFrame) expected_poa_global = pd.Series( [1000., 500., result_front['poa_global'][2] * (1 + 0.8 * 0.98), @@ -203,6 +200,30 @@ def test_get_irradiance(vectorize): assert_series_equal(result['poa_global'], expected_poa_global) +def test_get_irradiance_deprecated(): + kwargs = {"surface_tilt": 0, "surface_azimuth": 0, "solar_zenith": 0, + "solar_azimuth": 0, "gcr": 0.5, "height": 1, "pitch": 1, + "ghi": 1000, "dhi": 200, "dni": 800, "albedo": 0.2} + + with pytest.warns(pvlibDeprecationWarning, match="have no effect"): + _ = infinite_sheds.get_irradiance(**kwargs, npoints=10) + + with pytest.warns(pvlibDeprecationWarning, match="have no effect"): + _ = infinite_sheds.get_irradiance(**kwargs, vectorize=True) + + +def test_get_irradiance_poa_deprecated(): + kwargs = {"surface_tilt": 0, "surface_azimuth": 0, "solar_zenith": 0, + "solar_azimuth": 0, "gcr": 0.5, "height": 1, "pitch": 1, + "ghi": 1000, "dhi": 200, "dni": 800, "albedo": 0.2} + + with pytest.warns(pvlibDeprecationWarning, match="have no effect"): + _ = infinite_sheds.get_irradiance_poa(**kwargs, npoints=10) + + with pytest.warns(pvlibDeprecationWarning, match="have no effect"): + _ = infinite_sheds.get_irradiance_poa(**kwargs, vectorize=True) + + def test_get_irradiance_limiting_gcr(): # test confirms that irradiance on widely spaced rows is approximately # the same as for a single row array @@ -219,12 +240,10 @@ def test_get_irradiance_limiting_gcr(): albedo = 1. iam_front = 1.0 iam_back = 1.0 - npoints = 100 result = infinite_sheds.get_irradiance( surface_tilt, surface_azimuth, solar_zenith, solar_azimuth, gcr, height, pitch, ghi, dhi, dni, albedo, iam_front, iam_back, - bifaciality=1., shade_factor=-0.00, transmission_factor=0., - npoints=npoints) + bifaciality=1., shade_factor=-0.00, transmission_factor=0.) expected_ground_diffuse = np.array([500.]) expected_sky_diffuse = np.array([150.]) expected_direct = np.array([0.]) @@ -263,12 +282,11 @@ def test_get_irradiance_with_haydavies(): model = 'haydavies' iam_front = 1.0 iam_back = 1.0 - npoints = 100 result = infinite_sheds.get_irradiance( surface_tilt, surface_azimuth, solar_zenith, solar_azimuth, gcr, height, pitch, ghi, dhi, dni, albedo, model, dni_extra, iam_front, iam_back, bifaciality=0.8, shade_factor=-0.02, - transmission_factor=0, npoints=npoints) + transmission_factor=0) expected_front_diffuse = np.array([151.38]) expected_front_direct = np.array([848.62]) expected_front_global = expected_front_diffuse + expected_front_direct @@ -282,4 +300,4 @@ def test_get_irradiance_with_haydavies(): surface_tilt, surface_azimuth, solar_zenith, solar_azimuth, gcr, height, pitch, ghi, dhi, dni, albedo, model, None, iam_front, iam_back, bifaciality=0.8, shade_factor=-0.02, - transmission_factor=0, npoints=npoints) + transmission_factor=0) diff --git a/pvlib/tests/bifacial/test_utils.py b/pvlib/tests/bifacial/test_utils.py index cb49c39efa..0004dc45ae 100644 --- a/pvlib/tests/bifacial/test_utils.py +++ b/pvlib/tests/bifacial/test_utils.py @@ -7,6 +7,7 @@ from pvlib.shading import masking_angle, ground_angle from pvlib.tools import cosd +from pvlib._deprecation import pvlibDeprecationWarning @pytest.fixture def test_system_fixed_tilt(): @@ -90,19 +91,26 @@ def test__vf_ground_sky_2d(test_system_fixed_tilt): assert np.isclose(vf, vfs_gnd_sky[0]) -@pytest.mark.parametrize("vectorize", [True, False]) -def test_vf_ground_sky_2d_integ(test_system_fixed_tilt, vectorize): +def test_vf_ground_sky_2d_integ(test_system_fixed_tilt): ts, pts, vfs_gnd_sky = test_system_fixed_tilt # pass rotation here since max_rows=1 for the hand-solved case in # the fixture test_system, which means the ground-to-sky view factor # isn't summed over enough rows for symmetry to hold. vf_integ = utils.vf_ground_sky_2d_integ( ts['rotation'], ts['gcr'], ts['height'], ts['pitch'], - max_rows=1, npoints=3, vectorize=vectorize) + max_rows=1, npoints=3) expected_vf_integ = np.trapz(vfs_gnd_sky, pts, axis=0) assert np.isclose(vf_integ, expected_vf_integ, rtol=0.1) +def test_vf_ground_sky_2d_integ_deprecated(): + with pytest.warns(pvlibDeprecationWarning, match="have no effect"): + _ = utils.vf_ground_sky_2d_integ(0, 0.5, 1, 1, npoints=10) + + with pytest.warns(pvlibDeprecationWarning, match="have no effect"): + _ = utils.vf_ground_sky_2d_integ(0, 0.5, 1, 1, vectorize=True) + + def test_vf_row_sky_2d(test_system_fixed_tilt): ts, _, _ = test_system_fixed_tilt # with float input, fx at top of row From ee647990800193a7dbdaf47a2af002a6e19209dd Mon Sep 17 00:00:00 2001 From: Kevin Anderson Date: Tue, 15 Oct 2024 14:10:19 -0400 Subject: [PATCH 09/11] more deprecation --- pvlib/bifacial/infinite_sheds.py | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/pvlib/bifacial/infinite_sheds.py b/pvlib/bifacial/infinite_sheds.py index a3aab2f742..e7ba131ee9 100644 --- a/pvlib/bifacial/infinite_sheds.py +++ b/pvlib/bifacial/infinite_sheds.py @@ -250,12 +250,19 @@ def get_irradiance_poa(surface_tilt, surface_azimuth, solar_zenith, on the surface that is not reflected away. [unitless] npoints : int, default 100 - Number of discretization points for calculating integrated view - factors. + + .. deprecated:: v0.11.2 + + This parameter has no effect; integrated view factors are now + calculated exactly instead of with discretized approximations. vectorize : bool, default False - If True, vectorize the view factor calculation across ``surface_tilt``. - This increases speed with the cost of increased memory usage. + + .. deprecated:: v0.11.2 + + This parameter has no effect; calculations are now vectorized + with no memory usage penality. + Returns ------- @@ -470,12 +477,18 @@ def get_irradiance(surface_tilt, surface_azimuth, solar_zenith, solar_azimuth, etc. A negative value is a reduction in back irradiance. [unitless] npoints : int, default 100 - Number of discretization points for calculating integrated view - factors. + + .. deprecated:: v0.11.2 + + This parameter has no effect; integrated view factors are now + calculated exactly instead of with discretized approximations. vectorize : bool, default False - If True, vectorize the view factor calculation across ``surface_tilt``. - This increases speed with the cost of increased memory usage. + + .. deprecated:: v0.11.2 + + This parameter has no effect; calculations are now vectorized + with no memory usage penality. Returns ------- From 19a829017b6bd61d0112a2de7a9bf423f8b73eea Mon Sep 17 00:00:00 2001 From: Kevin Anderson Date: Tue, 15 Oct 2024 14:24:50 -0400 Subject: [PATCH 10/11] replace npoints-based test with alternative --- pvlib/tests/bifacial/test_utils.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/pvlib/tests/bifacial/test_utils.py b/pvlib/tests/bifacial/test_utils.py index 666cd436cb..1f6429466c 100644 --- a/pvlib/tests/bifacial/test_utils.py +++ b/pvlib/tests/bifacial/test_utils.py @@ -92,16 +92,15 @@ def test__vf_ground_sky_2d(test_system_fixed_tilt): assert np.isclose(vf, vfs_gnd_sky[0]) -def test_vf_ground_sky_2d_integ(test_system_fixed_tilt): - ts, pts, vfs_gnd_sky = test_system_fixed_tilt - # pass rotation here since max_rows=1 for the hand-solved case in - # the fixture test_system, which means the ground-to-sky view factor - # isn't summed over enough rows for symmetry to hold. - vf_integ = utils.vf_ground_sky_2d_integ( - ts['rotation'], ts['gcr'], ts['height'], ts['pitch'], - max_rows=1, npoints=3) - expected_vf_integ = trapezoid(vfs_gnd_sky, pts, axis=0) - assert np.isclose(vf_integ, expected_vf_integ, rtol=0.1) +def test_vf_ground_sky_2d_integ(): + # test against numerical integration with vf_ground_sky_2d + x = np.linspace(0, 1, num=1000) + kwargs = dict(gcr=0.4, pitch=5.0, height=1.5) + vf_x = utils.vf_ground_sky_2d(rotation=-60, x=x, **kwargs) + vf_expected = trapezoid(vf_x, x, axis=0) + + vf_actual = utils.vf_ground_sky_2d_integ(surface_tilt=60, **kwargs) + assert np.isclose(vf_expected, vf_actual) def test_vf_ground_sky_2d_integ_deprecated(): From 09e87dfe115322baeb9ca1714b9eb03d31f53253 Mon Sep 17 00:00:00 2001 From: Kevin Anderson Date: Tue, 15 Oct 2024 14:30:45 -0400 Subject: [PATCH 11/11] lint --- pvlib/bifacial/utils.py | 1 - pvlib/tests/bifacial/test_utils.py | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/pvlib/bifacial/utils.py b/pvlib/bifacial/utils.py index d343e7e26b..69a0d86d7c 100644 --- a/pvlib/bifacial/utils.py +++ b/pvlib/bifacial/utils.py @@ -6,7 +6,6 @@ from pvlib.tools import sind, cosd, tand import warnings from pvlib._deprecation import pvlibDeprecationWarning -from scipy.integrate import trapezoid def _solar_projection_tangent(solar_zenith, solar_azimuth, surface_azimuth): diff --git a/pvlib/tests/bifacial/test_utils.py b/pvlib/tests/bifacial/test_utils.py index 1f6429466c..7cd4ef7fb0 100644 --- a/pvlib/tests/bifacial/test_utils.py +++ b/pvlib/tests/bifacial/test_utils.py @@ -10,6 +10,7 @@ from pvlib._deprecation import pvlibDeprecationWarning + @pytest.fixture def test_system_fixed_tilt(): syst = {'height': 1.0,