Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
3b004e1
feat: set marginal costs for biomass and biogas generators
measrainsey May 28, 2026
20a86c1
feat: remove volume limits on biomass and biogas dispatch
measrainsey May 28, 2026
7f2a678
chore: remove checks and logs
measrainsey May 28, 2026
bcc5199
Merge branch 'master' into feat/bio-dispatch
measrainsey Jun 15, 2026
3c3be42
Merge branch 'master' into feat/bio-dispatch
measrainsey Jun 16, 2026
68634be
chore: remove logger and update docs
measrainsey Jun 16, 2026
d6d7114
refac: make simple refactoring and add comments
measrainsey Jun 16, 2026
9f233ac
feat: revert `scripts/cba/solve_cba_network.py` back to `master`
measrainsey Jun 16, 2026
d7c2854
feat: add slack to biomass/biogas dispatch `e_sum_min`
measrainsey Jun 17, 2026
ea075e4
feat: increase slack to 40%
measrainsey Jun 17, 2026
1977d07
docs: add #719 to doc/release_notes
measrainsey Jun 17, 2026
0698bd4
refac: use `n_msv.snapshots` instead of `msv_mp.index`
measrainsey Jul 2, 2026
9dc4fee
feat: directly set marginal price as marginal cost, instead of adding
measrainsey Jul 2, 2026
be81501
chore: edit comment
measrainsey Jul 2, 2026
ee16ef4
feat: create config option `cba.biomass_biogas_slack` for slack inste…
measrainsey Jul 2, 2026
ab0f708
refac: filter by both carriers and volume limits
measrainsey Jul 3, 2026
622132e
refac: remove setting marginal cost dataframe
measrainsey Jul 3, 2026
dde4f58
chore: add type hints
measrainsey Jul 3, 2026
ca262db
feat: add `cba.biomass_biogas_slack` config option as rule input
measrainsey Jul 3, 2026
9413b84
Merge branch 'master' into feat/bio-dispatch
measrainsey Jul 3, 2026
e5195a7
fix: add back Generator type
measrainsey Jul 3, 2026
7dbda4a
Merge branch 'master' into feat/bio-dispatch
measrainsey Jul 6, 2026
ea3aa4e
docs: add #719 to doc/release_notes
measrainsey Jul 6, 2026
ecbd803
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jul 6, 2026
5532a4f
chore: add details in comments of configs
measrainsey Jul 6, 2026
34f15ec
docs: change function description
measrainsey Jul 6, 2026
4e0abee
chore: edit comment for slack in config/test/config.tyndp.yaml
measrainsey Jul 8, 2026
41b387e
doc: edit link in PR description
measrainsey Jul 8, 2026
cb0a84f
refac: change `has_volume_limit` to bool
measrainsey Jul 8, 2026
d973cdb
Merge branch 'master' into feat/bio-dispatch
measrainsey Jul 9, 2026
7a6d7af
feat: remove `e_sum` energy budget constraint on biomass/biogas slack
measrainsey Jul 9, 2026
29e7e87
feat: remove `cba.biomass_biogas_slack` config option
measrainsey Jul 9, 2026
4930a70
refac: remove `get_components_with_volume_limits()` function
measrainsey Jul 9, 2026
7cf340d
docs: edit PR description in doc/release_notes
measrainsey Jul 9, 2026
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
2 changes: 2 additions & 0 deletions doc/release_notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
- Add `include_objective_constant` and `assign_all_duals` to solving config validator.
- Add `gurobi-simplex` as solver option.

