markets module

A model of the NEM spot market dispatch process.

Overview

The market, both in real life and in this model, is implemented as a linear program. Linear programs consist of three elements:

  1. Decision variables: the quantities being optimised for. In an electricity market these will be things like the outputs of generators, the consumption of dispatchable loads and interconnector flows.

  2. An objective function: the linear function being optimised. In this model of the spot market the cost of production is being minimised, and is defined as the sum of each bids dispatch level multiplied by the bid price.

  3. A set of linear constraints: used to implement market features such as network constraints and interconnectors.

The class nempy.SpotMarket is used to construct these elements and then solve the linear program to calculate dispatch and pricing. The examples below give an overview of how method calls build the linear program.

  • Initialising the market instance, doesn’t create any part of the linear program, just saves general information for later use.

market = markets.SpotMarket(unit_info=unit_info, market_regions=['NSW'])
  • Providing volume bids creates a set of n decision variables, where n is the number of bids with a volume greater than zero.

market.set_unit_volume_bids(volume_bids)
  • Providing price bids creates the objective function, i.e. units will be dispatch to minimise cost, as determined by the bid prices.

market.set_unit_price_bids(price_bids)
  • Providing unit capacities creates a constraint for each unit that caps its total dispatch at a set capacity

market.set_unit_bid_capacity_constraints(unit_limits)
  • Providing regional energy demand creates a constraint for each region that forces supply from units and interconnectors to equal demand

market.set_demand_constraints(demand)

Specific examples for using this class are provided on the `examples1`_ page, detailed documentation of the class nempy.markets.SpotMarket is provided in the Reference material below.

Reference

Classes:

SpotMarket(market_regions, unit_info[, ...])

Class for constructing and dispatching the spot market on an interval basis.

Exceptions:

ModelBuildError

Raise for building model components in wrong order.

MissingTable

Raise for trying to access missing table.

class nempy.markets.SpotMarket(market_regions, unit_info, dispatch_interval=5)

Class for constructing and dispatching the spot market on an interval basis.

Note: bidirectional units are defined by including the unit twice in the unit_info input, once with the dispatch type “generator” and once with the type “load”. Then energy and FCAS regulation bids (and FCAS trapezium paramters) can be provided for both the generation and the load components of the unit. Note only a single set of bids can be provided for FCAS contigency, these should be given the dispatch_type “generator”, but both the load and generator side can contribute to delivery.

Examples

Define the unit information data needed to initialise the market, in this example all units are in the same region.

>>> unit_info = pd.DataFrame({
...     'unit': ['A', 'B'],
...     'region': ['NSW', 'NSW']})

Initialise the market instance.

>>> market = SpotMarket(market_regions=['NSW'],
...                            unit_info=unit_info)

The units are given a default dispatch_type and loss_factor. Note this data is stored in a private method and not intended for public use.

>>> market._unit_info
  unit region  loss_factor dispatch_type
0    A    NSW          1.0     generator
1    B    NSW          1.0     generator
Parameters:
  • market_regions (list[str]) – The market regions, used to validate inputs.

  • unit_info (pd.DataFrame) –

    Information on a unit basis, not all columns are required.

    Columns:

    Description:

    unit

    unique identifier of a dispatch unit,

    (as str)

    region

    location of unit, required (as str)

    dispatch_type

    ”load” or “generator”, optional default

    ’generator’, if provided for unit_info must also

    be provided other input tables with ‘unit’ column

    except for uigf limits and fast start profiles.

    loss_factor

    marginal, average or combined loss factors,

    see AEMO doc,

    optional, (as np.int64)

  • dispatch_interval (int) – The length of the dispatch interval in minutes, used for interpreting ramp rates.

solver_name

The solver to use must be one of solver options of the mip-python package that is used to interface to solvers. Currently, the only supported solvers are CBC and Gurobi, so allowed solver names are ‘CBC’ and ‘GUROBI’. Default value is CBC, CBC works out of the box after installing Nempy, but Gurobi must be installed separately.

Type:

str

Raises:
  • RepeatedRowError – If there is more than one row for any ‘unit’.

  • ColumnDataTypeError – If columns are not of the required type.

  • MissingColumnError – If the column ‘units’ or ‘regions’ is missing.

  • UnexpectedColumn – There is a column that is not ‘units’, ‘regions’, ‘dispatch_type’ or ‘loss_factor’.

  • ColumnValues – If there are inf, null or negative values in the ‘loss_factor’ column.

Methods:

set_unit_volume_bids(volume_bids)

Creates the decision variables corresponding to unit bids.

set_unit_price_bids(price_bids)

Creates the objective function costs corresponding to energy bids.

set_unit_bid_capacity_constraints(unit_limits)

Creates constraints that limit unit output based on their bid in max capacity.

set_unconstrained_intermittent_generation_forecast_constraint(...)

Creates constraints that limit unit output based on their forecast output.

set_unit_ramp_rate_constraints(ramp_details)

Creates constraints on unit output based on ramp rates.

set_fast_start_constraints(fast_start_profiles)

Create the constraints on fast start units dispatch, see AEMO doc

set_demand_constraints(demand[, violation_cost])

Creates constraints that force supply to equal to demand.

set_fcas_requirements_constraints(...[, ...])

Creates constraints that force FCAS supply to equal requirements.

set_fcas_max_availability(fcas_max_availability)

Creates constraints to ensure fcas dispatch is limited to the availability specified in the FCAS trapezium.

set_joint_ramping_constraints_reg(...[, ...])

Create constraints that ensure the provision of energy and fcas raise are within unit ramping capabilities.

set_joint_capacity_constraints(...[, ...])

Creates constraints to ensure there is adequate capacity for contingency, regulation and energy dispatch.

set_energy_and_regulation_capacity_constraints(...)

Creates constraints to ensure there is adequate capacity for regulation and energy dispatch targets.

set_interconnectors(...)

Create lossless links between specified regions.

set_interconnector_losses(loss_functions, ...)

Creates linearised loss functions for interconnectors.

set_generic_constraints(...[, violation_cost])

Creates a set of generic constraints, adding the constraint type, rhs.

link_units_to_generic_constraints(...)

Set the lhs coefficients of generic constraints on unit basis.

link_regions_to_generic_constraints(...)

Set the lhs coefficients of generic constraints on region basis.

link_interconnectors_to_generic_constraints(...)

Set the lhs coefficients of generic constraints on an interconnector basis.

make_constraints_elastic(constraints_key, ...)

Make a set of constraints elastic, so they can be violated at a predefined cost.

set_tie_break_constraints(cost)

Creates a cost that attempts to balance the energy dispatch of equally priced bids within a region.

dispatch([energy_market_ceiling_price, ...])

Combines the elements of the linear program and solves to find optimal dispatch.

get_unit_dispatch()

Retrieves the energy dispatch for each unit.

get_energy_prices()

Retrieves the energy price in each market region.

get_fcas_prices()

Retrives the price associated with each set of FCAS requirement constraints.

get_interconnector_flows()

Retrieves the flows for each interconnector.

get_region_dispatch_summary()

Calculates a dispatch summary at the regional level.

get_fcas_availability()

Get the availability of fcas service on a unit level, after constraints.

set_unit_volume_bids(volume_bids)

Creates the decision variables corresponding to unit bids.

Variables are created by reserving a variable id (as int) for each bid. Bids with a volume of 0 MW do not have a variable created. The lower bound of the variables are set to zero and the upper bound to the bid volume, the variable type is set to continuous. If service is not specified for the bids they are given the default service value of ‘energy’. If dispatch_type is not specified for the bids they are given the default value of ‘generator’.

Examples

Define the unit information data set needed to initialise the market, in this example all units are in the same region.

>>> unit_info = pd.DataFrame({
...     'unit': ['A', 'B'],
...     'region': ['NSW', 'NSW']})

Initialise the market instance.

>>> market = SpotMarket(market_regions=['NSW'],
...                            unit_info=unit_info)

Define a set of bids, in this example we have two units called A and B, with three bid bands.

>>> volume_bids = pd.DataFrame({
...     'unit': ['A', 'B'],
...     '1': [20.0, 50.0],
...     '2': [20.0, 30.0],
...     '3': [5.0, 0.0]})

Create energy unit bid decision variables.

>>> market.set_unit_volume_bids(volume_bids)

The market should now have the variables.

>>> print(market._decision_variables['bids'])
  unit capacity_band service dispatch_type  variable_id  lower_bound  upper_bound        type
0    A             1  energy     generator            0          0.0         20.0  continuous
1    A             2  energy     generator            1          0.0         20.0  continuous
2    A             3  energy     generator            2          0.0          5.0  continuous
3    B             1  energy     generator            3          0.0         50.0  continuous
4    B             2  energy     generator            4          0.0         30.0  continuous

A mapping of these variables to constraints acting on that unit and service should also exist.

>>> print(market._variable_to_constraint_map['unit_level']['bids'])
   variable_id unit service dispatch_type  coefficient
0            0    A  energy     generator          1.0
1            1    A  energy     generator          1.0
2            2    A  energy     generator          1.0
3            3    B  energy     generator          1.0
4            4    B  energy     generator          1.0

A mapping of these variables to constraints acting on the units region and service should also exist.

>>> print(market._variable_to_constraint_map['regional']['bids'])
   variable_id region service dispatch_type  coefficient
0            0    NSW  energy     generator          1.0
1            1    NSW  energy     generator          1.0
2            2    NSW  energy     generator          1.0
3            3    NSW  energy     generator          1.0
4            4    NSW  energy     generator          1.0
Parameters:

volume_bids (pd.DataFrame) – Bids by unit, in MW, can contain up to 10 bid bands, these should be labeled ‘1’ to ‘10’.

Return type:

None

Raises:
  • RepeatedRowError – If there is more than one row for any unit and service combination.

  • ColumnDataTypeError – If columns are not of the required type.

  • MissingColumnError – If the column ‘units’ is missing or there are no bid bands.

  • UnexpectedColumn – There is a column that is not ‘unit’, ‘service’ or ‘1’ to ‘10’.

  • ColumnValues – If there are inf, null or negative values in the bid band columns.

