pharmapy-org 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.
Files changed (74) hide show
  1. pharmapy_org-0.1.0/LICENSE.md +41 -0
  2. pharmapy_org-0.1.0/PKG-INFO +64 -0
  3. pharmapy_org-0.1.0/PharmaPy/CakePhysics.py +117 -0
  4. pharmapy_org-0.1.0/PharmaPy/Calibration.py +300 -0
  5. pharmapy_org-0.1.0/PharmaPy/CheckModule.py +70 -0
  6. pharmapy_org-0.1.0/PharmaPy/Classifier.py +275 -0
  7. pharmapy_org-0.1.0/PharmaPy/Commons.py +718 -0
  8. pharmapy_org-0.1.0/PharmaPy/Connections.py +581 -0
  9. pharmapy_org-0.1.0/PharmaPy/Containers.py +1006 -0
  10. pharmapy_org-0.1.0/PharmaPy/Crystallizers.py +2229 -0
  11. pharmapy_org-0.1.0/PharmaPy/Crystallizers_Refactored.py +215 -0
  12. pharmapy_org-0.1.0/PharmaPy/DataClasses.py +529 -0
  13. pharmapy_org-0.1.0/PharmaPy/Distillation.py +905 -0
  14. pharmapy_org-0.1.0/PharmaPy/Distributions.py +142 -0
  15. pharmapy_org-0.1.0/PharmaPy/Drying_Model.py +734 -0
  16. pharmapy_org-0.1.0/PharmaPy/DynamicExtraction.py +600 -0
  17. pharmapy_org-0.1.0/PharmaPy/Errors.py +23 -0
  18. pharmapy_org-0.1.0/PharmaPy/Evaporators.py +1972 -0
  19. pharmapy_org-0.1.0/PharmaPy/Extractors.py +305 -0
  20. pharmapy_org-0.1.0/PharmaPy/Gaussians.py +138 -0
  21. pharmapy_org-0.1.0/PharmaPy/IntegratorBackends.py +1885 -0
  22. pharmapy_org-0.1.0/PharmaPy/Interpolation.py +305 -0
  23. pharmapy_org-0.1.0/PharmaPy/Kinetics.py +1078 -0
  24. pharmapy_org-0.1.0/PharmaPy/LevMarq.py +176 -0
  25. pharmapy_org-0.1.0/PharmaPy/Mechanisms.py +2034 -0
  26. pharmapy_org-0.1.0/PharmaPy/MetaModeler.py +273 -0
  27. pharmapy_org-0.1.0/PharmaPy/MixedPhases.py +648 -0
  28. pharmapy_org-0.1.0/PharmaPy/MixedPhases_Refactored.py +366 -0
  29. pharmapy_org-0.1.0/PharmaPy/MultiPhaseVessel.py +3204 -0
  30. pharmapy_org-0.1.0/PharmaPy/NameAnalysis.py +368 -0
  31. pharmapy_org-0.1.0/PharmaPy/ParamEstim.py +1382 -0
  32. pharmapy_org-0.1.0/PharmaPy/Phases.py +1227 -0
  33. pharmapy_org-0.1.0/PharmaPy/Phases_Refactored.py +1206 -0
  34. pharmapy_org-0.1.0/PharmaPy/Plotting.py +386 -0
  35. pharmapy_org-0.1.0/PharmaPy/ProcessControl.py +64 -0
  36. pharmapy_org-0.1.0/PharmaPy/ProcessControl_Refactored.py +491 -0
  37. pharmapy_org-0.1.0/PharmaPy/Reactors.py +2071 -0
  38. pharmapy_org-0.1.0/PharmaPy/Reactors_Refactored.py +113 -0
  39. pharmapy_org-0.1.0/PharmaPy/Results.py +345 -0
  40. pharmapy_org-0.1.0/PharmaPy/SimExec.py +719 -0
  41. pharmapy_org-0.1.0/PharmaPy/SolidLiquidSep.py +1299 -0
  42. pharmapy_org-0.1.0/PharmaPy/SolidLiquidSep_Refactored.py +871 -0
  43. pharmapy_org-0.1.0/PharmaPy/StatsModule.py +508 -0
  44. pharmapy_org-0.1.0/PharmaPy/Streams.py +287 -0
  45. pharmapy_org-0.1.0/PharmaPy/Streams_Refactored.py +582 -0
  46. pharmapy_org-0.1.0/PharmaPy/ThermoModule.py +967 -0
  47. pharmapy_org-0.1.0/PharmaPy/ThreePhaseSettler.py +236 -0
  48. pharmapy_org-0.1.0/PharmaPy/Utilities.py +71 -0
  49. pharmapy_org-0.1.0/PharmaPy/__init__.py +0 -0
  50. pharmapy_org-0.1.0/PharmaPy/_assimulo.py +203 -0
  51. pharmapy_org-0.1.0/PharmaPy/animate_profiles.py +255 -0
  52. pharmapy_org-0.1.0/PharmaPy/data/evaporator/props_nitrogen.json +18 -0
  53. pharmapy_org-0.1.0/PharmaPy/data/minimum_modeling_objects.json +7 -0
  54. pharmapy_org-0.1.0/PharmaPy/data/thermodynamics/unifac_interaction_params.csv +757 -0
  55. pharmapy_org-0.1.0/PharmaPy/data/thermodynamics/unifac_rk_qk.csv +126 -0
  56. pharmapy_org-0.1.0/PharmaPy/general_interpolation.py +29 -0
  57. pharmapy_org-0.1.0/PharmaPy/jac_module.py +184 -0
  58. pharmapy_org-0.1.0/README.md +36 -0
  59. pharmapy_org-0.1.0/pharmapy_org.egg-info/PKG-INFO +64 -0
  60. pharmapy_org-0.1.0/pharmapy_org.egg-info/SOURCES.txt +72 -0
  61. pharmapy_org-0.1.0/pharmapy_org.egg-info/dependency_links.txt +1 -0
  62. pharmapy_org-0.1.0/pharmapy_org.egg-info/requires.txt +12 -0
  63. pharmapy_org-0.1.0/pharmapy_org.egg-info/top_level.txt +1 -0
  64. pharmapy_org-0.1.0/pyproject.toml +70 -0
  65. pharmapy_org-0.1.0/setup.cfg +4 -0
  66. pharmapy_org-0.1.0/tests/test_distribution_basis.py +252 -0
  67. pharmapy_org-0.1.0/tests/test_multiphase_vessel_batch_crystallizer.py +617 -0
  68. pharmapy_org-0.1.0/tests/test_multiphase_vessel_continuous_crystallizer.py +446 -0
  69. pharmapy_org-0.1.0/tests/test_multiphase_vessel_reactor.py +351 -0
  70. pharmapy_org-0.1.0/tests/test_optional_assimulo_imports.py +301 -0
  71. pharmapy_org-0.1.0/tests/test_package_data.py +32 -0
  72. pharmapy_org-0.1.0/tests/test_scipy_backend.py +393 -0
  73. pharmapy_org-0.1.0/tests/test_solid_liquid_sep_refactored.py +296 -0
  74. pharmapy_org-0.1.0/tests/test_vessel_state_packing.py +175 -0
