run.py 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. import pandas as pd
  2. import numpy as np
  3. from matplotlib import pyplot as plt
  4. import matplotlib.dates as mdates
  5. import yaml
  6. from mix_simul.scenarios import Scenario
  7. with open("scenarios/rte.yml", 'r') as f:
  8. rte = yaml.load(f, Loader=yaml.FullLoader)
  9. potential = pd.read_parquet("data/potential.parquet")
  10. potential = potential.loc['1985-01-01 00:00:00':'2015-01-01 00:00:00', :]
  11. potential.fillna(0, inplace=True)
  12. begin = "2012-01-01"
  13. end = "2015-01-01"
  14. flexibility = False
  15. potential = potential.loc[(
  16. slice(f'{begin} 00:00:00', f'{end} 00:00:00'), 'FR'), :]
  17. # intermittent sources potential
  18. p = potential[["onshore", "offshore", "solar"]].to_xarray().to_array()
  19. p = np.insert(p, 3, 0.7, axis=0) # nuclear power-like
  20. times = potential.index.get_level_values(0)
  21. fig, axes = plt.subplots(nrows=6, ncols=2, sharex="col", sharey=True)
  22. w, h = fig.get_size_inches()
  23. fig.set_figwidth(w*1.5)
  24. fig.set_figheight(h*1.5)
  25. fig_storage, axes_storage = plt.subplots(nrows=6, ncols=2, sharex="col", sharey=True)
  26. fig_storage.set_figwidth(w*1.5)
  27. fig_storage.set_figheight(h*1.5)
  28. fig_dispatch, axes_dispatch = plt.subplots(nrows=6, ncols=2, sharex="col", sharey=True)
  29. fig_dispatch.set_figwidth(w*1.5)
  30. fig_dispatch.set_figheight(h*1.5)
  31. fig_gap_distribution, axes_gap_distribution = plt.subplots(nrows=3, ncols=2, sharex="col", sharey=True)
  32. fig_gap_distribution.set_figwidth(w*1.5)
  33. fig_gap_distribution.set_figheight(h*1.5)
  34. date_fmt = mdates.DateFormatter('%d/%m')
  35. # for step in np.linspace(start, stop, 2050-2022, True)[::-1]:
  36. row = 0
  37. for scenario in rte:
  38. rte[scenario]["flexibility_power"] = 0
  39. scenario_model = Scenario(**rte[scenario])
  40. S, load, production, gap, storage, dp = scenario_model.run(times, p)
  41. print(f"{scenario}:", S, gap.max(), np.quantile(gap, 0.95))
  42. print(f"exports: {np.minimum(np.maximum(-gap, 0), 39).sum()/1000} TWh; imports: {np.minimum(np.maximum(gap, 0), 39).sum()/1000} TWh")
  43. print(f"dispatchable: " + ", ".join([f"{dp[i].sum()/1000:.2f} TWh" for i in range(dp.shape[0])]))
  44. potential['load'] = load
  45. potential['production'] = production
  46. potential['available'] = production-np.diff(storage.sum(axis=0), append=0)
  47. for i in range(3):
  48. potential[f"storage_{i}"] = np.diff(storage[i,:], append=0)#storage[i,:]/1000
  49. potential[f"storage_{i}"] = storage[i,:]/1000
  50. for i in range(dp.shape[0]):
  51. potential[f"dispatch_{i}"] = dp[i,:]
  52. potential["dispatch"] = dp.sum(axis=0)
  53. data = [
  54. potential.loc[(slice('2013-02-01 00:00:00',
  55. '2013-03-01 00:00:00'), 'FR'), :],
  56. potential.loc[(slice('2013-06-01 00:00:00',
  57. '2013-07-01 00:00:00'), 'FR'), :]
  58. ]
  59. months = [
  60. "Février",
  61. "Juin"
  62. ]
  63. labels = [
  64. "load (GW)",
  65. "production (GW)",
  66. "available power (production-storage) (GW)",
  67. "power deficit"
  68. ]
  69. labels_storage = [
  70. "Batteries (TWh)",
  71. "STEP (TWh)",
  72. "P2G (TWh)"
  73. ]
  74. labels_dispatch = [
  75. "Hydro (GW)",
  76. "Biomass (GW)",
  77. "Thermal (GW)"
  78. ]
  79. for col in range(2):
  80. ax = axes[row, col]
  81. ax.plot(data[col].index.get_level_values(0), data[col]
  82. ["load"], label="adjusted load (GW)", lw=1)
  83. ax.plot(data[col].index.get_level_values(0), data[col]
  84. ["production"], label="production (GW)", ls="dotted", lw=1)
  85. ax.plot(data[col].index.get_level_values(0), data[col]["available"],
  86. label="available power (production-d(storage)/dt) (GW)", lw=1)
  87. ax.fill_between(
  88. data[col].index.get_level_values(0),
  89. data[col]["available"],
  90. data[col]["load"],
  91. where=data[col]["load"] > data[col]["available"],
  92. color='red',
  93. alpha=0.15
  94. )
  95. ax.xaxis.set_major_formatter(date_fmt)
  96. ax.text(
  97. 0.5, 0.87, f"Scénario {scenario} ({months[col]})", ha='center', transform=ax.transAxes)
  98. ax.set_ylim(10, 210)
  99. ax = axes_storage[row, col]
  100. for i in np.arange(3):
  101. if i == 2:
  102. base = 0
  103. else:
  104. base = np.sum([data[col][f"storage_{j}"] for j in np.arange(i+1,3)], axis=0)
  105. ax.fill_between(data[col].index.get_level_values(0), base, base+data[col]
  106. [f"storage_{i}"], label=f"storage {i}", alpha=0.5)
  107. ax.plot(data[col].index.get_level_values(0), base+data[col]
  108. [f"storage_{i}"], label=f"storage {i}", lw=0.25)
  109. ax.xaxis.set_major_formatter(date_fmt)
  110. ax.text(
  111. 0.5, 0.87, f"Scénario {scenario} ({months[col]})", ha='center', transform=ax.transAxes)
  112. ax = axes_dispatch[row, col]
  113. for i in range(dp.shape[0]):
  114. if i == 0:
  115. base = 0
  116. else:
  117. base = np.sum([data[col][f"dispatch_{j}"] for j in np.arange(i)], axis=0)
  118. ax.fill_between(data[col].index.get_level_values(0), base, base+data[col]
  119. [f"dispatch_{i}"], label=f"dispatch {i}", alpha=0.5)
  120. ax.plot(data[col].index.get_level_values(0), base+data[col]
  121. [f"dispatch_{i}"], label=f"dispatch {i}", lw=0.25)
  122. ax.xaxis.set_major_formatter(date_fmt)
  123. ax.text(
  124. 0.5, 0.87, f"Scénario {scenario} ({months[col]})", ha='center', transform=ax.transAxes)
  125. ax = axes_gap_distribution[row%3, row//3]
  126. hist, bin_edges = np.histogram(gap, bins=1000)
  127. hist = np.cumsum(hist)
  128. hist = 100*(hist - hist.min()) / hist.ptp()
  129. keep = np.abs(bin_edges[:-1]) < 50
  130. ax.plot(bin_edges[:-1][keep], hist[keep], label="power gap")
  131. ax.text(
  132. 0.5, 0.87, f"Scénario {scenario}", ha='center', transform=ax.transAxes)
  133. row += 1
  134. for label in axes[-1, 0].get_xmajorticklabels() + axes[-1, 1].get_xmajorticklabels():
  135. label.set_rotation(30)
  136. label.set_horizontalalignment("right")
  137. for label in axes_storage[-1, 0].get_xmajorticklabels() + axes_storage[-1, 1].get_xmajorticklabels():
  138. label.set_rotation(30)
  139. label.set_horizontalalignment("right")
  140. for label in axes_dispatch[-1, 0].get_xmajorticklabels() + axes_dispatch[-1, 1].get_xmajorticklabels():
  141. label.set_rotation(30)
  142. label.set_horizontalalignment("right")
  143. flex = "With" if flexibility else "Without"
  144. plt.subplots_adjust(wspace=0, hspace=0)
  145. fig.suptitle(f"Simulations based on {begin}--{end} weather data.\n{flex} consumption flexibility; no nuclear seasonality (unrealistic)")
  146. fig.text(1, 0, 'Lucas Gautheron', ha="right")
  147. fig.legend(labels, loc='lower right', bbox_to_anchor=(1, -0.1),
  148. ncol=len(labels), bbox_transform=fig.transFigure)
  149. fig.savefig("output/load_supply.png", bbox_inches="tight", dpi=200)
  150. fig_storage.suptitle(f"Simulations based on {begin}--{end} weather data.\n{flex} consumption flexibility; no nuclear seasonality (unrealistic)")
  151. fig_storage.text(1, 0, 'Lucas Gautheron', ha="right")
  152. fig_storage.legend(labels_storage, loc='lower right', bbox_to_anchor=(1, -0.1),
  153. ncol=len(labels_storage), bbox_transform=fig_storage.transFigure)
  154. fig_storage.savefig("output/storage.png", bbox_inches="tight", dpi=200)
  155. fig_dispatch.suptitle(f"Simulations based on {begin}--{end} weather data.\n{flex} consumption flexibility; no nuclear seasonality (unrealistic)")
  156. fig_dispatch.text(1, 0, 'Lucas Gautheron', ha="right")
  157. fig_dispatch.legend(labels_dispatch, loc='lower right', bbox_to_anchor=(1, -0.1),
  158. ncol=len(labels_dispatch), bbox_transform=fig_dispatch.transFigure)
  159. fig_dispatch.savefig("output/dispatch.png", bbox_inches="tight", dpi=200)
  160. fig_gap_distribution.suptitle(f"Power gap cumulative distribution (%)\nSimulations based on {begin}--{end} weather data.\n{flex} consumption flexibility; no nuclear seasonality (unrealistic)")
  161. fig_gap_distribution.legend(["Power gap (available-load) (GW)"], loc='lower right', bbox_to_anchor=(1, -0.1),
  162. ncol=1, bbox_transform=fig_dispatch.transFigure)
  163. fig_gap_distribution.text(1, 0, 'Lucas Gautheron', ha="right")
  164. fig_gap_distribution.savefig("output/gap_distribution.png", bbox_inches="tight", dpi=200)
  165. plt.show()