Example #1
0
def create_constraints(unit_limits, next_constraint_id, rhs_col, direction):
    # If no service column is present assume the constraints are for the energy service.
    if 'service' not in unit_limits.columns:
        unit_limits['service'] = 'energy'

    # Create a constraint for each unit in unit limits.
    type_and_rhs = hf.save_index(unit_limits.reset_index(drop=True),
                                 'constraint_id', next_constraint_id)
    type_and_rhs = type_and_rhs.loc[:, [
        'unit', 'service', 'constraint_id', rhs_col
    ]]
    type_and_rhs[
        'type'] = direction  # the type i.e. >=, <=, or = is set by a parameter.
    type_and_rhs['rhs'] = type_and_rhs[
        rhs_col]  # column used to set the rhs is set by a parameter.
    type_and_rhs = type_and_rhs.loc[:, [
        'unit', 'service', 'constraint_id', 'type', 'rhs'
    ]]

    # These constraints always map to energy variables and have a coefficient of one.
    variable_map = type_and_rhs.loc[:, ['constraint_id', 'unit', 'service']]
    variable_map['coefficient'] = 1.0

    return type_and_rhs, variable_map
Example #2
0
def create_loss_variables(inter_variables, inter_constraint_map, loss_shares,
                          next_variable_id):
    """

    Examples
    --------

    Setup function inputs

    >>> inter_variables = pd.DataFrame({
    ...   'interconnector': ['I'],
    ...   'variable_id': [0],
    ...   'lower_bound': [-50.0],
    ...   'upper_bound': [100.0],
    ...   'type': ['continuous']})

    >>> inter_constraint_map = pd.DataFrame({
    ... 'variable_id': [0, 0],
    ... 'region': ['X', 'Y'],
    ... 'service': ['energy', 'energy'],
    ... 'coefficient': [1.0, -1.0]})

    >>> loss_shares = pd.DataFrame({
    ...    'interconnector': ['I'],
    ...    'from_region_loss_share': [0.5]})

    >>> next_constraint_id = 0

    Create the constraints.

    >>> loss_variables, constraint_map = create_loss_variables(inter_variables, inter_constraint_map, loss_shares,
    ...                                                        next_constraint_id)

    >>> print(loss_variables)
      interconnector  variable_id  lower_bound  upper_bound        type
    0              I            0       -100.0        100.0  continuous

    >>> print(constraint_map)
       variable_id region service  coefficient
    0            0      X  energy         -0.5
    1            0      Y  energy         -0.5


    Parameters
    ----------
    inter_variables : pd.DataFrame

        ==============  ==============================================================================
        Columns:        Description:
        interconnector  unique identifier of a interconnector (as `str`)
        variable_id     the id of the variable (as `np.int64`)
        lower_bound     the lower bound of the variable, the min interconnector flow (as `np.float64`)
        upper_bound     the upper bound of the variable, the max inerconnector flow (as `np.float64`)
        type            the type of variable, is 'continuous' for interconnectors losses  (as `str`)
        ==============  ==============================================================================

    inter_constraint_map : pd.DataFrame

        =============  ==========================================================================
        Columns:       Description:
        variable_id  the id of the variable (as `np.int64`)
        region         the regional variables the variable should map too (as `str`)
        service        the service type of the constraints the variable should map to (as `str`)
        coefficient    the upper bound of the variable, the volume bid (as `np.float64`)
        =============  ==========================================================================

    loss_shares : 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`)
        ======================  ==============================================================================

    next_variable_id : int

    Returns
    -------
    loss_variables : pd.DataFrame

        ==============  ===============================================================================================
        Columns:        Description:
        interconnector  unique identifier of a interconnector (as `str`)
        variable_id     the id of the variable (as `np.int64`)
        lower_bound     the lower bound of the variable, negative of the absolute max of inter flow (as `np.float64`)
        upper_bound     the upper bound of the variable, the absolute max of inter flow (as `np.float64`)
        type            the type of variable, is continuous for interconnectors  (as `str`)
        ==============  ===============================================================================================

    constraint_map : pd.DataFrame

        =============  ==========================================================================
        Columns:       Description:
        variable_id  the id of the variable (as `np.int64`)
        region         the regional variables the variable should map too (as `str`)
        service        the service type of the constraints the variable should map to (as `str`)
        coefficient    the upper bound of the variable, the volume bid (as `np.float64`)
        =============  ==========================================================================
    """

    # Preserve the interconnector variable id for merging later.
    columns_for_loss_variables = \
        inter_variables.loc[:, ['interconnector', 'variable_id', 'lower_bound', 'upper_bound', 'type']]
    columns_for_loss_variables.columns = [
        'interconnector', 'inter_variable_id', 'lower_bound', 'upper_bound',
        'type'
    ]
    inter_constraint_map = inter_constraint_map.loc[:, [
        'variable_id', 'region', 'service', 'coefficient'
    ]]
    inter_constraint_map.columns = [
        'inter_variable_id', 'region', 'service', 'coefficient'
    ]

    # Create a variable id for loss variables
    loss_variables = hf.save_index(
        loss_shares.loc[:, ['interconnector', 'from_region_loss_share']],
        'variable_id', next_variable_id)
    # Use interconnector variable definitions to formulate loss variable definitions.
    columns_for_loss_variables['upper_bound'] = \
        columns_for_loss_variables.loc[:, ['lower_bound', 'upper_bound']].abs().max(axis=1)
    columns_for_loss_variables[
        'lower_bound'] = -1 * columns_for_loss_variables['upper_bound']
    loss_variables = pd.merge(loss_variables,
                              columns_for_loss_variables,
                              'inner',
                              on='interconnector')

    # Create the loss variable constraint map by combining the new variables and the flow variable constraint map.
    constraint_map = pd.merge(loss_variables.loc[:, [
        'variable_id', 'inter_variable_id', 'interconnector',
        'from_region_loss_share'
    ]],
                              inter_constraint_map,
                              'inner',
                              on='inter_variable_id')

    # Assign losses to regions according to the from_region_loss_share
    constraint_map['coefficient'] = np.where(
        constraint_map['coefficient'] < 0.0,
        -1 * constraint_map['from_region_loss_share'],
        -1 * (1 - constraint_map['from_region_loss_share']))

    loss_variables = loss_variables.loc[:, [
        'interconnector', 'variable_id', 'lower_bound', 'upper_bound', 'type'
    ]]
    constraint_map = constraint_map.loc[:, [
        'variable_id', 'region', 'service', 'coefficient'
    ]]
    return loss_variables, constraint_map
