diff --git a/assume/common/outputs.py b/assume/common/outputs.py index b79abd54..ce8d8603 100644 --- a/assume/common/outputs.py +++ b/assume/common/outputs.py @@ -568,9 +568,9 @@ def get_sum_reward(self): query = text( f"select unit, SUM(reward) FROM rl_params where simulation='{self.simulation_id}' GROUP BY unit" ) - - with self.db.begin() as db: - rewards_by_unit = db.execute(query).fetchall() + if self.db is not None: + with self.db.begin() as db: + rewards_by_unit = db.execute(query).fetchall() # convert into a numpy array rewards_by_unit = [r[1] for r in rewards_by_unit] diff --git a/docs/source/learning.rst b/docs/source/learning.rst index 315714a9..38070aec 100644 --- a/docs/source/learning.rst +++ b/docs/source/learning.rst @@ -9,7 +9,7 @@ Reinforcement Learning Overview One unique characteristic of ASSUME is the usage of Reinforcement Learning (RL) for the bidding of the agents. To enable this the architecture of the simulation is designed in a way to accommodate the learning process. In this part of the documentation, we give a short introduction to reinforcement learning in general and then pinpoint you to the -relevant parts of the code. the descriptions are mostly based on the following paper +relevant parts of the code. The descriptions are mostly based on the following paper Harder, Nick & Qussous, Ramiz & Weidlich, Anke. (2023). Fit for purpose: Modeling wholesale electricity markets realistically with multi-agent deep reinforcement learning. Energy and AI. 14. 100295. `10.1016/j.egyai.2023.100295 `. If you want a hands-on introduction check out the prepared tutorial in Colab: https://colab.research.google.com/github/assume-framework/assume @@ -18,7 +18,7 @@ If you want a hands-on introduction check out the prepared tutorial in Colab: ht The Basics of Reinforcement Learning ===================================== -In general RL and deep reinforcement learning (DRL), in particular, open new prospects for agent-based electricity market modeling. +In general, RL and deep reinforcement learning (DRL) in particular, open new prospects for agent-based electricity market modeling. Such algorithms offer the potential for agents to learn bidding strategies in the interplay between market participants. In contrast to traditional rule-based approaches, DRL allows for a faster adaptation of the bidding strategies to a changing market environment, which is impossible with fixed strategies that a market modeller explicitly formulates. Hence, DRL algorithms offer the @@ -105,8 +105,8 @@ Similar to TD3, the smaller value of the two critics and target action noise :ma y_i,k = r_i,k + γ * min_j=1,2 Q_i,θ′_j(S′_k, a_1,k, ..., a_N,k, π′(o_i,k)) -where r_i,k is the reward obtained by agent i at time step k, γ is the discount factor, S′_k is the next state of the -environment, and π′(o_i,k) is the target policy of agent i. +where :math:`r_i,k` is the reward obtained by agent :math:`i` at time step :math:`k`, :math:`\gamma` is the discount factor, :math:`S'_k` is the next state of the +environment, and :math:`\pi'(o_i,k)` is the target policy of agent :math:`i`. The critics are trained using the mean squared Bellman error (MSBE) loss: @@ -120,8 +120,8 @@ The actor policy of each agent is updated using the deterministic policy gradien ∇_a Q_i,θ_j(S_k, a_1,k, ..., a_N,k, π(o_i,k))|a_i,k=π(o_i,k) * ∇_θ π(o_i,k) -The actor is updated similarly using only one critic network Q_{θ1}. These changes to the original DDPG algorithm allow increased stability and convergence of the TD3 algorithm. This is especially relevant when approaching a multi-agent RL setup, as discussed in the foregoing section. -Please note that the actor and critics are updated by sampling experience from the buffer where all interactions of the agents are stored, namley the observations, actions and rewards. There are more complex buffers possible, like those that use importance sampling, but the default buffer is a simple replay buffer. You can find a documentation of the latter in :doc:`buffers` +The actor is updated similarly using only one critic network :math:`Q_{θ1}`. These changes to the original DDPG algorithm allow increased stability and convergence of the TD3 algorithm. This is especially relevant when approaching a multi-agent RL setup, as discussed in the foregoing section. +Please note that the actor and critics are updated by sampling experience from the buffer where all intercations of the agents are stored, namely the observations, actions and rewards. There are more complex buffers possible, like those that use importance sampling, but the default buffer is a simple replay buffer. You can find a documentation of the latter in :doc:`buffers` The Learning Implementation in ASSUME @@ -136,15 +136,15 @@ The Actor We will explain the way learning works in ASSUME starting from the interface to the simulation, namely the bidding strategy of the power plants. The bidding strategy, per definition in ASSUME, defines the way we formulate bids based on the technical restrictions of the unit. In a learning setting, this is done by the actor network. Which maps the observation to an action. The observation thereby is managed and collected by the units operator as -summarized in the following picture. As you can see in the current working version the observation space contains of a residula load forecast for the next 24 h and aprice forecast for 24 h as well as the -the current capacity of the powerplant and its marginal costs. +summarized in the following picture. As you can see in the current working version, the observation space contains a residual load forecast for the next 24 hours and a price +forecast for 24 hours, as well as the current capacity of the power plant and its marginal costs. .. image:: img/ActorTask.jpg :align: center :width: 500px -The action space is a continuous space, which means that the actor can choose any price between 0 and the maximum bid price defined in the code. It gives two prices for two different party of its capacity. -One, namley :math:`p_inlfex` for the minimum capacity of the power plant and one for the rest ( :math:`p_flex`). The action space is defined in the config file and can be adjusted to your needs. +The action space is a continuous space, which means that the actor can choose any price between 0 and the maximum bid price defined in the code. It gives two prices for two different parts of its capacity. +One, namley :math:`p_{inflex}` for the minimum capacity of the power plant and one for the rest ( :math:`p_{flex}`). The action space is defined in the config file and can be adjusted to your needs. After the bids are formulated in the bidding strategy they are sent to the market via the units operator. .. image:: img/ActorOutput.jpg @@ -171,7 +171,7 @@ You can read more about the different algorithms and the learning role in :doc:` The Learning Results in ASSUME ===================================== -Similarly, to the other results, the learning progress is tracked in the database, either with postgresql or timescale. The latter, enables the usage of the +Similarly to the other results, the learning progress is tracked in the database, either with postgresql or timescale. The latter enables the usage of the predefined dashboards to track the leanring process in the "Assume:Training Process" dashboard. The following pictures show the learning process of a simple reinforcement learning setting. A more detailed description is given in the dashboard itself. diff --git a/docs/source/learning_algorithm.rst b/docs/source/learning_algorithm.rst index 4b504329..210745d1 100644 --- a/docs/source/learning_algorithm.rst +++ b/docs/source/learning_algorithm.rst @@ -6,22 +6,23 @@ Reinforcement Learning Algorithms ################################## -In the chapter :doc:`learning` we got an general overview about how RL is implements for a multi-agent setting in Assume. In the case one wants to apply these RL algorithms -to a new problem, one does not necessarily need to understand how the RL algorithms are are working in detail. The only thing needed is the adaptation of the bidding strategies, -which is covered in the tutorial. Yet, for the interested reader we will give a short overview about the RL algorithms used in Assume. We start with the learning role which is the core of the leanring implementation. - +In the chapter :doc:`learning` we got a general overview of how RL is implemented for a multi-agent setting in Assume. +If you want to apply these RL algorithms to a new problem, you do not necessarily need to understand how the RL algorithms work in detail. +All that is needed is to adapt the bidding strategies, which is covered in the tutorial. +However, for the interested reader, we will give a brief overview of the RL algorithms used in Assume. +We start with the learning role, which is the core of the learning implementation. The Learning Role ================= -The learning role orchestrates the learning process. It initializes the training process and manages the experiences gained in a buffer. -Furthermore, it schedules the policy updates and, hence, brings the critic and the actor together during the learning process. -Particularly this means, that at the beginning of the simulation, we schedule recurrent policy updates, where the output of the critic is used as a loss -of the actor, which then updates its weights using backward propagation. +The learning role orchestrates the learning process. It initializes the training process and manages the experience gained in a buffer. +It also schedules policy updates, thus bringing critic and actor together during the learning process. +Specifically, this means that at the beginning of the simulation we schedule recurrent policy updates, where the output of the critic +is used as a loss for the actor, which then updates its weights using backward propagation. With the learning role, we can also choose which RL algorithm should be used. The algorithm and the buffer have base classes and can be customized if needed. But without touching the code there are easy adjustments to the algorithms that can and eventually need to be done in the config file. -The following table shows the options that can be adjusted and gives a short explanation. For more advanced users is the functionality of the algorithm also documented below. +The following table shows the options that can be adjusted and gives a short explanation. For more advanced users, the functionality of the algorithm is also documented below. @@ -43,7 +44,7 @@ The following table shows the options that can be adjusted and gives a short exp batch_size The batch size of experience considered from the buffer for an update. gamma The discount factor, with which future expected rewards are considered in the decision-making. device The device to use. - noise_sigma The standard deviation of the distribution used to draw the noise, which is added to the actions and forces exploration. noise_scale + noise_sigma The standard deviation of the distribution used to draw the noise, which is added to the actions and forces exploration. noise_dt Determines how quickly the noise weakens over time. noise_scale The scale of the noise, which is multiplied by the noise drawn from the distribution. early_stopping_steps The number of steps considered for early stopping. If the moving average reward does not improve over this number of steps, the learning is stopped. @@ -58,7 +59,7 @@ TD3 (Twin Delayed DDPG) ----------------------- TD3 is a direct successor of DDPG and improves it using three major tricks: clipped double Q-Learning, delayed policy update and target policy smoothing. -We recommend reading OpenAI Spinning guide or the original paper to understand the algorithm in detail. +We recommend reading the OpenAI Spinning guide or the original paper to understand the algorithm in detail. Original paper: https://arxiv.org/pdf/1802.09477.pdf @@ -66,7 +67,7 @@ OpenAI Spinning Guide for TD3: https://spinningup.openai.com/en/latest/algorithm Original Implementation: https://github.com/sfujim/TD3 -In general the TD3 works in the following way. It maintains a pair of critics and a single actor. For each step so after every time interval in our simulation, we update both critics towards the minimum +In general, the TD3 works in the following way. It maintains a pair of critics and a single actor. For each step (after every time interval in our simulation), we update both critics towards the minimum target value of actions selected by the current target policy: @@ -77,7 +78,7 @@ target value of actions selected by the current target policy: Every :math:`d` iterations, which is implemented with the train_freq, the policy is updated with respect to :math:`Q_{\theta_1}` following the deterministic policy gradient algorithm (Silver et al., 2014). -TD3 is summarized in the following picture from the others of the original paper (Fujimoto, Hoof and Meger, 2018). +TD3 is summarized in the following picture from the authors of the original paper (Fujimoto, Hoof and Meger, 2018). .. image:: img/TD3_algorithm.jpeg @@ -88,10 +89,10 @@ TD3 is summarized in the following picture from the others of the original paper The steps in the algorithm are translated to implementations in ASSUME in the following way. The initialization of the actors and critics is done by the :func:`assume.reinforcement_learning.algorithms.matd3.TD3.initialize_policy` function, which is called in the learning role. The replay buffer needs to be stable across different episodes, which corresponds to runs of the entire simulation, hence it needs to be detached from the -entities of the simulation that are killed after each episode, like the elarning role. Therefore, it is initialized independently and given to the learning role +entities of the simulation that are killed after each episode, like the learning role. Therefore, it is initialized independently and given to the learning role at the beginning of each episode. For more information regarding the buffer see :doc:`buffers`. -The core of the algorithm is embodied by the :func:`assume.reinforcement_learning.algorithms.matd3.TD3.update_policy` in the learning algorithms. Here the critic and the actor are updated according to the algorithm. +The core of the algorithm is embodied by the :func:`assume.reinforcement_learning.algorithms.matd3.TD3.update_policy` in the learning algorithms. Here, the critic and the actor are updated according to the algorithm. The network architecture for the actor in the RL algorithm can be customized by specifying the network architecture used. In stablebaselines3 they are also referred to as "policies". The architecture is defined as a list of names that represent the layers of the neural network. @@ -99,6 +100,6 @@ For example, to implement a multi-layer perceptron (MLP) architecture for the ac This will create a neural network with multiple fully connected layers. Other available options for the "policy" include Long-Short-Term Memory (LSTMs). The architecture for the observation handling is implemented from [2]. -Note that the specific implementation of each network architecture is defined in the corresponding classes in the codebase. You can refer to the implementation of each architecture for more details on how they are implemented. +Note, that the specific implementation of each network architecture is defined in the corresponding classes in the codebase. You can refer to the implementation of each architecture for more details on how they are implemented. [2] Y. Ye, D. Qiu, J. Li and G. Strbac, "Multi-Period and Multi-Spatial Equilibrium Analysis in Imperfect Electricity Markets: A Novel Multi-Agent Deep Reinforcement Learning Approach," in IEEE Access, vol. 7, pp. 130515-130529, 2019, doi: 10.1109/ACCESS.2019.2940005. diff --git a/docs/source/release_notes.rst b/docs/source/release_notes.rst index 7f0b5602..cf14eeba 100644 --- a/docs/source/release_notes.rst +++ b/docs/source/release_notes.rst @@ -13,7 +13,8 @@ Upcoming Release The features in this section are not released yet, but will be part of the next release! To use the features already you have to install the main branch, e.g. ``pip install git+https://github.com/assume-framework/assume`` - **Bugfixes:** +**Bugfixes:** + - **Tutorials**: General fixes of the tutorials, to align with updated functionalitites of Assume - **Tutorial 07**: Aligned Amiris loader with changes in format in Amiris compare (https://gitlab.com/fame-framework/fame-io/-/issues/203 and https://gitlab.com/fame-framework/fame-io/-/issues/208) v0.4.3 - (11th November 2024) diff --git a/examples/notebooks/01_minimal_manual_example.ipynb b/examples/notebooks/01_minimal_manual_example.ipynb index fd46b69a..c6fa06e5 100644 --- a/examples/notebooks/01_minimal_manual_example.ipynb +++ b/examples/notebooks/01_minimal_manual_example.ipynb @@ -5,7 +5,7 @@ "metadata": {}, "source": [ "# 1. Minimal manual tutorial\n", - "In this notebook, we will walk through a minimal example of how to use the ASSUME framework. We will first initialize the world instance, next we will create a single market and its operator, afterwards we will add a generation and a demand agents, and finally start the simulation." + "In this notebook, we will walk through a minimal example of how to use the ASSUME framework. We will first initialize the world instance, next we will create a single market and its operator, afterwards we will add a generation and a demand agent, and finally start the simulation." ] }, { @@ -27,14 +27,16 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "vscode": { - "languageId": "shellscript" - } - }, + "metadata": {}, "outputs": [], "source": [ - "!pip install assume-framework" + "import importlib.util\n", + "\n", + "# Check whether notebook is run in google colab\n", + "IN_COLAB = importlib.util.find_spec(\"google.colab\") is not None\n", + "\n", + "if IN_COLAB:\n", + " !pip install assume-framework" ] }, { @@ -227,20 +229,20 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "This code segment sets up a demand unit managed by the \"my_demand\" unit operator, equipped with a naive demand forecast, and establishes its operational parameters within the electricity market simulation framework.\n", + "This code segment sets up a demand unit managed by the \"demand_operator\" unit operator, equipped with a naive demand forecast, and establishes its operational parameters within the electricity market simulation framework.\n", "\n", "In this code:\n", - "- `world.add_unit_operator(\"demand_operator\")` adds a unit operator with the identifier \"my_demand\" to the simulation world. A unit operator manages a group of similar units within the simulation.\n", + "- `world.add_unit_operator(\"demand_operator\")` adds a unit operator with the identifier \"demand_operator\" to the simulation world. A unit operator manages a group of similar units within the simulation.\n", "\n", "- `demand_forecast = NaiveForecast(index, demand=100)` creates a naive demand forecast object named `demand_forecast`. This forecast is initialized with an index and a constant demand value of 100.\n", "\n", "- `world.add_unit(...)` adds a demand unit to the simulation world with the following specifications:\n", "\n", - " - `id=\"demand_unit\"` assigns the identifier \"demand1\" to the demand unit.\n", + " - `id=\"demand_unit\"` assigns the identifier \"demand_unit\" to the demand unit.\n", "\n", " - `unit_type=\"demand\"` specifies that this unit is of type \"demand\", indicating that it represents a consumer of electricity.\n", "\n", - " - `unit_operator_id=\"demand_operator\"` associates the unit with the unit operator identified as \"my_demand\".\n", + " - `unit_operator_id=\"demand_operator\"` associates the unit with the unit operator identified as \"demand_operator\".\n", "\n", " - `unit_params` provides various parameters for the demand unit, including minimum and maximum power, bidding strategies, and technology type.\n", "\n", @@ -319,7 +321,7 @@ "source": [ "## Conclusion\n", "\n", - "In this notebook, we have demonstrated the basic steps involved in setting up and running a simulation using the ASSUME framework for simulating electricity markets. This example is intended to provide a detailed overview of internal workings of the framework and its components. This approach can be used for small simulations with a few agents and markets. In the next notebook we will explore how this process is automated for large scale simulation using input files." + "In this notebook, we have demonstrated the basic steps involved in setting up and running a simulation using the ASSUME framework for simulating electricity markets. This example is intended to provide a detailed overview of internal workings of the framework and its components. This approach can be used for small simulations with a few agents and markets. In the next notebook we will explore how this process is automated for large scale simulations using input files." ] }, { @@ -331,24 +333,9 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "INFO:assume.world:connected to db\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "world_script_simulation 2023-03-31 00:00:00: : 7689601.0it [00:02, 2611673.02it/s] \n" - ] - } - ], + "outputs": [], "source": [ "import logging\n", "from datetime import datetime, timedelta\n", @@ -436,6 +423,13 @@ "\n", "world.run()" ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { @@ -445,7 +439,7 @@ "toc_visible": true }, "kernelspec": { - "display_name": "assume-framework", + "display_name": "assume", "language": "python", "name": "python3" }, @@ -459,7 +453,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.7" + "version": "3.12.7" }, "nbsphinx": { "execute": "never" diff --git a/examples/notebooks/02_automated_run_example.ipynb b/examples/notebooks/02_automated_run_example.ipynb index 0b3e6b1d..69e9a210 100644 --- a/examples/notebooks/02_automated_run_example.ipynb +++ b/examples/notebooks/02_automated_run_example.ipynb @@ -14,17 +14,17 @@ "\n", "## Tutorial outline:\n", "\n", - "- Introduction\n", - "- Setting up the environment\n", - "- Creating input files\n", - " - Power plant units\n", - " - Fuel prices\n", - " - Demand units\n", - " - Demand time series\n", - "- Creating a configuration file\n", - "- Running the simulation\n", - "- Adjusting market configuration\n", - "- Conclusion" + "- Introduction \n", + "- [Setting up the environment](#setting-up-the-environment)\n", + "- [Creating input files](#creating-input-files)\n", + " - [Power plant units](#power-plant-units)\n", + " - [Fuel prices](#fuel-prices)\n", + " - [Demand units](#demand-units)\n", + " - [Demand time series](#demand-time-series)\n", + "- [Creating a configuration file](#creating-a-configuration-file)\n", + "- [Running the simulation](#running-the-simulation)\n", + "- [Adjusting market configuration](#adjusting-market-configuration)\n", + "- [Conclusion](#conclusion)" ] }, { @@ -46,14 +46,16 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "vscode": { - "languageId": "shellscript" - } - }, + "metadata": {}, "outputs": [], "source": [ - "!pip install assume-framework" + "import importlib.util\n", + "\n", + "# Check whether notebook is run in google colab\n", + "IN_COLAB = importlib.util.find_spec(\"google.colab\") is not None\n", + "\n", + "if IN_COLAB:\n", + " !pip install assume-framework" ] }, { @@ -222,6 +224,25 @@ "demand_units_df.to_csv(f\"{input_path}/demand_units.csv\", index=False)" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Here's what each attribute in our dataset represents:\n", + "\n", + "- `name`: This is the identifier for the demand unit. In our case, we have a single demand unit named `demand_EOM`, which could represent the total electricity demand of an entire market or a specific region within the market.\n", + "\n", + "- `technology`: Indicates the type of demand. Here, `inflex_demand` is used to denote inelastic demand, meaning that the demand does not change in response to price fluctuations within the short term. This is a typical assumption for electricity markets within a short time horizon.\n", + "\n", + "- `bidding_EOM`: Specifies the bidding strategy for the demand unit. Even though demand is typically price-inelastic in the short term, it still needs to be represented in the market. The `naive` strategy here bids the demand value into the market at a price of 3000 EUR/MWh.\n", + "\n", + "- `max_power`: The maximum power that the demand unit can request. In this example, we've set it to 1,000,000 MW, which is a placeholder. This value can be used for more sophisticated bidding strategies.\n", + "\n", + "- `min_power`: The minimum power level that the demand unit can request. In this case it also serves as a placeholder for more sophisticated bidding strategies.\n", + "\n", + "- `unit_operator`: The entity responsible for the demand unit. In this example, `eom_de` could represent an electricity market operator in Germany." + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -230,7 +251,7 @@ "\n", "Lastly, we'll create a time series for the demand. \n", "\n", - "You might notice, that the column name we use if demand_EOM, which is similar to the name of our demand unit. The framework is designed in such way, that multiple demand units can be defined in the same file. The column name is used to match the demand time series with the correct demand unit. Afterwards, each demand unit following a naive bidding strategy will bid the respecrive demand value into the market.\n", + "You might notice, that the column name we use is demand_EOM, which is similar to the name of our demand unit. The framework is designed in such way, that multiple demand units can be defined in the same file. The column name is used to match the demand time series with the correct demand unit. Afterwards, each demand unit following a naive bidding strategy will bid the respective demand value into the market.\n", "\n", "Also, the length of the demand time series must be at least as long as the simulation time horizon. If the time series is longer than the simulation time horizon, the framework will automatically truncate it to the correct length. If the resolution of the time series is higher than the simulation time step, the framework will automatically resample the time series to match the simulation time step. If it is shorter, an error will be raised." ] @@ -252,32 +273,13 @@ "demand_profile.to_csv(f\"{input_path}/demand_df.csv\", index=False)" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Here's what each attribute in our dataset represents:\n", - "\n", - "- `name`: This is the identifier for the demand unit. In our case, we have a single demand unit named `demand_EOM`, which could represent the total electricity demand of an entire market or a specific region within the market.\n", - "\n", - "- `technology`: Indicates the type of demand. Here, `inflex_demand` is used to denote inelastic demand, meaning that the demand does not change in response to price fluctuations within the short term. This is a typical assumption for electricity markets within a short time horizon.\n", - "\n", - "- `bidding_EOM`: Specifies the bidding strategy for the demand unit. Even though demand is typically price-inelastic in the short term, it still needs to be represented in the market. The `naive` strategy here bids the demand value into the market at price of 3000 EUR/MWh.\n", - "\n", - "- `max_power`: The maximum power that the demand unit can request. In this example, we've set it to 1,000,000 MW, which is a placeholder. This value can be used for more sophisticated bidding strategies.\n", - "\n", - "- `min_power`: The minimum power level that the demand unit can request. In this case also serves as a placeholder for more sophisticated bidding strategies.\n", - "\n", - "- `unit_operator`: The entity responsible for the demand unit. In this example, `eom_de` could represent an electricity market operator in Germany." - ] - }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Creating a Configuration File\n", "\n", - "With our input files ready, we'll now create a configuration file that ASSUME will use to load the simulation. The confi file allows easy customization of the simulation parameters, such as the simulation time horizon, the time step, and the market configuration. The configuration file is written in YAML format, which is a human-readable markup language that is commonly used for configuration files." + "With our input files ready, we'll now create a configuration file that ASSUME will use to load the simulation. The config file allows easy customization of the simulation parameters, such as the simulation time horizon, the time step, and the market configuration. The configuration file is written in YAML format, which is a human-readable markup language that is commonly used for configuration files." ] }, { @@ -490,8 +492,13 @@ "\n", "Congratulations! You've learned how to automate the setup and execution of simulations in ASSUME using configuration files and input files. This approach is particularly useful for handling large and complex simulations. \n", "\n", - "You are welcome to experiment with different configurations and variying input data. For example, you can try changing the bidding strategy for the power plant units to a more sophisticated strategy, such as a `flexable_eom`" + "You are welcome to experiment with different configurations and varying input data. For example, you can try changing the bidding strategy for the power plant units to a more sophisticated strategy, such as a `flexable_eom`" ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [] } ], "metadata": { @@ -501,7 +508,7 @@ "toc_visible": true }, "kernelspec": { - "display_name": "assume-framework", + "display_name": "assume", "language": "python", "name": "python3" }, @@ -515,7 +522,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.9" + "version": "3.12.7" }, "nbsphinx": { "execute": "never" diff --git a/examples/notebooks/03_custom_unit_example.ipynb b/examples/notebooks/03_custom_unit_example.ipynb index 1656b3c9..0820c9a6 100644 --- a/examples/notebooks/03_custom_unit_example.ipynb +++ b/examples/notebooks/03_custom_unit_example.ipynb @@ -10,11 +10,11 @@ "\n", "**We will cover the following topics:**\n", "\n", - "1. Essential concepts and terminology in electricity market modeling\n", - "2. Setting up the ASSUME framework\n", - "3. Developing a new Demand Side Unit\n", - "4. Formulating a rule-based bidding strategy\n", - "5. Integrating the new unit and strategy into the ASSUME simulation" + "1. [Essential concepts and terminology in electricity market modeling](#1-introduction-to-unit-agents-and-bidding-strategy)\n", + "2. [Setting up the ASSUME framework](#2-setting-up-assume)\n", + "3. [Developing a new Demand Side Unit](#3-developing-a-new-demand-side-unit)\n", + "4. [Formulating a rule-based bidding strategy](#4-rule-based-bidding-strategy)\n", + "5. [Integrating the new unit and strategy into the ASSUME simulation](#5-integrating-the-new-unit-and-strategy-into-assume)" ] }, { @@ -51,21 +51,14 @@ { "cell_type": "code", "execution_count": null, - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAABkUAAAPtCAYAAAAwn2h0AAAAAXNSR0IArs4c6QAAAIRlWElmTU0AKgAAAAgABQESAAMAAAABAAEAAAEaAAUAAAABAAAASgEbAAUAAAABAAAAUgEoAAMAAAABAAIAAIdpAAQAAAABAAAAWgAAAAAAAADIAAAAAQAAAMgAAAABAAOgAQADAAAAAQABAACgAgAEAAAAAQAABkWgAwAEAAAAAQAAA+0AAAAAkxTMAQAAAAlwSFlzAAAewgAAHsIBbtB1PgAAQABJREFUeAHs3QW8bVW1+PHle/pCRFFSQLlIipQCIn3p7g4BEVREkAcokoKCgoS0wqW7u7tBuqQbESQMbN97f//nO3Wet+66a++99ql7YozPZ5+9z4oZvxlr7zHmGLMoQoJAEAgCQSAIBIEgEASCQBAIAkEgCASBIBAEgkAQCAJBIAgEgSAQBIJAEAgCQSAIBIEgEASCQBAIAkEgCASBIBAEgkAQCAJBIAgEgSAQBEYHgfepxt///vf9Rkd1ohZBIAgEgSAQBIJAEAgCQSAIBIEgEASCQBAIAkEgCASBIBAEgkAQqCeQjSJ/rz8dR4NAEAgCQSAIBIEgEASCQBAIAkEgCASBIBAEgkAQCAJBIAgEgSAwOgj8y+ioRtQiCASBIBAEgkAQCAJBIAgEgSAQBIJAEAgCQSAIBIEgEASCQBAIAu0JhFGkPZ84GwSCQBAIAkEgCASBIBAEgkAQCAJBIAgEgSAQBIJAEAgCQSAIjBICYRQZJQ0Z1QgCQSAIBIEgEASCQBAIAkEgCASBIBAEgkAQCAJBIAgEgSAQBNoTCKNIez5xNggEgSAQBIJAEAgCQSAIBIEgEASCQBAIAkEgCASBIBAEgkAQGCUEwigyShoyqhEEgkAQCAJBIAgEgSAQBIJAEAgCQSAIBIEgEASCQBAIAkEgCLQnEEaR9nzibBAIAkEgCASBIBAEgkAQCAJBIAgEgSAQBIJAEAgCQSAIBIEgMEoIhFFklDRkVCMIBIEgEASCQBAIAkEgCASBIBAEgkAQCAJBIAgEgSAQBIJAEGhPIIwi7fnE2SAQBIJAEAgCQSAIBIEgEASCQBAIAkEgCASBIBAEgkAQCAJBYJQQCKPIKGnIqEYQCAJBIAgEgSAQBIJAEAgCQSAIBIEgEASCQBAIAkEgCASBINCeQBhF2vOJs0EgCASBIBAEgkAQCAJBIAgEgSAQBIJAEAgCQSAIBIEgEASCwCghEEaRUdKQUY0gEASCQBAIAkEgCASBIBAEgkAQCAJBIAgEgSAQBIJAEAgCQaA9gTCKtOcTZ4NAEAgCQSAIBIEgEASCQBAIAkEgCASBIBAEgkAQCAJBIAgEgVFCIIwio6QhoxpBIAgEgSAQBIJAEAgCQSAIBIEgEASCQBAIAkEgCASBIBAEgkB7AmEUac8nzgaBIBAEgkAQCAJBIAgEgSAQBIJAEAgCQSAIBIEgEASCQBAIAqOEQBhFRklDRjWCQBAIAkEgCASBIBAEgkAQCAJBIAgEgSAQBIJAEAgCQSAIBIH2BMIo0p5PnA0CQSAIBIEgEASCwJAS+Pvf/z6k+UVmQSAIBIEmBGJuakIprgkCQSAIBIEgEASCQBAYCQTep5A9X3Dj1/dIaK0oYxAIAkEgCASBUUbAV5D3vS99HZmoZq2OT3TRKP7npJNOKq666irf0Yr/+Z//KU455ZRimmmmGcU1jqr1h8BYHy/9YfeXv/ylOOuss4qLLrqoeP/731/MPvvsxa677lrMNNNM/Ul21N571FFHFbfeemuam/S7U089tZhqqqlGbX0Hu2LvvPNO8eqrrxbe//rXv6Y++OEPf7iYbrrpUl+sez4Odpki/SAQBIJAEAgCQSAIjAUC7x8LlYw6BoEgEASCQBAIAsOTAIXPhAkTinPPPbf43//93+Jf//Vfi1VXXbXYbbfdui4wpdLuu+9ePPfcc8W//Ms/nGEvvfTSEamw+81vfpMUZZSOf/vb35JhpGsgccOYIWAcnXjiien1H//xHy3rbVxMMcUUBaXrbLPNVsw///zFyiuvXEw55ZQt7xntJ/7f//t/hfH28ssvJ4U0Pv/93/892qvd5/q9++67vXOT+cm8HdIdgeeff764/PLLi9tuuy31PX0QSy9iPHt94AMfKMaNG1essMIKxVprrTWmx2l3hOPqIBAEgkAQCAJBIAh0JhBGkc6M4oogEASCQBAIAkFgEAlYHfuHP/yh1yhi5XZfhGLpT3/6U0orG0Wykqkv6U3Oe7JSTBl8DgkCnQgwnv3+97/vaEB77733ijfeeKN45plnkjfSIYccUqy55prJENnOoNIp/5F8Po+38vtIrk+7st99993FI488kuaVqaeeulhmmWWK6aefvt0tE53LjBwcqfPrRBUawn9ef/314sgjj0zGEB6AneZ2z8LHH388vY444ohijTXWKHbcccfiYx/72BCWOrIKAkEgCASBIBAEgsDoJBBGkdHZrlGrIBAEgkAQCAJBIAgEgTFMQCgonldVoci2ut+LUta70FH33HNPITQSD5KQ0UlA2991113FySefnLxi5phjjmLuuefuyigijfJrdJIa2FoZY7fccktx0EEHFW+99Vbxb//2bykDxnvjlEeIz4ySPjPuM3K6j9eSd8Lz8eabby6OOeaYYr755kvH4k8QCAJBIAgEgSAQBIJA3wiEUaRv3OKuIBAEgkAQCAJBIAgEgSAwLAlQpK677rrFEkssMVH5eFPxJnn66aeLBx54oHjttdeSZwnjCe+Rb33rW8UJJ5wQ+9dMRG10/ZMV8ZTxXp28Faq1t7fRLLPM0msYqTO8Ve8Zy//zCLE/1Pe+973C+MsGESHsFlxwweSps/jii6c9RMqchIPk0XPDDTcUTzzxRPH222+n+xmkfA4JAkEgCASBIBAEgkAQ6B+BMIr0j1/cHQSCQBAIAkEgCASBIBAEhhUBK8vnmmuuYvz48bXlWnvttYs///nPyTPE6nNGFMryX/ziF8Vxxx1X7LvvvrX3xcGxTYBCXvgmr5BmBO68887i+9//fjJoGGNkgQUWKLbddttiscUWa5kI45O9RLxeeeWV4rTTTktePoyaOZ2WN8eJIBAEgkAQCAJBIAgEgY4E/vHNrONlcUEQCAJBIAgEgSAQBIJAEAgCI4VADrnTqrz/+Z//Wey+++4TGU4YRx5++OGkhG11XxwfuwS69SoZu6T+UXNGxh/+8Ie93liOLrXUUimMVjuDSJUbzxyGSuN13nnnTUbM6jXxfxAIAkEgCASBIBAEgkB3BMJTpDtecXUQCAJBIAgEgSAQBIJAEBg1BHbaaae0TwEjihXov/3tb5NhhCI2JAgEgb4TOP7445P3FQOkPUI+97nPFbvssksx7bTT9inR5ZZbrphzzjkLG7CHBIEgEASCQBAIAkEgCPSPQBhF+scv7g4CQSAIBIEgEARGEAHhXyb3aud33303bbb7xz/+sfj3f//3YoYZZuizkqxpfX75y18WYtRTptnM91Of+lTxoQ99qG3Lvffeeyl2vXAtYuFPOeWUKe79Rz7ykbb3NT1JSfjyyy8Xf/jDH1KbTD311MUnP/nJjrc3rXM5IRsX/+pXv0r7afCGUPfpppuu+OhHP1q+rO3npvnijLf6UYZSgMpruMrHP/7xtEfECy+8kNpBv7TCvZNUeWjH119/PbWne/WT2WefvVMy6Z7f/e53KZyXvjnVVFMVM800U8f7ml6g3e3BoO9/8IMfTGkPVB9uWoZW11UZtrqufNw9OBuf+rV9QfRn7TjFFFOUL02fy3mY+8w5xHFGsLp70gX//FO+v/y5fE23n80nr776aqqDPTe0i7E4/fTTd5tUo+tfeuml4je/+U261jxmnskcGiXQh4uMp2uvvTbNAeprHthggw2KT3ziE31I7f9umXnmmf/vnw6fBqq9OmQzyWlzoHGnf9pzRpnVf3I/exXUXMDwm5879nZRvrzXyySViQNBIAgEgSAQBILAqCUQRpFR27RRsSAQBIJAEAgCQaBKgFLmrLPOKu64446kTKS43mOPPYpZZ521emnb/21QLSyK9Chb11hjjWK11VZrq2j72c9+Vlx++eUpNJH9HORNoUkpSQksDaFVCGVWE5H/KaecUjz44IO9lx900EFJSapcF110UXHPPff0GkR4A0h71113LZZddtnee8ofbr311uKmm25KSstcTvdQGlHyUyAtv/zy6VW+r+ln7M4+++ziqaeeSoopRgpCIf6xj30sMdhkk02SwpZB5pvf/GZSmqrPRhttVKy00kpNsypuv/32Qn0oRSn75YXBBz7wgVSXGWecMcXsb5Im1tLThrhg8uUvf7lYaKGFUnnuvvvudI6y13n5aF/MKKzXXHPNtKly48IP4YXjxo0rnn/++dSfMWKkKMubb75ZHHrooamv47DwwgsXW221Vbrk8ccfLy655JKCEhhj/ZpQPhtrdSI9HG0kTVn917/+tTfEEGU1BflnPvOZYsMNN+yzovz6668vvBgQ9J3cHsYbo+D6669fzD///HXFa3mMQvXkk09O/Ul6K664YrHWWmulvtvypn+eoCQ+7LDDkqIYw89+9rPFNtts0+m23vM///nPi6uvvjq1k/bBmUGBYUN/Vi9GBW2zzDLL9Bri5GVfiwsuuCAxYIh0vePY2ABcW1XnHOkussgixRe/+MXeMrjnjDPOKMxlxD3mQYrlJvLMM88U9rB57rnnkoFSu0vDODH+GQ+Vn/GgidECg/POOy/N58rLi2LzzTdPHPTF888/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3fGKq4NAvwgwFPjSSqlLgSxkkQ2uKdsp159++uli9913T8aTL37xi70KXBth20fERu4Uv5T7lF95RbCNs7MiWgEp1y+++OKk0HfNtNNOW2RFvjR++tOfJuXxYYcdlvaXKFeKAYIi3YuymlGA8cQeFFNMMUV6d71VCFYlqw9ltY3fX3jhheLjH/94Mcsss6Q9LCj8GTyEbVprrbUmMYy47+ijj06KcWkrpxejxn333VfccccdaaPzffbZZxIDRrnM1c8MRN///vdTftj5gqC80mXEwV3ZlAv/LNoBZ3u34DbddNMl1owLjCKuLRtoMssJEyak63CaddZZ070MMXvssUcygv3gBz+YxAAlPcaxU045pTdtPLUdwwAj1EILLTQJs1zWdu/SYAgjc845Z3q1u77VOcz0K21T9QDJ97hGv3ZN3Ybwuc+/8847xZ577pkMVdp4pplmShu8O7/hhhum/vjWW2+lPXMY0epEn/jd736XjDPLLLPMRJc899xzyfhjDGk3eTAQaXP96MYbbyy++c1vJmNTeaxMlEj8EwRGOIHsSRUrRUd4Q0bxg0AfCZgDPJc7eV32Mfm4LQgEgWFKIP9WaeVJPUyLHcUKAkFggAjQLZgH4jfAAAGNZMYMgTCKjJmm7n9FX3nllaT89mOrqZiYKZop1KtiU2eK0m5EehTTM8wwwyS3Uaj+6le/Sg+DSU6WDnhg8ILgjTA5hEL2xBNPTOW0el7YIop7fHla3HDDDcnzwor9vKG6a3hOCIF07rnnJk+HAw88MBke1AEXxon8mYcEowADxeqrr57y4IGg7SiNpXHXXXelFf6U8lbTl8UXajxt0s2jgtKasp8RgMK9LBThxx13XArlteuuuxaU1ZT7NivnBcAA8cMf/jAx502S+490bADOC0U5eUesscYa6bN25E1wySWXJB6MQBTqTYVxQr3lhyEFuXylq948dG7t2VxcnXiHZIX/N77xjeTpwMODl8NGG22UvBF4HBDXqVsWxgttOe+88yZONmankFc3Cnz1v+eee1JbHHnkkfm29K4M5513XjKgrLTSSsXGG2/cy/imm25KRi1eOt0o8ClDlPHhhx9O6eoXSy655ET5DvU/uHsxVJkHMNZH9PnXXnsttTeDyrge75vXX3+94JXSyijCYwp/1xrDWXgm8aR69dVXi+WWWy71JZ468nj77beT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AiKQOwEJRXJnpTNFQAREINYEcErHR0EERCBMAghGFURABMIkgFCkXbt2YVZetRaBwAmw2KzxH3gniEn1P/jgA2cma8aMGfb+++8bprPYsMemnQMHDhhz1Q4dOsSktOVTjObNmxsfBREQgfwISCiSHy+dLQIiIAKxJbB7927bu3evtWzZUrtEYttKKpgIFI8Au/J46WzVqlXxMlHKIiACsSTA2N+yZYuxOKrd4rFsIhVKBIpGgMXmHTt2uM1RDRs2LFo+SlgEMhFYvny5vfHGG04YsmfPHsOkE5oLCEYQ3A8dOtTwJyIzWZko1ixu165dhiN73gFkRqtmDHVVmAQkFAmz3VVrERCBMiTAYsj69eutf//+EoqUYfuqSiKQjQDmCdihJ6FINlKKF4HyI3Do0CFbsWKFG/8SipRf+6pGIpCJAJuiWJDu2rWrTOhlAqW4ohBgQf7FF1+0+fPnuw16COlZmOebvnnyySfb+eef77SZENwrFJ4AZsk2b97stEUkFCk8X6VYvgQkFCnftlXNREAEAiPAYiiLIkxAFURABMIj4O8B4dVcNRYBEYAAcwDuAwoiIAJhEWDuz258vQOE1e71XVueNxMnTrRJkyY5LQX+RzuED9pL7du3twsuuMB69uzptBjru7zlnD/Pfz4KIiAC+RGQUCQ/XjpbBERABERABERABERABERABERABERABERABIIlgEbC9OnTzZvKQhiCYA4TbsOGDbPBgwdLc6mOegfsFURABPIncFT+l+gKERABERABERABERABERABERABERABERABERCBUAmMHTu2UkMJh+onnHCC3XTTTc5clvzb1F2vkJZY3bFWTuVFQJoi5dWeqo0IiEDABNghwo4d7RQJuBOo6kET8PeAoCGo8iIQMAHmALIlHnAHUNWDJqB3gKCbv6iVxywW5rDwB5L8ntmrVy/nzxKfFvgN6devX1HLosRTE9DzPzUXHRWBbAQkFMlGSPEiIAIiUCIEWrRoYcccc4ycrJdIe6mYIlBoAh06dKjcrVfotJWeCIhAvAmwINKxY0dr1KhRvAuq0omACBScADvyO3XqZE2aNCl42kowbAI7duywBQsW2Lp16+ycc86x1q1bVwNyySWXuHdQOVGvhqbODrRq1cqZKjv66KPrLE9lJALlQEBCkXJoRdVBBERABCICCEX4KIiACIRJAIeW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ERABERABERABESgAArt377aPP/7YaQPs3LnTbX5PnjzZrrnmmgqlk7PtCkj0gwiIgAiIgAiIgAiIgAiIgAiUE8i4UARH688884w73Xbw4EE7evSoc7SOZJMFXO3atZ3K//e+9z076aSTyjOqP0RABERABERABERABEQgaAQwizRt2jSbN2+e8xmB6SzmzfiQwGQaWiP9+vULGhaVVwREQAREQAREQAREQAREQAQqTSBjQpHt27fbvffeaxMnTjSEISzo+LCg8wF1b1Q/i4uL7d1337Wf/exndtlll7nf/D36FgEREAERiEyA8ZRxVUEERCCYBPzcKpilL8xS4yPivffes23btpWP78yVqWvmzWeccYbTsi7M0qtUyRIIX1sl+7zuFwERyE8Cfg0QureSnyVRrkVABCpDQGuAylDTMyJglhGhyJ49e+znP/+5/for/3TaIJx4q1+/vjvVhh3kmjVrOjNaS5YssaVLlzrtkdLSUkNbpE6dOjZ69GjVlQiIgAiIQBwCLVu2tKZNm1qNGjXi3KnLIiAChUjg5JNPPu6wSSGWMShlKikpsQkTJhhzY4QgoYGFb5cuXez888+3Ro0ahV7S3wEmgPZQz5495VsswG1ARQ8uAUyRY4a8evXqwYWgkotAgAm0adPG2AuQX5EANwIVvVIEMiIUGT9+vP3jH/9wk/QmTZrYHXfcYVdeeWWFRR4l2LJli91///32xhtvuFNwt956qw0ePNgaNGhQqQLqIREQAREICgFMEMqOfFBqW+UUgYoEOGSikL8EOOF7+PBhmzFjhuErBBOzoU6zEY40btzYRo0aZZ06dcrfgirnaSFA+6hVq1Za4lakIiACuU0AoShmyBVEQASCSUAC0WDWu0pddQJpF4ps2rTJEIocOnTInV568sknrWvXrlFz3qJFC3v44YcNDZLHHnvMOCn39NNP2/e///2oz+iCCIiACIiAuXG2rKzM6tWrd9xGmtiIgAgEg8DevXtdQRkDFPKPAAeD/ud//sf5DUHAjUAEQYnf7DrttNPszDPP1Pief1WbkRzTVtC09z4aM5KoEhEBEcgJAgjR9+/f7wSj2hzNiSpRJkQgowRwUcDBGizyhGsYZzQjSkwE8ozAf6Q7v/gSmT9/vlPjwhxWLIFIaF5uu+02ZxqAiT22lBVEQAREQARiE9i6daszQciESEEERCB4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RTKxmy2kAgoooIACCiigQHsLUPHPmB1UglPBzd9NTVR2M6A3AYS33347VrATZCFgQpCBcUQYBL3S9M1vfjOcd955cXZacBSrcCdw8fDDD8cK9q5duxYcZL3QNvv06RO70mJ5usciIMC4KCTKQjCnb9++Md+Mg5GfKNftt98el6FVCxX87ZF69uwZLrroonDmmWdG62Jdh5XKGy02CELRaoZyVPI9GDlyZKALshRAYR+zn0olWuMwrgzWbJNE6xGTAgoooIACCiiggAJZFzAokvVvgOVXQAEFFFBAAQUUaBMBBrned999W3RbVIyXqxyvZIOMP8GrXCIo0JwumAho8GpsolL/gAMOaOxijZqfFjPluu0iOMN4KLyakwgANabVBu5psPVKt0teGfS9OQmP9gpANSffLquAAgoooIACCiigQCkBgyKldJymgAIKKKCAAgoooIACdS9A65S//vWvMfBCEIAWGd/97nfDySefXPdlzy/gD3/4w/D9738/tuChyzcs0kDt+fP6twIKKKCAAgoooIACtShgUKQW95p5VkABBRRQQAEFFFBAgRYToCXKmjVrNo5XQiCA7r0YQyR1PdViG6vyFa1YsSKOmWIgpMp3lNlTQAEFFFBAAQUUaLKAQZEm07mgAgoooIACCiiggAIK1LIAY5m8//3vjwPT091USgREGMcjawERyk83ZWeffXYcMyV5MDD83nvvHegCzqSAAgoooIACCiigQK0LxBH3Gp6AWl/rBTH/CiiggAIKKKCAAgoooIACCiiggAIKKKCAAgoooEApgQ6lJjpNAQUUUEABBRRQQAEFFFBAAQUUUEABBRRQQAEFFKgXAYMi9bInLYcCCiiggAIKKKCAAgoooIACCiiggAIKKKCAAgqUFDAoUpLHiQoooIACCiiggAIKKKCAAgoooIACCiiggAIKKFAvAgZF6mVPWg4FFFBAAQUUUEABBRRQQAEFFFBAAQUUUEABBRQoKWBQpCSPExVQQAEFFFBAAQUUUEABBRRQQAEFFFBAAQUUUKBeBAyK1MuetBwKKKCAAgoooIACCiiggAIKKKCAAgoooIACCihQUsCgSEkeJyqggAIKKKCAAgoooIACCiiggAIKKKCAAgoooEC9CBgUqZc9aTkUUEABBRRQQAEFFFBAAQUUUEABBRRQQAEFFFCgpIBBkZI8TlRAAQUUUEABBRRQQAEFFFBAAQUUUEABBRRQQIF6ETAoUi970nIooIACCiiggAIKKKCAAgoooIACCiiggAIKKKBASQGDIiV5nKiAAgoooIACCiiggAIKKKCAAgoooIACCiiggAL1ImBQpF72pOVQQAEFFFBAAQUUUEABBRRQQAEFFFBAAQUUUECBkgIGRUryOFEBBRRQQAEFFFBAAQUUUEABBRRQQAEFFFBAAQXqRcCgSL3sScuhgAIKKKCAAgoooIACCiiggAIKKKCAAgoooIACJQUMipTkcaICCiiggAIKKKCAAgoooIACCiiggAIKKKCAAgrUi4BBkXrZk5ZDAQUUUEABBRRQQAEFFFBAAQUUUEABBRRQQAEFSgoYFCnJ40QFFFBAAQUUUEABBRRQQAEFFFBAAQUUUEABBRSoFwGDIvWyJy2HAgoooIACCiiggAIKKKCAAgoooIACCiiggAIKlBT4/8LwJmbKwW1YAAAAAElFTkSuQmCC", - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" + "metadata": { + "vscode": { + "languageId": "shellscript" } - ], + }, + "outputs": [], "source": [ - "# this cell is used to display the image in the notebook when using collab\n", + "# this cell is used to display the image in the notebook when using colab\n", "# or running the notebook locally\n", "\n", "import os\n", @@ -126,27 +119,25 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "vscode": { - "languageId": "shellscript" - } - }, + "metadata": {}, "outputs": [], "source": [ - "!pip install assume-framework" + "import importlib.util\n", + "\n", + "# Check if 'google.colab' is available\n", + "IN_COLAB = importlib.util.find_spec(\"google.colab\") is not None\n", + "if IN_COLAB:\n", + " !pip install assume-framework" ] }, { "cell_type": "code", "execution_count": null, - "metadata": { - "vscode": { - "languageId": "shellscript" - } - }, + "metadata": {}, "outputs": [], "source": [ - "!git clone --depth=1 https://github.com/assume-framework/assume.git assume-repo" + "if IN_COLAB:\n", + " !git clone --depth=1 https://github.com/assume-framework/assume.git assume-repo" ] }, { @@ -171,11 +162,6 @@ "metadata": {}, "outputs": [], "source": [ - "import importlib.util\n", - "\n", - "# Check if 'google.colab' is available\n", - "IN_COLAB = importlib.util.find_spec(\"google.colab\") is not None\n", - "\n", "colab_inputs_path = \"assume-repo/examples/inputs\"\n", "local_inputs_path = \"../inputs\"\n", "\n", @@ -205,7 +191,7 @@ "- **Bidding Strategies**: The strategies used by the unit for bidding in the electricity market.\n", "- **Max Power and Min Power**: The maximum and minimum electrical power that the unit can handle.\n", "- **Max Hydrogen and Min Hydrogen**: The maximum and minimum hydrogen production levels.\n", - "- **Fixed Cost**: The fixed operational cost for the unit." + "- **Additional Cost**: The fixed operational cost for the unit." ] }, { @@ -441,7 +427,7 @@ "source": [ "The `NaiveStrategyElectrolyser` class inherits from the `BaseStrategy` class and implements the `calculate_bids` method, which is responsible for formulating the market bids:\n", "\n", - "`calculate_bids` method takes several arguments, including the unit to be dispatched (`unit`), the market configuration (`market_config`), and a list of products (`product_tuples`). It returns an `Orderbook` containing the bids.\n", + "The `calculate_bids` method takes several arguments, including the unit to be dispatched (`unit`), the market configuration (`market_config`), and a list of products (`product_tuples`). It returns an `Orderbook` containing the bids.\n", "\n", "In this case, we use **Marginal Revenue** to determine the price at which the unit should make its bid. The equation used in the code is as follows:\n", "\n", @@ -768,11 +754,16 @@ "source": [ "This concludes our tutorial. By following these steps, you have successfully created a Demand Side Unit with a Rule-Based Bidding Strategy and integrated it into the ASSUME framework." ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [] } ], "metadata": { "kernelspec": { - "display_name": "Python 3", + "display_name": "assume", "language": "python", "name": "python3" }, @@ -786,7 +777,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.9" + "version": "3.12.7" }, "nbsphinx": { "execute": "never" diff --git a/examples/notebooks/04_reinforcement_learning_algorithm_example.ipynb b/examples/notebooks/04_reinforcement_learning_algorithm_example.ipynb index 63fa583f..291fcf34 100644 --- a/examples/notebooks/04_reinforcement_learning_algorithm_example.ipynb +++ b/examples/notebooks/04_reinforcement_learning_algorithm_example.ipynb @@ -9,7 +9,7 @@ "source": [ "# 4.1 RL Algorithm tutorial\n", "\n", - "This tutorial will introduce users into the MATD3 implementation in ASSUME and hence how we use reinforcement learning (RL). The main objective of this tutorial is to ensure participants grasp the steps required to equip ASSUME with a RL algorithm. It ,therefore, start one level deeper, than the RL_application example and the knowledge from this tutorial is not required, if the already perconfigured algorithm in Assume should be used. The algorithm explained here is usable as a plug and play solution in the framework. The following coding snippets will highlight the key in the algorithm class and will explain the interactions with the learning role and other classes along the way. \n", + "This tutorial will introduce users into the MATD3 implementation in ASSUME and hence how we use reinforcement learning (RL). The main objective of this tutorial is to ensure participants grasp the steps required to equip ASSUME with a RL algorithm. It therefore starts one level deeper than the RL_application example and the knowledge from this tutorial is not required if the already pre-configured algorithm in Assume is to be used. The algorithm explained here is usable as a plug and play solution in the framework. The following coding snippets will highlight the key in the algorithm class and will explain the interactions with the learning role and other classes along the way. \n", "\n", "The outline of this tutorial is as follows. We will start with an introduction to the changed simulation flow when we use reinforcement learning (1. From one simulation year to learning episodes). If you need a refresher on RL in general, please visit our readthedocs (https://assume.readthedocs.io/en/latest/). Afterwards, we dive into the tasks and reason behind a learning role (2. What role has a learning role) and then dive into the characteristics of the algorithm (3. The MATD3).\n", "\n", @@ -37,49 +37,18 @@ "id": "m0DaRwFA7VgW", "outputId": "5655adad-5b7a-4fe3-9067-6b502a06136b" }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Requirement already satisfied: assume-framework[learning] in c:\\users\\aeppl\\.conda\\envs\\assume\\lib\\site-packages (0.4.3)\n", - "Requirement already satisfied: argcomplete>=3.1.4 in c:\\users\\aeppl\\.conda\\envs\\assume\\lib\\site-packages (from assume-framework[learning]) (3.5.1)\n", - "Requirement already satisfied: nest-asyncio>=1.5.6 in c:\\users\\aeppl\\.conda\\envs\\assume\\lib\\site-packages (from assume-framework[learning]) (1.6.0)\n", - "Requirement already satisfied: mango-agents>=2.1.1 in c:\\users\\aeppl\\.conda\\envs\\assume\\lib\\site-packages (from assume-framework[learning]) (2.1.1)\n", - "Requirement already satisfied: numpy>=1.26.4 in c:\\users\\aeppl\\.conda\\envs\\assume\\lib\\site-packages (from assume-framework[learning]) (1.26.4)\n", - "Requirement already satisfied: tqdm>=4.64.1 in c:\\users\\aeppl\\.conda\\envs\\assume\\lib\\site-packages (from assume-framework[learning]) (4.66.6)\n", - "Requirement already satisfied: python-dateutil>=2.8.2 in c:\\users\\aeppl\\.conda\\envs\\assume\\lib\\site-packages (from assume-framework[learning]) (2.9.0)\n", - "Requirement already satisfied: sqlalchemy>=2.0.9 in c:\\users\\aeppl\\.conda\\envs\\assume\\lib\\site-packages (from assume-framework[learning]) (2.0.36)\n", - "Requirement already satisfied: pandas>=2.0.0 in c:\\users\\aeppl\\.conda\\envs\\assume\\lib\\site-packages (from assume-framework[learning]) (2.2.3)\n", - "Requirement already satisfied: psycopg2-binary>=2.9.5 in c:\\users\\aeppl\\.conda\\envs\\assume\\lib\\site-packages (from assume-framework[learning]) (2.9.10)\n", - "Requirement already satisfied: pyyaml>=6.0 in c:\\users\\aeppl\\.conda\\envs\\assume\\lib\\site-packages (from assume-framework[learning]) (6.0.2)\n", - "Requirement already satisfied: pyyaml-include>=2.2a in c:\\users\\aeppl\\.conda\\envs\\assume\\lib\\site-packages (from assume-framework[learning]) (2.2a1)\n", - "Requirement already satisfied: pyomo>=6.8.0 in c:\\users\\aeppl\\.conda\\envs\\assume\\lib\\site-packages (from assume-framework[learning]) (6.8.0)\n", - "Requirement already satisfied: highspy in c:\\users\\aeppl\\.conda\\envs\\assume\\lib\\site-packages (from assume-framework[learning]) (1.8.0)\n", - "Requirement already satisfied: torch>=2.0.1 in c:\\users\\aeppl\\.conda\\envs\\assume\\lib\\site-packages (from assume-framework[learning]) (2.5.1)\n", - "Requirement already satisfied: paho-mqtt>=2.1.0 in c:\\users\\aeppl\\.conda\\envs\\assume\\lib\\site-packages (from mango-agents>=2.1.1->assume-framework[learning]) (2.1.0)\n", - "Requirement already satisfied: dill>=0.3.8 in c:\\users\\aeppl\\.conda\\envs\\assume\\lib\\site-packages (from mango-agents>=2.1.1->assume-framework[learning]) (0.3.9)\n", - "Requirement already satisfied: protobuf==5.27.2 in c:\\users\\aeppl\\.conda\\envs\\assume\\lib\\site-packages (from mango-agents>=2.1.1->assume-framework[learning]) (5.27.2)\n", - "Requirement already satisfied: networkx>=3.4.1 in c:\\users\\aeppl\\.conda\\envs\\assume\\lib\\site-packages (from mango-agents>=2.1.1->assume-framework[learning]) (3.4.2)\n", - "Requirement already satisfied: pytz>=2020.1 in c:\\users\\aeppl\\.conda\\envs\\assume\\lib\\site-packages (from pandas>=2.0.0->assume-framework[learning]) (2024.2)\n", - "Requirement already satisfied: tzdata>=2022.7 in c:\\users\\aeppl\\.conda\\envs\\assume\\lib\\site-packages (from pandas>=2.0.0->assume-framework[learning]) (2024.2)\n", - "Requirement already satisfied: ply in c:\\users\\aeppl\\.conda\\envs\\assume\\lib\\site-packages (from pyomo>=6.8.0->assume-framework[learning]) (3.11)\n", - "Requirement already satisfied: six>=1.5 in c:\\users\\aeppl\\.conda\\envs\\assume\\lib\\site-packages (from python-dateutil>=2.8.2->assume-framework[learning]) (1.16.0)\n", - "Requirement already satisfied: fsspec>=2021.04.0 in c:\\users\\aeppl\\.conda\\envs\\assume\\lib\\site-packages (from pyyaml-include>=2.2a->assume-framework[learning]) (2024.10.0)\n", - "Requirement already satisfied: typing-extensions>=4.6.0 in c:\\users\\aeppl\\.conda\\envs\\assume\\lib\\site-packages (from sqlalchemy>=2.0.9->assume-framework[learning]) (4.12.2)\n", - "Requirement already satisfied: greenlet!=0.4.17 in c:\\users\\aeppl\\.conda\\envs\\assume\\lib\\site-packages (from sqlalchemy>=2.0.9->assume-framework[learning]) (3.1.1)\n", - "Requirement already satisfied: filelock in c:\\users\\aeppl\\.conda\\envs\\assume\\lib\\site-packages (from torch>=2.0.1->assume-framework[learning]) (3.16.1)\n", - "Requirement already satisfied: jinja2 in c:\\users\\aeppl\\.conda\\envs\\assume\\lib\\site-packages (from torch>=2.0.1->assume-framework[learning]) (3.1.4)\n", - "Requirement already satisfied: setuptools in c:\\users\\aeppl\\.conda\\envs\\assume\\lib\\site-packages (from torch>=2.0.1->assume-framework[learning]) (75.1.0)\n", - "Requirement already satisfied: sympy==1.13.1 in c:\\users\\aeppl\\.conda\\envs\\assume\\lib\\site-packages (from torch>=2.0.1->assume-framework[learning]) (1.13.1)\n", - "Requirement already satisfied: mpmath<1.4,>=1.1.0 in c:\\users\\aeppl\\.conda\\envs\\assume\\lib\\site-packages (from sympy==1.13.1->torch>=2.0.1->assume-framework[learning]) (1.3.0)\n", - "Requirement already satisfied: colorama in c:\\users\\aeppl\\.conda\\envs\\assume\\lib\\site-packages (from tqdm>=4.64.1->assume-framework[learning]) (0.4.6)\n", - "Requirement already satisfied: MarkupSafe>=2.0 in c:\\users\\aeppl\\.conda\\envs\\assume\\lib\\site-packages (from jinja2->torch>=2.0.1->assume-framework[learning]) (3.0.2)\n" - ] - } - ], + "outputs": [], "source": [ - "!pip install assume-framework[learning]" + "import importlib.util\n", + "\n", + "# Check whether notebook is run in google colab\n", + "IN_COLAB = importlib.util.find_spec(\"google.colab\") is not None\n", + "\n", + "if IN_COLAB:\n", + " !pip install assume-framework[learning]\n", + " # Colab currently has issues with pyomo version 6.8.2, causing the notebook to crash\n", + " # Installing an older version resolves this issue. This should only be considered a temporary fix.\n", + " !pip install pyomo==6.8.0" ] }, { @@ -107,7 +76,8 @@ }, "outputs": [], "source": [ - "!git clone https://github.com/assume-framework/assume.git assume-repo" + "if IN_COLAB:\n", + " !git clone https://github.com/assume-framework/assume.git assume-repo" ] }, { @@ -135,7 +105,8 @@ }, "outputs": [], "source": [ - "!cd assume-repo && assume -s example_01b -db \"sqlite:///./examples/local_db/assume_db_example_01b.db\"" + "if IN_COLAB:\n", + " !cd assume-repo && assume -s example_01b -db \"sqlite:///./examples/local_db/assume_db_example_01b.db\"" ] }, { @@ -155,11 +126,6 @@ "metadata": {}, "outputs": [], "source": [ - "import importlib.util\n", - "\n", - "# Check if 'google.colab' is available\n", - "IN_COLAB = importlib.util.find_spec(\"google.colab\") is not None\n", - "\n", "colab_inputs_path = \"assume-repo/examples/inputs\"\n", "local_inputs_path = \"../inputs\"\n", "\n", @@ -175,7 +141,7 @@ "source": [ "## 1. From one simulation year to learning episodes\n", "\n", - "In a normal simulation without reinforcement learning, we only run the time horizon of the simulation once. For RL the agents need to learn their strategy based on interactions. For that to work an RL agent has to see a situation, aka a simulation hour, multiple times, and hence we need to run the entire simulation horizon multiple times as well. \n", + "In a normal simulation without reinforcement learning, we only run the time horizon of the simulation once. For RL the agents need to learn their strategy based on interactions. For that to work, a RL agent has to see a situation, aka a simulation hour, multiple times, and hence we need to run the entire simulation horizon multiple times as well. \n", "\n", "To enable this we define a run learning function that will be called if the simulation is started and we defined in our config that we want to activate learning. " ] @@ -443,7 +409,7 @@ "source": [ "## 2. What role has a learning role\n", "\n", - "The LearningRole class in learning_role.py is a central component of the reinforcement learning framework. It manages configurations, device settings, early stopping of the learning process, and initializes various RL strategies the algorithm and buffers. This class ensures that the RL agent can be trained or evaluated effectively, leveraging the available hardware and adhering to the specified configurations. The parameters of the learning process are also described in the read-the-docs under learning_algorithms.\n", + "The LearningRole class in learning_role.py is a central component of the reinforcement learning framework. It manages configurations, device settings, early stopping of the learning process, and initializes various RL strategies, the algorithm and buffers. This class ensures that the RL agent can be trained or evaluated effectively, leveraging the available hardware and adhering to the specified configurations. The parameters of the learning process are also described in the read-the-docs under learning_algorithms.\n", "\n", "### 2.1 Learning Data Management\n", "\n", @@ -539,7 +505,7 @@ "\n", "### 2.2 Learning Algorithm\n", "\n", - "If learning is used, then the learning role initializes a learning algorithm which is the heart of the learning progress. Currently, only the MATD3 is implemented, but we are working on different PPO implementations as well. If you would like to add an algorithm it woulb be integrated here." + "If learning is used, then the learning role initializes a learning algorithm which is the heart of the learning progress. Currently, only the MATD3 is implemented, but we are working on different PPO implementations as well. If you would like to add an algorithm it would be integrated here." ] }, { @@ -837,7 +803,7 @@ "source": [ "The other functions within the reinforcement learning algorithm are primarily there to store, update, and save the new policies. These functions either write the updated policies to a designated location or save them into the `inter_episodic_data`.\n", "\n", - "If you would like to make a change to this algorithm, the most likely modification would be to the `update_policy` function, as it plays a central role in the learning process. The other functions would only need adjustments if the different algorithm features vary likethe target critics or critic architectures.\n" + "If you would like to make a change to this algorithm, the most likely modification would be to the `update_policy` function, as it plays a central role in the learning process. The other functions would only need adjustments if the different algorithm features vary like the target critics or critic architectures.\n" ] }, { @@ -937,61 +903,7 @@ "lines_to_next_cell": 0, "outputId": "e30f4279-7a4e-4efc-9cfb-61416e4fe2f1" }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "INFO:assume.world:connected to db\n", - "INFO:assume.scenario.loader_csv:Starting Scenario example_02a/base from ../inputs\n", - "INFO:assume.scenario.loader_csv:storage_units not found. Returning None\n", - "INFO:assume.scenario.loader_csv:industrial_dsm_units not found. Returning None\n", - "INFO:assume.scenario.loader_csv:residential_dsm_units not found. Returning None\n", - "INFO:assume.scenario.loader_csv:forecasts_df not found. Returning None\n", - "INFO:assume.scenario.loader_csv:Downsampling demand_df successful.\n", - "INFO:assume.scenario.loader_csv:cross_border_flows not found. Returning None\n", - "INFO:assume.scenario.loader_csv:availability_df not found. Returning None\n", - "INFO:assume.scenario.loader_csv:electricity_prices not found. Returning None\n", - "INFO:assume.scenario.loader_csv:price_forecasts not found. Returning None\n", - "INFO:assume.scenario.loader_csv:temperature not found. Returning None\n", - "INFO:assume.scenario.loader_csv:Adding markets\n", - "INFO:assume.scenario.loader_csv:Read units from file\n", - "INFO:assume.scenario.loader_csv:Adding power_plant units\n", - "INFO:assume.scenario.loader_csv:Adding demand units\n", - "INFO:assume.scenario.loader_csv:Adding unit operators and units\n", - "INFO:assume.scenario.loader_csv:storage_units not found. Returning None\n", - "INFO:assume.scenario.loader_csv:industrial_dsm_units not found. Returning None\n", - "INFO:assume.scenario.loader_csv:residential_dsm_units not found. Returning None\n", - "INFO:assume.scenario.loader_csv:forecasts_df not found. Returning None\n", - "INFO:assume.scenario.loader_csv:Downsampling demand_df successful.\n", - "INFO:assume.scenario.loader_csv:cross_border_flows not found. Returning None\n", - "INFO:assume.scenario.loader_csv:availability_df not found. Returning None\n", - "INFO:assume.scenario.loader_csv:electricity_prices not found. Returning None\n", - "INFO:assume.scenario.loader_csv:price_forecasts not found. Returning None\n", - "INFO:assume.scenario.loader_csv:temperature not found. Returning None\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "Training Episodes: 0%| | 0/100 [00:00 YOUR CODE HERE\n", " base_bid = None # TODO\n", - " # add niose to the last dimension of the observation\n", + " # add noise to the last dimension of the observation\n", " # needs to be adjusted if observation space is changed, because only makes sense\n", " # if the last dimension of the observation space are the marginal cost\n", " curr_action = noise + base_bid.clone().detach()\n", @@ -785,7 +811,7 @@ "\n", "So how do we define the base bid?\n", "\n", - "Assuming the described auction is a efficient market with full information and competition, we know that bidding the marginal costs of the power plant is the economically best bid. With the RL strategy we can recreate the abuse of market power and incomplete information, which enables us to model different market settings. Yet, starting of with the theoretically styleized optimal solution guides our RL agents properly. As the marginal costs of the power plant are part of the oberservations we can define the base bid in the following way. " + "Assuming the described auction is an efficient market with full information and competition, we know that bidding the marginal costs of the power plant is the economically best bid. With the RL strategy we can recreate the abuse of market power and incomplete information, which enables us to model different market settings. Yet, starting off with the theoretically styleized optimal solution guides our RL agents properly. As the marginal costs of the power plant are part of the oberservations we can define the base bid in the following way. " ] }, { @@ -818,7 +844,7 @@ "#### **Task 2.2**\n", "**Goal: Define the actual bids with the outputs of the actors**\n", "\n", - "Similarly to every other output of a neuronal network, the actions are in the range of 0-1. These values need to be translated into the actual bids $a_{i,t} = [ep^\\mathrm{inflex}_{i,t}, ep^\\mathrm{flex}_{i,t}] \\in [ep^{min},ep^{max}]$. This can be done in a way that further helps the RL agent to learn, if we put some thought into.\n", + "Similarly to every other output of a neuronal network, the actions are in the range of 0-1. These values need to be translated into the actual bids $a_{i,t} = [ep^\\mathrm{inflex}_{i,t}, ep^\\mathrm{flex}_{i,t}] \\in [ep^{min},ep^{max}].$ This can be done in a way that further helps the RL agent to learn, if we put some thought into.\n", "\n", "For this we go back into the calculate_bids() function and instead of just defining bids=actions, which was just a place holder, we actually make them into bids. Think about a smart way to transform them and fill the gaps in the following code. Remember:\n", "\n", @@ -828,7 +854,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": null, "id": "337833a5", "metadata": { "id": "Y81HzlkjNHJ0" @@ -1017,12 +1043,12 @@ "#### **Task 3**\n", "**Goal**: Define the reward guiding the learning process of the agent.\n", "\n", - "As the reward plays such a crucial role in the learning think of ways how to integrate further signals exceeding the monetary profit. One example could be integrating a regret term, namely the opportunity costs. Your task is to define the rewrad using the opportunity costs and to scale it." + "As the reward plays such a crucial role in the learning think of ways how to integrate further signals exceeding the monetary profit. One example could be integrating a regret term, namely the opportunity costs. Your task is to define the reward using the opportunity costs and to scale it." ] }, { "cell_type": "code", - "execution_count": 15, + "execution_count": null, "id": "43e813e2", "metadata": { "id": "U9HX41mODuBU" @@ -1064,7 +1090,7 @@ " end = order[\"end_time\"]\n", " end_excl = end - unit.index.freq\n", "\n", - " # depending on way the unit calaculates marginal costs we take costs\n", + " # depending on whether the unit calaculates marginal costs we take costs\n", " if unit.marginal_cost is not None:\n", " marginal_cost = (\n", " unit.marginal_cost[start]\n", @@ -1196,7 +1222,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": null, "id": "6aa54f30", "metadata": { "id": "ZwVtpK3B5gR6" @@ -1253,7 +1279,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": null, "id": "23b299f8", "metadata": { "id": "moZ_UD7FfkOh" @@ -1262,7 +1288,7 @@ "source": [ "learning_config = {\n", " \"continue_learning\": False,\n", - " \"trained_policies_save_path\": \"null\",\n", + " \"trained_policies_save_path\": None,\n", " \"max_bid_price\": 100,\n", " \"algorithm\": \"matd3\",\n", " \"learning_rate\": 0.001,\n", @@ -1280,6 +1306,26 @@ "}" ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "bac01731", + "metadata": {}, + "outputs": [], + "source": [ + "# Read the YAML file\n", + "with open(f\"{inputs_path}/example_02a/config.yaml\") as file:\n", + " data = yaml.safe_load(file)\n", + "\n", + "# store our modifications to the config file\n", + "data[\"base\"][\"learning_mode\"] = True\n", + "data[\"base\"][\"learning_config\"] = learning_config\n", + "\n", + "# Write the modified data back to the file\n", + "with open(f\"{inputs_path}/example_02a/config.yaml\", \"w\") as file:\n", + " yaml.safe_dump(data, file)" + ] + }, { "cell_type": "markdown", "id": "132f9429", @@ -1289,7 +1335,7 @@ "source": [ "In order to let the simulation run with the integrated learning we need to touch up the main file that runs it in the following way.\n", "\n", - "In the following cell, we let the example run in case 1 of [1], where we have one big reinforcement learning power plan exists that technically can exert my power.\n", + "In the following cell, we let the example run in case 1 of [1], where one big reinforcement learning power plant exists that technically can exert max power.\n", "\n", "[1] Harder, N.; Qussous, R.; Weidlich, A. Fit for purpose: Modeling wholesale electricity markets realistically with multi-agent deep reinforcement learning. *Energy and AI* **2023**. 14. 100295. https://doi.org/10.1016/j.egyai.2023.100295.\n", "\n" @@ -1324,8 +1370,8 @@ " world = World(database_uri=db_uri, export_csv_path=csv_path)\n", "\n", " # we import our defined bidding strategey class including the learning into the world bidding strategies\n", - " # in the example files we provided the name of the learning bidding strategeis in the input csv is \"pp_learning\"\n", - " # hence we define this strategey to be one of the learning class\n", + " # in the example files we provided the name of the learning bidding strategies in the input csv \"pp_learning\"\n", + " # hence we define this strategey to be the one of the learning class\n", " world.bidding_strategies[\"pp_learning\"] = RLStrategy\n", "\n", " # then we load the scenario specified above from the respective input files\n", @@ -1637,6 +1683,866 @@ "lines_to_next_cell": 2 }, "outputs": [], + "source": [ + "# @title Complete notebook code with tasks already filled in\n", + "\n", + "# this cell is used to display the image in the notebook when using colab\n", + "# or running the notebook locally\n", + "\n", + "import os\n", + "\n", + "import importlib.util\n", + "\n", + "# Check if 'google.colab' is available\n", + "IN_COLAB = importlib.util.find_spec(\"google.colab\") is not None\n", + "\n", + "if IN_COLAB:\n", + " !pip install 'assume-framework[learning]'\n", + " # Colab currently has issues with pyomo version 6.8.2, causing the notebook to crash\n", + " # Installing an older version resolves this issue. This should only be considered a temporary fix.\n", + " !pip install pyomo==6.8.0\n", + " !git clone --depth=1 https://github.com/assume-framework/assume.git assume-repo\n", + " !cd assume-repo && assume -s example_01b -db \"sqlite:///./examples/local_db/assume_db_example_01b.db\"\n", + "\n", + "colab_inputs_path = \"assume-repo/examples/inputs\"\n", + "local_inputs_path = \"../inputs\"\n", + "\n", + "inputs_path = colab_inputs_path if IN_COLAB else local_inputs_path \n", + "\n", + "import logging\n", + "import os\n", + "import yaml\n", + "from datetime import datetime, timedelta\n", + "from pathlib import Path\n", + "\n", + "import numpy as np\n", + "import pandas as pd\n", + "import torch as th\n", + "\n", + "from assume import World\n", + "from assume.common.base import LearningStrategy, SupportsMinMax\n", + "from assume.common.market_objects import MarketConfig, Orderbook, Product\n", + "from assume.reinforcement_learning.algorithms import actor_architecture_aliases\n", + "from assume.reinforcement_learning.learning_utils import NormalActionNoise\n", + "from assume.scenario.loader_csv import load_scenario_folder, run_learning\n", + "\n", + "class RLStrategy(LearningStrategy):\n", + " \"\"\"\n", + " Reinforcement Learning Strategy\n", + " \"\"\"\n", + "\n", + " def __init__(self, *args, **kwargs):\n", + " super().__init__(obs_dim=50, act_dim=2, unique_obs_dim=2, *args, **kwargs)\n", + "\n", + " self.unit_id = kwargs[\"unit_id\"]\n", + "\n", + " # defines bounds of actions space\n", + " self.max_bid_price = kwargs.get(\"max_bid_price\", 100)\n", + " self.max_demand = kwargs.get(\"max_demand\", 10e3)\n", + "\n", + " # tells us whether we are training the agents or just executing per-learnind strategies\n", + " self.learning_mode = kwargs.get(\"learning_mode\", False)\n", + " self.perform_evaluation = kwargs.get(\"perform_evaluation\", False)\n", + "\n", + " # based on learning config define algorithm configuration\n", + " self.algorithm = kwargs.get(\"algorithm\", \"matd3\")\n", + " actor_architecture = kwargs.get(\"actor_architecture\", \"mlp\")\n", + "\n", + " # define the architecture of the actor neural network\n", + " # if you use many time series niputs you might want to use the LSTM instead of the MLP for example\n", + " if actor_architecture in actor_architecture_aliases.keys():\n", + " self.actor_architecture_class = actor_architecture_aliases[\n", + " actor_architecture\n", + " ]\n", + " else:\n", + " raise ValueError(\n", + " f\"Policy '{actor_architecture}' unknown. Supported architectures are {list(actor_architecture_aliases.keys())}\"\n", + " )\n", + "\n", + " # sets the devide of the actor network\n", + " device = kwargs.get(\"device\", \"cpu\")\n", + " self.device = th.device(device if th.cuda.is_available() else \"cpu\")\n", + " if not self.learning_mode:\n", + " self.device = th.device(\"cpu\")\n", + "\n", + " # future: add option to choose between float16 and float32\n", + " # float_type = kwargs.get(\"float_type\", \"float32\")\n", + " self.float_type = th.float\n", + "\n", + " # for definition of observation space\n", + " self.foresight = kwargs.get(\"foresight\", 24)\n", + "\n", + " if self.learning_mode:\n", + " self.learning_role = None\n", + " self.collect_initial_experience_mode = kwargs.get(\n", + " \"episodes_collecting_initial_experience\", True\n", + " )\n", + "\n", + " self.action_noise = NormalActionNoise(\n", + " mu=0.0,\n", + " sigma=kwargs.get(\"noise_sigma\", 0.1),\n", + " action_dimension=self.act_dim,\n", + " scale=kwargs.get(\"noise_scale\", 1.0),\n", + " dt=kwargs.get(\"noise_dt\", 1.0),\n", + " )\n", + "\n", + " elif Path(load_path=kwargs[\"trained_policies_save_path\"]).is_dir():\n", + " self.load_actor_params(load_path=kwargs[\"trained_policies_save_path\"])\n", + "\n", + "# we define the class again and inherit from the initial class just to add the additional method to the original class\n", + "# this is a workaround to have different methods of the class in different cells\n", + "# which is good for the purpose of this tutorial\n", + "# however, you should have all functions in a single class when using this example in .py files\n", + "\n", + "\n", + "class RLStrategy(RLStrategy):\n", + " def calculate_bids(\n", + " self,\n", + " unit: SupportsMinMax,\n", + " market_config: MarketConfig,\n", + " product_tuples: list[Product],\n", + " **kwargs,\n", + " ) -> Orderbook:\n", + " \"\"\"\n", + " Calculate bids for a unit -> STEP 1 & 2\n", + " \"\"\"\n", + "\n", + " start = product_tuples[0][0]\n", + " end = product_tuples[0][1]\n", + " # get technical bounds for the unit output from the unit\n", + " min_power, max_power = unit.calculate_min_max_power(start, end)\n", + " min_power = min_power[start]\n", + " max_power = max_power[start]\n", + "\n", + " # =============================================================================\n", + " # 1. Get the Observations, which are the basis of the action decision\n", + " # =============================================================================\n", + " next_observation = self.create_observation(\n", + " unit=unit,\n", + " market_id=market_config.market_id,\n", + " start=start,\n", + " end=end,\n", + " )\n", + "\n", + " # =============================================================================\n", + " # 2. Get the Actions, based on the observations\n", + " # =============================================================================\n", + " actions, noise = self.get_actions(next_observation)\n", + "\n", + " bids = actions\n", + "\n", + " bids = self.remove_empty_bids(bids)\n", + "\n", + " return bids\n", + " \n", + "# we define the class again and inherit from the initial class just to add the additional method to the original class\n", + "# this is a workaround to have different methods of the class in different cells\n", + "# which is good for the purpose of this tutorial\n", + "# however, you should have all functions in a single class when using this example in .py files\n", + "\n", + "\n", + "class RLStrategy(RLStrategy):\n", + " def calculate_reward(\n", + " self,\n", + " unit,\n", + " marketconfig: MarketConfig,\n", + " orderbook: Orderbook,\n", + " ):\n", + " \"\"\"\n", + " Calculate reward\n", + " \"\"\"\n", + "\n", + " return None\n", + " \n", + "# we define the class again and inherit from the initial class just to add the additional method to the original class\n", + "# this is a workaround to have different methods of the class in different cells\n", + "# which is good for the purpose of this tutorial\n", + "# however, you should have all functions in a single class when using this example in .py files\n", + "\n", + "class RLStrategy(RLStrategy):\n", + " def create_observation(\n", + " self,\n", + " unit: SupportsMinMax,\n", + " market_id: str,\n", + " start: datetime,\n", + " end: datetime,\n", + " ):\n", + " \"\"\"\n", + " Create observation\n", + " \"\"\"\n", + "\n", + " end_excl = end - unit.index.freq\n", + "\n", + " # get the forecast length depending on the time unit considered in the modelled unit\n", + " forecast_len = pd.Timedelta((self.foresight - 1) * unit.index.freq)\n", + "\n", + " # =============================================================================\n", + " # 1.1 Get the Observations, which are the basis of the action decision\n", + " # =============================================================================\n", + " \n", + " # residual load forecast\n", + " scaling_factor_res_load = self.max_demand\n", + "\n", + " # price forecast\n", + " scaling_factor_price = self.max_bid_price\n", + "\n", + " # total capacity\n", + " scaling_factor_total_capacity = unit.max_power\n", + "\n", + " # marginal cost\n", + " scaling_factor_marginal_cost = self.max_bid_price\n", + "\n", + " # checks if we are at the end of the simulation horizon, since we need to change the forecast then\n", + " # for residual load and price forecast and scale them\n", + " if (\n", + " end_excl + forecast_len\n", + " > unit.forecaster[f\"residual_load_{market_id}\"].index[-1]\n", + " ):\n", + " scaled_res_load_forecast = (\n", + " unit.forecaster[f\"residual_load_{market_id}\"].loc[start:].values\n", + " / scaling_factor_res_load\n", + " )\n", + " scaled_res_load_forecast = np.concatenate(\n", + " [\n", + " scaled_res_load_forecast,\n", + " unit.forecaster[f\"residual_load_{market_id}\"].iloc[\n", + " : self.foresight - len(scaled_res_load_forecast)\n", + " ],\n", + " ]\n", + " )\n", + "\n", + " else:\n", + " scaled_res_load_forecast = (\n", + " unit.forecaster[f\"residual_load_{market_id}\"]\n", + " .loc[start : end_excl + forecast_len]\n", + " .values\n", + " / scaling_factor_res_load\n", + " )\n", + "\n", + " if end_excl + forecast_len > unit.forecaster[f\"price_{market_id}\"].index[-1]:\n", + " scaled_price_forecast = (\n", + " unit.forecaster[f\"price_{market_id}\"].loc[start:].values\n", + " / scaling_factor_price\n", + " )\n", + " scaled_price_forecast = np.concatenate(\n", + " [\n", + " scaled_price_forecast,\n", + " unit.forecaster[f\"price_{market_id}\"].iloc[\n", + " : self.foresight - len(scaled_price_forecast)\n", + " ],\n", + " ]\n", + " )\n", + "\n", + " else:\n", + " scaled_price_forecast = (\n", + " unit.forecaster[f\"price_{market_id}\"]\n", + " .loc[start : end_excl + forecast_len]\n", + " .values\n", + " / scaling_factor_price\n", + " )\n", + "\n", + " # get last accepted bid volume and the current marginal costs of the unit\n", + " current_volume = unit.get_output_before(start)\n", + " current_costs = unit.calc_marginal_cost_with_partial_eff(current_volume, start)\n", + "\n", + " # scale unit outputs\n", + " scaled_total_capacity = current_volume / scaling_factor_total_capacity\n", + " scaled_marginal_cost = current_costs / scaling_factor_marginal_cost\n", + "\n", + " # concat all obsverations into one array\n", + " observation = np.concatenate(\n", + " [\n", + " scaled_res_load_forecast,\n", + " scaled_price_forecast,\n", + " np.array([scaled_total_capacity, scaled_marginal_cost]),\n", + " ]\n", + " )\n", + "\n", + " # transfer array to GPU for NN processing\n", + " observation = (\n", + " th.tensor(observation, dtype=self.float_type)\n", + " .to(self.device, non_blocking=True)\n", + " .view(-1)\n", + " )\n", + "\n", + " return observation.detach().clone()\n", + "\n", + "# we define the class again and inherit from the initial class just to add the additional method to the original class\n", + "# this is a workaround to have different methods of the class in different cells\n", + "# which is good for the purpose of this tutorial\n", + "# however, you should have all functions in a single class when using this example in .py files\n", + "\n", + "\n", + "class RLStrategy(RLStrategy):\n", + " def get_actions(self, next_observation):\n", + " \"\"\"\n", + " Get actions\n", + " \"\"\"\n", + "\n", + " # distinction whether we are in learning mode or not to handle exploration realised with noise\n", + " if self.learning_mode:\n", + " # if we are in learning mode, the first x episodes we want to explore the entire action space\n", + " # to get a good initial experience in the area around the costs of the agent\n", + " if self.collect_initial_experience_mode:\n", + " # define current action as solely noise\n", + " noise = (\n", + " th.normal(\n", + " mean=0.0, std=0.2, size=(1, self.act_dim), dtype=self.float_type\n", + " )\n", + " .to(self.device)\n", + " .squeeze()\n", + " )\n", + "\n", + " # =============================================================================\n", + " # 2.1 Get Actions and handle exploration\n", + " # =============================================================================\n", + " # ==> YOUR CODE HERE\n", + " base_bid = next_observation[-1] # = marginal_costs\n", + " # add noise to the last dimension of the observation\n", + " # needs to be adjusted if observation space is changed, because only makes sense\n", + " # if the last dimension of the observation space are the marginal cost\n", + " curr_action = noise + base_bid.clone().detach()\n", + "\n", + " else:\n", + " # if we are not in the initial exploration phase we chose the action with the actor neuronal net\n", + " # and add noise to the action\n", + " curr_action = self.actor(next_observation).detach()\n", + " noise = th.tensor(\n", + " self.action_noise.noise(), device=self.device, dtype=self.float_type\n", + " )\n", + " curr_action += noise\n", + " else:\n", + " # if we are not in learning mode we just use the actor neuronal net to get the action without adding noise\n", + "\n", + " curr_action = self.actor(next_observation).detach()\n", + " noise = tuple(0 for _ in range(self.act_dim))\n", + "\n", + " curr_action = curr_action.clamp(-1, 1)\n", + "\n", + " return curr_action, noise\n", + " \n", + "# we define the class again and inherit from the initial class just to add the additional method to the original class\n", + "# this is a workaround to have different methods of the class in different cells\n", + "# which is good for the purpose of this tutorial\n", + "# however, you should have all functions in a single class when using this example in .py files\n", + "\n", + "\n", + "class RLStrategy(RLStrategy):\n", + " def calculate_bids(\n", + " self,\n", + " unit: SupportsMinMax,\n", + " market_config: MarketConfig,\n", + " product_tuples: list[Product],\n", + " **kwargs,\n", + " ) -> Orderbook:\n", + " \"\"\"\n", + " Calculate bids for a unit\n", + " \"\"\"\n", + "\n", + " bid_quantity_inflex, bid_price_inflex = 0, 0\n", + " bid_quantity_flex, bid_price_flex = 0, 0\n", + "\n", + " start = product_tuples[0][0]\n", + " end = product_tuples[0][1]\n", + " # get technical bounds for the unit output from the unit\n", + " min_power, max_power = unit.calculate_min_max_power(start, end)\n", + " min_power = min_power[start]\n", + " max_power = max_power[start]\n", + "\n", + " # =============================================================================\n", + " # 1. Get the Observations, which are the basis of the action decision\n", + " # =============================================================================\n", + " next_observation = self.create_observation(\n", + " unit=unit,\n", + " market_id=market_config.market_id,\n", + " start=start,\n", + " end=end,\n", + " )\n", + "\n", + " # =============================================================================\n", + " # 2. Get the Actions, based on the observations\n", + " # =============================================================================\n", + " actions, noise = self.get_actions(next_observation)\n", + "\n", + " bids = actions\n", + "\n", + " # =============================================================================\n", + " # 3.2 Transform Actions into bids\n", + " # =============================================================================\n", + " # ==> YOUR CODE HERE\n", + " # actions are in the range [0,1], we need to transform them into actual bids\n", + " # we can use our domain knowledge to guide the bid formulation\n", + "\n", + " #calculate actual bids\n", + " #rescale actions to actual prices\n", + " bid_prices = actions * self.max_bid_price\n", + "\n", + " #calculate inflexible part of the bid\n", + " bid_quantity_inflex = min_power\n", + " bid_price_inflex = min(bid_prices)\n", + "\n", + " #calculate flexible part of the bid\n", + " bid_quantity_flex = max_power - bid_quantity_inflex\n", + " bid_price_flex = max(bid_prices)\n", + "\n", + " # actually formulate bids in orderbook format\n", + " bids = [\n", + " {\n", + " \"start_time\": start,\n", + " \"end_time\": end,\n", + " \"only_hours\": None,\n", + " \"price\": bid_price_inflex,\n", + " \"volume\": bid_quantity_inflex,\n", + " },\n", + " {\n", + " \"start_time\": start,\n", + " \"end_time\": end,\n", + " \"only_hours\": None,\n", + " \"price\": bid_price_flex,\n", + " \"volume\": bid_quantity_flex,\n", + " },\n", + " ]\n", + "\n", + " # store results in unit outputs as lists to be written to the buffer for learning\n", + " unit.outputs[\"rl_observations\"].append(next_observation)\n", + " unit.outputs[\"rl_actions\"].append(actions)\n", + "\n", + " # store results in unit outputs as series to be written to the database by the unit operator\n", + " unit.outputs[\"actions\"][start] = actions\n", + " unit.outputs[\"exploration_noise\"][start] = noise\n", + "\n", + " bids = self.remove_empty_bids(bids)\n", + "\n", + " return bids\n", + " \n", + "# we define the class again and inherit from the initial class just to add the additional method to the original class\n", + "# this is a workaround to have different methods of the class in different cells\n", + "# which is good for the purpose of this tutorial\n", + "# however, you should have all functions in a single class when using this example in .py files\n", + "\n", + "\n", + "class RLStrategy(RLStrategy):\n", + " def calculate_reward(\n", + " self,\n", + " unit,\n", + " marketconfig: MarketConfig,\n", + " orderbook: Orderbook,\n", + " ):\n", + " \"\"\"\n", + " Calculate reward\n", + " \"\"\"\n", + "\n", + " # =============================================================================\n", + " # 3. Calculate Reward\n", + " # =============================================================================\n", + " # function is called after the market is cleared and we get the market feedback,\n", + " # so we can calculate the profit\n", + "\n", + " product_type = marketconfig.product_type\n", + "\n", + " profit = 0\n", + " reward = 0\n", + " opportunity_cost = 0\n", + "\n", + " # iterate over all orders in the orderbook, to calculate order specific profit\n", + " for order in orderbook:\n", + " start = order[\"start_time\"]\n", + " end = order[\"end_time\"]\n", + " end_excl = end - unit.index.freq\n", + "\n", + " # depending on whether the unit calaculates marginal costs we take costs\n", + " if unit.marginal_cost is not None:\n", + " marginal_cost = (\n", + " unit.marginal_cost[start]\n", + " if len(unit.marginal_cost) > 1\n", + " else unit.marginal_cost\n", + " )\n", + " else:\n", + " marginal_cost = unit.calc_marginal_cost_with_partial_eff(\n", + " power_output=unit.outputs[product_type].loc[start:end_excl],\n", + " timestep=start,\n", + " )\n", + "\n", + " duration = (end - start) / timedelta(hours=1)\n", + "\n", + " # calculate profit as income - running_cost from this event\n", + " price_difference = order[\"accepted_price\"] - marginal_cost\n", + " order_profit = price_difference * order[\"accepted_volume\"] * duration\n", + "\n", + " # calculate opportunity cost\n", + " # as the loss of income we have because we are not running at full power\n", + " order_opportunity_cost = (\n", + " price_difference\n", + " * (\n", + " unit.max_power - unit.outputs[product_type].loc[start:end_excl]\n", + " ).sum()\n", + " * duration\n", + " )\n", + "\n", + " # if our opportunity costs are negative, we did not miss an opportunity to earn money and we set them to 0\n", + " order_opportunity_cost = max(order_opportunity_cost, 0)\n", + "\n", + " # collect profit and opportunity cost for all orders\n", + " opportunity_cost += order_opportunity_cost\n", + " profit += order_profit\n", + "\n", + " # consideration of start-up costs, which are evenly divided between the\n", + " # upward and downward regulation events\n", + " if (\n", + " unit.outputs[product_type].loc[start] != 0\n", + " and unit.outputs[product_type].loc[start - unit.index.freq] == 0\n", + " ):\n", + " profit = profit - unit.hot_start_cost / 2\n", + " elif (\n", + " unit.outputs[product_type].loc[start] == 0\n", + " and unit.outputs[product_type].loc[start - unit.index.freq] != 0\n", + " ):\n", + " profit = profit - unit.hot_start_cost / 2\n", + "\n", + " # =============================================================================\n", + " # =============================================================================\n", + " # ==> YOUR CODE HERE\n", + " # The straight forward implementation would be reward = profit, yet we would like to give the agent more guidance\n", + " # in the learning process, so we add a regret term to the reward, which is the opportunity cost\n", + " # define the reward and scale it\n", + "\n", + " scaling = 0.1 / unit.max_power\n", + " regret_scale = 0.2\n", + " reward = float(profit - regret_scale * opportunity_cost) * scaling\n", + "\n", + " # store results in unit outputs which are written to database by unit operator\n", + " unit.outputs[\"profit\"].loc[start:end_excl] += profit\n", + " unit.outputs[\"reward\"].loc[start:end_excl] = reward\n", + " unit.outputs[\"regret\"].loc[start:end_excl] = opportunity_cost\n", + "\n", + "# we define the class again and inherit from the initial class just to add the additional method to the original class\n", + "# this is a workaround to have different methods of the class in different cells\n", + "# which is good for the purpose of this tutorial\n", + "# however, you should have all functions in a single class when using this example in .py files\n", + "\n", + "\n", + "class RLStrategy(RLStrategy):\n", + " def load_actor_params(self, load_path):\n", + " \"\"\"\n", + " Load actor parameters\n", + " \"\"\"\n", + " directory = f\"{load_path}/actors/actor_{self.unit_id}.pt\"\n", + "\n", + " params = th.load(directory, map_location=self.device)\n", + "\n", + " self.actor = self.actor_architecture_class(\n", + " obs_dim=self.obs_dim,\n", + " act_dim=self.act_dim,\n", + " float_type=self.float_type,\n", + " unique_obs_dim=self.unique_obs_dim,\n", + " num_timeseries_obs_dim=self.num_timeseries_obs_dim,\n", + " ).to(self.device)\n", + "\n", + " self.actor.load_state_dict(params[\"actor\"])\n", + "\n", + " if self.learning_mode:\n", + " self.actor_target = self.actor_architecture_class(\n", + " obs_dim=self.obs_dim,\n", + " act_dim=self.act_dim,\n", + " float_type=self.float_type,\n", + " unique_obs_dim=self.unique_obs_dim,\n", + " num_timeseries_obs_dim=self.num_timeseries_obs_dim,\n", + " ).to(self.device)\n", + " self.actor_target.load_state_dict(params[\"actor_target\"])\n", + " self.actor_target.eval()\n", + " self.actor.optimizer.load_state_dict(params[\"actor_optimizer\"])\n", + "\n", + "learning_config = {\n", + " \"continue_learning\": False,\n", + " \"trained_policies_save_path\": None,\n", + " \"max_bid_price\": 100,\n", + " \"algorithm\": \"matd3\",\n", + " \"learning_rate\": 0.001,\n", + " \"training_episodes\": 2,\n", + " \"episodes_collecting_initial_experience\": 1,\n", + " \"train_freq\": \"24h\",\n", + " \"gradient_steps\": -1,\n", + " \"batch_size\": 256,\n", + " \"gamma\": 0.99,\n", + " \"device\": \"cpu\",\n", + " \"noise_sigma\": 0.1,\n", + " \"noise_scale\": 1,\n", + " \"noise_dt\": 1,\n", + " \"validation_episodes_interval\": 5,\n", + "}\n", + "\n", + "# Read the YAML file\n", + "with open(f\"{inputs_path}/example_02a/config.yaml\") as file:\n", + " data = yaml.safe_load(file)\n", + "\n", + "# store our modifications to the config file\n", + "data[\"base\"][\"learning_mode\"] = True\n", + "data[\"base\"][\"learning_config\"] = learning_config\n", + "\n", + "# Write the modified data back to the file\n", + "with open(f\"{inputs_path}/example_02a/config.yaml\", \"w\") as file:\n", + " yaml.safe_dump(data, file)\n", + "\n", + "# Read the YAML file\n", + "with open(f\"{inputs_path}/example_02b/config.yaml\") as file:\n", + " data = yaml.safe_load(file)\n", + "\n", + "# store our modifications to the config file\n", + "data[\"base\"][\"learning_mode\"] = True\n", + "data[\"base\"][\"learning_config\"] = learning_config\n", + "\n", + "# Write the modified data back to the file\n", + "with open(f\"{inputs_path}/example_02b/config.yaml\", \"w\") as file:\n", + " yaml.safe_dump(data, file)\n", + "\n", + "# Read the YAML file\n", + "with open(f\"{inputs_path}/example_02c/config.yaml\") as file:\n", + " data = yaml.safe_load(file)\n", + "\n", + "# store our modifications to the config file\n", + "data[\"base\"][\"learning_mode\"] = True\n", + "data[\"base\"][\"learning_config\"] = learning_config\n", + "\n", + "# Write the modified data back to the file\n", + "with open(f\"{inputs_path}/example_02c/config.yaml\", \"w\") as file:\n", + " yaml.safe_dump(data, file)\n", + "\n", + "log = logging.getLogger(__name__)\n", + "\n", + "csv_path = \"outputs\"\n", + "os.makedirs(\"local_db\", exist_ok=True)\n", + "\n", + "if __name__ == \"__main__\":\n", + " db_uri = \"sqlite:///local_db/assume_db.db\"\n", + "\n", + " scenario = \"example_02a\"\n", + " study_case = \"base\"\n", + "\n", + " # create world\n", + " world = World(database_uri=db_uri, export_csv_path=csv_path)\n", + "\n", + " # we import our defined bidding strategey class including the learning into the world bidding strategies\n", + " # in the example files we provided the name of the learning bidding strategies in the input csv \"pp_learning\"\n", + " # hence we define this strategey to be the one of the learning class\n", + " world.bidding_strategies[\"pp_learning\"] = RLStrategy\n", + "\n", + " # then we load the scenario specified above from the respective input files\n", + " load_scenario_folder(\n", + " world,\n", + " inputs_path=inputs_path,\n", + " scenario=scenario,\n", + " study_case=study_case,\n", + " )\n", + "\n", + " # run learning if learning mode is enabled\n", + " # needed as we simulate the modelling horizon multiple times to train reinforcement learning run_learning( world, inputs_path=input_path, scenario=scenario, study_case=study_case, )\n", + "\n", + " if world.learning_config.get(\"learning_mode\", False):\n", + " run_learning(\n", + " world,\n", + " inputs_path=inputs_path,\n", + " scenario=scenario,\n", + " study_case=study_case,\n", + " )\n", + "\n", + " # after the learning is done we make a normal run of the simulation, which equals a test run\n", + " world.run()\n", + "\n", + "log = logging.getLogger(__name__)\n", + "\n", + "csv_path = \"outputs\"\n", + "os.makedirs(\"local_db\", exist_ok=True)\n", + "\n", + "if __name__ == \"__main__\":\n", + " db_uri = \"sqlite:///local_db/assume_db.db\"\n", + "\n", + " scenario = \"example_02b\"\n", + " study_case = \"base\"\n", + "\n", + " # create world\n", + " world = World(database_uri=db_uri, export_csv_path=csv_path)\n", + "\n", + " # we import our defined bidding strategey class including the learning into the world bidding strategies\n", + " # in the example files we provided the name of the learning bidding strategeis in the input csv is \"pp_learning\"\n", + " # hence we define this strategey to be one of the learning class\n", + " world.bidding_strategies[\"pp_learning\"] = RLStrategy\n", + "\n", + " # then we load the scenario specified above from the respective input files\n", + " load_scenario_folder(\n", + " world,\n", + " inputs_path=inputs_path,\n", + " scenario=scenario,\n", + " study_case=study_case,\n", + " )\n", + "\n", + " # run learning if learning mode is enabled\n", + " # needed as we simulate the modelling horizon multiple times to train reinforcement learning run_learning( world, inputs_path=input_path, scenario=scenario, study_case=study_case, )\n", + "\n", + " if world.learning_config.get(\"learning_mode\", False):\n", + " run_learning(\n", + " world,\n", + " inputs_path=inputs_path,\n", + " scenario=scenario,\n", + " study_case=study_case,\n", + " )\n", + "\n", + " # after the learning is done we make a normal run of the simulation, which equals a test run\n", + " world.run()\n", + "\n", + "log = logging.getLogger(__name__)\n", + "\n", + "csv_path = \"outputs\"\n", + "os.makedirs(\"local_db\", exist_ok=True)\n", + "\n", + "if __name__ == \"__main__\":\n", + " db_uri = \"sqlite:///local_db/assume_db.db\"\n", + "\n", + " scenario = \"example_02c\"\n", + " study_case = \"base\"\n", + "\n", + " # create world\n", + " world = World(database_uri=db_uri, export_csv_path=csv_path)\n", + "\n", + " # we import our defined bidding strategey class including the learning into the world bidding strategies\n", + " # in the example files we provided the name of the learning bidding strategeis in the input csv is \"pp_learning\"\n", + " # hence we define this strategey to be one of the learning class\n", + " world.bidding_strategies[\"pp_learning\"] = RLStrategy\n", + "\n", + " # then we load the scenario specified above from the respective input files\n", + " load_scenario_folder(\n", + " world,\n", + " inputs_path=inputs_path,\n", + " scenario=scenario,\n", + " study_case=study_case,\n", + " )\n", + "\n", + " # run learning if learning mode is enabled\n", + " # needed as we simulate the modelling horizon multiple times to train reinforcement learning run_learning( world, inputs_path=input_path, scenario=scenario, study_case=study_case, )\n", + "\n", + " if world.learning_config.get(\"learning_mode\", False):\n", + " run_learning(\n", + " world,\n", + " inputs_path=inputs_path,\n", + " scenario=scenario,\n", + " study_case=study_case,\n", + " )\n", + "\n", + " # after the learning is done we make a normal run of the simulation, which equals a test run\n", + " world.run()\n", + "\n", + "!pip install matplotlib\n", + "\n", + "import os\n", + "from functools import partial\n", + "\n", + "import matplotlib.pyplot as plt\n", + "from sqlalchemy import create_engine\n", + "\n", + "os.makedirs(\"outputs\", exist_ok=True)\n", + "\n", + "db_uri = \"sqlite:///local_db/assume_db.db\"\n", + "\n", + "engine = create_engine(db_uri)\n", + "\n", + "\n", + "sql = \"\"\"\n", + "SELECT ident, simulation,\n", + "sum(round(CAST(value AS numeric), 2)) FILTER (WHERE variable = 'total_cost') as total_cost,\n", + "sum(round(CAST(value AS numeric), 2)*1000) FILTER (WHERE variable = 'total_volume') as total_volume,\n", + "sum(round(CAST(value AS numeric), 2)) FILTER (WHERE variable = 'avg_price') as average_cost\n", + "FROM kpis\n", + "where variable in ('total_cost', 'total_volume', 'avg_price')\n", + "and simulation in ('example_02a_base', 'example_02b_base', 'example_02c_base')\n", + "group by simulation, ident ORDER BY simulation\n", + "\"\"\"\n", + "\n", + "\n", + "kpis = pd.read_sql(sql, engine)\n", + "\n", + "# sort the dataframe to have sho, bo and lo case in the right order\n", + "\n", + "# sort kpis in the order sho, bo, lo\n", + "\n", + "kpis = kpis.sort_values(\n", + " by=\"simulation\",\n", + " # key=lambda x: x.map({\"example_02a\": 1, \"example_02b\": 2, \"example_02c\": 3}),\n", + ")\n", + "\n", + "\n", + "kpis[\"total_volume\"] /= 1e9\n", + "kpis[\"total_cost\"] /= 1e6\n", + "savefig = partial(plt.savefig, transparent=False, bbox_inches=\"tight\")\n", + "\n", + "xticks = kpis[\"simulation\"].unique()\n", + "plt.style.use(\"seaborn-v0_8\")\n", + "\n", + "fig, ax = plt.subplots(1, 1, figsize=(10, 6))\n", + "\n", + "ax2 = ax.twinx() # Create another axes that shares the same x-axis as ax.\n", + "\n", + "width = 0.4\n", + "\n", + "kpis.total_volume.plot(kind=\"bar\", ax=ax, width=width, position=1, color=\"royalblue\")\n", + "kpis.total_cost.plot(kind=\"bar\", ax=ax2, width=width, position=0, color=\"green\")\n", + "\n", + "# set x-achxis limits\n", + "ax.set_xlim(-0.6, len(kpis[\"simulation\"]) - 0.4)\n", + "\n", + "# set y-achxis limits\n", + "ax.set_ylim(0, max(kpis.total_volume) * 1.1 + 0.1)\n", + "ax2.set_ylim(0, max(kpis.total_cost) * 1.1 + 0.1)\n", + "\n", + "ax.set_ylabel(\"Total Volume (GWh)\")\n", + "ax2.set_ylabel(\"Total Cost (M€)\")\n", + "\n", + "ax.set_xticklabels(xticks, rotation=45)\n", + "ax.set_xlabel(\"Simulation\")\n", + "\n", + "ax.legend([\"Total Volume\"], loc=\"upper left\")\n", + "ax2.legend([\"Total Cost\"], loc=\"upper right\")\n", + "\n", + "plt.title(\"Total Volume and Total Cost for each Simulation\")\n", + "\n", + "sql = \"\"\"\n", + "SELECT\n", + " product_start AS \"time\",\n", + " price AS \"Price\",\n", + " simulation AS \"simulation\",\n", + " node\n", + "FROM market_meta\n", + "WHERE simulation in ('example_02a_base', 'example_02b_base', 'example_02c_base') AND market_id in ('EOM') \n", + "GROUP BY market_id, simulation, product_start, price, node\n", + "ORDER BY product_start, node\n", + "\n", + "\"\"\"\n", + "\n", + "df = pd.read_sql(sql, engine)\n", + "\n", + "df\n", + "\n", + "# Convert the 'time' column to datetime\n", + "df[\"time\"] = pd.to_datetime(df[\"time\"])\n", + "\n", + "# Plot the data\n", + "plt.figure(figsize=(14, 7))\n", + "# Loop through each simulation and plot\n", + "for simulation in df[\"simulation\"].unique():\n", + " subset = df[df[\"simulation\"] == simulation]\n", + " plt.plot(subset[\"time\"], subset[\"Price\"], label=simulation)\n", + "\n", + "plt.title(\"Price over Time for Different Simulations\")\n", + "plt.xlabel(\"Time\")\n", + "plt.ylabel(\"Price\")\n", + "plt.legend(title=\"Simulation\")\n", + "plt.show()\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "97f4d181", + "metadata": {}, + "outputs": [], "source": [] } ], diff --git a/examples/notebooks/05_market_comparison.ipynb b/examples/notebooks/05_market_comparison.ipynb index f451942b..1d413d83 100644 --- a/examples/notebooks/05_market_comparison.ipynb +++ b/examples/notebooks/05_market_comparison.ipynb @@ -70,14 +70,17 @@ "base_uri": "https://localhost:8080/" }, "id": "m0DaRwFA7VgW", - "outputId": "5655adad-5b7a-4fe3-9067-6b502a06136b", - "vscode": { - "languageId": "shellscript" - } + "outputId": "5655adad-5b7a-4fe3-9067-6b502a06136b" }, "outputs": [], "source": [ - "!pip install assume-framework\n", + "import importlib.util\n", + "\n", + "# Check if 'google.colab' is available\n", + "IN_COLAB = importlib.util.find_spec(\"google.colab\") is not None\n", + "\n", + "if IN_COLAB:\n", + " !pip install assume-framework\n", "!pip install matplotlib" ] }, @@ -91,14 +94,11 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "vscode": { - "languageId": "shellscript" - } - }, + "metadata": {}, "outputs": [], "source": [ - "!git clone --depth=1 https://github.com/assume-framework/assume.git assume-repo" + "if IN_COLAB:\n", + " !git clone --depth=1 https://github.com/assume-framework/assume.git assume-repo" ] }, { @@ -116,11 +116,6 @@ "metadata": {}, "outputs": [], "source": [ - "import importlib.util\n", - "\n", - "# Check if 'google.colab' is available\n", - "IN_COLAB = importlib.util.find_spec(\"google.colab\") is not None\n", - "\n", "colab_inputs_path = \"assume-repo/examples/inputs\"\n", "local_inputs_path = \"../inputs\"\n", "\n", @@ -197,7 +192,7 @@ "\n", "This is a behavior which works similarly well for both markets, though the results can not be taken to reality for various reasons.\n", "\n", - "But lets look at the scenarios themselves:" + "But let's look at the scenarios themselves:" ] }, { @@ -247,7 +242,7 @@ "For more information on the market configuration and an example gantt chart, look here:\n", "https://assume.readthedocs.io/en/latest/market_config.html\n", "\n", - "So lets run this:" + "So let's run this:" ] }, { @@ -276,14 +271,11 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "vscode": { - "languageId": "shellscript" - } - }, + "metadata": {}, "outputs": [], "source": [ - "!cd assume && assume -s example_01f -c eom_case -db \"sqlite:///./examples/local_db/assume_db_example_01f.db\"" + "if IN_COLAB:\n", + " !cd assume-repo && assume -s example_01f -c eom_case -db \"sqlite:///./examples/local_db/assume_db_example_01f.db\"" ] }, { @@ -361,14 +353,11 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "vscode": { - "languageId": "shellscript" - } - }, + "metadata": {}, "outputs": [], "source": [ - "!cd assume && assume -s example_01f -c ltm_case -db \"sqlite:///./examples/local_db/assume_db_example_01f.db\"" + "if IN_COLAB:\n", + " !cd assume-repo && assume -s example_01f -c ltm_case -db \"sqlite:///./examples/local_db/assume_db_example_01f.db\"" ] }, { @@ -377,7 +366,7 @@ "id": "zMyZhaNM7NRP" }, "source": [ - "## 3. Visualize the results\n", + "## 5. Visualize the results\n", "\n", "We can visualize the results using the following functions" ] @@ -535,7 +524,7 @@ "toc_visible": true }, "kernelspec": { - "display_name": "assume-testing-minimal", + "display_name": "assume", "language": "python", "name": "python3" }, @@ -549,7 +538,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.9" + "version": "3.12.7" }, "nbsphinx": { "execute": "never" diff --git a/examples/notebooks/06_advanced_orders_example.ipynb b/examples/notebooks/06_advanced_orders_example.ipynb index 535869db..aac25c64 100644 --- a/examples/notebooks/06_advanced_orders_example.ipynb +++ b/examples/notebooks/06_advanced_orders_example.ipynb @@ -12,24 +12,24 @@ "\n", "In this example the advanced strategies using regular block orders and linked orders are presented and their impact on the market outcome both on system and unit level compared to strategies using only single hourly orders.\n", "\n", - "With the integration of block orders, minimum acceptance ratios are added to the orders as additional field. To account for those in the clearing, a new market clearing algorithm becomes necessary.\n", + "With the integration of block orders, minimum acceptance ratios are added to the orders as an additional field. To account for those in the clearing, a new market clearing algorithm becomes necessary.\n", "\n", - "In this tutorial, we will show, how to create and integrate this advanced market clearing and adjust bidding strategies to allow the use of regular block orders and linked orders.\n", + "In this tutorial, we will show how to create and integrate this advanced market clearing and adjust bidding strategies to allow for the use of regular block orders and linked orders.\n", "Finally, we will create a small comparison study of the results using matplotlib.\n", "\n", "**As a whole, this tutorial covers the following**\n", "\n", - "1. Explain the basic rules of block and linked orders.\n", + "1. [Explain the basic rules of block and linked orders.](#1-basics)\n", "\n", - "2. Run a small example with single hourly orders.\n", + "2. [Run a small example with single hourly orders.](#2-get-assume-running)\n", "\n", - "3. Create the new market clearing algorithm.\n", + "3. [Create the new market clearing algorithm.](#3-market-clearing-algorithm)\n", "\n", - "4. Adjust a given strategy to integrate block orders.\n", + "4. [Adjust a given strategy to integrate block orders.](#4-block-orders)\n", "\n", - "5. Adjust a given strategy to integrate linked orders.\n", + "5. [Adjust a given strategy to integrate linked orders.](#5-linked-orders)\n", "\n", - "6. Extract graphs from the simulation run and interpret results." + "6. [Extract graphs from the simulation run and interpret results.](#6-using-advanced-orders-effects-of-regular-block-orders-and-linked-orders)" ] }, { @@ -88,7 +88,7 @@ "\n", "According to flexABLE, the inflexible and flexible power of a unit is bid separately, compare [Qussous et al. 2022](https://doi.org/10.3390/en15020494).\n", "\n", - "The inflexible power $P^{\\mathrm{inflex}}_{t}$ at time $t$ is the minimum volume that can by dispatched. \n", + "The inflexible power $P^{\\mathrm{inflex}}_{t}$ at time $t$ is the minimum volume that can be dispatched. \n", "It is defined by the current operation status of the unit, ramp-down limitations and the must-run time.\n", "The inflexible bid price depends on the marginal cost $C^{\\mathrm{marginal}}_t$ at time $t$ and the power dispatch of the previous time step $P^{\\mathrm{dispatch}}_{t-1}$ and adds a markup, if the unit has to be started newly and a reduction, to prevent a shut-down, including the start-up costs $C^{\\mathrm{su}}_t$.\n", "Here, the average time of continuous operation is given by $T^{\\mathrm{op, avg}}$ and the average time of continuous shut down is given by $T^{\\mathrm{down, avg}}$:\n", @@ -131,14 +131,16 @@ "base_uri": "https://localhost:8080/" }, "id": "m0DaRwFA7VgW", - "outputId": "5655adad-5b7a-4fe3-9067-6b502a06136b", - "vscode": { - "languageId": "shellscript" - } + "outputId": "5655adad-5b7a-4fe3-9067-6b502a06136b" }, "outputs": [], "source": [ - "!pip install 'assume-framework'" + "import importlib.util\n", + "\n", + "# Check if 'google.colab' is available\n", + "IN_COLAB = importlib.util.find_spec(\"google.colab\") is not None\n", + "if IN_COLAB:\n", + " !pip install 'assume-framework'" ] }, { @@ -151,14 +153,11 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "vscode": { - "languageId": "shellscript" - } - }, + "metadata": {}, "outputs": [], "source": [ - "!git clone --depth=1 https://github.com/assume-framework/assume.git assume-repo" + "if IN_COLAB:\n", + " !git clone --depth=1 https://github.com/assume-framework/assume.git assume-repo" ] }, { @@ -176,11 +175,6 @@ "metadata": {}, "outputs": [], "source": [ - "import importlib.util\n", - "\n", - "# Check if 'google.colab' is available\n", - "IN_COLAB = importlib.util.find_spec(\"google.colab\") is not None\n", - "\n", "colab_inputs_path = \"assume-repo/examples/inputs\"\n", "local_inputs_path = \"../inputs\"\n", "\n", @@ -234,7 +228,7 @@ "* \"bid_type\" defines the order structure and can be \"SB\" for single hourly orders (Simple Bid), \"BB\" for block orders (Block Bid) or \"LB\" for linked orders (Linked Bid).\n", "* \"min_acceptance_ratio\" defines how much a bid can be curtailed before it is rejected. If it is set to 1, the bid is either accepted or rejected with it's full volume.\n", "* \"parent_bid_id\" is needed to include linked bids. Here the id of the parent order is defined, where the child order is linked to.\n", - "The market clearing algorithm then ensures, that the minimum acceptance ratio of the child order is less or equal to the one of its parent order." + "The market clearing algorithm then ensures that the minimum acceptance ratio of the child order is less or equal to the one of its parent order." ] }, { @@ -254,7 +248,7 @@ "from operator import itemgetter\n", "\n", "import pyomo.environ as pyo\n", - "from pyomo.opt import SolverFactory, TerminationCondition, check_available_solvers\n", + "from pyomo.opt import SolverFactory, TerminationCondition\n", "\n", "from assume.common.market_objects import MarketConfig, Orderbook\n", "from assume.markets.clearing_algorithms.complex_clearing import (\n", @@ -283,24 +277,31 @@ "metadata": {}, "outputs": [], "source": [ - "SOLVERS = [\"appsi_highs\", \"gurobi\"]\n", "EPS = 1e-4\n", "\n", "\n", - "def market_clearing_opt(orders, market_products, mode, with_linked_bids):\n", + "def market_clearing_opt(\n", + " orders: Orderbook,\n", + " market_products,\n", + " mode,\n", + " with_linked_bids,\n", + " incidence_matrix: pd.DataFrame = None,\n", + " lines: pd.DataFrame = None,\n", + " solver: str = \"appsi_highs\",\n", + " solver_options: dict = {},\n", + "):\n", " \"\"\"\n", " Sets up and solves the market clearing optimization problem.\n", "\n", - " Args:\n", - " orders (Orderbook): The list of the orders.\n", - " market_products (list[MarketProduct]): The products to be traded.\n", - " mode (str): The mode of the market clearing determining whether the minimum acceptance ratio is considered.\n", - " with_linked_bids (bool): Whether the market clearing should include linked bids.\n", - "\n", - " Returns:\n", - " tuple[pyomo.ConcreteModel, pyomo.opt.results.SolverResults]: The solved pyomo model and the solver results.\n", " \"\"\"\n", - " # initiate the pyomo model\n", + " # Set nodes and lines based on the incidence matrix and lines DataFrame\n", + " if incidence_matrix is not None:\n", + " nodes = list(incidence_matrix.index)\n", + " line_ids = list(incidence_matrix.columns)\n", + " else:\n", + " nodes = [\"node0\"]\n", + " line_ids = [\"line0\"]\n", + "\n", " model = pyo.ConcreteModel()\n", "\n", " # add dual suffix to the model (we need this to extract the market clearing prices later)\n", @@ -308,11 +309,11 @@ " if mode != \"with_min_acceptance_ratio\":\n", " model.dual = pyo.Suffix(direction=pyo.Suffix.IMPORT_EXPORT)\n", "\n", - " # add sets for the orders, timesteps, and bids, to specify the indexes for the decision variables\n", " model.T = pyo.Set(\n", " initialize=[market_product[0] for market_product in market_products],\n", " doc=\"timesteps\",\n", " )\n", + "\n", " model.sBids = pyo.Set(\n", " initialize=[order[\"bid_id\"] for order in orders if order[\"bid_type\"] == \"SB\"],\n", " doc=\"simple_bids\",\n", @@ -323,16 +324,17 @@ " ],\n", " doc=\"block_bids\",\n", " )\n", - " model.nodes = pyo.Set(initialize=[\"node0\"], doc=\"nodes\")\n", "\n", - " # decision variables: the acceptance ratio of simple bids\n", + " model.nodes = pyo.Set(initialize=nodes, doc=\"nodes\")\n", + " model.lines = pyo.Set(initialize=line_ids, doc=\"lines\")\n", + "\n", + " # decision variables for the acceptance ratio of simple and block bids (including linked bids)\n", " model.xs = pyo.Var(\n", " model.sBids,\n", " domain=pyo.NonNegativeReals,\n", " bounds=(0, 1),\n", " doc=\"simple_bid_acceptance\",\n", " )\n", - " # decision variables: the acceptance ratio of block bids (including linked bids)\n", " model.xb = pyo.Var(\n", " model.bBids,\n", " domain=pyo.NonNegativeReals,\n", @@ -340,10 +342,13 @@ " doc=\"block_bid_acceptance\",\n", " )\n", "\n", - " # if the mode is 'with_min_acceptance_ratio', add the binary decision variables for the acceptance\n", - " # and the minimum acceptance ratio constraints\n", + " # decision variables that define flows between nodes\n", + " # assuming the orders contain the node and are collected in nodes\n", + " if incidence_matrix is not None:\n", + " # Decision variables for flows on each line at each timestep\n", + " model.flows = pyo.Var(model.T, model.lines, domain=pyo.Reals, doc=\"power_flows\")\n", + "\n", " if mode == \"with_min_acceptance_ratio\":\n", - " # add set for all bids, since the minimum acceptance ratio constraints are defined for all bids\n", " model.Bids = pyo.Set(\n", " initialize=[order[\"bid_id\"] for order in orders], doc=\"all_bids\"\n", " )\n", @@ -355,16 +360,10 @@ " )\n", "\n", " # add minimum acceptance ratio constraints\n", - " \"\"\"\n", - " Minimum acceptance constraints are defined as:\n", - " acceptance ratio (decision variable) >= min_acceptance_ratio * acceptance (binary decision variable)\n", - " acceptance ratio (decision variable) <= acceptance (binary decision variable)\n", - " \"\"\"\n", " model.mar_constr = pyo.ConstraintList()\n", " for order in orders:\n", " if order[\"min_acceptance_ratio\"] is None:\n", " continue\n", - "\n", " elif order[\"bid_type\"] == \"SB\":\n", " model.mar_constr.add(\n", " model.xs[order[\"bid_id\"]]\n", @@ -382,31 +381,8 @@ " model.mar_constr.add(\n", " model.xb[order[\"bid_id\"]] <= model.x[order[\"bid_id\"]]\n", " )\n", - " # add energy balance constraint\n", - " \"\"\"\n", - " Energy balance is defined as:\n", - " sum over all orders of (acceptance reatio (decision variable) * offered volume) = 0\n", - " \"\"\"\n", - " balance_expr = {t: 0.0 for t in model.T}\n", - " for order in orders:\n", - " if order[\"bid_type\"] == \"SB\":\n", - " balance_expr[order[\"start_time\"]] += (\n", - " order[\"volume\"] * model.xs[order[\"bid_id\"]]\n", - " )\n", - " elif order[\"bid_type\"] in [\"BB\", \"LB\"]:\n", - " for start_time, volume in order[\"volume\"].items():\n", - " balance_expr[start_time] += volume * model.xb[order[\"bid_id\"]]\n", - "\n", - " def energy_balance_rule(m, t):\n", - " return balance_expr[t] == 0\n", - "\n", - " model.energy_balance = pyo.Constraint(model.T, rule=energy_balance_rule)\n", "\n", " # limit the acceptance of child bids by the acceptance of their parent bid\n", - " \"\"\"\n", - " The linked bid constraints are defined as:\n", - " acceptance ratio of child bid (decision variable) <= acceptance ratio of parent bid (decision variable)\n", - " \"\"\"\n", " if with_linked_bids:\n", " model.linked_bid_constr = pyo.ConstraintList()\n", " for order in orders:\n", @@ -416,12 +392,60 @@ " model.xb[order[\"bid_id\"]] <= model.xb[parent_bid_id]\n", " )\n", "\n", + " # Function to calculate the balance for each node and time\n", + " def energy_balance_rule(model, node, t):\n", + " \"\"\"\n", + " Calculate the energy balance for a given node and time.\n", + "\n", + " This function calculates the energy balance for a specific node and time in a complex clearing algorithm. It iterates over the orders and adjusts the balance expression based on the bid type. It also adjusts the flow subtraction to account for actual connections if an incidence matrix is provided.\n", + "\n", + " Args:\n", + " model: The complex clearing model.\n", + " node: The node for which to calculate the energy balance.\n", + " t: The time for which to calculate the energy balance.\n", + "\n", + " Returns:\n", + " bool: True if the energy balance is zero, False otherwise.\n", + " \"\"\"\n", + " balance_expr = 0.0 # Initialize the balance expression\n", + " # Iterate over orders to adjust the balance expression based on bid type\n", + " for order in orders:\n", + " if (\n", + " order[\"bid_type\"] == \"SB\"\n", + " and order[\"node\"] == node\n", + " and order[\"start_time\"] == t\n", + " ):\n", + " balance_expr += order[\"volume\"] * model.xs[order[\"bid_id\"]]\n", + " elif order[\"bid_type\"] in [\"BB\", \"LB\"] and order[\"node\"] == node:\n", + " for start_time, volume in order[\"volume\"].items():\n", + " if start_time == t:\n", + " balance_expr += volume * model.xb[order[\"bid_id\"]]\n", + "\n", + " # Add contributions from line flows based on the incidence matrix\n", + " if incidence_matrix is not None:\n", + " for line in model.lines:\n", + " incidence_value = incidence_matrix.loc[node, line]\n", + " if incidence_value != 0:\n", + " balance_expr += incidence_value * model.flows[t, line]\n", + "\n", + " return balance_expr == 0\n", + "\n", + " # Add the energy balance constraints for each node and time period using the rule\n", + " # Define the energy balance constraint using two indices (node and time)\n", + " model.energy_balance = pyo.Constraint(\n", + " model.nodes, model.T, rule=energy_balance_rule\n", + " )\n", + "\n", + " if incidence_matrix is not None:\n", + " model.transmission_constr = pyo.ConstraintList()\n", + " for t in model.T:\n", + " for line in model.lines:\n", + " capacity = lines.at[line, \"s_nom\"]\n", + " # Limit the flow on each line\n", + " model.transmission_constr.add(model.flows[t, line] <= capacity)\n", + " model.transmission_constr.add(model.flows[t, line] >= -capacity)\n", + "\n", " # define the objective function as cost minimization\n", - " \"\"\"\n", - " The objective function is defined as:\n", - " sum over all orders of (price * volume * acceptance ratio (decision variable))\n", - " The sense of the objective function is minimize.\n", - " \"\"\"\n", " obj_expr = 0\n", " for order in orders:\n", " if order[\"bid_type\"] == \"SB\":\n", @@ -432,45 +456,24 @@ "\n", " model.objective = pyo.Objective(expr=obj_expr, sense=pyo.minimize)\n", "\n", - " # check available solvers, gurobi is preferred\n", - " solvers = check_available_solvers(*SOLVERS)\n", - " if len(solvers) < 1:\n", - " raise Exception(f\"None of {SOLVERS} are available\")\n", - "\n", - " solver = SolverFactory(solvers[0])\n", - "\n", - " if solver.name == \"gurobi\":\n", - " options = {\"cutoff\": -1.0, \"MIPGap\": EPS}\n", - " elif solver.name == \"cplex\":\n", - " options = {\n", - " \"mip.tolerances.lowercutoff\": -1.0,\n", - " \"mip.tolerances.absmipgap\": EPS,\n", - " }\n", - " elif solver.name == \"cbc\":\n", - " options = {\"sec\": 60, \"ratio\": 0.1}\n", - " else:\n", - " options = {}\n", - "\n", + " solver = SolverFactory(solver)\n", " # Solve the model\n", " instance = model.create_instance()\n", - " results = solver.solve(instance, options=options)\n", + " results = solver.solve(instance, options=solver_options)\n", "\n", - " \"\"\"\n", - " After solving the model, \n", - " fix the acceptance of each order to the value in the solution and \n", - " solve the model again as simple linear problem.\n", - " This is necessary to get dual variables.\n", - " \"\"\"\n", - " # fix all model.x to the values in the solution\n", + " # Fix all model.x to the values in the solution\n", " if mode == \"with_min_acceptance_ratio\":\n", - " # add dual suffix to the model (we need this to extract the market clearing prices later)\n", + " # Add dual suffix to the model (needed to extract duals later)\n", " instance.dual = pyo.Suffix(direction=pyo.Suffix.IMPORT_EXPORT)\n", "\n", " for bid_id in instance.Bids:\n", + " # Fix the binary variable to its value\n", " instance.x[bid_id].fix(instance.x[bid_id].value)\n", + " # Change the domain to Reals (or appropriate continuous domain)\n", + " instance.x[bid_id].domain = pyo.Reals\n", "\n", - " # resolve the model\n", - " results = solver.solve(instance, options=options)\n", + " # Resolve the model\n", + " results = solver.solve(instance, options=solver_options)\n", "\n", " return instance, results" ] @@ -481,7 +484,7 @@ "source": [ "So this function defines how the objective is solved. Let's create the market clearing algorithm as a MarketRole inheriting from the ComplexClearingRole in the ASSUME framework.\n", "\n", - "First, we define the class ComplexClearRole and initiate it.\n", + "First, we define the class ComplexClearingRole and initiate it.\n", "Then, we specify the main function to clear the market using the function market_clearing_opt() to calculate the market outcome as optimization:" ] }, @@ -502,17 +505,49 @@ "\n", " def validate_orderbook(self, orderbook: Orderbook, agent_addr) -> None:\n", " \"\"\"\n", - " This function ensures that the bid type is in ['SB', 'BB', 'LB'] and that the order volume is not larger than the maximum bid volume.\n", + " Checks whether the bid types are valid and whether the volumes are within the maximum bid volume.\n", "\n", " Args:\n", - " orderbook: The orderbook\n", - " agent_addr: The agent address\n", + " orderbook (Orderbook): The orderbook to be validated.\n", + " agent_addr (AgentAddress): The agent address of the market.\n", "\n", " Raises:\n", - " AssertionError: If the bid type is not in ['SB', 'BB', 'LB'] or the order volume is larger than the maximum bid volume\n", + " ValueError: If the bid type is invalid.\n", " \"\"\"\n", + " market_id = self.marketconfig.market_id\n", + " max_volume = self.marketconfig.maximum_bid_volume\n", + "\n", + " for order in orderbook:\n", + " # if bid_type is None, set to default bid_type\n", + " if order[\"bid_type\"] is None:\n", + " order[\"bid_type\"] = \"SB\"\n", + " # Validate bid_type\n", + " elif order[\"bid_type\"] not in [\"SB\", \"BB\", \"LB\"]:\n", + " order[\"bid_type\"] = \"SB\" # Set to default bid_type\n", + "\n", " super().validate_orderbook(orderbook, agent_addr)\n", "\n", + " for order in orderbook:\n", + " # Validate volumes\n", + " if order[\"bid_type\"] in [\"BB\", \"LB\"]:\n", + " for key, volume in order.get(\"volume\", {}).items():\n", + " if abs(volume) > max_volume:\n", + " order[\"volume\"][key] = max_volume if volume > 0 else -max_volume\n", + "\n", + " # Node validation\n", + " node = order.get(\"node\")\n", + " if node:\n", + " if self.zones_id:\n", + " node = self.node_to_zone.get(node, self.nodes[0])\n", + " order[\"node\"] = node\n", + " if node not in self.nodes:\n", + " order[\"node\"] = self.nodes[0]\n", + " else:\n", + " if self.incidence_matrix is not None:\n", + " order[\"node\"] = self.nodes[0]\n", + " else:\n", + " order[\"node\"] = \"node0\"\n", + "\n", " def clear(\n", " self, orderbook: Orderbook, market_products\n", " ) -> tuple[Orderbook, Orderbook, list[dict]]:\n", @@ -530,6 +565,13 @@ " accepted_orders (Orderbook): The accepted orders.\n", " rejected_orders (Orderbook): The rejected orders.\n", " meta (list[dict]): The market clearing results.\n", + "\n", + " Notes:\n", + " First the market clearing is solved using the cost minimization with the pyomo model market_clearing_opt.\n", + " Then the market clearing prices are extracted from the solved model as dual variables of the energy balance constraint.\n", + " Next the surplus of each order and its children is calculated and orders with negative surplus are removed from the orderbook.\n", + " This is repeated until all orders remaining in the orderbook have positive surplus.\n", + " Optional additional fields are: min_acceptance_ratio, parent_bid_id, node\n", " \"\"\"\n", "\n", " if len(orderbook) == 0:\n", @@ -538,7 +580,6 @@ " orderbook.sort(key=itemgetter(\"start_time\", \"end_time\", \"only_hours\"))\n", "\n", " # create a list of all orders linked as child to a bid\n", - " # this helps to late check the surplus for linked bids\n", " child_orders = []\n", " for order in orderbook:\n", " order[\"accepted_price\"] = {}\n", @@ -552,14 +593,13 @@ " )\n", " if parent_bid is None:\n", " order[\"parent_bid_id\"] = None\n", - " log.warning(f\"Parent bid {parent_bid_id} not in orderbook\")\n", " else:\n", " child_orders.append(order)\n", "\n", " with_linked_bids = bool(child_orders)\n", + "\n", " rejected_orders: Orderbook = []\n", "\n", - " # check whether the minimum acceptance ratio is specified\n", " mode = \"default\"\n", " if \"min_acceptance_ratio\" in self.marketconfig.additional_fields:\n", " mode = \"with_min_acceptance_ratio\"\n", @@ -572,18 +612,21 @@ " market_products=market_products,\n", " mode=mode,\n", " with_linked_bids=with_linked_bids,\n", + " incidence_matrix=self.incidence_matrix,\n", + " lines=self.lines,\n", + " solver=self.solver,\n", + " solver_options=self.solver_options,\n", " )\n", "\n", " if results.solver.termination_condition == TerminationCondition.infeasible:\n", " raise Exception(\"infeasible\")\n", "\n", " # extract dual from model.energy_balance\n", - " # we iterate over nodes to make a nested dictionary since this\n", - " # is the format the later functions expect\n", " market_clearing_prices = {}\n", " for node in self.nodes:\n", " market_clearing_prices[node] = {\n", - " t: instance.dual[instance.energy_balance[t]] for t in instance.T\n", + " t: instance.dual[instance.energy_balance[node, t]]\n", + " for t in instance.T\n", " }\n", "\n", " # check the surplus of each order and remove those with negative surplus\n", @@ -591,16 +634,12 @@ " for order in orderbook:\n", " children = []\n", " if with_linked_bids:\n", - " # get all children of the current order\n", " children = [\n", " child\n", " for child in child_orders\n", " if child[\"parent_bid_id\"] == order[\"bid_id\"]\n", " ]\n", "\n", - " # here we use the predefined fluction calculate_order_surplus,\n", - " # the surplus is given as (market_clearing_price - order_price) * order_volume\n", - " # the surplus of children is added to the surplus of the parent bid if positive\n", " order_surplus = calculate_order_surplus(\n", " order, market_clearing_prices, instance, children\n", " )\n", @@ -623,27 +662,27 @@ " if all(order_surplus >= 0 for order_surplus in orders_surplus):\n", " break\n", "\n", - " # here we use the predefined function extract_results,\n", - " # it returns the accepted and rejected orders, and the market meta data for each timestep\n", - " # if you want to take a closer look, please refer to our documentation.\n", - " accepted_orders, rejected_orders, meta = extract_results(\n", + " log_flows = True\n", + "\n", + " accepted_orders, rejected_orders, meta, flows = extract_results(\n", " model=instance,\n", " orders=orderbook,\n", " rejected_orders=rejected_orders,\n", " market_products=market_products,\n", " market_clearing_prices=market_clearing_prices,\n", + " log_flows=log_flows,\n", " )\n", "\n", " self.all_orders = []\n", "\n", - " return accepted_orders, rejected_orders, meta" + " return accepted_orders, rejected_orders, meta, flows" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "So lets add the advanced clearing algorithm to the possible clearing mechanisms in world and load the example." + "So let's add the advanced clearing algorithm to the possible clearing mechanisms in world and load the example." ] }, { @@ -674,7 +713,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "In the market config we have 24 single market product which have a duration of one hour.\n", + "In the market config we have 24 single market products which have a duration of one hour.\n", "And every time the market opens, the next 24 hours can be traded (see count).\n", "The first delivery of the market is 24 hours after the opening of the market (to have some spare time before delivery).\n", "\n", @@ -900,6 +939,7 @@ " \"price\": bid_price_flex, # ====== new\n", " \"volume\": bid_quantity_flex, # ====== new\n", " \"bid_type\": \"SB\", # ====== new\n", + " \"node\": unit.node,\n", " },\n", " )\n", " # calculate previous power with planned dispatch (bid_quantity)\n", @@ -931,6 +971,7 @@ " \"accepted_volume\": {\n", " product[0]: 0 for product in product_tuples\n", " }, # ====== new\n", + " \"node\": unit.node,\n", " }\n", " )\n", "\n", @@ -967,9 +1008,9 @@ "source": [ "With this the strategy is ready to test.\n", "As before, we add the new class to our world and load the scenario.\n", - "Additionally, we now have to change the set bidding strategy for one example unit. Here we choose the combined cycle gas turbine and set its strategy to our modified class 'blockStrategy'.\n", + "Additionally, we now have to change the set bidding strategy for one example unit. Here, we choose the combined cycle gas turbine and set its strategy to our modified class 'blockStrategy'.\n", "\n", - "Don't forget to add also the defined advanced clearing mechanism to the newly generated world." + "Don't forget to also add the defined advanced clearing mechanism to the newly generated world." ] }, { @@ -1001,7 +1042,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Now lets run this example" + "Now let's run this example" ] }, { @@ -1173,6 +1214,7 @@ " \"volume\": {start: bid_quantity_flex}, # ====== new\n", " \"bid_type\": \"LB\", # ====== new\n", " \"parent_bid_id\": parent_id, # ====== new\n", + " \"node\": unit.node,\n", " },\n", " )\n", " # calculate previous power with planned dispatch (bid_quantity)\n", @@ -1200,6 +1242,7 @@ " \"min_acceptance_ratio\": 1,\n", " \"accepted_volume\": {product[0]: 0 for product in product_tuples},\n", " \"bid_id\": block_id,\n", + " \"node\": unit.node,\n", " }\n", " )\n", "\n", @@ -1479,7 +1522,7 @@ "toc_visible": true }, "kernelspec": { - "display_name": "assume-framework", + "display_name": "assume", "language": "python", "name": "python3" }, @@ -1493,7 +1536,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.7" + "version": "3.12.7" }, "nbsphinx": { "execute": "never" diff --git a/examples/notebooks/07_interoperability_example.ipynb b/examples/notebooks/07_interoperability_example.ipynb index fa584422..23ba567e 100644 --- a/examples/notebooks/07_interoperability_example.ipynb +++ b/examples/notebooks/07_interoperability_example.ipynb @@ -16,13 +16,13 @@ "\n", "**As a whole, this tutorial covers the following**\n", "\n", - "1. running a small scenario from CSV folder with the CLI\n", + "1. [running a small scenario from CSV folder with the CLI](#1-scenario-from-cli)\n", "\n", - "2. creating a small simulation from scratch as shown in tutorial 01\n", + "2. [creating a small simulation from scratch as shown in tutorial 01](#2-run-from-a-script-to-customize-scenario-yourself)\n", "\n", - "3. load a scenario from an AMIRIS scenario.yaml\n", + "3. [load a scenario from an AMIRIS scenario.yaml](#3-load-amiris-scenario)\n", "\n", - "4. load a scenario from a pypsa network" + "4. [load a scenario from a pypsa network](#4-load-pypsa-scenario)" ] }, { @@ -256,7 +256,7 @@ }, "outputs": [], "source": [ - "!cd .. && git clone https://gitlab.com/dlr-ve/esy/amiris/examples.git amiris-examples" + "!cd inputs && git clone https://gitlab.com/dlr-ve/esy/amiris/examples.git amiris-examples" ] }, { @@ -276,7 +276,7 @@ "from assume.scenario.loader_amiris import load_amiris\n", "\n", "scenario = \"Simple\" # Germany20{15-19}, Austria2019 or Simple\n", - "base_path = f\"../amiris-examples/{scenario}/\"\n", + "base_path = f\"inputs/amiris-examples/{scenario}/\"\n", "\n", "# make sure that you have a database server up and running - preferabely in docker\n", "# DB_URI = \"postgresql://assume:assume@localhost:5432/assume\"\n", diff --git a/examples/notebooks/08_market_zone_coupling.ipynb b/examples/notebooks/08_market_zone_coupling.ipynb index ef23084c..608f187f 100644 --- a/examples/notebooks/08_market_zone_coupling.ipynb +++ b/examples/notebooks/08_market_zone_coupling.ipynb @@ -1,8676 +1,1950 @@ { - "cells": [ - { - "cell_type": "markdown", - "id": "ff81547a", - "metadata": { - "id": "ff81547a" - }, - "source": [ - "# 8. Market Zone Coupling in the ASSUME Framework\n", - "\n", - "Welcome to the **Market Zone Coupling** tutorial for the ASSUME framework. In this workshop, we will guide you through understanding how market zone coupling is implemented within the ASSUME simulation environment. By the end of this tutorial, you will gain insights into the internal mechanisms of the framework, including how different market zones interact, how constraints are managed, how bids are assigned, and how market prices are extracted.\n", - "\n", - "**We will cover the following topics:**\n", - "\n", - "1. **Introduction to Market Zone Coupling**\n", - "2. **Setting Up the ASSUME Framework for Market Zone Coupling**\n", - "3. **Understanding the Market Clearing Optimization**\n", - "4. **Creating Exemplary Input Files for Market Zone Coupling**\n", - " - 4.1. Defining Buses and Zones\n", - " - 4.2. Configuring Transmission Lines\n", - " - 4.3. Setting Up Power Plant and Demand Units\n", - " - 4.4. Preparing Demand Data\n", - "5. **Understanding the Market Clearing with Zone Coupling**\n", - " - 5.1. Calculating the Incidence Matrix\n", - " - 5.2. Implementing the Simplified Market Clearing Function\n", - " - 5.3. Running the Market Clearing Simulation\n", - " - 5.4. Extracting and Interpreting the Results\n", - " - 5.5. Comparing Simulations\n", - "6. **Execution with ASSUME**\n", - "7. **Analyzing the Results**\n", - "\n", - "Let's get started!" - ] - }, - { - "cell_type": "markdown", - "id": "76281e67", - "metadata": { - "id": "76281e67" - }, - "source": [ - "## 1. Introduction to Market Zone Coupling\n", - "\n", - "**Market Zone Coupling** is a mechanism that enables different geographical zones within an electricity market to interact and trade energy seamlessly. In the ASSUME framework, implementing market zone coupling is straightforward: by properly defining the input data and configuration files, the framework automatically manages the interactions between zones, including transmission constraints and cross-zone trading.\n", - "\n", - "This tutorial aims to provide a deeper understanding of how market zone coupling operates within ASSUME. While the framework handles much of the complexity internally, we will explore the underlying processes, such as the calculation of transmission capacities and the market clearing optimization. This detailed walkthrough is designed to enhance your comprehension of the framework's capabilities and the dynamics of multi-zone electricity markets.\n", - "\n", - "Throughout this tutorial, you will:\n", - "\n", - "- **Define Multiple Market Zones:** Segment the market into distinct zones based on geographical or operational criteria.\n", - "- **Configure Transmission Lines:** Establish connections that allow energy flow between different market zones.\n", - "- **Understand the Market Clearing Process:** Examine how the market clearing algorithm accounts for interactions and constraints across zones.\n", - "\n", - "By the end of this workshop, you will not only know how to set up market zone coupling in ASSUME but also gain insights into the internal mechanisms that drive market interactions and price formations across different zones." - ] - }, - { - "cell_type": "markdown", - "id": "42ff364e", - "metadata": { - "id": "42ff364e" - }, - "source": [ - "## 2. Setting Up the ASSUME Framework for Market Zone Coupling\n", - "\n", - "Before diving into market zone coupling, ensure that you have the ASSUME framework installed and set up correctly. If you haven't done so already, follow the steps below to install the ASSUME core package and clone the repository containing predefined scenarios.\n", - "\n", - "**Note:** If you already have the ASSUME framework installed and the repository cloned, you can skip executing the following code cells." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "0dd1c254", - "metadata": { - "id": "0dd1c254", - "vscode": { - "languageId": "shellscript" - } - }, - "outputs": [], - "source": [ - "# Install the ASSUME framework\n", - "!pip install assume-framework\n", - "\n", - "# Install Plotly if not already installed\n", - "!pip install plotly" - ] - }, - { - "cell_type": "markdown", - "id": "4266c838", - "metadata": { - "id": "4266c838" - }, - "source": [ - "Let's also import some basic libraries that we will use throughout the tutorial." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "a1543685", - "metadata": { - "id": "a1543685" - }, - "outputs": [], - "source": [ - "import pandas as pd\n", - "\n", - "# import plotly for visualization\n", - "import plotly.graph_objects as go\n", - "\n", - "# import yaml for reading and writing YAML files\n", - "import yaml\n", - "\n", - "# Function to display DataFrame in Jupyter\n", - "from IPython.display import display" - ] - }, - { - "cell_type": "markdown", - "id": "902fc3a9", - "metadata": { - "id": "902fc3a9" - }, - "source": [ - "## 3. Understanding the Market Clearing Optimization\n", - "\n", - "Market clearing is a crucial component of electricity market simulations. It involves determining the optimal dispatch of supply and demand bids to maximize social welfare while respecting network constraints.\n", - "\n", - "In the context of market zone coupling, the market clearing process must account for:\n", - "\n", - "- **Connection Between Zones:** Transmission lines that allow energy flow between different market zones.\n", - "- **Constraints:** Limits on transmission capacities and ensuring energy balance within and across zones.\n", - "- **Bid Assignment:** Properly assigning bids to their respective zones and considering cross-zone trading.\n", - "- **Price Extraction:** Determining market prices for each zone based on the cleared bids and network constraints.\n", - "\n", - "The ASSUME framework uses Pyomo to formulate and solve the market clearing optimization problem. Below is a simplified version of the market clearing function, highlighting key components related to zone coupling." - ] - }, - { - "cell_type": "markdown", - "id": "4f874cfd", - "metadata": { - "id": "4f874cfd" - }, - "source": [ - "### Simplified Market Clearing Optimization Problem\n", - "\n", - "We consider a simplified market clearing optimization model focusing on zone coupling, aiming to minimize the total cost.\n", - "\n", - "#### Sets and Variables:\n", - "- $T$: Set of time periods.\n", - "- $N$: Set of nodes (zones).\n", - "- $L$: Set of lines.\n", - "- $x_o \\in [0, 1]$: Bid acceptance ratio for order $o$.\n", - "- $f_{t, l} \\in \\mathbb{R}$: Power flow on line $l$ at time $t$.\n", - "\n", - "#### Constants:\n", - "- $P_o$: Price of order $o$.\n", - "- $V_o$: Volume of order $o$.\n", - "- $S_l$: Nominal capacity of line $l$.\n", - "\n", - "#### Objective Function:\n", - "Minimize the total cost of accepted orders:\n", - "\n", - "$$\n", - "\\min \\sum_{o \\in O} P_o V_o x_o\n", - "$$\n", - "\n", - "#### Constraints:\n", - "\n", - "1. **Energy Balance for Each Node and Time Period**:\n", - "\n", - "$$\n", - "\\sum_{\\substack{o \\in O \\\\ \\text{node}(o) = n \\\\ \\text{time}(o) = t}} V_o x_o + \\sum_{l \\in L} I_{n, l} f_{t, l} = 0 \\quad \\forall n \\in N, \\, t \\in T\n", - "$$\n", - "\n", - "Where:\n", - "- $I_{n, l}$ is the incidence value for node $n$ and line $l$ (from the incidence matrix).\n", - "\n", - "2. **Transmission Capacity Constraints for Each Line and Time Period**:\n", - "\n", - "$$\n", - "-S_l \\leq f_{t, l} \\leq S_l \\quad \\forall l \\in L, \\, t \\in T\n", - "$$\n", - "\n", - "#### Summary:\n", - "The goal is to minimize the total cost while ensuring energy balance at each node and respecting transmission line capacity limits for each time period.\n", - "\n", - "In actual ASSUME Framework, the optimization problem is more complex and includes additional constraints and variables, and supports also additional bid types such as block and linked orders. However, the simplified model above captures the essence of market clearing with zone coupling.\n" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "e2be3fe2", - "metadata": { - "id": "e2be3fe2" - }, - "outputs": [], - "source": [ - "import pyomo.environ as pyo\n", - "from pyomo.opt import SolverFactory, TerminationCondition\n", - "\n", - "\n", - "def simplified_market_clearing_opt(orders, incidence_matrix, lines):\n", - " \"\"\"\n", - " Simplified market clearing optimization focusing on zone coupling.\n", - "\n", - " Args:\n", - " orders (dict): Dictionary of orders with bid_id as keys.\n", - " lines (DataFrame): DataFrame containing information about the transmission lines.\n", - " incidence_matrix (DataFrame): Incidence matrix describing the network structure.\n", - "\n", - " Returns:\n", - " model (ConcreteModel): The solved Pyomo model.\n", - " results (SolverResults): The solver results.\n", - " \"\"\"\n", - " nodes = list(incidence_matrix.index)\n", - " line_ids = list(incidence_matrix.columns)\n", - "\n", - " model = pyo.ConcreteModel()\n", - " # Define dual suffix\n", - " model.dual = pyo.Suffix(direction=pyo.Suffix.IMPORT)\n", - "\n", - " # Define the set of time periods\n", - " model.T = pyo.Set(\n", - " initialize=sorted(set(order[\"time\"] for order in orders.values())),\n", - " doc=\"timesteps\",\n", - " )\n", - " # Define the set of nodes (zones)\n", - " model.nodes = pyo.Set(initialize=nodes, doc=\"nodes\")\n", - " # Define the set of lines\n", - " model.lines = pyo.Set(initialize=line_ids, doc=\"lines\")\n", - "\n", - " # Decision variables for bid acceptance ratios (0 to 1)\n", - " model.x = pyo.Var(\n", - " orders.keys(),\n", - " domain=pyo.NonNegativeReals,\n", - " bounds=(0, 1),\n", - " doc=\"bid_acceptance_ratio\",\n", - " )\n", - "\n", - " # Decision variables for power flows on each line at each time period\n", - " model.flows = pyo.Var(model.T, model.lines, domain=pyo.Reals, doc=\"power_flows\")\n", - "\n", - " # Energy balance constraint for each node and time period\n", - " def energy_balance_rule(model, node, t):\n", - " balance_expr = 0.0\n", - " # Add contributions from orders\n", - " for order_key, order in orders.items():\n", - " if order[\"node\"] == node and order[\"time\"] == t:\n", - " balance_expr += order[\"volume\"] * model.x[order_key]\n", - "\n", - " # Add contributions from line flows based on the incidence matrix\n", - " if incidence_matrix is not None:\n", - " for line in model.lines:\n", - " incidence_value = incidence_matrix.loc[node, line]\n", - " if incidence_value != 0:\n", - " balance_expr += incidence_value * model.flows[t, line]\n", - "\n", - " return balance_expr == 0\n", - "\n", - " model.energy_balance = pyo.Constraint(\n", - " model.nodes, model.T, rule=energy_balance_rule\n", - " )\n", - "\n", - " # Transmission capacity constraints for each line and time period\n", - " def transmission_capacity_rule(model, t, line):\n", - " \"\"\"\n", - " Limits the power flow on each line based on its capacity.\n", - " \"\"\"\n", - " capacity = lines.at[line, \"s_nom\"]\n", - " return (-capacity, model.flows[t, line], capacity)\n", - "\n", - " # Apply transmission capacity constraints to all lines and time periods\n", - " model.transmission_constraints = pyo.Constraint(\n", - " model.T, model.lines, rule=transmission_capacity_rule\n", - " )\n", - "\n", - " # Objective: Minimize total cost (sum of bid prices multiplied by accepted volumes)\n", - " model.objective = pyo.Objective(\n", - " expr=sum(orders[o][\"price\"] * orders[o][\"volume\"] * model.x[o] for o in orders),\n", - " sense=pyo.minimize,\n", - " doc=\"Total Cost Minimization\",\n", - " )\n", - "\n", - " # Choose the solver (HIGHS is used here)\n", - " solver = SolverFactory(\"appsi_highs\")\n", - " results = solver.solve(model)\n", - "\n", - " # Check if the solver found an optimal solution\n", - " if results.solver.termination_condition != TerminationCondition.optimal:\n", - " raise Exception(\"Solver did not find an optimal solution.\")\n", - "\n", - " return model, results" - ] - }, - { - "cell_type": "markdown", - "id": "8d42c532", - "metadata": { - "id": "8d42c532" - }, - "source": [ - "The above function is a simplified representation focusing on the essential aspects of market zone coupling. In the following sections, we will delve deeper into creating input files and mimicking the market clearing process using this function to understand the inner workings of the ASSUME framework." - ] - }, - { - "cell_type": "markdown", - "id": "11addaf0", - "metadata": { - "id": "11addaf0" - }, - "source": [ - "## 4. Creating Exemplary Input Files for Market Zone Coupling\n", - "\n", - "To implement market zone coupling, users need to prepare specific input files that define the network's structure, units, and demand profiles. Below, we will guide you through creating the necessary DataFrames for buses, transmission lines, power plant units, demand units, and demand profiles." - ] - }, - { - "cell_type": "markdown", - "id": "2a095ffb", - "metadata": { - "id": "2a095ffb" - }, - "source": [ - "### 4.1. Defining Buses and Zones\n", - "\n", - "**Buses** represent nodes in the network where energy can be injected or withdrawn. Each bus is assigned to a **zone**, which groups buses into market areas. This zoning facilitates market coupling by managing interactions between different market regions." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "c1731cdc", - "metadata": { - "cellView": "form", - "colab": { - "base_uri": "https://localhost:8080/", - "height": 192 - }, - "id": "c1731cdc", - "outputId": "0d0a8060-aa86-4ba8-a0b1-0e528bc9d0d2" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Buses DataFrame:\n" - ] - }, - { - "data": { - "application/vnd.google.colaboratory.intrinsic+json": { - "summary": "{\n \"name\": \"buses\",\n \"rows\": 3,\n \"fields\": [\n {\n \"column\": \"name\",\n \"properties\": {\n \"dtype\": \"string\",\n \"num_unique_values\": 3,\n \"samples\": [\n \"north_1\",\n \"north_2\",\n \"south\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"v_nom\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 0.0,\n \"min\": 380.0,\n \"max\": 380.0,\n \"num_unique_values\": 1,\n \"samples\": [\n 380.0\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"zone_id\",\n \"properties\": {\n \"dtype\": \"string\",\n \"num_unique_values\": 2,\n \"samples\": [\n \"DE_2\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"x\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 1.0969655114602888,\n \"min\": 9.5,\n \"max\": 11.6,\n \"num_unique_values\": 3,\n \"samples\": [\n 10.0\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"y\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 3.2715949219506575,\n \"min\": 48.1,\n \"max\": 54.0,\n \"num_unique_values\": 3,\n \"samples\": [\n 54.0\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n }\n ]\n}", - "type": "dataframe", - "variable_name": "buses" - }, - "text/html": [ - "\n", - "
\n", - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
v_nomzone_idxy
name
north_1380.0DE_110.054.0
north_2380.0DE_19.553.5
south380.0DE_211.648.1
\n", - "
\n", - "
\n", - "\n", - "
\n", - " \n", - "\n", - " \n", - "\n", - " \n", - "
\n", - "\n", - "\n", - "
\n", - " \n", - "\n", - "\n", - "\n", - " \n", - "
\n", - "\n", - "
\n", - " \n", - " \n", - " \n", - "
\n", - "\n", - "
\n", - "
\n" - ], - "text/plain": [ - " v_nom zone_id x y\n", - "name \n", - "north_1 380.0 DE_1 10.0 54.0\n", - "north_2 380.0 DE_1 9.5 53.5\n", - "south 380.0 DE_2 11.6 48.1" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "# @title Define the buses DataFrame with three nodes and two zones\n", - "buses = pd.DataFrame(\n", - " {\n", - " \"name\": [\"north_1\", \"north_2\", \"south\"],\n", - " \"v_nom\": [380.0, 380.0, 380.0],\n", - " \"zone_id\": [\"DE_1\", \"DE_1\", \"DE_2\"],\n", - " \"x\": [10.0, 9.5, 11.6],\n", - " \"y\": [54.0, 53.5, 48.1],\n", - " }\n", - ").set_index(\"name\")\n", - "\n", - "# Display the buses DataFrame\n", - "print(\"Buses DataFrame:\")\n", - "display(buses)" - ] - }, - { - "cell_type": "markdown", - "id": "50a27c51", - "metadata": { - "id": "50a27c51" - }, - "source": [ - "**Explanation:**\n", - "\n", - "- **name:** Identifier for each bus (`north_1`, `north_2`, and `south`).\n", - "- **v_nom:** Nominal voltage level (in kV) for all buses.\n", - "- **zone_id:** Identifier for the market zone to which the bus belongs (`DE_1` for north buses and `DE_2` for the south bus).\n", - "- **x, y:** Geographical coordinates (optional, can be used for mapping or spatial analyses)." - ] - }, - { - "cell_type": "markdown", - "id": "1545e3bf", - "metadata": { - "id": "1545e3bf" - }, - "source": [ - "### 4.2. Configuring Transmission Lines\n", - "\n", - "**Transmission Lines** connect buses, allowing energy to flow between them. Each line has a specified capacity and electrical parameters." - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "64769ec7", - "metadata": { - "cellView": "form", - "colab": { - "base_uri": "https://localhost:8080/", - "height": 192 - }, - "id": "64769ec7", - "outputId": "a47490cb-d06c-4152-8be6-64985a8dcbd0" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Transmission Lines DataFrame:\n" - ] - }, - { - "data": { - "application/vnd.google.colaboratory.intrinsic+json": { - "summary": "{\n \"name\": \"lines\",\n \"rows\": 3,\n \"fields\": [\n {\n \"column\": \"name\",\n \"properties\": {\n \"dtype\": \"string\",\n \"num_unique_values\": 3,\n \"samples\": [\n \"Line_N1_S\",\n \"Line_N2_S\",\n \"Line_N1_N2\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"bus0\",\n \"properties\": {\n \"dtype\": \"string\",\n \"num_unique_values\": 2,\n \"samples\": [\n \"north_2\",\n \"north_1\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"bus1\",\n \"properties\": {\n \"dtype\": \"string\",\n \"num_unique_values\": 2,\n \"samples\": [\n \"north_2\",\n \"south\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"s_nom\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 0.0,\n \"min\": 5000.0,\n \"max\": 5000.0,\n \"num_unique_values\": 1,\n \"samples\": [\n 5000.0\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"x\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 0.0,\n \"min\": 0.01,\n \"max\": 0.01,\n \"num_unique_values\": 1,\n \"samples\": [\n 0.01\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"r\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 0.0,\n \"min\": 0.001,\n \"max\": 0.001,\n \"num_unique_values\": 1,\n \"samples\": [\n 0.001\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n }\n ]\n}", - "type": "dataframe", - "variable_name": "lines" - }, - "text/html": [ - "\n", - "
\n", - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
bus0bus1s_nomxr
name
Line_N1_Snorth_1south5000.00.010.001
Line_N2_Snorth_2south5000.00.010.001
Line_N1_N2north_1north_25000.00.010.001
\n", - "
\n", - "
\n", - "\n", - "
\n", - " \n", - "\n", - " \n", - "\n", - " \n", - "
\n", - "\n", - "\n", - "
\n", - " \n", - "\n", - "\n", - "\n", - " \n", - "
\n", - "\n", - "
\n", - " \n", - " \n", - " \n", - "
\n", - "\n", - "
\n", - "
\n" - ], - "text/plain": [ - " bus0 bus1 s_nom x r\n", - "name \n", - "Line_N1_S north_1 south 5000.0 0.01 0.001\n", - "Line_N2_S north_2 south 5000.0 0.01 0.001\n", - "Line_N1_N2 north_1 north_2 5000.0 0.01 0.001" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "# @title Define three transmission lines\n", - "lines = pd.DataFrame(\n", - " {\n", - " \"name\": [\"Line_N1_S\", \"Line_N2_S\", \"Line_N1_N2\"],\n", - " \"bus0\": [\"north_1\", \"north_2\", \"north_1\"],\n", - " \"bus1\": [\"south\", \"south\", \"north_2\"],\n", - " \"s_nom\": [5000.0, 5000.0, 5000.0],\n", - " \"x\": [0.01, 0.01, 0.01],\n", - " \"r\": [0.001, 0.001, 0.001],\n", - " }\n", - ").set_index(\"name\")\n", - "\n", - "print(\"Transmission Lines DataFrame:\")\n", - "display(lines)" - ] - }, - { - "cell_type": "markdown", - "id": "f2290793", - "metadata": { - "id": "f2290793" - }, - "source": [ - "**Explanation:**\n", - "\n", - "- **name:** Identifier for each transmission line (`Line_N1_S`, `Line_N2_S`, and `Line_N1_N2`).\n", - "- **bus0, bus1:** The two buses that the line connects.\n", - "- **s_nom:** Nominal apparent power capacity of the line (in MVA).\n", - "- **x:** Reactance of the line (in per unit).\n", - "- **r:** Resistance of the line (in per unit)." - ] - }, - { - "cell_type": "markdown", - "id": "c931cf9f", - "metadata": { - "id": "c931cf9f" - }, - "source": [ - "### 4.3. Setting Up Power Plant and Demand Units\n", - "\n", - "**Power Plant Units** represent energy generation sources, while **Demand Units** represent consumption. Each unit is associated with a specific bus (node) and has operational parameters that define its behavior in the market." - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "8a1f9e35", - "metadata": { - "cellView": "form", - "colab": { - "base_uri": "https://localhost:8080/", - "height": 224 - }, - "id": "8a1f9e35", - "outputId": "b7d43816-40af-4526-bb64-53d4a20ba911" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Power Plant Units DataFrame:\n" - ] - }, - { - "data": { - "application/vnd.google.colaboratory.intrinsic+json": { - "summary": "{\n \"name\": \"display(powerplant_units\",\n \"rows\": 5,\n \"fields\": [\n {\n \"column\": \"name\",\n \"properties\": {\n \"dtype\": \"string\",\n \"num_unique_values\": 5,\n \"samples\": [\n \"Unit 2\",\n \"Unit 5\",\n \"Unit 3\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"technology\",\n \"properties\": {\n \"dtype\": \"category\",\n \"num_unique_values\": 1,\n \"samples\": [\n \"nuclear\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"bidding_zonal\",\n \"properties\": {\n \"dtype\": \"category\",\n \"num_unique_values\": 1,\n \"samples\": [\n \"naive_eom\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"fuel_type\",\n \"properties\": {\n \"dtype\": \"category\",\n \"num_unique_values\": 1,\n \"samples\": [\n \"uranium\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"emission_factor\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 0.0,\n \"min\": 0.0,\n \"max\": 0.0,\n \"num_unique_values\": 1,\n \"samples\": [\n 0.0\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"max_power\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 0.0,\n \"min\": 1000.0,\n \"max\": 1000.0,\n \"num_unique_values\": 1,\n \"samples\": [\n 1000.0\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"min_power\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 0.0,\n \"min\": 0.0,\n \"max\": 0.0,\n \"num_unique_values\": 1,\n \"samples\": [\n 0.0\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"efficiency\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 0.0,\n \"min\": 0.3,\n \"max\": 0.3,\n \"num_unique_values\": 1,\n \"samples\": [\n 0.3\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"additional_cost\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 1,\n \"min\": 5,\n \"max\": 9,\n \"num_unique_values\": 5,\n \"samples\": [\n 6\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"node\",\n \"properties\": {\n \"dtype\": \"category\",\n \"num_unique_values\": 1,\n \"samples\": [\n \"north_1\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"unit_operator\",\n \"properties\": {\n \"dtype\": \"category\",\n \"num_unique_values\": 1,\n \"samples\": [\n \"Operator North\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n }\n ]\n}", - "type": "dataframe" - }, - "text/html": [ - "\n", - "
\n", - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
nametechnologybidding_zonalfuel_typeemission_factormax_powermin_powerefficiencyadditional_costnodeunit_operator
0Unit 1nuclearnaive_eomuranium0.01000.00.00.35north_1Operator North
1Unit 2nuclearnaive_eomuranium0.01000.00.00.36north_1Operator North
2Unit 3nuclearnaive_eomuranium0.01000.00.00.37north_1Operator North
3Unit 4nuclearnaive_eomuranium0.01000.00.00.38north_1Operator North
4Unit 5nuclearnaive_eomuranium0.01000.00.00.39north_1Operator North
\n", - "
\n", - "
\n", - "\n", - "
\n", - " \n", - "\n", - " \n", - "\n", - " \n", - "
\n", - "\n", - "\n", - "
\n", - " \n", - "\n", - "\n", - "\n", - " \n", - "
\n", - "\n", - "
\n", - "
\n" - ], - "text/plain": [ - " name technology bidding_zonal fuel_type emission_factor max_power \\\n", - "0 Unit 1 nuclear naive_eom uranium 0.0 1000.0 \n", - "1 Unit 2 nuclear naive_eom uranium 0.0 1000.0 \n", - "2 Unit 3 nuclear naive_eom uranium 0.0 1000.0 \n", - "3 Unit 4 nuclear naive_eom uranium 0.0 1000.0 \n", - "4 Unit 5 nuclear naive_eom uranium 0.0 1000.0 \n", - "\n", - " min_power efficiency additional_cost node unit_operator \n", - "0 0.0 0.3 5 north_1 Operator North \n", - "1 0.0 0.3 6 north_1 Operator North \n", - "2 0.0 0.3 7 north_1 Operator North \n", - "3 0.0 0.3 8 north_1 Operator North \n", - "4 0.0 0.3 9 north_1 Operator North " - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "# @title Create the power plant units DataFrame\n", - "\n", - "# Define the total number of units\n", - "num_units = 30 # Reduced for simplicity\n", - "\n", - "# Generate the 'name' column: Unit 1 to Unit 30\n", - "names = [f\"Unit {i}\" for i in range(1, num_units + 1)]\n", - "\n", - "# All other columns with constant values\n", - "technology = [\"nuclear\"] * num_units\n", - "bidding_zonal = [\"naive_eom\"] * num_units\n", - "fuel_type = [\"uranium\"] * num_units\n", - "emission_factor = [0.0] * num_units\n", - "max_power = [1000.0] * num_units\n", - "min_power = [0.0] * num_units\n", - "efficiency = [0.3] * num_units\n", - "\n", - "# Generate 'additional_cost':\n", - "# - North units (1-15): 5 to 19\n", - "# - South units (16-30): 20 to 34\n", - "additional_cost = list(range(5, 5 + num_units))\n", - "\n", - "# Initialize 'node' and 'unit_operator' lists\n", - "node = []\n", - "unit_operator = []\n", - "\n", - "for i in range(1, num_units + 1):\n", - " if 1 <= i <= 8:\n", - " node.append(\"north_1\") # All north units connected to 'north_1'\n", - " unit_operator.append(\"Operator North\")\n", - " elif 9 <= i <= 15:\n", - " node.append(\"north_2\")\n", - " unit_operator.append(\"Operator North\")\n", - " else:\n", - " node.append(\"south\") # All south units connected to 'south'\n", - " unit_operator.append(\"Operator South\")\n", - "\n", - "# Create the DataFrame\n", - "powerplant_units = pd.DataFrame(\n", - " {\n", - " \"name\": names,\n", - " \"technology\": technology,\n", - " \"bidding_zonal\": bidding_zonal, # bidding strategy used to bid on the zonal market. Should be same name as in config file\n", - " \"fuel_type\": fuel_type,\n", - " \"emission_factor\": emission_factor,\n", - " \"max_power\": max_power,\n", - " \"min_power\": min_power,\n", - " \"efficiency\": efficiency,\n", - " \"additional_cost\": additional_cost,\n", - " \"node\": node,\n", - " \"unit_operator\": unit_operator,\n", - " }\n", - ")\n", - "\n", - "print(\"Power Plant Units DataFrame:\")\n", - "display(powerplant_units.head())" - ] - }, - { - "cell_type": "markdown", - "id": "Uwp8L0rombac", - "metadata": { - "id": "Uwp8L0rombac" - }, - "source": [ - "- **Power Plant Units:**\n", - " - **name:** Identifier for each power plant unit (`Unit 1` to `Unit 30`).\n", - " - **technology:** Type of technology (`nuclear` for all units).\n", - " - **bidding_nodal:** Bidding strategy used (`naive_eom` for all units).\n", - " - **fuel_type:** Type of fuel used (`uranium` for all units).\n", - " - **emission_factor:** Emissions per unit of energy produced (`0.0` for all units).\n", - " - **max_power, min_power:** Operational power limits (`1000.0` MW max, `0.0` MW min for all units).\n", - " - **efficiency:** Conversion efficiency (`0.3` for all units).\n", - " - **additional_cost:** Additional operational costs (`5` to `34`, with southern units being more expensive).\n", - " - **node:** The bus (zone) to which the unit is connected (`north_1` for units `1-15`, `south` for units `16-30`).\n", - " - **unit_operator:** Operator responsible for the unit (`Operator North` for northern units, `Operator South` for southern units)." - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "16f8a13c", - "metadata": { - "cellView": "form", - "colab": { - "base_uri": "https://localhost:8080/", - "height": 161 - }, - "id": "16f8a13c", - "outputId": "aad8a140-a6ed-47fd-d06e-1e794aa1a829" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Demand Units DataFrame:\n" - ] - }, - { - "data": { - "application/vnd.google.colaboratory.intrinsic+json": { - "summary": "{\n \"name\": \"demand_units\",\n \"rows\": 3,\n \"fields\": [\n {\n \"column\": \"name\",\n \"properties\": {\n \"dtype\": \"string\",\n \"num_unique_values\": 3,\n \"samples\": [\n \"demand_north_1\",\n \"demand_north_2\",\n \"demand_south\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"technology\",\n \"properties\": {\n \"dtype\": \"category\",\n \"num_unique_values\": 1,\n \"samples\": [\n \"inflex_demand\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"bidding_zonal\",\n \"properties\": {\n \"dtype\": \"category\",\n \"num_unique_values\": 1,\n \"samples\": [\n \"naive_eom\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"max_power\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 0,\n \"min\": 100000,\n \"max\": 100000,\n \"num_unique_values\": 1,\n \"samples\": [\n 100000\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"min_power\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 0,\n \"min\": 0,\n \"max\": 0,\n \"num_unique_values\": 1,\n \"samples\": [\n 0\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"unit_operator\",\n \"properties\": {\n \"dtype\": \"category\",\n \"num_unique_values\": 1,\n \"samples\": [\n \"eom_de\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"node\",\n \"properties\": {\n \"dtype\": \"string\",\n \"num_unique_values\": 3,\n \"samples\": [\n \"north_1\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n }\n ]\n}", - "type": "dataframe", - "variable_name": "demand_units" - }, - "text/html": [ - "\n", - "
\n", - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
nametechnologybidding_zonalmax_powermin_powerunit_operatornode
0demand_north_1inflex_demandnaive_eom1000000eom_denorth_1
1demand_north_2inflex_demandnaive_eom1000000eom_denorth_2
2demand_southinflex_demandnaive_eom1000000eom_desouth
\n", - "
\n", - "
\n", - "\n", - "
\n", - " \n", - "\n", - " \n", - "\n", - " \n", - "
\n", - "\n", - "\n", - "
\n", - " \n", - "\n", - "\n", - "\n", - " \n", - "
\n", - "\n", - "
\n", - " \n", - " \n", - " \n", - "
\n", - "\n", - "
\n", - "
\n" - ], - "text/plain": [ - " name technology bidding_zonal max_power min_power \\\n", - "0 demand_north_1 inflex_demand naive_eom 100000 0 \n", - "1 demand_north_2 inflex_demand naive_eom 100000 0 \n", - "2 demand_south inflex_demand naive_eom 100000 0 \n", - "\n", - " unit_operator node \n", - "0 eom_de north_1 \n", - "1 eom_de north_2 \n", - "2 eom_de south " - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "# @title Define the demand units\n", - "demand_units = pd.DataFrame(\n", - " {\n", - " \"name\": [\"demand_north_1\", \"demand_north_2\", \"demand_south\"],\n", - " \"technology\": [\"inflex_demand\"] * 3,\n", - " \"bidding_zonal\": [\"naive_eom\"] * 3,\n", - " \"max_power\": [100000, 100000, 100000],\n", - " \"min_power\": [0, 0, 0],\n", - " \"unit_operator\": [\"eom_de\"] * 3,\n", - " \"node\": [\"north_1\", \"north_2\", \"south\"],\n", - " }\n", - ")\n", - "\n", - "# Display the demand_units DataFrame\n", - "print(\"Demand Units DataFrame:\")\n", - "display(demand_units)" - ] - }, - { - "cell_type": "markdown", - "id": "d847ac5f", - "metadata": { - "id": "d847ac5f" - }, - "source": [ - "- **Demand Units:**\n", - " - **name:** Identifier for each demand unit (`demand_north_1`, `demand_north_2`, and `demand_south`).\n", - " - **technology:** Type of demand (`inflex_demand` for all units).\n", - " - **bidding_zonal:** Bidding strategy used (`naive_eom` for all units).\n", - " - **max_power, min_power:** Operational power limits (`100000` MW max, `0` MW min for all units).\n", - " - **unit_operator:** Operator responsible for the unit (`eom_de` for all units).\n", - " - **node:** The bus (zone) to which the unit is connected (`north_1`, `north_2`, and `south`)." - ] - }, - { - "cell_type": "markdown", - "id": "8f1d684a", - "metadata": { - "id": "8f1d684a" - }, - "source": [ - "### 4.4. Preparing Demand Data\n", - "\n", - "**Demand Data** provides the expected electricity demand for each demand unit over time. This data is essential for simulating how demand varies and affects market dynamics." - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "a0591f14", - "metadata": { - "cellView": "form", - "colab": { - "base_uri": "https://localhost:8080/", - "height": 255 - }, - "id": "a0591f14", - "outputId": "d590647b-7522-4fce-bfe7-dc66b7b566e8" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Demand DataFrame:\n" - ] - }, - { - "data": { - "application/vnd.google.colaboratory.intrinsic+json": { - "summary": "{\n \"name\": \"display(demand_df\",\n \"rows\": 5,\n \"fields\": [\n {\n \"column\": \"datetime\",\n \"properties\": {\n \"dtype\": \"date\",\n \"min\": \"2019-01-01 00:00:00\",\n \"max\": \"2019-01-01 04:00:00\",\n \"num_unique_values\": 5,\n \"samples\": [\n \"2019-01-01 01:00:00\",\n \"2019-01-01 04:00:00\",\n \"2019-01-01 02:00:00\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"demand_north_1\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 632,\n \"min\": 2400,\n \"max\": 4000,\n \"num_unique_values\": 5,\n \"samples\": [\n 2800,\n 4000,\n 3200\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"demand_north_2\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 632,\n \"min\": 2400,\n \"max\": 4000,\n \"num_unique_values\": 5,\n \"samples\": [\n 2800,\n 4000,\n 3200\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"demand_south\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 948,\n \"min\": 15000,\n \"max\": 17400,\n \"num_unique_values\": 5,\n \"samples\": [\n 16800,\n 15000,\n 16200\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n }\n ]\n}", - "type": "dataframe" - }, - "text/html": [ - "\n", - "
\n", - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
demand_north_1demand_north_2demand_south
datetime
2019-01-01 00:00:002400240017400
2019-01-01 01:00:002800280016800
2019-01-01 02:00:003200320016200
2019-01-01 03:00:003600360015600
2019-01-01 04:00:004000400015000
\n", - "
\n", - "
\n", - "\n", - "
\n", - " \n", - "\n", - " \n", - "\n", - " \n", - "
\n", - "\n", - "\n", - "
\n", - " \n", - "\n", - "\n", - "\n", - " \n", - "
\n", - "\n", - "
\n", - "
\n" - ], - "text/plain": [ - " demand_north_1 demand_north_2 demand_south\n", - "datetime \n", - "2019-01-01 00:00:00 2400 2400 17400\n", - "2019-01-01 01:00:00 2800 2800 16800\n", - "2019-01-01 02:00:00 3200 3200 16200\n", - "2019-01-01 03:00:00 3600 3600 15600\n", - "2019-01-01 04:00:00 4000 4000 15000" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "# @title Define the demand DataFrame\n", - "\n", - "# the demand for the north_1 and north_2 zones increases by 400 MW per hour\n", - "# while the demand for the south zone decreases by 600 MW per hour\n", - "# the demand starts at 2400 MW for the north zones and 17400 MW for the south zone\n", - "demand_df = pd.DataFrame(\n", - " {\n", - " \"datetime\": pd.date_range(start=\"2019-01-01\", periods=24, freq=\"h\"),\n", - " \"demand_north_1\": [2400 + i * 400 for i in range(24)],\n", - " \"demand_north_2\": [2400 + i * 400 for i in range(24)],\n", - " \"demand_south\": [17400 - i * 600 for i in range(24)],\n", - " }\n", - ")\n", - "\n", - "# Convert the 'datetime' column to datetime objects and set as index\n", - "demand_df.set_index(\"datetime\", inplace=True)\n", - "\n", - "# Display the demand_df DataFrame\n", - "print(\"Demand DataFrame:\")\n", - "display(demand_df.head())" - ] - }, - { - "cell_type": "markdown", - "id": "1756e6e3", - "metadata": { - "id": "1756e6e3" - }, - "source": [ - "**Explanation:**\n", - "\n", - "- **datetime:** Timestamp for each demand value.\n", - "- **demand_north_1, demand_north_2, demand_south:** Demand values for each respective demand unit.\n", - "\n", - "**Note:** The demand timeseries has been designed to be fulfillable by the defined power plants in both zones." - ] - }, - { - "cell_type": "markdown", - "id": "478211c6", - "metadata": { - "id": "478211c6" - }, - "source": [ - "## 5. Reproducing the Market Clearing Process\n", - "\n", - "With the input files prepared, we can now reproduce the market clearing process using the simplified market clearing function. This will help us understand how different market zones interact, how constraints are managed, how bids are assigned, and how market prices are extracted." - ] - }, - { - "cell_type": "markdown", - "id": "01680700", - "metadata": { - "id": "01680700" - }, - "source": [ - "### 5.1. Calculating the Incidence Matrix\n", - "\n", - "The **Incidence Matrix** represents the connection relationships between different nodes in a network. In the context of market zones, it indicates which transmission lines connect which zones. The incidence matrix is a binary matrix where each element denotes whether a particular node is connected to a line or not. This matrix is essential for understanding the structure of the transmission network and for formulating power flow equations during the market clearing process." - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "c9fb8458", - "metadata": { - "cellView": "form", - "colab": { - "base_uri": "https://localhost:8080/", - "height": 142 - }, - "id": "c9fb8458", - "outputId": "380d3471-2a05-4cf2-bd37-77b944a6dc98" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Calculated Incidence Matrix between Zones:\n" - ] - }, - { - "data": { - "application/vnd.google.colaboratory.intrinsic+json": { - "summary": "{\n \"name\": \"incidence_matrix\",\n \"rows\": 2,\n \"fields\": [\n {\n \"column\": \"Line_N1_S\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 1,\n \"min\": -1,\n \"max\": 1,\n \"num_unique_values\": 2,\n \"samples\": [\n -1,\n 1\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"Line_N2_S\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 1,\n \"min\": -1,\n \"max\": 1,\n \"num_unique_values\": 2,\n \"samples\": [\n -1,\n 1\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"Line_N1_N2\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 0,\n \"min\": 0,\n \"max\": 0,\n \"num_unique_values\": 1,\n \"samples\": [\n 0\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n }\n ]\n}", - "type": "dataframe", - "variable_name": "incidence_matrix" - }, - "text/html": [ - "\n", - "
\n", - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
Line_N1_SLine_N2_SLine_N1_N2
DE_1110
DE_2-1-10
\n", - "
\n", - "
\n", - "\n", - "
\n", - " \n", - "\n", - " \n", - "\n", - " \n", - "
\n", - "\n", - "\n", - "
\n", - " \n", - "\n", - "\n", - "\n", - " \n", - "
\n", - "\n", - "
\n", - " \n", - " \n", - " \n", - "
\n", - "\n", - "
\n", - "
\n" - ], - "text/plain": [ - " Line_N1_S Line_N2_S Line_N1_N2\n", - "DE_1 1 1 0\n", - "DE_2 -1 -1 0" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "# @title Create the incidence matrix\n", - "def create_incidence_matrix(lines, buses, zones_id=None):\n", - " # Determine nodes based on whether we're working with zones or individual buses\n", - " if zones_id:\n", - " nodes = buses[zones_id].unique() # Use zones as nodes\n", - " node_mapping = buses[zones_id].to_dict() # Map bus IDs to zones\n", - " else:\n", - " nodes = buses.index.values # Use buses as nodes\n", - " node_mapping = {bus: bus for bus in nodes} # Identity mapping for buses\n", - "\n", - " # Use the line indices as columns for the incidence matrix\n", - " line_indices = lines.index.values\n", - "\n", - " # Initialize incidence matrix as a DataFrame for easier label-based indexing\n", - " incidence_matrix = pd.DataFrame(0, index=nodes, columns=line_indices)\n", - "\n", - " # Fill in the incidence matrix by iterating over lines\n", - " for line_idx, line in lines.iterrows():\n", - " bus0 = line[\"bus0\"]\n", - " bus1 = line[\"bus1\"]\n", - "\n", - " # Retrieve mapped nodes (zones or buses)\n", - " node0 = node_mapping.get(bus0)\n", - " node1 = node_mapping.get(bus1)\n", - "\n", - " # Ensure both nodes are valid and part of the defined nodes\n", - " if (\n", - " node0 is not None\n", - " and node1 is not None\n", - " and node0 in nodes\n", - " and node1 in nodes\n", - " ):\n", - " if node0 != node1: # Only create incidence for different nodes\n", - " # Set incidence values: +1 for the \"from\" node and -1 for the \"to\" node\n", - " incidence_matrix.at[node0, line_idx] = (\n", - " 1 # Outgoing from bus0 (or zone0)\n", - " )\n", - " incidence_matrix.at[node1, line_idx] = -1 # Incoming to bus1 (or zone1)\n", - "\n", - " # Return the incidence matrix as a DataFrame\n", - " return incidence_matrix\n", - "\n", - "\n", - "# Calculate the incidence matrix\n", - "incidence_matrix = create_incidence_matrix(lines, buses, \"zone_id\")\n", - "\n", - "print(\"Calculated Incidence Matrix between Zones:\")\n", - "display(incidence_matrix)" - ] - }, - { - "cell_type": "markdown", - "id": "61e9050c", - "metadata": { - "id": "61e9050c" - }, - "source": [ - "**Explanation:**\n", - "\n", - "- **Nodes (Zones):** Extracted from the `buses` DataFrame (`DE_1` and `DE_2`).\n", - "- **Transmission Lines:** Iterated over to sum their capacities between different zones.\n", - "- **Bidirectional Flow Assumption:** Transmission capacities are added in both directions (`DE_1 -> DE_2` and `DE_2 -> DE_1`).\n", - "- **Lower Triangle Negative Values:** To indicate the opposite direction of power flow, capacities in the lower triangle of the matrix are converted to negative values." - ] - }, - { - "cell_type": "markdown", - "id": "12ccae5f", - "metadata": { - "id": "12ccae5f" - }, - "source": [ - "### 5.2. Creating and Mapping Market Orders\n", - "\n", - "We will construct a dictionary of market orders representing supply and demand bids from power plants and demand units.\n", - "The orders include details such as price, volume, location (node), and time. Once the orders are generated, they will be\n", - "mapped from nodes to corresponding zones using a pre-defined node-to-zone mapping." - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "id": "4f7366ae", - "metadata": { - "cellView": "form", - "colab": { - "base_uri": "https://localhost:8080/", - "height": 225 - }, - "id": "4f7366ae", - "outputId": "1c291cb1-8e7b-4e36-cce9-ddd00735225d" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "Sample Supply Order:\n" - ] - }, - { - "data": { - "text/plain": [ - "{'price': 5,\n", - " 'volume': 1000.0,\n", - " 'node': 'north_1',\n", - " 'time': Timestamp('2019-01-01 00:00:00')}" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "Sample Demand Order:\n" - ] - }, - { - "data": { - "text/plain": [ - "{'price': 100,\n", - " 'volume': -2400,\n", - " 'node': 'north_1',\n", - " 'time': Timestamp('2019-01-01 00:00:00')}" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "# @title Construct Orders and Map Nodes to Zones\n", - "# Initialize orders dictionary\n", - "orders = {}\n", - "\n", - "# Add power plant bids\n", - "for _, row in powerplant_units.iterrows():\n", - " bid_id = row[\"name\"]\n", - " for timestamp in demand_df.index:\n", - " orders[f\"{bid_id}_{timestamp}\"] = {\n", - " \"price\": row[\"additional_cost\"], # Assuming additional_cost as bid price\n", - " \"volume\": row[\"max_power\"], # Assuming max_power as bid volume\n", - " \"node\": row[\"node\"],\n", - " \"time\": timestamp,\n", - " }\n", - "\n", - "# Add demand bids\n", - "for _, row in demand_units.iterrows():\n", - " bid_id = row[\"name\"]\n", - " for timestamp in demand_df.index:\n", - " orders[f\"{bid_id}_{timestamp}\"] = {\n", - " \"price\": 100, # Demand bids with high price\n", - " \"volume\": -demand_df.loc[\n", - " timestamp, row[\"name\"]\n", - " ], # Negative volume for demand\n", - " \"node\": row[\"node\"],\n", - " \"time\": timestamp,\n", - " }\n", - "\n", - "# Display a sample order\n", - "print(\"\\nSample Supply Order:\")\n", - "display(orders[\"Unit 1_2019-01-01 00:00:00\"])\n", - "\n", - "print(\"\\nSample Demand Order:\")\n", - "display(orders[\"demand_north_1_2019-01-01 00:00:00\"])" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "id": "e8b8a17f", - "metadata": { - "cellView": "form", - "colab": { - "base_uri": "https://localhost:8080/", - "height": 224 - }, - "id": "e8b8a17f", - "outputId": "ae3db259-f2e7-4b60-91b1-ca130140fb30" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Mapped Orders:\n" - ] - }, - { - "data": { - "application/vnd.google.colaboratory.intrinsic+json": { - "summary": "{\n \"name\": \"display(pd\",\n \"rows\": 5,\n \"fields\": [\n {\n \"column\": \"price\",\n \"properties\": {\n \"dtype\": \"date\",\n \"min\": 5,\n \"max\": 5,\n \"num_unique_values\": 1,\n \"samples\": [\n 5\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"volume\",\n \"properties\": {\n \"dtype\": \"date\",\n \"min\": 1000.0,\n \"max\": 1000.0,\n \"num_unique_values\": 1,\n \"samples\": [\n 1000.0\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"node\",\n \"properties\": {\n \"dtype\": \"category\",\n \"num_unique_values\": 1,\n \"samples\": [\n \"DE_1\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"time\",\n \"properties\": {\n \"dtype\": \"date\",\n \"min\": \"2019-01-01 00:00:00\",\n \"max\": \"2019-01-01 04:00:00\",\n \"num_unique_values\": 5,\n \"samples\": [\n \"2019-01-01 01:00:00\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n }\n ]\n}", - "type": "dataframe" - }, - "text/html": [ - "\n", - "
\n", - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
pricevolumenodetime
Unit 1_2019-01-01 00:00:0051000.0DE_12019-01-01 00:00:00
Unit 1_2019-01-01 01:00:0051000.0DE_12019-01-01 01:00:00
Unit 1_2019-01-01 02:00:0051000.0DE_12019-01-01 02:00:00
Unit 1_2019-01-01 03:00:0051000.0DE_12019-01-01 03:00:00
Unit 1_2019-01-01 04:00:0051000.0DE_12019-01-01 04:00:00
\n", - "
\n", - "
\n", - "\n", - "
\n", - " \n", - "\n", - " \n", - "\n", - " \n", - "
\n", - "\n", - "\n", - "
\n", - " \n", - "\n", - "\n", - "\n", - " \n", - "
\n", - "\n", - "
\n", - "
\n" - ], - "text/plain": [ - " price volume node time\n", - "Unit 1_2019-01-01 00:00:00 5 1000.0 DE_1 2019-01-01 00:00:00\n", - "Unit 1_2019-01-01 01:00:00 5 1000.0 DE_1 2019-01-01 01:00:00\n", - "Unit 1_2019-01-01 02:00:00 5 1000.0 DE_1 2019-01-01 02:00:00\n", - "Unit 1_2019-01-01 03:00:00 5 1000.0 DE_1 2019-01-01 03:00:00\n", - "Unit 1_2019-01-01 04:00:00 5 1000.0 DE_1 2019-01-01 04:00:00" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "# @title Map the orders to zones\n", - "# Create a mapping from node_id to zone_id\n", - "node_mapping = buses[\"zone_id\"].to_dict()\n", - "\n", - "# Create a new dictionary with mapped zone IDs\n", - "orders_mapped = {}\n", - "for bid_id, bid in orders.items():\n", - " original_node = bid[\"node\"]\n", - " mapped_zone = node_mapping.get(\n", - " original_node, original_node\n", - " ) # Default to original_node if not found\n", - " orders_mapped[bid_id] = {\n", - " \"price\": bid[\"price\"],\n", - " \"volume\": bid[\"volume\"],\n", - " \"node\": mapped_zone, # Replace bus with zone ID\n", - " \"time\": bid[\"time\"],\n", - " }\n", - "\n", - "# Display the mapped orders\n", - "print(\"Mapped Orders:\")\n", - "display(pd.DataFrame(orders_mapped).T.head())" - ] - }, - { - "cell_type": "markdown", - "id": "1a5d589c", - "metadata": { - "id": "1a5d589c" - }, - "source": [ - "**Explanation:**\n", - "\n", - "- **Power Plant Bids:** Each power plant unit submits a bid for each time period with its `additional_cost` as the bid price and `max_power` as the bid volume.\n", - "- **Demand Bids:** Each demand unit submits a bid for each time period with a high price (set to 100) and a negative volume representing the demand.\n", - "- **Node to Zone Mapping:** After creating the bids, the node information is mapped to corresponding zones for further market clearing steps.\n", - " The mapping uses a pre-defined dictionary (`node_mapping`) to replace each node ID with the corresponding zone ID. In ASSUME, this mapping happens automatically on the market side, but we are simulating it here for educational purposes." - ] - }, - { - "cell_type": "markdown", - "id": "f11b487c", - "metadata": { - "id": "f11b487c" - }, - "source": [ - "### 5.3. Running the Market Clearing Simulation\n", - "\n", - "We will conduct three simulations:\n", - "\n", - "1. **Simulation 1:** Transmission capacities between `DE_1` (north) and `DE_2` (south) are **zero**.\n", - "2. **Simulation 2:** Transmission capacities between `DE_1` (north) and `DE_2` (south) are **medium**.\n", - "3. **Simulation 3:** Transmission capacities between `DE_1` (north) and `DE_2` (south) are **high**." - ] - }, - { - "cell_type": "markdown", - "id": "07082c73", - "metadata": { - "id": "07082c73" - }, - "source": [ - "#### Simulation 1: Zero Transmission Capacity Between Zones" - ] - }, - { - "cell_type": "code", - "execution_count": 36, - "id": "1c7dfee2", - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 210 - }, - "id": "1c7dfee2", - "outputId": "86090b82-98e1-4d3b-bb1b-74b3c1c37e43" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "### Simulation 1: Zero Transmission Capacity Between Zones\n", - "Transmission Lines for Simulation 1:\n" - ] - }, - { - "data": { - "application/vnd.google.colaboratory.intrinsic+json": { - "summary": "{\n \"name\": \"lines_sim1\",\n \"rows\": 3,\n \"fields\": [\n {\n \"column\": \"name\",\n \"properties\": {\n \"dtype\": \"string\",\n \"num_unique_values\": 3,\n \"samples\": [\n \"Line_N1_S\",\n \"Line_N2_S\",\n \"Line_N1_N2\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"bus0\",\n \"properties\": {\n \"dtype\": \"string\",\n \"num_unique_values\": 2,\n \"samples\": [\n \"north_2\",\n \"north_1\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"bus1\",\n \"properties\": {\n \"dtype\": \"string\",\n \"num_unique_values\": 2,\n \"samples\": [\n \"north_2\",\n \"south\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"s_nom\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 0,\n \"min\": 0,\n \"max\": 0,\n \"num_unique_values\": 1,\n \"samples\": [\n 0\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"x\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 0.0,\n \"min\": 0.01,\n \"max\": 0.01,\n \"num_unique_values\": 1,\n \"samples\": [\n 0.01\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"r\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 0.0,\n \"min\": 0.001,\n \"max\": 0.001,\n \"num_unique_values\": 1,\n \"samples\": [\n 0.001\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n }\n ]\n}", - "type": "dataframe", - "variable_name": "lines_sim1" - }, - "text/html": [ - "\n", - "
\n", - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
bus0bus1s_nomxr
name
Line_N1_Snorth_1south00.010.001
Line_N2_Snorth_2south00.010.001
Line_N1_N2north_1north_200.010.001
\n", - "
\n", - "
\n", - "\n", - "
\n", - " \n", - "\n", - " \n", - "\n", - " \n", - "
\n", - "\n", - "\n", - "
\n", - " \n", - "\n", - "\n", - "\n", - " \n", - "
\n", - "\n", - "
\n", - " \n", - " \n", - " \n", - "
\n", - "\n", - "
\n", - "
\n" - ], - "text/plain": [ - " bus0 bus1 s_nom x r\n", - "name \n", - "Line_N1_S north_1 south 0 0.01 0.001\n", - "Line_N2_S north_2 south 0 0.01 0.001\n", - "Line_N1_N2 north_1 north_2 0 0.01 0.001" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "print(\"### Simulation 1: Zero Transmission Capacity Between Zones\")\n", - "\n", - "lines_sim1 = lines.copy()\n", - "lines_sim1[\"s_nom\"] = 0 # Set transmission capacity to zero for all lines\n", - "\n", - "print(\"Transmission Lines for Simulation 1:\")\n", - "display(lines_sim1)\n", - "\n", - "# Run the simplified market clearing for Simulation 1\n", - "model_sim1, results_sim1 = simplified_market_clearing_opt(\n", - " orders=orders_mapped,\n", - " incidence_matrix=incidence_matrix,\n", - " lines=lines_sim1,\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "aef7c083", - "metadata": { - "id": "aef7c083" - }, - "source": [ - "#### Simulation 2: Medium Transmission Capacity Between Zones" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "id": "86304253", - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 210 - }, - "id": "86304253", - "outputId": "3fa73e8b-d0e3-4fe8-d88c-1a896fb3e1ff" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "### Simulation 2: Medium Transmission Capacity Between Zones\n", - "Transmission Lines for Simulation 2:\n" - ] - }, - { - "data": { - "application/vnd.google.colaboratory.intrinsic+json": { - "summary": "{\n \"name\": \"lines_sim2\",\n \"rows\": 3,\n \"fields\": [\n {\n \"column\": \"name\",\n \"properties\": {\n \"dtype\": \"string\",\n \"num_unique_values\": 3,\n \"samples\": [\n \"Line_N1_S\",\n \"Line_N2_S\",\n \"Line_N1_N2\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"bus0\",\n \"properties\": {\n \"dtype\": \"string\",\n \"num_unique_values\": 2,\n \"samples\": [\n \"north_2\",\n \"north_1\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"bus1\",\n \"properties\": {\n \"dtype\": \"string\",\n \"num_unique_values\": 2,\n \"samples\": [\n \"north_2\",\n \"south\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"s_nom\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 0.0,\n \"min\": 3000.0,\n \"max\": 3000.0,\n \"num_unique_values\": 1,\n \"samples\": [\n 3000.0\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"x\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 0.0,\n \"min\": 0.01,\n \"max\": 0.01,\n \"num_unique_values\": 1,\n \"samples\": [\n 0.01\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"r\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 0.0,\n \"min\": 0.001,\n \"max\": 0.001,\n \"num_unique_values\": 1,\n \"samples\": [\n 0.001\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n }\n ]\n}", - "type": "dataframe", - "variable_name": "lines_sim2" - }, - "text/html": [ - "\n", - "
\n", - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
bus0bus1s_nomxr
name
Line_N1_Snorth_1south3000.00.010.001
Line_N2_Snorth_2south3000.00.010.001
Line_N1_N2north_1north_23000.00.010.001
\n", - "
\n", - "
\n", - "\n", - "
\n", - " \n", - "\n", - " \n", - "\n", - " \n", - "
\n", - "\n", - "\n", - "
\n", - " \n", - "\n", - "\n", - "\n", - " \n", - "
\n", - "\n", - "
\n", - " \n", - " \n", - " \n", - "
\n", - "\n", - "
\n", - "
\n" - ], - "text/plain": [ - " bus0 bus1 s_nom x r\n", - "name \n", - "Line_N1_S north_1 south 3000.0 0.01 0.001\n", - "Line_N2_S north_2 south 3000.0 0.01 0.001\n", - "Line_N1_N2 north_1 north_2 3000.0 0.01 0.001" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "print(\"### Simulation 2: Medium Transmission Capacity Between Zones\")\n", - "\n", - "# Define the lines for Simulation 2 with medium transmission capacity\n", - "lines_sim2 = lines.copy()\n", - "lines_sim2[\"s_nom\"] = 3000.0 # Set transmission capacity to 3000 MW for all lines\n", - "\n", - "# Display the incidence matrix for Simulation 2\n", - "print(\"Transmission Lines for Simulation 2:\")\n", - "display(lines_sim2)\n", - "\n", - "# Run the simplified market clearing for Simulation 2\n", - "model_sim2, results_sim2 = simplified_market_clearing_opt(\n", - " orders=orders_mapped,\n", - " incidence_matrix=incidence_matrix,\n", - " lines=lines_sim2,\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "5c721991", - "metadata": { - "id": "5c721991" - }, - "source": [ - "#### Simulation 3: High Transmission Capacity Between Zones" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "id": "a1c7f344", - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 210 - }, - "id": "a1c7f344", - "lines_to_end_of_cell_marker": 0, - "lines_to_next_cell": 1, - "outputId": "78e208e2-81f7-4678-9adc-bbdddd2802ea" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "### Simulation 3: High Transmission Capacity Between Zones\n", - "Transmission Lines for Simulation 3:\n" - ] - }, - { - "data": { - "application/vnd.google.colaboratory.intrinsic+json": { - "summary": "{\n \"name\": \"lines_sim3\",\n \"rows\": 3,\n \"fields\": [\n {\n \"column\": \"name\",\n \"properties\": {\n \"dtype\": \"string\",\n \"num_unique_values\": 3,\n \"samples\": [\n \"Line_N1_S\",\n \"Line_N2_S\",\n \"Line_N1_N2\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"bus0\",\n \"properties\": {\n \"dtype\": \"string\",\n \"num_unique_values\": 2,\n \"samples\": [\n \"north_2\",\n \"north_1\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"bus1\",\n \"properties\": {\n \"dtype\": \"string\",\n \"num_unique_values\": 2,\n \"samples\": [\n \"north_2\",\n \"south\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"s_nom\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 0.0,\n \"min\": 5000.0,\n \"max\": 5000.0,\n \"num_unique_values\": 1,\n \"samples\": [\n 5000.0\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"x\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 0.0,\n \"min\": 0.01,\n \"max\": 0.01,\n \"num_unique_values\": 1,\n \"samples\": [\n 0.01\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"r\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 0.0,\n \"min\": 0.001,\n \"max\": 0.001,\n \"num_unique_values\": 1,\n \"samples\": [\n 0.001\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n }\n ]\n}", - "type": "dataframe", - "variable_name": "lines_sim3" - }, - "text/html": [ - "\n", - "
\n", - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
bus0bus1s_nomxr
name
Line_N1_Snorth_1south5000.00.010.001
Line_N2_Snorth_2south5000.00.010.001
Line_N1_N2north_1north_25000.00.010.001
\n", - "
\n", - "
\n", - "\n", - "
\n", - " \n", - "\n", - " \n", - "\n", - " \n", - "
\n", - "\n", - "\n", - "
\n", - " \n", - "\n", - "\n", - "\n", - " \n", - "
\n", - "\n", - "
\n", - " \n", - " \n", - " \n", - "
\n", - "\n", - "
\n", - "
\n" - ], - "text/plain": [ - " bus0 bus1 s_nom x r\n", - "name \n", - "Line_N1_S north_1 south 5000.0 0.01 0.001\n", - "Line_N2_S north_2 south 5000.0 0.01 0.001\n", - "Line_N1_N2 north_1 north_2 5000.0 0.01 0.001" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "print(\"### Simulation 3: High Transmission Capacity Between Zones\")\n", - "\n", - "# Define the lines for Simulation 3 with high transmission capacity\n", - "lines_sim3 = lines.copy()\n", - "lines_sim3[\"s_nom\"] = 5000.0 # Set transmission capacity to 5000 MW for all lines\n", - "\n", - "# Display the line capacities for Simulation 3\n", - "print(\"Transmission Lines for Simulation 3:\")\n", - "display(lines_sim3)\n", - "\n", - "# Run the simplified market clearing for Simulation 3\n", - "model_sim3, results_sim3 = simplified_market_clearing_opt(\n", - " orders=orders_mapped,\n", - " incidence_matrix=incidence_matrix,\n", - " lines=lines_sim3,\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "661e6c30", - "metadata": { - "id": "661e6c30" - }, - "source": [ - "### 5.4. Extracting and Interpreting the Results\n", - "\n", - "After running all three simulations, we can extract the results to understand how the presence or absence of transmission capacity affects bid acceptances and power flows between zones.\n", - "\n", - "#### Extracting Clearing Prices\n", - "\n", - "The **clearing prices** for each market zone and time period are extracted using the dual variables associated with the energy balance constraints in the optimization model. Specifically, the dual variable of the energy balance constraint for a given zone and time period represents the marginal price of electricity in that zone at that time.\n", - "\n", - "In the `extract_results` function, the following steps are performed to obtain the clearing prices:\n", - "\n", - "1. **Energy Balance Constraints:** For each zone and time period, the energy balance equation ensures that the total supply plus imports minus exports equals the demand.\n", - "2. **Dual Variables:** The dual variable (`model.dual[model.energy_balance[node, t]]`) associated with each energy balance constraint captures the sensitivity of the objective function (total cost) to a marginal increase in demand or supply.\n", - "3. **Clearing Price Interpretation:** The value of the dual variable corresponds to the clearing price in the respective zone and time period, reflecting the cost of supplying an additional unit of electricity.\n", - "\n", - "This method leverages the duality in optimization to efficiently extract market prices resulting from the optimal dispatch of bids under the given constraints." - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "id": "bdcc49e7", - "metadata": { - "cellView": "form", - "id": "bdcc49e7" - }, - "outputs": [], - "source": [ - "# @title Function to extract market clearing results from the optimization model\n", - "def extract_results(model, incidence_matrix):\n", - " nodes = list(incidence_matrix.index)\n", - " lines = list(incidence_matrix.columns)\n", - "\n", - " # Extract accepted bid ratios using a dictionary comprehension\n", - " accepted_bids = {\n", - " o: pyo.value(model.x[o]) for o in model.x if pyo.value(model.x[o]) > 0\n", - " }\n", - "\n", - " # Extract power flows on each line for each time period\n", - " power_flows = [\n", - " {\"time\": t, \"line\": line, \"flow_MW\": pyo.value(model.flows[t, line])}\n", - " for t in model.T\n", - " for line in lines\n", - " if pyo.value(model.flows[t, line]) != 0\n", - " ]\n", - " power_flows_df = pd.DataFrame(power_flows)\n", - "\n", - " # Extract market clearing prices from dual variables\n", - " clearing_prices = [\n", - " {\n", - " \"zone\": node,\n", - " \"time\": t,\n", - " \"clearing_price\": pyo.value(model.dual[model.energy_balance[node, t]]),\n", - " }\n", - " for node in nodes\n", - " for t in model.T\n", - " ]\n", - " clearing_prices_df = pd.DataFrame(clearing_prices)\n", - "\n", - " return accepted_bids, power_flows_df, clearing_prices_df" - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "id": "512ed95f", - "metadata": { - "id": "512ed95f" - }, - "outputs": [], - "source": [ - "# Extract results for Simulation 1\n", - "accepted_bids_sim1, power_flows_df_sim1, clearing_prices_df_sim1 = extract_results(\n", - " model_sim1, incidence_matrix\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "id": "7b32b7c3", - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 70 - }, - "id": "7b32b7c3", - "outputId": "7d56dd2f-8ab9-4a95-df0b-dbd6aac660e4" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Simulation 1: Power Flows Between Zones\n" - ] - }, - { - "data": { - "application/vnd.google.colaboratory.intrinsic+json": { - "summary": "{\n \"name\": \"display(power_flows_df_sim1\",\n \"rows\": 0,\n \"fields\": []\n}", - "type": "dataframe" - }, - "text/html": [ - "\n", - "
\n", - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
\n", - "
\n", - "
\n", - "\n", - "
\n", - " \n", - "\n", - " \n", - "\n", - " \n", - "
\n", - "\n", - "\n", - "
\n", - "
\n" - ], - "text/plain": [ - "Empty DataFrame\n", - "Columns: []\n", - "Index: []" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "print(\"Simulation 1: Power Flows Between Zones\")\n", - "display(power_flows_df_sim1.head())" - ] - }, - { - "cell_type": "markdown", - "id": "Q37fGve_m7sf", - "metadata": { - "id": "Q37fGve_m7sf" - }, - "source": [ - "As it is to be expected, there are no flows printed since there is no transfer capacity available." - ] - }, - { - "cell_type": "code", - "execution_count": 20, - "id": "2d386677", - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 413 - }, - "id": "2d386677", - "outputId": "7062cc2c-e168-45a6-9294-5ea193ad78c2" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Simulation 1: Clearing Prices per Zone and Time\n" - ] - }, - { - "data": { - "application/vnd.google.colaboratory.intrinsic+json": { - "summary": "{\n \"name\": \"display(clearing_prices_df_sim1\",\n \"rows\": 5,\n \"fields\": [\n {\n \"column\": \"zone\",\n \"properties\": {\n \"dtype\": \"category\",\n \"num_unique_values\": 1,\n \"samples\": [\n \"DE_1\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"time\",\n \"properties\": {\n \"dtype\": \"date\",\n \"min\": \"2019-01-01 00:00:00\",\n \"max\": \"2019-01-01 04:00:00\",\n \"num_unique_values\": 5,\n \"samples\": [\n \"2019-01-01 01:00:00\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"clearing_price\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 1.3038404810405297,\n \"min\": 9.0,\n \"max\": 12.0,\n \"num_unique_values\": 4,\n \"samples\": [\n 10.0\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n }\n ]\n}", - "type": "dataframe" - }, - "text/html": [ - "\n", - "
\n", - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
zonetimeclearing_price
0DE_12019-01-01 00:00:009.0
1DE_12019-01-01 01:00:0010.0
2DE_12019-01-01 02:00:0011.0
3DE_12019-01-01 03:00:0012.0
4DE_12019-01-01 04:00:0012.0
\n", - "
\n", - "
\n", - "\n", - "
\n", - " \n", - "\n", - " \n", - "\n", - " \n", - "
\n", - "\n", - "\n", - "
\n", - " \n", - "\n", - "\n", - "\n", - " \n", - "
\n", - "\n", - "
\n", - "
\n" - ], - "text/plain": [ - " zone time clearing_price\n", - "0 DE_1 2019-01-01 00:00:00 9.0\n", - "1 DE_1 2019-01-01 01:00:00 10.0\n", - "2 DE_1 2019-01-01 02:00:00 11.0\n", - "3 DE_1 2019-01-01 03:00:00 12.0\n", - "4 DE_1 2019-01-01 04:00:00 12.0" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/vnd.google.colaboratory.intrinsic+json": { - "summary": "{\n \"name\": \"display(clearing_prices_df_sim1\",\n \"rows\": 5,\n \"fields\": [\n {\n \"column\": \"zone\",\n \"properties\": {\n \"dtype\": \"category\",\n \"num_unique_values\": 1,\n \"samples\": [\n \"DE_2\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"time\",\n \"properties\": {\n \"dtype\": \"date\",\n \"min\": \"2019-01-01 00:00:00\",\n \"max\": \"2019-01-01 04:00:00\",\n \"num_unique_values\": 5,\n \"samples\": [\n \"2019-01-01 01:00:00\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"clearing_price\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 0.0,\n \"min\": 100.0,\n \"max\": 100.0,\n \"num_unique_values\": 1,\n \"samples\": [\n 100.0\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n }\n ]\n}", - "type": "dataframe" - }, - "text/html": [ - "\n", - "
\n", - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
zonetimeclearing_price
24DE_22019-01-01 00:00:00100.0
25DE_22019-01-01 01:00:00100.0
26DE_22019-01-01 02:00:00100.0
27DE_22019-01-01 03:00:00100.0
28DE_22019-01-01 04:00:00100.0
\n", - "
\n", - "
\n", - "\n", - "
\n", - " \n", - "\n", - " \n", - "\n", - " \n", - "
\n", - "\n", - "\n", - "
\n", - " \n", - "\n", - "\n", - "\n", - " \n", - "
\n", - "\n", - "
\n", - "
\n" - ], - "text/plain": [ - " zone time clearing_price\n", - "24 DE_2 2019-01-01 00:00:00 100.0\n", - "25 DE_2 2019-01-01 01:00:00 100.0\n", - "26 DE_2 2019-01-01 02:00:00 100.0\n", - "27 DE_2 2019-01-01 03:00:00 100.0\n", - "28 DE_2 2019-01-01 04:00:00 100.0" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "print(\"Simulation 1: Clearing Prices per Zone and Time\")\n", - "display(clearing_prices_df_sim1.loc[clearing_prices_df_sim1[\"zone\"] == \"DE_1\"].head())\n", - "display(clearing_prices_df_sim1.loc[clearing_prices_df_sim1[\"zone\"] == \"DE_2\"].head())" - ] - }, - { - "cell_type": "code", - "execution_count": 21, - "id": "d8327407", - "metadata": { - "id": "d8327407" - }, - "outputs": [], - "source": [ - "# Extract results for Simulation 2\n", - "accepted_bids_sim2, power_flows_df_sim2, clearing_prices_df_sim2 = extract_results(\n", - " model_sim2, incidence_matrix\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 22, - "id": "9b5fc1de", - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 224 - }, - "id": "9b5fc1de", - "outputId": "25af541d-12cb-47d6-bc08-92ee847cd820" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Simulation 2: Power Flows Between Zones\n" - ] - }, - { - "data": { - "application/vnd.google.colaboratory.intrinsic+json": { - "summary": "{\n \"name\": \"display(power_flows_df_sim2\",\n \"rows\": 5,\n \"fields\": [\n {\n \"column\": \"time\",\n \"properties\": {\n \"dtype\": \"date\",\n \"min\": \"2019-01-01 00:00:00\",\n \"max\": \"2019-01-01 01:00:00\",\n \"num_unique_values\": 2,\n \"samples\": [\n \"2019-01-01 01:00:00\",\n \"2019-01-01 00:00:00\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"line\",\n \"properties\": {\n \"dtype\": \"string\",\n \"num_unique_values\": 3,\n \"samples\": [\n \"Line_N1_S\",\n \"Line_N2_S\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"flow_MW\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 0.0,\n \"min\": -3000.0,\n \"max\": -3000.0,\n \"num_unique_values\": 1,\n \"samples\": [\n -3000.0\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n }\n ]\n}", - "type": "dataframe" - }, - "text/html": [ - "\n", - "
\n", - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
timelineflow_MW
02019-01-01 00:00:00Line_N1_S-3000.0
12019-01-01 00:00:00Line_N2_S-3000.0
22019-01-01 00:00:00Line_N1_N2-3000.0
32019-01-01 01:00:00Line_N1_S-3000.0
42019-01-01 01:00:00Line_N2_S-3000.0
\n", - "
\n", - "
\n", - "\n", - "
\n", - " \n", - "\n", - " \n", - "\n", - " \n", - "
\n", - "\n", - "\n", - "
\n", - " \n", - "\n", - "\n", - "\n", - " \n", - "
\n", - "\n", - "
\n", - "
\n" - ], - "text/plain": [ - " time line flow_MW\n", - "0 2019-01-01 00:00:00 Line_N1_S -3000.0\n", - "1 2019-01-01 00:00:00 Line_N2_S -3000.0\n", - "2 2019-01-01 00:00:00 Line_N1_N2 -3000.0\n", - "3 2019-01-01 01:00:00 Line_N1_S -3000.0\n", - "4 2019-01-01 01:00:00 Line_N2_S -3000.0" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "print(\"Simulation 2: Power Flows Between Zones\")\n", - "display(power_flows_df_sim2.head())" - ] - }, - { - "cell_type": "code", - "execution_count": 23, - "id": "b7c5d148", - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 413 - }, - "id": "b7c5d148", - "outputId": "4abfe739-2b01-485c-cde7-e385debad088" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Simulation 2: Clearing Prices per Zone and Time\n" - ] - }, - { - "data": { - "application/vnd.google.colaboratory.intrinsic+json": { - "summary": "{\n \"name\": \"display(clearing_prices_df_sim2\",\n \"rows\": 5,\n \"fields\": [\n {\n \"column\": \"zone\",\n \"properties\": {\n \"dtype\": \"category\",\n \"num_unique_values\": 1,\n \"samples\": [\n \"DE_1\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"time\",\n \"properties\": {\n \"dtype\": \"date\",\n \"min\": \"2019-01-01 00:00:00\",\n \"max\": \"2019-01-01 04:00:00\",\n \"num_unique_values\": 5,\n \"samples\": [\n \"2019-01-01 01:00:00\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"clearing_price\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 1.5811388300841898,\n \"min\": 15.0,\n \"max\": 19.0,\n \"num_unique_values\": 5,\n \"samples\": [\n 16.0\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n }\n ]\n}", - "type": "dataframe" - }, - "text/html": [ - "\n", - "
\n", - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
zonetimeclearing_price
0DE_12019-01-01 00:00:0015.0
1DE_12019-01-01 01:00:0016.0
2DE_12019-01-01 02:00:0017.0
3DE_12019-01-01 03:00:0018.0
4DE_12019-01-01 04:00:0019.0
\n", - "
\n", - "
\n", - "\n", - "
\n", - " \n", - "\n", - " \n", - "\n", - " \n", - "
\n", - "\n", - "\n", - "
\n", - " \n", - "\n", - "\n", - "\n", - " \n", - "
\n", - "\n", - "
\n", - "
\n" - ], - "text/plain": [ - " zone time clearing_price\n", - "0 DE_1 2019-01-01 00:00:00 15.0\n", - "1 DE_1 2019-01-01 01:00:00 16.0\n", - "2 DE_1 2019-01-01 02:00:00 17.0\n", - "3 DE_1 2019-01-01 03:00:00 18.0\n", - "4 DE_1 2019-01-01 04:00:00 19.0" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/vnd.google.colaboratory.intrinsic+json": { - "summary": "{\n \"name\": \"display(clearing_prices_df_sim2\",\n \"rows\": 5,\n \"fields\": [\n {\n \"column\": \"zone\",\n \"properties\": {\n \"dtype\": \"category\",\n \"num_unique_values\": 1,\n \"samples\": [\n \"DE_2\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"time\",\n \"properties\": {\n \"dtype\": \"date\",\n \"min\": \"2019-01-01 00:00:00\",\n \"max\": \"2019-01-01 04:00:00\",\n \"num_unique_values\": 5,\n \"samples\": [\n \"2019-01-01 01:00:00\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"clearing_price\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 0.8366600265340756,\n \"min\": 29.0,\n \"max\": 31.0,\n \"num_unique_values\": 3,\n \"samples\": [\n 31.0\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n }\n ]\n}", - "type": "dataframe" - }, - "text/html": [ - "\n", - "
\n", - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
zonetimeclearing_price
24DE_22019-01-01 00:00:0031.0
25DE_22019-01-01 01:00:0030.0
26DE_22019-01-01 02:00:0030.0
27DE_22019-01-01 03:00:0029.0
28DE_22019-01-01 04:00:0029.0
\n", - "
\n", - "
\n", - "\n", - "
\n", - " \n", - "\n", - " \n", - "\n", - " \n", - "
\n", - "\n", - "\n", - "
\n", - " \n", - "\n", - "\n", - "\n", - " \n", - "
\n", - "\n", - "
\n", - "
\n" - ], - "text/plain": [ - " zone time clearing_price\n", - "24 DE_2 2019-01-01 00:00:00 31.0\n", - "25 DE_2 2019-01-01 01:00:00 30.0\n", - "26 DE_2 2019-01-01 02:00:00 30.0\n", - "27 DE_2 2019-01-01 03:00:00 29.0\n", - "28 DE_2 2019-01-01 04:00:00 29.0" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "print(\"Simulation 2: Clearing Prices per Zone and Time\")\n", - "display(clearing_prices_df_sim2.loc[clearing_prices_df_sim2[\"zone\"] == \"DE_1\"].head())\n", - "display(clearing_prices_df_sim2.loc[clearing_prices_df_sim2[\"zone\"] == \"DE_2\"].head())" - ] - }, - { - "cell_type": "code", - "execution_count": 24, - "id": "7f850cf5", - "metadata": { - "id": "7f850cf5" - }, - "outputs": [], - "source": [ - "# Extract results for Simulation 3\n", - "accepted_bids_sim3, power_flows_df_sim3, clearing_prices_df_sim3 = extract_results(\n", - " model_sim3, incidence_matrix\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 25, - "id": "3b2528a2", - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 224 - }, - "id": "3b2528a2", - "outputId": "f97d364c-890e-40b7-aeb9-691052170a64" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Simulation 3: Power Flows Between Zones\n" - ] - }, - { - "data": { - "application/vnd.google.colaboratory.intrinsic+json": { - "summary": "{\n \"name\": \"display(power_flows_df_sim3\",\n \"rows\": 5,\n \"fields\": [\n {\n \"column\": \"time\",\n \"properties\": {\n \"dtype\": \"date\",\n \"min\": \"2019-01-01 00:00:00\",\n \"max\": \"2019-01-01 01:00:00\",\n \"num_unique_values\": 2,\n \"samples\": [\n \"2019-01-01 01:00:00\",\n \"2019-01-01 00:00:00\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"line\",\n \"properties\": {\n \"dtype\": \"string\",\n \"num_unique_values\": 3,\n \"samples\": [\n \"Line_N1_S\",\n \"Line_N2_S\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"flow_MW\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 268.32815729997475,\n \"min\": -5000.0,\n \"max\": -4400.0,\n \"num_unique_values\": 2,\n \"samples\": [\n -4400.0,\n -5000.0\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n }\n ]\n}", - "type": "dataframe" - }, - "text/html": [ - "\n", - "
\n", - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
timelineflow_MW
02019-01-01 00:00:00Line_N1_S-5000.0
12019-01-01 00:00:00Line_N2_S-5000.0
22019-01-01 00:00:00Line_N1_N2-5000.0
32019-01-01 01:00:00Line_N1_S-4400.0
42019-01-01 01:00:00Line_N2_S-5000.0
\n", - "
\n", - "
\n", - "\n", - "
\n", - " \n", - "\n", - " \n", - "\n", - " \n", - "
\n", - "\n", - "\n", - "
\n", - " \n", - "\n", - "\n", - "\n", - " \n", - "
\n", - "\n", - "
\n", - "
\n" - ], - "text/plain": [ - " time line flow_MW\n", - "0 2019-01-01 00:00:00 Line_N1_S -5000.0\n", - "1 2019-01-01 00:00:00 Line_N2_S -5000.0\n", - "2 2019-01-01 00:00:00 Line_N1_N2 -5000.0\n", - "3 2019-01-01 01:00:00 Line_N1_S -4400.0\n", - "4 2019-01-01 01:00:00 Line_N2_S -5000.0" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "print(\"Simulation 3: Power Flows Between Zones\")\n", - "display(power_flows_df_sim3.head())" - ] - }, - { - "cell_type": "code", - "execution_count": 26, - "id": "05961462", - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 413 - }, - "id": "05961462", - "outputId": "d6e9c38d-ab03-4828-e243-181791179ead" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Simulation 3: Clearing Prices per Zone and Time\n" - ] - }, - { - "data": { - "application/vnd.google.colaboratory.intrinsic+json": { - "summary": "{\n \"name\": \"display(clearing_prices_df_sim3\",\n \"rows\": 5,\n \"fields\": [\n {\n \"column\": \"zone\",\n \"properties\": {\n \"dtype\": \"category\",\n \"num_unique_values\": 1,\n \"samples\": [\n \"DE_1\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"time\",\n \"properties\": {\n \"dtype\": \"date\",\n \"min\": \"2019-01-01 00:00:00\",\n \"max\": \"2019-01-01 04:00:00\",\n \"num_unique_values\": 5,\n \"samples\": [\n \"2019-01-01 01:00:00\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"clearing_price\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 3.7148351242013415,\n \"min\": 19.0,\n \"max\": 28.0,\n \"num_unique_values\": 3,\n \"samples\": [\n 19.0\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n }\n ]\n}", - "type": "dataframe" - }, - "text/html": [ - "\n", - "
\n", - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
zonetimeclearing_price
0DE_12019-01-01 00:00:0019.0
1DE_12019-01-01 01:00:0027.0
2DE_12019-01-01 02:00:0027.0
3DE_12019-01-01 03:00:0027.0
4DE_12019-01-01 04:00:0028.0
\n", - "
\n", - "
\n", - "\n", - "
\n", - " \n", - "\n", - " \n", - "\n", - " \n", - "
\n", - "\n", - "\n", - "
\n", - " \n", - "\n", - "\n", - "\n", - " \n", - "
\n", - "\n", - "
\n", - "
\n" - ], - "text/plain": [ - " zone time clearing_price\n", - "0 DE_1 2019-01-01 00:00:00 19.0\n", - "1 DE_1 2019-01-01 01:00:00 27.0\n", - "2 DE_1 2019-01-01 02:00:00 27.0\n", - "3 DE_1 2019-01-01 03:00:00 27.0\n", - "4 DE_1 2019-01-01 04:00:00 28.0" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/vnd.google.colaboratory.intrinsic+json": { - "summary": "{\n \"name\": \"display(clearing_prices_df_sim3\",\n \"rows\": 5,\n \"fields\": [\n {\n \"column\": \"zone\",\n \"properties\": {\n \"dtype\": \"category\",\n \"num_unique_values\": 1,\n \"samples\": [\n \"DE_2\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"time\",\n \"properties\": {\n \"dtype\": \"date\",\n \"min\": \"2019-01-01 00:00:00\",\n \"max\": \"2019-01-01 04:00:00\",\n \"num_unique_values\": 5,\n \"samples\": [\n \"2019-01-01 01:00:00\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"clearing_price\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 0.4472135954999579,\n \"min\": 27.0,\n \"max\": 28.0,\n \"num_unique_values\": 2,\n \"samples\": [\n 28.0\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n }\n ]\n}", - "type": "dataframe" - }, - "text/html": [ - "\n", - "
\n", - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
zonetimeclearing_price
24DE_22019-01-01 00:00:0027.0
25DE_22019-01-01 01:00:0027.0
26DE_22019-01-01 02:00:0027.0
27DE_22019-01-01 03:00:0027.0
28DE_22019-01-01 04:00:0028.0
\n", - "
\n", - "
\n", - "\n", - "
\n", - " \n", - "\n", - " \n", - "\n", - " \n", - "
\n", - "\n", - "\n", - "
\n", - " \n", - "\n", - "\n", - "\n", - " \n", - "
\n", - "\n", - "
\n", - "
\n" - ], - "text/plain": [ - " zone time clearing_price\n", - "24 DE_2 2019-01-01 00:00:00 27.0\n", - "25 DE_2 2019-01-01 01:00:00 27.0\n", - "26 DE_2 2019-01-01 02:00:00 27.0\n", - "27 DE_2 2019-01-01 03:00:00 27.0\n", - "28 DE_2 2019-01-01 04:00:00 28.0" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "print(\"Simulation 3: Clearing Prices per Zone and Time\")\n", - "display(clearing_prices_df_sim3.loc[clearing_prices_df_sim3[\"zone\"] == \"DE_1\"].head())\n", - "display(clearing_prices_df_sim3.loc[clearing_prices_df_sim3[\"zone\"] == \"DE_2\"].head())" - ] - }, - { - "cell_type": "markdown", - "id": "fb62e2fd", - "metadata": { - "id": "fb62e2fd" - }, - "source": [ - "**Explanation:**\n", - "\n", - "- **Accepted Bids:** Shows which bids were accepted in each simulation and the ratio at which they were accepted.\n", - "- **Power Flows:** Indicates the amount of energy transmitted between zones. In Simulation 1, with zero transmission capacity, there should be no power flows between `DE_1` and `DE_2`. In Simulation 2 and 3, with medium and high transmission capacities, power flows can occur between zones.\n", - "- **Clearing Prices:** Represents the average bid price in each zone at each time period. Comparing prices across simulations can reveal the impact of transmission capacity on market prices." - ] - }, - { - "cell_type": "markdown", - "id": "3dbd64e0", - "metadata": { - "id": "3dbd64e0" - }, - "source": [ - "### 5.5. Comparing Simulations\n", - "\n", - "To better understand the impact of transmission capacity, let's compare the key results from all three simulations." - ] - }, - { - "cell_type": "code", - "execution_count": 27, - "id": "0ffe7033", - "metadata": { - "cellView": "form", - "colab": { - "base_uri": "https://localhost:8080/", - "height": 617 - }, - "id": "0ffe7033", - "outputId": "b0b4295a-095b-4871-aeef-d5aa44f866f8" - }, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - "
\n", - "
\n", - "\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "# @title Plot the market clearing prices for each zone and simulation run\n", - "# Initialize the Plotly figure\n", - "fig = go.Figure()\n", - "\n", - "# Iterate over each zone to plot clearing prices for all three simulations\n", - "for zone in incidence_matrix.index:\n", - " # Filter data for the current zone and Simulation 1\n", - " zone_prices_sim1 = clearing_prices_df_sim1[clearing_prices_df_sim1[\"zone\"] == zone]\n", - " # Filter data for the current zone and Simulation 2\n", - " zone_prices_sim2 = clearing_prices_df_sim2[clearing_prices_df_sim2[\"zone\"] == zone]\n", - " # Filter data for the current zone and Simulation 3\n", - " zone_prices_sim3 = clearing_prices_df_sim3[clearing_prices_df_sim3[\"zone\"] == zone]\n", - "\n", - " # Add trace for Simulation 1\n", - " fig.add_trace(\n", - " go.Scatter(\n", - " x=zone_prices_sim1[\"time\"],\n", - " y=zone_prices_sim1[\"clearing_price\"],\n", - " mode=\"lines\",\n", - " name=f\"{zone} - Sim1 (Zero Capacity)\",\n", - " line=dict(dash=\"dash\"), # Dashed line for Simulation 1\n", - " )\n", - " )\n", - "\n", - " # Add trace for Simulation 2\n", - " fig.add_trace(\n", - " go.Scatter(\n", - " x=zone_prices_sim2[\"time\"],\n", - " y=zone_prices_sim2[\"clearing_price\"],\n", - " mode=\"lines\",\n", - " name=f\"{zone} - Sim2 (Medium Capacity)\",\n", - " line=dict(dash=\"dot\"), # Dotted line for Simulation 2\n", - " )\n", - " )\n", - "\n", - " # Add trace for Simulation 3\n", - " fig.add_trace(\n", - " go.Scatter(\n", - " x=zone_prices_sim3[\"time\"],\n", - " y=zone_prices_sim3[\"clearing_price\"],\n", - " mode=\"lines\",\n", - " name=f\"{zone} - Sim3 (High Capacity)\",\n", - " line=dict(dash=\"solid\"), # Solid line for Simulation 3\n", - " )\n", - " )\n", - "\n", - "# Update layout for better aesthetics and interactivity\n", - "fig.update_layout(\n", - " title=\"Clearing Prices per Zone Over Time: Sim1, Sim2, & Sim3\",\n", - " xaxis_title=\"Time\",\n", - " yaxis_title=\"Clearing Price\",\n", - " legend_title=\"Simulations\",\n", - " xaxis=dict(\n", - " tickangle=45,\n", - " type=\"date\", # Ensure the x-axis is treated as dates\n", - " ),\n", - " hovermode=\"x unified\", # Unified hover for better comparison\n", - " template=\"plotly_white\", # Clean white background\n", - " width=1000,\n", - " height=600,\n", - ")\n", - "\n", - "# Display the interactive plot\n", - "fig.show()" - ] - }, - { - "cell_type": "markdown", - "id": "7ee17c77", - "metadata": { - "id": "7ee17c77" - }, - "source": [ - "**Explanation:**\n", - "\n", - "- **Clearing Prices Plot:** Shows how market prices vary over time for each zone across all three simulations. The dashed lines represent Simulation 1 (no transmission capacity), dotted lines represent Simulation 2 (medium transmission capacity), and solid lines represent Simulation 3 (high transmission capacity). This visualization helps in observing how the presence of transmission capacity affects price convergence or divergence between zones." - ] - }, - { - "cell_type": "markdown", - "id": "fb8f157c", - "metadata": { - "id": "fb8f157c" - }, - "source": [ - "## 6. Execution with ASSUME\n", - "\n", - "In a real-world scenario, the ASSUME framework handles the reading of CSV files and the configuration of the simulation through configuration files. For the purpose of this tutorial, we'll integrate our prepared data and configuration into ASSUME to execute the simulation seamlessly.\n", - "\n", - "### Step 1: Saving Input Files\n", - "\n", - "We will save the generated input DataFrames to the `inputs/tutorial_08` folder. The required files are:\n", - "- `demand_units.csv`\n", - "- `demand_df.csv`\n", - "- `powerplant_units.csv`\n", - "- `buses.csv`\n", - "- `lines.csv`\n", - "\n", - "Additionally, we'll create a new file `fuel_prices.csv`.\n", - "\n", - "**Note:** The demand timeseries has been extended to cover 48 hours as ASSUME always requires an additional day of data for the market simulation.\n", - "\n", - "#### Create the Inputs Directory and Save CSV Files" - ] - }, - { - "cell_type": "code", - "execution_count": 28, - "id": "531a7a24", - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "531a7a24", - "outputId": "abc151f4-2f50-4ebd-b405-49f0340cd96d" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Input CSV files have been saved to 'inputs/tutorial_08'.\n" - ] - } - ], - "source": [ - "import os\n", - "\n", - "# Define the input directory\n", - "input_dir = \"inputs/tutorial_08\"\n", - "\n", - "# Create the directory if it doesn't exist\n", - "os.makedirs(input_dir, exist_ok=True)\n", - "\n", - "# extend demand_df for another day with the same demand profile\n", - "demand_df = pd.concat([demand_df, demand_df])\n", - "demand_df.index = pd.date_range(start=\"2019-01-01\", periods=48, freq=\"h\")\n", - "\n", - "# Save the DataFrames to CSV files\n", - "buses.to_csv(os.path.join(input_dir, \"buses.csv\"), index=True)\n", - "lines.to_csv(os.path.join(input_dir, \"lines.csv\"), index=True)\n", - "powerplant_units.to_csv(os.path.join(input_dir, \"powerplant_units.csv\"), index=False)\n", - "demand_units.to_csv(os.path.join(input_dir, \"demand_units.csv\"), index=False)\n", - "demand_df.to_csv(os.path.join(input_dir, \"demand_df.csv\"))\n", - "\n", - "print(\"Input CSV files have been saved to 'inputs/tutorial_08'.\")" - ] - }, - { - "cell_type": "code", - "execution_count": 29, - "id": "2d61a40b", - "metadata": { - "cellView": "form", - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "2d61a40b", - "outputId": "8ce46e76-c462-4c8e-db62-8f787b354403" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Fuel Prices CSV file has been saved to 'inputs/tutorial_08/fuel_prices.csv'.\n" - ] - } - ], - "source": [ - "# @title Create fuel prices\n", - "fuel_prices = {\n", - " \"fuel\": [\"uranium\", \"co2\"],\n", - " \"price\": [5, 25],\n", - "}\n", - "\n", - "# Convert to DataFrame and save as CSV\n", - "fuel_prices_df = pd.DataFrame(fuel_prices).T\n", - "fuel_prices_df.to_csv(\n", - " os.path.join(input_dir, \"fuel_prices_df.csv\"), index=True, header=False\n", - ")\n", - "\n", - "print(\"Fuel Prices CSV file has been saved to 'inputs/tutorial_08/fuel_prices.csv'.\")" - ] - }, - { - "cell_type": "markdown", - "id": "e0e47625", - "metadata": { - "id": "e0e47625" - }, - "source": [ - "### Step 2: Creating the Configuration YAML File\n", - "\n", - "The configuration file defines the simulation parameters, including market settings and network configurations. Below is the YAML configuration tailored for our tutorial." - ] - }, - { - "cell_type": "markdown", - "id": "44e22a14", - "metadata": { - "id": "44e22a14" - }, - "source": [ - "#### Create `config.yaml`" - ] - }, - { - "cell_type": "code", - "execution_count": 30, - "id": "821a4002", - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "821a4002", - "outputId": "ac8bf62b-8e38-4199-a45a-5c5397342bef" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Configuration YAML file has been saved to 'inputs/tutorial_08/config.yaml'.\n" - ] - } - ], - "source": [ - "config = {\n", - " \"zonal_case\": {\n", - " \"start_date\": \"2019-01-01 00:00\",\n", - " \"end_date\": \"2019-01-01 23:00\",\n", - " \"time_step\": \"1h\",\n", - " \"save_frequency_hours\": 24,\n", - " \"markets_config\": {\n", - " \"zonal\": {\n", - " \"operator\": \"EOM_operator\",\n", - " \"product_type\": \"energy\",\n", - " \"products\": [{\"duration\": \"1h\", \"count\": 1, \"first_delivery\": \"1h\"}],\n", - " \"opening_frequency\": \"1h\",\n", - " \"opening_duration\": \"1h\",\n", - " \"volume_unit\": \"MWh\",\n", - " \"maximum_bid_volume\": 100000,\n", - " \"maximum_bid_price\": 3000,\n", - " \"minimum_bid_price\": -500,\n", - " \"price_unit\": \"EUR/MWh\",\n", - " \"market_mechanism\": \"pay_as_clear_complex\",\n", - " \"additional_fields\": [\"bid_type\", \"node\"],\n", - " \"param_dict\": {\"network_path\": \".\", \"zones_identifier\": \"zone_id\"},\n", - " }\n", - " },\n", - " }\n", - "}\n", - "\n", - "# Define the path for the config file\n", - "config_path = os.path.join(input_dir, \"config.yaml\")\n", - "\n", - "# Save the configuration to a YAML file\n", - "with open(config_path, \"w\") as file:\n", - " yaml.dump(config, file, sort_keys=False)\n", - "\n", - "print(f\"Configuration YAML file has been saved to '{config_path}'.\")" - ] - }, - { - "cell_type": "markdown", - "id": "e2e9403a", - "metadata": { - "id": "e2e9403a" - }, - "source": [ - "### Detailed Configuration Explanation\n", - "\n", - "The `config.yaml` file plays a key role in defining the simulation parameters. Below is a detailed explanation of each configuration parameter:\n", - "\n", - "- **zonal_case:**\n", - " - **start_date:** The start date and time for the simulation (`2019-01-01 00:00`).\n", - " - **end_date:** The end date and time for the simulation (`2019-01-02 00:00`).\n", - " - **time_step:** The simulation time step (`1h`), indicating hourly intervals.\n", - " - **save_frequency_hours:** How frequently the simulation results are saved (`24` hours).\n", - "\n", - "- **markets_config:**\n", - " - **zonal:** The name of the market. Remember, that our power plant units had a column named bidding_zonal. This is how a particluar bidding strategy is assigned to a particluar market.\n", - " - **operator:** The market operator (`EOM_operator`).\n", - " - **product_type:** Type of market product (`energy`).\n", - " - **products:** List defining the market products:\n", - " - **duration:** Duration of the product (`1h`).\n", - " - **count:** Number of products (`1`).\n", - " - **first_delivery:** When the first delivery occurs (`1h`).\n", - " - **opening_frequency:** Frequency of market openings (`1h`).\n", - " - **opening_duration:** Duration of market openings (`1h`).\n", - " - **volume_unit:** Unit of volume measurement (`MWh`).\n", - " - **maximum_bid_volume:** Maximum volume allowed per bid (`100000` MWh).\n", - " - **maximum_bid_price:** Maximum price allowed per bid (`3000` EUR/MWh).\n", - " - **minimum_bid_price:** Minimum price allowed per bid (`-500` EUR/MWh).\n", - " - **price_unit:** Unit of price measurement (`EUR/MWh`).\n", - " - **market_mechanism:** The market clearing mechanism (`pay_as_clear_complex`).\n", - " - **additional_fields:** Additional fields required for bids:\n", - " - **bid_type:** Type of bid (e.g., supply or demand).\n", - " - **node:** The market zone associated with the bid.\n", - " - **param_dict:**\n", - " - **network_path:** Path to the network files (`.` indicates current directory).\n", - " - **zones_identifier:** Identifier used for market zones (`zone_id`).\n", - "\n", - "This configuration ensures that the simulation accurately represents the zonal market dynamics, including bid restrictions and market operations." - ] - }, - { - "cell_type": "markdown", - "id": "6fd79730", - "metadata": { - "id": "6fd79730" - }, - "source": [ - "### Step 3: Running the Simulation\n", - "\n", - "With the input files and configuration in place, we can now run the simulation using ASSUME's built-in functions." - ] - }, - { - "cell_type": "markdown", - "id": "33ff62b1", - "metadata": { - "id": "33ff62b1" - }, - "source": [ - "#### Example Simulation Code" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "3a79848a", - "metadata": { - "id": "3a79848a" - }, - "outputs": [], - "source": [ - "# import the main World class and the load_scenario_folder functions from assume\n", - "from assume import World\n", - "from assume.scenario.loader_csv import load_scenario_folder\n", - "\n", - "# Define paths for input and output data\n", - "csv_path = \"outputs\"\n", - "\n", - "# Define the data format and database URI\n", - "# Use \"local_db\" for SQLite database or \"timescale\" for TimescaleDB in Docker\n", - "\n", - "# Create directories if they don't exist\n", - "os.makedirs(csv_path, exist_ok=True)\n", - "os.makedirs(\"local_db\", exist_ok=True)\n", - "\n", - "data_format = \"local_db\" # \"local_db\" or \"timescale\"\n", - "\n", - "if data_format == \"local_db\":\n", - " db_uri = \"sqlite:///local_db/assume_db.db\"\n", - "elif data_format == \"timescale\":\n", - " db_uri = \"postgresql://assume:assume@localhost:5432/assume\"\n", - "\n", - "# Create the World instance\n", - "world = World(database_uri=db_uri, export_csv_path=csv_path)\n", - "\n", - "# Load the scenario by providing the world instance\n", - "# The path to the inputs folder and the scenario name (subfolder in inputs)\n", - "# and the study case name (which config to use for the simulation)\n", - "load_scenario_folder(\n", - " world,\n", - " inputs_path=\"inputs\",\n", - " scenario=\"tutorial_08\",\n", - " study_case=\"zonal_case\",\n", - ")\n", - "\n", - "# Run the simulation\n", - "world.run()" - ] - }, - { - "cell_type": "markdown", - "id": "be819122", - "metadata": { - "id": "be819122" - }, - "source": [ - "## 7. Analyzing the Results\n", - "\n", - "After running the simulation, you can analyze the results using the methods demonstrated in section 5. This integration with ASSUME allows for more extensive and scalable simulations, leveraging the framework's capabilities for handling complex market scenarios.\n", - "\n", - "In this section, we will:\n", - "\n", - "1. **Locate the Simulation Output Files:** Understand where the simulation results are saved.\n", - "2. **Load and Inspect the Output Data:** Read the output CSV files and examine their structure.\n", - "3. **Plot Clearing Prices:** Visualize the market clearing prices to compare with our previous manual simulations." - ] - }, - { - "cell_type": "markdown", - "id": "5ca43ca3", - "metadata": { - "id": "5ca43ca3" - }, - "source": [ - "### 7.1. Locating the Simulation Output Files\n", - "\n", - "The simulation outputs are saved in the `outputs/tutorial_08_zonal_case` directory. Specifically, the key output file we'll work with is `market_meta.csv`, which contains detailed information about the market outcomes for each zone and time period." - ] - }, - { - "cell_type": "markdown", - "id": "78707ac9", - "metadata": { - "id": "78707ac9" - }, - "source": [ - "### 7.2. Loading and Inspecting the Output Data" - ] - }, - { - "cell_type": "code", - "execution_count": 32, - "id": "6e71a328", - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 255 - }, - "id": "6e71a328", - "outputId": "738e1589-5d53-4831-cbcf-4fefca4f7860" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Sample of market_meta.csv:\n" - ] - }, - { - "data": { - "application/vnd.google.colaboratory.intrinsic+json": { - "summary": "{\n \"name\": \"display(market_meta\",\n \"rows\": 5,\n \"fields\": [\n {\n \"column\": \"time\",\n \"properties\": {\n \"dtype\": \"date\",\n \"min\": \"2019-01-01 01:00:00\",\n \"max\": \"2019-01-01 03:00:00\",\n \"num_unique_values\": 3,\n \"samples\": [\n \"2019-01-01 01:00:00\",\n \"2019-01-01 02:00:00\",\n \"2019-01-01 03:00:00\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"supply_volume\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 4108,\n \"min\": 7400,\n \"max\": 15000,\n \"num_unique_values\": 3,\n \"samples\": [\n 15000,\n 7400,\n 7600\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"demand_volume\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 5564,\n \"min\": 5600,\n \"max\": 16800,\n \"num_unique_values\": 5,\n \"samples\": [\n 16800,\n 7200,\n 6400\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"demand_volume_energy\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 5564,\n \"min\": 5600,\n \"max\": 16800,\n \"num_unique_values\": 5,\n \"samples\": [\n 16800,\n 7200,\n 6400\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"supply_volume_energy\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 4108,\n \"min\": 7400,\n \"max\": 15000,\n \"num_unique_values\": 3,\n \"samples\": [\n 15000,\n 7400,\n 7600\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"price\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 0.0,\n \"min\": 43.667,\n \"max\": 43.667,\n \"num_unique_values\": 1,\n \"samples\": [\n 43.667\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"max_price\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 0.0,\n \"min\": 43.667,\n \"max\": 43.667,\n \"num_unique_values\": 1,\n \"samples\": [\n 43.667\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"min_price\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 0.0,\n \"min\": 43.667,\n \"max\": 43.667,\n \"num_unique_values\": 1,\n \"samples\": [\n 43.667\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"node\",\n \"properties\": {\n \"dtype\": \"category\",\n \"num_unique_values\": 2,\n \"samples\": [\n \"DE_2\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"product_start\",\n \"properties\": {\n \"dtype\": \"object\",\n \"num_unique_values\": 3,\n \"samples\": [\n \"2019-01-01 01:00:00\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"product_end\",\n \"properties\": {\n \"dtype\": \"object\",\n \"num_unique_values\": 3,\n \"samples\": [\n \"2019-01-01 02:00:00\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"only_hours\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": null,\n \"min\": null,\n \"max\": null,\n \"num_unique_values\": 0,\n \"samples\": [],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"market_id\",\n \"properties\": {\n \"dtype\": \"category\",\n \"num_unique_values\": 1,\n \"samples\": [],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"simulation\",\n \"properties\": {\n \"dtype\": \"category\",\n \"num_unique_values\": 1,\n \"samples\": [],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n }\n ]\n}", - "type": "dataframe" - }, - "text/html": [ - "\n", - "
\n", - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
supply_volumedemand_volumedemand_volume_energysupply_volume_energypricemax_pricemin_pricenodeproduct_startproduct_endonly_hoursmarket_idsimulation
time
2019-01-01 01:00:0015000560056001500043.66743.66743.667DE_12019-01-01 01:00:002019-01-01 02:00:00NaNzonaltutorial_08_zonal_case
2019-01-01 01:00:0074001680016800740043.66743.66743.667DE_22019-01-01 01:00:002019-01-01 02:00:00NaNzonaltutorial_08_zonal_case
2019-01-01 02:00:0015000640064001500043.66743.66743.667DE_12019-01-01 02:00:002019-01-01 03:00:00NaNzonaltutorial_08_zonal_case
2019-01-01 02:00:0076001620016200760043.66743.66743.667DE_22019-01-01 02:00:002019-01-01 03:00:00NaNzonaltutorial_08_zonal_case
2019-01-01 03:00:0015000720072001500043.66743.66743.667DE_12019-01-01 03:00:002019-01-01 04:00:00NaNzonaltutorial_08_zonal_case
\n", - "
\n", - "
\n", - "\n", - "
\n", - " \n", - "\n", - " \n", - "\n", - " \n", - "
\n", - "\n", - "\n", - "
\n", - " \n", - "\n", - "\n", - "\n", - " \n", - "
\n", - "\n", - "
\n", - "
\n" - ], - "text/plain": [ - " supply_volume demand_volume demand_volume_energy \\\n", - "time \n", - "2019-01-01 01:00:00 15000 5600 5600 \n", - "2019-01-01 01:00:00 7400 16800 16800 \n", - "2019-01-01 02:00:00 15000 6400 6400 \n", - "2019-01-01 02:00:00 7600 16200 16200 \n", - "2019-01-01 03:00:00 15000 7200 7200 \n", - "\n", - " supply_volume_energy price max_price min_price node \\\n", - "time \n", - "2019-01-01 01:00:00 15000 43.667 43.667 43.667 DE_1 \n", - "2019-01-01 01:00:00 7400 43.667 43.667 43.667 DE_2 \n", - "2019-01-01 02:00:00 15000 43.667 43.667 43.667 DE_1 \n", - "2019-01-01 02:00:00 7600 43.667 43.667 43.667 DE_2 \n", - "2019-01-01 03:00:00 15000 43.667 43.667 43.667 DE_1 \n", - "\n", - " product_start product_end only_hours \\\n", - "time \n", - "2019-01-01 01:00:00 2019-01-01 01:00:00 2019-01-01 02:00:00 NaN \n", - "2019-01-01 01:00:00 2019-01-01 01:00:00 2019-01-01 02:00:00 NaN \n", - "2019-01-01 02:00:00 2019-01-01 02:00:00 2019-01-01 03:00:00 NaN \n", - "2019-01-01 02:00:00 2019-01-01 02:00:00 2019-01-01 03:00:00 NaN \n", - "2019-01-01 03:00:00 2019-01-01 03:00:00 2019-01-01 04:00:00 NaN \n", - "\n", - " market_id simulation \n", - "time \n", - "2019-01-01 01:00:00 zonal tutorial_08_zonal_case \n", - "2019-01-01 01:00:00 zonal tutorial_08_zonal_case \n", - "2019-01-01 02:00:00 zonal tutorial_08_zonal_case \n", - "2019-01-01 02:00:00 zonal tutorial_08_zonal_case \n", - "2019-01-01 03:00:00 zonal tutorial_08_zonal_case " - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "# Define the path to the simulation output\n", - "output_dir = \"outputs/tutorial_08_zonal_case\"\n", - "market_meta_path = os.path.join(output_dir, \"market_meta.csv\")\n", - "\n", - "# Load the market_meta.csv file\n", - "market_meta = pd.read_csv(market_meta_path, index_col=\"time\", parse_dates=True)\n", - "# drop the first column\n", - "market_meta = market_meta.drop(columns=market_meta.columns[0])\n", - "\n", - "# Display a sample of the data\n", - "print(\"Sample of market_meta.csv:\")\n", - "display(market_meta.head())" - ] - }, - { - "cell_type": "markdown", - "id": "870b1c74", - "metadata": { - "id": "870b1c74" - }, - "source": [ - "**Explanation:**\n", - "\n", - "- **market_meta.csv:** This file contains the market outcomes for each zone and time period, including supply and demand volumes, clearing prices, and other relevant metrics.\n", - "- **Columns:**\n", - " - `supply_volume`: Total volume supplied in the zone.\n", - " - `demand_volume`: Total volume demanded in the zone.\n", - " - `demand_volume_energy`: Energy demand volume (same as `demand_volume` for energy markets).\n", - " - `supply_volume_energy`: Energy supply volume (same as `supply_volume` for energy markets).\n", - " - `price`: Clearing price in the zone for the time period.\n", - " - `max_price`: Maximum bid price accepted.\n", - " - `min_price`: Minimum bid price accepted.\n", - " - `node`: Identifier for the market zone (`DE_1` or `DE_2`).\n", - " - `product_start`: Start time of the market product.\n", - " - `product_end`: End time of the market product.\n", - " - `only_hours`: Indicator flag (not used in this context).\n", - " - `market_id`: Identifier for the market (`zonal`).\n", - " - `time`: Timestamp for the market product.\n", - " - `simulation`: Identifier for the simulation case (`tutorial_08_zonal_case`)." - ] - }, - { - "cell_type": "markdown", - "id": "d0fd6e1b", - "metadata": { - "id": "d0fd6e1b" - }, - "source": [ - "### 7.3. Plotting Clearing Prices\n", - "\n", - "To verify that the simulation results align with our previous manual demonstrations, we'll plot the clearing prices for each zone over time. This will help us observe how transmission capacities influence price convergence or divergence between zones." - ] - }, - { - "cell_type": "markdown", - "id": "934872ad", - "metadata": { - "id": "934872ad" - }, - "source": [ - "#### Processing the Market Meta Data" - ] - }, - { - "cell_type": "code", - "execution_count": 33, - "id": "fd2e3048", - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 255 - }, - "id": "fd2e3048", - "outputId": "7d9d0dc5-7042-488f-93d9-655bf4139807" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Sample of Processed Clearing Prices:\n" - ] - }, - { - "data": { - "application/vnd.google.colaboratory.intrinsic+json": { - "summary": "{\n \"name\": \"display(clearing_prices_df\",\n \"rows\": 5,\n \"fields\": [\n {\n \"column\": \"time\",\n \"properties\": {\n \"dtype\": \"date\",\n \"min\": \"2019-01-01 01:00:00\",\n \"max\": \"2019-01-01 05:00:00\",\n \"num_unique_values\": 5,\n \"samples\": [\n \"2019-01-01 02:00:00\",\n \"2019-01-01 05:00:00\",\n \"2019-01-01 03:00:00\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"DE_1_price\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 0.5477225575051661,\n \"min\": 43.667,\n \"max\": 44.667,\n \"num_unique_values\": 2,\n \"samples\": [\n 44.667,\n 43.667\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"DE_2_price\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 0.5477225575051661,\n \"min\": 43.667,\n \"max\": 44.667,\n \"num_unique_values\": 2,\n \"samples\": [\n 44.667,\n 43.667\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n }\n ]\n}", - "type": "dataframe" - }, - "text/html": [ - "\n", - "
\n", - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
DE_1_priceDE_2_price
time
2019-01-01 01:00:0043.66743.667
2019-01-01 02:00:0043.66743.667
2019-01-01 03:00:0043.66743.667
2019-01-01 04:00:0044.66744.667
2019-01-01 05:00:0044.66744.667
\n", - "
\n", - "
\n", - "\n", - "
\n", - " \n", - "\n", - " \n", - "\n", - " \n", - "
\n", - "\n", - "\n", - "
\n", - " \n", - "\n", - "\n", - "\n", - " \n", - "
\n", - "\n", - "
\n", - "
\n" - ], - "text/plain": [ - " DE_1_price DE_2_price\n", - "time \n", - "2019-01-01 01:00:00 43.667 43.667\n", - "2019-01-01 02:00:00 43.667 43.667\n", - "2019-01-01 03:00:00 43.667 43.667\n", - "2019-01-01 04:00:00 44.667 44.667\n", - "2019-01-01 05:00:00 44.667 44.667" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "# Extract unique zones\n", - "zones = market_meta[\"node\"].unique()\n", - "\n", - "# Initialize an empty DataFrame to store clearing prices per zone and time\n", - "clearing_prices_df = pd.DataFrame()\n", - "\n", - "# Populate the DataFrame with clearing prices for each zone\n", - "for zone in zones:\n", - " zone_data = market_meta[market_meta[\"node\"] == zone][[\"price\"]]\n", - " zone_data = zone_data.rename(columns={\"price\": f\"{zone}_price\"})\n", - " clearing_prices_df = (\n", - " pd.merge(\n", - " clearing_prices_df,\n", - " zone_data,\n", - " left_index=True,\n", - " right_index=True,\n", - " how=\"outer\",\n", - " )\n", - " if not clearing_prices_df.empty\n", - " else zone_data\n", - " )\n", - "\n", - "# Sort the DataFrame by time\n", - "clearing_prices_df = clearing_prices_df.sort_index()\n", - "\n", - "# Display a sample of the processed clearing prices\n", - "print(\"Sample of Processed Clearing Prices:\")\n", - "display(clearing_prices_df.head())" - ] - }, - { - "cell_type": "code", - "execution_count": 35, - "id": "87102b35", - "metadata": { - "cellView": "form", - "colab": { - "base_uri": "https://localhost:8080/", - "height": 617 - }, - "id": "87102b35", - "outputId": "ebc6d249-88cc-4df8-eeb6-2738f16351b2" - }, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - "
\n", - "
\n", - "\n", - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "# @title Plot market clearing prices\n", - "# Initialize the Plotly figure\n", - "fig = go.Figure()\n", - "\n", - "# Iterate over each zone to plot clearing prices\n", - "for zone in zones:\n", - " fig.add_trace(\n", - " go.Scatter(\n", - " x=clearing_prices_df.index,\n", - " y=clearing_prices_df[f\"{zone}_price\"],\n", - " mode=\"lines\",\n", - " name=f\"{zone} - Simulation\",\n", - " line=dict(width=2),\n", - " )\n", - " )\n", - "\n", - "# Update layout for better aesthetics and interactivity\n", - "fig.update_layout(\n", - " title=\"Clearing Prices per Zone Over Time: Simulation Results\",\n", - " xaxis_title=\"Time\",\n", - " yaxis_title=\"Clearing Price (EUR/MWh)\",\n", - " legend_title=\"Market Zones\",\n", - " xaxis=dict(\n", - " tickangle=45,\n", - " type=\"date\", # Ensure the x-axis is treated as dates\n", - " ),\n", - " hovermode=\"x unified\", # Unified hover for better comparison\n", - " template=\"plotly_white\", # Clean white background\n", - " width=1000,\n", - " height=600,\n", - ")\n", - "\n", - "# Display the interactive plot\n", - "fig.show()" - ] - }, - { - "cell_type": "markdown", - "id": "b34407b1", - "metadata": { - "id": "b34407b1" - }, - "source": [ - "**Explanation:**\n", - "\n", - "- **Plot Details:**\n", - " - **Lines:** Each zone's clearing price over time is represented by a distinct line.\n", - " - **Interactivity:** The Plotly plot allows for interactive exploration of the data, such as zooming and hovering for specific values.\n", - " - **Aesthetics:** The clean white template and clear labels enhance readability.\n", - "\n", - "- **Interpretation:**\n", - " - **Price Trends:** Observing how clearing prices fluctuate over time within each zone.\n", - " - **Impact of Transmission Capacity:** Comparing price levels between zones can reveal the effects of transmission capacities on market equilibrium. For instance, higher transmission capacity might lead to more price convergence between zones, while zero capacity could result in divergent price levels due to isolated supply and demand dynamics." - ] - }, - { - "cell_type": "markdown", - "id": "3f448fb4", - "metadata": { - "id": "3f448fb4" - }, - "source": [ - "## **Conclusion**\n", - "\n", - "Congratulations! You've successfully navigated through the **Market Zone Coupling** process using the **ASSUME Framework**. Here's a quick recap of what you've accomplished:\n", - "\n", - "### **Key Achievements:**\n", - "\n", - "1. **Market Setup:**\n", - " - **Defined Zones and Buses:** Established distinct market zones and configured their connections through transmission lines.\n", - " - **Configured Units:** Set up power plant and demand units within each zone, detailing their operational parameters.\n", - "\n", - "2. **Market Clearing Optimization:**\n", - " - **Implemented Optimization Model:** Utilized a simplified Pyomo-based model to perform market clearing, accounting for bid acceptances and power flows.\n", - " - **Simulated Transmission Scenarios:** Ran simulations with varying transmission capacities to observe their impact on energy distribution and pricing.\n", - "\n", - "3. **Result Analysis:**\n", - " - **Extracted Clearing Prices:** Retrieved and interpreted market prices from the optimization results.\n", - " - **Visualized Outcomes:** Created interactive plots to compare how different transmission capacities influence market dynamics across zones.\n", - "\n", - "### **Key Takeaways:**\n", - "\n", - "- **Impact of Transmission Capacity:** Transmission limits play a crucial role in determining energy flows and price convergence between market zones.\n", - "- **ASSUME Framework Efficiency:** ASSUME streamlines complex market simulations, making it easier to model and analyze multi-zone interactions.\n", - "\n", - "### **Next Steps:**\n", - "\n", - "- **Integrate Renewable Sources:** Expand the model to include renewable energy units and assess their impact on market dynamics.\n", - "- **Scale Up Simulations:** Apply the framework to larger, more complex market scenarios to further test its capabilities.\n", - "\n", - "Thank you for participating in this tutorial! With the foundational knowledge gained, you're now equipped to delve deeper into energy market simulations and leverage the ASSUME framework for more advanced analyses." - ] + "cells": [ + { + "cell_type": "markdown", + "id": "ff81547a", + "metadata": { + "id": "ff81547a" + }, + "source": [ + "# 8. Market Zone Coupling in the ASSUME Framework\n", + "\n", + "Welcome to the **Market Zone Coupling** tutorial for the ASSUME framework. In this workshop, we will guide you through understanding how market zone coupling is implemented within the ASSUME simulation environment. By the end of this tutorial, you will gain insights into the internal mechanisms of the framework, including how different market zones interact, how constraints are managed, how bids are assigned, and how market prices are extracted.\n", + "\n", + "**We will cover the following topics:**\n", + "\n", + "1. [**Introduction to Market Zone Coupling**](#1-introduction-to-market-zone-coupling)\n", + "2. [**Setting Up the ASSUME Framework for Market Zone Coupling**](#2-setting-up-the-assume-framework-for-market-zone-coupling)\n", + "3. [**Understanding the Market Clearing Optimization**](#3-understanding-the-market-clearing-optimization)\n", + "4. [**Creating Exemplary Input Files for Market Zone Coupling**](#4-creating-exemplary-input-files-for-market-zone-coupling)\n", + " - 4.1. [Defining Buses and Zones](#41-defining-buses-and-zones)\n", + " - 4.2. [Configuring Transmission Lines](#42-configuring-transmission-lines)\n", + " - 4.3. [Setting Up Power Plant and Demand Units](#43-setting-up-power-plant-and-demand-units)\n", + " - 4.4. [Preparing Demand Data](#44-preparing-demand-data)\n", + "5. [**Understanding the Market Clearing with Zone Coupling**](#5-reproducing-the-market-clearing-process)\n", + " - 5.1. [Calculating the Incidence Matrix](#51-calculating-the-incidence-matrix)\n", + " - 5.2. [Implementing the Simplified Market Clearing Function](#52-creating-and-mapping-market-orders)\n", + " - 5.3. [Running the Market Clearing Simulation](#53-running-the-market-clearing-simulation)\n", + " - 5.4. [Extracting and Interpreting the Results](#54-extracting-and-interpreting-the-results)\n", + " - 5.5. [Comparing Simulations](#55-comparing-simulations)\n", + "6. [**Execution with ASSUME**](#6-execution-with-assume)\n", + "7. [**Analyzing the Results**](#7-analyzing-the-results)\n", + "\n", + "Let's get started!" + ] + }, + { + "cell_type": "markdown", + "id": "76281e67", + "metadata": { + "id": "76281e67" + }, + "source": [ + "## 1. Introduction to Market Zone Coupling\n", + "\n", + "**Market Zone Coupling** is a mechanism that enables different geographical zones within an electricity market to interact and trade energy seamlessly. In the ASSUME framework, implementing market zone coupling is straightforward: by properly defining the input data and configuration files, the framework automatically manages the interactions between zones, including transmission constraints and cross-zone trading.\n", + "\n", + "This tutorial aims to provide a deeper understanding of how market zone coupling operates within ASSUME. While the framework handles much of the complexity internally, we will explore the underlying processes, such as the calculation of transmission capacities and the market clearing optimization. This detailed walkthrough is designed to enhance your comprehension of the framework's capabilities and the dynamics of multi-zone electricity markets.\n", + "\n", + "Throughout this tutorial, you will:\n", + "\n", + "- **Define Multiple Market Zones:** Segment the market into distinct zones based on geographical or operational criteria.\n", + "- **Configure Transmission Lines:** Establish connections that allow energy flow between different market zones.\n", + "- **Understand the Market Clearing Process:** Examine how the market clearing algorithm accounts for interactions and constraints across zones.\n", + "\n", + "By the end of this workshop, you will not only know how to set up market zone coupling in ASSUME but also gain insights into the internal mechanisms that drive market interactions and price formations across different zones." + ] + }, + { + "cell_type": "markdown", + "id": "42ff364e", + "metadata": { + "id": "42ff364e" + }, + "source": [ + "## 2. Setting Up the ASSUME Framework for Market Zone Coupling\n", + "\n", + "Before diving into market zone coupling, ensure that you have the ASSUME framework installed and set up correctly. If you haven't done so already, follow the steps below to install the ASSUME core package and clone the repository containing predefined scenarios.\n", + "\n", + "**Note:** If you already have the ASSUME framework installed and the repository cloned, you can skip executing the following code cells." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0dd1c254", + "metadata": { + "id": "0dd1c254", + "vscode": { + "languageId": "shellscript" } - ], - "metadata": { + }, + "outputs": [], + "source": [ + "# Install the ASSUME framework\n", + "!pip install assume-framework\n", + "\n", + "# Install Plotly if not already installed\n", + "!pip install plotly" + ] + }, + { + "cell_type": "markdown", + "id": "4266c838", + "metadata": { + "id": "4266c838" + }, + "source": [ + "Let's also import some basic libraries that we will use throughout the tutorial." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a1543685", + "metadata": { + "id": "a1543685" + }, + "outputs": [], + "source": [ + "import pandas as pd\n", + "\n", + "# import plotly for visualization\n", + "import plotly.graph_objects as go\n", + "\n", + "# import yaml for reading and writing YAML files\n", + "import yaml\n", + "\n", + "# Function to display DataFrame in Jupyter\n", + "from IPython.display import display" + ] + }, + { + "cell_type": "markdown", + "id": "902fc3a9", + "metadata": { + "id": "902fc3a9" + }, + "source": [ + "## 3. Understanding the Market Clearing Optimization\n", + "\n", + "Market clearing is a crucial component of electricity market simulations. It involves determining the optimal dispatch of supply and demand bids to maximize social welfare while respecting network constraints.\n", + "\n", + "In the context of market zone coupling, the market clearing process must account for:\n", + "\n", + "- **Connection Between Zones:** Transmission lines that allow energy flow between different market zones.\n", + "- **Constraints:** Limits on transmission capacities and ensuring energy balance within and across zones.\n", + "- **Bid Assignment:** Properly assigning bids to their respective zones and considering cross-zone trading.\n", + "- **Price Extraction:** Determining market prices for each zone based on the cleared bids and network constraints.\n", + "\n", + "The ASSUME framework uses Pyomo to formulate and solve the market clearing optimization problem. Below is a simplified version of the market clearing function, highlighting key components related to zone coupling." + ] + }, + { + "cell_type": "markdown", + "id": "4f874cfd", + "metadata": { + "id": "4f874cfd" + }, + "source": [ + "### Simplified Market Clearing Optimization Problem\n", + "\n", + "We consider a simplified market clearing optimization model focusing on zone coupling, aiming to minimize the total cost.\n", + "\n", + "#### Sets and Variables:\n", + "- $T$: Set of time periods.\n", + "- $N$: Set of nodes (zones).\n", + "- $L$: Set of lines.\n", + "- $x_o \\in [0, 1]$: Bid acceptance ratio for order $o$.\n", + "- $f_{t, l} \\in \\mathbb{R}$: Power flow on line $l$ at time $t$.\n", + "\n", + "#### Constants:\n", + "- $P_o$: Price of order $o$.\n", + "- $V_o$: Volume of order $o$.\n", + "- $S_l$: Nominal capacity of line $l$.\n", + "\n", + "#### Objective Function:\n", + "Minimize the total cost of accepted orders:\n", + "\n", + "$$\n", + "\\min \\sum_{o \\in O} P_o V_o x_o\n", + "$$\n", + "\n", + "#### Constraints:\n", + "\n", + "1. **Energy Balance for Each Node and Time Period**:\n", + "\n", + "$$\n", + "\\sum_{\\substack{o \\in O \\\\ \\text{node}(o) = n \\\\ \\text{time}(o) = t}} V_o x_o + \\sum_{l \\in L} I_{n, l} f_{t, l} = 0 \\quad \\forall n \\in N, \\, t \\in T\n", + "$$\n", + "\n", + "Where:\n", + "- $I_{n, l}$ is the incidence value for node $n$ and line $l$ (from the incidence matrix).\n", + "\n", + "2. **Transmission Capacity Constraints for Each Line and Time Period**:\n", + "\n", + "$$\n", + "-S_l \\leq f_{t, l} \\leq S_l \\quad \\forall l \\in L, \\, t \\in T\n", + "$$\n", + "\n", + "#### Summary:\n", + "The goal is to minimize the total cost while ensuring energy balance at each node and respecting transmission line capacity limits for each time period.\n", + "\n", + "In the actual ASSUME Framework, the optimization problem is more complex and includes additional constraints and variables, and supports also additional bid types such as block orders and linked orders. However, the simplified model above captures the essence of market clearing with zone coupling.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e2be3fe2", + "metadata": { + "id": "e2be3fe2" + }, + "outputs": [], + "source": [ + "import pyomo.environ as pyo\n", + "from pyomo.opt import SolverFactory, TerminationCondition\n", + "\n", + "\n", + "def simplified_market_clearing_opt(orders, incidence_matrix, lines):\n", + " \"\"\"\n", + " Simplified market clearing optimization focusing on zone coupling.\n", + "\n", + " Args:\n", + " orders (dict): Dictionary of orders with bid_id as keys.\n", + " lines (DataFrame): DataFrame containing information about the transmission lines.\n", + " incidence_matrix (DataFrame): Incidence matrix describing the network structure.\n", + "\n", + " Returns:\n", + " model (ConcreteModel): The solved Pyomo model.\n", + " results (SolverResults): The solver results.\n", + " \"\"\"\n", + " nodes = list(incidence_matrix.index)\n", + " line_ids = list(incidence_matrix.columns)\n", + "\n", + " model = pyo.ConcreteModel()\n", + " # Define dual suffix\n", + " model.dual = pyo.Suffix(direction=pyo.Suffix.IMPORT)\n", + "\n", + " # Define the set of time periods\n", + " model.T = pyo.Set(\n", + " initialize=sorted(set(order[\"time\"] for order in orders.values())),\n", + " doc=\"timesteps\",\n", + " )\n", + " # Define the set of nodes (zones)\n", + " model.nodes = pyo.Set(initialize=nodes, doc=\"nodes\")\n", + " # Define the set of lines\n", + " model.lines = pyo.Set(initialize=line_ids, doc=\"lines\")\n", + "\n", + " # Decision variables for bid acceptance ratios (0 to 1)\n", + " model.x = pyo.Var(\n", + " orders.keys(),\n", + " domain=pyo.NonNegativeReals,\n", + " bounds=(0, 1),\n", + " doc=\"bid_acceptance_ratio\",\n", + " )\n", + "\n", + " # Decision variables for power flows on each line at each time period\n", + " model.flows = pyo.Var(model.T, model.lines, domain=pyo.Reals, doc=\"power_flows\")\n", + "\n", + " # Energy balance constraint for each node and time period\n", + " def energy_balance_rule(model, node, t):\n", + " balance_expr = 0.0\n", + " # Add contributions from orders\n", + " for order_key, order in orders.items():\n", + " if order[\"node\"] == node and order[\"time\"] == t:\n", + " balance_expr += order[\"volume\"] * model.x[order_key]\n", + "\n", + " # Add contributions from line flows based on the incidence matrix\n", + " if incidence_matrix is not None:\n", + " for line in model.lines:\n", + " incidence_value = incidence_matrix.loc[node, line]\n", + " if incidence_value != 0:\n", + " balance_expr += incidence_value * model.flows[t, line]\n", + "\n", + " return balance_expr == 0\n", + "\n", + " model.energy_balance = pyo.Constraint(\n", + " model.nodes, model.T, rule=energy_balance_rule\n", + " )\n", + "\n", + " # Transmission capacity constraints for each line and time period\n", + " def transmission_capacity_rule(model, t, line):\n", + " \"\"\"\n", + " Limits the power flow on each line based on its capacity.\n", + " \"\"\"\n", + " capacity = lines.at[line, \"s_nom\"]\n", + " return (-capacity, model.flows[t, line], capacity)\n", + "\n", + " # Apply transmission capacity constraints to all lines and time periods\n", + " model.transmission_constraints = pyo.Constraint(\n", + " model.T, model.lines, rule=transmission_capacity_rule\n", + " )\n", + "\n", + " # Objective: Minimize total cost (sum of bid prices multiplied by accepted volumes)\n", + " model.objective = pyo.Objective(\n", + " expr=sum(orders[o][\"price\"] * orders[o][\"volume\"] * model.x[o] for o in orders),\n", + " sense=pyo.minimize,\n", + " doc=\"Total Cost Minimization\",\n", + " )\n", + "\n", + " # Choose the solver (HIGHS is used here)\n", + " solver = SolverFactory(\"appsi_highs\")\n", + " results = solver.solve(model)\n", + "\n", + " # Check if the solver found an optimal solution\n", + " if results.solver.termination_condition != TerminationCondition.optimal:\n", + " raise Exception(\"Solver did not find an optimal solution.\")\n", + "\n", + " return model, results" + ] + }, + { + "cell_type": "markdown", + "id": "8d42c532", + "metadata": { + "id": "8d42c532" + }, + "source": [ + "The above function is a simplified representation focusing on the essential aspects of market zone coupling. In the following sections, we will delve deeper into creating input files and mimicking the market clearing process using this function to understand the inner workings of the ASSUME framework." + ] + }, + { + "cell_type": "markdown", + "id": "11addaf0", + "metadata": { + "id": "11addaf0" + }, + "source": [ + "## 4. Creating Exemplary Input Files for Market Zone Coupling\n", + "\n", + "To implement market zone coupling, users need to prepare specific input files that define the network's structure, units, and demand profiles. Below, we will guide you through creating the necessary DataFrames for buses, transmission lines, power plant units, demand units, and demand profiles." + ] + }, + { + "cell_type": "markdown", + "id": "2a095ffb", + "metadata": { + "id": "2a095ffb" + }, + "source": [ + "### 4.1. Defining Buses and Zones\n", + "\n", + "**Buses** represent nodes in the network where energy can be injected or withdrawn. Each bus is assigned to a **zone**, which groups buses into market areas. This zoning facilitates market coupling by managing interactions between different market regions." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c1731cdc", + "metadata": { + "cellView": "form", "colab": { - "provenance": [], - "toc_visible": true - }, - "jupytext": { - "cell_metadata_filter": "-all", - "main_language": "python", - "notebook_metadata_filter": "-all" - }, - "kernelspec": { - "display_name": "assume-framework", - "language": "python", - "name": "python3" - }, - "language_info": { - "name": "python", - "version": "3.11.7" - } + "base_uri": "https://localhost:8080/", + "height": 192 + }, + "id": "c1731cdc", + "outputId": "0d0a8060-aa86-4ba8-a0b1-0e528bc9d0d2" + }, + "outputs": [], + "source": [ + "# @title Define the buses DataFrame with three nodes and two zones\n", + "buses = pd.DataFrame(\n", + " {\n", + " \"name\": [\"north_1\", \"north_2\", \"south\"],\n", + " \"v_nom\": [380.0, 380.0, 380.0],\n", + " \"zone_id\": [\"DE_1\", \"DE_1\", \"DE_2\"],\n", + " \"x\": [10.0, 9.5, 11.6],\n", + " \"y\": [54.0, 53.5, 48.1],\n", + " }\n", + ").set_index(\"name\")\n", + "\n", + "# Display the buses DataFrame\n", + "print(\"Buses DataFrame:\")\n", + "display(buses)" + ] + }, + { + "cell_type": "markdown", + "id": "50a27c51", + "metadata": { + "id": "50a27c51" + }, + "source": [ + "**Explanation:**\n", + "\n", + "- **name:** Identifier for each bus (`north_1`, `north_2`, and `south`).\n", + "- **v_nom:** Nominal voltage level (in kV) for all buses.\n", + "- **zone_id:** Identifier for the market zone to which the bus belongs (`DE_1` for north buses and `DE_2` for the south bus).\n", + "- **x, y:** Geographical coordinates (optional, can be used for mapping or spatial analyses)." + ] + }, + { + "cell_type": "markdown", + "id": "1545e3bf", + "metadata": { + "id": "1545e3bf" + }, + "source": [ + "### 4.2. Configuring Transmission Lines\n", + "\n", + "**Transmission Lines** connect buses, allowing energy to flow between them. Each line has a specified capacity and electrical parameters." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "64769ec7", + "metadata": { + "cellView": "form", + "colab": { + "base_uri": "https://localhost:8080/", + "height": 192 + }, + "id": "64769ec7", + "outputId": "a47490cb-d06c-4152-8be6-64985a8dcbd0" + }, + "outputs": [], + "source": [ + "# @title Define three transmission lines\n", + "lines = pd.DataFrame(\n", + " {\n", + " \"name\": [\"Line_N1_S\", \"Line_N2_S\", \"Line_N1_N2\"],\n", + " \"bus0\": [\"north_1\", \"north_2\", \"north_1\"],\n", + " \"bus1\": [\"south\", \"south\", \"north_2\"],\n", + " \"s_nom\": [5000.0, 5000.0, 5000.0],\n", + " \"x\": [0.01, 0.01, 0.01],\n", + " \"r\": [0.001, 0.001, 0.001],\n", + " }\n", + ").set_index(\"name\")\n", + "\n", + "print(\"Transmission Lines DataFrame:\")\n", + "display(lines)" + ] + }, + { + "cell_type": "markdown", + "id": "f2290793", + "metadata": { + "id": "f2290793" + }, + "source": [ + "**Explanation:**\n", + "\n", + "- **name:** Identifier for each transmission line (`Line_N1_S`, `Line_N2_S`, and `Line_N1_N2`).\n", + "- **bus0, bus1:** The two buses that the line connects.\n", + "- **s_nom:** Nominal apparent power capacity of the line (in MVA).\n", + "- **x:** Reactance of the line (in per unit).\n", + "- **r:** Resistance of the line (in per unit)." + ] + }, + { + "cell_type": "markdown", + "id": "c931cf9f", + "metadata": { + "id": "c931cf9f" + }, + "source": [ + "### 4.3. Setting Up Power Plant and Demand Units\n", + "\n", + "**Power Plant Units** represent energy generation sources, while **Demand Units** represent consumption. Each unit is associated with a specific bus (node) and has operational parameters that define its behavior in the market." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8a1f9e35", + "metadata": { + "cellView": "form", + "colab": { + "base_uri": "https://localhost:8080/", + "height": 224 + }, + "id": "8a1f9e35", + "outputId": "b7d43816-40af-4526-bb64-53d4a20ba911" + }, + "outputs": [], + "source": [ + "# @title Create the power plant units DataFrame\n", + "\n", + "# Define the total number of units\n", + "num_units = 30 # Reduced for simplicity\n", + "\n", + "# Generate the 'name' column: Unit 1 to Unit 30\n", + "names = [f\"Unit {i}\" for i in range(1, num_units + 1)]\n", + "\n", + "# All other columns with constant values\n", + "technology = [\"nuclear\"] * num_units\n", + "bidding_zonal = [\"naive_eom\"] * num_units\n", + "fuel_type = [\"uranium\"] * num_units\n", + "emission_factor = [0.0] * num_units\n", + "max_power = [1000.0] * num_units\n", + "min_power = [0.0] * num_units\n", + "efficiency = [0.3] * num_units\n", + "\n", + "# Generate 'additional_cost':\n", + "# - North units (1-15): 5 to 19\n", + "# - South units (16-30): 20 to 34\n", + "additional_cost = list(range(5, 5 + num_units))\n", + "\n", + "# Initialize 'node' and 'unit_operator' lists\n", + "node = []\n", + "unit_operator = []\n", + "\n", + "for i in range(1, num_units + 1):\n", + " if 1 <= i <= 8:\n", + " node.append(\"north_1\") # All north units connected to 'north_1'\n", + " unit_operator.append(\"Operator North\")\n", + " elif 9 <= i <= 15:\n", + " node.append(\"north_2\")\n", + " unit_operator.append(\"Operator North\")\n", + " else:\n", + " node.append(\"south\") # All south units connected to 'south'\n", + " unit_operator.append(\"Operator South\")\n", + "\n", + "# Create the DataFrame\n", + "powerplant_units = pd.DataFrame(\n", + " {\n", + " \"name\": names,\n", + " \"technology\": technology,\n", + " \"bidding_zonal\": bidding_zonal, # bidding strategy used to bid on the zonal market. Should be same name as in config file\n", + " \"fuel_type\": fuel_type,\n", + " \"emission_factor\": emission_factor,\n", + " \"max_power\": max_power,\n", + " \"min_power\": min_power,\n", + " \"efficiency\": efficiency,\n", + " \"additional_cost\": additional_cost,\n", + " \"node\": node,\n", + " \"unit_operator\": unit_operator,\n", + " }\n", + ")\n", + "\n", + "print(\"Power Plant Units DataFrame:\")\n", + "display(powerplant_units.head())" + ] + }, + { + "cell_type": "markdown", + "id": "Uwp8L0rombac", + "metadata": { + "id": "Uwp8L0rombac" + }, + "source": [ + "- **Power Plant Units:**\n", + " - **name:** Identifier for each power plant unit (`Unit 1` to `Unit 30`).\n", + " - **technology:** Type of technology (`nuclear` for all units).\n", + " - **bidding_nodal:** Bidding strategy used (`naive_eom` for all units).\n", + " - **fuel_type:** Type of fuel used (`uranium` for all units).\n", + " - **emission_factor:** Emissions per unit of energy produced (`0.0` for all units).\n", + " - **max_power, min_power:** Operational power limits (`1000.0` MW max, `0.0` MW min for all units).\n", + " - **efficiency:** Conversion efficiency (`0.3` for all units).\n", + " - **additional_cost:** Additional operational costs (`5` to `34`, with southern units being more expensive).\n", + " - **node:** The bus (zone) to which the unit is connected (`north_1` for units `1-15`, `south` for units `16-30`).\n", + " - **unit_operator:** Operator responsible for the unit (`Operator North` for northern units, `Operator South` for southern units)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "16f8a13c", + "metadata": { + "cellView": "form", + "colab": { + "base_uri": "https://localhost:8080/", + "height": 161 + }, + "id": "16f8a13c", + "outputId": "aad8a140-a6ed-47fd-d06e-1e794aa1a829" + }, + "outputs": [], + "source": [ + "# @title Define the demand units\n", + "demand_units = pd.DataFrame(\n", + " {\n", + " \"name\": [\"demand_north_1\", \"demand_north_2\", \"demand_south\"],\n", + " \"technology\": [\"inflex_demand\"] * 3,\n", + " \"bidding_zonal\": [\"naive_eom\"] * 3,\n", + " \"max_power\": [100000, 100000, 100000],\n", + " \"min_power\": [0, 0, 0],\n", + " \"unit_operator\": [\"eom_de\"] * 3,\n", + " \"node\": [\"north_1\", \"north_2\", \"south\"],\n", + " }\n", + ")\n", + "\n", + "# Display the demand_units DataFrame\n", + "print(\"Demand Units DataFrame:\")\n", + "display(demand_units)" + ] + }, + { + "cell_type": "markdown", + "id": "d847ac5f", + "metadata": { + "id": "d847ac5f" + }, + "source": [ + "- **Demand Units:**\n", + " - **name:** Identifier for each demand unit (`demand_north_1`, `demand_north_2`, and `demand_south`).\n", + " - **technology:** Type of demand (`inflex_demand` for all units).\n", + " - **bidding_zonal:** Bidding strategy used (`naive_eom` for all units).\n", + " - **max_power, min_power:** Operational power limits (`100000` MW max, `0` MW min for all units).\n", + " - **unit_operator:** Operator responsible for the unit (`eom_de` for all units).\n", + " - **node:** The bus (zone) to which the unit is connected (`north_1`, `north_2`, and `south`)." + ] + }, + { + "cell_type": "markdown", + "id": "8f1d684a", + "metadata": { + "id": "8f1d684a" + }, + "source": [ + "### 4.4. Preparing Demand Data\n", + "\n", + "**Demand Data** provides the expected electricity demand for each demand unit over time. This data is essential for simulating how demand varies and affects market dynamics." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a0591f14", + "metadata": { + "cellView": "form", + "colab": { + "base_uri": "https://localhost:8080/", + "height": 255 + }, + "id": "a0591f14", + "outputId": "d590647b-7522-4fce-bfe7-dc66b7b566e8" + }, + "outputs": [], + "source": [ + "# @title Define the demand DataFrame\n", + "\n", + "# the demand for the north_1 and north_2 zones increases by 400 MW per hour\n", + "# while the demand for the south zone decreases by 600 MW per hour\n", + "# the demand starts at 2400 MW for the north zones and 17400 MW for the south zone\n", + "demand_df = pd.DataFrame(\n", + " {\n", + " \"datetime\": pd.date_range(start=\"2019-01-01\", periods=24, freq=\"h\"),\n", + " \"demand_north_1\": [2400 + i * 400 for i in range(24)],\n", + " \"demand_north_2\": [2400 + i * 400 for i in range(24)],\n", + " \"demand_south\": [17400 - i * 600 for i in range(24)],\n", + " }\n", + ")\n", + "\n", + "# Convert the 'datetime' column to datetime objects and set as index\n", + "demand_df.set_index(\"datetime\", inplace=True)\n", + "\n", + "# Display the demand_df DataFrame\n", + "print(\"Demand DataFrame:\")\n", + "display(demand_df.head())" + ] + }, + { + "cell_type": "markdown", + "id": "1756e6e3", + "metadata": { + "id": "1756e6e3" + }, + "source": [ + "**Explanation:**\n", + "\n", + "- **datetime:** Timestamp for each demand value.\n", + "- **demand_north_1, demand_north_2, demand_south:** Demand values for each respective demand unit.\n", + "\n", + "**Note:** The demand timeseries has been designed to be fulfillable by the defined power plants in both zones." + ] + }, + { + "cell_type": "markdown", + "id": "478211c6", + "metadata": { + "id": "478211c6" + }, + "source": [ + "## 5. Reproducing the Market Clearing Process\n", + "\n", + "With the input files prepared, we can now reproduce the market clearing process using the simplified market clearing function. This will help us understand how different market zones interact, how constraints are managed, how bids are assigned, and how market prices are extracted." + ] + }, + { + "cell_type": "markdown", + "id": "01680700", + "metadata": { + "id": "01680700" + }, + "source": [ + "### 5.1. Calculating the Incidence Matrix\n", + "\n", + "The **Incidence Matrix** represents the connection relationships between different nodes in a network. In the context of market zones, it indicates which transmission lines connect which zones. The incidence matrix is a binary matrix where each element denotes whether a particular node is connected to a line or not. This matrix is essential for understanding the structure of the transmission network and for formulating power flow equations during the market clearing process." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c9fb8458", + "metadata": { + "cellView": "form", + "colab": { + "base_uri": "https://localhost:8080/", + "height": 142 + }, + "id": "c9fb8458", + "outputId": "380d3471-2a05-4cf2-bd37-77b944a6dc98" + }, + "outputs": [], + "source": [ + "# @title Create the incidence matrix\n", + "def create_incidence_matrix(lines, buses, zones_id=None):\n", + " # Determine nodes based on whether we're working with zones or individual buses\n", + " if zones_id:\n", + " nodes = buses[zones_id].unique() # Use zones as nodes\n", + " node_mapping = buses[zones_id].to_dict() # Map bus IDs to zones\n", + " else:\n", + " nodes = buses.index.values # Use buses as nodes\n", + " node_mapping = {bus: bus for bus in nodes} # Identity mapping for buses\n", + "\n", + " # Use the line indices as columns for the incidence matrix\n", + " line_indices = lines.index.values\n", + "\n", + " # Initialize incidence matrix as a DataFrame for easier label-based indexing\n", + " incidence_matrix = pd.DataFrame(0, index=nodes, columns=line_indices)\n", + "\n", + " # Fill in the incidence matrix by iterating over lines\n", + " for line_idx, line in lines.iterrows():\n", + " bus0 = line[\"bus0\"]\n", + " bus1 = line[\"bus1\"]\n", + "\n", + " # Retrieve mapped nodes (zones or buses)\n", + " node0 = node_mapping.get(bus0)\n", + " node1 = node_mapping.get(bus1)\n", + "\n", + " # Ensure both nodes are valid and part of the defined nodes\n", + " if (\n", + " node0 is not None\n", + " and node1 is not None\n", + " and node0 in nodes\n", + " and node1 in nodes\n", + " ):\n", + " if node0 != node1: # Only create incidence for different nodes\n", + " # Set incidence values: +1 for the \"from\" node and -1 for the \"to\" node\n", + " incidence_matrix.at[node0, line_idx] = (\n", + " 1 # Outgoing from bus0 (or zone0)\n", + " )\n", + " incidence_matrix.at[node1, line_idx] = -1 # Incoming to bus1 (or zone1)\n", + "\n", + " # Return the incidence matrix as a DataFrame\n", + " return incidence_matrix\n", + "\n", + "\n", + "# Calculate the incidence matrix\n", + "incidence_matrix = create_incidence_matrix(lines, buses, \"zone_id\")\n", + "\n", + "print(\"Calculated Incidence Matrix between Zones:\")\n", + "display(incidence_matrix)" + ] + }, + { + "cell_type": "markdown", + "id": "61e9050c", + "metadata": { + "id": "61e9050c" + }, + "source": [ + "**Explanation:**\n", + "\n", + "- **Nodes (Zones):** Extracted from the `buses` DataFrame (`DE_1` and `DE_2`).\n", + "- **Transmission Lines:** Iterated over to sum their capacities between different zones.\n", + "- **Bidirectional Flow Assumption:** Transmission capacities are added in both directions (`DE_1 -> DE_2` and `DE_2 -> DE_1`).\n", + "- **Lower Triangle Negative Values:** To indicate the opposite direction of power flow, capacities in the lower triangle of the matrix are converted to negative values." + ] + }, + { + "cell_type": "markdown", + "id": "12ccae5f", + "metadata": { + "id": "12ccae5f" + }, + "source": [ + "### 5.2. Creating and Mapping Market Orders\n", + "\n", + "We will construct a dictionary of market orders representing supply and demand bids from power plants and demand units.\n", + "The orders include details such as price, volume, location (node), and time. Once the orders are generated, they will be\n", + "mapped from nodes to corresponding zones using a pre-defined node-to-zone mapping." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4f7366ae", + "metadata": { + "cellView": "form", + "colab": { + "base_uri": "https://localhost:8080/", + "height": 225 + }, + "id": "4f7366ae", + "outputId": "1c291cb1-8e7b-4e36-cce9-ddd00735225d" + }, + "outputs": [], + "source": [ + "# @title Construct Orders and Map Nodes to Zones\n", + "# Initialize orders dictionary\n", + "orders = {}\n", + "\n", + "# Add power plant bids\n", + "for _, row in powerplant_units.iterrows():\n", + " bid_id = row[\"name\"]\n", + " for timestamp in demand_df.index:\n", + " orders[f\"{bid_id}_{timestamp}\"] = {\n", + " \"price\": row[\"additional_cost\"], # Assuming additional_cost as bid price\n", + " \"volume\": row[\"max_power\"], # Assuming max_power as bid volume\n", + " \"node\": row[\"node\"],\n", + " \"time\": timestamp,\n", + " }\n", + "\n", + "# Add demand bids\n", + "for _, row in demand_units.iterrows():\n", + " bid_id = row[\"name\"]\n", + " for timestamp in demand_df.index:\n", + " orders[f\"{bid_id}_{timestamp}\"] = {\n", + " \"price\": 100, # Demand bids with high price\n", + " \"volume\": -demand_df.loc[\n", + " timestamp, row[\"name\"]\n", + " ], # Negative volume for demand\n", + " \"node\": row[\"node\"],\n", + " \"time\": timestamp,\n", + " }\n", + "\n", + "# Display a sample order\n", + "print(\"\\nSample Supply Order:\")\n", + "display(orders[\"Unit 1_2019-01-01 00:00:00\"])\n", + "\n", + "print(\"\\nSample Demand Order:\")\n", + "display(orders[\"demand_north_1_2019-01-01 00:00:00\"])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e8b8a17f", + "metadata": { + "cellView": "form", + "colab": { + "base_uri": "https://localhost:8080/", + "height": 224 + }, + "id": "e8b8a17f", + "outputId": "ae3db259-f2e7-4b60-91b1-ca130140fb30" + }, + "outputs": [], + "source": [ + "# @title Map the orders to zones\n", + "# Create a mapping from node_id to zone_id\n", + "node_mapping = buses[\"zone_id\"].to_dict()\n", + "\n", + "# Create a new dictionary with mapped zone IDs\n", + "orders_mapped = {}\n", + "for bid_id, bid in orders.items():\n", + " original_node = bid[\"node\"]\n", + " mapped_zone = node_mapping.get(\n", + " original_node, original_node\n", + " ) # Default to original_node if not found\n", + " orders_mapped[bid_id] = {\n", + " \"price\": bid[\"price\"],\n", + " \"volume\": bid[\"volume\"],\n", + " \"node\": mapped_zone, # Replace bus with zone ID\n", + " \"time\": bid[\"time\"],\n", + " }\n", + "\n", + "# Display the mapped orders\n", + "print(\"Mapped Orders:\")\n", + "display(pd.DataFrame(orders_mapped).T.head())" + ] + }, + { + "cell_type": "markdown", + "id": "1a5d589c", + "metadata": { + "id": "1a5d589c" + }, + "source": [ + "**Explanation:**\n", + "\n", + "- **Power Plant Bids:** Each power plant unit submits a bid for each time period with its `additional_cost` as the bid price and `max_power` as the bid volume.\n", + "- **Demand Bids:** Each demand unit submits a bid for each time period with a high price (set to 100) and a negative volume representing the demand.\n", + "- **Node to Zone Mapping:** After creating the bids, the node information is mapped to corresponding zones for further market clearing steps.\n", + " The mapping uses a pre-defined dictionary (`node_mapping`) to replace each node ID with the corresponding zone ID. In ASSUME, this mapping happens automatically on the market side, but we are simulating it here for educational purposes." + ] + }, + { + "cell_type": "markdown", + "id": "f11b487c", + "metadata": { + "id": "f11b487c" + }, + "source": [ + "### 5.3. Running the Market Clearing Simulation\n", + "\n", + "We will conduct three simulations:\n", + "\n", + "1. **Simulation 1:** Transmission capacities between `DE_1` (north) and `DE_2` (south) are **zero**.\n", + "2. **Simulation 2:** Transmission capacities between `DE_1` (north) and `DE_2` (south) are **medium**.\n", + "3. **Simulation 3:** Transmission capacities between `DE_1` (north) and `DE_2` (south) are **high**." + ] + }, + { + "cell_type": "markdown", + "id": "07082c73", + "metadata": { + "id": "07082c73" + }, + "source": [ + "#### Simulation 1: Zero Transmission Capacity Between Zones" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1c7dfee2", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 210 + }, + "id": "1c7dfee2", + "outputId": "86090b82-98e1-4d3b-bb1b-74b3c1c37e43" + }, + "outputs": [], + "source": [ + "print(\"### Simulation 1: Zero Transmission Capacity Between Zones\")\n", + "\n", + "lines_sim1 = lines.copy()\n", + "lines_sim1[\"s_nom\"] = 0 # Set transmission capacity to zero for all lines\n", + "\n", + "print(\"Transmission Lines for Simulation 1:\")\n", + "display(lines_sim1)\n", + "\n", + "# Run the simplified market clearing for Simulation 1\n", + "model_sim1, results_sim1 = simplified_market_clearing_opt(\n", + " orders=orders_mapped,\n", + " incidence_matrix=incidence_matrix,\n", + " lines=lines_sim1,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "aef7c083", + "metadata": { + "id": "aef7c083" + }, + "source": [ + "#### Simulation 2: Medium Transmission Capacity Between Zones" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "86304253", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 210 + }, + "id": "86304253", + "outputId": "3fa73e8b-d0e3-4fe8-d88c-1a896fb3e1ff" + }, + "outputs": [], + "source": [ + "print(\"### Simulation 2: Medium Transmission Capacity Between Zones\")\n", + "\n", + "# Define the lines for Simulation 2 with medium transmission capacity\n", + "lines_sim2 = lines.copy()\n", + "lines_sim2[\"s_nom\"] = 3000.0 # Set transmission capacity to 3000 MW for all lines\n", + "\n", + "# Display the incidence matrix for Simulation 2\n", + "print(\"Transmission Lines for Simulation 2:\")\n", + "display(lines_sim2)\n", + "\n", + "# Run the simplified market clearing for Simulation 2\n", + "model_sim2, results_sim2 = simplified_market_clearing_opt(\n", + " orders=orders_mapped,\n", + " incidence_matrix=incidence_matrix,\n", + " lines=lines_sim2,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "5c721991", + "metadata": { + "id": "5c721991" + }, + "source": [ + "#### Simulation 3: High Transmission Capacity Between Zones" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a1c7f344", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 210 + }, + "id": "a1c7f344", + "lines_to_end_of_cell_marker": 0, + "lines_to_next_cell": 1, + "outputId": "78e208e2-81f7-4678-9adc-bbdddd2802ea" + }, + "outputs": [], + "source": [ + "print(\"### Simulation 3: High Transmission Capacity Between Zones\")\n", + "\n", + "# Define the lines for Simulation 3 with high transmission capacity\n", + "lines_sim3 = lines.copy()\n", + "lines_sim3[\"s_nom\"] = 5000.0 # Set transmission capacity to 5000 MW for all lines\n", + "\n", + "# Display the line capacities for Simulation 3\n", + "print(\"Transmission Lines for Simulation 3:\")\n", + "display(lines_sim3)\n", + "\n", + "# Run the simplified market clearing for Simulation 3\n", + "model_sim3, results_sim3 = simplified_market_clearing_opt(\n", + " orders=orders_mapped,\n", + " incidence_matrix=incidence_matrix,\n", + " lines=lines_sim3,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "661e6c30", + "metadata": { + "id": "661e6c30" + }, + "source": [ + "### 5.4. Extracting and Interpreting the Results\n", + "\n", + "After running all three simulations, we can extract the results to understand how the presence or absence of transmission capacity affects bid acceptances and power flows between zones.\n", + "\n", + "#### Extracting Clearing Prices\n", + "\n", + "The **clearing prices** for each market zone and time period are extracted using the dual variables associated with the energy balance constraints in the optimization model. Specifically, the dual variable of the energy balance constraint for a given zone and time period represents the marginal price of electricity in that zone at that time.\n", + "\n", + "In the `extract_results` function, the following steps are performed to obtain the clearing prices:\n", + "\n", + "1. **Energy Balance Constraints:** For each zone and time period, the energy balance equation ensures that the total supply plus imports minus exports equals the demand.\n", + "2. **Dual Variables:** The dual variable (`model.dual[model.energy_balance[node, t]]`) associated with each energy balance constraint captures the sensitivity of the objective function (total cost) to a marginal increase in demand or supply.\n", + "3. **Clearing Price Interpretation:** The value of the dual variable corresponds to the clearing price in the respective zone and time period, reflecting the cost of supplying an additional unit of electricity.\n", + "\n", + "This method leverages the duality in optimization to efficiently extract market prices resulting from the optimal dispatch of bids under the given constraints." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bdcc49e7", + "metadata": { + "cellView": "form", + "id": "bdcc49e7" + }, + "outputs": [], + "source": [ + "# @title Function to extract market clearing results from the optimization model\n", + "def extract_results(model, incidence_matrix):\n", + " nodes = list(incidence_matrix.index)\n", + " lines = list(incidence_matrix.columns)\n", + "\n", + " # Extract accepted bid ratios using a dictionary comprehension\n", + " accepted_bids = {\n", + " o: pyo.value(model.x[o]) for o in model.x if pyo.value(model.x[o]) > 0\n", + " }\n", + "\n", + " # Extract power flows on each line for each time period\n", + " power_flows = [\n", + " {\"time\": t, \"line\": line, \"flow_MW\": pyo.value(model.flows[t, line])}\n", + " for t in model.T\n", + " for line in lines\n", + " if pyo.value(model.flows[t, line]) != 0\n", + " ]\n", + " power_flows_df = pd.DataFrame(power_flows)\n", + "\n", + " # Extract market clearing prices from dual variables\n", + " clearing_prices = [\n", + " {\n", + " \"zone\": node,\n", + " \"time\": t,\n", + " \"clearing_price\": pyo.value(model.dual[model.energy_balance[node, t]]),\n", + " }\n", + " for node in nodes\n", + " for t in model.T\n", + " ]\n", + " clearing_prices_df = pd.DataFrame(clearing_prices)\n", + "\n", + " return accepted_bids, power_flows_df, clearing_prices_df" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "512ed95f", + "metadata": { + "id": "512ed95f" + }, + "outputs": [], + "source": [ + "# Extract results for Simulation 1\n", + "accepted_bids_sim1, power_flows_df_sim1, clearing_prices_df_sim1 = extract_results(\n", + " model_sim1, incidence_matrix\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7b32b7c3", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 70 + }, + "id": "7b32b7c3", + "outputId": "7d56dd2f-8ab9-4a95-df0b-dbd6aac660e4" + }, + "outputs": [], + "source": [ + "print(\"Simulation 1: Power Flows Between Zones\")\n", + "display(power_flows_df_sim1.head())" + ] + }, + { + "cell_type": "markdown", + "id": "Q37fGve_m7sf", + "metadata": { + "id": "Q37fGve_m7sf" + }, + "source": [ + "As it is to be expected, there are no flows printed since there is no transfer capacity available." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2d386677", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 413 + }, + "id": "2d386677", + "outputId": "7062cc2c-e168-45a6-9294-5ea193ad78c2" + }, + "outputs": [], + "source": [ + "print(\"Simulation 1: Clearing Prices per Zone and Time\")\n", + "display(clearing_prices_df_sim1.loc[clearing_prices_df_sim1[\"zone\"] == \"DE_1\"].head())\n", + "display(clearing_prices_df_sim1.loc[clearing_prices_df_sim1[\"zone\"] == \"DE_2\"].head())" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d8327407", + "metadata": { + "id": "d8327407" + }, + "outputs": [], + "source": [ + "# Extract results for Simulation 2\n", + "accepted_bids_sim2, power_flows_df_sim2, clearing_prices_df_sim2 = extract_results(\n", + " model_sim2, incidence_matrix\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9b5fc1de", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 224 + }, + "id": "9b5fc1de", + "outputId": "25af541d-12cb-47d6-bc08-92ee847cd820" + }, + "outputs": [], + "source": [ + "print(\"Simulation 2: Power Flows Between Zones\")\n", + "display(power_flows_df_sim2.head())" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b7c5d148", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 413 + }, + "id": "b7c5d148", + "outputId": "4abfe739-2b01-485c-cde7-e385debad088" + }, + "outputs": [], + "source": [ + "print(\"Simulation 2: Clearing Prices per Zone and Time\")\n", + "display(clearing_prices_df_sim2.loc[clearing_prices_df_sim2[\"zone\"] == \"DE_1\"].head())\n", + "display(clearing_prices_df_sim2.loc[clearing_prices_df_sim2[\"zone\"] == \"DE_2\"].head())" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7f850cf5", + "metadata": { + "id": "7f850cf5" + }, + "outputs": [], + "source": [ + "# Extract results for Simulation 3\n", + "accepted_bids_sim3, power_flows_df_sim3, clearing_prices_df_sim3 = extract_results(\n", + " model_sim3, incidence_matrix\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3b2528a2", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 224 + }, + "id": "3b2528a2", + "outputId": "f97d364c-890e-40b7-aeb9-691052170a64" + }, + "outputs": [], + "source": [ + "print(\"Simulation 3: Power Flows Between Zones\")\n", + "display(power_flows_df_sim3.head())" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "05961462", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 413 + }, + "id": "05961462", + "outputId": "d6e9c38d-ab03-4828-e243-181791179ead" + }, + "outputs": [], + "source": [ + "print(\"Simulation 3: Clearing Prices per Zone and Time\")\n", + "display(clearing_prices_df_sim3.loc[clearing_prices_df_sim3[\"zone\"] == \"DE_1\"].head())\n", + "display(clearing_prices_df_sim3.loc[clearing_prices_df_sim3[\"zone\"] == \"DE_2\"].head())" + ] + }, + { + "cell_type": "markdown", + "id": "fb62e2fd", + "metadata": { + "id": "fb62e2fd" + }, + "source": [ + "**Explanation:**\n", + "\n", + "- **Accepted Bids:** Shows which bids were accepted in each simulation and the ratio at which they were accepted.\n", + "- **Power Flows:** Indicates the amount of energy transmitted between zones. In Simulation 1, with zero transmission capacity, there should be no power flows between `DE_1` and `DE_2`. In Simulation 2 and 3, with medium and high transmission capacities, power flows can occur between zones.\n", + "- **Clearing Prices:** Represents the average bid price in each zone at each time period. Comparing prices across simulations can reveal the impact of transmission capacity on market prices." + ] + }, + { + "cell_type": "markdown", + "id": "3dbd64e0", + "metadata": { + "id": "3dbd64e0" + }, + "source": [ + "### 5.5. Comparing Simulations\n", + "\n", + "To better understand the impact of transmission capacity, let's compare the key results from all three simulations." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0ffe7033", + "metadata": { + "cellView": "form", + "colab": { + "base_uri": "https://localhost:8080/", + "height": 617 + }, + "id": "0ffe7033", + "outputId": "b0b4295a-095b-4871-aeef-d5aa44f866f8" + }, + "outputs": [], + "source": [ + "# @title Plot the market clearing prices for each zone and simulation run\n", + "# Initialize the Plotly figure\n", + "fig = go.Figure()\n", + "\n", + "# Iterate over each zone to plot clearing prices for all three simulations\n", + "for zone in incidence_matrix.index:\n", + " # Filter data for the current zone and Simulation 1\n", + " zone_prices_sim1 = clearing_prices_df_sim1[clearing_prices_df_sim1[\"zone\"] == zone]\n", + " # Filter data for the current zone and Simulation 2\n", + " zone_prices_sim2 = clearing_prices_df_sim2[clearing_prices_df_sim2[\"zone\"] == zone]\n", + " # Filter data for the current zone and Simulation 3\n", + " zone_prices_sim3 = clearing_prices_df_sim3[clearing_prices_df_sim3[\"zone\"] == zone]\n", + "\n", + " # Add trace for Simulation 1\n", + " fig.add_trace(\n", + " go.Scatter(\n", + " x=zone_prices_sim1[\"time\"],\n", + " y=zone_prices_sim1[\"clearing_price\"],\n", + " mode=\"lines\",\n", + " name=f\"{zone} - Sim1 (Zero Capacity)\",\n", + " line=dict(dash=\"dash\"), # Dashed line for Simulation 1\n", + " )\n", + " )\n", + "\n", + " # Add trace for Simulation 2\n", + " fig.add_trace(\n", + " go.Scatter(\n", + " x=zone_prices_sim2[\"time\"],\n", + " y=zone_prices_sim2[\"clearing_price\"],\n", + " mode=\"lines\",\n", + " name=f\"{zone} - Sim2 (Medium Capacity)\",\n", + " line=dict(dash=\"dot\"), # Dotted line for Simulation 2\n", + " )\n", + " )\n", + "\n", + " # Add trace for Simulation 3\n", + " fig.add_trace(\n", + " go.Scatter(\n", + " x=zone_prices_sim3[\"time\"],\n", + " y=zone_prices_sim3[\"clearing_price\"],\n", + " mode=\"lines\",\n", + " name=f\"{zone} - Sim3 (High Capacity)\",\n", + " line=dict(dash=\"solid\"), # Solid line for Simulation 3\n", + " )\n", + " )\n", + "\n", + "# Update layout for better aesthetics and interactivity\n", + "fig.update_layout(\n", + " title=\"Clearing Prices per Zone Over Time: Sim1, Sim2, & Sim3\",\n", + " xaxis_title=\"Time\",\n", + " yaxis_title=\"Clearing Price\",\n", + " legend_title=\"Simulations\",\n", + " xaxis=dict(\n", + " tickangle=45,\n", + " type=\"date\", # Ensure the x-axis is treated as dates\n", + " ),\n", + " hovermode=\"x unified\", # Unified hover for better comparison\n", + " template=\"plotly_white\", # Clean white background\n", + " width=1000,\n", + " height=600,\n", + ")\n", + "\n", + "# Display the interactive plot\n", + "fig.show()" + ] + }, + { + "cell_type": "markdown", + "id": "7ee17c77", + "metadata": { + "id": "7ee17c77" + }, + "source": [ + "**Explanation:**\n", + "\n", + "- **Clearing Prices Plot:** Shows how market prices vary over time for each zone across all three simulations. The dashed lines represent Simulation 1 (no transmission capacity), dotted lines represent Simulation 2 (medium transmission capacity), and solid lines represent Simulation 3 (high transmission capacity). This visualization helps in observing how the presence of transmission capacity affects price convergence or divergence between zones." + ] + }, + { + "cell_type": "markdown", + "id": "fb8f157c", + "metadata": { + "id": "fb8f157c" + }, + "source": [ + "## 6. Execution with ASSUME\n", + "\n", + "In a real-world scenario, the ASSUME framework handles the reading of CSV files and the configuration of the simulation through configuration files. For the purpose of this tutorial, we'll integrate our prepared data and configuration into ASSUME to execute the simulation seamlessly.\n", + "\n", + "### Step 1: Saving Input Files\n", + "\n", + "We will save the generated input DataFrames to the `inputs/tutorial_08` folder. The required files are:\n", + "- `demand_units.csv`\n", + "- `demand_df.csv`\n", + "- `powerplant_units.csv`\n", + "- `buses.csv`\n", + "- `lines.csv`\n", + "\n", + "Additionally, we'll create a new file `fuel_prices.csv`.\n", + "\n", + "**Note:** The demand timeseries has been extended to cover 48 hours as ASSUME always requires an additional day of data for the market simulation.\n", + "\n", + "#### Create the Inputs Directory and Save CSV Files" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "531a7a24", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "531a7a24", + "outputId": "abc151f4-2f50-4ebd-b405-49f0340cd96d" + }, + "outputs": [], + "source": [ + "import os\n", + "\n", + "# Define the input directory\n", + "input_dir = \"inputs/tutorial_08\"\n", + "\n", + "# Create the directory if it doesn't exist\n", + "os.makedirs(input_dir, exist_ok=True)\n", + "\n", + "# extend demand_df for another day with the same demand profile\n", + "demand_df = pd.concat([demand_df, demand_df])\n", + "demand_df.index = pd.date_range(start=\"2019-01-01\", periods=48, freq=\"h\")\n", + "\n", + "# Save the DataFrames to CSV files\n", + "buses.to_csv(os.path.join(input_dir, \"buses.csv\"), index=True)\n", + "lines.to_csv(os.path.join(input_dir, \"lines.csv\"), index=True)\n", + "powerplant_units.to_csv(os.path.join(input_dir, \"powerplant_units.csv\"), index=False)\n", + "demand_units.to_csv(os.path.join(input_dir, \"demand_units.csv\"), index=False)\n", + "demand_df.to_csv(os.path.join(input_dir, \"demand_df.csv\"))\n", + "\n", + "print(\"Input CSV files have been saved to 'inputs/tutorial_08'.\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2d61a40b", + "metadata": { + "cellView": "form", + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "2d61a40b", + "outputId": "8ce46e76-c462-4c8e-db62-8f787b354403" + }, + "outputs": [], + "source": [ + "# @title Create fuel prices\n", + "fuel_prices = {\n", + " \"fuel\": [\"uranium\", \"co2\"],\n", + " \"price\": [5, 25],\n", + "}\n", + "\n", + "# Convert to DataFrame and save as CSV\n", + "fuel_prices_df = pd.DataFrame(fuel_prices).T\n", + "fuel_prices_df.to_csv(\n", + " os.path.join(input_dir, \"fuel_prices_df.csv\"), index=True, header=False\n", + ")\n", + "\n", + "print(\"Fuel Prices CSV file has been saved to 'inputs/tutorial_08/fuel_prices.csv'.\")" + ] + }, + { + "cell_type": "markdown", + "id": "e0e47625", + "metadata": { + "id": "e0e47625" + }, + "source": [ + "### Step 2: Creating the Configuration YAML File\n", + "\n", + "The configuration file defines the simulation parameters, including market settings and network configurations. Below is the YAML configuration tailored for our tutorial." + ] + }, + { + "cell_type": "markdown", + "id": "44e22a14", + "metadata": { + "id": "44e22a14" + }, + "source": [ + "#### Create `config.yaml`" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "821a4002", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "821a4002", + "outputId": "ac8bf62b-8e38-4199-a45a-5c5397342bef" + }, + "outputs": [], + "source": [ + "config = {\n", + " \"zonal_case\": {\n", + " \"start_date\": \"2019-01-01 00:00\",\n", + " \"end_date\": \"2019-01-01 23:00\",\n", + " \"time_step\": \"1h\",\n", + " \"save_frequency_hours\": 24,\n", + " \"markets_config\": {\n", + " \"zonal\": {\n", + " \"operator\": \"EOM_operator\",\n", + " \"product_type\": \"energy\",\n", + " \"products\": [{\"duration\": \"1h\", \"count\": 1, \"first_delivery\": \"1h\"}],\n", + " \"opening_frequency\": \"1h\",\n", + " \"opening_duration\": \"1h\",\n", + " \"volume_unit\": \"MWh\",\n", + " \"maximum_bid_volume\": 100000,\n", + " \"maximum_bid_price\": 3000,\n", + " \"minimum_bid_price\": -500,\n", + " \"price_unit\": \"EUR/MWh\",\n", + " \"market_mechanism\": \"pay_as_clear_complex\",\n", + " \"additional_fields\": [\"bid_type\", \"node\"],\n", + " \"param_dict\": {\"network_path\": \".\", \"zones_identifier\": \"zone_id\"},\n", + " }\n", + " },\n", + " }\n", + "}\n", + "\n", + "# Define the path for the config file\n", + "config_path = os.path.join(input_dir, \"config.yaml\")\n", + "\n", + "# Save the configuration to a YAML file\n", + "with open(config_path, \"w\") as file:\n", + " yaml.dump(config, file, sort_keys=False)\n", + "\n", + "print(f\"Configuration YAML file has been saved to '{config_path}'.\")" + ] + }, + { + "cell_type": "markdown", + "id": "e2e9403a", + "metadata": { + "id": "e2e9403a" + }, + "source": [ + "### Detailed Configuration Explanation\n", + "\n", + "The `config.yaml` file plays a key role in defining the simulation parameters. Below is a detailed explanation of each configuration parameter:\n", + "\n", + "- **zonal_case:**\n", + " - **start_date:** The start date and time for the simulation (`2019-01-01 00:00`).\n", + " - **end_date:** The end date and time for the simulation (`2019-01-02 00:00`).\n", + " - **time_step:** The simulation time step (`1h`), indicating hourly intervals.\n", + " - **save_frequency_hours:** How frequently the simulation results are saved (`24` hours).\n", + "\n", + "- **markets_config:**\n", + " - **zonal:** The name of the market. Remember, that our power plant units had a column named bidding_zonal. This is how a particluar bidding strategy is assigned to a particluar market.\n", + " - **operator:** The market operator (`EOM_operator`).\n", + " - **product_type:** Type of market product (`energy`).\n", + " - **products:** List defining the market products:\n", + " - **duration:** Duration of the product (`1h`).\n", + " - **count:** Number of products (`1`).\n", + " - **first_delivery:** When the first delivery occurs (`1h`).\n", + " - **opening_frequency:** Frequency of market openings (`1h`).\n", + " - **opening_duration:** Duration of market openings (`1h`).\n", + " - **volume_unit:** Unit of volume measurement (`MWh`).\n", + " - **maximum_bid_volume:** Maximum volume allowed per bid (`100000` MWh).\n", + " - **maximum_bid_price:** Maximum price allowed per bid (`3000` EUR/MWh).\n", + " - **minimum_bid_price:** Minimum price allowed per bid (`-500` EUR/MWh).\n", + " - **price_unit:** Unit of price measurement (`EUR/MWh`).\n", + " - **market_mechanism:** The market clearing mechanism (`pay_as_clear_complex`).\n", + " - **additional_fields:** Additional fields required for bids:\n", + " - **bid_type:** Type of bid (e.g., supply or demand).\n", + " - **node:** The market zone associated with the bid.\n", + " - **param_dict:**\n", + " - **network_path:** Path to the network files (`.` indicates current directory).\n", + " - **zones_identifier:** Identifier used for market zones (`zone_id`).\n", + "\n", + "This configuration ensures that the simulation accurately represents the zonal market dynamics, including bid restrictions and market operations." + ] + }, + { + "cell_type": "markdown", + "id": "6fd79730", + "metadata": { + "id": "6fd79730" + }, + "source": [ + "### Step 3: Running the Simulation\n", + "\n", + "With the input files and configuration in place, we can now run the simulation using ASSUME's built-in functions." + ] + }, + { + "cell_type": "markdown", + "id": "33ff62b1", + "metadata": { + "id": "33ff62b1" + }, + "source": [ + "#### Example Simulation Code" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3a79848a", + "metadata": { + "id": "3a79848a" + }, + "outputs": [], + "source": [ + "# import the main World class and the load_scenario_folder functions from assume\n", + "from assume import World\n", + "from assume.scenario.loader_csv import load_scenario_folder\n", + "\n", + "# Define paths for input and output data\n", + "csv_path = \"outputs\"\n", + "\n", + "# Define the data format and database URI\n", + "# Use \"local_db\" for SQLite database or \"timescale\" for TimescaleDB in Docker\n", + "\n", + "# Create directories if they don't exist\n", + "os.makedirs(csv_path, exist_ok=True)\n", + "os.makedirs(\"local_db\", exist_ok=True)\n", + "\n", + "data_format = \"local_db\" # \"local_db\" or \"timescale\"\n", + "\n", + "if data_format == \"local_db\":\n", + " db_uri = \"sqlite:///local_db/assume_db.db\"\n", + "elif data_format == \"timescale\":\n", + " db_uri = \"postgresql://assume:assume@localhost:5432/assume\"\n", + "\n", + "# Create the World instance\n", + "world = World(database_uri=db_uri, export_csv_path=csv_path)\n", + "\n", + "# Load the scenario by providing the world instance\n", + "# The path to the inputs folder and the scenario name (subfolder in inputs)\n", + "# and the study case name (which config to use for the simulation)\n", + "load_scenario_folder(\n", + " world,\n", + " inputs_path=\"inputs\",\n", + " scenario=\"tutorial_08\",\n", + " study_case=\"zonal_case\",\n", + ")\n", + "\n", + "# Run the simulation\n", + "world.run()" + ] + }, + { + "cell_type": "markdown", + "id": "be819122", + "metadata": { + "id": "be819122" + }, + "source": [ + "## 7. Analyzing the Results\n", + "\n", + "After running the simulation, you can analyze the results using the methods demonstrated in section 5. This integration with ASSUME allows for more extensive and scalable simulations, leveraging the framework's capabilities for handling complex market scenarios.\n", + "\n", + "In this section, we will:\n", + "\n", + "1. **Locate the Simulation Output Files:** Understand where the simulation results are saved.\n", + "2. **Load and Inspect the Output Data:** Read the output CSV files and examine their structure.\n", + "3. **Plot Clearing Prices:** Visualize the market clearing prices to compare with our previous manual simulations." + ] + }, + { + "cell_type": "markdown", + "id": "5ca43ca3", + "metadata": { + "id": "5ca43ca3" + }, + "source": [ + "### 7.1. Locating the Simulation Output Files\n", + "\n", + "The simulation outputs are saved in the `outputs/tutorial_08_zonal_case` directory. Specifically, the key output file we'll work with is `market_meta.csv`, which contains detailed information about the market outcomes for each zone and time period." + ] + }, + { + "cell_type": "markdown", + "id": "78707ac9", + "metadata": { + "id": "78707ac9" + }, + "source": [ + "### 7.2. Loading and Inspecting the Output Data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6e71a328", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 255 + }, + "id": "6e71a328", + "outputId": "738e1589-5d53-4831-cbcf-4fefca4f7860" + }, + "outputs": [], + "source": [ + "# Define the path to the simulation output\n", + "output_dir = \"outputs/tutorial_08_zonal_case\"\n", + "market_meta_path = os.path.join(output_dir, \"market_meta.csv\")\n", + "\n", + "# Load the market_meta.csv file\n", + "market_meta = pd.read_csv(market_meta_path, index_col=\"time\", parse_dates=True)\n", + "# drop the first column\n", + "market_meta = market_meta.drop(columns=market_meta.columns[0])\n", + "\n", + "# Display a sample of the data\n", + "print(\"Sample of market_meta.csv:\")\n", + "display(market_meta.head())" + ] + }, + { + "cell_type": "markdown", + "id": "870b1c74", + "metadata": { + "id": "870b1c74" + }, + "source": [ + "**Explanation:**\n", + "\n", + "- **market_meta.csv:** This file contains the market outcomes for each zone and time period, including supply and demand volumes, clearing prices, and other relevant metrics.\n", + "- **Columns:**\n", + " - `supply_volume`: Total volume supplied in the zone.\n", + " - `demand_volume`: Total volume demanded in the zone.\n", + " - `demand_volume_energy`: Energy demand volume (same as `demand_volume` for energy markets).\n", + " - `supply_volume_energy`: Energy supply volume (same as `supply_volume` for energy markets).\n", + " - `price`: Clearing price in the zone for the time period.\n", + " - `max_price`: Maximum bid price accepted.\n", + " - `min_price`: Minimum bid price accepted.\n", + " - `node`: Identifier for the market zone (`DE_1` or `DE_2`).\n", + " - `product_start`: Start time of the market product.\n", + " - `product_end`: End time of the market product.\n", + " - `only_hours`: Indicator flag (not used in this context).\n", + " - `market_id`: Identifier for the market (`zonal`).\n", + " - `time`: Timestamp for the market product.\n", + " - `simulation`: Identifier for the simulation case (`tutorial_08_zonal_case`)." + ] + }, + { + "cell_type": "markdown", + "id": "d0fd6e1b", + "metadata": { + "id": "d0fd6e1b" + }, + "source": [ + "### 7.3. Plotting Clearing Prices\n", + "\n", + "To verify that the simulation results align with our previous manual demonstrations, we'll plot the clearing prices for each zone over time. This will help us observe how transmission capacities influence price convergence or divergence between zones." + ] + }, + { + "cell_type": "markdown", + "id": "934872ad", + "metadata": { + "id": "934872ad" + }, + "source": [ + "#### Processing the Market Meta Data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fd2e3048", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 255 + }, + "id": "fd2e3048", + "outputId": "7d9d0dc5-7042-488f-93d9-655bf4139807" + }, + "outputs": [], + "source": [ + "# Extract unique zones\n", + "zones = market_meta[\"node\"].unique()\n", + "\n", + "# Initialize an empty DataFrame to store clearing prices per zone and time\n", + "clearing_prices_df = pd.DataFrame()\n", + "\n", + "# Populate the DataFrame with clearing prices for each zone\n", + "for zone in zones:\n", + " zone_data = market_meta[market_meta[\"node\"] == zone][[\"price\"]]\n", + " zone_data = zone_data.rename(columns={\"price\": f\"{zone}_price\"})\n", + " clearing_prices_df = (\n", + " pd.merge(\n", + " clearing_prices_df,\n", + " zone_data,\n", + " left_index=True,\n", + " right_index=True,\n", + " how=\"outer\",\n", + " )\n", + " if not clearing_prices_df.empty\n", + " else zone_data\n", + " )\n", + "\n", + "# Sort the DataFrame by time\n", + "clearing_prices_df = clearing_prices_df.sort_index()\n", + "\n", + "# Display a sample of the processed clearing prices\n", + "print(\"Sample of Processed Clearing Prices:\")\n", + "display(clearing_prices_df.head())" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "87102b35", + "metadata": { + "cellView": "form", + "colab": { + "base_uri": "https://localhost:8080/", + "height": 617 + }, + "id": "87102b35", + "outputId": "ebc6d249-88cc-4df8-eeb6-2738f16351b2" + }, + "outputs": [], + "source": [ + "# @title Plot market clearing prices\n", + "# Initialize the Plotly figure\n", + "fig = go.Figure()\n", + "\n", + "# Iterate over each zone to plot clearing prices\n", + "for zone in zones:\n", + " fig.add_trace(\n", + " go.Scatter(\n", + " x=clearing_prices_df.index,\n", + " y=clearing_prices_df[f\"{zone}_price\"],\n", + " mode=\"lines\",\n", + " name=f\"{zone} - Simulation\",\n", + " line=dict(width=2),\n", + " )\n", + " )\n", + "\n", + "# Update layout for better aesthetics and interactivity\n", + "fig.update_layout(\n", + " title=\"Clearing Prices per Zone Over Time: Simulation Results\",\n", + " xaxis_title=\"Time\",\n", + " yaxis_title=\"Clearing Price (EUR/MWh)\",\n", + " legend_title=\"Market Zones\",\n", + " xaxis=dict(\n", + " tickangle=45,\n", + " type=\"date\", # Ensure the x-axis is treated as dates\n", + " ),\n", + " hovermode=\"x unified\", # Unified hover for better comparison\n", + " template=\"plotly_white\", # Clean white background\n", + " width=1000,\n", + " height=600,\n", + ")\n", + "\n", + "# Display the interactive plot\n", + "fig.show()" + ] + }, + { + "cell_type": "markdown", + "id": "b34407b1", + "metadata": { + "id": "b34407b1" + }, + "source": [ + "**Explanation:**\n", + "\n", + "- **Plot Details:**\n", + " - **Lines:** Each zone's clearing price over time is represented by a distinct line.\n", + " - **Interactivity:** The Plotly plot allows for interactive exploration of the data, such as zooming and hovering for specific values.\n", + " - **Aesthetics:** The clean white template and clear labels enhance readability.\n", + "\n", + "- **Interpretation:**\n", + " - **Price Trends:** Observing how clearing prices fluctuate over time within each zone.\n", + " - **Impact of Transmission Capacity:** Comparing price levels between zones can reveal the effects of transmission capacities on market equilibrium. For instance, higher transmission capacity might lead to more price convergence between zones, while zero capacity could result in divergent price levels due to isolated supply and demand dynamics." + ] + }, + { + "cell_type": "markdown", + "id": "3f448fb4", + "metadata": { + "id": "3f448fb4" + }, + "source": [ + "## **Conclusion**\n", + "\n", + "Congratulations! You've successfully navigated through the **Market Zone Coupling** process using the **ASSUME Framework**. Here's a quick recap of what you've accomplished:\n", + "\n", + "### **Key Achievements:**\n", + "\n", + "1. **Market Setup:**\n", + " - **Defined Zones and Buses:** Established distinct market zones and configured their connections through transmission lines.\n", + " - **Configured Units:** Set up power plant and demand units within each zone, detailing their operational parameters.\n", + "\n", + "2. **Market Clearing Optimization:**\n", + " - **Implemented Optimization Model:** Utilized a simplified Pyomo-based model to perform market clearing, accounting for bid acceptances and power flows.\n", + " - **Simulated Transmission Scenarios:** Ran simulations with varying transmission capacities to observe their impact on energy distribution and pricing.\n", + "\n", + "3. **Result Analysis:**\n", + " - **Extracted Clearing Prices:** Retrieved and interpreted market prices from the optimization results.\n", + " - **Visualized Outcomes:** Created interactive plots to compare how different transmission capacities influence market dynamics across zones.\n", + "\n", + "### **Key Takeaways:**\n", + "\n", + "- **Impact of Transmission Capacity:** Transmission limits play a crucial role in determining energy flows and price convergence between market zones.\n", + "- **ASSUME Framework Efficiency:** ASSUME streamlines complex market simulations, making it easier to model and analyze multi-zone interactions.\n", + "\n", + "### **Next Steps:**\n", + "\n", + "- **Integrate Renewable Sources:** Expand the model to include renewable energy units and assess their impact on market dynamics.\n", + "- **Scale Up Simulations:** Apply the framework to larger, more complex market scenarios to further test its capabilities.\n", + "\n", + "Thank you for participating in this tutorial! With the foundational knowledge gained, you're now equipped to delve deeper into energy market simulations and leverage the ASSUME framework for more advanced analyses." + ] + } + ], + "metadata": { + "colab": { + "provenance": [], + "toc_visible": true + }, + "jupytext": { + "cell_metadata_filter": "-all", + "main_language": "python", + "notebook_metadata_filter": "-all" + }, + "kernelspec": { + "display_name": "assume", + "language": "python", + "name": "python3" }, - "nbformat": 4, - "nbformat_minor": 5 + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 } diff --git a/examples/notebooks/09_example_Sim_and_xRL.ipynb b/examples/notebooks/09_example_Sim_and_xRL.ipynb index 74bf467a..3b4de359 100644 --- a/examples/notebooks/09_example_Sim_and_xRL.ipynb +++ b/examples/notebooks/09_example_Sim_and_xRL.ipynb @@ -43,9 +43,9 @@ "\n", "2. [Explainable AI and SHAP Values](#2-explainable-ai-and-shap-values)\n", "\n", - " 2.1 Understanding Explainable AI\n", + " 2.1 [Understanding Explainable AI](#21-understanding-explainable-ai)\n", "\n", - " 2.2 Introduction to SHAP Values\n", + " 2.2 [Introduction to SHAP Values](#22-introduction-to-shap-values)\n", "\n", "3. [Calculating SHAP Values](#3-calculating-shap-values)\n", "\n", @@ -118,7 +118,7 @@ "- **Rewards**: The agents are rewarded based on profits and opportunity costs, helping them learn optimal bidding strategies.\n", "- **Algorithm**: We utilize a multi-agent version of the TD3 (Twin Delayed Deep Deterministic Policy Gradient) algorithm, which ensures stable learning even in non-stationary environments.\n", "\n", - "For a more detailed explanation of the RL configurations, refer to the [Deep Reinforcement Learning Tutorial](https://example.com/deep-rl-tutorial).\n", + "For a more detailed explanation of the RL configurations, refer to the [Deep Reinforcement Learning Tutorial](04_reinforcement_learning_example.ipynb).\n", "\n", "### Key Aspects of the Simulation\n", "\n", @@ -163,17 +163,23 @@ "height": 1000 }, "id": "ee220130", - "outputId": "ffd98b47-2b07-41cd-dfe4-ff0381571825", - "vscode": { - "languageId": "shellscript" - } + "outputId": "ffd98b47-2b07-41cd-dfe4-ff0381571825" }, "outputs": [], "source": [ - "!pip install 'assume-framework[learning]'\n", + "import importlib.util\n", + "\n", + "# Check if 'google.colab' is available\n", + "IN_COLAB = importlib.util.find_spec(\"google.colab\") is not None\n", + "\n", + "if IN_COLAB:\n", + " !pip install 'assume-framework[learning]'\n", + " !git clone --depth=1 https://github.com/assume-framework/assume.git assume-repo\n", + " # Colab currently has issues with pyomo version 6.8.2, causing the notebook to crash\n", + " # Installing an older version resolves this issue. This should only be considered a temporary fix.\n", + " !pip install pyomo==6.8.0\n", "!pip install plotly\n", - "!pip install nbconvert\n", - "!git clone --depth=1 https://github.com/assume-framework/assume.git assume-repo" + "!pip install nbconvert" ] }, { @@ -225,27 +231,25 @@ "execution_count": null, "id": "85fdfe19", "metadata": { - "lines_to_next_cell": 2, - "vscode": { - "languageId": "shellscript" - } + "lines_to_next_cell": 2 }, "outputs": [], "source": [ "# For local execution:\n", "%cd assume/examples/notebooks/\n", "\n", - "# For execution in Google Colab:\n", - "%cd assume-repo/examples/notebooks/\n", + "if IN_COLAB:\n", + " # For execution in Google Colab:\n", + " %cd assume-repo/examples/notebooks/\n", "\n", - "# Execute the Market Zone Splitting tutorial:\n", - "!jupyter nbconvert --to notebook --execute --ExecutePreprocessor.timeout=60 --output output.ipynb 08_market_zone_coupling.ipynb\n", + " # Execute the Market Zone Splitting tutorial:\n", + " !jupyter nbconvert --to notebook --execute --ExecutePreprocessor.timeout=60 --output output.ipynb 08_market_zone_coupling.ipynb\n", "\n", - "# Return to content folder (for Colab):\n", - "%cd /content\n", + " # Return to content folder (for Colab):\n", + " %cd /content\n", "\n", - "# Copy inputs directory to the working folder (for Colab):\n", - "!cp -r assume-repo/examples/notebooks/inputs ." + " # Copy inputs directory to the working folder (for Colab):\n", + " !cp -r assume-repo/examples/notebooks/inputs ." ] }, { @@ -266,7 +270,6 @@ "# Define the input directory\n", "input_dir = os.path.join(\"inputs\", \"tutorial_08\")\n", "\n", - "\n", "# Read the DataFrames from CSV files\n", "powerplant_units = pd.read_csv(os.path.join(input_dir, \"powerplant_units.csv\"))\n", "demand_df = pd.read_csv(os.path.join(input_dir, \"demand_df.csv\"))\n", @@ -1460,7 +1463,15 @@ "name": "python3" }, "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", "version": "3.11.9" } }, diff --git a/examples/notebooks/10_DSU_and_flexibility.ipynb b/examples/notebooks/10_DSU_and_flexibility.ipynb index d7e07d59..9761f6e2 100644 --- a/examples/notebooks/10_DSU_and_flexibility.ipynb +++ b/examples/notebooks/10_DSU_and_flexibility.ipynb @@ -47,20 +47,26 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "vscode": { - "languageId": "shellscript" - } - }, + "metadata": {}, "outputs": [], "source": [ - "# Install the ASSUME framework with the PyPSA library for network optimization\n", - "!pip install assume-framework[network]\n", + "import importlib.util\n", + "\n", + "# Check whether notebook is run in google colab\n", + "IN_COLAB = importlib.util.find_spec(\"google.colab\") is not None\n", + "\n", + "if IN_COLAB:\n", + " # Install the ASSUME framework with the PyPSA library for network optimization\n", + " !pip install assume-framework[network]\n", + " # Colab currently has issues with pyomo version 6.8.2, causing the notebook to crash\n", + " # Installing an older version resolves this issue. This should only be considered a temporary fix.\n", + " !pip install pyomo==6.8.0\n", "\n", - "#Install some additional packages for plotting\n", + "# Install some additional packages for plotting\n", "!pip install plotly\n", "!pip install cartopy\n", - "!pip install seaborn" + "!pip install seaborn\n", + "!pip install requests==2.32.2" ] }, { @@ -2948,7 +2954,7 @@ ], "metadata": { "kernelspec": { - "display_name": "assume-framework", + "display_name": "assume", "language": "python", "name": "python3" }, @@ -2962,7 +2968,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.7" + "version": "3.12.7" } }, "nbformat": 4, diff --git a/examples/notebooks/11_redispatch.ipynb b/examples/notebooks/11_redispatch.ipynb index 212dc51b..d940a347 100644 --- a/examples/notebooks/11_redispatch.ipynb +++ b/examples/notebooks/11_redispatch.ipynb @@ -62,24 +62,15 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "vscode": { - "languageId": "shellscript" - } - }, + "metadata": {}, "outputs": [], "source": [ - "import os\n", - "import matplotlib.pyplot as plt\n", + "from pathlib import Path\n", + "\n", "import numpy as np\n", "import pandas as pd\n", - "import plotly.graph_objects as go\n", - "import pyomo as pyo\n", - "import seaborn as sns\n", - "import yaml\n", "import pypsa\n", - "from assume import World\n", - "from pathlib import Path\n", + "\n", "\n", "# Simplified function to add read required CSV files\n", "def read_grid(network_path: str | Path) -> dict[str, pd.DataFrame]:\n", @@ -107,11 +98,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "vscode": { - "languageId": "shellscript" - } - }, + "metadata": {}, "outputs": [], "source": [ "# Simplified function to add generators to the grid network\n", @@ -154,11 +141,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "vscode": { - "languageId": "shellscript" - } - }, + "metadata": {}, "outputs": [], "source": [ "# Simplified function to add loads to the grid network\n", @@ -200,11 +183,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "vscode": { - "languageId": "shellscript" - } - }, + "metadata": {}, "outputs": [], "source": [ "# Simplified function to add loads to the redispatch network\n", @@ -246,11 +225,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "vscode": { - "languageId": "shellscript" - } - }, + "metadata": {}, "outputs": [], "source": [ "# Simplified function to add grid buses and lines to the redispatch network\n", @@ -299,19 +274,16 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "vscode": { - "languageId": "shellscript" - } - }, + "metadata": {}, "outputs": [], "source": [ - "from assume.common.market_objects import MarketConfig, Orderbook\n", + "from assume.common.grid_utils import calculate_network_meta\n", + "from assume.common.market_objects import Orderbook\n", + "\n", "\n", "def clear(\n", " self, orderbook: Orderbook, market_products\n", ") -> tuple[Orderbook, Orderbook, list[dict]]:\n", - "\n", " orderbook_df = pd.DataFrame(orderbook)\n", " orderbook_df[\"accepted_volume\"] = 0.0\n", " orderbook_df[\"accepted_price\"] = 0.0\n", @@ -369,9 +341,7 @@ "\n", " # Update p_max_pu for generators with _up and _down suffixes\n", " redispatch_network.generators_t.p_max_pu.update(p_max_pu_up.add_suffix(\"_up\"))\n", - " redispatch_network.generators_t.p_max_pu.update(\n", - " p_max_pu_down.add_suffix(\"_down\")\n", - " )\n", + " redispatch_network.generators_t.p_max_pu.update(p_max_pu_down.add_suffix(\"_down\"))\n", "\n", " # Add _up and _down suffix to costs and update the network\n", " redispatch_network.generators_t.marginal_cost.update(costs.add_suffix(\"_up\"))\n", @@ -383,21 +353,16 @@ " redispatch_network.lpf()\n", "\n", " # check lines for congestion where power flow is larget than s_nom\n", - " line_loading = (\n", - " redispatch_network.lines_t.p0.abs() / redispatch_network.lines.s_nom\n", - " )\n", + " line_loading = redispatch_network.lines_t.p0.abs() / redispatch_network.lines.s_nom\n", "\n", " # if any line is congested, perform redispatch\n", " if line_loading.max().max() > 1:\n", - " log.debug(\"Congestion detected\")\n", - "\n", " status, termination_condition = redispatch_network.optimize(\n", " solver_name=self.solver,\n", " env=self.env,\n", " )\n", "\n", " if status != \"ok\":\n", - " log.error(f\"Solver exited with {termination_condition}\")\n", " raise Exception(\"Solver in redispatch market did not converge\")\n", "\n", " # process dispatch data\n", @@ -405,10 +370,6 @@ " network=redispatch_network, orderbook_df=orderbook_df\n", " )\n", "\n", - " # if no congestion is detected set accepted volume and price to 0\n", - " else:\n", - " log.debug(\"No congestion detected\")\n", - "\n", " # return orderbook_df back to orderbook format as list of dicts\n", " accepted_orders = orderbook_df.to_dict(\"records\")\n", " rejected_orders = []\n", diff --git a/pyproject.toml b/pyproject.toml index 4ea505c1..bb18c2aa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,6 +26,7 @@ classifiers=[ "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", ] requires-python = ">=3.10" dependencies = [