Ejemplo n.º 1
0
    def generation_emissions_per_bus_rule(model, bus):

        bus_emission_reference = get_co2_emission_level_for_country(
            bus, refyear)
        bus_emission_target = (1-reduction_share_per_country[bus]) * bus_emission_reference \
            * sum(net.snapshot_weightings['objective']) / 8760.

        bus_gens = net.generators[(net.generators.carrier.astype(bool))
                                  & (net.generators.bus == bus)]

        generator_emissions_sum = 0.
        for tech in bus_gens.type.unique():

            fuel, efficiency = get_tech_info(tech, ["fuel", "efficiency_ds"])
            fuel_emissions_el = get_fuel_info(fuel, ['CO2'])
            fuel_emissions_thermal = fuel_emissions_el / efficiency

            gens = bus_gens[bus_gens.type == tech]

            for g in gens.index.values:
                for s in net.snapshots:
                    generator_emissions_sum += model.generator_p[
                        g, s] * fuel_emissions_thermal.values[0]

        return generator_emissions_sum <= bus_emission_target
Ejemplo n.º 2
0
    def generation_emissions_rule(model):

        generator_emissions_sum = 0.
        for tech in gens.type.unique():

            fuel, efficiency = get_tech_info(tech, ["fuel", "efficiency_ds"])
            fuel_emissions_el = get_fuel_info(fuel, ['CO2'])
            fuel_emissions_thermal = fuel_emissions_el / efficiency

            gen = gens[gens.index.str.contains(tech)]

            for g in gen.index.values:
                for s in network.snapshots:
                    generator_emissions_sum += model.generator_p[
                        g, s] * fuel_emissions_thermal.values[0]

        return generator_emissions_sum <= co2_budget
Ejemplo n.º 3
0
def add_generators(network: pypsa.Network, tech: str) -> pypsa.Network:
    """
    Add conventional generators to a Network instance.

    Parameters
    ----------
    network: pypsa.Network
        A PyPSA Network instance with nodes associated to regions.
    tech: str
        Type of conventional generator (ccgt or ocgt)

    Returns
    -------
    network: pypsa.Network
        Updated network
    """
    logger.info(f"Adding {tech} generation.")

    assert hasattr(network.buses, "onshore_region"), "Some buses must be associated to an onshore region to add" \
                                                     "conventional generators."

    # Filter to keep only onshore buses
    # buses = network.buses[network.buses.onshore]
    buses = network.buses.dropna(subset=["onshore_region"], axis=0)

    capital_cost, marginal_cost = get_costs(
        tech, sum(network.snapshot_weightings['objective']))

    # Get fuel type and efficiency
    fuel, efficiency = get_tech_info(tech, ["fuel", "efficiency_ds"])

    network.madd("Generator",
                 buses.index,
                 suffix=f" Gen {tech}",
                 bus=buses.index,
                 p_nom_extendable=True,
                 type=tech,
                 carrier=fuel,
                 efficiency=efficiency,
                 marginal_cost=marginal_cost,
                 capital_cost=capital_cost,
                 x=buses.x.values,
                 y=buses.y.values)

    return network
Ejemplo n.º 4
0
def add_co2_budget_per_country(net: pypsa.Network,
                               co2_reduction_share: Dict[str, float],
                               co2_reduction_refyear: int):
    """
    Add CO2 budget per country.

    Parameters
    ----------
    net: pypsa.Network
        A PyPSA Network instance with buses associated to regions
    co2_reduction_share: float
        Percentage of reduction of emission.
    co2_reduction_refyear: int
        Reference year from which the reduction in emission is computed.

    """

    for bus in net.loads.bus:

        bus_emission_reference = get_co2_emission_level_for_country(
            bus, co2_reduction_refyear)
        co2_budget = (1-co2_reduction_share[bus]) * bus_emission_reference \
            * sum(net.snapshot_weightings['objective']) / 8760.

        # Drop rows (gens) without an associated carrier (i.e., technologies not emitting)
        gens = net.generators[(net.generators.carrier.astype(bool))
                              & (net.generators.bus == bus)]
        gens_p = get_var(net, 'Generator', 'p')[gens.index]

        coefficients = pd.DataFrame(index=gens_p.index,
                                    columns=gens_p.columns,
                                    dtype=float)
        for tech in gens.type.unique():
            fuel, efficiency = get_tech_info(tech, ["fuel", "efficiency_ds"])
            fuel_emissions_el = get_fuel_info(fuel, ['CO2'])
            fuel_emissions_thermal = fuel_emissions_el / efficiency

            gens_with_tech = gens[gens.index.str.contains(tech)]
            coefficients[
                gens_with_tech.index] = fuel_emissions_thermal.values[0]

        lhs = linexpr((coefficients, gens_p)).sum().sum()
        define_constraints(net, lhs, '<=', co2_budget,
                           'generation_emissions_global', bus)