Example #3
0
def create(definitions, next_variable_id):
    """Create decision variables, and their mapping to constraints. For modeling interconnector flows. As DataFrames.

    Examples
    --------
    Definitions for two interconnectors, one called A, that nominal flows from region X to region Y, note A can flow in
    both directions because of the way max and min are defined. The interconnector B nominal flows from Y to Z, but can
    only flow in the forward direction.

    >>> inter_definitions = pd.DataFrame({
    ...   'interconnector': ['A', 'B'],
    ...   'from_region': ['X', 'Y'],
    ...   'to_region': ['Y', 'Z'],
    ...   'max': [100.0, 400.0],
    ...   'min': [-100.0, 50.0]})

    >>> print(inter_definitions)
      interconnector from_region to_region    max    min
    0              A           X         Y  100.0 -100.0
    1              B           Y         Z  400.0   50.0

    Start creating new variable ids from 0.

    >>> next_variable_id = 0

    Run the function and print results.

    >>> decision_variables, constraint_map = create(inter_definitions, next_variable_id)

    >>> print(decision_variables)
      interconnector  variable_id  lower_bound  upper_bound        type
    0              A            0       -100.0        100.0  continuous
    1              B            1         50.0        400.0  continuous

    >>> print(constraint_map)
       variable_id region service  coefficient
    0            0      Y  energy          1.0
    1            1      Z  energy          1.0
    2            0      X  energy         -1.0
    3            1      Y  energy         -1.0

    Parameters
    ----------
    definitions : pd.DataFrame
        Interconnector definition.

        ==============  =====================================================================================
        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`)
        ==============  =====================================================================================

    next_variable_id : int

    Returns
    -------
    decision_variables : pd.DataFrame

        ==============  ==============================================================================
        Columns:        Description:
        interconnector  unique identifier of a interconnector (as `str`)
        variable_id     the id of the variable (as `np.int64`)
        lower_bound     the lower bound of the variable, the min interconnector flow (as `np.float64`)
        upper_bound     the upper bound of the variable, the max inerconnector flow (as `np.float64`)
        type            the type of variable, is continuous for interconnectors  (as `str`)
        ==============  ==============================================================================

    constraint_map : pd.DataFrame
        Sets out which regional demand constraints the variable should be linked to.

        =============  ===================================================================================
        Columns:       Description:
        variable_id    the id of the variable (as `int`)
        region         the regional constraints to map the variable to  (as `str`)
        service        the service type constraints to map too, only energy for interconnectors (as `str`)
        coefficient    the variable side contribution to the coefficient (as `np.float64`)
        =============  ====================================================================================
    """

    # Create a variable_id for each interconnector.
    decision_variables = hf.save_index(definitions, 'variable_id',
                                       next_variable_id)

    # Create two entries in the constraint_map for each interconnector. This means the variable will be mapped to the
    # demand constraint of both connected regions.
    constraint_map = hf.stack_columns(
        decision_variables, ['variable_id', 'interconnector', 'max', 'min'],
        ['to_region', 'from_region'], 'direction', 'region')

    # Define decision variable attributes.
    decision_variables['type'] = 'continuous'
    decision_variables = decision_variables.loc[:, [
        'interconnector', 'variable_id', 'min', 'max', 'type'
    ]]
    decision_variables.columns = [
        'interconnector', 'variable_id', 'lower_bound', 'upper_bound', 'type'
    ]

    # Set positive coefficient for the to_region so the interconnector flowing in the nominal direction helps meet the
    # to_region demand constraint. Negative for the from_region, same logic.
    constraint_map['coefficient'] = np.where(
        constraint_map['direction'] == 'to_region', 1.0, -1.0)
    constraint_map['service'] = 'energy'
    constraint_map = constraint_map.loc[:, [
        'variable_id', 'region', 'service', 'coefficient'
    ]]

    return decision_variables, constraint_map
