|
| 1 | +""" |
| 2 | +Seasonal Tilt |
| 3 | +============= |
| 4 | +
|
| 5 | +Example of a custom Mount class. |
| 6 | +""" |
| 7 | + |
| 8 | +# %% |
| 9 | +# Some PV systems are built with the option to adjust the module |
| 10 | +# tilt to follow seasonal changes in solar position. For example, |
| 11 | +# SAM calls this strategy "Seasonal Tilt". This example shows how |
| 12 | +# to use a custom Mount class to use the Seasonal Tilt strategy |
| 13 | +# with :py:class:`~pvlib.modelchain.ModelChain`. |
| 14 | + |
| 15 | +import pvlib |
| 16 | +from pvlib import pvsystem, location, modelchain, iotools |
| 17 | +from pvlib.temperature import TEMPERATURE_MODEL_PARAMETERS |
| 18 | +import pandas as pd |
| 19 | +import pathlib |
| 20 | +import matplotlib.pyplot as plt |
| 21 | +from dataclasses import dataclass |
| 22 | + |
| 23 | + |
| 24 | +# %% |
| 25 | +# New Mount classes should extend ``pvlib.pvsystem.AbstractMount`` |
| 26 | +# and must implement a ``get_orientation(solar_zenith, solar_azimuth)`` method: |
| 27 | + |
| 28 | + |
| 29 | +@dataclass |
| 30 | +class SeasonalTiltMount(pvsystem.AbstractMount): |
| 31 | + monthly_tilts: list # length 12, one tilt per calendar month |
| 32 | + surface_azimuth: float = 180.0 |
| 33 | + |
| 34 | + def get_orientation(self, solar_zenith, solar_azimuth): |
| 35 | + tilts = [self.monthly_tilts[m-1] for m in solar_zenith.index.month] |
| 36 | + return pd.DataFrame({ |
| 37 | + 'surface_tilt': tilts, |
| 38 | + 'surface_azimuth': self.surface_azimuth, |
| 39 | + }, index=solar_zenith.index) |
| 40 | + |
| 41 | + |
| 42 | +# %% |
| 43 | +# First let's grab some weather data and make sure our mount produces tilts |
| 44 | +# like we expect: |
| 45 | + |
| 46 | +DATA_DIR = pathlib.Path(pvlib.__file__).parent / 'data' |
| 47 | +tmy, metadata = iotools.read_tmy3(DATA_DIR / '723170TYA.CSV', coerce_year=1990) |
| 48 | +# shift from TMY3 right-labeled index to left-labeled index: |
| 49 | +tmy.index = tmy.index - pd.Timedelta(hours=1) |
| 50 | +weather = pd.DataFrame({ |
| 51 | + 'ghi': tmy['GHI'], 'dhi': tmy['DHI'], 'dni': tmy['DNI'], |
| 52 | + 'temp_air': tmy['DryBulb'], 'wind_speed': tmy['Wspd'], |
| 53 | +}) |
| 54 | +loc = location.Location.from_tmy(metadata) |
| 55 | +solpos = loc.get_solarposition(weather.index) |
| 56 | +# same default monthly tilts as SAM: |
| 57 | +tilts = [40, 40, 40, 20, 20, 20, 20, 20, 20, 40, 40, 40] |
| 58 | +mount = SeasonalTiltMount(monthly_tilts=tilts) |
| 59 | +orientation = mount.get_orientation(solpos.apparent_zenith, solpos.azimuth) |
| 60 | +orientation['surface_tilt'].plot() |
| 61 | +plt.ylabel('Surface Tilt [degrees]') |
| 62 | +plt.show() |
| 63 | + |
| 64 | +# %% |
| 65 | +# With our custom tilt strategy defined, we can create the corresponding |
| 66 | +# Array and PVSystem, and then run a ModelChain as usual: |
| 67 | + |
| 68 | +module_parameters = {'pdc0': 1, 'gamma_pdc': -0.004, 'b': 0.05} |
| 69 | +temp_params = TEMPERATURE_MODEL_PARAMETERS['sapm']['open_rack_glass_polymer'] |
| 70 | +array = pvsystem.Array(mount=mount, module_parameters=module_parameters, |
| 71 | + temperature_model_parameters=temp_params) |
| 72 | +system = pvsystem.PVSystem(arrays=[array], inverter_parameters={'pdc0': 1}) |
| 73 | +mc = modelchain.ModelChain(system, loc, spectral_model='no_loss') |
| 74 | + |
| 75 | +_ = mc.run_model(weather) |
| 76 | + |
| 77 | +# %% |
| 78 | +# Now let's re-run the simulation assuming tilt=30 for the entire year: |
| 79 | + |
| 80 | +array2 = pvsystem.Array(mount=pvsystem.FixedMount(30, 180), |
| 81 | + module_parameters=module_parameters, |
| 82 | + temperature_model_parameters=temp_params) |
| 83 | +system2 = pvsystem.PVSystem(arrays=[array2], inverter_parameters={'pdc0': 1}) |
| 84 | +mc2 = modelchain.ModelChain(system2, loc, spectral_model='no_loss') |
| 85 | +_ = mc2.run_model(weather) |
| 86 | + |
| 87 | +# %% |
| 88 | +# And finally, compare simulated monthly generation between the two tilt |
| 89 | +# strategies: |
| 90 | + |
| 91 | +# sphinx_gallery_thumbnail_number = 2 |
| 92 | +results = pd.DataFrame({ |
| 93 | + 'Seasonal 20/40 Production': mc.results.ac, |
| 94 | + 'Fixed 30 Production': mc2.results.ac, |
| 95 | +}) |
| 96 | +results.resample('m').sum().plot() |
| 97 | +plt.ylabel('Monthly Production') |
| 98 | +plt.show() |
0 commit comments