Skip to content

Correct infinite_sheds view factor from row to sky and ground; expose vf functions in bifacial.utils #1666

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

Merged
merged 27 commits into from
Jun 23, 2023
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
f684623
correct view factor from row to sky and ground
mikofski Feb 15, 2023
ef346dd
update latex math in vf_row_gnd
mikofski Feb 15, 2023
510e8ad
revise vf_row_sky and test
Mar 22, 2023
fdc7a63
remake tests for vf_row_ground
cwhanse Mar 23, 2023
784a977
fix my mistakes
cwhanse Mar 23, 2023
c48b35c
fix row_ground_integ
cwhanse Mar 23, 2023
253bb89
new vf functions in utils
cwhanse Mar 24, 2023
2a9bff1
merge 0.9.5 upstream
cwhanse Mar 24, 2023
5837fd3
stickler
cwhanse Mar 24, 2023
3246196
use functions from utils
cwhanse Mar 24, 2023
a0451b7
documents
cwhanse Mar 24, 2023
a77dbb0
revise whatsnew
cwhanse Mar 24, 2023
09f3f86
add technical document
cwhanse Mar 24, 2023
e7b7d5f
doc edits
cwhanse Apr 3, 2023
fc6c7d9
fix conflicts
cwhanse May 15, 2023
df56b3a
Merge branch 'fix_vf_row_sky_gh1665' of https://github.com/mikofski/p…
cwhanse May 15, 2023
6a026ad
move fns to utils
cwhanse May 15, 2023
47bd4de
more fcn shuffling
cwhanse May 15, 2023
7e1be57
update docs
cwhanse May 15, 2023
02bc783
reorder arguments
cwhanse May 16, 2023
fbf3d93
docstring edits
cwhanse May 16, 2023
bdb80ea
add defaults to _integ functions
cwhanse May 16, 2023
ccac4b2
Merge branch 'main' into fix_vf_row_sky_gh1665
cwhanse May 24, 2023
710a5bd
Merge branch 'main' of https://github.com/pvlib/pvlib-python into fix…
cwhanse Jun 7, 2023
667e3f5
Merge branch 'fix_vf_row_sky_gh1665' of https://github.com/mikofski/p…
cwhanse Jun 7, 2023
a11257d
Merge remote-tracking branch 'upstream/main' into pr/1666
kandersolar Jun 23, 2023
b6996e5
fix whatsnew formatting issues
kandersolar Jun 23, 2023
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
Binary file not shown.
11 changes: 11 additions & 0 deletions docs/sphinx/source/reference/bifacial.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,14 @@ Functions for calculating front and back surface irradiance
bifacial.pvfactors.pvfactors_timeseries
bifacial.infinite_sheds.get_irradiance
bifacial.infinite_sheds.get_irradiance_poa

Utility functions for bifacial modeling

.. autosummary::
:toctree: generated/

bifacial.utils.vf_row_sky_2d
bifacial.utils.vf_row_sky_2d_integ
bifacial.utils.vf_row_ground_2d
bifacial.utils.vf_row_ground_2d_integ

4 changes: 4 additions & 0 deletions docs/sphinx/source/whatsnew/v0.9.6.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ Enhancements

Bug fixes
~~~~~~~~~
* Corrects an error in view factor calculations which are part of
:py:func:`pvlib.bifacial.infinite_sheds.get_irradiance` (:issue:`1665`, :pull:`1666`)


Testing
Expand All @@ -35,4 +37,6 @@ Requirements

Contributors
~~~~~~~~~~~~
* Mark Mikofski (:ghuser:`mikofski`)
* Cliff Hansen (:ghuser:`cwhanse`)

220 changes: 39 additions & 181 deletions pvlib/bifacial/infinite_sheds.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
import pandas as pd
from pvlib.tools import cosd, sind, tand
from pvlib.bifacial import utils
from pvlib.shading import masking_angle
from pvlib.irradiance import beam_component, aoi, haydavies