Example #4
0
def create_weights(break_points, next_variable_id):
    """Create interpolation weight variables for each breakpoint.

    Examples
    --------

    >>> break_points = pd.DataFrame({
    ...   'interconnector': ['I', 'I', 'I'],
    ...   'loss_segment': [1, 2, 3],
    ...   'break_point': [-100.0, 0.0, 100.0]})

    >>> next_variable_id = 0

    >>> weight_variables = create_weights(break_points, next_variable_id)

    >>> print(weight_variables.loc[:, ['interconnector', 'loss_segment', 'break_point', 'variable_id']])
      interconnector  loss_segment  break_point  variable_id
    0              I             1       -100.0            0
    1              I             2          0.0            1
    2              I             3        100.0            2

    >>> print(weight_variables.loc[:, ['variable_id', 'lower_bound', 'upper_bound', 'type']])
       variable_id  lower_bound  upper_bound        type
    0            0          0.0          1.0  continuous
    1            1          0.0          1.0  continuous
    2            2          0.0          1.0  continuous

    Parameters
    ----------
    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_points    the interconnector flow values to interpolate losses between (as `np.float64`)
        ==============  ================================================================================

    next_variable_id : int

    Returns
    -------
    weight_variables : 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_points    the interconnector flow values to interpolate losses between (as `np.int64`)
        variable_id     the id of the variable (as `np.int64`)
        lower_bound    the lower bound of the variable, is zero for weight variables (as `np.float64`)
        upper_bound    the upper bound of the variable, is one for weight variables (as `np.float64`)
        type           the type of variable, is continuous for bids  (as `str`)
        ==============  ==============================================================================
    """
    # Create a variable for each break point.
    weight_variables = hf.save_index(break_points, 'variable_id',
                                     next_variable_id)
    weight_variables['lower_bound'] = 0.0
    weight_variables['upper_bound'] = 1.0
    weight_variables['type'] = 'continuous'
    return weight_variables
Example #5
0
def create_weights_must_sum_to_one(weight_variables, next_constraint_id):
    """Create the constraint to force weight variable to sum to one, need for interpolation to work.

    For one interconnector, if we had  three weight variables w1, w2, and w3, then the constraint would be of the form.

        w1 * 1.0 + w2 * 1.0 + w3 * 1.0 = 1.0

    Examples
    --------

    Setup function inputs

    >>> weight_variables = pd.DataFrame({
    ...   'interconnector': ['I', 'I', 'I'],
    ...   'variable_id': [1, 2, 3],
    ...   'break_point': [-100.0, 0, 100.0]})

    >>> next_constraint_id = 0

    Create the constraints.

    >>> lhs, rhs = create_weights_must_sum_to_one(weight_variables, next_constraint_id)

    >>> print(lhs)
       variable_id  constraint_id  coefficient
    0            1              0          1.0
    1            2              0          1.0
    2            3              0          1.0

    >>> print(rhs)
      interconnector  constraint_id type  rhs
    0              I              0    =  1.0


    Parameters
    ----------
    weight_variables : pd.DataFrame

        ==============  ==============================================================================
        Columns:        Description:
        interconnector  unique identifier of a interconnector (as `str`)
        variable_id     the id of the variable (as `np.int64`)
        break_points    the interconnector flow values to interpolate losses between (as `np.int64`)
        ==============  ==============================================================================

    next_constraint_id : int

    Returns
    -------
    lhs : pd.DataFrame

        ==============  ==============================================================================
        Columns:        Description:
        variable_id     the id of the variable (as `np.int64`)
        constraint_id   the id of the constraint (as `np.int64`)
        coefficient     the coefficient of the variable on the lhs of the constraint (as `np.float64`)
        ==============  ==============================================================================

    rhs : pd.DataFrame

        ================  ==============================================================================
        Columns:          Description:
        interconnector    unique identifier of a interconnector (as `str`)
        constraint_id     the id of the constraint (as `np.int64`)
        type              the type of the constraint, e.g. "=" (as `str`)
        rhs               the rhs of the constraint (as `np.float64`)
        ================  ==============================================================================
    """

    # Create a constraint for each set of weight variables.
    constraint_ids = weight_variables.loc[:,
                                          ['interconnector']].drop_duplicates(
                                              'interconnector')
    constraint_ids = hf.save_index(constraint_ids, 'constraint_id',
                                   next_constraint_id)

    # Map weight variables to their corresponding constraints.
    lhs = pd.merge(weight_variables.loc[:, ['interconnector', 'variable_id']],
                   constraint_ids,
                   'inner',
                   on='interconnector')
    lhs['coefficient'] = 1.0
    lhs = lhs.loc[:, ['variable_id', 'constraint_id', 'coefficient']]

    # Create rhs details for each constraint.
    rhs = constraint_ids
    rhs['type'] = '='
    rhs['rhs'] = 1.0
    return lhs, rhs
