pypharm 1.6.1__py3-none-any.whl → 1.6.2__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.
- PyPharm/__init__.py +4 -3
- PyPharm/algorithms/country_optimization.py +469 -469
- PyPharm/algorithms/country_optimization_v2.py +330 -330
- PyPharm/algorithms/country_optimization_v3.py +428 -428
- PyPharm/algorithms/genetic_optimization.py +130 -130
- PyPharm/algorithms/gold_digger_optimization.py +126 -126
- PyPharm/constants.py +80 -80
- PyPharm/country_optimization.py +470 -0
- PyPharm/country_optimization_v2.py +330 -0
- PyPharm/country_optimization_v3.py +426 -0
- PyPharm/genetic_optimization.py +130 -0
- PyPharm/gold_digger_optimization.py +127 -0
- PyPharm/models/__init__.py +3 -3
- PyPharm/models/compartment_models.py +985 -985
- PyPharm/models/pbpk.py +683 -683
- PyPharm/models.py +638 -0
- {pypharm-1.6.1.dist-info → pypharm-1.6.2.dist-info}/METADATA +11 -7
- pypharm-1.6.2.dist-info/RECORD +21 -0
- {pypharm-1.6.1.dist-info → pypharm-1.6.2.dist-info}/WHEEL +1 -1
- pypharm-1.6.1.dist-info/RECORD +0 -15
- {pypharm-1.6.1.dist-info → pypharm-1.6.2.dist-info}/top_level.txt +0 -0
PyPharm/models.py
ADDED
|
@@ -0,0 +1,638 @@
|
|
|
1
|
+
from multiprocessing import shared_memory
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
from scipy.integrate import solve_ivp, RK45
|
|
5
|
+
from scipy.integrate import simps
|
|
6
|
+
from scipy.optimize import minimize
|
|
7
|
+
from .country_optimization import CountriesAlgorithm
|
|
8
|
+
from .country_optimization_v2 import CountriesAlgorithm_v2
|
|
9
|
+
from .genetic_optimization import GeneticAlgorithm
|
|
10
|
+
from numba import njit
|
|
11
|
+
import matplotlib.pyplot as plt
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class BaseCompartmentModel:
|
|
15
|
+
|
|
16
|
+
configuration_matrix_target = None
|
|
17
|
+
outputs_target = None
|
|
18
|
+
volumes_target = None
|
|
19
|
+
_optim = False
|
|
20
|
+
numba_option = False
|
|
21
|
+
|
|
22
|
+
def __init__(self, configuration_matrix, outputs, volumes=None, numba_option=False, use_shared_memory=False):
|
|
23
|
+
"""
|
|
24
|
+
Базовая камерная модель для описания фармакокинетики системы
|
|
25
|
+
|
|
26
|
+
Неизвестные параметры при необходимости задаются как None
|
|
27
|
+
например configuration_matrix = [[0, 1], [None, 0]]
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
configuration_matrix: Настроечная матрица модели, отображающая константы перехода между матрицами
|
|
31
|
+
outputs: Вектор констант перехода во вне камер
|
|
32
|
+
volumes: Объемы камер
|
|
33
|
+
"""
|
|
34
|
+
self.configuration_matrix = np.array(configuration_matrix)
|
|
35
|
+
self.configuration_matrix_target_count = 0
|
|
36
|
+
if np.any(self.configuration_matrix == None):
|
|
37
|
+
self.configuration_matrix_target = np.where(self.configuration_matrix == None)
|
|
38
|
+
self.configuration_matrix_target_count = np.sum(self.configuration_matrix == None)
|
|
39
|
+
self.outputs = np.array(outputs)
|
|
40
|
+
self.outputs_target_count = 0
|
|
41
|
+
if np.any(self.outputs == None):
|
|
42
|
+
self.outputs_target = np.where(self.outputs == None)
|
|
43
|
+
self.outputs_target_count = np.sum(self.outputs == None)
|
|
44
|
+
if not volumes:
|
|
45
|
+
self.volumes = np.ones(self.outputs.size)
|
|
46
|
+
else:
|
|
47
|
+
self.volumes = np.array(volumes)
|
|
48
|
+
self.volumes_target_count = 0
|
|
49
|
+
if np.any(self.volumes == None):
|
|
50
|
+
self.volumes_target = np.where(self.volumes == None)
|
|
51
|
+
self.volumes_target_count = np.sum(self.volumes == None)
|
|
52
|
+
self.last_result = None
|
|
53
|
+
self.numba_option = numba_option
|
|
54
|
+
self.use_shared_memory = use_shared_memory
|
|
55
|
+
if self.use_shared_memory:
|
|
56
|
+
self.memory_size = 2 + self.configuration_matrix_target_count + self.outputs_target_count + self.volumes_target_count
|
|
57
|
+
self.memory = shared_memory.ShareableList(self.memory_size * [None])
|
|
58
|
+
self.memory_name = self.memory.shm.name
|
|
59
|
+
|
|
60
|
+
def __del__(self):
|
|
61
|
+
if getattr(self, 'memory', None):
|
|
62
|
+
self.memory.shm.close()
|
|
63
|
+
self.memory.shm.unlink()
|
|
64
|
+
|
|
65
|
+
def _compartment_model(self, t, c):
|
|
66
|
+
"""
|
|
67
|
+
Функция для расчета камерной модели
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
t: Текущее время
|
|
71
|
+
c: Вектор концентраций
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
Вектор изменений концентраций (c) в момент времени (t)
|
|
75
|
+
"""
|
|
76
|
+
dc_dt = (self.configuration_matrix.T @ (c * self.volumes) \
|
|
77
|
+
- self.configuration_matrix.sum(axis=1) * (c * self.volumes)\
|
|
78
|
+
- self.outputs * (c * self.volumes)) / self.volumes
|
|
79
|
+
return dc_dt
|
|
80
|
+
|
|
81
|
+
@staticmethod
|
|
82
|
+
@njit
|
|
83
|
+
def _numba_compartment_model(t, c, configuration_matrix, outputs, volumes):
|
|
84
|
+
"""
|
|
85
|
+
Функция для расчета камерной модели
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
t: Текущее время
|
|
89
|
+
c: Вектор концентраций
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
Вектор изменений концентраций (c) в момент времени (t)
|
|
93
|
+
"""
|
|
94
|
+
dc_dt = (configuration_matrix.T @ (c * volumes) \
|
|
95
|
+
- configuration_matrix.sum(axis=1) * (c * volumes)\
|
|
96
|
+
- outputs * (c * volumes)) / volumes
|
|
97
|
+
return dc_dt
|
|
98
|
+
|
|
99
|
+
def __call__(self, t_max, c0=None, d=None, compartment_number=None, max_step=0.01, t_eval=None):
|
|
100
|
+
"""
|
|
101
|
+
Расчет кривых концентраций по фармакокинетической модели
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
t_max: Предельное время расчета
|
|
105
|
+
c0: Вектор нулевых концентраций
|
|
106
|
+
d: Вводимая доза
|
|
107
|
+
compartment_number: Номер камеры в которую вводится доза
|
|
108
|
+
max_step: Максимальный шаг при решении СДУ
|
|
109
|
+
t_eval: Временные точки, в которых необходимо молучить решение
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
Результат работы решателя scipy solve_ivp
|
|
113
|
+
"""
|
|
114
|
+
if not self._optim:
|
|
115
|
+
assert (not any([self.configuration_matrix_target, self.outputs_target, self.volumes_target])), \
|
|
116
|
+
"It is impossible to make a calculation with unknown parameters"
|
|
117
|
+
assert any([c0 is not None, d, compartment_number is not None]), "Need to set c0 or d and compartment_number"
|
|
118
|
+
if c0 is None:
|
|
119
|
+
assert all([d, compartment_number is not None]), "Need to set d and compartment_number"
|
|
120
|
+
c0 = np.zeros(self.outputs.size)
|
|
121
|
+
c0[compartment_number] = d / self.volumes[compartment_number]
|
|
122
|
+
else:
|
|
123
|
+
c0 = np.array(c0)
|
|
124
|
+
ts = [0, t_max]
|
|
125
|
+
self.last_result = solve_ivp(
|
|
126
|
+
fun=self._compartment_model if not self.numba_option else lambda t, c: self._numba_compartment_model(t, c, self.configuration_matrix.astype(np.float64), self.outputs.astype(np.float64), self.volumes.astype(np.float64)),
|
|
127
|
+
t_span=ts,
|
|
128
|
+
y0=c0,
|
|
129
|
+
max_step=max_step,
|
|
130
|
+
t_eval=t_eval
|
|
131
|
+
)
|
|
132
|
+
return self.last_result
|
|
133
|
+
|
|
134
|
+
def get_kinetic_params(self, t_max, d, compartment_number, max_step=0.01):
|
|
135
|
+
one_hour_result = self(t_max=1, d=d, compartment_number=compartment_number, max_step=max_step)
|
|
136
|
+
auc_1h = simps(one_hour_result.y[compartment_number], one_hour_result.t)
|
|
137
|
+
self(t_max=t_max, d=d, compartment_number=compartment_number, max_step=max_step)
|
|
138
|
+
auc = simps(self.last_result.y[compartment_number], self.last_result.t)
|
|
139
|
+
result_dict = {
|
|
140
|
+
'c_max': self.last_result.y[compartment_number].max(),
|
|
141
|
+
'V': self.volumes[compartment_number] if self.volumes is not None else None,
|
|
142
|
+
'AUC': auc,
|
|
143
|
+
'AUC_1h': auc_1h,
|
|
144
|
+
'Cl': d / auc
|
|
145
|
+
}
|
|
146
|
+
return result_dict
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def load_data_from_list(self, x):
|
|
150
|
+
if self.configuration_matrix_target:
|
|
151
|
+
self.configuration_matrix[self.configuration_matrix_target] = x[:self.configuration_matrix_target_count]
|
|
152
|
+
if self.outputs_target:
|
|
153
|
+
self.outputs[self.outputs_target] = x[
|
|
154
|
+
self.configuration_matrix_target_count:self.configuration_matrix_target_count + self.outputs_target_count]
|
|
155
|
+
if self.volumes_target:
|
|
156
|
+
self.volumes[self.volumes_target] = x[self.configuration_matrix_target_count + self.outputs_target_count:self.configuration_matrix_target_count + self.outputs_target_count + self.volumes_target_count]
|
|
157
|
+
|
|
158
|
+
def _target_function(self, x, max_step=0.01, metric='R2'):
|
|
159
|
+
"""
|
|
160
|
+
Функция расчета значения целевой функции
|
|
161
|
+
|
|
162
|
+
Args:
|
|
163
|
+
x: Значение искомых параметров модели
|
|
164
|
+
max_step: Максимальный шаг при решении СДУ
|
|
165
|
+
|
|
166
|
+
Returns:
|
|
167
|
+
Значение целевой функции, характеризующее отклонение от эксперементальных данных
|
|
168
|
+
"""
|
|
169
|
+
self.load_data_from_list(x)
|
|
170
|
+
c0 = self.c0
|
|
171
|
+
if c0 is None:
|
|
172
|
+
c0 = np.zeros(self.outputs.size)
|
|
173
|
+
c0[self.compartment_number] = self.d / self.volumes[self.compartment_number]
|
|
174
|
+
self(
|
|
175
|
+
t_max=np.max(self.teoretic_x),
|
|
176
|
+
c0=c0,
|
|
177
|
+
t_eval=self.teoretic_x,
|
|
178
|
+
max_step=max_step
|
|
179
|
+
)
|
|
180
|
+
target_results = self.last_result.y[tuple(self.know_compartments), :]
|
|
181
|
+
if metric == 'R2':
|
|
182
|
+
return np.sum(np.sum(self.w * ((target_results - self.teoretic_y) ** 2), axis=1) / np.sum((self.teoretic_avg - self.teoretic_y) ** 2, axis=1))
|
|
183
|
+
elif metric == 'norm':
|
|
184
|
+
return np.linalg.norm(target_results - self.teoretic_y)
|
|
185
|
+
else:
|
|
186
|
+
return np.sum(np.sum(self.w * ((target_results - self.teoretic_y) ** 2), axis=1) / np.sum((self.teoretic_avg - self.teoretic_y) ** 2, axis=1))
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def load_optimization_data(self, teoretic_x, teoretic_y, know_compartments, w = None, c0=None, d=None, compartment_number=None):
|
|
190
|
+
"""
|
|
191
|
+
Функция загрузки в модель эксперементальных данных
|
|
192
|
+
|
|
193
|
+
Args:
|
|
194
|
+
teoretic_x: Вектор временных точек теоретических значений
|
|
195
|
+
teoretic_y: Матрица с теоретическими значениями
|
|
196
|
+
know_compartments: Вектор с номерами камер, по которым есть данные
|
|
197
|
+
c0: Вектор нулевых концентраций
|
|
198
|
+
d: Вводимая доза
|
|
199
|
+
compartment_number: Номер камеры в которую вводится доза
|
|
200
|
+
|
|
201
|
+
Returns:
|
|
202
|
+
None
|
|
203
|
+
"""
|
|
204
|
+
self.teoretic_x = np.array(teoretic_x)
|
|
205
|
+
self.teoretic_y = np.array(teoretic_y)
|
|
206
|
+
self.know_compartments = know_compartments
|
|
207
|
+
self.teoretic_avg = np.average(self.teoretic_y, axis=1)
|
|
208
|
+
self.teoretic_avg = np.repeat(self.teoretic_avg, self.teoretic_x.size)
|
|
209
|
+
self.teoretic_avg = np.reshape(self.teoretic_avg, self.teoretic_y.shape)
|
|
210
|
+
assert any([c0, d, compartment_number is not None]), "Need to set c0 or d and compartment_number"
|
|
211
|
+
if not c0:
|
|
212
|
+
assert all([d, compartment_number is not None]), "Need to set d and compartment_number"
|
|
213
|
+
self.d = d
|
|
214
|
+
self.compartment_number = compartment_number
|
|
215
|
+
self.c0 = None
|
|
216
|
+
else:
|
|
217
|
+
self.c0 = np.array(c0)
|
|
218
|
+
self.w = np.ones(self.teoretic_y.shape) if w is None else np.array(w)
|
|
219
|
+
|
|
220
|
+
def optimize(self, method=None, user_method=None, method_is_func=True,
|
|
221
|
+
optimization_func_name='__call__', max_step=0.01, metric='R2', **kwargs):
|
|
222
|
+
"""
|
|
223
|
+
Функция оптимизации модели
|
|
224
|
+
|
|
225
|
+
Args:
|
|
226
|
+
method: Метод оптимизации, любой доступный minimize + 'country_optimization' и 'country_optimization_v2'
|
|
227
|
+
max_step: Максимальный шаг при решении СДУ
|
|
228
|
+
**kwargs: Дополнительные именованные аргументы
|
|
229
|
+
|
|
230
|
+
Returns:
|
|
231
|
+
None
|
|
232
|
+
"""
|
|
233
|
+
self._optim = True
|
|
234
|
+
f = lambda x: self._target_function(x, max_step=max_step, metric=metric)
|
|
235
|
+
if user_method is not None:
|
|
236
|
+
if method_is_func:
|
|
237
|
+
x = user_method(f, **kwargs)
|
|
238
|
+
else:
|
|
239
|
+
optimization_obj = user_method(f, **kwargs)
|
|
240
|
+
x = getattr(optimization_obj, optimization_func_name)()
|
|
241
|
+
else:
|
|
242
|
+
if method == 'country_optimization':
|
|
243
|
+
CA = CountriesAlgorithm(
|
|
244
|
+
f=f,
|
|
245
|
+
memory_list=getattr(self, 'memory', None),
|
|
246
|
+
**kwargs
|
|
247
|
+
)
|
|
248
|
+
CA.start()
|
|
249
|
+
x = CA.countries[0].population[0].x
|
|
250
|
+
elif method == 'country_optimization_v2':
|
|
251
|
+
CA = CountriesAlgorithm_v2(
|
|
252
|
+
f=f,
|
|
253
|
+
**kwargs
|
|
254
|
+
)
|
|
255
|
+
CA.start()
|
|
256
|
+
x = CA.countries[0].population[0].x
|
|
257
|
+
elif method == 'GA':
|
|
258
|
+
CA = GeneticAlgorithm(
|
|
259
|
+
f=f,
|
|
260
|
+
**kwargs
|
|
261
|
+
)
|
|
262
|
+
x = CA.start()
|
|
263
|
+
else:
|
|
264
|
+
res = minimize(
|
|
265
|
+
fun=f,
|
|
266
|
+
method=method,
|
|
267
|
+
**kwargs
|
|
268
|
+
)
|
|
269
|
+
x = res.x
|
|
270
|
+
if self.configuration_matrix_target:
|
|
271
|
+
self.configuration_matrix[self.configuration_matrix_target] = x[:self.configuration_matrix_target_count]
|
|
272
|
+
if self.outputs_target:
|
|
273
|
+
self.outputs[self.outputs_target] = x[
|
|
274
|
+
self.configuration_matrix_target_count:self.configuration_matrix_target_count + self.outputs_target_count]
|
|
275
|
+
if self.volumes_target:
|
|
276
|
+
self.volumes[self.volumes_target] = x[self.configuration_matrix_target_count + self.outputs_target_count:self.configuration_matrix_target_count + self.outputs_target_count + self.volumes_target_count]
|
|
277
|
+
self.configuration_matrix_target = None
|
|
278
|
+
self.outputs_target = None
|
|
279
|
+
self.volumes_target = None
|
|
280
|
+
self._optim = False
|
|
281
|
+
return x
|
|
282
|
+
|
|
283
|
+
def plot_model(self, compartment_numbers=None, compartment_names={}, left=None, right=None, y_lims={}, **kwargs):
|
|
284
|
+
"""
|
|
285
|
+
Функция для построения графиков модели
|
|
286
|
+
|
|
287
|
+
Args:
|
|
288
|
+
compartment_numbers: Камеры, которые нужно отобразить (если не указать, отобразим все)
|
|
289
|
+
compartment_names: Имена камер
|
|
290
|
+
"""
|
|
291
|
+
if compartment_numbers:
|
|
292
|
+
compartment_numbers = np.array(compartment_numbers)
|
|
293
|
+
else:
|
|
294
|
+
compartment_numbers = np.arange(self.outputs.size)
|
|
295
|
+
self(**kwargs)
|
|
296
|
+
for i in compartment_numbers:
|
|
297
|
+
if hasattr(self, "teoretic_x") and hasattr(self, "teoretic_y") and i in self.know_compartments:
|
|
298
|
+
plt.plot(self.teoretic_x, self.teoretic_y[self.know_compartments.index(i)], "*r")
|
|
299
|
+
plt.plot(self.last_result.t, self.last_result.y[i])
|
|
300
|
+
plt.title(compartment_names.get(i, i))
|
|
301
|
+
plt.xlim(left=left, right=right)
|
|
302
|
+
if y_lims.get(i):
|
|
303
|
+
plt.ylim(y_lims.get(i))
|
|
304
|
+
plt.grid()
|
|
305
|
+
plt.show()
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
class MagicCompartmentModel(BaseCompartmentModel):
|
|
309
|
+
|
|
310
|
+
need_magic_optimization = False
|
|
311
|
+
|
|
312
|
+
def __init__(self, configuration_matrix, outputs, volumes=None, magic_coefficient=1, exclude_compartments=[], numba_option=False, use_shared_memory=False):
|
|
313
|
+
super().__init__(configuration_matrix, outputs, volumes, numba_option, use_shared_memory)
|
|
314
|
+
self.magic_coefficient = magic_coefficient
|
|
315
|
+
self.exclude_compartments = np.array(exclude_compartments)
|
|
316
|
+
self.need_magic_optimization = self.magic_coefficient is None
|
|
317
|
+
if getattr(self, "memory", None):
|
|
318
|
+
self.memory.shm.close()
|
|
319
|
+
self.memory.shm.unlink()
|
|
320
|
+
self.memory_size += int(self.need_magic_optimization)
|
|
321
|
+
self.memory = shared_memory.ShareableList(
|
|
322
|
+
sequence=self.memory_size * [None]
|
|
323
|
+
)
|
|
324
|
+
self.memory_name = self.memory.shm.name
|
|
325
|
+
|
|
326
|
+
def __call__(self, t_max, c0=None, d=None, compartment_number=None, max_step=0.01, t_eval=None):
|
|
327
|
+
if not self._optim and not self.magic_coefficient:
|
|
328
|
+
raise Exception("Magic_coefficient parameter not specified")
|
|
329
|
+
res = super().__call__(t_max, c0, d, compartment_number, max_step, t_eval)
|
|
330
|
+
magic_arr = np.ones(self.configuration_matrix.shape[0]) * self.magic_coefficient
|
|
331
|
+
if self.exclude_compartments:
|
|
332
|
+
magic_arr[self.exclude_compartments] = 1
|
|
333
|
+
magic_arr = np.repeat(magic_arr, res.y.shape[1])
|
|
334
|
+
magic_arr = np.reshape(magic_arr, res.y.shape)
|
|
335
|
+
res.y = magic_arr * res.y
|
|
336
|
+
self.last_result = res
|
|
337
|
+
return res
|
|
338
|
+
|
|
339
|
+
def load_data_from_list(self, x):
|
|
340
|
+
super().load_data_from_list(x)
|
|
341
|
+
if self.need_magic_optimization:
|
|
342
|
+
self.magic_coefficient = x[-1]
|
|
343
|
+
|
|
344
|
+
def optimize(self, method=None, user_method=None, method_is_func=True,
|
|
345
|
+
optimization_func_name='__call__', max_step=0.01, **kwargs):
|
|
346
|
+
x = super().optimize(method, user_method, method_is_func, optimization_func_name, max_step, **kwargs)
|
|
347
|
+
if self.need_magic_optimization:
|
|
348
|
+
self.magic_coefficient = x[-1]
|
|
349
|
+
self.need_magic_optimization = False
|
|
350
|
+
return x
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
class ReleaseCompartmentModel(BaseCompartmentModel):
|
|
354
|
+
|
|
355
|
+
need_v_release_optimization = False
|
|
356
|
+
release_parameters_target = None
|
|
357
|
+
accumulation_parameters_target = None
|
|
358
|
+
|
|
359
|
+
class ReleaseRK45(RK45):
|
|
360
|
+
|
|
361
|
+
def __init__(self, fun, t0, y0, t_bound, release_function, compartment_number, c0, max_step=np.inf,
|
|
362
|
+
with_accumulate=False, accumulation_function=None, accumulation_type=1, rtol=1e-3, atol=1e-6, vectorized=False,
|
|
363
|
+
first_step=None, **extraneous):
|
|
364
|
+
super().__init__(fun, t0, y0, t_bound, max_step=max_step,
|
|
365
|
+
rtol=rtol, atol=atol, vectorized=vectorized,
|
|
366
|
+
first_step=first_step, **extraneous)
|
|
367
|
+
self.release_function = release_function
|
|
368
|
+
self.compartment_number = compartment_number
|
|
369
|
+
self.c0 = c0
|
|
370
|
+
self.old_release_correction = 0
|
|
371
|
+
self.old_accumulation_correction = c0
|
|
372
|
+
self.with_accumulate = with_accumulate
|
|
373
|
+
self.accumulation_function = accumulation_function
|
|
374
|
+
self.accumulation_type = accumulation_type
|
|
375
|
+
|
|
376
|
+
def _step_impl(self):
|
|
377
|
+
result = super()._step_impl()
|
|
378
|
+
release_correction = self.release_function(self.t, self.c0)
|
|
379
|
+
minus_accumulation = 0
|
|
380
|
+
if self.with_accumulate:
|
|
381
|
+
if self.accumulation_type == 1:
|
|
382
|
+
accumulation_correction = self.accumulation_function(self.t, self.c0 )
|
|
383
|
+
minus_accumulation = self.old_accumulation_correction - accumulation_correction
|
|
384
|
+
elif self.accumulation_type == 2:
|
|
385
|
+
coef_accumulation = self.accumulation_function(self.t, self.c0) / self.c0
|
|
386
|
+
plus_release = release_correction - self.old_release_correction
|
|
387
|
+
all_corrections = plus_release
|
|
388
|
+
if self.accumulation_type == 1:
|
|
389
|
+
if plus_release - minus_accumulation > 0:
|
|
390
|
+
all_corrections = plus_release - minus_accumulation
|
|
391
|
+
elif self.accumulation_type == 2:
|
|
392
|
+
all_corrections *= coef_accumulation
|
|
393
|
+
self.y[self.compartment_number] += all_corrections
|
|
394
|
+
self.old_release_correction = release_correction
|
|
395
|
+
if self.with_accumulate and self.accumulation_type == 1:
|
|
396
|
+
self.old_accumulation_correction = accumulation_correction
|
|
397
|
+
return result
|
|
398
|
+
|
|
399
|
+
def __init__(self, v_release, release_parameters, release_compartment,
|
|
400
|
+
release_function=None, with_accumulate=False, accumulation_function=None,
|
|
401
|
+
accumulation_parameters=[None], accumulation_type=1, *args, **kwargs):
|
|
402
|
+
"""
|
|
403
|
+
Камерная модель с высвобождением для описания фармакокинетики системы
|
|
404
|
+
|
|
405
|
+
Неизвестные параметры при необходимости задаются как None
|
|
406
|
+
например configuration_matrix = [[0, 1], [None, 0]]
|
|
407
|
+
|
|
408
|
+
Args:
|
|
409
|
+
configuration_matrix: Настроечная матрица модели, отображающая константы перехода между матрицами
|
|
410
|
+
outputs: Вектор констант перехода во вне камер
|
|
411
|
+
volumes: Объемы камер
|
|
412
|
+
v_release: Объем гепотетической камеры из которой происходит высвобождение
|
|
413
|
+
release_parameters: Параметры функции высвобождения
|
|
414
|
+
release_compartment: Номер камеры в которую происходит высвобождение
|
|
415
|
+
release_function: Функция высвобождения по умолчанию f(t,m,b,c) = c0 * c * t ** b / (t ** b + m)
|
|
416
|
+
"""
|
|
417
|
+
super().__init__(*args, **kwargs)
|
|
418
|
+
self.release_parameters = np.array(release_parameters)
|
|
419
|
+
self.release_parameters_target_count = 0
|
|
420
|
+
if np.any(self.release_parameters == None):
|
|
421
|
+
self.release_parameters_target = np.where(self.release_parameters == None)
|
|
422
|
+
self.release_parameters_target_count = np.sum(self.release_parameters == None)
|
|
423
|
+
self.v_release = v_release
|
|
424
|
+
if self.v_release is None:
|
|
425
|
+
self.need_v_release_optimization = True
|
|
426
|
+
self.release_compartment = release_compartment
|
|
427
|
+
self.release_function = release_function
|
|
428
|
+
self.with_accumulate = with_accumulate
|
|
429
|
+
self.accumulation_type = accumulation_type
|
|
430
|
+
|
|
431
|
+
if self.with_accumulate:
|
|
432
|
+
self.accumulation_function = accumulation_function
|
|
433
|
+
self.accumulation_parameters = np.array(accumulation_parameters)
|
|
434
|
+
if np.any(self.accumulation_parameters == None):
|
|
435
|
+
self.accumulation_parameters_target = np.where(self.accumulation_parameters == None)
|
|
436
|
+
self.accumulation_parameters_target_count = np.sum(self.accumulation_parameters == None)
|
|
437
|
+
|
|
438
|
+
if getattr(self, "memory", None):
|
|
439
|
+
self.memory.shm.close()
|
|
440
|
+
self.memory.shm.unlink()
|
|
441
|
+
self.memory_size += self.release_parameters_target_count + int(self.need_v_release_optimization)
|
|
442
|
+
self.memory = shared_memory.ShareableList(
|
|
443
|
+
sequence=self.memory_size * [None]
|
|
444
|
+
)
|
|
445
|
+
self.memory_name = self.memory.shm.name
|
|
446
|
+
|
|
447
|
+
def load_data_from_list(self, x):
|
|
448
|
+
super().load_data_from_list(x)
|
|
449
|
+
s = self.configuration_matrix_target_count + self.outputs_target_count + self.volumes_target_count
|
|
450
|
+
if self.release_parameters_target:
|
|
451
|
+
self.release_parameters[self.release_parameters_target] = x[s:s + self.release_parameters_target_count]
|
|
452
|
+
if self.need_v_release_optimization:
|
|
453
|
+
self.v_release = x[s + self.release_parameters_target_count]
|
|
454
|
+
if self.with_accumulate:
|
|
455
|
+
if self.accumulation_parameters_target:
|
|
456
|
+
s += self.release_parameters_target_count + int(self.need_v_release_optimization)
|
|
457
|
+
self.accumulation_parameters[self.accumulation_parameters_target] = x[s:s + self.accumulation_parameters_target_count]
|
|
458
|
+
|
|
459
|
+
def _default_release_function(self, t, c0):
|
|
460
|
+
"""
|
|
461
|
+
Функция для поправки на высвобождение
|
|
462
|
+
"""
|
|
463
|
+
m, b, c = self.release_parameters
|
|
464
|
+
return c0 * c * t ** b / (t ** b + m)
|
|
465
|
+
|
|
466
|
+
def get_release_function(self):
|
|
467
|
+
if self.release_function is not None:
|
|
468
|
+
return lambda t, c0: self.release_function(t, c0, *self.release_parameters)
|
|
469
|
+
else:
|
|
470
|
+
return self._default_release_function
|
|
471
|
+
|
|
472
|
+
def _default_accumulation_function(self, t, c0):
|
|
473
|
+
"""
|
|
474
|
+
Функция для поправки на накопление
|
|
475
|
+
"""
|
|
476
|
+
k, = self.accumulation_parameters
|
|
477
|
+
return c0 * np.exp(-k * t)
|
|
478
|
+
|
|
479
|
+
def get_accumulation_function(self):
|
|
480
|
+
if self.accumulation_function is not None:
|
|
481
|
+
return lambda t, c0: self.accumulation_function(t, c0, *self.accumulation_parameters)
|
|
482
|
+
else:
|
|
483
|
+
return self._default_accumulation_function
|
|
484
|
+
|
|
485
|
+
def __call__(self, t_max, c0=None, d=None, max_step=0.01, t_eval=None, **kwargs):
|
|
486
|
+
"""
|
|
487
|
+
Расчет кривых концентраций по фармакокинетической модели
|
|
488
|
+
|
|
489
|
+
Args:
|
|
490
|
+
t_max: Предельное время расчета
|
|
491
|
+
c0: Начальная концентрация в камере из которой высвобождается вещество
|
|
492
|
+
d: Вводимая доза
|
|
493
|
+
max_step: Максимальный шаг при решении СДУ
|
|
494
|
+
t_eval: Временные точки, в которых необходимо молучить решение
|
|
495
|
+
|
|
496
|
+
Returns:
|
|
497
|
+
Результат работы решателя scipy solve_ivp
|
|
498
|
+
"""
|
|
499
|
+
if not self._optim:
|
|
500
|
+
assert (not any([self.configuration_matrix_target, self.outputs_target, self.volumes_target])), \
|
|
501
|
+
"It is impossible to make a calculation with unknown parameters"
|
|
502
|
+
assert any([c0 is not None, d]), "Need to set c0 or d and compartment_number"
|
|
503
|
+
if c0 is None:
|
|
504
|
+
assert d, "Need to set d"
|
|
505
|
+
c0 = d / self.v_release
|
|
506
|
+
ts = [0, t_max]
|
|
507
|
+
y0 = np.zeros(self.outputs.shape)
|
|
508
|
+
self.last_result = solve_ivp(
|
|
509
|
+
fun=self._compartment_model if
|
|
510
|
+
not self.numba_option
|
|
511
|
+
else lambda t, c: self._numba_compartment_model(t, c,
|
|
512
|
+
self.configuration_matrix.astype(
|
|
513
|
+
np.float64),
|
|
514
|
+
self.outputs.astype(
|
|
515
|
+
np.float64),
|
|
516
|
+
self.volumes.astype(
|
|
517
|
+
np.float64)),
|
|
518
|
+
t_span=ts,
|
|
519
|
+
y0=y0,
|
|
520
|
+
max_step=max_step,
|
|
521
|
+
t_eval=t_eval,
|
|
522
|
+
method=self.ReleaseRK45,
|
|
523
|
+
release_function=self.get_release_function(),
|
|
524
|
+
compartment_number=self.release_compartment,
|
|
525
|
+
with_accumulate=self.with_accumulate,
|
|
526
|
+
accumulation_function=self.get_accumulation_function() if self.with_accumulate else None,
|
|
527
|
+
accumulation_type=self.accumulation_type,
|
|
528
|
+
c0=c0
|
|
529
|
+
)
|
|
530
|
+
self.last_result.model_realized = c0 - self.get_release_function()(self.last_result.t, c0)
|
|
531
|
+
if self.with_accumulate:
|
|
532
|
+
model_accumulation = self.get_accumulation_function()(self.last_result.t, c0)
|
|
533
|
+
if self.accumulation_type == 1:
|
|
534
|
+
self.last_result.model_realized = model_accumulation - self.get_release_function()(self.last_result.t, c0)
|
|
535
|
+
elif self.accumulation_type == 2:
|
|
536
|
+
accumulation_coeffs = model_accumulation / c0
|
|
537
|
+
self.last_result.model_realized= accumulation_coeffs * self.get_release_function()(self.last_result.t, c0)
|
|
538
|
+
self.last_result.model_realized = model_accumulation - self.last_result.model_realized
|
|
539
|
+
return self.last_result
|
|
540
|
+
|
|
541
|
+
def _target_function(self, x, max_step=0.01, metric='R2'):
|
|
542
|
+
"""
|
|
543
|
+
Функция расчета значения целевой функции
|
|
544
|
+
|
|
545
|
+
Args:
|
|
546
|
+
x: Значение искомых параметров модели
|
|
547
|
+
max_step: Максимальный шаг при решении СДУ
|
|
548
|
+
|
|
549
|
+
Returns:
|
|
550
|
+
Значение целевой функции, характеризующее отклонение от эксперементальных данных
|
|
551
|
+
"""
|
|
552
|
+
self.load_data_from_list(x)
|
|
553
|
+
c0 = self.c0
|
|
554
|
+
if c0 is None:
|
|
555
|
+
c0 = self.d / self.v_release
|
|
556
|
+
self(
|
|
557
|
+
t_max=np.max(self.teoretic_x),
|
|
558
|
+
c0=c0,
|
|
559
|
+
t_eval=self.teoretic_x,
|
|
560
|
+
max_step=max_step
|
|
561
|
+
)
|
|
562
|
+
target_results = self.last_result.y[tuple(self.know_compartments), :]
|
|
563
|
+
if metric == 'R2':
|
|
564
|
+
plus = 0
|
|
565
|
+
if self.teoretic_realized is not None:
|
|
566
|
+
model_realized = self.last_result.model_realized
|
|
567
|
+
plus = np.sum(((model_realized - self.teoretic_realized) ** 2) / ((self.teoretic_realized - self.teoretic_realized_avg) ** 2))
|
|
568
|
+
return plus + np.sum(np.sum(self.w * ((target_results - self.teoretic_y) ** 2), axis=1) / np.sum((self.teoretic_avg - self.teoretic_y) ** 2, axis=1))
|
|
569
|
+
elif metric == 'norm':
|
|
570
|
+
plus = 0
|
|
571
|
+
if self.teoretic_realized is not None:
|
|
572
|
+
model_realized = self.last_result.model_realized
|
|
573
|
+
plus = np.linalg.norm(self.teoretic_realized - model_realized)
|
|
574
|
+
return plus + np.linalg.norm(target_results - self.teoretic_y)
|
|
575
|
+
else:
|
|
576
|
+
return np.sum(np.sum(self.w * ((target_results - self.teoretic_y) ** 2), axis=1) / np.sum((self.teoretic_avg - self.teoretic_y) ** 2, axis=1))
|
|
577
|
+
|
|
578
|
+
def load_optimization_data(self, teoretic_x, teoretic_y, know_compartments,
|
|
579
|
+
w = None, c0=None, d=None, compartment_number=None, teoretic_realized=None):
|
|
580
|
+
"""
|
|
581
|
+
Функция загрузки в модель эксперементальных данных
|
|
582
|
+
|
|
583
|
+
Args:
|
|
584
|
+
teoretic_x: Вектор временных точек теоретических значений
|
|
585
|
+
teoretic_y: Матрица с теоретическими значениями
|
|
586
|
+
know_compartments: Вектор с номерами камер, по которым есть данные
|
|
587
|
+
c0: Начальная концентрация в камере из которой высвобождается вещество
|
|
588
|
+
d: Вводимая доза
|
|
589
|
+
|
|
590
|
+
Returns:
|
|
591
|
+
None
|
|
592
|
+
"""
|
|
593
|
+
self.teoretic_x = np.array(teoretic_x)
|
|
594
|
+
self.teoretic_y = np.array(teoretic_y)
|
|
595
|
+
self.teoretic_realized = np.array(teoretic_realized) if teoretic_realized is not None else teoretic_realized
|
|
596
|
+
if teoretic_realized is not None:
|
|
597
|
+
self.teoretic_realized_avg = np.average(self.teoretic_realized)
|
|
598
|
+
self.know_compartments = know_compartments
|
|
599
|
+
self.teoretic_avg = np.average(self.teoretic_y, axis=1)
|
|
600
|
+
self.teoretic_avg = np.repeat(self.teoretic_avg, self.teoretic_x.size)
|
|
601
|
+
self.teoretic_avg = np.reshape(self.teoretic_avg, self.teoretic_y.shape)
|
|
602
|
+
assert any([c0, d, compartment_number is not None]), "Need to set c0 or d and compartment_number"
|
|
603
|
+
if not c0:
|
|
604
|
+
assert all([d, compartment_number is not None]), "Need to set d and compartment_number"
|
|
605
|
+
self.d = d
|
|
606
|
+
self.c0 = None
|
|
607
|
+
else:
|
|
608
|
+
self.c0 = np.array(c0)
|
|
609
|
+
self.w = np.ones(self.teoretic_y.shape) if w is None else np.array(w)
|
|
610
|
+
|
|
611
|
+
def optimize(self, method=None, user_method=None, method_is_func=True,
|
|
612
|
+
optimization_func_name='__call__', max_step=0.01, **kwargs):
|
|
613
|
+
x = super().optimize(method, user_method, method_is_func, optimization_func_name, max_step, **kwargs)
|
|
614
|
+
s = self.configuration_matrix_target_count + self.outputs_target_count + self.volumes_target_count
|
|
615
|
+
if self.release_parameters_target:
|
|
616
|
+
self.release_parameters[self.release_parameters_target] = x[s:s + self.release_parameters_target_count]
|
|
617
|
+
if self.need_v_release_optimization:
|
|
618
|
+
self.v_release = x[s:s + self.release_parameters_target_count + 1]
|
|
619
|
+
self.need_v_release_optimization = False
|
|
620
|
+
if self.with_accumulate:
|
|
621
|
+
if self.accumulation_parameters_target:
|
|
622
|
+
s += self.release_parameters_target_count + int(self.need_v_release_optimization)
|
|
623
|
+
self.accumulation_parameters[self.accumulation_parameters_target] = x[s:s + self.accumulation_parameters_target_count]
|
|
624
|
+
return x
|
|
625
|
+
|
|
626
|
+
def plot_model(self, compartment_numbers=None, compartment_names={},
|
|
627
|
+
left=None, right=None, y_lims={}, plot_accumulation=False, **kwargs):
|
|
628
|
+
super().plot_model(compartment_numbers, compartment_names, left, right, y_lims, **kwargs)
|
|
629
|
+
if plot_accumulation:
|
|
630
|
+
if hasattr(self, "teoretic_x") and hasattr(self, "teoretic_realized"):
|
|
631
|
+
plt.plot(self.teoretic_x, self.teoretic_realized, "*r")
|
|
632
|
+
plt.plot(self.last_result.t, self.last_result.model_realized)
|
|
633
|
+
plt.title(compartment_names.get('realized', 'realized'))
|
|
634
|
+
plt.xlim(left=left, right=right)
|
|
635
|
+
if y_lims.get('realized'):
|
|
636
|
+
plt.ylim(y_lims.get('realized'))
|
|
637
|
+
plt.grid()
|
|
638
|
+
plt.show()
|
|
@@ -1,22 +1,24 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: pypharm
|
|
3
|
-
Version: 1.6.
|
|
3
|
+
Version: 1.6.2
|
|
4
4
|
Summary: Module for solving pharmacokinetic problems
|
|
5
5
|
Home-page: https://github.com/Krash13/PyPharm
|
|
6
6
|
Author: Krash13
|
|
7
7
|
Author-email: krasheninnikov.r.s@muctr.ru
|
|
8
|
+
License: UNKNOWN
|
|
8
9
|
Keywords: pharmacokinetics compartment-model
|
|
10
|
+
Platform: UNKNOWN
|
|
9
11
|
Classifier: Programming Language :: Python :: 3.9
|
|
10
12
|
Classifier: License :: OSI Approved :: BSD License
|
|
11
13
|
Classifier: Operating System :: OS Independent
|
|
12
14
|
Requires-Python: >=3.9
|
|
13
15
|
Description-Content-Type: text/markdown
|
|
14
|
-
Requires-Dist: numpy >=1.22.1
|
|
15
|
-
Requires-Dist: scipy <=1.13.0
|
|
16
|
-
Requires-Dist: numba >=0.58.1
|
|
17
|
-
Requires-Dist: matplotlib >=3.5.1
|
|
18
|
-
Requires-Dist: graycode >=1.0.5
|
|
19
|
-
Requires-Dist: numbalsoda >=0.3.4
|
|
16
|
+
Requires-Dist: numpy (>=1.22.1)
|
|
17
|
+
Requires-Dist: scipy (<=1.13.0)
|
|
18
|
+
Requires-Dist: numba (>=0.58.1)
|
|
19
|
+
Requires-Dist: matplotlib (>=3.5.1)
|
|
20
|
+
Requires-Dist: graycode (>=1.0.5)
|
|
21
|
+
Requires-Dist: numbalsoda (>=0.3.4)
|
|
20
22
|
|
|
21
23
|
PyPharm
|
|
22
24
|
----------
|
|
@@ -695,3 +697,5 @@ print(c)
|
|
|
695
697
|
```
|
|
696
698
|
|
|
697
699
|
The data is stored in list format [current_iteration, x0, ... , xn, f], works only for the country_optimization algorithm.
|
|
700
|
+
|
|
701
|
+
|