Problem accessing indexed results two stage stochastic programming Pyomo

2024/9/20 23:36:37

When running a stochastic programming problem in Pyomo, the resulting solution works only when running 10 precisely the same scenarios but the results remain zero when running different scenarios.

I aim to run 10 different scenarios with corresponding probabilities in a stochastic fashion. To provide some context, the problem concerns a cost-minimization battery dispatch problem with uncertainty regarding wind power production.

As said before, when running the 10 similar scenarios with corresponding probabilities 0.1 this results in the following plot. The wind scenarios are described in a [s, t] tuple-indexed dictionary. The wind parameter is indexed similarly in the objective function and constraints.

Battery dispatch with 10 similar scenarios

However, when running 10 different scenarios all with probability 0.1 the resulting plot ends up empty. The resulting plot is provided below. It says that no primal feasible solution can be found, but I'm not sure why this is the case.

Empty battery dispatch plot 10 different scenarios

Hence my question, how do I retrieve indexed results?

## Import packages
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import pyomo.environ as pyoload_data = ([5.76186621, 5.37067822, 5.29351123, 5.52940967, 5.39076172,5.43196387, 5.48100537, 5.81333301, 5.69059522, 5.48861719,6.69299756, 5.19573291, 5.09057129, 5.21503565, 5.21353174,5.39826172, 5.23854395, 5.54866699, 5.22932715, 5.57071094,5.68876563, 5.39178857, 5.37514307, 5.63663672, 5.73555957,5.81333301, 5.32496875, 5.8409707 , 5.1021416 , 5.44398438,5.30204785, 5.63930811, 5.50765918, 5.17532715, 5.24306397,5.25783154, 5.2048418 , 5.91519141, 5.90498242, 5.44157129,5.84772656, 5.39375586, 5.50630371, 5.41415381, 5.34967041,5.58918799, 4.95105859, 5.34705859]) # Electrical load of the machine [MW]
three_days = ([ 66.75,  50.74,  50.  ,  50.72,  51.3 ,  48.6 ,  64.46,  85.66,100.  ,  67.8 ,  50.28,  48.12,  45.57,  40.26,  39.09,  38.94,40.71,  44.84,  70.72,  80.  ,  80.1 ,  78.16,  55.  ,  47.92,46.65,  48.7 ,  47.85,  50.08,  50.  ,  46.  ,  58.  ,  72.97,75.12,  51.47,  45.91,  45.4 ,  42.9 ,  42.71,  41.26,  41.84,44.  ,  66.36,  54.09,  69.83,  88.32,  63.6 ,  58.88,  47.56]) # Electricity price data [$/MWh]
park_data = ([10.548, 15.84 , 10.548,  3.636,  6.525,  0.   ,  0.315, 10.548,6.525,  0.315,  0.315,  6.525,  6.525, 10.548, 10.548,  6.525,6.525, 10.548, 10.548, 10.548,  6.525,  6.525,  6.525,  6.525,6.525,  3.636,  6.525,  6.525,  6.525,  6.525,  3.636,  3.636,0.315,  0.315,  0.   ,  0.315,  0.315,  1.656,  3.636,  1.656,3.636,  1.656,  0.315,  0.   ,  0.   ,  0.   ,  0.   ,  0.   ]) # Wind power production [MW]park_data_2 = [x*0.9 for x in park_data]
Scen_prob = [0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1] # Scenario probabilities
Scen_prob = np.array(Scen_prob) # List to array
Scen_prob = Scen_prob.reshape(10,1) # List to array# 10 similar scenarios
raw_wind_data = [park_data,park_data,park_data,park_data,park_data,park_data,park_data,park_data,park_data,park_data]# 10 different scenarios
# raw_wind_data = [park_data,
#                  park_data,
#                  park_data,
#                  park_data,
#                  park_data,
#                  park_data_2,
#                  park_data_2,
#                  park_data_2,
#                  park_data_2,
#                  park_data_2]#Transform wind data to dataframe
raw_wind_df = pd.DataFrame(raw_wind_data)
raw_wind_df = raw_wind_df.transpose()
raw_wind_df.columns = ['Scenario 1', 'Scenario 2', 'Scenario 3', 'Scenario 4', 'Scenario 5', 'Scenario 6', 'Scenario 7', 'Scenario 8', 'Scenario 9', 'Scenario 10']
ax = raw_wind_df.plot()
plt.show()def build_model(price_data, horizon_length, scenario_length, load_calc, park_calc, raw_wind_data):""" Builds HORIZON pyomo model:param raw_wind_data::param scenario_length::param price_data: dictionary of price data:param horizon_length: number of timesteps to evaluate:param include_pbc: Boolean, include periodic boundary condition constraint:param load_calc: dictionary of laod data:param park_calc: dictionary of windpark power production:return: m: pyomo model"""m = pyo.ConcreteModel()### BEGIN SOLUTION# Probability distribution per scenariovector = [0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1]## Sets# Save the number of timestepsm.N = horizon_lengthm.S = len(scenario_length)# Define the horizon set starting at hour 1 until horizon length +1m.HORIZON = pyo.Set(initialize=range(1, m.N + 1))# Define scenario setm.SCENARIO = pyo.Set(initialize=range(1, m.S + 1))## Parameters# Round trip efficiencym.teta = pyo.Param(initialize=0.95)# Energy [MWh] in battery at t=0m.E0 = pyo.Param(initialize=2.0, mutable=True)# Guarantee of origin for local wind [€/MWh]m.goNL = pyo.Param(initialize=5)# Guarantee of origin for grid power [€/MWh]m.goBE = pyo.Param(initialize=150)# Maximum discharge powerm.d_max = pyo.Param(initialize=5)# Maximum charge powerm.c_max = pyo.Param(initialize=5)# Maximum export powerm.im_max = pyo.Param(initialize=10)# Maximum import powerm.ex_max = pyo.Param(initialize=100)## CREATE DICTS FOR DATA: Price, Load & Calc# Create empty dictionaryprice_data_dict = {}# Loop over Price data elements of numpy arrayfor i in range(0, len(price_data)):# Add element to data_dictprice_data_dict[i + 1] = price_data[i]# Create empty dictionaryload_data_dict = {}# Loop over Load data elements of numpy arrayfor i in range(0, len(load_calc)):# Add element to data_dictload_data_dict[i + 1] = load_calc[i]# Create empty dictionarypark_data_dict = {}# Loop over Wind park data elements of numpy arrayfor i in range(0, len(park_calc)):# Add element to data_dictpark_data_dict[i + 1] = park_calc[i]# Create empty dictionaryprob_dict = {}# Loop over probability data elements of numpy arrayfor i in range(0, len(vector)):# Add element to prob_dictprob_dict[i + 1] = vector[i]wind_1 = {}for i in range(len(raw_wind_data)):for j in range(len(raw_wind_data[0])):wind_1[(i + 1, j + 1)] = raw_wind_data[i][j]# Price datam.price = pyo.Param(m.HORIZON, initialize=price_data_dict, domain=pyo.Reals, mutable=True)# Load datam.Load = pyo.Param(m.HORIZON, initialize=load_data_dict, domain=pyo.Reals, mutable=True)# Wind park datam.wind = pyo.Param(m.SCENARIO, m.HORIZON, initialize=wind_1, mutable=True) #park_data_dict# Scenario probabilitym.prob = pyo.Param(m.SCENARIO, initialize=prob_dict)  # Was Scen_prob## Variables## Battery related variables# Charging rate [MW]m.c = pyo.Var(m.HORIZON, initialize=0.0, bounds=(0, 10), domain=pyo.NonNegativeReals)  # initialize=0.0,# Discharging rate [MW]m.d = pyo.Var(m.HORIZON, initialize=0.0, bounds=(0, 10), domain=pyo.NonNegativeReals) # initialize=0.0,# Battery powerm.Bat = pyo.Var(m.HORIZON, initialize=0.0, domain=pyo.NonNegativeReals) # initialize=0.0,# Binary variables charging and gridm.u = pyo.Var(m.HORIZON, initialize=0.0, domain=pyo.Binary) # initialize=0.0,m.v = pyo.Var(m.HORIZON, initialize=0.0, domain=pyo.Binary) # initialize=0.0,# Energy (state-of-charge) [MWh]m.E = pyo.Var(m.HORIZON, initialize=2.0, bounds=(0, 5), domain=pyo.NonNegativeReals) # initialize=2.0,m.G_im = pyo.Var(m.HORIZON, initialize=0.0, bounds=(0, 10), domain=pyo.NonNegativeReals) # initialize=0,m.G_ex = pyo.Var(m.HORIZON, initialize=0.0, bounds=(0, 100), domain=pyo.NonNegativeReals) # initialize=0,m.grid = pyo.Var(m.HORIZON, initialize=m.Load, bounds=(0, 10), domain=pyo.NonNegativeReals) #  initialize=m.Load,def objfun(m):return sum((m.price[t] + m.goBE) * m.G_im[t] + (m.price[t] + m.goNL) * sum(m.prob[s] * m.wind[s, t] for s in m.SCENARIO) for t in m.HORIZON)m.OBJ = pyo.Objective(rule=objfun, sense=pyo.minimize)def PowerBalance(m, t):return m.Load[t] + m.c[t] == m.grid[t] + m.d[t]def EnergyBalance(m, t):# First timestepif t == 1:return m.E[t] == m.E0 + m.c[t] * m.teta - m.d[t] / m.teta# Subsequent timestepselse:return m.E[t] == m.E[t - 1] + m.c[t] * m.teta - m.d[t] / m.tetadef GridBalance(m, s, t):return m.grid[t] == m.wind[s, t] + m.G_im[t] - m.G_ex[t] # for s in m.SCENARIO for t in m.HORIZON)def ImMax(m, t):return m.G_ex[t] - m.v[t] * m.ex_max <= 0def ExMax(m, t):return m.G_im[t] + m.v[t] * m.im_max <= m.im_maxdef ChargeMax(m, t):return m.d[t] - m.u[t] * m.d_max <= 0def DischargeMax(m, t):return m.c[t] + m.u[t] * m.c_max <= m.c_maxm.EnergyBalance_Con = pyo.Constraint(m.HORIZON, rule=EnergyBalance)m.PowerBalance_Con = pyo.Constraint(m.HORIZON, rule=PowerBalance)m.GridBalance_Con = pyo.Constraint(m.SCENARIO, m.HORIZON, rule=GridBalance)m.ChargeMax_Con = pyo.Constraint(m.HORIZON, rule=ChargeMax)m.DischargeMax_Con = pyo.Constraint(m.HORIZON, rule=DischargeMax)m.ImMax_Con = pyo.Constraint(m.HORIZON, rule=ImMax)m.ExMax_Con = pyo.Constraint(m.HORIZON, rule=ExMax)## END SOLUTIONreturn mdef extract_solution(m):'''Function to extract the solution from the solved pyomo modelInputs:m: Solved pyomo modelOutputs:c_control: numpy array of charging power values, MWd_control: numpy array of discharging power values, MWE_control: numpy array of energy stored values, MWht: numpy array of time indices from the Pyomo model, hr'''c_control = np.empty(m.N)d_control = np.empty(m.N)E_control = np.empty(m.N)price_control = np.empty(m.N)Load_control = np.empty(m.N)Grid_control = np.empty(m.N)G_im_control = np.empty(m.N)G_ex_control = np.empty(m.N)t = np.empty(m.N)### BEGIN SOLUTION# Loop over elements of HORIZON set.count = 0for i in m.HORIZON:t[count] = pyo.value(i)# Use value( ) function to extract the solution for each variable and append to the results listsc_control[count] = pyo.value(m.c[i])# Adding negative sign to discharge for plottingd_control[count] = -pyo.value(m.d[i])E_control[count] = pyo.value(m.E[i])price_control[count] = pyo.value(m.price[i])Load_control[count] = pyo.value(m.Load[i])Grid_control[count] = pyo.value(m.grid[i])G_im_control[count] = pyo.value(m.G_im[i])G_ex_control[count] = pyo.value(m.G_ex[i])count = count + 1### END SOLUTION## Calculate revenue# include minus sign in order to have proper values [Charge = Neg][Discharge = Pos]all_control_sum = np.add(-c_control, -d_control)# original: [Charge = Pos][Discharge = Neg]all_control_norm = np.add(c_control, d_control)my_data_list = list(price_control)revenue_hour = [a * b for a, b in zip(all_control_sum, my_data_list)]revenue_hour_array = np.array(revenue_hour)CumRev = revenue_hour_array.cumsum()return c_control, d_control, Load_control, Grid_control, G_im_control, G_ex_control, tdef plot_solution(c_control, d_control, Load_control, Grid_control, G_im_control, G_ex_control, t=None):plt.figure()# Make a plotplt.plot(t, Grid_control, 'm.-', label='Cold ironing power')  # Energy level Was P_ciplt.plot(t, G_im_control, 'k.-', label='Grid import')plt.plot(t, G_ex_control, 'b.-', label='Grid export')plt.plot(t, Load_control, 'g.-', label='Load Power')# plt.plot(t, P_grid, 'm.-', label='Grid Power')plt.plot(t, park_data, 'y.-', label='Wind Power')  # was P_windplt.plot(t, c_control, 'c.-', label='Charge')plt.plot(t, d_control, 'c.-', label='Discharge')plt.title('Check: P_ci is ideally lower than P_Load')plt.xlabel('Time (hr)')plt.legend(loc='upper center', bbox_to_anchor=(0.5, 1.15),ncol=3, fancybox=True, shadow=True)plt.ylabel('Cold ironing and load energy [MWh]')  # Cumulative revenue [€]plt.xticks()  # 72 was len(three_days) and 10 was int(step_size)# plt.legend()plt.grid(True)plt.show()fig, ax = plt.subplots()# Make a plotplt.step(t, c_control, 'b.-')  # Energy levelplt.step(t, d_control, 'b.-')plt.xlabel('Time (hr)')plt.ylabel('Energy in battery [MWh]', color="blue")  # Cumulative revenue [€]plt.xticks(range(0, 50, 10))  # 72 was len(three_days) and 10 was int(step_size)plt.grid(True)plt.title('Charging based on day-ahead price')# insert the second plotax2 = ax.twinx()ax2.step(t, three_days, 'r.-')  # 96 was three_daysax2.set_ylabel("Day-ahead price [EUR/MWh]", color="red")# Make sure axis label fitplt.tight_layout()plt.show()fig, ax = plt.subplots()# Make a plotplt.step(t, c_control, 'b.-')  # Energy levelplt.step(t, d_control, 'b.-')plt.xlabel('Time (hr)')plt.ylabel('Energy in battery [MWh]', color="blue")  # Cumulative revenue [€]plt.xticks(range(0, 50, 10))  # 72 was len(three_days) and 10 was int(step_size)plt.grid(True)plt.title('Charging and Wind speeds')# insert the second plotax2 = ax.twinx()ax2.step(t, park_data, 'k.-')  # 96 was three_daysax2.set_ylabel("Wind power production [MW]", color="black")# Make sure axis label fitplt.tight_layout()plt.show()return## BEGIN SOLUTION
# Build model
m_og = build_model(three_days, len(three_days), Scen_prob, load_data, park_data, raw_wind_data)
# Specify the solver
solver = pyo.SolverFactory('glpk')
results = solver.solve(m_og, tee=True)
# Extract solution
c_control, d_control, Load_control, Grid_control, G_im_control, G_ex_control, t = extract_solution(m_og)
plot_solution(c_control, d_control, Load_control, Grid_control, G_im_control, G_ex_control, t)
Answer