Example #6
0
def link_weights_to_inter_flow(weight_variables, flow_variables,
                               next_constraint_id):
    """Create the constraints that link the interpolation weights to interconnector flow.

    For one interconnector, if we had 3 break points at -100 MW, 0 MW and 100 MW, three weight variables w1, w2, and w3,
    then the constraint would be of the form.

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

    Examples
    --------

    Setup function inputs

    >>> flow_variables = pd.DataFrame({
    ...   'interconnector': ['I'],
    ...   'variable_id': [0]})

    >>> weight_variables = pd.DataFrame({
    ...   'interconnector': ['I', 'I', 'I'],
    ...   'variable_id': [1, 2, 3],
    ...   'break_point': [-100.0, 0, 100.0]})

    >>> next_constraint_id = 0

    Create the constraints.

    >>> lhs, rhs = link_weights_to_inter_flow(weight_variables, flow_variables, next_constraint_id)

    >>> print(lhs)
       variable_id  constraint_id  coefficient
    0            1              0       -100.0
    1            2              0          0.0
    2            3              0        100.0

    >>> print(rhs)
      interconnector  constraint_id type  rhs_variable_id
    0              I              0    =                0


    Parameters
    ----------
    weight_variables : pd.DataFrame

        ==============  ==============================================================================
        Columns:        Description:
        interconnector  unique identifier of a interconnector (as `str`)
        variable_id     the id of the variable (as `np.int64`)
        break_points    the interconnector flow values to interpolate losses between (as `np.int64`)
        ==============  ==============================================================================

    flow_variables : pd.DataFrame

        ==============  ==============================================================================
        Columns:        Description:
        interconnector  unique identifier of a interconnector (as `str`)
        variable_id     the id of the variable (as `np.int64`)
        ==============  ==============================================================================

    next_constraint_id : int

    Returns
    -------
    lhs : pd.DataFrame

        ==============  ==============================================================================
        Columns:        Description:
        variable_id     the id of the variable (as `np.int64`)
        constraint_id   the id of the constraint (as `np.int64`)
        coefficient     the coefficient of the variable on the lhs of the constraint (as `np.float64`)
        ==============  ==============================================================================

    rhs : pd.DataFrame

        ================  ==============================================================================
        Columns:          Description:
        interconnector    unique identifier of a interconnector (as `str`)
        constraint_id     the id of the constraint (as `np.int64`)
        type              the type of the constraint, e.g. "=" (as `str`)
        rhs_variable_id   the rhs of the constraint (as `np.int64`)
        ================  ==============================================================================
    """

    # Create a constraint for each set of weight variables.
    constraint_ids = weight_variables.loc[:,
                                          ['interconnector']].drop_duplicates(
                                              'interconnector')
    constraint_ids = hf.save_index(constraint_ids, 'constraint_id',
                                   next_constraint_id)

    # Map weight variables to their corresponding constraints.
    lhs = pd.merge(
        weight_variables.loc[:,
                             ['interconnector', 'variable_id', 'break_point']],
        constraint_ids,
        'inner',
        on='interconnector')
    lhs['coefficient'] = lhs['break_point']
    lhs = lhs.loc[:, ['variable_id', 'constraint_id', 'coefficient']]

    # Get the interconnector variables that will be on the rhs of constraint.
    rhs_variables = flow_variables.loc[:, ['interconnector', 'variable_id']]
    rhs_variables.columns = ['interconnector', 'rhs_variable_id']
    # Map the rhs variables to their constraints.
    rhs = pd.merge(constraint_ids, rhs_variables, 'inner', on='interconnector')
    rhs['type'] = '='
    rhs = rhs.loc[:, [
        'interconnector', 'constraint_id', 'type', 'rhs_variable_id'
    ]]
    return lhs, rhs
Example #7
0
def link_inter_loss_to_interpolation_weights(weight_variables, loss_variables,
                                             loss_functions,
                                             next_constraint_id):
    """Create the constraints that force the interconnector losses to be set by the interpolation weights.

    For one interconnector, 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 constraint would be of the form.

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

    Examples
    --------

    Setup function inputs

    >>> loss_variables = pd.DataFrame({
    ...   'interconnector': ['I'],
    ...   'variable_id': [0]})

    >>> weight_variables = pd.DataFrame({
    ...   'interconnector': ['I', 'I', 'I'],
    ...   'variable_id': [1, 2, 3],
    ...   'break_point': [-100.0, 0, 100.0]})

    Loss functions can arbitrary, they just need to take the flow as input and return losses as an output.

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

    The loss function get assigned to an interconnector by its row in the loss functions DataFrame.

    >>> loss_functions = pd.DataFrame({
    ...    'interconnector': ['I'],
    ...    'from_region_loss_share': [0.5],
    ...    'loss_function': [constant_losses]})

    >>> next_constraint_id = 0

    Create the constraints.

    >>> lhs, rhs = link_inter_loss_to_interpolation_weights(weight_variables, loss_variables, loss_functions,
    ...                                                     next_constraint_id)

    >>> print(lhs)
       variable_id  constraint_id  coefficient
    0            1              0          5.0
    1            2              0          0.0
    2            3              0          5.0

    >>> print(rhs)
      interconnector  constraint_id type  rhs_variable_id
    0              I              0    =                0


    Parameters
    ----------
    weight_variables : pd.DataFrame

        ==============  ==============================================================================
        Columns:        Description:
        interconnector  unique identifier of a interconnector (as `str`)
        variable_id     the id of the variable (as `np.int64`)
        break_points    the interconnector flow values to interpolate losses between (as `np.int64`)
        ==============  ==============================================================================

    loss_variables : pd.DataFrame

        ==============  ==============================================================================
        Columns:        Description:
        interconnector  unique identifier of a interconnector (as `str`)
        variable_id     the id of the variable (as `np.int64`)
        ==============  ==============================================================================

    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`)
        ======================  ==============================================================================

    next_constraint_id : int

    Returns
    -------
    lhs : pd.DataFrame

        ==============  ==============================================================================
        Columns:        Description:
        variable_id     the id of the variable (as `np.int64`)
        constraint_id   the id of the constraint (as `np.int64`)
        coefficient     the coefficient of the variable on the lhs of the constraint (as `np.float64`)
        ==============  ==============================================================================

    rhs : pd.DataFrame

        ================  ==============================================================================
        Columns:          Description:
        interconnector    unique identifier of a interconnector (as `str`)
        constraint_id     the id of the constraint (as `np.int64`)
        type              the type of the constraint, e.g. "=" (as `str`)
        rhs_variable_id   the rhs of the constraint (as `np.int64`)
        ================  ==============================================================================
    """

    # Create a constraint for each set of weight variables.
    constraint_ids = weight_variables.loc[:,
                                          ['interconnector']].drop_duplicates(
                                              'interconnector')
    constraint_ids = hf.save_index(constraint_ids, 'constraint_id',
                                   next_constraint_id)

    # Map weight variables to their corresponding constraints.
    lhs = pd.merge(
        weight_variables.loc[:,
                             ['interconnector', 'variable_id', 'break_point']],
        constraint_ids,
        'inner',
        on='interconnector')
    lhs = pd.merge(lhs, loss_functions, 'inner', on='interconnector')

    # Evaluate the loss function at each break point to get the lhs coefficient.
    lhs['coefficient'] = lhs.apply(lambda x: x['loss_function']
                                   (x['break_point']),
                                   axis=1)
    lhs = lhs.loc[:, ['variable_id', 'constraint_id', 'coefficient']]

    # Get the loss variables that will be on the rhs of the constraints.
    rhs_variables = loss_variables.loc[:, ['interconnector', 'variable_id']]
    rhs_variables.columns = ['interconnector', 'rhs_variable_id']
    # Map the rhs variables to their constraints.
    rhs = pd.merge(constraint_ids, rhs_variables, 'inner', on='interconnector')
    rhs['type'] = '='
    rhs = rhs.loc[:, [
        'interconnector', 'constraint_id', 'type', 'rhs_variable_id'
    ]]
    return lhs, rhs