Ejemplo n.º 5
0
def add_co2_budget_global(net: pypsa.Network, region: str,
                          co2_reduction_share: float,
                          co2_reduction_refyear: int):
    """
    Add global CO2 budget.

    Parameters
    ----------
    region: str
        Region over which the network is defined.
    net: pypsa.Network
        A PyPSA Network instance with buses associated to regions
    co2_reduction_share: float
        Percentage of reduction of emission.
    co2_reduction_refyear: int
        Reference year from which the reduction in emission is computed.

    """

    co2_reference_kt = get_reference_emission_levels_for_region(
        region, co2_reduction_refyear)
    co2_budget = co2_reference_kt * (1 - co2_reduction_share) * sum(
        net.snapshot_weightings['objective']) / 8760.

    gens = net.generators[net.generators.carrier.astype(bool)]
    gens_p = get_var(net, 'Generator', 'p')[gens.index]

    coefficients = pd.DataFrame(index=gens_p.index,
                                columns=gens_p.columns,
                                dtype=float)
    for tech in gens.type.unique():

        fuel, efficiency = get_tech_info(tech, ["fuel", "efficiency_ds"])
        fuel_emissions_el = get_fuel_info(fuel, ['CO2'])
        fuel_emissions_thermal = fuel_emissions_el / efficiency

        gens_with_tech = gens[gens.index.str.contains(tech)]
        coefficients[gens_with_tech.index] = fuel_emissions_thermal.values[0]

    lhs = linexpr((coefficients, gens_p)).sum().sum()
    define_constraints(net, lhs, '<=', co2_budget,
                       'generation_emissions_global')
Ejemplo n.º 6
0
def add_generators(net: pypsa.Network,
                   countries: List[str],
                   use_ex_cap: bool = True,
                   extendable: bool = False) -> pypsa.Network:
    """
    Add nuclear generators to a PyPsa Network instance.

    Parameters
    ----------
    net: pypsa.Network
        A Network instance with nodes associated to parameters: 'onshore' and 'region'.
    countries: List[str]
        Codes of countries over which the network is built
    use_ex_cap: bool (default: True)
        Whether to consider existing capacity or not
    extendable: bool (default: False)
        Whether generators are extendable

    Returns
    -------
    network: pypsa.Network
        Updated network
    """

    for attr in ["onshore_region"]:
        assert hasattr(
            net.buses,
            attr), f"Error: Buses must contain a '{attr}' attribute."

    # Nuclear plants can only be added onshore
    # onshore_buses = net.buses[net.buses.onshore]
    onshore_buses = net.buses.dropna(subset=["onshore_region"], axis=0)
    if len(onshore_buses) == 0:
        warn(
            "Warning: Trying to add nuclear to network without onshore buses.")
        return net

    gens = get_powerplants('nuclear', countries)
    buses_countries = list(onshore_buses.country) if hasattr(
        onshore_buses, 'country') else None
    gens["bus_id"] = match_powerplants_to_regions(
        gens,
        onshore_buses.onshore_region,
        shapes_countries=buses_countries,
        dist_threshold=50.0)

    # If no plants in the chosen countries, return directly the network
    if len(gens) == 0:
        return net

    logger.info(
        f"Adding {gens['Capacity'].sum() * 1e-3:.2f} GW of nuclear capacity "
        f"in {sorted(gens['ISO2'].unique())}.")

    if not use_ex_cap:
        gens.Capacity = 0.
    gens.Capacity /= 1000.  # Convert MW to GW

    capital_cost, marginal_cost = get_costs(
        'nuclear', sum(net.snapshot_weightings['objective']))

    # Get fuel type, efficiency and ramp rates
    fuel, efficiency, ramp_rate, base_level = \
        get_tech_info('nuclear', ["fuel", "efficiency_ds", "ramp_rate", "base_level"])

    net.madd("Generator",
             "Gen nuclear " + gens.Name + " " + gens.bus_id,
             bus=gens.bus_id.values,
             p_nom=gens.Capacity.values,
             p_nom_min=gens.Capacity.values,
             p_nom_extendable=extendable,
             type='nuclear',
             carrier=fuel,
             efficiency=efficiency,
             marginal_cost=marginal_cost,
             capital_cost=capital_cost,
             ramp_limit_up=ramp_rate,
             ramp_limit_down=ramp_rate,
             p_min_pu=base_level,
             x=gens.lon.values,
             y=gens.lat.values)

    return net
