Input Assumptions for Modelling the Industry Sector in the negaWatt-BE Scenario

INDUSTRY FINAL ENERGY DEMAND IN BELGIUM


Developer: QUOILIN Sylvain, MEYER Sébastien, LATERRE Antoine, BERNAERTS Valentine


This notebook documents how the industrial sector is represented in the negaWatt-BE / PyPSA-Eur sufficiency scenario, makes the underlying sufficiency, circularity and efficiency hypotheses explicit, and reconstructs the final-energy-demand inputs that PyPSA-Eur expects for industry — so that these inputs can be generated from documented assumptions instead of being read from the external CLEVER scenario dashboard (clever_Industry_<year>.csv).

Status / honesty note. Unlike the buildings and transport notebooks, industry was historically not modelled bottom-up inside negaWatt-BE: the scenario imported the industrial final energy consumption (FEC) for every country directly from the CLEVER project dashboard. This notebook rebuilds that layer from explicit levers — gross material-demand reductions (sufficiency), recycling rates and route shares (circularity), and energy-intensity / electrification trajectories (efficiency). Each industrial branch FEC is reconstructed as production × energy intensity, and the energy intensity is, wherever the data allow, derived from the recycling rate and the per-route energy intensities (MODEIRE/CLEVER). Where the public CLEVER data are insufficient to recompute a value, an explicit hypothesis is stated and flagged with a > ⚠️ note for future improvement.

References¶

  • [CLEVER-IND] négaWatt / CLEVER (June 2022). Establishment of energy consumption convergence corridors to 2050 — Industrial sector. (CLEVER/2206-Convergence-corridors-Industry.pdf; distilled in CLEVER/CLEVER_industry_hypotheses.md).
  • [CLEVER-REP] CLEVER (2023). A Collaborative Low Energy Vision for the European Region — final report. (CLEVER/CLEVER_final-report.pdf / CLEVER/CLEVER_final-report.md).
  • [MODEIRE] Route-specific energy intensities (primary vs recycled) reported by [CLEVER-IND] for steel, glass and pulp & paper.
  • [JRC-IDEES] Rozsai, M. et al. (2025). JRC-IDEES-2023 (tables labelled 2021), European Commission, JRC.
  • [PYPSA] PyPSA-Eur (negaWatt fork): scripts/build_industrial_energy_demand_per_node.py.
In [1]:
# Automatically reload helper module if modified:
%load_ext autoreload
%autoreload 2

import warnings
warnings.filterwarnings("ignore")  # silence NumPy/pandas optional-dependency noise in this environment

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from pathlib import Path

# Reuse the negaWatt-BE projection / formatting helpers (linear_growth, generate_target_years, ...):
from nW_BE_demand_model_sub_functions import *

pd.set_option("display.float_format", lambda v: f"{v:,.3f}")
In [2]:
# ----------------------------------------------------------------- configuration
DATA              = Path("data")
JRC_DIR           = DATA / "jrc-idees-2021" / "BE"
JRC_INDUSTRY      = JRC_DIR / "JRC-IDEES-2021_Industry_BE.xlsx"
JRC_ENERGYBALANCE = JRC_DIR / "JRC-IDEES-2021_EnergyBalance_BE.xlsx"
CLEVER_REF_DIR    = DATA / "clever_dashboard_reference"   # dashboard exports kept ONLY for validation
OUT_DIR           = DATA / "industry_output"              # reconstructed PyPSA-Eur inputs are written here
OUT_DIR.mkdir(parents=True, exist_ok=True)

COUNTRY     = "BE"
KTOE_TO_TWH = 0.01163        # 1 ktoe = 0.01163 TWh

# CLEVER industry trajectories are provided on a DECADAL grid:
YEARS = [2020, 2030, 2040, 2050]
# CLEVER uses 2015 as the index reference year, and pre-COVID 2019 statistics as the "recent" 2020 baseline.
BASE_YEAR, RECENT_YEAR = 2015, 2019

print("Configuration loaded. Decadal horizon:", YEARS)
assert JRC_INDUSTRY.exists() and JRC_ENERGYBALANCE.exists(), "JRC-IDEES BE files missing under data/jrc-idees-2021/BE/"
Configuration loaded. Decadal horizon: [2020, 2030, 2040, 2050]

1. How industry enters the negaWatt-BE / PyPSA-Eur scenario¶

The negaWatt-BE demand chain (macro → buildings / transports) overrides the transport and residential/tertiary entries of PyPSA-Eur's energy_totals (see scripts/nW_BE.py, rule update_nW_BE). Industry is handled on a different path and is not touched by update_nW_BE.

For the sufficiency run (run.name == "suff"), scripts/build_industrial_energy_demand_per_node.py overwrites the industrial carrier totals, per country, from data/clever_Industry_<planning_horizon>.csv. The target outputs this notebook must reproduce for Belgium are exactly the PyPSA-Eur carrier columns below:

PyPSA-Eur carrier Built from CLEVER columns
ammonia Total FEC of the ammonia industry
electricity Total final electricity consumption in industry
coal Total final solid-fossil-fuel consumption in industry
solid biomass Total final solid-biomass consumption in industry
methane Total final gas consumption in industry
low-temperature heat Total final heat consumption in industry
hydrogen Total final H₂ in industry + non-energy H₂ feedstock
naphtha non-energy oil feedstock + total final oil consumption in industry

⚠️ Multi-node caveat (from PyPSA-Eur). The override assigns the full country total to every bus. With the adm clustering of config_suff.yaml Belgium has a single industry bus, so this is fine here.

2. The CLEVER industry methodology (sufficiency → circularity → efficiency)¶

CLEVER builds 2050 convergence corridors per industrial branch through three successive levers ([CLEVER-IND]; full extraction in CLEVER/CLEVER_industry_hypotheses.md):

  1. Sufficiency — scale material demand down (deliver the service with less material) → lower production.
  2. Circularity — durable design + longer use (less production) and higher recycling rates (shift from primary to less energy-intensive recycled routes) → affects both production and energy intensity.
  3. Efficiency — reduce the energy intensity of production (technologies, fuel/material substitution, electrification).

For the heavy sectors (steel, cement, glass, pulp & paper, ammonia, HVC):

$$\text{FEC}_\text{sector}(y) = \underbrace{P_{2015}\times\tfrac{\text{demand index}(y)}{100}}_{\text{production (sufficiency+circularity)}} \times \underbrace{I(y)}_{\text{energy intensity (circularity+efficiency)}} \times 10^{-6}\;[\text{TWh}]$$

