Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Resource Classes #1481

Open
wants to merge 31 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
da02898
build_renewable_profiles: implement resource classes dim
fneum Jan 3, 2025
76881b6
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jan 3, 2025
641853e
add_electricity: add renewable generators per resource class
fneum Jan 4, 2025
3cad80b
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jan 4, 2025
46ccd6d
add_electricity: for current RES use GEM not OPSD
fneum Jan 4, 2025
c50cd0c
implement resource class split per bus
fneum Jan 6, 2025
fe09657
reorder indicatormatrix calculation
fneum Jan 6, 2025
32fa01a
calculate clustered regions split by resource classes
fneum Jan 6, 2025
d5ffa3e
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jan 6, 2025
717909c
distinguish between regions for distance and resource calculations
fneum Jan 6, 2025
7c6ef96
Merge branch 'resource-classes' of github.com:pypsa/pypsa-eur into re…
fneum Jan 6, 2025
4ecb495
correct file export
fneum Jan 6, 2025
b9ae6b0
typo correction
fneum Jan 6, 2025
454f473
attach_GEM_renewables: attach to correct resource class
fneum Jan 6, 2025
0eb2683
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jan 6, 2025
b8f2d21
adjust estimate_renewable_capacities
fneum Jan 6, 2025
e3cb8f7
distinguish offshore/onshore regions for resource class masks
fneum Jan 6, 2025
e00980c
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jan 6, 2025
e106ad6
class_regions: add shortcut for single resource class
fneum Jan 6, 2025
875c347
prepare_sector: resource classes for rooftop PV and updated offwind c…
fneum Jan 6, 2025
97b9333
build_renewable_profiles: add distance and resource regions as inputs
fneum Jan 6, 2025
85eb183
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jan 6, 2025
e377c49
build_sector: check config for solar rooftop
fneum Jan 6, 2025
51c45ce
add_existing_baseyear: compat with resource classes
fneum Jan 6, 2025
50be5d6
solve: add_land_use_constraint resource class compat
fneum Jan 6, 2025
bab427d
add_brownfield: compat with resource classes
fneum Jan 6, 2025
fc2c891
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jan 6, 2025
c8bac30
build_renewable_profiles: update docstring
fneum Jan 6, 2025
2659938
Merge branch 'master' into resource-classes
fneum Jan 6, 2025
7f973a2
exclude hydro from resource classes
fneum Jan 24, 2025
1d44339
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jan 24, 2025
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
14 changes: 10 additions & 4 deletions config/config.default.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -118,13 +118,13 @@ electricity:

estimate_renewable_capacities:
enable: true
from_opsd: true
from_gem: true
year: 2020
expansion_limit: false
technology_mapping:
Offshore: [offwind-ac, offwind-dc, offwind-float]
Onshore: [onwind]
PV: [solar]
Offshore: offwind-ac
Onshore: onwind
PV: solar

