pypharm 1.5.2__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,639 +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
-
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
- method='LSODA'
132
- )
133
- return self.last_result
134
-
135
- def get_kinetic_params(self, t_max, d, compartment_number, max_step=0.01):
136
- one_hour_result = self(t_max=1, d=d, compartment_number=compartment_number, max_step=max_step)
137
- auc_1h = simps(one_hour_result.y[compartment_number], one_hour_result.t)
138
- self(t_max=t_max, d=d, compartment_number=compartment_number, max_step=max_step)
139
- auc = simps(self.last_result.y[compartment_number], self.last_result.t)
140
- result_dict = {
141
- 'c_max': self.last_result.y[compartment_number].max(),
142
- 'V': self.volumes[compartment_number] if self.volumes is not None else None,
143
- 'AUC': auc,
144
- 'AUC_1h': auc_1h,
145
- 'Cl': d / auc
146
- }
147
- return result_dict
148
-
149
-
150
- def load_data_from_list(self, x):
151
- if self.configuration_matrix_target:
152
- self.configuration_matrix[self.configuration_matrix_target] = x[:self.configuration_matrix_target_count]
153
- if self.outputs_target:
154
- self.outputs[self.outputs_target] = x[
155
- self.configuration_matrix_target_count:self.configuration_matrix_target_count + self.outputs_target_count]
156
- if self.volumes_target:
157
- 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]
158
-
159
- def _target_function(self, x, max_step=0.01, metric='R2'):
160
- """
161
- Функция расчета значения целевой функции
162
-
163
- Args:
164
- x: Значение искомых параметров модели
165
- max_step: Максимальный шаг при решении СДУ
166
-
167
- Returns:
168
- Значение целевой функции, характеризующее отклонение от эксперементальных данных
169
- """
170
- self.load_data_from_list(x)
171
- c0 = self.c0
172
- if c0 is None:
173
- c0 = np.zeros(self.outputs.size)
174
- c0[self.compartment_number] = self.d / self.volumes[self.compartment_number]
175
- self(
176
- t_max=np.max(self.teoretic_x),
177
- c0=c0,
178
- t_eval=self.teoretic_x,
179
- max_step=max_step
180
- )
181
- target_results = self.last_result.y[tuple(self.know_compartments), :]
182
- if metric == 'R2':
183
- 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))
184
- elif metric == 'norm':
185
- return np.linalg.norm(target_results - self.teoretic_y)
186
- else:
187
- 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))
188
-
189
-
190
- def load_optimization_data(self, teoretic_x, teoretic_y, know_compartments, w = None, c0=None, d=None, compartment_number=None):
191
- """
192
- Функция загрузки в модель эксперементальных данных
193
-
194
- Args:
195
- teoretic_x: Вектор временных точек теоретических значений
196
- teoretic_y: Матрица с теоретическими значениями
197
- know_compartments: Вектор с номерами камер, по которым есть данные
198
- c0: Вектор нулевых концентраций
199
- d: Вводимая доза
200
- compartment_number: Номер камеры в которую вводится доза
201
-
202
- Returns:
203
- None
204
- """
205
- self.teoretic_x = np.array(teoretic_x)
206
- self.teoretic_y = np.array(teoretic_y)
207
- self.know_compartments = know_compartments
208
- self.teoretic_avg = np.average(self.teoretic_y, axis=1)
209
- self.teoretic_avg = np.repeat(self.teoretic_avg, self.teoretic_x.size)
210
- self.teoretic_avg = np.reshape(self.teoretic_avg, self.teoretic_y.shape)
211
- assert any([c0, d, compartment_number is not None]), "Need to set c0 or d and compartment_number"
212
- if not c0:
213
- assert all([d, compartment_number is not None]), "Need to set d and compartment_number"
214
- self.d = d
215
- self.compartment_number = compartment_number
216
- self.c0 = None
217
- else:
218
- self.c0 = np.array(c0)
219
- self.w = np.ones(self.teoretic_y.shape) if w is None else np.array(w)
220
-
221
- def optimize(self, method=None, user_method=None, method_is_func=True,
222
- optimization_func_name='__call__', max_step=0.01, metric='R2', **kwargs):
223
- """
224
- Функция оптимизации модели
225
-
226
- Args:
227
- method: Метод оптимизации, любой доступный minimize + 'country_optimization' и 'country_optimization_v2'
228
- max_step: Максимальный шаг при решении СДУ
229
- **kwargs: Дополнительные именованные аргументы
230
-
231
- Returns:
232
- None
233
- """
234
- self._optim = True
235
- f = lambda x: self._target_function(x, max_step=max_step, metric=metric)
236
- if user_method is not None:
237
- if method_is_func:
238
- x = user_method(f, **kwargs)
239
- else:
240
- optimization_obj = user_method(f, **kwargs)
241
- x = getattr(optimization_obj, optimization_func_name)()
242
- else:
243
- if method == 'country_optimization':
244
- CA = CountriesAlgorithm(
245
- f=f,
246
- memory_list=getattr(self, 'memory', None),
247
- **kwargs
248
- )
249
- CA.start()
250
- x = CA.countries[0].population[0].x
251
- elif method == 'country_optimization_v2':
252
- CA = CountriesAlgorithm_v2(
253
- f=f,
254
- **kwargs
255
- )
256
- CA.start()
257
- x = CA.countries[0].population[0].x
258
- elif method == 'GA':
259
- CA = GeneticAlgorithm(
260
- f=f,
261
- **kwargs
262
- )
263
- x = CA.start()
264
- else:
265
- res = minimize(
266
- fun=f,
267
- method=method,
268
- **kwargs
269
- )
270
- x = res.x
271
- if self.configuration_matrix_target:
272
- self.configuration_matrix[self.configuration_matrix_target] = x[:self.configuration_matrix_target_count]
273
- if self.outputs_target:
274
- self.outputs[self.outputs_target] = x[
275
- self.configuration_matrix_target_count:self.configuration_matrix_target_count + self.outputs_target_count]
276
- if self.volumes_target:
277
- 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]
278
- self.configuration_matrix_target = None
279
- self.outputs_target = None
280
- self.volumes_target = None
281
- self._optim = False
282
- return x
283
-
284
- def plot_model(self, compartment_numbers=None, compartment_names={}, left=None, right=None, y_lims={}, **kwargs):
285
- """
286
- Функция для построения графиков модели
287
-
288
- Args:
289
- compartment_numbers: Камеры, которые нужно отобразить (если не указать, отобразим все)
290
- compartment_names: Имена камер
291
- """
292
- if compartment_numbers:
293
- compartment_numbers = np.array(compartment_numbers)
294
- else:
295
- compartment_numbers = np.arange(self.outputs.size)
296
- self(**kwargs)
297
- for i in compartment_numbers:
298
- if hasattr(self, "teoretic_x") and hasattr(self, "teoretic_y") and i in self.know_compartments:
299
- plt.plot(self.teoretic_x, self.teoretic_y[self.know_compartments.index(i)], "*r")
300
- plt.plot(self.last_result.t, self.last_result.y[i])
301
- plt.title(compartment_names.get(i, i))
302
- plt.xlim(left=left, right=right)
303
- if y_lims.get(i):
304
- plt.ylim(y_lims.get(i))
305
- plt.grid()
306
- plt.show()
307
-
308
-
309
- class MagicCompartmentModel(BaseCompartmentModel):
310
-
311
- need_magic_optimization = False
312
-
313
- def __init__(self, configuration_matrix, outputs, volumes=None, magic_coefficient=1, exclude_compartments=[], numba_option=False, use_shared_memory=False):
314
- super().__init__(configuration_matrix, outputs, volumes, numba_option, use_shared_memory)
315
- self.magic_coefficient = magic_coefficient
316
- self.exclude_compartments = np.array(exclude_compartments)
317
- self.need_magic_optimization = self.magic_coefficient is None
318
- if getattr(self, "memory", None):
319
- self.memory.shm.close()
320
- self.memory.shm.unlink()
321
- self.memory_size += int(self.need_magic_optimization)
322
- self.memory = shared_memory.ShareableList(
323
- sequence=self.memory_size * [None]
324
- )
325
- self.memory_name = self.memory.shm.name
326
-
327
- def __call__(self, t_max, c0=None, d=None, compartment_number=None, max_step=0.01, t_eval=None):
328
- if not self._optim and not self.magic_coefficient:
329
- raise Exception("Magic_coefficient parameter not specified")
330
- res = super().__call__(t_max, c0, d, compartment_number, max_step, t_eval)
331
- magic_arr = np.ones(self.configuration_matrix.shape[0]) * self.magic_coefficient
332
- if self.exclude_compartments:
333
- magic_arr[self.exclude_compartments] = 1
334
- magic_arr = np.repeat(magic_arr, res.y.shape[1])
335
- magic_arr = np.reshape(magic_arr, res.y.shape)
336
- res.y = magic_arr * res.y
337
- self.last_result = res
338
- return res
339
-
340
- def load_data_from_list(self, x):
341
- super().load_data_from_list(x)
342
- if self.need_magic_optimization:
343
- self.magic_coefficient = x[-1]
344
-
345
- def optimize(self, method=None, user_method=None, method_is_func=True,
346
- optimization_func_name='__call__', max_step=0.01, **kwargs):
347
- x = super().optimize(method, user_method, method_is_func, optimization_func_name, max_step, **kwargs)
348
- if self.need_magic_optimization:
349
- self.magic_coefficient = x[-1]
350
- self.need_magic_optimization = False
351
- return x
352
-
353
-
354
- class ReleaseCompartmentModel(BaseCompartmentModel):
355
-
356
- need_v_release_optimization = False
357
- release_parameters_target = None
358
- accumulation_parameters_target = None
359
-
360
- class ReleaseRK45(RK45):
361
-
362
- def __init__(self, fun, t0, y0, t_bound, release_function, compartment_number, c0, max_step=np.inf,
363
- with_accumulate=False, accumulation_function=None, accumulation_type=1, rtol=1e-3, atol=1e-6, vectorized=False,
364
- first_step=None, **extraneous):
365
- super().__init__(fun, t0, y0, t_bound, max_step=max_step,
366
- rtol=rtol, atol=atol, vectorized=vectorized,
367
- first_step=first_step, **extraneous)
368
- self.release_function = release_function
369
- self.compartment_number = compartment_number
370
- self.c0 = c0
371
- self.old_release_correction = 0
372
- self.old_accumulation_correction = c0
373
- self.with_accumulate = with_accumulate
374
- self.accumulation_function = accumulation_function
375
- self.accumulation_type = accumulation_type
376
-
377
- def _step_impl(self):
378
- result = super()._step_impl()
379
- release_correction = self.release_function(self.t, self.c0)
380
- minus_accumulation = 0
381
- if self.with_accumulate:
382
- if self.accumulation_type == 1:
383
- accumulation_correction = self.accumulation_function(self.t, self.c0 )
384
- minus_accumulation = self.old_accumulation_correction - accumulation_correction
385
- elif self.accumulation_type == 2:
386
- coef_accumulation = self.accumulation_function(self.t, self.c0) / self.c0
387
- plus_release = release_correction - self.old_release_correction
388
- all_corrections = plus_release
389
- if self.accumulation_type == 1:
390
- if plus_release - minus_accumulation > 0:
391
- all_corrections = plus_release - minus_accumulation
392
- elif self.accumulation_type == 2:
393
- all_corrections *= coef_accumulation
394
- self.y[self.compartment_number] += all_corrections
395
- self.old_release_correction = release_correction
396
- if self.with_accumulate and self.accumulation_type == 1:
397
- self.old_accumulation_correction = accumulation_correction
398
- return result
399
-
400
- def __init__(self, v_release, release_parameters, release_compartment,
401
- release_function=None, with_accumulate=False, accumulation_function=None,
402
- accumulation_parameters=[None], accumulation_type=1, *args, **kwargs):
403
- """
404
- Камерная модель с высвобождением для описания фармакокинетики системы
405
-
406
- Неизвестные параметры при необходимости задаются как None
407
- например configuration_matrix = [[0, 1], [None, 0]]
408
-
409
- Args:
410
- configuration_matrix: Настроечная матрица модели, отображающая константы перехода между матрицами
411
- outputs: Вектор констант перехода во вне камер
412
- volumes: Объемы камер
413
- v_release: Объем гепотетической камеры из которой происходит высвобождение
414
- release_parameters: Параметры функции высвобождения
415
- release_compartment: Номер камеры в которую происходит высвобождение
416
- release_function: Функция высвобождения по умолчанию f(t,m,b,c) = c0 * c * t ** b / (t ** b + m)
417
- """
418
- super().__init__(*args, **kwargs)
419
- self.release_parameters = np.array(release_parameters)
420
- self.release_parameters_target_count = 0
421
- if np.any(self.release_parameters == None):
422
- self.release_parameters_target = np.where(self.release_parameters == None)
423
- self.release_parameters_target_count = np.sum(self.release_parameters == None)
424
- self.v_release = v_release
425
- if self.v_release is None:
426
- self.need_v_release_optimization = True
427
- self.release_compartment = release_compartment
428
- self.release_function = release_function
429
- self.with_accumulate = with_accumulate
430
- self.accumulation_type = accumulation_type
431
-
432
- if self.with_accumulate:
433
- self.accumulation_function = accumulation_function
434
- self.accumulation_parameters = np.array(accumulation_parameters)
435
- if np.any(self.accumulation_parameters == None):
436
- self.accumulation_parameters_target = np.where(self.accumulation_parameters == None)
437
- self.accumulation_parameters_target_count = np.sum(self.accumulation_parameters == None)
438
-
439
- if getattr(self, "memory", None):
440
- self.memory.shm.close()
441
- self.memory.shm.unlink()
442
- self.memory_size += self.release_parameters_target_count + int(self.need_v_release_optimization)
443
- self.memory = shared_memory.ShareableList(
444
- sequence=self.memory_size * [None]
445
- )
446
- self.memory_name = self.memory.shm.name
447
-
448
- def load_data_from_list(self, x):
449
- super().load_data_from_list(x)
450
- s = self.configuration_matrix_target_count + self.outputs_target_count + self.volumes_target_count
451
- if self.release_parameters_target:
452
- self.release_parameters[self.release_parameters_target] = x[s:s + self.release_parameters_target_count]
453
- if self.need_v_release_optimization:
454
- self.v_release = x[s + self.release_parameters_target_count]
455
- if self.with_accumulate:
456
- if self.accumulation_parameters_target:
457
- s += self.release_parameters_target_count + int(self.need_v_release_optimization)
458
- self.accumulation_parameters[self.accumulation_parameters_target] = x[s:s + self.accumulation_parameters_target_count]
459
-
460
- def _default_release_function(self, t, c0):
461
- """
462
- Функция для поправки на высвобождение
463
- """
464
- m, b, c = self.release_parameters
465
- return c0 * c * t ** b / (t ** b + m)
466
-
467
- def get_release_function(self):
468
- if self.release_function is not None:
469
- return lambda t, c0: self.release_function(t, c0, *self.release_parameters)
470
- else:
471
- return self._default_release_function
472
-
473
- def _default_accumulation_function(self, t, c0):
474
- """
475
- Функция для поправки на накопление
476
- """
477
- k, = self.accumulation_parameters
478
- return c0 * np.exp(-k * t)
479
-
480
- def get_accumulation_function(self):
481
- if self.accumulation_function is not None:
482
- return lambda t, c0: self.accumulation_function(t, c0, *self.accumulation_parameters)
483
- else:
484
- return self._default_accumulation_function
485
-
486
- def __call__(self, t_max, c0=None, d=None, max_step=0.01, t_eval=None, **kwargs):
487
- """
488
- Расчет кривых концентраций по фармакокинетической модели
489
-
490
- Args:
491
- t_max: Предельное время расчета
492
- c0: Начальная концентрация в камере из которой высвобождается вещество
493
- d: Вводимая доза
494
- max_step: Максимальный шаг при решении СДУ
495
- t_eval: Временные точки, в которых необходимо молучить решение
496
-
497
- Returns:
498
- Результат работы решателя scipy solve_ivp
499
- """
500
- if not self._optim:
501
- assert (not any([self.configuration_matrix_target, self.outputs_target, self.volumes_target])), \
502
- "It is impossible to make a calculation with unknown parameters"
503
- assert any([c0 is not None, d]), "Need to set c0 or d and compartment_number"
504
- if c0 is None:
505
- assert d, "Need to set d"
506
- c0 = d / self.v_release
507
- ts = [0, t_max]
508
- y0 = np.zeros(self.outputs.shape)
509
- self.last_result = solve_ivp(
510
- fun=self._compartment_model if
511
- not self.numba_option
512
- else lambda t, c: self._numba_compartment_model(t, c,
513
- self.configuration_matrix.astype(
514
- np.float64),
515
- self.outputs.astype(
516
- np.float64),
517
- self.volumes.astype(
518
- np.float64)),
519
- t_span=ts,
520
- y0=y0,
521
- max_step=max_step,
522
- t_eval=t_eval,
523
- method=self.ReleaseRK45,
524
- release_function=self.get_release_function(),
525
- compartment_number=self.release_compartment,
526
- with_accumulate=self.with_accumulate,
527
- accumulation_function=self.get_accumulation_function() if self.with_accumulate else None,
528
- accumulation_type=self.accumulation_type,
529
- c0=c0
530
- )
531
- self.last_result.model_realized = c0 - self.get_release_function()(self.last_result.t, c0)
532
- if self.with_accumulate:
533
- model_accumulation = self.get_accumulation_function()(self.last_result.t, c0)
534
- if self.accumulation_type == 1:
535
- self.last_result.model_realized = model_accumulation - self.get_release_function()(self.last_result.t, c0)
536
- elif self.accumulation_type == 2:
537
- accumulation_coeffs = model_accumulation / c0
538
- self.last_result.model_realized= accumulation_coeffs * self.get_release_function()(self.last_result.t, c0)
539
- self.last_result.model_realized = model_accumulation - self.last_result.model_realized
540
- return self.last_result
541
-
542
- def _target_function(self, x, max_step=0.01, metric='R2'):
543
- """
544
- Функция расчета значения целевой функции
545
-
546
- Args:
547
- x: Значение искомых параметров модели
548
- max_step: Максимальный шаг при решении СДУ
549
-
550
- Returns:
551
- Значение целевой функции, характеризующее отклонение от эксперементальных данных
552
- """
553
- self.load_data_from_list(x)
554
- c0 = self.c0
555
- if c0 is None:
556
- c0 = self.d / self.v_release
557
- self(
558
- t_max=np.max(self.teoretic_x),
559
- c0=c0,
560
- t_eval=self.teoretic_x,
561
- max_step=max_step
562
- )
563
- target_results = self.last_result.y[tuple(self.know_compartments), :]
564
- if metric == 'R2':
565
- plus = 0
566
- if self.teoretic_realized is not None:
567
- model_realized = self.last_result.model_realized
568
- plus = np.sum(((model_realized - self.teoretic_realized) ** 2) / ((self.teoretic_realized - self.teoretic_realized_avg) ** 2))
569
- 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))
570
- elif metric == 'norm':
571
- plus = 0
572
- if self.teoretic_realized is not None:
573
- model_realized = self.last_result.model_realized
574
- plus = np.linalg.norm(self.teoretic_realized - model_realized)
575
- return plus + np.linalg.norm(target_results - self.teoretic_y)
576
- else:
577
- 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))
578
-
579
- def load_optimization_data(self, teoretic_x, teoretic_y, know_compartments,
580
- w = None, c0=None, d=None, compartment_number=None, teoretic_realized=None):
581
- """
582
- Функция загрузки в модель эксперементальных данных
583
-
584
- Args:
585
- teoretic_x: Вектор временных точек теоретических значений
586
- teoretic_y: Матрица с теоретическими значениями
587
- know_compartments: Вектор с номерами камер, по которым есть данные
588
- c0: Начальная концентрация в камере из которой высвобождается вещество
589
- d: Вводимая доза
590
-
591
- Returns:
592
- None
593
- """
594
- self.teoretic_x = np.array(teoretic_x)
595
- self.teoretic_y = np.array(teoretic_y)
596
- self.teoretic_realized = np.array(teoretic_realized) if teoretic_realized is not None else teoretic_realized
597
- if teoretic_realized is not None:
598
- self.teoretic_realized_avg = np.average(self.teoretic_realized)
599
- self.know_compartments = know_compartments
600
- self.teoretic_avg = np.average(self.teoretic_y, axis=1)
601
- self.teoretic_avg = np.repeat(self.teoretic_avg, self.teoretic_x.size)
602
- self.teoretic_avg = np.reshape(self.teoretic_avg, self.teoretic_y.shape)
603
- assert any([c0, d, compartment_number is not None]), "Need to set c0 or d and compartment_number"
604
- if not c0:
605
- assert all([d, compartment_number is not None]), "Need to set d and compartment_number"
606
- self.d = d
607
- self.c0 = None
608
- else:
609
- self.c0 = np.array(c0)
610
- self.w = np.ones(self.teoretic_y.shape) if w is None else np.array(w)
611
-
612
- def optimize(self, method=None, user_method=None, method_is_func=True,
613
- optimization_func_name='__call__', max_step=0.01, **kwargs):
614
- x = super().optimize(method, user_method, method_is_func, optimization_func_name, max_step, **kwargs)
615
- s = self.configuration_matrix_target_count + self.outputs_target_count + self.volumes_target_count
616
- if self.release_parameters_target:
617
- self.release_parameters[self.release_parameters_target] = x[s:s + self.release_parameters_target_count]
618
- if self.need_v_release_optimization:
619
- self.v_release = x[s:s + self.release_parameters_target_count + 1]
620
- self.need_v_release_optimization = False
621
- if self.with_accumulate:
622
- if self.accumulation_parameters_target:
623
- s += self.release_parameters_target_count + int(self.need_v_release_optimization)
624
- self.accumulation_parameters[self.accumulation_parameters_target] = x[s:s + self.accumulation_parameters_target_count]
625
- return x
626
-
627
- def plot_model(self, compartment_numbers=None, compartment_names={},
628
- left=None, right=None, y_lims={}, plot_accumulation=False, **kwargs):
629
- super().plot_model(compartment_numbers, compartment_names, left, right, y_lims, **kwargs)
630
- if plot_accumulation:
631
- if hasattr(self, "teoretic_x") and hasattr(self, "teoretic_realized"):
632
- plt.plot(self.teoretic_x, self.teoretic_realized, "*r")
633
- plt.plot(self.last_result.t, self.last_result.model_realized)
634
- plt.title(compartment_names.get('realized', 'realized'))
635
- plt.xlim(left=left, right=right)
636
- if y_lims.get('realized'):
637
- plt.ylim(y_lims.get('realized'))
638
- plt.grid()
639
- plt.show()
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
+