Example #1
0
def capacity_factor(results, model_data, systemwide=False):
    """
    Returns a DataArray with capacity factor for the given results.
    The results are either indexed by loc_tech_carriers_prod and timesteps,
    or by techs and carriers if systemwide results are being calculated.

    The weight of timesteps is considered when computing systemwide capacity factors,
    such that higher-weighted timesteps have a stronger influence
    on the resulting system-wide time-averaged capacity factor.

    """
    # In operate mode, energy_cap is an input parameter
    if "energy_cap" not in results.keys():
        energy_cap = model_data.energy_cap
    else:
        energy_cap = results.energy_cap

    _prod = split_loc_techs(results["carrier_prod"])
    _cap = split_loc_techs(energy_cap)
    if systemwide:
        # Aggregated/clustered days are represented `timestep_weights` times
        prod_sum = (_prod * model_data.timestep_weights).sum(
            ["timesteps", "locs"])
        cap_sum = _cap.sum(dim="locs")
        time_sum = (model_data.timestep_resolution *
                    model_data.timestep_weights).sum()
        capacity_factors = prod_sum / (cap_sum * time_sum)

    else:
        extra_dims = {
            i: model_data[i].to_index()
            for i in _prod.dims if i not in _cap.dims
        }
        capacity_factors = ((_prod /
                             _cap.expand_dims(extra_dims)).fillna(0).stack({
                                 "loc_tech_carriers_prod":
                                 ["locs", "techs", "carriers"]
                             }))
        new_idx = concat_iterable(
            capacity_factors.loc_tech_carriers_prod.values, ["::", "::"])
        capacity_factors = capacity_factors.assign_coords({
            "loc_tech_carriers_prod":
            new_idx
        }).reindex({"loc_tech_carriers_prod": results.loc_tech_carriers_prod})

    return capacity_factors