Ejemplo n.º 7
0
def add_phs_plants(net: pypsa.Network,
                   topology_type: str = "countries",
                   extendable: bool = False,
                   cyclic_sof: bool = True) -> pypsa.Network:
    """
    Add pumped-hydro storage units to a PyPSA Network instance.

    Parameters
    ----------
    net: pypsa.Network
        A Network instance.
    topology_type: str
        Can currently be countries (for one node per country topologies) or ehighway (for topologies based on ehighway)
    extendable: bool (default: False)
        Whether generators are extendable
    cyclic_sof: bool (default: True)
        Whether to set to True the cyclic_state_of_charge for the storage_unit component

    Returns
    -------
    net: pypsa.Network
        Updated network
    """
    check_assertions(net, topology_type)

    # Hydro generators can only be added onshore
    buses_onshore = net.buses.dropna(subset=["onshore_region"], axis=0)

    # Load capacities
    aggr_level = "countries" if topology_type == "countries" else "NUTS3"
    pow_cap, en_cap = get_phs_capacities(aggr_level)

    if topology_type == 'countries':
        # Extract only countries for which data is available
        countries_with_capacity = sorted(
            list(set(buses_onshore.country) & set(pow_cap.index)))
        buses_with_capacity_indexes = net.buses[net.buses.country.isin(
            countries_with_capacity)].index
        bus_pow_cap = pow_cap.loc[countries_with_capacity]
        bus_pow_cap.index = buses_with_capacity_indexes
        bus_en_cap = en_cap.loc[countries_with_capacity]
        bus_en_cap.index = buses_with_capacity_indexes
    else:  # topology_type == 'ehighway
        bus_pow_cap, bus_en_cap = phs_inputs_nuts_to_ehighway(
            buses_onshore.index, pow_cap, en_cap)
        countries_with_capacity = set(bus_pow_cap.index.str[2:])

    logger.info(
        f"Adding {bus_pow_cap.sum():.3f} GW of PHS hydro "
        f"with {bus_en_cap.sum():.3f} GWh of storage in {countries_with_capacity}."
    )

    max_hours = bus_en_cap / bus_pow_cap

    # Get cost and efficiencies
    capital_cost, marginal_cost = get_costs(
        'phs', sum(net.snapshot_weightings['objective']))
    efficiency_dispatch, efficiency_store, self_discharge = \
        get_tech_info('phs', ["efficiency_ds", "efficiency_ch", "efficiency_sd"])
    self_discharge = round(1 - self_discharge, 4)

    net.madd("StorageUnit",
             bus_pow_cap.index,
             suffix=" Storage PHS",
             bus=bus_pow_cap.index,
             type='phs',
             p_nom=bus_pow_cap,
             p_nom_min=bus_pow_cap,
             p_nom_extendable=extendable,
             max_hours=max_hours.values,
             capital_cost=capital_cost,
             marginal_cost=marginal_cost,
             efficiency_store=efficiency_store,
             efficiency_dispatch=efficiency_dispatch,
             self_discharge=self_discharge,
             cyclic_state_of_charge=cyclic_sof,
             x=buses_onshore.loc[bus_pow_cap.index].x,
             y=buses_onshore.loc[bus_pow_cap.index].y)

    return net
