Beispiel #1
0
def carrier_production_min_conversion_plus_milp_constraint_rule(
        backend_model, loc_tech, timestep):
    """
    Set minimum carrier production of conversion_plus MILP techs

    .. container:: scrolling-wrapper

        .. math::
            \sum_{loc::tech::carrier \\in loc::tech::carriers_{out}}
            \\boldsymbol{carrier_{prod}}(loc::tech::carrier, timestep)
            \\geq energy_{cap, per unit}(loc::tech) \\times timestep\_resolution(timestep)
            \\times \\boldsymbol{operating\_units}(loc::tech, timestep)
            \\times energy_{cap, min use}(loc::tech)
            \\quad \\forall loc::tech \\in loc::techs_{milp, conversion^{+}},
            \\forall timestep \\in timesteps

    """
    model_data_dict = backend_model.__calliope_model_data__['data']
    timestep_resolution = backend_model.timestep_resolution[timestep]
    energy_cap = get_param(backend_model, 'energy_cap_per_unit', loc_tech)
    min_use = get_param(backend_model, 'energy_cap_min_use',
                        (loc_tech, timestep))
    loc_tech_carriers_out = (split_comma_list(
        model_data_dict['lookup_loc_techs_conversion_plus']['out', loc_tech]))

    carrier_prod = sum(backend_model.carrier_prod[loc_tech_carrier, timestep]
                       for loc_tech_carrier in loc_tech_carriers_out)

    return carrier_prod >= (backend_model.operating_units[loc_tech, timestep] *
                            timestep_resolution * energy_cap * min_use)
Beispiel #2
0
def storage_capacity_max_purchase_constraint_rule(backend_model, loc_tech):
    """
    Set maximum storage capacity.

    The first valid case is applied:

    .. container:: scrolling-wrapper

        .. math::

            \\boldsymbol{storage_{cap}}(loc::tech)
            \\begin{cases}
                = storage_{cap, equals}(loc::tech) \\times \\boldsymbol{purchased},&
                    \\text{if } storage_{cap, equals} \\\\
                \\leq storage_{cap, max}(loc::tech) \\times \\boldsymbol{purchased},&
                    \\text{if } storage_{cap, max}(loc::tech)\\\\
                \\text{unconstrained},& \\text{otherwise}
            \\end{cases}
            \\forall loc::tech \\in loc::techs_{purchase, store}

    """
    storage_cap_max = get_param(backend_model, 'storage_cap_max', loc_tech)
    storage_cap_equals = get_param(backend_model, 'storage_cap_equals',
                                   loc_tech)

    if po.value(storage_cap_equals):
        return backend_model.storage_cap[loc_tech] == (
            storage_cap_equals * backend_model.purchased[loc_tech])

    elif po.value(storage_cap_max):
        return backend_model.storage_cap[loc_tech] <= (
            storage_cap_max * backend_model.purchased[loc_tech])

    else:
        return po.Constraint.Skip
Beispiel #3
0
def energy_capacity_systemwide_constraint_rule(backend_model, tech):
    """
    Set constraints to limit the capacity of a single technology type across all locations in the model.

    The first valid case is applied:

    .. container:: scrolling-wrapper

        .. math::

            \\sum_{loc}\\boldsymbol{energy_{cap}}(loc::tech)
            \\begin{cases}
                = energy_{cap, equals, systemwide}(loc::tech),&
                    \\text{if } energy_{cap, equals, systemwide}(loc::tech)\\\\
                \\leq energy_{cap, max, systemwide}(loc::tech),&
                    \\text{if } energy_{cap, max, systemwide}(loc::tech)\\\\
                \\text{unconstrained},& \\text{otherwise}
            \\end{cases}
            \\forall tech \\in techs

    """

    max_systemwide = get_param(backend_model, "energy_cap_max_systemwide",
                               tech)
    equals_systemwide = get_param(backend_model,
                                  "energy_cap_equals_systemwide", tech)
    energy_cap = po.quicksum(
        backend_model.energy_cap[node, tech] for node in backend_model.nodes
        if [node, tech] in backend_model.energy_cap._index)
    if not invalid(equals_systemwide):
        return energy_cap == equals_systemwide
    else:
        return energy_cap <= max_systemwide
Beispiel #4
0
def carrier_production_max_milp_constraint_rule(backend_model, loc_tech_carrier, timestep):
    """
    Set maximum carrier production of MILP techs that aren't conversion plus

    .. container:: scrolling-wrapper

        .. math::

            \\boldsymbol{carrier_{prod}}(loc::tech::carrier, timestep)
            \\leq energy_{cap, per unit}(loc::tech) \\times timestep\\_resolution(timestep)
            \\times \\boldsymbol{operating\\_units}(loc::tech, timestep)
            \\times \\eta_{parasitic}(loc::tech, timestep)
            \\quad \\forall loc::tech \\in loc::techs_{milp}, \\forall timestep \\in timesteps

    :math:`\\eta_{parasitic}` is only activated for `supply_plus` technologies
    """
    loc_tech = get_loc_tech(loc_tech_carrier)

    carrier_prod = backend_model.carrier_prod[loc_tech_carrier, timestep]
    timestep_resolution = backend_model.timestep_resolution[timestep]
    parasitic_eff = get_param(backend_model, 'parasitic_eff', (loc_tech, timestep))
    energy_cap = get_param(backend_model, 'energy_cap_per_unit', loc_tech)

    return carrier_prod <= (
        backend_model.operating_units[loc_tech, timestep] *
        timestep_resolution * energy_cap * parasitic_eff
    )
Beispiel #5
0
def get_capacity_constraint(
    backend_model, parameter, loc_tech, _equals=None, _max=None, _min=None, scale=None
):

    decision_variable = getattr(backend_model, parameter)

    if not _equals:
        _equals = get_param(backend_model, parameter + "_equals", loc_tech)
    if not _max:
        _max = get_param(backend_model, parameter + "_max", loc_tech)
    if not _min:
        _min = get_param(backend_model, parameter + "_min", loc_tech)
    if po.value(_equals) is not False and po.value(_equals) is not None:
        if np.isinf(po.value(_equals)):
            e = exceptions.ModelError
            raise e(
                "Cannot use inf for {}_equals for loc:tech `{}`".format(
                    parameter, loc_tech
                )
            )
        if scale:
            _equals *= scale
        return decision_variable[loc_tech] == _equals
    else:
        if po.value(_min) == 0 and np.isinf(po.value(_max)):
            return po.Constraint.NoConstraint
        else:
            if scale:
                _max *= scale
                _min *= scale
            return (_min, decision_variable[loc_tech], _max)
def balance_storage_constraint_rule(backend_model, carrier, node, tech,
                                    timestep):
    """
    Balance carrier production and consumption of storage technologies,
    alongside any use of the stored volume.

    .. container:: scrolling-wrapper

        .. math::

            \\boldsymbol{storage}(loc::tech, timestep) =
            \\boldsymbol{storage}(loc::tech, timestep_{previous})
            \\times (1 - storage\\_loss(loc::tech, timestep))^{resolution(timestep)}
            - \\boldsymbol{carrier_{con}}(loc::tech::carrier, timestep)
            \\times \\eta_{energy}(loc::tech, timestep)
            - \\frac{\\boldsymbol{carrier_{prod}}(loc::tech::carrier, timestep)}{\\eta_{energy}(loc::tech, timestep)}
            \\quad \\forall loc::tech \\in loc::techs_{storage}, \\forall timestep \\in timesteps
    """
    run_config = backend_model.__calliope_run_config

    energy_eff = get_param(backend_model, "energy_eff", (node, tech, timestep))

    if po.value(energy_eff) == 0:
        carrier_prod = 0
    else:
        carrier_prod = (
            backend_model.carrier_prod[carrier, node, tech, timestep] /
            energy_eff)

    carrier_con = backend_model.carrier_con[carrier, node, tech,
                                            timestep] * energy_eff

    # Pyomo returns the order 1-indexed, but we want 0-indexing
    current_timestep = backend_model.timesteps.ord(timestep) - 1
    if current_timestep == 0 and not run_config["cyclic_storage"]:
        storage_previous_step = (get_param(backend_model, "storage_initial",
                                           (node, tech)) *
                                 backend_model.storage_cap[node, tech])
    elif (hasattr(backend_model, "storage_inter_cluster")
          and backend_model.lookup_cluster_first_timestep[timestep]):
        storage_previous_step = 0
    else:
        if (hasattr(backend_model, "clusters")
                and backend_model.lookup_cluster_first_timestep[timestep]):
            previous_step = backend_model.lookup_cluster_last_timestep[
                timestep].value
        elif current_timestep == 0 and run_config["cyclic_storage"]:
            previous_step = backend_model.timesteps[-1]
        else:
            previous_step = get_previous_timestep(backend_model.timesteps,
                                                  timestep)
        storage_loss = get_param(backend_model, "storage_loss", (node, tech))
        time_resolution = backend_model.timestep_resolution[previous_step]
        storage_previous_step = (
            (1 - storage_loss)**
            time_resolution) * backend_model.storage[node, tech, previous_step]

    return (backend_model.storage[node, tech,
                                  timestep] == storage_previous_step -
            carrier_prod - carrier_con)
Beispiel #7
0
def energy_capacity_max_purchase_milp_constraint_rule(backend_model, loc_tech):
    """
    Set maximum energy capacity decision variable upper bound as a function of
    binary purchase variable

    The first valid case is applied:

    .. container:: scrolling-wrapper

        .. math::

            \\frac{\\boldsymbol{energy_{cap}}(loc::tech)}{energy_{cap, scale}(loc::tech)}
            \\begin{cases}
                = energy_{cap, equals}(loc::tech) \\times \\boldsymbol{purchased}(loc::tech),&
                    \\text{if } energy_{cap, equals}(loc::tech)\\\\
                \\leq energy_{cap, max}(loc::tech) \\times \\boldsymbol{purchased}(loc::tech),&
                    \\text{if } energy_{cap, max}(loc::tech)\\\\
                \\text{unconstrained},& \\text{otherwise}
            \\end{cases}
            \\forall loc::tech \\in loc::techs_{purchase}

    """
    energy_cap_max = get_param(backend_model, "energy_cap_max", loc_tech)
    energy_cap_equals = get_param(backend_model, "energy_cap_equals", loc_tech)
    energy_cap_scale = get_param(backend_model, "energy_cap_scale", loc_tech)

    if po.value(energy_cap_equals):
        return backend_model.energy_cap[loc_tech] == (
            energy_cap_equals * energy_cap_scale * backend_model.purchased[loc_tech]
        )

    else:
        return backend_model.energy_cap[loc_tech] <= (
            energy_cap_max * energy_cap_scale * backend_model.purchased[loc_tech]
        )
Beispiel #8
0
def carrier_production_min_conversion_plus_milp_constraint_rule(
        backend_model, node, tech, timestep):
    """
    Set minimum carrier production of conversion_plus MILP techs

    .. container:: scrolling-wrapper

        .. math::
            \\sum_{loc::tech::carrier \\in loc::tech::carriers_{out}}
            \\boldsymbol{carrier_{prod}}(loc::tech::carrier, timestep)
            \\geq energy_{cap, per unit}(loc::tech) \\times timestep\\_resolution(timestep)
            \\times \\boldsymbol{operating\\_units}(loc::tech, timestep)
            \\times energy_{cap, min use}(loc::tech)
            \\quad \\forall loc::tech \\in loc::techs_{milp, conversion^{+}},
            \\forall timestep \\in timesteps

    """
    timestep_resolution = backend_model.timestep_resolution[timestep]
    energy_cap = get_param(backend_model, "energy_cap_per_unit", (node, tech))
    min_use = get_param(backend_model, "energy_cap_min_use",
                        (node, tech, timestep))
    carriers_out = backend_model.carrier["out", :, tech].index()

    carrier_prod = po.quicksum(backend_model.carrier_prod[idx[1], node, tech,
                                                          timestep]
                               for idx in carriers_out)

    return carrier_prod >= (backend_model.operating_units[node, tech, timestep]
                            * timestep_resolution * energy_cap * min_use)
