Skip to content
Draft
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
19 changes: 14 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# SHIFT – Steel & Hydrogen Integrated Freight Trade

[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.21875103.svg)](https://doi.org/10.5281/zenodo.21875103)

**SHIFT** is a spatially resolved techno-economic optimization of global iron
and steel supply chains under decarbonization. It explores how
hydrogen-based direct reduced iron (DRI) production and hot-briquetted iron
Expand All @@ -10,6 +13,8 @@ orchestrated with [Snakemake](https://snakemake.readthedocs.io/).

## Quick start – installation & execution

SHIFT requires Python 3.10+ and uses [pixi](https://pixi.sh/latest/installation/) to manage the environment. Install pixi before you continue.

```sh
git clone https://github.com/energyLS/shift.git
cd shift
Expand All @@ -20,12 +25,16 @@ pixi run snakemake model_trade_all
Approximate runtimes:

- Installation (`pixi install`): ~5 minutes
- Full model (`model_trade_all`): ~5 hours
- Demo model (this branch): ~1 hour

By default, the model solves with [HiGHS](https://highs.dev/), a license-free
solver. If you have a [Gurobi](https://www.gurobi.com/) license, set
`solver.name: gurobi` in `config/config.yaml` for faster solves.

> [!TIP]
> For a quick, low-resolution run, check out the
> [`demo` branch](https://github.com/energyLS/shift/tree/demo), which uses
> fewer regions and quantities and completes in ~1 hour.
> [!NOTE]
> This is the `demo` branch: it uses fewer regions and quantities so it
> runs much faster. For the full-resolution model (~5 hours), see the
> [`main` branch](https://github.com/energyLS/shift/tree/main).

## Key results

Expand Down
48 changes: 32 additions & 16 deletions config/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ scenario:
# Absolute steel demand levels (Mt/year) for supply curve sweep
# For each level, PyPSA minimizes cost with fixed renewable capacity
# Values represent different production scales
steel_demand_levels: [0.1, 0.5, 1, 5, 10, 25, 50, 75, 100, 150, 200, 250, 300, 400, 500, 600, 700, 800, 900, 1000] # Mt/year
steel_demand_levels: [1, 10, 100, 1000] #[0.1, 0.5, 1, 5, 10, 25, 50, 75, 100, 150, 200, 250, 300, 400, 500, 600, 700, 800, 900, 1000] # Mt/year

electricity_steel_ratio: 5.25 #TWh/Mt or MWh/t, see notebooks 'analysis-steel.ipynb'
embodied_energy_steel: 2.1 #TWh/Mt or MWh/t, see iron oxide reduction
Expand All @@ -144,19 +144,19 @@ embodied_energy_steel: 2.1 #TWh/Mt or MWh/t, see iron oxide reduction
# Region definitions (ISO3 codes matching renewable clusters metadata)
regions:
"Europe": ["ALB","AUT","BLR","BEL","BIH","BGR","HRV","CYP","CZE","DNK","EST","FIN","DEU","GRC","HUN","ITA","XKX","LVA","LTU","LUX","MKD","MDA","MNE","NLD","NOR","POL","ROU","SRB","SVK","SVN","SWE","CHE","TUR","UKR"]
"Far_West_Europe": ["FRA","GRL","ISL","IRL","PRT","ESP","GBR"]
"Middle_East": ["BHR","IRN","IRQ","ISR","JOR","KWT","LBN","OMN","PSE","QAT","SAU","SYR","ARE","YEM"]
"North_West_Africa": ["BEN","BFA","CIV","GMB","GHA","GIN","GNB","LBR","NGA","SEN","SLE","TGO","DZA","TCD","EGY","ERI","LBY","MLI","MRT","MAR","NER","SDN","TUN","ESH"]
"Subsaharan_Africa": ["AGO","BWA","BDI","CMR","CAF","COD","ETH","KEN","GAB","MDG","MWI","MOZ","NAM","COG","RWA","SOM","ZAF","SSD","TZA","UGA","ZMB","ZWE"]
# "Far_West_Europe": ["FRA","GRL","ISL","IRL","PRT","ESP","GBR"]
# "Middle_East": ["BHR","IRN","IRQ","ISR","JOR","KWT","LBN","OMN","PSE","QAT","SAU","SYR","ARE","YEM"]
# "North_West_Africa": ["BEN","BFA","CIV","GMB","GHA","GIN","GNB","LBR","NGA","SEN","SLE","TGO","DZA","TCD","EGY","ERI","LBY","MLI","MRT","MAR","NER","SDN","TUN","ESH"]
# "Subsaharan_Africa": ["AGO","BWA","BDI","CMR","CAF","COD","ETH","KEN","GAB","MDG","MWI","MOZ","NAM","COG","RWA","SOM","ZAF","SSD","TZA","UGA","ZMB","ZWE"]
"North_America": ["CAN","USA"]
"Eurasia": ["ARM","AZE","GEO","KAZ","KGZ","TJK","TKM","UZB","RUS"]
"South_America": ["BOL","BRA","COL","ECU","GUF","GUY","PRY","PER","SUR","VEN"]
"South_South_America": ["ARG","CHL","URY"]
"Central_America": ["CRI","SLV","GTM","HND","MEX","NIC","PAN","CUB","DOM","JAM","HTI"]
"West_Asia": ["AFG","BGD","BTN","IND","NPL","PAK","LKA"]
"East_Asia": ["CHN","HKG","MNG","TWN"]
"Pacific_Asia": ["BRN","KHM","IDN","LAO","MYS","MMR","PNG","PHL","SGP","THA","VNM"]
"East_East_Asia": ["JPN","KOR","PRK"]
# "Eurasia": ["ARM","AZE","GEO","KAZ","KGZ","TJK","TKM","UZB","RUS"]
# "South_America": ["BOL","BRA","COL","ECU","GUF","GUY","PRY","PER","SUR","VEN"]
# "South_South_America": ["ARG","CHL","URY"]
# "Central_America": ["CRI","SLV","GTM","HND","MEX","NIC","PAN","CUB","DOM","JAM","HTI"]
# "West_Asia": ["AFG","BGD","BTN","IND","NPL","PAK","LKA"]
# "East_Asia": ["CHN","HKG","MNG","TWN"]
# "Pacific_Asia": ["BRN","KHM","IDN","LAO","MYS","MMR","PNG","PHL","SGP","THA","VNM"]
# "East_East_Asia": ["JPN","KOR","PRK"]
"Oceania": ["AUS","NZL"]

region_nice_names:
Expand Down Expand Up @@ -254,8 +254,9 @@ plot:
bus_size: 2.0e-7
link_width: 3.0e-8
solver:
name: gurobi
options: gurobi-default
name: highs # license-free by default; set to "gurobi" (with a valid license) for faster solves
options: highs-default # used for the trade LP, where crossover-derived duals (nodal prices) matter
options_lcox: highs-lcox # used for regional LCOX/supply-curve solves, which only consume the primal solution
compute_iis: False # Set to false to skip expensive IIS computation for infeasible models
solver_options:
highs-default:
Expand All @@ -270,6 +271,21 @@ solver_options:
ipm_optimality_tolerance: 1e-6
parallel: "on"
random_seed: 123
highs-lcox:
# No crossover: calculate_lcox.py only reads network.objective and the
# primal capacity mix, never duals, so a basic (vertex) solution is not
# required and skipping crossover saves the simplex clean-up pass.
# solver left as "choose" so HiGHS can pick simplex over IPM where cheaper.
threads: 4
solver: "choose"
run_crossover: "off"
small_matrix_value: 1e-6
large_matrix_value: 1e15
primal_feasibility_tolerance: 1e-6
dual_feasibility_tolerance: 1e-6
ipm_optimality_tolerance: 1e-5
parallel: "on"
random_seed: 123
gurobi-default:
threads: 4
method: 2 # barrier
Expand Down Expand Up @@ -360,4 +376,4 @@ colors:
# Friendshoring colors
bloc_1: "#17679E" # Europe (Western bloc: Europe + Far West Europe + North America)
bloc_2: "#8352D8" # Middle_East (Global South bloc)
bloc_3: "#BF9A39" # East_East_Asia (East Asia + Oceania bloc)
bloc_3: "#BF9A39" # East_East_Asia (East Asia + Oceania bloc)
8 changes: 4 additions & 4 deletions rules/retrieve.smk
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,10 @@ storage:
keep_local=True,
retries=3,


rule retrieve_data:
input:
data_url=storage(
f"https://zenodo.org/records/21875103/files/shift_data.zip"
),
data_url=storage(f"https://zenodo.org/records/21875103/files/shift_data.zip"),
output:
bus_locations="data/bus_locations.csv",
ip_market_fabrication="data/demand/ip_market__fabrication.csv",
Expand All @@ -17,6 +16,7 @@ rule retrieve_data:
unido_raw="data/labour/unido-raw/data.csv",
iron_ore_production="data/owid-iron-ore/iron-ore-crude-ore-production.csv",
political_stability="data/political-stability/globaleconomy.csv",
trade_opt="data/trade_opt.csv",
renewable_profiles_geojson="data/renewable_profiles_global_merged.geojson",
renewable_profiles_nc="data/renewable_profiles_global_merged.nc",
log:
Expand All @@ -27,4 +27,4 @@ rule retrieve_data:
params:
folder=".",
run:
unpack_archive(input.data_url, params.folder)
unpack_archive(input.data_url, params.folder)
3 changes: 2 additions & 1 deletion workflow/scripts/calculate_lcox.py
Original file line number Diff line number Diff line change
Expand Up @@ -365,7 +365,8 @@ def solve_network(network, config):
solver_cfg = config.get("solver", {})
solver_name = os.getenv("SHIFT_SOLVER", solver_cfg.get("name", "glpk"))
solver_options_key = os.getenv(
"SHIFT_SOLVER_OPTIONS", solver_cfg.get("options", "default")
"SHIFT_SOLVER_OPTIONS",
solver_cfg.get("options_lcox", solver_cfg.get("options", "default")),
)
solver_options = config.get("solver_options", {}).get(solver_options_key, {})

Expand Down