OK. Got something breathing...

You have a math/logic problem in your model. First though, you said that the model was "empty" which I assumed meant producing zeros when you had differing "wind" scenarios...that is not quite true. It is INFEASIBLE when the wind scenarios differ, and there is a significant difference there. Be sure you are checking the solver status carefully when troubleshooting.

Here's your problem:

def GridBalance(m, s, t):return m.grid[t] == m.wind[s, t] + m.G_im[t] - m.G_ex[t] # for s in m.SCENARIO for t in m.HORIZON)

this "hard equality" (with equals sign) cannot be simultaneously true for multiple wind values that differ in different scenarios. You'll have to reformulate or reconsider how you are handling scenarios.

I used this for testing with random wind 0-10. You may find it useful:

raw_wind_data = [  [random()*10 for _ in range(len(park_data))] for scenario in range(10)]
https://en.xdnf.cn/q/119270.html

Related Q&A

Pandas python + format for values

This is the code:import pandas as pd from pandas import Series, DataFrame import numpy as np import matplotlib.pyplot as pltdf.head(3).style.format({Budget: "€ {:,.0f}"}) Year …

Is there any implementation of deconvolution?

Some one may prefer to call it the transposed convolution, as introduced here. Im looking forward to an implementation of the transposed convolution, in Python or C/C++. Thank you all for helping me!