Beispiel #9
0
def get_capacity_constraint(backend_model, parameter, loc_tech,
                            _equals=None, _max=None, _min=None, scale=None):

    decision_variable = getattr(backend_model, parameter)

    if not _equals:
        _equals = get_param(backend_model, parameter + '_equals', loc_tech)
    if not _max:
        _max = get_param(backend_model, parameter + '_max', loc_tech)
    if not _min:
        _min = get_param(backend_model, parameter + '_min', loc_tech)
    if po.value(_equals) is not False and po.value(_equals) is not None:
        if np.isinf(po.value(_equals)):
            e = exceptions.ModelError
            raise e('Cannot use inf for {}_equals for loc:tech `{}`'.format(parameter, loc_tech))
        if scale:
            _equals *= scale
        return decision_variable[loc_tech] == _equals
    else:
        if po.value(_min) == 0 and np.isinf(po.value(_max)):
            return po.Constraint.NoConstraint
        else:
            if scale:
                _max *= scale
                _min *= scale
            return (_min, decision_variable[loc_tech], _max)
Beispiel #10
0
def balance_transmission_constraint_rule(backend_model, loc_tech, timestep):
    """
    Balance carrier production and consumption of transmission technologies

    .. container:: scrolling-wrapper

        .. math::

            -1 * \\boldsymbol{carrier_{con}}(loc_{from}::tech:loc_{to}::carrier, timestep)
            \\times \\eta_{energy}(loc::tech, timestep)
            = \\boldsymbol{carrier_{prod}}(loc_{to}::tech:loc_{from}::carrier, timestep)
            \\quad \\forall loc::tech:loc \in locs::techs:locs_{transmission},
            \\forall timestep \in timesteps

    Where a link is the connection between :math:`loc_{from}::tech:loc_{to}`
    and :math:`loc_{to}::tech:loc_{from}` for locations `to` and `from`.

    """
    model_data_dict = backend_model.__calliope_model_data__['data']

    energy_eff = get_param(backend_model, 'energy_eff', (loc_tech, timestep))
    loc_tech_carrier = model_data_dict['lookup_loc_techs'][loc_tech]
    remote_loc_tech = model_data_dict['lookup_remotes'][loc_tech]
    remote_loc_tech_carrier = model_data_dict['lookup_loc_techs'][
        remote_loc_tech]

    if (loc_tech_is_in(backend_model, remote_loc_tech,
                       'loc_techs_transmission')
            and get_param(backend_model, 'energy_prod', (loc_tech)) == 1):
        return (backend_model.carrier_prod[loc_tech_carrier, timestep] == -1 *
                backend_model.carrier_con[remote_loc_tech_carrier, timestep] *
                energy_eff)
    else:
        return po.Constraint.NoConstraint
Beispiel #11
0
def carrier_production_min_milp_constraint_rule(
    backend_model, loc_tech_carrier, timestep
):
    """
    Set minimum carrier production of MILP techs that aren't conversion plus

    .. container:: scrolling-wrapper

        .. math::

            \\boldsymbol{carrier_{prod}}(loc::tech::carrier, timestep)
            \\geq energy_{cap, per unit}(loc::tech) \\times timestep\\_resolution(timestep)
            \\times \\boldsymbol{operating\\_units}(loc::tech, timestep)
            \\times energy_{cap, min use}(loc::tech)
            \\quad \\forall loc::tech \\in loc::techs_{milp}, \\forall timestep \\in timesteps
    """
    loc_tech = get_loc_tech(loc_tech_carrier)
    carrier_prod = backend_model.carrier_prod[loc_tech_carrier, timestep]
    timestep_resolution = backend_model.timestep_resolution[timestep]
    min_use = get_param(backend_model, "energy_cap_min_use", (loc_tech, timestep))
    energy_cap = get_param(backend_model, "energy_cap_per_unit", loc_tech)

    return carrier_prod >= (
        backend_model.operating_units[loc_tech, timestep]
        * timestep_resolution
        * energy_cap
        * min_use
    )
Beispiel #12
0
def get_capacity_constraint(backend_model, parameter, loc_tech,
                            _equals=None, _max=None, _min=None, scale=None):

    decision_variable = getattr(backend_model, parameter)

    if not _equals:
        _equals = get_param(backend_model, parameter + '_equals', loc_tech)
    if not _max:
        _max = get_param(backend_model, parameter + '_max', loc_tech)
    if not _min:
        _min = get_param(backend_model, parameter + '_min', loc_tech)
    if scale:
        _equals = scale * _equals
        _min = scale * _min
        _max = scale * _max
    if po.value(_equals) is not False and po.value(_equals) is not None:
        if np.isinf(po.value(_equals)):
            e = exceptions.ModelError
            raise e('Cannot use inf for {}_equals for loc:tech `{}`'.format(parameter, loc_tech))
        return decision_variable[loc_tech] == _equals
    else:
        if np.isinf(po.value(_max)):
            _max = None  # to disable upper bound
        if po.value(_min) == 0 and po.value(_max) is None:
            return po.Constraint.NoConstraint
        else:
            return (_min, decision_variable[loc_tech], _max)
Beispiel #13
0
def energy_capacity_max_purchase_constraint_rule(backend_model, loc_tech):
    """
    Set maximum energy capacity decision variable upper bound as a function of
    binary purchase variable

    The first valid case is applied:

    .. container:: scrolling-wrapper

        .. math::

            \\frac{\\boldsymbol{energy_{cap}}(loc::tech)}{energy_{cap, scale}(loc::tech)}
            \\begin{cases}
                = energy_{cap, equals}(loc::tech) \\times \\boldsymbol{purchased}(loc::tech),&
                    \\text{if } energy_{cap, equals}(loc::tech)\\\\
                \\leq energy_{cap, max}(loc::tech) \\times \\boldsymbol{purchased}(loc::tech),&
                    \\text{if } energy_{cap, max}(loc::tech)\\\\
                \\text{unconstrained},& \\text{otherwise}
            \\end{cases}
            \\forall loc::tech \\in loc::techs_{purchase}

    """
    energy_cap_max = get_param(backend_model, 'energy_cap_max', loc_tech)
    energy_cap_equals = get_param(backend_model, 'energy_cap_equals', loc_tech)
    energy_cap_scale = get_param(backend_model, 'energy_cap_scale', loc_tech)

    if po.value(energy_cap_equals):
        return backend_model.energy_cap[loc_tech] == (
            energy_cap_equals * energy_cap_scale * backend_model.purchased[loc_tech]
        )

    else:
        return backend_model.energy_cap[loc_tech] <= (
            energy_cap_max * energy_cap_scale * backend_model.purchased[loc_tech]
        )
Beispiel #14
0
def carrier_production_min_conversion_plus_milp_constraint_rule(backend_model, loc_tech, timestep):
    """
    Set minimum carrier production of conversion_plus MILP techs

    .. container:: scrolling-wrapper

        .. math::
            \\sum_{loc::tech::carrier \\in loc::tech::carriers_{out}}
            \\boldsymbol{carrier_{prod}}(loc::tech::carrier, timestep)
            \\geq energy_{cap, per unit}(loc::tech) \\times timestep\\_resolution(timestep)
            \\times \\boldsymbol{operating\\_units}(loc::tech, timestep)
            \\times energy_{cap, min use}(loc::tech)
            \\quad \\forall loc::tech \\in loc::techs_{milp, conversion^{+}},
            \\forall timestep \\in timesteps

    """
    model_data_dict = backend_model.__calliope_model_data__['data']
    timestep_resolution = backend_model.timestep_resolution[timestep]
    energy_cap = get_param(backend_model, 'energy_cap_per_unit', loc_tech)
    min_use = get_param(backend_model, 'energy_cap_min_use', (loc_tech, timestep))
    loc_tech_carriers_out = (
        split_comma_list(model_data_dict['lookup_loc_techs_conversion_plus']['out', loc_tech])
    )

    carrier_prod = sum(backend_model.carrier_prod[loc_tech_carrier, timestep]
                       for loc_tech_carrier in loc_tech_carriers_out)

    return carrier_prod >= (
        backend_model.operating_units[loc_tech, timestep] *
        timestep_resolution * energy_cap * min_use
    )
def balance_transmission_constraint_rule(backend_model, loc_tech, timestep):
    """
    Balance carrier production and consumption of transmission technologies

    .. container:: scrolling-wrapper

        .. math::

            -1 * \\boldsymbol{carrier_{con}}(loc_{from}::tech:loc_{to}::carrier, timestep)
            \\times \\eta_{energy}(loc::tech, timestep)
            = \\boldsymbol{carrier_{prod}}(loc_{to}::tech:loc_{from}::carrier, timestep)
            \\quad \\forall loc::tech:loc \\in locs::techs:locs_{transmission},
            \\forall timestep \\in timesteps

    Where a link is the connection between :math:`loc_{from}::tech:loc_{to}`
    and :math:`loc_{to}::tech:loc_{from}` for locations `to` and `from`.

    """
    model_data_dict = backend_model.__calliope_model_data__['data']

    energy_eff = get_param(backend_model, 'energy_eff', (loc_tech, timestep))
    loc_tech_carrier = model_data_dict['lookup_loc_techs'][loc_tech]
    remote_loc_tech = model_data_dict['lookup_remotes'][loc_tech]
    remote_loc_tech_carrier = model_data_dict['lookup_loc_techs'][remote_loc_tech]

    if (loc_tech_is_in(backend_model, remote_loc_tech, 'loc_techs_transmission')
            and get_param(backend_model, 'energy_prod', (loc_tech)) == 1):
        return (
            backend_model.carrier_prod[loc_tech_carrier, timestep] ==
            -1 * backend_model.carrier_con[remote_loc_tech_carrier, timestep] *
            energy_eff
        )
    else:
        return po.Constraint.NoConstraint
Beispiel #16
0
def carrier_production_max_milp_constraint_rule(backend_model, carrier, node,
                                                tech, timestep):
    """
    Set maximum carrier production of MILP techs that aren't conversion plus

    .. container:: scrolling-wrapper

        .. math::

            \\boldsymbol{carrier_{prod}}(loc::tech::carrier, timestep)
            \\leq energy_{cap, per unit}(loc::tech) \\times timestep\\_resolution(timestep)
            \\times \\boldsymbol{operating\\_units}(loc::tech, timestep)
            \\times \\eta_{parasitic}(loc::tech, timestep)
            \\quad \\forall loc::tech \\in loc::techs_{milp}, \\forall timestep \\in timesteps

    :math:`\\eta_{parasitic}` is only activated for `supply_plus` technologies
    """

    carrier_prod = backend_model.carrier_prod[carrier, node, tech, timestep]
    timestep_resolution = backend_model.timestep_resolution[timestep]
    parasitic_eff = get_param(backend_model, "parasitic_eff",
                              (node, tech, timestep))
    energy_cap = get_param(backend_model, "energy_cap_per_unit", (node, tech))

    return carrier_prod <= (backend_model.operating_units[node, tech, timestep]
                            * timestep_resolution * energy_cap * parasitic_eff)