def _vf_ground_sky_integ(surface_tilt, surface_azimuth, gcr, height,
pitch, max_rows=10, npoints=100, vectorize=False):
"""
Expand Down Expand Up @@ -94,90 +94,42 @@ def _poa_ground_shadows(poa_ground, f_gnd_beam, df, vf_gnd_sky):
return poa_ground * (f_gnd_beam*(1 - df) + df*vf_gnd_sky)


def _vf_row_sky_integ(f_x, surface_tilt, gcr, npoints=100):
def _poa_sky_diffuse_pv(dhi, gcr, surface_tilt):
"""
Integrated view factors from the shaded and unshaded parts of
the row slant height to the sky.

Parameters
----------
f_x : numeric
Fraction of row slant height from the bottom that is shaded. [unitless]
surface_tilt : numeric
Surface tilt angle in degrees from horizontal, e.g., surface facing up
= 0, surface facing horizon = 90. [degree]
gcr : float
Ratio of row slant length to row spacing (pitch). [unitless]
npoints : int, default 100
Number of points for integration. [unitless]
Sky diffuse POA from averaged view factors combined for both shaded and
unshaded parts of the surface.

Returns
-------
vf_shade_sky_integ : numeric
Integrated view factor from the shaded part of the row to the sky.
[unitless]
vf_noshade_sky_integ : numeric
Integrated view factor from the unshaded part of the row to the sky.
[unitless]
A detailed calculation would be

Notes
-----
The view factor to the sky at a point x along the row slant height is
given by
dhi * (f_x * vf_shade_sky_integ + (1 - f_x) * vf_noshade_sky_integ)