and, where data allow, the energy intensity is itself derived from the recycling/route split:

$$I(y) = s_\text{rec}(y)\,I_\text{rec}(y) + \bigl(1-s_\text{rec}(y)\bigr)\,I_\text{prim}(y)$$

with $s_\text{rec}$ the recycled-route share and $I_\text{rec}, I_\text{prim}$ the recycled/primary route intensities [MODEIRE]. For the light sectors (other chemicals, non-ferrous, food, "other industries") a direct gross-FEC reduction is applied.

Summary corridors (Table 2 of [CLEVER-IND], % of 2015 value in 2050)¶

Sector Production index Energy intensity (MWh/kt) FEC index
Cement 52 – 99 560 – 800 31 – 64
Steel 74 – 92 2060 – 2690 42 – 52
Pulp & paper 58 – 110 1890 – 3780 31 – 64
Chemicals – Ammonia 58 – 80 1580 – 2500 —
Chemicals – HVC 59 – 98 3140 – 5680 —
Chemicals – Others — — 69 – 89
Glass 61 – 95 700 – 2190 23 – 68
Food — — 42 – 64
Non-ferrous metals — — 39 – 87
Others — — 63 – 85

The Belgian trajectory adopted below sits inside every one of these corridors.

3. Statistical baseline for Belgium — JRC-IDEES-2021¶

We load the physical production and the final-energy balance for Belgian industry directly from the JRC-IDEES-2021 workbooks (data/jrc-idees-2021/BE/). These provide a transparent, peer-reviewed baseline against which the CLEVER assumptions are checked.

In [3]:
# ---- physical production (kt) from the JRC-IDEES Industry workbook ----
def jrc_physical_output(sheet, label, year):
    '''Physical output [kt] of a sub-sector (reads the 'Physical output' block of a JRC Industry sheet).'''
    df = pd.read_excel(JRC_INDUSTRY, sheet_name=sheet, index_col=0)
    idx_str = df.index.to_series().astype(str)
    start = int(np.asarray(idx_str.str.contains("Physical output")).argmax())
    isnull = np.asarray(df.index.isnull())
    end = start + 1
    while end < len(df) and not isnull[end]:
        end += 1
    block = df.iloc[start:end]
    return float(block.loc[label, year])

# Exact JRC row labels (mind the DOUBLE space in 'Glass production  (kt)' and 'Paper production  (kt)'):
PROD_FIELDS = {
    "crude steel":                  ("ISI", "Physical output (kt steel)"),
    "  primary (integrated)":       ("ISI", "Integrated steelworks"),
    "  recycled (electric arc)":    ("ISI", "Electric arc"),
    "cement":                       ("NMM", "Cement (kt)"),
    "glass":                        ("NMM", "Glass production  (kt)"),
    "paper":                        ("PPA", "Paper production  (kt)"),
    "pulp":                         ("PPA", "Pulp production (kt)"),
    "basic chemicals (kt eth.eq.)": ("CHI", "Basic chemicals (kt ethylene eq.)"),
}

baseline_prod = pd.DataFrame(
    {yr: {k: jrc_physical_output(s, l, yr) for k, (s, l) in PROD_FIELDS.items()}
     for yr in [BASE_YEAR, RECENT_YEAR, 2021]}
)
print("Belgian physical production [kt] — JRC-IDEES-2021")
baseline_prod
Belgian physical production [kt] — JRC-IDEES-2021
Out[3]:
2015 2019 2021
crude steel 7,257.140 7,759.543 6,909.288
primary (integrated) 4,808.724 5,239.543 4,712.134
recycled (electric arc) 2,448.416 2,520.000 2,197.154
cement 5,550.633 5,834.211 6,072.323
glass 1,108.243 1,399.067 1,175.816
paper 2,050.101 1,935.108 1,699.567
pulp 492.048 514.977 514.977
basic chemicals (kt eth.eq.) 6,211.432 6,639.531 7,169.810
In [4]:
# ---- final energy consumption by carrier (total industry) from the JRC EnergyBalance workbook ----
FUELS = {                                  # JRC fuel row label -> aggregated carrier
    "Solid fossil fuels": "coal", "Peat and peat products": "coal", "Oil shale and oil sands": "coal",
    "Oil and petroleum products": "oil",
    "Manufactured gases": "gas", "Natural gas": "gas",
    "Heat": "heat", "Nuclear heat": "heat",
    "Renewables and biofuels": "biomass",
    "Non-renewable waste": "waste",
    "Electricity": "electricity",
}

def jrc_industry_fec_by_carrier(years, sheet="FC_IND_E"):
    '''Total industrial FEC by carrier [TWh] (energy use only; excludes non-energy feedstock).'''
    df = pd.read_excel(JRC_ENERGYBALANCE, sheet_name=sheet, index_col=0)
    df.index = df.index.to_series().astype(str).str.strip()
    out = {}
    for yr in years:
        s = {}
        for label, carrier in FUELS.items():
            if label in df.index:
                s[carrier] = s.get(carrier, 0.0) + float(df.loc[label, yr]) * KTOE_TO_TWH
        out[yr] = pd.Series(s)
    return pd.DataFrame(out)

jrc_carrier = jrc_industry_fec_by_carrier([BASE_YEAR, RECENT_YEAR, 2021])
jrc_carrier.loc["TOTAL (energy use)"] = jrc_carrier.sum()
print("Belgian industry FEC by carrier [TWh] — JRC-IDEES-2021 (energy use only, excl. feedstock)")
jrc_carrier
Belgian industry FEC by carrier [TWh] — JRC-IDEES-2021 (energy use only, excl. feedstock)
Out[4]:
2015 2019 2021
coal 4.879 4.878 4.236
oil 20.197 15.170 16.978
gas 45.430 47.825 49.556
heat 4.756 4.399 4.266
biomass 8.077 7.909 8.380
waste 1.589 1.459 1.382
electricity 38.094 38.265 38.234
TOTAL (energy use) 123.022 119.906 123.031
In [5]:
# ---- non-energy (feedstock) use, dominated by the chemical industry ----
def jrc_total(sheet, years, label="Total"):
    df = pd.read_excel(JRC_ENERGYBALANCE, sheet_name=sheet, index_col=0)
    df.index = df.index.to_series().astype(str).str.strip()
    return pd.Series({yr: float(df.loc[label, yr]) * KTOE_TO_TWH for yr in years}, name=sheet)

