diff --git a/src/gsy_e/gsy_e_core/util.py b/src/gsy_e/gsy_e_core/util.py index e5f9d13cf..41d167f37 100644 --- a/src/gsy_e/gsy_e_core/util.py +++ b/src/gsy_e/gsy_e_core/util.py @@ -454,7 +454,11 @@ def get_market_maker_rate_from_time_slot(time_slot: Optional[DateTime]) -> float if isinstance(GlobalConfig.market_maker_rate, dict): if time_slot is None: assert time_slot, "time_slot parameter is missing to get market_maker" - return get_from_profile_same_weekday_and_time(GlobalConfig.market_maker_rate, time_slot) + if GlobalConfig.is_canary_network(): + return get_from_profile_same_weekday_and_time( + GlobalConfig.market_maker_rate, time_slot + ) + return GlobalConfig.market_maker_rate.get(time_slot) return GlobalConfig.market_maker_rate diff --git a/src/gsy_e/models/strategy/finite_power_plant.py b/src/gsy_e/models/strategy/finite_power_plant.py index 3c5e38b1c..627fe036a 100644 --- a/src/gsy_e/models/strategy/finite_power_plant.py +++ b/src/gsy_e/models/strategy/finite_power_plant.py @@ -18,12 +18,13 @@ from typing import Union, Optional -from gsy_framework.read_user_profile import UserProfileReader, InputProfileTypes +from gsy_framework.read_user_profile import InputProfileTypes from gsy_framework.utils import convert_str_to_pendulum_in_dict, convert_pendulum_to_str_in_dict -from gsy_framework.utils import get_from_profile_same_weekday_and_time, convert_kW_to_kWh +from gsy_framework.utils import convert_kW_to_kWh from gsy_framework.validators import FiniteDieselGeneratorValidator from gsy_e.models.strategy.commercial_producer import CommercialStrategy +from gsy_e.models.strategy.strategy_profile import profile_factory, StrategyProfileBase class FinitePowerPlant(CommercialStrategy): @@ -43,12 +44,11 @@ def __init__( FiniteDieselGeneratorValidator.validate(max_available_power_kW=max_available_power_kW) super().__init__(energy_rate=energy_rate) self.max_available_power_kW = max_available_power_kW + self._max_available_power_kW_profile: StrategyProfileBase = None def event_activate(self, **kwargs): super().event_activate() - self.max_available_power_kW = UserProfileReader().read_arbitrary_profile( - InputProfileTypes.IDENTITY, self.max_available_power_kW - ) + self._init_max_power_profile(self.max_available_power_kW) def event_offer_traded(self, *, market_id, trade): # Disable offering more energy than the initial offer, in order to adhere to the max @@ -56,11 +56,9 @@ def event_offer_traded(self, *, market_id, trade): pass def event_market_cycle(self): - power_from_profile = get_from_profile_same_weekday_and_time( - self.max_available_power_kW, self.area.spot_market.time_slot - ) self.energy_per_slot_kWh = convert_kW_to_kWh( - power_from_profile, self.simulation_config.slot_length + self._max_available_power_kW_profile.get_value(self.area.spot_market.time_slot), + self.simulation_config.slot_length, ) if self.energy_per_slot_kWh <= 0.0: return @@ -69,13 +67,19 @@ def event_market_cycle(self): def get_state(self): return { "energy_rate": convert_pendulum_to_str_in_dict(self._sell_energy_profile.profile), - "max_available_power_kW": convert_pendulum_to_str_in_dict(self.max_available_power_kW), + "max_available_power_kW": convert_pendulum_to_str_in_dict( + self._max_available_power_kW_profile.profile + ), } def restore_state(self, saved_state): self._sell_energy_profile.profile = convert_str_to_pendulum_in_dict( saved_state["energy_rate"] ) - self.max_available_power_kW.update( - convert_str_to_pendulum_in_dict(saved_state["max_available_power_kW"]) + self._init_max_power_profile(saved_state["max_available_power_kW"]) + + def _init_max_power_profile(self, max_available_power_kW: Union[float, dict, str]): + self._max_available_power_kW_profile = profile_factory( + input_profile=max_available_power_kW, profile_type=InputProfileTypes.IDENTITY ) + self._max_available_power_kW_profile.read_or_rotate_profiles() diff --git a/src/gsy_e/models/strategy/load_hours.py b/src/gsy_e/models/strategy/load_hours.py index b63e98e9f..8f31bd10a 100644 --- a/src/gsy_e/models/strategy/load_hours.py +++ b/src/gsy_e/models/strategy/load_hours.py @@ -22,10 +22,9 @@ from gsy_framework.constants_limits import ConstSettings, FLOATING_POINT_TOLERANCE from gsy_framework.data_classes import Offer, TraderDetails from gsy_framework.exceptions import GSyDeviceException -from gsy_framework.read_user_profile import UserProfileReader, InputProfileTypes +from gsy_framework.read_user_profile import InputProfileTypes from gsy_framework.utils import ( limit_float_precision, - get_from_profile_same_weekday_and_time, is_time_slot_in_simulation_duration, ) from gsy_framework.validators.load_validator import LoadValidator @@ -45,6 +44,7 @@ from gsy_e.models.strategy.settlement.strategy import settlement_market_strategy_factory from gsy_e.models.strategy.state import LoadState from gsy_e.models.strategy.update_frequency import TemplateStrategyBidUpdater +from gsy_e.models.strategy.strategy_profile import profile_factory, StrategyProfileBase BalancingRatio = namedtuple("BalancingRatio", ("demand", "supply")) @@ -110,7 +110,6 @@ def __init__( self._calculate_active_markets() self._cycled_market = set() self._simulation_start_timestamp = None - self._reader = UserProfileReader() @property def state(self) -> LoadState: @@ -156,10 +155,14 @@ def _init_price_update( ) def _validate_rates( - self, initial_rate, final_rate, energy_rate_change_per_update, fit_to_limit + self, + initial_rate: StrategyProfileBase, + final_rate: StrategyProfileBase, + energy_rate_change_per_update: StrategyProfileBase, + fit_to_limit: bool, ): - # all parameters have to be validated for each time slot starting from the current time - for time_slot in initial_rate.keys(): + # all parameters have pvto be validated for each time slot starting from the current time + for time_slot in initial_rate.profile.keys(): if not is_time_slot_in_simulation_duration(time_slot, self.area.config): continue @@ -170,16 +173,12 @@ def _validate_rates( ): continue rate_change = ( - None - if fit_to_limit - else get_from_profile_same_weekday_and_time( - energy_rate_change_per_update, time_slot - ) + None if fit_to_limit else energy_rate_change_per_update.get_value(time_slot) ) LoadValidator.validate_rate( - initial_buying_rate=initial_rate[time_slot], + initial_buying_rate=initial_rate.get_value(time_slot), energy_rate_increase_per_update=rate_change, - final_buying_rate=get_from_profile_same_weekday_and_time(final_rate, time_slot), + final_buying_rate=final_rate.get_value(time_slot), fit_to_limit=fit_to_limit, ) @@ -232,21 +231,26 @@ def _delete_past_state(self): def _area_reconfigure_prices(self, **kwargs): if kwargs.get("initial_buying_rate") is not None: - initial_rate = self._reader.read_arbitrary_profile( - InputProfileTypes.IDENTITY, kwargs["initial_buying_rate"] + initial_rate = profile_factory( + profile_type=InputProfileTypes.IDENTITY, + input_profile=kwargs["initial_buying_rate"], ) + initial_rate.read_or_rotate_profiles() else: initial_rate = self.bid_update.initial_rate_profile_buffer if kwargs.get("final_buying_rate") is not None: - final_rate = self._reader.read_arbitrary_profile( - InputProfileTypes.IDENTITY, kwargs["final_buying_rate"] + final_rate = profile_factory( + profile_type=InputProfileTypes.IDENTITY, input_profile=kwargs["final_buying_rate"] ) + final_rate.read_or_rotate_profiles() else: final_rate = self.bid_update.final_rate_profile_buffer if kwargs.get("energy_rate_increase_per_update") is not None: - energy_rate_change_per_update = self._reader.read_arbitrary_profile( - InputProfileTypes.IDENTITY, kwargs["energy_rate_increase_per_update"] + energy_rate_change_per_update = profile_factory( + profile_type=InputProfileTypes.IDENTITY, + input_profile=kwargs["energy_rate_increase_per_update"], ) + energy_rate_change_per_update.read_or_rotate_profiles() else: energy_rate_change_per_update = ( self.bid_update.energy_rate_change_per_update_profile_buffer @@ -276,19 +280,19 @@ def _area_reconfigure_prices(self, **kwargs): return self.bid_update.set_parameters( - initial_rate=initial_rate, - final_rate=final_rate, - energy_rate_change_per_update=energy_rate_change_per_update, + initial_rate=initial_rate.profile, + final_rate=final_rate.profile, + energy_rate_change_per_update=energy_rate_change_per_update.profile, fit_to_limit=fit_to_limit, update_interval=update_interval, ) + self.bid_update.update_and_populate_price_settings(self.area) def area_reconfigure_event(self, *args, **kwargs): """Reconfigure the device properties at runtime using the provided arguments.""" self._energy_params.reset(self.area.spot_market.time_slot, **kwargs) self._update_energy_requirement_in_state() self._area_reconfigure_prices(**kwargs) - self.bid_update.update_and_populate_price_settings(self.area) def event_activate_price(self): """Update the strategy prices upon the activation and validate them afterwards.""" diff --git a/src/gsy_e/models/strategy/mixins.py b/src/gsy_e/models/strategy/mixins.py index fd646f88c..702fd4a32 100644 --- a/src/gsy_e/models/strategy/mixins.py +++ b/src/gsy_e/models/strategy/mixins.py @@ -1,8 +1,9 @@ from typing import TYPE_CHECKING -from gsy_framework.read_user_profile import InputProfileTypes, UserProfileReader +from gsy_framework.read_user_profile import InputProfileTypes from gsy_e.gsy_e_core.util import get_market_maker_rate_from_config +from gsy_e.models.strategy.strategy_profile import profile_factory if TYPE_CHECKING: from gsy_e.models.area import Area @@ -23,16 +24,18 @@ class UseMarketMakerMixin: def _reconfigure_initial_selling_rate(self, initial_selling_rate: float): """validation is not needed""" - initial_selling_rate = UserProfileReader().read_arbitrary_profile( - InputProfileTypes.IDENTITY, initial_selling_rate + initial_selling_rate = profile_factory( + profile_type=InputProfileTypes.IDENTITY, input_profile=initial_selling_rate ) - self.offer_update.set_parameters(initial_rate=initial_selling_rate) + initial_selling_rate.read_or_rotate_profiles() + self.offer_update.set_parameters(initial_rate=initial_selling_rate.profile) def _reconfigure_final_buying_rate(self, final_buying_rate: float): - final_buying_rate = UserProfileReader().read_arbitrary_profile( - InputProfileTypes.IDENTITY, final_buying_rate + final_buying_rate = profile_factory( + profile_type=InputProfileTypes.IDENTITY, input_profile=final_buying_rate ) - self.bid_update.set_parameters(final_rate=final_buying_rate) + final_buying_rate.read_or_rotate_profiles() + self.bid_update.set_parameters(final_rate=final_buying_rate.profile) def _replace_rates_with_market_maker_rates(self): if not self.use_market_maker_rate: diff --git a/src/gsy_e/models/strategy/pv.py b/src/gsy_e/models/strategy/pv.py index 66921d005..228b5844d 100644 --- a/src/gsy_e/models/strategy/pv.py +++ b/src/gsy_e/models/strategy/pv.py @@ -22,8 +22,8 @@ from gsy_framework.constants_limits import ConstSettings from gsy_framework.data_classes import TraderDetails from gsy_framework.exceptions import GSyException -from gsy_framework.read_user_profile import UserProfileReader, InputProfileTypes -from gsy_framework.utils import get_from_profile_same_weekday_and_time, key_in_dict_and_not_none +from gsy_framework.read_user_profile import InputProfileTypes, UserProfileReader +from gsy_framework.utils import key_in_dict_and_not_none from gsy_framework.validators import PVValidator from pendulum import duration @@ -37,6 +37,7 @@ from gsy_e.models.strategy.settlement.strategy import settlement_market_strategy_factory from gsy_e.models.strategy.state import PVState from gsy_e.models.strategy.update_frequency import TemplateStrategyOfferUpdater +from gsy_e.models.strategy.strategy_profile import StrategyProfileBase, profile_factory log = getLogger(__name__) @@ -134,35 +135,35 @@ def _init_price_update( def area_reconfigure_event(self, *args, **kwargs): """Reconfigure the device properties at runtime using the provided arguments.""" self._area_reconfigure_prices(**kwargs) - self.offer_update.update_and_populate_price_settings(self.area) self._energy_params.reset(**kwargs) self.set_produced_energy_forecast_in_state(reconfigure=True) def _area_reconfigure_prices(self, **kwargs): - - initial_rate = ( - self._reader.read_arbitrary_profile( - InputProfileTypes.IDENTITY, kwargs["initial_selling_rate"] + if kwargs.get("initial_selling_rate") is not None: + initial_rate = profile_factory( + profile_type=InputProfileTypes.IDENTITY, + input_profile=kwargs["initial_selling_rate"], ) - if kwargs.get("initial_selling_rate") is not None - else self.offer_update.initial_rate_profile_buffer - ) - - final_rate = ( - self._reader.read_arbitrary_profile( - InputProfileTypes.IDENTITY, kwargs["final_selling_rate"] + initial_rate.read_or_rotate_profiles() + else: + initial_rate = self.offer_update.initial_rate_profile_buffer + if kwargs.get("final_selling_rate") is not None: + final_rate = profile_factory( + profile_type=InputProfileTypes.IDENTITY, input_profile=kwargs["final_selling_rate"] ) - if kwargs.get("final_selling_rate") is not None - else self.offer_update.final_rate_profile_buffer - ) - - energy_rate_change_per_update = ( - self._reader.read_arbitrary_profile( - InputProfileTypes.IDENTITY, kwargs["energy_rate_decrease_per_update"] + final_rate.read_or_rotate_profiles() + else: + final_rate = self.offer_update.final_rate_profile_buffer + if kwargs.get("energy_rate_increase_per_update") is not None: + energy_rate_change_per_update = profile_factory( + profile_type=InputProfileTypes.IDENTITY, + input_profile=kwargs["energy_rate_increase_per_update"], + ) + energy_rate_change_per_update.read_or_rotate_profiles() + else: + energy_rate_change_per_update = ( + self.offer_update.energy_rate_change_per_update_profile_buffer ) - if kwargs.get("energy_rate_decrease_per_update") is not None - else self.offer_update.energy_rate_change_per_update_profile_buffer - ) fit_to_limit = ( kwargs["fit_to_limit"] @@ -187,25 +188,29 @@ def _area_reconfigure_prices(self, **kwargs): ) except GSyException as e: # pylint: disable=broad-except log.error( - "PVStrategy._area_reconfigure_prices failed. Exception: %s. " "Traceback: %s", + "PVStrategy._area_reconfigure_prices failed. Exception: %s. Traceback: %s", e, traceback.format_exc(), ) return - self.offer_update.set_parameters( - initial_rate=initial_rate, - final_rate=final_rate, - energy_rate_change_per_update=energy_rate_change_per_update, + initial_rate=initial_rate.profile, + final_rate=final_rate.profile, + energy_rate_change_per_update=energy_rate_change_per_update.profile, fit_to_limit=fit_to_limit, update_interval=update_interval, ) + self.offer_update.update_and_populate_price_settings(self.area) def _validate_rates( - self, initial_rate, final_rate, energy_rate_change_per_update, fit_to_limit + self, + initial_rate: StrategyProfileBase, + final_rate: StrategyProfileBase, + energy_rate_change_per_update: StrategyProfileBase, + fit_to_limit: bool, ): # all parameters have to be validated for each time slot here - for time_slot in initial_rate.keys(): + for time_slot in initial_rate.profile.keys(): if ( self.area and self.area.current_market @@ -213,15 +218,11 @@ def _validate_rates( ): continue rate_change = ( - None - if fit_to_limit - else get_from_profile_same_weekday_and_time( - energy_rate_change_per_update, time_slot - ) + None if fit_to_limit else energy_rate_change_per_update.get_value(time_slot) ) PVValidator.validate_rate( - initial_selling_rate=initial_rate[time_slot], - final_selling_rate=get_from_profile_same_weekday_and_time(final_rate, time_slot), + initial_selling_rate=initial_rate.get_value(time_slot), + final_selling_rate=final_rate.get_value(time_slot), energy_rate_decrease_per_update=rate_change, fit_to_limit=fit_to_limit, ) diff --git a/src/gsy_e/models/strategy/smart_meter.py b/src/gsy_e/models/strategy/smart_meter.py index 1fe4b7091..7c308568d 100644 --- a/src/gsy_e/models/strategy/smart_meter.py +++ b/src/gsy_e/models/strategy/smart_meter.py @@ -19,8 +19,8 @@ from gsy_framework.constants_limits import ConstSettings, FLOATING_POINT_TOLERANCE from gsy_framework.data_classes import Offer, TraderDetails -from gsy_framework.read_user_profile import InputProfileTypes, UserProfileReader -from gsy_framework.utils import get_from_profile_same_weekday_and_time, limit_float_precision +from gsy_framework.read_user_profile import InputProfileTypes +from gsy_framework.utils import limit_float_precision from gsy_framework.validators.smart_meter_validator import SmartMeterValidator from numpy import random from pendulum import duration @@ -38,6 +38,7 @@ TemplateStrategyBidUpdater, TemplateStrategyOfferUpdater, ) +from gsy_e.models.strategy.strategy_profile import profile_factory, StrategyProfileBase log = getLogger(__name__) @@ -133,7 +134,6 @@ def __init__( update_interval=update_interval, rate_limit_object=max, ) - self._reader = UserProfileReader() @property def state(self) -> SmartMeterState: @@ -284,29 +284,31 @@ def area_reconfigure_event(self, *args, **kwargs): self._energy_params.reset(time_slots=time_slots, **kwargs) def _area_reconfigure_production_prices(self, **kwargs): - initial_rate = ( - self._reader.read_arbitrary_profile( - InputProfileTypes.IDENTITY, kwargs["initial_selling_rate"] + if kwargs.get("initial_selling_rate") is not None: + initial_rate = profile_factory( + profile_type=InputProfileTypes.IDENTITY, + input_profile=kwargs["initial_selling_rate"], ) - if kwargs.get("initial_selling_rate") is not None - else self.offer_update.initial_rate_profile_buffer - ) - - final_rate = ( - self._reader.read_arbitrary_profile( - InputProfileTypes.IDENTITY, kwargs["final_selling_rate"] + initial_rate.read_or_rotate_profiles() + else: + initial_rate = self.offer_update.initial_rate_profile_buffer + if kwargs.get("final_selling_rate") is not None: + final_rate = profile_factory( + profile_type=InputProfileTypes.IDENTITY, input_profile=kwargs["final_selling_rate"] ) - if kwargs.get("final_selling_rate") is not None - else self.offer_update.final_rate_profile_buffer - ) - - energy_rate_change_per_update = ( - self._reader.read_arbitrary_profile( - InputProfileTypes.IDENTITY, kwargs["energy_rate_decrease_per_update"] + final_rate.read_or_rotate_profiles() + else: + final_rate = self.offer_update.final_rate_profile_buffer + if kwargs.get("energy_rate_increase_per_update") is not None: + energy_rate_change_per_update = profile_factory( + profile_type=InputProfileTypes.IDENTITY, + input_profile=kwargs["energy_rate_increase_per_update"], + ) + energy_rate_change_per_update.read_or_rotate_profiles() + else: + energy_rate_change_per_update = ( + self.offer_update.energy_rate_change_per_update_profile_buffer ) - if kwargs.get("energy_rate_decrease_per_update") is not None - else self.offer_update.energy_rate_change_per_update_profile_buffer - ) fit_to_limit = ( kwargs["fit_to_limit"] @@ -334,37 +336,40 @@ def _area_reconfigure_production_prices(self, **kwargs): return self.offer_update.set_parameters( - initial_rate=initial_rate, - final_rate=final_rate, - energy_rate_change_per_update=energy_rate_change_per_update, + initial_rate=initial_rate.profile, + final_rate=final_rate.profile, + energy_rate_change_per_update=energy_rate_change_per_update.profile, fit_to_limit=fit_to_limit, update_interval=update_interval, ) + self.offer_update.update_and_populate_price_settings(self.area) def _area_reconfigure_consumption_prices(self, **kwargs): - initial_rate = ( - self._reader.read_arbitrary_profile( - InputProfileTypes.IDENTITY, kwargs["initial_buying_rate"] + if kwargs.get("initial_buying_rate") is not None: + initial_rate = profile_factory( + profile_type=InputProfileTypes.IDENTITY, + input_profile=kwargs["initial_buying_rate"], ) - if kwargs.get("initial_buying_rate") is not None - else self.bid_update.initial_rate_profile_buffer - ) - - final_rate = ( - self._reader.read_arbitrary_profile( - InputProfileTypes.IDENTITY, kwargs["final_buying_rate"] + initial_rate.read_or_rotate_profiles() + else: + initial_rate = self.bid_update.initial_rate_profile_buffer + if kwargs.get("final_buying_rate") is not None: + final_rate = profile_factory( + profile_type=InputProfileTypes.IDENTITY, input_profile=kwargs["final_buying_rate"] ) - if kwargs.get("final_buying_rate") is not None - else self.bid_update.final_rate_profile_buffer - ) - - energy_rate_change_per_update = ( - self._reader.read_arbitrary_profile( - InputProfileTypes.IDENTITY, kwargs["energy_rate_increase_per_update"] + final_rate.read_or_rotate_profiles() + else: + final_rate = self.bid_update.final_rate_profile_buffer + if kwargs.get("energy_rate_increase_per_update") is not None: + energy_rate_change_per_update = profile_factory( + profile_type=InputProfileTypes.IDENTITY, + input_profile=kwargs["energy_rate_increase_per_update"], + ) + energy_rate_change_per_update.read_or_rotate_profiles() + else: + energy_rate_change_per_update = ( + self.bid_update.energy_rate_change_per_update_profile_buffer ) - if kwargs.get("energy_rate_increase_per_update") is not None - else self.bid_update.energy_rate_change_per_update_profile_buffer - ) fit_to_limit = ( kwargs["fit_to_limit"] @@ -392,12 +397,13 @@ def _area_reconfigure_consumption_prices(self, **kwargs): return self.bid_update.set_parameters( - initial_rate=initial_rate, - final_rate=final_rate, - energy_rate_change_per_update=energy_rate_change_per_update, + initial_rate=initial_rate.profile, + final_rate=final_rate.profile, + energy_rate_change_per_update=energy_rate_change_per_update.profile, fit_to_limit=fit_to_limit, update_interval=update_interval, ) + self.bid_update.update_and_populate_price_settings(self.area) def _reset_rates_and_update_prices(self): """Set the initial/final rates and update the price of all bids/offers consequently.""" @@ -470,39 +476,39 @@ def _delete_past_state(self): self._future_market_strategy.delete_past_state_values(self.area.current_market.time_slot) def _validate_consumption_rates( - self, initial_rate, final_rate, energy_rate_change_per_update, fit_to_limit + self, + initial_rate: StrategyProfileBase, + final_rate: StrategyProfileBase, + energy_rate_change_per_update: StrategyProfileBase, + fit_to_limit: bool, ): - for time_slot in initial_rate.keys(): + for time_slot in initial_rate.profile.keys(): rate_change = ( - None - if fit_to_limit - else get_from_profile_same_weekday_and_time( - energy_rate_change_per_update, time_slot - ) + None if fit_to_limit else energy_rate_change_per_update.get_value(time_slot) ) self.validator.validate_rate( - initial_buying_rate=initial_rate[time_slot], + initial_buying_rate=initial_rate.get_value(time_slot), energy_rate_increase_per_update=rate_change, - final_buying_rate=get_from_profile_same_weekday_and_time(final_rate, time_slot), + final_buying_rate=final_rate.get_value(time_slot), fit_to_limit=fit_to_limit, ) def _validate_production_rates( - self, initial_rate, final_rate, energy_rate_change_per_update, fit_to_limit + self, + initial_rate: StrategyProfileBase, + final_rate: StrategyProfileBase, + energy_rate_change_per_update: StrategyProfileBase, + fit_to_limit: bool, ): - for time_slot in initial_rate.keys(): + for time_slot in initial_rate.profile.keys(): rate_change = ( - None - if fit_to_limit - else get_from_profile_same_weekday_and_time( - energy_rate_change_per_update, time_slot - ) + None if fit_to_limit else energy_rate_change_per_update.get_value(time_slot) ) self.validator.validate_rate( - initial_selling_rate=initial_rate[time_slot], - final_selling_rate=get_from_profile_same_weekday_and_time(final_rate, time_slot), + initial_selling_rate=initial_rate.get_value(time_slot), + final_selling_rate=final_rate.get_value(time_slot), energy_rate_decrease_per_update=rate_change, fit_to_limit=fit_to_limit, ) diff --git a/src/gsy_e/models/strategy/storage.py b/src/gsy_e/models/strategy/storage.py index 1f53fc3e3..ac0f280ab 100644 --- a/src/gsy_e/models/strategy/storage.py +++ b/src/gsy_e/models/strategy/storage.py @@ -24,8 +24,8 @@ from gsy_framework.constants_limits import ConstSettings, FLOATING_POINT_TOLERANCE from gsy_framework.data_classes import TraderDetails from gsy_framework.exceptions import GSyException -from gsy_framework.read_user_profile import InputProfileTypes, UserProfileReader -from gsy_framework.utils import get_from_profile_same_weekday_and_time, key_in_dict_and_not_none +from gsy_framework.read_user_profile import InputProfileTypes +from gsy_framework.utils import key_in_dict_and_not_none from gsy_framework.validators import StorageValidator from pendulum import duration @@ -41,6 +41,7 @@ TemplateStrategyBidUpdater, TemplateStrategyOfferUpdater, ) +from gsy_e.models.strategy.strategy_profile import profile_factory, StrategyProfileBase log = getLogger(__name__) @@ -121,11 +122,13 @@ def __init__( # pylint: disable=too-many-arguments, too-many-locals energy_rate_change_per_update=energy_rate_decrease_per_update, update_interval=update_interval, ) - for time_slot in self.offer_update.initial_rate_profile_buffer.keys(): + for time_slot in self.offer_update.initial_rate_profile_buffer.profile.keys(): StorageValidator.validate( - initial_selling_rate=self.offer_update.initial_rate_profile_buffer[time_slot], - final_selling_rate=get_from_profile_same_weekday_and_time( - self.offer_update.final_rate_profile_buffer, time_slot + initial_selling_rate=self.offer_update.initial_rate_profile_buffer.get_value( + time_slot + ), + final_selling_rate=self.offer_update.final_rate_profile_buffer.get_value( + time_slot ), ) self.bid_update = TemplateStrategyBidUpdater( @@ -136,12 +139,12 @@ def __init__( # pylint: disable=too-many-arguments, too-many-locals update_interval=update_interval, rate_limit_object=min, ) - for time_slot in self.bid_update.initial_rate_profile_buffer.keys(): + for time_slot in self.bid_update.initial_rate_profile_buffer.profile.keys(): StorageValidator.validate( - initial_buying_rate=self.bid_update.initial_rate_profile_buffer[time_slot], - final_buying_rate=get_from_profile_same_weekday_and_time( - self.bid_update.final_rate_profile_buffer, time_slot + initial_buying_rate=self.bid_update.initial_rate_profile_buffer.get_value( + time_slot ), + final_buying_rate=self.bid_update.final_rate_profile_buffer.get_value(time_slot), ) self._state = StorageState( initial_soc=initial_soc, @@ -153,7 +156,6 @@ def __init__( # pylint: disable=too-many-arguments, too-many-locals ) self.cap_price_strategy = cap_price_strategy self.balancing_energy_ratio = BalancingRatio(*balancing_energy_ratio) - self._reader = UserProfileReader() def _create_future_market_strategy(self): return future_market_strategy_factory(self.asset_type) @@ -164,41 +166,51 @@ def state(self) -> StorageState: def _area_reconfigure_prices(self, **kwargs): # pylint: disable=too-many-branches if key_in_dict_and_not_none(kwargs, "initial_selling_rate"): - initial_selling_rate = self._reader.read_arbitrary_profile( - InputProfileTypes.IDENTITY, kwargs["initial_selling_rate"] + initial_selling_rate = profile_factory( + profile_type=InputProfileTypes.IDENTITY, + input_profile=kwargs["initial_selling_rate"], ) + initial_selling_rate.read_or_rotate_profiles() else: initial_selling_rate = self.offer_update.initial_rate_profile_buffer if key_in_dict_and_not_none(kwargs, "final_selling_rate"): - final_selling_rate = self._reader.read_arbitrary_profile( - InputProfileTypes.IDENTITY, kwargs["final_selling_rate"] + final_selling_rate = profile_factory( + profile_type=InputProfileTypes.IDENTITY, input_profile=kwargs["final_selling_rate"] ) + final_selling_rate.read_or_rotate_profiles() else: final_selling_rate = self.offer_update.final_rate_profile_buffer if key_in_dict_and_not_none(kwargs, "initial_buying_rate"): - initial_buying_rate = self._reader.read_arbitrary_profile( - InputProfileTypes.IDENTITY, kwargs["initial_buying_rate"] + initial_buying_rate = profile_factory( + profile_type=InputProfileTypes.IDENTITY, + input_profile=kwargs["initial_buying_rate"], ) + initial_buying_rate.read_or_rotate_profiles() else: initial_buying_rate = self.bid_update.initial_rate_profile_buffer if key_in_dict_and_not_none(kwargs, "final_buying_rate"): - final_buying_rate = self._reader.read_arbitrary_profile( - InputProfileTypes.IDENTITY, kwargs["final_buying_rate"] + final_buying_rate = profile_factory( + profile_type=InputProfileTypes.IDENTITY, input_profile=kwargs["final_buying_rate"] ) + final_buying_rate.read_or_rotate_profiles() else: final_buying_rate = self.bid_update.final_rate_profile_buffer if key_in_dict_and_not_none(kwargs, "energy_rate_decrease_per_update"): - energy_rate_decrease_per_update = self._reader.read_arbitrary_profile( - InputProfileTypes.IDENTITY, kwargs["energy_rate_decrease_per_update"] + energy_rate_decrease_per_update = profile_factory( + profile_type=InputProfileTypes.IDENTITY, + input_profile=kwargs["energy_rate_decrease_per_update"], ) + energy_rate_decrease_per_update.read_or_rotate_profiles() else: energy_rate_decrease_per_update = ( self.offer_update.energy_rate_change_per_update_profile_buffer ) if key_in_dict_and_not_none(kwargs, "energy_rate_increase_per_update"): - energy_rate_increase_per_update = self._reader.read_arbitrary_profile( - InputProfileTypes.IDENTITY, kwargs["energy_rate_increase_per_update"] + energy_rate_increase_per_update = profile_factory( + profile_type=InputProfileTypes.IDENTITY, + input_profile=kwargs["energy_rate_increase_per_update"], ) + energy_rate_increase_per_update.read_or_rotate_profiles() else: energy_rate_increase_per_update = ( self.bid_update.energy_rate_change_per_update_profile_buffer @@ -233,19 +245,22 @@ def _area_reconfigure_prices(self, **kwargs): # pylint: disable=too-many-branch return self.offer_update.set_parameters( - initial_rate=initial_selling_rate, - final_rate=final_selling_rate, - energy_rate_change_per_update=energy_rate_decrease_per_update, + initial_rate=initial_selling_rate.profile, + final_rate=final_selling_rate.profile, + energy_rate_change_per_update=energy_rate_decrease_per_update.profile, fit_to_limit=offer_fit_to_limit, update_interval=update_interval, ) + self.offer_update.update_and_populate_price_settings(self.area) + self.bid_update.set_parameters( - initial_rate=initial_buying_rate, - final_rate=final_buying_rate, - energy_rate_change_per_update=energy_rate_increase_per_update, + initial_rate=initial_buying_rate.profile, + final_rate=final_buying_rate.profile, + energy_rate_change_per_update=energy_rate_increase_per_update.profile, fit_to_limit=bid_fit_to_limit, update_interval=update_interval, ) + self.bid_update.update_and_populate_price_settings(self.area) def area_reconfigure_event(self, *args, **kwargs): """Reconfigure the device properties at runtime using the provided arguments.""" @@ -254,17 +269,17 @@ def area_reconfigure_event(self, *args, **kwargs): def _validate_rates( # pylint: disable=too-many-arguments self, - initial_selling_rate, - final_selling_rate, - initial_buying_rate, - final_buying_rate, - energy_rate_increase_per_update, - energy_rate_decrease_per_update, - bid_fit_to_limit, - offer_fit_to_limit, + initial_selling_rate: StrategyProfileBase, + final_selling_rate: StrategyProfileBase, + initial_buying_rate: StrategyProfileBase, + final_buying_rate: StrategyProfileBase, + energy_rate_increase_per_update: StrategyProfileBase, + energy_rate_decrease_per_update: StrategyProfileBase, + bid_fit_to_limit: bool, + offer_fit_to_limit: bool, ): - for time_slot in initial_selling_rate.keys(): + for time_slot in initial_selling_rate.profile.keys(): if ( self.area and self.area.current_market @@ -273,30 +288,18 @@ def _validate_rates( # pylint: disable=too-many-arguments continue bid_rate_change = ( - None - if bid_fit_to_limit - else get_from_profile_same_weekday_and_time( - energy_rate_increase_per_update, time_slot - ) + None if bid_fit_to_limit else energy_rate_increase_per_update.get_value(time_slot) ) offer_rate_change = ( None if offer_fit_to_limit - else get_from_profile_same_weekday_and_time( - energy_rate_decrease_per_update, time_slot - ) + else energy_rate_decrease_per_update.get_value(time_slot) ) StorageValidator.validate( - initial_selling_rate=initial_selling_rate[time_slot], - final_selling_rate=get_from_profile_same_weekday_and_time( - final_selling_rate, time_slot - ), - initial_buying_rate=get_from_profile_same_weekday_and_time( - initial_buying_rate, time_slot - ), - final_buying_rate=get_from_profile_same_weekday_and_time( - final_buying_rate, time_slot - ), + initial_selling_rate=initial_selling_rate.get_value(time_slot), + final_selling_rate=final_selling_rate.get_value(time_slot), + initial_buying_rate=initial_buying_rate.get_value(time_slot), + final_buying_rate=final_buying_rate.get_value(time_slot), energy_rate_increase_per_update=bid_rate_change, energy_rate_decrease_per_update=offer_rate_change, ) @@ -333,89 +336,6 @@ def event_activate(self, **kwargs): self.event_activate_energy() self.event_activate_price() - def _validate_constructor_arguments( # pylint: disable=too-many-arguments, too-many-branches - self, - initial_soc=None, - min_allowed_soc=None, - battery_capacity_kWh=None, - max_abs_battery_power_kW=None, - initial_selling_rate=None, - final_selling_rate=None, - initial_buying_rate=None, - final_buying_rate=None, - energy_rate_change_per_update=None, - ): - - if battery_capacity_kWh is not None and battery_capacity_kWh < 0: - raise ValueError("Battery capacity should be a positive integer") - if max_abs_battery_power_kW is not None and max_abs_battery_power_kW < 0: - raise ValueError("Battery Power rating must be a positive integer.") - if initial_soc is not None and 0 < initial_soc > 100: - raise ValueError("initial SOC must be in between 0-100 %") - if min_allowed_soc is not None and 0 < min_allowed_soc > 100: - raise ValueError("initial SOC must be in between 0-100 %") - if ( - initial_soc is not None - and min_allowed_soc is not None - and initial_soc < min_allowed_soc - ): - raise ValueError("Initial charge must be more than the minimum allowed soc.") - if initial_selling_rate is not None and initial_selling_rate < 0: - raise ValueError("Initial selling rate must be greater equal 0.") - if final_selling_rate is not None: - if isinstance(final_selling_rate, float) and final_selling_rate < 0: - raise ValueError("Final selling rate must be greater equal 0.") - if isinstance(final_selling_rate, dict) and any( - rate < 0 for _, rate in final_selling_rate.items() - ): - raise ValueError("Final selling rate must be greater equal 0.") - if initial_selling_rate is not None and final_selling_rate is not None: - initial_selling_rate = self._reader.read_arbitrary_profile( - InputProfileTypes.IDENTITY, initial_selling_rate - ) - final_selling_rate = self._reader.read_arbitrary_profile( - InputProfileTypes.IDENTITY, final_selling_rate - ) - if any( - initial_selling_rate[hour] < final_selling_rate[hour] - for hour, _ in initial_selling_rate.items() - ): - raise ValueError("Initial selling rate must be greater than final selling rate.") - if initial_buying_rate is not None and initial_buying_rate < 0: - raise ValueError("Initial buying rate must be greater equal 0.") - if final_buying_rate is not None: - final_buying_rate = self._reader.read_arbitrary_profile( - InputProfileTypes.IDENTITY, final_buying_rate - ) - if any(rate < 0 for _, rate in final_buying_rate.items()): - raise ValueError("Final buying rate must be greater equal 0.") - if initial_buying_rate is not None and final_buying_rate is not None: - initial_buying_rate = self._reader.read_arbitrary_profile( - InputProfileTypes.IDENTITY, initial_buying_rate - ) - final_buying_rate = self._reader.read_arbitrary_profile( - InputProfileTypes.IDENTITY, final_buying_rate - ) - if any( - initial_buying_rate[hour] > final_buying_rate[hour] - for hour, _ in initial_buying_rate.items() - ): - raise ValueError("Initial buying rate must be less than final buying rate.") - if final_selling_rate is not None and final_buying_rate is not None: - final_selling_rate = self._reader.read_arbitrary_profile( - InputProfileTypes.IDENTITY, final_selling_rate - ) - final_buying_rate = self._reader.read_arbitrary_profile( - InputProfileTypes.IDENTITY, final_buying_rate - ) - if any( - final_buying_rate[hour] >= final_selling_rate[hour] - for hour, _ in final_selling_rate.items() - ): - raise ValueError("final_buying_rate should be higher than final_selling_rate.") - if energy_rate_change_per_update is not None and energy_rate_change_per_update < 0: - raise ValueError("energy_rate_change_per_update should be a non-negative value.") - def event_tick(self): """Post bids or update existing bid prices on market tick. diff --git a/src/gsy_e/models/strategy/update_frequency.py b/src/gsy_e/models/strategy/update_frequency.py index ce81460bb..7398bb826 100644 --- a/src/gsy_e/models/strategy/update_frequency.py +++ b/src/gsy_e/models/strategy/update_frequency.py @@ -22,14 +22,13 @@ from gsy_framework.constants_limits import ConstSettings, GlobalConfig from gsy_framework.read_user_profile import InputProfileTypes from gsy_framework.utils import ( - get_from_profile_same_weekday_and_time, is_time_slot_in_simulation_duration, ) from pendulum import duration, DateTime, Duration import gsy_e.constants -from gsy_e.gsy_e_core.global_objects_singleton import global_objects from gsy_e.gsy_e_core.util import write_default_to_dict, is_time_slot_in_past_markets +from gsy_e.models.strategy.strategy_profile import profile_factory if TYPE_CHECKING: from gsy_e.models.area import Area @@ -92,10 +91,7 @@ def __init__( self.final_rate_input = final_rate self.energy_rate_change_per_update_input = energy_rate_change_per_update - # buffer of populated input values Dict[DateTime, float] - self.initial_rate_profile_buffer = {} - self.final_rate_profile_buffer = {} - self.energy_rate_change_per_update_profile_buffer = {} + self._reset_buffer_profiles() # dicts that are used for price calculations, contain only # all_markets Dict[DateTime, float] @@ -103,7 +99,7 @@ def __init__( self.final_rate = {} self.energy_rate_change_per_update = {} - self._read_or_rotate_rate_profiles() + self._read_or_rotate_rate_buffer_profiles() self.update_interval = update_interval self.update_counter = {} @@ -119,35 +115,15 @@ def serialize(self): """Return dict with configuration parameters.""" return {"fit_to_limit": self.fit_to_limit, "update_interval": self.update_interval} - def _read_or_rotate_rate_profiles(self, reconfigure=False) -> None: + def _read_or_rotate_rate_buffer_profiles(self, reconfigure=False) -> None: """ Creates a new chunk of profiles if the current_timestamp is not in the profile buffers """ - - self.initial_rate_profile_buffer = global_objects.profiles_handler.rotate_profile( - profile_type=InputProfileTypes.IDENTITY, - profile=( - self.initial_rate_input - if reconfigure or not self.initial_rate_profile_buffer - else self.initial_rate_profile_buffer - ), - input_profile_path=self.initial_rate_input, - ) - self.final_rate_profile_buffer = global_objects.profiles_handler.rotate_profile( - profile_type=InputProfileTypes.IDENTITY, - profile=( - self.final_rate_input - if reconfigure or not self.final_rate_profile_buffer - else self.final_rate_profile_buffer - ), - input_profile_path=self.final_rate_input, - ) - if self.fit_to_limit is False: - self.energy_rate_change_per_update_profile_buffer = ( - global_objects.profiles_handler.rotate_profile( - InputProfileTypes.IDENTITY, self.energy_rate_change_per_update_input - ) - ) + self.initial_rate_profile_buffer.read_or_rotate_profiles(reconfigure) + self.final_rate_profile_buffer.read_or_rotate_profiles(reconfigure) + if self.fit_to_limit: + return + self.energy_rate_change_per_update_profile_buffer.read_or_rotate_profiles(reconfigure) def _delete_market_slot_data(self, market_time_slot: DateTime) -> None: self.initial_rate.pop(market_time_slot, None) @@ -183,16 +159,10 @@ def _populate_profiles(self, area: "Area") -> None: continue if self.fit_to_limit is False: self.energy_rate_change_per_update[time_slot] = ( - get_from_profile_same_weekday_and_time( - self.energy_rate_change_per_update_profile_buffer, time_slot - ) + self.energy_rate_change_per_update_profile_buffer.get_value(time_slot) ) - initial_rate = get_from_profile_same_weekday_and_time( - self.initial_rate_profile_buffer, time_slot - ) - final_rate = get_from_profile_same_weekday_and_time( - self.final_rate_profile_buffer, time_slot - ) + initial_rate = self.initial_rate_profile_buffer.get_value(time_slot) + final_rate = self.final_rate_profile_buffer.get_value(time_slot) if initial_rate is None or final_rate is None: logging.warning( @@ -201,13 +171,9 @@ def _populate_profiles(self, area: "Area") -> None: gsy_e.constants.CONFIGURATION_ID, area.uuid, ) - self._read_or_rotate_rate_profiles() - initial_rate = get_from_profile_same_weekday_and_time( - self.initial_rate_profile_buffer, time_slot - ) - final_rate = get_from_profile_same_weekday_and_time( - self.final_rate_profile_buffer, time_slot - ) + self._read_or_rotate_rate_buffer_profiles() + initial_rate = self.initial_rate_profile_buffer.get_value(time_slot) + final_rate = self.final_rate_profile_buffer.get_value(time_slot) # Hackathon TODO: get rid of self.initial_rate, self.final_rate, self.update_counter # and self.market_slot_added_time_mapping in favor of one object @@ -230,26 +196,19 @@ def _add_slot_to_mapping(self, area, time_slot): def _set_or_update_energy_rate_change_per_update(self, time_slot: DateTime) -> None: energy_rate_change_per_update = {} if self.fit_to_limit: - initial_rate = get_from_profile_same_weekday_and_time( - self.initial_rate_profile_buffer, time_slot - ) - final_rate = get_from_profile_same_weekday_and_time( - self.final_rate_profile_buffer, time_slot - ) + initial_rate = self.initial_rate_profile_buffer.get_value(time_slot) + final_rate = self.final_rate_profile_buffer.get_value(time_slot) energy_rate_change_per_update[time_slot] = ( initial_rate - final_rate ) / self.number_of_available_updates else: if self.rate_limit_object is min: energy_rate_change_per_update[time_slot] = ( - -1 - * get_from_profile_same_weekday_and_time( - self.energy_rate_change_per_update_profile_buffer, time_slot - ) + -1 * self.energy_rate_change_per_update_profile_buffer.get_value(time_slot) ) elif self.rate_limit_object is max: - energy_rate_change_per_update[time_slot] = get_from_profile_same_weekday_and_time( - self.energy_rate_change_per_update_profile_buffer, time_slot + energy_rate_change_per_update[time_slot] = ( + self.energy_rate_change_per_update_profile_buffer.get_value(time_slot) ) self.energy_rate_change_per_update.update(energy_rate_change_per_update) @@ -266,7 +225,7 @@ def _calculate_number_of_available_updates_per_slot(self) -> int: def update_and_populate_price_settings(self, area: "Area") -> None: """Populate the price profiles for every available time slot.""" - self._read_or_rotate_rate_profiles() + self._read_or_rotate_rate_buffer_profiles() # Handling the case where future markets are disabled during a simulation. if self._time_slot_duration_in_seconds <= 0: return @@ -320,6 +279,19 @@ def time_for_price_update(self, strategy: "BaseStrategy", time_slot: DateTime) - self.update_interval.seconds * self.update_counter[time_slot] ) + def _reset_buffer_profiles(self): + self.initial_rate_profile_buffer = profile_factory( + input_profile=self.initial_rate_input, profile_type=InputProfileTypes.IDENTITY + ) + self.final_rate_profile_buffer = profile_factory( + input_profile=self.final_rate_input, profile_type=InputProfileTypes.IDENTITY + ) + self.energy_rate_change_per_update_profile_buffer = profile_factory( + input_profile=self.energy_rate_change_per_update_input, + profile_type=InputProfileTypes.IDENTITY, + ) + self._read_or_rotate_rate_buffer_profiles() + def set_parameters( self, *, @@ -341,7 +313,7 @@ def set_parameters( self.fit_to_limit = fit_to_limit if update_interval is not None: self.update_interval = update_interval - self._read_or_rotate_rate_profiles(reconfigure=True) + self._reset_buffer_profiles() def reset(self, strategy: "BaseStrategy") -> None: raise NotImplementedError diff --git a/tests/strategies/test_strategy_ev_charger.py b/tests/strategies/test_strategy_ev_charger.py index 4bff4944a..b82dd91b0 100644 --- a/tests/strategies/test_strategy_ev_charger.py +++ b/tests/strategies/test_strategy_ev_charger.py @@ -146,10 +146,10 @@ def test_if_preferred_charging_power_overrides_bought_energy(ev_charger_strategy preferred_power_kW = 1.5 strategy = EVChargerStrategy( maximum_power_rating_kW=ev_charger_strategy.maximum_power_rating_kW, - initial_buying_rate=ev_charger_strategy.bid_update.initial_rate_profile_buffer, - final_buying_rate=ev_charger_strategy.bid_update.final_rate_profile_buffer, - initial_selling_rate=ev_charger_strategy.offer_update.initial_rate_profile_buffer, - final_selling_rate=ev_charger_strategy.offer_update.final_rate_profile_buffer, + initial_buying_rate=ev_charger_strategy.bid_update.initial_rate_profile_buffer.profile, + final_buying_rate=ev_charger_strategy.bid_update.final_rate_profile_buffer.profile, + initial_selling_rate=ev_charger_strategy.offer_update.initial_rate_profile_buffer.profile, + final_selling_rate=ev_charger_strategy.offer_update.final_rate_profile_buffer.profile, charging_sessions=ev_charger_strategy.charging_sessions, preferred_charging_power=preferred_power_kW, ) diff --git a/tests/test_area_serializer.py b/tests/test_area_serializer.py index 28da9b031..1fb2f630c 100644 --- a/tests/test_area_serializer.py +++ b/tests/test_area_serializer.py @@ -113,7 +113,9 @@ def test_non_attr_param(): recovered1 = area_from_string(area_to_string(area1), _create_config()) assert recovered1.strategy._energy_params.capacity_kW is None assert ( - recovered1.strategy.offer_update.final_rate_profile_buffer[area1.config.start_date] + recovered1.strategy.offer_update.final_rate_profile_buffer.get_value( + area1.config.start_date + ) == ConstSettings.PVSettings.SELLING_RATE_RANGE.final )