Skip to content
Merged
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
7 changes: 7 additions & 0 deletions config/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,13 @@ trade:
ports: "predefined" # "closest": route from region centroid (searoute snaps to nearest sea node); "predefined": route from hand-curated port city coordinates parsed from the notes column in trade_opt.csv
diversity_factor: False # "False" or float as max constraint for one single hbi supply route in the trade model. 1: no limit, 0.5: max 50% of demand can be supplied by one single supply route, etc.
# Note: The diversity factor constraints the trade route, not the producer itself. (Import from exporter route R <= diversity_factor * demand of importer I.). As a result, if the trade passes through one region, it is limited based on the demand of this particular region * diversity_factor. Therefore the trade routes in a scenario with a diversity_factor of 100% may differ to a scenario where diversity_factor=False.
shipping:
port_loading: 7 # USD/t, for each loading, see 10.1038/s41467-023-38123-2 supplementary S12
nh3_cost: 460 # USD/t_NH3, see 10.1038/s41467-023-38123-2 supplementary S12
nh3_consumption: 0.2 # t_NH3/nm, see 10.1038/s41467-023-38123-2 supplementary S12
charter_rate: 0.0008 # USD/(t*km), see 10.1038/s41467-023-38123-2 supplementary S12
panamax_load: 80000 # t
nm_to_km: 1.852 # 1 nautical mile equals 1.852 kilometers