set_unit_price_bids(price_bids)

Creates the objective function costs corresponding to energy bids.

If no loss factors have been provided as part of the unit information when the model was initialised then the costs in the objective function are as bid. If loss factors are provided then the bid costs are referred to the regional reference node by dividing by the loss factor. If service is not specified for the bids they are given the default service value of ‘energy’. If dispatch_type is not specified for the bids they are given the default value of ‘generator’.

Examples

Define the unit information data set needed to initialise the market, in this example all units are in the same region.

>>> unit_info = pd.DataFrame({
...     'unit': ['A', 'B'],
...     'region': ['NSW', 'NSW']})

Initialise the market instance.

>>> market = SpotMarket(market_regions=['NSW'],
...                     unit_info=unit_info)

Define a set of bids, in this example we have two units called A and B, with three bid bands.

>>> volume_bids = pd.DataFrame({
...     'unit': ['A', 'B'],
...     '1': [20.0, 50.0],
...     '2': [20.0, 30.0],
...     '3': [5.0, 10.0]})

Create energy unit bid decision variables.

>>> market.set_unit_volume_bids(volume_bids)

Define a set of prices for the bids. Bids for each unit need to be monotonically increasing.

>>> price_bids = pd.DataFrame({
...     'unit': ['A', 'B'],
...     '1': [50.0, 100.0],
...     '2': [100.0, 130.0],
...     '3': [100.0, 150.0]})

Create the objective function components corresponding to the energy bids.

>>> market.set_unit_price_bids(price_bids)

The variable assocaited with each bid should now have a cost.

>>> print(market._objective_function_components['bids'])
   variable_id unit service dispatch_type capacity_band   cost
0            0    A  energy     generator             1   50.0
1            1    A  energy     generator             2  100.0
2            2    A  energy     generator             3  100.0
3            3    B  energy     generator             1  100.0
4            4    B  energy     generator             2  130.0
5            5    B  energy     generator             3  150.0
Parameters:

price_bids (pd.DataFrame) – Bids by unit, in $/MW, can contain up to 10 bid bands.

Return type:

None

Raises:
  • ModelBuildError – If the volume bids have not been set yet.

  • RepeatedRowError – If there is more than one row for any unit and service combination.

  • ColumnDataTypeError – If columns are not of the required type.

  • MissingColumnError – If the column ‘units’ is missing or there are no bid bands.

  • UnexpectedColumn – There is a column that is not ‘units’, ‘region’ or ‘1’ to ‘10’.

  • ColumnValues – If there are inf, -inf or null values in the bid band columns.

  • BidsNotMonotonicIncreasing – If the bids band price for all units are not monotonic increasing.

set_unit_bid_capacity_constraints(unit_limits, violation_cost=None)

Creates constraints that limit unit output based on their bid in max capacity. If a unit bids in zero volume then a constraint is not created.

Examples

Define the unit information data set needed to initialise the market, in this example all units are in the same region.

>>> unit_info = pd.DataFrame({
...     'unit': ['A', 'B'],
...     'region': ['NSW', 'NSW']})

Initialise the market instance.

>>> market = SpotMarket(market_regions=['NSW'],
...                     unit_info=unit_info)

Define a set of bids, in this example we have two units called A and B, with three bid bands.

>>> volume_bids = pd.DataFrame({
...     'unit': ['A', 'B'],
...     '1': [20.0, 50.0],
...     '2': [20.0, 30.0],
...     '3': [5.0, 10.0]})

Create energy unit bid decision variables.

>>> market.set_unit_volume_bids(volume_bids)

Define a set of unit capacities.

>>> unit_limits = pd.DataFrame({
...     'unit': ['A', 'B'],
...     'capacity': [60.0, 100.0]})

Create unit capacity based constraints.

>>> market.set_unit_bid_capacity_constraints(unit_limits)

The market should now have a set of constraints.

>>> print(market._constraints_rhs_and_type['unit_bid_capacity'])
  unit service dispatch_type  constraint_id type    rhs
0    A  energy     generator              0   <=   60.0
1    B  energy     generator              1   <=  100.0

… and a mapping of those constraints to the variable types on the lhs.

>>> unit_mapping = market._constraint_to_variable_map['unit_level']
>>> print(unit_mapping['unit_bid_capacity'])
   constraint_id unit service dispatch_type  coefficient
0              0    A  energy     generator          1.0
1              1    B  energy     generator          1.0
Parameters:
  • unit_limits (pd.DataFrame) – Capacity by unit.

  • violation_cost (float) – Makes assocaited constrainst elastic using the given violation_cost (in $/MW).

Return type:

None

Raises:
  • ModelBuildError – If the volume bids have not been set yet.

  • RepeatedRowError – If there is more than one row for any unit.

  • ColumnDataTypeError – If columns are not of the required types.

  • MissingColumnError – If the column ‘units’ or ‘capacity’ is missing.

  • UnexpectedColumn – There is a column that is not ‘units’ or ‘capacity’.

  • ColumnValues – If there are inf, null or negative values in the bid band columns.

set_unconstrained_intermittent_generation_forecast_constraint(unit_limits, violation_cost=None)

Creates constraints that limit unit output based on their forecast output.

Note: All semi-scheduled units are assumed not to be bidirectional.

Examples

Define the unit information data set needed to initialise the market, in this example all units are in the same region.

>>> unit_info = pd.DataFrame({
...     'unit': ['A', 'B'],
...     'region': ['NSW', 'NSW']})

Initialise the market instance.

>>> market = SpotMarket(market_regions=['NSW'],
...                     unit_info=unit_info)

Define a set of bids, in this example we have two units called A and B, with three bid bands.

>>> volume_bids = pd.DataFrame({
...     'unit': ['A', 'B'],
...     '1': [20.0, 50.0],
...     '2': [20.0, 30.0],
...     '3': [5.0, 10.0]})

Create energy unit bid decision variables.

>>> market.set_unit_volume_bids(volume_bids)

Define a set of unit forecast capacities.

>>> unit_limits = pd.DataFrame({
...     'unit': ['A', 'B'],
...     'capacity': [60.0, 100.0]})

Create unit capacity based constraints.

>>> market.set_unconstrained_intermittent_generation_forecast_constraint(unit_limits)

The market should now have a set of constraints.

>>> print(market._constraints_rhs_and_type['uigf_capacity'])
  unit service dispatch_type  constraint_id type    rhs
0    A  energy     generator              0   <=   60.0
1    B  energy     generator              1   <=  100.0

… and a mapping of those constraints to the variable types on the lhs.

>>> unit_mapping = market._constraint_to_variable_map['unit_level']
>>> print(unit_mapping['uigf_capacity'])
   constraint_id unit service dispatch_type  coefficient
0              0    A  energy     generator          1.0
1              1    B  energy     generator          1.0
Parameters:
  • unit_limits (pd.DataFrame) –

    Capacity by unit.

    Columns:

    Description:

    unit

    unique identifier of a dispatch unit (as str)

    capacity

    The maximum output of the unit if unconstrained

    by ramp rate, in MW (as np.float64)

  • violation_cost (float) – Makes assocaited constrainst elastic using the given violation_cost (in $/MW).

Return type:

None

Raises:
  • ModelBuildError – If the volume bids have not been set yet.

  • RepeatedRowError – If there is more than one row for any unit.

  • ColumnDataTypeError – If columns are not of the required type.

  • MissingColumnError – If the column ‘units’ or ‘capacity’ is missing.

  • UnexpectedColumn – There is a column that is not ‘units’ or ‘capacity’.

  • ColumnValues – If there are inf, null or negative values in the bid band columns.

set_unit_ramp_rate_constraints(ramp_details, scada_ramp_rates=None, fast_start_profiles=None, run_type='no_fast_start_units', violation_cost=None)

Creates constraints on unit output based on ramp rates.

  1. For bidirectional units composite ramp rates are caclauted as per see AEMO doc

  2. If scada ramp rates are provided then the lesser of the as bid and scada ramp rates are used.

  3. If fast_start_profiles are provided ramps rates are adjusted to account for the fast start profiles.

Constrains the unit output to be: target <= initial_output + ramp_up_rate * (dispatch_interval / 60) and target >= initial_output - ramp_down_rate * (dispatch_interval / 60).

Examples

Define the unit information data set needed to initialise the market, in this example all units are in the same region.

>>> unit_info = pd.DataFrame({
...     'unit': ['A', 'B'],
...     'region': ['NSW', 'NSW']})

Initialise the market instance.

>>> market = SpotMarket(market_regions=['NSW'],
...                     unit_info=unit_info,
...                     dispatch_interval=30)

Define a set of bids, in this example we have two units called A and B, with three bid bands.

>>> volume_bids = pd.DataFrame({
...     'unit': ['A', 'B'],
...     '1': [20.0, 50.0],
...     '2': [20.0, 30.0],
...     '3': [5.0, 10.0]})

Create energy unit bid decision variables.

>>> market.set_unit_volume_bids(volume_bids)

Define a set of unit ramp up rates.

>>> ramp_details = pd.DataFrame({
...     'unit': ['A', 'B'],
...     'initial_output': [20.0, 50.0],
...     'ramp_up_rate': [30.0, 100.0],
...     'ramp_down_rate': [30.0, 100.0]})

Create unit capacity based constraints.

>>> market.set_unit_ramp_rate_constraints(ramp_details)

The market should now have a set of constraints.

>>> print(market._constraints_rhs_and_type['ramp_up'])
  unit service dispatch_type  constraint_id type    rhs
0    A  energy     generator              0   <=   35.0
1    B  energy     generator              1   <=  100.0
>>> print(market._constraints_rhs_and_type['ramp_down'])
  unit service dispatch_type  constraint_id type  rhs
0    A  energy     generator              2   >=  5.0
1    B  energy     generator              3   >=  0.0

… and a mapping of those constraints to variable type for the lhs.