Beispiel #17
0
def resource_availability_supply_plus_constraint_rule(backend_model, loc_tech,
                                                      timestep):
    """
    Limit production from supply_plus techs to their available resource.

    .. container:: scrolling-wrapper

        .. math::

            \\boldsymbol{resource_{con}}(loc::tech, timestep)
            \\leq available\_resource(loc::tech, timestep)
            \\quad \\forall loc::tech \\in loc::techs_{supply^{+}}, \\forall timestep \\in timesteps

    If :math:`force\_resource(loc::tech)` is set:

    .. container:: scrolling-wrapper

        .. math::

            \\boldsymbol{resource_{con}}(loc::tech, timestep)
            = available\_resource(loc::tech, timestep)
            \\quad \\forall loc::tech \\in loc::techs_{supply^{+}}, \\forall timestep \\in timesteps

    Where:

    .. container:: scrolling-wrapper

        .. math::

            available\_resource(loc::tech, timestep) = resource(loc::tech, timestep)
            \\times resource_{scale}(loc::tech)

    if :math:`loc::tech` is in :math:`loc::techs_{area}`:

    .. container:: scrolling-wrapper

        .. math::

            available\_resource(loc::tech, timestep) = resource(loc::tech, timestep)
            \\times resource_{scale}(loc::tech)
            \\times resource_{area}(loc::tech)

    """
    resource = get_param(backend_model, 'resource', (loc_tech, timestep))
    resource_scale = get_param(backend_model, 'resource_scale', loc_tech)
    force_resource = get_param(backend_model, 'force_resource', loc_tech)

    if loc_tech_is_in(backend_model, loc_tech, 'loc_techs_area'):
        available_resource = resource * resource_scale * backend_model.resource_area[
            loc_tech]
    else:
        available_resource = resource * resource_scale

    if po.value(force_resource):
        return backend_model.resource_con[loc_tech,
                                          timestep] == available_resource
    else:
        return backend_model.resource_con[loc_tech,
                                          timestep] <= available_resource
Beispiel #18
0
def unit_capacity_systemwide_milp_constraint_rule(backend_model, tech):
    """
    Set constraints to limit the number of purchased units of a single technology
    type across all locations in the model.

    The first valid case is applied:

    .. container:: scrolling-wrapper

        .. math::

            \\sum_{loc}\\boldsymbol{units}(loc::tech) + \\boldsymbol{purchased}(loc::tech)
            \\begin{cases}
                = units_{equals, systemwide}(tech),&
                    \\text{if } units_{equals, systemwide}(tech)\\\\
                \\leq units_{max, systemwide}(tech),&
                    \\text{if } units_{max, systemwide}(tech)\\\\
                \\text{unconstrained},& \\text{otherwise}
            \\end{cases}
            \\forall tech \\in techs

    """

    if tech in getattr(backend_model, 'techs_transmission_names', []):
        all_loc_techs = [
            i for i in backend_model.loc_techs_transmission
            if i.split('::')[1].split(':')[0] == tech
        ]
        multiplier = 2  # there are always two technologies associated with one link
    else:
        all_loc_techs = [
            i for i in backend_model.loc_techs
            if i.split('::')[1] == tech
        ]
        multiplier = 1

    max_systemwide = get_param(backend_model, 'units_max_systemwide', tech)
    equals_systemwide = get_param(backend_model, 'units_equals_systemwide', tech)

    if np.isinf(po.value(max_systemwide)) and not equals_systemwide:
        return po.Constraint.NoConstraint
    elif equals_systemwide and np.isinf(po.value(equals_systemwide)):
        raise ValueError(
            'Cannot use inf for energy_cap_equals_systemwide for tech `{}`'.format(tech)
        )

    sum_expr_units = sum(
        backend_model.units[loc_tech] for loc_tech in all_loc_techs
        if loc_tech_is_in(backend_model, loc_tech, 'loc_techs_milp')
    )
    sum_expr_purchase = sum(
        backend_model.purchased[loc_tech] for loc_tech in all_loc_techs
        if loc_tech_is_in(backend_model, loc_tech, 'loc_techs_purchase')
    )

    if equals_systemwide:
        return sum_expr_units + sum_expr_purchase == equals_systemwide * multiplier
    else:
        return sum_expr_units + sum_expr_purchase <= max_systemwide * multiplier
Beispiel #19
0
def unit_capacity_systemwide_constraint_rule(backend_model, tech):
    """
    Set constraints to limit the number of purchased units of a single technology
    type across all locations in the model.

    The first valid case is applied:

    .. container:: scrolling-wrapper

        .. math::

            \\sum_{loc}\\boldsymbol{units}(loc::tech) + \\boldsymbol{purchased}(loc::tech)
            \\begin{cases}
                = units_{equals, systemwide}(tech),&
                    \\text{if } units_{equals, systemwide}(tech)\\\\
                \\leq units_{max, systemwide}(tech),&
                    \\text{if } units_{max, systemwide}(tech)\\\\
                \\text{unconstrained},& \\text{otherwise}
            \\end{cases}
            \\forall tech \\in techs

    """

    if tech in backend_model.techs_transmission_names:
        all_loc_techs = [
            i for i in backend_model.loc_techs_transmission
            if i.split('::')[1].split(':')[0] == tech
        ]
        multiplier = 2  # there are always two technologies associated with one link
    else:
        all_loc_techs = [
            i for i in backend_model.loc_techs
            if i.split('::')[1] == tech
        ]
        multiplier = 1

    max_systemwide = get_param(backend_model, 'units_max_systemwide', tech)
    equals_systemwide = get_param(backend_model, 'units_equals_systemwide', tech)

    if np.isinf(po.value(max_systemwide)) and not equals_systemwide:
        return po.Constraint.NoConstraint
    elif equals_systemwide and np.isinf(po.value(equals_systemwide)):
        raise ValueError(
            'Cannot use inf for energy_cap_equals_systemwide for tech `{}`'.format(tech)
        )

    sum_expr_units = sum(
        backend_model.units[loc_tech] for loc_tech in all_loc_techs
        if loc_tech_is_in(backend_model, loc_tech, 'loc_techs_milp')
    )
    sum_expr_purchase = sum(
        backend_model.purchased[loc_tech] for loc_tech in all_loc_techs
        if loc_tech_is_in(backend_model, loc_tech, 'loc_techs_purchase')
    )

    if equals_systemwide:
        return sum_expr_units + sum_expr_purchase == equals_systemwide * multiplier
    else:
        return sum_expr_units + sum_expr_purchase <= max_systemwide * multiplier
Beispiel #20
0
def balance_storage_constraint_rule(backend_model, loc_tech, timestep):
    """
    Balance carrier production and consumption of storage technologies,
    alongside any use of the stored volume.

    .. container:: scrolling-wrapper

        .. math::

            \\boldsymbol{storage}(loc::tech, timestep) =
            \\boldsymbol{storage}(loc::tech, timestep_{previous})
            \\times (1 - storage\\_loss(loc::tech, timestep))^{resolution(timestep)}
            - \\boldsymbol{carrier_{con}}(loc::tech::carrier, timestep)
            \\times \\eta_{energy}(loc::tech, timestep)
            - \\frac{\\boldsymbol{carrier_{prod}}(loc::tech::carrier, timestep)}{\\eta_{energy}(loc::tech, timestep)}
            \\quad \\forall loc::tech \\in loc::techs_{storage}, \\forall timestep \\in timesteps
    """
    model_data_dict = backend_model.__calliope_model_data['data']
    run_config = backend_model.__calliope_run_config

    energy_eff = get_param(backend_model, 'energy_eff', (loc_tech, timestep))

    if po.value(energy_eff) == 0:
        carrier_prod = 0
    else:
        loc_tech_carrier = model_data_dict['lookup_loc_techs'][loc_tech]
        carrier_prod = backend_model.carrier_prod[loc_tech_carrier,
                                                  timestep] / energy_eff

    carrier_con = backend_model.carrier_con[loc_tech_carrier,
                                            timestep] * energy_eff

    current_timestep = backend_model.timesteps.order_dict[timestep]
    if current_timestep == 0 and not run_config['cyclic_storage']:
        storage_previous_step = (
            get_param(backend_model, 'storage_initial', loc_tech) *
            backend_model.storage_cap[loc_tech])
    elif (hasattr(backend_model, 'storage_inter_cluster')
          and model_data_dict['lookup_cluster_first_timestep'][timestep]):
        storage_previous_step = 0
    else:
        if (hasattr(backend_model, 'clusters') and
                model_data_dict['lookup_cluster_first_timestep'][timestep]):
            previous_step = model_data_dict['lookup_cluster_last_timestep'][
                timestep]
        elif current_timestep == 0 and run_config['cyclic_storage']:
            previous_step = backend_model.timesteps[-1]
        else:
            previous_step = get_previous_timestep(backend_model.timesteps,
                                                  timestep)
        storage_loss = get_param(backend_model, 'storage_loss', loc_tech)
        time_resolution = backend_model.timestep_resolution[previous_step]
        storage_previous_step = (
            ((1 - storage_loss)**time_resolution) *
            backend_model.storage[loc_tech, previous_step])

    return (backend_model.storage[loc_tech,
                                  timestep] == storage_previous_step -
            carrier_prod - carrier_con)
def resource_availability_supply_plus_constraint_rule(backend_model, loc_tech, timestep):
    """
    Limit production from supply_plus techs to their available resource.

    .. container:: scrolling-wrapper

        .. math::

            \\boldsymbol{resource_{con}}(loc::tech, timestep)
            \\leq available\\_resource(loc::tech, timestep)
            \\quad \\forall loc::tech \\in loc::techs_{supply^{+}}, \\forall timestep \\in timesteps

    If :math:`force\\_resource(loc::tech)` is set:

    .. container:: scrolling-wrapper

        .. math::

            \\boldsymbol{resource_{con}}(loc::tech, timestep)
            = available\\_resource(loc::tech, timestep)
            \\quad \\forall loc::tech \\in loc::techs_{supply^{+}}, \\forall timestep \\in timesteps

    Where:

    .. container:: scrolling-wrapper

        .. math::

            available\\_resource(loc::tech, timestep) = resource(loc::tech, timestep)
            \\times resource_{scale}(loc::tech)

    if :math:`loc::tech` is in :math:`loc::techs_{area}`:

    .. container:: scrolling-wrapper

        .. math::

            available\\_resource(loc::tech, timestep) = resource(loc::tech, timestep)
            \\times resource_{scale}(loc::tech)
            \\times resource_{area}(loc::tech)

    """
    resource = get_param(backend_model, 'resource', (loc_tech, timestep))
    resource_scale = get_param(backend_model, 'resource_scale', loc_tech)
    force_resource = get_param(backend_model, 'force_resource', loc_tech)
    resource_unit = get_param(backend_model, 'resource_unit', loc_tech)

    if po.value(resource_unit) == 'energy_per_area':
        available_resource = resource * resource_scale * backend_model.resource_area[loc_tech]
    elif po.value(resource_unit) == 'energy_per_cap':
        available_resource = resource * resource_scale * backend_model.energy_cap[loc_tech]
    else:
        available_resource = resource * resource_scale

    if po.value(force_resource):
        return backend_model.resource_con[loc_tech, timestep] == available_resource
    else:
        return backend_model.resource_con[loc_tech, timestep] <= available_resource
Beispiel #22
0
def cost_var_constraint_rule(backend_model, cost, loc_tech, timestep):
    """
    Calculate costs from time-varying decision variables

    .. container:: scrolling-wrapper

        .. math::

    """
    model_data_dict = backend_model.__calliope_model_data__

    cost_om_prod = get_param(backend_model, 'cost_om_prod',
                             (cost, loc_tech, timestep))
    cost_om_con = get_param(backend_model, 'cost_om_con',
                            (cost, loc_tech, timestep))
    weight = backend_model.timestep_weights[timestep]

    loc_tech_carrier = model_data_dict['data']['lookup_loc_techs'][loc_tech]

    if po.value(cost_om_prod):
        cost_prod = cost_om_prod * weight * backend_model.carrier_prod[
            loc_tech_carrier, timestep]
    else:
        cost_prod = 0

    if loc_tech_is_in(backend_model, loc_tech,
                      'loc_techs_supply_plus') and cost_om_con:
        resource_eff = get_param(backend_model, 'resource_eff',
                                 (loc_tech, timestep))
        if po.value(
                resource_eff
        ) > 0:  # In case resource_eff is zero, to avoid an infinite value
            # Dividing by r_eff here so we get the actual r used, not the r
            # moved into storage...
            cost_con = cost_om_con * weight * (
                backend_model.resource_con[loc_tech, timestep] / resource_eff)
        else:
            cost_con = 0
    elif loc_tech_is_in(backend_model, loc_tech,
                        'loc_techs_supply') and cost_om_con:
        energy_eff = get_param(backend_model, 'energy_eff',
                               (loc_tech, timestep))
        if po.value(
                energy_eff
        ) > 0:  # in case energy_eff is zero, to avoid an infinite value
            cost_con = cost_om_con * weight * (
                backend_model.carrier_prod[loc_tech_carrier, timestep] /
                energy_eff)
        else:
            cost_con = 0
    else:
        cost_con = 0

    backend_model.cost_var_rhs[cost, loc_tech,
                               timestep].expr = cost_prod + cost_con
    return (backend_model.cost_var[cost, loc_tech, timestep] ==
            backend_model.cost_var_rhs[cost, loc_tech, timestep])
