markets module — nempy 0.0.1 documentation

# 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)

## Reference

Exceptions:

`MissingTable` | Raise for trying to access missing table.
`ModelBuildError` | Raise for building model components in wrong order.

Classes:

`SpotMarket`(market_regions, unit_info[, …]) | Class for constructing and dispatching the spot market on an interval basis.

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

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

  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 dispatch_type  loss_factor
      0    A    NSW     generator          1.0
      1    B    NSW     generator          1.0

  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)
      loss_factor | marginal, average or combined loss factors, `see AEMO doc`, optional, (as np.int64)
      dispatch_type | ”load” or “generator”, optional, (as str)

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

  Raises

    - RepeatedRowError – If there is more than one row for any ‘unit’.

    - ColumnDataTypeError – If columns are not of the require 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:

  `dispatch`([energy_market_ceiling_price, …]) | Combines the elements of the linear program and solves to find optimal dispatch.
  `get_energy_prices`() | Retrieves the energy price in each market region.
  `get_fcas_availability`() | Get the availability of fcas service on a unit level, after constraints.
  `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_unit_dispatch`() | Retrieves the energy dispatch for each unit.
  `link_interconnectors_to_generic_constraints`(…) | Set the lhs coefficients of generic constraints on an interconnector basis.
  `link_regions_to_generic_constraints`(…) | Set the lhs coefficients of generic constraints on region basis.
  `link_units_to_generic_constraints`(…) | Set the lhs coefficients of generic constraints on unit basis.
  `make_constraints_elastic`(constraints_key, …) | Make a set of constraints elastic, so they can be violated at a predefined cost.
  `set_demand_constraints`(demand) | Creates constraints that force supply to equal to demand.
  `set_energy_and_regulation_capacity_constraints`(…) | Creates constraints to ensure there is adequate capacity for regulation and energy dispatch targets.
  `set_fast_start_constraints`(fast_start_profiles) | Create the constraints on fast start units dispatch, `see AEMO doc`
  `set_fcas_max_availability`(fcas_max_availability) | Creates constraints to ensure fcas dispatch is limited to the availability specified in the FCAS trapezium.
  `set_fcas_requirements_constraints`(…) | Creates constraints that force FCAS supply to equal requirements.
  `set_generic_constraints`(…) | Creates a set of generic constraints, adding the constraint type, rhs.
  `set_interconnector_losses`(loss_functions, …) | Creates linearised loss functions for interconnectors.
  `set_interconnectors`(…) | Create lossless links between specified regions.
  `set_joint_capacity_constraints`(…) | Creates constraints to ensure there is adequate capacity for contingency, regulation and energy dispatch.
  `set_joint_ramping_constraints_lower_reg`(…) | Create constraints that ensure the provision of energy and fcas are within unit ramping capabilities.
  `set_joint_ramping_constraints_raise_reg`(…) | Create constraints that ensure the provision of energy and fcas raise are within unit ramping capabilities.
  `set_tie_break_constraints`(cost) | Creates a cost that attempts to balance the energy dispatch of equally priced bids within a region.
  `set_unconstrained_intermitent_generation_forecast_constraint`(…) | Creates constraints that limit unit output based on their forecast output.
  `set_unit_bid_capacity_constraints`(unit_limits) | Creates constraints that limit unit output based on their bid in max capacity.
  `set_unit_price_bids`(price_bids) | Creates the objective function costs corresponding to energy bids.
  `set_unit_ramp_down_constraints`(ramp_details) | Creates constraints on unit output based on ramp down rate.
  `set_unit_ramp_up_constraints`(ramp_details) | Creates constraints on unit output based on ramp up rate.
  `set_unit_volume_bids`(volume_bids) | Creates the decision variables corresponding to unit bids.

  `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 a no services is specified for the bids they are given the default service value of energy is used.

    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)

    The market should now have the variables.

        >>> print(market._decision_variables['bids'])
          unit capacity_band service  variable_id  lower_bound  upper_bound        type
        0    A             1  energy            0          0.0         20.0  continuous
        1    A             2  energy            1          0.0         20.0  continuous
        2    A             3  energy            2          0.0          5.0  continuous
        3    B             1  energy            3          0.0         50.0  continuous
        4    B             2  energy            4          0.0         30.0  continuous
        5    B             3  energy            5          0.0         10.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  coefficient
        0            0    A  energy          1.0
        1            1    A  energy          1.0
        2            2    A  energy          1.0
        3            3    B  energy          1.0
        4            4    B  energy          1.0
        5            5    B  energy          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  coefficient
        0            0    NSW  energy          1.0
        1            1    NSW  energy          1.0
        2            2    NSW  energy          1.0
        3            3    NSW  energy          1.0
        4            4    NSW  energy          1.0
        5            5    NSW  energy          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’.

      Columns: | Description:
      unit | unique identifier of a dispatch unit (as str)
      service | the service being provided, optional, default ‘energy’, (as str)
      1 | bid volume in the 1st band, in MW, (as np.float64)
      2 | bid volume in the 2nd band, in MW, optional, (as np.float64)
      : |
      10 | bid volume in the nth band, in MW, optional, (as np.float64)

    Returns

    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 require 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.

    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 the energy bids.

        >>> market.set_unit_price_bids(price_bids)

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

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

    Parameters

      price_bids (pd.DataFrame) –

      Bids by unit, in $/MW, can contain up to 10 bid bands.

      Columns: | Description:
      unit | unique identifier of a dispatch unit (as str)
      service | the service being provided, optional, default ‘energy’, (as str)
      1 | bid price in the 1st band, in $/MW, (as np.float64)
      2 | bid price in the 2nd band, in $/MW, optional, (as np.float64)
      : |
      10 | bid price in the nth band, in $/MW, optional, (as np.float64)

    Returns

    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 require 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)

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

    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  constraint_id type    rhs
        0    A  energy              0   <=   60.0
        1    B  energy              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  coefficient
        0              0    A  energy          1.0
        1              1    B  energy          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)

    Returns

    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 require 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_unconstrained_intermitent_generation_forecast_constraint`(unit_limits)

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

    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_intermitent_generation_forecast_constraint(unit_limits)

    The market should now have a set of constraints.

        >>> print(market._constraints_rhs_and_type['uigf_capacity'])
          unit service  constraint_id type    rhs
        0    A  energy              0   <=   60.0
        1    B  energy              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  coefficient
        0              0    A  energy          1.0
        1              1    B  energy          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)

    Returns

    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 require 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_up_constraints`(ramp_details)

    Creates constraints on unit output based on ramp up rate.

    Constrains the unit output to be <= initial_output + ramp_up_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]})

    Create unit capacity based constraints.

        >>> market.set_unit_ramp_up_constraints(ramp_details)

    The market should now have a set of constraints.

        >>> print(market._constraints_rhs_and_type['ramp_up'])
          unit service  constraint_id type    rhs
        0    A  energy              0   <=   35.0
        1    B  energy              1   <=  100.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  coefficient
        0              0    A  energy          1.0
        1              1    B  energy          1.0

    Parameters

      ramp_details (pd.DataFrame) –

      Columns: | Description:
      unit | unique identifier of a dispatch unit, (as str)
      initial_output | the output of the unit at the start of the dispatch interval, in MW, (as np.float64)
      ramp_up_rate | the maximum rate at which the unit can increase output, in MW/h, (as np.float64)

    Returns

    Return type

      None

    Raises

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

      - ColumnDataTypeError – If columns are not of the require 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_unit_ramp_down_constraints`(ramp_details)

    Creates constraints on unit output based on ramp down rate.

    Will constrain the unit output to be >= 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 down rates, also need to provide the initial output of the units at the start of dispatch interval.

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

    Create unit capacity based constraints.

        >>> market.set_unit_ramp_down_constraints(ramp_details)

    The market should now have a set of constraints.

        >>> print(market._constraints_rhs_and_type['ramp_down'])
          unit service  constraint_id type   rhs
        0    A  energy              0   >=  10.0
        1    B  energy              1   >=  45.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_down'])
           constraint_id unit service  coefficient
        0              0    A  energy          1.0
        1              1    B  energy          1.0

    Parameters

      ramp_details (pd.DataFrame) –

      Columns: | Description:
      unit | unique identifier of a dispatch unit, (as str)
      initial_output | the output of the unit at the start of the dispatch interval, in MW, (as np.float64)
      ramp_down_rate | the maximum rate at which the unit can, decrease output, in MW/h, (as np.float64)

    Returns

    Return type

      None

    Raises

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

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

      - MissingColumnError – If the column ‘units’, ‘initial_output’ or ‘ramp_down_rate’ is missing.

      - UnexpectedColumn – There is a column that is not ‘units’, ‘initial_output’ or ‘ramp_down_rate’.

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

  `set_fast_start_constraints`(fast_start_profiles)

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

    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  constraint_id type   rhs
        0    A  energy              0   <=   0.0
        1    B  energy              1   <=   0.0
        0    C  energy              2   >=  17.5
        0    C  energy              3   <=  17.5
        0    D  energy              4   >=  50.0
        0    E  energy              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  coefficient
        0              0    A  energy          1.0
        1              1    B  energy          1.0
        0              3    C  energy          1.0
        0              2    C  energy          1.0
        0              4    D  energy          1.0
        0              5    E  energy          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)

    Raises

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

      - ColumnDataTypeError – If columns are not of the require 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)

    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)

    Returns

    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)

    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)

    Returns

    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)

    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  constraint_id type   rhs
        0    A  raise_6s              0   <=  60.0

        >>> unit_mapping = market._constraint_to_variable_map['unit_level']

        >>> print(unit_mapping['fcas_max_availability'])
           constraint_id unit   service  coefficient
        0              0    A  raise_6s          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)
      max_availability | the maximum volume of the contingency service, in MW, (as np.float64)

    Returns

    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_raise_reg`(ramp_details)

    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)

    Define unit initial outputs and ramping capabilities.

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

    Create the joint ramping constraints.

        >>> market.set_joint_ramping_constraints_raise_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  coefficient
        0              0    A  raise_reg          1.0
        1              1    B  raise_reg          1.0
        0              0    A     energy          1.0
        1              1    B     energy          1.0

    Parameters

      ramp_details (pd.DataFrame) –

      Columns: | Description:
      unit | unique identifier of a dispatch unit, (as str)
      initial_output | the output of the unit at the start of the dispatch interval, in MW, (as np.float64)
      ramp_up_rate | the maximum rate at which the unit can increase output, in MW/h, (as np.float64)

    Returns

    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_ramping_constraints_lower_reg`(ramp_details)

    Create constraints that ensure the provision of energy and fcas 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 lower target <= initial output - ramp down 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)

    Define unit initial outputs and ramping capabilities.

        >>> ramp_details = pd.DataFrame({
        ...   'unit': ['A', 'B'],
        ...   'initial_output': [100.0, 80.0],
        ...   'ramp_down_rate': [15.0, 25.0]})

    Create the joint ramping constraints.

        >>> market.set_joint_ramping_constraints_lower_reg(ramp_details)

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

        >>> print(market._constraints_rhs_and_type['joint_ramping_lower_reg'])
          unit  constraint_id type   rhs
        0    A              0   >=  85.0
        1    B              1   >=  55.0

        >>> print(market._constraint_to_variable_map['unit_level']['joint_ramping_lower_reg'])
           constraint_id unit    service  coefficient
        0              0    A  lower_reg         -1.0
        1              1    B  lower_reg         -1.0
        0              0    A     energy          1.0
        1              1    B     energy          1.0

    Parameters

      ramp_details (pd.DataFrame) –

      Columns: | Description:
      unit | unique identifier of a dispatch unit, (as str)
      initial_output | the output of the unit at the start of, the dispatch interval, in MW, (as np.float64)
      ramp_down_rate | the maximum rate at which the unit can, decrease output, in MW/h, (as np.float64)

    Returns

    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_down_rate’ are missing from unit_limits.

      - UnexpectedColumn – If there are columns other than ‘unit’, ‘initial_output’ or ‘ramp_down_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)

    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  constraint_id type   rhs
        0    A  raise_6s              0   <=  80.0
        0    A  raise_6s              1   >=  20.0

        >>> unit_mapping = market._constraint_to_variable_map['unit_level']

        >>> print(unit_mapping['joint_capacity'])
           constraint_id unit    service  coefficient
        0              0    A     energy     1.000000
        0              0    A   raise_6s     0.333333
        0              0    A  raise_reg     1.000000
        0              1    A     energy     1.000000
        0              1    A   raise_6s    -0.333333
        0              1    A  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)
      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)

    Returns

    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)

    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  constraint_id type   rhs
        0    A  raise_reg              0   <=  80.0
        0    A  raise_reg              1   >=  20.0

        >>> unit_mapping = market._constraint_to_variable_map['unit_level']

        >>> print(unit_mapping['energy_and_regulation_capacity'])
           constraint_id unit    service  coefficient
        0              0    A     energy     1.000000
        0              0    A  raise_reg     0.333333
        0              1    A     energy     1.000000
        0              1    A  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)
      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)

    Returns

    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)

    Returns

    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)

    Returns

    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)

    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)

    Returns

    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.

  `link_units_to_generic_constraints`(unit_coefficients)

    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.

  `link_regions_to_generic_constraints`(region_coefficients)

    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.

  `link_interconnectors_to_generic_constraints`(interconnector_coefficients)

    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) –

    Returns

    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  variable_id  lower_bound  upper_bound        type
        0    A             1  energy            0          0.0         20.0  continuous
        1    A             2  energy            1          0.0         20.0  continuous
        2    A             3  energy            2          0.0          5.0  continuous
        3    B             1  energy            3          0.0         50.0  continuous
        4    B             2  energy            4          0.0         30.0  continuous
        5    B             3  energy            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 service  dispatch
        0    A  energy      45.0
        1    B  energy      55.0

    And the market prices can be retrieved.

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

    Returns

    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 service  dispatch
        0    A  energy      45.0
        1    B  energy      55.0

    Returns

    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

    Returns

    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.

    Returns

    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 service  dispatch
        0    A  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

    Returns

    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 the 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    service  dispatch
        0    A     energy     100.0
        1    A   raise_6s       5.0
        2    B     energy      95.0
        3    B   raise_6s       5.0
        4    B  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.