Example #2
0
def generate_constraint_sets(model_run):
    """
    Generate loc-tech sets for a given pre-processed ``model_run``

    Parameters
    ----------
    model_run : AttrDict
    """

    sets = model_run.sets
    ## From here on, everything is a `key=value` pair within a dictionary

    constraint_sets = dict()
    # energy_balance.py
    constraint_sets[
        "loc_carriers_system_balance_constraint"] = sets.loc_carriers
    constraint_sets[
        "loc_techs_balance_supply_constraint"] = sets.loc_techs_finite_resource_supply
    constraint_sets[
        "loc_techs_balance_demand_constraint"] = sets.loc_techs_finite_resource_demand
    constraint_sets[
        "loc_techs_resource_availability_supply_plus_constraint"] = sets.loc_techs_finite_resource_supply_plus
    constraint_sets[
        "loc_techs_balance_transmission_constraint"] = sets.loc_techs_transmission
    constraint_sets[
        "loc_techs_balance_supply_plus_constraint"] = sets.loc_techs_supply_plus
    constraint_sets[
        "loc_techs_balance_storage_constraint"] = sets.loc_techs_storage
    if model_run.run.cyclic_storage is True:
        constraint_sets["loc_techs_storage_initial_constraint"] = [
            i for i in sets.loc_techs_store if constraint_exists(
                model_run, i, "constraints.storage_initial") is not None
        ]
    constraint_sets["loc_techs_storage_discharge_depth"] = [
        i for i in sets.loc_techs_store if constraint_exists(
            model_run, i, "constraints.storage_discharge_depth")
    ]
    constraint_sets["carriers_reserve_margin_constraint"] = [
        i for i in sets.carriers
        if i in model_run.model.get_key("reserve_margin", {}).keys()
    ]
    # clustering-specific balance constraints
    if model_run.model.get_key(
            "time.function",
            None) == "apply_clustering" and model_run.model.get_key(
                "time.function_options.storage_inter_cluster", True):
        set_name = "loc_techs_balance_storage_inter_cluster_constraint"
        constraint_sets[set_name] = sets.loc_techs_store

    # costs.py
    constraint_sets["loc_techs_cost_constraint"] = sets.loc_techs_cost
    constraint_sets[
        "loc_techs_cost_investment_constraint"] = sets.loc_techs_investment_cost
    constraint_sets["loc_techs_cost_var_constraint"] = [
        i for i in sets.loc_techs_om_cost
        if i not in sets.loc_techs_conversion_plus + sets.loc_techs_conversion
    ]

    # export.py
    constraint_sets["loc_carriers_update_system_balance_constraint"] = [
        i for i in sets.loc_carriers if sets.loc_techs_export and any([
            "{0}::{2}".format(*j.split("::")) == i
            for j in sets.loc_tech_carriers_export
        ])
    ]
    constraint_sets[
        "loc_tech_carriers_export_balance_constraint"] = sets.loc_tech_carriers_export
    constraint_sets["loc_techs_update_costs_var_constraint"] = [
        i for i in sets.loc_techs_om_cost if i in sets.loc_techs_export
    ]

    constraint_sets["loc_tech_carriers_export_max_constraint"] = [
        i for i in sets.loc_tech_carriers_export if constraint_exists(
            model_run,
            i.rsplit("::", 1)[0], "constraints.export_cap") is not None
    ]

    # capacity.py
    constraint_sets["loc_techs_storage_capacity_constraint"] = [
        i for i in sets.loc_techs_store if i not in sets.loc_techs_milp
    ]
    constraint_sets["loc_techs_energy_capacity_storage_constraint_old"] = [
        i for i in sets.loc_techs_store
        if constraint_exists(model_run, i, "constraints.charge_rate")
    ]
    constraint_sets["loc_techs_energy_capacity_storage_equals_constraint"] = [
        i for i in sets.loc_techs_store if constraint_exists(
            model_run, i, "constraints.energy_cap_per_storage_cap_equals")
    ]
    constraint_sets["loc_techs_energy_capacity_storage_min_constraint"] = [
        i for i in sets.loc_techs_store if constraint_exists(
            model_run, i, "constraints.energy_cap_per_storage_cap_min")
        and not constraint_exists(
            model_run, i, "constraints.energy_cap_per_storage_cap_equals")
    ]
    constraint_sets["loc_techs_energy_capacity_storage_max_constraint"] = [
        i for i in sets.loc_techs_store if constraint_exists(
            model_run, i, "constraints.energy_cap_per_storage_cap_max")
        and not constraint_exists(
            model_run, i, "constraints.energy_cap_per_storage_cap_equals")
    ]
    constraint_sets["loc_techs_resource_capacity_constraint"] = [
        i for i in sets.loc_techs_finite_resource_supply_plus if any([
            constraint_exists(model_run, i, "constraints.resource_cap_equals"),
            constraint_exists(model_run, i, "constraints.resource_cap_max"),
            constraint_exists(model_run, i, "constraints.resource_cap_min"),
        ])
    ]
    constraint_sets[
        "loc_techs_resource_capacity_equals_energy_capacity_constraint"] = [
            i for i in sets.loc_techs_finite_resource_supply_plus
            if constraint_exists(model_run, i,
                                 "constraints.resource_cap_equals_energy_cap")
        ]
    constraint_sets["loc_techs_resource_area_constraint"] = sets.loc_techs_area
    constraint_sets[
        "loc_techs_resource_area_per_energy_capacity_constraint"] = [
            i for i in sets.loc_techs_area if constraint_exists(
                model_run, i, "constraints.resource_area_per_energy_cap")
            is not None
        ]
    constraint_sets["locs_resource_area_capacity_per_loc_constraint"] = [
        i for i in sets.locs
        if model_run.locations[i].get_key("available_area", None) is not None
        and sets.loc_techs_area
    ]
    constraint_sets["loc_techs_energy_capacity_constraint"] = [
        i for i in sets.loc_techs
        if i not in sets.loc_techs_milp + sets.loc_techs_purchase
    ]
    constraint_sets["techs_energy_capacity_systemwide_constraint"] = [
        i for i in sets.techs if model_run.get_key(
            "techs.{}.constraints.energy_cap_max_systemwide".format(i), None)
        or model_run.get_key(
            "techs.{}.constraints.energy_cap_equals_systemwide".format(i),
            None)
    ]

    # dispatch.py
    constraint_sets["loc_tech_carriers_carrier_production_max_constraint"] = [
        i for i in sets.loc_tech_carriers_prod
        if i not in sets.loc_tech_carriers_conversion_plus
        and i.rsplit("::", 1)[0] not in sets.loc_techs_milp
    ]
    constraint_sets["loc_tech_carriers_carrier_production_min_constraint"] = [
        i for i in sets.loc_tech_carriers_prod if
        i not in sets.loc_tech_carriers_conversion_plus and constraint_exists(
            model_run,
            i.rsplit("::", 1)[0], "constraints.energy_cap_min_use")
        and i.rsplit("::", 1)[0] not in sets.loc_techs_milp
    ]
    constraint_sets["loc_tech_carriers_carrier_consumption_max_constraint"] = [
        i for i in sets.loc_tech_carriers_con
        if i.rsplit("::", 1)[0] in sets.loc_techs_demand +
        sets.loc_techs_storage + sets.loc_techs_transmission
        and i.rsplit("::", 1)[0] not in sets.loc_techs_milp
    ]
    constraint_sets[
        "loc_techs_resource_max_constraint"] = sets.loc_techs_supply_plus
    constraint_sets["loc_tech_carriers_ramping_constraint"] = [
        i for i in sets.loc_tech_carriers_prod
        if i.rsplit("::", 1)[0] in sets.loc_techs_ramping
    ]
    # clustering-specific dispatch constraints
    if model_run.model.get_key(
            "time.function",
            None) == "apply_clustering" and model_run.model.get_key(
                "time.function_options.storage_inter_cluster", True):
        constraint_sets[
            "loc_techs_storage_intra_max_constraint"] = sets.loc_techs_store
        constraint_sets[
            "loc_techs_storage_intra_min_constraint"] = sets.loc_techs_store
        constraint_sets[
            "loc_techs_storage_inter_max_constraint"] = sets.loc_techs_store
        constraint_sets[
            "loc_techs_storage_inter_min_constraint"] = sets.loc_techs_store
    else:
        constraint_sets[
            "loc_techs_storage_max_constraint"] = sets.loc_techs_store

    # milp.py
    constraint_sets[
        "loc_techs_unit_commitment_milp_constraint"] = sets.loc_techs_milp
    constraint_sets[
        "loc_techs_unit_capacity_milp_constraint"] = sets.loc_techs_milp
    constraint_sets[
        "loc_tech_carriers_carrier_production_max_milp_constraint"] = [
            i for i in sets.loc_tech_carriers_prod
            if i not in sets.loc_tech_carriers_conversion_plus
            and i.rsplit("::", 1)[0] in sets.loc_techs_milp
        ]
    constraint_sets[
        "loc_techs_carrier_production_max_conversion_plus_milp_constraint"] = [
            i for i in sets.loc_techs_conversion_plus
            if i in sets.loc_techs_milp
        ]
    constraint_sets[
        "loc_tech_carriers_carrier_production_min_milp_constraint"] = [
            i for i in sets.loc_tech_carriers_prod
            if i not in sets.loc_tech_carriers_conversion_plus
            and constraint_exists(model_run,
                                  i.rsplit("::", 1)[0],
                                  "constraints.energy_cap_min_use")
            and i.rsplit("::", 1)[0] in sets.loc_techs_milp
        ]
    constraint_sets[
        "loc_techs_carrier_production_min_conversion_plus_milp_constraint"] = [
            i for i in sets.loc_techs_conversion_plus
            if constraint_exists(model_run, i, "constraints.energy_cap_min_use"
                                 ) and i in sets.loc_techs_milp
        ]
    constraint_sets[
        "loc_tech_carriers_carrier_consumption_max_milp_constraint"] = [
            i for i in sets.loc_tech_carriers_con
            if i.rsplit("::", 1)[0] in sets.loc_techs_demand +
            sets.loc_techs_storage + sets.loc_techs_transmission
            and i.rsplit("::", 1)[0] in sets.loc_techs_milp
        ]
    constraint_sets["loc_techs_energy_capacity_units_milp_constraint"] = [
        i for i in sets.loc_techs_milp if constraint_exists(
            model_run, i, "constraints.energy_cap_per_unit") is not None
    ]
    constraint_sets["loc_techs_storage_capacity_units_milp_constraint"] = [
        i for i in sets.loc_techs_milp if i in sets.loc_techs_store
    ]
    constraint_sets[
        "loc_techs_energy_capacity_max_purchase_milp_constraint"] = [
            i for i in sets.loc_techs_purchase
            if (constraint_exists(
                model_run, i, "constraints.energy_cap_equals") is not None or
                (constraint_exists(model_run, i, "constraints.energy_cap_max")
                 is not None and constraint_exists(
                     model_run, i, "constraints.energy_cap_max") != np.inf))
        ]
    constraint_sets[
        "loc_techs_energy_capacity_min_purchase_milp_constraint"] = [
            i for i in sets.loc_techs_purchase
            if (not constraint_exists(model_run, i,
                                      "constraints.energy_cap_equals") and
                constraint_exists(model_run, i, "constraints.energy_cap_min"))
        ]
    constraint_sets[
        "loc_techs_storage_capacity_max_purchase_milp_constraint"] = [
            i for i in set(sets.loc_techs_purchase).intersection(
                sets.loc_techs_store)
            if (constraint_exists(
                model_run, i, "constraints.storage_cap_equals") is not None or
                (constraint_exists(model_run, i, "constraints.storage_cap_max")
                 is not None and constraint_exists(
                     model_run, i, "constraints.storage_cap_max") != np.inf))
        ]
    constraint_sets[
        "loc_techs_storage_capacity_min_purchase_milp_constraint"] = [
            i for i in set(sets.loc_techs_purchase).intersection(
                sets.loc_techs_store)
            if (not constraint_exists(model_run, i,
                                      "constraints.storage_cap_equals") and
                constraint_exists(model_run, i, "constraints.storage_cap_min"))
        ]
    constraint_sets[
        "loc_techs_update_costs_investment_units_milp_constraint"] = [
            i for i in sets.loc_techs_milp
            if i in sets.loc_techs_investment_cost and any(
                constraint_exists(model_run, i, "costs.{}.purchase".format(j))
                for j in model_run.sets.costs)
        ]

    # loc_techs_purchase technologies only exist because they have defined a purchase cost
    constraint_sets[
        "loc_techs_update_costs_investment_purchase_milp_constraint"] = sets.loc_techs_purchase

    constraint_sets["techs_unit_capacity_systemwide_milp_constraint"] = [
        i for i in sets.techs if model_run.get_key(
            "techs.{}.constraints.units_max_systemwide".format(i), None)
        or model_run.get_key(
            "techs.{}.constraints.units_equals_systemwide".format(i), None)
    ]
    constraint_sets[
        "loc_techs_asynchronous_prod_con_milp_constraint"] = sets.loc_techs_asynchronous_prod_con

    # conversion.py
    constraint_sets[
        "loc_techs_balance_conversion_constraint"] = sets.loc_techs_conversion
    constraint_sets[
        "loc_techs_cost_var_conversion_constraint"] = sets.loc_techs_om_cost_conversion

    # conversion_plus.py
    constraint_sets[
        "loc_techs_balance_conversion_plus_primary_constraint"] = sets.loc_techs_conversion_plus
    constraint_sets[
        "loc_techs_carrier_production_max_conversion_plus_constraint"] = [
            i for i in sets.loc_techs_conversion_plus
            if i not in sets.loc_techs_milp
        ]
    constraint_sets[
        "loc_techs_carrier_production_min_conversion_plus_constraint"] = [
            i for i in sets.loc_techs_conversion_plus
            if constraint_exists(model_run, i, "constraints.energy_cap_min_use"
                                 ) and i not in sets.loc_techs_milp
        ]
    constraint_sets[
        "loc_techs_cost_var_conversion_plus_constraint"] = sets.loc_techs_om_cost_conversion_plus
    constraint_sets[
        "loc_techs_balance_conversion_plus_in_2_constraint"] = sets.loc_techs_in_2
    constraint_sets[
        "loc_techs_balance_conversion_plus_in_3_constraint"] = sets.loc_techs_in_3
    constraint_sets[
        "loc_techs_balance_conversion_plus_out_2_constraint"] = sets.loc_techs_out_2
    constraint_sets[
        "loc_techs_balance_conversion_plus_out_3_constraint"] = sets.loc_techs_out_3

    # network.py
    constraint_sets[
        "loc_techs_symmetric_transmission_constraint"] = sets.loc_techs_transmission

    # policy.py
    for sense in ["min", "max", "equals"]:
        constraint_sets[
            f"techlists_group_share_energy_cap_{sense}_constraint"] = [
                i for i in sets.techlists
                if f"energy_cap_{sense}" in model_run.model.get_key(
                    "group_share.{}".format(i), {}).keys()
            ]
        constraint_sets[
            f"techlists_carrier_group_share_carrier_prod_{sense}_constraint"] = [
                i + "::" + carrier for i in sets.techlists
                if f"carrier_prod_{sense}" in model_run.model.get_key(
                    "group_share.{}".format(i), {}).keys()
                for carrier in sets.carriers
                if carrier in model_run.model.get_key(
                    f"group_share.{i}.carrier_prod_{sense}", {}).keys()
            ]

    # group.py
    group_constraints = {
        name: data
        for name, data in model_run["group_constraints"].items()
        if data.get("exists", True)
    }
    constraint_sets["constraint_groups"] = list(group_constraints.keys())

    for group_constraint_name, group_constraint in group_constraints.items():
        tech_groups = [[
            k for k, v in checks.DEFAULTS.tech_groups.items()
            if i in v["allowed_group_constraints"]
        ] for i in group_constraint.keys()
                       if i not in ["techs", "locs", "exists"]]
        allowed_tech_groups = set(tech_groups[0]).intersection(*tech_groups)
        allowed_techs = sum(
            [sets["techs_{}".format(i)] for i in allowed_tech_groups], [])
        techs = group_constraint.get("techs", allowed_techs)

        locs = group_constraint.get("locs", sets["locs"])

        # If there are transmission techs, keep only those that link to allowed locations
        techs = [
            i for i in techs if ":" not in techs or i.split(":")[-1] in locs
        ]

        trans_techs = set(techs).intersection(sets["techs_transmission_names"])
        for i in trans_techs:
            techs += [i + ":" + j for j in locs]
            techs.remove(i)

        # If the group constraint defines its own techs, remove those that are
        # not allowed (there is a warning for this in checks.py)
        techs = list(set(techs).intersection(allowed_techs))

        # All possible loc_techs for this constraint
        loc_techs_all = list(
            set(
                concat_iterable([(l, t) for l, t in product(locs, techs)],
                                ["::"])))

        # Some loc_techs may not actually exist in the actual model,
        # so we must filter with actually exising loc_techs
        loc_techs = [i for i in loc_techs_all if i in sets.loc_techs]

        constraint_sets["group_constraint_loc_techs_{}".format(
            group_constraint_name)] = loc_techs

    return constraint_sets