Beispiel #23
0
def balance_conversion_plus_tiers_constraint_rule(backend_model, tier,
                                                  loc_tech, timestep):
    """
    Force all carrier_in_2/carrier_in_3 and carrier_out_2/carrier_out_3 to follow
    carrier_in and carrier_out (respectively).

    If `tier` in ['out_2', 'out_3']:

    .. container:: scrolling-wrapper

        .. math::

            \\sum_{loc::tech::carrier \\in loc::tech::carriers_{out}} (
            \\frac{\\boldsymbol{carrier_{prod}}(loc::tech::carrier, timestep)}{
                carrier\\_ratio(loc::tech::carrier, `out')} =
            \\sum_{loc::tech::carrier \\in loc::tech::carriers_{tier}} (
            \\frac{\\boldsymbol{carrier_{prod}}(loc::tech::carrier, timestep)}{
                carrier\\_ratio(loc::tech::carrier, tier)}
            \\quad \\forall \\text { tier } \\in [`out_2', `out_3'], \\forall loc::tech
                \\in loc::techs_{conversion^{+}}, \\forall timestep \\in timesteps

    If `tier` in ['in_2', 'in_3']:

    .. container:: scrolling-wrapper

        .. math::

            \\sum_{loc::tech::carrier \\in loc::tech::carriers_{in}}
            \\frac{\\boldsymbol{carrier_{con}}(loc::tech::carrier, timestep)}{
                carrier\\_ratio(loc::tech::carrier, `in')} =
            \\sum_{loc::tech::carrier \\in loc::tech::carriers_{tier}}
            \\frac{\\boldsymbol{carrier_{prod}}(loc::tech::carrier, timestep)}{
                carrier\\_ratio(loc::tech::carrier, tier)}
            \\quad \\forall \\text{ tier } \\in [`in_2', `in_3'], \\forall loc::tech
                \\in loc::techs_{conversion^{+}}, \\forall timestep \\in timesteps
    """
    primary_tier, decision_variable = get_conversion_plus_io(
        backend_model, tier)
    model_data_dict = backend_model.__calliope_model_data['data']

    loc_tech_carriers_1 = split_comma_list(
        model_data_dict['lookup_loc_techs_conversion_plus'][primary_tier,
                                                            loc_tech])
    loc_tech_carriers_2 = split_comma_list(
        model_data_dict['lookup_loc_techs_conversion_plus'][tier, loc_tech])

    c_1 = sum(decision_variable[loc_tech_carrier, timestep] /
              get_param(backend_model, 'carrier_ratios', (primary_tier,
                                                          loc_tech_carrier))
              for loc_tech_carrier in loc_tech_carriers_1)
    c_2 = sum(
        decision_variable[loc_tech_carrier, timestep] /
        get_param(backend_model, 'carrier_ratios', (tier, loc_tech_carrier))
        for loc_tech_carrier in loc_tech_carriers_2)

    return c_1 == c_2
def balance_storage_constraint_rule(backend_model, loc_tech, timestep):
    """
    Balance carrier production and consumption of storage technologies,
    alongside any use of the stored volume.

    .. container:: scrolling-wrapper

        .. math::

            \\boldsymbol{storage}(loc::tech, timestep) =
            \\boldsymbol{storage}(loc::tech, timestep_{previous})
            \\times (1 - storage\\_loss(loc::tech, timestep))^{resolution(timestep)}
            - \\boldsymbol{carrier_{con}}(loc::tech::carrier, timestep)
            \\times \\eta_{energy}(loc::tech, timestep)
            - \\frac{\\boldsymbol{carrier_{prod}}(loc::tech::carrier, timestep)}{\\eta_{energy}(loc::tech, timestep)}
            \\quad \\forall loc::tech \\in loc::techs_{storage}, \\forall timestep \\in timesteps
    """
    model_data_dict = backend_model.__calliope_model_data__['data']
    model_attrs = backend_model.__calliope_model_data__['attrs']

    energy_eff = get_param(backend_model, 'energy_eff', (loc_tech, timestep))

    if po.value(energy_eff) == 0:
        carrier_prod = 0
    else:
        loc_tech_carrier = model_data_dict['lookup_loc_techs'][loc_tech]
        carrier_prod = backend_model.carrier_prod[loc_tech_carrier, timestep] / energy_eff

    carrier_con = backend_model.carrier_con[loc_tech_carrier, timestep] * energy_eff

    current_timestep = backend_model.timesteps.order_dict[timestep]
    if current_timestep == 0 and not model_attrs['run.cyclic_storage']:
        storage_previous_step = get_param(backend_model, 'storage_initial', loc_tech)
    elif (hasattr(backend_model, 'storage_inter_cluster') and
            model_data_dict['lookup_cluster_first_timestep'][timestep]):
        storage_previous_step = 0
    else:
        if (hasattr(backend_model, 'clusters') and
                model_data_dict['lookup_cluster_first_timestep'][timestep]):
            previous_step = model_data_dict['lookup_cluster_last_timestep'][timestep]
        elif current_timestep == 0 and model_attrs['run.cyclic_storage']:
            previous_step = backend_model.timesteps[-1]
        else:
            previous_step = get_previous_timestep(backend_model.timesteps, timestep)
        storage_loss = get_param(backend_model, 'storage_loss', loc_tech)
        time_resolution = backend_model.timestep_resolution[previous_step]
        storage_previous_step = (
            ((1 - storage_loss) ** time_resolution) *
            backend_model.storage[loc_tech, previous_step]
        )

    return (
        backend_model.storage[loc_tech, timestep] ==
        storage_previous_step - carrier_prod - carrier_con
    )
    def test_get_param_no_timestep_possible(self):
        """ """
        m = build_model({}, "simple_supply,two_hours,investment_costs")
        m.run()
        param = get_param(m._backend_model, "energy_cap_max",
                          ("b", "test_supply_elec"))
        assert po.value(param) == 10  # see test model.yaml

        param = get_param(m._backend_model, "cost_energy_cap",
                          ("monetary", "a", "test_supply_elec"))
        assert po.value(param) == 10
Beispiel #26
0
def cost_var_conversion_constraint_rule(backend_model, cost, loc_tech, timestep):
    """
    Add time-varying conversion technology costs

    .. container:: scrolling-wrapper

        .. math::

            \\boldsymbol{cost_{var}}(loc::tech, cost, timestep) =
            \\boldsymbol{carrier_{prod}}(loc::tech::carrier, timestep)
            \\times timestep_{weight}(timestep) \\times cost_{om, prod}(loc::tech, cost, timestep)
            +
            \\boldsymbol{carrier_{con}}(loc::tech::carrier, timestep)
            \\times timestep_{weight}(timestep) \\times cost_{om, con}(loc::tech, cost, timestep)
            \\quad \\forall loc::tech \\in loc::techs_{cost_{var}, conversion}
    """
    model_data_dict = backend_model.__calliope_model_data
    weight = backend_model.timestep_weights[timestep]

    loc_tech_carrier_in = model_data_dict["data"]["lookup_loc_techs_conversion"][
        ("in", loc_tech)
    ]

    loc_tech_carrier_out = model_data_dict["data"]["lookup_loc_techs_conversion"][
        ("out", loc_tech)
    ]

    cost_om_prod = get_param(backend_model, "cost_om_prod", (cost, loc_tech, timestep))
    cost_om_con = get_param(backend_model, "cost_om_con", (cost, loc_tech, timestep))
    if po.value(cost_om_prod):
        cost_prod = (
            cost_om_prod
            * weight
            * backend_model.carrier_prod[loc_tech_carrier_out, timestep]
        )
    else:
        cost_prod = 0

    if po.value(cost_om_con):
        cost_con = (
            cost_om_con
            * weight
            * -1
            * backend_model.carrier_con[loc_tech_carrier_in, timestep]
        )
    else:
        cost_con = 0

    backend_model.cost_var_rhs[cost, loc_tech, timestep] = cost_prod + cost_con

    return (
        backend_model.cost_var[cost, loc_tech, timestep]
        == backend_model.cost_var_rhs[cost, loc_tech, timestep]
    )
Beispiel #27
0
def energy_capacity_systemwide_constraint_rule(backend_model, tech):
    """
    Set constraints to limit the capacity of a single technology type across all locations in the model.

    The first valid case is applied:

    .. container:: scrolling-wrapper

        .. math::

            \\sum_{loc}\\boldsymbol{energy_{cap}}(loc::tech)
            \\begin{cases}
                = energy_{cap, equals, systemwide}(loc::tech),&
                    \\text{if } energy_{cap, equals, systemwide}(loc::tech)\\\\
                \\leq energy_{cap, max, systemwide}(loc::tech),&
                    \\text{if } energy_{cap, max, systemwide}(loc::tech)\\\\
                \\text{unconstrained},& \\text{otherwise}
            \\end{cases}
            \\forall tech \\in techs

    """

    if tech in backend_model.techs_transmission_names:
        all_loc_techs = [
            i for i in backend_model.loc_techs_transmission
            if i.split('::')[1].split(':')[0] == tech
        ]
        multiplier = 2  # there are always two technologies associated with one link
    else:
        all_loc_techs = [
            i for i in backend_model.loc_techs if i.split('::')[1] == tech
        ]
        multiplier = 1

    max_systemwide = get_param(backend_model, 'energy_cap_max_systemwide',
                               tech)
    equals_systemwide = get_param(backend_model,
                                  'energy_cap_equals_systemwide', tech)

    if np.isinf(po.value(max_systemwide)) and not equals_systemwide:
        return po.Constraint.NoConstraint
    elif equals_systemwide and np.isinf(po.value(equals_systemwide)):
        raise exceptions.ModelError(
            'Cannot use inf for energy_cap_equals_systemwide for tech `{}`'.
            format(tech))

    sum_expr = sum(backend_model.energy_cap[loc_tech]
                   for loc_tech in all_loc_techs)

    if equals_systemwide:
        return sum_expr == equals_systemwide * multiplier
    else:
        return sum_expr <= max_systemwide * multiplier
