diff --git a/src/antares/tsgen/cluster_import.py b/src/antares/tsgen/cluster_import.py index 9806c31..a5a23df 100644 --- a/src/antares/tsgen/cluster_import.py +++ b/src/antares/tsgen/cluster_import.py @@ -26,7 +26,7 @@ def import_thermal_cluster(path: Path, days_per_year: int = 365) -> ThermalClust return ThermalCluster( unit_count=int(array[1][1]), nominal_power=float(array[2][1]), - modulation=[float(i) for i in array[3][1 : 24 + 1]], + modulation=array[3][1 : 24 + 1].astype(int), fo_law=law_dict[array[4][1]], fo_volatility=float(array[5][1]), po_law=law_dict[array[6][1]], diff --git a/src/antares/tsgen/cluster_resolve.py b/src/antares/tsgen/cluster_resolve.py index dbf0461..91b9c8c 100644 --- a/src/antares/tsgen/cluster_resolve.py +++ b/src/antares/tsgen/cluster_resolve.py @@ -28,15 +28,15 @@ def resolve_thermal_cluster( return ThermalCluster( unit_count=parsed_yaml.unit_count, nominal_power=parsed_yaml.nominal_power, - modulation=modulation["modulation"].to_list(), + modulation=modulation["modulation"].to_numpy(dtype=float), fo_law=law_dict[parsed_yaml.fo_law], fo_volatility=parsed_yaml.fo_volatility, po_law=law_dict[parsed_yaml.po_law], po_volatility=parsed_yaml.po_volatility, - fo_duration=parameters_ts["FOD"].to_list(), - fo_rate=parameters_ts["FOR"].to_list(), - po_duration=parameters_ts["POD"].to_list(), - po_rate=parameters_ts["POR"].to_list(), - npo_min=parameters_ts["POMax"].to_list(), - npo_max=parameters_ts["POMin"].to_list(), + fo_duration=parameters_ts["FOD"].to_numpy(dtype=int), + fo_rate=parameters_ts["FOR"].to_numpy(dtype=float), + po_duration=parameters_ts["POD"].to_numpy(dtype=int), + po_rate=parameters_ts["POR"].to_numpy(dtype=float), + npo_min=parameters_ts["POMax"].to_numpy(dtype=int), + npo_max=parameters_ts["POMin"].to_numpy(dtype=int), ) diff --git a/src/antares/tsgen/duration_generator.py b/src/antares/tsgen/duration_generator.py index 2f99c94..bdfd4ff 100644 --- a/src/antares/tsgen/duration_generator.py +++ b/src/antares/tsgen/duration_generator.py @@ -12,9 +12,9 @@ from abc import ABC, abstractmethod from enum import Enum from math import log, sqrt -from typing import List import numpy as np +import numpy.typing as npt from .random_generator import RNG @@ -41,7 +41,10 @@ class GeneratorWrapper(DurationGenerator): """ def __init__( - self, delegate: DurationGenerator, volatility: float, expecs: List[int] + self, + delegate: DurationGenerator, + volatility: float, + expecs: npt.NDArray[np.int_], ) -> None: self.volatility = volatility self.expectations = expecs @@ -59,7 +62,9 @@ def generate_duration(self, day: int) -> int: class UniformDurationGenerator(DurationGenerator): - def __init__(self, rng: RNG, volatility: float, expecs: List[int]) -> None: + def __init__( + self, rng: RNG, volatility: float, expecs: npt.NDArray[np.int_] + ) -> None: self.rng = rng self.a = np.empty(len(expecs), dtype=float) self.b = np.empty(len(expecs), dtype=float) @@ -77,7 +82,9 @@ def generate_duration(self, day: int) -> int: class GeometricDurationGenerator(DurationGenerator): - def __init__(self, rng: RNG, volatility: float, expecs: List[int]) -> None: + def __init__( + self, rng: RNG, volatility: float, expecs: npt.NDArray[np.int_] + ) -> None: self.rng = rng self.a = np.empty(len(expecs), dtype=float) self.b = np.empty(len(expecs), dtype=float) @@ -100,7 +107,7 @@ def generate_duration(self, day: int) -> int: def make_duration_generator( - rng: RNG, law: ProbabilityLaw, volatility: float, expectations: List[int] + rng: RNG, law: ProbabilityLaw, volatility: float, expectations: npt.NDArray[np.int_] ) -> DurationGenerator: """ return a DurationGenerator for the given law diff --git a/src/antares/tsgen/ts_generator.py b/src/antares/tsgen/ts_generator.py index cf8d1a4..baadfaf 100644 --- a/src/antares/tsgen/ts_generator.py +++ b/src/antares/tsgen/ts_generator.py @@ -31,16 +31,18 @@ class ThermalCluster: nominal_power: float # modulation of the nominal power for a certain hour in the day (between 0 and 1) # TODO: check that it should be 24 or 8760 ? - modulation: List[float] ### maybe group nominal_power and modulation in one vaiable + modulation: npt.NDArray[ + np.int_ + ] ### maybe group nominal_power and modulation in one vaiable # forced and planed outage parameters # indexed by day of the year - fo_duration: List[int] - fo_rate: List[float] - po_duration: List[int] - po_rate: List[float] - npo_min: List[int] # number of planed outage min in a day - npo_max: List[int] # number of planed outage max in a day + fo_duration: npt.NDArray[np.int_] + fo_rate: npt.NDArray[np.float_] + po_duration: npt.NDArray[np.int_] + po_rate: npt.NDArray[np.float_] + npo_min: npt.NDArray[np.int_] # number of planed outage min in a day + npo_max: npt.NDArray[np.int_] # number of planed outage max in a day # forced and planed outage probability law and volatility # volatility characterizes the distance from the expect at which the value drawn can be @@ -96,48 +98,55 @@ def generate_time_series( # --- precalculation --- # cached values for (1-lf)**k and (1-lp)**k - self.FPOW: List[List[float]] = [] - self.PPOW: List[List[float]] = [] - - for day in range(self.days): - # lf and lp represent the forced and programed failure rate - # failure rate means the probability to enter in outage each day - # its value is given by: OR / [OR + OD * (1 - OR)] - FOR = cluster.fo_rate[day] - FOD = cluster.fo_duration[day] - self.lf[day] = FOR / (FOR + FOD * (1 - FOR)) - - POR = cluster.po_rate[day] - POD = cluster.po_duration[day] - self.lp[day] = POR / (POR + POD * (1 - POR)) - - if self.lf[day] < 0: - raise ValueError(f"forced failure rate is negative on day {day}") - if self.lp[day] < 0: - raise ValueError(f"programed failure rate is negative on day {day}") - - ## i dont understand what these calulations are for - ## consequently reduce the lower failure rate - if self.lf[day] < self.lp[day]: - self.lf[day] *= (1 - self.lp[day]) / (1 - self.lf[day]) - if self.lp[day] < self.lf[day]: - self.lp[day] *= (1 - self.lf[day]) / (1 - self.lp[day]) - - a = 0 - b = 0 - if self.lf[day] <= FAILURE_RATE_EQ_1: - a = 1 - self.lf[day] - self.ff[day] = self.lf[day] / a - if self.lp[day] <= FAILURE_RATE_EQ_1: - b = 1 - self.lp[day] - self.pp[day] = self.lp[day] / b - - # pre calculating power values - self.FPOW.append([]) - self.PPOW.append([]) - for k in range(cluster.unit_count + 1): - self.FPOW[-1].append(pow(a, k)) - self.PPOW[-1].append(pow(b, k)) + # TODO: why +1 ? + self.FPOW = np.zeros(shape=(self.days, cluster.unit_count + 1)) + self.PPOW = np.zeros(shape=(self.days, cluster.unit_count + 1)) + + # lf and lp represent the forced and programed failure rate + # failure rate means the probability to enter in outage each day + # its value is given by: OR / [OR + OD * (1 - OR)] + self.lf = cluster.fo_rate / ( + cluster.fo_rate + cluster.fo_duration * (1 - cluster.fo_rate) + ) + self.lp = cluster.po_rate / ( + cluster.po_rate + cluster.po_duration * (1 - cluster.po_rate) + ) + + invalid_days = self.lf < 0 + if invalid_days.any(): + raise ValueError( + f"forced failure rate is negative on days {invalid_days.nonzero()[0].tolist()}" + ) + invalid_days = self.lp < 0 + if invalid_days.any(): + raise ValueError( + f"planned failure rate is negative on days {invalid_days.nonzero()[0].tolist()}" + ) + + ## i dont understand what these calulations are for + ## consequently reduce the lower failure rate + mask = self.lf < self.lp + self.lf[mask] *= (1 - self.lp[mask]) / (1 - self.lf[mask]) + mask = self.lp < self.lf + self.lp[mask] *= (1 - self.lf[mask]) / (1 - self.lp[mask]) + + a = np.zeros(shape=self.days, dtype=float) + b = np.zeros(shape=self.days, dtype=float) + mask = self.lf <= FAILURE_RATE_EQ_1 + a[mask] = 1 - self.lf[mask] + self.ff[mask] = self.lf[mask] / a + + mask = self.lp <= FAILURE_RATE_EQ_1 + b[mask] = 1 - self.lp[mask] + self.pp[mask] = self.lp[mask] / b + + # change dimensions to get fpow and ppow with right shape + k = np.arange(cluster.unit_count + 1) + k.shape = (1, len(k)) + a.shape = (self.days, 1) + b.shape = (self.days, 1) + self.FPOW = pow(a, k) + self.PPOW = pow(b, k) fod_generator = make_duration_generator( self.rng, cluster.fo_law, cluster.fo_volatility, cluster.fo_duration diff --git a/tests/test_parsing.py b/tests/test_parsing.py index cf6722d..bfdd1b6 100644 --- a/tests/test_parsing.py +++ b/tests/test_parsing.py @@ -10,6 +10,8 @@ # # This file is part of the Antares project. +import numpy as np +import numpy.testing as npt import pytest from antares.tsgen.cluster_parsing import parse_cluster_ts, parse_yaml_cluster @@ -24,17 +26,17 @@ def cluster() -> ThermalCluster: return ThermalCluster( unit_count=1, nominal_power=500, - modulation=[1 for i in range(24)], + modulation=np.ones(dtype=float, shape=24), fo_law=ProbabilityLaw.UNIFORM, fo_volatility=0, po_law=ProbabilityLaw.UNIFORM, po_volatility=0, - fo_duration=[2 for i in range(NB_OF_DAY)], - fo_rate=[0.2 for i in range(NB_OF_DAY)], - po_duration=[1 for i in range(NB_OF_DAY)], - po_rate=[0.1 for i in range(NB_OF_DAY)], - npo_min=[0 for i in range(NB_OF_DAY)], - npo_max=[1 for i in range(NB_OF_DAY)], + fo_duration=np.ones(dtype=int, shape=NB_OF_DAY) * 2, + fo_rate=np.ones(dtype=float, shape=NB_OF_DAY) * 0.2, + po_duration=np.ones(dtype=int, shape=NB_OF_DAY), + po_rate=np.ones(dtype=float, shape=NB_OF_DAY) * 0.1, + npo_min=np.zeros(dtype=int, shape=NB_OF_DAY), + npo_max=np.ones(dtype=int, shape=NB_OF_DAY), ) @@ -47,14 +49,14 @@ def test(cluster, data_directory): assert cld.unit_count == cluster.unit_count assert cld.nominal_power == cluster.nominal_power - assert cld.modulation == cluster.modulation + npt.assert_equal(cld.modulation, cluster.modulation) assert cld.fo_law == cluster.fo_law assert cld.fo_volatility == cluster.fo_volatility assert cld.po_law == cluster.po_law assert cld.po_volatility == cluster.po_volatility - assert cld.fo_duration == cluster.fo_duration - assert cld.fo_rate == cluster.fo_rate - assert cld.po_duration == cluster.po_duration - assert cld.po_rate == cluster.po_rate - assert cld.npo_min == cluster.npo_min - assert cld.npo_max == cluster.npo_max + npt.assert_equal(cld.fo_duration, cluster.fo_duration) + npt.assert_equal(cld.fo_rate, cluster.fo_rate) + npt.assert_equal(cld.po_duration, cluster.po_duration) + npt.assert_equal(cld.po_rate, cluster.po_rate) + npt.assert_equal(cld.npo_min, cluster.npo_min) + npt.assert_equal(cld.npo_max, cluster.npo_max) diff --git a/tests/test_unit.py b/tests/test_unit.py index 1754f7a..17c787b 100644 --- a/tests/test_unit.py +++ b/tests/test_unit.py @@ -25,17 +25,17 @@ def test_forced_outages(rng): cluster = ThermalCluster( unit_count=10, nominal_power=100, - modulation=[1 for i in range(24)], + modulation=np.ones(dtype=float, shape=24), fo_law=ProbabilityLaw.UNIFORM, fo_volatility=0, po_law=ProbabilityLaw.UNIFORM, po_volatility=0, - fo_duration=[10 for i in range(days)], - fo_rate=[0.2 for i in range(days)], - po_duration=[10 for i in range(days)], - po_rate=[0 for i in range(days)], - npo_min=[0 for i in range(days)], - npo_max=[10 for i in range(days)], + fo_duration=10 * np.ones(dtype=int, shape=days), + fo_rate=0.2 * np.ones(dtype=float, shape=days), + po_duration=10 * np.ones(dtype=int, shape=days), + po_rate=np.zeros(dtype=float, shape=days), + npo_min=np.zeros(dtype=int, shape=days), + npo_max=10 * np.ones(dtype=int, shape=days), ) cluster.modulation[12] = 0.5 @@ -60,17 +60,17 @@ def test_planned_outages(rng): cluster = ThermalCluster( unit_count=10, nominal_power=100, - modulation=[1 for i in range(24)], + modulation=np.ones(dtype=float, shape=24), fo_law=ProbabilityLaw.UNIFORM, fo_volatility=0, po_law=ProbabilityLaw.UNIFORM, po_volatility=0, - fo_duration=[10 for i in range(days)], - fo_rate=[0 for i in range(days)], - po_duration=[10 for i in range(days)], - po_rate=[0.2 for i in range(days)], - npo_min=[0 for i in range(days)], - npo_max=[10 for i in range(days)], + fo_duration=10 * np.ones(dtype=int, shape=days), + fo_rate=np.zeros(dtype=float, shape=days), + po_duration=10 * np.ones(dtype=int, shape=days), + po_rate=0.2 * np.ones(dtype=float, shape=days), + npo_min=np.zeros(dtype=int, shape=days), + npo_max=10 * np.ones(dtype=int, shape=days), ) cluster.modulation[12] = 0.5 @@ -94,17 +94,17 @@ def test_planned_outages_limitation(rng): cluster = ThermalCluster( unit_count=10, nominal_power=100, - modulation=[1 for i in range(24)], + modulation=np.ones(dtype=float, shape=24), fo_law=ProbabilityLaw.UNIFORM, fo_volatility=0, po_law=ProbabilityLaw.UNIFORM, po_volatility=0, - fo_duration=[10 for i in range(days)], - fo_rate=[0 for i in range(days)], - po_duration=[2 for i in range(days)], - po_rate=[0.2 for i in range(days)], - npo_min=[0 for i in range(days)], - npo_max=[1 for i in range(days)], + fo_duration=10 * np.ones(dtype=int, shape=days), + fo_rate=np.zeros(dtype=float, shape=days), + po_duration=2 * np.ones(dtype=int, shape=days), + po_rate=0.2 * np.ones(dtype=float, shape=days), + npo_min=np.zeros(dtype=int, shape=days), + npo_max=1 * np.ones(dtype=int, shape=days), ) generator = ThermalDataGenerator(rng=rng, days=days) @@ -127,17 +127,17 @@ def test_planned_outages_min_limitation(rng): cluster = ThermalCluster( unit_count=10, nominal_power=100, - modulation=[1 for i in range(24)], + modulation=np.ones(dtype=float, shape=24), fo_law=ProbabilityLaw.UNIFORM, fo_volatility=0, po_law=ProbabilityLaw.UNIFORM, po_volatility=0, - fo_duration=[10 for i in range(days)], - fo_rate=[0 for i in range(days)], - po_duration=[10 for i in range(days)], - po_rate=[0.2 for i in range(days)], - npo_min=[2 for i in range(days)], - npo_max=[5 for i in range(days)], + fo_duration=10 * np.ones(dtype=int, shape=days), + fo_rate=np.zeros(dtype=float, shape=days), + po_duration=10 * np.ones(dtype=int, shape=days), + po_rate=0.2 * np.ones(dtype=float, shape=days), + npo_min=2 * np.ones(dtype=int, shape=days), + npo_max=5 * np.ones(dtype=int, shape=days), ) generator = ThermalDataGenerator(rng=rng, days=days)