Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions config/config.default.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,8 @@ renewable:
natura: true
excluder_resolution: 100
clip_p_max_pu: 0.01
dc_ac_ratio: 1.25
costs_given_for_ac: true
"solar-hsat":
cutout: default
resource:
Expand Down Expand Up @@ -404,6 +406,8 @@ renewable:
natura: true
excluder_resolution: 100
clip_p_max_pu: 0.01
dc_ac_ratio: 1.25
costs_given_for_ac: true
hydro:
cutout: default
carriers:
Expand Down
55 changes: 55 additions & 0 deletions config/schema.default.json
Original file line number Diff line number Diff line change
Expand Up @@ -3771,6 +3771,17 @@
"default": 0.01,
"description": "To avoid too small values in the renewables` per-unit availability time series values below this threshold are set to zero.",
"type": "number"
},
"dc_ac_ratio": {
"default": 1.25,
"description": "DC/AC ratio for solar PV. Capacity factors are clipped at 1/dc_ac_ratio and rescaled to express output as a fraction of AC capacity.",
"minimum": 1,
"type": "number"
},
"costs_given_for_ac": {
"default": true,
"description": "Switch to indicate that the capital_costs for solar are given for AC capacity. If `false`, costs are given for DC peak capacity.",
"type": "boolean"
}
}
},
Expand Down Expand Up @@ -3895,6 +3906,17 @@
"default": 0.01,
"description": "To avoid too small values in the renewables` per-unit availability time series values below this threshold are set to zero.",
"type": "number"
},
"dc_ac_ratio": {
"default": 1.25,
"description": "DC/AC ratio for solar PV. Capacity factors are clipped at 1/dc_ac_ratio and rescaled to express output as a fraction of AC capacity.",
"minimum": 1,
"type": "number"
},
"costs_given_for_ac": {
"default": true,
"description": "Switch to indicate that the capital_costs for solar are given for AC capacity. If `false`, costs are given for DC peak capacity.",
"type": "boolean"
}
}
},
Expand Down Expand Up @@ -7609,6 +7631,17 @@
"default": 0.01,
"description": "To avoid too small values in the renewables` per-unit availability time series values below this threshold are set to zero.",
"type": "number"
},
"dc_ac_ratio": {
"default": 1.25,
"description": "DC/AC ratio for solar PV. Capacity factors are clipped at 1/dc_ac_ratio and rescaled to express output as a fraction of AC capacity.",
"minimum": 1,
"type": "number"
},
"costs_given_for_ac": {
"default": true,
"description": "Switch to indicate that the capital_costs for solar are given for AC capacity. If `false`, costs are given for DC peak capacity.",
"type": "boolean"
}
}
},
Expand Down Expand Up @@ -9650,6 +9683,17 @@
"default": 0.01,
"description": "To avoid too small values in the renewables` per-unit availability time series values below this threshold are set to zero.",
"type": "number"
},
"dc_ac_ratio": {
"default": 1.25,
"description": "DC/AC ratio for solar PV. Capacity factors are clipped at 1/dc_ac_ratio and rescaled to express output as a fraction of AC capacity.",
"minimum": 1,
"type": "number"
},
"costs_given_for_ac": {
"default": true,
"description": "Switch to indicate that the capital_costs for solar are given for AC capacity. If `false`, costs are given for DC peak capacity.",
"type": "boolean"
}
}
},
Expand Down Expand Up @@ -9774,6 +9818,17 @@
"default": 0.01,
"description": "To avoid too small values in the renewables` per-unit availability time series values below this threshold are set to zero.",
"type": "number"
},
"dc_ac_ratio": {
"default": 1.25,
"description": "DC/AC ratio for solar PV. Capacity factors are clipped at 1/dc_ac_ratio and rescaled to express output as a fraction of AC capacity.",
"minimum": 1,
"type": "number"
},
"costs_given_for_ac": {
"default": true,
"description": "Switch to indicate that the capital_costs for solar are given for AC capacity. If `false`, costs are given for DC peak capacity.",
"type": "boolean"
}
}
},
Expand Down
9 changes: 9 additions & 0 deletions scripts/add_electricity.py
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,7 @@ def attach_wind_and_solar(
extendable_carriers: list | set,
line_length_factor: float = 1.0,
landfall_lengths: dict = None,
params_renewable: dict = None,
) -> None:
"""
Attach wind and solar generators to the network.
Expand Down Expand Up @@ -543,6 +544,13 @@ def attach_wind_and_solar(
logger.info(
f"Added connection cost of {connection_cost.min():0.0f}-{connection_cost.max():0.0f} Eur/MW/a to {car}"
)
elif (
params_renewable[car].get("costs_given_for_ac")
and params_renewable[car].get("dc_ac_ratio", 1.0) != 1.0
):
capital_cost = (
costs.at[car, "capital_cost"] / params_renewable[car]["dc_ac_ratio"]
)
else:
capital_cost = costs.at[car, "capital_cost"]

Expand Down Expand Up @@ -1269,6 +1277,7 @@ def attach_stores(
extendable_carriers,
params.line_length_factor,
landfall_lengths,
params.renewable,
)

if "hydro" in renewable_carriers:
Expand Down
7 changes: 7 additions & 0 deletions scripts/build_renewable_profiles.py
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,13 @@
)
)

dc_ac_ratio = params.get("dc_ac_ratio", 1.0)
if dc_ac_ratio != 1.0:
logger.info(
f"Applying DC/AC ratio of {dc_ac_ratio} by clipping solar profiles at {1 / dc_ac_ratio:.2f}."
)
ds["profile"] = ds["profile"].clip(max=1 / dc_ac_ratio)

if "clip_p_max_pu" in params:
min_p_max_pu = params["clip_p_max_pu"]
ds["profile"] = ds["profile"].where(ds["profile"] >= min_p_max_pu, 0)
Expand Down
9 changes: 9 additions & 0 deletions scripts/lib/validation/config/renewable.py
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,15 @@ class _SolarConfig(BaseModel):
0.01,
description="To avoid too small values in the renewables` per-unit availability time series values below this threshold are set to zero.",
)
dc_ac_ratio: float = Field(
1.25,
description="DC/AC ratio for solar PV. Capacity factors are clipped at 1/dc_ac_ratio and rescaled to express output as a fraction of AC capacity.",
ge=1,
)
costs_given_for_ac: bool = Field(
True,
description="Switch to indicate that the capital_costs for solar are given for AC capacity. If `false`, costs are given for DC peak capacity.",
)


class _HydroConfig(BaseModel):
Expand Down
64 changes: 51 additions & 13 deletions scripts/prepare_sector_network.py
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,7 @@ def update_wind_solar_costs(
profiles: dict[str, str],
landfall_lengths: dict = None,
line_length_factor: int | float = 1,
params_renewable: dict = None,
) -> None:
"""
Update costs for wind and solar generators added with pypsa-eur to those
Expand All @@ -445,6 +446,8 @@ def update_wind_solar_costs(
profiles : dict[str, str]
Dictionary mapping technology names to profile file paths
e.g. {'offwind-dc': 'path/to/profile.nc'}
params_renewable : dict, optional
Dictionary of renewable parameters, by default None
"""

if landfall_lengths is None:
Expand All @@ -464,6 +467,13 @@ def update_wind_solar_costs(
n.generators.loc[n.generators.carrier == carrier, "capital_cost"] = costs.at[
cost_key, "capital_cost"
]
if (
params_renewable[carrier].get("costs_given_for_ac")
and params_renewable[carrier].get("dc_ac_ratio", 1.0) != 1.0
):
n.generators.loc[n.generators.carrier == carrier, "capital_cost"] /= (
params_renewable[carrier]["dc_ac_ratio"]
)

# for offshore wind, need to calculated connection costs
for key, fn in profiles.items():
Expand Down Expand Up @@ -689,11 +699,17 @@ def remove_non_electric_buses(n):
n.buses = n.buses[n.buses.carrier.isin(["AC", "DC"])]


def patch_electricity_network(n, costs, carriers_to_keep, profiles, landfall_lengths):
def patch_electricity_network(
n, costs, carriers_to_keep, profiles, landfall_lengths, params_renewable=None
):
remove_elec_base_techs(n, carriers_to_keep)
remove_non_electric_buses(n)
update_wind_solar_costs(
n, costs, landfall_lengths=landfall_lengths, profiles=profiles
n,
costs,
landfall_lengths=landfall_lengths,
profiles=profiles,
params_renewable=params_renewable,
)
n.loads["carrier"] = "electricity"
n.buses["location"] = n.buses.index
Expand Down Expand Up @@ -1505,6 +1521,7 @@ def insert_electricity_distribution_grid(
options: dict,
pop_layout: pd.DataFrame,
solar_rooftop_potentials_fn: str,
params_renewable: dict,
) -> None:
"""
Insert electricity distribution grid components into the network.
Expand All @@ -1529,6 +1546,8 @@ def insert_electricity_distribution_grid(
Population data per node with at least:
- 'total' column containing population in thousands
Index should match network nodes
params_renewable : dict
Renewable energy parameters

Returns
-------
Expand Down Expand Up @@ -1611,11 +1630,18 @@ def insert_electricity_distribution_grid(
solar = n.generators.index[n.generators.carrier == "solar"]
n.generators.loc[solar, "capital_cost"] = costs.at["solar-utility", "capital_cost"]

dc_ac_ratio = params_renewable["solar"].get("dc_ac_ratio", 1.0)
if params_renewable["solar"].get("costs_given_for_ac") and dc_ac_ratio != 1.0:
n.generators.loc[solar, "capital_cost"] /= dc_ac_ratio

fn = solar_rooftop_potentials_fn
if len(fn) > 0:
potential = pd.read_csv(fn, index_col=["bus", "bin"]).squeeze(axis=1)
potential.index = potential.index.map(flatten) + " solar"

capital_cost = costs.at["solar rooftop", "capital_cost"]
if params_renewable["solar"].get("costs_given_for_ac") and dc_ac_ratio != 1.0:
capital_cost /= dc_ac_ratio
n.add(
"Generator",
solar,
Expand All @@ -1625,7 +1651,7 @@ def insert_electricity_distribution_grid(
p_nom_extendable=True,
p_nom_max=potential.loc[solar],
marginal_cost=n.generators.loc[solar, "marginal_cost"],
capital_cost=costs.at["solar-rooftop", "capital_cost"],
capital_cost=capital_cost,
efficiency=n.generators.loc[solar, "efficiency"],
p_max_pu=n.generators_t.p_max_pu[solar],
lifetime=costs.at["solar-rooftop", "lifetime"],
Expand Down Expand Up @@ -1737,14 +1763,14 @@ def insert_gas_distribution_costs(
n.links.loc[mchp, "capital_cost"] += capital_cost


def add_electricity_grid_connection(n, costs):
def add_electricity_grid_connection(n, costs, params_renewables):
carriers = ["onwind", "solar", "solar-hsat"]

gens = n.generators.index[n.generators.carrier.isin(carriers)]

n.generators.loc[gens, "capital_cost"] += costs.at[
"electricity grid connection", "capital_cost"
]
for car in carriers:
gens = n.generators.index[n.generators.carrier == car]
dc_ac_ratio = params_renewables[car].get("dc_ac_ratio", 1.0)
n.generators.loc[gens, "capital_cost"] += (
costs.at["electricity grid connection", "capital_cost"] / dc_ac_ratio
)


def add_h2_gas_infrastructure(
Expand Down Expand Up @@ -6287,7 +6313,14 @@ def add_import_options(
for tech, settings in snakemake.params.renewable.items()
if "landfall_length" in settings.keys()
}
patch_electricity_network(n, costs, carriers_to_keep, profiles, landfall_lengths)
patch_electricity_network(
n,
costs,
carriers_to_keep,
profiles,
landfall_lengths,
snakemake.params.renewable,
)

fn = snakemake.input.heating_efficiencies
year = int(snakemake.params["energy_totals_year"])
Expand Down Expand Up @@ -6548,7 +6581,12 @@ def add_import_options(

if options["electricity_distribution_grid"]:
insert_electricity_distribution_grid(
n, costs, options, pop_layout, snakemake.input.solar_rooftop_potentials
n,
costs,
options,
pop_layout,
snakemake.input.solar_rooftop_potentials,
snakemake.params.renewable,
)

if options["enhanced_geothermal"].get("enable", False):
Expand All @@ -6570,7 +6608,7 @@ def add_import_options(
insert_gas_distribution_costs(n, costs, options=options)

if options["electricity_grid_connection"]:
add_electricity_grid_connection(n, costs)
add_electricity_grid_connection(n, costs, snakemake.params["renewable"])

for k, v in options["transmission_efficiency"].items():
if k in options["transmission_efficiency"]["enable"]:
Expand Down
Loading