* Change dispatch of biomass and biogas generators in CBA by (a) setting the buses' marginal prices as the generators' marginal costs and (b) removing energy budget constraints ([#719](https://github.com/open-energy-transition/open-tyndp/pull/719)).

**Bugfixes and Compatibility**

* Rename bus for `t339` project (Tyrrhenian) from ITSI to ITVI ([#751](https://github.com/open-energy-transition/open-tyndp/pull/751)).
Expand Down
53 changes: 51 additions & 2 deletions scripts/cba/prepare_rolling_horizon.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,8 +135,8 @@ def disable_volume_limits(n: pypsa.Network):
for c in n.components[{"Generator", "Link"}]:
has_e_sum_min = isfinite(c.static.get("e_sum_min", []))
if has_e_sum_min.any():
c.static["has_volume_limit"] = 0
c.static.loc[has_e_sum_min, "has_volume_limit"] = 1
c.static["has_volume_limit"] = False
c.static.loc[has_e_sum_min, "has_volume_limit"] = True
c.static.loc[has_e_sum_min, "e_sum_min"] = -inf
c.static.loc[has_e_sum_min, "e_sum_max"] = inf

Expand Down Expand Up @@ -196,6 +196,52 @@ def apply_msv_to_network(
n.c[c].dynamic["marginal_cost"].loc[:, s_i] = msv


def apply_biomass_biogas_bus_marginal_prices(
Comment thread
tgilon marked this conversation as resolved.
n: pypsa.Network,
n_msv: pypsa.Network,
carriers: list[str] = ["solid biomass", "biogas"],
resample_method: str = "ffill",
):
"""
Set biomass/biogas buses' marginal prices as the generators' marginal costs.

For each biomass/biogas generator in the rolling horizon network:
use the marginal price time series of its attached bus from the MSV network and
set the marginal price as the generator's marginal cost.

Parameters
----------
n : pypsa.Network
Target network.
n_msv : pypsa.Network
Solved MSV network containing bus marginal prices.
carriers : list[str], optional
Generator carriers to apply bus marginal prices to. Defaults to ["solid biomass", "biogas"].
resample_method : str, optional
Method for resampling MSV bus marginal prices to target resolution. Default "ffill".
"""
if isinstance(carriers, str):
carriers = [carriers]

# Get bus marginal prices from MSV network
msv_mp = n_msv.buses_t.marginal_price

# Resample marginal prices if needed
bus_mp = (
msv_mp
if n_msv.snapshots.equals(n.snapshots)
else resample_msv_to_target(msv_mp, n.snapshots, method=resample_method)
)

# Get index of generators with target carriers
gen_index = n.generators.index[n.generators.carrier.isin(carriers)]

# Set marginal cost for each generator based on the marginal price of its bus
for g in gen_index:
bus = n.generators.at[g, "bus"]
n.generators_t.marginal_cost[g] = bus_mp[bus].reindex(n.snapshots)


def set_initial_state_from_pf(
n: pypsa.Network,
n_msv: pypsa.Network,
Expand Down Expand Up @@ -359,6 +405,9 @@ def fix_reservoir_soc_at_boundaries(
# Apply marginal storage value to all non-cyclic carriers
apply_msv_to_network(n, n_msv, cyclic_carriers, resample_method)

# Add bus marginal prices to the marginal costs of the biomass/biogas generators
apply_biomass_biogas_bus_marginal_prices(n, n_msv, resample_method=resample_method)

# Fix reservoir state of charge at window boundaries from perfect foresight
soc_boundary_carriers = snakemake.params.get("soc_boundary_carriers", [])
cba_solving = snakemake.config.get("cba", {}).get("solving", {})
Expand Down
20 changes: 1 addition & 19 deletions scripts/cba/solve_cba_network.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,24 +191,6 @@ def optimize_with_rolling_horizon(
n.storage_units_t.state_of_charge.loc[snapshots[start - 1]]
)

# Set per-window energy budgets for volume-limited components
# (biomass, biogas) based on PF dispatch stored in generators_t.p
for c_name in ["Generator", "Link"]:
c = n.c[c_name]
if "has_volume_limit" not in c.static.columns:
continue
vol_idx = c.static.index[c.static["has_volume_limit"] == 1]
if vol_idx.empty:
continue
p_col = "p" if c_name == "Generator" else "p0"
pf_p = c.dynamic[p_col]
for comp in vol_idx:
if comp not in pf_p.columns:
continue
window_energy = pf_p.loc[sns, comp].sum()
c.static.loc[comp, "e_sum_min"] = window_energy
c.static.loc[comp, "e_sum_max"] = window_energy

status, condition = n.optimize(sns, **kwargs) # type: ignore

# If solve is successful, dispose of Model object to release license before next rolling horizon
Expand Down Expand Up @@ -341,7 +323,7 @@ def solve_network(
snakemake = mock_snakemake(
"solve_cba_network",
run="NT",
cba_project="t4",
cba_project="t16",
planning_horizons="2030",
configfiles=["config/config.tyndp.yaml"],
)
Expand Down