pypsa-eur/scripts/solve_operations_network_other_year_myopic.py
2023-04-30 08:53:00 +00:00

168 lines
5.3 KiB
Python

# -*- coding: utf-8 -*-
"""
Solve myopic operations network.
"""
import logging
import pandas as pd
import pypsa
from helper import override_component_attrs
from solve_network import prepare_network, solve_network
from solve_operations_network import (
add_load_shedding,
remove_unused_components,
set_parameters_from_optimized,
)
logger = logging.getLogger(__name__)
pypsa.pf.logger.setLevel(logging.WARNING)
def prepare_myopic(n, config, store_soc, storage_unit_soc):
n.stores.e_cyclic = False
n.storage_units.cyclic_state_of_charge = False
biomass_stores = n.stores.carrier.str.isin(["solid biomass", "biogas"])
biomass_potential = n.stores.loc[biomass_stores, "e_initial"]
# storage level contiguity across years
n.stores.e_initial = store_soc
n.storage_units.state_of_charge_initial = storage_unit_soc
# replace co2 limit with co2 price
n.remove("GlobalConstraint", "CO2Limit")
n.stores.at["co2 atmosphere", "marginal_cost"] = -config["co2_price"]
# handle co2 sequestration
assert (
sum(n.stores.carriers == "co2 stored") == 1
), "Myopic operation not implemented for spatially resolved CO2 sequestration."
n.stores.at["co2 stored", "e_nom"] = config["co2_sequestration_limit"] * 1e6 # t/a
# reset co2 emissions
n.stores.loc[n.stores.carrier == "co2 stored", "e_initial"] = 0.0
n.stores.at["co2 atmosphere", "e_initial"] = 0.0
# replenish fossil gas and oil with 1000 TWh each
fossil_stores = n.stores.carrier.str.isin(["gas", "oil"])
n.stores.loc[fossil_stores, "e_initial"] = 1e9
n.stores.loc[fossil_stores, "e_nom"] = 10e9
# replenish annual solid biomass and biogas potentials
n.stores.loc[biomass_stores, "e_initial"] = biomass_potential
# set storage bidding prices
bidding_prices = config["bidding_prices"]
for c in n.iterate_components({"Store", "Link", "StorageUnit"}):
c.df.marginal_cost.update(c.df.carrier.map(bidding_prices).dropna())
# deduct industry solid biomass
assert (
sum(n.stores.carriers == "solid biomass") == 1
), "Myopic operation not implemented for spatially resolved solid biomass."
n.stores.at["EU solid biomass", "e_initial"] -= (
n.loads.at["solid biomass for industry", "p_set"] * 8760
)
n.remove("Load", "solid biomass for industry")
return n
def solve_network_myopic(n, config, opts="", **kwargs):
rolling_horizon = config["operations"]["rolling_horizon"]
freq = int(pd.infer_freq(n.snapshots)[:-1])
window = rolling_horizon["window"] * 24 // freq
overlap = rolling_horizon["overlap"] * 24 // freq
kept = window - overlap
length = len(n.snapshots)
assert (
kept > 0
), f"Overlap ({overlap} days) must be smaller than windows ({window} days)."
for i in range(length // kept):
snapshots = n.snapshots[i * kept : (i + 1) * kept + overlap]
logger.info(f"Optimising operations from {snapshots[0]} to {snapshots[-1]}")
n = solve_network(n, config, opts=opts, snapshots=snapshots, **kwargs)
last_kept = n.snapshots[(i + 1) * kept - 1]
logger.info(f"Setting initial SOCs from {last_kept} for next iteration.\n")
n.stores.e_initial = n.stores_t.e.loc[last_kept]
n.storage_units.state_of_charge_initial = n.storage_units_t.state_of_charge.loc[
last_kept
]
# final segment until end of year
snapshots = n.snapshots[(i + 1) * kept :]
n = solve_network(n, config, opts=opts, snapshots=snapshots, **kwargs)
return n
if __name__ == "__main__":
if "snakemake" not in globals():
from helper import mock_snakemake
snakemake = mock_snakemake(
"solve_operations_network_myopic",
capacity_year=1952,
simpl="",
opts="",
clusters=37,
lv=2.0,
sector_opts="Co2L0-25H-T-H-B-I-A",
planning_horizons=2030,
weather_year=2013,
)
logging.basicConfig(
filename=snakemake.log.python, level=snakemake.config["logging_level"]
)
tmpdir = snakemake.config["solving"].get("tmpdir")
if tmpdir is not None:
from pathlib import Path
Path(tmpdir).mkdir(parents=True, exist_ok=True)
config = snakemake.config["operations"]
overrides = override_component_attrs(snakemake.input.overrides)
n = pypsa.Network(snakemake.input.pre, override_component_attrs=overrides)
n_post = pypsa.Network(snakemake.input.post, override_component_attrs=overrides)
n = set_parameters_from_optimized(n, n_post)
del n_post
n_previous = pypsa.Network(
snakemake.input.previous, override_component_attrs=overrides
)
store_soc = n_previous.stores_t.e.iloc[-1]
storage_unit_soc = n_previous.storage_units_t.state_of_charge.iloc[-1]
del n_previous
n = remove_unused_components(n)
n = add_load_shedding(n)
n = prepare_myopic(n, config, store_soc, storage_unit_soc)
opts = snakemake.wildcards.sector_opts.split("-")
solve_opts = snakemake.config["solving"]["options"]
solve_opts["skip_iterations"] = True
n = prepare_network(n, solve_opts)
n = solve_network_myopic(
n,
config=snakemake.config,
opts=opts,
solver_dir=tmpdir,
solver_logfile=snakemake.log.solver,
)
n.export_to_netcdf(snakemake.output[0])