pharmapy-org 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. PharmaPy/CakePhysics.py +117 -0
  2. PharmaPy/Calibration.py +300 -0
  3. PharmaPy/CheckModule.py +70 -0
  4. PharmaPy/Classifier.py +275 -0
  5. PharmaPy/Commons.py +718 -0
  6. PharmaPy/Connections.py +581 -0
  7. PharmaPy/Containers.py +1006 -0
  8. PharmaPy/Crystallizers.py +2229 -0
  9. PharmaPy/Crystallizers_Refactored.py +215 -0
  10. PharmaPy/DataClasses.py +529 -0
  11. PharmaPy/Distillation.py +905 -0
  12. PharmaPy/Distributions.py +142 -0
  13. PharmaPy/Drying_Model.py +734 -0
  14. PharmaPy/DynamicExtraction.py +600 -0
  15. PharmaPy/Errors.py +23 -0
  16. PharmaPy/Evaporators.py +1972 -0
  17. PharmaPy/Extractors.py +305 -0
  18. PharmaPy/Gaussians.py +138 -0
  19. PharmaPy/IntegratorBackends.py +1885 -0
  20. PharmaPy/Interpolation.py +305 -0
  21. PharmaPy/Kinetics.py +1078 -0
  22. PharmaPy/LevMarq.py +176 -0
  23. PharmaPy/Mechanisms.py +2034 -0
  24. PharmaPy/MetaModeler.py +273 -0
  25. PharmaPy/MixedPhases.py +648 -0
  26. PharmaPy/MixedPhases_Refactored.py +366 -0
  27. PharmaPy/MultiPhaseVessel.py +3204 -0
  28. PharmaPy/NameAnalysis.py +368 -0
  29. PharmaPy/ParamEstim.py +1382 -0
  30. PharmaPy/Phases.py +1227 -0
  31. PharmaPy/Phases_Refactored.py +1206 -0
  32. PharmaPy/Plotting.py +386 -0
  33. PharmaPy/ProcessControl.py +64 -0
  34. PharmaPy/ProcessControl_Refactored.py +491 -0
  35. PharmaPy/Reactors.py +2071 -0
  36. PharmaPy/Reactors_Refactored.py +113 -0
  37. PharmaPy/Results.py +345 -0
  38. PharmaPy/SimExec.py +719 -0
  39. PharmaPy/SolidLiquidSep.py +1299 -0
  40. PharmaPy/SolidLiquidSep_Refactored.py +871 -0
  41. PharmaPy/StatsModule.py +508 -0
  42. PharmaPy/Streams.py +287 -0
  43. PharmaPy/Streams_Refactored.py +582 -0
  44. PharmaPy/ThermoModule.py +967 -0
  45. PharmaPy/ThreePhaseSettler.py +236 -0
  46. PharmaPy/Utilities.py +71 -0
  47. PharmaPy/__init__.py +0 -0
  48. PharmaPy/_assimulo.py +203 -0
  49. PharmaPy/animate_profiles.py +255 -0
  50. PharmaPy/data/evaporator/props_nitrogen.json +18 -0
  51. PharmaPy/data/minimum_modeling_objects.json +7 -0
  52. PharmaPy/data/thermodynamics/unifac_interaction_params.csv +757 -0
  53. PharmaPy/data/thermodynamics/unifac_rk_qk.csv +126 -0
  54. PharmaPy/general_interpolation.py +29 -0
  55. PharmaPy/jac_module.py +184 -0
  56. pharmapy_org-0.1.0.dist-info/METADATA +64 -0
  57. pharmapy_org-0.1.0.dist-info/RECORD +60 -0
  58. pharmapy_org-0.1.0.dist-info/WHEEL +5 -0
  59. pharmapy_org-0.1.0.dist-info/licenses/LICENSE.md +41 -0
  60. pharmapy_org-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,117 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Cake physics correlations shared by the solid-liquid separation units.