Example #8
0
def fcas(fcas_requirements, next_constraint_id):
    """Create the constraints that ensure the amount of FCAS supply dispatched  equals requirements.

    Examples
    --------

    >>> import pandas

    Defined the unit capacities.

    >>> 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]})

    >>> next_constraint_id = 0

    Create the constraint information.

    >>> type_and_rhs, variable_map = fcas(fcas_requirements, next_constraint_id)

    >>> print(type_and_rhs)
                  set  constraint_id type    rhs
    0  raise_reg_main              0    =  100.0

    >>> print(variable_map)
       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    unique identifier of a region (as `str`)
        volume    the amount of service required, in MW (as `np.float64`)
        ========  ===================================================================

    next_constraint_id : int
        The next integer to start using for constraint ids.

    Returns
    -------
    type_and_rhs : pd.DataFrame
        The type and rhs of each constraint.

        =============  ===================================================================
        Columns:       Description:
        set            unique identifier of a market region (as `str`)
        constraint_id  the id of the variable (as `int`)
        type           the type of the constraint, e.g. "=" (as `str`)
        rhs            the rhs of the constraint (as `np.float64`)
        =============  ===================================================================

    variable_map : pd.DataFrame
        The type of variables that should appear on the lhs of the constraint.

        =============  ==========================================================================
        Columns:       Description:
        constraint_id  the id of the constraint (as `np.int64`)
        region         the regional variables the constraint should map too (as `str`)
        service        the service type of the variables the constraint should map to (as `str`)
        coefficient    the upper bound of the variable, the volume bid (as `np.float64`)
        =============  ==========================================================================
    """

    # Create an index for each constraint.
    type_and_rhs = fcas_requirements.loc[:, ['set', 'volume']]
    type_and_rhs = type_and_rhs.drop_duplicates('set')
    type_and_rhs = hf.save_index(type_and_rhs, 'constraint_id',
                                 next_constraint_id)
    type_and_rhs[
        'type'] = '='  # Supply and interconnector flow must exactly equal demand.
    type_and_rhs['rhs'] = type_and_rhs['volume']
    type_and_rhs = type_and_rhs.loc[:, ['set', 'constraint_id', 'type', 'rhs']]

    # Map constraints to energy variables in their region.
    variable_map = fcas_requirements.loc[:, ['set', 'service', 'region']]
    variable_map = pd.merge(variable_map,
                            type_and_rhs.loc[:, ['set', 'constraint_id']],
                            'inner',
                            on='set')
    variable_map['coefficient'] = 1.0
    variable_map = variable_map.loc[:, [
        'constraint_id', 'service', 'region', 'coefficient'
    ]]
    return type_and_rhs, variable_map
