Simulatore Inverter

This commit is contained in:
2026-07-19 00:50:23 +00:00
parent 9fdc07888d
commit 65cd36892d

768
pvsim/inverter.py Normal file
View File

@@ -0,0 +1,768 @@
```python
"""
inverter.py
Modello di un inverter fotovoltaico.
Gerarchia:
PV Plant
|
+-- Inverter
|
+-- Combiner Box
| |
| +-- Panels
|
+-- Combiner Box
|
+-- Panels
Responsabilità principali:
- aggregare la produzione DC delle Combiner Box;
- convertire DC -> AC;
- applicare il rendimento dell'inverter;
- simulare il clipping della potenza;
- simulare il consumo notturno;
- simulare tensione e corrente AC;
- simulare la frequenza di rete;
- simulare guasti;
- accumulare energia prodotta.
"""
from __future__ import annotations
from dataclasses import dataclass, field
from datetime import datetime
from typing import Dict, List
import random
from .combiner import CombinerBox
@dataclass
class Inverter:
"""
Rappresenta un inverter fotovoltaico.
Parameters
----------
inverter_id:
Identificativo univoco dell'inverter.
nominal_power_kW:
Potenza nominale AC dell'inverter [kW].
combiners:
Lista delle Combiner Box collegate all'inverter.
nominal_efficiency:
Rendimento nominale dell'inverter.
night_consumption_W:
Consumo interno dell'inverter durante la notte [W].
ac_voltage_V:
Tensione nominale AC [V].
grid_frequency_Hz:
Frequenza nominale della rete [Hz].
sensor_noise:
Rumore relativo delle misure.
enabled:
Stato operativo dell'inverter.
"""
inverter_id: str
nominal_power_kW: float = 100.0
combiners: List[CombinerBox] = field(
default_factory=list
)
nominal_efficiency: float = 0.985
night_consumption_W: float = 50.0
ac_voltage_V: float = 400.0
grid_frequency_Hz: float = 50.0
sensor_noise: float = 0.005
enabled: bool = True
internal_temperature_C: float = 25.0
total_energy_Wh: float = 0.0
total_dc_energy_Wh: float = 0.0
total_ac_energy_Wh: float = 0.0
# ------------------------------------------------------------------
# Proprietà
# ------------------------------------------------------------------
@property
def nominal_power_W(self) -> float:
"""
Potenza nominale AC dell'inverter in Watt.
"""
return self.nominal_power_kW * 1000.0
# ------------------------------------------------------------------
# Gestione Combiner
# ------------------------------------------------------------------
def add_combiner(
self,
combiner: CombinerBox
) -> None:
"""
Aggiunge una Combiner Box all'inverter.
"""
self.combiners.append(combiner)
# ------------------------------------------------------------------
def remove_combiner(
self,
combiner_id: str
) -> None:
"""
Rimuove una Combiner Box utilizzando il suo ID.
"""
self.combiners = [
combiner
for combiner in self.combiners
if combiner.combiner_id != combiner_id
]
# ------------------------------------------------------------------
def get_combiner_count(self) -> int:
"""
Restituisce il numero di Combiner Box collegate.
"""
return len(self.combiners)
# ------------------------------------------------------------------
def get_active_combiner_count(self) -> int:
"""
Restituisce il numero di Combiner Box attualmente operative.
"""
return sum(
1
for combiner in self.combiners
if combiner.enabled
)
# ------------------------------------------------------------------
# Calcolo potenza DC
# ------------------------------------------------------------------
def calculate_dc_power(
self,
combiner_data: List[Dict]
) -> float:
"""
Somma la potenza DC proveniente dalle Combiner Box.
"""
return sum(
data["power_W"]
for data in combiner_data
)
# ------------------------------------------------------------------
# Rendimento
# ------------------------------------------------------------------
def calculate_efficiency(
self,
dc_power_W: float
) -> float:
"""
Calcola il rendimento dell'inverter.
Il rendimento varia leggermente in funzione del carico.
A carico molto basso il rendimento è inferiore.
Intorno al carico nominale raggiunge il rendimento nominale.
"""
if dc_power_W <= 0:
return 0.0
load_ratio = (
dc_power_W
/ self.nominal_power_W
)
# Rendimento ridotto a bassissimo carico
if load_ratio < 0.05:
efficiency = (
self.nominal_efficiency
* 0.90
)
elif load_ratio < 0.20:
efficiency = (
self.nominal_efficiency
* 0.96
)
else:
efficiency = (
self.nominal_efficiency
)
# Piccola variazione casuale
efficiency *= random.gauss(
1.0,
0.002
)
return min(
max(efficiency, 0.0),
1.0
)
# ------------------------------------------------------------------
# Conversione DC -> AC
# ------------------------------------------------------------------
def convert_dc_to_ac(
self,
dc_power_W: float
) -> Dict:
"""
Converte la potenza DC in potenza AC.
Include:
- rendimento;
- clipping;
- rumore della misura.
"""
if not self.enabled:
return {
"ac_power_W": 0.0,
"efficiency": 0.0,
"clipping_W": 0.0
}
if dc_power_W <= 0:
return {
"ac_power_W": 0.0,
"efficiency": 0.0,
"clipping_W": 0.0
}
efficiency = (
self.calculate_efficiency(
dc_power_W
)
)
theoretical_ac_power = (
dc_power_W
* efficiency
)
# Clipping alla potenza nominale
ac_power = min(
theoretical_ac_power,
self.nominal_power_W
)
clipping = max(
theoretical_ac_power
- self.nominal_power_W,
0.0
)
# Rumore della misura
measured_ac_power = (
ac_power
* random.gauss(
1.0,
self.sensor_noise
)
)
return {
"ac_power_W":
max(
measured_ac_power,
0.0
),
"efficiency":
efficiency,
"clipping_W":
clipping
}
# ------------------------------------------------------------------
# Tensione AC
# ------------------------------------------------------------------
def calculate_ac_voltage(
self,
ac_power_W: float
) -> float:
"""
Simula la tensione AC dell'inverter.
"""
if ac_power_W <= 0:
return 0.0
voltage = (
self.ac_voltage_V
* random.gauss(
1.0,
0.002
)
)
return voltage
# ------------------------------------------------------------------
# Corrente AC
# ------------------------------------------------------------------
def calculate_ac_current(
self,
ac_power_W: float,
voltage_V: float
) -> float:
"""
Calcola la corrente AC.
Per semplicità viene utilizzato un modello trifase
semplificato.
"""
if (
ac_power_W <= 0
or voltage_V <= 0
):
return 0.0
power_factor = 0.98
current = (
ac_power_W
/
(
3 ** 0.5
* voltage_V
* power_factor
)
)
return current
# ------------------------------------------------------------------
# Frequenza
# ------------------------------------------------------------------
def calculate_frequency(
self
) -> float:
"""
Simula la frequenza della rete AC.
"""
return random.gauss(
self.grid_frequency_Hz,
0.01
)
# ------------------------------------------------------------------
# Temperatura
# ------------------------------------------------------------------
def calculate_temperature(
self,
dc_power_W: float,
ambient_temperature: float
) -> float:
"""
Stima la temperatura interna dell'inverter.
La temperatura aumenta in funzione
della potenza dissipata.
"""
if not self.enabled:
return ambient_temperature
efficiency = self.calculate_efficiency(
dc_power_W
)
if efficiency <= 0:
losses_W = 0.0
else:
losses_W = (
dc_power_W
* (1.0 - efficiency)
)
thermal_effect = (
losses_W
/ 500.0
)
temperature = (
ambient_temperature
+ thermal_effect
)
return temperature
# ------------------------------------------------------------------
# Aggiornamento principale
# ------------------------------------------------------------------
def update(
self,
timestamp: datetime,
irradiance: float,
ambient_temperature: float,
years_from_start: float,
timestep_minutes: float
) -> Dict:
"""
Aggiorna l'inverter e tutte le Combiner Box collegate.
Returns
-------
Dict
Misure aggregate dell'inverter.
"""
# --------------------------------------------------------------
# Inverter offline
# --------------------------------------------------------------
if not self.enabled:
return {
"timestamp":
timestamp,
"inverter_id":
self.inverter_id,
"dc_power_W":
0.0,
"ac_power_W":
0.0,
"efficiency":
0.0,
"clipping_W":
0.0,
"ac_voltage_V":
0.0,
"ac_current_A":
0.0,
"frequency_Hz":
0.0,
"energy_Wh":
0.0,
"dc_energy_Wh":
0.0,
"ac_energy_Wh":
0.0,
"total_energy_Wh":
self.total_energy_Wh,
"active_combiners":
0,
"total_combiners":
self.get_combiner_count(),
"internal_temperature_C":
ambient_temperature,
"enabled":
False
}
# --------------------------------------------------------------
# Aggiornamento Combiner Box
# --------------------------------------------------------------
combiner_data = []
for combiner in self.combiners:
data = combiner.update(
timestamp=timestamp,
irradiance=irradiance,
ambient_temperature=
ambient_temperature,
years_from_start=
years_from_start,
timestep_minutes=
timestep_minutes
)
combiner_data.append(
data
)
# --------------------------------------------------------------
# Potenza DC totale
# --------------------------------------------------------------
dc_power = (
self.calculate_dc_power(
combiner_data
)
)
# --------------------------------------------------------------
# Conversione DC -> AC
# --------------------------------------------------------------
conversion = (
self.convert_dc_to_ac(
dc_power
)
)
ac_power = (
conversion[
"ac_power_W"
]
)
efficiency = (
conversion[
"efficiency"
]
)
clipping = (
conversion[
"clipping_W"
]
)
# --------------------------------------------------------------
# Misure AC
# --------------------------------------------------------------
ac_voltage = (
self.calculate_ac_voltage(
ac_power
)
)
ac_current = (
self.calculate_ac_current(
ac_power,
ac_voltage
)
)
frequency = (
self.calculate_frequency()
if ac_power > 0
else 0.0
)
# --------------------------------------------------------------
# Temperatura
# --------------------------------------------------------------
temperature = (
self.calculate_temperature(
dc_power,
ambient_temperature
)
)
self.internal_temperature_C = (
temperature
)
# --------------------------------------------------------------
# Energia
# --------------------------------------------------------------
dc_energy = (
dc_power
* timestep_minutes
/ 60.0
)
ac_energy = (
ac_power
* timestep_minutes
/ 60.0
)
self.total_dc_energy_Wh += (
dc_energy
)
self.total_ac_energy_Wh += (
ac_energy
)
self.total_energy_Wh += (
ac_energy
)
# --------------------------------------------------------------
# Output
# --------------------------------------------------------------
return {
"timestamp":
timestamp,
"inverter_id":
self.inverter_id,
"dc_power_W":
dc_power,
"ac_power_W":
ac_power,
"efficiency":
efficiency,
"clipping_W":
clipping,
"ac_voltage_V":
ac_voltage,
"ac_current_A":
ac_current,
"frequency_Hz":
frequency,
"energy_Wh":
ac_energy,
"dc_energy_Wh":
dc_energy,
"ac_energy_Wh":
ac_energy,
"total_energy_Wh":
self.total_energy_Wh,
"dc_total_energy_Wh":
self.total_dc_energy_Wh,
"ac_total_energy_Wh":
self.total_ac_energy_Wh,
"active_combiners":
self.get_active_combiner_count(),
"total_combiners":
self.get_combiner_count(),
"internal_temperature_C":
temperature,
"enabled":
self.enabled
}
# ------------------------------------------------------------------
# Gestione guasti
# ------------------------------------------------------------------
def fail(self) -> None:
"""
Simula un guasto dell'inverter.
L'inverter smette di produrre energia AC.
"""
self.enabled = False
# ------------------------------------------------------------------
def repair(self) -> None:
"""
Ripristina l'inverter.
"""
self.enabled = True
# ------------------------------------------------------------------
def __repr__(self) -> str:
"""
Rappresentazione leggibile dell'inverter.
"""
return (
f"Inverter("
f"id={self.inverter_id}, "
f"nominal_power="
f"{self.nominal_power_kW}kW, "
f"combiners="
f"{len(self.combiners)}, "
f"enabled="
f"{self.enabled})"
)
```