def _area_reconfigure_prices(self, validate=True, **kwargs): if validate: validate_pv_device_price(**kwargs) if key_in_dict_and_not_none(kwargs, 'initial_selling_rate'): self.offer_update.initial_rate = read_arbitrary_profile( InputProfileTypes.IDENTITY, kwargs['initial_selling_rate']) if key_in_dict_and_not_none(kwargs, 'final_selling_rate'): self.offer_update.final_rate = read_arbitrary_profile( InputProfileTypes.IDENTITY, kwargs['final_selling_rate']) if key_in_dict_and_not_none(kwargs, 'energy_rate_decrease_per_update'): self.offer_update.energy_rate_change_per_update = \ read_arbitrary_profile(InputProfileTypes.IDENTITY, kwargs['energy_rate_decrease_per_update']) if key_in_dict_and_not_none(kwargs, 'fit_to_limit'): self.offer_update.fit_to_limit = kwargs['fit_to_limit'] if key_in_dict_and_not_none(kwargs, 'update_interval'): if isinstance(kwargs['update_interval'], int): update_interval = duration(minutes=kwargs['update_interval']) else: update_interval = kwargs['update_interval'] self.offer_update.update_interval = update_interval if key_in_dict_and_not_none(kwargs, 'use_market_maker_rate'): self.use_market_maker_rate = kwargs['use_market_maker_rate'] self._validate_rates() self.offer_update.update_offer(self)
def __init__( self, initial_rate, final_rate, fit_to_limit=True, energy_rate_change_per_update=None, update_interval=duration( minutes=ConstSettings.GeneralSettings.DEFAULT_UPDATE_INTERVAL), rate_limit_object=max): self.fit_to_limit = fit_to_limit self.initial_rate = read_arbitrary_profile(InputProfileTypes.IDENTITY, initial_rate) self.final_rate = read_arbitrary_profile(InputProfileTypes.IDENTITY, final_rate) if fit_to_limit is False: self.energy_rate_change_per_update = \ read_arbitrary_profile(InputProfileTypes.IDENTITY, energy_rate_change_per_update) else: self.energy_rate_change_per_update = None self.update_interval = update_interval self.update_counter = read_arbitrary_profile( InputProfileTypes.IDENTITY, 0) self.number_of_available_updates = 0 self.rate_limit_object = rate_limit_object
def test_if_storage_doesnt_buy_too_expensive(storage_strategy_test3, area_test3): from d3a.models.read_user_profile import read_arbitrary_profile, InputProfileTypes storage_strategy_test3.bid_update.initial_rate = \ read_arbitrary_profile(InputProfileTypes.IDENTITY, 0) storage_strategy_test3.bid_update.final_rate = \ read_arbitrary_profile(InputProfileTypes.IDENTITY, 1) storage_strategy_test3.event_activate() storage_strategy_test3.event_tick() assert len(storage_strategy_test3.accept_offer.calls) == 0
def __init__( self, risk: int = GeneralSettings.DEFAULT_RISK, initial_capacity_kWh: float = StorageSettings.MIN_ALLOWED_SOC * StorageSettings.CAPACITY, initial_soc: float = None, initial_rate_option: int = StorageSettings.INITIAL_RATE_OPTION, energy_rate_decrease_option: int = StorageSettings. RATE_DECREASE_OPTION, energy_rate_decrease_per_update: float = GeneralSettings. ENERGY_RATE_DECREASE_PER_UPDATE, # NOQA battery_capacity_kWh: float = StorageSettings.CAPACITY, max_abs_battery_power_kW: float = StorageSettings.MAX_ABS_POWER, break_even: Union[tuple, dict] = (StorageSettings.BREAK_EVEN_BUY, StorageSettings.BREAK_EVEN_SELL), balancing_energy_ratio: tuple = ( BalancingSettings.OFFER_DEMAND_RATIO, BalancingSettings.OFFER_SUPPLY_RATIO), cap_price_strategy: bool = False, min_allowed_soc=None): if min_allowed_soc is None: min_allowed_soc = StorageSettings.MIN_ALLOWED_SOC break_even = read_arbitrary_profile(InputProfileTypes.IDENTITY, break_even) self._validate_constructor_arguments(risk, initial_capacity_kWh, initial_soc, battery_capacity_kWh, break_even, min_allowed_soc) self.break_even = break_even self.min_selling_rate = list(break_even.values())[0][1] BaseStrategy.__init__(self) OfferUpdateFrequencyMixin.__init__(self, initial_rate_option, energy_rate_decrease_option, energy_rate_decrease_per_update) # Normalize min/max buying rate profiles before passing to the bid mixin self.min_buying_rate_profile = read_arbitrary_profile( InputProfileTypes.IDENTITY, StorageSettings.MIN_BUYING_RATE) self.max_buying_rate_profile = {k: v[1] for k, v in break_even.items()} BidUpdateFrequencyMixin.__init__( self, initial_rate_profile=self.min_buying_rate_profile, final_rate_profile=self.max_buying_rate_profile) self.risk = risk self.state = StorageState( initial_capacity_kWh=initial_capacity_kWh, initial_soc=initial_soc, capacity=battery_capacity_kWh, max_abs_battery_power_kW=max_abs_battery_power_kW, loss_per_hour=0.0, strategy=self, min_allowed_soc=min_allowed_soc) self.cap_price_strategy = cap_price_strategy self.balancing_energy_ratio = BalancingRatio(*balancing_energy_ratio)
def _area_reconfigure_prices(self, validate=True, **kwargs): if validate: validate_pv_device_price(**kwargs) if 'initial_selling_rate' in kwargs and kwargs[ 'initial_selling_rate'] is not None: self.offer_update.initial_rate = read_arbitrary_profile( InputProfileTypes.IDENTITY, kwargs['initial_selling_rate']) if 'final_selling_rate' in kwargs and kwargs[ 'final_selling_rate'] is not None: self.offer_update.final_rate = read_arbitrary_profile( InputProfileTypes.IDENTITY, kwargs['final_selling_rate']) self._validate_rates() self.offer_update.update_offer(self)
def _read_predefined_profile_for_pv(self) -> Dict[DateTime, float]: """ Reads profile data from the predefined power profiles. Reads config and constructor parameters and selects the appropriate predefined profile. :return: key value pairs of time to energy in kWh """ if self._power_profile_index is None or self._power_profile_index == 4: if self.owner.config.pv_user_profile is not None: return self.owner.config.pv_user_profile else: self._power_profile_index = self.owner.config.cloud_coverage if self._power_profile_index == 0: # 0:sunny profile_path = pathlib.Path(d3a_path + '/resources/Solar_Curve_W_sunny.csv') elif self._power_profile_index == 1: # 1:partial profile_path = pathlib.Path(d3a_path + '/resources/Solar_Curve_W_partial.csv') elif self._power_profile_index == 2: # 2:cloudy profile_path = pathlib.Path(d3a_path + '/resources/Solar_Curve_W_cloudy.csv') else: raise ValueError("Energy_profile has to be in [0,1,2]") # Populate energy production forecast data return read_arbitrary_profile(InputProfileTypes.POWER, str(profile_path))
def _read_predefined_profile_for_pv(self) -> Dict[DateTime, float]: """ Reads profile data from the power profile. Handles csv files and dicts. :return: key value pairs of time to energy in kWh """ return read_arbitrary_profile(InputProfileTypes.POWER, self._power_profile_W)
def test_correct_time_expansion_read_arbitrary_profile(): market_maker_rate = 30 GlobalConfig.sim_duration = duration(hours=3) mmr = read_arbitrary_profile(InputProfileTypes.IDENTITY, market_maker_rate) assert (list(mmr.keys())[-1] - today(tz=TIME_ZONE)).days == 0 GlobalConfig.sim_duration = duration(hours=36) mmr = read_arbitrary_profile(InputProfileTypes.IDENTITY, market_maker_rate) assert (list(mmr.keys())[-1] - today(tz=TIME_ZONE)).days == 1 GlobalConfig.sim_duration = duration(hours=48) mmr = read_arbitrary_profile(InputProfileTypes.IDENTITY, market_maker_rate) assert (list(mmr.keys())[-1] - today(tz=TIME_ZONE)).days == 2 GlobalConfig.sim_duration = duration(hours=49) mmr = read_arbitrary_profile(InputProfileTypes.IDENTITY, market_maker_rate) # read_arbitrary_profile expands until 01:00 after the last day in sim_duration # because of the future markets assert (sorted(list(mmr.keys()))[-1] == today(tz=TIME_ZONE).add(hours=49))
def __init__(self, avg_power_W, hrs_per_day=None, hrs_of_day=None, daily_budget=None, min_energy_rate: Union[float, dict, str] = ConstSettings.LoadSettings.MIN_ENERGY_RATE, max_energy_rate: Union[float, dict, str] = ConstSettings.LoadSettings.MAX_ENERGY_RATE, balancing_energy_ratio: tuple = (ConstSettings.BalancingSettings.OFFER_DEMAND_RATIO, ConstSettings.BalancingSettings.OFFER_SUPPLY_RATIO)): BaseStrategy.__init__(self) self.min_energy_rate = read_arbitrary_profile(InputProfileTypes.IDENTITY, min_energy_rate) self.max_energy_rate = read_arbitrary_profile(InputProfileTypes.IDENTITY, max_energy_rate) BidUpdateFrequencyMixin.__init__(self, initial_rate_profile=self.min_energy_rate, final_rate_profile=self.max_energy_rate) self.state = LoadState() self.avg_power_W = avg_power_W # consolidated_cycle is KWh energy consumed for the entire year self.daily_energy_required = None # Budget for a single day in eur self.daily_budget = daily_budget * 100 if daily_budget is not None else None # Energy consumed during the day ideally should not exceed daily_energy_required self.energy_per_slot_Wh = None self.energy_requirement_Wh = {} # type: Dict[Time, float] self.hrs_per_day = {} # type: Dict[int, int] if hrs_of_day is None: hrs_of_day = list(range(24)) # be a parameter on the constructor or if we want to deal in percentages if hrs_per_day is None: hrs_per_day = len(hrs_of_day) if hrs_of_day is None: hrs_of_day = list(range(24)) self.hrs_of_day = hrs_of_day self._initial_hrs_per_day = hrs_per_day self.balancing_energy_ratio = BalancingRatio(*balancing_energy_ratio) if not all([0 <= h <= 23 for h in hrs_of_day]): raise ValueError("Hrs_of_day list should contain integers between 0 and 23.") if len(hrs_of_day) < hrs_per_day: raise ValueError("Length of list 'hrs_of_day' must be greater equal 'hrs_per_day'")
def event_activate(self): super().event_activate() load_profile_raw_kg = read_arbitrary_profile( InputProfileTypes.IDENTITY, self.discharge_profile) for key, value in load_profile_raw_kg.items(): self.load_profile_kWh[key] = value * self.conversion_factor_kWh_kg
def _event_activate_energy(self, daily_load_profile): """ Reads the power profile data and calculates the required energy for each slot. """ load_profile = read_arbitrary_profile(InputProfileTypes.POWER, daily_load_profile) self._update_energy_requirement(load_profile)
def test_correct_interpolation_power_profile(): slot_length = 20 GlobalConfig.slot_length = duration(minutes=slot_length) profile_path = pathlib.Path(d3a_path + '/resources/Solar_Curve_W_sunny.csv') profile = read_arbitrary_profile(InputProfileTypes.POWER, str(profile_path)) times = list(profile) for ii in range(len(times)-1): assert abs((times[ii]-times[ii+1]).in_seconds()) == slot_length * 60
def hour_profile_of_market_maker_rate(context, scenario): import importlib from d3a.models.read_user_profile import InputProfileTypes setup_file_module = importlib.import_module( "d3a.setup.{}".format(scenario)) context._market_maker_rate = \ read_arbitrary_profile(InputProfileTypes.IDENTITY, setup_file_module.market_maker_rate) assert context._market_maker_rate is not None
def event_activate(self): """ Runs on activate event. Reads the power profile data and calculates the required energy for each slot. :return: None """ self.bid_update.update_on_activate() self.load_profile = read_arbitrary_profile(InputProfileTypes.POWER, self.daily_load_profile) self._update_energy_requirement()
def area_reconfigure_event(self, validate=True, **kwargs): assert all(k in self.parameters for k in kwargs.keys()) if validate: validate_pv_device(**kwargs) for name, value in kwargs.items(): setattr(self, name, value) if 'initial_selling_rate' in kwargs and kwargs[ 'initial_selling_rate'] is not None: self.offer_update.initial_rate = read_arbitrary_profile( InputProfileTypes.IDENTITY, kwargs['initial_selling_rate']) if 'final_selling_rate' in kwargs and kwargs[ 'final_selling_rate'] is not None: self.offer_update.final_rate = read_arbitrary_profile( InputProfileTypes.IDENTITY, kwargs['final_selling_rate']) self._validate_rates() self.produced_energy_forecast_kWh() self.offer_update.update_offer(self)
def read_market_maker_rate(self, market_maker_rate): """ Reads market_maker_rate from arbitrary input types """ market_maker_rate_parsed = ast.literal_eval(str(market_maker_rate)) self.market_maker_rate = read_arbitrary_profile( InputProfileTypes.IDENTITY, market_maker_rate_parsed) self.market_maker_rate = { k: float(v) for k, v in self.market_maker_rate.items() }
def check_pv_csv_profile(context): house1 = list( filter(lambda x: x.name == "House 1", context.simulation.area.children))[0] pv = list(filter(lambda x: x.name == "H1 PV", house1.children))[0] from d3a.setup.strategy_tests.user_profile_pv_csv import user_profile_path profile_data = read_arbitrary_profile(InputProfileTypes.POWER, user_profile_path) for timepoint, energy in pv.strategy.energy_production_forecast_kWh.items( ): if timepoint in profile_data.keys(): assert energy == profile_data[timepoint] else: assert energy == 0
def event_activate(self): """ Runs on activate event. Reads the power profile data and calculates the required energy for each slot. :return: None """ # If use_market_maker_rate is true, overwrite final_buying_rate to market maker rate if self.use_market_maker_rate: self.area_reconfigure_event(final_buying_rate=GlobalConfig.market_maker_rate) self._validate_rates() self.bid_update.update_on_activate() self.load_profile = read_arbitrary_profile( InputProfileTypes.POWER, self.daily_load_profile) self._update_energy_requirement()
def check_pv_profile_csv(context): house1 = list( filter(lambda x: x.name == "House 1", context.simulation.area.children))[0] pv = list(filter(lambda x: x.name == "H1 PV", house1.children))[0] input_profile = read_arbitrary_profile(InputProfileTypes.POWER, context._device_profile) produced_energy = { from_format(f'{TODAY_STR}T{k.hour:02}:{k.minute:02}', DATE_TIME_FORMAT): v for k, v in pv.strategy.energy_production_forecast_kWh.items() } for timepoint, energy in produced_energy.items(): if timepoint in input_profile: assert energy == input_profile[timepoint] else: assert False
def check_pv_profile(context): house1 = list( filter(lambda x: x.name == "House 1", context.simulation.area.children))[0] pv = list(filter(lambda x: x.name == "H1 PV", house1.children))[0] if pv.strategy._power_profile_index == 0: path = os.path.join(d3a_path, "resources/Solar_Curve_W_sunny.csv") if pv.strategy._power_profile_index == 1: path = os.path.join(d3a_path, "resources/Solar_Curve_W_partial.csv") if pv.strategy._power_profile_index == 2: path = os.path.join(d3a_path, "resources/Solar_Curve_W_cloudy.csv") profile_data = read_arbitrary_profile(InputProfileTypes.POWER, str(path)) for timepoint, energy in pv.strategy.energy_production_forecast_kWh.items( ): if timepoint in profile_data.keys(): assert energy == profile_data[timepoint] else: assert energy == 0
def area_reconfigure_event(self, avg_power_W=None, hrs_per_day=None, hrs_of_day=None, final_buying_rate=None): if hrs_per_day is not None or hrs_of_day is not None: self.assign_hours_of_per_day(hrs_of_day, hrs_per_day) self.hrs_per_day = { day: self._initial_hrs_per_day for day in range(self.area.config.sim_duration.days + 1) } if avg_power_W is not None: self.avg_power_W = avg_power_W self.assign_energy_requirement(avg_power_W) if final_buying_rate is not None: self.bid_update.final_rate = read_arbitrary_profile( InputProfileTypes.IDENTITY, final_buying_rate)
def __init__(self, duration: duration, slot_length: duration, tick_length: duration, market_count: int, cloud_coverage: int, market_maker_rate, iaa_fee: int, pv_user_profile=None): self.duration = duration self.slot_length = slot_length self.tick_length = tick_length self.market_count = market_count self.ticks_per_slot = self.slot_length / self.tick_length if self.ticks_per_slot != int(self.ticks_per_slot): raise D3AException( "Non integer ticks per slot ({}) are not supported. " "Adjust simulation parameters.".format(self.ticks_per_slot)) self.ticks_per_slot = int(self.ticks_per_slot) if self.ticks_per_slot < 10: raise D3AException( "Too few ticks per slot ({}). Adjust simulation parameters". format(self.ticks_per_slot)) self.total_ticks = self.duration // self.slot_length * self.ticks_per_slot # TODO: Once the d3a uses a common API to the d3a-web, this should be removed # since this limitation already exists on d3a-web if 0 <= cloud_coverage <= 2: self.cloud_coverage = cloud_coverage else: raise D3AException( "Invalid cloud coverage value ({}).".format(cloud_coverage)) self.pv_user_profile = None \ if pv_user_profile is None \ else read_arbitrary_profile(InputProfileTypes.POWER, ast.literal_eval(pv_user_profile), self.slot_length) self.read_market_maker_rate(market_maker_rate) if iaa_fee is None: self.iaa_fee = ConstSettings.IAASettings.FEE_PERCENTAGE else: self.iaa_fee = iaa_fee
def _update_rate_parameters(self, initial_selling_rate, final_selling_rate, initial_buying_rate, final_buying_rate, energy_rate_change_per_update): if initial_selling_rate is not None: self.offer_update.initial_rate = read_arbitrary_profile( InputProfileTypes.IDENTITY, initial_selling_rate) if final_selling_rate is not None: self.offer_update.final_rate = read_arbitrary_profile( InputProfileTypes.IDENTITY, final_selling_rate) if initial_buying_rate is not None: self.bid_update.initial_rate = read_arbitrary_profile( InputProfileTypes.IDENTITY, initial_buying_rate) if final_buying_rate is not None: self.bid_update.final_rate = read_arbitrary_profile( InputProfileTypes.IDENTITY, final_buying_rate) if energy_rate_change_per_update is not None: self.offer_update.energy_rate_change_per_update = \ read_arbitrary_profile(InputProfileTypes.IDENTITY, energy_rate_change_per_update) self.bid_update.energy_rate_change_per_update = \ read_arbitrary_profile(InputProfileTypes.IDENTITY, energy_rate_change_per_update)
def _read_solar_profile(profile_path): return read_arbitrary_profile(InputProfileTypes.POWER, str(profile_path))
def _validate_constructor_arguments(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 type(final_selling_rate) is float and final_selling_rate < 0: raise ValueError("Final selling rate must be greater equal 0.") elif type(final_selling_rate) is 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 = read_arbitrary_profile( InputProfileTypes.IDENTITY, initial_selling_rate) final_selling_rate = 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 = 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 = read_arbitrary_profile( InputProfileTypes.IDENTITY, initial_buying_rate) final_buying_rate = 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 = read_arbitrary_profile( InputProfileTypes.IDENTITY, final_selling_rate) final_buying_rate = 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 _area_reconfigure_prices(self, final_buying_rate): if final_buying_rate is not None: self.bid_update.final_rate = read_arbitrary_profile( InputProfileTypes.IDENTITY, final_buying_rate) self._validate_rates()
def event_activate(self): super().event_activate() self.max_available_power_kW = \ read_arbitrary_profile(InputProfileTypes.IDENTITY, self.max_available_power_kW)
def read_pv_user_profile(self, pv_user_profile=None): self.pv_user_profile = None \ if pv_user_profile is None \ else read_arbitrary_profile(InputProfileTypes.POWER, ast.literal_eval(pv_user_profile))
def event_activate(self): self.energy_rate = self.area.config.market_maker_rate if self.energy_rate is None \ else read_arbitrary_profile(InputProfileTypes.IDENTITY, self.energy_rate)