10000 Agrivoltaics - PAR diffuse fraction model by echedey-ls · Pull Request #2048 · pvlib/pvlib-python · GitHub
[go: up one dir, main page]

Skip to content

Agrivoltaics - PAR diffuse fraction model #2048

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 37 commits into from
Jun 20, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
90dd1e0
New PAR module with spitters_relationship
echedey-ls May 3, 2024
8cc7ffc
Merge branch 'main' into par-diffuse-fraction-of-global-par
echedey-ls May 6, 2024
ae63127
Update v0.11.0.rst
echedey-ls May 12, 2024
5880453
linter
echedey-ls May 12, 2024
5af860a
API update
echedey-ls May 12, 2024
bdc2303
Example rendering
echedey-ls May 12, 2024
aec7870
Update test_par.py
echedey-ls May 12, 2024
02ebe81
Apply suggestions from code review (Adam)
echedey-ls May 24, 2024
c5d133d
Update par.py
echedey-ls May 24, 2024
149961e
Move function to spectrum (mismatch.py)
echedey-ls May 24, 2024
eff3a25
Improve units formatting
echedey-ls May 24, 2024
d18827e
Split legends
echedey-ls May 24, 2024
18619a4
Remove api page, move to spectrum index
echedey-ls May 24, 2024
feded95
Update v0.11.0.rst
echedey-ls May 24, 2024
b9aa23e
Merge branch 'main' into par-diffuse-fraction-of-global-par
echedey-ls May 25, 2024
1c6d416
Move to ``irradiance.py``
echedey-ls May 30, 2024
899844c
Flake8 :knife:
echedey-ls May 30, 2024
9268a44
Fix trigonometry - double testing with a spreadsheet
echedey-ls Jun 14, 2024
bce50b6
Move section of PAR
echedey-ls Jun 14, 2024
2825e2c
Merge branch 'main' into par-diffuse-fraction-of-global-par
echedey-ls Jun 14, 2024
764bf8b
I should read more carefully
echedey-ls Jun 14, 2024
0e0f836
Update decomposition.rst
echedey-ls Jun 18, 2024
0d23fe0
Apply suggestions from code review (Cliff)
echedey-ls Jun 18, 2024
a7c84b5
Merge branch 'par-diffuse-fraction-of-global-par' of https://github.c…
echedey-ls Jun 18, 2024
d1bb64b
More docs refurbishment
echedey-ls Jun 18, 2024
eeef154
Rename to `diffuse_par_spitters`
echedey-ls Jun 18, 2024
6082fe7
`global` -> `broadband`
echedey-ls Jun 18, 2024
94df738
Merge branch 'main' into par-diffuse-fraction-of-global-par
echedey-ls Jun 18, 2024
ac9d45c
Code review from Adam, first batch
echedey-ls Jun 19, 2024
77f3ba8
Apply trigonometric property
echedey-ls Jun 19, 2024
a6c3c6b
Merge branch 'main' into par-diffuse-fraction-of-global-par
echedey-ls Jun 19, 2024
f5449d7
Fix merge - linter
echedey-ls Jun 19, 2024
bafe63b
Forgot to apply this comment
echedey-ls Jun 19, 2024
1a5d0d9
Remove model from eq
echedey-ls Jun 19, 2024
189efe5
Dailies, insolation instead of instant, irradiance values
echedey-ls Jun 20, 2024
7c28be6
More docs refurbishment
echedey-ls Jun 20, 2024
59faee0
Review from Cliff
AdamRJensen Jun 20, 2024
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
Prev Previous commit
Next Next commit
Move to irradiance.py
  • Loading branch information