Ejemplo n.º 8
0
def add_sto_plants(net: pypsa.Network,
                   topology_type: str = "countries",
                   extendable: bool = False,
                   cyclic_sof: bool = True) -> pypsa.Network:
    """
    Add run-of-river generators to a Network instance

    Parameters
    ----------
    net: pypsa.Network
        A Network instance.
    topology_type: str
        Can currently be countries (for one node per country topologies) or ehighway (for topologies based on ehighway)
    extendable: bool (default: False)
        Whether generators are extendable
    cyclic_sof: bool (default: True)
        Whether to set to True the cyclic_state_of_charge for the storage_unit component

    Returns
    -------
    net: pypsa.Network
        Updated network
    """
    check_assertions(net, topology_type)

    # Hydro generators can only be added onshore
    buses_onshore = net.buses.dropna(subset=["onshore_region"], axis=0)

    # Load capacities and inflows
    aggr_level = "countries" if topology_type == "countries" else "NUTS3"
    pow_cap, en_cap = get_sto_capacities(aggr_level)
    inflows = get_sto_inflows(aggr_level, net.snapshots)

    if topology_type == 'countries':
        # Extract only countries for which data is available
        countries_with_capacity = sorted(
            list(set(buses_onshore.country) & set(pow_cap.index)))
        buses_with_capacity_indexes = net.buses[net.buses.country.isin(
            countries_with_capacity)].index
        bus_pow_cap = pow_cap.loc[countries_with_capacity]
        bus_pow_cap.index = buses_with_capacity_indexes
        bus_en_cap = en_cap.loc[countries_with_capacity]
        bus_en_cap.index = buses_with_capacity_indexes
        bus_inflows = inflows[countries_with_capacity]
        bus_inflows.columns = buses_with_capacity_indexes
    else:  # topology_type == 'ehighway'
        bus_pow_cap, bus_en_cap, bus_inflows = \
            sto_inputs_nuts_to_ehighway(buses_onshore.index, pow_cap, en_cap, inflows)
        countries_with_capacity = set(bus_pow_cap.index.str[2:])

    logger.info(
        f"Adding {bus_pow_cap.sum():.2f} GW of STO hydro "
        f"with {bus_en_cap.sum() * 1e-3:.2f} TWh of storage in {countries_with_capacity}."
    )
    bus_inflows = bus_inflows.round(3)

    max_hours = bus_en_cap / bus_pow_cap

    capital_cost, marginal_cost = get_costs(
        'sto', sum(net.snapshot_weightings['objective']))

    # Get efficiencies
    efficiency_dispatch = get_tech_info('sto',
                                        ['efficiency_ds'])["efficiency_ds"]

    net.madd("StorageUnit",
             bus_pow_cap.index,
             suffix=" Storage reservoir",
             bus=bus_pow_cap.index,
             type='sto',
             p_nom=bus_pow_cap,
             p_nom_min=bus_pow_cap,
             p_min_pu=0.,
             p_nom_extendable=extendable,
             capital_cost=capital_cost,
             marginal_cost=marginal_cost,
             efficiency_store=0.,
             efficiency_dispatch=efficiency_dispatch,
             cyclic_state_of_charge=cyclic_sof,
             max_hours=max_hours,
             inflow=bus_inflows,
             x=buses_onshore.loc[bus_pow_cap.index.values].x,
             y=buses_onshore.loc[bus_pow_cap.index.values].y)

    return net
Ejemplo n.º 9
0
def add_ror_plants(net: pypsa.Network,
                   topology_type: str = "countries",
                   extendable: bool = False) -> pypsa.Network:
    """
    Add run-of-river generators to a Network instance.

    Parameters
    ----------
    net: pypsa.Network
        A Network instance.
    topology_type: str
        Can currently be countries (for one node per country topologies) or ehighway (for topologies based on ehighway)
    extendable: bool (default: False)
        Whether generators are extendable

    Returns
    -------
    net: pypsa.Network
        Updated network
    """
    check_assertions(net, topology_type)

    # Hydro generators can only be added onshore
    buses_onshore = net.buses.dropna(subset=["onshore_region"], axis=0)

    # Load capacities and inflows
    aggr_level = "countries" if topology_type == "countries" else "NUTS3"
    pow_cap = get_ror_capacities(aggr_level)
    inflows = get_ror_inflows(aggr_level, net.snapshots)

    if topology_type == 'countries':
        # Extract only countries for which data is available
        countries_with_capacity = sorted(
            list(set(buses_onshore.country) & set(pow_cap.index)))
        buses_with_capacity_indexes = net.buses[net.buses.country.isin(
            countries_with_capacity)].index
        bus_pow_cap = pow_cap.loc[countries_with_capacity]
        bus_pow_cap.index = buses_with_capacity_indexes
        bus_inflows = inflows[countries_with_capacity]
        bus_inflows.columns = buses_with_capacity_indexes
    else:  # topology_type == 'ehighway'
        bus_pow_cap, bus_inflows = \
            ror_inputs_nuts_to_ehighway(buses_onshore.index, pow_cap, inflows)
        countries_with_capacity = set(bus_pow_cap.index.str[2:])

    logger.info(
        f"Adding {bus_pow_cap.sum():.2f} GW of ROR hydro in {countries_with_capacity}."
    )

    bus_inflows = bus_inflows.dropna().round(3)

    # Get cost and efficiencies
    capital_cost, marginal_cost = get_costs(
        'ror', sum(net.snapshot_weightings['objective']))
    efficiency = get_tech_info('ror', ["efficiency_ds"])["efficiency_ds"]

    net.madd("Generator",
             bus_pow_cap.index,
             suffix=" Generator ror",
             bus=bus_pow_cap.index,
             type='ror',
             p_nom=bus_pow_cap,
             p_nom_min=bus_pow_cap,
             p_nom_extendable=extendable,
             capital_cost=capital_cost,
             marginal_cost=marginal_cost,
             efficiency=efficiency,
             p_min_pu=0.,
             p_max_pu=bus_inflows,
             x=buses_onshore.loc[bus_pow_cap.index].x,
             y=buses_onshore.loc[bus_pow_cap.index].y)

    return net
