pypharm 1.6.0__py3-none-any.whl → 1.6.1__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,979 +1,985 @@
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 PyPharm.algorithms.country_optimization import CountriesAlgorithm
8
- from PyPharm.algorithms.country_optimization_v2 import CountriesAlgorithm_v2
9
- from PyPharm.algorithms.genetic_optimization import GeneticAlgorithm
10
- from numba import njit
11
- import matplotlib.pyplot as plt
12
-
13
- BIG_VALUE = 2 ** 24
14
-
15
-
16
- class BaseCompartmentModel:
17
-
18
- configuration_matrix_target = None
19
- outputs_target = None
20
- volumes_target = None
21
- _optim = False
22
- numba_option = False
23
-
24
- def __init__(self, configuration_matrix, outputs, volumes=None, numba_option=False, use_shared_memory=False):
25
- """
26
- Базовая камерная модель для описания фармакокинетики системы
27
-
28
- Неизвестные параметры при необходимости задаются как None
29
- например configuration_matrix = [[0, 1], [None, 0]]
30
-
31
- Args:
32
- configuration_matrix: Настроечная матрица модели, отображающая константы перехода между матрицами
33
- outputs: Вектор констант перехода во вне камер
34
- volumes: Объемы камер
35
- """
36
- self.configuration_matrix = np.array(configuration_matrix)
37
- self.configuration_matrix_target_count = 0
38
- if np.any(self.configuration_matrix == None):
39
- self.configuration_matrix_target = np.where(self.configuration_matrix == None)
40
- self.configuration_matrix_target_count = np.sum(self.configuration_matrix == None)
41
- self.outputs = np.array(outputs)
42
- self.outputs_target_count = 0
43
- if np.any(self.outputs == None):
44
- self.outputs_target = np.where(self.outputs == None)
45
- self.outputs_target_count = np.sum(self.outputs == None)
46
- if not volumes:
47
- self.volumes = np.ones(self.outputs.size)
48
- else:
49
- self.volumes = np.array(volumes)
50
- self.volumes_target_count = 0
51
- if np.any(self.volumes == None):
52
- self.volumes_target = np.where(self.volumes == None)
53
- self.volumes_target_count = np.sum(self.volumes == None)
54
- self.last_result = None
55
- self.numba_option = numba_option
56
- self.use_shared_memory = use_shared_memory
57
- if self.use_shared_memory:
58
- self.memory_size = 2 + self.configuration_matrix_target_count + self.outputs_target_count + self.volumes_target_count
59
- self.memory = shared_memory.ShareableList(self.memory_size * [None])
60
- self.memory_name = self.memory.shm.name
61
-
62
- def __del__(self):
63
- if getattr(self, 'memory', None):
64
- self.memory.shm.close()
65
- self.memory.shm.unlink()
66
-
67
- def _compartment_model(self, t, c):
68
- """
69
- Функция для расчета камерной модели
70
-
71
- Args:
72
- t: Текущее время
73
- c: Вектор концентраций
74
-
75
- Returns:
76
- Вектор изменений концентраций (c) в момент времени (t)
77
- """
78
- dc_dt = (self.configuration_matrix.T @ (c * self.volumes) \
79
- - self.configuration_matrix.sum(axis=1) * (c * self.volumes)\
80
- - self.outputs * (c * self.volumes)) / self.volumes
81
- return dc_dt
82
-
83
- @staticmethod
84
- @njit
85
- def _numba_compartment_model(t, c, configuration_matrix, outputs, volumes):
86
- """
87
- Функция для расчета камерной модели
88
-
89
- Args:
90
- t: Текущее время
91
- c: Вектор концентраций
92
-
93
- Returns:
94
- Вектор изменений концентраций (c) в момент времени (t)
95
- """
96
- dc_dt = (configuration_matrix.T @ (c * volumes) \
97
- - configuration_matrix.sum(axis=1) * (c * volumes)\
98
- - outputs * (c * volumes)) / volumes
99
- return dc_dt
100
-
101
- def __call__(self, t_max, c0=None, d=None, compartment_number=None, max_step=0.01, t_eval=None):
102
- """
103
- Расчет кривых концентраций по фармакокинетической модели
104
-
105
- Args:
106
- t_max: Предельное время расчета
107
- c0: Вектор нулевых концентраций
108
- d: Вводимая доза
109
- compartment_number: Номер камеры в которую вводится доза
110
- max_step: Максимальный шаг при решении СДУ
111
- t_eval: Временные точки, в которых необходимо молучить решение
112
-
113
- Returns:
114
- Результат работы решателя scipy solve_ivp
115
- """
116
- if not self._optim:
117
- assert (not any([self.configuration_matrix_target, self.outputs_target, self.volumes_target])), \
118
- "It is impossible to make a calculation with unknown parameters"
119
- assert any([c0 is not None, d, compartment_number is not None]), "Need to set c0 or d and compartment_number"
120
- if c0 is None:
121
- assert all([d, compartment_number is not None]), "Need to set d and compartment_number"
122
- c0 = np.zeros(self.outputs.size)
123
- c0[compartment_number] = d / self.volumes[compartment_number]
124
- else:
125
- c0 = np.array(c0)
126
- ts = [0, t_max]
127
- self.last_result = solve_ivp(
128
- 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)),
129
- t_span=ts,
130
- y0=c0,
131
- max_step=max_step,
132
- t_eval=t_eval,
133
- method='LSODA'
134
- )
135
- return self.last_result
136
-
137
- def get_kinetic_params(self, t_max, d, compartment_number, max_step=0.01):
138
- one_hour_result = self(t_max=1, d=d, compartment_number=compartment_number, max_step=max_step)
139
- auc_1h = simps(one_hour_result.y[compartment_number], one_hour_result.t)
140
- self(t_max=t_max, d=d, compartment_number=compartment_number, max_step=max_step)
141
- auc = simps(self.last_result.y[compartment_number], self.last_result.t)
142
- result_dict = {
143
- 'c_max': self.last_result.y[compartment_number].max(),
144
- 'V': self.volumes[compartment_number] if self.volumes is not None else None,
145
- 'AUC': auc,
146
- 'AUC_1h': auc_1h,
147
- 'Cl': d / auc
148
- }
149
- return result_dict
150
-
151
-
152
- def load_data_from_list(self, x):
153
- if self.configuration_matrix_target:
154
- self.configuration_matrix[self.configuration_matrix_target] = x[:self.configuration_matrix_target_count]
155
- if self.outputs_target:
156
- self.outputs[self.outputs_target] = x[
157
- self.configuration_matrix_target_count:self.configuration_matrix_target_count + self.outputs_target_count]
158
- if self.volumes_target:
159
- 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]
160
-
161
- def _target_function(self, x, max_step=0.01, metric='R2'):
162
- """
163
- Функция расчета значения целевой функции
164
-
165
- Args:
166
- x: Значение искомых параметров модели
167
- max_step: Максимальный шаг при решении СДУ
168
-
169
- Returns:
170
- Значение целевой функции, характеризующее отклонение от эксперементальных данных
171
- """
172
- self.load_data_from_list(x)
173
- c0 = self.c0
174
- if c0 is None:
175
- c0 = np.zeros(self.outputs.size)
176
- c0[self.compartment_number] = self.d / self.volumes[self.compartment_number]
177
- self(
178
- t_max=np.max(self.teoretic_x),
179
- c0=c0,
180
- t_eval=self.teoretic_x,
181
- max_step=max_step
182
- )
183
- target_results = self.last_result.y[tuple(self.know_compartments), :]
184
- if metric == 'R2':
185
- 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))
186
- elif metric == 'norm':
187
- return np.linalg.norm(target_results - self.teoretic_y)
188
- else:
189
- 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))
190
-
191
-
192
- def load_optimization_data(self, teoretic_x, teoretic_y, know_compartments, w = None, c0=None, d=None, compartment_number=None):
193
- """
194
- Функция загрузки в модель эксперементальных данных
195
-
196
- Args:
197
- teoretic_x: Вектор временных точек теоретических значений
198
- teoretic_y: Матрица с теоретическими значениями
199
- know_compartments: Вектор с номерами камер, по которым есть данные
200
- c0: Вектор нулевых концентраций
201
- d: Вводимая доза
202
- compartment_number: Номер камеры в которую вводится доза
203
-
204
- Returns:
205
- None
206
- """
207
- self.teoretic_x = np.array(teoretic_x)
208
- self.teoretic_y = np.array(teoretic_y)
209
- self.know_compartments = know_compartments
210
- self.teoretic_avg = np.average(self.teoretic_y, axis=1)
211
- self.teoretic_avg = np.repeat(self.teoretic_avg, self.teoretic_x.size)
212
- self.teoretic_avg = np.reshape(self.teoretic_avg, self.teoretic_y.shape)
213
- assert any([c0, d, compartment_number is not None]), "Need to set c0 or d and compartment_number"
214
- if not c0:
215
- assert all([d, compartment_number is not None]), "Need to set d and compartment_number"
216
- self.d = d
217
- self.compartment_number = compartment_number
218
- self.c0 = None
219
- else:
220
- self.c0 = np.array(c0)
221
- self.w = np.ones(self.teoretic_y.shape) if w is None else np.array(w)
222
-
223
- def optimize(self, method=None, user_method=None, method_is_func=True,
224
- optimization_func_name='__call__', max_step=0.01, metric='R2', **kwargs):
225
- """
226
- Функция оптимизации модели
227
-
228
- Args:
229
- method: Метод оптимизации, любой доступный minimize + 'country_optimization' и 'country_optimization_v2'
230
- max_step: Максимальный шаг при решении СДУ
231
- **kwargs: Дополнительные именованные аргументы
232
-
233
- Returns:
234
- None
235
- """
236
- self._optim = True
237
- f = lambda x: self._target_function(x, max_step=max_step, metric=metric)
238
- if user_method is not None:
239
- if method_is_func:
240
- x = user_method(f, **kwargs)
241
- else:
242
- optimization_obj = user_method(f, **kwargs)
243
- x = getattr(optimization_obj, optimization_func_name)()
244
- else:
245
- if method == 'country_optimization':
246
- CA = CountriesAlgorithm(
247
- f=f,
248
- memory_list=getattr(self, 'memory', None),
249
- **kwargs
250
- )
251
- CA.start()
252
- x = CA.countries[0].population[0].x
253
- elif method == 'country_optimization_v2':
254
- CA = CountriesAlgorithm_v2(
255
- f=f,
256
- **kwargs
257
- )
258
- CA.start()
259
- x = CA.countries[0].population[0].x
260
- elif method == 'GA':
261
- CA = GeneticAlgorithm(
262
- f=f,
263
- **kwargs
264
- )
265
- x = CA.start()
266
- else:
267
- res = minimize(
268
- fun=f,
269
- method=method,
270
- **kwargs
271
- )
272
- x = res.x
273
- if self.configuration_matrix_target:
274
- self.configuration_matrix[self.configuration_matrix_target] = x[:self.configuration_matrix_target_count]
275
- if self.outputs_target:
276
- self.outputs[self.outputs_target] = x[
277
- self.configuration_matrix_target_count:self.configuration_matrix_target_count + self.outputs_target_count]
278
- if self.volumes_target:
279
- 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]
280
- self.configuration_matrix_target = None
281
- self.outputs_target = None
282
- self.volumes_target = None
283
- self._optim = False
284
- return x
285
-
286
- def plot_model(self, compartment_numbers=None, compartment_names=None, left=None, right=None, y_lims={}, **kwargs):
287
- """
288
- Функция для построения графиков модели
289
-
290
- Args:
291
- compartment_numbers: Камеры, которые нужно отобразить (если не указать, отобразим все)
292
- compartment_names: Имена камер
293
- """
294
- if compartment_numbers:
295
- compartment_numbers = np.array(compartment_numbers)
296
- else:
297
- compartment_numbers = np.arange(self.outputs.size)
298
- if not compartment_names:
299
- compartment_names = {}
300
- if not y_lims:
301
- y_lims = {}
302
- self(**kwargs)
303
- for i in compartment_numbers:
304
- if hasattr(self, "teoretic_x") and hasattr(self, "teoretic_y") and i in self.know_compartments:
305
- plt.plot(self.teoretic_x, self.teoretic_y[self.know_compartments.index(i)], "*r")
306
- plt.plot(self.last_result.t, self.last_result.y[i])
307
- plt.title(compartment_names.get(i, i))
308
- plt.xlim(left=left, right=right)
309
- if y_lims.get(i):
310
- plt.ylim(y_lims.get(i))
311
- plt.grid()
312
- try:
313
- plt.show()
314
- except AttributeError:
315
- plt.savefig(f'{compartment_names.get(i, str(i))}.png')
316
- plt.cla()
317
-
318
-
319
-
320
- class MagicCompartmentModel(BaseCompartmentModel):
321
-
322
- need_magic_optimization = False
323
-
324
- def __init__(self, configuration_matrix, outputs, volumes=None, magic_coefficient=1, exclude_compartments=None, numba_option=False, use_shared_memory=False):
325
- super().__init__(configuration_matrix, outputs, volumes, numba_option, use_shared_memory)
326
- self.magic_coefficient = magic_coefficient
327
- self.exclude_compartments = np.array(exclude_compartments) if bool(exclude_compartments) else np.array([])
328
- self.need_magic_optimization = self.magic_coefficient is None
329
- if getattr(self, "memory", None):
330
- self.memory.shm.close()
331
- self.memory.shm.unlink()
332
- self.memory_size += int(self.need_magic_optimization)
333
- self.memory = shared_memory.ShareableList(
334
- sequence=self.memory_size * [None]
335
- )
336
- self.memory_name = self.memory.shm.name
337
-
338
- def __call__(self, t_max, c0=None, d=None, compartment_number=None, max_step=0.01, t_eval=None):
339
- if not self._optim and not self.magic_coefficient:
340
- raise Exception("Magic_coefficient parameter not specified")
341
- res = super().__call__(t_max, c0, d, compartment_number, max_step, t_eval)
342
- magic_arr = np.ones(self.configuration_matrix.shape[0]) * self.magic_coefficient
343
- if self.exclude_compartments:
344
- magic_arr[self.exclude_compartments] = 1
345
- magic_arr = np.repeat(magic_arr, res.y.shape[1])
346
- magic_arr = np.reshape(magic_arr, res.y.shape)
347
- res.y = magic_arr * res.y
348
- self.last_result = res
349
- return res
350
-
351
- def load_data_from_list(self, x):
352
- super().load_data_from_list(x)
353
- if self.need_magic_optimization:
354
- self.magic_coefficient = x[-1]
355
-
356
- def optimize(self, method=None, user_method=None, method_is_func=True,
357
- optimization_func_name='__call__', max_step=0.01, **kwargs):
358
- x = super().optimize(method, user_method, method_is_func, optimization_func_name, max_step, **kwargs)
359
- if self.need_magic_optimization:
360
- self.magic_coefficient = x[-1]
361
- self.need_magic_optimization = False
362
- return x
363
-
364
-
365
- class ReleaseCompartmentModel(BaseCompartmentModel):
366
-
367
- need_v_release_optimization = False
368
- release_parameters_target = None
369
- accumulation_parameters_target = None
370
-
371
- class ReleaseRK45(RK45):
372
-
373
- def __init__(self, fun, t0, y0, t_bound, release_function, compartment_number, c0, max_step=np.inf,
374
- with_accumulate=False, accumulation_function=None, accumulation_type=1, rtol=1e-3, atol=1e-6, vectorized=False,
375
- first_step=None, **extraneous):
376
- super().__init__(fun, t0, y0, t_bound, max_step=max_step,
377
- rtol=rtol, atol=atol, vectorized=vectorized,
378
- first_step=first_step, **extraneous)
379
- self.release_function = release_function
380
- self.compartment_number = compartment_number
381
- self.c0 = c0
382
- self.old_release_correction = 0
383
- self.old_accumulation_correction = c0
384
- self.with_accumulate = with_accumulate
385
- self.accumulation_function = accumulation_function
386
- self.accumulation_type = accumulation_type
387
-
388
- def _step_impl(self):
389
- result = super()._step_impl()
390
- release_correction = self.release_function(self.t, self.c0)
391
- minus_accumulation = 0
392
- if self.with_accumulate:
393
- if self.accumulation_type == 1:
394
- accumulation_correction = self.accumulation_function(self.t, self.c0 )
395
- minus_accumulation = self.old_accumulation_correction - accumulation_correction
396
- elif self.accumulation_type == 2:
397
- coef_accumulation = self.accumulation_function(self.t, self.c0) / self.c0
398
- plus_release = release_correction - self.old_release_correction
399
- all_corrections = plus_release
400
- if self.accumulation_type == 1:
401
- if plus_release - minus_accumulation > 0:
402
- all_corrections = plus_release - minus_accumulation
403
- elif self.accumulation_type == 2:
404
- all_corrections *= coef_accumulation
405
- self.y[self.compartment_number] += all_corrections
406
- self.old_release_correction = release_correction
407
- if self.with_accumulate and self.accumulation_type == 1:
408
- self.old_accumulation_correction = accumulation_correction
409
- return result
410
-
411
- def __init__(self, v_release, release_parameters, release_compartment,
412
- release_function=None, with_accumulate=False, accumulation_function=None,
413
- accumulation_parameters=[None], accumulation_type=1, *args, **kwargs):
414
- """
415
- Камерная модель с высвобождением для описания фармакокинетики системы
416
-
417
- Неизвестные параметры при необходимости задаются как None
418
- например configuration_matrix = [[0, 1], [None, 0]]
419
-
420
- Args:
421
- configuration_matrix: Настроечная матрица модели, отображающая константы перехода между матрицами
422
- outputs: Вектор констант перехода во вне камер
423
- volumes: Объемы камер
424
- v_release: Объем гепотетической камеры из которой происходит высвобождение
425
- release_parameters: Параметры функции высвобождения
426
- release_compartment: Номер камеры в которую происходит высвобождение
427
- release_function: Функция высвобождения по умолчанию f(t,m,b,c) = c0 * c * t ** b / (t ** b + m)
428
- """
429
- super().__init__(*args, **kwargs)
430
- self.release_parameters = np.array(release_parameters)
431
- self.release_parameters_target_count = 0
432
- if np.any(self.release_parameters == None):
433
- self.release_parameters_target = np.where(self.release_parameters == None)
434
- self.release_parameters_target_count = np.sum(self.release_parameters == None)
435
- self.v_release = v_release
436
- if self.v_release is None:
437
- self.need_v_release_optimization = True
438
- self.release_compartment = release_compartment
439
- self.release_function = release_function
440
- self.with_accumulate = with_accumulate
441
- self.accumulation_type = accumulation_type
442
-
443
- if self.with_accumulate:
444
- self.accumulation_function = accumulation_function
445
- self.accumulation_parameters = np.array(accumulation_parameters)
446
- if np.any(self.accumulation_parameters == None):
447
- self.accumulation_parameters_target = np.where(self.accumulation_parameters == None)
448
- self.accumulation_parameters_target_count = np.sum(self.accumulation_parameters == None)
449
-
450
- if getattr(self, "memory", None):
451
- self.memory.shm.close()
452
- self.memory.shm.unlink()
453
- self.memory_size += self.release_parameters_target_count + int(self.need_v_release_optimization)
454
- self.memory = shared_memory.ShareableList(
455
- sequence=self.memory_size * [None]
456
- )
457
- self.memory_name = self.memory.shm.name
458
-
459
- def load_data_from_list(self, x):
460
- super().load_data_from_list(x)
461
- s = self.configuration_matrix_target_count + self.outputs_target_count + self.volumes_target_count
462
- if self.release_parameters_target:
463
- self.release_parameters[self.release_parameters_target] = x[s:s + self.release_parameters_target_count]
464
- if self.need_v_release_optimization:
465
- self.v_release = x[s + self.release_parameters_target_count]
466
- if self.with_accumulate:
467
- if self.accumulation_parameters_target:
468
- s += self.release_parameters_target_count + int(self.need_v_release_optimization)
469
- self.accumulation_parameters[self.accumulation_parameters_target] = x[s:s + self.accumulation_parameters_target_count]
470
-
471
- def _default_release_function(self, t, c0):
472
- """
473
- Функция для поправки на высвобождение
474
- """
475
- m, b, c = self.release_parameters
476
- return c0 * c * t ** b / (t ** b + m)
477
-
478
- def get_release_function(self):
479
- if self.release_function is not None:
480
- return lambda t, c0: self.release_function(t, c0, *self.release_parameters)
481
- else:
482
- return self._default_release_function
483
-
484
- def _default_accumulation_function(self, t, c0):
485
- """
486
- Функция для поправки на накопление
487
- """
488
- k, = self.accumulation_parameters
489
- return c0 * np.exp(-k * t)
490
-
491
- def get_accumulation_function(self):
492
- if self.accumulation_function is not None:
493
- return lambda t, c0: self.accumulation_function(t, c0, *self.accumulation_parameters)
494
- else:
495
- return self._default_accumulation_function
496
-
497
- def __call__(self, t_max, c0=None, d=None, max_step=0.01, t_eval=None, **kwargs):
498
- """
499
- Расчет кривых концентраций по фармакокинетической модели
500
-
501
- Args:
502
- t_max: Предельное время расчета
503
- c0: Начальная концентрация в камере из которой высвобождается вещество
504
- d: Вводимая доза
505
- max_step: Максимальный шаг при решении СДУ
506
- t_eval: Временные точки, в которых необходимо молучить решение
507
-
508
- Returns:
509
- Результат работы решателя scipy solve_ivp
510
- """
511
- if not self._optim:
512
- assert (not any([self.configuration_matrix_target, self.outputs_target, self.volumes_target])), \
513
- "It is impossible to make a calculation with unknown parameters"
514
- assert any([c0 is not None, d]), "Need to set c0 or d and compartment_number"
515
- if c0 is None:
516
- assert d, "Need to set d"
517
- c0 = d / self.v_release
518
- ts = [0, t_max]
519
- y0 = np.zeros(self.outputs.shape)
520
- self.last_result = solve_ivp(
521
- fun=self._compartment_model if
522
- not self.numba_option
523
- else lambda t, c: self._numba_compartment_model(t, c,
524
- self.configuration_matrix.astype(
525
- np.float64),
526
- self.outputs.astype(
527
- np.float64),
528
- self.volumes.astype(
529
- np.float64)),
530
- t_span=ts,
531
- y0=y0,
532
- max_step=max_step,
533
- t_eval=t_eval,
534
- method=self.ReleaseRK45,
535
- release_function=self.get_release_function(),
536
- compartment_number=self.release_compartment,
537
- with_accumulate=self.with_accumulate,
538
- accumulation_function=self.get_accumulation_function() if self.with_accumulate else None,
539
- accumulation_type=self.accumulation_type,
540
- c0=c0
541
- )
542
- self.last_result.model_realized = c0 - self.get_release_function()(self.last_result.t, c0)
543
- if self.with_accumulate:
544
- model_accumulation = self.get_accumulation_function()(self.last_result.t, c0)
545
- if self.accumulation_type == 1:
546
- self.last_result.model_realized = model_accumulation - self.get_release_function()(self.last_result.t, c0)
547
- elif self.accumulation_type == 2:
548
- accumulation_coeffs = model_accumulation / c0
549
- self.last_result.model_realized= accumulation_coeffs * self.get_release_function()(self.last_result.t, c0)
550
- self.last_result.model_realized = model_accumulation - self.last_result.model_realized
551
- return self.last_result
552
-
553
- def _target_function(self, x, max_step=0.01, metric='R2'):
554
- """
555
- Функция расчета значения целевой функции
556
-
557
- Args:
558
- x: Значение искомых параметров модели
559
- max_step: Максимальный шаг при решении СДУ
560
-
561
- Returns:
562
- Значение целевой функции, характеризующее отклонение от эксперементальных данных
563
- """
564
- self.load_data_from_list(x)
565
- c0 = self.c0
566
- if c0 is None:
567
- c0 = self.d / self.v_release
568
- self(
569
- t_max=np.max(self.teoretic_x),
570
- c0=c0,
571
- t_eval=self.teoretic_x,
572
- max_step=max_step
573
- )
574
- target_results = self.last_result.y[tuple(self.know_compartments), :]
575
- if metric == 'R2':
576
- plus = 0
577
- if self.teoretic_realized is not None:
578
- model_realized = self.last_result.model_realized
579
- plus = np.sum(((model_realized - self.teoretic_realized) ** 2) / ((self.teoretic_realized - self.teoretic_realized_avg) ** 2))
580
- 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))
581
- elif metric == 'norm':
582
- plus = 0
583
- if self.teoretic_realized is not None:
584
- model_realized = self.last_result.model_realized
585
- plus = np.linalg.norm(self.teoretic_realized - model_realized)
586
- return plus + np.linalg.norm(target_results - self.teoretic_y)
587
- else:
588
- 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))
589
-
590
- def load_optimization_data(self, teoretic_x, teoretic_y, know_compartments,
591
- w = None, c0=None, d=None, compartment_number=None, teoretic_realized=None):
592
- """
593
- Функция загрузки в модель эксперементальных данных
594
-
595
- Args:
596
- teoretic_x: Вектор временных точек теоретических значений
597
- teoretic_y: Матрица с теоретическими значениями
598
- know_compartments: Вектор с номерами камер, по которым есть данные
599
- c0: Начальная концентрация в камере из которой высвобождается вещество
600
- d: Вводимая доза
601
-
602
- Returns:
603
- None
604
- """
605
- self.teoretic_x = np.array(teoretic_x)
606
- self.teoretic_y = np.array(teoretic_y)
607
- self.teoretic_realized = np.array(teoretic_realized) if teoretic_realized is not None else teoretic_realized
608
- if teoretic_realized is not None:
609
- self.teoretic_realized_avg = np.average(self.teoretic_realized)
610
- self.know_compartments = know_compartments
611
- self.teoretic_avg = np.average(self.teoretic_y, axis=1)
612
- self.teoretic_avg = np.repeat(self.teoretic_avg, self.teoretic_x.size)
613
- self.teoretic_avg = np.reshape(self.teoretic_avg, self.teoretic_y.shape)
614
- assert any([c0, d, compartment_number is not None]), "Need to set c0 or d and compartment_number"
615
- if not c0:
616
- assert all([d, compartment_number is not None]), "Need to set d and compartment_number"
617
- self.d = d
618
- self.c0 = None
619
- else:
620
- self.c0 = np.array(c0)
621
- self.w = np.ones(self.teoretic_y.shape) if w is None else np.array(w)
622
-
623
- def optimize(self, method=None, user_method=None, method_is_func=True,
624
- optimization_func_name='__call__', max_step=0.01, **kwargs):
625
- x = super().optimize(method, user_method, method_is_func, optimization_func_name, max_step, **kwargs)
626
- s = self.configuration_matrix_target_count + self.outputs_target_count + self.volumes_target_count
627
- if self.release_parameters_target:
628
- self.release_parameters[self.release_parameters_target] = x[s:s + self.release_parameters_target_count]
629
- if self.need_v_release_optimization:
630
- self.v_release = x[s:s + self.release_parameters_target_count + 1]
631
- self.need_v_release_optimization = False
632
- if self.with_accumulate:
633
- if self.accumulation_parameters_target:
634
- s += self.release_parameters_target_count + int(self.need_v_release_optimization)
635
- self.accumulation_parameters[self.accumulation_parameters_target] = x[s:s + self.accumulation_parameters_target_count]
636
- return x
637
-
638
- def plot_model(self, compartment_numbers=None, compartment_names={},
639
- left=None, right=None, y_lims={}, plot_accumulation=False, **kwargs):
640
- super().plot_model(compartment_numbers, compartment_names, left, right, y_lims, **kwargs)
641
- if plot_accumulation:
642
- if hasattr(self, "teoretic_x") and hasattr(self, "teoretic_realized"):
643
- plt.plot(self.teoretic_x, self.teoretic_realized, "*r")
644
- plt.plot(self.last_result.t, self.last_result.model_realized)
645
- plt.title(compartment_names.get('realized', 'realized'))
646
- plt.xlim(left=left, right=right)
647
- if y_lims.get('realized'):
648
- plt.ylim(y_lims.get('realized'))
649
- plt.grid()
650
- plt.show()
651
-
652
-
653
- class TwoSubstancesCompartmentModel(MagicCompartmentModel):
654
-
655
- released_configuration_matrix_target = None
656
- released_outputs_target = None
657
- release_parameters_target = None
658
- has_teoretic_y = False
659
- has_teoretic_released = False
660
-
661
- class TwoSubstancesRK45(RK45):
662
-
663
- def __init__(self, fun, t0, y0, t_bound, release_function, max_step=np.inf,
664
- rtol=1e-3, atol=1e-6, vectorized=False,
665
- first_step=None, **extraneous):
666
- self.old_corrections = 0
667
- super().__init__(fun, t0, y0, t_bound, max_step=max_step,
668
- rtol=rtol, atol=atol, vectorized=vectorized,
669
- first_step=first_step, **extraneous)
670
- self.release_function = release_function
671
-
672
- def _step_impl(self):
673
- result = super()._step_impl()
674
- release_correction: float = self.release_function(self.y, self.t)
675
- correction = release_correction - self.old_corrections
676
- c = self.y[:self.y.size // 2]
677
- real_release_correction = np.append(-1 * correction * c, correction * c)
678
- self.y += real_release_correction
679
- self.old_corrections = release_correction
680
- return result
681
-
682
- def __init__(self, configuration_matrix, outputs, released_configuration_matrix, released_outputs,
683
- release_parameters, release_function=None,
684
- volumes=None, magic_coefficient=1, exclude_compartments=None,
685
- numba_option=False, use_shared_memory=False):
686
- super().__init__(
687
- configuration_matrix=configuration_matrix,
688
- outputs=outputs,
689
- volumes=volumes,
690
- magic_coefficient=magic_coefficient,
691
- exclude_compartments=exclude_compartments,
692
- numba_option=numba_option,
693
- use_shared_memory=use_shared_memory
694
- )
695
- self.released_configuration_matrix = np.array(released_configuration_matrix)
696
- self.released_configuration_matrix_target_count = 0
697
- if np.any(self.released_configuration_matrix == None):
698
- self.released_configuration_matrix_target = np.where(self.released_configuration_matrix == None)
699
- self.released_configuration_matrix_target_count = np.sum(self.released_configuration_matrix == None)
700
- self.released_outputs = np.array(released_outputs)
701
- self.released_outputs_target_count = 0
702
- if np.any(self.released_outputs == None):
703
- self.released_outputs_target = np.where(self.released_outputs == None)
704
- self.released_outputs_target_count = np.sum(self.released_outputs == None)
705
- self.release_parameters = np.array(release_parameters)
706
- if np.any(self.release_parameters == None):
707
- self.release_parameters_target = np.where(self.release_parameters == None)
708
- self.release_parameters_target_count = np.sum(self.release_parameters == None)
709
- self.release_function = release_function
710
- if getattr(self, "memory", None):
711
- self.memory.shm.close()
712
- self.memory.shm.unlink()
713
- self.memory_size += (self.release_parameters_target_count +
714
- self.released_configuration_matrix_target_count + self.released_outputs_target_count)
715
- self.memory = shared_memory.ShareableList(
716
- sequence=self.memory_size * [None]
717
- )
718
- self.memory_name = self.memory.shm.name
719
-
720
- def __call__(self, t_max, c0=None, d=None, compartment_number=None, max_step=0.01, t_eval=None, **kwargs):
721
- """
722
- Расчет кривых концентраций по фармакокинетической модели
723
-
724
- Args:
725
- t_max: Предельное время расчета
726
- c0: Начальная концентрация в камере из которой высвобождается вещество
727
- d: Вводимая доза
728
- max_step: Максимальный шаг при решении СДУ
729
- t_eval: Временные точки, в которых необходимо молучить решение
730
-
731
- Returns:
732
- Результат работы решателя scipy solve_ivp
733
- """
734
- if not self._optim:
735
- assert (not any([self.configuration_matrix_target, self.outputs_target, self.volumes_target])), \
736
- "It is impossible to make a calculation with unknown parameters"
737
- assert any([c0 is not None, d]), "Need to set c0 or d and compartment_number"
738
-
739
-
740
- if c0 is None:
741
- assert all([d, compartment_number is not None]), "Need to set d and compartment_number"
742
- c0 = np.zeros((2, self.outputs.size))
743
- c0[0][compartment_number] = d / self.volumes[compartment_number]
744
- else:
745
- c0 = np.array(c0)
746
- c0 = c0.reshape((1, c0.size))[0]
747
- ts = [0, t_max]
748
- self.last_result = solve_ivp(
749
- fun=self._compartment_model if
750
- not self.numba_option
751
- else lambda t, c: self._numba_compartment_model(t, c,
752
- self.configuration_matrix.astype(
753
- np.float64),
754
- self.released_configuration_matrix.astype(
755
- np.float64),
756
- self.outputs.astype(
757
- np.float64),
758
- self.released_outputs.astype(
759
- np.float64),
760
- self.volumes.astype(
761
- np.float64)),
762
- t_span=ts,
763
- y0=c0,
764
- max_step=max_step,
765
- t_eval=t_eval,
766
- method=self.TwoSubstancesRK45,
767
- release_function=self.get_release_function()
768
- )
769
- return self.last_result
770
-
771
- def _default_release_function(self, current_c, t):
772
- """
773
- Функция для поправки на высвобождение
774
- """
775
- # c_resh = c.reshape((2, c.size // 2))
776
- # k = self.release_parameters[0]
777
- # return k * c_resh[0]
778
- m, b, c = self.release_parameters
779
- return c * t ** b / (t ** b + m)
780
-
781
- def get_release_function(self):
782
- if self.release_function is not None:
783
- return lambda c, t: self.release_function(c, t, *self.release_parameters)
784
- else:
785
- return self._default_release_function
786
-
787
- def _compartment_model(self, t, c):
788
- """
789
- Функция для расчета камерной модели
790
-
791
- Args:
792
- t: Текущее время
793
- c: Вектор концентраций
794
-
795
- Returns:
796
- Вектор изменений концентраций (c) в момент времени (t)
797
- """
798
- c_resh = c.reshape((2, c.size // 2))
799
- c_primary = c_resh[0]
800
- dc_dt = (self.configuration_matrix.T @ (c_primary * self.volumes) \
801
- - self.configuration_matrix.sum(axis=1) * (c_primary * self.volumes)\
802
- - self.outputs * (c_primary * self.volumes)) / self.volumes
803
- c_released = c_resh[1]
804
- dc_dt_released = (self.released_configuration_matrix.T @ (c_released * self.volumes) \
805
- - self.released_configuration_matrix.sum(axis=1) * (c_released * self.volumes) \
806
- - self.released_outputs * (c_released * self.volumes)) / self.volumes
807
- np.append(dc_dt, dc_dt_released)
808
-
809
- @staticmethod
810
- @njit
811
- def _numba_compartment_model(t, c, configuration_matrix, released_configuration_matrix, outputs, released_outputs, volumes):
812
- """
813
- Функция для расчета камерной модели
814
-
815
- Args:
816
- t: Текущее время
817
- c: Вектор концентраций
818
-
819
- Returns:
820
- Вектор изменений концентраций (c) в момент времени (t)
821
- """
822
- c_resh = c.reshape((2, c.size // 2))
823
- c_primary = c_resh[0]
824
- dc_dt = (configuration_matrix.T @ (c_primary * volumes) \
825
- - configuration_matrix.sum(axis=1) * (c_primary * volumes) \
826
- - outputs * (c_primary * volumes)) / volumes
827
- c_released = c_resh[1]
828
- dc_dt_released = (released_configuration_matrix.T @ (c_released * volumes) \
829
- - released_configuration_matrix.sum(axis=1) * (c_released * volumes) \
830
- - released_outputs * (c_released * volumes)) / volumes
831
- return np.append(dc_dt, dc_dt_released)
832
-
833
- def load_optimization_data(self, teoretic_x, teoretic_y=None, teoretic_released=None, know_compartments=None, know_released_compartments=None,
834
- w=None, released_w=None, c0=None, d=None, compartment_number=None):
835
- self.has_teoretic_y = bool(teoretic_y)
836
- self.has_teoretic_released = bool(teoretic_released)
837
- self.teoretic_x = np.array(teoretic_x)
838
- if self.has_teoretic_y:
839
- self.teoretic_y = np.array(teoretic_y if teoretic_y is not None else [[]])
840
- self.know_compartments = know_compartments if know_compartments is not None else []
841
- self.teoretic_avg = np.average(self.teoretic_y, axis=1)
842
- self.teoretic_avg = np.repeat(self.teoretic_avg, self.teoretic_x.size)
843
- self.teoretic_avg = np.reshape(self.teoretic_avg, self.teoretic_y.shape)
844
- self.w = np.ones(self.teoretic_y.shape) if w is None else np.array(w)
845
- if self.has_teoretic_released:
846
- self.teoretic_released = np.array(teoretic_released if teoretic_released is not None else [[]])
847
- self.know_released_compartments = know_released_compartments if know_released_compartments is not None else []
848
- self.released_avg = np.average(self.teoretic_released, axis=1)
849
- self.released_avg = np.repeat(self.released_avg, self.teoretic_x.size)
850
- self.released_avg = np.reshape(self.released_avg, self.teoretic_released.shape)
851
- self.released_w = np.ones(self.teoretic_released.shape) if released_w is None else np.array(released_w)
852
-
853
- assert any([c0, d, compartment_number is not None]), "Need to set c0 or d and compartment_number"
854
- if not c0:
855
- assert all([d, compartment_number is not None]), "Need to set d and compartment_number"
856
- self.d = d
857
- self.compartment_number = compartment_number
858
- self.c0 = None
859
- else:
860
- self.c0 = np.array(c0)
861
-
862
-
863
- def load_data_from_list(self, x):
864
- super().load_data_from_list(x)
865
- n = self.configuration_matrix_target_count + self.outputs_target_count + self.volumes_target_count
866
- if self.released_configuration_matrix_target:
867
- self.released_configuration_matrix[self.released_configuration_matrix_target] = x[n:n + self.released_configuration_matrix_target_count]
868
- n += self.released_configuration_matrix_target_count
869
- if self.released_outputs_target:
870
- self.released_outputs[self.released_outputs_target] = x[n:n + self.released_outputs_target_count]
871
- n += self.released_outputs_target_count
872
- if self.release_parameters_target:
873
- self.release_parameters[self.release_parameters_target] = x[n:n + self.release_parameters_target_count]
874
-
875
- def _target_function(self, x, max_step=0.01, metric='R2'):
876
- self.load_data_from_list(x)
877
- c0 = self.c0
878
- if c0 is None:
879
- c0 = np.zeros((2, self.outputs.size))
880
- c0[0][self.compartment_number] = self.d / self.volumes[self.compartment_number]
881
- self(
882
- t_max=np.max(self.teoretic_x),
883
- c0=c0,
884
- t_eval=self.teoretic_x,
885
- max_step=max_step
886
- )
887
- if not self.last_result.success:
888
- return BIG_VALUE
889
- if self.has_teoretic_y:
890
- target_results = self.last_result.y[tuple(self.know_compartments), :]
891
- if self.has_teoretic_released:
892
- t = tuple([self.configuration_matrix.shape[0] + i for i in self.know_released_compartments])
893
- released_target_results = self.last_result.y[t, :]
894
- if metric == 'R2':
895
- if self.has_teoretic_y:
896
- a = 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))
897
- else:
898
- a = 0
899
- if self.has_teoretic_released:
900
- b = np.sum(np.sum(self.released_w * ((released_target_results - self.teoretic_released) ** 2), axis=1) / np.sum(
901
- (self.released_avg - self.teoretic_released) ** 2, axis=1))
902
- else:
903
- b = 0
904
- return a + b
905
- elif metric == 'norm':
906
- if self.has_teoretic_y:
907
- a = np.linalg.norm(target_results - self.teoretic_y)
908
- else:
909
- a = 0
910
- if self.has_teoretic_released:
911
- b = np.linalg.norm(released_target_results - self.teoretic_released)
912
- else:
913
- b = 0
914
- return a + b
915
- else:
916
- if self.has_teoretic_y:
917
- a = np.sum(np.sum(self.w * ((target_results - self.teoretic_y) ** 2), axis=1) / np.sum(
918
- (self.teoretic_avg - self.teoretic_y) ** 2, axis=1))
919
- else:
920
- a = 0
921
- if self.has_teoretic_released:
922
- b = np.sum(np.sum(self.released_w * ((released_target_results - self.teoretic_released) ** 2), axis=1) / np.sum(
923
- (self.released_avg - self.teoretic_released) ** 2, axis=1))
924
- else:
925
- b = 0
926
- return a + b
927
-
928
- def optimize(self, method=None, user_method=None, method_is_func=True,
929
- optimization_func_name='__call__', max_step=0.01, **kwargs):
930
- x = super().optimize(method, user_method, method_is_func, optimization_func_name, max_step, **kwargs)
931
- n = self.configuration_matrix_target_count + self.outputs_target_count + self.volumes_target_count + int(
932
- self.need_magic_optimization)
933
- if self.released_configuration_matrix_target:
934
- self.released_configuration_matrix[self.released_configuration_matrix_target] = x[n:n + self.released_configuration_matrix_target_count]
935
- n += self.released_configuration_matrix_target_count
936
- if self.released_outputs_target:
937
- self.released_outputs[self.released_outputs_target] = x[n: n + self.released_outputs_target_count]
938
- n += self.released_outputs_target_count
939
- if self.release_parameters_target:
940
- self.release_parameters[self.release_parameters_target] = x[n:n + self.release_parameters_target_count]
941
- self.released_configuration_matrix_target = None
942
- self.released_outputs_target = None
943
- return x
944
-
945
- def plot_model(self, compartment_numbers=None, released_compartment_numbers=None,
946
- released_compartment_names=None, compartment_names=None, left=None, right=None,
947
- y_lims=None, released_y_lims=None, **kwargs):
948
- """
949
- Функция для построения графиков модели
950
-
951
- Args:
952
- compartment_numbers: Камеры, которые нужно отобразить (если не указать, отобразим все)
953
- compartment_names: Имена камер
954
- """
955
- super().plot_model(compartment_numbers=compartment_numbers, compartment_names=compartment_names,
956
- left=left, right=right, y_lims=y_lims, **kwargs)
957
-
958
- if not released_compartment_numbers:
959
- released_compartment_numbers = []
960
- if not released_compartment_names:
961
- released_compartment_names = {}
962
- if not released_y_lims:
963
- released_y_lims = {}
964
- for i in released_compartment_numbers:
965
- j = i + self.outputs.size
966
- if hasattr(self, "teoretic_x") and hasattr(self, "teoretic_released") and i in self.know_released_compartments:
967
- plt.plot(self.teoretic_x, self.teoretic_released[self.know_compartments.index(i)], "*r")
968
- plt.plot(self.last_result.t, self.last_result.y[j])
969
- plt.title(released_compartment_names.get(i, j))
970
- plt.xlim(left=left, right=right)
971
- if released_y_lims.get(i):
972
- plt.ylim(released_y_lims.get(i))
973
- plt.grid()
974
- try:
975
- plt.show()
976
- except AttributeError:
977
- plt.savefig(f'{released_compartment_names.get(i, str(j))}.png')
978
- plt.cla()
979
-
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 PyPharm.algorithms.country_optimization import CountriesAlgorithm
8
+ from PyPharm.algorithms.country_optimization_v2 import CountriesAlgorithm_v2
9
+ from PyPharm.algorithms.genetic_optimization import GeneticAlgorithm
10
+ from numba import njit
11
+ import matplotlib.pyplot as plt
12
+
13
+ BIG_VALUE = 2 ** 24
14
+
15
+
16
+ class BaseCompartmentModel:
17
+
18
+ configuration_matrix_target = None
19
+ outputs_target = None
20
+ volumes_target = None
21
+ _optim = False
22
+ numba_option = False
23
+
24
+ def __init__(self, configuration_matrix, outputs, volumes=None, numba_option=False, use_shared_memory=False):
25
+ """
26
+ Базовая камерная модель для описания фармакокинетики системы
27
+
28
+ Неизвестные параметры при необходимости задаются как None
29
+ например configuration_matrix = [[0, 1], [None, 0]]
30
+
31
+ Args:
32
+ configuration_matrix: Настроечная матрица модели, отображающая константы перехода между матрицами
33
+ outputs: Вектор констант перехода во вне камер
34
+ volumes: Объемы камер
35
+ """
36
+ self.configuration_matrix = np.array(configuration_matrix)
37
+ self.configuration_matrix_target_count = 0
38
+ if np.any(self.configuration_matrix == None):
39
+ self.configuration_matrix_target = np.where(self.configuration_matrix == None)
40
+ self.configuration_matrix_target_count = np.sum(self.configuration_matrix == None)
41
+ self.outputs = np.array(outputs)
42
+ self.outputs_target_count = 0
43
+ if np.any(self.outputs == None):
44
+ self.outputs_target = np.where(self.outputs == None)
45
+ self.outputs_target_count = np.sum(self.outputs == None)
46
+ if not volumes:
47
+ self.volumes = np.ones(self.outputs.size)
48
+ else:
49
+ self.volumes = np.array(volumes)
50
+ self.volumes_target_count = 0
51
+ if np.any(self.volumes == None):
52
+ self.volumes_target = np.where(self.volumes == None)
53
+ self.volumes_target_count = np.sum(self.volumes == None)
54
+ self.last_result = None
55
+ self.numba_option = numba_option
56
+ self.use_shared_memory = use_shared_memory
57
+ if self.use_shared_memory:
58
+ self.memory_size = 2 + self.configuration_matrix_target_count + self.outputs_target_count + self.volumes_target_count
59
+ self.memory = shared_memory.ShareableList(self.memory_size * [None])
60
+ self.memory_name = self.memory.shm.name
61
+
62
+ def __del__(self):
63
+ if getattr(self, 'memory', None):
64
+ self.memory.shm.close()
65
+ self.memory.shm.unlink()
66
+
67
+ def _compartment_model(self, t, c):
68
+ """
69
+ Функция для расчета камерной модели
70
+
71
+ Args:
72
+ t: Текущее время
73
+ c: Вектор концентраций
74
+
75
+ Returns:
76
+ Вектор изменений концентраций (c) в момент времени (t)
77
+ """
78
+ dc_dt = (self.configuration_matrix.T @ (c * self.volumes) \
79
+ - self.configuration_matrix.sum(axis=1) * (c * self.volumes)\
80
+ - self.outputs * (c * self.volumes)) / self.volumes
81
+ return dc_dt
82
+
83
+ @staticmethod
84
+ @njit
85
+ def _numba_compartment_model(t, c, configuration_matrix, outputs, volumes):
86
+ """
87
+ Функция для расчета камерной модели
88
+
89
+ Args:
90
+ t: Текущее время
91
+ c: Вектор концентраций
92
+
93
+ Returns:
94
+ Вектор изменений концентраций (c) в момент времени (t)
95
+ """
96
+ dc_dt = (configuration_matrix.T @ (c * volumes) \
97
+ - configuration_matrix.sum(axis=1) * (c * volumes)\
98
+ - outputs * (c * volumes)) / volumes
99
+ return dc_dt
100
+
101
+ def __call__(self, t_max, c0=None, d=None, compartment_number=None, max_step=0.01, t_eval=None):
102
+ """
103
+ Расчет кривых концентраций по фармакокинетической модели
104
+
105
+ Args:
106
+ t_max: Предельное время расчета
107
+ c0: Вектор нулевых концентраций
108
+ d: Вводимая доза
109
+ compartment_number: Номер камеры в которую вводится доза
110
+ max_step: Максимальный шаг при решении СДУ
111
+ t_eval: Временные точки, в которых необходимо молучить решение
112
+
113
+ Returns:
114
+ Результат работы решателя scipy solve_ivp
115
+ """
116
+ if not self._optim:
117
+ assert (not any([self.configuration_matrix_target, self.outputs_target, self.volumes_target])), \
118
+ "It is impossible to make a calculation with unknown parameters"
119
+ assert any([c0 is not None, d, compartment_number is not None]), "Need to set c0 or d and compartment_number"
120
+ if c0 is None:
121
+ assert all([d, compartment_number is not None]), "Need to set d and compartment_number"
122
+ c0 = np.zeros(self.outputs.size)
123
+ c0[compartment_number] = d / self.volumes[compartment_number]
124
+ else:
125
+ c0 = np.array(c0)
126
+ ts = [0, t_max]
127
+ self.last_result = solve_ivp(
128
+ 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)),
129
+ t_span=ts,
130
+ y0=c0,
131
+ max_step=max_step,
132
+ t_eval=t_eval,
133
+ method='LSODA'
134
+ )
135
+ return self.last_result
136
+
137
+ def get_kinetic_params(self, t_max, d, compartment_number, max_step=0.01):
138
+ one_hour_result = self(t_max=1, d=d, compartment_number=compartment_number, max_step=max_step)
139
+ auc_1h = simps(one_hour_result.y[compartment_number], one_hour_result.t)
140
+ self(t_max=t_max, d=d, compartment_number=compartment_number, max_step=max_step)
141
+ auc = simps(self.last_result.y[compartment_number], self.last_result.t)
142
+ result_dict = {
143
+ 'c_max': self.last_result.y[compartment_number].max(),
144
+ 'V': self.volumes[compartment_number] if self.volumes is not None else None,
145
+ 'AUC': auc,
146
+ 'AUC_1h': auc_1h,
147
+ 'Cl': d / auc
148
+ }
149
+ return result_dict
150
+
151
+
152
+ def load_data_from_list(self, x):
153
+ if self.configuration_matrix_target:
154
+ self.configuration_matrix[self.configuration_matrix_target] = x[:self.configuration_matrix_target_count]
155
+ if self.outputs_target:
156
+ self.outputs[self.outputs_target] = x[
157
+ self.configuration_matrix_target_count:self.configuration_matrix_target_count + self.outputs_target_count]
158
+ if self.volumes_target:
159
+ 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]
160
+
161
+ def _target_function(self, x, max_step=0.01, metric='R2'):
162
+ """
163
+ Функция расчета значения целевой функции
164
+
165
+ Args:
166
+ x: Значение искомых параметров модели
167
+ max_step: Максимальный шаг при решении СДУ
168
+
169
+ Returns:
170
+ Значение целевой функции, характеризующее отклонение от эксперементальных данных
171
+ """
172
+ self.load_data_from_list(x)
173
+ c0 = self.c0
174
+ if c0 is None:
175
+ c0 = np.zeros(self.outputs.size)
176
+ c0[self.compartment_number] = self.d / self.volumes[self.compartment_number]
177
+ self(
178
+ t_max=np.max(self.teoretic_x),
179
+ c0=c0,
180
+ t_eval=self.teoretic_x,
181
+ max_step=max_step
182
+ )
183
+ target_results = self.last_result.y[tuple(self.know_compartments), :]
184
+ if metric == 'R2':
185
+ 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))
186
+ elif metric == 'norm':
187
+ return np.linalg.norm(target_results - self.teoretic_y)
188
+ else:
189
+ 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))
190
+
191
+
192
+ def load_optimization_data(self, teoretic_x, teoretic_y, know_compartments, w = None, c0=None, d=None, compartment_number=None):
193
+ """
194
+ Функция загрузки в модель эксперементальных данных
195
+
196
+ Args:
197
+ teoretic_x: Вектор временных точек теоретических значений
198
+ teoretic_y: Матрица с теоретическими значениями
199
+ know_compartments: Вектор с номерами камер, по которым есть данные
200
+ c0: Вектор нулевых концентраций
201
+ d: Вводимая доза
202
+ compartment_number: Номер камеры в которую вводится доза
203
+
204
+ Returns:
205
+ None
206
+ """
207
+ self.teoretic_x = np.array(teoretic_x)
208
+ self.teoretic_y = np.array(teoretic_y)
209
+ self.know_compartments = know_compartments
210
+ self.teoretic_avg = np.average(self.teoretic_y, axis=1)
211
+ self.teoretic_avg = np.repeat(self.teoretic_avg, self.teoretic_x.size)
212
+ self.teoretic_avg = np.reshape(self.teoretic_avg, self.teoretic_y.shape)
213
+ assert any([c0, d, compartment_number is not None]), "Need to set c0 or d and compartment_number"
214
+ if not c0:
215
+ assert all([d, compartment_number is not None]), "Need to set d and compartment_number"
216
+ self.d = d
217
+ self.compartment_number = compartment_number
218
+ self.c0 = None
219
+ else:
220
+ self.c0 = np.array(c0)
221
+ self.w = np.ones(self.teoretic_y.shape) if w is None else np.array(w)
222
+
223
+ def optimize(self, method=None, user_method=None, method_is_func=True,
224
+ optimization_func_name='__call__', max_step=0.01, metric='R2', **kwargs):
225
+ """
226
+ Функция оптимизации модели
227
+
228
+ Args:
229
+ method: Метод оптимизации, любой доступный minimize + 'country_optimization' и 'country_optimization_v2'
230
+ max_step: Максимальный шаг при решении СДУ
231
+ **kwargs: Дополнительные именованные аргументы
232
+
233
+ Returns:
234
+ None
235
+ """
236
+ self._optim = True
237
+ f = lambda x: self._target_function(x, max_step=max_step, metric=metric)
238
+ if user_method is not None:
239
+ if method_is_func:
240
+ x = user_method(f, **kwargs)
241
+ else:
242
+ optimization_obj = user_method(f, **kwargs)
243
+ x = getattr(optimization_obj, optimization_func_name)()
244
+ else:
245
+ if method == 'country_optimization':
246
+ CA = CountriesAlgorithm(
247
+ f=f,
248
+ memory_list=getattr(self, 'memory', None),
249
+ **kwargs
250
+ )
251
+ CA.start()
252
+ x = CA.countries[0].population[0].x
253
+ elif method == 'country_optimization_v2':
254
+ CA = CountriesAlgorithm_v2(
255
+ f=f,
256
+ **kwargs
257
+ )
258
+ CA.start()
259
+ x = CA.countries[0].population[0].x
260
+ elif method == 'GA':
261
+ CA = GeneticAlgorithm(
262
+ f=f,
263
+ **kwargs
264
+ )
265
+ x = CA.start()
266
+ else:
267
+ res = minimize(
268
+ fun=f,
269
+ method=method,
270
+ **kwargs
271
+ )
272
+ x = res.x
273
+ if self.configuration_matrix_target:
274
+ self.configuration_matrix[self.configuration_matrix_target] = x[:self.configuration_matrix_target_count]
275
+ if self.outputs_target:
276
+ self.outputs[self.outputs_target] = x[
277
+ self.configuration_matrix_target_count:self.configuration_matrix_target_count + self.outputs_target_count]
278
+ if self.volumes_target:
279
+ 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]
280
+ self.configuration_matrix_target = None
281
+ self.outputs_target = None
282
+ self.volumes_target = None
283
+ self._optim = False
284
+ return x
285
+
286
+ def plot_model(self, compartment_numbers=None, compartment_names=None, left=None, right=None, y_lims={}, **kwargs):
287
+ """
288
+ Функция для построения графиков модели
289
+
290
+ Args:
291
+ compartment_numbers: Камеры, которые нужно отобразить (если не указать, отобразим все)
292
+ compartment_names: Имена камер
293
+ """
294
+ if compartment_numbers:
295
+ compartment_numbers = np.array(compartment_numbers)
296
+ else:
297
+ compartment_numbers = np.arange(self.outputs.size)
298
+ if not compartment_names:
299
+ compartment_names = {}
300
+ if not y_lims:
301
+ y_lims = {}
302
+ self(**kwargs)
303
+ for i in compartment_numbers:
304
+ if hasattr(self, "teoretic_x") and hasattr(self, "teoretic_y") and i in self.know_compartments:
305
+ plt.plot(self.teoretic_x, self.teoretic_y[self.know_compartments.index(i)], "*r")
306
+ plt.plot(self.last_result.t, self.last_result.y[i])
307
+ plt.title(compartment_names.get(i, i))
308
+ plt.xlim(left=left, right=right)
309
+ if y_lims.get(i):
310
+ plt.ylim(y_lims.get(i))
311
+ plt.grid()
312
+ try:
313
+ plt.show()
314
+ except AttributeError:
315
+ plt.savefig(f'{compartment_names.get(i, str(i))}.png')
316
+ plt.cla()
317
+
318
+
319
+ class MagicCompartmentModel(BaseCompartmentModel):
320
+
321
+ need_magic_optimization = False
322
+
323
+ def __init__(self, configuration_matrix, outputs, volumes=None, magic_coefficient=1, exclude_compartments=None, numba_option=False, use_shared_memory=False):
324
+ super().__init__(configuration_matrix, outputs, volumes, numba_option, use_shared_memory)
325
+ self.magic_coefficient = magic_coefficient
326
+ self.exclude_compartments = np.array(exclude_compartments) if bool(exclude_compartments) else np.array([])
327
+ self.need_magic_optimization = self.magic_coefficient is None
328
+ if getattr(self, "memory", None):
329
+ self.memory.shm.close()
330
+ self.memory.shm.unlink()
331
+ self.memory_size += int(self.need_magic_optimization)
332
+ self.memory = shared_memory.ShareableList(
333
+ sequence=self.memory_size * [None]
334
+ )
335
+ self.memory_name = self.memory.shm.name
336
+
337
+ def __call__(self, t_max, c0=None, d=None, compartment_number=None, max_step=0.01, t_eval=None):
338
+ if not self._optim and not self.magic_coefficient:
339
+ raise Exception("Magic_coefficient parameter not specified")
340
+ res = super().__call__(t_max, c0, d, compartment_number, max_step, t_eval)
341
+ magic_arr = np.ones(self.configuration_matrix.shape[0]) * self.magic_coefficient
342
+ if self.exclude_compartments:
343
+ magic_arr[self.exclude_compartments] = 1
344
+ magic_arr = np.repeat(magic_arr, res.y.shape[1])
345
+ magic_arr = np.reshape(magic_arr, res.y.shape)
346
+ res.y = magic_arr * res.y
347
+ self.last_result = res
348
+ return res
349
+
350
+ def load_data_from_list(self, x):
351
+ super().load_data_from_list(x)
352
+ if self.need_magic_optimization:
353
+ self.magic_coefficient = x[-1]
354
+
355
+ def optimize(self, method=None, user_method=None, method_is_func=True,
356
+ optimization_func_name='__call__', max_step=0.01, **kwargs):
357
+ x = super().optimize(method, user_method, method_is_func, optimization_func_name, max_step, **kwargs)
358
+ if self.need_magic_optimization:
359
+ self.magic_coefficient = x[-1]
360
+ self.need_magic_optimization = False
361
+ return x
362
+
363
+
364
+ class ReleaseCompartmentModel(BaseCompartmentModel):
365
+
366
+ need_v_release_optimization = False
367
+ release_parameters_target = None
368
+ accumulation_parameters_target = None
369
+
370
+ class ReleaseRK45(RK45):
371
+
372
+ def __init__(self, fun, t0, y0, t_bound, release_function, compartment_number, c0, max_step=np.inf,
373
+ with_accumulate=False, accumulation_function=None, accumulation_type=1, rtol=1e-3, atol=1e-6, vectorized=False,
374
+ first_step=None, **extraneous):
375
+ super().__init__(fun, t0, y0, t_bound, max_step=max_step,
376
+ rtol=rtol, atol=atol, vectorized=vectorized,
377
+ first_step=first_step, **extraneous)
378
+ self.release_function = release_function
379
+ self.compartment_number = compartment_number
380
+ self.c0 = c0
381
+ self.old_release_correction = 0
382
+ self.old_accumulation_correction = c0
383
+ self.with_accumulate = with_accumulate
384
+ self.accumulation_function = accumulation_function
385
+ self.accumulation_type = accumulation_type
386
+
387
+ def _step_impl(self):
388
+ result = super()._step_impl()
389
+ release_correction = self.release_function(self.t, self.c0)
390
+ minus_accumulation = 0
391
+ if self.with_accumulate:
392
+ if self.accumulation_type == 1:
393
+ accumulation_correction = self.accumulation_function(self.t, self.c0 )
394
+ minus_accumulation = self.old_accumulation_correction - accumulation_correction
395
+ elif self.accumulation_type == 2:
396
+ coef_accumulation = self.accumulation_function(self.t, self.c0) / self.c0
397
+ plus_release = release_correction - self.old_release_correction
398
+ all_corrections = plus_release
399
+ if self.accumulation_type == 1:
400
+ if plus_release - minus_accumulation > 0:
401
+ all_corrections = plus_release - minus_accumulation
402
+ elif self.accumulation_type == 2:
403
+ all_corrections *= coef_accumulation
404
+ self.y[self.compartment_number] += all_corrections
405
+ self.old_release_correction = release_correction
406
+ if self.with_accumulate and self.accumulation_type == 1:
407
+ self.old_accumulation_correction = accumulation_correction
408
+ return result
409
+
410
+ def __init__(self, v_release, release_parameters, release_compartment,
411
+ release_function=None, with_accumulate=False, accumulation_function=None,
412
+ accumulation_parameters=[None], accumulation_type=1, *args, **kwargs):
413
+ """
414
+ Камерная модель с высвобождением для описания фармакокинетики системы
415
+
416
+ Неизвестные параметры при необходимости задаются как None
417
+ например configuration_matrix = [[0, 1], [None, 0]]
418
+
419
+ Args:
420
+ configuration_matrix: Настроечная матрица модели, отображающая константы перехода между матрицами
421
+ outputs: Вектор констант перехода во вне камер
422
+ volumes: Объемы камер
423
+ v_release: Объем гепотетической камеры из которой происходит высвобождение
424
+ release_parameters: Параметры функции высвобождения
425
+ release_compartment: Номер камеры в которую происходит высвобождение
426
+ release_function: Функция высвобождения по умолчанию f(t,m,b,c) = c0 * c * t ** b / (t ** b + m)
427
+ """
428
+ super().__init__(*args, **kwargs)
429
+ self.release_parameters = np.array(release_parameters)
430
+ self.release_parameters_target_count = 0
431
+ if np.any(self.release_parameters == None):
432
+ self.release_parameters_target = np.where(self.release_parameters == None)
433
+ self.release_parameters_target_count = np.sum(self.release_parameters == None)
434
+ self.v_release = v_release
435
+ if self.v_release is None:
436
+ self.need_v_release_optimization = True
437
+ self.release_compartment = release_compartment
438
+ self.release_function = release_function
439
+ self.with_accumulate = with_accumulate
440
+ self.accumulation_type = accumulation_type
441
+
442
+ if self.with_accumulate:
443
+ self.accumulation_function = accumulation_function
444
+ self.accumulation_parameters = np.array(accumulation_parameters)
445
+ if np.any(self.accumulation_parameters == None):
446
+ self.accumulation_parameters_target = np.where(self.accumulation_parameters == None)
447
+ self.accumulation_parameters_target_count = np.sum(self.accumulation_parameters == None)
448
+
449
+ if getattr(self, "memory", None):
450
+ self.memory.shm.close()
451
+ self.memory.shm.unlink()
452
+ self.memory_size += self.release_parameters_target_count + int(self.need_v_release_optimization)
453
+ self.memory = shared_memory.ShareableList(
454
+ sequence=self.memory_size * [None]
455
+ )
456
+ self.memory_name = self.memory.shm.name
457
+
458
+ def load_data_from_list(self, x):
459
+ super().load_data_from_list(x)
460
+ s = self.configuration_matrix_target_count + self.outputs_target_count + self.volumes_target_count
461
+ if self.release_parameters_target:
462
+ self.release_parameters[self.release_parameters_target] = x[s:s + self.release_parameters_target_count]
463
+ if self.need_v_release_optimization:
464
+ self.v_release = x[s + self.release_parameters_target_count]
465
+ if self.with_accumulate:
466
+ if self.accumulation_parameters_target:
467
+ s += self.release_parameters_target_count + int(self.need_v_release_optimization)
468
+ self.accumulation_parameters[self.accumulation_parameters_target] = x[s:s + self.accumulation_parameters_target_count]
469
+
470
+ def _default_release_function(self, t, c0):
471
+ """
472
+ Функция для поправки на высвобождение
473
+ """
474
+ m, b, c = self.release_parameters
475
+ return c0 * c * t ** b / (t ** b + m)
476
+
477
+ def get_release_function(self):
478
+ if self.release_function is not None:
479
+ return lambda t, c0: self.release_function(t, c0, *self.release_parameters)
480
+ else:
481
+ return self._default_release_function
482
+
483
+ def _default_accumulation_function(self, t, c0):
484
+ """
485
+ Функция для поправки на накопление
486
+ """
487
+ k, = self.accumulation_parameters
488
+ return c0 * np.exp(-k * t)
489
+
490
+ def get_accumulation_function(self):
491
+ if self.accumulation_function is not None:
492
+ return lambda t, c0: self.accumulation_function(t, c0, *self.accumulation_parameters)
493
+ else:
494
+ return self._default_accumulation_function
495
+
496
+ def __call__(self, t_max, c0=None, d=None, max_step=0.01, t_eval=None, **kwargs):
497
+ """
498
+ Расчет кривых концентраций по фармакокинетической модели
499
+
500
+ Args:
501
+ t_max: Предельное время расчета
502
+ c0: Начальная концентрация в камере из которой высвобождается вещество
503
+ d: Вводимая доза
504
+ max_step: Максимальный шаг при решении СДУ
505
+ t_eval: Временные точки, в которых необходимо молучить решение
506
+
507
+ Returns:
508
+ Результат работы решателя scipy solve_ivp
509
+ """
510
+ if not self._optim:
511
+ assert (not any([self.configuration_matrix_target, self.outputs_target, self.volumes_target])), \
512
+ "It is impossible to make a calculation with unknown parameters"
513
+ assert any([c0 is not None, d]), "Need to set c0 or d and compartment_number"
514
+ if c0 is None:
515
+ assert d, "Need to set d"
516
+ c0 = d / self.v_release
517
+ ts = [0, t_max]
518
+ y0 = np.zeros(self.outputs.shape)
519
+ self.last_result = solve_ivp(
520
+ fun=self._compartment_model if
521
+ not self.numba_option
522
+ else lambda t, c: self._numba_compartment_model(t, c,
523
+ self.configuration_matrix.astype(
524
+ np.float64),
525
+ self.outputs.astype(
526
+ np.float64),
527
+ self.volumes.astype(
528
+ np.float64)),
529
+ t_span=ts,
530
+ y0=y0,
531
+ max_step=max_step,
532
+ t_eval=t_eval,
533
+ method=self.ReleaseRK45,
534
+ release_function=self.get_release_function(),
535
+ compartment_number=self.release_compartment,
536
+ with_accumulate=self.with_accumulate,
537
+ accumulation_function=self.get_accumulation_function() if self.with_accumulate else None,
538
+ accumulation_type=self.accumulation_type,
539
+ c0=c0
540
+ )
541
+ self.last_result.model_realized = c0 - self.get_release_function()(self.last_result.t, c0)
542
+ if self.with_accumulate:
543
+ model_accumulation = self.get_accumulation_function()(self.last_result.t, c0)
544
+ if self.accumulation_type == 1:
545
+ self.last_result.model_realized = model_accumulation - self.get_release_function()(self.last_result.t, c0)
546
+ elif self.accumulation_type == 2:
547
+ accumulation_coeffs = model_accumulation / c0
548
+ self.last_result.model_realized= accumulation_coeffs * self.get_release_function()(self.last_result.t, c0)
549
+ self.last_result.model_realized = model_accumulation - self.last_result.model_realized
550
+ return self.last_result
551
+
552
+ def _target_function(self, x, max_step=0.01, metric='R2'):
553
+ """
554
+ Функция расчета значения целевой функции
555
+
556
+ Args:
557
+ x: Значение искомых параметров модели
558
+ max_step: Максимальный шаг при решении СДУ
559
+
560
+ Returns:
561
+ Значение целевой функции, характеризующее отклонение от эксперементальных данных
562
+ """
563
+ self.load_data_from_list(x)
564
+ c0 = self.c0
565
+ if c0 is None:
566
+ c0 = self.d / self.v_release
567
+ self(
568
+ t_max=np.max(self.teoretic_x),
569
+ c0=c0,
570
+ t_eval=self.teoretic_x,
571
+ max_step=max_step
572
+ )
573
+ target_results = self.last_result.y[tuple(self.know_compartments), :]
574
+ if metric == 'R2':
575
+ plus = 0
576
+ if self.teoretic_realized is not None:
577
+ model_realized = self.last_result.model_realized
578
+ plus = np.sum(((model_realized - self.teoretic_realized) ** 2) / ((self.teoretic_realized - self.teoretic_realized_avg) ** 2))
579
+ 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))
580
+ elif metric == 'norm':
581
+ plus = 0
582
+ if self.teoretic_realized is not None:
583
+ model_realized = self.last_result.model_realized
584
+ plus = np.linalg.norm(self.teoretic_realized - model_realized)
585
+ return plus + np.linalg.norm(target_results - self.teoretic_y)
586
+ else:
587
+ 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))
588
+
589
+ def load_optimization_data(self, teoretic_x, teoretic_y, know_compartments,
590
+ w = None, c0=None, d=None, compartment_number=None, teoretic_realized=None):
591
+ """
592
+ Функция загрузки в модель эксперементальных данных
593
+
594
+ Args:
595
+ teoretic_x: Вектор временных точек теоретических значений
596
+ teoretic_y: Матрица с теоретическими значениями
597
+ know_compartments: Вектор с номерами камер, по которым есть данные
598
+ c0: Начальная концентрация в камере из которой высвобождается вещество
599
+ d: Вводимая доза
600
+
601
+ Returns:
602
+ None
603
+ """
604
+ self.teoretic_x = np.array(teoretic_x)
605
+ self.teoretic_y = np.array(teoretic_y)
606
+ self.teoretic_realized = np.array(teoretic_realized) if teoretic_realized is not None else teoretic_realized
607
+ if teoretic_realized is not None:
608
+ self.teoretic_realized_avg = np.average(self.teoretic_realized)
609
+ self.know_compartments = know_compartments
610
+ self.teoretic_avg = np.average(self.teoretic_y, axis=1)
611
+ self.teoretic_avg = np.repeat(self.teoretic_avg, self.teoretic_x.size)
612
+ self.teoretic_avg = np.reshape(self.teoretic_avg, self.teoretic_y.shape)
613
+ assert any([c0, d, compartment_number is not None]), "Need to set c0 or d and compartment_number"
614
+ if not c0:
615
+ assert all([d, compartment_number is not None]), "Need to set d and compartment_number"
616
+ self.d = d
617
+ self.c0 = None
618
+ else:
619
+ self.c0 = np.array(c0)
620
+ self.w = np.ones(self.teoretic_y.shape) if w is None else np.array(w)
621
+
622
+ def optimize(self, method=None, user_method=None, method_is_func=True,
623
+ optimization_func_name='__call__', max_step=0.01, **kwargs):
624
+ x = super().optimize(method, user_method, method_is_func, optimization_func_name, max_step, **kwargs)
625
+ s = self.configuration_matrix_target_count + self.outputs_target_count + self.volumes_target_count
626
+ if self.release_parameters_target:
627
+ self.release_parameters[self.release_parameters_target] = x[s:s + self.release_parameters_target_count]
628
+ if self.need_v_release_optimization:
629
+ self.v_release = x[s:s + self.release_parameters_target_count + 1]
630
+ self.need_v_release_optimization = False
631
+ if self.with_accumulate:
632
+ if self.accumulation_parameters_target:
633
+ s += self.release_parameters_target_count + int(self.need_v_release_optimization)
634
+ self.accumulation_parameters[self.accumulation_parameters_target] = x[s:s + self.accumulation_parameters_target_count]
635
+ return x
636
+
637
+ def plot_model(self, compartment_numbers=None, compartment_names={},
638
+ left=None, right=None, y_lims={}, plot_accumulation=False, **kwargs):
639
+ super().plot_model(compartment_numbers, compartment_names, left, right, y_lims, **kwargs)
640
+ if plot_accumulation:
641
+ if hasattr(self, "teoretic_x") and hasattr(self, "teoretic_realized"):
642
+ plt.plot(self.teoretic_x, self.teoretic_realized, "*r")
643
+ plt.plot(self.last_result.t, self.last_result.model_realized)
644
+ plt.title(compartment_names.get('realized', 'realized'))
645
+ plt.xlim(left=left, right=right)
646
+ if y_lims.get('realized'):
647
+ plt.ylim(y_lims.get('realized'))
648
+ plt.grid()
649
+ plt.show()
650
+
651
+
652
+ class TwoSubstancesCompartmentModel(MagicCompartmentModel):
653
+
654
+ released_configuration_matrix_target = None
655
+ released_outputs_target = None
656
+ release_parameters_target = None
657
+ has_teoretic_y = False
658
+ has_teoretic_released = False
659
+
660
+ class TwoSubstancesRK45(RK45):
661
+
662
+ def __init__(self, fun, t0, y0, t_bound, release_function, max_step=np.inf,
663
+ rtol=1e-3, atol=1e-6, vectorized=False,
664
+ first_step=None, **extraneous):
665
+ self.old_corrections = 0
666
+ super().__init__(fun, t0, y0, t_bound, max_step=max_step,
667
+ rtol=rtol, atol=atol, vectorized=vectorized,
668
+ first_step=first_step, **extraneous)
669
+ self.release_function = release_function
670
+
671
+ def _step_impl(self):
672
+ result = super()._step_impl()
673
+ release_correction: float = self.release_function(self.y, self.t)
674
+ correction = release_correction - self.old_corrections
675
+ c = self.y[:self.y.size // 2]
676
+ real_release_correction = np.append(-1 * correction * c, correction * c)
677
+ self.y += real_release_correction
678
+ self.old_corrections = release_correction
679
+ return result
680
+
681
+ def __init__(self, configuration_matrix, outputs, released_configuration_matrix, released_outputs,
682
+ release_parameters, release_function=None,
683
+ volumes=None, magic_coefficient=1, exclude_compartments=None,
684
+ numba_option=False, use_shared_memory=False):
685
+ super().__init__(
686
+ configuration_matrix=configuration_matrix,
687
+ outputs=outputs,
688
+ volumes=volumes,
689
+ magic_coefficient=magic_coefficient,
690
+ exclude_compartments=exclude_compartments,
691
+ numba_option=numba_option,
692
+ use_shared_memory=use_shared_memory
693
+ )
694
+ self.released_configuration_matrix = np.array(released_configuration_matrix)
695
+ self.released_configuration_matrix_target_count = 0
696
+ if np.any(self.released_configuration_matrix == None):
697
+ self.released_configuration_matrix_target = np.where(self.released_configuration_matrix == None)
698
+ self.released_configuration_matrix_target_count = np.sum(self.released_configuration_matrix == None)
699
+ self.released_outputs = np.array(released_outputs)
700
+ self.released_outputs_target_count = 0
701
+ if np.any(self.released_outputs == None):
702
+ self.released_outputs_target = np.where(self.released_outputs == None)
703
+ self.released_outputs_target_count = np.sum(self.released_outputs == None)
704
+ self.release_parameters = np.array(release_parameters)
705
+ if np.any(self.release_parameters == None):
706
+ self.release_parameters_target = np.where(self.release_parameters == None)
707
+ self.release_parameters_target_count = np.sum(self.release_parameters == None)
708
+ self.release_function = release_function
709
+ if getattr(self, "memory", None):
710
+ self.memory.shm.close()
711
+ self.memory.shm.unlink()
712
+ self.memory_size += (self.release_parameters_target_count +
713
+ self.released_configuration_matrix_target_count + self.released_outputs_target_count)
714
+ self.memory = shared_memory.ShareableList(
715
+ sequence=self.memory_size * [None]
716
+ )
717
+ self.memory_name = self.memory.shm.name
718
+
719
+ def __call__(self, t_max, c0=None, d=None, compartment_number=None, max_step=0.01, t_eval=None, **kwargs):
720
+ """
721
+ Расчет кривых концентраций по фармакокинетической модели
722
+
723
+ Args:
724
+ t_max: Предельное время расчета
725
+ c0: Начальная концентрация в камере из которой высвобождается вещество
726
+ d: Вводимая доза
727
+ max_step: Максимальный шаг при решении СДУ
728
+ t_eval: Временные точки, в которых необходимо молучить решение
729
+
730
+ Returns:
731
+ Результат работы решателя scipy solve_ivp
732
+ """
733
+ if not self._optim:
734
+ assert (not any([self.configuration_matrix_target, self.outputs_target, self.volumes_target])), \
735
+ "It is impossible to make a calculation with unknown parameters"
736
+ assert any([c0 is not None, d]), "Need to set c0 or d and compartment_number"
737
+
738
+
739
+ if c0 is None:
740
+ assert all([d, compartment_number is not None]), "Need to set d and compartment_number"
741
+ c0 = np.zeros((2, self.outputs.size))
742
+ c0[0][compartment_number] = d / self.volumes[compartment_number]
743
+ else:
744
+ c0 = np.array(c0)
745
+ c0 = c0.reshape((1, c0.size))[0]
746
+ ts = [0, t_max]
747
+ res = solve_ivp(
748
+ fun=self._compartment_model if
749
+ not self.numba_option
750
+ else lambda t, c: self._numba_compartment_model(t, c,
751
+ self.configuration_matrix.astype(
752
+ np.float64),
753
+ self.released_configuration_matrix.astype(
754
+ np.float64),
755
+ self.outputs.astype(
756
+ np.float64),
757
+ self.released_outputs.astype(
758
+ np.float64),
759
+ self.volumes.astype(
760
+ np.float64)),
761
+ t_span=ts,
762
+ y0=c0,
763
+ max_step=max_step,
764
+ t_eval=t_eval,
765
+ method=self.TwoSubstancesRK45,
766
+ release_function=self.get_release_function()
767
+ )
768
+ magic_arr = np.ones(self.configuration_matrix.shape[0] + self.released_configuration_matrix.shape[0]) * self.magic_coefficient
769
+ if self.exclude_compartments.size:
770
+ magic_arr[self.exclude_compartments] = 1
771
+ magic_arr = np.repeat(magic_arr, res.y.shape[1])
772
+ magic_arr = np.reshape(magic_arr, res.y.shape)
773
+ res.y = magic_arr * res.y
774
+ self.last_result = res
775
+ return self.last_result
776
+
777
+ def _default_release_function(self, current_c, t):
778
+ """
779
+ Функция для поправки на высвобождение
780
+ """
781
+ # c_resh = c.reshape((2, c.size // 2))
782
+ # k = self.release_parameters[0]
783
+ # return k * c_resh[0]
784
+ m, b, c = self.release_parameters
785
+ return c * t ** b / (t ** b + m)
786
+
787
+ def get_release_function(self):
788
+ if self.release_function is not None:
789
+ return lambda c, t: self.release_function(c, t, *self.release_parameters)
790
+ else:
791
+ return self._default_release_function
792
+
793
+ def _compartment_model(self, t, c):
794
+ """
795
+ Функция для расчета камерной модели
796
+
797
+ Args:
798
+ t: Текущее время
799
+ c: Вектор концентраций
800
+
801
+ Returns:
802
+ Вектор изменений концентраций (c) в момент времени (t)
803
+ """
804
+ c_resh = c.reshape((2, c.size // 2))
805
+ c_primary = c_resh[0]
806
+ dc_dt = (self.configuration_matrix.T @ (c_primary * self.volumes) \
807
+ - self.configuration_matrix.sum(axis=1) * (c_primary * self.volumes)\
808
+ - self.outputs * (c_primary * self.volumes)) / self.volumes
809
+ c_released = c_resh[1]
810
+ dc_dt_released = (self.released_configuration_matrix.T @ (c_released * self.volumes) \
811
+ - self.released_configuration_matrix.sum(axis=1) * (c_released * self.volumes) \
812
+ - self.released_outputs * (c_released * self.volumes)) / self.volumes
813
+ np.append(dc_dt, dc_dt_released)
814
+
815
+ @staticmethod
816
+ @njit
817
+ def _numba_compartment_model(t, c, configuration_matrix, released_configuration_matrix, outputs, released_outputs, volumes):
818
+ """
819
+ Функция для расчета камерной модели
820
+
821
+ Args:
822
+ t: Текущее время
823
+ c: Вектор концентраций
824
+
825
+ Returns:
826
+ Вектор изменений концентраций (c) в момент времени (t)
827
+ """
828
+ c_resh = c.reshape((2, c.size // 2))
829
+ c_primary = c_resh[0]
830
+ dc_dt = (configuration_matrix.T @ (c_primary * volumes) \
831
+ - configuration_matrix.sum(axis=1) * (c_primary * volumes) \
832
+ - outputs * (c_primary * volumes)) / volumes
833
+ c_released = c_resh[1]
834
+ dc_dt_released = (released_configuration_matrix.T @ (c_released * volumes) \
835
+ - released_configuration_matrix.sum(axis=1) * (c_released * volumes) \
836
+ - released_outputs * (c_released * volumes)) / volumes
837
+ return np.append(dc_dt, dc_dt_released)
838
+
839
+ def load_optimization_data(self, teoretic_x, teoretic_y=None, teoretic_released=None, know_compartments=None, know_released_compartments=None,
840
+ w=None, released_w=None, c0=None, d=None, compartment_number=None):
841
+ self.has_teoretic_y = bool(teoretic_y)
842
+ self.has_teoretic_released = bool(teoretic_released)
843
+ self.teoretic_x = np.array(teoretic_x)
844
+ if self.has_teoretic_y:
845
+ self.teoretic_y = np.array(teoretic_y if teoretic_y is not None else [[]])
846
+ self.know_compartments = know_compartments if know_compartments is not None else []
847
+ self.teoretic_avg = np.average(self.teoretic_y, axis=1)
848
+ self.teoretic_avg = np.repeat(self.teoretic_avg, self.teoretic_x.size)
849
+ self.teoretic_avg = np.reshape(self.teoretic_avg, self.teoretic_y.shape)
850
+ self.w = np.ones(self.teoretic_y.shape) if w is None else np.array(w)
851
+ if self.has_teoretic_released:
852
+ self.teoretic_released = np.array(teoretic_released if teoretic_released is not None else [[]])
853
+ self.know_released_compartments = know_released_compartments if know_released_compartments is not None else []
854
+ self.released_avg = np.average(self.teoretic_released, axis=1)
855
+ self.released_avg = np.repeat(self.released_avg, self.teoretic_x.size)
856
+ self.released_avg = np.reshape(self.released_avg, self.teoretic_released.shape)
857
+ self.released_w = np.ones(self.teoretic_released.shape) if released_w is None else np.array(released_w)
858
+
859
+ assert any([c0, d, compartment_number is not None]), "Need to set c0 or d and compartment_number"
860
+ if not c0:
861
+ assert all([d, compartment_number is not None]), "Need to set d and compartment_number"
862
+ self.d = d
863
+ self.compartment_number = compartment_number
864
+ self.c0 = None
865
+ else:
866
+ self.c0 = np.array(c0)
867
+
868
+
869
+ def load_data_from_list(self, x):
870
+ super().load_data_from_list(x)
871
+ n = self.configuration_matrix_target_count + self.outputs_target_count + self.volumes_target_count
872
+ if self.released_configuration_matrix_target:
873
+ self.released_configuration_matrix[self.released_configuration_matrix_target] = x[n:n + self.released_configuration_matrix_target_count]
874
+ n += self.released_configuration_matrix_target_count
875
+ if self.released_outputs_target:
876
+ self.released_outputs[self.released_outputs_target] = x[n:n + self.released_outputs_target_count]
877
+ n += self.released_outputs_target_count
878
+ if self.release_parameters_target:
879
+ self.release_parameters[self.release_parameters_target] = x[n:n + self.release_parameters_target_count]
880
+
881
+ def _target_function(self, x, max_step=0.01, metric='R2'):
882
+ self.load_data_from_list(x)
883
+ c0 = self.c0
884
+ if c0 is None:
885
+ c0 = np.zeros((2, self.outputs.size))
886
+ c0[0][self.compartment_number] = self.d / self.volumes[self.compartment_number]
887
+ self(
888
+ t_max=np.max(self.teoretic_x),
889
+ c0=c0,
890
+ t_eval=self.teoretic_x,
891
+ max_step=max_step
892
+ )
893
+ if not self.last_result.success:
894
+ return BIG_VALUE
895
+ if self.has_teoretic_y:
896
+ target_results = self.last_result.y[tuple(self.know_compartments), :]
897
+ if self.has_teoretic_released:
898
+ t = tuple([self.configuration_matrix.shape[0] + i for i in self.know_released_compartments])
899
+ released_target_results = self.last_result.y[t, :]
900
+ if metric == 'R2':
901
+ if self.has_teoretic_y:
902
+ a = 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))
903
+ else:
904
+ a = 0
905
+ if self.has_teoretic_released:
906
+ b = np.sum(np.sum(self.released_w * ((released_target_results - self.teoretic_released) ** 2), axis=1) / np.sum(
907
+ (self.released_avg - self.teoretic_released) ** 2, axis=1))
908
+ else:
909
+ b = 0
910
+ return a + b
911
+ elif metric == 'norm':
912
+ if self.has_teoretic_y:
913
+ a = np.linalg.norm(target_results - self.teoretic_y)
914
+ else:
915
+ a = 0
916
+ if self.has_teoretic_released:
917
+ b = np.linalg.norm(released_target_results - self.teoretic_released)
918
+ else:
919
+ b = 0
920
+ return a + b
921
+ else:
922
+ if self.has_teoretic_y:
923
+ a = np.sum(np.sum(self.w * ((target_results - self.teoretic_y) ** 2), axis=1) / np.sum(
924
+ (self.teoretic_avg - self.teoretic_y) ** 2, axis=1))
925
+ else:
926
+ a = 0
927
+ if self.has_teoretic_released:
928
+ b = np.sum(np.sum(self.released_w * ((released_target_results - self.teoretic_released) ** 2), axis=1) / np.sum(
929
+ (self.released_avg - self.teoretic_released) ** 2, axis=1))
930
+ else:
931
+ b = 0
932
+ return a + b
933
+
934
+ def optimize(self, method=None, user_method=None, method_is_func=True,
935
+ optimization_func_name='__call__', max_step=0.01, **kwargs):
936
+ x = super().optimize(method, user_method, method_is_func, optimization_func_name, max_step, **kwargs)
937
+ n = self.configuration_matrix_target_count + self.outputs_target_count + self.volumes_target_count + int(
938
+ self.need_magic_optimization)
939
+ if self.released_configuration_matrix_target:
940
+ self.released_configuration_matrix[self.released_configuration_matrix_target] = x[n:n + self.released_configuration_matrix_target_count]
941
+ n += self.released_configuration_matrix_target_count
942
+ if self.released_outputs_target:
943
+ self.released_outputs[self.released_outputs_target] = x[n: n + self.released_outputs_target_count]
944
+ n += self.released_outputs_target_count
945
+ if self.release_parameters_target:
946
+ self.release_parameters[self.release_parameters_target] = x[n:n + self.release_parameters_target_count]
947
+ self.released_configuration_matrix_target = None
948
+ self.released_outputs_target = None
949
+ return x
950
+
951
+ def plot_model(self, compartment_numbers=None, released_compartment_numbers=None,
952
+ released_compartment_names=None, compartment_names=None, left=None, right=None,
953
+ y_lims=None, released_y_lims=None, **kwargs):
954
+ """
955
+ Функция для построения графиков модели
956
+
957
+ Args:
958
+ compartment_numbers: Камеры, которые нужно отобразить (если не указать, отобразим все)
959
+ compartment_names: Имена камер
960
+ """
961
+ super().plot_model(compartment_numbers=compartment_numbers, compartment_names=compartment_names,
962
+ left=left, right=right, y_lims=y_lims, **kwargs)
963
+
964
+ if not released_compartment_numbers:
965
+ released_compartment_numbers = []
966
+ if not released_compartment_names:
967
+ released_compartment_names = {}
968
+ if not released_y_lims:
969
+ released_y_lims = {}
970
+ for i in released_compartment_numbers:
971
+ j = i + self.outputs.size
972
+ if hasattr(self, "teoretic_x") and hasattr(self, "teoretic_released") and i in self.know_released_compartments:
973
+ plt.plot(self.teoretic_x, self.teoretic_released[self.know_compartments.index(i)], "*r")
974
+ plt.plot(self.last_result.t, self.last_result.y[j])
975
+ plt.title(released_compartment_names.get(i, j))
976
+ plt.xlim(left=left, right=right)
977
+ if released_y_lims.get(i):
978
+ plt.ylim(released_y_lims.get(i))
979
+ plt.grid()
980
+ try:
981
+ plt.show()
982
+ except AttributeError:
983
+ plt.savefig(f'{released_compartment_names.get(i, str(j))}.png')
984
+ plt.cla()
985
+