>>> unit_mapping = market._constraint_to_variable_map['unit_level']
>>> print(unit_mapping['ramp_up'])
   constraint_id unit service dispatch_type  coefficient
0              0    A  energy     generator          1.0
1              1    B  energy     generator          1.0
>>> print(unit_mapping['ramp_down'])
   constraint_id unit service dispatch_type  coefficient
0              2    A  energy     generator          1.0
1              3    B  energy     generator          1.0
Parameters:
  • ramp_details (pd.DataFrame) –

  • scada_ramp_rates (pd.DataFrame) – Bidirectional units share scada ramp rates so only one set is given per unit and there is no need for the dispatch_type to be specified.

fast_start_profiles : pd.DataFrame

When run_type is set to ‘no_fast_start_units’: this table is not

requried.

When run_type is set to ‘fast_start_first_run’:

When run_type is set to ‘fast_start_first_run’:

run_type: str specifying whever this the fist fast start run or the second, or

if fast start units are not being considered. One of ‘no_fast_start_units’, ‘fast_start_first_run’, or ‘fast_start_second_run’.

violation_costfloat

Makes assocaited constrainst elastic using the given violation_cost (in $/MW).

Return type:

None

Raises:
  • RepeatedRowError – If there is more than one row for any unit.

  • ColumnDataTypeError – If columns are not of the required type.

  • MissingColumnError – If the column ‘units’, ‘initial_output’ or ‘ramp_up_rate’ is missing.

  • UnexpectedColumn – There is a column that is not ‘units’, ‘initial_output’ or ‘ramp_up_rate’.

  • ColumnValues – If there are inf, null or negative values in the bid band columns.

set_fast_start_constraints(fast_start_profiles, violation_cost=None)

Create the constraints on fast start units dispatch, see AEMO doc

Note: All fast start type units are assumed not to be bidirectional.

Examples

Define the unit information data set needed to initialise the market, in this example all units are in the same region.

>>> unit_info = pd.DataFrame({
...     'unit': ['A', 'B', 'C', 'D', 'E'],
...     'region': ['NSW', 'NSW', 'NSW', 'NSW', 'NSW']})

Initialise the market instance.

>>> market = SpotMarket(market_regions=['NSW'],
...                     unit_info=unit_info,
...                     dispatch_interval=30)

Define some example fast start conditions.

>>> fast_start_conditions = pd.DataFrame({
...     'unit': ['A', 'B', 'C', 'D', 'E'],
...     'end_mode': [0, 1, 2, 3, 4],
...     'time_in_end_mode': [4.0, 5.0, 5.0, 12.0, 10.0],
...     'mode_two_length': [7.0, 4.0, 10.0, 8.0, 6.0],
...     'mode_four_length': [10.0, 10.0, 20.0, 8.0, 20.0],
...     'min_loading': [30.0, 40.0, 35.0, 50.0, 60.0]})

Add fast start constraints.

>>> market.set_fast_start_constraints(fast_start_conditions)

The market should now have a set of constraints.

>>> print(market._constraints_rhs_and_type['fast_start'])
  unit service dispatch_type  constraint_id type   rhs
0    A  energy     generator              0   <=   0.0
1    B  energy     generator              1   <=   0.0
0    C  energy     generator              2   >=  17.5
0    C  energy     generator              3   <=  17.5
0    D  energy     generator              4   >=  50.0
0    E  energy     generator              5   >=  30.0

… and a mapping of those constraints to variable type for the lhs.

>>> unit_mapping = market._constraint_to_variable_map['unit_level']
>>> print(unit_mapping['fast_start'])
   constraint_id unit service dispatch_type  coefficient
0              0    A  energy     generator          1.0
1              1    B  energy     generator          1.0
0              3    C  energy     generator          1.0
0              2    C  energy     generator          1.0
0              4    D  energy     generator          1.0
0              5    E  energy     generator          1.0
Parameters:
  • fast_start_profiles (pd.DataFrame) –

    Columns:

    Description:

    unit

    unique identifier of a dispatch unit,

    (as str)

    end_mode

    the fast start dispatch mode the unit

    will end the dispatch interval in,

    in minutes, (as np.int64),

    time_in_end_mode

    the time the unit will have spent in the

    end mode at the end of this dispatch

    interval, in minutes (as np.int64)

    mode_two_length

    the length of dispatch mode 2 for the

    unit, in minutes, (as np.int64)

    mode_four_length

    the length of dispatch mode 4 for the

    unit, in minutes, (as np.int64)

    min_loading

    the minimum stable operating level of

    unit, in MW, (as np.float64)

  • violation_cost (float) – Makes assocaited constrainst elastic using the given violation_cost (in $/MW).

Raises:
  • RepeatedRowError – If there is more than one row for any unit.

  • ColumnDataTypeError – If columns are not of the required type.

  • MissingColumnError – If any columns are missing.

  • UnexpectedColumn – If any additional columns are present.

  • ColumnValues – If there are inf, null or negative values in any of the numeric columns.

set_demand_constraints(demand, violation_cost=None)

Creates constraints that force supply to equal to demand.

Examples

Define the unit information data set needed to initialise the market, in this example all units are in the same region.

>>> unit_info = pd.DataFrame({
...     'unit': ['A', 'B'],
...     'region': ['NSW', 'NSW']})

Initialise the market instance.

>>> market = SpotMarket(market_regions=['NSW'],
...                     unit_info=unit_info)

Define a demand level in each region.

>>> demand = pd.DataFrame({
...     'region': ['NSW'],
...     'demand': [100.0]})

Create constraints.

>>> market.set_demand_constraints(demand)

The market should now have a set of constraints.

>>> print(market._market_constraints_rhs_and_type['demand'])
  region  constraint_id type    rhs
0    NSW              0    =  100.0

… and a mapping of those constraints to variable type for the lhs.

>>> regional_mapping = market._constraint_to_variable_map['regional']
>>> print(regional_mapping['demand'])
   constraint_id region service  coefficient
0              0    NSW  energy          1.0
Parameters:
  • demand (pd.DataFrame) –

    Demand by region.

    Columns:

    Description:

    region

    unique identifier of a region, (as str)

    demand

    the non dispatchable demand, in MW,

    (as np.float64)

  • violation_cost (float) – Makes assocaited constrainst elastic using the given violation_cost (in $/MW).

Return type:

None

Raises:
  • RepeatedRowError – If there is more than one row for any unit.

  • ColumnDataTypeError – If columns are not of the required type.

  • MissingColumnError – If the column ‘region’ or ‘demand’ is missing.

  • UnexpectedColumn – There is a column that is not ‘region’ or ‘demand’.

  • ColumnValues – If there are inf, null or negative values in the volume column.

set_fcas_requirements_constraints(fcas_requirements, violation_cost=None)

Creates constraints that force FCAS supply to equal requirements.

Examples

Define the unit information data set needed to initialise the market, in this example all units are in the same region.

>>> unit_info = pd.DataFrame({
...     'unit': ['A', 'B'],
...     'region': ['NSW', 'NSW']})

Initialise the market instance.

>>> market = SpotMarket(market_regions=['QLD', 'NSW', 'VIC', 'SA'],
...                     unit_info=unit_info)

Define a regulation raise FCAS requirement that apply to all mainland states.

>>> fcas_requirements = pd.DataFrame({
...     'set': ['raise_reg_main', 'raise_reg_main',
...             'raise_reg_main', 'raise_reg_main'],
...     'service': ['raise_reg', 'raise_reg',
...                 'raise_reg', 'raise_reg'],
...     'region': ['QLD', 'NSW', 'VIC', 'SA'],
...     'volume': [100.0, 100.0, 100.0, 100.0]})

Create constraints.

>>> market.set_fcas_requirements_constraints(fcas_requirements)

The market should now have a set of constraints.

>>> print(market._market_constraints_rhs_and_type['fcas'])
              set  constraint_id type    rhs
0  raise_reg_main              0    =  100.0

… and a mapping of those constraints to variable type for the lhs.

>>> regional_mapping =             market._constraint_to_variable_map['regional']
>>> print(regional_mapping['fcas'])
   constraint_id    service region  coefficient
0              0  raise_reg    QLD          1.0
1              0  raise_reg    NSW          1.0
2              0  raise_reg    VIC          1.0
3              0  raise_reg     SA          1.0
Parameters:
  • fcas_requirements (pd.DataFrame) –

    requirement by set and the regions and service the requirement applies to.

    Columns:

    Description:

    set

    unique identifier of the requirement set,

    (as str)

    service

    the service or services the requirement set

    applies to (as str)

    region

    the regions that can contribute to meeting a

    requirement, (as str)

    volume

    the amount of service required, in MW,

    (as np.float64)

    type

    the direction of the constrain ‘=’, ‘>=’ or

    ’<=’, optional, a value of ‘=’ is assumed if

    the column is missing (as str)

  • violation_cost (float | pd.DataFrame) – Makes assocaited constrainst elastic using the given violation_cost (in $/MW).

Return type:

None

Raises:
  • RepeatedRowError – If there is more than one row for any set, region and service combination.

  • ColumnDataTypeError – If columns are not of the required type.

  • MissingColumnError – If the column ‘set’, ‘service’, ‘region’, or ‘volume’ is missing.

  • UnexpectedColumn – There is a column that is not ‘set’, ‘service’, ‘region’, ‘volume’ or ‘type’.

  • ColumnValues – If there are inf, null or negative values in the volume column.

set_fcas_max_availability(fcas_max_availability, violation_cost=None)

Creates constraints to ensure fcas dispatch is limited to the availability specified in the FCAS trapezium.

The constraints are described in the FCAS MODEL IN NEMDE documentation section 2.

Examples

Define the unit information data set needed to initialise the market.

>>> unit_info = pd.DataFrame({
...     'unit': ['A', 'B'],
...     'region': ['NSW', 'NSW']})

Initialise the market instance.

>>> market = SpotMarket(market_regions=['NSW'],
...                     unit_info=unit_info)

