emhass 0.9.0__py3-none-any.whl → 0.10.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
emhass/optimization.py CHANGED
@@ -3,6 +3,9 @@
3
3
 
4
4
  import logging
5
5
  import copy
6
+ import pathlib
7
+ import bz2
8
+ import pickle as cPickle
6
9
  from typing import Optional, Tuple
7
10
  import pandas as pd
8
11
  import numpy as np
@@ -89,7 +92,7 @@ class Optimization:
89
92
  if self.lp_solver == 'COIN_CMD' and self.lp_solver_path == 'empty': #if COIN_CMD but lp_solver_path is empty
90
93
  self.logger.warning("lp_solver=COIN_CMD but lp_solver_path=empty, attempting to use lp_solver_path=/usr/bin/cbc")
91
94
  self.lp_solver_path = '/usr/bin/cbc'
92
-
95
+
93
96
  def perform_optimization(self, data_opt: pd.DataFrame, P_PV: np.array, P_load: np.array,
94
97
  unit_load_cost: np.array, unit_prod_price: np.array,
95
98
  soc_init: Optional[float] = None, soc_final: Optional[float] = None,
@@ -153,14 +156,14 @@ class Optimization:
153
156
  if def_end_timestep is None:
154
157
  def_end_timestep = self.optim_conf['def_end_timestep']
155
158
  type_self_conso = 'bigm' # maxmin
156
-
159
+
157
160
  #### The LP problem using Pulp ####
158
161
  opt_model = plp.LpProblem("LP_Model", plp.LpMaximize)
159
-
162
+
160
163
  n = len(data_opt.index)
161
164
  set_I = range(n)
162
165
  M = 10e10
163
-
166
+
164
167
  ## Add decision variables
165
168
  P_grid_neg = {(i):plp.LpVariable(cat='Continuous',
166
169
  lowBound=-self.plant_conf['P_to_grid_max'], upBound=0,
@@ -171,12 +174,16 @@ class Optimization:
171
174
  P_deferrable = []
172
175
  P_def_bin1 = []
173
176
  for k in range(self.optim_conf['num_def_loads']):
177
+ if type(self.optim_conf['P_deferrable_nom'][k]) == list:
178
+ upBound = np.max(self.optim_conf['P_deferrable_nom'][k])
179
+ else:
180
+ upBound = self.optim_conf['P_deferrable_nom'][k]
174
181
  if self.optim_conf['treat_def_as_semi_cont'][k]:
175
182
  P_deferrable.append({(i):plp.LpVariable(cat='Continuous',
176
183
  name="P_deferrable{}_{}".format(k, i)) for i in set_I})
177
184
  else:
178
185
  P_deferrable.append({(i):plp.LpVariable(cat='Continuous',
179
- lowBound=0, upBound=self.optim_conf['P_deferrable_nom'][k],
186
+ lowBound=0, upBound=upBound,
180
187
  name="P_deferrable{}_{}".format(k, i)) for i in set_I})
181
188
  P_def_bin1.append({(i):plp.LpVariable(cat='Binary',
182
189
  name="P_def{}_bin1_{}".format(k, i)) for i in set_I})
@@ -201,11 +208,16 @@ class Optimization:
201
208
  else:
202
209
  P_sto_pos = {(i):i*0 for i in set_I}
203
210
  P_sto_neg = {(i):i*0 for i in set_I}
204
-
211
+
205
212
  if self.costfun == 'self-consumption':
206
213
  SC = {(i):plp.LpVariable(cat='Continuous',
207
214
  name="SC_{}".format(i)) for i in set_I}
208
-
215
+ if self.plant_conf['inverter_is_hybrid']:
216
+ P_hybrid_inverter = {(i):plp.LpVariable(cat='Continuous',
217
+ name="P_hybrid_inverter{}".format(i)) for i in set_I}
218
+ P_PV_curtailment = {(i):plp.LpVariable(cat='Continuous', lowBound=0,
219
+ name="P_PV_curtailment{}".format(i)) for i in set_I}
220
+
209
221
  ## Define objective
210
222
  P_def_sum= []
211
223
  for i in set_I:
@@ -238,17 +250,116 @@ class Optimization:
238
250
  objective = objective + plp.lpSum(-0.001*self.timeStep*(
239
251
  self.optim_conf['weight_battery_discharge']*P_sto_pos[i] + \
240
252
  self.optim_conf['weight_battery_charge']*P_sto_neg[i]) for i in set_I)
253
+
254
+ # Add term penalizing each startup where configured
255
+ if ("def_start_penalty" in self.optim_conf and self.optim_conf["def_start_penalty"]):
256
+ for k in range(self.optim_conf["num_def_loads"]):
257
+ if (len(self.optim_conf["def_start_penalty"]) > k and self.optim_conf["def_start_penalty"][k]):
258
+ objective = objective + plp.lpSum(
259
+ -0.001 * self.timeStep * self.optim_conf["def_start_penalty"][k] * P_def_start[k][i] *\
260
+ unit_load_cost[i] * self.optim_conf['P_deferrable_nom'][k]
261
+ for i in set_I)
262
+
241
263
  opt_model.setObjective(objective)
242
-
264
+
243
265
  ## Setting constraints
244
266
  # The main constraint: power balance
245
- constraints = {"constraint_main1_{}".format(i) :
246
- plp.LpConstraint(
247
- e = P_PV[i] - P_def_sum[i] - P_load[i] + P_grid_neg[i] + P_grid_pos[i] + P_sto_pos[i] + P_sto_neg[i],
248
- sense = plp.LpConstraintEQ,
249
- rhs = 0)
250
- for i in set_I}
267
+ if self.plant_conf['inverter_is_hybrid']:
268
+ constraints = {"constraint_main1_{}".format(i) :
269
+ plp.LpConstraint(
270
+ e = P_hybrid_inverter[i] - P_def_sum[i] - P_load[i] + P_grid_neg[i] + P_grid_pos[i] ,
271
+ sense = plp.LpConstraintEQ,
272
+ rhs = 0)
273
+ for i in set_I}
274
+ else:
275
+ constraints = {"constraint_main1_{}".format(i) :
276
+ plp.LpConstraint(
277
+ e = P_PV[i] - P_PV_curtailment[i] - P_def_sum[i] - P_load[i] + P_grid_neg[i] + P_grid_pos[i] + P_sto_pos[i] + P_sto_neg[i],
278
+ sense = plp.LpConstraintEQ,
279
+ rhs = 0)
280
+ for i in set_I}
281
+
282
+ # Constraint for hybrid inverter and curtailment cases
283
+ if type(self.plant_conf['module_model']) == list:
284
+ P_nom_inverter = 0.0
285
+ for i in range(len(self.plant_conf['inverter_model'])):
286
+ if type(self.plant_conf['inverter_model'][i]) == str:
287
+ cec_inverters = bz2.BZ2File(pathlib.Path(__file__).parent / 'data/cec_inverters.pbz2', "rb")
288
+ cec_inverters = cPickle.load(cec_inverters)
289
+ inverter = cec_inverters[self.plant_conf['inverter_model'][i]]
290
+ P_nom_inverter += inverter.Paco
291
+ else:
292
+ P_nom_inverter += self.plant_conf['inverter_model'][i]
293
+ else:
294
+ if type(self.plant_conf['inverter_model'][i]) == str:
295
+ cec_inverters = bz2.BZ2File(pathlib.Path(__file__).parent / 'data/cec_inverters.pbz2', "rb")
296
+ cec_inverters = cPickle.load(cec_inverters)
297
+ inverter = cec_inverters[self.plant_conf['inverter_model']]
298
+ P_nom_inverter = inverter.Paco
299
+ else:
300
+ P_nom_inverter = self.plant_conf['inverter_model']
301
+ if self.plant_conf['inverter_is_hybrid']:
302
+ constraints.update({"constraint_hybrid_inverter1_{}".format(i) :
303
+ plp.LpConstraint(
304
+ e = P_PV[i] - P_PV_curtailment[i] + P_sto_pos[i] + P_sto_neg[i] - P_nom_inverter,
305
+ sense = plp.LpConstraintLE,
306
+ rhs = 0)
307
+ for i in set_I})
308
+ constraints.update({"constraint_hybrid_inverter2_{}".format(i) :
309
+ plp.LpConstraint(
310
+ e = P_PV[i] - P_PV_curtailment[i] + P_sto_pos[i] + P_sto_neg[i] - P_hybrid_inverter[i],
311
+ sense = plp.LpConstraintEQ,
312
+ rhs = 0)
313
+ for i in set_I})
314
+ else:
315
+ constraints.update({"constraint_curtailment_{}".format(i) :
316
+ plp.LpConstraint(
317
+ e = P_PV[i] - P_PV_curtailment[i] - P_nom_inverter,
318
+ sense = plp.LpConstraintLE,
319
+ rhs = 0)
320
+ for i in set_I})
251
321
 
322
+ # Constraint for sequence of deferrable
323
+ # WARNING: This is experimental, formulation seems correct but feasibility problems.
324
+ # Probably uncomptabile with other constraints
325
+ for k in range(self.optim_conf['num_def_loads']):
326
+ if type(self.optim_conf['P_deferrable_nom'][k]) == list:
327
+ power_sequence = self.optim_conf['P_deferrable_nom'][k]
328
+ sequence_length = len(power_sequence)
329
+ def create_matrix(input_list, n):
330
+ matrix = []
331
+ for i in range(n + 1):
332
+ row = [0] * i + input_list + [0] * (n - i)
333
+ matrix.append(row[:n*2])
334
+ return matrix
335
+ matrix = create_matrix(power_sequence, n-sequence_length)
336
+ y = plp.LpVariable.dicts(f"y{k}", (i for i in range(len(matrix))), cat='Binary')
337
+ constraints.update({f"single_value_constraint_{k}" :
338
+ plp.LpConstraint(
339
+ e = plp.lpSum(y[i] for i in range(len(matrix))) - 1,
340
+ sense = plp.LpConstraintEQ,
341
+ rhs = 0)
342
+ })
343
+ constraints.update({f"pdef{k}_sumconstraint_{i}" :
344
+ plp.LpConstraint(
345
+ e = plp.lpSum(P_deferrable[k][i] for i in set_I) - np.sum(power_sequence),
346
+ sense = plp.LpConstraintEQ,
347
+ rhs = 0)
348
+ })
349
+ constraints.update({f"pdef{k}_positive_constraint_{i}" :
350
+ plp.LpConstraint(
351
+ e = P_deferrable[k][i],
352
+ sense = plp.LpConstraintGE,
353
+ rhs = 0)
354
+ for i in set_I})
355
+ for num, mat in enumerate(matrix):
356
+ constraints.update({f"pdef{k}_value_constraint_{num}_{i}" :
357
+ plp.LpConstraint(
358
+ e = P_deferrable[k][i] - mat[i]*y[num],
359
+ sense = plp.LpConstraintEQ,
360
+ rhs = 0)
361
+ for i in set_I})
362
+
252
363
  # Two special constraints just for a self-consumption cost function
253
364
  if self.costfun == 'self-consumption':
254
365
  if type_self_conso == 'maxmin': # maxmin linear problem
@@ -264,7 +375,7 @@ class Optimization:
264
375
  sense = plp.LpConstraintLE,
265
376
  rhs = 0)
266
377
  for i in set_I})
