Market Mechanisms#

A Market Mechanism is used to execute the clearing, scheduled by the MarketRole in base_market.py

The method signature for the market_mechanism is given as:

def clearing_mechanism_name(
  market_agent: MarketRole,
  market_products: list[MarketProduct],
):
  accepted_orders: Orderbook = []
  rejected_orders: Orderbook = []
  meta: list[Meta] = []
  return accepted_orders, rejected_orders, meta

The market_mechanism is called by the MarketRole, which is the agent that is responsible for the market. It is called with the market_agent and the market_products, which are the products that are traded in the current opening of the market. This gives maximum flexibility as it allows to access properties from the MarketRole directly. The market_mechanism returns a list of accepted orders, a list of rejected orders and a list of meta information (for each tradable market product or trading zone, if needed). The meta information is used to store information about the clearing, e.g. the min and max price, the cleared demand volume and supply volume, as well as the information about the cleared product.

In the Market Mechanism, the MarketRole is available to access the market configuration with market_agent.marketconfig and the available Orders from previous clearings through market_agent.all_orders. In the future, the MarketMechanism will be a class which contains the additional information like grid information without changing the MarketRole.

The available market mechanisms are the following:

  1. assume.markets.clearing_algorithms.simple.PayAsClearRole()

  2. assume.markets.clearing_algorithms.simple.PayAsBidRole()

  3. assume.markets.clearing_algorithms.complex_clearing.ComplexClearingRole()

  4. assume.markets.clearing_algorithms.complex_clearing_dmas.ComplexDmasClearingRole()

  5. assume.markets.clearing_algorithms.redispatch.RedispatchMarketRole()

  6. assume.markets.clearing_algorithms.nodal_clearing.NodalClearingRole()

  7. assume.markets.clearing_algorithms.contracts.PayAsBidContractRole()

The PayAsClearRole performs an electricity market clearing using a pay-as-clear mechanism. This means that the clearing price is the highest price that is still accepted. This price is then valid for all accepted orders. For this, the demand and supply are separated, before the demand is sorted from highest to lowest order price and the supply lowest to highest order price. Where those two curves in a price over power plot meet, the market is cleared for the price at the intersection. All supply orders with a price below and all demand orders above are accepted. Where the price is equal, only partial volume is accepted.

The PayAsBidRole clears the market in the same manner as the pay-as-clear mechanism, but the accepted_price is the price of the supply order for both the demand order and the supply orders that meet this demand.

Complex clearing#

The ComplexClearingRole performs an electricity market clearing using an optimization to clear the market. Here, also profile block and linked orders are supported. The objective function is a social welfare maximization, which is equivalent to a cost minimization:

\[\min \left( {\sum_{b \in \mathcal{B}}\quad{u_b \: C_{b} \: P_{b, t}} \: T} \right),\]

where \(\mathcal{B}\) is the set of all submitted bids, \(C_{b}\) is the bid price, \(P_{b, t}\) is the volume offered (demand is negative) and \(T\) is the clearing horizon of 24 hours. Decision variables are the acceptance ratio \(u_b\) with \(u_b \in [0, 1] \quad \forall \: b \in \mathcal{B}\), and the clearing status \(x_b\) with \(x_b \in \{0, 1\} \: \forall \: b \in \mathcal{B}\).

The optimization problem is subject to the following constraints:

The energy balance constraint: \(\quad \sum_{b \in \mathcal{B}} P_{b, t} \: u_b = 0 \quad \forall \: t \in \mathcal{T}\),

The minimum acceptance ratio constraint: \(\quad u_{b} \geq U_{b} \: x_{b} \quad \mathrm{and} \quad u_{b} \leq x_{b} \quad \forall \: b \in \mathcal{B}\),

with the minimum acceptance ratio \(U_{b}\) defined for each bid b.

The linked bid constraint, ensuring that the acceptance of child bids c is below the acceptance of their parent bids p is given by: \(\mathbf{a}_{c, p} \: u_c \leq u_{p} \quad \forall \: c, p \in \mathcal{B}\),

with the incidence matrix \(\mathbf{a}_{c, p}\) defining the links between bids as 1, if c is linked as child to p, 0 else.

Flows in the network are limited by the Net Transfer Capacity (‘s_nom’) of each line l: \(\quad -NTC_{l} \leq F_{l, t} \leq NTC_{l} \quad \forall \: l \in \mathcal{L}, t \in \mathcal{T}\),

Because with this algorithm, paradoxically accepted bids (PABs) can occur, the objective is solved in an iterative manner:

  1. The optimization problem is solved with the objective function and all constraints.

  2. The binary variables \(x_b\) are fixed to the current solution.

  3. The optimization problem is solved again without the minimum acceptance ratio constraint.

  4. The market clearing prices are given as the dual variables of the energy balance constraint.

  5. The surplus of each bid is calculated as the difference between the bid price and the market clearing price.

  6. If the surplus for one or more bids is negative, the clearing status \(x_b\) for those bids is set to 0 and the algorithm starts again with step 1.

If you want a hands-on use-case of the complex clearing check out the prepared tutorial in Colab: https://colab.research.google.com/github/assume-framework/assume

Nodal clearing#

The NodalClearingRole performs an electricity market clearing of the bids submitted by market participants using an optimal power flow (OPF) approach. Profile, block and linked orders are not supported. The algorithm utilizes PyPSA to solve the OPF problem, allowing for a physics based representation of network constraints.