Define the FCAS max_availability.

>>> fcas_max_availability = pd.DataFrame({
... 'unit': ['A'],
... 'service': ['raise_6s'],
... 'max_availability': [60.0]})

Set the joint availability constraints.

>>> market.set_fcas_max_availability(fcas_max_availability)

TNow the market should have the constraints and their mapping to decision varibales.

>>> print(market._constraints_rhs_and_type['fcas_max_availability'])
  unit   service dispatch_type  constraint_id type   rhs
0    A  raise_6s     generator              0   <=  60.0
>>> unit_mapping = market._constraint_to_variable_map['unit_level']
>>> print(unit_mapping['fcas_max_availability'])
   constraint_id unit   service dispatch_type  coefficient
0              0    A  raise_6s     generator          1.0
Parameters:
  • fcas_max_availability (pd.DataFrame) –

    Columns:

    Description:

    unit

    unique identifier of a dispatch unit,

    (as str)

    service

    the fcas service being offered,

    (as str)

    dispatch_type

    ”load” or “generator”, optional default

    (as str)

    max_availability

    the maximum volume of the contingency

    service, in MW, (as np.float64)

  • violation_cost (float) – Makes assocaited constrainst elastic using the given violation_cost (in $/MW).

Return type:

None

Raises:
  • RepeatedRowError – If there is more than one row for any unit and service combination.

  • ColumnDataTypeError – If columns are not of the required type.

  • MissingColumnError – If the columns ‘unit’, ‘service’ or ‘max_availability’ is missing.

  • UnexpectedColumn – If there are columns other than ‘unit’, ‘service’ or ‘max_availability’.

  • ColumnValues – If there are inf, null or negative values in the columns of type np.float64.

set_joint_ramping_constraints_reg(scada_ramp_rates, fast_start_profiles=None, run_type='no_fast_start_units', violation_cost=None)

Create constraints that ensure the provision of energy and fcas raise are within unit ramping capabilities.

The constraints are described in the FCAS MODEL IN NEMDE documentation section 6.1.

On a unit basis for generators they take the form of:

Energy dispatch + Regulation raise target <= initial output + ramp up rate * (dispatch_interval / 60)

Examples

Define the unit information data set needed to initialise the market.

>>> unit_info = pd.DataFrame({
...     'unit': ['A', 'B'],
...     'region': ['NSW', 'NSW']})

Initialise the market instance.

>>> market = SpotMarket(market_regions=['NSW'],
...                     unit_info=unit_info,
...                     dispatch_interval=60)

Add bids to the market.

>>> volume_bids = pd.DataFrame({
...     'unit': ['A', 'B'],
...     'service': ['raise_reg', 'raise_reg'],
...     '1': [20.0, 50.0],
...     '2': [20.0, 30.0],
...     '3': [5.0, 10.0]})
>>> market.set_unit_volume_bids(volume_bids)

Define unit initial outputs and ramping capabilities.

>>> ramp_details = pd.DataFrame({
...   'unit': ['A', 'B'],
...   'initial_output': [100.0, 80.0],
...   'scada_ramp_up_rate': [20.0, 10.0],
...   'scada_ramp_down_rate': [20.0, 10.0]})

Create the joint ramping constraints.

>>> market.set_joint_ramping_constraints_reg(ramp_details)

Now the market should have the constraints and their mapping to decision varibales.

>>> print(market._constraints_rhs_and_type['joint_ramping_raise_reg'])
  unit  constraint_id type    rhs
0    A              0   <=  120.0
1    B              1   <=   90.0
>>> unit_mapping = market._constraint_to_variable_map['unit_level']
>>> print(unit_mapping['joint_ramping_raise_reg'])
   constraint_id unit    service dispatch_type  coefficient
0              0    A  raise_reg     generator          1.0
1              1    B  raise_reg     generator          1.0
0              0    A     energy     generator          1.0
1              1    B     energy     generator          1.0
Parameters:

scada_ramp_rates (pd.DataFrame) – Bidirectional units share scada ramp rates so only one set is given per unit and there is no need for the dispatch_type to be specified.

fast_start_profiles : pd.DataFrame

When run_type is set to ‘no_fast_start_units’: this table is not

requried.

When run_type is set to ‘fast_start_first_run’:

When run_type is set to ‘fast_start_first_run’:

run_type: str specifying whever this the fist fast start run or the second, or

if fast start units are not being considered. One of ‘no_fast_start_units’, ‘fast_start_first_run’, or ‘fast_start_second_run’.

violation_costfloat

Makes assocaited constrainst elastic using the given violation_cost (in $/MW).

Return type:

None

Raises:
  • RepeatedRowError – If there is more than one row for any unit in unit_limits.

  • ColumnDataTypeError – If columns are not of the required type.

  • MissingColumnError – If the columns ‘unit’, ‘initial_output’ or ‘ramp_up_rate’ are missing from unit_limits.

  • UnexpectedColumn – If there are columns other than ‘unit’, ‘initial_output’ or ‘ramp_up_rate’ in unit_limits.

  • ColumnValues – If there are inf, null or negative values in the columns of type np.float64.

set_joint_capacity_constraints(contingency_trapeziums, violation_cost=None)

Creates constraints to ensure there is adequate capacity for contingency, regulation and energy dispatch.

Create two constraints for each contingency services, one ensures operation on upper slope of the fcas contingency trapezium is consistent with regulation raise and energy dispatch, the second ensures operation on upper slope of the fcas contingency trapezium is consistent with regulation lower and energy dispatch.

The constraints are described in the FCAS MODEL IN NEMDE documentation section 6.2.

Examples

Define the unit information data set needed to initialise the market.

>>> unit_info = pd.DataFrame({
...     'unit': ['A'],
...     'region': ['NSW']})

Initialise the market instance.

>>> market = SpotMarket(market_regions=['NSW'],
...                     unit_info=unit_info)

Define the FCAS contingency trapeziums.

>>> contingency_trapeziums = pd.DataFrame({
... 'unit': ['A'],
... 'service': ['raise_6s'],
... 'max_availability': [60.0],
... 'enablement_min': [20.0],
... 'low_break_point': [40.0],
... 'high_break_point': [60.0],
... 'enablement_max': [80.0]})

Set the joint capacity constraints.

>>> market.set_joint_capacity_constraints(contingency_trapeziums)

TNow the market should have the constraints and their mapping to decision varibales.

>>> print(market._constraints_rhs_and_type['joint_capacity'])
  unit   service dispatch_type  constraint_id type   rhs
0    A  raise_6s     generator              0   <=  80.0
0    A  raise_6s     generator              1   >=  20.0
>>> unit_mapping = market._constraint_to_variable_map['unit_level']
>>> print(unit_mapping['joint_capacity'])
   constraint_id unit dispatch_type    service  coefficient
0              0    A     generator     energy     1.000000
0              0    A     generator   raise_6s     0.333333
0              0    A     generator  raise_reg     1.000000
0              1    A     generator     energy     1.000000
0              1    A     generator   raise_6s    -0.333333
0              1    A     generator  lower_reg    -1.000000
Parameters:
  • contingency_trapeziums (pd.DataFrame) –

    Columns:

    Description:

    unit

    unique identifier of a dispatch unit,

    (as str)

    service

    the contingency service being offered,

    (as str)

    dispatch_type

    ”load” or “generator”, optional default

    (as str)

    max_availability

    the maximum volume of the contingency

    service, in MW, (as np.float64)

    enablement_min

    the energy dispatch level at which the

    unit can begin to provide the,

    contingency service, in MW,

    (as np.float64)

    low_break_point

    the energy dispatch level at which

    the unit can provide the full

    contingency service offered, in MW,

    (as np.float64)

    high_break_point

    the energy dispatch level at which

    the unit can no longer provide the

    full contingency service offered,

    in MW, (as np.float64)

    enablement_max

    the energy dispatch level at which

    the unit can no longer provide

    the contingency service, in MW,

    (as np.float64)

  • violation_cost (float) – Makes assocaited constrainst elastic using the given violation_cost (in $/MW).

Return type:

None

Raises:
  • RepeatedRowError – If there is more than one row for any unit and service combination in contingency_trapeziums.

  • ColumnDataTypeError – If columns are not of the required type.

  • MissingColumnError – If the columns ‘unit’, ‘service’, ‘max_availability’, ‘enablement_min’, ‘low_break_point’, ‘high_break_point’ or ‘enablement_max’ from contingency_trapeziums.

  • UnexpectedColumn – If there are columns other than ‘unit’, ‘service’, ‘max_availability’, ‘enablement_min’, ‘low_break_point’, ‘high_break_point’ or ‘enablement_max’ in contingency_trapeziums.

  • ColumnValues – If there are inf, null or negative values in the columns of type np.float64.

set_energy_and_regulation_capacity_constraints(regulation_trapeziums, violation_cost=None)

Creates constraints to ensure there is adequate capacity for regulation and energy dispatch targets.

Create two constraints for each regulation services, one ensures operation on upper slope of the fcas regulation trapezium is consistent with energy dispatch, the second ensures operation on lower slope of the fcas regulation trapezium is consistent with energy dispatch.

The constraints are described in the FCAS MODEL IN NEMDE documentation section 6.3.

Examples

Define the unit information data set needed to initialise the market.

>>> unit_info = pd.DataFrame({
...     'unit': ['A'],
...     'region': ['NSW']})

Initialise the market instance.

>>> market = SpotMarket(market_regions=['NSW'],
...                     unit_info=unit_info)

Define the FCAS regulation trapeziums.

>>> regulation_trapeziums = pd.DataFrame({
... 'unit': ['A'],
... 'service': ['raise_reg'],
... 'max_availability': [60.0],
... 'enablement_min': [20.0],
... 'low_break_point': [40.0],
... 'high_break_point': [60.0],
... 'enablement_max': [80.0]})

Set the joint capacity constraints.

>>> market.set_energy_and_regulation_capacity_constraints(regulation_trapeziums)