Example #9
0
def energy(demand, next_constraint_id):
    """Create the constraints that ensure the amount of supply dispatched in each region equals demand.

    If only one region exists then the constraint will be of the form:

        unit 1 output + unit 2 output +. . .+ unit n output = region demand

    If multiple regions exist then a constraint will ne created for each region. If there were 2 units A and B in region
    X, and 2 units C and D in region Y, then the constraints would be of the form:

        constraint 1: unit A output + unit B output = region X demand
        constraint 2: unit C output + unit D output = region Y demand

    Examples
    --------

    >>> import pandas

    Defined the unit capacities.

    >>> demand = pd.DataFrame({
    ...   'region': ['X', 'Y'],
    ...   'demand': [1000.0, 2000.0]})

    >>> next_constraint_id = 0

    Create the constraint information.

    >>> type_and_rhs, variable_map = energy(demand, next_constraint_id)

    >>> print(type_and_rhs)
      region  constraint_id type     rhs
    0      X              0    =  1000.0
    1      Y              1    =  2000.0

    >>> print(variable_map)
       constraint_id region service  coefficient
    0              0      X  energy          1.0
    1              1      Y  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`)
        ========  =====================================================================================

    next_constraint_id : int
        The next integer to start using for constraint ids.


    Returns
    -------
    type_and_rhs : pd.DataFrame
        The type and rhs of each constraint.

        =============  ===============================================================
        Columns:       Description:
        region         unique identifier of a market region (as `str`)
        constraint_id  the id of the variable (as `int`)
        type           the type of the constraint, e.g. "=" (as `str`)
        rhs            the rhs of the constraint (as `np.float64`)
        =============  ===============================================================

    variable_map : pd.DataFrame
        The type of variables that should appear on the lhs of the constraint.

        =============  ==========================================================================
        Columns:       Description:
        constraint_id  the id of the constraint (as `np.int64`)
        region         the regional variables the constraint should map too (as `str`)
        service        the service type of the variables the constraint should map to (as `str`)
        coefficient    the upper bound of the variable, the volume bid (as `np.float64`)
        =============  ==========================================================================
    """
    # Create an index for each constraint.
    type_and_rhs = hf.save_index(demand, 'constraint_id', next_constraint_id)
    type_and_rhs[
        'type'] = '='  # Supply and interconnector flow must exactly equal demand.
    type_and_rhs['rhs'] = type_and_rhs['demand']
    type_and_rhs = type_and_rhs.loc[:,
                                    ['region', 'constraint_id', 'type', 'rhs']]

    # Map constraints to energy variables in their region.
    variable_map = type_and_rhs.loc[:, ['constraint_id', 'region']]
    variable_map['service'] = 'energy'
    variable_map['coefficient'] = 1.0
    return type_and_rhs, variable_map
Example #10
0
def bids(volume_bids, unit_info, next_variable_id):
    """Create decision variables that correspond to unit bids, for use in the linear program.

    This function defines the needed parameters for each variable, with a lower bound equal to zero, an upper bound
    equal to the bid volume, and a variable type of continuous. There is no limit on the number of bid bands and each
    column in the capacity_bids DataFrame other than unit is treated as a bid band. Volume bids should be positive.
    numeric values only.

    Examples
    --------

    >>> import pandas

    A set of capacity bids.

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

    The locations of the units.

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

    >>> next_variable_id = 0

    Create the decision variables and their mapping into constraints.

    >>> decision_variables, constraint_map = bids(volume_bids, unit_info, next_variable_id)

    >>> print(decision_variables)
      unit capacity_band service  variable_id  lower_bound  upper_bound        type
    0    A             1  energy            0          0.0         10.0  continuous
    1    A             2  energy            1          0.0         20.0  continuous
    2    B             1  energy            2          0.0         50.0  continuous
    3    B             2  energy            3          0.0         30.0  continuous

    >>> print(constraint_map)
       variable_id unit region service  coefficient
    0            0    A    NSW  energy          1.0
    1            1    A    NSW  energy          1.0
    2            2    B      X  energy          1.0
    3            3    B      X  energy          1.0

    Parameters
    ----------
    volume_bids : pd.DataFrame
        Bids by unit, in MW, can contain up to n bid bands.

        ========  ===============================================================
        Columns:  Description:
        unit      unique identifier of a dispatch unit (as `str`)
        service   the service being provided, optional, if missing energy assumed
                  (as `str`)
        1         bid volume in the 1st band, in MW (as `float`)
        2         bid volume in the 2nd band, in MW (as `float`)
        n         bid volume in the nth band, in MW (as `float`)
        ========  ===============================================================

    unit_info : pd.DataFrame
        The region each unit is located in.

        ========  ======================================================
        Columns:  Description:
        unit      unique identifier of a dispatch unit (as `str`)
        region    unique identifier of a market region (as `str`)
        ========  ======================================================

    next_variable_id : int
        The next integer to start using for variables ids.

    Returns
    -------
    decision_variables : pd.DataFrame

        =============  ===============================================================
        Columns:       Description:
        unit           unique identifier of a dispatch unit (as `str`)
        capacity_band  the bid band of the variable (as `str`)
        variable_id    the id of the variable (as `int`)
        lower_bound    the lower bound of the variable, is zero for bids (as `np.float64`)
        upper_bound    the upper bound of the variable, the volume bid (as `np.float64`)
        type           the type of variable, is continuous for bids  (as `str`)
        =============  ===============================================================

    constraint_map : pd.DataFrame

        =============  =============================================================================
        Columns:       Description:
        variable_id    the id of the variable (as `np.int64`)
        unit           the unit level constraints the variable should map to (as `str`)
        region         the regional constraints the variable should map to (as `str`)
        service        the service type of the constraints the variables should map to (as `str`)
        coefficient    the upper bound of the variable, the volume bid (as `np.float64`)
        =============  =============================================================================
    """
    # If no service column is provided assume bids are for energy.
    if 'service' not in volume_bids.columns:
        volume_bids['service'] = 'energy'

    # Get a list of all the columns that contain volume bids.
    bid_bands = [col for col in volume_bids.columns if col not in ['unit', 'service']]
    # Reshape the table so each bid band is on it own row.
    decision_variables = hf.stack_columns(volume_bids, cols_to_keep=['unit', 'service'], cols_to_stack=bid_bands,
                                          type_name='capacity_band', value_name='upper_bound')
    decision_variables = decision_variables[decision_variables['upper_bound'] >= 0.0001]
    # Group units together in the decision variable table.
    decision_variables = decision_variables.sort_values(['unit', 'capacity_band'])
    # Create a unique identifier for each decision variable.
    decision_variables = hf.save_index(decision_variables, 'variable_id', next_variable_id)
    # The lower bound of bidding decision variables will always be zero.
    decision_variables['lower_bound'] = 0.0
    decision_variables['type'] = 'continuous'

    # Map the variables into all constraints with the same unit and service type of energy.
    constraint_map = decision_variables.loc[:, ['variable_id', 'unit', 'service']]
    # Map variables into all constraints in their region and with service type of energy.
    constraint_map = pd.merge(constraint_map, unit_info.loc[:, ['unit', 'region']], 'inner', on='unit')
    # The variable specific contribution to these constraints is always zero.
    constraint_map['coefficient'] = 1.0

    constraint_map = constraint_map.loc[:,  ['variable_id', 'unit', 'region', 'service', 'coefficient']]
    decision_variables = \
        decision_variables.loc[:, ['unit', 'capacity_band', 'service', 'variable_id', 'lower_bound', 'upper_bound',
                                   'type']]

    return decision_variables, constraint_map
