Как решить систему ODE с scipy.integrate.odeint с переменной, зависящей от времени

Чтобы узнать о решениях систем ODE на Python.

В рамках этой модели существует уравнение, которое определяет численность населения на каждый день на основе коэффициента рождаемости, смертности и первоначальной численности населения. затем на основе численности населения он вычисляет, сколько зомби создано и убито.

Я заинтересован в замене дифференциального уравнения человеческой популяции списком данных, которые говорят нам о человеческой популяции на каждом временном шаге. Я получаю следующую ошибку:

TypeError: can't multiply sequence by non-int of type 'float'

Как отмечали люди, это связано с тем, что умножать отдельные числа на список не имеет смысла. Я не уверен, как поставить число из списка в каждый момент времени T в дифференциальные уравнения.

Вот код для двух попыток

# solve the system dy/dt = f(y, t)
def f(y, t):
    Si = [345, 299, 933, 444, 265, 322] # replaced an equation with list
    Zi = y[0]
    Ri = y[1]
    # the model equations (see Munz et al. 2009)
    f0 = B*Si*Zi + G*Ri - A*Si*Zi
    f1 = d*Si + A*Si*Zi - G*Ri
    return [f0, f1]

Я тоже пробовал

numbers = [345, 299, 933, 444, 265, 322]
for t in [0, 5]:
    Si = numbers

# solve the system dy/dt = f(y, t)
def f(y, t):

    Zi = y[0]
    Ri = y[1]
    # the model equations (see Munz et al. 2009)
    f0 = B*Si*Zi + G*Ri - A*Si*Zi
    f1 = d*Si + A*Si*Zi - G*Ri
    return [f0, f1]

Обе попытки имеют одну и ту же проблему с предоставлением всего списка f0 и f1 вместо итеративной подачи 1 числа из списка.


person Bobby M    schedule 23.03.2017    source источник
comment
для реальной проблемы, которую я делаю, мне нужно заменить одно дифференциальное уравнение в системе списком реальных данных. Я также пытался определить Si вне определения функции Y, но получил ту же ошибку codenumbers = [345, 299, 933, 444, 265, 322] codefor t в [0, 5]: code Si = numbers   -  person Bobby M    schedule 23.03.2017
comment
Вместо того, чтобы говорить о замене дифференциального уравнения списком чисел (что для меня не имеет смысла), попробуйте с самого начала объяснить проблему, используя имеющуюся у вас информацию. Например, что-то вроде у меня есть список чисел. Значение этих чисел [...]. Я хочу смоделировать процесс, в котором эти числа используются для [...]   -  person Warren Weckesser    schedule 23.03.2017
comment
Вы правы, я сформулировал свой вопрос особенно глупо, и прошу прощения. Я понимаю вашу ошибку point and pythons, что я не могу умножить свой номер на список. Я пытаюсь понять, как передать в уравнения f0 и f1 элемент в списке на каждом временном шаге t. спасибо, что нашли время помочь.   -  person Bobby M    schedule 23.03.2017
comment
я не ясно формулирую, что я пытаюсь сделать. Я биолог, и у меня есть простая система ODE, которая моделирует то, как белок активируется, а затем взаимодействует с несколькими другими белками. Теперь у меня есть реальные данные об уровнях активированного белка за заданный промежуток времени, я хочу заменить уравнение, которое представляет активацию этого белка в моей модели, данными реального временного ряда. в аналогии с моделью зомби-апокалипсиса я хотел бы заменить уравнение, которое описывает человеческое население в отношении уровней рождаемости и смертности, списком человеческого населения в разные моменты времени.   -  person Bobby M    schedule 23.03.2017
comment
Я отредактировал заголовок вопроса и вопрос. Спасибо за уделенное время.   -  person Bobby M    schedule 23.03.2017
comment
Я бы оставил уравнение для Si, а затем подогнал бы данные. Это вариант для вас?   -  person Cleb    schedule 24.03.2017
comment
это не вариант, мои данные слишком шумные, чтобы того стоить. На данный момент я думаю, что мне не нужно использовать Odeint или какой-либо решатель, а вместо этого использовать цикл for, чтобы сделать это вручную.   -  person Bobby M    schedule 24.03.2017
comment
@BobbyM: Я все еще думаю, что это возможно, несмотря на шум. Ниже я добавил пример того, как к этому можно подойти. Сообщите мне, подходит ли вам это.   -  person Cleb    schedule 24.03.2017


Ответы (3)


Насколько я понял из комментариев под вашим вопросом, вы пытаетесь включить данные измерений, которые могут быть зашумленными. Вместо того, чтобы подключать данные напрямую, вы можете использовать эти данные для соответствия своим временным курсам. Здесь я показываю результат для вашей переменной S:

