From 90dd1e06c6837d15de0cefc445740dcbda83c955 Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Fri, 3 May 2024 03:27:43 +0200 Subject: [PATCH 01/31] New PAR module with spitters_relationship --- docs/examples/agrivoltaics/README.rst | 2 + .../plot_diffuse_PAR_Spitters_relationship.py | 131 ++++++++++++++++++ pvlib/__init__.py | 1 + pvlib/par.py | 72 ++++++++++ pvlib/tests/test_par.py | 28 ++++ 5 files changed, 234 insertions(+) create mode 100644 docs/examples/agrivoltaics/README.rst create mode 100644 docs/examples/agrivoltaics/plot_diffuse_PAR_Spitters_relationship.py create mode 100644 pvlib/par.py create mode 100644 pvlib/tests/test_par.py diff --git a/docs/examples/agrivoltaics/README.rst b/docs/examples/agrivoltaics/README.rst new file mode 100644 index 0000000000..9c3c2ad045 --- /dev/null +++ b/docs/examples/agrivoltaics/README.rst @@ -0,0 +1,2 @@ +Agrivoltaic Systems Modelling +----------------------------- diff --git a/docs/examples/agrivoltaics/plot_diffuse_PAR_Spitters_relationship.py b/docs/examples/agrivoltaics/plot_diffuse_PAR_Spitters_relationship.py new file mode 100644 index 0000000000..2a17acc798 --- /dev/null +++ b/docs/examples/agrivoltaics/plot_diffuse_PAR_Spitters_relationship.py @@ -0,0 +1,131 @@ +""" +Calculating the diffuse PAR using Spitter's relationship +========================================================= + +This example demonstrates how to calculate the diffuse photosynthetically +active radiation (PAR) using Spitter's relationship. +""" + +# %% +# The photosynthetically active radiation (PAR) is a key component in the +# photosynthesis process of plants. As in photovoltaic systems, the PAR is +# divided into direct and diffuse components. The diffuse fraction of PAR +# 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.par.spitters_relationship`. +# This model requires the solar zenith angle and the fraction of the global +# radiation that is diffuse as inputs. +# +# .. note:: +# Understanding the distinction between the global radiation and the PAR is +# a key concept. The global radiation is the total amount of solar radiation +# that is usually accounted for in PV applications, while the PAR is a +# measurement of a narrower range of wavelengths that are used in +# photosynthesis. See section on *Photosynthetically Active Radiation* in +# pp. 222-223 of [1]_. +# +# Key functions used in this example: +# - :py:func:`pvlib.par.spitters_relationship` to calculate the diffuse PAR +# fraction, as a function of global diffuse fraction and solar zenith. +# +# 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`. +# +# Read some example data +# ^^^^^^^^^^^^^^^^^^^^^^ + +import pvlib +import pandas as pd +import matplotlib.pyplot as plt +from matplotlib.dates import DateFormatter +from pathlib import Path + +# Read some sample data +DATA_FILE = Path(pvlib.__path__[0]).joinpath("data", "723170TYA.CSV") + +tmy, metadata = pvlib.iotools.read_tmy3( + DATA_FILE, coerce_year=1990, map_variables=True +) +tmy = tmy.filter( + ["ghi", "dhi", "dni", "pressure", "temp_air"] +) # remaining data is not needed +tmy = tmy[ + "1990-04-11T06":"1990-04-11T22" +] # select a single day for this example + +solar_position = pvlib.solarposition.get_solarposition( + # TMY timestamp is at end of hour, so shift to center of interval + tmy.index.shift(freq="-30T"), + latitude=metadata["latitude"], + longitude=metadata["longitude"], + altitude=metadata["altitude"], + pressure=tmy["pressure"] * 100, # convert from millibar to Pa + temperature=tmy["temp_air"], +) +solar_position.index = tmy.index # reset index to end of the hour + +# %% +# Calculate Photosynthetically Active Radiation +# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +# The total PAR can be approximated as 0.50 times the global horizontal +# irradiance (GHI) for solar elevation higher that :math:`10^\circ`. +# See section on *Photosynthetically Active Radiation* in pp. 222-223 of [1]_. + +par = pd.DataFrame({"total": 0.50 * tmy["ghi"]}, index=tmy.index) + +# Calculate global irradiance diffuse fraction, input of the Spitter's model +tmy["diffuse_fraction"] = tmy["dhi"] / tmy["ghi"] + +# Calculate diffuse PAR fraction using Spitter's relationship +par["diffuse_fraction"] = pvlib.par.spitters_relationship( + solar_position["zenith"], tmy["diffuse_fraction"] +) + +# Finally, calculate the diffuse PAR +par["diffuse"] = par["total"] * par["diffuse_fraction"] +par[solar_position["zenith"] > 80] = 0 # set to zero for elevation < 10 degrees + +# %% +# Plot the results +# ^^^^^^^^^^^^^^^^ +# Irradiances on left axis, diffuse fractions on right axis + +fig, ax_l = plt.subplots(figsize=(12, 6)) +ax_l.set( + xlabel="Time", + ylabel="Irradiance $[W/m^2]$", + title="Diffuse PAR using Spitter's relationship", +) +ax_l.xaxis.set_major_formatter(DateFormatter("%H:%M", tz=tmy.index.tz)) +ax_l.plot(tmy.index, tmy["ghi"], label="Global: total", color="deepskyblue") +ax_l.plot(tmy.index, tmy["dhi"], label="Global: diffuse", color="skyblue", linestyle="-.") +ax_l.plot(tmy.index, par["total"], label="PAR: total", color="orangered") +ax_l.plot(tmy.index, par["diffuse"], label="PAR: diffuse", color="coral", linestyle="-.") +ax_l.grid() + +ax_r = ax_l.twinx() +ax_r.set(ylabel="Diffuse fraction") +ax_r.plot( + tmy.index, + tmy["diffuse_fraction"], + label="Global diffuse fraction", + color="plum", + linestyle=":", +) +ax_r.plot( + tmy.index, + par["diffuse_fraction"], + label="PAR diffuse fraction", + color="chocolate", + linestyle=":", +) + +lines = ax_l.get_lines() + ax_r.get_lines() +plt.legend(lines, (line.get_label() for line in lines)) +plt.show() diff --git a/pvlib/__init__.py b/pvlib/__init__.py index 413af8f607..6a3c62b842 100644 --- a/pvlib/__init__.py +++ b/pvlib/__init__.py @@ -14,6 +14,7 @@ ivtools, location, modelchain, + par, pvarray, pvsystem, scaling, diff --git a/pvlib/par.py b/pvlib/par.py new file mode 100644 index 0000000000..2f49a61bd2 --- /dev/null +++ b/pvlib/par.py @@ -0,0 +1,72 @@ +""" +Photosynthetically Active Radiation (PAR) module. +Utilities found here are specially interesting for agrivoltaic systems. +""" + +from pvlib.tools import cosd, sind + + +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 in degrees :math:`^{\circ}`. + + global_diffuse_fraction : numeric + Fraction of the global radiation that is diffuse. Unitless. + + Returns + ------- + par_diffuse_fraction : numeric + Photosynthetically active radiation in W/m^2. + + 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 diff --git a/pvlib/tests/test_par.py b/pvlib/tests/test_par.py new file mode 100644 index 0000000000..541760da9b --- /dev/null +++ b/pvlib/tests/test_par.py @@ -0,0 +1,28 @@ +""" +Test Photosynthetically Active Radiation (PAR) submodule. +""" + +from pvlib import par + +import numpy as np +from numpy.testing import assert_allclose + + +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 = par.spitters_relationship(solar_zenith, global_diffuse_fraction) + expected = np.array([ + 0.00650018, 0.00656213, 0.00706211, 0.0087417 , 0.01171437, 0.01260581, + 0.01299765, 0.0129997 , 0.06517588, 0.06579393, 0.07077986, 0.08750105, + 0.11699064, 0.12580782, 0.12967973, 0.1297 , 0.19994764, 0.20176275, + 0.21635259, 0.26460255, 0.34722693, 0.37134002, 0.38184514, 0.3819 , + 0.43609756, 0.43933488, 0.46497584, 0.54521789, 0.66826809, 0.70117647, + 0.71512774, 0.7152 , 0.65176471, 0.65503875, 0.68042968, 0.75414541, + 0.85271445, 0.87653894, 0.88634962, 0.8864 , 0.97647838, 0.97683827, + 0.97952006, 0.98634857, 0.99374028, 0.99529135, 0.99590717, 0.9959103 + ]) + assert_allclose(result, expected, atol=1e-8) From ae63127586a2eaa7e8f2d62f096487b228feac71 Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Sun, 12 May 2024 02:09:14 +0200 Subject: [PATCH 02/31] Update v0.11.0.rst --- docs/sphinx/source/whatsnew/v0.11.0.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/sphinx/source/whatsnew/v0.11.0.rst b/docs/sphinx/source/whatsnew/v0.11.0.rst index 7ea847a6d9..4bd7b6ef14 100644 --- a/docs/sphinx/source/whatsnew/v0.11.0.rst +++ b/docs/sphinx/source/whatsnew/v0.11.0.rst @@ -15,6 +15,12 @@ Deprecations Enhancements ~~~~~~~~~~~~ +* Add module :py:mod:`pvlib.par` to facilitate the design of Agrivoltaic + systems. + - Adds function :py:func:`pvlib.par.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`) Bug fixes From 58804539670956712bca285ed2cb29a07ec15b3d Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Sun, 12 May 2024 02:38:24 +0200 Subject: [PATCH 03/31] linter --- .../plot_diffuse_PAR_Spitters_relationship.py | 20 ++++++++++++++++--- pvlib/tests/test_par.py | 8 ++++---- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/docs/examples/agrivoltaics/plot_diffuse_PAR_Spitters_relationship.py b/docs/examples/agrivoltaics/plot_diffuse_PAR_Spitters_relationship.py index 2a17acc798..f28fc9857d 100644 --- a/docs/examples/agrivoltaics/plot_diffuse_PAR_Spitters_relationship.py +++ b/docs/examples/agrivoltaics/plot_diffuse_PAR_Spitters_relationship.py @@ -89,7 +89,9 @@ # Finally, calculate the diffuse PAR par["diffuse"] = par["total"] * par["diffuse_fraction"] -par[solar_position["zenith"] > 80] = 0 # set to zero for elevation < 10 degrees +par[solar_position["zenith"] > 80] = ( + 0 # set to zero for elevation < 10 degrees +) # %% # Plot the results @@ -104,9 +106,21 @@ ) ax_l.xaxis.set_major_formatter(DateFormatter("%H:%M", tz=tmy.index.tz)) ax_l.plot(tmy.index, tmy["ghi"], label="Global: total", color="deepskyblue") -ax_l.plot(tmy.index, tmy["dhi"], label="Global: diffuse", color="skyblue", linestyle="-.") +ax_l.plot( + tmy.index, + tmy["dhi"], + label="Global: diffuse", + color="skyblue", + linestyle="-.", +) ax_l.plot(tmy.index, par["total"], label="PAR: total", color="orangered") -ax_l.plot(tmy.index, par["diffuse"], label="PAR: diffuse", color="coral", linestyle="-.") +ax_l.plot( + tmy.index, + par["diffuse"], + label="PAR: diffuse", + color="coral", + linestyle="-.", +) ax_l.grid() ax_r = ax_l.twinx() diff --git a/pvlib/tests/test_par.py b/pvlib/tests/test_par.py index 541760da9b..303cf00443 100644 --- a/pvlib/tests/test_par.py +++ b/pvlib/tests/test_par.py @@ -18,11 +18,11 @@ def test_spitters_relationship(): expected = np.array([ 0.00650018, 0.00656213, 0.00706211, 0.0087417 , 0.01171437, 0.01260581, 0.01299765, 0.0129997 , 0.06517588, 0.06579393, 0.07077986, 0.08750105, - 0.11699064, 0.12580782, 0.12967973, 0.1297 , 0.19994764, 0.20176275, - 0.21635259, 0.26460255, 0.34722693, 0.37134002, 0.38184514, 0.3819 , + 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.7152 , 0.65176471, 0.65503875, 0.68042968, 0.75414541, - 0.85271445, 0.87653894, 0.88634962, 0.8864 , 0.97647838, 0.97683827, + 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 ]) assert_allclose(result, expected, atol=1e-8) From 5af860a25f29ab2352ab1a0ec00f238a05c03728 Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Sun, 12 May 2024 02:53:18 +0200 Subject: [PATCH 04/31] API update --- docs/sphinx/source/reference/agrivoltaics.rst | 9 +++++++++ docs/sphinx/source/reference/index.rst | 1 + 2 files changed, 10 insertions(+) create mode 100644 docs/sphinx/source/reference/agrivoltaics.rst diff --git a/docs/sphinx/source/reference/agrivoltaics.rst b/docs/sphinx/source/reference/agrivoltaics.rst new file mode 100644 index 0000000000..8512c3bc6c --- /dev/null +++ b/docs/sphinx/source/reference/agrivoltaics.rst @@ -0,0 +1,9 @@ +.. currentmodule:: pvlib + +Agrivoltaic Systems Modelling +============================= + +.. autosummary:: + :toctree: generated/ + + par.spitters_relationship diff --git a/docs/sphinx/source/reference/index.rst b/docs/sphinx/source/reference/index.rst index 9083f85bdd..39ae36645a 100644 --- a/docs/sphinx/source/reference/index.rst +++ b/docs/sphinx/source/reference/index.rst @@ -20,3 +20,4 @@ API reference bifacial scaling location + agrivoltaics From bdc2303490a9001969c3122931abd145f54aebea Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Sun, 12 May 2024 02:53:27 +0200 Subject: [PATCH 05/31] Example rendering --- .../agrivoltaics/plot_diffuse_PAR_Spitters_relationship.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/examples/agrivoltaics/plot_diffuse_PAR_Spitters_relationship.py b/docs/examples/agrivoltaics/plot_diffuse_PAR_Spitters_relationship.py index f28fc9857d..108c017261 100644 --- a/docs/examples/agrivoltaics/plot_diffuse_PAR_Spitters_relationship.py +++ b/docs/examples/agrivoltaics/plot_diffuse_PAR_Spitters_relationship.py @@ -25,9 +25,9 @@ # photosynthesis. See section on *Photosynthetically Active Radiation* in # pp. 222-223 of [1]_. # -# Key functions used in this example: -# - :py:func:`pvlib.par.spitters_relationship` to calculate the diffuse PAR -# fraction, as a function of global diffuse fraction and solar zenith. +# The key function used in this example is +# :py:func:`pvlib.par.spitters_relationship` to calculate the diffuse PAR +# fraction, as a function of global diffuse fraction and solar zenith. # # References # ---------- From aec787039bd65024b27227efae96948ec5eae664 Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Sun, 12 May 2024 02:55:49 +0200 Subject: [PATCH 06/31] Update test_par.py --- pvlib/tests/test_par.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pvlib/tests/test_par.py b/pvlib/tests/test_par.py index 303cf00443..7aeb416db1 100644 --- a/pvlib/tests/test_par.py +++ b/pvlib/tests/test_par.py @@ -16,8 +16,8 @@ def test_spitters_relationship(): global_diffuse_fraction = global_diffuse_fraction.ravel() result = par.spitters_relationship(solar_zenith, global_diffuse_fraction) expected = np.array([ - 0.00650018, 0.00656213, 0.00706211, 0.0087417 , 0.01171437, 0.01260581, - 0.01299765, 0.0129997 , 0.06517588, 0.06579393, 0.07077986, 0.08750105, + 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, From 02ebe8127141c66ae92167723bdfc2d3aebf6efa Mon Sep 17 00:00:00 2001 From: Echedey Luis <80125792+echedey-ls@users.noreply.github.com> Date: Fri, 24 May 2024 11:55:11 +0200 Subject: [PATCH 07/31] Apply suggestions from code review (Adam) Co-authored-by: Adam R. Jensen <39184289+AdamRJensen@users.noreply.github.com> --- .../agrivoltaics/plot_diffuse_PAR_Spitters_relationship.py | 4 ++-- pvlib/par.py | 2 +- pvlib/tests/test_par.py | 1 - 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/examples/agrivoltaics/plot_diffuse_PAR_Spitters_relationship.py b/docs/examples/agrivoltaics/plot_diffuse_PAR_Spitters_relationship.py index 108c017261..1e0cec35bc 100644 --- a/docs/examples/agrivoltaics/plot_diffuse_PAR_Spitters_relationship.py +++ b/docs/examples/agrivoltaics/plot_diffuse_PAR_Spitters_relationship.py @@ -3,12 +3,12 @@ ========================================================= This example demonstrates how to calculate the diffuse photosynthetically -active radiation (PAR) using Spitter's relationship. +active radiation (PAR) from total PAR using Spitter's relationship. """ # %% # The photosynthetically active radiation (PAR) is a key component in the -# photosynthesis process of plants. As in photovoltaic systems, the PAR is +# photosynthesis process of plants. As in photovoltaic systems, PAR can be # divided into direct and diffuse components. The diffuse fraction of PAR # 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 diff --git a/pvlib/par.py b/pvlib/par.py index 2f49a61bd2..13657e48d2 100644 --- a/pvlib/par.py +++ b/pvlib/par.py @@ -16,7 +16,7 @@ def spitters_relationship(solar_zenith, global_diffuse_fraction): Parameters ---------- solar_zenith : numeric - Solar zenith angle in degrees :math:`^{\circ}`. + Solar zenith angle. Degrees. global_diffuse_fraction : numeric Fraction of the global radiation that is diffuse. Unitless. diff --git a/pvlib/tests/test_par.py b/pvlib/tests/test_par.py index 7aeb416db1..2a73e8c1f2 100644 --- a/pvlib/tests/test_par.py +++ b/pvlib/tests/test_par.py @@ -3,7 +3,6 @@ """ from pvlib import par - import numpy as np from numpy.testing import assert_allclose From c5d133d9276f2a9f9b2206791e1f26fe58ddfc72 Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Fri, 24 May 2024 13:11:36 +0200 Subject: [PATCH 08/31] Update par.py --- pvlib/par.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pvlib/par.py b/pvlib/par.py index 13657e48d2..bb87541949 100644 --- a/pvlib/par.py +++ b/pvlib/par.py @@ -24,7 +24,7 @@ def spitters_relationship(solar_zenith, global_diffuse_fraction): Returns ------- par_diffuse_fraction : numeric - Photosynthetically active radiation in W/m^2. + Photosynthetically Active Radiation diffuse fraction. Unitless [0, 1]. Notes ----- From 149961e4ea4b2e3b54fe32c82c369f1137fe97ad Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Fri, 24 May 2024 13:30:55 +0200 Subject: [PATCH 09/31] Move function to spectrum (mismatch.py) --- .../plot_diffuse_PAR_Spitters_relationship.py | 6 +- docs/sphinx/source/reference/agrivoltaics.rst | 2 +- docs/sphinx/source/whatsnew/v0.11.0.rst | 2 +- pvlib/__init__.py | 1 - pvlib/par.py | 72 ------------------- pvlib/spectrum/__init__.py | 1 + pvlib/spectrum/mismatch.py | 67 +++++++++++++++++ pvlib/tests/test_par.py | 27 ------- pvlib/tests/test_spectrum.py | 22 ++++++ 9 files changed, 95 insertions(+), 105 deletions(-) delete mode 100644 pvlib/par.py delete mode 100644 pvlib/tests/test_par.py diff --git a/docs/examples/agrivoltaics/plot_diffuse_PAR_Spitters_relationship.py b/docs/examples/agrivoltaics/plot_diffuse_PAR_Spitters_relationship.py index 1e0cec35bc..625740446d 100644 --- a/docs/examples/agrivoltaics/plot_diffuse_PAR_Spitters_relationship.py +++ b/docs/examples/agrivoltaics/plot_diffuse_PAR_Spitters_relationship.py @@ -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.par.spitters_relationship`. +# :py:func:`~pvlib.spectrum.spitters_relationship`. # This model requires the solar zenith angle and the fraction of the global # radiation that is diffuse as inputs. # @@ -26,7 +26,7 @@ # pp. 222-223 of [1]_. # # The key function used in this example is -# :py:func:`pvlib.par.spitters_relationship` to calculate the diffuse PAR +# :py:func:`pvlib.spectrum.spitters_relationship` to calculate the diffuse PAR # fraction, as a function of global diffuse fraction and solar zenith. # # References @@ -83,7 +83,7 @@ tmy["diffuse_fraction"] = tmy["dhi"] / tmy["ghi"] # Calculate diffuse PAR fraction using Spitter's relationship -par["diffuse_fraction"] = pvlib.par.spitters_relationship( +par["diffuse_fraction"] = pvlib.spectrum.spitters_relationship( solar_position["zenith"], tmy["diffuse_fraction"] ) diff --git a/docs/sphinx/source/reference/agrivoltaics.rst b/docs/sphinx/source/reference/agrivoltaics.rst index 8512c3bc6c..84ce200ab4 100644 --- a/docs/sphinx/source/reference/agrivoltaics.rst +++ b/docs/sphinx/source/reference/agrivoltaics.rst @@ -6,4 +6,4 @@ Agrivoltaic Systems Modelling .. autosummary:: :toctree: generated/ - par.spitters_relationship + spectrum.spitters_relationship diff --git a/docs/sphinx/source/whatsnew/v0.11.0.rst b/docs/sphinx/source/whatsnew/v0.11.0.rst index 4bd7b6ef14..77b3477312 100644 --- a/docs/sphinx/source/whatsnew/v0.11.0.rst +++ b/docs/sphinx/source/whatsnew/v0.11.0.rst @@ -17,7 +17,7 @@ Enhancements ~~~~~~~~~~~~ * Add module :py:mod:`pvlib.par` to facilitate the design of Agrivoltaic systems. - - Adds function :py:func:`pvlib.par.spitters_relationship` to calculate the + - Adds function :py:func:`pvlib.spectrum.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`) diff --git a/pvlib/__init__.py b/pvlib/__init__.py index 6a3c62b842..413af8f607 100644 --- a/pvlib/__init__.py +++ b/pvlib/__init__.py @@ -14,7 +14,6 @@ ivtools, location, modelchain, - par, pvarray, pvsystem, scaling, diff --git a/pvlib/par.py b/pvlib/par.py deleted file mode 100644 index bb87541949..0000000000 --- a/pvlib/par.py +++ /dev/null @@ -1,72 +0,0 @@ -""" -Photosynthetically Active Radiation (PAR) module. -Utilities found here are specially interesting for agrivoltaic systems. -""" - -from pvlib.tools import cosd, sind - - -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. - - 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 diff --git a/pvlib/spectrum/__init__.py b/pvlib/spectrum/__init__.py index 6c97df978e..6fd09938f0 100644 --- a/pvlib/spectrum/__init__.py +++ b/pvlib/spectrum/__init__.py @@ -6,4 +6,5 @@ spectral_factor_caballero, spectral_factor_firstsolar, spectral_factor_sapm, + spitters_relationship, ) diff --git a/pvlib/spectrum/mismatch.py b/pvlib/spectrum/mismatch.py index 91bb19f6f0..524ad9718e 100644 --- a/pvlib/spectrum/mismatch.py +++ b/pvlib/spectrum/mismatch.py @@ -3,6 +3,7 @@ """ import pvlib +from pvlib.tools import sind, cosd import numpy as np import pandas as pd from scipy.interpolate import interp1d @@ -571,3 +572,69 @@ 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. + + 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 diff --git a/pvlib/tests/test_par.py b/pvlib/tests/test_par.py deleted file mode 100644 index 2a73e8c1f2..0000000000 --- a/pvlib/tests/test_par.py +++ /dev/null @@ -1,27 +0,0 @@ -""" -Test Photosynthetically Active Radiation (PAR) submodule. -""" - -from pvlib import par -import numpy as np -from numpy.testing import assert_allclose - - -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 = par.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 - ]) - assert_allclose(result, expected, atol=1e-8) diff --git a/pvlib/tests/test_spectrum.py b/pvlib/tests/test_spectrum.py index 793eaacfdf..2a5648a1ec 100644 --- a/pvlib/tests/test_spectrum.py +++ b/pvlib/tests/test_spectrum.py @@ -315,3 +315,25 @@ 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) From eff3a252f1a2505b41275c999e98222edfac9099 Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Fri, 24 May 2024 13:32:28 +0200 Subject: [PATCH 10/31] Improve units formatting --- .../agrivoltaics/plot_diffuse_PAR_Spitters_relationship.py | 2 +- pvlib/spectrum/mismatch.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/examples/agrivoltaics/plot_diffuse_PAR_Spitters_relationship.py b/docs/examples/agrivoltaics/plot_diffuse_PAR_Spitters_relationship.py index 625740446d..250e9d611b 100644 --- a/docs/examples/agrivoltaics/plot_diffuse_PAR_Spitters_relationship.py +++ b/docs/examples/agrivoltaics/plot_diffuse_PAR_Spitters_relationship.py @@ -74,7 +74,7 @@ # Calculate Photosynthetically Active Radiation # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ # The total PAR can be approximated as 0.50 times the global horizontal -# irradiance (GHI) for solar elevation higher that :math:`10^\circ`. +# irradiance (GHI) for solar elevation higher that 10°. # See section on *Photosynthetically Active Radiation* in pp. 222-223 of [1]_. par = pd.DataFrame({"total": 0.50 * tmy["ghi"]}, index=tmy.index) diff --git a/pvlib/spectrum/mismatch.py b/pvlib/spectrum/mismatch.py index 524ad9718e..f4479455f7 100644 --- a/pvlib/spectrum/mismatch.py +++ b/pvlib/spectrum/mismatch.py @@ -587,7 +587,7 @@ def spitters_relationship(solar_zenith, global_diffuse_fraction): Solar zenith angle. Degrees. global_diffuse_fraction : numeric - Fraction of the global radiation that is diffuse. Unitless. + Fraction of the global radiation that is diffuse. Unitless [0, 1]. Returns ------- From d18827e8fd0862a89b0d17f7450f823a78e6f467 Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Fri, 24 May 2024 13:45:36 +0200 Subject: [PATCH 11/31] Split legends --- .../agrivoltaics/plot_diffuse_PAR_Spitters_relationship.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/examples/agrivoltaics/plot_diffuse_PAR_Spitters_relationship.py b/docs/examples/agrivoltaics/plot_diffuse_PAR_Spitters_relationship.py index 250e9d611b..ce1c07b0a1 100644 --- a/docs/examples/agrivoltaics/plot_diffuse_PAR_Spitters_relationship.py +++ b/docs/examples/agrivoltaics/plot_diffuse_PAR_Spitters_relationship.py @@ -122,6 +122,7 @@ linestyle="-.", ) ax_l.grid() +ax_l.legend(loc="upper left") ax_r = ax_l.twinx() ax_r.set(ylabel="Diffuse fraction") @@ -139,7 +140,6 @@ color="chocolate", linestyle=":", ) +ax_r.legend(loc="upper right") -lines = ax_l.get_lines() + ax_r.get_lines() -plt.legend(lines, (line.get_label() for line in lines)) plt.show() From 18619a4c979afbc9a8878f61f3282fcf7267c34b Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Fri, 24 May 2024 13:52:45 +0200 Subject: [PATCH 12/31] Remove api page, move to spectrum index --- docs/sphinx/source/reference/agrivoltaics.rst | 9 --------- .../reference/effects_on_pv_system_output/spectrum.rst | 1 + docs/sphinx/source/reference/index.rst | 1 - 3 files changed, 1 insertion(+), 10 deletions(-) delete mode 100644 docs/sphinx/source/reference/agrivoltaics.rst diff --git a/docs/sphinx/source/reference/agrivoltaics.rst b/docs/sphinx/source/reference/agrivoltaics.rst deleted file mode 100644 index 84ce200ab4..0000000000 --- a/docs/sphinx/source/reference/agrivoltaics.rst +++ /dev/null @@ -1,9 +0,0 @@ -.. currentmodule:: pvlib - -Agrivoltaic Systems Modelling -============================= - -.. autosummary:: - :toctree: generated/ - - spectrum.spitters_relationship diff --git a/docs/sphinx/source/reference/effects_on_pv_system_output/spectrum.rst b/docs/sphinx/source/reference/effects_on_pv_system_output/spectrum.rst index 8041d8f49b..09d879fad4 100644 --- a/docs/sphinx/source/reference/effects_on_pv_system_output/spectrum.rst +++ b/docs/sphinx/source/reference/effects_on_pv_system_output/spectrum.rst @@ -13,3 +13,4 @@ Spectrum spectrum.spectral_factor_caballero spectrum.spectral_factor_firstsolar spectrum.spectral_factor_sapm + spectrum.spitters_relationship diff --git a/docs/sphinx/source/reference/index.rst b/docs/sphinx/source/reference/index.rst index 39ae36645a..9083f85bdd 100644 --- a/docs/sphinx/source/reference/index.rst +++ b/docs/sphinx/source/reference/index.rst @@ -20,4 +20,3 @@ API reference bifacial scaling location - agrivoltaics From feded95239fdb4245805bad4b5909ddc96511d02 Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Fri, 24 May 2024 13:53:30 +0200 Subject: [PATCH 13/31] Update v0.11.0.rst --- docs/sphinx/source/whatsnew/v0.11.0.rst | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/docs/sphinx/source/whatsnew/v0.11.0.rst b/docs/sphinx/source/whatsnew/v0.11.0.rst index 77b3477312..a2df6b630c 100644 --- a/docs/sphinx/source/whatsnew/v0.11.0.rst +++ b/docs/sphinx/source/whatsnew/v0.11.0.rst @@ -15,12 +15,10 @@ Deprecations Enhancements ~~~~~~~~~~~~ -* Add module :py:mod:`pvlib.par` to facilitate the design of Agrivoltaic - systems. - - Adds function :py:func:`pvlib.spectrum.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`) +- Add function :py:func:`pvlib.spectrum.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`) Bug fixes From 1c6d41623f9aa827e04574ccc5fb4f8bbbc82a94 Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Thu, 30 May 2024 19:13:44 +0200 Subject: [PATCH 14/31] Move to ``irradiance.py`` --- .../plot_diffuse_PAR_Spitters_relationship.py | 6 +- .../effects_on_pv_system_output/spectrum.rst | 1 - .../reference/irradiance/components.rst | 9 +++ docs/sphinx/source/whatsnew/v0.11.0.rst | 2 +- pvlib/irradiance.py | 66 +++++++++++++++++++ pvlib/spectrum/__init__.py | 1 - pvlib/spectrum/mismatch.py | 66 ------------------- pvlib/tests/test_irradiance.py | 22 +++++++ pvlib/tests/test_spectrum.py | 22 ------- 9 files changed, 101 insertions(+), 94 deletions(-) diff --git a/docs/examples/agrivoltaics/plot_diffuse_PAR_Spitters_relationship.py b/docs/examples/agrivoltaics/plot_diffuse_PAR_Spitters_relationship.py index ce1c07b0a1..683ac28296 100644 --- a/docs/examples/agrivoltaics/plot_diffuse_PAR_Spitters_relationship.py +++ b/docs/examples/agrivoltaics/plot_diffuse_PAR_Spitters_relationship.py @@ -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. # @@ -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 @@ -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"] ) diff --git a/docs/sphinx/source/reference/effects_on_pv_system_output/spectrum.rst b/docs/sphinx/source/reference/effects_on_pv_system_output/spectrum.rst index 09d879fad4..8041d8f49b 100644 --- a/docs/sphinx/source/reference/effects_on_pv_system_output/spectrum.rst +++ b/docs/sphinx/source/reference/effects_on_pv_system_output/spectrum.rst @@ -13,4 +13,3 @@ Spectrum spectrum.spectral_factor_caballero spectrum.spectral_factor_firstsolar spectrum.spectral_factor_sapm - spectrum.spitters_relationship diff --git a/docs/sphinx/source/reference/irradiance/components.rst b/docs/sphinx/source/reference/irradiance/components.rst index 70e94ab38d..ed5ceacebc 100644 --- a/docs/sphinx/source/reference/irradiance/components.rst +++ b/docs/sphinx/source/reference/irradiance/components.rst @@ -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 diff --git a/docs/sphinx/source/whatsnew/v0.11.0.rst b/docs/sphinx/source/whatsnew/v0.11.0.rst index 3b27560074..8295a5c7e3 100644 --- a/docs/sphinx/source/whatsnew/v0.11.0.rst +++ b/docs/sphinx/source/whatsnew/v0.11.0.rst @@ -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`) diff --git a/pvlib/irradiance.py b/pvlib/irradiance.py index d2f494021d..d0d194ebe7 100644 --- a/pvlib/irradiance.py +++ b/pvlib/irradiance.py @@ -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 diff --git a/pvlib/spectrum/__init__.py b/pvlib/spectrum/__init__.py index 6fd09938f0..6c97df978e 100644 --- a/pvlib/spectrum/__init__.py +++ b/pvlib/spectrum/__init__.py @@ -6,5 +6,4 @@ spectral_factor_caballero, spectral_factor_firstsolar, spectral_factor_sapm, - spitters_relationship, ) diff --git a/pvlib/spectrum/mismatch.py b/pvlib/spectrum/mismatch.py index 989d65f462..3c8a5a453f 100644 --- a/pvlib/spectrum/mismatch.py +++ b/pvlib/spectrum/mismatch.py @@ -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 diff --git a/pvlib/tests/test_irradiance.py b/pvlib/tests/test_irradiance.py index 75847c655a..2b4b8477b5 100644 --- a/pvlib/tests/test_irradiance.py +++ b/pvlib/tests/test_irradiance.py @@ -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) diff --git a/pvlib/tests/test_spectrum.py b/pvlib/tests/test_spectrum.py index 2a5648a1ec..793eaacfdf 100644 --- a/pvlib/tests/test_spectrum.py +++ b/pvlib/tests/test_spectrum.py @@ -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) From 899844cbad87a6351406fa97470ffe33c8532f01 Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Thu, 30 May 2024 19:16:14 +0200 Subject: [PATCH 15/31] Flake8 :knife: --- .../agrivoltaics/plot_diffuse_PAR_Spitters_relationship.py | 4 ++-- pvlib/spectrum/mismatch.py | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/examples/agrivoltaics/plot_diffuse_PAR_Spitters_relationship.py b/docs/examples/agrivoltaics/plot_diffuse_PAR_Spitters_relationship.py index 683ac28296..b892e120b1 100644 --- a/docs/examples/agrivoltaics/plot_diffuse_PAR_Spitters_relationship.py +++ b/docs/examples/agrivoltaics/plot_diffuse_PAR_Spitters_relationship.py @@ -26,8 +26,8 @@ # pp. 222-223 of [1]_. # # The key function used in this example is -# :py:func:`pvlib.irradiance.spitters_relationship` to calculate the diffuse PAR -# fraction, as a function of global diffuse fraction and solar zenith. +# :py:func:`pvlib.irradiance.spitters_relationship` to calculate the diffuse +# PAR fraction, as a function of global diffuse fraction and solar zenith. # # References # ---------- diff --git a/pvlib/spectrum/mismatch.py b/pvlib/spectrum/mismatch.py index 3c8a5a453f..5e00b2472d 100644 --- a/pvlib/spectrum/mismatch.py +++ b/pvlib/spectrum/mismatch.py @@ -3,7 +3,6 @@ """ import pvlib -from pvlib.tools import sind, cosd import numpy as np import pandas as pd from scipy.interpolate import interp1d From 9268a44ee1e260ad5ac27acade8e4f5440019e94 Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Fri, 14 Jun 2024 21:45:41 +0200 Subject: [PATCH 16/31] Fix trigonometry - double testing with a spreadsheet Co-Authored-By: Kevin Anderson <57452607+kandersolar@users.noreply.github.com> --- pvlib/irradiance.py | 4 ++-- pvlib/tests/test_irradiance.py | 16 +++++++--------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/pvlib/irradiance.py b/pvlib/irradiance.py index d0d194ebe7..163447cecf 100644 --- a/pvlib/irradiance.py +++ b/pvlib/irradiance.py @@ -3819,7 +3819,7 @@ def spitters_relationship(solar_zenith, global_diffuse_fraction): """ # notation change: # cosd(90-x) = sind(x) and 90-solar_elevation = solar_zenith - sind_solar_zenith = tools.sind(solar_zenith) + cosd_solar_zenith = tools.cosd(solar_zenith) cosd_solar_elevation = tools.cosd(90 - solar_zenith) par_diffuse_fraction = ( (1 + 0.3 * (1 - global_diffuse_fraction**2)) @@ -3827,7 +3827,7 @@ def spitters_relationship(solar_zenith, global_diffuse_fraction): / ( 1 + (1 - global_diffuse_fraction**2) - * sind_solar_zenith**2 + * cosd_solar_zenith**2 * cosd_solar_elevation**3 ) ) diff --git a/pvlib/tests/test_irradiance.py b/pvlib/tests/test_irradiance.py index 2b4b8477b5..10ce45df7c 100644 --- a/pvlib/tests/test_irradiance.py +++ b/pvlib/tests/test_irradiance.py @@ -1418,13 +1418,11 @@ def test_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 + 0.01300, 0.01290, 0.01226, 0.01118, 0.01125, 0.01189, 0.01293, 0.01300, + 0.12970, 0.12874, 0.12239, 0.11174, 0.11236, 0.11868, 0.12905, 0.12970, + 0.38190, 0.37931, 0.36201, 0.33273, 0.33446, 0.35188, 0.38014, 0.38190, + 0.71520, 0.71178, 0.68859, 0.64787, 0.65033, 0.67472, 0.71288, 0.71520, + 0.88640, 0.88401, 0.86755, 0.83745, 0.83931, 0.85746, 0.88478, 0.88640, + 0.99591, 0.99576, 0.99472, 0.99270, 0.99283, 0.99406, 0.99581, 0.99591, ]) # fmt: skip - assert_allclose(result, expected, atol=1e-8) + assert_allclose(result, expected, atol=1e-5) From bce50b6df326fb27db2cc0aa20529ec69bbc3ebc Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Fri, 14 Jun 2024 21:46:43 +0200 Subject: [PATCH 17/31] Move section of PAR Co-Authored-By: Kevin Anderson <57452607+kandersolar@users.noreply.github.com> --- docs/sphinx/source/reference/irradiance/components.rst | 9 --------- .../sphinx/source/reference/irradiance/decomposition.rst | 2 +- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/docs/sphinx/source/reference/irradiance/components.rst b/docs/sphinx/source/reference/irradiance/components.rst index ed5ceacebc..70e94ab38d 100644 --- a/docs/sphinx/source/reference/irradiance/components.rst +++ b/docs/sphinx/source/reference/irradiance/components.rst @@ -14,12 +14,3 @@ Decomposing and combining irradiance irradiance.get_ground_diffuse irradiance.dni irradiance.complete_irradiance - - -Photosynthetically Active Radiation ------------------------------------ - -.. autosummary:: - :toctree: ../generated/ - - irradiance.spitters_relationship diff --git a/docs/sphinx/source/reference/irradiance/decomposition.rst b/docs/sphinx/source/reference/irradiance/decomposition.rst index eede9df089..bf72342e45 100644 --- a/docs/sphinx/source/reference/irradiance/decomposition.rst +++ b/docs/sphinx/source/reference/irradiance/decomposition.rst @@ -18,4 +18,4 @@ DNI estimation models irradiance.campbell_norman irradiance.gti_dirint irradiance.louche - + irradiance.spitters_relationship From 764bf8b109ba62847dda91784b0ebbc8fc525eee Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Fri, 14 Jun 2024 21:49:53 +0200 Subject: [PATCH 18/31] I should read more carefully Co-Authored-By: Kevin Anderson <57452607+kandersolar@users.noreply.github.com> --- docs/sphinx/source/reference/irradiance/components.rst | 1 + docs/sphinx/source/reference/irradiance/decomposition.rst | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sphinx/source/reference/irradiance/components.rst b/docs/sphinx/source/reference/irradiance/components.rst index 70e94ab38d..eee429fe1b 100644 --- a/docs/sphinx/source/reference/irradiance/components.rst +++ b/docs/sphinx/source/reference/irradiance/components.rst @@ -14,3 +14,4 @@ Decomposing and combining irradiance irradiance.get_ground_diffuse irradiance.dni irradiance.complete_irradiance + irradiance.spitters_relationship diff --git a/docs/sphinx/source/reference/irradiance/decomposition.rst b/docs/sphinx/source/reference/irradiance/decomposition.rst index bf72342e45..55185a32bf 100644 --- a/docs/sphinx/source/reference/irradiance/decomposition.rst +++ b/docs/sphinx/source/reference/irradiance/decomposition.rst @@ -18,4 +18,3 @@ DNI estimation models irradiance.campbell_norman irradiance.gti_dirint irradiance.louche - irradiance.spitters_relationship From 0e0f836195f2be99c6d9f51134e68a218ccb69c9 Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Wed, 19 Jun 2024 01:02:43 +0200 Subject: [PATCH 19/31] Update decomposition.rst --- docs/sphinx/source/reference/irradiance/decomposition.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/sphinx/source/reference/irradiance/decomposition.rst b/docs/sphinx/source/reference/irradiance/decomposition.rst index 55185a32bf..eede9df089 100644 --- a/docs/sphinx/source/reference/irradiance/decomposition.rst +++ b/docs/sphinx/source/reference/irradiance/decomposition.rst @@ -18,3 +18,4 @@ DNI estimation models irradiance.campbell_norman irradiance.gti_dirint irradiance.louche + From 0d23fe02249a92e50c0e3ed04208c18c4d61b72d Mon Sep 17 00:00:00 2001 From: Echedey Luis <80125792+echedey-ls@users.noreply.github.com> Date: Wed, 19 Jun 2024 01:03:00 +0200 Subject: [PATCH 20/31] Apply suggestions from code review (Cliff) Co-authored-by: Cliff Hansen --- pvlib/irradiance.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/pvlib/irradiance.py b/pvlib/irradiance.py index 888a59da02..96e1316278 100644 --- a/pvlib/irradiance.py +++ b/pvlib/irradiance.py @@ -3770,8 +3770,7 @@ def louche(ghi, solar_zenith, datetime_or_doy, max_zenith=90): 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. + Derive the diffuse fraction of photosynthetically active radiation (PAR). The relationship is based on the work of Spitters et al. (1986) [1]_. @@ -3786,7 +3785,7 @@ def spitters_relationship(solar_zenith, global_diffuse_fraction): Returns ------- par_diffuse_fraction : numeric - Photosynthetically Active Radiation diffuse fraction. Unitless [0, 1]. + Fraction of photosynthetically active radiation (PAR) that is diffuse. Unitless [0, 1]. Notes ----- @@ -3803,7 +3802,7 @@ def spitters_relationship(solar_zenith, global_diffuse_fraction): 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 + A comparison using different models for the diffuse fraction of the global irradiance can be found in [2]_ in the context of Sweden. References From d1bb64b75aa82e05e9962e7fe67f876c9c2fd926 Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Wed, 19 Jun 2024 01:07:20 +0200 Subject: [PATCH 21/31] More docs refurbishment Co-Authored-By: Cliff Hansen <5393711+cwhanse@users.noreply.github.com> --- pvlib/irradiance.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/pvlib/irradiance.py b/pvlib/irradiance.py index 96e1316278..90f10491ef 100644 --- a/pvlib/irradiance.py +++ b/pvlib/irradiance.py @@ -3785,7 +3785,8 @@ def spitters_relationship(solar_zenith, global_diffuse_fraction): Returns ------- par_diffuse_fraction : numeric - Fraction of photosynthetically active radiation (PAR) that is diffuse. Unitless [0, 1]. + Fraction of photosynthetically active radiation (PAR) that is diffuse. + Unitless [0, 1]. Notes ----- @@ -3799,8 +3800,9 @@ def spitters_relationship(solar_zenith, global_diffuse_fraction): {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. + where :math:`k_d^{model}` is the diffuse fraction of the global radiation + provided by some model, and :math:`\beta` is the solar elevation angle + (in degrees [°]). A comparison using different models for the diffuse fraction of the global irradiance can be found in [2]_ in the context of Sweden. From eeef154e64a6d37cc1d0414fa4b5082c6c17d266 Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Wed, 19 Jun 2024 01:08:55 +0200 Subject: [PATCH 22/31] Rename to `diffuse_par_spitters` Instead of `spitters_relationship` Co-Authored-By: Cliff Hansen <5393711+cwhanse@users.noreply.github.com> --- .../agrivoltaics/plot_diffuse_PAR_Spitters_relationship.py | 6 +++--- docs/sphinx/source/reference/irradiance/components.rst | 2 +- docs/sphinx/source/whatsnew/v0.11.0.rst | 2 +- pvlib/irradiance.py | 2 +- pvlib/tests/test_irradiance.py | 4 ++-- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/examples/agrivoltaics/plot_diffuse_PAR_Spitters_relationship.py b/docs/examples/agrivoltaics/plot_diffuse_PAR_Spitters_relationship.py index b892e120b1..72293fbc9b 100644 --- a/docs/examples/agrivoltaics/plot_diffuse_PAR_Spitters_relationship.py +++ b/docs/examples/agrivoltaics/plot_diffuse_PAR_Spitters_relationship.py @@ -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.irradiance.spitters_relationship`. +# :py:func:`~pvlib.irradiance.diffuse_par_spitters`. # This model requires the solar zenith angle and the fraction of the global # radiation that is diffuse as inputs. # @@ -26,7 +26,7 @@ # pp. 222-223 of [1]_. # # The key function used in this example is -# :py:func:`pvlib.irradiance.spitters_relationship` to calculate the diffuse +# :py:func:`pvlib.irradiance.diffuse_par_spitters` to calculate the diffuse # PAR fraction, as a function of global diffuse fraction and solar zenith. # # References @@ -83,7 +83,7 @@ tmy["diffuse_fraction"] = tmy["dhi"] / tmy["ghi"] # Calculate diffuse PAR fraction using Spitter's relationship -par["diffuse_fraction"] = pvlib.irradiance.spitters_relationship( +par["diffuse_fraction"] = pvlib.irradiance.diffuse_par_spitters( solar_position["zenith"], tmy["diffuse_fraction"] ) diff --git a/docs/sphinx/source/reference/irradiance/components.rst b/docs/sphinx/source/reference/irradiance/components.rst index eee429fe1b..ce75d9d083 100644 --- a/docs/sphinx/source/reference/irradiance/components.rst +++ b/docs/sphinx/source/reference/irradiance/components.rst @@ -14,4 +14,4 @@ Decomposing and combining irradiance irradiance.get_ground_diffuse irradiance.dni irradiance.complete_irradiance - irradiance.spitters_relationship + irradiance.diffuse_par_spitters diff --git a/docs/sphinx/source/whatsnew/v0.11.0.rst b/docs/sphinx/source/whatsnew/v0.11.0.rst index 54d921b824..4355cf1339 100644 --- a/docs/sphinx/source/whatsnew/v0.11.0.rst +++ b/docs/sphinx/source/whatsnew/v0.11.0.rst @@ -38,7 +38,7 @@ Enhancements * Add function :py:func:`pvlib.spectrum.spectral_factor_pvspec`, which calculates the spectral mismatch factor as a function of absolute airmass and clearsky index using the PVSPEC model. (:issue:`1950`, :issue:`2065`, :pull:`2072`) -* Add function :py:func:`pvlib.irradiance.spitters_relationship` to calculate the +* Add function :py:func:`pvlib.irradiance.diffuse_par_spitters` to calculate the diffuse fraction of Photosynthetically Active Radiation (PAR) from the global diffuse fraction and the solar zenith. (:issue:`2047`, :pull:`2048`) diff --git a/pvlib/irradiance.py b/pvlib/irradiance.py index 90f10491ef..2ae4202c3c 100644 --- a/pvlib/irradiance.py +++ b/pvlib/irradiance.py @@ -3768,7 +3768,7 @@ def louche(ghi, solar_zenith, datetime_or_doy, max_zenith=90): return data -def spitters_relationship(solar_zenith, global_diffuse_fraction): +def diffuse_par_spitters(solar_zenith, global_diffuse_fraction): r""" Derive the diffuse fraction of photosynthetically active radiation (PAR). diff --git a/pvlib/tests/test_irradiance.py b/pvlib/tests/test_irradiance.py index eec49ae5d4..fbfd9d24ac 100644 --- a/pvlib/tests/test_irradiance.py +++ b/pvlib/tests/test_irradiance.py @@ -1408,13 +1408,13 @@ def test_louche(): assert_frame_equal(out, expected) -def test_spitters_relationship(): +def test_diffuse_par_spitters(): 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( + result = irradiance.diffuse_par_spitters( solar_zenith, global_diffuse_fraction ) expected = np.array([ From 6082fe747f497916e33d10d3b8d6cf77724fdb5d Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Wed, 19 Jun 2024 01:17:12 +0200 Subject: [PATCH 23/31] `global` -> `broadband` Co-Authored-By: Cliff Hansen <5393711+cwhanse@users.noreply.github.com> --- .../plot_diffuse_PAR_Spitters_relationship.py | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/examples/agrivoltaics/plot_diffuse_PAR_Spitters_relationship.py b/docs/examples/agrivoltaics/plot_diffuse_PAR_Spitters_relationship.py index 72293fbc9b..eb3317ccd1 100644 --- a/docs/examples/agrivoltaics/plot_diffuse_PAR_Spitters_relationship.py +++ b/docs/examples/agrivoltaics/plot_diffuse_PAR_Spitters_relationship.py @@ -14,20 +14,20 @@ # 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.irradiance.diffuse_par_spitters`. -# This model requires the solar zenith angle and the fraction of the global +# This model requires the solar zenith angle and the fraction of the broadband # radiation that is diffuse as inputs. # # .. note:: -# Understanding the distinction between the global radiation and the PAR is -# a key concept. The global radiation is the total amount of solar radiation -# that is usually accounted for in PV applications, while the PAR is a -# measurement of a narrower range of wavelengths that are used in +# Understanding the distinction between the broadband radiation and the PAR +# is a key concept. The broadband radiation is the total amount of solar +# radiation that is usually accounted for in PV applications, while the PAR +# is a measurement of a narrower range of wavelengths that are used in # photosynthesis. See section on *Photosynthetically Active Radiation* in # pp. 222-223 of [1]_. # # The key function used in this example is # :py:func:`pvlib.irradiance.diffuse_par_spitters` to calculate the diffuse -# PAR fraction, as a function of global diffuse fraction and solar zenith. +# PAR fraction, as a function of broadband diffuse fraction and solar zenith. # # References # ---------- @@ -73,13 +73,13 @@ # %% # Calculate Photosynthetically Active Radiation # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -# The total PAR can be approximated as 0.50 times the global horizontal +# The total PAR can be approximated as 0.50 times the broadband horizontal # irradiance (GHI) for solar elevation higher that 10°. # See section on *Photosynthetically Active Radiation* in pp. 222-223 of [1]_. par = pd.DataFrame({"total": 0.50 * tmy["ghi"]}, index=tmy.index) -# Calculate global irradiance diffuse fraction, input of the Spitter's model +# Calculate broadband irradiance diffuse fraction, input of the Spitter's model tmy["diffuse_fraction"] = tmy["dhi"] / tmy["ghi"] # Calculate diffuse PAR fraction using Spitter's relationship @@ -105,11 +105,11 @@ title="Diffuse PAR using Spitter's relationship", ) ax_l.xaxis.set_major_formatter(DateFormatter("%H:%M", tz=tmy.index.tz)) -ax_l.plot(tmy.index, tmy["ghi"], label="Global: total", color="deepskyblue") +ax_l.plot(tmy.index, tmy["ghi"], label="Broadband: total", color="deepskyblue") ax_l.plot( tmy.index, tmy["dhi"], - label="Global: diffuse", + label="Broadband: diffuse", color="skyblue", linestyle="-.", ) @@ -129,7 +129,7 @@ ax_r.plot( tmy.index, tmy["diffuse_fraction"], - label="Global diffuse fraction", + label="Broadband diffuse fraction", color="plum", linestyle=":", ) From ac9d45c94dcba42e5942e8e76d56c3655327d8c1 Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Wed, 19 Jun 2024 10:03:03 +0200 Subject: [PATCH 24/31] Code review from Adam, first batch Co-Authored-By: Adam R. Jensen <39184289+AdamRJensen@users.noreply.github.com> --- .../plot_diffuse_PAR_Spitters_relationship.py | 12 ++++-------- pvlib/irradiance.py | 7 +++---- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/docs/examples/agrivoltaics/plot_diffuse_PAR_Spitters_relationship.py b/docs/examples/agrivoltaics/plot_diffuse_PAR_Spitters_relationship.py index eb3317ccd1..7b554d9461 100644 --- a/docs/examples/agrivoltaics/plot_diffuse_PAR_Spitters_relationship.py +++ b/docs/examples/agrivoltaics/plot_diffuse_PAR_Spitters_relationship.py @@ -3,12 +3,12 @@ ========================================================= This example demonstrates how to calculate the diffuse photosynthetically -active radiation (PAR) from total PAR using Spitter's relationship. +active radiation (PAR) from broadband diffuse fraction. """ # %% # The photosynthetically active radiation (PAR) is a key component in the -# photosynthesis process of plants. As in photovoltaic systems, PAR can be +# photosynthesis process of plants. As with broadband irradiance, PAR can be # divided into direct and diffuse components. The diffuse fraction of PAR # 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 @@ -19,16 +19,12 @@ # # .. note:: # Understanding the distinction between the broadband radiation and the PAR -# is a key concept. The broadband radiation is the total amount of solar -# radiation that is usually accounted for in PV applications, while the PAR +# is a key concept. Broadband radiation is the total amount of solar +# radiation that is usually used as reference in PV applications, while PAR # is a measurement of a narrower range of wavelengths that are used in # photosynthesis. See section on *Photosynthetically Active Radiation* in # pp. 222-223 of [1]_. # -# The key function used in this example is -# :py:func:`pvlib.irradiance.diffuse_par_spitters` to calculate the diffuse -# PAR fraction, as a function of broadband diffuse fraction and solar zenith. -# # References # ---------- # .. [1] C. J. T. Spitters, H. A. J. M. Toussaint, and J. Goudriaan, diff --git a/pvlib/irradiance.py b/pvlib/irradiance.py index 2ae4202c3c..d8ffe5a3db 100644 --- a/pvlib/irradiance.py +++ b/pvlib/irradiance.py @@ -3800,9 +3800,8 @@ def diffuse_par_spitters(solar_zenith, global_diffuse_fraction): {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, and :math:`\beta` is the solar elevation angle - (in degrees [°]). + where :math:`k_d^{model}` is the diffuse fraction of the global radiation, + and :math:`\beta` is the solar elevation angle in degrees. A comparison using different models for the diffuse fraction of the global irradiance can be found in [2]_ in the context of Sweden. @@ -3819,7 +3818,7 @@ def diffuse_par_spitters(solar_zenith, global_diffuse_fraction): 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 + # cosd(90-x) = sind(x) and 90-solar_elevation = solar_zenith cosd_solar_zenith = tools.cosd(solar_zenith) cosd_solar_elevation = tools.cosd(90 - solar_zenith) par_diffuse_fraction = ( From 77f3ba84f233e64c1b3f69080b36d2cb2408f02c Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Wed, 19 Jun 2024 10:03:30 +0200 Subject: [PATCH 25/31] Apply trigonometric property --- pvlib/irradiance.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pvlib/irradiance.py b/pvlib/irradiance.py index d8ffe5a3db..4c7d2bef25 100644 --- a/pvlib/irradiance.py +++ b/pvlib/irradiance.py @@ -3820,7 +3820,7 @@ def diffuse_par_spitters(solar_zenith, global_diffuse_fraction): # notation change: # cosd(90-x) = sind(x) and 90-solar_elevation = solar_zenith cosd_solar_zenith = tools.cosd(solar_zenith) - cosd_solar_elevation = tools.cosd(90 - solar_zenith) + cosd_solar_elevation = tools.sind(solar_zenith) par_diffuse_fraction = ( (1 + 0.3 * (1 - global_diffuse_fraction**2)) * global_diffuse_fraction From f5449d7edfbe99e814efe2aedd9278a6d645ac8c Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Wed, 19 Jun 2024 10:32:30 +0200 Subject: [PATCH 26/31] Fix merge - linter --- pvlib/tests/test_irradiance.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pvlib/tests/test_irradiance.py b/pvlib/tests/test_irradiance.py index ffced6e93d..8e9f99f11f 100644 --- a/pvlib/tests/test_irradiance.py +++ b/pvlib/tests/test_irradiance.py @@ -1420,6 +1420,7 @@ def test_SURFACE_ALBEDOS_deprecated(): def test_SURFACE_ALBEDO_equals(): assert irradiance.SURFACE_ALBEDOS == albedo.SURFACE_ALBEDOS + def test_diffuse_par_spitters(): 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] From bafe63b4b5f2bbd51291d61ba7389a71a326e909 Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Wed, 19 Jun 2024 10:35:38 +0200 Subject: [PATCH 27/31] Forgot to apply this comment Co-Authored-By: Adam R. Jensen <39184289+AdamRJensen@users.noreply.github.com> --- pvlib/irradiance.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pvlib/irradiance.py b/pvlib/irradiance.py index 1ccdf85395..0701fe6497 100644 --- a/pvlib/irradiance.py +++ b/pvlib/irradiance.py @@ -3775,7 +3775,8 @@ def diffuse_par_spitters(solar_zenith, global_diffuse_fraction): Solar zenith angle. Degrees. global_diffuse_fraction : numeric - Fraction of the global radiation that is diffuse. Unitless [0, 1]. + Fraction of the global broadband irradiance that is diffuse + :math:`dhi/ghi`. Unitless [0, 1]. Returns ------- From 1a5d0d901bc5b940c4c02f785b3a8b4571848670 Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Wed, 19 Jun 2024 10:53:43 +0200 Subject: [PATCH 28/31] Remove model from eq Co-Authored-By: Adam R. Jensen <39184289+AdamRJensen@users.noreply.github.com> --- pvlib/irradiance.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pvlib/irradiance.py b/pvlib/irradiance.py index 0701fe6497..c570e30ec5 100644 --- a/pvlib/irradiance.py +++ b/pvlib/irradiance.py @@ -3791,13 +3791,13 @@ def diffuse_par_spitters(solar_zenith, global_diffuse_fraction): .. 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) + \frac{\left[1 + 0.3 \left(1 - \left(k_d\right) ^2\right)\right] + k_d} + {1 + \left(1 - \left(k_d\right)^2\right) \cos ^2 (90 - \beta) \cos ^3 \beta} - where :math:`k_d^{model}` is the diffuse fraction of the global radiation, - and :math:`\beta` is the solar elevation angle in degrees. + where :math:`k_d` is the diffuse fraction of the global radiation, and + :math:`\beta` is the daily average of the solar elevation angle in degrees. A comparison using different models for the diffuse fraction of the global irradiance can be found in [2]_ in the context of Sweden. From 189efe59a764ff713afffeb594234c57e1a5f6cf Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Thu, 20 Jun 2024 12:59:07 +0200 Subject: [PATCH 29/31] Dailies, insolation instead of instant, irradiance values Co-Authored-By: Adam R. Jensen <39184289+AdamRJensen@users.noreply.github.com> --- .../plot_diffuse_PAR_Spitters_relationship.py | 88 +++++++++++-------- pvlib/irradiance.py | 29 +++--- 2 files changed, 70 insertions(+), 47 deletions(-) diff --git a/docs/examples/agrivoltaics/plot_diffuse_PAR_Spitters_relationship.py b/docs/examples/agrivoltaics/plot_diffuse_PAR_Spitters_relationship.py index 7b554d9461..bc1203aab2 100644 --- a/docs/examples/agrivoltaics/plot_diffuse_PAR_Spitters_relationship.py +++ b/docs/examples/agrivoltaics/plot_diffuse_PAR_Spitters_relationship.py @@ -1,9 +1,9 @@ """ -Calculating the diffuse PAR using Spitter's relationship -========================================================= +Calculating daily diffuse PAR using Spitter's relationship +========================================================== This example demonstrates how to calculate the diffuse photosynthetically -active radiation (PAR) from broadband diffuse fraction. +active radiation (PAR) from diffuse fraction of broadband insolation. """ # %% @@ -14,15 +14,15 @@ # 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.irradiance.diffuse_par_spitters`. -# This model requires the solar zenith angle and the fraction of the broadband -# radiation that is diffuse as inputs. +# This model requires the average daily solar zenith angle and the +# daily fraction of the broadband insolation that is diffuse as inputs. # # .. note:: -# Understanding the distinction between the broadband radiation and the PAR -# is a key concept. Broadband radiation is the total amount of solar -# radiation that is usually used as reference in PV applications, while PAR +# Understanding the distinction between the broadband insolation and the PAR +# is a key concept. Broadband insolation is the total amount of solar +# energy that is usually used as reference in PV applications, while PAR # is a measurement of a narrower range of wavelengths that are used in -# photosynthesis. See section on *Photosynthetically Active Radiation* in +# photosynthesis. See section on *Photosynthetically Active insolation* in # pp. 222-223 of [1]_. # # References @@ -39,21 +39,19 @@ import pvlib import pandas as pd import matplotlib.pyplot as plt -from matplotlib.dates import DateFormatter +from matplotlib.dates import AutoDateLocator, ConciseDateFormatter from pathlib import Path -# Read some sample data +# Read some example data DATA_FILE = Path(pvlib.__path__[0]).joinpath("data", "723170TYA.CSV") tmy, metadata = pvlib.iotools.read_tmy3( - DATA_FILE, coerce_year=1990, map_variables=True + DATA_FILE, coerce_year=2002, map_variables=True ) tmy = tmy.filter( ["ghi", "dhi", "dni", "pressure", "temp_air"] -) # remaining data is not needed -tmy = tmy[ - "1990-04-11T06":"1990-04-11T22" -] # select a single day for this example +) # remaining columns are not needed +tmy = tmy["2002-09-06":"2002-09-21"] # select some days solar_position = pvlib.solarposition.get_solarposition( # TMY timestamp is at end of hour, so shift to center of interval @@ -66,52 +64,72 @@ ) solar_position.index = tmy.index # reset index to end of the hour +# %% +# Calculate daily values +# ^^^^^^^^^^^^^^^^^^^^^^ +# The daily average solar zenith angle and the daily diffuse fraction of +# broadband insolation are calculated as follows: + +daily_solar_zenith = solar_position["zenith"].resample("D").mean() +# integration over the day with a time step of 1 hour +daily_tmy = tmy[["ghi", "dhi"]].resample("D").sum() * 1 +daily_tmy["diffuse_fraction"] = daily_tmy["dhi"] / daily_tmy["ghi"] + # %% # Calculate Photosynthetically Active Radiation # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ # The total PAR can be approximated as 0.50 times the broadband horizontal -# irradiance (GHI) for solar elevation higher that 10°. +# insolation (integral of GHI) for an average solar elevation higher that 10°. # See section on *Photosynthetically Active Radiation* in pp. 222-223 of [1]_. -par = pd.DataFrame({"total": 0.50 * tmy["ghi"]}, index=tmy.index) +par = pd.DataFrame({"total": 0.50 * daily_tmy["ghi"]}, index=daily_tmy.index) +if daily_solar_zenith.min() < 10: + raise ValueError( + "The total PAR can't be assumed to be half the broadband insolation " + + "for average zenith angles lower than 10°." + ) -# Calculate broadband irradiance diffuse fraction, input of the Spitter's model -tmy["diffuse_fraction"] = tmy["dhi"] / tmy["ghi"] +# Calculate broadband insolation diffuse fraction, input of the Spitter's model +daily_tmy["diffuse_fraction"] = daily_tmy["dhi"] / daily_tmy["ghi"] # Calculate diffuse PAR fraction using Spitter's relationship par["diffuse_fraction"] = pvlib.irradiance.diffuse_par_spitters( - solar_position["zenith"], tmy["diffuse_fraction"] + solar_position["zenith"], daily_tmy["diffuse_fraction"] ) # Finally, calculate the diffuse PAR par["diffuse"] = par["total"] * par["diffuse_fraction"] -par[solar_position["zenith"] > 80] = ( - 0 # set to zero for elevation < 10 degrees -) # %% # Plot the results # ^^^^^^^^^^^^^^^^ -# Irradiances on left axis, diffuse fractions on right axis +# Insolation on left axis, diffuse fraction on right axis fig, ax_l = plt.subplots(figsize=(12, 6)) ax_l.set( xlabel="Time", - ylabel="Irradiance $[W/m^2]$", + ylabel="Daily insolation $[Wh/m^2/day]$", title="Diffuse PAR using Spitter's relationship", ) -ax_l.xaxis.set_major_formatter(DateFormatter("%H:%M", tz=tmy.index.tz)) -ax_l.plot(tmy.index, tmy["ghi"], label="Broadband: total", color="deepskyblue") +ax_l.xaxis.set_major_formatter( + ConciseDateFormatter(AutoDateLocator(), tz=daily_tmy.index.tz) +) +ax_l.plot( + daily_tmy.index, + daily_tmy["ghi"], + label="Broadband: total", + color="deepskyblue", +) ax_l.plot( - tmy.index, - tmy["dhi"], + daily_tmy.index, + daily_tmy["dhi"], label="Broadband: diffuse", color="skyblue", linestyle="-.", ) -ax_l.plot(tmy.index, par["total"], label="PAR: total", color="orangered") +ax_l.plot(daily_tmy.index, par["total"], label="PAR: total", color="orangered") ax_l.plot( - tmy.index, + daily_tmy.index, par["diffuse"], label="PAR: diffuse", color="coral", @@ -123,14 +141,14 @@ ax_r = ax_l.twinx() ax_r.set(ylabel="Diffuse fraction") ax_r.plot( - tmy.index, - tmy["diffuse_fraction"], + daily_tmy.index, + daily_tmy["diffuse_fraction"], label="Broadband diffuse fraction", color="plum", linestyle=":", ) ax_r.plot( - tmy.index, + daily_tmy.index, par["diffuse_fraction"], label="PAR diffuse fraction", color="chocolate", diff --git a/pvlib/irradiance.py b/pvlib/irradiance.py index c570e30ec5..d1637a6fb5 100644 --- a/pvlib/irradiance.py +++ b/pvlib/irradiance.py @@ -3763,26 +3763,31 @@ def louche(ghi, solar_zenith, datetime_or_doy, max_zenith=90): return data -def diffuse_par_spitters(solar_zenith, global_diffuse_fraction): +def diffuse_par_spitters(daily_solar_zenith, global_diffuse_fraction): r""" Derive the diffuse fraction of photosynthetically active radiation (PAR). - The relationship is based on the work of Spitters et al. (1986) [1]_. + The relationship is based on the work of Spitters et al. (1986) [1]_. The + resulting value is the fraction of daily PAR that is diffuse. + + .. note:: + This diffuse fractions are defined as the ratio of + diffuse to global daily insolations, in J m⁻² day⁻¹ or equivalent. Parameters ---------- - solar_zenith : numeric - Solar zenith angle. Degrees. + daily_solar_zenith : numeric + Average daily solar zenith angle. In degrees [°]. global_diffuse_fraction : numeric - Fraction of the global broadband irradiance that is diffuse - :math:`dhi/ghi`. Unitless [0, 1]. + Fraction of daily global broadband insolation that is diffuse. + Unitless [0, 1]. Returns ------- par_diffuse_fraction : numeric - Fraction of photosynthetically active radiation (PAR) that is diffuse. - Unitless [0, 1]. + Fraction of daily photosynthetically active radiation (PAR) that is + diffuse. Unitless [0, 1]. Notes ----- @@ -3796,11 +3801,11 @@ def diffuse_par_spitters(solar_zenith, global_diffuse_fraction): {1 + \left(1 - \left(k_d\right)^2\right) \cos ^2 (90 - \beta) \cos ^3 \beta} - where :math:`k_d` is the diffuse fraction of the global radiation, and + where :math:`k_d` is the diffuse fraction of the global insolation, and :math:`\beta` is the daily average of the solar elevation angle in degrees. A comparison using different models for the diffuse fraction of - the global irradiance can be found in [2]_ in the context of Sweden. + the global insolation can be found in [2]_ in the context of Sweden. References ---------- @@ -3815,8 +3820,8 @@ def diffuse_par_spitters(solar_zenith, global_diffuse_fraction): """ # notation change: # cosd(90-x) = sind(x) and 90-solar_elevation = solar_zenith - cosd_solar_zenith = tools.cosd(solar_zenith) - cosd_solar_elevation = tools.sind(solar_zenith) + cosd_solar_zenith = tools.cosd(daily_solar_zenith) + cosd_solar_elevation = tools.sind(daily_solar_zenith) par_diffuse_fraction = ( (1 + 0.3 * (1 - global_diffuse_fraction**2)) * global_diffuse_fraction From 7c28be63fbc99d900e0343c03d8a86c6859f1183 Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Thu, 20 Jun 2024 14:56:23 +0200 Subject: [PATCH 30/31] More docs refurbishment Co-Authored-By: Adam R. Jensen <39184289+AdamRJensen@users.noreply.github.com> --- .../plot_diffuse_PAR_Spitters_relationship.py | 18 ++++++++++-------- pvlib/irradiance.py | 3 ++- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/docs/examples/agrivoltaics/plot_diffuse_PAR_Spitters_relationship.py b/docs/examples/agrivoltaics/plot_diffuse_PAR_Spitters_relationship.py index bc1203aab2..97ebe860a6 100644 --- a/docs/examples/agrivoltaics/plot_diffuse_PAR_Spitters_relationship.py +++ b/docs/examples/agrivoltaics/plot_diffuse_PAR_Spitters_relationship.py @@ -7,9 +7,9 @@ """ # %% -# The photosynthetically active radiation (PAR) is a key component in the -# photosynthesis process of plants. As with broadband irradiance, PAR can be -# divided into direct and diffuse components. The diffuse fraction of PAR +# The photosynthetically active radiation (PAR) is a key metric in quantifying +# the photosynthesis process of plants. As with broadband irradiance, PAR can +# be divided into direct and diffuse components. The diffuse fraction of PAR # 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 @@ -20,10 +20,10 @@ # .. note:: # Understanding the distinction between the broadband insolation and the PAR # is a key concept. Broadband insolation is the total amount of solar -# energy that is usually used as reference in PV applications, while PAR -# is a measurement of a narrower range of wavelengths that are used in -# photosynthesis. See section on *Photosynthetically Active insolation* in -# pp. 222-223 of [1]_. +# energy that gets to a surface, often used in PV applications, while PAR +# is a measurement of a narrower spectrum of wavelengths that are involved +# in photosynthesis. See section on *Photosynthetically Active insolation* +# in pp. 222-223 of [1]_. # # References # ---------- @@ -35,6 +35,8 @@ # # Read some example data # ^^^^^^^^^^^^^^^^^^^^^^ +# Let's read some weather data from a TMY3 file and calculate the solar +# position. import pvlib import pandas as pd @@ -42,7 +44,7 @@ from matplotlib.dates import AutoDateLocator, ConciseDateFormatter from pathlib import Path -# Read some example data +# Datafile found in the pvlib distribution DATA_FILE = Path(pvlib.__path__[0]).joinpath("data", "723170TYA.CSV") tmy, metadata = pvlib.iotools.read_tmy3( diff --git a/pvlib/irradiance.py b/pvlib/irradiance.py index d1637a6fb5..1ac4f30294 100644 --- a/pvlib/irradiance.py +++ b/pvlib/irradiance.py @@ -3765,7 +3765,8 @@ def louche(ghi, solar_zenith, datetime_or_doy, max_zenith=90): def diffuse_par_spitters(daily_solar_zenith, global_diffuse_fraction): r""" - Derive the diffuse fraction of photosynthetically active radiation (PAR). + Derive daily diffuse fraction of Photosynthetically Active Radiation (PAR) + from daily average solar zenith and diffuse fraction of daily insolation. The relationship is based on the work of Spitters et al. (1986) [1]_. The resulting value is the fraction of daily PAR that is diffuse. From 59faee0102c9f4d9e79943fedc765ad43d7ca7ba Mon Sep 17 00:00:00 2001 From: "Adam R. Jensen" <39184289+AdamRJensen@users.noreply.github.com> Date: Thu, 20 Jun 2024 17:45:31 +0200 Subject: [PATCH 31/31] Review from Cliff Co-authored-by: Cliff Hansen --- pvlib/irradiance.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pvlib/irradiance.py b/pvlib/irradiance.py index 1ac4f30294..1f16bdf854 100644 --- a/pvlib/irradiance.py +++ b/pvlib/irradiance.py @@ -3772,8 +3772,8 @@ def diffuse_par_spitters(daily_solar_zenith, global_diffuse_fraction): resulting value is the fraction of daily PAR that is diffuse. .. note:: - This diffuse fractions are defined as the ratio of - diffuse to global daily insolations, in J m⁻² day⁻¹ or equivalent. + The diffuse fraction is defined as the ratio of + diffuse to global daily insolation, in J m⁻² day⁻¹ or equivalent. Parameters ----------