Example #3
0
def location_specific_to_dataset(model_run):
    """
    Extract location specific information from the processed dictionary
    (model.model_run) and return an xarray Dataset with DataArray variables
    describing distance, coordinate and available area information.

    Parameters
    ----------
    model_run : AttrDict
        processed Calliope model_run dict

    Returns
    -------
    data_dict : dict conforming to xarray conventions

    """
    # for every transmission technology, we extract distance information, if it
    # is available
    data_dict = dict()

    data_dict["distance"] = dict(
        dims="loc_techs_transmission",
        data=[
            model_run.get_key(
                "locations.{loc_from}.links.{loc_to}.techs.{tech}.distance".
                format(**split_loc_techs_transmission(loc_tech)),
                np.nan,
            ) for loc_tech in model_run.sets["loc_techs_transmission"]
        ],
    )
    # If there is no distance information stored, distance array is deleted
    if data_dict["distance"]["data"].count(np.nan) == len(
            data_dict["distance"]["data"]):
        del data_dict["distance"]

    data_dict["lookup_remotes"] = dict(
        dims="loc_techs_transmission",
        data=concat_iterable(
            [(k["loc_to"], k["tech"], k["loc_from"]) for k in [
                split_loc_techs_transmission(loc_tech)
                for loc_tech in model_run.sets["loc_techs_transmission"]
            ]],
            ["::", ":"],
        ),
    )
    # If there are no remote locations stored, lookup_remotes array is deleted
    if data_dict["lookup_remotes"]["data"].count(np.nan) == len(
            data_dict["lookup_remotes"]["data"]):
        del data_dict["lookup_remotes"]

    data_dict["available_area"] = dict(
        dims="locs",
        data=[
            model_run.locations[loc].get("available_area", np.nan)
            for loc in model_run.sets["locs"]
        ],
    )

    # remove this dictionary element if nothing is defined in it
    if set(data_dict["available_area"]["data"]) == {np.nan}:
        del data_dict["available_area"]

    # Coordinates are defined per location, but may not be defined at all for
    # the model
    if "coordinates" in model_run.sets:
        data_dict["loc_coordinates"] = dict(dims=["locs", "coordinates"],
                                            data=[])
        for loc in model_run.sets["locs"]:
            data_dict["loc_coordinates"]["data"].append([
                model_run.locations[loc].coordinates[coordinate]
                for coordinate in model_run.sets.coordinates
            ])

    return data_dict
