fix: Fix syntax errors, wire config-driven simulation in run.py, and correct fault-rate/availability KPI bugs
This commit is contained in:
@@ -478,4 +478,3 @@ class CombinerBox:
|
||||
f"panels={len(self.panels)}, "
|
||||
f"enabled={self.enabled})"
|
||||
)
|
||||
```
|
||||
|
||||
@@ -597,4 +597,3 @@ __all__ = [
|
||||
|
||||
"export_simulation",
|
||||
]
|
||||
```
|
||||
|
||||
139
pvsim/faults.py
139
pvsim/faults.py
@@ -317,6 +317,76 @@ class FaultConfig:
|
||||
|
||||
random_seed: Optional[int] = None
|
||||
|
||||
@classmethod
|
||||
def from_config(
|
||||
cls,
|
||||
config: Dict[str, object]
|
||||
) -> FaultConfig:
|
||||
"""
|
||||
Crea un'istanza di FaultConfig
|
||||
a partire da un dizionario di configurazione.
|
||||
"""
|
||||
|
||||
fault_config = config["faults"]
|
||||
|
||||
return cls(
|
||||
|
||||
enabled=
|
||||
fault_config["enabled"],
|
||||
|
||||
panel_fault_probability=
|
||||
fault_config["panel"]["failure_probability_daily"],
|
||||
|
||||
combiner_fault_probability=
|
||||
fault_config["combiner"]["failure_probability_daily"],
|
||||
|
||||
inverter_fault_probability=
|
||||
fault_config["inverter"]["failure_probability_daily"],
|
||||
|
||||
intermittent_probability=
|
||||
fault_config.get(
|
||||
"intermittent_probability",
|
||||
0.30
|
||||
),
|
||||
|
||||
default_fault_duration_hours=
|
||||
fault_config.get(
|
||||
"default_fault_duration_hours",
|
||||
4.0
|
||||
),
|
||||
|
||||
random_seed=
|
||||
fault_config.get(
|
||||
"random_seed",
|
||||
None
|
||||
)
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_json(
|
||||
cls,
|
||||
json_path: str
|
||||
) -> FaultConfig:
|
||||
"""
|
||||
Crea un'istanza di FaultConfig
|
||||
a partire da un file JSON.
|
||||
"""
|
||||
|
||||
import json
|
||||
|
||||
with open(
|
||||
json_path,
|
||||
"r"
|
||||
) as f:
|
||||
|
||||
config = json.load(
|
||||
f
|
||||
)
|
||||
|
||||
return cls.from_config(
|
||||
config
|
||||
)
|
||||
|
||||
|
||||
# ======================================================================
|
||||
# FAULT MANAGER
|
||||
@@ -671,11 +741,40 @@ class FaultManager:
|
||||
# Generazione fault casuale
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
@staticmethod
|
||||
def _daily_to_timestep_probability(
|
||||
daily_probability: float,
|
||||
timestep_minutes: float
|
||||
) -> float:
|
||||
"""
|
||||
Converte una probabilità di fault giornaliera
|
||||
nella probabilità equivalente per un singolo timestep.
|
||||
|
||||
Le probabilità configurate (*_probability_daily) sono
|
||||
riferite a un'intera giornata, ma generate_random_fault
|
||||
viene chiamato una volta per timestep: senza questa
|
||||
conversione la probabilità giornaliera verrebbe applicata
|
||||
una volta per ogni timestep, inflazionando il tasso di
|
||||
fault di un fattore pari al numero di timestep al giorno
|
||||
(ad es. 288x per timestep da 5 minuti).
|
||||
"""
|
||||
|
||||
timesteps_per_day = (
|
||||
24 * 60
|
||||
) / timestep_minutes
|
||||
|
||||
return 1 - (
|
||||
1 - daily_probability
|
||||
) ** (
|
||||
1 / timesteps_per_day
|
||||
)
|
||||
|
||||
def generate_random_fault(
|
||||
self,
|
||||
component_id: str,
|
||||
component_level: ComponentLevel,
|
||||
timestamp: datetime
|
||||
timestamp: datetime,
|
||||
timestep_minutes: float = 5.0
|
||||
) -> Optional[FaultEvent]:
|
||||
"""
|
||||
Genera casualmente un fault.
|
||||
@@ -684,7 +783,9 @@ class FaultManager:
|
||||
durante ogni timestep della simulazione.
|
||||
|
||||
La probabilità viene scelta in base
|
||||
al livello gerarchico.
|
||||
al livello gerarchico, e convertita da
|
||||
probabilità giornaliera a probabilità
|
||||
per il singolo timestep.
|
||||
"""
|
||||
|
||||
if not self.config.enabled:
|
||||
@@ -696,7 +797,7 @@ class FaultManager:
|
||||
== ComponentLevel.PANEL
|
||||
):
|
||||
|
||||
probability = (
|
||||
daily_probability = (
|
||||
|
||||
self.config
|
||||
.panel_fault_probability
|
||||
@@ -707,7 +808,7 @@ class FaultManager:
|
||||
== ComponentLevel.COMBINER
|
||||
):
|
||||
|
||||
probability = (
|
||||
daily_probability = (
|
||||
|
||||
self.config
|
||||
.combiner_fault_probability
|
||||
@@ -715,12 +816,19 @@ class FaultManager:
|
||||
|
||||
else:
|
||||
|
||||
probability = (
|
||||
daily_probability = (
|
||||
|
||||
self.config
|
||||
.inverter_fault_probability
|
||||
)
|
||||
|
||||
probability = (
|
||||
self._daily_to_timestep_probability(
|
||||
daily_probability,
|
||||
timestep_minutes
|
||||
)
|
||||
)
|
||||
|
||||
# Nessun fault
|
||||
if (
|
||||
random.random()
|
||||
@@ -905,7 +1013,8 @@ class FaultManager:
|
||||
timestamp: datetime,
|
||||
panel_ids: List[str],
|
||||
combiner_ids: List[str],
|
||||
inverter_ids: List[str]
|
||||
inverter_ids: List[str],
|
||||
timestep_minutes: float = 5.0
|
||||
) -> List[FaultEvent]:
|
||||
"""
|
||||
Esegue la generazione casuale dei fault
|
||||
@@ -931,7 +1040,10 @@ class FaultManager:
|
||||
ComponentLevel.PANEL,
|
||||
|
||||
timestamp=
|
||||
timestamp
|
||||
timestamp,
|
||||
|
||||
timestep_minutes=
|
||||
timestep_minutes
|
||||
)
|
||||
)
|
||||
|
||||
@@ -953,7 +1065,10 @@ class FaultManager:
|
||||
ComponentLevel.COMBINER,
|
||||
|
||||
timestamp=
|
||||
timestamp
|
||||
timestamp,
|
||||
|
||||
timestep_minutes=
|
||||
timestep_minutes
|
||||
)
|
||||
)
|
||||
|
||||
@@ -975,7 +1090,10 @@ class FaultManager:
|
||||
ComponentLevel.INVERTER,
|
||||
|
||||
timestamp=
|
||||
timestamp
|
||||
timestamp,
|
||||
|
||||
timestep_minutes=
|
||||
timestep_minutes
|
||||
)
|
||||
)
|
||||
|
||||
@@ -1130,5 +1248,4 @@ class FaultManager:
|
||||
f"{len(self.faults)}, "
|
||||
f"active="
|
||||
f"{active})"
|
||||
)
|
||||
```
|
||||
)
|
||||
@@ -764,4 +764,3 @@ class Inverter:
|
||||
f"enabled="
|
||||
f"{self.enabled})"
|
||||
)
|
||||
```
|
||||
|
||||
@@ -1123,4 +1123,3 @@ __all__ = [
|
||||
|
||||
"records_to_dataframe"
|
||||
]
|
||||
```
|
||||
|
||||
@@ -887,4 +887,3 @@ class PVPlant:
|
||||
f"panels="
|
||||
f"{self.get_panel_count()})"
|
||||
)
|
||||
```
|
||||
|
||||
@@ -589,6 +589,7 @@ class PVSimulator:
|
||||
def _generate_faults(
|
||||
self,
|
||||
timestamp: datetime,
|
||||
timestep_minutes: int,
|
||||
) -> None:
|
||||
|
||||
ids = self._component_ids()
|
||||
@@ -616,6 +617,10 @@ class PVSimulator:
|
||||
ids[
|
||||
"inverter"
|
||||
],
|
||||
|
||||
timestep_minutes=
|
||||
|
||||
timestep_minutes,
|
||||
)
|
||||
|
||||
# ==================================================================
|
||||
@@ -755,7 +760,8 @@ class PVSimulator:
|
||||
|
||||
enabled=
|
||||
|
||||
panel.enabled,
|
||||
panel.enabled
|
||||
and fault_factor > 0.0,
|
||||
)
|
||||
|
||||
records.append(
|
||||
@@ -1530,7 +1536,9 @@ class PVSimulator:
|
||||
|
||||
self._generate_faults(
|
||||
|
||||
timestamp
|
||||
timestamp,
|
||||
|
||||
config.timestep_minutes,
|
||||
)
|
||||
|
||||
panels = (
|
||||
|
||||
@@ -837,4 +837,3 @@ __all__ = [
|
||||
|
||||
"calculate_statistics",
|
||||
]
|
||||
```
|
||||
|
||||
@@ -1441,4 +1441,4 @@ __all__ = [
|
||||
"SimulationStorage",
|
||||
|
||||
"save_simulation",
|
||||
]"""
|
||||
]
|
||||
|
||||
49
pvsim/sun.py
49
pvsim/sun.py
@@ -675,4 +675,51 @@ class SunModel:
|
||||
f"timezone='{self.timezone}', "
|
||||
f"altitude={self.altitude}m)"
|
||||
)
|
||||
```
|
||||
|
||||
@classmethod
|
||||
def from_config(cls, config: Dict) -> SunModel:
|
||||
"""
|
||||
Crea un'istanza di SunModel da un dizionario di configurazione.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
config : Dict
|
||||
Dizionario contenente i parametri di configurazione.
|
||||
|
||||
Returns
|
||||
-------
|
||||
SunModel
|
||||
Istanza di SunModel.
|
||||
"""
|
||||
|
||||
location = config['plant']['location']
|
||||
|
||||
return cls(
|
||||
latitude=location['latitude'],
|
||||
longitude=location['longitude'],
|
||||
timezone=location['timezone'],
|
||||
altitude=location['altitude']
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, json_path: str) -> SunModel:
|
||||
"""
|
||||
Crea un'istanza di SunModel da un file JSON.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
json_path : str
|
||||
Percorso del file JSON contenente i parametri di configurazione.
|
||||
|
||||
Returns
|
||||
-------
|
||||
SunModel
|
||||
Istanza di SunModel.
|
||||
"""
|
||||
|
||||
import json
|
||||
|
||||
with open(json_path, 'r') as f:
|
||||
config = json.load(f)
|
||||
|
||||
return cls.from_config(config)
|
||||
@@ -587,4 +587,3 @@ __all__ = [
|
||||
|
||||
"save_json",
|
||||
]
|
||||
```
|
||||
|
||||
@@ -917,4 +917,49 @@ class WeatherModel:
|
||||
f"rain="
|
||||
f"{self.rain_enabled})"
|
||||
)
|
||||
```
|
||||
|
||||
@classmethod
|
||||
def from_config(
|
||||
cls,
|
||||
config: Dict,
|
||||
sun: SunModel
|
||||
) -> WeatherModel:
|
||||
"""
|
||||
Crea un'istanza di WeatherModel da un dizionario di configurazione.
|
||||
"""
|
||||
|
||||
weather_data = config['weather']
|
||||
|
||||
return cls(
|
||||
sun=sun,
|
||||
clear_sky_model=weather_data['clear_sky_model'],
|
||||
mean_annual_temperature_C=weather_data['temperature']['mean_annual_C'],
|
||||
daily_temperature_variation_C=weather_data['temperature']['daily_variation_C'],
|
||||
seasonal_temperature_variation_C=weather_data['temperature'].get('seasonal_variation_C', 12.0),
|
||||
cloud_enabled=weather_data['clouds']['enabled'],
|
||||
mean_cloud_factor=weather_data['clouds']['mean_cloud_factor'],
|
||||
cloud_variability=weather_data['clouds']['random_variability'],
|
||||
rain_enabled=weather_data['rain']['enabled'],
|
||||
rain_probability_daily=weather_data['rain']['probability_daily'],
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_json(
|
||||
cls,
|
||||
json_path: str,
|
||||
sun: SunModel
|
||||
) -> WeatherModel:
|
||||
"""
|
||||
Crea un'istanza di WeatherModel da un file JSON.
|
||||
"""
|
||||
|
||||
import json
|
||||
|
||||
with open(json_path, 'r') as f:
|
||||
|
||||
config = json.load(f)
|
||||
|
||||
return cls.from_config(
|
||||
config=config,
|
||||
sun=sun
|
||||
)
|
||||
Reference in New Issue
Block a user