def balance_conversion_plus_tiers_constraint_rule(backend_model, tier, loc_tech, timestep):
    """
    Force all carrier_in_2/carrier_in_3 and carrier_out_2/carrier_out_3 to follow
    carrier_in and carrier_out (respectively).

    If `tier` in ['out_2', 'out_3']:

    .. container:: scrolling-wrapper

        .. math::

            \\sum_{loc::tech::carrier \\in loc::tech::carriers_{out}} (
            \\frac{\\boldsymbol{carrier_{prod}}(loc::tech::carrier, timestep)}{
                carrier\\_ratio(loc::tech::carrier, `out')} =
            \\sum_{loc::tech::carrier \\in loc::tech::carriers_{tier}} (
            \\frac{\\boldsymbol{carrier_{prod}}(loc::tech::carrier, timestep)}{
                carrier\\_ratio(loc::tech::carrier, tier)}
            \\quad \\forall \\text { tier } \\in [`out_2', `out_3'], \\forall loc::tech
                \\in loc::techs_{conversion^{+}}, \\forall timestep \\in timesteps

    If `tier` in ['in_2', 'in_3']:

    .. container:: scrolling-wrapper

        .. math::

            \\sum_{loc::tech::carrier \\in loc::tech::carriers_{in}}
            \\frac{\\boldsymbol{carrier_{con}}(loc::tech::carrier, timestep)}{
                carrier\\_ratio(loc::tech::carrier, `in')} =
            \\sum_{loc::tech::carrier \\in loc::tech::carriers_{tier}}
            \\frac{\\boldsymbol{carrier_{prod}}(loc::tech::carrier, timestep)}{
                carrier\\_ratio(loc::tech::carrier, tier)}
            \\quad \\forall \\text{ tier } \\in [`in_2', `in_3'], \\forall loc::tech
                \\in loc::techs_{conversion^{+}}, \\forall timestep \\in timesteps
    """
    primary_tier, decision_variable = get_conversion_plus_io(backend_model, tier)
    model_data_dict = backend_model.__calliope_model_data__['data']

    loc_tech_carriers_1 = split_comma_list(
        model_data_dict['lookup_loc_techs_conversion_plus'][primary_tier, loc_tech]
    )
    loc_tech_carriers_2 = split_comma_list(
        model_data_dict['lookup_loc_techs_conversion_plus'][tier, loc_tech]
    )

    c_1 = sum(decision_variable[loc_tech_carrier, timestep]
        / get_param(backend_model, 'carrier_ratios', (primary_tier, loc_tech_carrier))
        for loc_tech_carrier in loc_tech_carriers_1)
    c_2 = sum(decision_variable[loc_tech_carrier, timestep]
        / get_param(backend_model, 'carrier_ratios', (tier, loc_tech_carrier))
        for loc_tech_carrier in loc_tech_carriers_2)

    return c_1 == c_2
Beispiel #29
0
def energy_capacity_systemwide_constraint_rule(backend_model, tech):
    """
    Set constraints to limit the capacity of a single technology type across all locations in the model.

    The first valid case is applied:

    .. container:: scrolling-wrapper

        .. math::

            \\sum_{loc}\\boldsymbol{energy_{cap}}(loc::tech)
            \\begin{cases}
                = energy_{cap, equals, systemwide}(loc::tech),&
                    \\text{if } energy_{cap, equals, systemwide}(loc::tech)\\\\
                \\leq energy_{cap, max, systemwide}(loc::tech),&
                    \\text{if } energy_{cap, max, systemwide}(loc::tech)\\\\
                \\text{unconstrained},& \\text{otherwise}
            \\end{cases}
            \\forall tech \\in techs

    """

    if tech in backend_model.techs_transmission_names:
        all_loc_techs = [
            i for i in backend_model.loc_techs_transmission
            if i.split('::')[1].split(':')[0] == tech
        ]
        multiplier = 2  # there are always two technologies associated with one link
    else:
        all_loc_techs = [
            i for i in backend_model.loc_techs
            if i.split('::')[1] == tech
        ]
        multiplier = 1

    max_systemwide = get_param(backend_model, 'energy_cap_max_systemwide', tech)
    equals_systemwide = get_param(backend_model, 'energy_cap_equals_systemwide', tech)

    if np.isinf(po.value(max_systemwide)) and not equals_systemwide:
        return po.Constraint.NoConstraint
    elif equals_systemwide and np.isinf(po.value(equals_systemwide)):
        raise exceptions.ModelError(
            'Cannot use inf for energy_cap_equals_systemwide for tech `{}`'.format(tech)
        )

    sum_expr = sum(backend_model.energy_cap[loc_tech] for loc_tech in all_loc_techs)

    if equals_systemwide:
        return sum_expr == equals_systemwide * multiplier
    else:
        return sum_expr <= max_systemwide * multiplier
Beispiel #30
0
 def test_get_param_no_default_defined(self):
     """
     If a default is not defined, raise KeyError
     """
     m = build_model({}, "simple_supply,two_hours,investment_costs")
     m.run()
     with pytest.raises(KeyError):
         get_param(
             m._backend_model,
             "random_param",
             ("1::test_demand_elec", m._backend_model.timesteps[1]),
         )
         get_param(m._backend_model, "random_param",
                   ("1::test_supply_elec"))
Beispiel #31
0
def cost_var_constraint_rule(backend_model, cost, loc_tech, timestep):
    """
    Calculate costs from time-varying decision variables

    .. container:: scrolling-wrapper

        .. math::

            \\boldsymbol{cost_{var}}(cost, loc::tech, timestep) = cost_{prod}(cost, loc::tech, timestep) + cost_{con}(cost, loc::tech, timestep)

            cost_{prod}(cost, loc::tech, timestep) = cost_{om\\_prod}(cost, loc::tech, timestep) \\times weight(timestep) \\times \\boldsymbol{carrier_{prod}}(loc::tech::carrier, timestep)

            prod\\_con\\_eff =
            \\begin{cases}
                = \\boldsymbol{resource_{con}}(loc::tech, timestep),&
                    \\text{if } loc::tech \\in loc\\_techs\\_supply\\_plus \\\\
                = \\frac{\\boldsymbol{carrier_{prod}}(loc::tech::carrier, timestep)}{energy_eff(loc::tech, timestep)},&
                    \\text{if } loc::tech \\in loc\\_techs\\_supply \\\\
            \\end{cases}

            cost_{con}(cost, loc::tech, timestep) = cost_{om\\_con}(cost, loc::tech, timestep) \\times weight(timestep) \\times prod\\_con\\_eff

    """
    model_data_dict = backend_model.__calliope_model_data__

    cost_om_prod = get_param(backend_model, 'cost_om_prod', (cost, loc_tech, timestep))
    cost_om_con = get_param(backend_model, 'cost_om_con', (cost, loc_tech, timestep))
    weight = backend_model.timestep_weights[timestep]

    loc_tech_carrier = model_data_dict['data']['lookup_loc_techs'][loc_tech]

    if po.value(cost_om_prod):
        cost_prod = cost_om_prod * weight * backend_model.carrier_prod[loc_tech_carrier, timestep]
    else:
        cost_prod = 0

    if loc_tech_is_in(backend_model, loc_tech, 'loc_techs_supply_plus') and cost_om_con:
        cost_con = cost_om_con * weight * backend_model.resource_con[loc_tech, timestep]
    elif loc_tech_is_in(backend_model, loc_tech, 'loc_techs_supply') and cost_om_con:
        energy_eff = get_param(backend_model, 'energy_eff', (loc_tech, timestep))
        if po.value(energy_eff) > 0:  # in case energy_eff is zero, to avoid an infinite value
            cost_con = cost_om_con * weight * (backend_model.carrier_prod[loc_tech_carrier, timestep] / energy_eff)
        else:
            cost_con = 0
    else:
        cost_con = 0

    backend_model.cost_var_rhs[cost, loc_tech, timestep].expr = cost_prod + cost_con
    return (backend_model.cost_var[cost, loc_tech, timestep] ==
            backend_model.cost_var_rhs[cost, loc_tech, timestep])
Beispiel #32
0
def cost_var_constraint_rule(backend_model, cost, loc_tech, timestep):
    """
    Calculate costs from time-varying decision variables

    .. container:: scrolling-wrapper

        .. math::

            \\boldsymbol{cost_{var}}(cost, loc::tech, timestep) = cost_{prod}(cost, loc::tech, timestep) + cost_{con}(cost, loc::tech, timestep)

            cost_{prod}(cost, loc::tech, timestep) = cost_{om\\_prod}(cost, loc::tech, timestep) \\times weight(timestep) \\times \\boldsymbol{carrier_{prod}}(loc::tech::carrier, timestep)

            prod\\_con\\_eff =
            \\begin{cases}
                = \\boldsymbol{resource_{con}}(loc::tech, timestep),&
                    \\text{if } loc::tech \\in loc\\_techs\\_supply\\_plus \\\\
                = \\frac{\\boldsymbol{carrier_{prod}}(loc::tech::carrier, timestep)}{energy_eff(loc::tech, timestep)},&
                    \\text{if } loc::tech \\in loc\\_techs\\_supply \\\\
            \\end{cases}

            cost_{con}(cost, loc::tech, timestep) = cost_{om\\_con}(cost, loc::tech, timestep) \\times weight(timestep) \\times prod\\_con\\_eff

    """
    model_data_dict = backend_model.__calliope_model_data

    cost_om_prod = get_param(backend_model, 'cost_om_prod', (cost, loc_tech, timestep))
    cost_om_con = get_param(backend_model, 'cost_om_con', (cost, loc_tech, timestep))
    weight = backend_model.timestep_weights[timestep]

    loc_tech_carrier = model_data_dict['data']['lookup_loc_techs'][loc_tech]

    if po.value(cost_om_prod):
        cost_prod = cost_om_prod * weight * backend_model.carrier_prod[loc_tech_carrier, timestep]
    else:
        cost_prod = 0

    if loc_tech_is_in(backend_model, loc_tech, 'loc_techs_supply_plus') and cost_om_con:
        cost_con = cost_om_con * weight * backend_model.resource_con[loc_tech, timestep]
    elif loc_tech_is_in(backend_model, loc_tech, 'loc_techs_supply') and cost_om_con:
        energy_eff = get_param(backend_model, 'energy_eff', (loc_tech, timestep))
        if po.value(energy_eff) > 0:  # in case energy_eff is zero, to avoid an infinite value
            cost_con = cost_om_con * weight * (backend_model.carrier_prod[loc_tech_carrier, timestep] / energy_eff)
        else:
            cost_con = 0
    else:
        cost_con = 0

    backend_model.cost_var_rhs[cost, loc_tech, timestep].expr = cost_prod + cost_con
    return (backend_model.cost_var[cost, loc_tech, timestep] ==
            backend_model.cost_var_rhs[cost, loc_tech, timestep])
Beispiel #33
0
def cost_var_conversion_plus_constraint_rule(backend_model, cost, loc_tech, timestep):
    """
    Add time-varying conversion_plus technology costs

    .. container:: scrolling-wrapper

        .. math::

            \\boldsymbol{cost_{var}}(loc::tech, cost, timestep) =
            \\boldsymbol{carrier_{prod}}(loc::tech::carrier_{primary}, timestep)
            \\times timestep_{weight}(timestep) \\times cost_{om, prod}(loc::tech, cost, timestep)
            +
            \\boldsymbol{carrier_{con}}(loc::tech::carrier_{primary}, timestep)
            \\times timestep_{weight}(timestep) \\times cost_{om, con}(loc::tech, cost, timestep)
            \\quad \\forall loc::tech \\in loc::techs_{cost_{var}, conversion^{+}}
    """
    model_data_dict = backend_model.__calliope_model_data['data']
    weight = backend_model.timestep_weights[timestep]

    loc_tech_carrier_con = (
        model_data_dict['lookup_primary_loc_tech_carriers_in'][loc_tech]
    )

    loc_tech_carrier_prod = (
        model_data_dict['lookup_primary_loc_tech_carriers_out'][loc_tech]
    )
    var_cost = 0

    if loc_tech_carrier_prod in backend_model.loc_tech_carriers_prod:
        cost_om_prod = get_param(backend_model, 'cost_om_prod',
                                 (cost, loc_tech, timestep))
        if cost_om_prod:
            var_cost += (
                cost_om_prod * weight *
                backend_model.carrier_prod[loc_tech_carrier_prod, timestep]
            )

    if loc_tech_carrier_con in backend_model.loc_tech_carriers_con:
        cost_om_con = get_param(backend_model, 'cost_om_con',
                                (cost, loc_tech, timestep))
        if cost_om_con:
            var_cost += (
                cost_om_con * weight * -1 *
                backend_model.carrier_con[loc_tech_carrier_con, timestep]
            )

    backend_model.cost_var_rhs[cost, loc_tech, timestep] = var_cost
    return (backend_model.cost_var[cost, loc_tech, timestep] ==
            backend_model.cost_var_rhs[cost, loc_tech, timestep])