autarky:
enable: false
Expand Down Expand Up @@ -156,6 +156,7 @@ renewable:
turbine: Vestas_V112_3MW
smooth: false
add_cutout_windspeed: true
resource_classes: 1
capacity_per_sqkm: 3
# correction_factor: 0.93
corine:
Expand All @@ -176,6 +177,7 @@ renewable:
turbine: NREL_ReferenceTurbine_2020ATB_5.5MW
smooth: false
add_cutout_windspeed: true
resource_classes: 1
capacity_per_sqkm: 2
correction_factor: 0.8855
corine: [44, 255]
Expand All @@ -194,6 +196,7 @@ renewable:
turbine: NREL_ReferenceTurbine_2020ATB_5.5MW
smooth: false
add_cutout_windspeed: true
resource_classes: 1
capacity_per_sqkm: 2
correction_factor: 0.8855
corine: [44, 255]
Expand All @@ -212,6 +215,7 @@ renewable:
turbine: NREL_ReferenceTurbine_5MW_offshore
smooth: false
add_cutout_windspeed: true
resource_classes: 1
# ScholzPhd Tab 4.3.1: 10MW/km^2
capacity_per_sqkm: 2
correction_factor: 0.8855
Expand All @@ -234,6 +238,7 @@ renewable:
orientation:
slope: 35.
azimuth: 180.
resource_classes: 1
capacity_per_sqkm: 5.1
# correction_factor: 0.854337
corine: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 26, 31, 32]
Expand All @@ -250,6 +255,7 @@ renewable:
slope: 35.
azimuth: 180.
tracking: horizontal
resource_classes: 1
capacity_per_sqkm: 4.43 # 15% higher land usage acc. to NREL
corine: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 26, 31, 32]
luisa: false # [1111, 1121, 1122, 1123, 1130, 1210, 1221, 1222, 1230, 1241, 1242, 1310, 1320, 1330, 1410, 1421, 1422, 2110, 2120, 2130, 2210, 2220, 2230, 2310, 2410, 2420, 3210, 3320, 3330]
Expand Down
8 changes: 4 additions & 4 deletions doc/configtables/electricity.csv
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,13 @@ conventional_carriers,--,"Any subset of {nuclear, oil, OCGT, CCGT, coal, lignite
renewable_carriers,--,"Any subset of {solar, onwind, offwind-ac, offwind-dc, offwind-float, hydro}",List of renewable generators to include in the model.
estimate_renewable_capacities,,,
-- enable,,bool,Activate routine to estimate renewable capacities in rule :mod:`add_electricity`. This option should not be used in combination with pathway planning ``foresight: myopic`` or ``foresight: perfect`` as renewable capacities are added differently in :mod:`add_existing_baseyear`.
-- from_opsd,--,bool,Add renewable capacities from `OPSD database <https://data.open-power-system-data.org/renewable_power_plants/2020-08-25>`_. The value is depreciated but still can be used.
-- from_gem,--,bool,Add renewable capacities from `Global Energy Monitor's Global Solar Power Tracker <https://globalenergymonitor.org/projects/global-solar-power-tracker/>`_ and `Global Energy Monitor's Global Wind Power Tracker <https://globalenergymonitor.org/projects/global-wind-power-tracker/>`_.
-- year,--,bool,Renewable capacities are based on existing capacities reported by IRENA (IRENASTAT) for the specified year
-- expansion_limit,--,float or false,"Artificially limit maximum IRENA capacities to a factor. For example, an ``expansion_limit: 1.1`` means 110% of capacities . If false are chosen, the estimated renewable potentials determine by the workflow are used."
-- technology_mapping,,,Mapping between PyPSA-Eur and powerplantmatching technology names
-- -- Offshore,--,"Any subset of {offwind-ac, offwind-dc, offwind-float}","List of PyPSA-Eur carriers that is considered as (IRENA, OPSD) onshore technology."
-- -- Offshore,--,{onwind},"List of PyPSA-Eur carriers that is considered as (IRENA, OPSD) offshore technology."
-- -- PV,--,{solar},"List of PyPSA-Eur carriers that is considered as (IRENA, OPSD) PV technology."
-- -- Offshore,--,"{onwind}","PyPSA-Eur carrier that is considered for existing onshore wind capacities (IRENA, GEM)."
-- -- Offshore,--,"Any of {offwind-ac, offwind-dc, offwind-float}","PyPSA-Eur carrier that is considered for existing offshore wind technology (IRENA, GEM)."
-- -- PV,--,{solar},"PyPSA-Eur carrier that is considered for existing solar PV capacities (IRENA, GEM)."
autarky,,,
-- enable,bool,true or false,Require each node to be autarkic by removing all lines and links.
-- by_country,bool,true or false,Require each country to be autarkic by removing all cross-border lines and links. ``electricity: autarky`` must be enabled.
1 change: 1 addition & 0 deletions doc/configtables/offwind-ac.csv
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ resource,,,
-- method,--,"Must be 'wind'","A superordinate technology type."
-- turbine,--,"One of turbine types included in `atlite <https://github.com/PyPSA/atlite/tree/master/atlite/resources/windturbine>`_. Can be a string or a dictionary with years as keys which denote the year another turbine model becomes available.","Specifies the turbine type and its characteristic power curve."
-- smooth,--,"{True, False}","Switch to apply a gaussian kernel density smoothing to the power curve."
resource_classes,--,int,"Number of resource classes per clustered region."
capacity_per_sqkm,:math:`MW/km^2`,float,"Allowable density of wind turbine placement."
correction_factor,--,float,"Correction factor for capacity factor time series."
excluder_resolution,m,float,"Resolution on which to perform geographical elibility analysis."
Expand Down
1 change: 1 addition & 0 deletions doc/configtables/offwind-dc.csv
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ resource,,,
-- method,--,"Must be 'wind'","A superordinate technology type."
-- turbine,--,"One of turbine types included in `atlite <https://github.com/PyPSA/atlite/tree/master/atlite/resources/windturbine>`_. Can be a string or a dictionary with years as keys which denote the year another turbine model becomes available.","Specifies the turbine type and its characteristic power curve."
-- smooth,--,"{True, False}","Switch to apply a gaussian kernel density smoothing to the power curve."
resource_classes,--,int,"Number of resource classes per clustered region."
capacity_per_sqkm,:math:`MW/km^2`,float,"Allowable density of wind turbine placement."
correction_factor,--,float,"Correction factor for capacity factor time series."
excluder_resolution,m,float,"Resolution on which to perform geographical elibility analysis."
Expand Down
1 change: 1 addition & 0 deletions doc/configtables/onwind.csv
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ resource,,,
-- method,--,"Must be 'wind'","A superordinate technology type."
-- turbine,--,"One of turbine types included in `atlite <https://github.com/PyPSA/atlite/tree/master/atlite/resources/windturbine>`_. Can be a string or a dictionary with years as keys which denote the year another turbine model becomes available.","Specifies the turbine type and its characteristic power curve."
-- smooth,--,"{True, False}","Switch to apply a gaussian kernel density smoothing to the power curve."
resource_classes,--,int,"Number of resource classes per clustered region."
capacity_per_sqkm,:math:`MW/km^2`,float,"Allowable density of wind turbine placement."
corine,,,
-- grid_codes,--,"Any subset of the `CORINE Land Cover code list <http://www.eea.europa.eu/data-and-maps/data/corine-land-cover-2006-raster-1/corine-land-cover-classes-and/clc_legend.csv/at_download/file>`_","Specifies areas according to CORINE Land Cover codes which are generally eligible for wind turbine placement."
Expand Down
1 change: 1 addition & 0 deletions doc/configtables/solar.csv
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ resource,,,
-- orientation,,,
-- -- slope,°,"Realistically any angle in [0., 90.]","Specifies the tilt angle (or slope) of the solar panel. A slope of zero corresponds to the face of the panel aiming directly overhead. A positive tilt angle steers the panel towards the equator."
-- -- azimuth,°,"Any angle in [0., 360.]","Specifies the `azimuth <https://en.wikipedia.org/wiki/Azimuth>`_ orientation of the solar panel. South corresponds to 180.°."
resource_classes,--,int,"Number of resource classes per clustered region."
capacity_per_sqkm,:math:`MW/km^2`,float,"Allowable density of solar panel placement."
correction_factor,--,float,"A correction factor for the capacity factor (availability) time series."
corine,--,"Any subset of the `CORINE Land Cover code list <http://www.eea.europa.eu/data-and-maps/data/corine-land-cover-2006-raster-1/corine-land-cover-classes-and/clc_legend.csv/at_download/file>`_","Specifies areas according to CORINE Land Cover codes which are generally eligible for solar panel placement."
Expand Down
19 changes: 18 additions & 1 deletion rules/build_electricity.smk
Original file line number Diff line number Diff line change
Expand Up @@ -290,13 +290,19 @@ rule build_renewable_profiles:
input:
availability_matrix=resources("availability_matrix_{clusters}_{technology}.nc"),
offshore_shapes=resources("offshore_shapes.geojson"),
regions=resources("regions_onshore_base_s_{clusters}.geojson"),
distance_regions=resources("regions_onshore_base_s_{clusters}.geojson"),
resource_regions=lambda w: (
resources("regions_onshore_base_s_{clusters}.geojson")
if w.technology in ("onwind", "solar", "solar-hsat")
else resources("regions_offshore_base_s_{clusters}.geojson")
),
cutout=lambda w: "cutouts/"
+ CDIR
+ config_provider("renewable", w.technology, "cutout")(w)
+ ".nc",
output:
profile=resources("profile_{clusters}_{technology}.nc"),
class_regions=resources("regions_by_class_{clusters}_{technology}.geojson"),
log:
logs("build_renewable_profile_{clusters}_{technology}.log"),
benchmark:
Expand Down Expand Up @@ -462,6 +468,16 @@ def input_profile_tech(w):
}


