-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathf_make_super_structure.py
2742 lines (2257 loc) · 128 KB
/
f_make_super_structure.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
"""
Created on the 30.01.2023
@author: Lucas Van der Hauwaert
@author contact: [email protected]
All functions used to create superstructure models
"""
import pandas as pd
import cobra
import cobra.io
import numpy as np
from collections import OrderedDict
import pyomo.environ as pe
import pyomo.opt as po
from f_usefull_functions import *
from f_screen_SBML import count_atom_in_formula
import time
########################################################################################################################
########################################################################################################################
# ============================================================================================================
# formulating the superstructure
# ============================================================================================================
########################################################################################################################
########################################################################################################################
# ============================================================================================================
# Generating surrogate models
# Functions to make the interval reaction equations from SBML models
# ============================================================================================================
def get_conversion_sbml(modelLocations, substrate_exchange_rnx, product_exchange_rnx,
substrate2zero='Ex_S_cpd00027_ext',
newObjectiveReaction=None, pFBA=None, printEq=False):
allYields_pFBA = []
allYields_FBA = []
objectiveBiomass = []
allEquations = []
modelNames = []
for i in modelLocations:
model = cobra.io.read_sbml_model(i)
# make sure the right objective is set
if newObjectiveReaction:
model.objective = newObjectiveReaction
# change the glucose reaction to zero
exchange_rnx_2_zero = substrate2zero
model.reactions.get_by_id(exchange_rnx_2_zero).bounds = 0, 0
# change bound of new substrate to -10 mol/h/gDW
model.reactions.get_by_id(substrate_exchange_rnx).bounds = -10, 0
# get names of the models
modelName = i.split("\\")[-1]
modelName = modelName.replace(".xml", "")
modelNames.append(modelName)
# run pFBA
if pFBA: # Todo fix flux to grams c for pFBA
pfba_solution = cobra.flux_analysis.pfba(model)
substrate_flux = pfba_solution.fluxes[substrate_exchange_rnx]
product_flux = pfba_solution.fluxes[product_exchange_rnx]
yield_pFBA = -product_flux / substrate_flux # fixed
allYields_pFBA.append(yield_pFBA)
else: # else do regular FBA
solution = model.optimize()
FBA_substrate_flux = solution.fluxes[substrate_exchange_rnx]
substrateMet = model.reactions.get_by_id(substrate_exchange_rnx).reactants[0]
substrateName = substrateMet.name
# substrateFormula = substrateMet.formula
Csub = count_atom_in_formula(substrateMet, 'C')
strEqlist = []
for j in product_exchange_rnx:
productMet = model.reactions.get_by_id(j).reactants[0]
productName = productMet.name
productFormula = productMet.formula
Cprod = count_atom_in_formula(productMet, 'C')
FBA_product_flux = solution.fluxes[j]
FBA_yield = abs((FBA_product_flux / FBA_substrate_flux) * (Cprod * 12) / (
Csub * 12)) # in gramsC / grams C: 12 gCarbon/mol
allYields_FBA.append(FBA_yield)
strEq = '{} == {} * {}'.format(productName, FBA_yield, substrateName)
allEquations.append(strEq)
if printEq:
print(modelName)
print(strEq)
return allEquations, allYields_FBA
def make_str_eq_smbl(modelName, substrate_exchange_rnx, product_exchange_rnx, equationInfo):
# modelLocations = modelName
loc = os.getcwd()
posAlquimia = loc.find('Alquimia')
loc = loc[0:posAlquimia + 8]
# modelLocations = loc + r'\excel files\' + modelName
modelLocations = [loc + r"\SBML models\{}".format(modelName)]
# make extended reaction equtions for sbml models or maybe even make them into JSON files? idk
# would run quicker instead of having to read the xlm files each time
equations, yields = get_conversion_sbml(modelLocations, substrate_exchange_rnx, product_exchange_rnx)
# make abbreviations dictionary
abbrDict = {}
inputSbmlName = equationInfo.input_name
inputAbrr = equationInfo.input_abrr
abbrDict.update({inputSbmlName: inputAbrr})
outputSbmlName = split_remove_spaces(equationInfo.output_name, ',')
outputAbrr = split_remove_spaces(equationInfo.output_abrr, ',')
for i, out in enumerate(outputSbmlName):
abbrDict.update({out: outputAbrr[i]})
allEquations = []
for eq in equations:
for name in abbrDict:
if name in eq:
eq = eq.replace(name, abbrDict[name])
allEquations.append(eq)
return allEquations
def make_str_eq_json(modelObject, equationInfo):
"""Reconstructs the surrogate model which is in a .json file into a equation that can be read by Pyomo
inputs:
model object (dict): is the unpacked .json file
equationInfo (DF): a pandas data frame containing the information from the Excel file about the surrogate model
i.e., the abbreviations for the model parameters
"""
# extract all the information
outputs = modelObject['outputs']
coef = modelObject['coef']
intercept = modelObject['intercept']
name = modelObject['name']
lable = modelObject['lable']
if 'waterEq' in modelObject:
waterEq = modelObject['waterEq']
else:
waterEq = '' # make an empty equation
# if the equation is to determin the yield of the reaction then we need to know from what component to take the
# yield of! specified in the Excel sheet 'models', column 'yield_of'
yieldOf = equationInfo['yield_of']
# make abbreviations dictionary
abbrDict = {}
# get the variable names of the inputs and outputs in the reaction and their respective abbreviations
if not equationInfo.input_name: # in other words the input names and abbreviations stay as their originals.
# used for cluster inputs
key1 = list(coef.keys())[0] # just take the first key, the substrates are the same for all the products
varNameInputs = list(coef[key1].keys())
AbrrInputs = varNameInputs
else:
varNameInputs = split_remove_spaces(equationInfo.input_name, ',')
AbrrInputs = split_remove_spaces(equationInfo.input_abrr, ',')
varNameOutputs = split_remove_spaces(equationInfo.output_name, ',')
varNames = varNameInputs + varNameOutputs
AbrrOutputs = split_remove_spaces(equationInfo.output_abrr, ',')
Abrr = AbrrInputs + AbrrOutputs
# make sure there are as many abbreviations as well as variable inputs
if len(varNameInputs) != len(AbrrInputs):
raise Exception(
"Missing an abbreviation for one of the inputs of model {}. Check out the Excel sheet 'models'".format(
name))
if len(varNameOutputs) != len(AbrrOutputs):
raise Exception(
"Missing an abbreviation for one of the outputs of model {}. Check out the Excel sheet 'models'".format(
name))
for i, varN in enumerate(varNames):
abbrDict.update({varN: Abrr[i]})
equationList = []
print(name)
for out in outputs:
outAbrr = abbrDict[out]
coefOfOutputs = coef[out]
# update the water Eq
waterEq = waterEq.replace(out, outAbrr)
# preallocate the right side of the reaction equation
yieldEq = ''
for feature in coefOfOutputs:
### this for loop to replace all the full variable names with the abbreviuations
featureAbbr = ''
for v in varNames:
if v in feature:
featureAbbr = feature.replace(v, abbrDict[v])
if not featureAbbr:
raise Exception('the feature {} has no abbreviation check the JSON file and the sheet models, '
'are names and abrr correct?'.format(feature))
###
featureCoef = coefOfOutputs[feature]
yieldEq += ' + {} * {} '.format(featureAbbr, featureCoef)
# if the eqation is from a sbml/GEM model the yield is already calculated
if lable == 'SBML':
equation = '{} == {}'.format(outAbrr, yieldEq)
# if the yield of the reaction needs to be caluculated, we need to know what input to take the yield of
elif lable == 'yield_equation': # the lable is then "yield_equation"
yieldEq += ' + {}'.format(intercept[out])
equation = '{} == ({}) * {}'.format(outAbrr, yieldEq, yieldOf)
else:
raise Exception("the lable given for the surrogate model '{}' must be either 'SBML' or "
"'yield_equation' ".format(name))
# print(equation)
# print('')
equationList.append(equation)
if waterEq: # if the string is not empty add it to the list of equations
equationList.append(waterEq)
return equationList
def make_str_eq_distilation_json(modelObject, intervalName):
"""Reconstructs the surrogate model which is in a .json file into a equation that can be read by Pyomo
inputs:
model object (dict): is the unpacked .json file
equationInfo (DF): a pandas data frame containing the information from the Excel file about the surrogate model
i.e., the abbreviations for the model parameters
"""
# extract all the information
inputs = modelObject['inputs']
outputs = modelObject['outputs']
coef = modelObject['coef']
intercept = modelObject['intercept']
name = modelObject['name']
lable = modelObject['lable']
# get the variable and the features of the regresion
varNameInputs = inputs
varNameOutputs = outputs
keysCoef = list(coef.keys()) # just take the first key, the substrates are the same for all the products
varNames = varNameInputs + varNameOutputs
replacementDict = {}
for var in varNames:
newVar = var.replace(var, '{}_{}'.format(var, intervalName))
replacementDict.update({var:newVar})
out = varNameOutputs[0] # there should only be one output name
outVar = replacementDict[out]
outVar = 'energy_consumption_{}'.format(intervalName)
coefOfOutputs = coef[out]
# preallocate the right side of the reaction equation
energyRequiermentEqLeft = "model.var['{}'] == ".format(outVar)
energyRequiermentEqRight = ""
for feature in coefOfOutputs:
featureCoef = coefOfOutputs[feature]
energyRequiermentEqRight += " + {} * {} ".format(feature, featureCoef)
# add intercept
energyRequiermentEqRight += " + {}".format(intercept[out])
energyRequiermentEqRight = '(' + energyRequiermentEqRight + ')'
for var in replacementDict:
rplc = "model.var['{}']".format(replacementDict[var])
energyRequiermentEqRight = energyRequiermentEqRight.replace(var,rplc)
eq = energyRequiermentEqLeft + energyRequiermentEqRight
variableList = list(replacementDict.values())
return eq, variableList
# Created on Tue Oct 04 2022
# Contains the classes to make the process interval objects
# ============================================================================================================
# Input, reactor and output Classes
# ============================================================================================================
def make_eqation_bool_dependent(equation, booleanVariable):
""" Adds the boolean variable to the equation
Params:
equation (str): string equation in pyomo format
booleanVariable (str): boolean variable to add
"""
equationModified = equation.replace('==', '== ( ')
equationModified += " ) * model.boolVar['{}'] ".format(booleanVariable)
return equationModified
def define_connect_info(connectInfo):
"""
determines the connection keys from the connectio matrix
parameters:
connectInfo (int or str): a cell from the connection matrix
returns:
connectKey (logical): if True the interval is connected one on one
sepKey (str): sep1 or sep2: defines which seperation stream is entering
splitKey (str): split1 or spilt2: defines which separation stream is entering
boolKey (str): redundant but indicates if the entering stream is dependent on a 'boolean flow'
"""
sepKey = ''
splitKey = ''
boolKey = ''
connectKey = False # connent key referes to if there is a simple conection without seperation or splitting
if isinstance(connectInfo, str):
if 'sep' in connectInfo:
sepIndex = connectInfo.find('sep')
sepKey = connectInfo[sepIndex: sepIndex + 4]
if 'split' in connectInfo:
splitIndex = connectInfo.find('split')
splitKey = connectInfo[splitIndex: splitIndex + 6]
if 'bool' in connectInfo:
bIndex = connectInfo.find('split')
boolKey = connectInfo[bIndex: bIndex + 4]
if not splitKey and not sepKey:
connectKey = True # thus a simple connection
return connectKey, sepKey, splitKey, boolKey
class BooleanClass:
def __init__(self, ExcelDict):
""" makes the equations that regulate if a certain process is chosen or not nl: 1 == sum(boolean variables)
the function works as followed: the DFconnectionMatrix excludes the inputs!! important!! the input boolean variables
are regulated in the first input objected.
returns:
boolean variables (list): list of boolean variables
boolean equations (lsit): list of boolean equations
"""
# create the label for the object
self.label = 'bool object'
# extract the necesary dataframes
DFIntervals = ExcelDict['input_output_DF']
DFconnectionMatrix = ExcelDict['connection_DF']
# find the input intervals
posInputs = DFIntervals.input_price.to_numpy() != 0
inputIntervalNames = DFIntervals.loc[posInputs, 'process_intervals'].to_list()
# ------------------------------ intput boolean equations---------------------------------------------------
# We need to find out which inputs are bound to boolean variables amongst the input variable themselves i.e.,
# if only certain inputs can be chosen amongst multiple possible inputs in other words we need to find the
# boolean variables from the connenction matrix (diagonals of the matrix) so the activation equation sum(y)
# == 1 can be made.
# there are 2 possible ways, the specific inputs can be specified in the Excel file or selected from a list in
# the case of "input clusters" clusters are made apparent when the column components in the Excel sheet
# input_output_intervals contains a string to a .json file
inputBooleanVariables = [] # prealloccate
ClusterDict = {} # prealloccate
inputBoolEquation = "1 == " # bool amoug different input intervals
inputBoolEquationCluster = "1 == " #bool equation for selection of substrtate in cluster of 1 interval
equationCheck = inputBoolEquation
clusterSwitch = False
for i, intervalName in enumerate(inputIntervalNames):
componentSpecification = DFIntervals.loc[posInputs, 'components'][i]
inputClusterDict = {} # preallocate
if '.json' in componentSpecification: # remember it's a pandas series still
clusterSwitch = True
if clusterSwitch:
jsonFile = componentSpecification
jsonLoc = get_location(file=jsonFile)
with open(jsonLoc) as file:
inputs_prices = json.load(file)
inputNames = list(inputs_prices.keys())
inputBooleanVariables = [] # prealloccate
# only one input from the cluster can be chosen
for iName in inputNames:
inputBoolVar = 'y_{}_{}'.format(iName,inputIntervalNames[0])
inputBooleanVariables.append(inputBoolVar)
inputBoolEquationCluster += " + " + "model.boolVar['{}']".format(inputBoolVar)
price = inputs_prices[iName]
inputClusterDict.update({iName: {'price': price, 'bool': inputBoolVar}})
# make the over arcing dictionary
ClusterDict.update({intervalName:inputClusterDict})
# look if there is a boolean amoung the input variables
inputBoolVar = DFconnectionMatrix[intervalName][intervalName] # diagonal position of the connection matrix
if isinstance(inputBoolVar, str):
inputBooleanVariables.append(inputBoolVar)
inputBoolEquation += " + " + "model.boolVar['{}']".format(inputBoolVar)
inputBoolEquation = [inputBoolEquation]
inputBoolEquationCluster = [inputBoolEquationCluster]
# if where is only a single input just make a boolean equation y = 1, so it is always chosen
# (best to acctually avoid) how could we do this?
if equationCheck == inputBoolEquation[0]:
inputBoolEquation = [] # just empty
if equationCheck == inputBoolEquationCluster[0]:
inputBoolEquationCluster = [] # just empty
# # find out if your working with clusters
# componentSpecification = '' # preallocate to avoid errors
# clusterSwitch = False
# if len(inputIntervalNames) == 1:
# componentSpecification = DFIntervals.loc[posInputs, 'components'][0]
# if '.json' in componentSpecification: # remember it's a pandas series still
# clusterSwitch = True
#
# # if there is a cluster of inputs to chose from, make the boolean equation based on the input.json file given in
# # the components column
# inputClusterDict = {} # preallocate
# if clusterSwitch:
# jsonFile = componentSpecification
# jsonLoc = get_location(file=jsonFile)
# with open(jsonLoc) as file:
# inputs_prices = json.load(file)
# inputNames = list(inputs_prices.keys())
# inputBooleanVariables = [] # prealloccate
#
# inputBoolEquation = "1 == "
# for iName in inputNames:
# inputBoolVar = 'y_{}_{}'.format(iName,inputIntervalNames[0])
# inputBooleanVariables.append(inputBoolVar)
# inputBoolEquation += " + " + "model.boolVar['{}']".format(inputBoolVar)
# price = inputs_prices[iName]
# inputClusterDict.update({iName: {'price': price, 'bool': inputBoolVar}})
#
# else:
# inputBooleanVariables = [] # prealloccate
# inputBoolEquation = "1 == "
# equationCheck = inputBoolEquation
# for i, intervalName in enumerate(inputIntervalNames):
# inputBoolVar = DFconnectionMatrix[intervalName][intervalName] # diagonal position of the connention matrix
# if isinstance(inputBoolVar, str):
# inputBooleanVariables.append(inputBoolVar)
# inputBoolEquation += " + " + "model.boolVar['{}']".format(inputBoolVar)
# # if where is only a single input just make a boolean equation y = 1, so it is always chosen
# # (best to acctually avoid) how could we do this?
# if equationCheck == inputBoolEquation:
# inputBoolVar = 'y'
# inputBooleanVariables.append(inputBoolVar)
# inputBoolEquation += " + " + "model.boolVar['{}']".format(inputBoolVar)
# if there exist bool inputs, make a unique list
if len(inputBooleanVariables) != len(set(inputBooleanVariables)):
# if not a unique list raise exception
raise Exception("The boolean variables for the inputs are not unique, check the diagonals of the "
"input intervals of the connection matrix ")
# ------------------------------ other interval boolean equations-----------------------------------------------
# main idea:
# 1) loop over the rows of the DF.
# 2) count the columns of this row which are not zero in sequence! (save the interval names in a list)
# 3) the longest list is the list of intervals dependant of the boolean variable
# 4) the boolean variable can be found on the diagonal (i.e. with the same row and column index)
# 5) make the boolean equation
# 6) delete the columns that contain the sequence
# 7) restart at step 1 till the DF is empty
# make a list of all the intervals excluding the inputs
DFconnectionMatrix = DFconnectionMatrix.drop(labels=inputIntervalNames, axis=1)
processIntervalnames = list(DFconnectionMatrix.index)[len(inputIntervalNames):]
# prun the Dataframe, anything that does not have a bool label on the diagonal can be dropped
toDrop = []
for i in processIntervalnames:
if not isinstance(DFconnectionMatrix[i][i], str):
toDrop.append(i)
DFconnectionMatrix = DFconnectionMatrix.drop(labels=toDrop, axis=1)
# start the while loop, aslong as the dataframe is not empty continue
switch = True
equationsSumOfBools = []
booleanVariables = []
iteration = 0
while switch:
iteration += 1
saveDict = {}
for index, row in DFconnectionMatrix.iterrows():
intervalNames = []
for indexCol, element in row.items():
if isinstance(element, str) or element != 0: # so comes from a separation or just connected by '1'
intervalNames.append(indexCol)
else:
break
saveDict.update({index: intervalNames})
# find the key in the saveDict that has the longest list
key_max_sequential = max(saveDict, key=lambda k: len(saveDict[k]))
# create the equation
eq = '1 == '
for interval in saveDict[key_max_sequential]:
boolVar = DFconnectionMatrix[interval][interval]
booleanVariables.append(boolVar)
eq += "+ model.boolVar['{}'] ".format(boolVar)
equationsSumOfBools.append(eq)
# now we need to drop the colums in the original dataframe that already form 1 set of equations
# the colunms that need to be droped are in saveDict[key_max_sequential]1
cols2drop = saveDict[key_max_sequential]
DFconnectionMatrix = DFconnectionMatrix.drop(labels=cols2drop, axis=1)
# once the DF is empty we can stop the while loop
if DFconnectionMatrix.empty:
switch = False
# if iteration 50 is hit, then stop the program, somthing wrong is going on
if iteration > 50:
raise Exception(
"50 iteration have passed to try and make the boolean equations, check to see if the "
"connection matrix is correctly formulated or check the function 'make_boolean_equations'")
# while loop has stopped
# check that all the boolean variables have unique values
uniqueSet = set(booleanVariables)
if len(uniqueSet) != len(booleanVariables):
raise Exception("the boolean variables are not all unique, check the diagonal of the connection matrix")
# add the cluster dictionary to the object, empty if there is none
self.clusterDict = ClusterDict
# add the boolean variables to the allVariable dictionary
allBoolVariables = booleanVariables + inputBooleanVariables
self.allVariables = {'continuous': [],
'boolean': allBoolVariables,
'fraction': []}
# define it's boundries
booleanBounds = {}
for i in allBoolVariables:
booleanBounds.update({i: 'bool'})
self.boundaries = booleanBounds
# add the equations to the pyomoEquations object
self.pyomoEquations = equationsSumOfBools + inputBoolEquation + inputBoolEquationCluster
class InputIntervalClass:
def __init__(self, inputName, compositionDict, inputPrice, boundryInputVar,clusterDict,boolDict=None,
split=None, separationDict=None, booleanVariable=None):
if separationDict is None:
separationDict = {}
if split is None:
split = []
if boolDict is None:
boolDict = {}
if booleanVariable is None:
booleanVariable = ''
# declare (preallocate) empty pyomo equations list
pyomoEq = []
# declare input interval name
self.label = 'input'
self.inputName = inputName.upper() # put in capitals
self.inputPrice = inputPrice
addOn4Variables = '_' + inputName.lower()
# initial Boundry dictionary of all the variables (add where aprropriate )
self.allBoundries = {}
# change the composition names in the dictionary
compositionDictNew = {}
initialCompositionNames = []
if len(compositionDict) == 1 and '.json' in list(compositionDict.keys())[0]:
compositionDictNew = compositionDict # keep the json name in the dictionary
else:
for i in compositionDict:
compositionDictNew.update({i + addOn4Variables: compositionDict[i]})
initialCompositionNames.append(i)
# self.initialCompositionNames = initialCompositionNames
self.compositionDict = compositionDictNew
# error if you choose a wrong name
for i in initialCompositionNames: # maybe don't place the warning here
if i in inputName:
raise Exception("the component {} is in interval name {}, change the component name of the reactor to "
"avoid conflict with the equations".format(inputName, i))
# make the component equations as string equations
# --------------------------------------------------------------------------- cluster section
try:
intervalCluster = clusterDict[inputName]
except:
intervalCluster = {}
self.clusterDict = intervalCluster
if intervalCluster: # i.e., working with clusters, if not clusterDict is empty
for key in intervalCluster:
# activation equations
inputVariable = key
booleanVariable = intervalCluster[key]['bool']
activationEqPyoUB = "model.var['{}'] <= {} * model.boolVar['{}'] ".format(inputVariable,
boundryInputVar[1],
booleanVariable)
activationEqPyoLB = "{} * model.boolVar['{}'] <= model.var['{}'] ".format(boundryInputVar[0],
booleanVariable,
inputVariable)
# add to the list of equations
pyomoEq.append(activationEqPyoLB)
pyomoEq.append(activationEqPyoUB)
# declare component variables
componentVariables = list(intervalCluster.keys())
continuousVariables = componentVariables
self.leavingInterval = componentVariables
#if compositionDictNew
else: # -------------------------------------------------------------------- non- cluster section
for component in compositionDictNew:
eqPy = "model.var['{}'] == {} * model.var['{}']".format(component, self.compositionDict[component],
self.inputName)
pyomoEq.append(eqPy)
componentVariables = list(compositionDictNew.keys())
continuousVariables = componentVariables + [self.inputName]
self.boolDict = boolDict # necesary?
self.split = split # necesary?
self.separationDict = separationDict # necesary?
self.leavingInterval = componentVariables
if booleanVariable:
activationEqPyoUB = "model.var['{}'] <= {} * model.boolVar['{}'] ".format(self.inputName,
boundryInputVar[1],
booleanVariable)
activationEqPyoLB = "{} * model.boolVar['{}'] <= model.var['{}'] ".format(boundryInputVar[0],
booleanVariable, self.inputName)
pyomoEq.append(activationEqPyoLB)
pyomoEq.append(activationEqPyoUB)
# ----------------- check ------------------------------------------------------------- point charly
# put all VARIABLES that pyomo needs to declare here
self.allVariables = {'continuous': continuousVariables, # continous variables
'boolean': [], # boolean variables
'fraction': []} # fraction variables
# make BOUNDARIES for all variables
# interval variables are bounded by inequality equations bounds (see above)
boundaryDict = {self.inputName: [0, None]}
# all other variables are positiveReals
for i in componentVariables:
boundaryDict.update({i: 'positiveReals'})
self.boundaries = boundaryDict
# put all EQUATIONS that pyomo needs to declare here
self.pyomoEquations = pyomoEq
class ProcessIntervalClass:
def __init__(self, inputs, outputs, reactionEquations, boundryInputVar, nameDict, mix=None,
utilities=None, energyUtility=None, booleanVariable=None, splitList=None, separationDict=None,
operationalVariablesDict=None, energyConsumption=0):
if utilities is None:
utilities = {}
if separationDict is None:
separationDict = {}
if mix is None:
mix = []
if booleanVariable is None:
booleanVariable = ''
if operationalVariablesDict is None:
operationalVariablesDict = {}
if energyUtility is None:
energyUtility = {}
self.label = 'process_interval'
self.booleanVariable = booleanVariable
self.operationalVariablesDict = operationalVariablesDict
# further the lable of the interval i.e.:reactor or seperator
if reactionEquations:
self.intervalType = 'reactor' # lable
elif reactionEquations is None and separationDict:
self.intervalType = 'separator' # lable
# attributes you want to be able to call from the object (some are probably redundant)
self.nameDict = nameDict
self.inputs = inputs # original unmodified names
self.outputs = outputs # original unmodified names
self.initialCompositionNames = inputs + outputs
self.mix = mix # found by the Excel file (don't need to specify in this script)
self.utilities = utilities # consists of a dictionary {nameUtilty: [bounds]}
self.separationDict = separationDict # dictionary defining separation fractions of each component
# error if you choose a wrong name
intervalName = list(nameDict.keys())[0]
self.intervalName = intervalName
for i in self.initialCompositionNames: # maybe don't place the warning here
if i in intervalName:
raise Exception("the component {} is in interval name {}, change the component name of the reactor to "
"avoid conflict with the equations")
# interval Variable name
originalIntervalName = list(self.nameDict.keys())[0]
intervalVariable = originalIntervalName.upper()
addOn4Variables = '_' + originalIntervalName
# declare (preallocate) empty pyomo equations list
pyomoEq = []
# reactor equations
reactionVariablesOutput = [] # preallocate to avoid error
if reactionEquations != None:
reactorEquations, reactionVariablesOutput, helpingDict = \
self.make_reaction_equations(reactionEquations=reactionEquations,
booleanVariable=booleanVariable,
intervalVariable=intervalVariable)
# pyomoEq += reactorEquations # added during the update fuction
else:
# If there is a dictionary for the separation equations but no reaction, then there is no helpingDict.
# In other words now the separation equations need to be updated according to the incomming flow...
# this is indicated in function update_interval_equations by the self.intervalType label!
helpingDict = {}
# separation equations
separationVariables = [] # preallocate to avoid error
if separationDict: # if there is separation make the separation equations
separationEquationsPyomo, separationVariables = self.make_separation_equations(separationDict, helpingDict,
booleanVariable=booleanVariable)
if helpingDict: # if the helping dict does noit exist the separation equations are added during the update function
pyomoEq += separationEquationsPyomo # otherwise they can be added strait away
# spliting equations
splitComponentVariables = [] # preallocate to avoid error
splitFractionVariables = [] # preallocate to avoid error
if splitList:
splittingEquations, splitComponentVariables, splitFractionVariables = self.make_split_equations(splitList,
addOn4Variables,
booleanVariable=booleanVariable)
pyomoEq += splittingEquations
# mixing equations
# see def make_mixing_equations(), these equations are made in the update when it is known
# to where each stream is going to and hence which streams are mixed
# utility (chemical) equations
# are made in the update_function where reactor equations are also completed
# utility (energy) equations
# are made in the update_function because we need to know what is entering the interval
# give the varibles needed to the object
self.utilityEnergy = energyUtility
# define wat is leaving the reactor
# can either be from the reactor, the separation process or the spliting
# maybe look at empty lists of the variables instead? check that out
if not splitList and not separationDict:
self.leavingInterval = reactionVariablesOutput
elif separationDict and not splitList:
self.leavingInterval = separationVariables
elif splitList and not separationDict:
self.leavingInterval = splitComponentVariables
elif splitList and separationDict:
VariablesGoingOutSep = separationVariables.copy()
toRemove = []
for i in splitList:
separationStream = ''
if 'sep' in i:
indexSep = i.find('sep')
separationStream = i[
indexSep:indexSep + 4] # the separation stream that is going te get knocked out
for j in VariablesGoingOutSep:
if separationStream in j:
toRemove.append(j)
# remove unwanted variables
for i in toRemove:
VariablesGoingOutSep.remove(i)
self.leavingInterval = VariablesGoingOutSep + splitComponentVariables
# put all self.VARIABLES that pyomo needs to declare here
self.reactionVariablesOutput = reactionVariablesOutput
# reactionVariablesInputs can be found in class function: update_reactor_equations
self.intervalVariable = intervalVariable
# define the bounds of the variables
boundaryDict = {} # intervals have bounds
boundaryDict.update({intervalVariable: 'positiveReals'})
for i in reactionVariablesOutput:
boundaryDict.update({i: 'positiveReals'})
for i in separationVariables:
boundaryDict.update({i: 'positiveReals'})
for i in boundryInputVar: # this is for when you want to add a specific bound to a reaction variable SEE EXCEL
boundaryDict[i] = boundryInputVar[i]
self.boundryInputVar = boundryInputVar
for i in splitComponentVariables:
boundaryDict.update({i: 'positiveReals'})
for i in splitFractionVariables:
boundaryDict.update({i: 'fraction'})
self.boundaries = boundaryDict
# make a list with all the variables
# continuousVariables = [self.intervalVariable] + self.reactionVariablesOutput + separationVariables + splitComponentVariables # self.separationVariables
continuousVariables = self.reactionVariablesOutput + separationVariables + splitComponentVariables # self.separationVariables
# booleanVariables = self.boolVariables
# + self.activationVariable don't need to add this, already in the previous interval under boolVariables
self.allVariables = {'continuous': continuousVariables,
'boolean': [],
'fraction': splitFractionVariables}
# add fraction variables
# make a list with all the equations
# self.allEquations = self.separationEquations + self.eqSumOfBools + self.boolActivationEquations + self.totalMassEquation
self.pyomoEquations = pyomoEq
def make_reaction_equations(self, reactionEquations, intervalVariable, booleanVariable=None):
""" function that creates the (preliminary) equations of the reactions that take place in an interval.
these equations do not have the input variable filled in. In the function update_interval_equations the
reaction equations are updated with the correct inputs variables
Parameters:
reactionEquations (str or int) reference to where the equations are stored
booleanVariable (str) the string referening to the boolean variable
intervalVariable (str) name of the interval variable
Returns:
allEquationsPyomo (list) list of reqction equations
reactionVariablesOut (list) list of variables after the reaction
helpingDict (dict) a dictionary helping to make the separation equations
"""
originalIntervalName = list(self.nameDict.keys())[0]
addOn4Variables = '_' + originalIntervalName
if isinstance(reactionEquations, str): # failsafe if you forget to code the string reactor expression as a list
reactionEquations = [reactionEquations] # make it a list
ouputs2change = self.outputs
ReactorEquationsPyomo = []
reactionVariablesOutput = []
helpingDict = {}
for eq in reactionEquations:
eqPyo = eq
for out in ouputs2change:
newOutputName = out + '{}'.format(addOn4Variables)
eq = eq.replace(out, newOutputName)
# pyomo version
newOutputNamePyo = "model.var['{}']".format(newOutputName)
eqPyo = eqPyo.replace(out, newOutputNamePyo)
if newOutputName not in reactionVariablesOutput:
reactionVariablesOutput.append(newOutputName)
helpingDict.update({out: newOutputName}) # helpìng dictionary for the separation equations
if booleanVariable:
eqPyo = make_eqation_bool_dependent(equation=eqPyo, booleanVariable=booleanVariable)
ReactorEquationsPyomo.append(eqPyo)
# place all the equations in the object
self.reactionEquations = ReactorEquationsPyomo
# mass equations (of the outputs from the reaction equations )
eqMassInterval = intervalVariable + " == "
eqPyoMass = "model.var['{}'] == ".format(intervalVariable)
for out in reactionVariablesOutput:
eqMassInterval += " + " + out
eqPyoMass += " + " + "model.var['{}']".format(out) # pyomo version
# decalre all equations and pass them on
self.totalMassEquation = [eqMassInterval] # TODO mass eq. not added to pyomo for the moment, necesary?
self.reactionEquationsPyomo = ReactorEquationsPyomo
allEquationsPyomo = [eqMassInterval] + ReactorEquationsPyomo
# old activation equations (keep in comments for the moment)
# bool activation constraints
# has been de-activated. bool variables are now in the reaction equations (solver is more efficient that way)
# boolActivationEquations = []
# if boolActivation: # if there is an activation constraint
# bounds = nameDict[originalIntervalName]
# lowerActivationEq = "{} * {} <= {}".format(boolActivation[0],bounds[0],intervalVariable)
# upperActivationEq = "{} <= {} * {}".format(intervalVariable,boolActivation[0], bounds[1])
# boolActivationEquations.append(lowerActivationEq)
# boolActivationEquations.append(upperActivationEq)
#
# # pyomo version
# lowerActivationEqPyo = "model.boolVar['{}'] * {} <= model.var['{}']".format(boolActivation[0], bounds[0], intervalVariable)
# upperActivationEqPyo = "model.var['{}'] <= model.boolVar['{}'] * {}".format(intervalVariable, boolActivation[0], bounds[1])
# pyomoEq.append(lowerActivationEqPyo)
# pyomoEq.append(upperActivationEqPyo)
# self.boolActivationEquations = boolActivationEquations
return allEquationsPyomo, reactionVariablesOutput, helpingDict
def make_separation_equations(self, separationDict, helpingDict, booleanVariable=None):
""" function that creates the separation equations of the process interval
Parameters:
separationDict (dict): list of components to separate
helpingDict (dict): dictionary to help create the variables
Returns:
separationEquations (list): list of separation equations
seperationVariables (list): list of variables
"""
separationEquationsPyomo = []
separationVariables = []
for sep in separationDict: # if it is empty it should not loop nmrly
for componentSep in separationDict[sep]:
# create the separation variable that is leaving
sepVar = componentSep + '_' + sep
separationVariables.append(sepVar)
# sepVarPyo = "model.var['{}']".format(sepVar)
if helpingDict:
var = helpingDict[componentSep] # helpìng dictionary to get the right variable
# pyomo equations
eqSepPyo = "model.var['{}'] == {} * model.var['{}']".format(sepVar,
separationDict[sep][componentSep], var)
else: # else if no helping dict, it will get updated in the update function
var = componentSep
eqSepPyo = "model.var['{}'] == {} * {}".format(sepVar, separationDict[sep][componentSep],
var)
if booleanVariable: # add boolean variable if there are any
eqSepPyo = eqSepPyo.replace('==', '== ( ')
eqSepPyo += " ) * model.boolVar['{}'] ".format(booleanVariable)
separationEquationsPyomo.append(eqSepPyo)
self.separationEquations = separationEquationsPyomo
self.separationVariables = separationVariables
return separationEquationsPyomo, separationVariables
def make_split_equations(self, splitList, addOn4Variables, booleanVariable=None):
""" function that creates the equations of the split streams
Params:
splitList (list): list of components to split
addOn4Variables (str): name of the add-on for the variables
Returns:
splittingEquations (list): list of reaction equations
splitComponentVariables (list): list of variables
"""
# Error messaging if there are not 2 stream to split to
if len(splitList) != 1 and len(splitList) != 2:
raise Exception('look at reactor row {} in the connection matrix, it looks like you are missing a split '
'statement'.format(addOn4Variables))
# preallocate variables and equation lists
splitFractionVariables = []
splitComponentVariables = []
splittingEquations = []
# make split equations
for splitStream in splitList:
splitFractionVar = 'split_fraction_{}'.format(splitStream)
splitFractionVariables.append(splitFractionVar)
for component in self.outputs: # the self.outputs are the original names given to the outputs of the reactor
# make and get variables
split1 = component + '_' + splitStream + '_' + 'split1'
split2 = component + '_' + splitStream + '_' + 'split2'
splitComponentVariables.append(split1)
splitComponentVariables.append(split2)
component2split = component + '_' + splitStream
# make equations
eqSplit1Pyo = "model.var['{}'] == model.fractionVar['{}'] * model.var['{}']".format(split1,
splitFractionVar,
component2split)
eqSplit2Pyo = "model.var['{}'] == (1 - model.fractionVar['{}']) * model.var['{}']".format(split2,
splitFractionVar,
component2split)
# add boolean variable if there are any
if booleanVariable:
eqSplit1Pyo = eqSplit1Pyo.replace('==', '== ( ')
eqSplit2Pyo = eqSplit2Pyo.replace('==', '== ( ')
eqSplit1Pyo += " ) * model.boolVar['{}'] ".format(booleanVariable)
eqSplit2Pyo += " ) * model.boolVar['{}'] ".format(booleanVariable)
# add equations to the lsit
splittingEquations.append(eqSplit1Pyo)
splittingEquations.append(eqSplit2Pyo)
self.splitEquations = splittingEquations
return splittingEquations, splitComponentVariables, splitFractionVariables