TNow the market should have the constraints and their mapping to decision varibales.

>>> print(market._constraints_rhs_and_type['energy_and_regulation_capacity'])
  unit    service dispatch_type  constraint_id type   rhs
0    A  raise_reg     generator              0   <=  80.0
0    A  raise_reg     generator              1   >=  20.0
>>> unit_mapping = market._constraint_to_variable_map['unit_level']
>>> print(unit_mapping['energy_and_regulation_capacity'])
   constraint_id unit dispatch_type    service  coefficient
0              0    A     generator     energy     1.000000
0              0    A     generator  raise_reg     0.333333
0              1    A     generator     energy     1.000000
0              1    A     generator  raise_reg    -0.333333
Parameters:
  • regulation_trapeziums (pd.DataFrame) –

    The FCAS trapeziums for the regulation services being offered.

    Columns:

    Description:

    unit

    unique identifier of a dispatch unit,

    (as str)

    service

    the regulation service being offered,

    (as str)

    dispatch_type

    ”load” or “generator”, optional default

    (as str)

    max_availability

    the maximum volume of the contingency

    service, in MW, (as np.float64)

    enablement_min

    the energy dispatch level at which

    the unit can begin to provide

    the regulation service, in MW,

    (as np.float64)

    low_break_point

    the energy dispatch level at which

    the unit can provide the full

    regulation service offered, in MW,

    (as np.float64)

    high_break_point

    the energy dispatch level at which the

    unit can no longer provide the

    full regulation service offered, in MW,

    (as np.float64)

    enablement_max

    the energy dispatch level at which the

    unit can no longer provide any

    regulation service, in MW,

    (as np.float64)

  • violation_cost (float) – Makes assocaited constrainst elastic using the given violation_cost (in $/MW).

Return type:

None

Raises:
  • RepeatedRowError – If there is more than one row for any unit and service combination in regulation_trapeziums.

  • ColumnDataTypeError – If columns are not of the required type.

  • MissingColumnError – If the columns ‘unit’, ‘service’, ‘max_availability’, ‘enablement_min’, ‘low_break_point’, ‘high_break_point’ or ‘enablement_max’ from regulation_trapeziums.

  • UnexpectedColumn – If there are columns other than ‘unit’, ‘service’, ‘max_availability’, ‘enablement_min’, ‘low_break_point’, ‘high_break_point’ or ‘enablement_max’ in regulation_trapeziums.

  • ColumnValues – If there are inf, null or negative values in the columns of type np.float64.

set_interconnectors(interconnector_directions_and_limits)

Create lossless links between specified regions.

Examples

Define the unit information data set needed to initialise the market.

>>> unit_info = pd.DataFrame({
...     'unit': ['A'],
...     'region': ['NSW']})

Initialise the market instance.

>>> market = SpotMarket(market_regions=['NSW', 'VIC'],
...                     unit_info=unit_info)

Define a an interconnector between NSW and VIC so generator can A can be used to meet demand in VIC.

>>> interconnector = pd.DataFrame({
...     'interconnector': ['inter_one'],
...     'to_region': ['VIC'],
...     'from_region': ['NSW'],
...     'max': [100.0],
...     'min': [-100.0]})

Create the interconnector.

>>> market.set_interconnectors(interconnector)

The market should now have a decision variable defined for each interconnector.

>>> print(market._decision_variables['interconnectors'])
  interconnector       link  variable_id  lower_bound  upper_bound        type  generic_constraint_factor
0      inter_one  inter_one            0       -100.0        100.0  continuous                          1

… and a mapping of those variables to to regional energy constraints.

>>> regional = market._variable_to_constraint_map['regional']
>>> print(regional['interconnectors'])
   variable_id interconnector       link region service  coefficient
0            0      inter_one  inter_one    VIC  energy          1.0
1            0      inter_one  inter_one    NSW  energy         -1.0
Parameters:

interconnector_directions_and_limits (pd.DataFrame) –

Columns:

Description:

interconnector

unique identifier of a

interconnector, (as str)

to_region

the region that receives power

when flow is in the positive

direction, (as str)

from_region

the region that power is drawn

from when flow is in the

positive direction, (as str)

max

the maximum power flow in the

positive direction, in MW,

(as np.float64)

min

the maximum power flow in the

negative direction, in MW,

(as np.float64)

from_region_loss_factor

the loss factor at the from

region end of the interconnector,

refers the the from region end

to the regional reference node,

optional, assumed to equal 1.0,

if the column is not provided,

(as np.float)

to_region_loss_factor

the loss factor at the to region

end of the interconnector,

refers the to region end to the

regional reference node,

optional, assumed equal to 1.0

if the column is not provided,

(as np.float)

Return type:

None

Raises:
  • RepeatedRowError – If there is more than one row for any interconnector.

  • ColumnDataTypeError – If columns are not of the require type.

  • MissingColumnError – If any columns are missing.

  • UnexpectedColumn – If there are any additional columns in the input DataFrame.

  • ColumnValues – If there are inf, null values in the max and min columns.

set_interconnector_losses(loss_functions, interpolation_break_points)

Creates linearised loss functions for interconnectors.

Creates a loss variable for each interconnector, this variable models losses by adding demand to each region. The losses are proportioned to each region according to the from_region_loss_share. In a region with one interconnector, where the region is the nominal from region, the impact on the demand constraint would be:

generation - interconnector flow - interconnector losses * from_region_loss_share = demand

If the region was the nominal to region, then:

generation + interconnector flow - interconnector losses * (1 - from_region_loss_share) = demand

The loss variable is constrained to be a linear interpolation of the loss function between the two break points either side of to the actual line flow. This is achieved using a type 2 Special ordered set, where each variable is bound between 0 and 1, only 2 variables can be greater than 0 and all variables must sum to 1. The actual loss function is evaluated at each break point, the variables of the special order set are constrained such that their values weight the distance of the actual flow from the break points on either side e.g. If we had 3 break points at -100 MW, 0 MW and 100 MW, three weight variables w1, w2, and w3, and a loss function f, then the constraints would be of the form.

Constrain the weight variables to sum to one:

w1 + w2 + w3 = 1

Constrain the weight variables to give the relative weighting of adjacent breakpoint:

w1 * -100.0 + w2 * 0.0 + w3 * 100.0 = interconnector flow

Constrain the interconnector losses to be the weighted sum of the losses at the adjacent break point:

w1 * f(-100.0) + w2 * f(0.0) + w3 * f(100.0) = interconnector losses

Examples

Define the unit information data set needed to initialise the market.

>>> unit_info = pd.DataFrame({
...     'unit': ['A'],
...     'region': ['NSW']})

Initialise the market instance.

>>> market = SpotMarket(market_regions=['NSW', 'VIC'],
...                     unit_info=unit_info)

Create the interconnector, this need to be done before a interconnector losses can be set.

>>> interconnectors = pd.DataFrame({
...    'interconnector': ['little_link'],
...    'to_region': ['VIC'],
...    'from_region': ['NSW'],
...    'max': [100.0],
...    'min': [-120.0]})
>>> market.set_interconnectors(interconnectors)

Define the interconnector loss function. In this case losses are always 5 % of line flow.

>>> def constant_losses(flow):
...     return abs(flow) * 0.05

Define the function on a per interconnector basis. Also details how the losses should be proportioned to the connected regions.

>>> loss_functions = pd.DataFrame({
...    'interconnector': ['little_link'],
...    'from_region_loss_share': [0.5],  # losses are shared equally.
...    'loss_function': [constant_losses]})

Define The points to linearly interpolate the loss function between. In this example the loss function is linear so only three points are needed, but if a non linear loss function was used then more points would result in a better approximation.

>>> interpolation_break_points = pd.DataFrame({
...    'interconnector': ['little_link', 'little_link', 'little_link'],
...    'loss_segment': [1, 2, 3],
...    'break_point': [-120.0, 0.0, 100]})
>>> market.set_interconnector_losses(loss_functions, interpolation_break_points)

The market should now have a decision variable defined for each interconnector’s losses.

>>> print(market._decision_variables['interconnector_losses'])
  interconnector         link  variable_id  lower_bound  upper_bound        type
0    little_link  little_link            1       -120.0        120.0  continuous

… and a mapping of those variables to regional energy constraints.

>>> print(market._variable_to_constraint_map['regional']['interconnector_losses'])
   variable_id region service  coefficient
0            1    VIC  energy         -0.5
1            1    NSW  energy         -0.5

The market will also have a special ordered set of weight variables for interpolating the loss function between the break points.

>>> print(market._decision_variables['interpolation_weights'].loc[:,
...       ['interconnector', 'loss_segment', 'break_point', 'variable_id']])
  interconnector  loss_segment  break_point  variable_id
0    little_link             1       -120.0            2
1    little_link             2          0.0            3
2    little_link             3        100.0            4
>>> print(market._decision_variables['interpolation_weights'].loc[:,
...       ['variable_id', 'lower_bound', 'upper_bound', 'type']])
   variable_id  lower_bound  upper_bound        type
0            2          0.0          1.0  continuous
1            3          0.0          1.0  continuous
2            4          0.0          1.0  continuous

and a set of constraints that implement the interpolation, see above explanation.

>>> print(market._constraints_rhs_and_type['interpolation_weights'])
  interconnector         link  constraint_id type  rhs
0    little_link  little_link              0    =  1.0
>>> print(market._constraints_dynamic_rhs_and_type['link_loss_to_flow'])
  interconnector         link  constraint_id type  rhs_variable_id
0    little_link  little_link              2    =                0
0    little_link  little_link              1    =                1
>>> print(market._lhs_coefficients['interconnector_losses'])
   variable_id  constraint_id  coefficient
