cosmopharm 0.0.21__py3-none-any.whl → 0.0.23__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.
- cosmopharm/actmodels/actmodel.py +44 -20
- cosmopharm/actmodels/cosmo.py +40 -23
- cosmopharm/equilibrium/lle.py +60 -23
- cosmopharm/equilibrium/sle.py +71 -38
- cosmopharm-0.0.23.dist-info/METADATA +38 -0
- {cosmopharm-0.0.21.dist-info → cosmopharm-0.0.23.dist-info}/RECORD +9 -9
- {cosmopharm-0.0.21.dist-info → cosmopharm-0.0.23.dist-info}/WHEEL +1 -1
- cosmopharm-0.0.21.dist-info/METADATA +0 -118
- {cosmopharm-0.0.21.dist-info → cosmopharm-0.0.23.dist-info}/LICENSE +0 -0
- {cosmopharm-0.0.21.dist-info → cosmopharm-0.0.23.dist-info}/top_level.txt +0 -0
cosmopharm/actmodels/actmodel.py
CHANGED
@@ -1,26 +1,18 @@
|
|
1
1
|
import numpy as np
|
2
2
|
import numbers
|
3
|
+
from numpy.typing import NDArray
|
4
|
+
from typing import List, Literal
|
3
5
|
|
6
|
+
from ..components import Component
|
4
7
|
from ..utils.convert import convert
|
5
8
|
|
6
9
|
class ActModel:
|
7
10
|
|
8
|
-
def __init__(self, components:
|
9
|
-
self.
|
11
|
+
def __init__(self, components: List[Component]):
|
12
|
+
self.mixture = components
|
10
13
|
|
11
14
|
def lngamma(self, T, x):
|
12
|
-
|
13
|
-
|
14
|
-
def dlngamma(self, T, x):
|
15
|
-
# Only binary case
|
16
|
-
def f(x1):
|
17
|
-
x = np.array([x1, 1-x1])
|
18
|
-
return self.lngamma(T, x)#[0]
|
19
|
-
h, x = 0.0001, x[0]
|
20
|
-
dy = (f(x+h)-f(x-h))/(2*h)
|
21
|
-
# Revert direction of dy2_dx2 --> dy2_dx1
|
22
|
-
dy[1] = dy[1][::-1]
|
23
|
-
return f(x), dy
|
15
|
+
raise NotImplementedError("lngamma() hasn't been implemented yet.")
|
24
16
|
|
25
17
|
def activity(self, T, x):
|
26
18
|
act = np.log(x) + self.lngamma(T, x)
|
@@ -28,6 +20,7 @@ class ActModel:
|
|
28
20
|
return act
|
29
21
|
|
30
22
|
def gmix(self, T, x):
|
23
|
+
is_scalar = np.isscalar(x)
|
31
24
|
# Convert input as needed
|
32
25
|
x = self._convert_input(x)
|
33
26
|
# Create mask to identify columns that don't contain 0 or 1
|
@@ -38,10 +31,10 @@ class ActModel:
|
|
38
31
|
_gmix = _x * (np.log(_x) + self.lngamma(T, _x))
|
39
32
|
_gmix = np.sum(_gmix, axis=0)
|
40
33
|
# Initialize gmix array with zeros
|
41
|
-
gmix = np.zeros(x.shape[1])
|
34
|
+
gmix = np.zeros(1 if x.ndim==1 else x.shape[1])
|
42
35
|
# Fill gmix with calculated values where the mask is True
|
43
36
|
gmix[mask] = _gmix
|
44
|
-
return gmix
|
37
|
+
return gmix[0] if is_scalar else gmix
|
45
38
|
|
46
39
|
|
47
40
|
# =============================================================================
|
@@ -74,10 +67,41 @@ class ActModel:
|
|
74
67
|
"""Converts input to a 1-dim ndarray if it's a number or 0-dim ndarray."""
|
75
68
|
if isinstance(x, numbers.Number) or (isinstance(x, np.ndarray) and x.ndim == 0):
|
76
69
|
return np.array([float(x), 1 - float(x)])
|
77
|
-
elif isinstance(x, np.ndarray) and x.ndim == 1 and len(x) != len(self.
|
70
|
+
elif isinstance(x, np.ndarray) and x.ndim == 1 and len(x) != len(self.mixture):
|
78
71
|
return np.array([x, 1 - x])
|
79
72
|
return x
|
80
73
|
|
81
|
-
def _convert(self,
|
82
|
-
|
83
|
-
|
74
|
+
def _convert(self,
|
75
|
+
x : NDArray[np.float64],
|
76
|
+
to : Literal['weight', 'mole'] ='weight'
|
77
|
+
) -> NDArray[np.float64]:
|
78
|
+
"""
|
79
|
+
Convert the fraction of a binary mixture between mole fraction and weight fraction.
|
80
|
+
|
81
|
+
This method is designed for internal use with binary mixtures, where the mixture is defined by two components.
|
82
|
+
It uses the 'convert' function to perform the conversion by creating an array with the fractions of both
|
83
|
+
components and the molecular weights from the mixture's attributes.
|
84
|
+
|
85
|
+
Parameters:
|
86
|
+
x (NDArray[np.float64]): The mole or weight fraction of the first component of the mixture.
|
87
|
+
If converting 'to' weight, 'x' represents mole fractions; if converting 'to' mole,
|
88
|
+
'x' represents weight fractions. This should be a single value or a 1D array of values.
|
89
|
+
to (Literal['weight', 'mole'], optional): The target type for the conversion. Defaults to 'weight'.
|
90
|
+
Use 'weight' to convert mole fractions to weight fractions,
|
91
|
+
and 'mole' to convert weight fractions to mole fractions.
|
92
|
+
|
93
|
+
Returns:
|
94
|
+
NDArray[np.float64]: The converted fraction(s) of the first component in the same shape as 'x'.
|
95
|
+
If 'x' is a single value, the return will be a single converted value;
|
96
|
+
if 'x' is a 1D array, the return will be a 1D array of converted values.
|
97
|
+
|
98
|
+
Example:
|
99
|
+
>>> mixture = Mixture(components=[component1, component2], Mw=np.array([18.01528, 46.06844]))
|
100
|
+
>>> sle = SLE(mix=mixture)
|
101
|
+
>>> x_mole_fraction = np.array([0.4]) # Mole fraction of the first component
|
102
|
+
>>> x_weight_fraction = sle._convert(x_mole_fraction, to='weight')
|
103
|
+
>>> print(x_weight_fraction)
|
104
|
+
array([0.01373165])
|
105
|
+
"""
|
106
|
+
Mw = np.array([c.Mw for c in self.mixture])
|
107
|
+
return convert(x=np.array([x, 1-x], dtype=np.float64), Mw=Mw, to=to)[0]
|
cosmopharm/actmodels/cosmo.py
CHANGED
@@ -1,17 +1,25 @@
|
|
1
|
+
|
1
2
|
import numpy as np
|
3
|
+
import cCOSMO
|
4
|
+
|
5
|
+
from typing import List, Union, Literal
|
2
6
|
from .actmodel import ActModel
|
7
|
+
from ..components import Component
|
3
8
|
|
4
9
|
class COSMOSAC(ActModel):
|
5
|
-
def __init__(self,
|
6
|
-
|
10
|
+
def __init__(self,
|
11
|
+
COSMO: Union[cCOSMO.COSMO1, cCOSMO.COSMO3],
|
12
|
+
mixture: List[Component],
|
13
|
+
combinatorial: Union[Literal['sg', 'fv'], bool] = 'sg',
|
14
|
+
dispersion: bool = False,
|
15
|
+
) -> None:
|
7
16
|
self.COSMO = COSMO
|
8
|
-
self.
|
17
|
+
self.mixture = mixture
|
9
18
|
# Flexible assignment of 'get_lngamma_comb' and 'get_lngamma_dsp'
|
10
|
-
# that changes dynamically if the values for '
|
11
|
-
#
|
12
|
-
self._free_volume = free_volume
|
13
|
-
self._dispersion = dispersion
|
19
|
+
# that changes dynamically if the values for 'combinatorial' or
|
20
|
+
# 'dispersion' are changed after initialization of an instance.
|
14
21
|
self._combinatorial = combinatorial
|
22
|
+
self._dispersion = dispersion
|
15
23
|
|
16
24
|
@ActModel.vectorize
|
17
25
|
def lngamma(self, T, x):
|
@@ -54,8 +62,8 @@ class COSMOSAC(ActModel):
|
|
54
62
|
(can replace ln_gamma_comb of normal COSMO-SAC) - Kuo2013
|
55
63
|
x, v_298, v_hc are 1D arrays (number of elements = number of components)
|
56
64
|
"""
|
57
|
-
v_298 = np.array([
|
58
|
-
v_hc = np.array([
|
65
|
+
v_298 = np.array([comp.v_298 for comp in self.mixture])
|
66
|
+
v_hc = np.array([comp.v_hc for comp in self.mixture])
|
59
67
|
vf = v_298-v_hc
|
60
68
|
sum_vf = np.sum(x*vf)
|
61
69
|
phix = vf/sum_vf
|
@@ -68,12 +76,12 @@ class COSMOSAC(ActModel):
|
|
68
76
|
return self.COSMO.get_lngamma_resid(T, x)
|
69
77
|
|
70
78
|
def get_lngamma_comb(self, x):
|
71
|
-
if
|
79
|
+
if self._combinatorial is False:
|
72
80
|
return np.zeros(len(x))
|
73
|
-
elif self.
|
74
|
-
return self.get_lngamma_fv(x)
|
75
|
-
else:
|
81
|
+
elif self._combinatorial.lower() == 'sg':
|
76
82
|
return self.get_lngamma_sg(x)
|
83
|
+
elif self._combinatorial.lower() == 'fv':
|
84
|
+
return self.get_lngamma_fv(x)
|
77
85
|
|
78
86
|
def get_lngamma_disp(self, x):
|
79
87
|
if self._dispersion:
|
@@ -81,14 +89,6 @@ class COSMOSAC(ActModel):
|
|
81
89
|
else:
|
82
90
|
return np.zeros(len(x))
|
83
91
|
|
84
|
-
@property
|
85
|
-
def free_volume(self):
|
86
|
-
return self._free_volume
|
87
|
-
|
88
|
-
@free_volume.setter
|
89
|
-
def free_volume(self, value):
|
90
|
-
self._free_volume = value
|
91
|
-
|
92
92
|
@property
|
93
93
|
def dispersion(self):
|
94
94
|
return self._dispersion
|
@@ -102,5 +102,22 @@ class COSMOSAC(ActModel):
|
|
102
102
|
return self._combinatorial
|
103
103
|
|
104
104
|
@combinatorial.setter
|
105
|
-
def combinatorial(self, value):
|
106
|
-
|
105
|
+
def combinatorial(self, value: Union[str, bool]):
|
106
|
+
is_valid_string = isinstance(value, str) and value.lower() in ('sg', 'fv')
|
107
|
+
is_False = value is False
|
108
|
+
if is_valid_string or is_False:
|
109
|
+
self._combinatorial = value
|
110
|
+
else:
|
111
|
+
msg = "Invalid value for combinatorial term. Please choose 'sg', 'fv', or set to False."
|
112
|
+
raise ValueError(msg)
|
113
|
+
|
114
|
+
# =============================================================================
|
115
|
+
# Auxilliary functions
|
116
|
+
# =============================================================================
|
117
|
+
def configuration(self,
|
118
|
+
comb: Union[Literal['sg', 'fv'], bool] = 'sg',
|
119
|
+
dsp: bool = False, **kwargs
|
120
|
+
):
|
121
|
+
""" Convenience function to quickly configure COSMO parameters """
|
122
|
+
self._combinatorial = comb
|
123
|
+
self._dispersion = dsp
|
cosmopharm/equilibrium/lle.py
CHANGED
@@ -1,34 +1,48 @@
|
|
1
1
|
import numpy as np
|
2
2
|
import pandas as pd
|
3
|
-
from scipy.optimize import least_squares
|
3
|
+
from scipy.optimize import least_squares, root
|
4
|
+
from typing import Union, Optional, Type, List
|
4
5
|
|
5
|
-
from ..
|
6
|
+
from ..components import Component
|
7
|
+
from ..actmodels import ActModel
|
8
|
+
from ..utils.spacing import spacing
|
6
9
|
from ..utils.lle_scanner import estimate_lle_from_gmix
|
7
10
|
|
8
11
|
|
9
12
|
class LLE:
|
10
|
-
def __init__(self,
|
11
|
-
|
12
|
-
|
13
|
+
def __init__(self,
|
14
|
+
actmodel: Union[ActModel, Type[ActModel]],
|
15
|
+
mixture: Optional[List[Component]] = None) -> None:
|
16
|
+
self.actmodel = actmodel
|
17
|
+
self.mixture = mixture
|
18
|
+
self._validate_arguments()
|
13
19
|
|
14
20
|
def fobj_binodal(self, x1, T):
|
15
21
|
# Equilibrium: Isoactivity criterion (aL1 - aL2 = 0)
|
16
22
|
x = np.array([x1, 1-x1])
|
17
|
-
activity = self.
|
23
|
+
activity = self.actmodel.activity(T, x)
|
18
24
|
equilibrium = np.diff(activity, axis=1)
|
19
25
|
return equilibrium.ravel() # reshape from (2,1) --> (2,)
|
20
26
|
|
21
27
|
def fobj_spinodal(self, x1):
|
22
28
|
T = 0
|
23
29
|
x = np.array([x1, 1-x1])
|
24
|
-
return self.
|
30
|
+
return self.actmodel.thermofac(T, x)
|
25
31
|
|
26
|
-
def binodal(self, T, x0=None):
|
32
|
+
def binodal(self, T, x0=None, solver='least_squares'):
|
27
33
|
if x0 is None:
|
28
34
|
x0 = [0.1, 0.999] # 1_N2_Ethan
|
29
|
-
|
30
|
-
|
31
|
-
|
35
|
+
|
36
|
+
if solver == 'least_squares':
|
37
|
+
kwargs = dict(bounds=(0,1), ftol=1e-15, xtol=1e-15)
|
38
|
+
res = least_squares(self.fobj_binodal, x0, args=(T,), **kwargs)
|
39
|
+
# print(res.nfev)
|
40
|
+
return res.x, res.nfev
|
41
|
+
else:
|
42
|
+
kwargs = dict(method='krylov', options={'maxiter': 5})
|
43
|
+
res = root(self.fobj_binodal, x0, args=(T,), **kwargs)
|
44
|
+
# print(res.nit)
|
45
|
+
return res.x, 30
|
32
46
|
|
33
47
|
def spinodal(self, x0=None):
|
34
48
|
if x0 is None:
|
@@ -36,26 +50,28 @@ class LLE:
|
|
36
50
|
return least_squares(self.fobj_spinodal, x0).x
|
37
51
|
|
38
52
|
# =============================================================================
|
39
|
-
#
|
53
|
+
# TODO: (1) Add some "approx_initial_values" function based on gmix
|
54
|
+
# TODO: (2) Overall improve this code to match the SLE code
|
40
55
|
# =============================================================================
|
41
56
|
def approx_init_x0(self, T):
|
42
57
|
x1 = spacing(0,1,51,'poly',n=3)
|
43
|
-
gmix = self.
|
58
|
+
gmix = self.actmodel.gmix(T, x1)
|
44
59
|
xL, xR, yL, yR = estimate_lle_from_gmix(x1, gmix, rough=True)
|
45
60
|
return xL, xR
|
46
61
|
|
47
|
-
def solve_lle(self, T, x0, info=True):
|
48
|
-
binodal_x = self.binodal(T, x0)
|
49
|
-
binodal_w = self.
|
62
|
+
def solve_lle(self, T, x0, solver='least_squares', info=True):
|
63
|
+
binodal_x, nfev = self.binodal(T, x0, solver)
|
64
|
+
binodal_w = self.actmodel._convert(binodal_x)
|
50
65
|
formatted_w_binodal = [f"wL{i+1}={value:.4f}" for i, value in enumerate(binodal_w)]
|
51
66
|
formatted_x_binodal = [f"xL{i+1}={value:.6f}" for i, value in enumerate(binodal_x)]
|
52
67
|
msg = ('LLE: ', f"{T=:.2f}", *formatted_w_binodal, *formatted_x_binodal)
|
53
68
|
if info:
|
54
69
|
print(*msg)
|
55
|
-
return binodal_x, binodal_w
|
56
|
-
return binodal_x, binodal_w, msg
|
70
|
+
return binodal_x, binodal_w, nfev
|
71
|
+
return binodal_x, binodal_w, nfev, msg
|
57
72
|
|
58
|
-
def miscibility(self, T, x0=None, max_gap=0.1, max_T=500, dT=25):
|
73
|
+
def miscibility(self, T, x0=None, max_gap=0.1, max_T=500, dT=25, exponent=2):
|
74
|
+
""" Calculate miscibility """
|
59
75
|
print()
|
60
76
|
print("Calculating LLE...")
|
61
77
|
res = []
|
@@ -63,27 +79,48 @@ class LLE:
|
|
63
79
|
if x0 is None:
|
64
80
|
print("...searching for suitable initial value...")
|
65
81
|
x0 = self.approx_init_x0(T)
|
66
|
-
binodal_x, binodal_w, msg = self.solve_lle(T, x0, info=False)
|
82
|
+
binodal_x, binodal_w, nfev, msg = self.solve_lle(T, x0, info=False)
|
67
83
|
|
68
84
|
# Check if initial guess is reasonalble - otherwise increase T
|
69
85
|
while binodal_x[0] < x0[0] and T <= max_T:
|
70
86
|
print('LLE: ', f"{T=:.2f}", "...no feasbible initial value found.")
|
71
87
|
T += 10 # Increase T by 10
|
72
88
|
x0 = self.approx_init_x0(T)
|
73
|
-
binodal_x, binodal_w, msg = self.solve_lle(T, x0, info=False)
|
89
|
+
binodal_x, binodal_w, nfev, msg = self.solve_lle(T, x0, info=False)
|
74
90
|
print("Suitable initial value found! Proceed with calculating LLE...")
|
75
91
|
print(*msg)
|
76
92
|
gap = np.diff(binodal_w)[0]
|
77
93
|
res.append((T, *binodal_w, *binodal_x))
|
78
94
|
|
79
|
-
exponent = 2.1
|
80
95
|
while gap > max_gap and T <= max_T:
|
96
|
+
solver = 'least_squares' if nfev <= 30 else 'root'
|
97
|
+
solver = 'least_squares'
|
98
|
+
# print(solver)
|
81
99
|
T += dT * gap**exponent
|
82
100
|
x0 = binodal_x
|
83
|
-
binodal_x, binodal_w = self.solve_lle(T, x0)
|
101
|
+
binodal_x, binodal_w, nfev = self.solve_lle(T, x0, solver)
|
84
102
|
gap = np.diff(binodal_w)[0]
|
85
103
|
res.append((T, *binodal_w, *binodal_x))
|
86
104
|
|
87
105
|
columns = ['T', 'wL1', 'wL2', 'xL1', 'xL2']
|
88
106
|
res = pd.DataFrame(res, columns=columns)
|
89
107
|
return res
|
108
|
+
|
109
|
+
# =============================================================================
|
110
|
+
# AUXILLIARY FUNCTIONS
|
111
|
+
# =============================================================================
|
112
|
+
def _validate_arguments(self):
|
113
|
+
"""Validate the arguments for the LLE class."""
|
114
|
+
# TODO: Insert case where both actmodel and mixture are provided (check if acmodel.mixture == mixture, if not raise warning)
|
115
|
+
if isinstance(self.actmodel, ActModel):
|
116
|
+
# If actmodel is an instance of ActModel
|
117
|
+
self.mixture: List[Component] = self.mixture or self.actmodel.mixture
|
118
|
+
elif isinstance(self.actmodel, type) and issubclass(self.actmodel, ActModel):
|
119
|
+
# If actmodel is a class (subclass of ActModel)
|
120
|
+
if self.mixture is None:
|
121
|
+
raise ValueError("Please provide a valid mixture:Mixture.")
|
122
|
+
self.actmodel: ActModel = self.actmodel(self.mixture)
|
123
|
+
else:
|
124
|
+
# If actmodel is neither an instance nor a subclass of ActModel
|
125
|
+
err = "'actmodel' must be an instance or a subclass of 'ActModel'"
|
126
|
+
raise ValueError(err)
|
cosmopharm/equilibrium/sle.py
CHANGED
@@ -1,35 +1,47 @@
|
|
1
|
-
import pandas as pd
|
2
1
|
import numpy as np
|
2
|
+
import pandas as pd
|
3
3
|
from scipy.optimize import fsolve, root
|
4
|
-
from typing import
|
4
|
+
from numpy.typing import NDArray
|
5
|
+
from typing import Literal, Optional, Type, Union, List, Tuple, Generator, Dict
|
5
6
|
|
6
7
|
from ..components import Component
|
8
|
+
from ..actmodels import ActModel
|
7
9
|
from ..utils.spacing import spacing
|
8
10
|
|
11
|
+
NumericOrFrame = Union[float, List[float], Tuple[float, ...], NDArray[np.float64], pd.DataFrame]
|
12
|
+
|
9
13
|
class SLE:
|
10
|
-
def __init__(self,
|
11
|
-
|
12
|
-
|
13
|
-
self.
|
14
|
+
def __init__(self,
|
15
|
+
actmodel: Union[ActModel, Type[ActModel]],
|
16
|
+
mixture: Optional[List[Component]] = None) -> None:
|
17
|
+
self.actmodel = actmodel
|
18
|
+
self.mixture = mixture
|
19
|
+
self._validate_arguments()
|
20
|
+
# Assign 'solute' and 'solvent' based on order in 'mixture'
|
21
|
+
# Default assignment can be changed in e.g. 'solubility()'
|
22
|
+
self.solute, self.solvent = self.mixture
|
14
23
|
|
15
24
|
def solubility(self,
|
16
|
-
solute: Component = None,
|
17
|
-
|
25
|
+
solute: Optional[Component] = None,
|
26
|
+
solvent: Optional[Component] = None,
|
18
27
|
vary: Literal['T', 'w', 'auto'] = 'auto',
|
19
|
-
|
28
|
+
mix_type: Literal['ideal', 'real'] = 'real',
|
29
|
+
args: Optional[NumericOrFrame] = None,
|
30
|
+
init: Optional[NumericOrFrame] = None,
|
20
31
|
solver: Literal['root', 'fsolve'] = 'root',
|
21
|
-
show_progress=False):
|
32
|
+
show_progress=False, **kwargs):
|
22
33
|
''' Calculate solubility curve of solute in solvent.'''
|
23
34
|
self.solute = solute or self.solute
|
24
35
|
self.solvent = solvent or self.solvent
|
25
|
-
self.vary, self.mix_type = vary,
|
36
|
+
self.vary, self.mix_type = vary, mix_type
|
26
37
|
self.show_progress = show_progress
|
38
|
+
self.config = getattr(self.actmodel, 'config', self.mix_type)
|
27
39
|
if self.vary == 'auto':
|
28
40
|
gen = self.auto_solve(solver)
|
29
41
|
else:
|
30
42
|
self._vary = self.vary
|
31
|
-
|
32
|
-
|
43
|
+
args = self.set_args(args)
|
44
|
+
init = self.set_x0(init)
|
33
45
|
gen = self.solve_sle(args, init, solver)
|
34
46
|
res = [k for k in gen]
|
35
47
|
res = pd.DataFrame(res, columns=['T', 'x', 'vary', 'w'])
|
@@ -40,33 +52,40 @@ class SLE:
|
|
40
52
|
# =============================================================================
|
41
53
|
# MATHEMATICS
|
42
54
|
# =============================================================================
|
43
|
-
def solve_sle(self, args, init,
|
44
|
-
|
55
|
+
def solve_sle(self, args: NDArray[np.float64], init: NDArray[np.float64],
|
56
|
+
solver: Literal['root', 'fsolve'] = 'root'
|
57
|
+
) -> Generator[Dict[str, Union[float, str]], None, None]:
|
58
|
+
# Check compatibility of the "init" values
|
59
|
+
is_iterable = init.size > 1
|
60
|
+
if is_iterable and not init.size == args.size:
|
61
|
+
msg = 'The length of "init" must be the same as "args".'
|
62
|
+
raise ValueError(msg)
|
63
|
+
x0 = init
|
64
|
+
# Setup solver and handle pure component case
|
45
65
|
key, lock = ['T', 'x'] if self._vary == 'T' else ['x', 'T']
|
46
66
|
solve = self.set_solver(solver=solver)
|
47
|
-
x0 = init
|
48
67
|
args, pure_component = self._handle_pure_component(args)
|
49
68
|
if pure_component: # no need to calculate pure component
|
50
69
|
yield pure_component
|
51
|
-
|
52
70
|
for i, arg in enumerate(args):
|
53
71
|
x0 = init[i] if is_iterable else x0
|
54
72
|
out = float(solve(x0, arg))
|
55
73
|
x0 = out if not is_iterable else x0
|
56
74
|
res = {key: arg, lock: out, 'vary': self._vary}
|
57
|
-
res['w'] = self.
|
75
|
+
res['w'] = self.actmodel._convert(res['x'])[0]
|
58
76
|
text = (f"T={res['T']:.2f}", f"w={res['w']:.4f}", f"x={res['x']:.4f}")
|
59
77
|
if self.show_progress:
|
60
|
-
print(f'SLE ({self.
|
78
|
+
print(f'SLE ({self.config}): ', *text)
|
61
79
|
yield res
|
62
80
|
|
63
81
|
def auto_solve(self, solver: Literal['root', 'fsolve'] = 'root'):
|
64
82
|
if self.show_progress:
|
65
83
|
print()
|
66
|
-
print(f"Calculating SLE ({self.
|
84
|
+
print(f"Calculating SLE ({self.config})...")
|
67
85
|
# Start with varying 'w' until dTdw > THRESHOLD
|
68
86
|
self._vary = 'w'
|
69
|
-
args
|
87
|
+
args = self.set_args()
|
88
|
+
x0 = self.set_x0()
|
70
89
|
gen = self.solve_sle(args, x0, solver)
|
71
90
|
previous = None
|
72
91
|
for i, current in enumerate(gen):
|
@@ -77,10 +96,7 @@ class SLE:
|
|
77
96
|
# Switch to varying 'T'
|
78
97
|
self._vary = 'T'
|
79
98
|
T0, x0 = current['T'], current['x']
|
80
|
-
|
81
|
-
# T1 = previous['T']; dT = T0 - T1
|
82
|
-
# T0 += dT if abs(dT) < 5 else np.sign(dT) * 5
|
83
|
-
args = self.set_args(xmax=T0)[1:] # exclude initial point (redundant)
|
99
|
+
args = self.set_args(xmax=T0)[1:] # exclude initial point
|
84
100
|
gen = self.solve_sle(args, x0)
|
85
101
|
yield from gen
|
86
102
|
|
@@ -92,7 +108,7 @@ class SLE:
|
|
92
108
|
return np.exp(-self.gibbs_fusion(T))
|
93
109
|
|
94
110
|
def real_mix(self, T, x):
|
95
|
-
lngamma = self.
|
111
|
+
lngamma = self.actmodel.lngamma(T, x)[0]
|
96
112
|
return np.log(x) + lngamma + self.gibbs_fusion(T)
|
97
113
|
|
98
114
|
# Gibbs energy of fusion, i.e., the right-hand side of the solubility equation:
|
@@ -115,12 +131,12 @@ class SLE:
|
|
115
131
|
# =============================================================================
|
116
132
|
# HELPER FUNCTIONS
|
117
133
|
# =============================================================================
|
118
|
-
def
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
134
|
+
def set_args(self,
|
135
|
+
args: Optional[NumericOrFrame] = None,
|
136
|
+
xmin: Optional[float] = None,
|
137
|
+
xmax: Optional[float] = None,
|
138
|
+
dx: Optional[float] = None
|
139
|
+
) -> NDArray[np.float64]:
|
124
140
|
vary = self._vary
|
125
141
|
# Determine argument values based on input data or generate
|
126
142
|
# them based on range and type
|
@@ -132,7 +148,7 @@ class SLE:
|
|
132
148
|
ma = defaults[vary]['max'] if xmax is None else xmax
|
133
149
|
dx = defaults[vary]['step'] if dx is None else dx
|
134
150
|
|
135
|
-
if
|
151
|
+
if args is None:
|
136
152
|
if self.vary != 'auto': # auto_vary == False
|
137
153
|
args = np.arange(ma, mi-dx, -dx)
|
138
154
|
args[-1] = np.maximum(args[-1], mi)
|
@@ -145,17 +161,18 @@ class SLE:
|
|
145
161
|
else: # vary == 'w'
|
146
162
|
num = 16 if self.mix_type == 'ideal' else 21
|
147
163
|
args = spacing(ma, mi, num, 'quadratic')
|
148
|
-
|
149
|
-
|
150
|
-
return args
|
164
|
+
args = np.asarray(args)
|
165
|
+
args = args if vary != 'w' else self.actmodel._convert(args, to='mole')
|
166
|
+
return args
|
151
167
|
|
152
|
-
def set_x0(self, init=None):
|
168
|
+
def set_x0(self, init: Optional[NumericOrFrame] = None) -> NDArray[np.float64]:
|
153
169
|
vary = self._vary
|
154
170
|
# Set up initial values based on the type of variable ('T' or 'w')
|
155
171
|
if vary == 'T':
|
156
|
-
x0 = 1. if init is None else self.
|
172
|
+
x0 = 1. if init is None else self.actmodel._convert(init, to='mole')
|
157
173
|
else: # vary == 'w'
|
158
174
|
x0 = self.solute.T_fus if init is None else init
|
175
|
+
x0 = np.asarray(x0)
|
159
176
|
return x0
|
160
177
|
|
161
178
|
def set_solver(self, solver: Literal['root', 'fsolve'] = 'root'):
|
@@ -205,3 +222,19 @@ class SLE:
|
|
205
222
|
args = args[args != 1]
|
206
223
|
return args, res
|
207
224
|
return args, None
|
225
|
+
|
226
|
+
def _validate_arguments(self):
|
227
|
+
"""Validate the arguments for the SLE class."""
|
228
|
+
# TODO: Insert case where both actmodel and mixture are provided (check if acmodel.mixture == mixture, if not raise warning)
|
229
|
+
if isinstance(self.actmodel, ActModel):
|
230
|
+
# If actmodel is an instance of ActModel
|
231
|
+
self.mixture: List[Component] = self.mixture or self.actmodel.mixture
|
232
|
+
elif isinstance(self.actmodel, type) and issubclass(self.actmodel, ActModel):
|
233
|
+
# If actmodel is a class (subclass of ActModel)
|
234
|
+
if self.mixture is None:
|
235
|
+
raise ValueError("Please provide a valid mixture:Mixture.")
|
236
|
+
self.actmodel: ActModel = self.actmodel(self.mixture)
|
237
|
+
else:
|
238
|
+
# If actmodel is neither an instance nor a subclass of ActModel
|
239
|
+
err = "'actmodel' must be an instance or a subclass of 'ActModel'"
|
240
|
+
raise ValueError(err)
|
@@ -0,0 +1,38 @@
|
|
1
|
+
Metadata-Version: 2.1
|
2
|
+
Name: cosmopharm
|
3
|
+
Version: 0.0.23
|
4
|
+
Summary: Predictive modeling for drug-polymer compatibility in pharmaceutical formulations using COSMO-SAC.
|
5
|
+
Home-page: https://github.com/ivanantolo/cosmopharm,
|
6
|
+
Author: Ivan Antolovic
|
7
|
+
Author-email: Ivan.Antolovic@tu-berlin.de
|
8
|
+
Maintainer: Martin Klajmon
|
9
|
+
Maintainer-email: Martin.Klajmon@vscht.cz
|
10
|
+
License: MIT
|
11
|
+
Keywords: Drug-Polymer Compatibility,Amorphous Solid Dispersions,Pharmaceutical Formulation,COSMO-SAC Model,Solubility Prediction,Miscibility Analysis,Phase Behavior Prediction,Pharmaceutical Sciences,Drug Formulation Research,Polymer Science,Predictive Modeling in Pharma,Drug Development Tools,Biopharmaceuticals
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
13
|
+
Classifier: Intended Audience :: Science/Research
|
14
|
+
Classifier: Intended Audience :: Healthcare Industry
|
15
|
+
Classifier: Intended Audience :: Developers
|
16
|
+
Classifier: Intended Audience :: Education
|
17
|
+
Classifier: License :: OSI Approved :: MIT License
|
18
|
+
Classifier: Topic :: Scientific/Engineering
|
19
|
+
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
|
20
|
+
Classifier: Topic :: Scientific/Engineering :: Chemistry
|
21
|
+
Classifier: Programming Language :: Python :: 3
|
22
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
23
|
+
Classifier: Programming Language :: Python :: 3.8
|
24
|
+
Classifier: Programming Language :: Python :: 3.9
|
25
|
+
Classifier: Programming Language :: Python :: 3.10
|
26
|
+
Classifier: Programming Language :: Python :: 3.11
|
27
|
+
Classifier: Programming Language :: Python :: 3.12
|
28
|
+
Classifier: Programming Language :: Python :: 3.13
|
29
|
+
Requires-Python: >=3.8
|
30
|
+
Description-Content-Type: text/markdown
|
31
|
+
License-File: LICENSE
|
32
|
+
Requires-Dist: numpy >=1.15
|
33
|
+
Requires-Dist: pandas >=1.0
|
34
|
+
Requires-Dist: scipy >=1.4
|
35
|
+
Requires-Dist: openpyxl >=3.0
|
36
|
+
Provides-Extra: examples
|
37
|
+
Requires-Dist: matplotlib >=3.0 ; extra == 'examples'
|
38
|
+
|
@@ -1,18 +1,18 @@
|
|
1
1
|
cosmopharm/__init__.py,sha256=sdgLzbqylG8DDAJ5J96YiO4egn9xVJTx2uzaIZ8qj4g,68
|
2
2
|
cosmopharm/components.py,sha256=yHbhgFvLt9VN0jcAsLLRb0vS9FXM06yM7Pc7UgBli4M,946
|
3
3
|
cosmopharm/actmodels/__init__.py,sha256=9iH67yrdSaf10Fj8LwRikUDUMeMxsvUHRPEaWc3384k,59
|
4
|
-
cosmopharm/actmodels/actmodel.py,sha256=
|
5
|
-
cosmopharm/actmodels/cosmo.py,sha256=
|
4
|
+
cosmopharm/actmodels/actmodel.py,sha256=69jluNR7Tb4BHwtkCQLI3NQ_0AEZcTDM69IdRPz9--w,5072
|
5
|
+
cosmopharm/actmodels/cosmo.py,sha256=BoO_Yny_UUr8CxUuu8uuwR5kg3eR3NXKomKH1zsbm78,4372
|
6
6
|
cosmopharm/equilibrium/__init__.py,sha256=5NsIbQEwELjeeoFEiWelnzHnhTzt5zsBh3r5icn_AIQ,44
|
7
|
-
cosmopharm/equilibrium/lle.py,sha256=
|
8
|
-
cosmopharm/equilibrium/sle.py,sha256=
|
7
|
+
cosmopharm/equilibrium/lle.py,sha256=k3ub2BXSpocY6TPsxLNkwSp8sAhU6LVsHEx84ptsKio,5470
|
8
|
+
cosmopharm/equilibrium/sle.py,sha256=rIbNcvLQ9O5rvAssjxp4anahlUtxE3HMBB8s-rRVV-o,10963
|
9
9
|
cosmopharm/utils/__init__.py,sha256=qfUPovmZ9ukj6ZbTfndUOH6EX0ZrzRNjLZEDIVS8UvM,113
|
10
10
|
cosmopharm/utils/convert.py,sha256=V-7jY-Sb7C38N5bQcp1c27EOiVJfriP6zRbLAIKgrdE,2470
|
11
11
|
cosmopharm/utils/helpers.py,sha256=D2Zx9P0ywWWl2XQtzC6e5ek2CrudBIncfAIp_7vQnC0,1430
|
12
12
|
cosmopharm/utils/lle_scanner.py,sha256=So9FCxLLcHmBkuF6zggMo3W3gFBocEmuRzyxVGy69JM,6587
|
13
13
|
cosmopharm/utils/spacing.py,sha256=vtM9b4wodpFGkZFGGLhiSXT51Zl6fNK2Og4oRcbLFH4,9222
|
14
|
-
cosmopharm-0.0.
|
15
|
-
cosmopharm-0.0.
|
16
|
-
cosmopharm-0.0.
|
17
|
-
cosmopharm-0.0.
|
18
|
-
cosmopharm-0.0.
|
14
|
+
cosmopharm-0.0.23.dist-info/LICENSE,sha256=25ZCycfBgonIECGYnZTy72eJVfzcHCEOz3DM9sTx7do,1162
|
15
|
+
cosmopharm-0.0.23.dist-info/METADATA,sha256=RIEg2FeVCKo7RTwxx7PIgOIkjWC1hH5LRHklyaP-Ywc,1851
|
16
|
+
cosmopharm-0.0.23.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
17
|
+
cosmopharm-0.0.23.dist-info/top_level.txt,sha256=MGniVgvs1yq4sn6HQ7ErDVYV_g3st3Fs8TTFHOJVQ9I,11
|
18
|
+
cosmopharm-0.0.23.dist-info/RECORD,,
|
@@ -1,118 +0,0 @@
|
|
1
|
-
Metadata-Version: 2.1
|
2
|
-
Name: cosmopharm
|
3
|
-
Version: 0.0.21
|
4
|
-
Summary: Predictive modeling for drug-polymer compatibility in pharmaceutical formulations using COSMO-SAC.
|
5
|
-
Home-page: https://github.com/ivanantolo/cosmopharm,
|
6
|
-
Author: Ivan Antolovic
|
7
|
-
Author-email: Ivan.Antolovic@tu-berlin.de
|
8
|
-
Maintainer: Martin Klajmon
|
9
|
-
Maintainer-email: Martin.Klajmon@vscht.cz
|
10
|
-
License: MIT
|
11
|
-
Keywords: Drug-Polymer Compatibility,Amorphous Solid Dispersions,Pharmaceutical Formulation,COSMO-SAC Model,Solubility Prediction,Miscibility Analysis,Phase Behavior Prediction,Pharmaceutical Sciences,Drug Formulation Research,Polymer Science,Predictive Modeling in Pharma,Drug Development Tools,Biopharmaceuticals
|
12
|
-
Classifier: Development Status :: 3 - Alpha
|
13
|
-
Classifier: Intended Audience :: Science/Research
|
14
|
-
Classifier: Intended Audience :: Healthcare Industry
|
15
|
-
Classifier: Intended Audience :: Developers
|
16
|
-
Classifier: Intended Audience :: Education
|
17
|
-
Classifier: License :: OSI Approved :: MIT License
|
18
|
-
Classifier: Topic :: Scientific/Engineering
|
19
|
-
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
|
20
|
-
Classifier: Topic :: Scientific/Engineering :: Chemistry
|
21
|
-
Classifier: Programming Language :: Python :: 3
|
22
|
-
Classifier: Programming Language :: Python :: 3 :: Only
|
23
|
-
Classifier: Programming Language :: Python :: 3.8
|
24
|
-
Classifier: Programming Language :: Python :: 3.9
|
25
|
-
Classifier: Programming Language :: Python :: 3.10
|
26
|
-
Classifier: Programming Language :: Python :: 3.11
|
27
|
-
Classifier: Programming Language :: Python :: 3.12
|
28
|
-
Classifier: Programming Language :: Python :: 3.13
|
29
|
-
Requires-Python: >=3.8
|
30
|
-
Description-Content-Type: text/markdown
|
31
|
-
License-File: LICENSE
|
32
|
-
Requires-Dist: numpy >=1.15
|
33
|
-
Requires-Dist: pandas >=1.0
|
34
|
-
Requires-Dist: scipy >=1.4
|
35
|
-
Requires-Dist: openpyxl >=3.0
|
36
|
-
Provides-Extra: examples
|
37
|
-
Requires-Dist: matplotlib >=3.0 ; extra == 'examples'
|
38
|
-
|
39
|
-
# COSMOPharm
|
40
|
-
|
41
|
-
<p align="center">
|
42
|
-
<img src="https://github.com/usnistgov/COSMOSAC/raw/master/JCTC2020.PNG" alt="TOC Figure" width="500">
|
43
|
-
</p>
|
44
|
-
|
45
|
-
COSMOPharm is a Python package designed for predictive modeling of drug-polymer compatibility and drug-solubility in common solvents. It leverages the COSMO-SAC (Conductor-like Screening Model Segment Activity Coefficient) model, offering a robust platform for solubility, miscibility, and phase behavior prediction in drug formulation processes.
|
46
|
-
|
47
|
-
## Features
|
48
|
-
|
49
|
-
- **Compatibility Prediction**: Predict drug-polymer compatibility using the open-source COSMO-SAC model.
|
50
|
-
- **Solubility Calculation**: Guide polymer selection for drug formulations by calculating drug-polymer solubilities.
|
51
|
-
- **Miscibility and Phase Behavior Analysis**: Understand drug-polymer miscibility and phase behavior under various conditions.
|
52
|
-
- **User-friendly Interface**: Facilitate research with easy-to-use functions and comprehensive documentation.
|
53
|
-
|
54
|
-
## Associated Research
|
55
|
-
|
56
|
-
The development of COSMOPharm is closely tied to ongoing research aimed at enhancing the understanding and application of COSMO-SAC models in drug formulation. A forthcoming manuscript detailing the theoretical foundations and empirical validations of the models used in this package will provide comprehensive insights into the work COSMOPharm supports.
|
57
|
-
|
58
|
-
**Note:** A related publication, provided as an example of the research context, can be found [here](https://dx.doi.org/10.1021/acs.jctc.9b01016). This link will be updated to directly point to our specific manuscript upon its publication, enabling users to delve deeper into the scientific basis and applications of COSMOPharm.
|
59
|
-
|
60
|
-
## Installation
|
61
|
-
|
62
|
-
Install COSMOPharm with pip:
|
63
|
-
|
64
|
-
`pip install cosmopharm`
|
65
|
-
|
66
|
-
Ensure you have installed the cCOSMO library as per instructions on the [COSMOSAC GitHub page](https://github.com/usnistgov/COSMOSAC).
|
67
|
-
|
68
|
-
## Quick Start
|
69
|
-
|
70
|
-
This minimal example demonstrates how to use COSMOPharm to calculate solubility and miscibility of a drug with a polymer:
|
71
|
-
|
72
|
-
```python
|
73
|
-
import cCOSMO
|
74
|
-
from cosmopharm import SLE, COSMOSAC
|
75
|
-
from cosmopharm.utils import create_components, read_params
|
76
|
-
|
77
|
-
# Define components - replace 'DrugName' and 'PolymerName' with your actual component names
|
78
|
-
names = ['DrugName', 'PolymerName']
|
79
|
-
params_file = "path/to/your/params.xlsx"
|
80
|
-
|
81
|
-
# Load parameters and create components
|
82
|
-
parameters = read_params(params_file)
|
83
|
-
components = create_components(names, parameters)
|
84
|
-
|
85
|
-
# Initialize COSMO-SAC model - replace paths with your local paths to COSMO profiles
|
86
|
-
db = cCOSMO.DelawareProfileDatabase(
|
87
|
-
"path/to/your/complist/complist.txt",
|
88
|
-
"path/to/your/profiles/")
|
89
|
-
|
90
|
-
for name in names:
|
91
|
-
iden = db.normalize_identifier(name)
|
92
|
-
db.add_profile(iden)
|
93
|
-
COSMO = cCOSMO.COSMO3(names, db)
|
94
|
-
|
95
|
-
# Setup the COSMO-SAC model with components
|
96
|
-
model = COSMOSAC(COSMO, components=components)
|
97
|
-
|
98
|
-
# Calculate solubility (SLE)
|
99
|
-
sle = SLE(solute=components[0], solvent=components[1], actmodel=model)
|
100
|
-
solubility = sle.solubility(mix='real')
|
101
|
-
|
102
|
-
# Output the solubility
|
103
|
-
print(solubility[['T', 'w', 'x']].to_string(index=False))
|
104
|
-
```
|
105
|
-
|
106
|
-
Replace 'DrugName', 'PolymerName', and file paths with your actual data and files. This example provides a straightforward demonstration of calculating the real solubility of a drug in a polymer using COSMOPharm.
|
107
|
-
|
108
|
-
## Contributing
|
109
|
-
|
110
|
-
Contributions are welcome! Please refer to our [GitHub repository](https://github.com/ivanantolo/cosmopharm) for more information.
|
111
|
-
|
112
|
-
## Citation
|
113
|
-
|
114
|
-
If you use COSMOPharm in your research, kindly cite our work. Citation details are available in [CITATION.md](https://github.com/ivanantolo/cosmopharm/CITATION.md).
|
115
|
-
|
116
|
-
## License
|
117
|
-
|
118
|
-
COSMOPharm is released under the MIT License. For more details, see the [LICENSE](https://github.com/ivanantolo/cosmopharm/LICENSE) file.
|
File without changes
|
File without changes
|