def cost_var_conversion_plus_constraint_rule(backend_model, cost, loc_tech, timestep):
    """
    Add time-varying conversion_plus technology costs

    .. container:: scrolling-wrapper

        .. math::

            \\boldsymbol{cost_{var}}(loc::tech, cost, timestep) =
            \\boldsymbol{carrier_{prod}}(loc::tech::carrier_{primary}, timestep)
            \\times timestep_{weight}(timestep) \\times cost_{om, prod}(loc::tech, cost, timestep)
            +
            \\boldsymbol{carrier_{con}}(loc::tech::carrier_{primary}, timestep)
            \\times timestep_{weight}(timestep) \\times cost_{om, con}(loc::tech, cost, timestep)
            \\quad \\forall loc::tech \\in loc::techs_{cost_{var}, conversion^{+}}
    """
    model_data_dict = backend_model.__calliope_model_data__['data']
    weight = backend_model.timestep_weights[timestep]

    loc_tech_carrier_con = (
        model_data_dict['lookup_primary_loc_tech_carriers_in'][loc_tech]
    )

    loc_tech_carrier_prod = (
        model_data_dict['lookup_primary_loc_tech_carriers_out'][loc_tech]
    )
    var_cost = 0

    if loc_tech_carrier_prod in backend_model.loc_tech_carriers_prod:
        cost_om_prod = get_param(backend_model, 'cost_om_prod',
                                 (cost, loc_tech, timestep))
        if cost_om_prod:
            var_cost += (
                cost_om_prod * weight *
                backend_model.carrier_prod[loc_tech_carrier_prod, timestep]
            )

    if loc_tech_carrier_con in backend_model.loc_tech_carriers_con:
        cost_om_con = get_param(backend_model, 'cost_om_con',
                                (cost, loc_tech, timestep))
        if cost_om_con:
            var_cost += (
                cost_om_con * weight * -1 *
                backend_model.carrier_con[loc_tech_carrier_con, timestep]
            )

    backend_model.cost_var_rhs[cost, loc_tech, timestep] = var_cost
    return (backend_model.cost_var[cost, loc_tech, timestep] ==
            backend_model.cost_var_rhs[cost, loc_tech, timestep])
Beispiel #35
0
def balance_conversion_constraint_rule(backend_model, loc_tech, timestep):
    """
    Balance energy carrier consumption and production

    .. container:: scrolling-wrapper

        .. math::

            -1 * \\boldsymbol{carrier_{con}}(loc::tech::carrier, timestep)
            \\times \\eta_{energy}(loc::tech, timestep)
            = \\boldsymbol{carrier_{prod}}(loc::tech::carrier, timestep)
            \\times \\eta_{energy}(loc::tech, timestep)
            \\quad \\forall loc::tech \\in locs::techs_{conversion},
            \\forall timestep \\in timesteps
    """
    model_data_dict = backend_model.__calliope_model_data__['data']

    loc_tech_carrier_out = model_data_dict['lookup_loc_techs_conversion'][('out', loc_tech)]
    loc_tech_carrier_in = model_data_dict['lookup_loc_techs_conversion'][('in', loc_tech)]

    energy_eff = get_param(backend_model, 'energy_eff', (loc_tech, timestep))

    return (
        backend_model.carrier_prod[loc_tech_carrier_out, timestep] == -1 *
        backend_model.carrier_con[loc_tech_carrier_in, timestep] * energy_eff
    )
Beispiel #36
0
def energy_capacity_constraint_rule(backend_model, loc_tech):
    """
    Set upper and lower bounds for energy_cap.

    The first valid case is applied:

    .. container:: scrolling-wrapper

        .. math::

            \\frac{\\boldsymbol{energy_{cap}}(loc::tech)}{energy_{cap, scale}(loc::tech)}
            \\begin{cases}
                = energy_{cap, equals}(loc::tech),& \\text{if } energy_{cap, equals}(loc::tech)\\\\
                \\leq energy_{cap, max}(loc::tech),& \\text{if } energy_{cap, max}(loc::tech)\\\\
                \\text{unconstrained},& \\text{otherwise}
            \\end{cases}
            \\forall loc::tech \\in loc::techs

    and (if ``equals`` not enforced):

    .. container:: scrolling-wrapper

        .. math::

            \\frac{\\boldsymbol{energy_{cap}}(loc::tech)}{energy_{cap, scale}(loc::tech)}
            \\geq energy_{cap, min}(loc::tech)
            \\quad \\forall loc::tech \\in loc::techs
    """
    scale = get_param(backend_model, 'energy_cap_scale', loc_tech)
    return get_capacity_constraint(backend_model, 'energy_cap', loc_tech, scale=scale)
Beispiel #37
0
def storage_inter_min_rule(backend_model, loc_tech, datestep):
    """
    When clustering days, to reduce the timeseries length, set minimum limit on
    the intra-cluster and inter-date stored energy.
    intra-cluster = all timesteps in a single cluster
    datesteps = all dates in the unclustered timeseries (each has a corresponding cluster)
    `Ref: DOI 10.1016/j.apenergy.2018.01.023 <https://doi.org/10.1016/j.apenergy.2018.01.023>`_

    .. container:: scrolling-wrapper

        .. math::

            \\boldsymbol{storage_{inter\\_cluster}}(loc::tech, datestep)
            \\times (1 - storage\\_loss(loc::tech, timestep))^{24} +
            \\boldsymbol{storage_{intra\\_cluster, min}}(loc::tech, cluster(datestep))
            \\geq 0 \\quad \\forall loc::tech \\in loc::techs_{store},
            \\forall datestep \\in datesteps

    Where :math:`cluster(datestep)` is the cluster number in which the datestep
    is located.
    """
    cluster = backend_model.__calliope_model_data__['data']['lookup_datestep_cluster'][datestep]
    storage_loss = get_param(backend_model, 'storage_loss', loc_tech)
    return (
        backend_model.storage_inter_cluster[loc_tech, datestep] * ((1 - storage_loss) ** 24) +
        backend_model.storage_intra_cluster_min[loc_tech, cluster] >= 0
    )
Beispiel #38
0
def resource_area_constraint_rule(backend_model, constraint_group, what):
    """
    Enforce upper and lower bounds of resource_area for groups of
    technologies and locations.

    .. container:: scrolling-wrapper

        .. math::

            \\boldsymbol{resource_{area}}(loc::tech) \\leq group\\_resource\\_area\\_max\\\\

            \\boldsymbol{resource_{area}}(loc::tech) \\geq group\\_resource\\_area\\_min

    """
    threshold = get_param(
        backend_model, "group_resource_area_{}".format(what), (constraint_group)
    )

    if check_value(threshold):
        return return_noconstraint("resource_area", constraint_group)
    else:
        lhs_loc_techs = getattr(
            backend_model, "group_constraint_loc_techs_{}".format(constraint_group)
        )

        lhs = sum(backend_model.resource_area[loc_tech] for loc_tech in lhs_loc_techs)
        rhs = threshold

        return equalizer(lhs, rhs, what)
Beispiel #39
0
def update_costs_investment_purchase_constraint(backend_model, cost, loc_tech):
    """
    Add binary investment costs (cost * binary_purchased_unit)

    .. container:: scrolling-wrapper

        .. math::

            \\boldsymbol{cost_{investment}}(cost, loc::tech) += \\boldsymbol{purchased}(loc::tech)
            \\times cost_{purchase}(cost, loc::tech) * timestep_{weight} * depreciation
            \\quad \\forall cost \\in costs, \\forall loc::tech \\in loc::techs_{cost_{investment}, purchase}

    """
    model_data_dict = backend_model.__calliope_model_data__
    ts_weight = get_timestep_weight(backend_model)
    depreciation_rate = model_data_dict['data']['cost_depreciation_rate'][(
        cost, loc_tech)]

    cost_purchase = get_param(backend_model, 'cost_purchase', (cost, loc_tech))
    cost_of_purchase = (backend_model.purchased[loc_tech] * cost_purchase *
                        ts_weight * depreciation_rate)

    backend_model.cost_investment_rhs[cost, loc_tech].expr += cost_of_purchase

    return None
Beispiel #40
0
def update_costs_var_constraint(backend_model, cost, loc_tech, timestep):
    """
    Update time varying cost constraint (from costs.py) to include export

    .. container:: scrolling-wrapper

        .. math::

            \\boldsymbol{cost_{var}}(cost, loc::tech, timestep) +=
            cost_{export}(cost, loc::tech, timestep) \\times
            \\boldsymbol{carrier_{export}}(loc::tech::carrier, timestep)
            * timestep_{weight} \\quad \\forall cost \\in costs,
            \\forall loc::tech \\in loc::techs_{cost_{var}, export},
            \\forall timestep \\in timesteps

    """
    model_data_dict = backend_model.__calliope_model_data__['data']

    loc_tech_carrier = model_data_dict['lookup_loc_techs_export'][(loc_tech)]
    weight = backend_model.timestep_weights[timestep]

    cost_export = (
        get_param(backend_model, 'cost_export', (cost, loc_tech, timestep))
        * backend_model.carrier_export[loc_tech_carrier, timestep]
        * weight
    )

    backend_model.cost_var_rhs[cost, loc_tech, timestep].expr += cost_export
Beispiel #41
0
def carrier_prod_constraint_rule(backend_model, constraint_group, carrier, what):
    """
    Enforces carrier_prod for groups of technologies and locations,
    as a sum over the entire model period.

    .. container:: scrolling-wrapper

        .. math::

            \\sum_{loc::tech::carrier \\in given\\_group, timestep \\in timesteps} carrier_{prod}(loc::tech::carrier, timestep) \\leq supply_max

    """
    limit = get_param(
        backend_model, "group_carrier_prod_{}".format(what), (carrier, constraint_group)
    )

    if check_value(limit):
        return return_noconstraint("carrier_prod", constraint_group)
    else:
        # We won't actually use the rhs techs
        lhs_loc_techs, rhs_loc_techs = get_carrier_prod_share_lhs_and_rhs_loc_techs(
            backend_model, constraint_group
        )

        lhs = sum(
            backend_model.carrier_prod[loc_tech + "::" + carrier, timestep]
            for loc_tech in lhs_loc_techs
            for timestep in backend_model.timesteps
            if loc_tech + "::" + carrier in backend_model.loc_tech_carriers_prod
        )

        return equalizer(lhs, limit, what)
Beispiel #42
0
def carrier_production_min_conversion_plus_constraint_rule(
        backend_model, loc_tech, timestep):
    """
    Set minimum conversion_plus carrier production.

    .. container:: scrolling-wrapper

        .. math::

            \\sum_{loc::tech::carrier \\in loc::tech::carriers_{out}}
            \\boldsymbol{carrier_{prod}}(loc::tech::carrier, timestep)
            \\leq \\boldsymbol{energy_{cap}}(loc::tech) \\times timestep\\_resolution(timestep)
            \\times energy_{cap, min use}(loc::tech)
            \\quad \\forall loc::tech \\in loc::techs_{conversion^{+}},
            \\forall timestep \\in timesteps
    """
    model_data_dict = backend_model.__calliope_model_data["data"]

    timestep_resolution = backend_model.timestep_resolution[timestep]
    min_use = get_param(backend_model, "energy_cap_min_use",
                        (loc_tech, timestep))

    loc_tech_carriers_out = split_comma_list(
        model_data_dict["lookup_loc_techs_conversion_plus"]["out", loc_tech])

    carrier_prod = sum(backend_model.carrier_prod[loc_tech_carrier, timestep]
                       for loc_tech_carrier in loc_tech_carriers_out)

    return carrier_prod >= (timestep_resolution *
                            backend_model.energy_cap[loc_tech] * min_use)