Example #11
0
def joint_ramping_constraints(regulation_units, unit_limits, dispatch_interval,
                              next_constraint_id):
    """Create constraints that ensure the provision of energy and fcas are within unit ramping capabilities.

    The constraints are described in the
    :download:`FCAS MODEL IN NEMDE documentation section 6.1  <../../docs/pdfs/FCAS Model in NEMDE.pdf>`.

    On a unit basis they take the form of:

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

    and

        Energy dispatch + Regulation lower target <= initial output - ramp down rate / (dispatch interval / 60)

    Examples
    --------

    >>> import pandas as pd

    >>> regulation_units = pd.DataFrame({
    ...   'unit': ['A', 'B', 'B'],
    ...   'service': ['raise_reg', 'lower_reg', 'raise_reg']})

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

    >>> dispatch_interval = 60

    >>> next_constraint_id = 1

    >>> type_and_rhs, variable_mapping = joint_ramping_constraints(regulation_units, unit_limits, dispatch_interval,
    ...                                                            next_constraint_id)

    >>> print(type_and_rhs)
      unit  constraint_id type    rhs
    0    A              1   <=  120.0
    1    B              2   >=   55.0
    2    B              3   <=   90.0

    >>> print(variable_mapping)
       constraint_id unit    service  coefficient
    0              1    A  raise_reg          1.0
    1              2    B  lower_reg          1.0
    2              3    B  raise_reg          1.0
    0              1    A     energy          1.0
    1              2    B     energy          1.0
    2              3    B     energy          1.0

    Parameters
    ----------
    regulation_units : pd.DataFrame
        The units with bids submitted to provide regulation FCAS

        ========  =======================================================================
        Columns:  Description:
        unit      unique identifier of a dispatch unit (as `str`)
        service   the regulation service being bid for raise_reg or lower_reg  (as `str`)
        ========  =======================================================================

    unit_limits : pd.DataFrame
        The initial output and ramp rates of units
        ==============  =====================================================================================
        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`)
        ramp_down_rate  the maximum rate at which the unit can decrease output, in MW/h (as `np.float64`)
        ==============  =====================================================================================

    dispatch_interval : int
        The length of the dispatch interval in minutes

    next_constraint_id : int
        The next integer to start using for constraint ids

    Returns
    -------
    type_and_rhs : pd.DataFrame
        The type and rhs of each constraint.

        =============  ====================================================================
        Columns:       Description:
        unit           unique identifier of a dispatch unit (as `str`)
        service        the regulation service the constraint is associated with (as `str`)
        constraint_id  the id of the variable (as `int`)
        type           the type of the constraint, e.g. "=" (as `str`)
        rhs            the rhs of the constraint (as `np.float64`)
        =============  ====================================================================

    variable_map : pd.DataFrame
        The type of variables that should appear on the lhs of the constraint.

        =============  ==========================================================================
        Columns:       Description:
        constraint_id  the id of the constraint (as `np.int64`)
        unit           the unit variables the constraint should map too (as `str`)
        service        the service type of the variables the constraint should map to (as `str`)
        coefficient    the upper bound of the variable, the volume bid (as `np.float64`)
        =============  ==========================================================================
    """

    # Create a constraint for each regulation service being offered by a unit.
    constraints = hf.save_index(regulation_units, 'constraint_id',
                                next_constraint_id)
    # Map the unit limit information to the constraints so the rhs values can be calculated.
    constraints = pd.merge(constraints, unit_limits, 'left', on='unit')
    constraints['rhs'] = np.where(
        constraints['service'] == 'raise_reg', constraints['initial_output'] +
        constraints['ramp_up_rate'] / (dispatch_interval / 60),
        constraints['initial_output'] - constraints['ramp_down_rate'] /
        (dispatch_interval / 60))
    # Set the inequality type based on the regulation service being provided.
    constraints['type'] = np.where(constraints['service'] == 'raise_reg', '<=',
                                   '>=')
    rhs_and_type = constraints.loc[:, ['unit', 'constraint_id', 'type', 'rhs']]

    # Map each constraint to it corresponding unit and regulation service.
    variable_mapping_reg = constraints.loc[:, [
        'constraint_id', 'unit', 'service'
    ]]
    # Also map to the energy service being provided by the unit.
    variable_mapping_energy = constraints.loc[:, [
        'constraint_id', 'unit', 'service'
    ]]
    variable_mapping_energy['service'] = 'energy'
    # Combine mappings.
    variable_mapping = pd.concat(
        [variable_mapping_reg, variable_mapping_energy])
    variable_mapping['coefficient'] = 1.0

    return rhs_and_type, variable_mapping