Ejemplo n.º 10
0
def add_batteries(network: pypsa.Network,
                  battery_type: str,
                  buses_ids: List[str] = None,
                  fixed_duration: bool = False) -> pypsa.Network:
    """
    Add a battery at each node of the network.

    Parameters
    ----------
    network: pypsa.Network
        PyPSA network
    battery_type: str
        Type of battery to add
    buses_ids: List[str]
        IDs of the buses at which we want to add batteries.
    fixed_duration: bool
        Whether the battery storage is modelled with fixed duration.

    Returns
    -------
    network: pypsa.Network
        Updated network

    """
    logger.info(f"Adding {battery_type} storage.")

    buses = network.buses
    if buses_ids is not None:
        buses = buses.loc[buses_ids]

    # buses = network.buses[network.buses.onshore]
    # onshore_bus_indexes = pd.Index([bus_id for bus_id in buses.index if buses.loc[bus_id].onshore])
    onshore_buses = buses.dropna(subset=["onshore_region"], axis=0)

    # Add batteries with fixed energy-power ratio
    if fixed_duration:

        capital_cost, marginal_cost = get_costs(
            battery_type, sum(network.snapshot_weightings['objective']))
        efficiency_dispatch, efficiency_store, self_discharge = \
            get_tech_info(battery_type, ["efficiency_ds", "efficiency_ch", "efficiency_sd"])
        self_discharge = round(1 - self_discharge, 4)

        # Get max number of hours of storage
        max_hours = get_config_values(battery_type, ["max_hours"])

        network.madd("StorageUnit",
                     onshore_buses.index,
                     suffix=f" StorageUnit {battery_type}",
                     type=battery_type,
                     bus=onshore_buses.index,
                     p_nom_extendable=True,
                     max_hours=max_hours,
                     capital_cost=capital_cost,
                     marginal_cost=marginal_cost,
                     efficiency_dispatch=efficiency_dispatch,
                     efficiency_store=efficiency_store,
                     standing_loss=self_discharge)

    # Add batteries where energy and power are sized independently
    else:

        battery_type_power = battery_type + '_p'
        battery_type_energy = battery_type + '_e'

        capital_cost, marginal_cost = get_costs(
            battery_type_power, sum(network.snapshot_weightings['objective']))
        capital_cost_e, marginal_cost_e = get_costs(
            battery_type_energy, sum(network.snapshot_weightings['objective']))
        efficiency_dispatch, efficiency_store = get_tech_info(
            battery_type_power, ["efficiency_ds", "efficiency_ch"])
        self_discharge = get_tech_info(battery_type_energy,
                                       ["efficiency_sd"]).astype(float)
        self_discharge = round(1 - self_discharge.values[0], 4)
        ctd_ratio = get_config_values(battery_type_power, ["ctd_ratio"])

        network.madd("StorageUnit",
                     onshore_buses.index,
                     suffix=f" StorageUnit {battery_type}",
                     type=battery_type,
                     bus=onshore_buses.index,
                     p_nom_extendable=True,
                     capital_cost=capital_cost,
                     marginal_cost=marginal_cost,
                     capital_cost_e=capital_cost_e,
                     marginal_cost_e=marginal_cost_e,
                     efficiency_dispatch=efficiency_dispatch,
                     efficiency_store=efficiency_store,
                     standing_loss=self_discharge,
                     ctd_ratio=ctd_ratio)

        storages = network.storage_units.index[network.storage_units.type ==
                                               battery_type]
        for storage_to_replace in storages:
            replace_su_closed_loop(network, storage_to_replace)

    return network