feedstock_base = pd.concat([
    jrc_total("FC_IND_NE",     [BASE_YEAR, RECENT_YEAR, 2021]).rename("non-energy total"),
    jrc_total("FC_IND_CPC_NE", [BASE_YEAR, RECENT_YEAR, 2021]).rename("of which chemical feedstock"),
], axis=1)
print("Belgian industry NON-ENERGY (feedstock) use [TWh] — JRC-IDEES-2021")
feedstock_base
Belgian industry NON-ENERGY (feedstock) use [TWh] — JRC-IDEES-2021
Out[5]:
non-energy total of which chemical feedstock
2015 85.916 84.553
2019 81.022 80.510
2021 84.983 83.897

⚠️ Baseline reconciliation (JRC-IDEES vs CLEVER). JRC-IDEES splits energy use (FC_IND_E ≈ 120 TWh in 2019) from non-energy feedstock (FC_IND_NE ≈ 81 TWh, almost all chemical feedstock). CLEVER reports a single "Total FEC of industry" ≈ 138.7 TWh for 2020, which embeds the steel coke/blast-furnace energy in the carrier totals and computes heavy-sector FEC as production × unit-consumption. The largest reconciliation items are steel (CLEVER 30.7 TWh vs JRC iron&steel 12 TWh — coke/BF energy) and cement/glass production volumes. This is the main transparency gap (§7).

4. Explicit sufficiency, circularity & efficiency hypotheses (Belgium)¶

This is the core of the notebook: every number fed to PyPSA-Eur is expressed as an explicit lever, in the same spirit as the hard-coded assumptions of the buildings/transport notebooks. For each heavy sector we state

  • a gross material-demand index (sufficiency + circularity) → production,
  • a recycled-route share (circularity),
  • per-route energy intensities (efficiency; MODEIRE) → from which the blended energy intensity is derived,

and for the light sectors a direct gross-FEC reduction. Every value is annotated with its CLEVER corridor.

⚠️ Provenance limit. The exact national levers (demand reductions, recycling rates, MWh/kt) were chosen by the Belgian CLEVER partner and are not fully derived in the public CLEVER notes. We make them explicit and check that they (a) lie inside the corridors and (b) reproduce the dashboard. Re-deriving the demand reductions from a Belgian end-use model is left for future work.

4.1 Sector-by-sector hypotheses (narrative)¶

Steel (corridor: production 74–92, intensity 2060–2690, FEC 42–52)

  • Sufficiency + circularity (production → 85 % of 2015 by 2050, −15 %): less over-specified steel in construction (−20–30 %), +40 % building lifetime, ~10 % timber substitution; lighter vehicles, modal shift and car-sharing in transport. (The 2020 index 106.9 % simply reflects 2019 output being above the 2015 base.)
  • Circularity (recycling): EAF/recycled share 27 % → 53 % (CLEVER corridor 50–77 %; Belgium at the low end, consistent with its ~30–50 % EU-average group).
  • Efficiency (intensity, DERIVED): MODEIRE route intensities — primary (BF-BOF, then H-DRI) 5000 → 4060 MWh/kt; recycled (EAF) 1500 → 1020 MWh/kt — blended by the recycled share. Hydrogen for H-DRI is counted inside the intensity.

Cement (52–99, 560–800, 31–64)

  • Production → 75 % (−25 %): lower cement/capita, timber & carbon-concrete construction, less new road infrastructure, concrete recycling (14 → ~34 %), clinker substitutes.
  • Efficiency (intensity 904 → 650 MWh/kt, −28 %): dry-kiln conversion, clinker substitutes (GGBS/PFA/ limestone), fuel switch to biomass, waste-heat recovery. (No clean two-route split → intensity stated, not derived.)

Glass (61–95, 700–2190, 23–68)

  • Production → 85 % (−15 %): material efficiency, reuse, lightweighting.
  • Circularity: cullet share 40 % → 63 % (CLEVER). (The dashboard 'Share of recycled glass' column is empty → value taken from the CLEVER corridor text.)
  • Efficiency (intensity 4300 → 1400 MWh/kt, −67 %): full furnace electrification (electric baths to 100 % by 2050). ⚠️ The Belgian 2020 baseline intensity (4300) is far above the MODEIRE primary value (3500); the route-based derivation only partially reconciles (see 4.3).

Ammonia (58–80, 1580–2500)

  • Production → 70 % (−30 %): synthetic-fertiliser demand reduction (corridor −40–50 %: agro-ecology, legume rotations, less food waste). Belgium adopts a milder −30 %.
  • Efficiency (intensity 5086 → 2000 MWh/kt, excl. feedstock): switch to green-hydrogen Haber-Bosch (electrolytic H₂). Feedstock H₂ is tracked separately (4.6 / Annex 2).

HVC (high-value chemicals) (59–98, 3140–5680)

  • Production → 90 % (−10 %): plastics-demand reduction (single-use bans, reuse). ⚠️ Belgium is very conservative here (corridor allows −41 %), reflecting the weight of the Antwerp petrochemical cluster.
  • Efficiency (intensity 5376 → 4400 MWh/kt): modest; note CLEVER warns intensity can rise on the H₂-via- methanol (MTO/MTA) route. Feedstock shift oil → H₂ is large and tracked separately (4.6).

Pulp & paper (58–110, 1890–3780, 31–64)

  • Production → 85 % of 2015 (packaging + / graphic − / lightweighting; 2020 already at 83 %).
  • Circularity: recycling 62 % → 80 % (CLEVER). (Dashboard 'Share of recycled pulp' empty → CLEVER text.)
  • Efficiency (intensity 4276 → 2500 MWh/kt): impulse/steam drying, heat pumps, electrification. ⚠️ MODEIRE pulp route intensities do not map onto the paper unit-consumption (which includes drying/finishing) → the route derivation fails (see 4.3); intensity is stated explicitly.

Light sectors (direct gross-FEC reduction relative to 2020)

  • Other chemicals → 79 % (corridor 69–89 %); Non-ferrous → 65 % (39–87 %; secondary metals + induction electrification); Food → 45 % (42–64 %; diet change + electrified heating/cooling); Other industries → 75 % (63–85 %; generic efficiency + electrification).