введите описание изображения здесь

green dots взяты из решения предоставленной вами системы ODE. Чтобы имитировать ошибки измерения, я добавил к этим данным немного шума (blue dots). Затем вы можете настроить свою систему ODE для максимально точного воспроизведения этих данных (red line).

Для этих задач вы можете использовать lmfit. Код, воспроизводящий сюжет, выглядит следующим образом (некоторые пояснения можно найти во встроенных комментариях):

# zombie apocalypse modeling
import numpy as np
import matplotlib.pyplot as plt
from scipy.integrate import odeint
from lmfit import minimize, Parameters, Parameter, report_fit
from scipy.integrate import odeint


# solve the system dy/dt = f(y, t)
def f(y, t, paras):

    Si = y[0]
    Zi = y[1]
    Ri = y[2]

    try:
        P = paras['P'].value
        d = paras['d'].value
        B = paras['B'].value
        G = paras['G'].value
        A = paras['A'].value

    except:
        P, d, B, G, A = paras
    # the model equations (see Munz et al. 2009)
    f0 = P - B * Si * Zi - d * Si
    f1 = B * Si * Zi + G * Ri - A * Si * Zi
    f2 = d * Si + A * Si * Zi - G * Ri
    return [f0, f1, f2]


def g(t, x0, paras):
    """
    Solution to the ODE x'(t) = f(t,x,p) with initial condition x(0) = x0
    """
    x = odeint(f, x0, t, args=(paras,))
    return x


def residual(paras, t, data):
    x0 = paras['S0'].value, paras['Z0'].value, paras['R0'].value
    model = g(t, x0, paras)
    s_model = model[:, 0]
    return (s_model - data).ravel()

# just for reproducibility reasons
np.random.seed(1)

# initial conditions
S0 = 500.              # initial population
Z0 = 0                 # initial zombie population
R0 = 0                 # initial death population
y0 = [S0, Z0, R0]     # initial condition vector
t = np.linspace(0, 5., 100)         # time grid

P = 12      # birth rate
d = 0.0001  # natural death percent (per day)
B = 0.0095  # transmission percent  (per day)
G = 0.0001  # resurect percent (per day)
A = 0.0001  # destroy percent  (per day)

# solve the DEs
soln = odeint(f, y0, t, args=((P, d, B, G, A), ))
S = soln[:, 0]
Z = soln[:, 1]
R = soln[:, 2]

# plot results
plt.figure()
plt.plot(t, S, label='Living')
plt.plot(t, Z, label='Zombies')
plt.xlabel('Days from outbreak')
plt.ylabel('Population')
plt.title('Zombie Apocalypse - No Init. Dead Pop.; No New Births.')
plt.legend(loc=0)
plt.show()

# generate fake data
S_real = S[0::8]
S_measured = S_real + np.random.randn(len(S_real)) * 100
t_measured = t[0::8]

plt.figure()
plt.plot(t_measured, S_real, 'o', color='g', label='real data')

# add some noise to your data to mimic measurement erros
plt.plot(t_measured, S_measured, 'o', color='b', label='noisy data')

# set parameters including bounds; you can also fix parameters (use vary=False)
params = Parameters()
params.add('S0', value=S0, min=490., max=510.)
params.add('Z0', value=Z0, vary=False)
params.add('R0', value=R0, vary=False)
params.add('P', value=10, min=8., max=12.)
params.add('d', value=0.0005, min=0.00001, max=0.005)
params.add('B', value=0.01, min=0.00001, max=0.01)
params.add('G', value=G, vary=False)
params.add('A', value=0.0005, min=0.00001, max=0.001)

# fit model
result = minimize(residual, params, args=(t_measured, S_measured), method='leastsq')  # leastsq nelder
# check results of the fit
data_fitted = g(t, y0, result.params)

plt.plot(t, data_fitted[:, 0], '-', linewidth=2, color='red', label='fitted data')
plt.legend()
# display fitted statistics
report_fit(result)