Congestion Management and Redispatch Modeling#

This section demonstrates the modeling and simulation of the redispatch mechanism using PyPSA as a plug-and-play module within the ASSUME framework. The model primarily considers grid constraints to identify bottlenecks in the grid, resolve them using the redispatch algorithm, and account for dispatches from the EOM (Energy-Only Market).

Concept of Redispatch#

The locational mismatch between electricity demand and generation requires the transmission of electricity from low-demand regions to high-demand regions. The transmission capacity limits the maximum amount of electricity that can be transmitted at any point in time.

When transmission capacity is insufficient to meet demand, generation must be reduced at locations with low demand and increased at locations with high demand. This process is known as Redispatch. In addition to spot markets, the redispatch mechanism is used to regulate grid flows and avoid congestion issues. It is operated and controlled by the system operators (SO).

Overview of Redispatch Modeling in PyPSA#

The PyPSA network model can be created to visualize line flows using EOM clearing outcomes of generation and loads at different nodes (locations).

PyPSA uses the following terminology to define grid infrastructure:

Attributes#

  1. Bus: Nodes (locations) where power plants and loads are connected
    • name: Unique identifier of the bus

    • v_nom: Nominal voltage of the node

    • carrier: Energy carrier, which can be “AC” or “DC” for electricity buses

    • x: Longitude coordinate

    • y: Latitude coordinate

  2. Generator: Power plants that generate electricity
    • name: Unique identifier of the generator

    • p_nom: Nominal power capacity, used as a limit in optimization

    • p_max_pu: Maximum output for each snapshot as a fraction of p_nom

    • p_min_pu: Minimum output for each snapshot as a fraction of p_nom

    • p_set: Active power set point (for power flow analysis)

    • marginal_cost: Marginal cost of producing 1 MWh

    • carrier: Energy carrier, such as “AC” or “DC”

    • x: Longitude coordinate

    • y: Latitude coordinate

    • sign: Power sign (positive for generation, negative for consumption)

  3. Load: Demand units that consume electricity
    • name: Unique identifier of the load

    • p_set: Active power set point (for inflexible demand)

    • marginal_cost: Marginal cost of producing 1 MWh

    • x: Longitude coordinate

    • y: Latitude coordinate

    • sign: Power sign (positive for generation, negative for consumption)

  4. Line: Transmission grids that transmit electricity
    • name: Unique identifier of the line

    • s_nom: Nominal transmission capacity

    • capital_cost: Capital cost for extending lines by 1 MVA

    • r: Resistance in Ohms

    • bus0: First node the line is connected to

    • bus1: Second node the line is connected to

    • x: Series reactance

    • s_nom_extendable: Flag to enable s_nom expansion

  5. Network: The PyPSA network to which all the components are integrated
    • name: Unique identifier of the network

    • snapshots: List of timesteps (e.g., hourly, quarter-hourly, daily, etc.)

A PyPSA network model can be created by defining nodes as locations for power generation and consumption, interconnected by transmission lines with nominal transmission capacity (s_nom). These components can be further constrained operationally, for instance, by nominal power, efficiency, ramp rates, and other factors.

Currently, a limitation of the PyPSA model is the inability to define flexible loads.

Modeling Redispatch in ASSUME#

Modeling redispatch in the ASSUME framework using PyPSA primarily includes two parts:

Congestion Identification#

The first step is to check for congestion in the network. The linear power flow (LPF) method is particularly useful for quick assessments of congestion and redispatch needs. PyPSA provides the network.lpf() function for running linear power flow. This method is significantly faster than a full non-linear AC power flow, making it suitable for real-time analysis or large network studies.

The active power flows through the lines can be retrieved using network.lines_t.p0. These can be compared to the nominal capacity of the lines (s_nom) to determine whether there is congestion.

`python line_loading = network.lines_t.p0 / network.lines.s_nom `

If line loading exceeds 1, it suggests there is congestion.

Redispatch of Power Plants#

Once congestion is identified at any line or timestep, the redispatch mechanism is applied to alleviate it.

Steps for Redispatch#

  1. Fixing Dispatches from the EOM Market EOM market dispatches are fixed to model redispatch from power plants with accurate cost considerations. EOM dispatches are treated as a Load in the network, with dispatches specified via p_set. Generators are assigned a positive sign, and demands are given a negative sign.

  2. Upward Redispatch from Market and Reserved Power Plants Due to PyPSA’s limitations in modeling load flexibility, upward redispatch is added as a Generator with a positive sign. The maximum available capacity for upward redispatch is restricted using the p_max_pu factor, estimated as the difference between the current generation and the maximum power of the power plant.

    `python p_max_pu_up = (max_power - volume) / max_power `

  3. Downward Redispatch from Market Power Plants Similarly, downward redispatch is modelled as a Generator with a negative sign. The maximum available capacity for downward redispatch is restricted by the p_max_pu factor.

  4. Upward and Downward Redispatch from Other Flexibilities Flexibility for redispatch is also modelled as generators, with positive signs for upward redispatch and negative signs for downward redispatch.

Objective#

The aim of redispatch is to minimize the overall cost of redispatch, including costs for starting up, shutting down, ramping up, ramping down, and other related actions.