Example #4
0
def generate_loc_tech_sets(model_run, simple_sets):
    """
    Generate loc-tech sets for a given pre-processed ``model_run``

    Parameters
    ----------
    model_run : AttrDict
    simple_sets : AttrDict
        Simple sets returned by ``generate_simple_sets(model_run)``.

    """
    sets = AttrDict()

    ##
    # First deal with transmission techs, which can show up only in
    # loc_techs_transmission, loc_techs_milp, and loc_techs_purchase
    ##

    # All `tech:loc` expanded transmission technologies
    sets.loc_techs_transmission = set(
        concat_iterable(
            [
                (i, u, j) for i, j, u in product(  # (loc, loc, tech) product
                    simple_sets.locs,
                    simple_sets.locs,
                    simple_sets.techs_transmission_names,
                ) if model_run.get_key(
                    "locations.{}.links.{}.techs.{}".format(i, j, u), None)
            ],
            ["::", ":"],
        ))

    # A dict of transmission tech config objects
    # to make parsing for set membership easier
    loc_techs_transmission_config = {
        k: model_run.get_key(
            "locations.{loc_from}.links.{loc_to}.techs.{tech}".format(
                **split_loc_techs_transmission(k)))
        for k in sets.loc_techs_transmission
    }

    ##
    # Now deal with the rest of the techs and other sets
    ##

    # Only loc-tech combinations that actually exist
    sets.loc_techs_non_transmission = set(
        concat_iterable(
            [(l, t) for l, t in product(simple_sets.locs,
                                        simple_sets.techs_non_transmission)
             if model_run.get_key("locations.{}.techs.{}".format(l, t), None)],
            ["::"],
        ))

    sets.loc_techs = sets.loc_techs_non_transmission | sets.loc_techs_transmission

    # A dict of non-transmission tech config objects
    # to make parsing for set membership easier
    loc_techs_config = {
        k: model_run.get_key("locations.{}.techs.{}".format(*k.split("::")))
        for k in sets.loc_techs_non_transmission
    }

    loc_techs_all_config = {
        **loc_techs_config,
        **loc_techs_transmission_config
    }

    ##
    # Sets based on membership in abstract base technology groups
    ##

    for group in [
            "storage",
            "demand",
            "supply",
            "supply_plus",
            "conversion",
            "conversion_plus",
    ]:
        tech_set = set(
            k for k in sets.loc_techs_non_transmission
            if model_run.techs[k.split("::")[1]].inheritance[-1] == group)
        sets["loc_techs_{}".format(group)] = tech_set

    sets.loc_techs_non_conversion = (set(
        k for k in sets.loc_techs_non_transmission if k not in
        sets.loc_techs_conversion and k not in sets.loc_techs_conversion_plus)
                                     | sets.loc_techs_transmission)

    # Techs that introduce energy into the system
    sets.loc_techs_supply_all = sets.loc_techs_supply | sets.loc_techs_supply_plus

    # Techs that change the energy carrier in the system
    sets.loc_techs_conversion_all = (sets.loc_techs_conversion
                                     | sets.loc_techs_conversion_plus)
    # All techs that can be used to generate a carrier (not just store or move it)
    sets.loc_techs_supply_conversion_all = (sets.loc_techs_supply_all
                                            | sets.loc_techs_conversion_all)

    ##
    # Sets based on specific constraints being active
    ##

    # Technologies that specify resource_area constraints
    sets.loc_techs_area = set(
        k for k in sets.loc_techs_non_transmission
        if (any(
            "resource_area" in i
            for i in loc_techs_config[k].keys_nested()) or loc_techs_config[k].
            constraints.get("resource_unit", "energy") == "energy_per_area"))

    # Technologies that define storage, which can include `supply_plus`
    # and `storage` groups.
    sets.loc_techs_store = (set(k for k in sets.loc_techs_supply_plus if any(
        "storage_" in i
        for i in loc_techs_config[k].constraints.keys_nested()))
                            | sets.loc_techs_storage)

    # technologies that specify a finite resource
    sets.loc_techs_finite_resource = set(
        k for k in sets.loc_techs_non_transmission
        if loc_techs_config[k].constraints.get("resource") and not (
            loc_techs_config[k].constraints.get("resource") in ["inf", np.inf])
    )

    # `supply` technologies that specify a finite resource
    sets.loc_techs_finite_resource_supply = sets.loc_techs_finite_resource.intersection(
        sets.loc_techs_supply)

    # `demand` technologies that specify a finite resource
    sets.loc_techs_finite_resource_demand = sets.loc_techs_finite_resource.intersection(
        sets.loc_techs_demand)

    # `supply_plus` technologies that specify a finite resource
    sets.loc_techs_finite_resource_supply_plus = sets.loc_techs_finite_resource.intersection(
        sets.loc_techs_supply_plus)

    # Technologies that define ramping constraints
    sets.loc_techs_ramping = set(
        k for k in sets.loc_techs_non_transmission
        if "energy_ramping" in loc_techs_config[k].constraints)

    # Technologies that allow export
    sets.loc_techs_export = set(
        k for k in sets.loc_techs_non_transmission
        if "export_carrier" in loc_techs_config[k].constraints)

    # Technologies that allow purchasing discrete units
    # NB: includes transmission techs!
    loc_techs_purchase = set(k for k in sets.loc_techs_non_transmission if any(
        ".purchase" in i
        for i in loc_techs_config[k].get("costs", AttrDict()).keys_nested(
        )) and not any("units_" in i for i in loc_techs_config[k].get(
            "constraints", AttrDict()).keys_nested()))

    transmission_purchase = set(
        k for k in sets.loc_techs_transmission
        if any(".purchase" in i for i in loc_techs_transmission_config[k].get(
            "costs", AttrDict()).keys_nested()) and not any(
                "units_" in i for i in loc_techs_transmission_config[k].get(
                    "constraints", AttrDict()).keys_nested()))

    sets.loc_techs_purchase = loc_techs_purchase | transmission_purchase

    # Technologies with MILP constraints
    loc_techs_milp = set(k for k in sets.loc_techs_non_transmission if any(
        "units_" in i for i in loc_techs_config[k].constraints.keys_nested()))

    transmission_milp = set(k for k in sets.loc_techs_transmission if any(
        "units_" in i
        for i in loc_techs_transmission_config[k].constraints.keys_nested()))

    sets.loc_techs_milp = loc_techs_milp | transmission_milp

    # Technologies with forced asynchronous production/consumption of energy
    loc_techs_storage_asynchronous_prod_con = set(
        k for k in sets.loc_techs_store if "force_asynchronous_prod_con" in
        loc_techs_config[k].constraints.keys_nested())

    loc_techs_transmission_asynchronous_prod_con = set(
        k for k in sets.loc_techs_transmission if "force_asynchronous_prod_con"
        in loc_techs_transmission_config[k].constraints.keys_nested())
    sets.loc_techs_asynchronous_prod_con = (
        loc_techs_storage_asynchronous_prod_con
        | loc_techs_transmission_asynchronous_prod_con)

    ##
    # Sets based on specific costs being active
    # NB includes transmission techs
    ##

    loc_techs_costs = set(k for k in sets.loc_techs_non_transmission if any(
        "costs" in i for i in loc_techs_config[k].keys()))

    loc_techs_transmission_costs = set(
        k for k in sets.loc_techs_transmission
        if any("costs" in i for i in loc_techs_transmission_config[k].keys()))

    # Any capacity or fixed annual costs
    loc_techs_investment_costs = set(k for k in loc_techs_costs if any(
        "_cap" in i or ".purchase" in i or "_area" in i
        for i in loc_techs_config[k].costs.keys_nested()))
    loc_techs_transmission_investment_costs = set(
        k for k in loc_techs_transmission_costs
        if any("_cap" in i or ".purchase" in i or "_area" in i
               for i in loc_techs_transmission_config[k].costs.keys_nested()))

    # Any operation and maintenance
    loc_techs_om_costs = set(k for k in loc_techs_costs if any(
        "om_" in i or "export" in i
        for i in loc_techs_config[k].costs.keys_nested()))
    loc_techs_transmission_om_costs = set(
        k for k in loc_techs_transmission_costs
        if any("om_" in i
               for i in loc_techs_transmission_config[k].costs.keys_nested()))

    # Any export costs
    sets.loc_techs_costs_export = set(k for k in loc_techs_costs if any(
        "export" in i for i in loc_techs_config[k].costs.keys_nested()))

    sets.loc_techs_cost = loc_techs_costs | loc_techs_transmission_costs
    sets.loc_techs_investment_cost = (loc_techs_investment_costs |
                                      loc_techs_transmission_investment_costs)
    sets.loc_techs_om_cost = loc_techs_om_costs | loc_techs_transmission_om_costs

    ##
    # Subsets of costs for different abstract base technologies
    ##

    sets.loc_techs_om_cost_conversion = loc_techs_om_costs.intersection(
        sets.loc_techs_conversion)
    sets.loc_techs_om_cost_conversion_plus = loc_techs_om_costs.intersection(
        sets.loc_techs_conversion_plus)
    sets.loc_techs_om_cost_supply = loc_techs_om_costs.intersection(
        sets.loc_techs_supply)
    sets.loc_techs_om_cost_supply_plus = loc_techs_om_costs.intersection(
        sets.loc_techs_supply_plus)

    ##
    # Subsets of `conversion_plus` technologies
    ##

    # `conversion_plus` technologies with secondary carrier(s) out
    sets.loc_techs_out_2 = set(k for k in sets.loc_techs_conversion_plus
                               if "carrier_out_2" in model_run.techs[k.split(
                                   "::")[1].split(":")[0]].essentials)

    # `conversion_plus` technologies with tertiary carrier(s) out
    sets.loc_techs_out_3 = set(k for k in sets.loc_techs_conversion_plus
                               if "carrier_out_3" in model_run.techs[k.split(
                                   "::")[1].split(":")[0]].essentials)

    # `conversion_plus` technologies with secondary carrier(s) in
    sets.loc_techs_in_2 = set(k for k in sets.loc_techs_conversion_plus
                              if "carrier_in_2" in model_run.techs[k.split(
                                  "::")[1].split(":")[0]].essentials)

    # `conversion_plus` technologies with tertiary carrier(s) in
    sets.loc_techs_in_3 = set(k for k in sets.loc_techs_conversion_plus
                              if "carrier_in_3" in model_run.techs[k.split(
                                  "::")[1].split(":")[0]].essentials)

    ##
    # `loc_tech_carrier` sets
    ##

    # loc_tech_carriers for all technologies that have energy_prod=True
    sets.loc_tech_carriers_prod = set(
        "{}::{}".format(k, carrier) for k in sets.loc_techs
        if loc_techs_all_config[k].constraints.get_key("energy_prod", False)
        for carrier in get_all_carriers(model_run.techs[k.split("::")[1].split(
            ":")[0]].essentials,
                                        direction="out"))

    # loc_tech_carriers for all technologies that have energy_con=True
    sets.loc_tech_carriers_con = set(
        "{}::{}".format(k, carrier) for k in sets.loc_techs
        if loc_techs_all_config[k].constraints.get_key("energy_con", False)
        for carrier in get_all_carriers(model_run.techs[k.split("::")[1].split(
            ":")[0]].essentials,
                                        direction="in"))

    # loc_tech_carriers for all supply technologies
    sets.loc_tech_carriers_supply_all = set(
        "{}::{}".format(k, carrier) for k in sets.loc_techs_supply_all
        for carrier in get_all_carriers(model_run.techs[k.split("::")[1].split(
            ":")[0]].essentials,
                                        direction="out"))

    # loc_tech_carriers for all conversion technologies
    sets.loc_tech_carriers_conversion_all = set(
        "{}::{}".format(k, carrier) for k in sets.loc_techs_conversion_all
        for carrier in get_all_carriers(model_run.techs[k.split("::")[1].split(
            ":")[0]].essentials,
                                        direction="out"))

    # loc_tech_carriers for all supply and conversion technologies
    sets.loc_tech_carriers_supply_conversion_all = (
        sets.loc_tech_carriers_supply_all
        | sets.loc_tech_carriers_conversion_all)
    # loc_tech_carriers for all demand technologies
    sets.loc_tech_carriers_demand = set(
        "{}::{}".format(k, carrier) for k in sets.loc_techs_demand
        for carrier in get_all_carriers(model_run.techs[k.split("::")[1].split(
            ":")[0]].essentials,
                                        direction="in"))

    # loc_tech_carriers for all technologies that have export
    sets.loc_tech_carriers_export = set(
        "{}::{}".format(k, loc_techs_all_config[k].constraints.export_carrier)
        for k in sets.loc_techs if loc_techs_all_config[k].constraints.get_key(
            "export_carrier", False))

    # loc_tech_carriers for `conversion_plus` technologies
    sets.loc_tech_carriers_conversion_plus = set(
        k for k in sets.loc_tech_carriers_con | sets.loc_tech_carriers_prod
        if k.rsplit("::", 1)[0] in sets.loc_techs_conversion_plus)

    # loc_carrier combinations that exist with either a con or prod tech
    sets.loc_carriers = set("{0}::{2}".format(*k.split("::"))
                            for k in sets.loc_tech_carriers_prod
                            | sets.loc_tech_carriers_con)

    return sets