Skip to content

Instantly share code, notes, and snippets.

@marians
Last active January 18, 2024 21:57
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save marians/b3ae2cb82b784bdef5cbde2e17d38265 to your computer and use it in GitHub Desktop.
Save marians/b3ae2cb82b784bdef5cbde2e17d38265 to your computer and use it in GitHub Desktop.
Modellierung eines Balkonkraftwerks mit pvlib-python und PVGIS
# Modellierung eines Balkonkraftwerks
# mit Hoymiles 600 Watt Mikrowechselrichter
# und 2 Modulen Typ Canadian Solar CS3L-370MS.
# Ausrichtung: 155 (Süd-Süd-Ost), Neigung 12 Grad.
# Vollständige Verschattung von Westen bis Norden (270° bis 0°)
# durch Gebäude.
#
# Es wird das Wetter des typischen meteorologischen Jahrs (TMY)
# auf Basis der Daten von 2005 bis 2020 verwendet. Mehr dazu
# unter https://joint-research-centre.ec.europa.eu/pvgis-online-tool/pvgis-tools/pvgis-typical-meteorological-year-tmy-generator_en
#
# Für den durchschnittlichen Tagesertrag im Monat werden für
# den jeweiligen Monat des TMY die Stundenwerte aller Tage
# gemittelt. Die Fehlerbalken in den Balkendiagrammen zeigen
# die Standardabweichung innerhalb des Monats.
from pvlib import pvsystem, modelchain, location
from pvlib.iotools import get_pvgis_tmy
import matplotlib.pyplot as plt
from numpy import mean, std
# Breitengrad des Standorts
LATITUDE = 50.9
# Längengrad des Standorts
LONGITUDE = 7.2
# Neigungswinkel (0 entspricht horizontal, 90 entspricht senkrecht)
TILT = 12
# Himmelsrichtung. 0 = Nord, 90 = Ost, 180 = Süd, 270 = West
AZIMUTH = 155
# Horizont-Linie. Jede Zahl steht für einen Azimuth-Bereich von 30°,
# angefangen bei 0° (Nord). Die Werte wiederum sind eine Grad-Angabe von
# 0 bis 90, wobei 0 für den tiefstmöglichen Hoorizont (keine Verschattung)
# steht. 90 Grad steht für vollständige Verschattung, zum Beispiel durch
# eine Wand.
USER_HORIZON = [90, 10, 10, 10, 10, 10, 10, 10, 10, 90, 90, 90]
# Ende Konfiguration
# Calculate average day from a series containing multiple days.
# Returns tuple of lists for average power and standard deviation.
def average_month_day(series):
hours = list()
for _ in range(0, 24):
hours.append([])
for index, value in series.items():
hour = int(index.hour)
if value < 0:
hours[hour].append(0)
else:
hours[hour].append(value)
averages = []
for n in range(0, 24):
averages.append(mean(hours[n]))
stdev = []
for n in range(0, 24):
stdev.append(std(hours[n]))
return (averages, stdev)
# Count the hours above a certain wattage.
# Returns tuple.
def hours_above_nwatts(hours_average):
above_50 = 0
above_100 = 0
above_200 = 0
above_300 = 0
above_400 = 0
above_500 = 0
for val in hours_average:
if val >= 50:
above_50 += 1
if val >= 100:
above_100 += 1
if val >= 200:
above_200 += 1
if val >= 300:
above_300 += 1
if val >= 400:
above_400 += 1
if val >= 500:
above_500 += 1
return (above_50, above_100, above_200, above_300, above_400, above_500)
loc = location.Location(LATITUDE, LONGITUDE)
data, months_selected, inputs, metadata = get_pvgis_tmy(latitude=LATITUDE,
longitude=LONGITUDE,
usehorizon=True,
userhorizon=USER_HORIZON,
map_variables=True,
url='https://re.jrc.ec.europa.eu/api/v5_2/')
# To get the data for e. g. 21st of march, we
# have to find the right year number to use first.
march_begin = f"{months_selected[2]['year']}-3-1"
march_end = f"{months_selected[2]['year']}-3-31"
weather_march = data.loc[march_begin:march_end]
december_begin = f"{months_selected[11]['year']}-12-1"
december_end = f"{months_selected[11]['year']}-12-31"
weather_december = data.loc[december_begin:december_end]
july_begin = f"{months_selected[6]['year']}-7-1"
july_end = f"{months_selected[6]['year']}-7-31"
weather_july = data.loc[july_begin:july_end]
modules = pvsystem.retrieve_sam(path='https://raw.githubusercontent.com/NREL/SAM/patch/deploy/libraries/CEC%20Modules.csv')
module = modules['CSI_Solar_Co__Ltd__CS3L_370MS']
inverters = pvsystem.retrieve_sam(path='https://raw.githubusercontent.com/NREL/SAM/patch/deploy/libraries/CEC%20Inverters.csv')
inverter = inverters['Hoymiles_Power_Electronics_Inc___HM_600N__240V_']
array_kwargs = dict(
module_parameters=module,
temperature_model_parameters=dict(a=-3.56, b=-0.075, deltaT=3)
)
# 2 Strings mit identischer Ausrichtung.
# Für Ost-West-Ausrichtung kann man hier auch verschiedene Werte angeben.
arrays = [
pvsystem.Array(pvsystem.FixedMount(TILT, AZIMUTH), name='Modules', modules_per_string=1, **array_kwargs),
pvsystem.Array(pvsystem.FixedMount(TILT, AZIMUTH), name='Modules', modules_per_string=1, **array_kwargs),
]
system = pvsystem.PVSystem(arrays=arrays, inverter_parameters=inverter)
plt.rcdefaults()
fig, (ax1, ax2, ax3) = plt.subplots(1, 3, sharey=True)
fig.suptitle(f'Durchschnittliche PV-Leistung im typischen Monat')
fig.set_size_inches(18, 5)
x_pos = range(0, 24)
months = [
("März", weather_march, ax1),
("Juli", weather_july, ax2),
("Dezember", weather_december, ax3),
]
for name, weather, ax in months:
print(f"\n{name}\n")
ax.set_title(name)
mc = modelchain.ModelChain(system, loc, aoi_model='physical', spectral_model='no_loss')
mc.run_model(weather)
hours_average, hours_stdev = average_month_day(mc.results.ac)
above_50, above_100, above_200, above_300, above_400, above_500 = hours_above_nwatts(hours_average)
print(f'Tägliche Stunden über 50 Watt: {above_50}')
print(f'Tägliche Stunden über 100 Watt: {above_100}')
print(f'Tägliche Stunden über 200 Watt: {above_200}')
print(f'Tägliche Stunden über 300 Watt: {above_300}')
print(f'Tägliche Stunden über 400 Watt: {above_400}')
print(f'Tägliche Stunden über 500 Watt: {above_500}')
print(f'Gesamt im Monat: {(mc.results.ac.sum() / 1000.0):.0f} kWh')
ax.bar(x=x_pos, height=hours_average, yerr=hours_stdev)
ax.set_ylabel('Leistung (W)')
ax.set_xlabel('Tageszeit')
ax.set_title(name)
fig.savefig(f'simulation-tmy.png', dpi=100)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment