Skip to content
3 changes: 3 additions & 0 deletions .readthedocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,6 @@ build:
python:
install:
- requirements: doc/requirements.txt

mkdocs:
configuration: mkdocs.yml
117 changes: 117 additions & 0 deletions docs/functionality/costs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
# Costs

## Overview

The model uses cost assumptions from
[technology-data v0.9.0](https://github.com/PyPSA/technology-data) as a
baseline (stored in `data/costs_{planning_horizon}.csv`). Selected investment
costs are **overridden per planning horizon** using values defined in
`config/config.agora.yaml`.

---

## Snakemake Rule

**Rule:** `update_costs_csv`
**Script:** `scripts/update_costs_csv.py`

This rule reads the baseline costs CSV and updates specific technology
investment costs before the network is built.

---

## How It Works

```python title="scripts/update_costs_csv.py"
def insert_new_costs(costs_file, invest_update, filename):
costs = pd.read_csv(costs_file).set_index('technology')
for key, investment in invest_update.items():
mask = (costs.parameter == "investment") & (costs.index == key)
costs.loc[mask, 'value'] = investment
costs.to_csv(filename)

# Called with per-horizon values from config:
investment_update = snakemake.params.invest_update[int(snakemake.wildcards.planning_horizons)]
insert_new_costs(snakemake.input.costs, investment_update, snakemake.output.costs)
```

---

## Technologies Updated in All Scenarios

These investment costs are overridden for **every scenario** and every
planning year:

| Technology | Config key |
|---|---|
| HVDC overhead line | `HVDC overhead` |
| HVDC submarine cable | `HVDC submarine` |
| HVAC overhead line | `HVAC overhead` |
| H₂ electrolysis | `electrolysis` |
| CO₂ pipeline | `CO2 pipeline` |
| CO₂ submarine pipeline | `CO2 submarine pipeline` |

---

## Additional Technologies (CE Flexibility Scenario Only)

In the CE flexibility scenario (`config.CE_flexibility.yaml`), investment
costs for the following heat-sector technologies are set to **20% above the
technology-data v0.9.0 baseline** for every planning year:

| Technology (config key) |
|---|
| `central air-sourced heat pump` |
| `central ground-sourced heat pump` |
| `decentral air-sourced heat pump` |
| `decentral ground-sourced heat pump` |
| `central solid biomass CHP` |
| `central gas CHP` |
| `micro CHP` |
| `biomass CHP capture` |
| `central water tank storage` |
| `decentral water tank storage` |
| `central resistive heater` |
| `decentral resistive heater` |

Example for 2030 (EUR/kW unless otherwise noted):

| Technology | Baseline (tech-data) | CE Flexibility (+20%) |
|---|---|---|
| `central air-sourced heat pump` | 906.1 | 1087.3 |
| `decentral air-sourced heat pump` | 899.5 | 1079.4 |
| `central gas CHP` | 592.6 | 711.1 |
| `micro CHP` | 7841.7 | 9410.1 |
| `central water tank storage` | 0.576 EUR/kWh | 0.691 EUR/kWh |

---

## How to Change Costs

### Step 1 — Find the correct technology name

Technology names must exactly match the `technology` column in
`data/costs_<year>.csv`, which in turn comes from
[technology-data v0.9.0](https://github.com/PyPSA/technology-data/blob/v0.9.0/outputs/).

### Step 2 — Edit the config

Add or update the technology under `costs.investment` in
`config/config.agora.yaml` (base config, applies to all scenarios) or in a
scenario-specific file to override only for that scenario. Units depend on
the technology — check the `unit` column in the costs CSV first.

```yaml title="config/config.agora.yaml or config/scenarios/config.*.yaml"
costs:
investment:
2020:
HVDC overhead: 2000.0 # EUR/MW — IEA 2023
HVDC submarine: 2000.0 # EUR/MW
HVAC overhead: 500.0 # EUR/MW
electrolysis: 1260.0 # EUR/kW — IEA Global Hydrogen Review 2023
CO2 pipeline: 13000.0 # EUR/MW — Danish Energy Agency
CO2 submarine pipeline: 33000.0 # EUR/MW — Danish Energy Agency
2025:
HVDC overhead: 2000.0
...
```
111 changes: 111 additions & 0 deletions docs/functionality/electricity.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
# Electricity Network

## Overview

TYNDP electricity transmission projects are enforced on the network in
`prepare_sector_network`. Per-project capacity limits are read from two CSV
files and applied for each planning horizon. Cross-border lines and links can
optionally be made extendable from a given year onwards.

---

## Snakemake Rule

**Rule:** `prepare_sector_network`
**Script:** `scripts/prepare_sector_network.py` — function `set_capacity_tyndp_elec()`

**Input data:**

- `data/links_2020-2050.csv` — DC link capacity limits per year
- `data/lines_2020-2050.csv` — AC line capacity additions per year and TYNDP status

---

## Scenario Behaviour

| Scenario | TYNDP statuses included | Cross-border extendable from |
|---|---|---|
| CE, CN | `under construction` | All horizons (`optimize_after: true`) |
| SE, SN | `in planning`, `in permitting`, `under construction` | 2040 onwards (`optimize_after: 2040`) |

---

## CSV Data Format

### `data/links_2020-2050.csv`

Columns: `name`, `bus0`, `bus1`, `p_nom`, `2020`, `2025`, …, `2050`

Each year column contains a fraction of `p_nom` or `opt`:

| Value | Effect |
|---|---|
| `0.0` | Link not active in this year |
| `1` (float > 0) | `p_nom_min = p_nom_max = p_nom × value`, link made extendable |
| `opt` | `p_nom_min = p_nom`, link remains extendable without upper bound |

### `data/lines_2020-2050.csv`

Columns: `name`, `bus0`, `bus1`, then one column per `{year}{status}` combination
(e.g., `2025under construction`, `2030in planning`).

Each cell holds the capacity addition [MW] for that year and status, or `opt`
for a soft minimum (extendable without upper bound). Values with an `opt`
suffix set the minimum and leave the line extendable.

---

## Configuration

TYNDP electricity enforcement is disabled by default and enabled per scenario:

```yaml title="config/config.agora.yaml"
policy_plans:
include_tyndp_elec:
enable: false # set to true in scenario configs
allowed_statuses:
- under consideration
- in planning
- in permitting
- under construction
optimize_after: true # true = all horizons; integer year = from that year
```

```yaml title="config/scenarios/config.CE.yaml"
policy_plans:
include_tyndp_elec:
enable: true
allowed_statuses:
- under construction
# optimize_after inherits true from base config
```

```yaml title="config/scenarios/config.SE.yaml"
policy_plans:
include_tyndp_elec:
enable: true
allowed_statuses:
- in planning
- in permitting
- under construction
optimize_after: 2040
```

---

## How to Change

### Which TYNDP projects are enforced

Edit `policy_plans.include_tyndp_elec.allowed_statuses` in the scenario config.
Only projects whose TYNDP status tag matches an entry in this list are enforced.

### When cross-border links become extendable

Set `policy_plans.include_tyndp_elec.optimize_after` to `true` (all horizons)
or to an integer year (e.g., `2040`) in the scenario config.

### Capacity values per horizon

Edit the year columns in `data/links_2020-2050.csv` (DC links) or the
`{year}{status}` columns in `data/lines_2020-2050.csv` (AC lines).
122 changes: 122 additions & 0 deletions docs/functionality/expansion.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
# Expansion Limits

## Overview

Minimum and maximum capacity constraints are enforced for selected renewable
and other carriers across European countries for each planning year.
The constraints target **total cumulative installed capacity** per
country and carrier. Existing (non-extendable) capacity is subtracted before
the bound is applied to the extendable `p_nom` decision variable.

---

## Snakemake Rule

**Script:** `scripts/solve_network.py` — function
`add_country_carrier_limit_constraints`, called from `extra_functionality`
**Rule:** `solve_sector_network_myopic`
**Data files:** selected via `policy_plans.agg_p_nom_limits` in the scenario config

| Scenario | Data file | Min/max band |
|---|---|---|
| CE, SE, and variants | `data/agg_p_nom_minmax_european.csv` | ±20 % |
| CN, SN | `data/agg_p_nom_minmax_national.csv` | ±15 % |

---

## Carriers and Components

| Carrier | Component | Geographic scope | Years with data |
|---|---|---|---|
| `solar` | Generator | All modelled countries | 2030, 2050 |
| `onwind` | Generator | All modelled countries | 2030, 2050 |
| `offwind` | Generator | All modelled countries | 2030, 2050 |
| `H2 Electrolysis` | Link | European-wide (`Eur`) + Romania | 2030 |
| `gas for electricity` | Link | Poland (2020–2030), Romania (2020–2050) | varies |
| `co2 sequestered` | Store | Romania | 2030–2050 |

`gas for electricity` is a **grouped carrier**: OCGT, CCGT, urban central gas
CHP (and CHP CC), micro gas CHP variants, and `allam` are all counted together
against this limit. For Links, the constraint is applied to output capacity
(`p_nom × efficiency`), not input capacity.

---

## CSV Format

Both files share the same long format: one row per (country, carrier, year)
combination. Empty `min` or `max` cells mean no constraint for that direction.

| Column | Description |
|---|---|
| `country` | ISO2 country code, or `Eur` for European-wide aggregate |
| `carrier` | Carrier name as it appears in the constraint (see grouping above) |
| `year` | Planning year (integer) |
| `min` | Minimum total installed capacity [MW or t for CO₂] — blank = unconstrained |
| `max` | Maximum total installed capacity [MW or t for CO₂] — blank = unconstrained |
| `unit` | Unit string (e.g. `MW`, `MW_H2`, `t`) |
| `Source` | Source reference |
| `component` | PyPSA component type in **lowercase** (`generator`, `link`, `store`) |

Example rows:

```csv
country,carrier,year,min,max,unit,Source,component
DE,solar,2030,172000.0,258000.0,MW,Ember,generator
DE,solar,2050,9600.0,175423.9,MW,Atlite,generator
PL,gas for electricity,2025,,5500.0,MW,Forum Energii 2025/03/07,link
Eur,H2 Electrolysis,2030,46978.8,111532.4,MW_H2,IEA hydrogen map,link
RO,co2 sequestered,2030,,10000000.0,t,NZIA communicated by EPG,store
```

---

## Config

```yaml
policy_plans:
agg_p_nom_limits: data/agg_p_nom_minmax_european.csv # default base config
```

Scenario configs override this key:

```yaml
# config.CE.yaml / config.SE.yaml
policy_plans:
agg_p_nom_limits: data/agg_p_nom_minmax_european.csv

# config.CN.yaml / config.SN.yaml
policy_plans:
agg_p_nom_limits: data/agg_p_nom_minmax_national.csv
```

!!! note
The two files differ only in the ±% band applied to the 2030 Ember/NECP
targets: ±20 % in the European file and ±15 % in the national file.
The 2050 upper bounds (Atlite technical potentials) and all non-renewable
entries are identical.

---

## How to Modify

### Change a capacity limit for a country

Edit the relevant CSV directly. Each row is a single (country, carrier, year)
entry. For example, to raise the solar minimum in Spain for 2030:

```csv
ES,solar,2030,130000.0,195000.0,MW,Custom,generator
```

### Add a new country or carrier

Add a new row with `country`, `carrier`, `year`, `min`/`max`, and `component`.
Make sure `carrier` matches the grouped name (e.g. use `offwind`, not
`offwind-ac`) and `component` is lowercase.

### Add a European-wide constraint

Use `Eur` as the `country` value. The code automatically duplicates every
component into an `Eur` group and adds both country-level and European-level
`p_nom` to the left-hand side of the constraint.
Loading