|
| 1 | +""" |
| 2 | +Reverse transposition limitations |
| 3 | +==================================== |
| 4 | +
|
| 5 | +Unfortunately, sometimes there is not a unique solution. |
| 6 | +
|
| 7 | +Author: Anton Driesse |
| 8 | +
|
| 9 | +""" |
| 10 | + |
| 11 | +# %% |
| 12 | +# |
| 13 | +# Introduction |
| 14 | +# ------------ |
| 15 | +# When irradiance is measured on a tilted plane, it is useful to be able to |
| 16 | +# estimate the GHI that produces the POA irradiance. |
| 17 | +# The estimation requires inverting a GHI-to-POA irradiance model, |
| 18 | +# which involves two parts: |
| 19 | +# a decomposition of GHI into direct and diffuse components, |
| 20 | +# and a transposition model that calculates the direct and diffuse irradiance |
| 21 | +# on the tilted plane. |
| 22 | +# Recovering GHI from POA irradiance is termed "reverse transposition." |
| 23 | +# |
| 24 | +# Unfortunately, for a given POA irradiance value, sometimes there is not a |
| 25 | +# unique solution for GHI. |
| 26 | +# Different GHI values can produce different combinations of direct and |
| 27 | +# diffuse irradiance that sum to the same POA irradiance value. |
| 28 | +# |
| 29 | +# In this example we look at a single point in time and consider a full range |
| 30 | +# of possible GHI and POA global values as shown in figures 3 and 4 of [1]_. |
| 31 | +# Then we use :py:meth:`pvlib.irradiance.ghi_from_poa_driesse_2023` to estimate |
| 32 | +# the original GHI from POA global. |
| 33 | +# |
| 34 | +# References |
| 35 | +# ---------- |
| 36 | +# .. [1] Driesse, A., Jensen, A., Perez, R., 2024. A Continuous form of the |
| 37 | +# Perez diffuse sky model for forward and reverse transposition. |
| 38 | +# Solar Energy vol. 267. :doi:`10.1016/j.solener.2023.112093` |
| 39 | +# |
| 40 | + |
| 41 | +import numpy as np |
| 42 | + |
| 43 | +import matplotlib |
| 44 | +import matplotlib.pyplot as plt |
| 45 | + |
| 46 | +from pvlib.irradiance import (erbs_driesse, |
| 47 | + get_total_irradiance, |
| 48 | + ghi_from_poa_driesse_2023, |
| 49 | + ) |
| 50 | + |
| 51 | +matplotlib.rcParams['axes.grid'] = True |
| 52 | + |
| 53 | +# %% |
| 54 | +# |
| 55 | +# Define the conditions that were used for figure 3 in [1]_. |
| 56 | +# |
| 57 | + |
| 58 | +dni_extra = 1366.1 |
| 59 | +albedo = 0.25 |
| 60 | +surface_tilt = 40 |
| 61 | +surface_azimuth = 180 |
| 62 | + |
| 63 | +solar_azimuth = 82 |
| 64 | +solar_zenith = 75 |
| 65 | + |
| 66 | +# %% |
| 67 | +# |
| 68 | +# Define a range of possible GHI values and calculate the corresponding |
| 69 | +# POA global. First estimate DNI and DHI using the Erbs-Driesse model, then |
| 70 | +# transpose using the Perez-Driesse model. |
| 71 | +# |
| 72 | + |
| 73 | +ghi = np.linspace(0, 500, 100+1) |
| 74 | + |
| 75 | +erbsout = erbs_driesse(ghi, solar_zenith, dni_extra=dni_extra) |
| 76 | + |
| 77 | +dni = erbsout['dni'] |
| 78 | +dhi = erbsout['dhi'] |
| 79 | + |
| 80 | +irrads = get_total_irradiance(surface_tilt, surface_azimuth, |
| 81 | + solar_zenith, solar_azimuth, |
| 82 | + dni, ghi, dhi, |
| 83 | + dni_extra, |
| 84 | + model='perez-driesse') |
| 85 | + |
| 86 | +poa_global = irrads['poa_global'] |
| 87 | + |
| 88 | +# %% |
| 89 | +# |
| 90 | +# Suppose you measure that POA global is 200 W/m2. What would GHI be? |
| 91 | +# |
| 92 | + |
| 93 | +poa_test = 200 |
| 94 | + |
| 95 | +ghi_hat = ghi_from_poa_driesse_2023(surface_tilt, surface_azimuth, |
| 96 | + solar_zenith, solar_azimuth, |
| 97 | + poa_test, |
| 98 | + dni_extra, |
| 99 | + full_output=False) |
| 100 | + |
| 101 | +print('Estimated GHI: %.2f W/m².' % ghi_hat) |
| 102 | + |
| 103 | +# %% |
| 104 | +# |
| 105 | +# Show this result on the graph of all possible combinations of GHI and POA. |
| 106 | +# |
| 107 | + |
| 108 | +plt.figure() |
| 109 | +plt.plot(ghi, poa_global, 'k-') |
| 110 | +plt.axvline(ghi_hat, color='g', lw=1) |
| 111 | +plt.axhline(poa_test, color='g', lw=1) |
| 112 | +plt.plot(ghi_hat, poa_test, 'gs') |
| 113 | +plt.annotate('GHI=%.2f' % (ghi_hat), |
| 114 | + xy=(ghi_hat-2, 200+2), |
| 115 | + xytext=(ghi_hat-20, 200+20), |
| 116 | + ha='right', |
| 117 | + arrowprops={'arrowstyle': 'simple'}) |
| 118 | +plt.xlim(0, 500) |
| 119 | +plt.ylim(0, 250) |
| 120 | +plt.xlabel('GHI [W/m²]') |
| 121 | +plt.ylabel('POA [W/m²]') |
| 122 | +plt.show() |
| 123 | + |
| 124 | +# %% |
| 125 | +# |
| 126 | +# Now change the solar azimuth to match the conditions for figure 4 in [1]_. |
| 127 | +# |
| 128 | + |
| 129 | +solar_azimuth = 76 |
| 130 | + |
| 131 | +# %% |
| 132 | +# |
| 133 | +# Again, estimate DNI and DHI using the Erbs-Driesse model, then |
| 134 | +# transpose using the Perez-Driesse model. |
| 135 | +# |
| 136 | + |
| 137 | +erbsout = erbs_driesse(ghi, solar_zenith, dni_extra=dni_extra) |
| 138 | + |
| 139 | +dni = erbsout['dni'] |
| 140 | +dhi = erbsout['dhi'] |
| 141 | + |
| 142 | +irrads = get_total_irradiance(surface_tilt, surface_azimuth, |
| 143 | + solar_zenith, solar_azimuth, |
| 144 | + dni, ghi, dhi, |
| 145 | + dni_extra, |
| 146 | + model='perez-driesse') |
| 147 | + |
| 148 | +poa_global = irrads['poa_global'] |
| 149 | + |
| 150 | +# %% |
| 151 | +# |
| 152 | +# Now reverse transpose all the POA values and observe that the original |
| 153 | +# GHI cannot always be found. There is a range of POA values that |
| 154 | +# maps to three possible GHI values, and there is not enough information |
| 155 | +# to choose one of them. Sometimes we get lucky and the right one comes |
| 156 | +# out, other times not. |
| 157 | +# |
| 158 | + |
| 159 | +result = ghi_from_poa_driesse_2023(surface_tilt, surface_azimuth, |
| 160 | + solar_zenith, solar_azimuth, |
| 161 | + poa_global, |
| 162 | + dni_extra, |
| 163 | + full_output=True, |
| 164 | + ) |
| 165 | + |
| 166 | +ghi_hat, conv, niter = result |
| 167 | +correct = np.isclose(ghi, ghi_hat, atol=0.01) |
| 168 | + |
| 169 | +plt.figure() |
| 170 | +plt.plot(np.where(correct, ghi, np.nan), np.where(correct, poa_global, np.nan), |
| 171 | + 'g.', label='correct GHI found') |
| 172 | +plt.plot(ghi[~correct], poa_global[~correct], 'r.', label='unreachable GHI') |
| 173 | +plt.plot(ghi[~conv], poa_global[~conv], 'm.', label='out of range (kt > 1.25)') |
| 174 | +plt.axhspan(88, 103, color='y', alpha=0.25, label='problem region') |
| 175 | + |
| 176 | +plt.xlim(0, 500) |
| 177 | +plt.ylim(0, 250) |
| 178 | +plt.xlabel('GHI [W/m²]') |
| 179 | +plt.ylabel('POA [W/m²]') |
| 180 | +plt.legend() |
| 181 | +plt.show() |
0 commit comments