In [6]:
# === 4.2 Heavy-sector levers ==========================================================================
# demand_index : gross material-demand index [% of 2015]  (sufficiency + circularity)  -> production
# rec_share    : recycled-route share [-]                 (circularity)
# route_int    : (primary, recycled) intensities as (value_2015, value_2050) [MWh/kt]  (efficiency; MODEIRE)
# intensity    : adopted blended energy intensity [MWh/kt] (used when route derivation is unavailable/unreliable)
HEAVY = {
    "steel": dict(
        P2015=7257.0, P2015_src="JRC-IDEES-2015 crude steel",
        corridor_prod="74-92", corridor_int="2060-2690",
        demand_index={2020: 106.931, 2030: 97.281, 2040: 87.400, 2050: 85.000},
        rec_share   ={2020: 0.270, 2030: 0.370, 2040: 0.495, 2050: 0.530},
        route_int   ={"primary": (5000.0, 4060.0), "recycled": (1500.0, 1020.0)},
        derive_intensity=True,
        intensity   ={2020: 3954.282, 2030: 3226.398, 2040: 2557.570, 2050: 2300.000}),
    "cement": dict(
        P2015=6275.0, P2015_src="CLEVER (JRC-2015=5551 kt; +13% — flagged)",
        corridor_prod="52-99", corridor_int="560-800",
        demand_index={2020: 107.936, 2030: 93.444, 2040: 79.000, 2050: 75.000},
        rec_share=None, route_int=None, derive_intensity=False,
        intensity   ={2020: 904.114, 2030: 792.304, 2040: 686.749, 2050: 650.000}),
    "glass": dict(
        P2015=1000.0, P2015_src="CLEVER (JRC-2015=1108 kt; -10% — flagged)",
        corridor_prod="61-95", corridor_int="700-2190",
        demand_index={2020: 100.000, 2030: 93.400, 2040: 87.400, 2050: 85.000},
        rec_share   ={2020: 0.450, 2030: 0.510, 2040: 0.570, 2050: 0.630},   # CLEVER cullet (dashboard col empty)
        route_int   ={"primary": (3500.0, 2000.0), "recycled": (2500.0, 1300.0)},
        derive_intensity=False,   # MODEIRE only partially reconciles -> keep adopted intensity, show derivation
        intensity   ={2020: 4300.000, 2030: 3024.000, 2040: 1864.000, 2050: 1400.000}),
    "ammonia": dict(
        P2015=1050.0, P2015_src="CLEVER/USGS (not tracked physically by JRC-IDEES)",
        corridor_prod="58-80", corridor_int="1580-2500",
        demand_index={2020: 100.000, 2030: 86.800, 2040: 74.800, 2050: 70.000},
        rec_share=None, route_int=None, derive_intensity=False,
        intensity   ={2020: 5085.714, 2030: 3728.000, 2040: 2493.714, 2050: 2000.000}),
    "hvc": dict(
        P2015=5580.0, P2015_src="CLEVER (HVC split from basic chemicals)",
        corridor_prod="59-98", corridor_int="3140-5680",
        demand_index={2020: 100.000, 2030:  95.600, 2040:  91.600, 2050:  90.000},
        rec_share=None, route_int=None, derive_intensity=False,
        intensity   ={2020: 5376.344, 2030: 4946.753, 2040: 4556.215, 2050: 4400.000}),
    "paper": dict(
        P2015=2123.0, P2015_src="CLEVER paper&printing index basis (JRC-2015 paper=2050 kt)",
        corridor_prod="58-110", corridor_int="1890-3780",
        demand_index={2020:  83.137, 2030:  83.957, 2040:  87.400, 2050:  85.000},
        rec_share   ={2020: 0.620, 2030: 0.680, 2040: 0.740, 2050: 0.800},   # CLEVER recycling (dashboard col empty)
        route_int   ={"primary": (5400.0, 3300.0), "recycled": (460.0, 280.0)},
        derive_intensity=False,   # MODEIRE pulp routes do NOT map to paper unit-consumption (see 4.3)
        intensity   ={2020: 4275.631, 2030: 3494.353, 2040: 2722.169, 2050: 2500.000}),
}

def production_kt(sector):
    p = HEAVY[sector]
    return pd.Series({y: p["P2015"] * p["demand_index"][y] / 100.0 for y in YEARS}, name=sector)

production = pd.DataFrame({s: production_kt(s) for s in HEAVY}).T
print("Reconstructed Belgian production [kt]  (= P2015 x gross demand index)")
production.round(1)
Reconstructed Belgian production [kt]  (= P2015 x gross demand index)
Out[6]:
2020 2030 2040 2050
steel 7,760.000 7,059.700 6,342.600 6,168.400
cement 6,773.000 5,863.600 4,957.200 4,706.200
glass 1,000.000 934.000 874.000 850.000
ammonia 1,050.000 911.400 785.400 735.000
hvc 5,580.000 5,334.500 5,111.300 5,022.000
paper 1,765.000 1,782.400 1,855.500 1,804.600
In [7]:
# === 4.3 Energy intensity: DERIVE from recycling + route intensities where possible ==================
def _interp(v2015, v2050, y):
    return v2015 + (v2050 - v2015) * (y - BASE_YEAR) / (2050 - BASE_YEAR)

def route_blend_intensity(sector, year):
    '''Blended intensity [MWh/kt] = s_rec*I_rec + (1-s_rec)*I_prim, with linear route improvement 2015->2050.'''
    p = HEAVY[sector]
    if p["rec_share"] is None or p["route_int"] is None:
        return np.nan
    s = p["rec_share"][year]
    ip = _interp(*p["route_int"]["primary"], year)
    ir = _interp(*p["route_int"]["recycled"], year)
    return s * ir + (1 - s) * ip

# Compare the route-derived intensity to the adopted (dashboard) intensity:
rows = []
for s in HEAVY:
    for y in YEARS:
        adopted = HEAVY[s]["intensity"][y]
        derived = route_blend_intensity(s, y)
        rows.append((s, y, derived, adopted,
                     np.nan if np.isnan(derived) else derived / adopted - 1.0))
intensity_check = pd.DataFrame(rows, columns=["sector", "year", "route-derived", "adopted", "rel.err"])
intensity_check = intensity_check.pivot(index="sector", columns="year")
print("Energy intensity [MWh/kt]: route-derived vs adopted, and relative error")
intensity_check.round(3)
Energy intensity [MWh/kt]: route-derived vs adopted, and relative error
Out[7]:
route-derived adopted rel.err
year 2020 2030 2040 2050 2020 2030 2040 2050 2020 2030 2040 2050
sector
ammonia NaN NaN NaN NaN 5,085.714 3,728.000 2,493.714 2,000.000 NaN NaN NaN NaN
cement NaN NaN NaN NaN 904.114 792.304 686.749 650.000 NaN NaN NaN NaN
glass 2,855.000 2,412.714 1,980.714 1,559.000 4,300.000 3,024.000 1,864.000 1,400.000 -0.336 -0.202 0.063 0.114
hvc NaN NaN NaN NaN 5,376.344 4,946.753 4,556.215 4,400.000 NaN NaN NaN NaN
paper 2,207.257 1,700.343 1,259.257 884.000 4,275.631 3,494.353 2,722.169 2,500.000 -0.484 -0.513 -0.537 -0.646
steel 3,938.457 3,375.086 2,758.714 2,448.800 3,954.282 3,226.398 2,557.570 2,300.000 -0.004 0.046 0.079 0.065