.. math ::
\\large{f_{sky} = \frac{1}{2} \\left(\\cos\\left(\\psi_t\\right) +
\\cos \\left(\\beta\\right) \\right)
where vf_shade_sky_integ is the average view factor between 0 and f_x
(the shaded portion). But the average view factor is

where :math:`\\psi_t` is the angle from horizontal of the line from point
x to the top of the facing row, and :math:`\\beta` is the surface tilt.
1/(f_x - 0) Integral_0^f_x vf(x) dx

View factors are integrated separately over shaded and unshaded portions
of the row slant height.
so the detailed calculation is equivalent to

"""
# handle Series inputs
surface_tilt = np.array(surface_tilt)
cst = cosd(surface_tilt)
# shaded portion
x = np.linspace(0, f_x, num=npoints)
psi_t_shaded = masking_angle(surface_tilt, gcr, x)
y = 0.5 * (cosd(psi_t_shaded) + cst)
# integrate view factors from each point in the discretization. This is an
# improvement over the algorithm described in [2]
vf_shade_sky_integ = np.trapz(y, x, axis=0)
# unshaded portion
x = np.linspace(f_x, 1., num=npoints)
psi_t_unshaded = masking_angle(surface_tilt, gcr, x)
y = 0.5 * (cosd(psi_t_unshaded) + cst)
vf_noshade_sky_integ = np.trapz(y, x, axis=0)
return vf_shade_sky_integ, vf_noshade_sky_integ


def _poa_sky_diffuse_pv(f_x, dhi, vf_shade_sky_integ, vf_noshade_sky_integ):
"""
Sky diffuse POA from integrated view factors combined for both shaded and
unshaded parts of the surface.
dhi * 1/(1 - 0) Integral_0^1 vf(x) dx

Parameters
----------
f_x : numeric
Fraction of row slant height from the bottom that is shaded. [unitless]
dhi : numeric
Diffuse horizontal irradiance (DHI). [W/m^2]
vf_shade_sky_integ : numeric
Integrated view factor from the shaded part of the row to the sky.
[unitless]
vf_noshade_sky_integ : numeric
Integrated view factor from the unshaded part of the row to the sky.
gcr : float
ground coverage ratio, ratio of row slant length to row spacing.
[unitless]
surface_tilt : numeric
Surface tilt angle in degrees from horizontal, e.g., surface facing up
= 0, surface facing horizon = 90. [degree]

Returns
-------
poa_sky_diffuse_pv : numeric
Total sky diffuse irradiance incident on the PV surface. [W/m^2]
"""
return dhi * (f_x * vf_shade_sky_integ + (1 - f_x) * vf_noshade_sky_integ)
vf_integ = utils.vf_row_sky_2d_integ(0., 1., gcr, surface_tilt)
return dhi * vf_integ


def _ground_angle(x, surface_tilt, gcr):
Expand Down Expand Up @@ -221,121 +173,45 @@ def _ground_angle(x, surface_tilt, gcr):
return np.rad2deg(psi)


def _vf_row_ground(x, surface_tilt, gcr):
"""
View factor from a point x on the row to the ground.

Parameters
----------
x : numeric
Fraction of row slant height from the bottom. [unitless]
surface_tilt : numeric
Surface tilt angle in degrees from horizontal, e.g., surface facing up
= 0, surface facing horizon = 90. [degree]
gcr : float
Ground coverage ratio, ratio of row slant length to row spacing.
[unitless]

Returns
-------
vf : numeric
View factor from the point at x to the ground. [unitless]

"""
cst = cosd(surface_tilt)
# angle from horizontal at the point x on the row slant height to the
# bottom of the facing row
psi_t_shaded = _ground_angle(x, surface_tilt, gcr)
# view factor from the point on the row to the ground
return 0.5 * (cosd(psi_t_shaded) - cst)


def _vf_row_ground_integ(f_x, surface_tilt, gcr, npoints=100):
def _poa_ground_pv(poa_ground, gcr, surface_tilt):
"""
View factors to the ground from shaded and unshaded parts of a row.
Reduce ground-reflected irradiance to account for limited view of the
ground from the row surface.

Parameters
----------
f_x : numeric
Fraction of row slant height from the bottom that is shaded. [unitless]
surface_tilt : numeric
Surface tilt angle in degrees from horizontal, e.g., surface facing up
= 0, surface facing horizon = 90. [degree]
gcr : float
Ground coverage ratio, ratio of row slant length to row spacing.
[unitless]
npoints : int, default 100
Number of points for integration. [unitless]
A detailed calculation would be

Returns
-------
vf_shade_ground_integ : numeric
View factor from the shaded portion of the row to the ground.
[unitless]
vf_noshade_ground_integ : numeric
View factor from the unshaded portion of the row to the ground.
[unitless]
poa_ground *
(f_x * vf_shade_ground_integ + (1 - f_x) * vf_noshade_ground_integ)

Notes
-----
The view factor to the ground at a point x along the row slant height is
given by
where vf_shade_ground_integ is the average view factor between 0 and f_x
(the shaded portion). But the average view factor is

.. math ::
\\large{f_{gr} = \frac{1}{2} \\left(\\cos\\left(\\psi_t\\right) -
\\cos \\left(\\beta\\right) \\right)
1/(f_x - 0) Integral_0^f_x vf(x) dx

where :math:`\\psi_t` is the angle from horizontal of the line from point
x to the bottom of the facing row, and :math:`\\beta` is the surface tilt.
so the detailed calculation is equivalent to

Each view factor is integrated over the relevant portion of the row
slant height.
"""
# handle Series inputs
surface_tilt = np.array(surface_tilt)
# shaded portion of row slant height
x = np.linspace(0, f_x, num=npoints)
# view factor from the point on the row to the ground
y = _vf_row_ground(x, surface_tilt, gcr)
# integrate view factors along the shaded portion of the row slant height.
# This is an improvement over the algorithm described in [2]
vf_shade_ground_integ = np.trapz(y, x, axis=0)

# unshaded portion of row slant height
x = np.linspace(f_x, 1., num=npoints)
# view factor from the point on the row to the ground
y = _vf_row_ground(x, surface_tilt, gcr)
# integrate view factors along the unshaded portion.
# This is an improvement over the algorithm described in [2]
vf_noshade_ground_integ = np.trapz(y, x, axis=0)

return vf_shade_ground_integ, vf_noshade_ground_integ


def _poa_ground_pv(f_x, poa_ground, f_gnd_pv_shade, f_gnd_pv_noshade):
"""
Reduce ground-reflected irradiance to account for limited view of the
ground from the row surface.
poa_ground * 1/(1 - 0) Integral_0^1 vf(x) dx

Parameters
----------
f_x : numeric
Fraction of row slant height from the bottom that is shaded. [unitless]
poa_ground : numeric
Ground-reflected irradiance that would reach the row surface if the
full ground was visible. poa_gnd_sky accounts for limited view of the
sky from the ground. [W/m^2]
f_gnd_pv_shade : numeric
fraction of ground visible from shaded part of PV surface. [unitless]
f_gnd_pv_noshade : numeric
fraction of ground visible from unshaded part of PV surface. [unitless]
gcr : float
ground coverage ratio, ratio of row slant length to row spacing.
[unitless]
surface_tilt : numeric
Surface tilt angle in degrees from horizontal, e.g., surface facing up
= 0, surface facing horizon = 90. [degree]

Returns
-------
numeric
Ground diffuse irradiance on the row plane. [W/m^2]
"""
return poa_ground * (f_x * f_gnd_pv_shade + (1 - f_x) * f_gnd_pv_noshade)
vf_integ = utils.vf_row_ground_2d_integ(0., 1., gcr, surface_tilt)
return poa_ground * vf_integ


def _shaded_fraction(solar_zenith, solar_azimuth, surface_tilt,
Expand Down Expand Up @@ -548,25 +424,8 @@ def get_irradiance_poa(surface_tilt, surface_azimuth, solar_zenith,
f_x = _shaded_fraction(solar_zenith, solar_azimuth, surface_tilt,
surface_azimuth, gcr)

# Integrated view factors to the sky from the shaded and unshaded parts of
# the row slant height
# Differs from [1] Eq. 15 and Eq. 16. Here, we integrate over each
# interval (shaded or unshaded) rather than averaging values at each
# interval's end points.
vf_shade_sky, vf_noshade_sky = _vf_row_sky_integ(
f_x, surface_tilt, gcr, npoints)

# view factors from the ground to shaded and unshaded portions of the row
# slant height
# Differs from [1] Eq. 17 and Eq. 18. Here, we integrate over each
# interval (shaded or unshaded) rather than averaging values at each
# interval's end points.
f_gnd_pv_shade, f_gnd_pv_noshade = _vf_row_ground_integ(
f_x, surface_tilt, gcr, npoints)

# Total sky diffuse received by both shaded and unshaded portions
poa_sky_pv = _poa_sky_diffuse_pv(
f_x, dhi, vf_shade_sky, vf_noshade_sky)
poa_sky_pv = _poa_sky_diffuse_pv(dhi, gcr, surface_tilt)

# irradiance reflected from the ground before accounting for shadows
# and restricted views
Expand All @@ -591,8 +450,7 @@ def get_irradiance_poa(surface_tilt, surface_azimuth, solar_zenith,
# the usual ground-reflected irradiance includes the single row to ground
# view factor (1 - cos(tilt))/2, and Eq. 10, 11 and later multiply
# this quantity by a ratio of view factors.
poa_gnd_pv = _poa_ground_pv(
f_x, ground_diffuse, f_gnd_pv_shade, f_gnd_pv_noshade)
poa_gnd_pv = _poa_ground_pv(ground_diffuse, gcr, surface_tilt)

# add sky and ground-reflected irradiance on the row by irradiance
# component
Expand Down
Loading