annular.satellite_model ======================= .. py:module:: annular.satellite_model Submodules ---------- .. toctree:: :maxdepth: 1 /autoapi/annular/satellite_model/multi_profile_strategy/index /autoapi/annular/satellite_model/reading_bids_strategy/index /autoapi/annular/satellite_model/satellite_model/index /autoapi/annular/satellite_model/simple_demo/index Attributes ---------- .. autoapisummary:: annular.satellite_model.strategies Classes ------- .. autoapisummary:: annular.satellite_model.MultiProfileBiddingStrategy annular.satellite_model.ReadingBidsStrategy annular.satellite_model.SatelliteModel annular.satellite_model.SimpleMultiHourBiddingStrategy Package Contents ---------------- .. py:class:: MultiProfileBiddingStrategy(demands_path: pathlib.Path | str, forecasts_path: pathlib.Path | str, carrier_prices_path: pathlib.Path | str, cronian_config_path: pathlib.Path | str, tariff_folder: pathlib.Path | str | None = None, tariff_categories: dict[str, str | int] | None = None, floor_price: int = 0, horizon_size: int = 48, cronian_storage_model: str = 'simple', forecast_scaling_factor: float = 1.5, **kwargs) Bases: :py:obj:`annular.satellite_model.satellite_model.SatelliteModel` A 24-hour bidding strategy to bid using multiple profiles based on multiple scenarios. .. seealso:: annular.tariffs.TariffManager :param demands_path: Path to csv file with demand values per timestamp, with different flexibility as separate columns named 'flex+N'. :param forecasts_path: Path to csv file containing the electricity price forecast based on which multiple forecast scenarios will be generated by adjusting with the `forecast_scaling_factor`. :param carrier_prices_path: Path to csv file containing price timeseries for any energy carrier other than electricity such as methane, hydrogen, etc. :param cronian_config_path: Path to the Cronian configuration describing the prosumer's demand and assets for building its optimization model. :param tariff_folder: Path to the folder with tariff data; passed to a TariffManager. :param tariff_categories: Dictionary that indicates the tariff category the satellite is subject to. Keys are categories, and values are the levels in that category. :param floor_price: Minimum price to bid at, defaults to 0. :param horizon_size: Full length of the horizon to use for an optimization iteration, i.e., bidding window + look ahead period, in number of snapshots. :param forecast_scaling_factor: Factor to scale up/down the electricity price forecast from the base scenario to create multiple forecast scenarios. :param cronian_storage_model: Type of storage model (`simple` or `complex`) to use in the optimization model if prosumer has storage assets. :param kwargs: Any other keyword arguments are passed to the initialization of the base class. .. py:attribute:: cur_timestamp_idx :value: 0 .. py:attribute:: floor_price :value: 0 .. py:attribute:: horizon_size :value: 48 .. py:attribute:: forecast_scaling_factor :value: 1.5 .. py:attribute:: storage_model :value: 'simple' .. py:attribute:: demands :value: None .. py:attribute:: base_forecast :value: None .. py:attribute:: carrier_prices :value: None .. py:attribute:: manager .. py:attribute:: max_electricity_withdrawal_so_far :value: 0.0 .. py:property:: bidding_window :type: pandas.Index Returns current bidding window as a pandas Index of timestamps. .. py:method:: determine_bids() -> pandas.DataFrame Determine bids based on multiple forecast price scenarios. Different forecast price scenarios are made as variation of the given electricity_price_forecast. An optimization is run based on each of these scenarios, to determine the corresponding demand profile. The set of these profiles is submitted in a single exclusive group, all bid for at ceiling price. :returns: A collection of bids covering the current bidding window. .. py:method:: meet_demand(market_price: numpy.ndarray | None, demand_met: numpy.ndarray | None) -> None Process the amount of demand that was met. Solve the cronian model again, now with the electric power fixed to the values given from the central market. Record dispatch of assets and update the state of charge for any storage assets. :param market_price: Price of electricity as provided per timestep. :param demand_met: Amount of demand that was met at the market price per timestep. .. py:method:: _update_internal_state(horizon: pandas.Index) -> None Store intermediate values to carry over to the next rolling horizon. Specifically, these values are the energy levels of storage assets if any. :param horizon: Current optimization horizon. .. py:method:: _get_horizon(length: int = None) -> pandas.Index Select the relevant horizon index values at the current timestamp index. :param length: length of the desired horizon in number of timestamps. Uses `self.horizon_size` when `None` is given. :returns: Pandas Index object of the selected horizon values. .. py:method:: make_forecast_scenarios(base_forecast: pandas.DataFrame, forecast_scaling_factor: float) -> dict[str, pandas.DataFrame] :staticmethod: Create different scenarios from a base forecast. The different scenarios are created by either scaling the base forecast according to some profile, or by scaling a fixed profile according to a numerical property of the base forecast. The following scenarios are created: - scale_up: `base_forecast_values * forecast_scaling_factor` - scale_down: `base_forecast_values / forecast_scaling_factor` - scale_up_increasing: `base_forecast_values * linear_increase` - scale_down_increasing: `base_forecast_values * linear_decrease` - peak_mid: `base_forecast_values * peak_shape_scaling` - valley_mid: `base_forecast_values * valley_shape_scaling` - day_period_boost: `base_forecast_values * boost_day_period` - morning_evening_boost: `base_forecast_values * boost_morning_evening` - base_min_day_period_boost: `base_forecast_min * boost_day_period` - base_min_morning_evening_boost: `base_forecast_min * boost_morning_evening` - base_max_day_period_boost: `base_forecast_max * boost_day_period` - base_max_morning_evening_boost: `base_forecast_max * boost_morning_evening` :param base_forecast: Electricity price forecast used as a base for generating different scenarios of electricity price variations. :param forecast_scaling_factor: Factor to scale the base forecast to create different scenarios. :returns: Electricity price forecast scenarios. :raises ValueError: If the length of the forecast is not 24. .. py:method:: plot_forecast_scenarios(forecast_scenarios: dict[str, pandas.DataFrame], output_path) -> None :staticmethod: Plot the electricity price forecast scenarios. :param forecast_scenarios: Dictionary of forecast scenarios. :param output_path: Path where the plot will be saved. .. py:method:: determine_power_profile(horizon: pandas.Index, scenario_forecast: pandas.DataFrame) -> list[float] Determine electricity consumption/generation profile based on a given scenario. :param horizon: Time horizon. :param scenario_forecast: DataFrame containing forecasted electricity prices. :param model: Abstract pyomo optimization model. :returns: Optimized electricity power profile for the given forecast price. .. py:method:: _make_bids(profiles: dict[str, list[float]]) -> pandas.DataFrame Create a bids table to submit given profiles as an exclusive group. Actual bid entries are only made for timestamps with non-zero demand. :param profiles: dictionary of lists of demand values for each timestamp. :returns: pd.DataFrame table of the constructed bids. .. py:method:: _create_model(horizon: pandas.Index) -> pyomo.environ.AbstractModel Create a model instance using `cronian`. :param horizon: Timestamps spanning the optimization horizon currently of interest. :param e_price: Forecasted electricity price. :returns: Abstract model for this satellite in the current optimization horizon. .. py:method:: _add_cost_objective(model: pyomo.environ.AbstractModel) -> pyomo.environ.AbstractModel Create a cost minimization or revenue maximization objective. Depending on the sign of the `electric_power` variable, the objective minimizes electricity consumption cost or maximizes revenues from electricity generated locally. It also minimizes the cost of consuming other externally priced energy carriers such as methane, biomass, etc. :param model: Pyomo model to add the cost objective to. :returns: The given model object, now with `cost_objective` added. .. py:method:: _define_max_electricity_withdrawal(model: pyomo.environ.AbstractModel) -> pyomo.environ.AbstractModel Define maximum electricity withdrawal for modeling capacity tariffs. This function defines the maximum electricity withdrawal in the current optimization horizon to which a capacity tariff charge is applied. Directly computing max withdrawal as `max (withdrawals in current horizon, previous maximum withdrawal)` will lead to a nonlinear optimization problem due to the max() operator. We linearize this by introducing a variable representing the maximum power withdrawal and enforcing the following constraints on it: (1) It must be greater than or equal to the instantaneous withdrawals in the current horizon. (2) It must be greater than or equal to the recorded maximum withdrawal from previous horizons. :param model: Pyomo model to add the constraint to. :returns: The given model object, now with `capacity_tariff_constraint` added. .. py:method:: _add_contracted_capacity_limit_constraint(model: pyomo.environ.AbstractModel) -> pyomo.environ.AbstractModel Constrain max electricity withdrawal based on contracted transport limit. NOTE: We assume that the unit of the contracted_transport_limit is MW. :param model: Pyomo model to add the constraint to. :returns: The given model object, now with `max_withdrawal_in_previous_constraint` added. .. py:class:: ReadingBidsStrategy(bids_csv_path: pathlib.Path | str, **kwargs) Bases: :py:obj:`annular.satellite_model.satellite_model.SatelliteModel` Bidding strategy that reads bids from a CSV file. This strategy loads bids from a CSV file once during initialization. When its `determine_bids` method is called, it identifies the next set of timestamps to provide bids for, shifting by `rolling_horizon_step` timestamps every iteration. It then reads bids for this set of timestamps from the loaded bids. When it reaches the end of the data, it cycles back to the beginning and starts serving the same bids again, but with the timestamps adjusted forward by however many time increments have passed. The data in the CSV does not need to span a perfect multiple of e.g. 24 hours. If the data ends at a shorter time (e.g., 11:00:00), the strategy will only serve the available bids and then reset to the beginning for the next cycle. The strategy expects the CSV file to have the following columns: +--------------------+------------------+-----------+----------+-------+ | exclusive_group_id | profile_block_id | timestamp | quantity | price | +--------------------+------------------+-----------+----------+-------+ | ... | ... | ... | ... | ... | +--------------------+------------------+-----------+----------+-------+ :param bids_csv_path: Path to the CSV file containing the bids. :param rolling_horizon_step: How many snapshots to advance at every iteration, ie, for how many snapshots bids need to be made. :param \*\*kwargs: Additional keyword arguments, which are currently ignored. .. py:attribute:: bids .. py:attribute:: timestamps .. py:attribute:: LAST_TIMESTAMP .. py:attribute:: increment .. py:attribute:: num_cycles :value: 0 .. py:attribute:: cycle_length .. py:method:: determine_bids() -> pandas.DataFrame Returns the bids from next timestamp. .. py:method:: meet_demand(market_price: numpy.ndarray | None, demand_met: numpy.ndarray | None) -> None No model for processing the demand_met, but forwards internal current timestep by 24 hours. .. py:method:: _get_horizon() -> pandas.Index Select the relevant horizon index values at the current timestep. :returns: Pandas Index object of the selected horizon values. .. py:method:: _reset_cycle() -> None Starting a new cycle. .. py:method:: adjust_timestamps(bids_table: pandas.DataFrame) -> pandas.DataFrame Adjusts the timestamps in the bids table based on the current cycle. :param bids_table: DataFrame containing the bids with timestamps to adjust. :returns: DataFrame containing the bids with adjusted timestamps. .. py:method:: read_multi_index_csv_with_utc_timestamps(bids_csv_path) -> pandas.DataFrame :staticmethod: Reads a multi-index CSV file with UTC timestamps. .. py:class:: SatelliteModel(rolling_horizon_step: int, ceiling_price: float, start_hour: float, num_hours: int, output_path: str | None = None, **kwargs) Bases: :py:obj:`abc.ABC` Base class for satellite models. Create a satellite model that can bid for demand between floor and ceiling price. :param rolling_horizon_step: How many snapshots to advance at every iteration, ie, for how many snapshots bids need to be made. :param ceiling_price: Maximum price to bid at, at which demand will always be satisfied. :param start_hour: The starting time of the simulation. :param num_hours: The length of the simulation time horizon. :param output_path: Path to the directory to store intermediate values such as bids and dispatch. If given, a target directory is created. :param \*\*kwargs: Any other keyword arguments are ignored. .. py:method:: expand_config(configuration: dict) -> dict[str, dict] :staticmethod: Expand a potential meta-configuration to a collection of concrete configurations. Basic case: no config 'expansion' is supported, so the configuration is returned in a list for datastructure consistency. :param configuration: A configuration dictionary. :returns: The configuration wrapped in a dictionary with empty string as a key. :rtype: configurations .. py:method:: from_file(config_file: pathlib.Path, **common_settings: dict) -> T :classmethod: Initialize a strategy from a yaml file. :param config_file: Path to configuration file. :param common_settings: A dictionary with additional keyword arguments. Arguments specified in config_file take precedence over arguments specified in common settings. Any values given under keys ending in `_path` will be treated as relative to the given configuration file: they will be explicitly replaced with `/`. As a result, absolute paths are not supported when initializing from a configuration file. If the config file or the common settings include a key `results_folder`, a setting key `output_path` is created as concatenation of the value of `results_folder` and the name of the config file. The `results_folder` key is deleted from the settings. .. py:attribute:: rolling_horizon_step .. py:attribute:: ceiling_price .. py:attribute:: start_hour .. py:attribute:: num_hours .. py:attribute:: output_path :value: None .. py:method:: meet_demand(market_price: numpy.ndarray | None, demand_met: numpy.ndarray | None) -> None :abstractmethod: Update internal state according to the amount of demand met and record market price. .. py:method:: determine_bids() -> pandas.DataFrame :abstractmethod: Determine the next set of bids based on the current internal state. .. py:class:: SimpleMultiHourBiddingStrategy(demands_path: str, forecasts_path: str, floor_price: int = 0, bid_margin: float = 0.05, horizon_size: int = 48, **kwargs) Bases: :py:obj:`annular.satellite_model.satellite_model.SatelliteModel` Simple multi-hour bidding strategy to bid for the lowest expected price at the cheapest expected hour. Demand is specified at its deadline. Any specified flexibility means it can be satisfied in any of the `n` earlier timesteps. :param demands_path: Path to csv file with demand values per timestamp, with different flexibility as separate columns named 'flex+N'. :type demands_path: str :param forecasts_path: Path to csv file with electricity price forecast information. If multiple forecasts columns are given, each is used in parallel to determine the bid curves. If only a single forecast is given, it will be adjusted using the `bid_curve_resolution` parameter to generate multiple forecasts in place. :type forecasts_path: str :param floor_price: Minimum price to bid at, defaults to 0. :type floor_price: int :param bid_margin: [0, ...) Amount of margin as a fraction to bid over the lowest found price within the look-ahead window. Example: 0.05 is interpreted as bidding 5% on top of the lowest found price. :type bid_margin: float :param horizon_size: full length of the horizon to use for an optimization iteration, i.e., bidding window + look ahead period, in number of snapshots. :type horizon_size: int :param kwargs: Any other keyword arguments are passed to the initialization of the base class. .. py:attribute:: _last_bids :type: list[Bid] :value: [] .. py:attribute:: cur_timestamp_idx :value: 0 .. py:attribute:: floor_price :value: 0 .. py:attribute:: bid_margin :value: 0.05 .. py:attribute:: horizon_size :value: 48 .. py:attribute:: demands :value: None .. py:attribute:: forecasts :value: None .. py:method:: determine_bids() -> pandas.DataFrame Determine when in the next bidding window we bid, and at what price. A bid is roughly determined as follows for each listed demand quantity: - Find the time of the cheapest expected price in the bidding window, i.e., the period for which bids have to be submitted. - If the flexibility extends _beyond_ the current bidding window, find the cheapest expected price within the available flexibility, and place a bid at that price + margin. - If there is no flexibility beyond the current bidding window, bid at the ceiling price instead. :returns: A collection of bids covering the next bid window. .. py:method:: meet_demand(market_price: numpy.ndarray | None, demand_met: numpy.ndarray | None) -> None Update the internal state to record the amount of demand that was met. Ensures all demand met is removed from the 'demand yet to be satisfied', and advances the internal 'clock' by one rolling horizon step. :param market_price: Price of electricity as provided per timestep. :param demand_met: Amount of demand that was met at the market price per timestep. :raises AssertionError: if any of the provided demand is under- or over-used. :raises ValueError: if any mandatory demand has not been satisfied by the given demand. .. py:method:: _get_horizon(length: int = None) -> pandas.Index Select the relevant horizon index values at the current timestamp index. :param length: length of the desired horizon in number of timestamps. Uses `self.horizon_size` when `None` is given. :returns: Pandas Index object of the selected horizon values. .. py:method:: _make_bid(time_idx: int, flexibility: int, prices_forecast: pandas.DataFrame) -> tuple[pandas.Timestamp, float] Give bid time and price for demand at given time and flexibility. :param time_idx: integer index of the demand that could be fulfilled within this bidding window :param flexibility: how many timesteps early this demand could be satisfied :param prices_forecast: forecast of electricity prices for the whole horizon in which the demand might be satisfied. :returns: Expected best timestamp for the bid (always within current bidding window) and the price for the bid. :rtype: tuple .. py:data:: strategies