Open and run in Colab (interactive) Edit on Gitlab Open and run in Kaggle (interactive) Launch with Binder

Evaluating the performance of a hybrid power plant with P2MeOH using HyDesign

In this notebook we will evaluate a hybrid power plant design in a specific location.

A hybrid power plant design consists on selecting the following parameters:

Wind Plant design:

  1. Number of wind turbines in the wind plant [-] (Nwt)

  2. Wind power installation density [MW/km2] (wind_MW_per_km2): This parameter controls how closely spaced are the turbines, which in turns affect how much wake losses are present.

PV Plant design:

  1. Solar plant power capacity [MW] (solar_MW)

Battery Storage design:

  1. Battery power [MW] (b_P)

  2. Battery energy capacity in hours [MWh] (b_E_h): Battery storage capacity in hours of full battery power (b_E = b_E_h * b_P).

  3. Cost of battery power fluctuations in peak price ratio [-] (cost_of_batt_degr): This parameter controls how much penalty is given to do ramps in battery power in the HPP operation.

Electrolyzer design:

  1. SOEC capacity [MW] (P_SOEC_MW)

Heater design:

  1. Heater capacity [MW] (P_heater_MW)

DAC design:

  1. DAC capacity [MW] (P_DAC_MW)

Reactor design:

  1. Reactor capacity [MW] (P_reactor_MW)

Electrolyzer design:

  1. MeOH tank capacity [MW] (m_MeOH_tank_max_kg)

Imports

Install hydesign if needed. Import basic libraries. Import HPP model assembly class. Import the examples file path.

[ ]:
# Install hydesign if needed
import importlib
if not importlib.util.find_spec("hydesign"):
    !pip install git+https://gitlab.windenergy.dtu.dk/TOPFARM/hydesign.git
[ ]:
import os
import time
import yaml
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

from hydesign.assembly.hpp_assembly_P2MeOH_bidirectional import hpp_model_P2MeOH_bidirectional as hpp_model

from hydesign.examples import examples_filepath

Specifying the site

Hydesign, provides example data from several sites in India and Europe.

The site coordinates (longitude, latitude, and altitude) are given in examples_sites.csv.

[ ]:
examples_sites = pd.read_csv(f'{examples_filepath}examples_sites.csv', index_col=0, sep=';')
examples_sites

Select a site to run

[ ]:
name = "France_good_wind_MeOH"

ex_site = examples_sites.loc[examples_sites.name == name]

longitude = ex_site['longitude'].values[0]
latitude = ex_site['latitude'].values[0]
altitude = ex_site['altitude'].values[0]
[ ]:
# Weather data and Price data
input_ts_fn = examples_filepath+ex_site['input_ts_fn'].values[0]

input_ts = pd.read_csv(input_ts_fn, index_col=0, parse_dates=True)

required_cols = [col for col in input_ts.columns if 'WD' not in col]
input_ts = input_ts.loc[:,required_cols]
input_ts
[ ]:
# Input data of technology's cost
sim_pars_fn = examples_filepath+ex_site['sim_pars_MeOH_fn'].values[0]

with open(sim_pars_fn) as file:
    sim_pars = yaml.load(file, Loader=yaml.FullLoader)

print(sim_pars_fn)
sim_pars
[ ]:
# MeOH demand data
MeOH_demand_fn = examples_filepath + "Europe/MeOH_demand.csv"

MeOH_demand_ts = pd.read_csv(MeOH_demand_fn, index_col=0, parse_dates=True)
if sim_pars['MeOH_demand_scheme'] == "fixed":
    print(MeOH_demand_ts)
if sim_pars['MeOH_demand_scheme'] == "infinite":
    print("Methanol demand is infinite")

Initializing the HPP model

Initialize the HPP model (hpp_model class) with the coordinates and the necessary input files.

[ ]:
hpp = hpp_model(
        latitude=latitude,
        longitude=longitude,
        altitude=altitude,
        num_batteries = 3,
        work_dir = './',
        sim_pars_fn = sim_pars_fn,
        input_ts_fn = input_ts_fn,
        MeOH_demand_fn = MeOH_demand_fn,
)

Evaluating the HPP model

[ ]:
start = time.time()