0            2              0          1.0
1            3              0          1.0
2            4              0          1.0
0            2              2       -120.0
1            3              2          0.0
2            4              2        100.0
0            2              1          6.0
1            3              1          0.0
2            4              1          5.0
Parameters:
  • loss_functions (pd.DataFrame) –

    Columns:

    Description:

    interconnector

    unique identifier of a

    interconnector, (as str)

    from_region_loss_share

    The fraction of loss occuring in

    the from region, 0.0 to 1.0,

    (as np.float64)

    loss_function

    A function that takes a flow,

    in MW as a float and returns the

    losses in MW, (as callable)

  • interpolation_break_points (pd.DataFrame) –

    Columns:

    Description:

    interconnector

    unique identifier of a interconnector,

    (as str)

    loss_segment

    unique identifier of a loss segment on

    an interconnector basis, (as np.float64)

    break_point

    points between which the loss function

    will be linearly interpolated, in MW,

    (as np.float64)

Return type:

None

Raises:
  • ModelBuildError – If all the interconnectors in the input data have not already been added to the model.

  • RepeatedRowError – If there is more than one row for any interconnector in loss_functions. Or if there is a repeated break point for an interconnector in interpolation_break_points.

  • ColumnDataTypeError – If columns are not of the required type.

  • MissingColumnError – If any columns are missing.

  • UnexpectedColumn – If there are any additional columns in the input DataFrames.

  • ColumnValues – If there are inf or null values in the numeric columns of either input DataFrames. Or if from_region_loss_share are outside the range of 0.0 to 1.0

set_generic_constraints(generic_constraint_parameters, violation_cost=None)

Creates a set of generic constraints, adding the constraint type, rhs.

This sets a set of arbitrary constraints, but only the type and rhs values. The lhs terms can be added to these constraints using the methods link_units_to_generic_constraints, link_interconnectors_to_generic_constraints and link_regions_to_generic_constraints.

Examples

Define the unit information data set needed to initialise the market.

>>> unit_info = pd.DataFrame({
...     'unit': ['A'],
...     'region': ['NSW']})

Initialise the market instance.

>>> market = SpotMarket(market_regions=['NSW'],
...                     unit_info=unit_info)

Define a set of generic constraints and add them to the market.

>>> generic_constraint_parameters = pd.DataFrame({
...   'set': ['A', 'B'],
...   'type': ['>=', '<='],
...   'rhs': [10.0, -100.0]})
>>> market.set_generic_constraints(generic_constraint_parameters)

Now the market should have a set of generic constraints.

>>> print(market._constraints_rhs_and_type['generic'])
  set  constraint_id type    rhs
0   A              0   >=   10.0
1   B              1   <= -100.0
Parameters:
  • generic_constraint_parameters (pd.DataFrame) –

    Columns:

    Description:

    set

    the unique identifier of the constraint set,

    (as str)

    type

    the direction of the constraint >=, <= or

    =, (as str)

    rhs

    the right hand side value of the constraint,

    (as np.float64)

  • violation_cost (float | pd.DataFrame) – Makes assocaited constrainst elastic using the given violation_cost (in $/MW).

Return type:

None

Raises:
  • RepeatedRowError – If there is more than one row for any unit.

  • ColumnDataTypeError – If columns are not of the required type.

  • MissingColumnError – If the column ‘set’, ‘type’ or ‘rhs’ is missing.

  • UnexpectedColumn – There is a column that is not ‘set’, ‘type’ or ‘rhs’ .

  • ColumnValues – If there are inf or null values in the rhs column.

Set the lhs coefficients of generic constraints on unit basis.

Notes

These sets also maps to the sets in the fcas market constraints. One potential use of this is prevent specific units from helping to meet fcas constraints by giving them a negative one (-1.0) coefficient using this method for particular fcas markey constraints.

Examples

Define the unit information data set needed to initialise the market.

>>> unit_info = pd.DataFrame({
...     'unit': ['A', 'X', 'Y'],
...     'region': ['NSW', 'NSW', 'NSW']})

Initialise the market instance.

>>> market = SpotMarket(market_regions=['NSW', 'VIC'],
...                     unit_info=unit_info)

Define unit lhs coefficients for generic constraints.

>>> unit_coefficients = pd.DataFrame({
...   'set': ['A', 'A', 'B'],
...   'unit': ['X', 'Y', 'X'],
...   'service': ['energy', 'energy', 'raise_reg'],
...   'coefficient': [1.0, 1.0, -1.0]})
>>> market.link_units_to_generic_constraints(unit_coefficients)

Note all this does is save this information to the market object, linking to specific variable ids and constraint id occurs when the dispatch method is called.

>>> print(market._generic_constraint_lhs['unit'])
  set unit    service  coefficient
0   A    X     energy          1.0
1   A    Y     energy          1.0
2   B    X  raise_reg         -1.0
Parameters:

unit_coefficients (pd.DataFrame) –

Columns:

Description:

set

the unique identifier of the constraint set

to map the lhs coefficients to, (as str)

unit

the unit whose variables will be mapped to

the lhs, (as str)

service

the service whose variables will be mapped to the lhs, (as str)

coefficient

the lhs coefficient (as np.float64)

Raises:
  • RepeatedRowError – If there is more than one row for any set, unit and service combination.

  • ColumnDataTypeError – If columns are not of the required type.

  • MissingColumnError – If the column ‘set’, ‘unit’, ‘serice’ or ‘coefficient’ is missing.

  • UnexpectedColumn – There is a column that is not ‘set’, ‘unit’, ‘serice’ or ‘coefficient’.

  • ColumnValues – If there are inf or null values in the rhs coefficient.

Set the lhs coefficients of generic constraints on region basis.

This effectively acts as short cut for mapping unit variables to a generic constraint. If a particular service in a particular region is included here then all units in this region will have their variables of this service included on the lhs of this constraint set. If a particular unit needs to be excluded from an otherwise region wide constraint it can be given a coefficient with opposite sign to the region wide sign in the generic unit constraints, the coefficients from the two lhs set will be summed and cancel each other out.

Notes

These sets also map to the sets in the fcas market constraints.

Examples

Define the unit information data set needed to initialise the market.

>>> unit_info = pd.DataFrame({
...     'unit': ['A', 'B'],
...     'region': ['X', 'X']})

Initialise the market instance.

>>> market = SpotMarket(market_regions=['X', 'Y'],
...                     unit_info=unit_info)

Define region lhs coefficients for generic constraints.

>>> region_coefficients = pd.DataFrame({
...   'set': ['A', 'A', 'B'],
...   'region': ['X', 'Y', 'X'],
...   'service': ['energy', 'energy', 'raise_reg'],
...   'coefficient': [1.0, 1.0, -1.0]})
>>> market.link_regions_to_generic_constraints(region_coefficients)

Note all this does is save this information to the market object, linking to specific variable ids and constraint id occurs when the dispatch method is called.

>>> print(market._generic_constraint_lhs['region'])
  set region    service  coefficient
0   A      X     energy          1.0
1   A      Y     energy          1.0
2   B      X  raise_reg         -1.0
Parameters:

region_coefficients (pd.DataFrame) –

Columns:

Description:

set

the unique identifier of the constraint set

to map the lhs coefficients to, (as str)

region

the region whose variables will be mapped

to the lhs, (as str)

service

the service whose variables will be mapped

to the lhs, (as str)

coefficient

the lhs coefficient (as np.float64)

Raises:
  • RepeatedRowError – If there is more than one row for any set, region and service combination.

  • ColumnDataTypeError – If columns are not of the required type.

  • MissingColumnError – If the column ‘set’, ‘region’, ‘service’ or ‘coefficient’ is missing.

  • UnexpectedColumn – There is a column that is not ‘set’, ‘region’, ‘service’ or ‘coefficient’.

  • ColumnValues – If there are inf or null values in the rhs coefficient.

Set the lhs coefficients of generic constraints on an interconnector basis.

Notes

These sets also map to the set in the fcas market constraints.

Examples

Define the unit information data set needed to initialise the market.

>>> unit_info = pd.DataFrame({
...     'unit': ['C', 'D'],
...     'region': ['X', 'X']})

Initialise the market instance.

>>> market = SpotMarket(market_regions=['X', 'Y'],
...                     unit_info=unit_info)

Define region lhs coefficients for generic constraints. All interconnector variables are for the energy service so no ‘service’ can be specified.

>>> interconnector_coefficients = pd.DataFrame({
...   'set': ['A', 'A', 'B'],
...   'interconnector': ['X', 'Y', 'X'],
...   'coefficient': [1.0, 1.0, -1.0]})
>>> market.link_interconnectors_to_generic_constraints(interconnector_coefficients)

Note all this does is save this information to the market object, linking to specific variable ids and constraint id occurs when the dispatch method is called.

>>> print(market._generic_constraint_lhs['interconnectors'])
  set interconnector  coefficient
0   A              X          1.0
1   A              Y          1.0
2   B              X         -1.0
Parameters:

unit_coefficients (pd.DataFrame) –

Columns:

Description:

set

the unique identifier of the constraint set

to map the lhs coefficients to, (as str)

interconnetor

the interconnetor whose variables will be

mapped to the lhs, (as str)

coefficient

the lhs coefficient (as np.float64)

Raises:
  • RepeatedRowError – If there is more than one row for any set, interconnetor and service combination.

  • ColumnDataTypeError – If columns are not of the required type.

  • MissingColumnError – If the column ‘set’, ‘interconnetor’ or ‘coefficient’ is missing.

  • UnexpectedColumn – There is a column that is not ‘set’, ‘interconnetor’ or ‘coefficient’.

  • ColumnValues – If there are inf or null values in the rhs coefficient.

make_constraints_elastic(constraints_key, violation_cost)

Make a set of constraints elastic, so they can be violated at a predefined cost.

If an int or float is provided as the violation_cost, then this directly sets the cost. If a pd.DataFrame is provided then it must contain the columns ‘set’ and ‘cost’, ‘set’ is used to match the cost to the constraints, sets in the constraints that do not appear in the pd.DataFrame will not be made elastic.

Examples

Define the unit information data set needed to initialise the market.

>>> unit_info = pd.DataFrame({
...     'unit': ['C', 'D'],
...     'region': ['X', 'X']})

Initialise the market instance.