4
+
5
+ These four functions carry no state and belong to no class. They were defined
6
+ at the top of ``SolidLiquidSep`` and imported from there by ``Drying_Model``,
7
+ which made a legacy module part of another module's public API. Keeping them
8
+ here lets the legacy units, the refactored ones and the dryer draw on one copy
9
+ instead of drifting apart.
10
+
11
+ ``SolidLiquidSep`` re-exports every name below, so the original import path
12
+ still works.
13
+ """
14
+
15
+ import numpy as np
16
+
17
+ eps = np.finfo(float).eps * 1.1
18
+ grav = 9.8 # m/s**2
19
+
20
+
21
+ def high_resolution_fvm(f, boundary_cond, limiter_type='Van Leer'):
22
+
23
+ # Ghost cells -1, 0 and N + 1 (see LeVeque 2002, Chapter 9)
24
+ f_extrap = 2*f[-1] - f[-2]
25
+ f_aug = np.concatenate(([boundary_cond]*2, f, [f_extrap]))
26
+
27
+ f_diff = np.diff(f_aug, axis=0)
28
+
29
+ theta = (f_diff[:-1]) / (f_diff[1:] + eps)
30
+
31
+ if limiter_type == 'Van Leer':
32
+ limiter = (np.abs(theta) + theta) / (1 + np.abs(theta))
33
+ else: # TODO: include more limiters
34
+ pass
35
+
36
+ fluxes = f_aug[1:-1] + 0.5 * f_diff[1:] * limiter
37
+
38
+ return fluxes
39
+
40
+
41
+ def upwind_fvm(f, boundary_cond):
42
+ f_aug = np.concatenate(([boundary_cond], f))
43
+
44
+ return f_aug
45
+
46
+
47
+ def get_alpha(solid_phase, porosity, sphericity, rho_sol, csd=None):
48
+ # if csd is None:
49
+ # csd = solid_phase.distrib
50
+
51
+ # x_grid = solid_phase.x_distrib
52
+
53
+ # alpha_x = 180 * (1 - porosity) / \
54
+ # (porosity**3 * (x_grid*1e-6)**2 * rho_sol * sphericity**2)
55
+
56
+ # numerator = trapezoidal_rule(x_grid, csd * alpha_x)
57
+ # denominator = solid_phase.moments[0]
58
+
59
+ # alpha = numerator / (denominator + eps)
60
+ csd = solid_phase.distrib
61
+ rho_sol = solid_phase.getDensity()
62
+ x_grid = solid_phase.x_distrib * 1e-6
63
+
64
+ kv = 0.524 # converting number based CSD to volume based:
65
+
66
+ del_x_dist = np.diff(x_grid)
67
+ node_x_dist = (x_grid[:-1] + x_grid[1:]) / 2
68
+ node_CSD = (csd[:-1] + csd[1:]) / 2
69
+
70
+ # Volume of crystals in each bin
71
+ vol_cry = node_CSD * del_x_dist * (kv * node_x_dist**3)
72
+ frac_vol_cry = vol_cry / (np.sum(vol_cry) + eps)
73
+
74
+ csd = vol_cry
75
+
76
+ # Calculate irreducible saturation in weighted csd (volume based)
77
+ vol_frac = vol_cry/ np.sum(vol_cry)
78
+ x_grid = node_x_dist
79
+ alpha_x = 180 * (1 - porosity) / porosity**3 / x_grid**2 / rho_sol
80
+ alpha = np.sum(alpha_x * vol_frac)
81
+
82
+ return alpha
83
+
84
+
85
+ def get_sat_inf(x_vec, csd, deltaP, porosity, height, mu_zero, props):
86
+ surf_tens, rho_liq = props
87
+
88
+ kv = 0.524 # converting number based CSD to volume based:
89
+
90
+ del_x_dist = np.diff(x_vec)
91
+ node_x_dist = (x_vec[:-1] + x_vec[1:]) / 2
92
+ node_CSD = (csd[:-1] + csd[1:]) / 2
93
+
94
+ x_vec = node_x_dist
95
+ if isinstance(surf_tens, float) or isinstance(rho_liq, float):
96
+ capillary_number = porosity**3 * x_vec**2 * \
97
+ (rho_liq*grav*height + deltaP) / (1 - porosity)**2 / height / surf_tens
98
+ else:
99
+ capillary_number = np.outer(
100
+ porosity**3 * x_vec**2,
101
+ (rho_liq*grav*height + deltaP)/(1 - porosity)**2 / height / surf_tens
102
+ )
103
+ # Volume of crystals in each bin
104
+ vol_cry = node_CSD * del_x_dist * (kv * node_x_dist**3)
105
+ frac_vol_cry = vol_cry / (np.sum(vol_cry) + eps)
106
+
107
+ csd = vol_cry
108
+
109
+ s_inf = 0.155 * (1 + 0.031*capillary_number**(-0.49))
110
+ s_inf = np.where(s_inf > 1, 1, s_inf)
111
+
112
+ # Calculate irreducible saturation in weighted csd (volume based)
113
+ vol_frac = vol_cry/ np.sum(vol_cry)
114
+
115
+ s_inf = np.sum(vol_frac *s_inf)
116
+
117
+ return s_inf
@@ -0,0 +1,300 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Created on Tue Nov 3 11:29:05 2020
5
+
6
+ @author: dcasasor
7
+ """
8
+
9
+ import numpy as np
10
+ import itertools
11
+ import matplotlib.pyplot as plt
12
+ from matplotlib.ticker import AutoMinorLocator
13
+
14
+
15
+ class PCR_calibration:
16
+ def __init__(self, data, num_comp=None, standardize=True, snv=False,
17
+ y_name=None, y_suffixes=None):
18
+
19
+ self.data = data
20
+ self.standardize = standardize
21
+ self.snv = snv
22
+
23
+ data_mean = data.mean(axis=0)
24
+ data_std = data.std(axis=0)
25
+
26
+ if snv:
27
+ self.data_centered = self.__center_data(data)
28
+ else:
29
+ self.data_centered = self.__center_data(data, data_mean, data_std)
30
+ # self.data_centered = self.__center_data(data, None, None)
31
+
32
+ self.data_mean = data_mean
33
+ self.data_std = data_std
34
+
35
+ (self.projections, self.explained_variance,
36
+ self.svd_dict) = self.__get_projections()
37
+
38
+ if num_comp is None:
39
+ self.num_comp = len(self.svd_dict['sv'])
40
+ else:
41
+ self.num_comp = num_comp
42
+
43
+ if y_name is None:
44
+ y_name = 'y_'
45
+
46
+ self.y_name = y_name
47
+ self.y_suffixes = y_suffixes
48
+
49
+ def __center_data(self, data=None, mean=None, std=None):
50
+ if mean is None and std is None:
51
+ mean = data.mean(axis=0)
52
+ std = data.std(axis=0)
53
+
54
+ data_centered = data - mean
55
+
56
+ if self.snv or self.standardize:
57
+ data_centered *= 1 / std
58
+
59
+ return data_centered
60
+
61
+ def __get_projections(self, data=None, n_comp=None):
62
+
63
+ if data is None:
64
+ data = self.data_centered
65
+
66
+ # Perform SVD
67
+ u_m, sv, v_nt = np.linalg.svd(data)
68
+ v_n = v_nt.T
69
+
70
+ # Percent of explained variance
71
+ explained_var = sv**2 / (sv**2).sum() * 100
72
+
73
+ # Store SVD in a dict
74
+ v_trunc = v_nt[:len(sv)].T
75
+
76
+ svd_dict = {'U': u_m, 'sv': sv, 'V': v_n, 'V_trunc': v_trunc}
77
+
78
+ # Projections
79
+ projections = np.dot(data, v_n)
80
+
81
+ if n_comp is not None:
82
+ projections = projections[:, :n_comp]
83
+
84
+ return projections, explained_var, svd_dict
85
+
86
+ def plot_projections(self, fig_size=None, num_comp=None):
87
+
88
+ if num_comp is None:
89
+ num_comp = self.num_comp
90
+
91
+ comb = itertools.combinations(range(num_comp), 2)
92
+
93
+ combs = [item for item in comb]
94
+ num_plots = len(combs)
95
+
96
+ if num_plots == 1:
97
+ fig, axes = plt.subplots(figsize=fig_size)
98
+ axes = np.atleast_1d(axes)
99
+ else:
100
+ ncols = 2
101
+ nrows = num_plots // ncols + num_plots % ncols
102
+
103
+ fig, axes = plt.subplots(nrows, ncols, figsize=fig_size)
104
+
105
+ axes_flat = axes.flatten()
106
+ num_axes = len(axes_flat)
107
+ for ind in range(num_plots):
108
+ axis = axes_flat[ind]
109
+ pc_one, pc_two = combs[ind]
110
+
111
+ my_map = plt.get_cmap('Reds')
112
+ data_plot = self.projections[:, combs[ind]].T
113
+ axis.scatter(data_plot[0], data_plot[1],
114
+ # 'o', mfc='None',
115
+ s=30/(num_axes/2), c=range(data_plot.shape[1]),
116
+ cmap=my_map,
117
+ marker='o', edgecolor='k')
118
+
119
+ axis.text(0.5, -0.08, 'PC%i' % (pc_one + 1),
120
+ transform=axis.transAxes, ha='center')
121
+
122
+ axis.text(-0.08, 0.5, 'PC%i' % (pc_two + 1), rotation=90,
123
+ transform=axis.transAxes, va='center')
124
+
125
+ axis.spines['bottom'].set_position('zero')
126
+ axis.spines['left'].set_position('zero')
127
+
128
+ axis.spines['top'].set_visible(False)
129
+ axis.spines['right'].set_visible(False)
130
+
131
+ if num_plots < num_axes:
132
+ fig.delaxes(axes_flat[-1])
133
+
134
+ fig.tight_layout()
135
+
136
+ return fig, axes
137
+
138
+ def get_regression(self, y_data, num_comp=None, update_instance=True):
139
+
140
+ if self.y_suffixes is None:
141
+ self.y_suffixes = ['%i' % num for num in range(1, len(y_data))]
142
+
143
+ self.y_labels = [r'$' + self.y_name + ('{%s}' % suffix) + '$'
144
+ for suffix in self.y_suffixes]
145
+
146
+ if num_comp is None:
147
+ num_comp = self.num_comp
148
+
149
+ # scores = self.projections[:, :num_comp]
150
+ scores = self.projections[:, :num_comp]
151
+
152
+ scores_inv = np.linalg.pinv(scores) # pseudoinverse of scores
153
+
154
+ # Regression coefficients w.r.t. principal components
155
+ if y_data.ndim == 1:
156
+ y_data = y_data[..., np.newaxis]
157
+
158
+ y_means = y_data.mean(axis=0)
159
+ y_center = y_data - y_means
160
+ q_coeff = np.dot(scores_inv, y_center)
161
+
162
+ regression_coeff = q_coeff
163
+
164
+ self.y_data = y_data
165
+ self.y_center = y_center
166
+ self.y_means = y_means
167
+
168
+ if update_instance:
169
+ self.regression_coeff = regression_coeff
170
+ self.num_comp = num_comp
171
+
172
+ y_pred = self.predict(self.data)
173
+ residuals = y_data - y_pred
174
+ self.residuals = residuals
175
+
176
+ # # Regression coefficients w.r.t. original X
177
+ # regression_coeff = np.dot(self.svd_dict['V'][:, :num_comp],
178
+ # q_coeff)
179
+
180
+
181
+ # if update_instance:
182
+ # self.regression_coeff = regression_coeff
183
+
184
+ # y_pred = self.predict(self.data)
185
+ # residuals = y_data - y_pred
186
+ # self.residuals = residuals
187
+
188
+ return regression_coeff
189
+
190
+ def predict(self, inputs, num_comp=None, regression_coeff=None,
191
+ full_output=False):
192
+ inputs = np.atleast_2d(inputs)
193
+
194
+ if self.snv:
195
+ inputs_centered = self.__center_data(inputs)
196
+ else:
197
+ inputs_centered = self.__center_data(inputs)
198
+
199
+ if regression_coeff is None:
200
+ coeff = self.regression_coeff
201
+ else:
202
+ coeff = regression_coeff
203
+
204
+ if num_comp is None:
205
+ num_comp = self.num_comp
206
+
207
+ p_matrix = self.svd_dict['V'][:, :num_comp]
208
+ new_projections = np.dot(inputs_centered, p_matrix)
209
+
210
+ resid_x = inputs_centered[0] - np.dot(new_projections[0],
211
+ p_matrix.T)
212
+
213
+ SPE_x = np.dot(resid_x, resid_x)
214
+ print(SPE_x)
215
+
216
+ # new_projections, _, di = self.__get_projections(inputs_centered,
217
+ # num_comp)
218
+
219
+
220
+ response = np.dot(new_projections, coeff) + self.y_means
221
+
222
+ num_data = self.y_data.size
223
+
224
+ if full_output:
225
+ resid = response - self.y_data
226
+ mse = 1 / num_data * np.dot(resid.T, resid)
227
+ info_out = {'x_projected': new_projections, 'y_pred': response,
228
+ 'MSE': mse}
229
+
230
+ return info_out
231
+ else:
232
+
233
+ return response
234
+
235
+ def evaluate_mse(self, num_comp=None):
236
+
237
+ if num_comp is None:
238
+ pc_counter = range(len(self.svd_dict['sv']))
239
+ else:
240
+ pc_counter = range(num_comp)
241
+
242
+ mse = []
243
+ residuals = []
244
+
245
+ n_data = np.prod(self.y_data.shape)
246
+ for n_component in pc_counter:
247
+ coeff = self.get_regression(self.y_data, n_component + 1,
248
+ update_instance=False)
249
+
250
+ pred = self.predict(self.data, regression_coeff=coeff,
251
+ num_comp=n_component + 1)
252
+
253
+ resid = self.y_data - pred
254
+
255
+ mse_val = 1 / n_data * (resid**2).sum()
256
+
257
+ mse.append(mse_val)
258
+ residuals.append(resid)
259
+
260
+ return mse, residuals
261
+
262
+ def plot_parity(self, figsize=None):
263
+ if figsize is None:
264
+ figsize = (4, 3.5)
265
+ fig, axis = plt.subplots(figsize=figsize)
266
+
267
+ markers = ['o', 's', 'd', '*']
268
+
269
+ y_pred = self.y_data - self.residuals
270
+
271
+ minim = (np.minimum(self.y_data, y_pred)).min()
272
+ maxim = (np.maximum(self.y_data, y_pred)).max()
273
+
274
+ range_vals = maxim - minim
275
+
276
+ left_bottom = [minim - range_vals*0.01]*2
277
+ right_top = [maxim + range_vals*0.01]*2
278
+
279
+ axis.plot(*zip(left_bottom, right_top), '--k', alpha=0.5)
280
+
281
+ for ind in range(self.y_data.shape[1]):
282
+ axis.plot(self.y_data[:, ind], y_pred[:, ind],
283
+ marker=markers[ind], mfc='None', ls='',
284
+ label=self.y_labels[ind])
285
+
286
+ axis.legend()
287
+
288
+ axis.set_xlabel('$%s{data}$' % self.y_name)
289
+ axis.set_ylabel('$%s{model}$' % self.y_name)
290
+
291
+ axis.xaxis.set_minor_locator(AutoMinorLocator(2))
292
+ axis.yaxis.set_minor_locator(AutoMinorLocator(2))
293
+
294
+ axis.text(1, 1.04, 'num_components = %i' % self.num_comp,
295
+ transform=axis.transAxes, ha='right')
296
+
297
+ return fig, axis
298
+
299
+ def cross_validation(self, num_groups=10):
300
+ perm = np.random.permutation(self.data.shape[0])
@@ -0,0 +1,70 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Created on Tue Mar 7 14:49:31 2023
4
+
5
+ @author: dcasasor
6
+ """
7
+
8
+ import json
9
+ import pathlib
10
+
11
+ from PharmaPy.Errors import PharmaPySpecificationError
12
+ import warnings
13
+
14
+
15
+ root = pathlib.Path(__file__).parent / 'data'
16
+
17
+
18
+ def check_modeling_objects(uo, instance_name=None):
19
+ with open(root / 'minimum_modeling_objects.json') as fi:
20
+ checks = json.load(fi)
21
+
22
+ class_name = uo.__class__.__name__
23
+
24
+ if instance_name is None:
25
+ instance_name = '<InstanceName>'
26
+ instance_descr = "a " + class_name + ' instance'
27
+ else:
28
+ instance_descr = "the '%s' %s instance" % (instance_name, class_name)
29
+
30
+ if class_name in checks['special']:
31
+ modeling_objs = checks['special'][class_name]
32
+ else:
33
+ modeling_objs = checks[uo.oper_mode]
34
+
35
+ module_name = uo.__module__.split('.')[-1]
36
+
37
+ cond_kin = (module_name in checks['has_kinetics']['modules'] or
38
+ class_name in checks['has_kinetics']['classes']) and \
39
+ 'Kinetics' not in modeling_objs
40
+
41
+ cond_utility = (module_name in checks['has_utility']['modules'] or
42
+ class_name in checks['has_utility']['classes']) and \
43
+ 'Utility' not in modeling_objs
44
+
45
+ if cond_kin:
46
+ modeling_objs.append('Kinetics')
47
+
48
+ if cond_utility:
49
+ modeling_objs.append('Utility')
50
+
51
+ missing_obj = []
52
+ for obj in modeling_objs:
53
+ if not hasattr(uo, obj) or getattr(uo, obj) is None:
54
+ missing_obj.append(obj)
55
+
56
+ if len(missing_obj) > 0:
57
+ intro = "The following PharmaPy modeling objects were " \
58
+ "not detected in %s:\n" % instance_descr
59
+
60
+ obj_enum = '\t' + ',\n\t'.join(missing_obj) + '.\n\n'
61
+
62
+ recommend = "Please create the missing modeling objects listed above" \
63
+ " and then aggregate them one by one to the corresponding unit " \
64
+ "operation instance, e.g. %s.%s = <%sClass>(...)" % (
65
+ instance_name, missing_obj[0], missing_obj[0])
66
+
67
+ message = intro + obj_enum + recommend
68
+
69
+ # raise PharmaPySpecificationError(message)
70
+ warnings.warn(message)