clearance = 10
sp = 303
p_rated = 5
Nwt = 48
wind_MW_per_km2 = 3
solar_MW = 80
surface_tilt = 50
surface_azimuth = 210
DC_AC_ratio = 1.5
P_batt_MW = 50
b_E_h = 3
cost_of_battery_P_fluct_in_peak_price_ratio = 5
P_SOEC_MW = 100
# P_heater_MW = 0
# P_DAC_MW = 5.6
# P_reactor_MW = 7.3
m_MeOH_tank_max_kg = 1800000

x = [clearance, sp, p_rated, Nwt, wind_MW_per_km2, solar_MW, \
        surface_tilt, surface_azimuth, DC_AC_ratio, P_batt_MW, b_E_h , cost_of_battery_P_fluct_in_peak_price_ratio, \
        P_SOEC_MW, m_MeOH_tank_max_kg]

output_names = [
    'NPV_over_CAPEX', 'NPV', 'IRR', 'LCOE', 'LCOgreenMeOH', 'LCOgridMeOH',
    'Revenue', 'CAPEX', 'OPEX', 'penalty lifetime', 'mean_AEP', 'mean_Power2Grid',
    'GUF', 'annual_green_MeOH', 'annual_grid_MeOH', 'annual_P_SOEC_green',
    'annual_P_purch_grid', 'hpp_grid_connection', 'wind_MW', 'solar_MW',
    'P_SOEC_MW', 'P_DAC_MW', 'P_reactor_MW', 'm_MeOH_tank_max_kg',
    'E_batt_MWh_t', 'P_batt_MW', 'total_curtailment', 'Awpp', 'Apvp', 'd',
    'hh', 'break_even_PPA_price', 'break_even_green_MeOH_price', 'cf_wind'
]

outs = hpp.evaluate(*x)

df = pd.DataFrame({name: [val] for name, val in zip(output_names, outs)})

# output_file = "outputs.xlsx"

# df.to_excel(output_file, index=False)

hpp.print_design()

end = time.time()
print(f'exec. time [min]:', (end - start)/60 )

# print(f"Results saved to {output_file}")

Plot economic and technical indicators

[ ]:
annual_green_MeOH = df['annual_green_MeOH'][0]
annual_grid_MeOH = df['annual_grid_MeOH'][0]

categories = ['Annual Grid MeOH', 'Annual Green MeOH']
values = [annual_grid_MeOH[0], annual_green_MeOH[0]]

plt.figure(figsize=(8, 5))
plt.bar(categories, values, color=['skyblue', 'green'], alpha=0.8)
plt.title('Comparison of Annual Grid and Green Methanol Distribution', fontsize=14)
plt.ylabel('Distribution (tons)', fontsize=12)
plt.xticks(fontsize=12)
plt.yticks(fontsize=12)
plt.grid(axis='y', linestyle='--', alpha=0.7)

plt.tight_layout()
plt.show()

Plot the HPP operation

[ ]:
E_SOC_t = hpp.prob.get_val('ems_P2MeOH_bidirectional.E_SOC_t')
P_charge_discharge_t = hpp.prob.get_val('ems_P2MeOH_bidirectional.P_charge_discharge_t')
elec_spot_price_t_ext = hpp.prob.get_val('ems_P2MeOH_bidirectional.elec_spot_price_t_ext')
elec_grid_price_t_ext = hpp.prob.get_val('ems_P2MeOH_bidirectional.elec_grid_price_t_ext')

wind_t_ext = hpp.prob.get_val('ems_P2MeOH_bidirectional.wind_t_ext')
solar_t_ext = hpp.prob.get_val('ems_P2MeOH_bidirectional.solar_t_ext')
P_HPP_t = hpp.prob.get_val('ems_P2MeOH_bidirectional.P_HPP_t')
P_curtailment_t = hpp.prob.get_val('ems_P2MeOH_bidirectional.P_curtailment_t')
P_SOEC_green_t = hpp.prob.get_val('ems_P2MeOH_bidirectional.P_SOEC_green_t')
P_SOEC_grid_t = hpp.prob.get_val('ems_P2MeOH_bidirectional.P_SOEC_grid_t')
hpp_grid_connection = hpp.prob.get_val('ems_P2MeOH_bidirectional.hpp_grid_connection')