discord.py How to check if user is on server?

I need to check if the user is on the server. Please help me

Inserting variable stored data into SQLite3 - Python 3

I have been reading information on how to insert data into a database using data stored in a variable. I have not been able to get my data to load to my database and I am not sure why.The program is w…

Print specific line in a .txt file in Python?

I have got a .txt file that contains a lot of lines. I would like my program to ask me what line I would like to print and then print it into the python shell. The .txt file is called packages.txt.

EOF error with both exec_command and invoke_shell method in paramiko

I am trying to execute a command on linux server from my windows machine using python paramiko , I used both of the methods1.exec_command2.invoke_shellBoth of them giving EOF error.import paramiko impo…

Trying to simulate an Overwatch loot box opening program in Python

So basically I am trying to recreate opening a loot box in Overwatch into a runnable program in python. Im trying to make it take four random items in an array and display them each time the user types…

How to remove extra commas from data in Python

I have a CSV file through which I am trying to load data into my SQL table containing 2 columns. I have 2 columns and the data is separated by commas, which identify the next field. The second column c…

How to linearize the sum of a product of two decision variables in LP?

Being new to Linear Programming and Gurobi, I am dealing with an Integer Linear program where I have two binary decision variables defined as B[u_v, x_y] and A[u_x], I am trying to implement this const…

Basic calculator program in python [closed]

Its difficult to tell what is being asked here. This question is ambiguous, vague, incomplete, overly broad, or rhetorical and cannot be reasonably answered in its current form. For help clarifying thi…