Esempio n. 1
0
def ramp_down_rule(mod, g, tmp):
    """
    **Constraint Name**: GenAlwaysOn_Ramp_Down_Constraint
    **Enforced Over**: GEN_ALWAYS_ON_OPR_TMPS

    Difference between power generation of consecutive timepoints, adjusted
    for reserve provision in current and previous timepoint, has to obey
    ramp down rate limits.

    We assume that a unit has to reach its setpoint at the start of the
    timepoint; as such, the ramping between 2 timepoints is assumed to
    take place during the duration of the first timepoint, and the
    ramp rate limit is adjusted for the duration of the first timepoint.
    """
    if check_if_boundary_type_and_first_timepoint(
        mod=mod,
        tmp=tmp,
        balancing_type=mod.balancing_type_project[g],
        boundary_type="linear",
    ):
        return Constraint.Skip
    else:
        if check_if_boundary_type_and_first_timepoint(
            mod=mod,
            tmp=tmp,
            balancing_type=mod.balancing_type_project[g],
            boundary_type="linked",
        ):
            prev_tmp_hrs_in_tmp = mod.hrs_in_linked_tmp[0]
            prev_tmp_power = mod.gen_always_on_linked_power[g, 0]
            prev_tmp_upwards_reserves = mod.gen_always_on_linked_upwards_reserves[g, 0]
        else:
            prev_tmp_hrs_in_tmp = mod.hrs_in_tmp[
                mod.prev_tmp[tmp, mod.balancing_type_project[g]]
            ]
            prev_tmp_power = mod.GenAlwaysOn_Gross_Power_MW[
                g, mod.prev_tmp[tmp, mod.balancing_type_project[g]]
            ]
            prev_tmp_upwards_reserves = mod.GenAlwaysOn_Upwards_Reserves_MW[
                g, mod.prev_tmp[tmp, mod.balancing_type_project[g]]
            ]

        # If ramp rate limits, adjusted for timepoint duration, allow you to
        # ramp down the full operable range between timepoints, constraint
        # won't bind, so skip
        if mod.gen_always_on_ramp_down_when_on_rate[g] * 60 * prev_tmp_hrs_in_tmp >= (
            1 - mod.gen_always_on_min_stable_level_fraction[g]
        ):
            return Constraint.Skip
        else:
            return (
                mod.GenAlwaysOn_Gross_Power_MW[g, tmp]
                - mod.GenAlwaysOn_Downwards_Reserves_MW[g, tmp]
                - (prev_tmp_power + prev_tmp_upwards_reserves)
                >= -mod.gen_always_on_ramp_down_when_on_rate[g]
                * 60
                * prev_tmp_hrs_in_tmp
                * mod.Capacity_MW[g, mod.period[tmp]]
                * mod.Availability_Derate[g, tmp]
            )
Esempio n. 2
0
def ramp_down_rule(mod, g, tmp):
    """
    **Constraint Name**: GenHydro_Ramp_Down_Constraint
    **Enforced Over**: GEN_HYDRO_OPR_TMPS

    Difference between power generation of consecutive timepoints, adjusted
    for reserve provision in current and previous timepoint, has to obey
    ramp down rate limits.

    We assume that a unit has to reach its setpoint at the start of the
    timepoint; as such, the ramping between 2 timepoints is assumed to
    take place during the duration of the first timepoint, and the
    ramp rate limit is adjusted for the duration of the first timepoint.
    """
    if check_if_boundary_type_and_first_timepoint(
            mod=mod,
            tmp=tmp,
            balancing_type=mod.balancing_type_project[g],
            boundary_type="linear"):
        return Constraint.Skip
    else:
        if check_if_boundary_type_and_first_timepoint(
                mod=mod,
                tmp=tmp,
                balancing_type=mod.balancing_type_project[g],
                boundary_type="linked"):
            prev_tmp_hrs_in_tmp = mod.hrs_in_linked_tmp[0]
            prev_tmp_power = \
                mod.gen_hydro_linked_power[g, 0]
            prev_tmp_curtailment = \
                mod.gen_hydro_linked_curtailment[g, 0]
            prev_tmp_upwards_reserves = \
                mod.gen_hydro_linked_upwards_reserves[g, 0]
        else:
            prev_tmp_hrs_in_tmp = mod.hrs_in_tmp[mod.prev_tmp[
                tmp, mod.balancing_type_project[g]]]
            prev_tmp_power = \
                mod.GenHydro_Gross_Power_MW[
                    g, mod.prev_tmp[tmp, mod.balancing_type_project[g]]
                ]
            prev_tmp_upwards_reserves = \
                mod.GenHydro_Upwards_Reserves_MW[
                    g, mod.prev_tmp[tmp, mod.balancing_type_project[g]]
                ]
        # If you can ramp down the the total project's capacity within the
        # previous timepoint, skip the constraint (it won't bind)
        if mod.gen_hydro_ramp_down_when_on_rate[g] * 60 \
            * prev_tmp_hrs_in_tmp \
                >= 1:
            return Constraint.Skip
        else:
            return (mod.GenHydro_Gross_Power_MW[g, tmp]
                    - mod.GenHydro_Downwards_Reserves_MW[g, tmp]) \
                - (prev_tmp_power + prev_tmp_curtailment
                   + prev_tmp_upwards_reserves) \
                >= \
                - mod.gen_hydro_ramp_down_when_on_rate[g] * 60 \
                * prev_tmp_hrs_in_tmp \
                * mod.Capacity_MW[g, mod.period[tmp]] \
                * mod.Availability_Derate[g, tmp]