>>> market = SpotMarket(market_regions=['X', 'Y'],
...                     unit_info=unit_info)

Define a set of generic constraints and add them to the market.

>>> generic_constraint_parameters = pd.DataFrame({
...   'set': ['A', 'B'],
...   'type': ['>=', '<='],
...   'rhs': [10.0, -100.0]})
>>> market.set_generic_constraints(generic_constraint_parameters)

Now the market should have a set of generic constraints.

>>> print(market._constraints_rhs_and_type['generic'])
  set  constraint_id type    rhs
0   A              0   >=   10.0
1   B              1   <= -100.0

Now these constraints can be made elastic.

>>> market.make_constraints_elastic('generic', violation_cost=1000.0)

Now the market will contain extra decision variables to capture the cost of violating the constraint.

>>> print(market._decision_variables['generic_deficit'])
   variable_id  lower_bound  upper_bound        type
0            0          0.0          inf  continuous
1            1          0.0          inf  continuous
>>> print(market._objective_function_components['generic_deficit'])
   variable_id    cost
0            0  1000.0
1            1  1000.0

These will be mapped to the constraints

>>> print(market._lhs_coefficients['generic_deficit'])
   variable_id  constraint_id  coefficient
0            0              0          1.0
1            1              1         -1.0

If a pd.DataFrame is provided then we can set cost on a constraint basis.

>>> violation_cost = pd.DataFrame({
...   'set': ['A', 'B'],
...   'cost': [1000.0, 2000.0]})
>>> market.make_constraints_elastic('generic', violation_cost=violation_cost)
>>> print(market._objective_function_components['generic_deficit'])
   variable_id    cost
0            2  1000.0
1            3  2000.0

Note will the variable id get incremented with every use of the method only the latest set of variables are used.

Parameters:
  • constraints_key (str) – The key used to reference the constraint set in the dict self.market_constraints_rhs_and_type or self.constraints_rhs_and_type. See the documentation for creating the constraint set to get this key.

  • violation_cost (str or float or int or pd.DataFrame)

Return type:

None

Raises:
  • ValueError – If violation_cost is not str, numeric or pd.DataFrame.

  • ModelBuildError – If the constraint_key provided does not match any existing constraints.

  • MissingColumnError – If violation_cost is a pd.DataFrame and does not contain the columns set and cost. Or if the constraints to be made elastic do not have the set idenetifier.

  • RepeatedRowError – If violation_cost is a pd.DataFrame and has more than one row per set.

  • ColumnDataTypeError – If violation_cost is a pd.DataFrame and the column set is not str and the column cost is not numeric.

set_tie_break_constraints(cost)

Creates a cost that attempts to balance the energy dispatch of equally priced bids within a region.

For each pair of bids from different generators in a region which are of the same price a constraint of the following form is created.

B1 * 1/C1 - B2 * 1/C2 + D1 - D2 = 0

Where B1 and B2 are the decision variables of each bid, C1 and C2 are the bid volumes, D1 and D2 are additional variables that have provided cost in the objective function. If a small cost (say 1e-6) is provided then this constraint balances the pro rata output of the bids.

For AEMO documentation of this constraint see AEMO doc <../../docs/pdfs/Schedule of Constraint Violation Penalty factors.pdf> section 3 item 47.

Examples

Define the unit information data set needed to initialise the market.

>>> unit_info = pd.DataFrame({
...     'unit': ['A', 'B'],
...     'region': ['X', 'X']})

Initialise the market instance.

>>> market = SpotMarket(market_regions=['X'],
...                     unit_info=unit_info)

Define a set of bids, in this example we have two units called A and B, with three bid bands.

>>> volume_bids = pd.DataFrame({
...     'unit': ['A', 'B'],
...     '1': [20.0, 50.0],
...     '2': [20.0, 30.0],
...     '3': [5.0, 10.0]})
>>> market.set_unit_volume_bids(volume_bids)

Define a set of prices for the bids.

>>> price_bids = pd.DataFrame({
...     'unit': ['A', 'B'],
...     '1': [50.0, 100.0],
...     '2': [100.0, 130.0],
...     '3': [110.0, 150.0]})
>>> market.set_unit_price_bids(price_bids)

Creat tie break constraints.

>>> market.set_tie_break_constraints(1e-3)

This should add set of constraints rhs, type and lhs coefficients

>>> market._decision_variables['bids']
  unit capacity_band service dispatch_type  variable_id  lower_bound  upper_bound        type
0    A             1  energy     generator            0          0.0         20.0  continuous
1    A             2  energy     generator            1          0.0         20.0  continuous
2    A             3  energy     generator            2          0.0          5.0  continuous
3    B             1  energy     generator            3          0.0         50.0  continuous
4    B             2  energy     generator            4          0.0         30.0  continuous
5    B             3  energy     generator            5          0.0         10.0  continuous
>>> market._constraints_rhs_and_type['tie_break']
   constraint_id type  rhs
0              0    =  0.0
>>> market._lhs_coefficients['tie_break']
   constraint_id  variable_id  coefficient
0              0            1         0.05
0              0            3        -0.02

And a set of deficiet variables that allow the constraints to violated at the specified cost.

>>> market._lhs_coefficients['tie_break_deficit']
   variable_id  constraint_id  coefficient
0            6              0         -1.0
0            7              0          1.0
>>> market._objective_function_components['tie_break_deficit']
   variable_id   cost
0            6  0.001
0            7  0.001
dispatch(energy_market_ceiling_price=None, energy_market_floor_price=None, fcas_market_ceiling_price=None, allow_over_constrained_dispatch_re_run=False)

Combines the elements of the linear program and solves to find optimal dispatch.

If allow_over_constrained_dispatch_re_run is set to True then constraints will be relaxed when market ceiling or floor prices are violated.

Examples

Define the unit information data set needed to initialise the market.

>>> unit_info = pd.DataFrame({
...     'unit': ['A', 'B'],
...     'region': ['NSW', 'NSW']})

Initialise the market instance.

>>> market = SpotMarket(market_regions=['NSW'],
...                     unit_info=unit_info)

Define a set of bids, in this example we have two units called A and B, with three bid bands.

>>> volume_bids = pd.DataFrame({
...     'unit': ['A', 'B'],
...     '1': [20.0, 50.0],
...     '2': [20.0, 30.0],
...     '3': [5.0, 10.0]})

Create energy unit bid decision variables.

>>> market.set_unit_volume_bids(volume_bids)

Define a set of prices for the bids.

>>> price_bids = pd.DataFrame({
...     'unit': ['A', 'B'],
...     '1': [50.0, 100.0],
...     '2': [100.0, 130.0],
...     '3': [100.0, 150.0]})

Create the objective function components corresponding to the the energy bids.

>>> market.set_unit_price_bids(price_bids)

Define a demand level in each region.

>>> demand = pd.DataFrame({
...     'region': ['NSW'],
...     'demand': [100.0]})

Create unit capacity based constraints.

>>> market.set_demand_constraints(demand)

Call the dispatch method.

>>> market.dispatch()

Now the market dispatch can be retrieved.

>>> print(market.get_unit_dispatch())
  unit dispatch_type service  dispatch
0    A     generator  energy      45.0
1    B     generator  energy      55.0

And the market prices can be retrieved.

>>> print(market.get_energy_prices())
  region  price
0    NSW  130.0
Return type:

None

Raises:

ModelBuildError – If a model build process is incomplete, i.e. there are energy bids but not energy demand set.

get_unit_dispatch()

Retrieves the energy dispatch for each unit.

Examples

Define the unit information data set needed to initialise the market.

>>> unit_info = pd.DataFrame({
...     'unit': ['A', 'B'],
...     'region': ['NSW', 'NSW']})

Initialise the market instance.

>>> market = SpotMarket(market_regions=['NSW'],
...                     unit_info=unit_info)

Define a set of bids, in this example we have two units called A and B, with three bid bands.

>>> volume_bids = pd.DataFrame({
...     'unit': ['A', 'B'],
...     '1': [20.0, 50.0],
...     '2': [20.0, 30.0],
...     '3': [5.0, 10.0]})

Create energy unit bid decision variables.

>>> market.set_unit_volume_bids(volume_bids)

Define a set of prices for the bids.

>>> price_bids = pd.DataFrame({
...     'unit': ['A', 'B'],
...     '1': [50.0, 100.0],
...     '2': [100.0, 130.0],
...     '3': [100.0, 150.0]})

Create the objective function components corresponding to the the energy bids.

>>> market.set_unit_price_bids(price_bids)

Define a demand level in each region.

>>> demand = pd.DataFrame({
...     'region': ['NSW'],
...     'demand': [100.0]})

Create unit capacity based constraints.

>>> market.set_demand_constraints(demand)

Call the dispatch method.

>>> market.dispatch()

Now the market dispatch can be retrieved.

>>> print(market.get_unit_dispatch())
  unit dispatch_type service  dispatch
0    A     generator  energy      45.0
1    B     generator  energy      55.0
Return type:

pd.DataFrame

Raises:

ModelBuildError – If a model build process is incomplete, i.e. there are energy bids but not energy demand set.

get_energy_prices()

Retrieves the energy price in each market region.

Energy prices are the shadow prices of the demand constraint in each market region.

Examples

Define the unit information data set needed to initialise the market.

>>> unit_info = pd.DataFrame({
...     'unit': ['A', 'B'],
...     'region': ['NSW', 'NSW']})

Initialise the market instance.

>>> market = SpotMarket(market_regions=['NSW'],
...                     unit_info=unit_info)

Define a set of bids, in this example we have two units called A and B, with three bid bands.

>>> volume_bids = pd.DataFrame({
...     'unit': ['A', 'B'],
...     '1': [20.0, 50.0],
...     '2': [20.0, 30.0],
...     '3': [5.0, 10.0]})

Create energy unit bid decision variables.

>>> market.set_unit_volume_bids(volume_bids)

Define a set of prices for the bids.