grid_electricity:
marginal_cost: 80 # EUR/MWh, guesstimate for average grid electricity cost in 2030
Expand Down
1 change: 0 additions & 1 deletion rules/trade_model.smk
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ rule model_trade:
region=config["regions"],
intertwo=["steel"],
),
transport_costs="data/transport_costs/steel_r_iron_r.csv",
trade_options="resources/trade_opt_chokepoints.csv",
bus_locations="data/bus_locations.csv",
demand="data/un_enerdata_demand_2050_final.csv",
Expand Down
102 changes: 102 additions & 0 deletions workflow/notebooks/analysis-transport-cost.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"id": "b0311363",
"metadata": {},
"outputs": [],
"source": [
"import pypsa\n",
"import pandas as pd"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a98efb58",
"metadata": {},
"outputs": [],
"source": [
"n = pypsa.Network(\"../../results/chain_id~newre_2050/cost_year~2050/interone~hbi/intertwo~eaf/wacc~regional/final~steel/scenario~default/network.nc\")"
]
},
{
"cell_type": "markdown",
"id": "5a18fba6",
"metadata": {},
"source": [
"### Transport efficiency"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6edd6dee",
"metadata": {},
"outputs": [],
"source": [
"n.links[n.links.carrier == \"shipping_iron_ore\"].efficiency.describe()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2a3a5201",
"metadata": {},
"outputs": [],
"source": [
"n.links[n.links.carrier == \"shipping_hbi\"].efficiency.describe()"
]
},
{
"cell_type": "markdown",
"id": "b69794c5",
"metadata": {},
"source": [
"### Transport cost"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b6b82f5f",
"metadata": {},
"outputs": [],
"source": [
"n.links[n.links.carrier == \"shipping_iron_ore\"].marginal_cost.describe()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e5942c78",
"metadata": {},
"outputs": [],
"source": [
"n.links[n.links.carrier == \"shipping_hbi\"].marginal_cost.describe()"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "shift",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.12.11"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
99 changes: 28 additions & 71 deletions workflow/scripts/model_trade.py
Original file line number Diff line number Diff line change
Expand Up @@ -260,47 +260,29 @@ def building_model(
return n


def create_links(transport_costs, trade_options):
def create_links(trade_options):

# for in range of length of input csv with all the different links, region_from = column , region_to = column 2
# create links with the correct corresponding costs

# marginal and fixed cost for the different type of transport
ship_mc = float(
transport_costs.loc[transport_costs["transport_type"] == "shipping"]
.loc[:, "marginal_cost"]
.values[0]
)
pipe_mc = float(
transport_costs.loc[transport_costs["transport_type"] == "pipeline"]
.loc[:, "marginal_cost"]
.values[0]
)
ship_c = float(
transport_costs.loc[transport_costs["transport_type"] == "shipping"]
.loc[:, "fixed_cost"]
.values[0]
)
shipping = snakemake.config["trade"]["shipping"]

ship_iron_ore_mc = (
transport_costs.loc[transport_costs["transport_type"] == "shipping_iron_ore"]
.loc[:, "marginal_cost"]
.values[0]
)
port_loading = shipping["port_loading"]
nh3_cost = shipping["nh3_cost"] # USD/t_NH3
nh3_consumption = shipping["nh3_consumption"] # t_NH3/nm
charter_rate = shipping["charter_rate"] # USD/(t*km)
panamax_load = shipping["panamax_load"] # t
nm_to_km = shipping["nm_to_km"]
eur_usd = snakemake.config["techno-economic parameters"]["eur_usd"]

ship_interone_mc = (
transport_costs.loc[transport_costs["transport_type"] == f"shipping_{interone}"]
.loc[:, "marginal_cost"]
.values[0]
)
input_demand = 0.42 # MWh/km for LH2, IEA future of hydrogen 2019
boat_capacity = 363000 # MWh for LH2, IEA future of hydrogen 2019
speed = 30 # km/h, IEA future of hydrogen 2019
BOG = 0.2 / 100 # %/day, IEA future of hydrogen 2019
variable_cost = (
(nh3_cost * nh3_consumption / nm_to_km) / panamax_load + charter_rate
) / eur_usd # EUR/(t*km)
fixed_cost = port_loading * 2 / eur_usd # EUR/t

logger.info(f"ship + pipe cost {ship_mc} {ship_c} {pipe_mc}")
logger.info(f"shipping cost {interone} {ship_interone_mc} EUR/(t*km)")
logger.info(f"shipping cost iron ore {ship_iron_ore_mc} EUR/(t*km)")
logger.info(
f"variable shipping cost {variable_cost:.6f} EUR/(t*km) and fixed cost {fixed_cost:.6f} EUR/t applied to shipping links"
)

# if there should be a link, create a link
# do this for both shipping and pipeline
Expand All @@ -310,68 +292,44 @@ def create_links(transport_costs, trade_options):
r_from = trade_options["region_from"][r]
r_to = trade_options["region_to"][r]

# If shipping costs are made up from marginal and capital
# total_cost = ship_c + int(
# float(trade_options["shipping_distance [km]"][r]) * ship_mc
# )
# If shipping costs are made up from marginal only
total_cost_interone = ship_interone_mc * float(
trade_options["shipping_distance [km]"][r]
)
total_cost = total_cost_interone

# calculating efficiency
days_at_sea = (
float(trade_options["shipping_distance [km]"][r]) / speed
) / 24
tot_BOG = 1 - (1 - BOG) ** days_at_sea
tot_fuel_demand = (
(2 * float(trade_options["shipping_distance [km]"][r]))
* input_demand
/ boat_capacity
)
eff = 1 - max(tot_BOG, tot_fuel_demand)
shipping_cost = (
variable_cost * float(trade_options["shipping_distance [km]"][r])
+ fixed_cost
) # EUR/t

n.add(
"Link",
f"shipping {interone} {r_from}-{r_to}",
carrier="shipping_" + interone,
bus0=r_from + "_" + interone,
bus1=r_to + "_" + interone,
efficiency=eff, # %, calculated above
marginal_cost=total_cost, # EUR/MWh or EUR/t
efficiency=1,
marginal_cost=shipping_cost,
capital_cost=1 / 1000, # to prevent optimisation shenenigans
p_nom_extendable=True,
)
logger.info(
"shipping %s link made from %s to %s - eff %s",
"shipping %s link made from %s to %s",
interone,
r_from,
r_to,
eff,
)

# Add iron ore shipping link
total_cost_iron_ore = ship_iron_ore_mc * float(
trade_options["shipping_distance [km]"][r]
) # TODO Capital cost are not separate but included

n.add(
"Link",
"shipping iron ore {}-{}".format(r_from, r_to),
carrier="shipping_iron_ore",
bus0=r_from + "_ore",
bus1=r_to + "_ore",
efficiency=1,
marginal_cost=total_cost_iron_ore, # EUR/t_ironore
marginal_cost=shipping_cost, # EUR/t_ironore
capital_cost=1 / 1000, # to prevent optimisation shenenigans
p_nom_extendable=True,
)
logger.info(
"iron ore shipping link made from %s_ore to %s_ore - eff %s",
"iron ore shipping link made from %s_ore to %s_ore",
r_from,
r_to,
eff,
)

# checking if the row connects with pipeline
Expand Down Expand Up @@ -1125,11 +1083,11 @@ def _link_weight(link_name):
"model_trade",
cost_year="2050",
interone="hbi",
intertwo="steel",
intertwo="eaf",
final="steel",
scenario="default",
wacc="regional",
chain_id="labour_2050",
chain_id="newre_2050",
)

final = snakemake.wildcards["final"]
Expand Down Expand Up @@ -1160,7 +1118,6 @@ def _link_weight(link_name):

logger.info("starting up with all regions--- ")
# making dataframes
transport_costs = pd.read_csv(snakemake.input.transport_costs, header=0)
trade_options = pd.read_csv(snakemake.input.trade_options, header=0)
supply_curves_interone = snakemake.input.supply_curves_interone
supply_curves_intertwo = snakemake.input.supply_curves_intertwo
Expand Down Expand Up @@ -1222,7 +1179,7 @@ def _link_weight(link_name):

# building transport network connecting the individual buses
logger.info("building transportation links")
create_links(transport_costs, trade_options)
create_links(trade_options)

# Cost penalty
if snakemake.config["scenario"][scenario]["modifiers"]["cost_penalty"] is None:
Expand Down