Esempio n. 3
0
def ramp_up_rule(mod, g, tmp):
    """
    **Constraint Name**: GenSimple_Ramp_Up_Constraint
    **Enforced Over**: GEN_SIMPLE_OPR_TMPS

    Difference between power generation of consecutive timepoints, adjusted
    for reserve provision in current and previous timepoint, has to obey
    ramp up rate limits.

    We assume that a unit has to reach its setpoint at the start of the
    timepoint; as such, the ramping between 2 timepoints is assumed to
    take place during the duration of the first timepoint, and the
    ramp rate limit is adjusted for the duration of the first timepoint.
    """
    if check_if_boundary_type_and_first_timepoint(
            mod=mod,
            tmp=tmp,
            balancing_type=mod.balancing_type_project[g],
            boundary_type="linear"):
        return Constraint.Skip
    else:
        if check_if_boundary_type_and_first_timepoint(
                mod=mod,
                tmp=tmp,
                balancing_type=mod.balancing_type_project[g],
                boundary_type="linked"):
            prev_tmp_hrs_in_tmp = mod.hrs_in_linked_tmp[0]
            prev_tmp_power = \
                mod.gen_simple_linked_power[g, 0]
            prev_tmp_downwards_reserves = \
                mod.gen_simple_linked_downwards_reserves[g, 0]
        else:
            prev_tmp_hrs_in_tmp = mod.hrs_in_tmp[mod.prev_tmp[
                tmp, mod.balancing_type_project[g]]]
            prev_tmp_power = \
                mod.GenSimple_Provide_Power_MW[
                    g, mod.prev_tmp[tmp, mod.balancing_type_project[g]]
                ]
            prev_tmp_downwards_reserves = \
                mod.GenSimple_Downwards_Reserves_MW[
                    g, mod.prev_tmp[tmp, mod.balancing_type_project[g]]
                ]
        # If ramp rate limits, adjusted for timepoint duration, allow you to
        # ramp up the full operable range between timepoints, constraint won't
        # bind, so skip
        if (mod.gen_simple_ramp_up_when_on_rate[g] * 60 * prev_tmp_hrs_in_tmp
                >= 1):
            return Constraint.Skip
        else:
            return mod.GenSimple_Provide_Power_MW[g, tmp] \
                + mod.GenSimple_Upwards_Reserves_MW[g, tmp] \
                - (prev_tmp_power - prev_tmp_downwards_reserves) \
                <= \
                mod.gen_simple_ramp_up_when_on_rate[g] * 60 \
                * prev_tmp_hrs_in_tmp \
                * mod.Capacity_MW[g, mod.period[tmp]] \
                * mod.Availability_Derate[g, tmp]
Esempio n. 4
0
def unavailability_start_and_stop_rule(mod, g, tmp):
    """
    **Constraint Name**: AvlBin_Unavl_Start_and_Stop_Constraint
    **Enforced Over**: AVL_BIN_OPR_TMPS

    Constrain the start and stop availability variables based on the
    availability status in the current and previous timepoint. If the
    project is down in the current timepoint and was not down in the
    previous timepoint, then the RHS is 1 and AvlBin_Start_Unavailability
    must be set to 1. If the project is not down in the current
    timepoint and was down in the previous timepoint, then the RHS is -1
    and AvlBin_Stop_Unavailability must be set to 1.
    """
    if check_if_boundary_type_and_first_timepoint(
            mod=mod,
            tmp=tmp,
            balancing_type=mod.balancing_type_project[g],
            boundary_type="linear",
    ):
        return Constraint.Skip
    else:
        return (mod.AvlBin_Start_Unavailability[g, tmp] -
                mod.AvlBin_Stop_Unavailability[g, tmp] ==
                mod.AvlBin_Unavailable[g, tmp] - mod.AvlBin_Unavailable[
                    g, mod.prev_tmp[tmp, mod.balancing_type_project[g]]])