Beispiel #43
0
def update_costs_investment_units_constraint(backend_model, cost, loc_tech):
    """
    Add MILP investment costs (cost * number of units purchased)

    .. container:: scrolling-wrapper

        .. math::

            \\boldsymbol{cost_{investment}}(cost, loc::tech) += \\boldsymbol{units}(loc::tech)
            \\times cost_{purchase}(cost, loc::tech) * timestep_{weight} * depreciation
            \\quad \\forall cost \\in costs, \\forall loc::tech \\in loc::techs_{cost_{investment}, milp}
    """
    model_data_dict = backend_model.__calliope_model_data__
    ts_weight = get_timestep_weight(backend_model)
    depreciation_rate = model_data_dict['data']['cost_depreciation_rate'][(
        cost, loc_tech)]

    cost_purchase = get_param(backend_model, 'cost_purchase', (cost, loc_tech))
    cost_of_purchase = (backend_model.units[loc_tech] * cost_purchase *
                        ts_weight * depreciation_rate)

    if loc_tech_is_in(backend_model, loc_tech, 'loc_techs_transmission'):
        cost_of_purchase = cost_of_purchase / 2

    backend_model.cost_investment_rhs[cost, loc_tech].expr += cost_of_purchase

    return None
Beispiel #44
0
def energy_capacity_constraint_rule(backend_model, loc_tech):
    """
    Set upper and lower bounds for energy_cap.

    The first valid case is applied:

    .. container:: scrolling-wrapper

        .. math::

            \\frac{\\boldsymbol{energy_{cap}}(loc::tech)}{energy_{cap, scale}(loc::tech)}
            \\begin{cases}
                = energy_{cap, equals}(loc::tech),& \\text{if } energy_{cap, equals}(loc::tech)\\\\
                \\leq energy_{cap, max}(loc::tech),& \\text{if } energy_{cap, max}(loc::tech)\\\\
                \\text{unconstrained},& \\text{otherwise}
            \\end{cases}
            \\forall loc::tech \\in loc::techs

    and (if ``equals`` not enforced):

    .. container:: scrolling-wrapper

        .. math::

            \\frac{\\boldsymbol{energy_{cap}}(loc::tech)}{energy_{cap, scale}(loc::tech)}
            \\geq energy_{cap, min}(loc::tech)
            \\quad \\forall loc::tech \\in loc::techs
    """
    scale = get_param(backend_model, 'energy_cap_scale', loc_tech)
    return get_capacity_constraint(backend_model,
                                   'energy_cap',
                                   loc_tech,
                                   scale=scale)
Beispiel #45
0
def energy_cap_share_constraint_rule(backend_model, constraint_group, what):
    """
    Enforces shares of energy_cap for groups of technologies and locations. The
    share is relative to ``supply`` and ``supply_plus`` technologies only.

    .. container:: scrolling-wrapper

        .. math::

            \\sum_{loc::tech \\in given\\_group} energy_{cap}(loc::tech) \\leq
            share \\times \\sum_{loc::tech \\in loc\\_tech\\_supply\\_all \\in given\\_locations} energy_{cap}(loc::tech)
    """
    share = get_param(
        backend_model, "group_energy_cap_share_{}".format(what), (constraint_group)
    )

    if check_value(share):
        return return_noconstraint("energy_cap_share", constraint_group)
    else:
        lhs_loc_techs = getattr(
            backend_model, "group_constraint_loc_techs_{}".format(constraint_group)
        )
        lhs_locs = [loc_tech.split("::")[0] for loc_tech in lhs_loc_techs]
        rhs_loc_techs = [
            i
            for i in backend_model.loc_techs_supply_conversion_all
            if i.split("::")[0] in lhs_locs
        ]

        lhs = sum(backend_model.energy_cap[loc_tech] for loc_tech in lhs_loc_techs)
        rhs = share * sum(
            backend_model.energy_cap[loc_tech] for loc_tech in rhs_loc_techs
        )

        return equalizer(lhs, rhs, what)
Beispiel #46
0
def update_costs_investment_purchase_constraint(backend_model, cost, loc_tech):
    """
    Add binary investment costs (cost * binary_purchased_unit)

    .. container:: scrolling-wrapper

        .. math::

            \\boldsymbol{cost_{investment}}(cost, loc::tech) += \\boldsymbol{purchased}(loc::tech)
            \\times cost_{purchase}(cost, loc::tech) * timestep_{weight} * depreciation
            \\quad \\forall cost \\in costs, \\forall loc::tech \\in loc::techs_{cost_{investment}, purchase}

    """
    model_data_dict = backend_model.__calliope_model_data__
    ts_weight = get_timestep_weight(backend_model)
    depreciation_rate = model_data_dict['data']['cost_depreciation_rate'][(cost, loc_tech)]

    cost_purchase = get_param(backend_model, 'cost_purchase', (cost, loc_tech))
    cost_of_purchase = (
        backend_model.purchased[loc_tech] * cost_purchase * ts_weight * depreciation_rate
    )

    if loc_tech_is_in(backend_model, loc_tech, 'loc_techs_transmission'):
        cost_of_purchase = cost_of_purchase / 2

    backend_model.cost_investment_rhs[cost, loc_tech].expr += cost_of_purchase

    return None
def carrier_production_min_conversion_plus_constraint_rule(backend_model, loc_tech, timestep):
    """
    Set minimum conversion_plus carrier production.

    .. container:: scrolling-wrapper

        .. math::

            \\sum_{loc::tech::carrier \\in loc::tech::carriers_{out}}
            \\boldsymbol{carrier_{prod}}(loc::tech::carrier, timestep)
            \\leq \\boldsymbol{energy_{cap}}(loc::tech) \\times timestep\\_resolution(timestep)
            \\times energy_{cap, min use}(loc::tech)
            \\quad \\forall loc::tech \\in loc::techs_{conversion^{+}},
            \\forall timestep \\in timesteps
    """
    model_data_dict = backend_model.__calliope_model_data__['data']

    timestep_resolution = backend_model.timestep_resolution[timestep]
    min_use = get_param(backend_model, 'energy_cap_min_use', (loc_tech, timestep))

    loc_tech_carriers_out = split_comma_list(
        model_data_dict['lookup_loc_techs_conversion_plus']['out', loc_tech]
    )

    carrier_prod = sum(backend_model.carrier_prod[loc_tech_carrier, timestep]
                       for loc_tech_carrier in loc_tech_carriers_out)

    return carrier_prod >= (
        timestep_resolution * backend_model.energy_cap[loc_tech] * min_use
    )
Beispiel #48
0
def cost_var_conversion_constraint_rule(backend_model, cost, loc_tech, timestep):
    """
    Add time-varying conversion technology costs

    .. container:: scrolling-wrapper

        .. math::

            \\boldsymbol{cost_{var}}(loc::tech, cost, timestep) =
            \\boldsymbol{carrier_{prod}}(loc::tech::carrier, timestep)
            \\times timestep_{weight}(timestep) \\times cost_{om, prod}(loc::tech, cost, timestep)
            +
            \\boldsymbol{carrier_{con}}(loc::tech::carrier, timestep)
            \\times timestep_{weight}(timestep) \\times cost_{om, con}(loc::tech, cost, timestep)
            \\quad \\forall loc::tech \\in loc::techs_{cost_{var}, conversion}
    """
    model_data_dict = backend_model.__calliope_model_data__
    weight = backend_model.timestep_weights[timestep]

    loc_tech_carrier_in = (
        model_data_dict['data']['lookup_loc_techs_conversion'][('in', loc_tech)]
    )

    loc_tech_carrier_out = (
        model_data_dict['data']['lookup_loc_techs_conversion'][('out', loc_tech)]
    )

    cost_om_prod = get_param(backend_model, 'cost_om_prod',
                                (cost, loc_tech, timestep))
    cost_om_con = get_param(backend_model, 'cost_om_con',
                            (cost, loc_tech, timestep))
    if po.value(cost_om_prod):
        cost_prod = (cost_om_prod * weight *
            backend_model.carrier_prod[loc_tech_carrier_out, timestep])
    else:
        cost_prod = 0

    if po.value(cost_om_con):
        cost_con = (cost_om_con * weight * -1 *
            backend_model.carrier_con[loc_tech_carrier_in, timestep])
    else:
        cost_con = 0

    backend_model.cost_var_rhs[cost, loc_tech, timestep] = cost_prod + cost_con

    return (backend_model.cost_var[cost, loc_tech, timestep] ==
            backend_model.cost_var_rhs[cost, loc_tech, timestep])
def storage_initial_rule(backend_model, loc_tech):
    """
    If storage is cyclic, allow an initial storage to still be set. This is
    applied to the storage of the final timestep/datestep of the series as that,
    in cyclic storage, is the 'storage_previous_step' for the first
    timestep/datestep.

    If clustering and ``storage_inter_cluster`` exists:

    .. container:: scrolling-wrapper

        .. math::

            \\boldsymbol{storage_{inter\\_cluster}}(loc::tech, datestep_{final})
            \\times ((1 - storage_loss) ** 24) = storage_{initial}(loc::tech)
            \\quad \\forall loc::tech \\in loc::techs_{store}, \\forall datestep \\in datesteps

    Where :math:`datestep_{final}` is the last datestep of the timeseries

    Else:
    .. container:: scrolling-wrapper

        .. math::

            \\boldsymbol{storage}(loc::tech, timestep_{final})
            \\times ((1 - storage_loss) ** 24) = storage_{initial}(loc::tech)
            \\quad \\forall loc::tech \\in loc::techs_{store}, \\forall timestep \\in timesteps

    Where :math:`timestep_{final}` is the last timestep of the timeseries
    """

    storage_initial = get_param(backend_model, 'storage_initial', loc_tech)
    storage_loss = get_param(backend_model, 'storage_loss', loc_tech)
    if hasattr(backend_model, 'storage_inter_cluster'):
        storage = backend_model.storage_inter_cluster
        final_step = backend_model.datesteps[-1]
        time_resolution = 24
    else:
        storage = backend_model.storage
        final_step = backend_model.timesteps[-1]
        time_resolution = backend_model.timestep_resolution[final_step]

    return (
        storage[loc_tech, final_step] * ((1 - storage_loss) ** time_resolution)
        == storage_initial
    )
def balance_storage_inter_cluster_rule(backend_model, loc_tech, datestep):
    """
    When clustering days, to reduce the timeseries length, balance the daily stored
    energy across all days of the original timeseries.

    `Ref: DOI 10.1016/j.apenergy.2018.01.023 <https://doi.org/10.1016/j.apenergy.2018.01.023>`_

    .. container:: scrolling-wrapper

        .. math::

            \\boldsymbol{storage_{inter\\_cluster}}(loc::tech, datestep) =
            \\boldsymbol{storage_{inter\\_cluster}}(loc::tech, datestep_{previous})
            \\times (1 - storage\\_loss(loc::tech, timestep))^{24}
            + \\boldsymbol{storage}(loc::tech, timestep_{final, cluster(datestep))})
            \\quad \\forall loc::tech \\in loc::techs_{store}, \\forall datestep \\in datesteps

    Where :math:`timestep_{final, cluster(datestep_{previous}))}` is the final timestep of the
    cluster in the clustered timeseries corresponding to the previous day
    """
    model_attrs = backend_model.__calliope_model_data__['attrs']
    current_datestep = backend_model.datesteps.order_dict[datestep]

    if current_datestep == 0 and not model_attrs['run.cyclic_storage']:
        storage_previous_step = get_param(backend_model, 'storage_initial', loc_tech)
        storage_intra = 0
    else:
        if current_datestep == 0 and model_attrs['run.cyclic_storage']:
            previous_step = backend_model.datesteps[-1]
        else:
            previous_step = get_previous_timestep(backend_model.datesteps, datestep)
        storage_loss = get_param(backend_model, 'storage_loss', loc_tech)
        storage_previous_step = (
            ((1 - storage_loss) ** 24) *
            backend_model.storage_inter_cluster[loc_tech, previous_step]
        )
        final_timestep = (
            backend_model.__calliope_model_data__
            ['data']['lookup_datestep_last_cluster_timestep'][previous_step]
        )
        storage_intra = backend_model.storage[loc_tech, final_timestep]
    return (
        backend_model.storage_inter_cluster[loc_tech, datestep] ==
        storage_previous_step + storage_intra
    )