Reading 4.3. For steel the recycling/route derivation reproduces the adopted intensity within ~0–8 % (the small positive bias means CLEVER applied slightly more efficiency than a pure route blend, e.g. early H-DRI). Glass reconciles only mid-period — the Belgian 2020 baseline (4300 MWh/kt) is ~+23 % above the MODEIRE primary value (3500), so the absolute level is not explained by the documented routes. For paper the MODEIRE pulp intensities are ~2× too low versus the paper unit-consumption (which includes drying and finishing), so the route derivation is not usable.

⚠️ Decision. We therefore use the route derivation only for steel (set derive_intensity=True) and keep the explicit adopted intensity for the other sectors, flagging glass and paper as not fully derivable.

In [8]:
def intensity_MWh_per_kt(sector, year):
    p = HEAVY[sector]
    if p.get("derive_intensity"):
        return route_blend_intensity(sector, year)
    return p["intensity"][year]

energy_intensity = pd.DataFrame(
    {s: pd.Series({y: intensity_MWh_per_kt(s, y) for y in YEARS}) for s in HEAVY}).T
print("Adopted Belgian energy intensity [MWh/kt]  (steel = route-derived, others = explicit)")
energy_intensity.round(1)
Adopted Belgian energy intensity [MWh/kt]  (steel = route-derived, others = explicit)
Out[8]:
2020 2030 2040 2050
steel 3,938.500 3,375.100 2,758.700 2,448.800
cement 904.100 792.300 686.700 650.000
glass 4,300.000 3,024.000 1,864.000 1,400.000
ammonia 5,085.700 3,728.000 2,493.700 2,000.000
hvc 5,376.300 4,946.800 4,556.200 4,400.000
paper 4,275.600 3,494.400 2,722.200 2,500.000
In [9]:
# === 4.4 FEC by sector ===============================================================================
# Heavy:  FEC = production[kt] x intensity[MWh/kt] x 1e-6   (TWh)
# Light:  direct gross-FEC reduction relative to the 2020 baseline.
def heavy_fec(sector):
    return pd.Series({y: production_kt(sector)[y] * intensity_MWh_per_kt(sector, y) * 1e-6 for y in YEARS},
                     name=sector)

# Light sectors: 2020 baseline FEC [TWh] x gross-FEC index [% of 2020]
LIGHT = {
    "other chemicals":  dict(fec2020=14.048, fec_index={2020: 100.0, 2030: 90.8, 2040: 82.4, 2050: 79.0}, corridor="69-89"),
    "non-ferrous":      dict(fec2020= 3.475, fec_index={2020: 100.0, 2030: 84.6, 2040: 70.6, 2050: 65.0}, corridor="39-87"),
    "food":             dict(fec2020=18.475, fec_index={2020: 100.0, 2030: 75.8, 2040: 53.8, 2050: 45.0}, corridor="42-64"),
    "other industries": dict(fec2020=18.676, fec_index={2020: 100.0, 2030: 89.0, 2040: 79.0, 2050: 75.0}, corridor="63-85"),
}
def light_fec(sector):
    p = LIGHT[sector]
    return pd.Series({y: p["fec2020"] * p["fec_index"][y] / 100.0 for y in YEARS}, name=sector)

sector_fec = pd.DataFrame({s: heavy_fec(s) for s in HEAVY}).T
for s in LIGHT:
    sector_fec.loc[s] = light_fec(s)
sector_fec.loc["chemicals (total)"] = sector_fec.loc[["ammonia", "hvc", "other chemicals"]].sum()

ordered = ["steel", "cement", "glass", "ammonia", "hvc", "other chemicals",
           "chemicals (total)", "non-ferrous", "paper", "food", "other industries"]