267
-
378
+
268
379
  # Avoid injecting and consuming from grid at the same time
269
380
  constraints.update({"constraint_pgridpos_{}".format(i) :
270
381
  plp.LpConstraint(
@@ -278,79 +389,145 @@ class Optimization:
278
389
  sense = plp.LpConstraintLE,
279
390
  rhs = 0)
280
391
  for i in set_I})
281
-
392
+
282
393
  # Treat deferrable loads constraints
283
394
  for k in range(self.optim_conf['num_def_loads']):
284
- # Total time of deferrable load
285
- constraints.update({"constraint_defload{}_energy".format(k) :
286
- plp.LpConstraint(
287
- e = plp.lpSum(P_deferrable[k][i]*self.timeStep for i in set_I),
288
- sense = plp.LpConstraintEQ,
289
- rhs = def_total_hours[k]*self.optim_conf['P_deferrable_nom'][k])
290
- })
291
- # Ensure deferrable loads consume energy between def_start_timestep & def_end_timestep
292
- self.logger.debug("Deferrable load {}: Proposed optimization window: {} --> {}".format(k, def_start_timestep[k], def_end_timestep[k]))
293
- def_start, def_end, warning = Optimization.validate_def_timewindow(def_start_timestep[k], def_end_timestep[k], ceil(def_total_hours[k]/self.timeStep), n)
294
- if warning is not None:
295
- self.logger.warning("Deferrable load {} : {}".format(k, warning))
296
- self.logger.debug("Deferrable load {}: Validated optimization window: {} --> {}".format(k, def_start, def_end))
297
- if def_start > 0:
298
- constraints.update({"constraint_defload{}_start_timestep".format(k) :
395
+ if type(self.optim_conf['P_deferrable_nom'][k]) == list:
396
+ continue
397
+ else:
398
+ # Total time of deferrable load
399
+ constraints.update({"constraint_defload{}_energy".format(k) :
299
400
  plp.LpConstraint(
300
- e = plp.lpSum(P_deferrable[k][i]*self.timeStep for i in range(0, def_start)),
401
+ e = plp.lpSum(P_deferrable[k][i]*self.timeStep for i in set_I),
301
402
  sense = plp.LpConstraintEQ,
302
- rhs = 0)
403
+ rhs = def_total_hours[k]*self.optim_conf['P_deferrable_nom'][k])
303
404
  })
304
- if def_end > 0:
305
- constraints.update({"constraint_defload{}_end_timestep".format(k) :
405
+ # Ensure deferrable loads consume energy between def_start_timestep & def_end_timestep
406
+ self.logger.debug("Deferrable load {}: Proposed optimization window: {} --> {}".format(
407
+ k, def_start_timestep[k], def_end_timestep[k]))
408
+ def_start, def_end, warning = Optimization.validate_def_timewindow(
409
+ def_start_timestep[k], def_end_timestep[k], ceil(def_total_hours[k]/self.timeStep), n)
410
+ if warning is not None:
411
+ self.logger.warning("Deferrable load {} : {}".format(k, warning))
412
+ self.logger.debug("Deferrable load {}: Validated optimization window: {} --> {}".format(
413
+ k, def_start, def_end))
414
+ if def_start > 0:
415
+ constraints.update({"constraint_defload{}_start_timestep".format(k) :
416
+ plp.LpConstraint(
417
+ e = plp.lpSum(P_deferrable[k][i]*self.timeStep for i in range(0, def_start)),
418
+ sense = plp.LpConstraintEQ,
419
+ rhs = 0)
420
+ })
421
+ if def_end > 0:
422
+ constraints.update({"constraint_defload{}_end_timestep".format(k) :
423
+ plp.LpConstraint(
424
+ e = plp.lpSum(P_deferrable[k][i]*self.timeStep for i in range(def_end, n)),
425
+ sense = plp.LpConstraintEQ,
426
+ rhs = 0)
427
+ })
428
+ # Treat deferrable load as a semi-continuous variable
429
+ if self.optim_conf['treat_def_as_semi_cont'][k]:
430
+ constraints.update({"constraint_pdef{}_semicont1_{}".format(k, i) :
431
+ plp.LpConstraint(
432
+ e=P_deferrable[k][i] - self.optim_conf['P_deferrable_nom'][k]*P_def_bin1[k][i],
433
+ sense=plp.LpConstraintGE,
434
+ rhs=0)
435
+ for i in set_I})
436
+ constraints.update({"constraint_pdef{}_semicont2_{}".format(k, i) :
437
+ plp.LpConstraint(
438
+ e=P_deferrable[k][i] - self.optim_conf['P_deferrable_nom'][k]*P_def_bin1[k][i],
439
+ sense=plp.LpConstraintLE,
440
+ rhs=0)
441
+ for i in set_I})
442
+ # Treat the number of starts for a deferrable load
443
+ if self.optim_conf['set_def_constant'][k]:
444
+ constraints.update({"constraint_pdef{}_start1_{}".format(k, i) :
445
+ plp.LpConstraint(
446
+ e=P_deferrable[k][i] - P_def_bin2[k][i]*M,
447
+ sense=plp.LpConstraintLE,
448
+ rhs=0)
449
+ for i in set_I})
450
+ constraints.update({"constraint_pdef{}_start2_{}".format(k, i):
451
+ plp.LpConstraint(
452
+ e=P_def_start[k][i] - P_def_bin2[k][i] + (P_def_bin2[k][i-1] if i-1 >= 0 else 0),
453
+ sense=plp.LpConstraintGE,
454
+ rhs=0)
455
+ for i in set_I})
456
+ constraints.update({"constraint_pdef{}_start3".format(k) :
306
457
  plp.LpConstraint(
307
- e = plp.lpSum(P_deferrable[k][i]*self.timeStep for i in range(def_end, n)),
458
+ e = plp.lpSum(P_def_start[k][i] for i in set_I),
308
459
  sense = plp.LpConstraintEQ,
309
- rhs = 0)
460
+ rhs = 1)
310
461
  })
311
-
312
- # Treat deferrable load as a semi-continuous variable
313
- if self.optim_conf['treat_def_as_semi_cont'][k]:
314
- constraints.update({"constraint_pdef{}_semicont1_{}".format(k, i) :
315
- plp.LpConstraint(
316
- e=P_deferrable[k][i] - self.optim_conf['P_deferrable_nom'][k]*P_def_bin1[k][i],
317
- sense=plp.LpConstraintGE,
318
- rhs=0)
319
- for i in set_I})
320
- constraints.update({"constraint_pdef{}_semicont2_{}".format(k, i) :
462
+ # Treat deferrable load as a semi-continuous variable
463
+ if self.optim_conf['treat_def_as_semi_cont'][k]:
464
+ constraints.update({"constraint_pdef{}_semicont1_{}".format(k, i) :
465
+ plp.LpConstraint(
466
+ e=P_deferrable[k][i] - self.optim_conf['P_deferrable_nom'][k]*P_def_bin1[k][i],
467
+ sense=plp.LpConstraintGE,
468
+ rhs=0)
469
+ for i in set_I})
470
+ constraints.update({"constraint_pdef{}_semicont2_{}".format(k, i) :
471
+ plp.LpConstraint(
472
+ e=P_deferrable[k][i] - self.optim_conf['P_deferrable_nom'][k]*P_def_bin1[k][i],
473
+ sense=plp.LpConstraintLE,
474
+ rhs=0)
475
+ for i in set_I})
476
+ # Treat the number of starts for a deferrable load
477
+ current_state = 0
478
+ if ("def_current_state" in self.optim_conf and len(self.optim_conf["def_current_state"]) > k):
479
+ current_state = 1 if self.optim_conf["def_current_state"][k] else 0
480
+ # P_deferrable < P_def_bin2 * 1 million
481
+ # P_deferrable must be zero if P_def_bin2 is zero
482
+ constraints.update({"constraint_pdef{}_start1_{}".format(k, i):
321
483
  plp.LpConstraint(
322
- e=P_deferrable[k][i] - self.optim_conf['P_deferrable_nom'][k]*P_def_bin1[k][i],
484
+ e=P_deferrable[k][i] - P_def_bin2[k][i] * M,
323
485
  sense=plp.LpConstraintLE,
324
486
  rhs=0)
325
487
  for i in set_I})
326
- # Treat the number of starts for a deferrable load
327
- if self.optim_conf['set_def_constant'][k]:
328
-
329
- constraints.update({"constraint_pdef{}_start1_{}".format(k, i) :
488
+ # P_deferrable - P_def_bin2 <= 0
489
+ # P_def_bin2 must be zero if P_deferrable is zero
490
+ constraints.update({"constraint_pdef{}_start1a_{}".format(k, i):
330
491
  plp.LpConstraint(
331
- e=P_deferrable[k][i] - P_def_bin2[k][i]*M,
492
+ e=P_def_bin2[k][i] - P_deferrable[k][i],
332
493
  sense=plp.LpConstraintLE,
333
494
  rhs=0)
334
495
  for i in set_I})
496
+ # P_def_start + P_def_bin2[i-1] >= P_def_bin2[i]
497
+ # If load is on this cycle (P_def_bin2[i] is 1) then P_def_start must be 1 OR P_def_bin2[i-1] must be 1
498
+ # For first timestep, use current state if provided by caller.
335
499
  constraints.update({"constraint_pdef{}_start2_{}".format(k, i):
336
500
  plp.LpConstraint(
337
- e=P_def_start[k][i] - P_def_bin2[k][i] + (P_def_bin2[k][i-1] if i-1 >= 0 else 0),
501
+ e=P_def_start[k][i]
502
+ - P_def_bin2[k][i]
503
+ + (P_def_bin2[k][i - 1] if i - 1 >= 0 else current_state),
338
504
  sense=plp.LpConstraintGE,
339
505
  rhs=0)
340
506
  for i in set_I})
341
- constraints.update({"constraint_pdef{}_start3".format(k) :
342
- plp.LpConstraint(
343
- e = plp.lpSum(P_def_start[k][i] for i in set_I),
344
- sense = plp.LpConstraintEQ,
345
- rhs = 1)
346
- })
347
- constraints.update({"constraint_pdef{}_start4".format(k) :
348
- plp.LpConstraint(
349
- e = plp.lpSum(P_def_bin2[k][i] for i in set_I),
350
- sense = plp.LpConstraintEQ,
351
- rhs = self.optim_conf['def_total_hours'][k]/self.timeStep)
352
- })
353
-
507
+ # P_def_bin2[i-1] + P_def_start <= 1
508
+ # If load started this cycle (P_def_start[i] is 1) then P_def_bin2[i-1] must be 0
509
+ constraints.update({"constraint_pdef{}_start3_{}".format(k, i):
510
+ plp.LpConstraint(
511
+ e=(P_def_bin2[k][i-1] if i-1 >= 0 else 0) + P_def_start[k][i],
512
+ sense=plp.LpConstraintLE,
513
+ rhs=1)
514
+ for i in set_I})
515
+ if self.optim_conf['set_def_constant'][k]:
516
+ # P_def_start[i] must be 1 for exactly 1 value of i
517
+ constraints.update({"constraint_pdef{}_start4".format(k) :
518
+ plp.LpConstraint(
519
+ e = plp.lpSum(P_def_start[k][i] for i in set_I),
520
+ sense = plp.LpConstraintEQ,
521
+ rhs = 1)
522
+ })
523
+ # P_def_bin2 must be 1 for exactly the correct number of timesteps.
524
+ constraints.update({"constraint_pdef{}_start5".format(k) :
525
+ plp.LpConstraint(
526
+ e = plp.lpSum(P_def_bin2[k][i] for i in set_I),
527
+ sense = plp.LpConstraintEQ,
528
+ rhs = def_total_hours[k]/self.timeStep)
529
+ })
530
+
354
531
  # The battery constraints
355
532
  if self.optim_conf['set_use_battery']:
356
533
  # Optional constraints to avoid charging the battery from the grid
@@ -423,7 +600,7 @@ class Optimization:
423
600
  rhs=(soc_init - soc_final)*self.plant_conf['Enom']/self.timeStep)
424
601
  })
425
602
  opt_model.constraints = constraints
426
-
603
+
427
604
  ## Finally, we call the solver to solve our optimization model:
428
605
  # solving with default solver CBC
429
606
  if self.lp_solver == 'PULP_CBC_CMD':
@@ -435,7 +612,7 @@ class Optimization:
435
612
  else:
436
613
  self.logger.warning("Solver %s unknown, using default", self.lp_solver)
437
614
  opt_model.solve()
438
-
615
+
439
616
  # The status of the solution is printed to the screen
440
617
  self.optim_status = plp.LpStatus[opt_model.status]
441
618
  self.logger.info("Status: " + self.optim_status)
@@ -444,7 +621,7 @@ class Optimization:
444
621
  return
445
622
  else:
446
623
  self.logger.info("Total value of the Cost function = %.02f", plp.value(opt_model.objective))
447
-
624
+
448
625
  # Build results Dataframe
449
626
  opt_tp = pd.DataFrame()
450
627
  opt_tp["P_PV"] = [P_PV[i] for i in set_I]
@@ -465,8 +642,11 @@ class Optimization:
465
642
  SOC_opt.append(SOCinit - SOC_opt_delta[i])
466
643
  SOCinit = SOC_opt[i]
467
644
  opt_tp["SOC_opt"] = SOC_opt
645
+ if self.plant_conf['inverter_is_hybrid']:
646
+ opt_tp["P_hybrid_inverter"] = [P_hybrid_inverter[i].varValue for i in set_I]
647
+ opt_tp["P_PV_curtailment"] = [P_PV_curtailment[i].varValue for i in set_I]
468
648
  opt_tp.index = data_opt.index
469
-
649
+
470
650
  # Lets compute the optimal cost function
471
651
  P_def_sum_tp = []
472
652
  for i in set_I:
@@ -479,7 +659,7 @@ class Optimization:
479
659
  else:
480
660
  opt_tp["cost_profit"] = [-0.001*self.timeStep*(unit_load_cost[i]*P_grid_pos[i].varValue + \
481
661
  unit_prod_price[i]*P_grid_neg[i].varValue) for i in set_I]
482
-
662
+
483
663
  if self.costfun == 'profit':
484
664
  if self.optim_conf['set_total_pv_sell']:
485
665
  opt_tp["cost_fun_profit"] = [-0.001*self.timeStep*(unit_load_cost[i]*(P_load[i] + P_def_sum_tp[i]) + \
@@ -500,17 +680,16 @@ class Optimization:
500
680
  unit_prod_price[i]*P_grid_neg[i].varValue) for i in set_I]
501
681
  else:
502
682
  self.logger.error("The cost function specified type is not valid")
503
-
683
+
504
684
  # Add the optimization status
505
685
  opt_tp["optim_status"] = self.optim_status
506
-
686
+
507
687
  # Debug variables
508
688
  if debug:
509
- opt_tp["P_def_start_0"] = [P_def_start[0][i].varValue for i in set_I]
510
- opt_tp["P_def_start_1"] = [P_def_start[1][i].varValue for i in set_I]
511
- opt_tp["P_def_bin2_0"] = [P_def_bin2[0][i].varValue for i in set_I]
512
- opt_tp["P_def_bin2_1"] = [P_def_bin2[1][i].varValue for i in set_I]
513
-
689
+ for k in range(self.optim_conf["num_def_loads"]):
690
+ opt_tp[f"P_def_start_{k}"] = [P_def_start[k][i].varValue for i in set_I]
691
+ opt_tp[f"P_def_bin2_{k}"] = [P_def_bin2[k][i].varValue for i in set_I]
692
+
514
693
  return opt_tp
515
694
 
516
695
  def perform_perfect_forecast_optim(self, df_input_data: pd.DataFrame, days_list: pd.date_range) -> pd.DataFrame:
@@ -548,9 +727,9 @@ class Optimization:
548
727
  self.opt_res = opt_tp
549
728
  else:
550
729
  self.opt_res = pd.concat([self.opt_res, opt_tp], axis=0)
551
-
730
+
552
731
  return self.opt_res
553
-
732
+
554
733
  def perform_dayahead_forecast_optim(self, df_input_data: pd.DataFrame,
555
734
  P_PV: pd.Series, P_load: pd.Series) -> pd.DataFrame:
556
735
  r"""
@@ -577,7 +756,7 @@ class Optimization:
577
756
  P_load.values.ravel(),
578
757
  unit_load_cost, unit_prod_price)
579
758
  return self.opt_res
580
-
759
+
581
760
  def perform_naive_mpc_optim(self, df_input_data: pd.DataFrame, P_PV: pd.Series, P_load: pd.Series,
582
761
  prediction_horizon: int, soc_init: Optional[float] = None, soc_final: Optional[float] = None,
583
762
  def_total_hours: Optional[list] = None,