1142 lines
24 KiB
Python
1142 lines
24 KiB
Python
```python
|
|
"""
|
|
simulator.py
|
|
|
|
Motore centrale della simulazione fotovoltaica.
|
|
|
|
Collega:
|
|
|
|
SunModel
|
|
|
|
|
v
|
|
WeatherModel
|
|
|
|
|
v
|
|
FaultManager
|
|
|
|
|
v
|
|
PVPlant
|
|
|
|
|
+-- Inverters
|
|
| |
|
|
| +-- CombinerBoxes
|
|
| |
|
|
| +-- PVPanels
|
|
|
|
|
v
|
|
SimulationResult
|
|
|
|
Il simulatore esegue la simulazione timestep per timestep.
|
|
|
|
Per ogni timestamp vengono calcolati:
|
|
|
|
- posizione del sole;
|
|
- condizioni meteorologiche;
|
|
- irraggiamento;
|
|
- temperatura;
|
|
- fault attivi;
|
|
- produzione dei pannelli;
|
|
- aggregazione Combiner Box;
|
|
- aggregazione Inverter;
|
|
- aggregazione Plant.
|
|
|
|
Il modulo è progettato per simulazioni:
|
|
|
|
- giornaliere;
|
|
- mensili;
|
|
- stagionali;
|
|
- annuali;
|
|
- multi-annuali.
|
|
|
|
Il timestep può essere configurato liberamente.
|
|
|
|
Esempio:
|
|
|
|
5 minuti
|
|
15 minuti
|
|
1 ora
|
|
|
|
Nota:
|
|
|
|
Il simulatore mantiene due livelli di output:
|
|
|
|
1. summary:
|
|
dati aggregati a livello impianto.
|
|
|
|
2. hierarchy:
|
|
dati dettagliati di pannelli,
|
|
combiner e inverter.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass, field
|
|
from datetime import datetime, timedelta
|
|
from typing import Any, Dict, List, Optional
|
|
|
|
import pandas as pd
|
|
|
|
from .plant import PVPlant
|
|
from .sun import SunModel
|
|
from .weather import WeatherModel
|
|
from .faults import FaultManager
|
|
|
|
|
|
# ======================================================================
|
|
# CONFIGURAZIONE SIMULAZIONE
|
|
# ======================================================================
|
|
|
|
|
|
@dataclass
|
|
class SimulationConfig:
|
|
"""
|
|
Configurazione temporale della simulazione.
|
|
|
|
Parameters
|
|
----------
|
|
start:
|
|
Timestamp iniziale.
|
|
|
|
end:
|
|
Timestamp finale.
|
|
|
|
timestep_minutes:
|
|
Durata del timestep in minuti.
|
|
|
|
years_from_start:
|
|
Anno relativo utilizzato per la degradazione.
|
|
|
|
store_panel_data:
|
|
Salva i dati dei singoli pannelli.
|
|
|
|
store_combiner_data:
|
|
Salva i dati delle Combiner Box.
|
|
|
|
store_inverter_data:
|
|
Salva i dati degli inverter.
|
|
|
|
store_plant_data:
|
|
Salva i dati aggregati dell'impianto.
|
|
|
|
generate_random_faults:
|
|
Abilita la generazione automatica dei fault.
|
|
"""
|
|
|
|
start: datetime
|
|
|
|
end: datetime
|
|
|
|
timestep_minutes: int = 5
|
|
|
|
years_from_start: float = 0.0
|
|
|
|
store_panel_data: bool = True
|
|
|
|
store_combiner_data: bool = True
|
|
|
|
store_inverter_data: bool = True
|
|
|
|
store_plant_data: bool = True
|
|
|
|
generate_random_faults: bool = False
|
|
|
|
def __post_init__(
|
|
self
|
|
) -> None:
|
|
|
|
if self.end <= self.start:
|
|
|
|
raise ValueError(
|
|
"end deve essere maggiore di start"
|
|
)
|
|
|
|
if self.timestep_minutes <= 0:
|
|
|
|
raise ValueError(
|
|
"timestep_minutes deve essere > 0"
|
|
)
|
|
|
|
|
|
# ======================================================================
|
|
# RISULTATO DELLA SIMULAZIONE
|
|
# ======================================================================
|
|
|
|
|
|
@dataclass
|
|
class SimulationResult:
|
|
"""
|
|
Contenitore dei risultati della simulazione.
|
|
|
|
I dati sono organizzati in quattro livelli:
|
|
|
|
plant
|
|
inverter
|
|
combiner
|
|
panel
|
|
|
|
Ogni elemento contiene una lista di record
|
|
successivamente convertibili in DataFrame.
|
|
"""
|
|
|
|
plant: List[Dict[str, Any]] = field(
|
|
default_factory=list
|
|
)
|
|
|
|
inverter: List[Dict[str, Any]] = field(
|
|
default_factory=list
|
|
)
|
|
|
|
combiner: List[Dict[str, Any]] = field(
|
|
default_factory=list
|
|
)
|
|
|
|
panel: List[Dict[str, Any]] = field(
|
|
default_factory=list
|
|
)
|
|
|
|
faults: List[Dict[str, Any]] = field(
|
|
default_factory=list
|
|
)
|
|
|
|
# ------------------------------------------------------------------
|
|
# Conversione DataFrame
|
|
# ------------------------------------------------------------------
|
|
|
|
def plant_dataframe(
|
|
self
|
|
) -> pd.DataFrame:
|
|
|
|
return pd.DataFrame(
|
|
self.plant
|
|
)
|
|
|
|
# ------------------------------------------------------------------
|
|
|
|
def inverter_dataframe(
|
|
self
|
|
) -> pd.DataFrame:
|
|
|
|
return pd.DataFrame(
|
|
self.inverter
|
|
)
|
|
|
|
# ------------------------------------------------------------------
|
|
|
|
def combiner_dataframe(
|
|
self
|
|
) -> pd.DataFrame:
|
|
|
|
return pd.DataFrame(
|
|
self.combiner
|
|
)
|
|
|
|
# ------------------------------------------------------------------
|
|
|
|
def panel_dataframe(
|
|
self
|
|
) -> pd.DataFrame:
|
|
|
|
return pd.DataFrame(
|
|
self.panel
|
|
)
|
|
|
|
# ------------------------------------------------------------------
|
|
|
|
def faults_dataframe(
|
|
self
|
|
) -> pd.DataFrame:
|
|
|
|
return pd.DataFrame(
|
|
self.faults
|
|
)
|
|
|
|
# ------------------------------------------------------------------
|
|
# Export CSV
|
|
# ------------------------------------------------------------------
|
|
|
|
def export_csv(
|
|
self,
|
|
output_dir: str
|
|
) -> None:
|
|
"""
|
|
Esporta tutti i livelli in file CSV.
|
|
"""
|
|
|
|
import os
|
|
|
|
os.makedirs(
|
|
output_dir,
|
|
exist_ok=True
|
|
)
|
|
|
|
if self.plant:
|
|
|
|
self.plant_dataframe().to_csv(
|
|
|
|
os.path.join(
|
|
|
|
output_dir,
|
|
|
|
"plant.csv"
|
|
),
|
|
|
|
index=False
|
|
)
|
|
|
|
if self.inverter:
|
|
|
|
self.inverter_dataframe().to_csv(
|
|
|
|
os.path.join(
|
|
|
|
output_dir,
|
|
|
|
"inverter.csv"
|
|
),
|
|
|
|
index=False
|
|
)
|
|
|
|
if self.combiner:
|
|
|
|
self.combiner_dataframe().to_csv(
|
|
|
|
os.path.join(
|
|
|
|
output_dir,
|
|
|
|
"combiner.csv"
|
|
),
|
|
|
|
index=False
|
|
)
|
|
|
|
if self.panel:
|
|
|
|
self.panel_dataframe().to_csv(
|
|
|
|
os.path.join(
|
|
|
|
output_dir,
|
|
|
|
"panel.csv"
|
|
),
|
|
|
|
index=False
|
|
)
|
|
|
|
if self.faults:
|
|
|
|
self.faults_dataframe().to_csv(
|
|
|
|
os.path.join(
|
|
|
|
output_dir,
|
|
|
|
"faults.csv"
|
|
),
|
|
|
|
index=False
|
|
)
|
|
|
|
|
|
# ======================================================================
|
|
# SIMULATORE
|
|
# ======================================================================
|
|
|
|
|
|
@dataclass
|
|
class PVSimulator:
|
|
"""
|
|
Motore principale della simulazione.
|
|
|
|
Parameters
|
|
----------
|
|
plant:
|
|
Modello gerarchico dell'impianto.
|
|
|
|
sun:
|
|
Modello della posizione solare.
|
|
|
|
weather:
|
|
Modello meteorologico.
|
|
|
|
fault_manager:
|
|
Gestore dei fault.
|
|
"""
|
|
|
|
plant: PVPlant
|
|
|
|
sun: SunModel
|
|
|
|
weather: WeatherModel
|
|
|
|
fault_manager: FaultManager
|
|
|
|
# ------------------------------------------------------------------
|
|
# Preparazione ID
|
|
# ------------------------------------------------------------------
|
|
|
|
def _get_component_ids(
|
|
self
|
|
) -> Dict[str, List[str]]:
|
|
"""
|
|
Estrae tutti gli ID della gerarchia.
|
|
"""
|
|
|
|
panel_ids = []
|
|
|
|
combiner_ids = []
|
|
|
|
inverter_ids = []
|
|
|
|
for inverter in self.plant.inverters:
|
|
|
|
inverter_ids.append(
|
|
|
|
inverter.inverter_id
|
|
)
|
|
|
|
for combiner in (
|
|
inverter.combiners
|
|
):
|
|
|
|
combiner_ids.append(
|
|
|
|
combiner.combiner_id
|
|
)
|
|
|
|
for panel in (
|
|
combiner.panels
|
|
):
|
|
|
|
panel_ids.append(
|
|
|
|
panel.panel_id
|
|
)
|
|
|
|
return {
|
|
|
|
"panel":
|
|
panel_ids,
|
|
|
|
"combiner":
|
|
combiner_ids,
|
|
|
|
"inverter":
|
|
inverter_ids
|
|
}
|
|
|
|
# ------------------------------------------------------------------
|
|
# Applicazione fault
|
|
# ------------------------------------------------------------------
|
|
|
|
def _get_fault_factor(
|
|
self,
|
|
component_id: str,
|
|
timestamp: datetime
|
|
) -> float:
|
|
"""
|
|
Restituisce il fattore di produzione dovuto ai fault.
|
|
"""
|
|
|
|
return (
|
|
|
|
self.fault_manager
|
|
.get_component_factor(
|
|
|
|
component_id,
|
|
|
|
timestamp
|
|
)
|
|
)
|
|
|
|
# ------------------------------------------------------------------
|
|
# Dati fault
|
|
# ------------------------------------------------------------------
|
|
|
|
def _collect_fault_records(
|
|
self,
|
|
timestamp: datetime
|
|
) -> List[Dict[str, Any]]:
|
|
"""
|
|
Converte i fault attivi in record serializzabili.
|
|
"""
|
|
|
|
records = []
|
|
|
|
active_faults = (
|
|
|
|
self.fault_manager
|
|
.get_active_faults(
|
|
timestamp
|
|
)
|
|
)
|
|
|
|
for fault in active_faults:
|
|
|
|
records.append({
|
|
|
|
"timestamp":
|
|
timestamp,
|
|
|
|
"fault_id":
|
|
fault.fault_id,
|
|
|
|
"fault_type":
|
|
fault.fault_type.value,
|
|
|
|
"component_level":
|
|
fault.component_level.value,
|
|
|
|
"component_id":
|
|
fault.component_id,
|
|
|
|
"severity":
|
|
fault.severity,
|
|
|
|
"reduction_factor":
|
|
fault.reduction_factor(),
|
|
|
|
"description":
|
|
fault.description
|
|
})
|
|
|
|
return records
|
|
|
|
# ------------------------------------------------------------------
|
|
# Simulazione singolo timestep
|
|
# ------------------------------------------------------------------
|
|
|
|
def simulate_timestep(
|
|
self,
|
|
timestamp: datetime,
|
|
config: SimulationConfig
|
|
) -> Dict[str, Any]:
|
|
"""
|
|
Esegue un singolo timestep.
|
|
|
|
Il metodo restituisce i dati gerarchici del timestep.
|
|
"""
|
|
|
|
# --------------------------------------------------------------
|
|
# Condizioni meteorologiche
|
|
# --------------------------------------------------------------
|
|
|
|
weather = (
|
|
|
|
self.weather.get_conditions(
|
|
|
|
timestamp=
|
|
|
|
timestamp
|
|
)
|
|
)
|
|
|
|
irradiance = (
|
|
|
|
weather[
|
|
"poa_global_Wm2"
|
|
]
|
|
)
|
|
|
|
ambient_temperature = (
|
|
|
|
weather[
|
|
"ambient_temperature_C"
|
|
]
|
|
)
|
|
|
|
# --------------------------------------------------------------
|
|
# Fault automatici
|
|
# --------------------------------------------------------------
|
|
|
|
if config.generate_random_faults:
|
|
|
|
ids = (
|
|
self._get_component_ids()
|
|
)
|
|
|
|
self.fault_manager.simulate_random_faults(
|
|
|
|
timestamp=
|
|
|
|
timestamp,
|
|
|
|
panel_ids=
|
|
|
|
ids[
|
|
"panel"
|
|
],
|
|
|
|
combiner_ids=
|
|
|
|
ids[
|
|
"combiner"
|
|
],
|
|
|
|
inverter_ids=
|
|
|
|
ids[
|
|
"inverter"
|
|
]
|
|
)
|
|
|
|
# --------------------------------------------------------------
|
|
# Aggiornamento Plant
|
|
# --------------------------------------------------------------
|
|
|
|
plant_data = (
|
|
|
|
self.plant.update(
|
|
|
|
timestamp=
|
|
|
|
timestamp,
|
|
|
|
irradiance=
|
|
|
|
irradiance,
|
|
|
|
ambient_temperature=
|
|
|
|
ambient_temperature,
|
|
|
|
years_from_start=
|
|
|
|
config.years_from_start,
|
|
|
|
timestep_minutes=
|
|
|
|
config.timestep_minutes
|
|
)
|
|
)
|
|
|
|
# --------------------------------------------------------------
|
|
# Applicazione fault Plant
|
|
# --------------------------------------------------------------
|
|
|
|
plant_factor = (
|
|
|
|
self._get_fault_factor(
|
|
|
|
self.plant.plant_id,
|
|
|
|
timestamp
|
|
)
|
|
)
|
|
|
|
plant_data[
|
|
"fault_factor"
|
|
] = plant_factor
|
|
|
|
plant_data[
|
|
"effective_ac_power_W"
|
|
] = (
|
|
|
|
plant_data[
|
|
"ac_power_W"
|
|
]
|
|
|
|
* plant_factor
|
|
)
|
|
|
|
# --------------------------------------------------------------
|
|
# Output
|
|
# --------------------------------------------------------------
|
|
|
|
return {
|
|
|
|
"timestamp":
|
|
timestamp,
|
|
|
|
"weather":
|
|
weather,
|
|
|
|
"plant":
|
|
plant_data,
|
|
|
|
"faults":
|
|
self._collect_fault_records(
|
|
|
|
timestamp
|
|
)
|
|
}
|
|
|
|
# ------------------------------------------------------------------
|
|
# Simulazione completa
|
|
# ------------------------------------------------------------------
|
|
|
|
def run(
|
|
self,
|
|
config: SimulationConfig
|
|
) -> SimulationResult:
|
|
"""
|
|
Esegue la simulazione completa.
|
|
|
|
Returns
|
|
-------
|
|
SimulationResult
|
|
Risultati a tutti i livelli.
|
|
"""
|
|
|
|
result = (
|
|
SimulationResult()
|
|
)
|
|
|
|
timestamp = (
|
|
config.start
|
|
)
|
|
|
|
# --------------------------------------------------------------
|
|
# Loop temporale
|
|
# --------------------------------------------------------------
|
|
|
|
while timestamp < config.end:
|
|
|
|
timestep = (
|
|
|
|
self.simulate_timestep(
|
|
|
|
timestamp,
|
|
|
|
config
|
|
)
|
|
)
|
|
|
|
weather = (
|
|
|
|
timestep[
|
|
"weather"
|
|
]
|
|
)
|
|
|
|
plant_data = (
|
|
|
|
timestep[
|
|
"plant"
|
|
]
|
|
)
|
|
|
|
# ----------------------------------------------------------
|
|
# Plant level
|
|
# ----------------------------------------------------------
|
|
|
|
if config.store_plant_data:
|
|
|
|
plant_record = {
|
|
|
|
**plant_data,
|
|
|
|
"ambient_temperature_C":
|
|
|
|
weather[
|
|
"ambient_temperature_C"
|
|
],
|
|
|
|
"ghi_Wm2":
|
|
|
|
weather[
|
|
"ghi_Wm2"
|
|
],
|
|
|
|
"dni_Wm2":
|
|
|
|
weather[
|
|
"dni_Wm2"
|
|
],
|
|
|
|
"dhi_Wm2":
|
|
|
|
weather[
|
|
"dhi_Wm2"
|
|
],
|
|
|
|
"poa_global_Wm2":
|
|
|
|
weather[
|
|
"poa_global_Wm2"
|
|
],
|
|
|
|
"cloud_factor":
|
|
|
|
weather[
|
|
"cloud_factor"
|
|
],
|
|
|
|
"rain_factor":
|
|
|
|
weather[
|
|
"rain_factor"
|
|
]
|
|
}
|
|
|
|
result.plant.append(
|
|
|
|
plant_record
|
|
)
|
|
|
|
# ----------------------------------------------------------
|
|
# Inverter level
|
|
# ----------------------------------------------------------
|
|
|
|
if config.store_inverter_data:
|
|
|
|
for inverter in (
|
|
self.plant.inverters
|
|
):
|
|
|
|
inverter_factor = (
|
|
|
|
self._get_fault_factor(
|
|
|
|
inverter.inverter_id,
|
|
|
|
timestamp
|
|
)
|
|
)
|
|
|
|
inverter_record = {
|
|
|
|
"timestamp":
|
|
timestamp,
|
|
|
|
"inverter_id":
|
|
inverter.inverter_id,
|
|
|
|
"nominal_power_kW":
|
|
inverter.nominal_power_kW,
|
|
|
|
"fault_factor":
|
|
inverter_factor,
|
|
|
|
"enabled":
|
|
inverter.enabled
|
|
}
|
|
|
|
result.inverter.append(
|
|
|
|
inverter_record
|
|
)
|
|
|
|
# --------------------------------------------------
|
|
# Combiner level
|
|
# --------------------------------------------------
|
|
|
|
if config.store_combiner_data:
|
|
|
|
for combiner in (
|
|
inverter.combiners
|
|
):
|
|
|
|
combiner_factor = (
|
|
|
|
self._get_fault_factor(
|
|
|
|
combiner.combiner_id,
|
|
|
|
timestamp
|
|
)
|
|
)
|
|
|
|
combiner_record = {
|
|
|
|
"timestamp":
|
|
timestamp,
|
|
|
|
"inverter_id":
|
|
inverter.inverter_id,
|
|
|
|
"combiner_id":
|
|
combiner.combiner_id,
|
|
|
|
"fault_factor":
|
|
combiner_factor,
|
|
|
|
"panel_count":
|
|
len(
|
|
combiner.panels
|
|
)
|
|
}
|
|
|
|
result.combiner.append(
|
|
|
|
combiner_record
|
|
)
|
|
|
|
# ------------------------------------------
|
|
# Panel level
|
|
# ------------------------------------------
|
|
|
|
if config.store_panel_data:
|
|
|
|
for panel in (
|
|
combiner.panels
|
|
):
|
|
|
|
panel_factor = (
|
|
|
|
self._get_fault_factor(
|
|
|
|
panel.panel_id,
|
|
|
|
timestamp
|
|
)
|
|
)
|
|
|
|
panel_record = {
|
|
|
|
"timestamp":
|
|
timestamp,
|
|
|
|
"inverter_id":
|
|
inverter.inverter_id,
|
|
|
|
"combiner_id":
|
|
combiner.combiner_id,
|
|
|
|
"panel_id":
|
|
panel.panel_id,
|
|
|
|
"fault_factor":
|
|
panel_factor,
|
|
|
|
"enabled":
|
|
panel.enabled,
|
|
|
|
"nominal_power_W":
|
|
panel.nominal_power
|
|
}
|
|
|
|
result.panel.append(
|
|
|
|
panel_record
|
|
)
|
|
|
|
# ----------------------------------------------------------
|
|
# Fault level
|
|
# ----------------------------------------------------------
|
|
|
|
result.faults.extend(
|
|
|
|
timestep[
|
|
"faults"
|
|
]
|
|
)
|
|
|
|
# ----------------------------------------------------------
|
|
# Incremento temporale
|
|
# ----------------------------------------------------------
|
|
|
|
timestamp += timedelta(
|
|
|
|
minutes=
|
|
config.timestep_minutes
|
|
)
|
|
|
|
return result
|
|
|
|
# ------------------------------------------------------------------
|
|
# Simulazione rapida
|
|
# ------------------------------------------------------------------
|
|
|
|
def run_single_day(
|
|
self,
|
|
date: datetime,
|
|
timestep_minutes: int = 5
|
|
) -> SimulationResult:
|
|
"""
|
|
Esegue una simulazione di una singola giornata.
|
|
"""
|
|
|
|
start = datetime(
|
|
|
|
year=
|
|
date.year,
|
|
|
|
month=
|
|
date.month,
|
|
|
|
day=
|
|
date.day
|
|
)
|
|
|
|
end = (
|
|
|
|
start
|
|
|
|
+ timedelta(
|
|
days=1
|
|
)
|
|
)
|
|
|
|
config = SimulationConfig(
|
|
|
|
start=
|
|
start,
|
|
|
|
end=
|
|
end,
|
|
|
|
timestep_minutes=
|
|
timestep_minutes
|
|
)
|
|
|
|
return self.run(
|
|
config
|
|
)
|
|
|
|
# ------------------------------------------------------------------
|
|
# Simulazione annuale
|
|
# ------------------------------------------------------------------
|
|
|
|
def run_year(
|
|
self,
|
|
year: int,
|
|
timestep_minutes: int = 15
|
|
) -> SimulationResult:
|
|
"""
|
|
Esegue una simulazione annuale.
|
|
|
|
Nota:
|
|
per una simulazione annuale a 5 minuti
|
|
il numero di record a livello pannello
|
|
può diventare molto elevato.
|
|
|
|
Per questo motivo il default è 15 minuti.
|
|
"""
|
|
|
|
start = datetime(
|
|
|
|
year=
|
|
|
|
year,
|
|
|
|
month=
|
|
|
|
1,
|
|
|
|
day=
|
|
|
|
1
|
|
)
|
|
|
|
end = datetime(
|
|
|
|
year=
|
|
|
|
year + 1,
|
|
|
|
month=
|
|
|
|
1,
|
|
|
|
day=
|
|
|
|
1
|
|
)
|
|
|
|
config = SimulationConfig(
|
|
|
|
start=
|
|
start,
|
|
|
|
end=
|
|
end,
|
|
|
|
timestep_minutes=
|
|
timestep_minutes,
|
|
|
|
years_from_start=
|
|
0.0
|
|
)
|
|
|
|
return self.run(
|
|
config
|
|
)
|
|
|
|
# ------------------------------------------------------------------
|
|
# Riepilogo
|
|
# ------------------------------------------------------------------
|
|
|
|
def summary(
|
|
self,
|
|
result: SimulationResult
|
|
) -> Dict[str, Any]:
|
|
"""
|
|
Calcola un riepilogo della simulazione.
|
|
"""
|
|
|
|
summary = {
|
|
|
|
"plant_id":
|
|
self.plant.plant_id,
|
|
|
|
"inverters":
|
|
self.plant.get_inverter_count(),
|
|
|
|
"combiners":
|
|
self.plant.get_combiner_count(),
|
|
|
|
"panels":
|
|
self.plant.get_panel_count(),
|
|
|
|
"plant_records":
|
|
len(
|
|
result.plant
|
|
),
|
|
|
|
"inverter_records":
|
|
len(
|
|
result.inverter
|
|
),
|
|
|
|
"combiner_records":
|
|
len(
|
|
result.combiner
|
|
),
|
|
|
|
"panel_records":
|
|
len(
|
|
result.panel
|
|
),
|
|
|
|
"fault_records":
|
|
len(
|
|
result.faults
|
|
),
|
|
|
|
"total_ac_energy_Wh":
|
|
self.plant.total_ac_energy_Wh,
|
|
|
|
"total_dc_energy_Wh":
|
|
self.plant.total_dc_energy_Wh
|
|
}
|
|
|
|
return summary
|
|
|
|
# ------------------------------------------------------------------
|
|
# Rappresentazione
|
|
# ------------------------------------------------------------------
|
|
|
|
def __repr__(
|
|
self
|
|
) -> str:
|
|
"""
|
|
Rappresentazione del simulatore.
|
|
"""
|
|
|
|
return (
|
|
|
|
f"PVSimulator("
|
|
f"plant="
|
|
f"{self.plant.plant_id}, "
|
|
f"inverters="
|
|
f"{self.plant.get_inverter_count()}, "
|
|
f"combiners="
|
|
f"{self.plant.get_combiner_count()}, "
|
|
f"panels="
|
|
f"{self.plant.get_panel_count()})"
|
|
)
|
|
```
|