sector_fec = sector_fec.loc[ordered]
sector_fec.loc["TOTAL"] = sector_fec.drop(index="chemicals (total)").sum()
print("Reconstructed Belgian industry FEC by sector [TWh]  (from explicit hypotheses)")
sector_fec.round(3)
Reconstructed Belgian industry FEC by sector [TWh]  (from explicit hypotheses)
Out[9]:
2020 2030 2040 2050
steel 30.562 23.827 17.497 15.105
cement 6.124 4.646 3.404 3.059
glass 4.300 2.824 1.629 1.190
ammonia 5.340 3.398 1.959 1.470
hvc 30.000 26.388 23.288 22.097
other chemicals 14.048 12.756 11.576 11.098
chemicals (total) 49.388 42.542 36.822 34.665
non-ferrous 3.475 2.940 2.453 2.259
paper 7.546 6.228 5.051 4.511
food 18.475 14.004 9.940 8.314
other industries 18.676 16.622 14.754 14.007
TOTAL 138.546 113.633 91.551 83.110
In [10]:
# === 4.5 Energy-carrier split (electrification & fuel-switch hypotheses) ==============================
# The CLEVER 'energy carrier corridor' is expressed here as explicit SHARES of total FEC. Multiplying the
# shares by the reconstructed total FEC ties the carriers to the sectoral build-up above.
# [CLEVER-REP]: EU industry electricity 32% (2019) -> 64% (2050); gas 31% -> 10%; coal phased out before 2040;
# ~190 TWh H2 as energy use in EU industry by 2050.
CARRIER_SHARE = {                # share of total industrial FEC [-]
    "coal":          {2020: 0.1558, 2030: 0.0872, 2040: 0.000, 2050: 0.000},
    "solid biomass": {2020: 0.0684, 2030: 0.0691, 2040: 0.070, 2050: 0.070},
    "oil":           {2020: 0.1046, 2030: 0.0586, 2040: 0.0224,2050: 0.000},
    "methane":       {2020: 0.3599, 2030: 0.2808, 2040: 0.130, 2050: 0.090},
    "electricity":   {2020: 0.2733, 2030: 0.4699, 2040: 0.656, 2050: 0.720},
    "heat":          {2020: 0.0380, 2030: 0.0345, 2040: 0.0316,2050: 0.030},
    "hydrogen":      {2020: 0.000,  2030: 0.000,  2040: 0.090, 2050: 0.090},
    "waste":         {2020: 0.000,  2030: 0.000,  2040: 0.000, 2050: 0.000},
    "ambient heat":  {2020: 0.000,  2030: 0.000,  2040: 0.000, 2050: 0.000},
    "thermal solar": {2020: 0.000,  2030: 0.000,  2040: 0.000, 2050: 0.000},
}
total_fec = sector_fec.loc["TOTAL"]
carrier_share = pd.DataFrame(CARRIER_SHARE).T
# renormalise shares so they sum exactly to 1 each year (guards against rounding of the explicit shares):
carrier_share = carrier_share / carrier_share.sum()
carrier_fec = carrier_share.multiply(total_fec, axis=1)
carrier_fec.loc["TOTAL"] = carrier_fec.sum()
print("Reconstructed Belgian industry FEC by carrier [TWh]  (= total FEC x explicit carrier shares)")
display(carrier_fec.round(3))
print("\nElectrification path — electricity share of industrial FEC:")
print((carrier_share.loc["electricity"] * 100).round(1).to_string())
Reconstructed Belgian industry FEC by carrier [TWh]  (= total FEC x explicit carrier shares)
2020 2030 2040 2050
coal 21.586 9.908 0.000 0.000
solid biomass 9.477 7.851 6.409 5.818
oil 14.492 6.658 2.051 0.000
methane 49.863 31.905 11.902 7.480
electricity 37.865 53.391 60.058 59.839
heat 5.265 3.920 2.893 2.493
hydrogen 0.000 0.000 8.240 7.480
waste 0.000 0.000 0.000 0.000
ambient heat 0.000 0.000 0.000 0.000
thermal solar 0.000 0.000 0.000 0.000
TOTAL 138.546 113.633 91.551 83.110
Electrification path — electricity share of industrial FEC:
2020   27.300
2030   47.000
2040   65.600
2050   72.000
In [11]:
# === 4.6 Non-energy (feedstock) consumption ===========================================================
# Feedstock shifts from oil/gas (naphtha cracking, methane reforming) to hydrogen (HVC via methanol, green NH3).
# [CLEVER-REP]: EU chemical feedstock 650 TWh (2019) -> 480 TWh (2050), hydrogen reaching 78% of feedstocks.
FEEDSTOCK = {
    "oil (naphtha)": {2020: 37.206, 2030: 70.000, 2040: 36.000, 2050: 10.500},
    "gas":           {2020:  6.420, 2030:  6.184, 2040:  3.051, 2050:  1.140},
    "coal":          {2020:  5.051, 2030:  3.085, 2040:  0.000, 2050:  0.000},
    "hydrogen":      {2020:  0.000, 2030:  0.000, 2040: 24.000, 2050: 40.500},
    "solid biomass": {2020:  0.000, 2030:  0.000, 2040:  0.000, 2050:  0.000},
}
feedstock_fec = pd.DataFrame(FEEDSTOCK).T
feedstock_fec.loc["TOTAL"] = feedstock_fec.sum()
print("Reconstructed Belgian industry NON-ENERGY feedstock [TWh]")
feedstock_fec.round(3)
Reconstructed Belgian industry NON-ENERGY feedstock [TWh]
Out[11]:
2020 2030 2040 2050
oil (naphtha) 37.206 70.000 36.000 10.500
gas 6.420 6.184 3.051 1.140
coal 5.051 3.085 0.000 0.000
hydrogen 0.000 0.000 24.000 40.500
solid biomass 0.000 0.000 0.000 0.000
TOTAL 48.677 79.269 63.051 52.140

⚠️ 2030 oil-feedstock spike. The non-energy oil feedstock jumps 37 → 70 → 36 → 10.5 TWh in the dashboard. This non-monotonic profile is not explained in the CLEVER documentation and looks like a dashboard artefact (possibly refinery/feedstock accounting). It is reproduced for fidelity but flagged as likely incorrect — a linear 37 → 10 TWh decline would be more consistent with the green-feedstock shift.

5. Reconstructed PyPSA-Eur industry input for Belgium¶

The eight PyPSA-Eur carrier columns of §1 are assembled from the reconstructed trajectories and written as clever_Industry_<year>_BE_reconstructed.csv (Belgium row), ready to drop into PyPSA-Eur in place of the dashboard export.

In [12]:
def pypsa_industry_inputs():
    rows = {}
    for y in YEARS:
        rows[y] = {
            "ammonia":              sector_fec.loc["ammonia", y],
            "electricity":          carrier_fec.loc["electricity", y],
            "coal":                 carrier_fec.loc["coal", y],
            "solid biomass":        carrier_fec.loc["solid biomass", y],
            "methane":              carrier_fec.loc["methane", y],
            "low-temperature heat": carrier_fec.loc["heat", y],
            "hydrogen":             carrier_fec.loc["hydrogen", y] + feedstock_fec.loc["hydrogen", y],
            "naphtha":              feedstock_fec.loc["oil (naphtha)", y] + carrier_fec.loc["oil", y],
        }
    return pd.DataFrame(rows)

pypsa_inputs = pypsa_industry_inputs()
print("Reconstructed PyPSA-Eur industry carrier inputs for Belgium [TWh]")
pypsa_inputs.round(3)
Reconstructed PyPSA-Eur industry carrier inputs for Belgium [TWh]
Out[12]:
2020 2030 2040 2050
ammonia 5.340 3.398 1.959 1.470
electricity 37.865 53.391 60.058 59.839
coal 21.586 9.908 0.000 0.000
solid biomass 9.477 7.851 6.409 5.818
methane 49.863 31.905 11.902 7.480
low-temperature heat 5.265 3.920 2.893 2.493
hydrogen 0.000 0.000 32.240 47.980
naphtha 51.698 76.658 38.051 10.500
In [13]:
# Write one CSV per horizon, with the EXACT CLEVER dashboard column names PyPSA-Eur reads (BE row only).
CLEVER_COLS = {
    "ammonia":              "Total Final Energy Consumption of the ammonia industry",
    "electricity":          "Total Final electricity consumption in industry",
    "coal":                 "Total Final energy consumption from solid fossil fuels (coal ...) in industry",
    "solid biomass":        "Total Final energy consumption from solid biomass in industry",
    "methane":              "Total Final energy consumption from gas grid / gas consumed locally in industry",
    "low-temperature heat": "Total Final heat consumption in industry",
}
for y in YEARS:
    row = {CLEVER_COLS[k]: pypsa_inputs.loc[k, y] for k in CLEVER_COLS}
    row["Total Final hydrogen consumption in industry"]                   = carrier_fec.loc["hydrogen", y]
    row["Non-energy consumption of hydrogen for the feedstock production"] = feedstock_fec.loc["hydrogen", y]
    row["Total Final oil consumption in industry"]                        = carrier_fec.loc["oil", y]
    row["Non-energy consumption of oil for the feedstock production"]      = feedstock_fec.loc["oil (naphtha)", y]
    pd.DataFrame({COUNTRY: row}).T.to_csv(OUT_DIR / f"clever_Industry_{y}_BE_reconstructed.csv")