def input_class_regions(w):
return {
f"class_regions_{tech}": resources(
f"regions_by_class_{{clusters}}_{tech}.geojson"
)
for tech in set(config_provider("electricity", "renewable_carriers")(w))
- {"hydro"}
}


def input_conventional(w):
return {
f"conventional_{carrier}_{attr}": fn
Expand Down Expand Up @@ -678,6 +694,7 @@ rule add_electricity:
exclude_carriers=config_provider("clustering", "exclude_carriers"),
input:
unpack(input_profile_tech),
unpack(input_class_regions),
unpack(input_conventional),
base_network=resources("networks/base_s_{clusters}.nc"),
tech_costs=lambda w: resources(
Expand Down
27 changes: 27 additions & 0 deletions rules/build_sector.smk
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,28 @@ rule build_clustered_population_layouts:
"../scripts/build_clustered_population_layouts.py"


rule build_clustered_solar_rooftop_potentials:
input:
pop_layout=resources("pop_layout_total.nc"),
class_regions=resources("regions_by_class_{clusters}_solar.geojson"),
cutout=lambda w: "cutouts/"
+ CDIR
+ config_provider("atlite", "default_cutout")(w)
+ ".nc",
output:
potentials=resources("solar_rooftop_potentials_s_{clusters}.csv"),
log:
logs("build_clustered_solar_rooftop_potentials_s_{clusters}.log"),
resources:
mem_mb=10000,
benchmark:
benchmarks("build_clustered_solar_rooftop_potentials/s_{clusters}")
conda:
"../envs/environment.yaml"
script:
"../scripts/build_clustered_solar_rooftop_potentials.py"


rule build_simplified_population_layouts:
input:
pop_layout_total=resources("pop_layout_total.nc"),
Expand Down Expand Up @@ -1183,6 +1205,11 @@ rule prepare_sector_network:
if config_provider("sector", "solar_thermal")(w)
else []
),
solar_rooftop_potentials=lambda w: (
resources("solar_rooftop_potentials_s_{clusters}.csv")
if "solar" in config_provider("electricity", "renewable_carriers")(w)
else []
),
egs_potentials=lambda w: (
resources("egs_potentials_{clusters}.csv")
if config_provider("sector", "enhanced_geothermal", "enable")(w)
Expand Down
2 changes: 1 addition & 1 deletion scripts/_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ def handle_exception(exc_type, exc_value, exc_traceback):

def update_p_nom_max(n):
# if extendable carriers (solar/onwind/...) have capacity >= 0,
# e.g. existing assets from the OPSD project are included to the network,
# e.g. existing assets from GEM are included to the network,
# the installed capacity might exceed the expansion limit.
# Hence, we update the assumptions.

Expand Down
12 changes: 5 additions & 7 deletions scripts/add_brownfield.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
set_scenario_config,
update_config_from_wildcards,
)
from add_electricity import flatten
from add_existing_baseyear import add_build_year_to_new_assets

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -200,17 +201,14 @@ def adjust_renewable_profiles(n, input_profiles, params, year):
if ds.indexes["bus"].empty or "year" not in ds.indexes:
continue

ds = ds.stack(bus_bin=["bus", "bin"])

closest_year = max(
(y for y in ds.year.values if y <= year), default=min(ds.year.values)
)

p_max_pu = (
ds["profile"]
.sel(year=closest_year)
.transpose("time", "bus")
.to_pandas()
)
p_max_pu.columns = p_max_pu.columns + f" {carrier}"
p_max_pu = ds["profile"].sel(year=closest_year).to_pandas()
p_max_pu.columns = p_max_pu.columns.map(flatten) + f" {carrier}"

# temporal_clustering
p_max_pu = p_max_pu.groupby(snapshotmaps).mean()
Expand Down
70 changes: 44 additions & 26 deletions scripts/add_electricity.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,10 @@
"""

import logging
from collections.abc import Iterable
from typing import Any

import geopandas as gpd
import numpy as np
import pandas as pd
import powerplantmatching as pm
Expand All @@ -124,7 +127,6 @@
set_scenario_config,
update_p_nom_max,
)
from powerplantmatching.export import map_country_bus
from pypsa.clustering.spatial import DEFAULT_ONE_PORT_STRATEGIES, normed_or_uniform

idx = pd.IndexSlice
Expand All @@ -136,6 +138,10 @@ def normed(s):
return s / s.sum()


def flatten(t: Iterable[Any]) -> str:
return " ".join(map(str, t))


def calculate_annuity(n, r):
"""
Calculate the annuity factor for an asset with lifetime n years and.
Expand Down Expand Up @@ -483,9 +489,12 @@ def attach_wind_and_solar(
if "year" in ds.indexes:
ds = ds.sel(year=ds.year.min(), drop=True)

ds = ds.stack(bus_bin=["bus", "bin"])

supcar = car.split("-", 2)[0]
if supcar == "offwind":
distance = ds["average_distance"].to_pandas()
distance.index = distance.index.map(flatten)
submarine_cost = costs.at[car + "-connection-submarine", "capital_cost"]
underground_cost = costs.at[
car + "-connection-underground", "capital_cost"
Expand All @@ -505,18 +514,27 @@ def attach_wind_and_solar(
else:
capital_cost = costs.at[car, "capital_cost"]

buses = ds.indexes["bus_bin"].get_level_values("bus")
bus_bins = ds.indexes["bus_bin"].map(flatten)

p_nom_max = ds["p_nom_max"].to_pandas()
p_nom_max.index = p_nom_max.index.map(flatten)

p_max_pu = ds["profile"].to_pandas()
p_max_pu.columns = p_max_pu.columns.map(flatten)

n.add(
"Generator",
ds.indexes["bus"],
" " + car,
bus=ds.indexes["bus"],
bus_bins,
suffix=" " + car,
bus=buses,
carrier=car,
p_nom_extendable=car in extendable_carriers["Generator"],
p_nom_max=ds["p_nom_max"].to_pandas(),
p_nom_max=p_nom_max,
marginal_cost=costs.at[supcar, "marginal_cost"],
capital_cost=capital_cost,
efficiency=costs.at[supcar, "efficiency"],
p_max_pu=ds["profile"].transpose("time", "bus").to_pandas(),
p_max_pu=p_max_pu,
lifetime=costs.at[supcar, "lifetime"],
)

Expand Down Expand Up @@ -742,9 +760,9 @@ def attach_hydro(n, costs, ppl, profile_hydro, hydro_capacities, carriers, **par
)


def attach_OPSD_renewables(n: pypsa.Network, tech_map: dict[str, list[str]]) -> None:
def attach_GEM_renewables(n: pypsa.Network, tech_map: dict[str, list[str]]) -> None:
"""
Attach renewable capacities from the OPSD dataset to the network.
Attach renewable capacities from the GEM dataset to the network.

Args:
- n: The PyPSA network to attach the capacities to.
Expand All @@ -753,28 +771,28 @@ def attach_OPSD_renewables(n: pypsa.Network, tech_map: dict[str, list[str]]) ->
Returns:
- None
"""
tech_string = ", ".join(sum(tech_map.values(), []))
logger.info(f"Using OPSD renewable capacities for carriers {tech_string}.")
tech_string = ", ".join(tech_map.values())
logger.info(f"Using GEM renewable capacities for carriers {tech_string}.")

df = pm.data.OPSD_VRE().powerplant.convert_country_to_alpha2()
df = pm.data.GEM().powerplant.convert_country_to_alpha2()
technology_b = ~df.Technology.isin(["Onshore", "Offshore"])
df["Fueltype"] = df.Fueltype.where(technology_b, df.Technology).replace(
{"Solar": "PV"}
)
df = df.query("Fueltype in @tech_map").powerplant.convert_country_to_alpha2()
df = df.dropna(subset=["lat", "lon"])

for fueltype, carriers in tech_map.items():
gens = n.generators[lambda df: df.carrier.isin(carriers)]
buses = n.buses.loc[gens.bus.unique()]
gens_per_bus = gens.groupby("bus").p_nom.count()
for fueltype, carrier in tech_map.items():
fn = snakemake.input.get(f"class_regions_{carrier}")
class_regions = gpd.read_file(fn)

caps = map_country_bus(df.query("Fueltype == @fueltype"), buses)
caps = caps.groupby(["bus"]).Capacity.sum()
caps = caps / gens_per_bus.reindex(caps.index, fill_value=1)
df_fueltype = df.query("Fueltype == @fueltype")
geometry = gpd.points_from_xy(df_fueltype.lon, df_fueltype.lat)
caps = gpd.GeoDataFrame(df_fueltype, geometry=geometry, crs=4326)
caps = caps.sjoin(class_regions)
caps = caps.groupby(["bus", "bin"]).Capacity.sum()
caps.index = caps.index.map(flatten) + " " + carrier

n.generators.update({"p_nom": gens.bus.map(caps).dropna()})
n.generators.update({"p_nom_min": gens.bus.map(caps).dropna()})
n.generators.update({"p_nom": caps.dropna()})
n.generators.update({"p_nom_min": caps.dropna()})


def estimate_renewable_capacities(
Expand Down Expand Up @@ -811,8 +829,8 @@ def estimate_renewable_capacities(
f"\n{capacities.groupby('Technology').sum().div(1e3).round(2)}"
)

for ppm_technology, techs in tech_map.items():
tech_i = n.generators.query("carrier in @techs").index
for ppm_technology, tech in tech_map.items():
tech_i = n.generators.query("carrier == @tech").index
if ppm_technology in capacities.index.get_level_values("Technology"):
stats = capacities.loc[ppm_technology].reindex(countries, fill_value=0.0)
else:
Expand Down Expand Up @@ -1079,8 +1097,8 @@ def attach_stores(n, costs, extendable_carriers):
expansion_limit = estimate_renewable_caps["expansion_limit"]
year = estimate_renewable_caps["year"]

if estimate_renewable_caps["from_opsd"]:
attach_OPSD_renewables(n, tech_map)
if estimate_renewable_caps["from_gem"]:
attach_GEM_renewables(n, tech_map)

estimate_renewable_capacities(
n, year, tech_map, expansion_limit, params.countries
Expand Down
Loading
Loading