Esempio n. 5
0
def ramp_up_rule(mod, g, tmp):
    """
    **Constraint Name**: Ramp_Up_Tuning_Cost_Constraint
    **Enforced Over**: PRJ_OPR_TMPS
    """
    gen_op_type = mod.operational_type[g]
    tuning_cost = (mod.ramp_tuning_cost_per_mw if gen_op_type
                   in ["gen_hydro", "gen_hydro_must_take", "stor"] else 0)
    if check_if_boundary_type_and_first_timepoint(
            mod=mod,
            tmp=tmp,
            balancing_type=mod.balancing_type_project[g],
            boundary_type="linear",
    ):
        return Constraint.Skip
    elif tuning_cost == 0:
        return Constraint.Skip
    else:
        return (mod.Ramp_Up_Tuning_Cost[g, tmp] >=
                mod.Ramp_Expression[g, tmp] * tuning_cost)
Esempio n. 6
0
def ramp_down_rule(mod, g, tmp):
    """
    **Constraint Name**: Ramp_Down_Tuning_Cost_Constraint
    **Enforced Over**: PRJ_OPR_TMPS
    """
    gen_op_type = mod.operational_type[g]
    # TODO: is storage missing on purpose?
    tuning_cost = mod.ramp_tuning_cost_per_mw \
        if gen_op_type in ["gen_hydro", "gen_hydro_must_take"] \
        else 0
    if check_if_boundary_type_and_first_timepoint(
            mod=mod,
            tmp=tmp,
            balancing_type=mod.balancing_type_project[g],
            boundary_type="linear"):
        return Constraint.Skip
    elif tuning_cost == 0:
        return Constraint.Skip
    else:
        return mod.Ramp_Down_Tuning_Cost[g, tmp] \
            >= mod.Ramp_Expression[g, tmp] \
            * - tuning_cost