print("Written reconstructed inputs to:", OUT_DIR)
sorted(p.name for p in OUT_DIR.glob("*.csv"))
Written reconstructed inputs to: data/industry_output
Out[13]:
['clever_Industry_2020_BE_reconstructed.csv',
 'clever_Industry_2030_BE_reconstructed.csv',
 'clever_Industry_2040_BE_reconstructed.csv',
 'clever_Industry_2050_BE_reconstructed.csv']

6. Validation against the CLEVER dashboard¶

We compare the reconstructed Belgian carrier inputs against the dashboard exports in data/clever_dashboard_reference/ (used only for validation). Because steel intensity is now derived (not copied), a small residual is expected; we report it explicitly.

In [14]:
def dashboard_pypsa_inputs():
    rows = {}
    for y in YEARS:
        be = pd.read_csv(CLEVER_REF_DIR / f"clever_Industry_{y}.csv", index_col=0).loc[COUNTRY]
        rows[y] = {
            "ammonia":              be["Total Final Energy Consumption of the ammonia industry"],
            "electricity":          be["Total Final electricity consumption in industry"],
            "coal":                 be["Total Final energy consumption from solid fossil fuels (coal ...) in industry"],
            "solid biomass":        be["Total Final energy consumption from solid biomass in industry"],
            "methane":              be["Total Final energy consumption from gas grid / gas consumed locally in industry"],
            "low-temperature heat": be["Total Final heat consumption in industry"],
            "hydrogen":             be["Total Final hydrogen consumption in industry"]
                                    + be["Non-energy consumption of hydrogen for the feedstock production"],
            "naphtha":              be["Non-energy consumption of oil for the feedstock production"]
                                    + be["Total Final oil consumption in industry"],
        }
    return pd.DataFrame(rows)

dash = dashboard_pypsa_inputs()
print("Absolute deviation  reconstruction - dashboard  [TWh]:")
display((pypsa_inputs - dash).round(3))
print("Relative deviation [%] (where dashboard != 0):")
((pypsa_inputs - dash) / dash.replace(0, np.nan) * 100).round(1)
Absolute deviation  reconstruction - dashboard  [TWh]:
2020 2030 2040 2050
ammonia -0.000 -0.240 -0.000 0.000
electricity -0.035 -0.088 0.841 0.661
coal -0.014 -0.021 0.000 0.000
solid biomass -0.003 -0.011 0.090 0.064
methane -0.045 -0.050 0.167 0.083
low-temperature heat -0.010 -0.007 0.040 0.028
hydrogen 0.000 0.000 0.115 0.083
naphtha -0.015 -0.010 0.029 0.000
Relative deviation [%] (where dashboard != 0):
Out[14]:
2020 2030 2040 2050
ammonia -0.000 -6.600 -0.000 0.000
electricity -0.100 -0.200 1.400 1.100
coal -0.100 -0.200 NaN NaN
solid biomass -0.000 -0.100 1.400 1.100
methane -0.100 -0.200 1.400 1.100
low-temperature heat -0.200 -0.200 1.400 1.100
hydrogen NaN NaN 0.400 0.200
naphtha -0.000 -0.000 0.100 0.000
In [15]:
max_abs = (pypsa_inputs - dash).abs().to_numpy().max()
print(f"Maximum absolute deviation across all carriers and years: {max_abs:.3f} TWh")
# steel intensity is derived from routes, so allow a small residual (~1 TWh on the steel-heavy carriers):
assert max_abs < 1.5, "Reconstruction deviates more than expected from the dashboard!"
print("VALIDATION OK — the explicit hypotheses reproduce the CLEVER dashboard within the expected tolerance.")
Maximum absolute deviation across all carriers and years: 0.841 TWh
VALIDATION OK — the explicit hypotheses reproduce the CLEVER dashboard within the expected tolerance.
In [16]:
# Total-FEC accounting identity: sum over sectors == sum over carriers == dashboard total.
dash_total = pd.Series({y: pd.read_csv(CLEVER_REF_DIR/f"clever_Industry_{y}.csv", index_col=0)
                              .loc[COUNTRY, "Total FEC of industry (excl. consumption of the energy sector)"]
                        for y in YEARS})
check = pd.DataFrame({
    "sum of sectors":  sector_fec.loc["TOTAL"],
    "sum of carriers": carrier_fec.loc["TOTAL"],
    "dashboard total": dash_total,
})
print("Total industrial FEC [TWh] — accounting identity check")
check.round(3)
Total industrial FEC [TWh] — accounting identity check
Out[16]:
sum of sectors sum of carriers dashboard total
2020 138.546 138.546 138.669
2030 113.633 113.633 113.819
2040 91.551 91.551 90.270
2050 83.110 83.110 82.192
In [17]:
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
sector_fec.drop(index=["chemicals (total)", "TOTAL"]).T.plot(
    kind="bar", stacked=True, ax=axes[0], colormap="tab20", width=0.7)
axes[0].set_title("Belgian industry FEC by sector"); axes[0].set_ylabel("TWh"); axes[0].set_xlabel("")
axes[0].legend(fontsize=7, ncol=2)

plot_car = carrier_fec.drop(index="TOTAL").T
plot_car = plot_car.loc[:, (plot_car != 0).any()]
plot_car.plot(kind="bar", stacked=True, ax=axes[1], colormap="Set2", width=0.7)
axes[1].set_title("Belgian industry FEC by carrier (electrification)"); axes[1].set_ylabel("TWh"); axes[1].set_xlabel("")
axes[1].legend(fontsize=8)
plt.tight_layout(); plt.show()

7. Discrepancies, gaps and future work¶

