123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338 |
- import pandas as pd
- import numpy as np
- from matplotlib import pyplot as plt
- import matplotlib.dates as mdates
- import argparse
- import yaml
- from mix_simul.scenarios import Scenario
- parser = argparse.ArgumentParser()
- parser.add_argument(
- "--begin",
- help="begin date (YYYY-MM-DD), between 1985-01-01 and 2015-01-01",
- required=True,
- )
- parser.add_argument(
- "--end",
- help="end date (YYYY-MM-DD), between 1985-01-01 and 2015-01-01",
- required=True,
- )
- parser.add_argument(
- "--flexibility", help="enable load flexibility modeling", action="store_true"
- )
- parser.add_argument(
- "--scenarios",
- help="path to scenarios parameters yml file",
- default="scenarios/rte_2050.yml",
- )
- args = parser.parse_args()
- with open(args.scenarios, "r") as f:
- scenarios = yaml.load(f, Loader=yaml.FullLoader)
- potential = pd.read_parquet("data/potential.parquet")
- potential = potential.loc["1985-01-01 00:00:00":"2015-01-01 00:00:00", :]
- potential.fillna(0, inplace=True)
- begin = args.begin
- end = args.end
- flexibility = args.flexibility
- potential = potential.loc[(slice(f"{begin} 00:00:00", f"{end} 00:00:00"), "FR"), :]
- # intermittent sources potential
- p = potential[["onshore", "offshore", "solar"]].to_xarray().to_array()
- p = np.insert(p, 3, 0.7, axis=0) # nuclear power-like
- times = potential.index.get_level_values(0)
- n_scenarios = len(scenarios)
- fig, axes = plt.subplots(nrows=n_scenarios, ncols=2, sharex="col", sharey=True)
- w, h = fig.get_size_inches()
- fig.set_figwidth(w * 1.5)
- fig.set_figheight(h * 1.5)
- fig_storage, axes_storage = plt.subplots(
- nrows=n_scenarios, ncols=2, sharex="col", sharey=True
- )
- fig_storage.set_figwidth(w * 1.5)
- fig_storage.set_figheight(h * 1.5)
- fig_dispatch, axes_dispatch = plt.subplots(
- nrows=n_scenarios, ncols=2, sharex="col", sharey=True
- )
- fig_dispatch.set_figwidth(w * 1.5)
- fig_dispatch.set_figheight(h * 1.5)
- fig_gap_distribution, axes_gap_distribution = plt.subplots(
- nrows=int(np.ceil(n_scenarios / 2)), ncols=2, sharex="col", sharey=True
- )
- fig_gap_distribution.set_figwidth(w * 1.5)
- fig_gap_distribution.set_figheight(h * 1.5)
- months = ["Février", "Juin"]
- labels = [
- "load (GW)",
- "production (GW)",
- "available power (production-storage) (GW)",
- "power deficit",
- ]
- labels_storage = ["Batteries (TWh)", "STEP (TWh)", "P2G (TWh)"]
- labels_dispatch = ["Hydro (GW)", "Biomass (GW)", "Thermal (GW)"]
- date_fmt = mdates.DateFormatter("%d/%m")
- # for step in np.linspace(start, stop, 2050-2022, True)[::-1]:
- row = 0
- for scenario in scenarios:
- if not flexibility:
- scenarios[scenario]["flexibility_power"] = 0
- scenario_model = Scenario(**scenarios[scenario])
- S, load, production, gap, storage, dp = scenario_model.run(times, p)
- print(f"{scenario}:", S, gap.max(), np.quantile(gap, 0.95))
- print(
- f"exports: {np.minimum(np.maximum(-gap, 0), 39).sum()/1000} TWh; imports: {np.minimum(np.maximum(gap, 0), 39).sum()/1000} TWh"
- )
- print(
- f"dispatchable: "
- + ", ".join([f"{dp[i].sum()/1000:.2f} TWh" for i in range(dp.shape[0])])
- )
- potential["load"] = load
- potential["production"] = production
- potential["available"] = production - np.diff(storage.sum(axis=0), append=0)
- potential["gap"] = gap
- for i in range(3):
- potential[f"storage_{i}"] = np.diff(
- storage[i, :], append=0
- ) # storage[i,:]/1000
- potential[f"storage_{i}"] = storage[i, :] / 1000
- for i in range(dp.shape[0]):
- potential[f"dispatch_{i}"] = dp[i, :]
- potential["dispatch"] = dp.sum(axis=0)
- data = [
- potential.loc[(slice("2013-02-01 00:00:00", "2013-03-01 00:00:00"), "FR"), :],
- potential.loc[(slice("2013-06-01 00:00:00", "2013-07-01 00:00:00"), "FR"), :],
- ]
- for col in range(2):
- ax = axes[row, col] if axes.ndim > 1 else axes[col]
- ax.plot(
- data[col].index.get_level_values(0),
- data[col]["load"],
- label="adjusted load (GW)",
- lw=1,
- )
- ax.plot(
- data[col].index.get_level_values(0),
- data[col]["production"],
- label="production (GW)",
- ls="dotted",
- lw=1,
- )
- ax.plot(
- data[col].index.get_level_values(0),
- data[col]["available"],
- label="available power (production-d(storage)/dt) (GW)",
- lw=1,
- )
- ax.fill_between(
- data[col].index.get_level_values(0),
- data[col]["available"],
- data[col]["load"],
- where=data[col]["load"] > data[col]["available"],
- color="red",
- alpha=0.15,
- )
- ax.xaxis.set_major_formatter(date_fmt)
- ax.text(
- 0.5,
- 0.87,
- f"Scénario {scenario} ({months[col]})",
- ha="center",
- transform=ax.transAxes,
- )
- ax.set_ylim(10, 210)
- ax = axes_storage[row, col] if axes.ndim > 1 else axes_storage[col]
- for i in np.arange(3):
- if i == 2:
- base = 0
- else:
- base = np.sum(
- [data[col][f"storage_{j}"] for j in np.arange(i + 1, 3)], axis=0
- )
- ax.fill_between(
- data[col].index.get_level_values(0),
- base,
- base + data[col][f"storage_{i}"],
- label=f"storage {i}",
- alpha=0.5,
- )
- ax.plot(
- data[col].index.get_level_values(0),
- base + data[col][f"storage_{i}"],
- label=f"storage {i}",
- lw=0.25,
- )
- ax.xaxis.set_major_formatter(date_fmt)
- ax.text(
- 0.5,
- 0.87,
- f"Scénario {scenario} ({months[col]})",
- ha="center",
- transform=ax.transAxes,
- )
- ax = axes_dispatch[row, col] if axes.ndim > 1 else axes_dispatch[col]
- for i in range(dp.shape[0]):
- if i == 0:
- base = 0
- else:
- base = np.sum(
- [data[col][f"dispatch_{j}"] for j in np.arange(i)], axis=0
- )
- ax.fill_between(
- data[col].index.get_level_values(0),
- base,
- base + data[col][f"dispatch_{i}"],
- label=f"dispatch {i}",
- alpha=0.5,
- )
- ax.plot(
- data[col].index.get_level_values(0),
- base + data[col][f"dispatch_{i}"],
- label=f"dispatch {i}",
- lw=0.25,
- )
- ax.xaxis.set_major_formatter(date_fmt)
- ax.text(
- 0.5,
- 0.87,
- f"Scénario {scenario} ({months[col]})",
- ha="center",
- transform=ax.transAxes,
- )
- div = int(np.ceil(n_scenarios / 2))
- ax = (
- axes_gap_distribution[row % div, row // div]
- if axes_gap_distribution.ndim > 1
- else axes_gap_distribution[row % div]
- )
- hist, bin_edges = np.histogram(-gap, bins=1000)
- hist = np.cumsum(hist)
- hist = 100 * (hist - hist.min()) / hist.ptp()
- keep = np.abs(bin_edges[:-1]) < 50
- ax.plot(bin_edges[:-1][keep], hist[keep], lw=1, label="power gap", color="#ff7f00")
- years = pd.date_range(start=begin, end=end, freq="Y")
- for i in range(len(years) - 1):
- year_data = potential.loc[
- (slice(f"{years[i]} 00:00:00", f"{years[i+1]} 00:00:00"), "FR"), "gap"
- ]
- hist, bin_edges = np.histogram(-year_data, bins=1000)
- hist = np.cumsum(hist)
- hist = 100 * (hist - hist.min()) / hist.ptp()
- keep = np.abs(bin_edges[:-1]) < 50
- ax.plot(
- bin_edges[:-1][keep], hist[keep], lw=0.5, alpha=0.2, color="#ff7f00"
- )
- ax.text(0.5, 0.87, f"Scénario {scenario}", ha="center", transform=ax.transAxes)
- row += 1
- for axs in [axes, axes_dispatch, axes_storage]:
- if axes.ndim > 1:
- for label in (
- axs[-1, 0].get_xmajorticklabels() + axs[-1, 1].get_xmajorticklabels()
- ):
- label.set_rotation(30)
- label.set_horizontalalignment("right")
- else:
- for label in axs[0].get_xmajorticklabels() + axs[-1].get_xmajorticklabels():
- label.set_rotation(30)
- label.set_horizontalalignment("right")
- flex = "With" if flexibility else "Without"
- plt.subplots_adjust(wspace=0, hspace=0)
- fig.suptitle(
- f"Simulations based on {begin}--{end} weather data.\n{flex} consumption flexibility; no nuclear seasonality (unrealistic)"
- )
- fig.text(1, 0, "Lucas Gautheron", ha="right")
- fig.legend(
- labels,
- loc="lower right",
- bbox_to_anchor=(1, -0.1),
- ncol=len(labels),
- bbox_transform=fig.transFigure,
- )
- fig.savefig("output/load_supply.png", bbox_inches="tight", dpi=200)
- fig_storage.suptitle(
- f"Simulations based on {begin}--{end} weather data.\n{flex} consumption flexibility; no nuclear seasonality (unrealistic)"
- )
- fig_storage.text(1, 0, "Lucas Gautheron", ha="right")
- fig_storage.legend(
- labels_storage,
- loc="lower right",
- bbox_to_anchor=(1, -0.1),
- ncol=len(labels_storage),
- bbox_transform=fig_storage.transFigure,
- )
- fig_storage.savefig("output/storage.png", bbox_inches="tight", dpi=200)
- fig_dispatch.suptitle(
- f"Simulations based on {begin}--{end} weather data.\n{flex} consumption flexibility; no nuclear seasonality (unrealistic)"
- )
- fig_dispatch.text(1, 0, "Lucas Gautheron", ha="right")
- fig_dispatch.legend(
- labels_dispatch,
- loc="lower right",
- bbox_to_anchor=(1, -0.1),
- ncol=len(labels_dispatch),
- bbox_transform=fig_dispatch.transFigure,
- )
- fig_dispatch.savefig("output/dispatch.png", bbox_inches="tight", dpi=200)
- fig_gap_distribution.suptitle(
- f"Power gap cumulative distribution (%)\nSimulations based on {begin}--{end} weather data.\n{flex} consumption flexibility; no nuclear seasonality (unrealistic)"
- )
- fig_gap_distribution.legend(
- ["Power gap (available-load) (GW)"],
- loc="lower right",
- bbox_to_anchor=(1, -0.1),
- ncol=1,
- bbox_transform=fig_dispatch.transFigure,
- )
- fig_gap_distribution.text(1, 0, "Lucas Gautheron", ha="right")
- fig_gap_distribution.savefig(
- "output/gap_distribution.png", bbox_inches="tight", dpi=200
- )
- plt.show()
|