cosmopharm 0.0.22__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.
@@ -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: list = []):
9
- self.mix = components
11
+ def __init__(self, components: List[Component]):
12
+ self.mixture = components
10
13
 
11
14
  def lngamma(self, T, x):
12
- pass
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.mix):
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, x, to='weight'):
82
- Mw = np.array([c.Mw for c in self.mix])
83
- return convert(x=np.array([x, 1-x]), Mw=Mw, to=to)[0]
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]
@@ -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, COSMO, components: list, free_volume=False,
6
- dispersion=False, combinatorial=True):
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.mix = components
17
+ self.mixture = mixture
9
18
  # Flexible assignment of 'get_lngamma_comb' and 'get_lngamma_dsp'
10
- # that changes dynamically if the values for 'free_volume', 'dispersion'
11
- # or "combinatorial" are changed after initialization of an instance.
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([c.v_298 for c in self.mix])
58
- v_hc = np.array([c.v_hc for c in self.mix])
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 not self._combinatorial:
79
+ if self._combinatorial is False:
72
80
  return np.zeros(len(x))
73
- elif self._free_volume:
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
- self._combinatorial = value
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
@@ -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 ..utils import spacing
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, actmodel):
11
- self.mix = actmodel.mix
12
- self.model = actmodel
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.model.activity(T, x)
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.model.thermofac(T, x)
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
- kwargs = dict(bounds=(0,1), ftol=1e-15, xtol=1e-15)
30
- res = least_squares(self.fobj_binodal, x0, args=(T,), **kwargs)
31
- return res.x
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.model.gmix(T, x1)
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.model._convert(binodal_x)
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)
@@ -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 Literal
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, solute, solvent, actmodel):
11
- self.mix = [solute, solvent]
12
- self.model = actmodel
13
- self.solute, self.solvent = solute, solvent
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, solvent: Component = None,
17
- args=None, init=None, data=None,
25
+ solute: Optional[Component] = None,
26
+ solvent: Optional[Component] = None,
18
27
  vary: Literal['T', 'w', 'auto'] = 'auto',
19
- mix: Literal['ideal', 'real'] = 'real',
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, mix
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
- if args is None or init is None:
32
- args, init = self.initialize(init=init, data=data)
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, solver='root'):
44
- is_iterable = hasattr(init, "__len__") and len(init) > 1
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.model._convert(res['x'])[0]
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.mix_type}): ', *text)
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.mix_type})...")
84
+ print(f"Calculating SLE ({self.config})...")
67
85
  # Start with varying 'w' until dTdw > THRESHOLD
68
86
  self._vary = 'w'
