I am trying to write a LP in PuLP which minimizes the electricity costs of a nitrogen blending process. The produced nitrogen can be either directly used for the blending or can be stored and extracted at a later stage. With this flexibility, the blending process can be optimized based on the day ahead electricity costs.
I have created a dictionary with all the possible configurations for the plant with the corresponding electricity consumption, storage application and configuration of the three air separation units. From this dictionary, I then create a nested dictionary based on the forecasted nitrogen demand. Each primary key represents a timestage of 1 hour for the next day and the nested dictionary contains all the possible configurations for that timestage (which are the configurations which at least meet the forecasted demand of that hour).
So the nested dictionary has the following structure:
d = {t : { cfg_key : [production value (total production of ASUs in m3/hr),
consumption value (MW), storage application (i.e. extraction or injection in m3/hr),(ASU 1, ASU 2, ASU 3)}}
e.g.: { 0 : { 135 : [(0, 0, 48000), 14.76, -115000,], 137 : [.....], ....etc. }}
if you need more details on this dictionary and how it was made, please ask I will add it. I've currently left it out to be more concise.
I then wrote the following LP in PuLP. This code selects one possible configuration for each timeslot, while meeting production and storage constraints.
from pulp import *
d = sols_cands
set_ASU = range(0,3)
M = 60000
#----Program Initialization--------------------------------------------------------------
lp_problem = LpProblem("SchedulingProgram", LpMinimize)
#----Variables---------------------------------------------------------------------------
var = {} # Binary variable for each possible config to select
for t in d:
var[t] = {}
for cfg in d[t]:
var[t][cfg] = LpVariable(f"x_{t}_{cfg}", 0, 1, LpBinary)
#----Objective function------------------------------------------------------------------
objective = sum([d[t][cfg][1] * var[t][cfg] * energy_prices[t]
for t in var.keys() for cfg in var[t].keys()])
lp_problem.setObjective(objective)
#----Constraints-------------------------------------------------------------------------
# Constraint which ensures only one configuration is selected for each timeslot
for t in var.keys():
lp_problem += sum([var[t][cfg] for cfg in var[t].keys()]) == 1
# Constraint to zero out the total storage utilization (extraction and injection) over the day
lp_problem += sum([d[t][cfg][2]*var[t][cfg] for t in var.keys()
for cfg in var[t].keys()]) == 0
# Solve the LP problem
lp_problem.solve()
# Print the solution
for t in var.keys():
for cfg in d[t].keys():
if var[t][cfg].varValue == 1:
print(f"Timeslot {t} : Energy Price {energy_prices[t]} \
: N2 Demand {n2_demand[t]} : Config {t} : {d[t][cfg]}")
print(f"Total energy costs are: {lp_problem.objective.value()}")
print(f"Status: {lp_problem.status}")
Then I've tried to add a constraint which limits the change in production of the ASUs to 9000 for sequential timeslots. I've added the following variable to track the status of the ASU and constraints.
ASU_status = {} # Binary variable to track whether an ASU is on or off
for t in d:
ASU_status[t] = {asu: LpVariable(f"z_{t}_{asu}", 0, 1, LpBinary)
for asu in set_ASU}
for t in set_T:
for cfg in d[t]:
for asu in set_ASU:
lp_problem += ASU_bin[t][asu] * M >= var[t][cfg]* d[t][cfg][0][asu]
lp_problem += ASU_bin[t][asu] <= var[t][cfg]* d[t][cfg][0][asu]
for t in range(1,24):
for cfg in d[t]:
for p_cfg in d[t-1]:
for asu in set_ASU:
ASU_change = var[t][cfg] * d[t][cfg][0][asu] - var[t-1][p_cfg] * d[t-1][p_cfg][0][asu]
lp_problem += ASU_change <= ASU_status[t][asu] * 9000 + \
(2 - ASU_status[t-1][asu] - ASU_status[t][asu])* M
lp_problem += ASU_change >= - ASU_status[t][asu] * 9000 - \
(2 - ASU_status[t-1][asu]- ASU_status[t][asu])* M
Part of config dictionary {config: [(ASU config), E-cons, Injection or extraction value]:
{0: [(0, 0, 0), 0.0, 0],
1: [(0, 0, 0), 0.0, -10000],
2: [(0, 0, 0), 0.0, -15000],
3: [(0, 0, 0), 0.0, -20000],
4: [(0, 0, 0), 0.0, -25000],
5: [(0, 0, 0), 0.0, -30000],
6: [(0, 0, 0), 0.0, -35000],
7: [(0, 0, 0), 0.0, -40000],
8: [(0, 0, 0), 0.0, -45000],
9: [(0, 0, 0), 0.0, -50000],
10: [(0, 0, 0), 0.0, -55000],
11: [(0, 0, 0), 0.0, -60000],
12: [(0, 0, 0), 0.0, -65000],
13: [(0, 0, 0), 0.0, -70000],
14: [(0, 0, 0), 0.0, -75000],
15: [(0, 0, 0), 0.0, -80000],
16: [(0, 0, 0), 0.0, -85000],
17: [(0, 0, 0), 0.0, -90000],
18: [(0, 0, 0), 0.0, -95000],
19: [(0, 0, 0), 0.0, -100000],
20: [(0, 0, 0), 0.0, -105000],
21: [(0, 0, 0), 0.0, -110000],
22: [(0, 0, 0), 0.0, -115000],
23: [(0, 0, 0), 0.0, -120000],
24: [(0, 0, 0), 0.0, -125000],
25: [(0, 0, 0), 0.0, -130000],
26: [(0, 0, 0), 0.0, -135000],
27: [(0, 0, 0), 0.0, -140000],
28: [(0, 0, 0), 0.0, -145000],
29: [(0, 0, 0), 0.0, -150000],
30: [(0, 0, 0), 0.0, -155000],
31: [(0, 0, 0), 0.0, -160000],
32: [(0, 0, 0), 0.0, -165000],
33: [(0, 0, 0), 0.0, -170000],
34: [(0, 0, 0), 0.0, -175000],
35: [(0, 0, 0), 0.0, -180000],
36: [(0, 0, 0), 0.0, -185000],
37: [(0, 0, 0), 0.0, -190000],
38: [(0, 0, 42000), 10.815, 0],
39: [(0, 0, 42000), 10.815, -10000],
40: [(0, 0, 42000), 10.815, -15000],
41: [(0, 0, 42000), 10.815, -20000],
42: [(0, 0, 42000), 10.815, -25000],
43: [(0, 0, 42000), 10.815, -30000],
44: [(0, 0, 42000), 10.815, -35000],
45: [(0, 0, 42000), 10.815, -40000],
46: [(0, 0, 42000), 10.815, -45000],
47: [(0, 0, 42000), 10.815, -50000],
48: [(0, 0, 42000), 10.815, -55000],
49: [(0, 0, 42000), 10.815, -60000],
50: [(0, 0, 42000), 10.815, -65000],
51: [(0, 0, 42000), 10.815, -70000],
52: [(0, 0, 42000), 10.815, -75000],
53: [(0, 0, 42000), 10.815, -80000],
54: [(0, 0, 42000), 10.815, -85000],
55: [(0, 0, 42000), 10.815, -90000],
56: [(0, 0, 42000), 10.815, -95000],
57: [(0, 0, 42000), 10.815, -100000],
58: [(0, 0, 42000), 10.815, -105000],
59: [(0, 0, 42000), 10.815, -110000],
60: [(0, 0, 42000), 10.815, -115000],
61: [(0, 0, 42000), 10.815, -120000],
62: [(0, 0, 42000), 10.815, -125000],
63: [(0, 0, 42000), 10.815, -130000],
64: [(0, 0, 42000), 10.815, -135000],
65: [(0, 0, 42000), 10.815, -140000],
66: [(0, 0, 42000), 10.815, -145000],
67: [(0, 0, 44000), 11.293, 0],
68: [(0, 0, 44000), 11.293, -10000],
69: [(0, 0, 44000), 11.293, -15000],
70: [(0, 0, 44000), 11.293, -20000],
71: [(0, 0, 44000), 11.293, -25000],
72: [(0, 0, 44000), 11.293, -30000],
73: [(0, 0, 44000), 11.293, -35000],
74: [(0, 0, 44000), 11.293, -40000],
75: [(0, 0, 44000), 11.293, -45000],
76: [(0, 0, 44000), 11.293, -50000],
77: [(0, 0, 44000), 11.293, -55000],
78: [(0, 0, 44000), 11.293, -60000],
79: [(0, 0, 44000), 11.293, -65000],
80: [(0, 0, 44000), 11.293, -70000],
81: [(0, 0, 44000), 11.293, -75000],
82: [(0, 0, 44000), 11.293, -80000],
83: [(0, 0, 44000), 11.293, -85000],
84: [(0, 0, 44000), 11.293, -90000],
85: [(0, 0, 44000), 11.293, -95000],
86: [(0, 0, 44000), 11.293, -100000],
87: [(0, 0, 44000), 11.293, -105000],
88: [(0, 0, 44000), 11.293, -110000],
89: [(0, 0, 44000), 11.293, -115000],
90: [(0, 0, 44000), 11.293, -120000],
91: [(0, 0, 44000), 11.293, -125000],
92: [(0, 0, 44000), 11.293, -130000],
93: [(0, 0, 44000), 11.293, -135000],
94: [(0, 0, 44000), 11.293, -140000],
95: [(0, 0, 44000), 11.293, -145000],
96: [(0, 0, 46000), 11.772, 0],
97: [(0, 0, 46000), 11.772, -10000],
98: [(0, 0, 46000), 11.772, -15000],
99: [(0, 0, 46000), 11.772, -20000],
100: [(0, 0, 46000), 11.772, -25000],
101: [(0, 0, 46000), 11.772, -30000],
102: [(0, 0, 46000), 11.772, -35000],
103: [(0, 0, 46000), 11.772, -40000],
104: [(0, 0, 46000), 11.772, -45000],
105: [(0, 0, 46000), 11.772, -50000],
106: [(0, 0, 46000), 11.772, -55000],
107: [(0, 0, 46000), 11.772, -60000],
108: [(0, 0, 46000), 11.772, -65000],
109: [(0, 0, 46000), 11.772, -70000],
110: [(0, 0, 46000), 11.772, -75000],
111: [(0, 0, 46000), 11.772, -80000],
112: [(0, 0, 46000), 11.772, -85000],
113: [(0, 0, 46000), 11.772, -90000],
114: [(0, 0, 46000), 11.772, -95000],
115: [(0, 0, 46000), 11.772, -100000],
116: [(0, 0, 46000), 11.772, -105000],
117: [(0, 0, 46000), 11.772, -110000],
118: [(0, 0, 46000), 11.772, -115000],
119: [(0, 0, 46000), 11.772, -120000],
120: [(0, 0, 46000), 11.772, -125000],
121: [(0, 0, 46000), 11.772, -130000],
122: [(0, 0, 46000), 11.772, -135000],
123: [(0, 0, 46000), 11.772, -140000],
124: [(0, 0, 48000), 12.25, 0],
125: [(0, 0, 48000), 12.25, -10000],
126: [(0, 0, 48000), 12.25, -15000],
127: [(0, 0, 48000), 12.25, -20000],
128: [(0, 0, 48000), 12.25, -25000],
129: [(0, 0, 48000), 12.25, -30000],
130: [(0, 0, 48000), 12.25, -35000],
131: [(0, 0, 48000), 12.25, -40000],
132: [(0, 0, 48000), 12.25, -45000],
133: [(0, 0, 48000), 12.25, -50000],
134: [(0, 0, 48000), 12.25, -55000],
135: [(0, 0, 48000), 12.25, -60000],
136: [(0, 0, 48000), 12.25, -65000],
137: [(0, 0, 48000), 12.25, -70000],
138: [(0, 0, 48000), 12.25, -75000],
139: [(0, 0, 48000), 12.25, -80000],
140: [(0, 0, 48000), 12.25, -85000],
141: [(0, 0, 48000), 12.25, -90000],
142: [(0, 0, 48000), 12.25, -95000],
143: [(0, 0, 48000), 12.25, -100000],
144: [(0, 0, 48000), 12.25, -105000],
145: [(0, 0, 48000), 12.25, -110000],
146: [(0, 0, 48000), 12.25, -115000],
147: [(0, 0, 48000), 12.25, -120000],
148: [(0, 0, 48000), 12.25, -125000],
149: [(0, 0, 48000), 12.25, -130000],
150: [(0, 0, 48000), 12.25, -135000],
151: [(0, 0, 48000), 12.25, -140000],
152: [(0, 0, 50000), 12.728, 0],
153: [(0, 0, 50000), 12.728, -10000],
154: [(0, 0, 50000), 12.728, -15000],
155: [(0, 0, 50000), 12.728, -20000],
156: [(0, 0, 50000), 12.728, -25000],
157: [(0, 0, 50000), 12.728, -30000],
158: [(0, 0, 50000), 12.728, -35000],
159: [(0, 0, 50000), 12.728, -40000],
160: [(0, 0, 50000), 12.728, -45000],
161: [(0, 0, 50000), 12.728, -50000],
162: [(0, 0, 50000), 12.728, -55000],
163: [(0, 0, 50000), 12.728, -60000],
164: [(0, 0, 50000), 12.728, -65000],
165: [(0, 0, 50000), 12.728, -70000],
166: [(0, 0, 50000), 12.728, -75000],
167: [(0, 0, 50000), 12.728, -80000],
168: [(0, 0, 50000), 12.728, -85000],
169: [(0, 0, 50000), 12.728, -90000],
170: [(0, 0, 50000), 12.728, -95000],
171: [(0, 0, 50000), 12.728, -100000],
172: [(0, 0, 50000), 12.728, -105000],
173: [(0, 0, 50000), 12.728, -110000],
174: [(0, 0, 50000), 12.728, -115000],
175: [(0, 0, 50000), 12.728, -120000],
176: [(0, 0, 50000), 12.728, -125000],
177: [(0, 0, 50000), 12.728, -130000],
178: [(0, 0, 50000), 12.728, -135000],
179: [(0, 0, 50000), 12.728, -140000],
180: [(0, 0, 52000), 13.207, 0],
181: [(0, 0, 52000), 13.207, -10000],
182: [(0, 0, 52000), 13.207, -15000],
183: [(0, 0, 52000), 13.207, -20000],
184: [(0, 0, 52000), 13.207, -25000],
185: [(0, 0, 52000), 13.207, -30000],
186: [(0, 0, 52000), 13.207, -35000],
187: [(0, 0, 52000), 13.207, -40000],
188: [(0, 0, 52000), 13.207, -45000],
189: [(0, 0, 52000), 13.207, -50000],
190: [(0, 0, 52000), 13.207, -55000],
191: [(0, 0, 52000), 13.207, -60000],
192: [(0, 0, 52000), 13.207, -65000],
193: [(0, 0, 52000), 13.207, -70000],
194: [(0, 0, 52000), 13.207, -75000],
195: [(0, 0, 52000), 13.207, -80000],
196: [(0, 0, 52000), 13.207, -85000],
197: [(0, 0, 52000), 13.207, -90000],
198: [(0, 0, 52000), 13.207, -95000],
199: [(0, 0, 52000), 13.207, -100000],
200: [(0, 0, 52000), 13.207, -105000],
201: [(0, 0, 52000), 13.207, -110000],
202: [(0, 0, 52000), 13.207, -115000],
203: [(0, 0, 52000), 13.207, -120000],
204: [(0, 0, 52000), 13.207, -125000],
205: [(0, 0, 52000), 13.207, -130000],
206: [(0, 0, 52000), 13.207, -135000],
207: [(0, 0, 54000), 13.685, 0],
208: [(0, 0, 54000), 13.685, -10000],
209: [(0, 0, 54000), 13.685, -15000],
210: [(0, 0, 54000), 13.685, -20000],
211: [(0, 0, 54000), 13.685, -25000],
212: [(0, 0, 54000), 13.685, -30000],
213: [(0, 0, 54000), 13.685, -35000],
214: [(0, 0, 54000), 13.685, -40000],
215: [(0, 0, 54000), 13.685, -45000],
216: [(0, 0, 54000), 13.685, -50000],
217: [(0, 0, 54000), 13.685, -55000],
218: [(0, 0, 54000), 13.685, -60000],
219: [(0, 0, 54000), 13.685, -65000],
220: [(0, 0, 54000), 13.685, -70000],
221: [(0, 0, 54000), 13.685, -75000],
222: [(0, 0, 54000), 13.685, -80000],
223: [(0, 0, 54000), 13.685, -85000],
224: [(0, 0, 54000), 13.685, -90000],
225: [(0, 0, 54000), 13.685, -95000],
226: [(0, 0, 54000), 13.685, -100000],
227: [(0, 0, 54000), 13.685, -105000],
228: [(0, 0, 54000), 13.685, -110000],
229: [(0, 0, 54000), 13.685, -115000],
230: [(0, 0, 54000), 13.685, -120000],
231: [(0, 0, 54000), 13.685, -125000],
232: [(0, 0, 54000), 13.685, -130000],
233: [(0, 0, 54000), 13.685, -135000],
234: [(0, 0, 56000), 14.163, 0],
235: [(0, 0, 56000), 14.163, -10000],
236: [(0, 0, 56000), 14.163, -15000],
237: [(0, 0, 56000), 14.163, -20000],
238: [(0, 0, 56000), 14.163, -25000],
239: [(0, 0, 56000), 14.163, -30000],
240: [(0, 0, 56000), 14.163, -35000],
241: [(0, 0, 56000), 14.163, -40000],
242: [(0, 0, 56000), 14.163, -45000],
243: [(0, 0, 56000), 14.163, -50000],
244: [(0, 0, 56000), 14.163, -55000],
245: [(0, 0, 56000), 14.163, -60000],
246: [(0, 0, 56000), 14.163, -65000],
247: [(0, 0, 56000), 14.163, -70000],
248: [(0, 0, 56000), 14.163, -75000],
249: [(0, 0, 56000), 14.163, -80000],
250: [(0, 0, 56000), 14.163, -85000],
251: [(0, 0, 56000), 14.163, -90000],
252: [(0, 0, 56000), 14.163, -95000],
253: [(0, 0, 56000), 14.163, -100000],
254: [(0, 0, 56000), 14.163, -105000],
255: [(0, 0, 56000), 14.163, -110000],
256: [(0, 0, 56000), 14.163, -115000],
257: [(0, 0, 56000), 14.163, -120000],
258: [(0, 0, 56000), 14.163, -125000],
259: [(0, 0, 56000), 14.163, -130000],
260: [(0, 0, 58000), 14.642, 0],
261: [(0, 0, 58000), 14.642, -10000],
262: [(0, 0, 58000), 14.642, -15000],
263: [(0, 0, 58000), 14.642, -20000],
264: [(0, 0, 58000), 14.642, -25000],
265: [(0, 0, 58000), 14.642, -30000],
266: [(0, 0, 58000), 14.642, -35000],
267: [(0, 0, 58000), 14.642, -40000],
268: [(0, 0, 58000), 14.642, -45000],
269: [(0, 0, 58000), 14.642, -50000],
270: [(0, 0, 58000), 14.642, -55000],
271: [(0, 0, 58000), 14.642, -60000],
272: [(0, 0, 58000), 14.642, -65000],
273: [(0, 0, 58000), 14.642, -70000],
274: [(0, 0, 58000), 14.642, -75000],
275: [(0, 0, 58000), 14.642, -80000],
276: [(0, 0, 58000), 14.642, -85000],
277: [(0, 0, 58000), 14.642, -90000],
278: [(0, 0, 58000), 14.642, -95000],
279: [(0, 0, 58000), 14.642, -100000],
280: [(0, 0, 58000), 14.642, -105000],
281: [(0, 0, 58000), 14.642, -110000],
282: [(0, 0, 58000), 14.642, -115000],
283: [(0, 0, 58000), 14.642, -120000],
284: [(0, 0, 58000), 14.642, -125000],
285: [(0, 0, 58000), 14.642, -130000],
286: [(0, 0, 58000), 18.212, 58000],
287: [(0, 0, 60000), 15.12, 0],
288: [(0, 0, 60000), 15.12, -10000],
289: [(0, 0, 60000), 15.12, -15000],
290: [(0, 0, 60000), 15.12, -20000],
291: [(0, 0, 60000), 15.12, -25000],
292: [(0, 0, 60000), 15.12, -30000],
293: [(0, 0, 60000), 15.12, -35000],
294: [(0, 0, 60000), 15.12, -40000],
295: [(0, 0, 60000), 15.12, -45000],
296: [(0, 0, 60000), 15.12, -50000],
297: [(0, 0, 60000), 15.12, -55000],
298: [(0, 0, 60000), 15.12, -60000],
299: [(0, 0, 60000), 15.12, -65000],
300: [(0, 0, 60000), 15.12, -70000],
301: [(0, 0, 60000), 15.12, -75000],
302: [(0, 0, 60000), 15.12, -80000],
303: [(0, 0, 60000), 15.12, -85000],
304: [(0, 0, 60000), 15.12, -90000],
305: [(0, 0, 60000), 15.12, -95000],
306: [(0, 0, 60000), 15.12, -100000],
307: [(0, 0, 60000), 15.12, -105000],
308: [(0, 0, 60000), 15.12, -110000],
309: [(0, 0, 60000), 15.12, -115000],
310: [(0, 0, 60000), 15.12, -120000],
311: [(0, 0, 60000), 15.12, -125000],
312: [(0, 0, 60000), 15.12, -130000],
313: [(0, 0, 60000), 18.69, 58000],
314: [(0, 0, 60000), 18.87, 60000],
315: [(0, 42000, 42000), 21.63, 0],
316: [(0, 42000, 42000), 21.63, -10000],
317: [(0, 42000, 42000), 21.63, -15000],
318: [(0, 42000, 42000), 21.63, -20000],
319: [(0, 42000, 42000), 21.63, -25000],
320: [(0, 42000, 42000), 21.63, -30000],
321: [(0, 42000, 42000), 21.63, -35000],
322: [(0, 42000, 42000), 21.63, -40000],
323: [(0, 42000, 42000), 21.63, -45000],
324: [(0, 42000, 42000), 21.63, -50000],
325: [(0, 42000, 42000), 21.63, -55000],
326: [(0, 42000, 42000), 21.63, -60000],
327: [(0, 42000, 42000), 21.63, -65000],
328: [(0, 42000, 42000), 21.63, -70000],
329: [(0, 42000, 42000), 21.63, -75000],
330: [(0, 42000, 42000), 21.63, -80000],
331: [(0, 42000, 42000), 21.63, -85000],
332: [(0, 42000, 42000), 21.63, -90000],
333: [(0, 42000, 42000), 21.63, -95000],
334: [(0, 42000, 42000), 21.63, -100000],
335: [(0, 42000, 42000), 21.63, -105000],
336: [(0, 42000, 42000), 25.2, 58000],
337: [(0, 42000, 42000), 25.38, 60000],
338: [(0, 42000, 42000), 25.56, 65000],
339: [(0, 42000, 42000), 25.74, 70000],
340: [(0, 42000, 42000), 25.92, 75000],
341: [(0, 42000, 42000), 26.1, 80000],
342: [(0, 42000, 44000), 22.108, 0],
343: [(0, 42000, 44000), 22.108, -10000],
344: [(0, 42000, 44000), 22.108, -15000],
345: [(0, 42000, 44000), 22.108, -20000],
346: [(0, 42000, 44000), 22.108, -25000],
347: [(0, 42000, 44000), 22.108, -30000],
348: [(0, 42000, 44000), 22.108, -35000],
349: [(0, 42000, 44000), 22.108, -40000],
350: [(0, 42000, 44000), 22.108, -45000],
351: [(0, 42000, 44000), 22.108, -50000],
352: [(0, 42000, 44000), 22.108, -55000],
353: [(0, 42000, 44000), 22.108, -60000],
354: [(0, 42000, 44000), 22.108, -65000],
355: [(0, 42000, 44000), 22.108, -70000],
356: [(0, 42000, 44000), 22.108, -75000],
357: [(0, 42000, 44000), 22.108, -80000],
358: [(0, 42000, 44000), 22.108, -85000],
359: [(0, 42000, 44000), 22.108, -90000],
360: [(0, 42000, 44000), 22.108, -95000],
361: [(0, 42000, 44000), 22.108, -100000],
362: [(0, 42000, 44000), 25.678, 58000],
363: [(0, 42000, 44000), 25.858, 60000],
364: [(0, 42000, 44000), 26.038, 65000],
365: [(0, 42000, 44000), 26.218, 70000],
366: [(0, 42000, 44000), 26.398, 75000],
367: [(0, 42000, 44000), 26.578, 80000],
368: [(0, 42000, 44000), 26.758, 85000],
369: [(0, 44000, 44000), 22.586, 0],
370: [(0, 44000, 44000), 22.586, -10000],
371: [(0, 44000, 44000), 22.586, -15000],
372: [(0, 44000, 44000), 22.586, -20000],
373: [(0, 44000, 44000), 22.586, -25000],
374: [(0, 44000, 44000), 22.586, -30000],
375: [(0, 44000, 44000), 22.586, -35000],
376: [(0, 44000, 44000), 22.586, -40000],
377: [(0, 44000, 44000), 22.586, -45000],
378: [(0, 44000, 44000), 22.586, -50000],
379: [(0, 44000, 44000), 22.586, -55000],
380: [(0, 44000, 44000), 22.586, -60000],
381: [(0, 44000, 44000), 22.586, -65000],
382: [(0, 44000, 44000), 22.586, -70000],
383: [(0, 44000, 44000), 22.586, -75000],
384: [(0, 44000, 44000), 22.586, -80000],
385: [(0, 44000, 44000), 22.586, -85000],
386: [(0, 44000, 44000), 22.586, -90000],
387: [(0, 44000, 44000), 22.586, -95000],
388: [(0, 44000, 44000), 22.586, -100000],
389: [(0, 44000, 44000), 26.156, 58000],
390: [(0, 44000, 44000), 26.336, 60000],
391: [(0, 44000, 44000), 26.516, 65000],
392: [(0, 44000, 44000), 26.696, 70000],
393: [(0, 44000, 44000), 26.876, 75000],
394: [(0, 44000, 44000), 27.056, 80000],
395: [(0, 44000, 44000), 27.236, 85000],
396: [(0, 42000, 48000), 23.065, 0],
397: [(0, 42000, 48000), 23.065, -10000],
398: [(0, 42000, 48000), 23.065, -15000],
399: [(0, 42000, 48000), 23.065, -20000],
400: [(0, 42000, 48000), 23.065, -25000],
401: [(0, 42000, 48000), 23.065, -30000],
402: [(0, 42000, 48000), 23.065, -35000],
403: [(0, 42000, 48000), 23.065, -40000],
404: [(0, 42000, 48000), 23.065, -45000],
405: [(0, 42000, 48000), 23.065, -50000],
406: [(0, 42000, 48000), 23.065, -55000],
407: [(0, 42000, 48000), 23.065, -60000],
408: [(0, 42000, 48000), 23.065, -65000],
409: [(0, 42000, 48000), 23.065, -70000],
410: [(0, 42000, 48000), 23.065, -75000],
411: [(0, 42000, 48000), 23.065, -80000],
412: [(0, 42000, 48000), 23.065, -85000],
413: [(0, 42000, 48000), 23.065, -90000],
414: [(0, 42000, 48000), 23.065, -95000],
415: [(0, 42000, 48000), 23.065, -100000],
416: [(0, 42000, 48000), 26.635, 58000],
417: [(0, 42000, 48000), 26.815, 60000],
418: [(0, 42000, 48000), 26.995, 65000],
419: [(0, 42000, 48000), 27.175, 70000],
420: [(0, 42000, 48000), 27.355, 75000],
421: [(0, 42000, 48000), 27.535, 80000],
422: [(0, 42000, 48000), 27.715, 85000],
423: [(0, 42000, 48000), 27.895, 90000],
424: [(0, 42000, 50000), 23.543, 0],
425: [(0, 42000, 50000), 23.543, -10000],
426: [(0, 42000, 50000), 23.543, -15000],
427: [(0, 42000, 50000), 23.543, -20000],
428: [(0, 42000, 50000), 23.543, -25000],
429: [(0, 42000, 50000), 23.543, -30000],
430: [(0, 42000, 50000), 23.543, -35000],
431: [(0, 42000, 50000), 23.543, -40000],
432: [(0, 42000, 50000), 23.543, -45000],
433: [(0, 42000, 50000), 23.543, -50000],
434: [(0, 42000, 50000), 23.543, -55000],
435: [(0, 42000, 50000), 23.543, -60000],
436: [(0, 42000, 50000), 23.543, -65000],
437: [(0, 42000, 50000), 23.543, -70000],
438: [(0, 42000, 50000), 23.543, -75000],
439: [(0, 42000, 50000), 23.543, -80000],
440: [(0, 42000, 50000), 23.543, -85000],
441: [(0, 42000, 50000), 23.543, -90000],
442: [(0, 42000, 50000), 23.543, -95000],
443: [(0, 42000, 50000), 27.113, 58000],
444: [(0, 42000, 50000), 27.293, 60000],
445: [(0, 42000, 50000), 27.473, 65000],
446: [(0, 42000, 50000), 27.653, 70000],
447: [(0, 42000, 50000), 27.833, 75000],
448: [(0, 42000, 50000), 28.013, 80000],
The current code I have is this:
import pulp
import matplotlib.pyplot as plt
import pickle
import pandas as pd
import numpy as np
#----DATA--------------------------------------------------------------------------------
time_steps = list(range(0,24))
asus = [0, 1, 2]
#----Nitrogen demand forecast------------------------------------------------------------
n2_demand = [121548, 121453, 121537, 121715, 119228, 118547, 118675, 115909, 108003, 103060, 100284, 99211, 99915, 103157, 102453,
106371, 107764, 117624, 123072, 123492, 120911, 113903, 107971, 107243]
energy_prices = [130.12, 128.01, 121.34, 114.94, 119.04, 132.98, 172.34, 190.85, 190, 176.92, 160.10, 151.80, 145.06, 132.50, 129.71,
128.53, 132.50, 165.21, 177.48, 191.34, 182.87, 163.08, 141.30, 131.18]
#----Import of configuration dictinary---------------------------------------------------
with open('saved_ConfigDict.pkl', 'rb') as f: # Dictionary containing all the configurations
cfg_d = pickle.load(f)
prob = pulp.LpProblem("Nitrogen_Optimization", pulp.LpMinimize)
t_c = [ (t, c) for t in time_steps for c in cfg_d ] # Time-Configurations combinations
t_a = [ (t, a) for t in time_steps for a in asus ] # Time-ASU combinations
tot_prod = {c:sum(vals[0]) for c, vals in cfg_d.items()} # Total production value of the 3 ASUs
#----Variables---------------------------------------------------------------------------
run_cfg = pulp.LpVariable.dicts('run', t_c, cat='Binary') # Run configuration c in timeslot t
asu_output_increase = pulp.LpVariable.dicts('increase_asu', t_a, cat='Binary') # ASU a changes status in timeslot t
ASU_on = {(t, asu): pulp.LpVariable(cat='Binary', name=f'ASU_on_{asu}_{t}')
for asu in asus for t in time_steps}
#----Objective---------------------------------------------------------------------------
obj = pulp.lpSum(cfg_d[c][1] * run_cfg[t, c] * energy_prices[t] for t, c in t_c)
prob.setObjective(obj)
#----Constraints-------------------------------------------------------------------------
for t in time_steps:
tot_n2 = pulp.lpSum(tot_prod[c] * run_cfg[t, c] - cfg_d[c][2] * run_cfg[t, c] for c in cfg_d) # Constraint 1: Ensure demand is met in each
prob += tot_n2 >= n2_demand[t] # timestep
prob += sum(run_cfg[t, c] for c in cfg_d.keys()) == 1 # Constraint 2: Exactly one config is run in
# each timestep
ramp_limit = 9000
# Constraint 3: Capture and limit change in ASUs
for t in time_steps[1:]:
for asu in asus:
prod_this_step = pulp.lpSum(cfg_d[c][0][asu] * run_cfg[t, c] for c in cfg_d)
prod_last_step = pulp.lpSum(cfg_d[c][0][asu] * run_cfg[t-1, c] for c in cfg_d)
ASU_change = prod_this_step - prod_last_step
prob += prod_this_step <= M * ASU_on[t, asu]
prob += prod_this_step >= 0.001 * ASU_on[t, asu]
prob += prod_last_step <= M * ASU_on[t-1, asu]
prob += prod_last_step >= 0.001 * ASU_on[t-1, asu]
prob += ASU_change <= ramp_limit + (2 - ASU_on[t, asu] - ASU_on[t-1, asu]) * M
prob += ASU_change >= -ramp_limit - (2 - ASU_on[t, asu] - ASU_on[t-1, asu]) * M
prob += pulp.lpSum(run_cfg[t, c] * cfg_d[c][2] for t in time_steps for c in cfg_d) == 0 # Constraint 4: Limit the utilization of the storage
#----Results-----------------------------------------------------------------------------
prob.solve()
for v in prob.variables():
if v.varValue > 0:
print(v.name, "=", v.varValue)
Some slight variation of the below example should do it. Without a slice of your data, it is tough to fit your current construct exactly, but this has all the elements that you are looking for.
To more clearly state what I mentioned in the comment, you must be repeating config information many many times in your data dictionary, which is a horrible practice. I note in your example above that configuration 1318 is used many times. Unless the configuration itself changes over time, pull it out.
Anyhow, the below uses a binary variable to capture and limit change by ASU and (as seen in graphic) walks down and up through lesser production configurations to meet the spikes at start and end.
# Nitrogen Production
import pulp
import matplotlib.pyplot as plt
# DATA
configs = { 'cfg-201' : (100, 0, 100),
'cfg-73' : (140, 40, 110),
'cfg-108' : (50, 60, 60),
'cfg-0' : (0, 0, 0)}
tot_prod = {cfg:sum(vals) for cfg, vals in configs.items()} # a convenience...
change_limit = 60 # max output change per ASU
demand = { 1: 210,
2: 0,
3: 0,
4: 0,
5: 0,
6: 0,
7: 275}
time_steps = list(demand.keys())
# make set of all time-config combos.
# note: if there are restrictions to which combos are avail in particular times,
# you could just make this as a dictionary from data
t_c = [ (t, c) for t in time_steps for c in configs ]
asus = [0, 1, 2]
t_a = [ (t, a) for t in time_steps for a in asus ]
prob = pulp.LpProblem("gas_production", pulp.LpMinimize)
# VARS
run_config = pulp.LpVariable.dicts('run', t_c, cat='Binary') # run config c in time t
asu_output_increase = pulp.LpVariable.dicts('increase_asu', t_a, cat='Binary') # asu a changes status in timeslot t
# OBJ: minimize total production
prob += pulp.lpSum(tot_prod[c] * run_config[t, c] for t, c in t_c)
# CONSTRAINTS
for t in time_steps:
# 1. meet demand in all timesteps
prob += pulp.lpSum(tot_prod[c] * run_config[t, c] for c in configs) >= demand[t]
# 2. must run one of the configs (and only one) in each timestep
prob += sum(run_config[t, c] for c in configs.keys()) == 1
# constraints for 2nd step and beyond
for t in time_steps[1:]:
# 3. capture change in asu status, and limit it
for asu in asus:
prod_this_step = sum(configs[c][asu] * run_config[t, c] for c in configs)
prod_last_step = sum(configs[c][asu] * run_config[t-1, c] for c in configs)
prob += prod_this_step - prod_last_step <= asu_output_increase[t, asu] * change_limit
prob += prod_last_step - prod_this_step <= (1 - asu_output_increase[t, asu]) * change_limit
prob.solve()
# print(prob)
for v in prob.variables():
if v.varValue > 0:
print(v.name, "=", v.varValue)
plt.step(time_steps, [sum(tot_prod[c]*run_config[t, c].varValue for c in configs) for t in time_steps], where='mid', lw=3, label='supply')
plt.bar(time_steps, [demand[t] for t in time_steps], color='gray', label='demand')
plt.legend()
Text output omitted... the status is "optimal"