>>> price_bids = pd.DataFrame({
...     'unit': ['A', 'B'],
...     '1': [50.0, 100.0],
...     '2': [100.0, 130.0],
...     '3': [100.0, 150.0]})

Create the objective function components corresponding to the the energy bids.

>>> market.set_unit_price_bids(price_bids)

Define a demand level in each region.

>>> demand = pd.DataFrame({
...     'region': ['NSW'],
...     'demand': [100.0]})

Create unit capacity based constraints.

>>> market.set_demand_constraints(demand)

Call the dispatch method.

>>> market.dispatch()

Now the market prices can be retrieved.

>>> print(market.get_energy_prices())
  region  price
0    NSW  130.0
Return type:

pd.DateFrame

Raises:

ModelBuildError – If a model build process is incomplete, i.e. there are energy bids but not energy demand set.

get_fcas_prices()

Retrives the price associated with each set of FCAS requirement constraints.

Return type:

pd.DateFrame

get_interconnector_flows()

Retrieves the flows for each interconnector.

Examples

Define the unit information data set needed to initialise the market.

>>> unit_info = pd.DataFrame({
...     'unit': ['A', 'B'],
...     'region': ['NSW', 'NSW']})

Initialise the market instance.

>>> market = SpotMarket(market_regions=['NSW', 'VIC'],
...                     unit_info=unit_info)

Define a set of bids, in this example we have just one unit that can provide 100 MW in NSW.

>>> volume_bids = pd.DataFrame({
...     'unit': ['A'],
...     '1': [100.0]})

Create energy unit bid decision variables.

>>> market.set_unit_volume_bids(volume_bids)

Define a set of prices for the bids.

>>> price_bids = pd.DataFrame({
...     'unit': ['A'],
...     '1': [80.0]})

Create the objective function components corresponding to the the energy bids.

>>> market.set_unit_price_bids(price_bids)

Define a demand level in each region, no power is required in NSW and 90.0 MW is required in VIC.

>>> demand = pd.DataFrame({
...     'region': ['NSW', 'VIC'],
...     'demand': [0.0, 90.0]})

Create unit capacity based constraints.

>>> market.set_demand_constraints(demand)

Define a an interconnector between NSW and VIC so generator can A can be used to meet demand in VIC.

>>> interconnector = pd.DataFrame({
...     'interconnector': ['inter_one'],
...     'to_region': ['VIC'],
...     'from_region': ['NSW'],
...     'max': [100.0],
...     'min': [-100.0]})

Create the interconnector.

>>> market.set_interconnectors(interconnector)

Call the dispatch method.

>>> market.dispatch()

Now the market dispatch can be retrieved.

>>> print(market.get_unit_dispatch())
  unit dispatch_type service  dispatch
0    A     generator  energy      90.0

And the interconnector flows can be retrieved.

>>> print(market.get_interconnector_flows())
  interconnector       link  flow
0      inter_one  inter_one  90.0
Return type:

pd.DataFrame

get_region_dispatch_summary()

Calculates a dispatch summary at the regional level.

Examples

Define the unit information data set needed to initialise the market.

>>> unit_info = pd.DataFrame({
...     'unit': ['A', 'B'],
...     'region': ['NSW', 'NSW']})

Initialise the market instance.

>>> market = SpotMarket(market_regions=['NSW', 'VIC'],
...                     unit_info=unit_info)

Define a set of bids, in this example we have just one unit that can provide 100 MW in NSW.

>>> volume_bids = pd.DataFrame({
...     'unit': ['A'],
...     '1': [100.0]})

Create energy unit bid decision variables.

>>> market.set_unit_volume_bids(volume_bids)

Define a set of prices for the bids.

>>> price_bids = pd.DataFrame({
...     'unit': ['A'],
...     '1': [80.0]})

Create the objective function components corresponding to the the energy bids.

>>> market.set_unit_price_bids(price_bids)

Define a demand level in each region, no power is required in NSW and 90.0 MW is required in VIC.

>>> demand = pd.DataFrame({
...     'region': ['NSW', 'VIC'],
...     'demand': [0.0, 90.0]})

Create unit capacity based constraints.

>>> market.set_demand_constraints(demand)

Define a an interconnector between NSW and VIC so generator can A can be used to meet demand in VIC.

>>> interconnector = pd.DataFrame({
...     'interconnector': ['inter_one'],
...     'to_region': ['VIC'],
...     'from_region': ['NSW'],
...     'max': [100.0],
...     'min': [-100.0]})

Create the interconnector.

>>> market.set_interconnectors(interconnector)

Define the interconnector loss function. In this case losses are always 5 % of line flow.

>>> def constant_losses(flow=None):
...     return abs(flow) * 0.05

Define the function on a per interconnector basis. Also details how the losses should be proportioned to the connected regions.

>>> loss_functions = pd.DataFrame({
...    'interconnector': ['inter_one'],
...    'from_region_loss_share': [0.5],  # losses are shared equally.
...    'loss_function': [constant_losses]})

Define the points to linearly interpolate the loss function between. In this example the loss function is linear so only three points are needed, but if a non linear loss function was used then more points would result in a better approximation.

>>> interpolation_break_points = pd.DataFrame({
...    'interconnector': ['inter_one', 'inter_one', 'inter_one'],
...    'loss_segment': [1, 2, 3],
...    'break_point': [-120.0, 0.0, 100]})
>>> market.set_interconnector_losses(loss_functions, interpolation_break_points)

Call the dispatch method.

>>> market.dispatch()

Now the region dispatch summary can be retreived.

>>> print(market.get_region_dispatch_summary())
  region   dispatch     inflow  transmission_losses  interconnector_losses
0    NSW  94.615385 -92.307692                  0.0               2.307692
1    VIC   0.000000  92.307692                  0.0               2.307692
Returns:

Columns:

Description:

region

unique identifier of a market

region, required (as str)

dispatch

the net dispatch of units inside

a region i.e. generators dispatch

minus load dispatch, in MW. (as np.float64)

inflow

the net inflow from interconnectors,

not including losses, in MW

(as np.float64)

interconnector_losses

interconnector losses attributed

to region, in MW, (as np.float64)

Return type:

pd.DataFrame

get_fcas_availability()

Get the availability of fcas service on a unit level, after constraints.

Examples

Volume of each bid.

>>> volume_bids = pd.DataFrame({
...   'unit': ['A', 'A', 'B', 'B', 'B'],
...   'service': ['energy', 'raise_6s', 'energy',
...               'raise_6s', 'raise_reg'],
...   '1': [100.0, 10.0, 110.0, 15.0, 15.0]})

Price of each bid.

>>> price_bids = pd.DataFrame({
...   'unit': ['A', 'A', 'B', 'B', 'B'],
...   'service': ['energy', 'raise_6s', 'energy',
...               'raise_6s', 'raise_reg'],
...   '1': [50.0, 35.0, 60.0, 20.0, 30.0]})

Participant defined operational constraints on FCAS enablement.

>>> fcas_trapeziums = pd.DataFrame({
...   'unit': ['B', 'B', 'A'],
...   'service': ['raise_reg', 'raise_6s', 'raise_6s'],
...   'max_availability': [15.0, 15.0, 10.0],
...   'enablement_min': [50.0, 50.0, 70.0],
...   'low_break_point': [65.0, 65.0, 80.0],
...   'high_break_point': [95.0, 95.0, 100.0],
...   'enablement_max': [110.0, 110.0, 110.0]})

Unit locations.

>>> unit_info = pd.DataFrame({
...   'unit': ['A', 'B'],
...   'region': ['NSW', 'NSW']})

The demand in the regions being dispatched.

>>> demand = pd.DataFrame({
...   'region': ['NSW'],
...   'demand': [195.0]})

FCAS requirement in the regions being dispatched.

>>> fcas_requirements = pd.DataFrame({
...   'set': ['nsw_regulation_requirement',
...           'nsw_raise_6s_requirement'],
...   'region': ['NSW', 'NSW'],
...   'service': ['raise_reg', 'raise_6s'],
...   'volume': [10.0, 10.0]})

Create the market model with unit service bids.

>>> market = SpotMarket(unit_info=unit_info,
...                     market_regions=['NSW'])
>>> market.set_unit_volume_bids(volume_bids)
>>> market.set_unit_price_bids(price_bids)

Create constraints that enforce the top of the FCAS trapezium.

>>> fcas_availability = fcas_trapeziums.loc[:, ['unit', 'service', 'max_availability']]
>>> market.set_fcas_max_availability(fcas_availability)

Create constraints that enforce the lower and upper slope of the FCAS regulation service trapeziums.

>>> regulation_trapeziums = fcas_trapeziums[fcas_trapeziums['service'] == 'raise_reg']
>>> market.set_energy_and_regulation_capacity_constraints(regulation_trapeziums)

Create constraints that enforce the lower and upper slope of the FCAS contingency trapezium. These constrains also scale slopes of the trapezium to ensure the co-dispatch of contingency and regulation services is technically feasible.

>>> contingency_trapeziums = fcas_trapeziums[fcas_trapeziums['service'] == 'raise_6s']
>>> market.set_joint_capacity_constraints(contingency_trapeziums)

Set the demand for energy.

>>> market.set_demand_constraints(demand)

Set the required volume of FCAS services.

>>> market.set_fcas_requirements_constraints(fcas_requirements)

Calculate dispatch and pricing

>>> market.dispatch()

Return the total dispatch of each unit in MW.

>>> print(market.get_unit_dispatch())
  unit dispatch_type    service  dispatch
0    A     generator     energy     100.0
1    A     generator   raise_6s       5.0
2    B     generator     energy      95.0
3    B     generator   raise_6s       5.0
4    B     generator  raise_reg      10.0

Return the constrained availability of each units fcas service.

>>> print(market.get_fcas_availability())
  unit    service  availability
0    A   raise_6s          10.0
1    B   raise_6s           5.0
2    B  raise_reg          10.0
exception nempy.markets.ModelBuildError

Raise for building model components in wrong order.

exception nempy.markets.MissingTable

Raise for trying to access missing table.