@@ -0,0 +1,41 @@
1
+ License
2
+ =======
3
+
4
+ Pythonic framework for the digital analysis of pharmaceutical processes: PharmaPy
5
+ Copyright (c) 2021, by the software owners: Purdue University.
6
+
7
+ Redistribution and use in source and binary forms, with or without modification,
8
+ are permitted provided that the following conditions are met:
9
+
10
+ 1. Redistributions of source code must retain the above copyright notice, this
11
+ list of conditions and the following disclaimer.
12
+
13
+ 2. Redistributions in binary form must reproduce the above copyright notice,
14
+ this list of conditions and the following disclaimer in the documentation and/or
15
+ other materials provided with the distribution.
16
+
17
+ 3. Neither the name PharmaPy, Purdue University, nor the names of its contributors
18
+ may be used to endorse or promote products derived from this software without specific
19
+ prior written permission.
20
+
21
+
22
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
23
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
24
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
25
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
26
+ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
27
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
29
+ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
31
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32
+
33
+ You are under no obligation whatsoever to provide any bug fixes, patches, or
34
+ upgrades to the features, functionality or performance of the source code
35
+ ("Enhancements") to anyone; however, if you choose to make your Enhancements
36
+ available either publicly, or directly to Purdue University, without imposing
37
+ a separate written license agreement for such Enhancements, then you hereby
38
+ grant Purdue Univeristy the following license: a non-exclusive, royalty-free
39
+ perpetual license to install, use, modify, prepare derivative works, incorporate
40
+ into other computer software, distribute, and sublicense such enhancements or
41
+ derivative works thereof, in binary and source code form.
@@ -0,0 +1,64 @@
1
+ Metadata-Version: 2.4
2
+ Name: pharmapy-org
3
+ Version: 0.1.0
4
+ Summary: A Pythonic framework for the digital analysis of pharmaceutical manufacturing processes.
5
+ Author-email: Daniel Casas-Orozco <dcasasor@purdue.edu>, Zachary Hillman <zachary.hillman18@gmail.com>
6
+ Maintainer-email: Zachary Hillman <zachary.hillman18@gmail.com>
7
+ License-Expression: BSD-3-Clause
8
+ Project-URL: Homepage, https://github.com/PharmaPy-org/PharmaPy
9
+ Project-URL: Repository, https://github.com/PharmaPy-org/PharmaPy
10
+ Keywords: pharmaceutical,process simulation,flowsheet,crystallization,chemical engineering
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Intended Audience :: Science/Research
15
+ Classifier: Topic :: Scientific/Engineering :: Chemistry
16
+ Requires-Python: >=3.9
17
+ Description-Content-Type: text/markdown
18
+ License-File: LICENSE.md
19
+ Requires-Dist: numpy
20
+ Requires-Dist: scipy
21
+ Requires-Dist: matplotlib
22
+ Requires-Dist: pandas
23
+ Provides-Extra: julia
24
+ Requires-Dist: diffeqpy>=2.6; python_version >= "3.10" and extra == "julia"
25
+ Provides-Extra: test
26
+ Requires-Dist: pytest; extra == "test"
27
+ Dynamic: license-file
28
+
29
+ # PharmaPy
30
+
31
+ PharmaPy is a pythonic library for the analysis of pharmaceutical manufacturing systems.
32
+
33
+ It allows to simulate the dynamics of standalone, drug substance unit operations in a variety of operating modes (batch, continuous, semibatch). Also, PharmaPy facilitates setting up and simulating pharmaceutical **flowsheets**, i.e., interconnected unit operations in a variety of operation modes, ranging from end-to-end batch, end-to-end continuous, and hybrid operation (combination of batch and/or continuous and semicontinuous unit operations).
34
+
35
+ ## Getting started
36
+ Source code, examples and documentation sources are on [GitHub](https://github.com/PharmaPy-org/PharmaPy).
37
+
38
+ ### Installation
39
+ PharmaPy is published on PyPI as `pharmapy-org`, and you import it as `PharmaPy`:
40
+
41
+ ```
42
+ pip install pharmapy-org
43
+ ```
44
+
45
+ ```python
46
+ import PharmaPy
47
+ ```
48
+
49
+ The only integrator this installs is scipy's. Unit operations built on `MultiPhaseVessel` (the `*_Refactored` modules) default to `ScipyBackend`. Two other solver backends are optional:
50
+
51
+ - **Assimulo (SUNDIALS CVode/IDA)** is used by `AssimuloBackend` and `AssimuloDAEBackend`. It is also needed by the legacy unit operations that have not been refactored yet: `Reactors`, `Crystallizers`, `Evaporators`, `Distillation`, `SolidLiquidSep`, `Containers`, `Drying_Model`, `DynamicExtraction` and `ThreePhaseSettler`. Install it from conda-forge, because the PyPI `assimulo` package is outdated and has no wheels:
52
+
53
+ ```
54
+ conda install -c conda-forge assimulo
55
+ ```
56
+
57
+ - **Julia (DifferentialEquations.jl)** is used by `DiffeqpyBackend` and needs Python 3.10 or newer:
58
+
59
+ ```
60
+ pip install "pharmapy-org[julia]"
61
+ python -c "import diffeqpy; diffeqpy.install()"
62
+ ```
63
+
64
+ To get a development install with every backend, clone the [GitHub repository](https://github.com/PharmaPy-org/PharmaPy) and follow `install_instructions.txt`. It sets up a conda environment that includes Assimulo and installs PharmaPy in editable mode.
@@ -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)