69
- args, x0 = self.initialize()
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
- # # (Deprecated): If last dT>5, make the next dT=5 (from old version)
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.model.lngamma(T, x)[0]
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 initialize(self, xmin=None, xmax=None, dx=None, data=None, init=None):
119
- args = self.set_args(xmin, xmax, dx, data)
120
- x0 = self.set_x0(init)
121
- return args, x0
122
-
123
- def set_args(self, xmin=None, xmax=None, dx=None, data=None):
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 data is None:
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
- else:
149
- args = data
150
- return args if vary != 'w' else self.model._convert(args, to='mole')
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.model._convert(init, to='mole')
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=ZCOTdk1NgT0SwkdxFgYeToqlVVUzeibsVXOCxW9gDjg,3090
5
- cosmopharm/actmodels/cosmo.py,sha256=gkZ6-D0b1EEdxXvAp1dM81gSX6uxpLprK8NhnKy88m4,3434
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=TvD4EV0bsbIO7PQdLnWWP0BvyiqmPz5kdeTyjLNtt4o,3343
8
- cosmopharm/equilibrium/sle.py,sha256=K1F2YQhK0GB35B0FuV-BXuY-D05apIeTpktLWvOul7o,9017
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.22.dist-info/LICENSE,sha256=25ZCycfBgonIECGYnZTy72eJVfzcHCEOz3DM9sTx7do,1162
15
- cosmopharm-0.0.22.dist-info/METADATA,sha256=01lyhRI79PhH9RiqUakGMjroAjPcO5oBij8LeC_FTjs,5520
16
- cosmopharm-0.0.22.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
17
- cosmopharm-0.0.22.dist-info/top_level.txt,sha256=MGniVgvs1yq4sn6HQ7ErDVYV_g3st3Fs8TTFHOJVQ9I,11
18
- cosmopharm-0.0.22.dist-info/RECORD,,
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,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.42.0)
2
+ Generator: bdist_wheel (0.43.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,117 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: cosmopharm
3
- Version: 0.0.22
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
- <img src="https://github.com/usnistgov/COSMOSAC/raw/master/JCTC2020.PNG" alt="TOC Figure">
44
- </p>
45
-
46
- Welcome to the COSMOPharm package, accompanying [our paper in *J. Chem. Theory Comput.*](https://dx.doi.org/10.1021/acs.jctc.9b01016). This project and its associated publication offer insights and a practical toolkit for researching drug-polymer and drug-solvent systems, aiming to provide the scientific community with the means to reproduce our findings and further the development of COSMO-SAC-based models.
47
-
48
- ## About
49
-
50
- 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.
51
-
52
- ## Features
53
-
54
- - **Compatibility Prediction**: Predict drug-polymer compatibility using the open-source COSMO-SAC model.
55
- - **Solubility Calculation**: Guide polymer selection for drug formulations by calculating drug-polymer solubilities.
56
- - **Miscibility and Phase Behavior Analysis**: Understand drug-polymer miscibility and phase behavior under various conditions.
57
- - **User-friendly Interface**: Facilitate research with easy-to-use functions and comprehensive documentation.
58
-
59
- ## Installation
60
-
61
- Install COSMOPharm with pip:
62
-
63
- `pip install cosmopharm`
64
-
65
- Ensure you have installed the cCOSMO library as per instructions on the [COSMOSAC GitHub page](https://github.com/usnistgov/COSMOSAC).
66
-
67
- ## Quick Start
68
-
69
- This minimal example demonstrates how to use COSMOPharm to calculate solubility and miscibility of a drug with a polymer:
70
-
71
- ```python
72
- import cCOSMO
73
- from cosmopharm import SLE, COSMOSAC
74
- from cosmopharm.utils import create_components, read_params
75
-
76
- # Define components - replace 'DrugName' and 'PolymerName' with your actual component names
77
- names = ['DrugName', 'PolymerName']
78
- params_file = "path/to/your/params.xlsx"
79
-
80
- # Load parameters and create components
81
- parameters = read_params(params_file)
82
- components = create_components(names, parameters)
83
-
84
- # Initialize COSMO-SAC model - replace paths with your local paths to COSMO profiles
85
- db = cCOSMO.DelawareProfileDatabase(
86
- "path/to/your/complist/complist.txt",
87
- "path/to/your/profiles/")
88
-
89
- for name in names:
90
- iden = db.normalize_identifier(name)
91
- db.add_profile(iden)
92
- COSMO = cCOSMO.COSMO3(names, db)
93
-
94
- # Setup the COSMO-SAC model with components
95
- model = COSMOSAC(COSMO, components=components)
96
-
97
- # Calculate solubility (SLE)
98
- sle = SLE(solute=components[0], solvent=components[1], actmodel=model)
99
- solubility = sle.solubility(mix='real')
100
-
101
- # Output the solubility
102
- print(solubility[['T', 'w', 'x']].to_string(index=False))
103
- ```
104
-
105
- 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.
106
-
107
- ## Contributing
108
-
109
- Contributions are welcome! Please refer to our [GitHub repository](https://github.com/ivanantolo/cosmopharm) for more information.
110
-
111
- ## Citation
112
-
113
- 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).
114
-
115
- ## License
116
-
117
- COSMOPharm is released under the MIT License. For more details, see the [LICENSE](https://github.com/ivanantolo/cosmopharm/LICENSE) file.