m_green_MeOH_reactor_t = hpp.prob.get_val('ems_P2MeOH_bidirectional.m_green_MeOH_reactor_t')
m_MeOH_demand_t_ext = hpp.prob.get_val('ems_P2MeOH_bidirectional.m_MeOH_demand_t_ext')
m_grid_MeOH_reactor_t = hpp.prob.get_val('ems_P2MeOH_bidirectional.m_grid_MeOH_reactor_t')
m_MeOH_tank_t = hpp.prob.get_val('ems_P2MeOH_bidirectional.m_MeOH_tank_t')

n_days_plot = 14
plt.figure(figsize=[12,4])
plt.plot(elec_spot_price_t_ext[:24*n_days_plot], label='spot market price of electricity [Euro/MWh]')

constant_price = elec_grid_price_t_ext[0]
plt.plot(elec_grid_price_t_ext[:24*n_days_plot], label='cost of purchased electricity [Euro/MWh]')
plt.text(len(elec_grid_price_t_ext[:24*n_days_plot]) / 2, constant_price,
         f"{constant_price:.2f} Euro/MWh",
         fontsize=12, color="red", ha="center", va="bottom")

plt.plot(E_SOC_t[:24*n_days_plot], label='SoC [MWh]')
plt.plot(P_charge_discharge_t[:24*n_days_plot], label='Battery P [MW]')
plt.xlabel('time [hours]')
plt.legend(loc='upper center', bbox_to_anchor=(0.5, 1.15),
           ncol=3, fancybox=0, shadow=0)

plt.figure(figsize=[12,4])
plt.plot(wind_t_ext[:24*n_days_plot], label='wind')
plt.plot(solar_t_ext[:24*n_days_plot], label='PV')
plt.plot(P_HPP_t[:24*n_days_plot], label='HPP')
plt.plot(P_curtailment_t[:24*n_days_plot], label='HPP curtailed')
plt.plot(P_charge_discharge_t[:24*n_days_plot], label='Battery P [MW]')
plt.plot(P_SOEC_green_t[:24*n_days_plot], label='Green power to SOEC')
plt.plot(P_SOEC_grid_t[:24*n_days_plot], label='Grid power to SOEC')
plt.axhline(hpp_grid_connection, label='Grid MW', color='k')
plt.xlabel('time [hours]')
plt.ylabel('Power [MW]')
plt.legend(loc='upper center', bbox_to_anchor=(0.5, 1.15),
           ncol=7, fancybox=0, shadow=0)

[ ]:
results = {'Wind': wind_t_ext[:8760], 'PV': solar_t_ext[:8760], 'HPP': P_HPP_t[:8760], 'Curtailment': P_curtailment_t[:8760],\
                    'Battery power': P_charge_discharge_t[:8760], 'Energy Level': E_SOC_t[:8760], 'Electricity Spot Market Prices': elec_spot_price_t_ext[:8760],\
                    'Cost of Purchased Electricity':elec_grid_price_t_ext[:8760], 'P_SOEC_green_t': P_SOEC_green_t[:8760], 'P_SOEC_grid_t': P_SOEC_grid_t[:8760],\
                    'm_green_MeOH_reactor_t': m_green_MeOH_reactor_t[:8760], 'm_grid_MeOH_reactor_t': m_grid_MeOH_reactor_t[:8760], 'm_MeOH_tank_t': m_MeOH_tank_t[:8760]}
df = pd.DataFrame(results)
df.to_csv('EMS_out_P2MeOH.csv')
[ ]:

plt.figure(figsize=[12,4]) plt.plot(m_green_MeOH_reactor_t[:24*n_days_plot], label='Green MeOH produced') plt.plot(m_grid_MeOH_reactor_t[:24*n_days_plot], label='Grid MeOH produced') plt.plot(m_MeOH_demand_t_ext[:24*n_days_plot], label='MeOH demand') plt.plot(m_MeOH_tank_t[:24*n_days_plot], label='LoS') plt.xlabel('time [hours]') plt.ylabel('Methanol [kg]') plt.legend(loc='upper center', bbox_to_anchor=(0.5, 1.15), ncol=5, fancybox=0, shadow=0)