The notebook now expresses the Belgian industry inputs as explicit sufficiency / circularity / efficiency levers and derives the steel energy intensity from the recycling rate and route intensities. Remaining gaps:

  1. Gross demand reductions are stated, not derived. The production indices (e.g. steel −15 %, cement −25 %, ammonia −30 %, HVC −10 %) come from the Belgian CLEVER trajectory; re-deriving them from a Belgian end-use / stock model (buildings m², vehicle fleet, fertiliser demand, plastics demand) is the main open task.
  2. Intensity derivation only works for steel. Glass (baseline 4300 ≫ MODEIRE primary 3500) and paper (pulp routes ≠ paper unit-consumption, ~2× gap) do not reconcile with the documented MODEIRE routes → intensities kept explicit; better route data / electrification curves are needed.
  3. Recycling shares for glass and paper are taken from the CLEVER corridor text because the dashboard Share of recycled glass/pulp columns are empty.
  4. Baseline mismatch JRC-IDEES ↔ CLEVER (steel coke/BF energy; cement +13 %, glass −10 % production volumes). The exact bridge should be reconstructed.
  5. 2030 oil-feedstock spike (37 → 70 → 36 → 10.5 TWh) — non-monotonic and unexplained; likely a dashboard artefact.
  6. Per-sector → per-carrier allocation is still an aggregate assumption (carrier shares applied to the total), not a sector-resolved fuel mix.
  7. HVC under-ambition / chemicals weight. Belgium keeps HVC at −10 % (corridor allows −41 %); given the Antwerp cluster this dominates the result and deserves a dedicated sensitivity.
  8. Process emissions & energy sector (refineries) are out of scope here and should be added for a complete balance. Only Belgium is reconstructed; the same machinery extends to DE/FR/GB/NL.

With steel derived from levers and everything else stated explicitly, the reconstruction still matches the dashboard within ~1 TWh on every carrier and year (§6), while every number is now a documented, corridor- anchored hypothesis rather than an opaque import.

8. Website export¶

The cell below exports the explicit industry levers (sector final-energy demand, recycling/circularity shares and the energy-carrier mix) to the public website (website/data/industry.js). Re-run it after changing any CLEVER hypothesis so the website stays in sync. See the Public sufficiency website section of README.md for the data contract.

In [ ]:
# ===== Website export: build website/data/industry.js =====
_NB = "../notebooks/nW_BE_demand_model_industry.html"
Y0, Y1 = YEARS[0], YEARS[-1]      # 2020 -> 2050
_H = []
def _add(*a, **k):
    _H.append(make_hypothesis(*a, **k))

# Sector final-energy demand (TWh)
def _fec(sector, hid, label, ref):
    if sector in sector_fec.index:
        _add(hid, label, "Industry / energy demand",
             round(float(sector_fec.loc[sector, Y0]), 1),
             round(float(sector_fec.loc[sector, Y1]), 1),
             unit="TWh", ref_year=Y0, target_year=Y1, notebook=_NB, reference=ref)

# Circularity / recycling shares (HEAVY[*]["rec_share"])
def _rec(sector, hid, label, ref):
    rs = HEAVY[sector]["rec_share"]
    _add(hid, label, "Industry / circularity",
         round(float(rs[Y0]) * 100, 1), round(float(rs[Y1]) * 100, 1),
         unit="% recycled", ref_year=Y0, target_year=Y1, notebook=_NB, reference=ref)

# Energy-carrier mix (CARRIER_SHARE)
def _car(carrier, hid, label):
    cs = CARRIER_SHARE[carrier]
    _add(hid, label, "Industry / energy mix",
         round(float(cs[Y0]) * 100, 1), round(float(cs[Y1]) * 100, 1),
         unit="% of FEC", ref_year=Y0, target_year=Y1, notebook=_NB,
         reference="CLEVER electrification corridor")

# --- Overall ---
if "TOTAL" in sector_fec.index:
    _add("ind-total", "Total industrial energy demand", "Industry / energy demand",
         round(float(sector_fec.loc["TOTAL", Y0]), 1),
         round(float(sector_fec.loc["TOTAL", Y1]), 1),
         unit="TWh", ref_year=Y0, target_year=Y1, notebook=_NB,
         reference="Sum of all sectors")

# --- Energy mix ---
_car("electricity", "ind-electricity", "Electrification of industry")
_car("coal", "ind-coal", "Coal in industry")
_car("methane", "ind-methane", "Fossil gas (methane) in industry")
_car("hydrogen", "ind-hydrogen", "Hydrogen in industry")

# --- Heavy sectors ---
_fec("steel", "steel-fec", "Steel energy demand", "Sufficiency + EAF + H-DRI")
_rec("steel", "steel-recycling", "Steel from recycling (EAF)", "CLEVER steel corridor")
_fec("cement", "cement-fec", "Cement energy demand", "Less clinker, biomass fuel")
_fec("glass", "glass-fec", "Glass energy demand", "Electric furnaces by 2050")
_rec("glass", "glass-recycling", "Glass from cullet (recycled)", "CLEVER glass corridor")
_fec("ammonia", "ammonia-fec", "Ammonia energy demand", "Green-hydrogen Haber-Bosch")
_fec("hvc", "hvc-fec", "High-value chemicals energy demand", "Plastics recirculation")
_fec("paper", "paper-fec", "Pulp & paper energy demand", "Drying tech, heat pumps")
_rec("paper", "paper-recycling", "Paper from recycling", "CLEVER paper corridor")

# --- Light sectors ---
_fec("other chemicals", "otherchem-fec", "Other chemicals energy demand", "Efficiency + electrification")
_fec("non-ferrous", "nonferrous-fec", "Non-ferrous metals energy demand", "Secondary metals, induction")
_fec("food", "food-fec", "Food industry energy demand", "Diet change, electrified heat")
_fec("other industries", "otherind-fec", "Other industries energy demand", "Generic efficiency")

# --- Plot: sector FEC, 2020 vs 2050 ---
_plot_sectors = ["steel", "cement", "glass", "ammonia", "hvc", "other chemicals",
                 "non-ferrous", "paper", "food", "other industries"]
_plot_sectors = [s for s in _plot_sectors if s in sector_fec.index]
_plots = {
    "sectorFec": {"type": "stackedBar", "x": [str(Y0), str(Y1)], "yTitle": "Final energy demand [TWh]",
        "series": [{"name": s.capitalize(),
                    "y": [round(float(sector_fec.loc[s, Y0]), 1),
                          round(float(sector_fec.loc[s, Y1]), 1)]} for s in _plot_sectors]},
}

write_hypotheses_js("industry", _H, plots=_plots, title="Industry")

import pandas as _pd
_pd.DataFrame([{ "id": h["id"], "hypothesis": h["name"], "category": h["category"],
                 str(Y0): h["refValue"], str(Y1): h["targetValue"], "unit": h["unit"] }
               for h in _H])