unifac 0.1.0__tar.gz
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.
- unifac-0.1.0/LICENSE +21 -0
- unifac-0.1.0/PKG-INFO +46 -0
- unifac-0.1.0/README.md +29 -0
- unifac-0.1.0/pyproject.toml +30 -0
- unifac-0.1.0/setup.cfg +4 -0
- unifac-0.1.0/src/unifac/__init__.py +1 -0
- unifac-0.1.0/src/unifac/groupe_surfaces_and_volumes.xlsx +0 -0
- unifac-0.1.0/src/unifac/interaction_parameters.xlsx +0 -0
- unifac-0.1.0/src/unifac/unifac_dortmund.py +663 -0
- unifac-0.1.0/src/unifac.egg-info/PKG-INFO +46 -0
- unifac-0.1.0/src/unifac.egg-info/SOURCES.txt +12 -0
- unifac-0.1.0/src/unifac.egg-info/dependency_links.txt +1 -0
- unifac-0.1.0/src/unifac.egg-info/requires.txt +5 -0
- unifac-0.1.0/src/unifac.egg-info/top_level.txt +1 -0
unifac-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Emil Kongsbach
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
unifac-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: unifac
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A Python implementation of the UNIFAC-Dortmund group contribution method
|
|
5
|
+
Author-email: Emil Kongsbach <emil.cheme@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/Emil-Kongsbach/UNIFAC
|
|
8
|
+
Requires-Python: >=3.9
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Requires-Dist: numpy
|
|
12
|
+
Requires-Dist: pandas
|
|
13
|
+
Requires-Dist: ugropy
|
|
14
|
+
Requires-Dist: openpyxl
|
|
15
|
+
Requires-Dist: scipy
|
|
16
|
+
Dynamic: license-file
|
|
17
|
+
|
|
18
|
+
# UNIFAC
|
|
19
|
+
An implementation of the group contribution method of the Dortmund modification of UNIFAC. Calculates the activity coeficients of species in a mixture based on functional group parameters
|
|
20
|
+
|
|
21
|
+
"""
|
|
22
|
+
from unifac import Unifac_Dortmund
|
|
23
|
+
|
|
24
|
+
# Set system parameters
|
|
25
|
+
smiles_lst = ["CCO", "CC(=O)C","CC=CC"] # NA
|
|
26
|
+
x_lst=[1/3, 1/3, 1/3] # NA
|
|
27
|
+
T=298 # K
|
|
28
|
+
|
|
29
|
+
mixture=Unifac_Dortmund(smiles_lst,x_lst,T)
|
|
30
|
+
|
|
31
|
+
# Returns activity coefficient of component 0 in the mixture, according to the order in which the components were passed.
|
|
32
|
+
print(mixture.gamma_singular(0))
|
|
33
|
+
|
|
34
|
+
# Returns activity coefficient of all components in the mixture according to the order in which componenets were passed
|
|
35
|
+
print(mixture.gamma_total())
|
|
36
|
+
|
|
37
|
+
# stability analysis
|
|
38
|
+
|
|
39
|
+
mixture.is_stable_local() # computes local stability (spinodal)
|
|
40
|
+
|
|
41
|
+
mixture.is_stable_global() # computes global stability (binodal)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
mixture.is_stable() # computes first local stability (cheap), and only if that is True it proceeds to calculate global stability (expensive)
|
|
45
|
+
|
|
46
|
+
"""
|
unifac-0.1.0/README.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# UNIFAC
|
|
2
|
+
An implementation of the group contribution method of the Dortmund modification of UNIFAC. Calculates the activity coeficients of species in a mixture based on functional group parameters
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
from unifac import Unifac_Dortmund
|
|
6
|
+
|
|
7
|
+
# Set system parameters
|
|
8
|
+
smiles_lst = ["CCO", "CC(=O)C","CC=CC"] # NA
|
|
9
|
+
x_lst=[1/3, 1/3, 1/3] # NA
|
|
10
|
+
T=298 # K
|
|
11
|
+
|
|
12
|
+
mixture=Unifac_Dortmund(smiles_lst,x_lst,T)
|
|
13
|
+
|
|
14
|
+
# Returns activity coefficient of component 0 in the mixture, according to the order in which the components were passed.
|
|
15
|
+
print(mixture.gamma_singular(0))
|
|
16
|
+
|
|
17
|
+
# Returns activity coefficient of all components in the mixture according to the order in which componenets were passed
|
|
18
|
+
print(mixture.gamma_total())
|
|
19
|
+
|
|
20
|
+
# stability analysis
|
|
21
|
+
|
|
22
|
+
mixture.is_stable_local() # computes local stability (spinodal)
|
|
23
|
+
|
|
24
|
+
mixture.is_stable_global() # computes global stability (binodal)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
mixture.is_stable() # computes first local stability (cheap), and only if that is True it proceeds to calculate global stability (expensive)
|
|
28
|
+
|
|
29
|
+
"""
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "unifac"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "A Python implementation of the UNIFAC-Dortmund group contribution method"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Emil Kongsbach", email = "emil.cheme@gmail.com" }
|
|
14
|
+
]
|
|
15
|
+
dependencies = [
|
|
16
|
+
"numpy",
|
|
17
|
+
"pandas",
|
|
18
|
+
"ugropy",
|
|
19
|
+
"openpyxl",
|
|
20
|
+
"scipy"
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
[project.urls]
|
|
24
|
+
Homepage = "https://github.com/Emil-Kongsbach/UNIFAC"
|
|
25
|
+
|
|
26
|
+
[tool.setuptools.packages.find]
|
|
27
|
+
where = ["src"]
|
|
28
|
+
|
|
29
|
+
[tool.setuptools.package-data]
|
|
30
|
+
unifac = ["*.xlsx"]
|
unifac-0.1.0/setup.cfg
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .unifac_dortmund import Unifac_Dortmund
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,663 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
import numpy as np
|
|
3
|
+
#from rdkit import Chem
|
|
4
|
+
from ugropy import Groups
|
|
5
|
+
from scipy.optimize import minimize
|
|
6
|
+
import os
|
|
7
|
+
|
|
8
|
+
script_dir = os.path.dirname(os.path.abspath(__file__))
|
|
9
|
+
|
|
10
|
+
# load from excel
|
|
11
|
+
file_path1 = os.path.join(script_dir, "interaction_parameters.xlsx")
|
|
12
|
+
df_ip = pd.read_excel(file_path1)
|
|
13
|
+
df_ip = df_ip.fillna(0) # replaces nan with 0
|
|
14
|
+
|
|
15
|
+
file_path2 = os.path.join(script_dir, "groupe_surfaces_and_volumes.xlsx")
|
|
16
|
+
df_sv=pd.read_excel(file_path2)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class Unifac_Dortmund:
|
|
20
|
+
_group_cache = {} # class-level cache shared across all instances, keyed by smiles tuple
|
|
21
|
+
|
|
22
|
+
def __init__(self,smiles_lst,mol_lst,T):
|
|
23
|
+
self.smiles_lst=smiles_lst
|
|
24
|
+
self.mol_lst=mol_lst
|
|
25
|
+
self.T=T
|
|
26
|
+
|
|
27
|
+
# making sure here to store the disected groups because of the stability analysis calls it repeatedly
|
|
28
|
+
# but doesent actually change the groups between instances
|
|
29
|
+
cache_key = tuple(smiles_lst)
|
|
30
|
+
if cache_key in Unifac_Dortmund._group_cache: # this is used the rest of the time
|
|
31
|
+
self.list_groups, self.unique_group_no = Unifac_Dortmund._group_cache[cache_key]
|
|
32
|
+
else:
|
|
33
|
+
self.list_groups, self.unique_group_no = self.format_data() # this is used for the very first instances
|
|
34
|
+
Unifac_Dortmund._group_cache[cache_key] = (self.list_groups, self.unique_group_no)
|
|
35
|
+
|
|
36
|
+
# error messages
|
|
37
|
+
if len(mol_lst) != len(smiles_lst):
|
|
38
|
+
raise ValueError(f"phi_lst (len={len(mol_lst)}) and smiles_lst (len={len(smiles_lst)}) must have same length.")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
if not np.isclose(sum(mol_lst), 1.0, atol=1e-6):
|
|
42
|
+
raise ValueError(f"molefractions must sum to unity, but where of sum {sum(mol_lst)}")
|
|
43
|
+
|
|
44
|
+
# smiles to groupes
|
|
45
|
+
def disect_smiles(self,smiles):
|
|
46
|
+
molecule = Groups(smiles, identifier_type="smiles")
|
|
47
|
+
unifac_counts = molecule.dortmund.subgroups
|
|
48
|
+
return unifac_counts
|
|
49
|
+
|
|
50
|
+
# making list of dataframes with group number, group type and number of that groupe
|
|
51
|
+
def format_data(self):
|
|
52
|
+
smiles_lst=self.smiles_lst
|
|
53
|
+
data = {"Group_No": pd.Series(dtype=int),
|
|
54
|
+
"Group": pd.Series(dtype=str),
|
|
55
|
+
"number": pd.Series(dtype=int)}
|
|
56
|
+
|
|
57
|
+
list_groups=[]
|
|
58
|
+
|
|
59
|
+
for i in range(len(smiles_lst)):
|
|
60
|
+
df = pd.DataFrame(data)
|
|
61
|
+
smiles=smiles_lst[i]
|
|
62
|
+
group_library=self.disect_smiles(smiles)
|
|
63
|
+
|
|
64
|
+
for j in range(len(group_library)):
|
|
65
|
+
group = list(group_library.keys())[j]
|
|
66
|
+
index = df_sv.index[df_sv["Subgroup Name"] == group][0]
|
|
67
|
+
group_no=df_sv.loc[index,"No."]
|
|
68
|
+
number=group_library[group]
|
|
69
|
+
|
|
70
|
+
df.loc[len(df),"Group_No"]=group_no
|
|
71
|
+
df.loc[len(df)-1,"Group"]=group
|
|
72
|
+
df.loc[len(df)-1,"number"]=number
|
|
73
|
+
list_groups.append(df)
|
|
74
|
+
|
|
75
|
+
# unique groups
|
|
76
|
+
df2=list_groups[0]
|
|
77
|
+
df2=df2["Group_No"]
|
|
78
|
+
for i in range(1,len(list_groups)):
|
|
79
|
+
df3=list_groups[i]["Group_No"]
|
|
80
|
+
df2 = pd.concat([df2, df3], ignore_index=True)
|
|
81
|
+
lst =df2.tolist()
|
|
82
|
+
|
|
83
|
+
unique_group_no = list(dict.fromkeys(lst))
|
|
84
|
+
|
|
85
|
+
return list_groups,unique_group_no
|
|
86
|
+
|
|
87
|
+
# get main group number from subgroup number
|
|
88
|
+
def get_main_group(self, group_no):
|
|
89
|
+
idx = df_sv.index[df_sv["No."] == group_no][0]
|
|
90
|
+
return df_sv.loc[idx, "Main Group No."]
|
|
91
|
+
|
|
92
|
+
#============================fractions based on whole mixture===============================
|
|
93
|
+
def cap_x(self,group_no):
|
|
94
|
+
sum_j1=0
|
|
95
|
+
sum_j2=0
|
|
96
|
+
list_groups=self.list_groups
|
|
97
|
+
|
|
98
|
+
mol_lst=self.mol_lst
|
|
99
|
+
#sum over all chemical species in mixture
|
|
100
|
+
for j in range(len(list_groups)):
|
|
101
|
+
df=list_groups[j] #select species df
|
|
102
|
+
|
|
103
|
+
try:
|
|
104
|
+
index = df.index[df["Group_No"] == group_no][0] # obs, problem her hvis en forbindelse er i et molekyle og ikke det andet, lige nu er den bare defineret sum nul i dataen, man kan evt, sige if none, continue uden at definere den sim 0
|
|
105
|
+
except:
|
|
106
|
+
continue
|
|
107
|
+
#if index==none continue
|
|
108
|
+
v_mj=df.loc[index,"number"]
|
|
109
|
+
|
|
110
|
+
x_j=mol_lst[j]
|
|
111
|
+
|
|
112
|
+
sum_j1+=v_mj*x_j
|
|
113
|
+
|
|
114
|
+
for j in range(len(list_groups)): #sum over all chemical species in mixture
|
|
115
|
+
df=list_groups[j]
|
|
116
|
+
x_j=mol_lst[j]
|
|
117
|
+
sum_n=0
|
|
118
|
+
for n in range(len(df)): # sum over all groups in molecule
|
|
119
|
+
|
|
120
|
+
v_nj=df.loc[n,"number"]
|
|
121
|
+
sum_n+=v_nj*x_j
|
|
122
|
+
sum_j2+=sum_n
|
|
123
|
+
|
|
124
|
+
return sum_j1/sum_j2
|
|
125
|
+
|
|
126
|
+
def cap_theta(self,group_no):
|
|
127
|
+
unique_group_no=self.unique_group_no
|
|
128
|
+
|
|
129
|
+
index = df_sv.index[df_sv["No."] == group_no][0]
|
|
130
|
+
Q_m=df_sv.loc[index,"Q"] #get from group contribution table
|
|
131
|
+
|
|
132
|
+
cap_x_m=self.cap_x(group_no)
|
|
133
|
+
|
|
134
|
+
sum_n=0
|
|
135
|
+
for n in range(len(unique_group_no)): #sum over all groups present in mixture
|
|
136
|
+
group_no2=unique_group_no[n]
|
|
137
|
+
|
|
138
|
+
index = df_sv.index[df_sv["No."] == group_no2][0]
|
|
139
|
+
Q_n=df_sv.loc[index,"Q"] #get from group contribution table
|
|
140
|
+
|
|
141
|
+
cap_x_n= self.cap_x(group_no2)
|
|
142
|
+
|
|
143
|
+
sum_n+=Q_n*cap_x_n
|
|
144
|
+
cap_theta_m=cap_x_m*Q_m/sum_n
|
|
145
|
+
return cap_theta_m
|
|
146
|
+
|
|
147
|
+
#============================fractions based on molecule=============================
|
|
148
|
+
def cap_x_i(self,group_no,species_no):
|
|
149
|
+
list_groups=self.list_groups
|
|
150
|
+
|
|
151
|
+
df=list_groups[species_no] #select species df
|
|
152
|
+
|
|
153
|
+
try:
|
|
154
|
+
index = df.index[df["Group_No"] == group_no][0] # obs, problem her hvis en forbindelse er i et molekyle og ikke det andet, lige nu er den bare defineret sum nul i dataen, man kan evt, sige if none, continue uden at definere den sim 0
|
|
155
|
+
v_mj=df.loc[index,"number"]
|
|
156
|
+
except:
|
|
157
|
+
v_mj=0
|
|
158
|
+
|
|
159
|
+
sum_n=0
|
|
160
|
+
for n in range(len(df)): # sum over all groups in molecule
|
|
161
|
+
v_nj=df.loc[n,"number"]
|
|
162
|
+
sum_n+=v_nj
|
|
163
|
+
|
|
164
|
+
return v_mj/sum_n
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def cap_theta_i(self,group_no, species_no):# pillede ved den her
|
|
168
|
+
list_groups=self.list_groups
|
|
169
|
+
|
|
170
|
+
index = df_sv.index[df_sv["No."] == group_no][0]
|
|
171
|
+
Q_m=df_sv.loc[index,"Q"] #get from group contribution table
|
|
172
|
+
|
|
173
|
+
df=list_groups[species_no]
|
|
174
|
+
index2 = df.index[df["Group_No"] == group_no][0]
|
|
175
|
+
#nu_m=df.loc[index2, "number"]
|
|
176
|
+
cap_x_m=self.cap_x_i(group_no,species_no)
|
|
177
|
+
|
|
178
|
+
sum_n=0
|
|
179
|
+
for n in range(len(df)): #sum over all groups present in molecule
|
|
180
|
+
group_no2=df.loc[n,"Group_No"]
|
|
181
|
+
|
|
182
|
+
index = df_sv.index[df_sv["No."] == group_no2][0]
|
|
183
|
+
Q_n=df_sv.loc[index,"Q"] #get from group contribution table
|
|
184
|
+
|
|
185
|
+
index2 = df.index[df["Group_No"] == group_no2][0]
|
|
186
|
+
#nu_n=df.loc[index2, "number"]
|
|
187
|
+
cap_x_n= self.cap_x_i(group_no2,species_no)
|
|
188
|
+
#sum_n+=nu_n*Q_n
|
|
189
|
+
sum_n+=cap_x_n*Q_n
|
|
190
|
+
#cap_theta_m_i=Q_m*nu_m/sum_n
|
|
191
|
+
cap_theta_m_i=Q_m*cap_x_m/sum_n
|
|
192
|
+
return cap_theta_m_i
|
|
193
|
+
|
|
194
|
+
#=======================================ln gamma_k=========================================
|
|
195
|
+
def ln_cap_gamma(self,group_no_1):
|
|
196
|
+
T=self.T
|
|
197
|
+
unique_group_no=self.unique_group_no
|
|
198
|
+
|
|
199
|
+
index = df_sv.index[df_sv["No."] == group_no_1][0]
|
|
200
|
+
cap_q_k=df_sv.loc[index,"Q"] #get from group contribution table
|
|
201
|
+
|
|
202
|
+
sum_m_2=0
|
|
203
|
+
sum_m_1=0
|
|
204
|
+
#==========================sum m 1:===========================
|
|
205
|
+
for m in range(len(unique_group_no)): # sum over all gropues in the mixture
|
|
206
|
+
|
|
207
|
+
group_no_2=unique_group_no[m]
|
|
208
|
+
|
|
209
|
+
cap_theta_m=self.cap_theta(group_no_2)
|
|
210
|
+
# convert subgroup number to main group number for interaction parameters
|
|
211
|
+
mg1 = self.get_main_group(group_no_1) #k
|
|
212
|
+
mg2 = self.get_main_group(group_no_2) #m
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
if mg1==mg2:
|
|
216
|
+
a_mk,a_km=0,0 # self interaction
|
|
217
|
+
else:
|
|
218
|
+
try:
|
|
219
|
+
# have to do try except as ij is in same row as ji and does not have thei own row
|
|
220
|
+
try:
|
|
221
|
+
index=df_ip.index[(df_ip["i"] == mg2) & (df_ip["j"] == mg1)][0]
|
|
222
|
+
Amk,Bmk,Cmk=df_ip.loc[index,"Aij"],df_ip.loc[index,"Bij"],df_ip.loc[index,"Cij"]
|
|
223
|
+
Akm,Bkm,Ckm=df_ip.loc[index,"Aji"],df_ip.loc[index,"Bji"],df_ip.loc[index,"Cji"]
|
|
224
|
+
except:
|
|
225
|
+
index=df_ip.index[(df_ip["i"] == mg1) & (df_ip["j"] == mg2)][0]
|
|
226
|
+
Amk,Bmk,Cmk=df_ip.loc[index,"Aij"],df_ip.loc[index,"Bij"],df_ip.loc[index,"Cij"]
|
|
227
|
+
Akm,Bkm,Ckm=df_ip.loc[index,"Aji"],df_ip.loc[index,"Bji"],df_ip.loc[index,"Cji"]
|
|
228
|
+
except:
|
|
229
|
+
raise Exception(f"Interaction parameters not found for groups {mg1} and {mg2}") from None
|
|
230
|
+
#print(f"Interaction parameters not found for groups {mg1} and {mg2}")
|
|
231
|
+
#return None
|
|
232
|
+
a_mk=Amk+Bmk*T+Cmk*T**2
|
|
233
|
+
a_km=Akm+Bkm*T+Ckm*T**2
|
|
234
|
+
cap_psi_mk=np.exp(-a_mk/T)
|
|
235
|
+
cap_psi_km=np.exp(-a_km/T)
|
|
236
|
+
|
|
237
|
+
sum_m_1+=cap_theta_m*cap_psi_mk
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
sum_n_1=0
|
|
241
|
+
#==============================sum m 2:=========================
|
|
242
|
+
#sum n:
|
|
243
|
+
for n in range(len(unique_group_no)):
|
|
244
|
+
group_no_3=unique_group_no[n]
|
|
245
|
+
cap_theta_n=self.cap_theta(group_no_3)
|
|
246
|
+
# convert subgroup number to main group number for interaction parameters
|
|
247
|
+
mg3 = self.get_main_group(group_no_3) #n
|
|
248
|
+
|
|
249
|
+
if mg2==mg3:
|
|
250
|
+
a_nm=0
|
|
251
|
+
else:
|
|
252
|
+
try:
|
|
253
|
+
# have to do try except as ij is in same row as ji and does not have thei own row
|
|
254
|
+
try:
|
|
255
|
+
index=df_ip.index[(df_ip["i"] == mg3) & (df_ip["j"] == mg2)][0]
|
|
256
|
+
Anm,Bnm,Cnm=df_ip.loc[index,"Aij"],df_ip.loc[index,"Bij"],df_ip.loc[index,"Cij"]
|
|
257
|
+
|
|
258
|
+
except:
|
|
259
|
+
index=df_ip.index[(df_ip["i"] == mg2) & (df_ip["j"] == mg3)][0]
|
|
260
|
+
Anm,Bnm,Cnm=df_ip.loc[index,"Aji"],df_ip.loc[index,"Bji"],df_ip.loc[index,"Cji"]
|
|
261
|
+
|
|
262
|
+
except:
|
|
263
|
+
raise Exception(f"Interaction parameters not found for groups {mg2} and {mg3}") from None
|
|
264
|
+
#print(f"Interaction parameters not found for groups {mg2} and {mg3}")
|
|
265
|
+
#return None
|
|
266
|
+
a_nm=Anm+Bnm*T+Cnm*T**2
|
|
267
|
+
cap_psi_nm=np.exp(-a_nm/(T))
|
|
268
|
+
|
|
269
|
+
sum_n_1+=cap_theta_n*cap_psi_nm
|
|
270
|
+
|
|
271
|
+
sum_m_2+=cap_theta_m*cap_psi_km/sum_n_1
|
|
272
|
+
|
|
273
|
+
ln_kap_gamma_k=cap_q_k*(1-np.log(sum_m_1)-sum_m_2)
|
|
274
|
+
return ln_kap_gamma_k
|
|
275
|
+
|
|
276
|
+
#=======================================ln gamma_k^i=========================================
|
|
277
|
+
def ln_cap_gamma_i(self,group_no_1,species_no):
|
|
278
|
+
T=self.T
|
|
279
|
+
list_groups=self.list_groups
|
|
280
|
+
|
|
281
|
+
index = df_sv.index[df_sv["No."] == group_no_1][0]
|
|
282
|
+
cap_q_k=df_sv.loc[index,"Q"] #get from group contribution table
|
|
283
|
+
|
|
284
|
+
df=list_groups[species_no]
|
|
285
|
+
|
|
286
|
+
sum_m_1=0
|
|
287
|
+
sum_m_2=0
|
|
288
|
+
#==========================sum m 1:===========================
|
|
289
|
+
for m in range(len(df)): # sum over all gropues in molecule
|
|
290
|
+
|
|
291
|
+
group_no_2=df.loc[m,"Group_No"]
|
|
292
|
+
|
|
293
|
+
cap_theta_m_i=self.cap_theta_i(group_no_2,species_no)
|
|
294
|
+
|
|
295
|
+
# convert subgroup number to main group number for interaction parameters
|
|
296
|
+
mg1 = self.get_main_group(group_no_1)
|
|
297
|
+
mg2 = self.get_main_group(group_no_2)
|
|
298
|
+
|
|
299
|
+
if mg1==mg2:
|
|
300
|
+
a_mk,a_km=0,0 # self interaction
|
|
301
|
+
else:
|
|
302
|
+
# have to do try except as ij is in same row as ji and does not have thei own row
|
|
303
|
+
try:
|
|
304
|
+
try:
|
|
305
|
+
index=df_ip.index[(df_ip["i"] == mg2) & (df_ip["j"] == mg1)][0]
|
|
306
|
+
Amk,Bmk,Cmk=df_ip.loc[index,"Aij"],df_ip.loc[index,"Bij"],df_ip.loc[index,"Cij"]
|
|
307
|
+
Akm,Bkm,Ckm=df_ip.loc[index,"Aji"],df_ip.loc[index,"Bji"],df_ip.loc[index,"Cji"]
|
|
308
|
+
except:
|
|
309
|
+
index=df_ip.index[(df_ip["i"] == mg1) & (df_ip["j"] == mg2)][0]
|
|
310
|
+
Amk,Bmk,Cmk=df_ip.loc[index,"Aij"],df_ip.loc[index,"Bij"],df_ip.loc[index,"Cij"]
|
|
311
|
+
Akm,Bkm,Ckm=df_ip.loc[index,"Aji"],df_ip.loc[index,"Bji"],df_ip.loc[index,"Cji"]
|
|
312
|
+
except:
|
|
313
|
+
raise Exception(f"Interaction parameters not found for groups {mg1} and {mg2}") from None
|
|
314
|
+
#print(f"Interaction parameters not found for groups {mg1} and {mg2}")
|
|
315
|
+
#return None
|
|
316
|
+
a_mk=Amk+Bmk*T+Cmk*T**2
|
|
317
|
+
a_km=Akm+Bkm*T+Ckm*T**2
|
|
318
|
+
cap_psi_mk=np.exp(-a_mk/T)
|
|
319
|
+
cap_psi_km=np.exp(-a_km/T)
|
|
320
|
+
|
|
321
|
+
sum_m_1+=cap_theta_m_i*cap_psi_mk
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
sum_n_1=0
|
|
325
|
+
#==============================sum m 2:=========================
|
|
326
|
+
#sum n:
|
|
327
|
+
for n in range(len(df)): # sum over all grupes in molecule
|
|
328
|
+
group_no_3=df.loc[n,"Group_No"]
|
|
329
|
+
cap_theta_n_i=self.cap_theta_i(group_no_3,species_no)
|
|
330
|
+
|
|
331
|
+
# convert subgroup number to main group number for interaction parameters
|
|
332
|
+
mg3 = self.get_main_group(group_no_3)
|
|
333
|
+
if mg2==mg3:
|
|
334
|
+
a_nm=0
|
|
335
|
+
else:
|
|
336
|
+
try:
|
|
337
|
+
# have to do try except as ij is in same row as ji and does not have thei own row
|
|
338
|
+
try:
|
|
339
|
+
index=df_ip.index[(df_ip["i"] == mg3) & (df_ip["j"] == mg2)][0]
|
|
340
|
+
Anm,Bnm,Cnm=df_ip.loc[index,"Aij"],df_ip.loc[index,"Bij"],df_ip.loc[index,"Cij"]
|
|
341
|
+
except:
|
|
342
|
+
index=df_ip.index[(df_ip["i"] == mg2) & (df_ip["j"] == mg3)][0]
|
|
343
|
+
Anm,Bnm,Cnm=df_ip.loc[index,"Aji"],df_ip.loc[index,"Bji"],df_ip.loc[index,"Cji"]
|
|
344
|
+
except:
|
|
345
|
+
raise Exception(f"Interaction parameters not found for groups {mg2} and {mg3}") from None
|
|
346
|
+
#print(f"Interaction parameters not found for groups {mg2} and {mg3}")
|
|
347
|
+
#return None
|
|
348
|
+
a_nm=Anm+Bnm*T+Cnm*T**2
|
|
349
|
+
cap_psi_nm=np.exp(-a_nm/(T))
|
|
350
|
+
|
|
351
|
+
sum_n_1+=cap_theta_n_i*cap_psi_nm
|
|
352
|
+
|
|
353
|
+
sum_m_2+=cap_theta_m_i*cap_psi_km/sum_n_1
|
|
354
|
+
|
|
355
|
+
ln_kap_gamma_k_i=cap_q_k*(1-np.log(sum_m_1)-sum_m_2)
|
|
356
|
+
return ln_kap_gamma_k_i
|
|
357
|
+
|
|
358
|
+
#=======================================residual=============================================
|
|
359
|
+
def ln_gamma_res_i(self,species_no):
|
|
360
|
+
list_groups=self.list_groups
|
|
361
|
+
df=list_groups[species_no]
|
|
362
|
+
|
|
363
|
+
sum_k=0
|
|
364
|
+
|
|
365
|
+
for k in range(len(df)): # sum over all gropupes present in the molecule i
|
|
366
|
+
group_no=df.loc[k,"Group_No"]
|
|
367
|
+
|
|
368
|
+
index2 = df.index[df["Group_No"] == group_no][0]
|
|
369
|
+
nu_m_i=df.loc[index2, "number"]
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
ln_cap_gamma_k_i=self.ln_cap_gamma_i(group_no,species_no)
|
|
373
|
+
ln_cap_gamma_k=self.ln_cap_gamma(group_no)
|
|
374
|
+
sum_k+=nu_m_i*(ln_cap_gamma_k-ln_cap_gamma_k_i)
|
|
375
|
+
return sum_k
|
|
376
|
+
|
|
377
|
+
#========================================combinatorial====================================0
|
|
378
|
+
def cap_phi(self,species_no):
|
|
379
|
+
mol_lst=self.mol_lst
|
|
380
|
+
list_groups=self.list_groups
|
|
381
|
+
|
|
382
|
+
# r_i
|
|
383
|
+
sum_j=0
|
|
384
|
+
def r(species_no):
|
|
385
|
+
sum_k=0
|
|
386
|
+
df2=list_groups[species_no]
|
|
387
|
+
for k in range(len(df2)): #sum over number of groupes in molecule
|
|
388
|
+
#print(f"her1_{j}")
|
|
389
|
+
group_no=df2.loc[k,"Group_No"]
|
|
390
|
+
try:
|
|
391
|
+
index = df2.index[df2["Group_No"] == group_no][0]
|
|
392
|
+
except:
|
|
393
|
+
continue
|
|
394
|
+
nu_ik=df2.loc[index, "number"]
|
|
395
|
+
|
|
396
|
+
index2 = df_sv.index[df_sv["No."] == group_no][0]
|
|
397
|
+
|
|
398
|
+
cap_r_k=df_sv.loc[index2,"R"]
|
|
399
|
+
|
|
400
|
+
sum_k+=cap_r_k*nu_ik
|
|
401
|
+
|
|
402
|
+
r_i=sum_k
|
|
403
|
+
return r_i
|
|
404
|
+
|
|
405
|
+
for j in range(len(list_groups)): # sum over number of species in mixture
|
|
406
|
+
|
|
407
|
+
x_j=mol_lst[j]
|
|
408
|
+
#print(f"her1_{j}")
|
|
409
|
+
r_j=r(j)
|
|
410
|
+
#print(f"her2_{j}")
|
|
411
|
+
sum_j+=r_j*x_j
|
|
412
|
+
|
|
413
|
+
x_i=mol_lst[species_no]
|
|
414
|
+
r_i=r(species_no)
|
|
415
|
+
|
|
416
|
+
cap_phi_i=r_i*x_i/sum_j
|
|
417
|
+
|
|
418
|
+
return cap_phi_i
|
|
419
|
+
|
|
420
|
+
def phi_mark(self,species_no):
|
|
421
|
+
mol_lst=self.mol_lst
|
|
422
|
+
list_groups=self.list_groups
|
|
423
|
+
# r_i
|
|
424
|
+
sum_j=0
|
|
425
|
+
def r(species_no):
|
|
426
|
+
sum_k=0
|
|
427
|
+
df2=list_groups[species_no]
|
|
428
|
+
for k in range(len(df2)): #sum over number of groupes in molecule
|
|
429
|
+
|
|
430
|
+
group_no=df2.loc[k,"Group_No"]
|
|
431
|
+
index = df2.index[df2["Group_No"] == group_no][0]
|
|
432
|
+
nu_ik=df2.loc[index, "number"]
|
|
433
|
+
|
|
434
|
+
index2 = df_sv.index[df_sv["No."] == group_no][0]
|
|
435
|
+
cap_r_k=df_sv.loc[index2,"R"]
|
|
436
|
+
sum_k+=cap_r_k*nu_ik
|
|
437
|
+
|
|
438
|
+
r_i=sum_k
|
|
439
|
+
return r_i
|
|
440
|
+
|
|
441
|
+
for j in range(len(list_groups)): # sum over number of species in mixture
|
|
442
|
+
x_j=mol_lst[j]
|
|
443
|
+
r_j=r(j)
|
|
444
|
+
|
|
445
|
+
sum_j+=r_j**(3/4)*x_j
|
|
446
|
+
|
|
447
|
+
x_i=mol_lst[species_no]
|
|
448
|
+
r_i=r(species_no)
|
|
449
|
+
|
|
450
|
+
phi_i=r_i**(3/4)*x_i/sum_j
|
|
451
|
+
return phi_i
|
|
452
|
+
|
|
453
|
+
def q(self,species_no):
|
|
454
|
+
list_groups=self.list_groups
|
|
455
|
+
df2=list_groups[species_no]
|
|
456
|
+
sum_k=0
|
|
457
|
+
for k in range(len(df2)): #sum over number of groupes in molecule
|
|
458
|
+
|
|
459
|
+
group_no=df2.loc[k,"Group_No"]
|
|
460
|
+
index = df2.index[df2["Group_No"] == group_no][0]
|
|
461
|
+
nu_ik=df2.loc[index, "number"]
|
|
462
|
+
|
|
463
|
+
index2 = df_sv.index[df_sv["No."] == group_no][0]
|
|
464
|
+
cap_q_k=df_sv.loc[index2,"Q"]
|
|
465
|
+
sum_k+=cap_q_k*nu_ik
|
|
466
|
+
|
|
467
|
+
q_i=sum_k
|
|
468
|
+
|
|
469
|
+
return q_i
|
|
470
|
+
|
|
471
|
+
def theta(self,species_no):
|
|
472
|
+
list_groups=self.list_groups
|
|
473
|
+
mol_lst=self.mol_lst
|
|
474
|
+
# r_i
|
|
475
|
+
sum_j=0
|
|
476
|
+
|
|
477
|
+
for j in range(len(list_groups)): # sum over number of species in mixture
|
|
478
|
+
x_j=mol_lst[j]
|
|
479
|
+
q_j=self.q(j)
|
|
480
|
+
|
|
481
|
+
sum_j+=q_j*x_j
|
|
482
|
+
|
|
483
|
+
x_i=mol_lst[species_no]
|
|
484
|
+
q_i=self.q(species_no)
|
|
485
|
+
|
|
486
|
+
theta_i=q_i*x_i/sum_j
|
|
487
|
+
return theta_i
|
|
488
|
+
#gamma
|
|
489
|
+
def ln_gamma_comb_i(self,species_no):
|
|
490
|
+
mol_lst=self.mol_lst
|
|
491
|
+
z=10
|
|
492
|
+
#df=list_groups[species_no]
|
|
493
|
+
cap_phi_i = self.cap_phi(species_no) # volume fraction
|
|
494
|
+
phi_mark_i= self.phi_mark(species_no) # modified volume fraction
|
|
495
|
+
x_i= mol_lst[species_no] # molefraction
|
|
496
|
+
theta_i= self.theta(species_no) # surface area fraction
|
|
497
|
+
q_i=self.q(species_no)
|
|
498
|
+
|
|
499
|
+
ln_cap_gamma=np.log(phi_mark_i/x_i)+1-phi_mark_i/x_i-z/2*q_i*(np.log(cap_phi_i/theta_i)+1-cap_phi_i/theta_i)
|
|
500
|
+
return ln_cap_gamma
|
|
501
|
+
|
|
502
|
+
#=================================main=================================
|
|
503
|
+
def gamma_singular(self,species_no):
|
|
504
|
+
gamma_i=np.exp(self.ln_gamma_comb_i(species_no)+self.ln_gamma_res_i(species_no))
|
|
505
|
+
return gamma_i
|
|
506
|
+
|
|
507
|
+
def gamma_total(self):
|
|
508
|
+
mol_lst=self.mol_lst
|
|
509
|
+
lst=[]
|
|
510
|
+
for i in range(len(mol_lst)):
|
|
511
|
+
gamma_main_i=self.gamma_singular(i)
|
|
512
|
+
lst.append(gamma_main_i)
|
|
513
|
+
return lst
|
|
514
|
+
|
|
515
|
+
|
|
516
|
+
#=================================stability analysis=================================
|
|
517
|
+
def constrained_hessian(self):
|
|
518
|
+
h = 1e-5
|
|
519
|
+
R_gas = 8.314
|
|
520
|
+
N = len(self.mol_lst)
|
|
521
|
+
T = self.T
|
|
522
|
+
smiles_lst = self.smiles_lst
|
|
523
|
+
x_reduced0 = np.array(self.mol_lst[:-1]) # N-1 independent variables
|
|
524
|
+
|
|
525
|
+
def x_from_reduced(xr):
|
|
526
|
+
xN = 1.0 - np.sum(xr)
|
|
527
|
+
return np.concatenate([xr, [xN]])
|
|
528
|
+
|
|
529
|
+
def G_mix(xr):
|
|
530
|
+
x_vec = x_from_reduced(xr)
|
|
531
|
+
temp = Unifac_Dortmund(smiles_lst, list(x_vec), T)
|
|
532
|
+
gamma = np.array(temp.gamma_total())
|
|
533
|
+
|
|
534
|
+
ideal = R_gas * T * np.sum(x_vec * np.log(np.maximum(x_vec, 1e-300)))
|
|
535
|
+
excess = R_gas * T * np.sum(x_vec * np.log(gamma))
|
|
536
|
+
return ideal + excess
|
|
537
|
+
|
|
538
|
+
Nr = N - 1
|
|
539
|
+
H = np.zeros((Nr, Nr))
|
|
540
|
+
g0 = G_mix(x_reduced0)
|
|
541
|
+
|
|
542
|
+
for i in range(Nr):
|
|
543
|
+
xf = x_reduced0.copy(); xf[i] += h
|
|
544
|
+
xb = x_reduced0.copy(); xb[i] -= h
|
|
545
|
+
H[i, i] = (G_mix(xf) - 2*g0 + G_mix(xb)) / h**2
|
|
546
|
+
|
|
547
|
+
for j in range(i+1, Nr):
|
|
548
|
+
xpp = x_reduced0.copy(); xpp[i] += h; xpp[j] += h
|
|
549
|
+
xpm = x_reduced0.copy(); xpm[i] += h; xpm[j] -= h
|
|
550
|
+
xmp = x_reduced0.copy(); xmp[i] -= h; xmp[j] += h
|
|
551
|
+
xmm = x_reduced0.copy(); xmm[i] -= h; xmm[j] -= h
|
|
552
|
+
H[i, j] = (G_mix(xpp) - G_mix(xpm) - G_mix(xmp) + G_mix(xmm)) / (4*h**2)
|
|
553
|
+
H[j, i] = H[i, j]
|
|
554
|
+
|
|
555
|
+
return H
|
|
556
|
+
|
|
557
|
+
def is_stable_local(self):
|
|
558
|
+
hessian_m = self.constrained_hessian()
|
|
559
|
+
try:
|
|
560
|
+
np.linalg.cholesky(hessian_m) # checking for all eigenvalues positive
|
|
561
|
+
return True
|
|
562
|
+
except np.linalg.LinAlgError:
|
|
563
|
+
return False
|
|
564
|
+
|
|
565
|
+
def tpd(self, w):
|
|
566
|
+
w = np.array(w) * 1 / sum(w) # normalized molefraction
|
|
567
|
+
N = len(self.mol_lst)
|
|
568
|
+
z = self.mol_lst
|
|
569
|
+
gamma_z = np.array(self.gamma_total()) # gamma at feed composition
|
|
570
|
+
gamma_w = Unifac_Dortmund(self.smiles_lst, list(w), self.T).gamma_total()
|
|
571
|
+
|
|
572
|
+
sum_i = 0
|
|
573
|
+
for i in range(N):
|
|
574
|
+
w_i = w[i]
|
|
575
|
+
z_i = z[i]
|
|
576
|
+
sum_i += w_i * (np.log(w_i * gamma_w[i]) - np.log(z_i * gamma_z[i]))
|
|
577
|
+
return sum_i
|
|
578
|
+
|
|
579
|
+
def is_stable_global(self):
|
|
580
|
+
ss_iters = 10
|
|
581
|
+
N = len(self.mol_lst)
|
|
582
|
+
T = self.T
|
|
583
|
+
smiles_lst = self.smiles_lst
|
|
584
|
+
z = np.array(self.mol_lst)
|
|
585
|
+
gamma_z = np.array(self.gamma_total())
|
|
586
|
+
a = z * gamma_z # feed activities
|
|
587
|
+
|
|
588
|
+
def magnussen_guess():
|
|
589
|
+
if a.max() > 1:
|
|
590
|
+
return None, None
|
|
591
|
+
|
|
592
|
+
candidates, F_values = [], []
|
|
593
|
+
|
|
594
|
+
for j in range(N):
|
|
595
|
+
# infinite-dilution-like gammas: composition ~pure in component j
|
|
596
|
+
x_pure = np.full(N, 1e-6)
|
|
597
|
+
x_pure[j] = 1 - 1e-6 * (N - 1)
|
|
598
|
+
gamma_inf_j = np.array(Unifac_Dortmund(smiles_lst, list(x_pure), T).gamma_total())
|
|
599
|
+
|
|
600
|
+
x = a / gamma_inf_j
|
|
601
|
+
x = x / x.sum()
|
|
602
|
+
|
|
603
|
+
for _ in range(ss_iters):
|
|
604
|
+
gamma_i = np.array(Unifac_Dortmund(smiles_lst, list(x), T).gamma_total())
|
|
605
|
+
x = a / gamma_i
|
|
606
|
+
x = x / x.sum()
|
|
607
|
+
|
|
608
|
+
gamma_i = np.array(Unifac_Dortmund(smiles_lst, list(x), T).gamma_total())
|
|
609
|
+
S_j = np.sum(x * (np.log(x * gamma_i) - np.log(a)))
|
|
610
|
+
G_j = np.sum((x - z) * (np.log(x * gamma_i) - np.log(a)))
|
|
611
|
+
F_j = S_j if S_j < 0 else S_j - 0.5 * G_j
|
|
612
|
+
|
|
613
|
+
candidates.append(x)
|
|
614
|
+
F_values.append(F_j)
|
|
615
|
+
|
|
616
|
+
j_star = int(np.argmin(F_values))
|
|
617
|
+
return candidates[j_star], candidates
|
|
618
|
+
|
|
619
|
+
best_guess, all_guesses = magnussen_guess()
|
|
620
|
+
if best_guess is None:
|
|
621
|
+
print("a_max > 1 -> unstable by Magnussen shortcut")
|
|
622
|
+
return False
|
|
623
|
+
|
|
624
|
+
# unconstrained reparametrization: u = ln(w), normalized inside f
|
|
625
|
+
def tpd_u(u):
|
|
626
|
+
w = np.exp(u)
|
|
627
|
+
w = w / w.sum()
|
|
628
|
+
return self.tpd(w)
|
|
629
|
+
|
|
630
|
+
best = None
|
|
631
|
+
for x0 in all_guesses: # multistart over all N Magnussen trial phases
|
|
632
|
+
u0 = np.log(np.clip(x0, 1e-10, None))
|
|
633
|
+
res = minimize(tpd_u, u0, method='BFGS',
|
|
634
|
+
options={'gtol': 1e-8, 'maxiter': 200})
|
|
635
|
+
if best is None or res.fun < best:
|
|
636
|
+
best = res.fun
|
|
637
|
+
|
|
638
|
+
print(f"min TPD={round(best,5)}")
|
|
639
|
+
return bool(round(best, 8) >= 0)
|
|
640
|
+
|
|
641
|
+
def is_stable(self):
|
|
642
|
+
if self.is_stable_local():
|
|
643
|
+
return self.is_stable_global()
|
|
644
|
+
else:
|
|
645
|
+
return False
|
|
646
|
+
|
|
647
|
+
|
|
648
|
+
|
|
649
|
+
|
|
650
|
+
|
|
651
|
+
|
|
652
|
+
|
|
653
|
+
|
|
654
|
+
|
|
655
|
+
|
|
656
|
+
|
|
657
|
+
|
|
658
|
+
|
|
659
|
+
|
|
660
|
+
|
|
661
|
+
|
|
662
|
+
|
|
663
|
+
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: unifac
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A Python implementation of the UNIFAC-Dortmund group contribution method
|
|
5
|
+
Author-email: Emil Kongsbach <emil.cheme@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/Emil-Kongsbach/UNIFAC
|
|
8
|
+
Requires-Python: >=3.9
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Requires-Dist: numpy
|
|
12
|
+
Requires-Dist: pandas
|
|
13
|
+
Requires-Dist: ugropy
|
|
14
|
+
Requires-Dist: openpyxl
|
|
15
|
+
Requires-Dist: scipy
|
|
16
|
+
Dynamic: license-file
|
|
17
|
+
|
|
18
|
+
# UNIFAC
|
|
19
|
+
An implementation of the group contribution method of the Dortmund modification of UNIFAC. Calculates the activity coeficients of species in a mixture based on functional group parameters
|
|
20
|
+
|
|
21
|
+
"""
|
|
22
|
+
from unifac import Unifac_Dortmund
|
|
23
|
+
|
|
24
|
+
# Set system parameters
|
|
25
|
+
smiles_lst = ["CCO", "CC(=O)C","CC=CC"] # NA
|
|
26
|
+
x_lst=[1/3, 1/3, 1/3] # NA
|
|
27
|
+
T=298 # K
|
|
28
|
+
|
|
29
|
+
mixture=Unifac_Dortmund(smiles_lst,x_lst,T)
|
|
30
|
+
|
|
31
|
+
# Returns activity coefficient of component 0 in the mixture, according to the order in which the components were passed.
|
|
32
|
+
print(mixture.gamma_singular(0))
|
|
33
|
+
|
|
34
|
+
# Returns activity coefficient of all components in the mixture according to the order in which componenets were passed
|
|
35
|
+
print(mixture.gamma_total())
|
|
36
|
+
|
|
37
|
+
# stability analysis
|
|
38
|
+
|
|
39
|
+
mixture.is_stable_local() # computes local stability (spinodal)
|
|
40
|
+
|
|
41
|
+
mixture.is_stable_global() # computes global stability (binodal)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
mixture.is_stable() # computes first local stability (cheap), and only if that is True it proceeds to calculate global stability (expensive)
|
|
45
|
+
|
|
46
|
+
"""
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
src/unifac/__init__.py
|
|
5
|
+
src/unifac/groupe_surfaces_and_volumes.xlsx
|
|
6
|
+
src/unifac/interaction_parameters.xlsx
|
|
7
|
+
src/unifac/unifac_dortmund.py
|
|
8
|
+
src/unifac.egg-info/PKG-INFO
|
|
9
|
+
src/unifac.egg-info/SOURCES.txt
|
|
10
|
+
src/unifac.egg-info/dependency_links.txt
|
|
11
|
+
src/unifac.egg-info/requires.txt
|
|
12
|
+
src/unifac.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
unifac
|