From 81eb7fdabe846fdf71572edf7e001b77b87e49a4 Mon Sep 17 00:00:00 2001 From: Giovanni Date: Sun, 19 Jul 2026 20:27:52 +0000 Subject: [PATCH] Update pvsim/simulator.py --- pvsim/simulator.py | 1527 +++++++++++++++----------------------------- 1 file changed, 528 insertions(+), 999 deletions(-) diff --git a/pvsim/simulator.py b/pvsim/simulator.py index 2fd1762..614e51c 100644 --- a/pvsim/simulator.py +++ b/pvsim/simulator.py @@ -1,20 +1,10 @@ -```python """ pvsim/simulator.py -Motore centrale della simulazione fotovoltaica. +Motore principale della simulazione fotovoltaica. -Il simulatore coordina: +Gerarchia: - SunModel - | - v - WeatherModel - | - v - FaultManager - | - v PVPlant | +-- Inverter @@ -24,54 +14,39 @@ Il simulatore coordina: | +-- PVPanel | v - SimulationStep + SimulationResult | - +-- WeatherData - +-- PanelData[] - +-- CombinerData[] - +-- InverterData[] - +-- PlantData - +-- FaultData[] + +-- SimulationStep + | + +-- WeatherData + +-- PanelData[] + +-- CombinerData[] + +-- InverterData[] + +-- PlantData + +-- FaultData[] -Il simulatore non utilizza più dizionari generici -per rappresentare la telemetria. +Responsabilità: -Tutti i dati prodotti vengono convertiti nei modelli -definiti in pvsim.models. + simulator.py + -> simula -Output gerarchico: + statistics.py + -> analizza - Plant - | - +-- Inverter - | | - | +-- Combiner - | | - | +-- Panel - | - +-- Faults - | - +-- Weather - -Il risultato finale è contenuto in SimulationResult. + exporter.py + -> esporta """ from __future__ import annotations from dataclasses import dataclass, field + from datetime import datetime, timedelta -from typing import Any, Dict, List + +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, - ComponentLevel, -) - from .models import ( WeatherData, PanelData, @@ -80,12 +55,19 @@ from .models import ( PlantData, FaultData, SimulationStep, - create_panel_data, ) +from .plant import PVPlant + +from .sun import SunModel + +from .weather import WeatherModel + +from .faults import FaultManager + # ====================================================================== -# CONFIGURAZIONE +# CONFIGURATION # ====================================================================== @@ -93,41 +75,6 @@ from .models import ( class SimulationConfig: """ Configurazione temporale della simulazione. - - Parameters - ---------- - start: - Timestamp iniziale. - - end: - Timestamp finale. - - timestep_minutes: - Durata del timestep in minuti. - - years_from_start: - Anni trascorsi dall'installazione dell'impianto. - - generate_random_faults: - Abilita la generazione automatica dei fault. - - store_panel_data: - Memorizza i dati dei pannelli. - - store_combiner_data: - Memorizza i dati dei combiner. - - store_inverter_data: - Memorizza i dati degli inverter. - - store_plant_data: - Memorizza i dati dell'impianto. - - store_weather_data: - Memorizza i dati meteorologici. - - store_fault_data: - Memorizza i dati dei fault. """ start: datetime @@ -136,8 +83,6 @@ class SimulationConfig: timestep_minutes: int = 5 - years_from_start: float = 0.0 - generate_random_faults: bool = False store_panel_data: bool = True @@ -159,18 +104,22 @@ class SimulationConfig: if self.end <= self.start: raise ValueError( - "end deve essere maggiore di start" + + "end deve essere " + "maggiore di start" ) if self.timestep_minutes <= 0: raise ValueError( - "timestep_minutes deve essere maggiore di zero" + + "timestep_minutes deve " + "essere maggiore di zero" ) # ====================================================================== -# RISULTATI +# RESULT # ====================================================================== @@ -178,32 +127,15 @@ class SimulationConfig: class SimulationResult: """ Risultato completo della simulazione. - - La struttura principale è: - - steps - | - +-- SimulationStep - | - +-- WeatherData - +-- PanelData[] - +-- CombinerData[] - +-- InverterData[] - +-- PlantData - +-- FaultData[] - - Per comodità vengono inoltre mantenuti metodi - per ottenere direttamente DataFrame tabellari. """ steps: List[ SimulationStep ] = field( + default_factory=list ) - # ------------------------------------------------------------------ - # Proprietà # ------------------------------------------------------------------ @property @@ -216,6 +148,7 @@ class SimulationResult: for step in self.steps: records.extend( + step.panels ) @@ -233,6 +166,7 @@ class SimulationResult: for step in self.steps: records.extend( + step.combiners ) @@ -250,6 +184,7 @@ class SimulationResult: for step in self.steps: records.extend( + step.inverters ) @@ -269,6 +204,7 @@ class SimulationResult: if step.plant is not None: records.append( + step.plant ) @@ -288,6 +224,7 @@ class SimulationResult: if step.weather is not None: records.append( + step.weather ) @@ -305,6 +242,7 @@ class SimulationResult: for step in self.steps: records.extend( + step.faults ) @@ -314,13 +252,10 @@ class SimulationResult: # DATAFRAME # ================================================================== - def _records_to_dataframe( - self, + @staticmethod + def _to_dataframe( records: List[Any], ) -> pd.DataFrame: - """ - Converte una lista di modelli in DataFrame. - """ if not records: @@ -329,6 +264,7 @@ class SimulationResult: return pd.DataFrame( [ + record.to_dict() for record @@ -342,7 +278,7 @@ class SimulationResult: self, ) -> pd.DataFrame: - return self._records_to_dataframe( + return self._to_dataframe( self.panel_records ) @@ -353,7 +289,7 @@ class SimulationResult: self, ) -> pd.DataFrame: - return self._records_to_dataframe( + return self._to_dataframe( self.combiner_records ) @@ -364,7 +300,7 @@ class SimulationResult: self, ) -> pd.DataFrame: - return self._records_to_dataframe( + return self._to_dataframe( self.inverter_records ) @@ -375,7 +311,7 @@ class SimulationResult: self, ) -> pd.DataFrame: - return self._records_to_dataframe( + return self._to_dataframe( self.plant_records ) @@ -386,7 +322,7 @@ class SimulationResult: self, ) -> pd.DataFrame: - return self._records_to_dataframe( + return self._to_dataframe( self.weather_records ) @@ -397,133 +333,11 @@ class SimulationResult: self, ) -> pd.DataFrame: - return self._records_to_dataframe( + return self._to_dataframe( self.fault_records ) - # ================================================================== - # EXPORT CSV - # ================================================================== - - def export_csv( - self, - output_dir: str, - ) -> None: - """ - Esporta i dati della simulazione in CSV. - """ - - from pathlib import Path - - output_path = Path( - output_dir - ) - - output_path.mkdir( - - parents=True, - - exist_ok=True - ) - - datasets = { - - "weather": - self.weather_dataframe(), - - "panel": - self.panel_dataframe(), - - "combiner": - self.combiner_dataframe(), - - "inverter": - self.inverter_dataframe(), - - "plant": - self.plant_dataframe(), - - "faults": - self.faults_dataframe(), - } - - for name, dataframe in datasets.items(): - - if dataframe.empty: - - continue - - dataframe.to_csv( - - output_path - / f"{name}.csv", - - index=False - ) - - # ================================================================== - # EXPORT PARQUET - # ================================================================== - - def export_parquet( - self, - output_dir: str, - ) -> None: - """ - Esporta i dati in formato Parquet. - - Richiede pyarrow o fastparquet. - """ - - from pathlib import Path - - output_path = Path( - output_dir - ) - - output_path.mkdir( - - parents=True, - - exist_ok=True - ) - - datasets = { - - "weather": - self.weather_dataframe(), - - "panel": - self.panel_dataframe(), - - "combiner": - self.combiner_dataframe(), - - "inverter": - self.inverter_dataframe(), - - "plant": - self.plant_dataframe(), - - "faults": - self.faults_dataframe(), - } - - for name, dataframe in datasets.items(): - - if dataframe.empty: - - continue - - dataframe.to_parquet( - - output_path - / f"{name}.parquet", - - index=False - ) - # ================================================================== # SUMMARY # ================================================================== @@ -531,43 +345,47 @@ class SimulationResult: def summary( self, ) -> Dict[str, Any]: - """ - Restituisce un riepilogo del risultato. - """ return { "timesteps": + len( self.steps ), "weather_records": + len( self.weather_records ), "panel_records": + len( self.panel_records ), "combiner_records": + len( self.combiner_records ), "inverter_records": + len( self.inverter_records ), "plant_records": + len( self.plant_records ), "fault_records": + len( self.fault_records ), @@ -575,122 +393,52 @@ class SimulationResult: # ====================================================================== -# SIMULATORE +# SIMULATOR # ====================================================================== -@dataclass class PVSimulator: """ - Motore principale della simulazione. - - Parameters - ---------- - plant: - Modello gerarchico dell'impianto. - - sun: - Modello solare. - - weather: - Modello meteorologico. - - fault_manager: - Gestore dei fault. + Motore centrale di simulazione. """ - plant: PVPlant - - sun: SunModel - - weather: WeatherModel - - fault_manager: FaultManager - - # ================================================================== - # ID COMPONENTI - # ================================================================== - - def _get_component_ids( + def __init__( self, - ) -> Dict[str, List[str]]: - """ - Estrae gli ID di tutti i componenti. + plant: PVPlant, + sun: SunModel, + weather: WeatherModel, + fault_manager: FaultManager, + ) -> None: - Gerarchia: + self.plant = plant - inverter - | - +-- combiner - | - +-- panel - """ + self.sun = sun - panel_ids = [] + self.weather = weather - 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, - } + self.fault_manager = fault_manager # ================================================================== - # WEATHER MODEL + # WEATHER # ================================================================== def _build_weather_data( self, timestamp: datetime, ) -> WeatherData: - """ - Converte l'output del WeatherModel - nel modello WeatherData. - """ conditions = ( self.weather.get_conditions( - timestamp= - - timestamp + timestamp ) ) return WeatherData( timestamp= + timestamp, ambient_temperature_C= @@ -785,78 +533,65 @@ class PVSimulator: ) # ================================================================== - # FAULT DATA + # COMPONENT IDS # ================================================================== - def _build_fault_data( + def _component_ids( self, - timestamp: datetime, - ) -> List[FaultData]: - """ - Converte i fault attivi in FaultData. - """ + ) -> Dict[ + str, + List[str] + ]: - records = [] + panel_ids = [] - active_faults = ( + combiner_ids = [] - self.fault_manager - .get_active_faults( + inverter_ids = [] - timestamp + for inverter in self.plant.inverters: + + inverter_ids.append( + + inverter.inverter_id ) - ) - for fault in active_faults: + for combiner in inverter.combiners: - records.append( + combiner_ids.append( - FaultData( - - 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, + combiner.combiner_id ) - ) - return records + for panel in combiner.panels: + + panel_ids.append( + + panel.panel_id + ) + + return { + + "panel": + panel_ids, + + "combiner": + combiner_ids, + + "inverter": + inverter_ids, + } # ================================================================== - # FAULT AUTOMATICI + # RANDOM FAULTS # ================================================================== - def _generate_random_faults( + def _generate_faults( self, timestamp: datetime, ) -> None: - """ - Genera eventuali fault casuali. - """ - ids = ( - - self._get_component_ids() - ) + ids = self._component_ids() self.fault_manager.simulate_random_faults( @@ -884,23 +619,14 @@ class PVSimulator: ) # ================================================================== - # PANEL DATA + # PANEL # ================================================================== - def _build_panel_data( + def _simulate_panels( self, timestamp: datetime, weather: WeatherData, ) -> List[PanelData]: - """ - Genera la telemetria di tutti i pannelli. - - La produzione del pannello viene calcolata - dal modello PVPanel. - - Il fault factor viene applicato dopo - il calcolo della produzione fisica. - """ records = [] @@ -910,10 +636,6 @@ class PVSimulator: for panel in combiner.panels: - # -------------------------------------------------- - # Fault - # -------------------------------------------------- - fault_factor = ( self.fault_manager @@ -925,13 +647,9 @@ class PVSimulator: ) ) - # -------------------------------------------------- - # Produzione fisica - # -------------------------------------------------- - try: - panel_result = ( + result = ( panel.simulate( @@ -953,14 +671,14 @@ class PVSimulator: dc_power = ( - panel_result[ + result[ "dc_power_W" ] ) panel_temperature = ( - panel_result.get( + result.get( "panel_temperature_C", @@ -975,10 +693,6 @@ class PVSimulator: KeyError, ): - # -------------------------------------------------- - # Fallback compatibilità - # -------------------------------------------------- - dc_power = ( panel.nominal_power @@ -998,22 +712,9 @@ class PVSimulator: .ambient_temperature_C ) - # -------------------------------------------------- - # Fault - # -------------------------------------------------- + dc_power *= fault_factor - effective_power = ( - - dc_power - - * fault_factor - ) - - # -------------------------------------------------- - # Model - # -------------------------------------------------- - - record = create_panel_data( + record = PanelData( timestamp= @@ -1033,7 +734,7 @@ class PVSimulator: dc_power_W= - effective_power, + dc_power, nominal_power_W= @@ -1058,23 +759,21 @@ class PVSimulator: ) records.append( + record ) return records # ================================================================== - # COMBINER DATA + # COMBINER # ================================================================== - def _build_combiner_data( + def _simulate_combiners( self, timestamp: datetime, - panel_records: List[PanelData], + panels: List[PanelData], ) -> List[CombinerData]: - """ - Aggrega i dati dei pannelli a livello Combiner Box. - """ records = [] @@ -1082,12 +781,11 @@ class PVSimulator: for combiner in inverter.combiners: - panels = [ + component_panels = [ panel - for panel - in panel_records + for panel in panels if ( @@ -1097,22 +795,24 @@ class PVSimulator: ) ] - if not panels: + if not component_panels: continue - dc_power = sum( + raw_power = sum( panel.dc_power_W - for panel in panels + for panel + in component_panels ) nominal_power = sum( panel.nominal_power_W - for panel in panels + for panel + in component_panels ) active_panels = sum( @@ -1120,7 +820,7 @@ class PVSimulator: 1 for panel - in panels + in component_panels if panel.enabled and panel.dc_power_W > 0 @@ -1137,39 +837,25 @@ class PVSimulator: ) ) - # ------------------------------------------------------ - # Applica fault Combiner - # ------------------------------------------------------ + dc_power = ( - effective_power = ( - - dc_power + raw_power * fault_factor ) - # ------------------------------------------------------ - # Corrente e tensione - # ------------------------------------------------------ + dc_voltage = 400.0 - dc_voltage = 0.0 + dc_current = ( - dc_current = 0.0 + dc_power - if effective_power > 0: + / dc_voltage - dc_voltage = 400.0 + if dc_voltage > 0 - dc_current = ( - - effective_power - - / dc_voltage - ) - - # ------------------------------------------------------ - # Stato - # ------------------------------------------------------ + else 0.0 + ) fault_active = ( @@ -1192,95 +878,93 @@ class PVSimulator: active_panels - / len(panels) - ) - - record = CombinerData( - - timestamp= - - timestamp, - - combiner_id= - - combiner.combiner_id, - - inverter_id= - - inverter.inverter_id, - - dc_power_W= - - effective_power, - - dc_voltage_V= - - dc_voltage, - - dc_current_A= - - dc_current, - - nominal_power_W= - - nominal_power, - - panel_count= - - len(panels), - - active_panel_count= - - active_panels, - - fault_factor= - - fault_factor, - - fault_active= - - fault_active, - - availability= - - availability, - - status= - - status, + / len( + component_panels + ) ) records.append( - record + + CombinerData( + + timestamp= + + timestamp, + + combiner_id= + + combiner.combiner_id, + + inverter_id= + + inverter.inverter_id, + + dc_power_W= + + dc_power, + + dc_voltage_V= + + dc_voltage, + + dc_current_A= + + dc_current, + + nominal_power_W= + + nominal_power, + + panel_count= + + len( + component_panels + ), + + active_panel_count= + + active_panels, + + fault_factor= + + fault_factor, + + fault_active= + + fault_active, + + availability= + + availability, + + status= + + status, + ) ) return records # ================================================================== - # INVERTER DATA + # INVERTER # ================================================================== - def _build_inverter_data( + def _simulate_inverters( self, timestamp: datetime, - combiner_records: List[CombinerData], + combiners: List[CombinerData], weather: WeatherData, ) -> List[InverterData]: - """ - Aggrega i dati delle Combiner Box - a livello inverter. - """ records = [] for inverter in self.plant.inverters: - combiners = [ + component_combiners = [ combiner - for combiner - in combiner_records + for combiner in combiners if ( @@ -1290,7 +974,7 @@ class PVSimulator: ) ] - if not combiners: + if not component_combiners: continue @@ -1299,7 +983,7 @@ class PVSimulator: combiner.dc_power_W for combiner - in combiners + in component_combiners ) nominal_power = sum( @@ -1307,24 +991,9 @@ class PVSimulator: combiner.nominal_power_W for combiner - in combiners + in component_combiners ) - active_combiners = sum( - - 1 - - for combiner - in combiners - - if combiner.status - != "fault" - ) - - # ---------------------------------------------------------- - # Fault inverter - # ---------------------------------------------------------- - fault_factor = ( self.fault_manager @@ -1338,23 +1007,15 @@ class PVSimulator: dc_power *= fault_factor - # ---------------------------------------------------------- - # Efficienza - # ---------------------------------------------------------- - - inverter_efficiency = 0.97 + efficiency = 0.97 ac_power = ( dc_power - * inverter_efficiency + * efficiency ) - # ---------------------------------------------------------- - # Clipping - # ---------------------------------------------------------- - inverter_nominal = ( inverter.nominal_power_kW @@ -1374,69 +1035,37 @@ class PVSimulator: ) ac_power = ( + inverter_nominal ) - # ---------------------------------------------------------- - # Fault inverter - # ---------------------------------------------------------- - - fault_active = ( - - fault_factor < 1.0 - ) - - if fault_active: - - status = "fault" - - elif ac_power <= 0: - - status = "idle" - - else: - - status = "normal" - - # ---------------------------------------------------------- - # Elettrico - # ---------------------------------------------------------- - dc_voltage = 800.0 - if dc_voltage > 0: + dc_current = ( - dc_current = ( + dc_power - dc_power + / dc_voltage - / dc_voltage - ) + if dc_voltage > 0 - else: - - dc_current = 0.0 + else 0.0 + ) ac_voltage = 400.0 - if ac_voltage > 0: + ac_current = ( - ac_current = ( + ac_power - ac_power + / ac_voltage - / ac_voltage - ) + if ac_voltage > 0 - else: + else 0.0 + ) - ac_current = 0.0 - - # ---------------------------------------------------------- - # Temperatura inverter - # ---------------------------------------------------------- - - inverter_temperature = ( + temperature = ( weather.ambient_temperature_C @@ -1455,138 +1084,167 @@ class PVSimulator: * 20.0 ) + active_combiners = sum( + + 1 + + for combiner + in component_combiners + + if combiner.status + != "fault" + ) + + fault_active = ( + + fault_factor < 1.0 + ) + + if fault_active: + + status = "fault" + + elif ac_power <= 0: + + status = "idle" + + else: + + status = "normal" + availability = ( active_combiners - / len(combiners) - ) - - record = InverterData( - - timestamp= - - timestamp, - - inverter_id= - - inverter.inverter_id, - - dc_power_W= - - dc_power, - - ac_power_W= - - ac_power, - - dc_voltage_V= - - dc_voltage, - - dc_current_A= - - dc_current, - - ac_voltage_V= - - ac_voltage, - - ac_current_A= - - ac_current, - - efficiency= - - inverter_efficiency, - - nominal_power_W= - - inverter_nominal, - - clipping_loss_W= - - clipping_loss, - - temperature_C= - - inverter_temperature, - - fault_factor= - - fault_factor, - - combiner_count= - - len(combiners), - - active_combiner_count= - - active_combiners, - - fault_active= - - fault_active, - - availability= - - availability, - - status= - - status, + / len( + component_combiners + ) ) records.append( - record + + InverterData( + + timestamp= + + timestamp, + + inverter_id= + + inverter.inverter_id, + + dc_power_W= + + dc_power, + + ac_power_W= + + ac_power, + + dc_voltage_V= + + dc_voltage, + + dc_current_A= + + dc_current, + + ac_voltage_V= + + ac_voltage, + + ac_current_A= + + ac_current, + + efficiency= + + efficiency, + + nominal_power_W= + + inverter_nominal, + + clipping_loss_W= + + clipping_loss, + + temperature_C= + + temperature, + + fault_factor= + + fault_factor, + + combiner_count= + + len( + component_combiners + ), + + active_combiner_count= + + active_combiners, + + fault_active= + + fault_active, + + availability= + + availability, + + status= + + status, + ) ) return records # ================================================================== - # PLANT DATA + # PLANT # ================================================================== - def _build_plant_data( + def _simulate_plant( self, timestamp: datetime, - panel_records: List[PanelData], - combiner_records: List[CombinerData], - inverter_records: List[InverterData], + panels: List[PanelData], + combiners: List[CombinerData], + inverters: List[InverterData], config: SimulationConfig, ) -> PlantData: - """ - Aggrega tutti i dati a livello di impianto. - """ dc_power = sum( - inverter.dc_power_W + item.dc_power_W - for inverter - in inverter_records + for item + in inverters ) ac_power = sum( - inverter.ac_power_W + item.ac_power_W - for inverter - in inverter_records + for item + in inverters ) - nominal_power = sum( + nominal_power = max( - inverter.nominal_power_W + ( - for inverter - in inverter_records + item.nominal_power_W + + for item + in inverters + ), + + default=0.0 ) - # -------------------------------------------------------------- - # Energia del timestep - # -------------------------------------------------------------- - timestep_hours = ( config.timestep_minutes @@ -1601,51 +1259,9 @@ class PVSimulator: * timestep_hours ) - # -------------------------------------------------------------- - # Conteggi - # -------------------------------------------------------------- + panel_count = len( - inverter_count = ( - - len( - inverter_records - ) - ) - - active_inverters = sum( - - 1 - - for inverter - in inverter_records - - if inverter.status - != "fault" - ) - - combiner_count = ( - - len( - combiner_records - ) - ) - - active_combiners = sum( - - 1 - - for combiner - in combiner_records - - if combiner.status - != "fault" - ) - - panel_count = ( - - len( - panel_records - ) + panels ) active_panels = sum( @@ -1653,84 +1269,60 @@ class PVSimulator: 1 for panel - in panel_records + in panels if panel.enabled ) - # -------------------------------------------------------------- - # Efficienza - # -------------------------------------------------------------- + availability = ( - if dc_power > 0: + active_panels - efficiency = ( + / panel_count - ac_power + if panel_count > 0 - / dc_power + else 0.0 + ) + + efficiency = ( + + ac_power + + / dc_power + + if dc_power > 0 + + else 0.0 + ) + + performance_ratio = ( + + ac_power + + / nominal_power + + if nominal_power > 0 + + else 0.0 + ) + + fault_factor = ( + + sum( + + panel.fault_factor + + for panel + in panels ) - else: + / panel_count - efficiency = 0.0 + if panel_count > 0 - # -------------------------------------------------------------- - # Performance Ratio - # -------------------------------------------------------------- - - if nominal_power > 0: - - performance_ratio = ( - - ac_power - - / nominal_power - ) - - else: - - performance_ratio = 0.0 - - # -------------------------------------------------------------- - # Availability - # -------------------------------------------------------------- - - if panel_count > 0: - - availability = ( - - active_panels - - / panel_count - ) - - else: - - availability = 0.0 - - # -------------------------------------------------------------- - # Fault factor - # -------------------------------------------------------------- - - if panel_count > 0: - - fault_factor = ( - - sum( - - panel.fault_factor - - for panel - in panel_records - ) - - / panel_count - ) - - else: - - fault_factor = 1.0 + else 1.0 + ) fault_active = ( @@ -1781,30 +1373,45 @@ class PVSimulator: cumulative_energy_Wh= - getattr( - - self.plant, - - "total_ac_energy_Wh", - - 0.0 - ), + 0.0, inverter_count= - inverter_count, + len( + inverters + ), active_inverter_count= - active_inverters, + sum( + + 1 + + for item + in inverters + + if item.status + != "fault" + ), combiner_count= - combiner_count, + len( + combiners + ), active_combiner_count= - active_combiners, + sum( + + 1 + + for item + in combiners + + if item.status + != "fault" + ), panel_count= @@ -1840,7 +1447,69 @@ class PVSimulator: ) # ================================================================== - # SINGOLO TIMESTEP + # FAULTS + # ================================================================== + + def _simulate_faults( + self, + timestamp: datetime, + ) -> List[FaultData]: + + records = [] + + active_faults = ( + + self.fault_manager + .get_active_faults( + + timestamp + ) + ) + + for fault in active_faults: + + records.append( + + FaultData( + + 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 + + # ================================================================== + # TIMESTEP # ================================================================== def simulate_timestep( @@ -1848,14 +1517,6 @@ class PVSimulator: timestamp: datetime, config: SimulationConfig, ) -> SimulationStep: - """ - Esegue un singolo timestep e restituisce - un SimulationStep completamente tipizzato. - """ - - # -------------------------------------------------------------- - # Weather - # -------------------------------------------------------------- weather = ( @@ -1865,24 +1526,16 @@ class PVSimulator: ) ) - # -------------------------------------------------------------- - # Random faults - # -------------------------------------------------------------- - if config.generate_random_faults: - self._generate_random_faults( + self._generate_faults( timestamp ) - # -------------------------------------------------------------- - # Panel - # -------------------------------------------------------------- + panels = ( - panel_records = ( - - self._build_panel_data( + self._simulate_panels( timestamp, @@ -1894,17 +1547,13 @@ class PVSimulator: else [] ) - # -------------------------------------------------------------- - # Combiner - # -------------------------------------------------------------- + combiners = ( - combiner_records = ( - - self._build_combiner_data( + self._simulate_combiners( timestamp, - panel_records + panels ) if config.store_combiner_data @@ -1912,17 +1561,13 @@ class PVSimulator: else [] ) - # -------------------------------------------------------------- - # Inverter - # -------------------------------------------------------------- + inverters = ( - inverter_records = ( - - self._build_inverter_data( + self._simulate_inverters( timestamp, - combiner_records, + combiners, weather ) @@ -1932,21 +1577,17 @@ class PVSimulator: else [] ) - # -------------------------------------------------------------- - # Plant - # -------------------------------------------------------------- + plant = ( - plant_record = ( - - self._build_plant_data( + self._simulate_plant( timestamp, - panel_records, + panels, - combiner_records, + combiners, - inverter_records, + inverters, config ) @@ -1956,13 +1597,9 @@ class PVSimulator: else None ) - # -------------------------------------------------------------- - # Faults - # -------------------------------------------------------------- + faults = ( - fault_records = ( - - self._build_fault_data( + self._simulate_faults( timestamp ) @@ -1972,10 +1609,6 @@ class PVSimulator: else [] ) - # -------------------------------------------------------------- - # SimulationStep - # -------------------------------------------------------------- - return SimulationStep( timestamp= @@ -1993,23 +1626,23 @@ class PVSimulator: panels= - panel_records, + panels, combiners= - combiner_records, + combiners, inverters= - inverter_records, + inverters, plant= - plant_record, + plant, faults= - fault_records, + faults, ) # ================================================================== @@ -2020,26 +1653,13 @@ class PVSimulator: self, config: SimulationConfig, ) -> SimulationResult: - """ - Esegue la simulazione completa. - """ - result = ( + result = SimulationResult() - SimulationResult() - ) - - timestamp = ( - - config.start - ) + timestamp = config.start while timestamp < config.end: - # ---------------------------------------------------------- - # Simula timestep - # ---------------------------------------------------------- - step = ( self.simulate_timestep( @@ -2055,10 +1675,6 @@ class PVSimulator: step ) - # ---------------------------------------------------------- - # Incremento temporale - # ---------------------------------------------------------- - timestamp += timedelta( minutes= @@ -2069,7 +1685,7 @@ class PVSimulator: return result # ================================================================== - # RUN SINGLE DAY + # SINGLE DAY # ================================================================== def run_single_day( @@ -2078,33 +1694,14 @@ class PVSimulator: timestep_minutes: int = 5, generate_random_faults: bool = False, ) -> SimulationResult: - """ - Esegue la simulazione di una giornata. - """ start = datetime( - year= + date.year, - date.year, + date.month, - month= - - date.month, - - day= - - date.day - ) - - end = ( - - start - - + timedelta( - - days=1 - ) + date.day ) config = SimulationConfig( @@ -2115,7 +1712,10 @@ class PVSimulator: end= - end, + start + + timedelta( + days=1 + ), timestep_minutes= @@ -2132,7 +1732,7 @@ class PVSimulator: ) # ================================================================== - # RUN YEAR + # YEAR # ================================================================== def run_year( @@ -2141,30 +1741,23 @@ class PVSimulator: timestep_minutes: int = 15, generate_random_faults: bool = False, ) -> SimulationResult: - """ - Esegue una simulazione annuale. - """ start = datetime( - year= + year, - year, + 1, - month=1, - - day=1 + 1 ) end = datetime( - year= + year + 1, - year + 1, + 1, - month=1, - - day=1 + 1 ) config = SimulationConfig( @@ -2191,71 +1784,7 @@ class PVSimulator: config ) - # ================================================================== - # SUMMARY - # ================================================================== - def summary( - self, - result: SimulationResult, - ) -> Dict[str, Any]: - """ - Restituisce un riepilogo della simulazione. - """ - - return { - - "plant_id": - - self.plant.plant_id, - - "inverters": - - self.plant.get_inverter_count(), - - "combiners": - - self.plant.get_combiner_count(), - - "panels": - - self.plant.get_panel_count(), - - **result.summary(), - } - - # ================================================================== - # REPR - # ================================================================== - - def __repr__( - self, - ) -> str: - - 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()})" - ) - - -# ====================================================================== -# EXPORT # ====================================================================== @@ -2266,4 +1795,4 @@ __all__ = [ "SimulationResult", "PVSimulator", -] +] \ No newline at end of file