Beispiel #51
0
def ramping_constraint(backend_model, loc_tech_carrier, timestep, direction=0):
    """
    Ramping rate constraints.

    Direction: 0 is up, 1 is down.

    .. container:: scrolling-wrapper

        .. math::

            \\boldsymbol{max\\_ramping\\_rate}(loc::tech::carrier, timestep) =
            energy_{ramping}(loc::tech, timestep) \\times energy_{cap}(loc::tech)

            \\boldsymbol{diff}(loc::tech::carrier, timestep) =
            (carrier_{prod}(loc::tech::carrier, timestep) + carrier_{con}(loc::tech::carrier, timestep))
            / timestep\\_resolution(timestep) -
            (carrier_{prod}(loc::tech::carrier, timestep-1) + carrier_{con}(loc::tech::carrier, timestep-1))
            / timestep\\_resolution(timestep-1)

    """

    # No constraint for first timestep
    if backend_model.timesteps.order_dict[timestep] == 0:
        return po.Constraint.NoConstraint
    else:
        previous_step = get_previous_timestep(backend_model.timesteps, timestep)
        time_res = backend_model.timestep_resolution[timestep]
        time_res_prev = backend_model.timestep_resolution[previous_step]
        loc_tech = loc_tech_carrier.rsplit('::', 1)[0]
        # Ramping rate (fraction of installed capacity per hour)
        ramping_rate = get_param(backend_model, 'energy_ramping', (loc_tech, timestep))

        try:
            prod_this = backend_model.carrier_prod[loc_tech_carrier, timestep]
            prod_prev = backend_model.carrier_prod[loc_tech_carrier, previous_step]
        except KeyError:
            prod_this = 0
            prod_prev = 0

        try:
            con_this = backend_model.carrier_con[loc_tech_carrier, timestep]
            con_prev = backend_model.carrier_con[loc_tech_carrier, previous_step]
        except KeyError:
            con_this = 0
            con_prev = 0

        diff = (
            (prod_this + con_this) / time_res -
            (prod_prev + con_prev) / time_res_prev
        )

        max_ramping_rate = ramping_rate * backend_model.energy_cap[loc_tech]

        if direction == 0:
            return diff <= max_ramping_rate
        else:
            return -1 * max_ramping_rate <= diff
Beispiel #52
0
 def _get_investment_cost(capacity_decision_variable, calliope_set):
     """
     Conditionally add investment costs, if the relevant set of technologies
     exists. Both inputs are strings.
     """
     if loc_tech_is_in(backend_model, loc_tech, calliope_set):
         _cost = (getattr(backend_model, capacity_decision_variable)[loc_tech] *
             get_param(backend_model, 'cost_' + capacity_decision_variable, (cost, loc_tech)))
         return _cost
     else:
         return 0
Beispiel #53
0
def energy_capacity_min_purchase_constraint_rule(backend_model, loc_tech):
    """
    Set minimum energy capacity decision variable upper bound as a function of
    binary purchase variable

    and (if ``equals`` not enforced):

    .. container:: scrolling-wrapper

        .. math::

            \\frac{\\boldsymbol{energy_{cap}}(loc::tech)}{energy_{cap, scale}(loc::tech)}
            \\geq energy_{cap, min}(loc::tech) \\times \\boldsymbol{purchased}(loc::tech)
            \\quad \\forall loc::tech \\in loc::techs
    """
    energy_cap_min = get_param(backend_model, 'energy_cap_min', loc_tech)

    energy_cap_scale = get_param(backend_model, 'energy_cap_scale', loc_tech)
    return backend_model.energy_cap[loc_tech] >= (
        energy_cap_min * energy_cap_scale * backend_model.purchased[loc_tech]
    )
def balance_conversion_plus_primary_constraint_rule(backend_model, loc_tech, timestep):
    """
    Balance energy carrier consumption and production for carrier_in and carrier_out

    .. container:: scrolling-wrapper

        .. math::

            \\sum_{loc::tech::carrier \\in loc::tech::carriers_{out}}
            \\frac{\\boldsymbol{carrier_{prod}}(loc::tech::carrier, timestep)}{
                carrier\\_ratio(loc::tech::carrier, `out')} =
            -1 * \\sum_{loc::tech::carrier \\in loc::tech::carriers_{in}} (
            \\boldsymbol{carrier_{con}}(loc::tech::carrier, timestep)
            * carrier\\_ratio(loc::tech::carrier, `in') * \\eta_{energy}(loc::tech, timestep))
            \\quad \\forall loc::tech \\in loc::techs_{conversion^{+}}, \\forall timestep \\in timesteps
    """
    model_data_dict = backend_model.__calliope_model_data__['data']

    loc_tech_carriers_out = split_comma_list(
        model_data_dict['lookup_loc_techs_conversion_plus']['out', loc_tech]
    )
    loc_tech_carriers_in = split_comma_list(
        model_data_dict['lookup_loc_techs_conversion_plus']['in', loc_tech]
    )

    energy_eff = get_param(backend_model, 'energy_eff', (loc_tech, timestep))

    carrier_prod = sum(
        backend_model.carrier_prod[loc_tech_carrier, timestep] /
        get_param(backend_model, 'carrier_ratios', ('out', loc_tech_carrier))
        for loc_tech_carrier in loc_tech_carriers_out
    )
    carrier_con = sum(
        backend_model.carrier_con[loc_tech_carrier, timestep] *
        get_param(backend_model, 'carrier_ratios', ('in', loc_tech_carrier))
        for loc_tech_carrier in loc_tech_carriers_in
    )

    return carrier_prod == -1 * carrier_con * energy_eff
Beispiel #55
0
def carrier_production_min_milp_constraint_rule(backend_model, loc_tech_carrier, timestep):
    """
    Set minimum carrier production of MILP techs that aren't conversion plus

    .. container:: scrolling-wrapper

        .. math::

            \\boldsymbol{carrier_{prod}}(loc::tech::carrier, timestep)
            \\geq energy_{cap, per unit}(loc::tech) \\times timestep\\_resolution(timestep)
            \\times \\boldsymbol{operating\\_units}(loc::tech, timestep)
            \\times energy_{cap, min use}(loc::tech)
            \\quad \\forall loc::tech \\in loc::techs_{milp}, \\forall timestep \\in timesteps
    """
    loc_tech = get_loc_tech(loc_tech_carrier)
    carrier_prod = backend_model.carrier_prod[loc_tech_carrier, timestep]
    timestep_resolution = backend_model.timestep_resolution[timestep]
    min_use = get_param(backend_model, 'energy_cap_min_use', (loc_tech, timestep))
    energy_cap = get_param(backend_model, 'energy_cap_per_unit', loc_tech)

    return carrier_prod >= (
        backend_model.operating_units[loc_tech, timestep] *
        timestep_resolution * energy_cap * min_use
    )
Beispiel #56
0
def resource_area_constraint_rule(backend_model, loc_tech):
    """
    Set upper and lower bounds for resource_area.

    The first valid case is applied:

    .. container:: scrolling-wrapper

        .. math::

            \\boldsymbol{resource_{area}}(loc::tech)
            \\begin{cases}
                = resource_{area, equals}(loc::tech),& \\text{if } resource_{area, equals}(loc::tech)\\\\
                \\leq resource_{area, max}(loc::tech),& \\text{if } resource_{area, max}(loc::tech)\\\\
                \\text{unconstrained},& \\text{otherwise}
            \\end{cases}
            \\forall loc::tech \\in loc::techs_{area}

    and (if ``equals`` not enforced):

    .. container:: scrolling-wrapper

        .. math::

            \\boldsymbol{resource_{area}}(loc::tech) \\geq resource_{area, min}(loc::tech)
            \\quad \\forall loc::tech \\in loc::techs_{area}
    """
    energy_cap_max = get_param(backend_model, 'energy_cap_max', loc_tech)
    area_per_energy_cap = get_param(backend_model, 'resource_area_per_energy_cap', loc_tech)

    if po.value(energy_cap_max) == 0 and not po.value(area_per_energy_cap):
        # If a technology has no energy_cap here, we force resource_area to zero,
        # so as not to accrue spurious costs
        return backend_model.resource_area[loc_tech] == 0
    else:
        return get_capacity_constraint(backend_model, 'resource_area', loc_tech)
Beispiel #57
0
def storage_capacity_max_purchase_constraint_rule(backend_model, loc_tech):
    """
    Set maximum storage capacity.

    The first valid case is applied:

    .. container:: scrolling-wrapper

        .. math::

            \\boldsymbol{storage_{cap}}(loc::tech)
            \\begin{cases}
                = storage_{cap, equals}(loc::tech) \\times \\boldsymbol{purchased},&
                    \\text{if } storage_{cap, equals} \\\\
                \\leq storage_{cap, max}(loc::tech) \\times \\boldsymbol{purchased},&
                    \\text{if } storage_{cap, max}(loc::tech)\\\\
                \\text{unconstrained},& \\text{otherwise}
            \\end{cases}
            \\forall loc::tech \\in loc::techs_{purchase, store}

    """
    storage_cap_max = get_param(backend_model, 'storage_cap_max', loc_tech)
    storage_cap_equals = get_param(backend_model, 'storage_cap_equals', loc_tech)

    if po.value(storage_cap_equals):
        return backend_model.storage_cap[loc_tech] == (
            storage_cap_equals * backend_model.purchased[loc_tech]
        )

    elif po.value(storage_cap_max):
        return backend_model.storage_cap[loc_tech] <= (
            storage_cap_max * backend_model.purchased[loc_tech]
        )

    else:
        return po.Constraint.Skip
Beispiel #58
0
def resource_area_per_energy_capacity_constraint_rule(backend_model, loc_tech):
    """
    Add equality constraint for resource_area to equal a percentage of energy_cap,
    for any technologies which have defined resource_area_per_energy_cap

    .. container:: scrolling-wrapper

        .. math::

            \\boldsymbol{resource_{area}}(loc::tech) =
            \\boldsymbol{energy_{cap}}(loc::tech) \\times area\\_per\\_energy\\_cap(loc::tech)
            \\quad \\forall loc::tech \\in locs::techs_{area} \\text{ if } area\\_per\\_energy\\_cap(loc::tech)
    """
    area_per_energy_cap = get_param(backend_model, 'resource_area_per_energy_cap', loc_tech)

    return (backend_model.resource_area[loc_tech] ==
                backend_model.energy_cap[loc_tech] * area_per_energy_cap)
Beispiel #59
0
def storage_capacity_units_constraint_rule(backend_model, loc_tech):
    """
    Set storage capacity decision variable as a function of purchased units

    .. container:: scrolling-wrapper

        .. math::

            \\boldsymbol{storage_{cap}}(loc::tech) =
            \\boldsymbol{units}(loc::tech) \\times storage_{cap, per unit}(loc::tech)
            \\quad \\forall loc::tech \\in loc::techs_{milp, store}

    """
    return backend_model.storage_cap[loc_tech] == (
        backend_model.units[loc_tech] *
        get_param(backend_model, 'storage_cap_per_unit', loc_tech)
    )
Beispiel #60
0
def energy_capacity_storage_constraint_rule(backend_model, loc_tech):
    """
    Set an additional energy capacity constraint on storage technologies,
    based on their use of `charge_rate`.

    .. container:: scrolling-wrapper

        .. math::

            \\boldsymbol{energy_{cap}}(loc::tech)
            \\leq \\boldsymbol{storage_{cap}}(loc::tech) \\times charge\\_rate(loc::tech)
            \\quad \\forall loc::tech \\in loc::techs_{store}

    """
    charge_rate = get_param(backend_model, 'charge_rate', loc_tech)

    return backend_model.energy_cap[loc_tech] <= (
        backend_model.storage_cap[loc_tech] * charge_rate
    )