plt.show()
person Cleb    schedule 24.03.2017
comment
Большое спасибо за то, что нашли время дать этот ответ. Я изучу его и уверен, что многому научусь. Я должен был быть более ясным в своих сомнениях (я узнал, что кодирование и даже разговоры о кодировании требуют большой точности). Проблема в том, что и зеленые, и синие точки дают одинаковое уравнение подгонки. Моя цель - найти относительный вклад каждого шага биологического процесса в шум на выходе. Вводя в модель реальные данные, я надеялся увидеть, сколько шума она создает в конечном результате. Возможно, мне придется переосмыслить свой подход - person Bobby M; 26.03.2017
comment
@BobbyM: Зеленые точки не использовались для подгонки. Они представляют реальные данные, т.е. данные без каких-либо ошибок измерения. В реальной жизни это нереально, у вас всегда будут ошибки измерения, биологическая изменчивость (экспрессия генов стохастическая). Чтобы имитировать это, я добавил шум к реальным данным, представленным синими точками. Красная линия создается ТОЛЬКО с учетом синих точек, т.е. измеренных данных. Как видно, несмотря на шум, можно очень хорошо воспроизвести реальные данные. И это то, чем мы всегда занимаемся в биологии: бороться с шумом :) - person Cleb; 26.03.2017
comment
@BobbyM: Как продвигается исследование? Есть вопросы по приведенному выше коду? Это решило вашу проблему? - person Cleb; 31.03.2017
comment
Привет, извините, что я не ответил ранее, я не работал над этой проблемой на прошлой неделе (много промежуточных оценок). Теперь я понимаю ваше решение, и код работает хорошо. Ваш комментарий об ошибке измерения заставил меня задуматься. Хотя я часто думаю об ошибке измерения, глядя на экспериментальные данные, мне никогда не приходило в голову, что ее также необходимо учитывать при моделировании. Я все еще пытаюсь найти лучший подход. Большое спасибо за Вашу помощь. - person Bobby M; 04.04.2017

Вы не можете заранее знать, в каких точках числовой интегратор вычисляет функцию ОДУ. Интегратор (odeint и другие, которые явно не являются «фиксированным размером шага») динамически генерирует внутренний список точек, который может иметь меньший, а иногда и больший размер шага, чем данный список точек выборки. Значения для вывода интерполируются из внутреннего списка.

Если вы хотите заменить часть ODE функцией, вам необходимо преобразовать данные образца в функцию. Это можно сделать с помощью интерполяции. Используйте scipy.interpolate. interp1 для создания функциональных объектов, которые затем можно использовать, как любую другую скалярную функцию.

person Lutz Lehmann    schedule 23.03.2017
comment
спасибо за ваш ответ, он был ясным и легким для понимания. к сожалению, интерпорляция у меня не сработает, мои данные очень зашумлены, в основном это облако. есть ли альтернатива odeint, которая может использовать фиксированный размер шага? если нет, мне нужно использовать цикл, чтобы явно указать размер шага 1? - person Bobby M; 23.03.2017
comment
@BobbyM: Шум не имеет значения; вы все равно можете попытаться уместить свои данные (я добавил код ниже). - person Cleb; 24.03.2017

чтобы конкретно сделать то, что я задавал в вопросе, а именно использовать значения вместо одного из hte ODES, вам нужно будет использовать цикл, в котором вы используете odesolver для решения вашей системы в течение 1 секунды, а затем принимаете выходные данные как начальные условия для следующей итерации цикла. Код для этого подхода приведен ниже. Однако, как отмечали многие, в большинстве ситуаций будет лучше использовать интерполяцию, как описано Клебом и другими.

import numpy as np
import matplotlib.pyplot as plt
from scipy.integrate import odeint

Si = [345, 299, 933, 444, 265, 322] # replaced an equation with list

#Parameters
P = 0           # birth rate
d = 0.0001  # natural death percent (per day)
B = 0.0095  # transmission percent  (per day)
G = 0.0001  # resurect percent (per day)
A = 0.0001  # destroy percent  (per day)

# solve the system dy/dt = f(y, t)
def f(y, t):
    Zi = y[0]
    Ri = y[1]
    # the model equations (see Munz et al. 2009)
    f0 = B*Si*Zi + G*Ri - A*Si*Zi
    f1 = d*Si + A*Si*Zi - G*Ri
    return [f0, f1]

# initial conditions
Z0 = 0                      # initial zombie population
R0 = 0                      # initial death population
y0 = [Z0, R0]   # initial condition vector
# a timestep of 1 forces the odesolve to use your inputs at the beginning and provide outputs at the end of the timestep. 
# In this way the problem that LutzL has described is avoided.
t  = np.linspace(0, 1, 2)       
Si =np.array(Si).T

#create a space for storing your outputdata
dataZ =[]
dataR =[]
#use a for loop to use your custom inputs for Si
for Si in Si:
    y0 = [Z0, R0]
    soln = odeint(f, y0, t)
    Z = soln[:, 0]
    R = soln[:, 1]
    #define your outputs as the initial conditions for the next iteration of the loop
    Z_0 = Z[1]
    R_0 = R[1]
    #store your outputs 
    dataZ.append(Z[1])
    dataR.append(R[1])


print (dataZ)
print (dataR)
person Bobby M    schedule 04.04.2017