Example #12
0
def energy_and_regulation_capacity_constraints(regulation_trapeziums,
                                               next_constraint_id):
    """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 contingency
    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
    :download:`FCAS MODEL IN NEMDE documentation section 6.3  <../../docs/pdfs/FCAS Model in NEMDE.pdf>`.

    Examples
    --------
    >>> import pandas as pd

    >>> 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]})

    >>> next_constraint_id = 1

    >>> type_and_rhs, variable_mapping = energy_and_regulation_capacity_constraints(regulation_trapeziums,
    ...                                                                             next_constraint_id)

    >>> print(type_and_rhs)
      unit    service  constraint_id type   rhs
    0    A  raise_reg              1   <=  80.0
    0    A  raise_reg              2   >=  20.0

    >>> print(variable_mapping)
       constraint_id unit    service  coefficient
    0              1    A     energy     1.000000
    0              1    A  raise_reg     0.333333
    0              2    A     energy     1.000000
    0              2    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
                       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 begin
                       the contingency service, in MW (as `np.float64`)
    ================   ======================================================================


    next_constraint_id : int
        The next integer to start using for constraint ids

    Returns
    -------
    type_and_rhs : pd.DataFrame
        The type and rhs of each constraint.

        =============  ====================================================================
        Columns:       Description:
        unit           unique identifier of a dispatch unit (as `str`)
        service        the regulation service the constraint is associated with (as `str`)
        constraint_id  the id of the variable (as `int`)
        type           the type of the constraint, e.g. "=" (as `str`)
        rhs            the rhs of the constraint (as `np.float64`)
        =============  ====================================================================

    variable_map : pd.DataFrame
        The type of variables that should appear on the lhs of the constraint.

        =============  ==========================================================================
        Columns:       Description:
        constraint_id  the id of the constraint (as `np.int64`)
        unit           the unit variables the constraint should map too (as `str`)
        service        the service type of the variables the constraint should map to (as `str`)
        coefficient    the upper bound of the variable, the volume bid (as `np.float64`)
        =============  ==========================================================================

    """

    # Create each constraint set.
    constraints_upper_slope = hf.save_index(regulation_trapeziums,
                                            'constraint_id',
                                            next_constraint_id)
    next_constraint_id = max(constraints_upper_slope['constraint_id']) + 1
    constraints_lower_slope = hf.save_index(regulation_trapeziums,
                                            'constraint_id',
                                            next_constraint_id)

    # Calculate the slope coefficients for the constraints.
    constraints_upper_slope['upper_slope_coefficient'] = (
        (constraints_upper_slope['enablement_max'] -
         constraints_upper_slope['high_break_point']) /
        constraints_upper_slope['max_availability'])
    constraints_lower_slope['lower_slope_coefficient'] = (
        (constraints_lower_slope['low_break_point'] -
         constraints_lower_slope['enablement_min']) /
        constraints_lower_slope['max_availability'])

    # Define the direction of the upper slope constraints and the rhs value.
    constraints_upper_slope['type'] = '<='
    constraints_upper_slope['rhs'] = constraints_upper_slope['enablement_max']
    type_and_rhs_upper_slope = constraints_upper_slope.loc[:, [
        'unit', 'service', 'constraint_id', 'type', 'rhs'
    ]]

    # Define the direction of the lower slope constraints and the rhs value.
    constraints_lower_slope['type'] = '>='
    constraints_lower_slope['rhs'] = constraints_lower_slope['enablement_min']
    type_and_rhs_lower_slope = constraints_lower_slope.loc[:, [
        'unit', 'service', 'constraint_id', 'type', 'rhs'
    ]]

    # Define the variables on the lhs of the upper slope constraints and their coefficients.
    energy_mapping_upper_slope = constraints_upper_slope.loc[:, [
        'constraint_id', 'unit'
    ]]
    energy_mapping_upper_slope['service'] = 'energy'
    energy_mapping_upper_slope['coefficient'] = 1.0
    regulation_mapping_upper_slope = constraints_upper_slope.loc[:, [
        'constraint_id', 'unit', 'service', 'upper_slope_coefficient'
    ]]
    regulation_mapping_upper_slope = \
        regulation_mapping_upper_slope.rename(columns={"upper_slope_coefficient": "coefficient"})

    # Define the variables on the lhs of the lower slope constraints and their coefficients.
    energy_mapping_lower_slope = constraints_lower_slope.loc[:, [
        'constraint_id', 'unit'
    ]]
    energy_mapping_lower_slope['service'] = 'energy'
    energy_mapping_lower_slope['coefficient'] = 1.0
    regulation_mapping_lower_slope = constraints_lower_slope.loc[:, [
        'constraint_id', 'unit', 'service', 'lower_slope_coefficient'
    ]]
    regulation_mapping_lower_slope = \
        regulation_mapping_lower_slope.rename(columns={"lower_slope_coefficient": "coefficient"})
    regulation_mapping_lower_slope[
        'coefficient'] = -1 * regulation_mapping_lower_slope['coefficient']

    # Combine type_and_rhs and variable_mapping.
    type_and_rhs = pd.concat(
        [type_and_rhs_upper_slope, type_and_rhs_lower_slope])
    variable_mapping = pd.concat([
        energy_mapping_upper_slope, regulation_mapping_upper_slope,
        energy_mapping_lower_slope, regulation_mapping_lower_slope
    ])
    return type_and_rhs, variable_mapping