Esempio n. 7
0
def determine_relevant_timepoints(mod, g, tmp, min_time):
    """
    :param mod:
    :param g:
    :param tmp:
    :param min_time:
    :return: the relevant timepoints to look at for the minimum up/down time
        constraints

    We need to figure out how far back we need to look,
    i.e. which timepoints we need to consider when capacity could have
    been turned on/off that must still be on/off in the current timepoint *t*.

    Any capacity that was turned on/off between t and t-1 must still be
    on/off in t, so timepoint *t* is a relevant timepoint.

    Capacity must also still be on/off if it was turned on/off less than its
    up/down time ago, i.e. if it was turned on/off between timepoints that
    are within the min up/down time from the beginning of the current
    timepoint. Once we reach a timepoint whose duration takes us to a
    point in time that is removed from the beginning of timepoint *t* by a
    time greater than or equal to the min_time, that timepoint is not
    relevant anymore and we have completed our list of relevant timepoints.

    In a simple case, let's assume all timepoints have durations of 1 hour.
    Timepoint t-1 is removed from the start of timepoint *t* by an hour,
    timepoint t-2 by 2 hours, timepoint t-3 by 3 hours, etc. Therefore, if
    if a generator has a 4-hour minimum up time and was started up in t-3 (
    i.e. between t-4 and t-3), then it must still be on in the current
    timepoint. If it was started up in t-4, it has already been up for 4
    hours by the time timepoint *t* begins, so it can be turned off. The
    relevant timepoints are therefore t-1, t-2, and t-3; we do not need to
    constrain capacity turned on/off in t-4 or farther in the past.

    If t-2 has duration of 2-hours, on the other hand, the total duration of
    the previous three timepoints would be 4 hours and the generator turned
    on in t-3 should therefore be allowed to turn off in the current
    timepoint *t*. In this case, the relevant timepoints would be t-1 and
    t-2. By the time we reach t-3, we will have reached the 4-hour minimum
    up/down time, so t-3 will not be relevant for the minimum up time
    constraint in timepoint *t*.
    """

    # The first possible relevant timepoint is the current timepoint
    relevant_tmps = [tmp]
    relevant_linked_tmps = []
    # The first possible linked timepoint is 0
    linked_tmp = 0

    # If we have already reached the first timepoint of a horizon in a
    # linear boundary type we'll just pass, as there are no more relevant
    # timepoints to add
    if check_if_boundary_type_and_first_timepoint(
            mod=mod,
            tmp=tmp,
            balancing_type=mod.balancing_type_project[g],
            boundary_type="linear"):
        pass  # no more relevant timepoints, keep list limited to *t*
    # If we have already reached the first timepoint in a linked horizon
    # setting, we'll immediately move on to the linked timepoints without
    # looking for a previous timepoint
    elif check_if_boundary_type_and_first_timepoint(
            mod=mod,
            tmp=tmp,
            balancing_type=mod.balancing_type_project[g],
            boundary_type="linked"):
        # Add the first linked timepoint's duration to hours_from_tmp
        hours_from_tmp = mod.hrs_in_linked_tmp[linked_tmp]
        # If we haven't exceeded the min time yet, the first linked
        # timepoint is relevant, so we'll add it and move on to the
        # next one
        while hours_from_tmp < min_time:
            relevant_linked_tmps.append(linked_tmp)
            # If this is the furthest linked timepoint, break out of
            # the linked timepoints loop; otherwise, move on to the next
            # linked timepoint
            if linked_tmp == mod.furthest_linked_tmp:
                break
            else:
                hours_from_tmp += mod.hrs_in_linked_tmp[linked_tmp]
                linked_tmp += -1
    # If we haven't reached the first timepoint of a linear or linked
    # horizon, we'll look for the previous timepoint
    else:
        # The next possible relevant timepoint is the previous timepoint,
        # so we'll check its duration (if it's longer than or equal to the
        # minimum up/down time, we'll break out of the loop immediately)
        relevant_tmp = mod.prev_tmp[tmp, mod.balancing_type_project[g]]
        hours_from_tmp = \
            mod.hrs_in_tmp[
                mod.prev_tmp[tmp, mod.balancing_type_project[g]]]

        while hours_from_tmp < min_time:
            # If we haven't exceed the minimum up/down time yet, this timepoint
            # is relevant and we add it to our list
            relevant_tmps.append(relevant_tmp)

            # In a 'linear' horizon setting, once we reach the first
            # timepoint of the horizon, we break out of the loop since there
            # are no more timepoints to consider
            if check_if_boundary_type_and_first_timepoint(
                    mod=mod,
                    tmp=relevant_tmp,
                    balancing_type=mod.balancing_type_project[g],
                    boundary_type="linear"):
                break
            # In a 'circular' horizon setting, once we reach timepoint *t*,
            # we break out of the loop since there are no more timepoints to
            # consider (we have already added all horizon timepoints as
            # relevant)
            elif check_boundary_type(
                    mod=mod,
                    tmp=tmp,
                    balancing_type=mod.balancing_type_project[g],
                    boundary_type="circular") and relevant_tmp == tmp:
                break
            # TODO: only allow the first horizon of a subproblem to have
            #  linked timepoints
            # In a 'linked' horizon setting, once we reach the first
            # timepoint of the horizon, we'll start adding the linked
            # timepoints until we reach the target min time
            elif check_if_boundary_type_and_first_timepoint(
                    mod=mod,
                    tmp=relevant_tmp,
                    balancing_type=mod.balancing_type_project[g],
                    boundary_type="linked"):
                # Add the first linked timepoint's duration to hours_from_tmp
                hours_from_tmp += mod.hrs_in_linked_tmp[linked_tmp]
                # If we haven't exceeded the min time yet, the first linked
                # timepoint is relevant, so we'll add it and move on to the
                # next one
                while hours_from_tmp < min_time:
                    relevant_linked_tmps.append(linked_tmp)
                    # If this is the furthest linked timepoint, break out of
                    # the linked timepoints loop; otherwise, move on to the
                    # next linked timepoint
                    if linked_tmp == mod.furthest_linked_tmp:
                        break
                    else:
                        hours_from_tmp += mod.hrs_in_linked_tmp[linked_tmp]
                        linked_tmp += -1
                # Break out from the outer while loop when done with the
                # linked timepoints
                break
            # Otherwise, we move on to the relevant timepoint's previous
            # timepoint and will add that timepoint's duration to
            # hours_from_tmp
            else:
                hours_from_tmp += \
                    mod.hrs_in_tmp[
                        mod.prev_tmp[
                            relevant_tmp, mod.balancing_type_project[g]
                        ]
                    ]
                relevant_tmp = mod.prev_tmp[relevant_tmp,
                                            mod.balancing_type_project[g]]

    return relevant_tmps, relevant_linked_tmps