Simulazione del modello metereologico

This commit is contained in:
2026-07-19 15:08:15 +00:00
parent aff2d7a4c9
commit 06ab37f46a

921
pvsim/weather.py Normal file
View File

@@ -0,0 +1,921 @@
```python
"""
weather.py
Modello meteorologico per il simulatore fotovoltaico.
Responsabilità:
- generazione dell'irraggiamento clear-sky;
- generazione di GHI, DNI e DHI;
- simulazione nuvolosità;
- simulazione pioggia;
- simulazione temperatura ambiente;
- ciclicità giornaliera;
- ciclicità stagionale;
- variabilità casuale;
- calcolo dell'irraggiamento sul piano del pannello (POA);
- produzione di un output meteorologico coerente con SunModel.
Flusso:
Timestamp
|
v
SunModel
|
+-- Solar Position
|
v
Clear Sky Model
|
+-- DNI
+-- GHI
+-- DHI
|
v
Cloud Model
|
+-- Cloud Factor
|
v
Rain Model
|
v
POA Irradiance
|
v
PVPanel
"""
from __future__ import annotations
from dataclasses import dataclass
from datetime import datetime
from typing import Dict, Optional
import math
import random
import pandas as pd
import pvlib
from .sun import SunModel
@dataclass
class WeatherModel:
"""
Modello meteorologico del campo fotovoltaico.
Parameters
----------
sun:
Istanza di SunModel utilizzata per la posizione solare.
clear_sky_model:
Modello clear-sky utilizzato da pvlib.
Valori tipici: "ineichen", "haurwitz", "simplified_solis".
mean_annual_temperature_C:
Temperatura media annuale del sito [°C].
daily_temperature_variation_C:
Ampiezza della variazione giornaliera della temperatura [°C].
seasonal_temperature_variation_C:
Ampiezza della variazione stagionale [°C].
cloud_enabled:
Abilita la simulazione delle nuvole.
mean_cloud_factor:
Fattore medio di trasmissione delle nuvole.
cloud_variability:
Variabilità casuale delle nuvole.
rain_enabled:
Abilita la simulazione della pioggia.
rain_probability_daily:
Probabilità giornaliera di pioggia.
random_seed:
Seed opzionale per rendere la simulazione riproducibile.
"""
sun: SunModel
clear_sky_model: str = "ineichen"
mean_annual_temperature_C: float = 15.0
daily_temperature_variation_C: float = 10.0
seasonal_temperature_variation_C: float = 12.0
cloud_enabled: bool = True
mean_cloud_factor: float = 0.85
cloud_variability: float = 0.15
rain_enabled: bool = True
rain_probability_daily: float = 0.08
random_seed: Optional[int] = None
# Stato interno della pioggia
_rain_active: bool = False
_rain_factor: float = 1.0
# ------------------------------------------------------------------
# Inizializzazione
# ------------------------------------------------------------------
def __post_init__(self) -> None:
"""
Inizializza il generatore casuale.
"""
if self.random_seed is not None:
random.seed(
self.random_seed
)
# ------------------------------------------------------------------
# Clear Sky
# ------------------------------------------------------------------
def clear_sky(
self,
timestamp: datetime
) -> Dict[str, float]:
"""
Calcola l'irraggiamento clear-sky tramite pvlib.
Returns
-------
Dict
Contiene:
ghi_Wm2
dni_Wm2
dhi_Wm2
"""
times = pd.DatetimeIndex(
[
pd.Timestamp(
timestamp
)
]
)
if times.tz is None:
times = times.tz_localize(
self.sun.timezone
)
location = (
self.sun.get_location()
)
clear_sky = (
location.get_clearsky(
times,
model=
self.clear_sky_model
)
)
row = clear_sky.iloc[0]
return {
"ghi_Wm2":
max(
float(
row["ghi"]
),
0.0
),
"dni_Wm2":
max(
float(
row["dni"]
),
0.0
),
"dhi_Wm2":
max(
float(
row["dhi"]
),
0.0
)
}
# ------------------------------------------------------------------
# Ciclo stagionale
# ------------------------------------------------------------------
def seasonal_temperature(
self,
timestamp: datetime
) -> float:
"""
Calcola la componente stagionale della temperatura.
La temperatura massima viene raggiunta indicativamente
durante l'estate e la minima durante l'inverno.
Formula:
T_season =
amplitude *
sin(2π * (day_of_year - phase) / 365)
La fase è scelta per avere il massimo intorno a luglio.
"""
day_of_year = (
timestamp.timetuple()
.tm_yday
)
phase = 172
seasonal = (
self.seasonal_temperature_variation_C
* math.sin(
2.0
* math.pi
* (
day_of_year
- phase
)
/ 365.25
)
)
return seasonal
# ------------------------------------------------------------------
# Ciclo giornaliero temperatura
# ------------------------------------------------------------------
def daily_temperature(
self,
timestamp: datetime
) -> float:
"""
Calcola la componente giornaliera della temperatura.
La temperatura minima viene raggiunta intorno all'alba.
La temperatura massima nel primo pomeriggio.
"""
hour = (
timestamp.hour
+ timestamp.minute / 60.0
)
# Massimo intorno alle 15:00
phase = 15.0
daily = (
self.daily_temperature_variation_C
* math.sin(
2.0
* math.pi
* (
hour
- phase
+ 6.0
)
/ 24.0
)
)
return daily
# ------------------------------------------------------------------
# Temperatura ambiente
# ------------------------------------------------------------------
def ambient_temperature(
self,
timestamp: datetime
) -> float:
"""
Genera la temperatura ambiente.
Combina:
- media annuale;
- variazione stagionale;
- variazione giornaliera;
- rumore casuale.
"""
seasonal = (
self.seasonal_temperature(
timestamp
)
)
daily = (
self.daily_temperature(
timestamp
)
)
noise = random.gauss(
0.0,
1.5
)
temperature = (
self.mean_annual_temperature_C
+ seasonal
+ daily
+ noise
)
return temperature
# ------------------------------------------------------------------
# Nuvole
# ------------------------------------------------------------------
def cloud_factor(
self,
timestamp: datetime
) -> float:
"""
Genera un fattore di attenuazione dovuto alle nuvole.
1.0:
cielo sereno.
0.0:
oscuramento completo.
Il fattore viene applicato all'irraggiamento clear-sky.
"""
if not self.cloud_enabled:
return 1.0
if not self.sun.is_daylight(
timestamp
):
return 0.0
base = (
self.mean_cloud_factor
)
variation = random.gauss(
0.0,
self.cloud_variability
)
factor = (
base
+ variation
)
return min(
max(
factor,
0.0
),
1.0
)
# ------------------------------------------------------------------
# Pioggia
# ------------------------------------------------------------------
def update_rain_state(
self,
timestamp: datetime
) -> None:
"""
Aggiorna lo stato della pioggia.
La probabilità di inizio pioggia viene valutata
una volta al giorno circa.
La pioggia può ridurre significativamente
l'irraggiamento disponibile.
"""
if not self.rain_enabled:
self._rain_active = False
self._rain_factor = 1.0
return
# Valutazione approssimata all'inizio della giornata
if (
timestamp.hour == 0
and timestamp.minute == 0
):
rain_event = (
random.random()
< self.rain_probability_daily
)
self._rain_active = (
rain_event
)
if self._rain_active:
self._rain_factor = random.uniform(
0.15,
0.60
)
else:
self._rain_factor = 1.0
# ------------------------------------------------------------------
def rain_factor(
self,
timestamp: datetime
) -> float:
"""
Restituisce il fattore di attenuazione dovuto alla pioggia.
"""
self.update_rain_state(
timestamp
)
if not self._rain_active:
return 1.0
# La pioggia è tipicamente associata
# a maggiore copertura nuvolosa.
return self._rain_factor
# ------------------------------------------------------------------
# Irraggiamento globale
# ------------------------------------------------------------------
def irradiance(
self,
timestamp: datetime
) -> Dict[str, float]:
"""
Calcola l'irraggiamento meteorologico.
Processo:
Clear Sky
Cloud Factor
Rain Factor
Effective Irradiance
Returns
-------
Dict
GHI, DNI, DHI e irraggiamento effettivo.
"""
clear = (
self.clear_sky(
timestamp
)
)
cloud = (
self.cloud_factor(
timestamp
)
)
rain = (
self.rain_factor(
timestamp
)
)
weather_factor = (
cloud
* rain
)
ghi = (
clear[
"ghi_Wm2"
]
* weather_factor
)
dni = (
clear[
"dni_Wm2"
]
* weather_factor
)
dhi = (
clear[
"dhi_Wm2"
]
* weather_factor
)
return {
"ghi_Wm2":
max(
ghi,
0.0
),
"dni_Wm2":
max(
dni,
0.0
),
"dhi_Wm2":
max(
dhi,
0.0
),
"clear_sky_ghi_Wm2":
clear[
"ghi_Wm2"
],
"clear_sky_dni_Wm2":
clear[
"dni_Wm2"
],
"clear_sky_dhi_Wm2":
clear[
"dhi_Wm2"
],
"cloud_factor":
cloud,
"rain_factor":
rain,
"rain_active":
self._rain_active
}
# ------------------------------------------------------------------
# Irraggiamento sul piano del pannello
# ------------------------------------------------------------------
def plane_of_array_irradiance(
self,
timestamp: datetime,
surface_tilt_deg: float,
surface_azimuth_deg: float
) -> Dict[str, float]:
"""
Calcola l'irraggiamento sul piano del pannello (POA).
Vengono utilizzati:
- DNI;
- DHI;
- GHI;
- posizione del sole;
- inclinazione del pannello;
- azimuth del pannello.
Returns
-------
Dict
POA totale e componenti diretta, diffusa e riflessa.
"""
irradiance = (
self.irradiance(
timestamp
)
)
solar = (
self.sun.solar_position(
timestamp
)
)
poa = (
pvlib.irradiance.get_total_irradiance(
surface_tilt=
surface_tilt_deg,
surface_azimuth=
surface_azimuth_deg,
solar_zenith=
solar[
"apparent_zenith_deg"
],
solar_azimuth=
solar[
"azimuth_deg"
],
dni=
irradiance[
"dni_Wm2"
],
ghi=
irradiance[
"ghi_Wm2"
],
dhi=
irradiance[
"dhi_Wm2"
]
)
)
return {
"poa_global_Wm2":
max(
float(
poa[
"poa_global"
]
),
0.0
),
"poa_direct_Wm2":
max(
float(
poa[
"poa_direct"
]
),
0.0
),
"poa_diffuse_Wm2":
max(
float(
poa[
"poa_diffuse"
]
),
0.0
),
"poa_sky_diffuse_Wm2":
max(
float(
poa[
"poa_sky_diffuse"
]
),
0.0
),
"poa_ground_diffuse_Wm2":
max(
float(
poa[
"poa_ground_diffuse"
]
),
0.0
)
}
# ------------------------------------------------------------------
# Output completo
# ------------------------------------------------------------------
def get_conditions(
self,
timestamp: datetime,
surface_tilt_deg: float = 30.0,
surface_azimuth_deg: float = 180.0
) -> Dict[str, float]:
"""
Restituisce tutte le condizioni ambientali e solari
necessarie per simulare un pannello.
"""
solar = (
self.sun.get_conditions(
timestamp=
timestamp,
surface_tilt_deg=
surface_tilt_deg,
surface_azimuth_deg=
surface_azimuth_deg
)
)
weather = (
self.irradiance(
timestamp
)
)
poa = (
self.plane_of_array_irradiance(
timestamp=
timestamp,
surface_tilt_deg=
surface_tilt_deg,
surface_azimuth_deg=
surface_azimuth_deg
)
)
temperature = (
self.ambient_temperature(
timestamp
)
)
return {
"timestamp":
timestamp,
"ambient_temperature_C":
temperature,
"solar_elevation_deg":
solar[
"solar_elevation_deg"
],
"solar_azimuth_deg":
solar[
"solar_azimuth_deg"
],
"solar_zenith_deg":
solar[
"solar_zenith_deg"
],
"angle_of_incidence_deg":
solar[
"angle_of_incidence_deg"
],
"incidence_factor":
solar[
"incidence_factor"
],
"daylight":
solar[
"daylight"
],
"ghi_Wm2":
weather[
"ghi_Wm2"
],
"dni_Wm2":
weather[
"dni_Wm2"
],
"dhi_Wm2":
weather[
"dhi_Wm2"
],
"poa_global_Wm2":
poa[
"poa_global_Wm2"
],
"poa_direct_Wm2":
poa[
"poa_direct_Wm2"
],
"poa_diffuse_Wm2":
poa[
"poa_diffuse_Wm2"
],
"cloud_factor":
weather[
"cloud_factor"
],
"rain_factor":
weather[
"rain_factor"
],
"rain_active":
weather[
"rain_active"
]
}
# ------------------------------------------------------------------
# Rappresentazione
# ------------------------------------------------------------------
def __repr__(
self
) -> str:
"""
Rappresentazione leggibile del modello meteorologico.
"""
return (
f"WeatherModel("
f"location="
f"{self.sun.latitude},"
f"{self.sun.longitude}, "
f"clear_sky="
f"'{self.clear_sky_model}', "
f"clouds="
f"{self.cloud_enabled}, "
f"rain="
f"{self.rain_enabled})"
)
```