echedey-ls committed May 30, 2024
commit 1c6d41623f9aa827e04574ccc5fb4f8bbbc82a94
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
# with respect to the total PAR is important in agrivoltaic systems, where
# crops are grown under solar panels. The diffuse fraction of PAR can be
# calculated using the Spitter's relationship [1]_ implemented in
# :py:func:`~pvlib.spectrum.spitters_relationship`.
# :py:func:`~pvlib.irradiance.spitters_relationship`.
# This model requires the solar zenith angle and the fraction of the global
# radiation that is diffuse as inputs.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# radiation that is diffuse as inputs.
# irradiance that is diffuse as inputs.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To (perhaps) head off a discussion of whether "radiation" or "irradiance" should be used here: radiation is usually used when referring to the source, and irradiance (or irradiation) when referring to the receiver. So the sun emits radiation, and a tilted plane on the earth's surface receives solar irradiation. Either perspective seems OK to me in the context of this PR. But mixing PAR (..."radiation") with "irradiance" may be confusing to some.

#
Expand All @@ -26,7 +26,7 @@
# pp. 222-223 of [1]_.
#
# The key function used in this example is
# :py:func:`pvlib.spectrum.spitters_relationship` to calculate the diffuse PAR
# :py:func:`pvlib.irradiance.spitters_relationship` to calculate the diffuse PAR
# fraction, as a function of global diffuse fraction and solar zenith.
#
# References
Expand Down Expand Up @@ -83,7 +83,7 @@
tmy["diffuse_fraction"] = tmy["dhi"] / tmy["ghi"]

# Calculate diffuse PAR fraction using Spitter's relationship
par["diffuse_fraction"] = pvlib.spectrum.spitters_relationship(
par["diffuse_fraction"] = pvlib.irradiance.spitters_relationship(
solar_position["zenith"], tmy["diffuse_fraction"]
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,3 @@ Spectrum
spectrum.spectral_factor_caballero
spectrum.spectral_factor_firstsolar
spectrum.spectral_factor_sapm
spectrum.spitters_relationship
9 changes: 9 additions & 0 deletions docs/sphinx/source/reference/irradiance/components.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,12 @@ Decomposing and combining irradiance
irradiance.get_ground_diffuse
irradiance.dni
irradiance.complete_irradiance


Photosynthetically Active Radiation
-----------------------------------

.. autosummary::
:toctree: ../generated/

irradiance.spitters_relationship
2 changes: 1 addition & 1 deletion docs/sphinx/source/whatsnew/v0.11.0.rst
10000 Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Enhancements
shade perpendicular to ``axis_azimuth``. The function is applicable to both
fixed-tilt and one-axis tracking systems.
(:issue:`1689`, :pull:`1725`, :pull:`1962`)
- Add function :py:func:`pvlib.spectrum.spitters_relationship` to calculate the
- Add function :py:func:`pvlib.irradiance.spitters_relationship` to calculate the
diffuse fraction of Photosynthetically Active Radiation (PAR) from the
global diffuse fraction and the solar zenith.
(:issue:`2047`, :pull:`2048`)
Expand Down
66 changes: 66 additions & 0 deletions pvlib/irradiance.py
Original file line number Diff line number Diff line change
Expand Up @@ -3766,3 +3766,69 @@ def louche(ghi, solar_zenith, datetime_or_doy, max_zenith=90):
data = pd.DataFrame(data, index=datetime_or_doy)

return data


def spitters_relationship(solar_zenith, global_diffuse_fraction):
r"""
Derive the diffuse fraction of photosynthetically active radiation (PAR)
respect to the global radiation diffuse fraction.

The relationship is based on the work of Spitters et al. (1986) [1]_.

Parameters
----------
solar_zenith : numeric
Solar zenith angle. Degrees.

global_diffuse_fraction : numeric
Fraction of the global radiation that is diffuse. Unitless [0, 1].

Returns
-------
par_diffuse_fraction : numeric
Photosynthetically Active Radiation diffuse fraction. Unitless [0, 1].

Notes
-----
The relationship is given by equations (9) & (10) in [1]_ and (1) in [2]_:

.. math::

k_{diffuse\_PAR}^{model} = \frac{PAR_{diffuse}}{PAR_{total}} =
\frac{\left[1 + 0.3 \left(1 - \left(k_d^{model}\right) ^2\right)\right]
k_d^{model}}
{1 + \left(1 - \left(k_d^{model}\right)^2\right) \cos ^2 (90 - \beta)
\cos ^3 \beta}

where :math:`k_d^{model}` is the diffuse fraction of the global radiation,
provided by some model.

A comparison of different models performance for the diffuse fraction of
the global irradiance can be found in [2]_ in the context of Sweden.

References
----------
.. [1] C. J. T. Spitters, H. A. J. M. Toussaint, and J. Goudriaan,
'Separating the diffuse and direct component of global radiation and its
implications for modeling canopy photosynthesis Part I. Components of
incoming radiation', Agricultural and Forest Meteorology, vol. 38,
no. 1, pp. 217-229, Oct. 1986, :doi:`10.1016/0168-1923(86)90060-2`.
.. [2] S. Ma Lu et al., 'Photosynthetically active radiation decomposition
models for agrivoltaic systems applications', Solar Energy, vol. 244,
pp. 536-549, Sep. 2022, :doi:`10.1016/j.solener.2022.05.046`.
"""
# notation change:
# cosd(90-x) = sind(x) and 90-solar_elevation = solar_zenith
sind_solar_zenith = tools.sind(solar_zenith)
cosd_solar_elevation = tools.cosd(90 - solar_zenith)
par_diffuse_fraction = (
(1 + 0.3 * (1 - global_diffuse_fraction**2))
* global_diffuse_fraction
/ (
1
+ (1 - global_diffuse_fraction**2)
* sind_solar_zenith**2
* cosd_solar_elevation**3
)
)
return par_diffuse_fraction
1 change: 0 additions & 1 deletion pvlib/spectrum/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,4 @@
spectral_factor_caballero,
spectral_factor_firstsolar,
spectral_factor_sapm,
spitters_relationship,
)
66 changes: 0 additions & 66 deletions pvlib/spectrum/mismatch.py
Original file line number Diff line number Diff line change
Expand Up @@ -572,69 +572,3 @@ def spectral_factor_caballero(precipitable_water, airmass_absolute, aod500,
)
modifier = f_AM + f_AOD + f_PW # Eq 5
return modifier


def spitters_relationship(solar_zenith, global_diffuse_fraction):
r"""
Derive the diffuse fraction of photosynthetically active radiation (PAR)
respect to the global radiation diffuse fraction.

The relationship is based on the work of Spitters et al. (1986) [1]_.

Parameters
----------
solar_zenith : numeric
Solar zenith angle. Degrees.

global_diffuse_fraction : numeric
Fraction of the global radiation that is diffuse. Unitless [0, 1].

Returns
-------
par_diffuse_fraction : numeric
Photosynthetically Active Radiation diffuse fraction. Unitless [0, 1].

Notes
-----
The relationship is given by equations (9) & (10) in [1]_ and (1) in [2]_:

.. math::

k_{diffuse\_PAR}^{model} = \frac{PAR_{diffuse}}{PAR_{total}} =
\frac{\left[1 + 0.3 \left(1 - \left(k_d^{model}\right) ^2\right)\right]
k_d^{model}}
{1 + \left(1 - \left(k_d^{model}\right)^2\right) \cos ^2 (90 - \beta)
\cos ^3 \beta}

where :math:`k_d^{model}` is the diffuse fraction of the global radiation,
provided by some model.

A comparison of different models performance for the diffuse fraction of
the global irradiance can be found in [2]_ in the context of Sweden.

References
----------
.. [1] C. J. T. Spitters, H. A. J. M. Toussaint, and J. Goudriaan,
'Separating the diffuse and direct component of global radiation and its
implications for modeling canopy photosynthesis Part I. Components of
incoming radiation', Agricultural and Forest Meteorology, vol. 38,
no. 1, pp. 217-229, Oct. 1986, :doi:`10.1016/0168-1923(86)90060-2`.
.. [2] S. Ma Lu et al., 'Photosynthetically active radiation decomposition
models for agrivoltaic systems applications', Solar Energy, vol. 244,
pp. 536-549, Sep. 2022, :doi:`10.1016/j.solener.2022.05.046`.
"""
# notation change:
# cosd(90-x) = sind(x) and 90-solar_elevation = solar_zenith
sind_solar_zenith = sind(solar_zenith)
cosd_solar_elevation = cosd(90 - solar_zenith)
par_diffuse_fraction = (
(1 + 0.3 * (1 - global_diffuse_fraction**2))
* global_diffuse_fraction
/ (
1
+ (1 - global_diffuse_fraction**2)
* sind_solar_zenith**2
* cosd_solar_elevation**3
)
)
return par_diffuse_fraction
22 changes: 22 additions & 0 deletions pvlib/tests/test_irradiance.py
Original file line number Diff line number Diff line change
Expand Up @@ -1406,3 +1406,25 @@ def test_louche():
out = irradiance.louche(ghi, zenith, index)

assert_frame_equal(out, expected)


def test_spitters_relationship():
solar_zenith, global_diffuse_fraction = np.meshgrid(
[90, 85, 75, 60, 40, 30, 10, 0], [0.01, 0.1, 0.3, 0.6, 0.8, 0.99]
)
solar_zenith = solar_zenith.ravel()
global_diffuse_fraction = global_diffuse_fraction.ravel()
result = irradiance.spitters_relationship(
solar_zenith, global_diffuse_fraction
)
expected = np.array([
0.00650018, 0.00656213, 0.00706211, 0.00874170, 0.01171437, 0.01260581,
0.01299765, 0.01299970, 0.06517588, 0.06579393, 0.07077986, 0.08750105,
0.11699064, 0.12580782, 0.12967973, 0.12970000, 0.19994764, 0.20176275,
0.21635259, 0.26460255, 0.34722693, 0.37134002, 0.38184514, 0.38190000,
0.43609756, 0.43933488, 0.46497584, 0.54521789, 0.66826809, 0.70117647,
0.71512774, 0.71520000, 0.65176471, 0.65503875, 0.68042968, 0.75414541,
0.85271445, 0.87653894, 0.88634962, 0.88640000, 0.97647838, 0.97683827,
0.97952006, 0.98634857, 0.99374028, 0.99529135, 0.99590717, 0.9959103
]) # fmt: skip
assert_allclose(result, expected, atol=1e-8)
22 changes: 0 additions & 22 deletions pvlib/tests/test_spectrum.py
Original file line number Diff line number Diff line change
Expand Up @@ -315,25 +315,3 @@ def test_spectral_factor_caballero_supplied_ambiguous():
with pytest.raises(ValueError):
spectrum.spectral_factor_caballero(1, 1, 1, module_type=None,
coefficients=None)


def test_spitters_relationship():
solar_zenith, global_diffuse_fraction = np.meshgrid(
[90, 85, 75, 60, 40, 30, 10, 0], [0.01, 0.1, 0.3, 0.6, 0.8, 0.99]
)
solar_zenith = solar_zenith.ravel()
global_diffuse_fraction = global_diffuse_fraction.ravel()
result = spectrum.spitters_relationship(
solar_zenith, global_diffuse_fraction
)
expected = np.array([
0.00650018, 0.00656213, 0.00706211, 0.00874170, 0.01171437, 0.01260581,
0.01299765, 0.01299970, 0.06517588, 0.06579393, 0.07077986, 0.08750105,
0.11699064, 0.12580782, 0.12967973, 0.12970000, 0.19994764, 0.20176275,
0.21635259, 0.26460255, 0.34722693, 0.37134002, 0.38184514, 0.38190000,
0.43609756, 0.43933488, 0.46497584, 0.54521789, 0.66826809, 0.70117647,
0.71512774, 0.71520000, 0.65176471, 0.65503875, 0.68042968, 0.75414541,
0.85271445, 0.87653894, 0.88634962, 0.88640000, 0.97647838, 0.97683827,
0.97952006, 0.98634857, 0.99374028, 0.99529135, 0.99590717, 0.9959103
]) # fmt: skip
assert_allclose(result, expected, atol=1e-8)
0