Одной из распространенных задач, возникающих при исследовании различных объектов — построение математической модели. Нередко математическая модель представляется в виде системы дифференциальных уравнений, однако непосредственное измерение всех, входящих в них параметров, как правило, невозможно по различным причинам. В таком случае, одним из подходов является проведение идентификационных экспериментов и оценка параметров ДУ путем решения оптимизационной задачи.
В статье рассмотрен простой способ оценки параметров системы ДУ в форме Коши на языке Python.
Постановка задачи
Разработка простого модуля Python для решения задачи оценки параметров.
Исходные данные для задачи:
- Представление модели в форме Коши, первые N величин — наблюдаемы (порядок уравнений не имеет значения с точки зрения математики, но упорядочивание сильно упростит жизнь при разработке);
- Имеются данные эксперимента, на основе которого будет производится оценка параметров/
Исходные данные
Исходными данными, передаваемыми при создании экземпляра класса оценки параметров являются:
- Функция, реализующая вычисление ДУ.
- Массив экспериментальных данных (Два массива: время и значения)
Таким образом, получаем следующий класс с конструктором:
class parameter_estimator():
def __init__(self, x_data, y_data, f):
self._x_data = x_data
self._y_data = y_data
self._f = f
self._c = None
self.n_observed = y_data.shape[1]
Тут просто сохраняются значения параметров и функция в поля класса.
Оценка параметров
Процедуру оценки параметров можно сделать на основе функции scipy.optimize.leastsq. Эта функция минимизирует сумму квадратов полученных величин и может принимать два интересных для начального понимания аргумента: func и x0.
Функция func должна принимать один аргумент (который м.б. вектором), являющийся параметрами, а x0 — начальные значения параметров.
Метод оценки параметров можно реализовать следующим образом:
def estimate(self, y0, guess):
"""
Произвести оценку параметров дифференциального уравнения с заданными
начальными значениями параметров:
y0 -- начальные условия ДУ
guess -- параметры ДУ
"""
# Сохраняем число начальных условий
self._y0_len = len(y0)
# Создаем вектор оцениваемых параметров,
# включающий в себя начальные условия
self._est_values = np.concatenate((y0, guess))
# Решить оптимизационную задачу - решение в переменной c
(c, kvg) = optimize.leastsq(self.f_resid, self._est_values)
self._c = c
# В возвращаемом значении разделяем начальные условия и параметры
return c[self._y0_len:], c[0:self._y0_len]
Метод получает начальные значения начальных условий ДУ и начальные значения параметров системы. Функцию вычисления вектора ошибок между решением системы ДУ с текущими значениями параметров и экспериментальными значениями необходимо реализовывать самостоятельно, потому, следующим шагом будет ее написание:
def f_resid(self, p):
"""
Функция для передачи в optimize.leastsq
При дальнейших вычислениях значения, возвращаемые этой функцией,
будут возведены в квадрат и просуммированы
"""
delta = self._y_data - self.my_ls_func(self._x_data, p)
return delta.flatten() # Преобразуем в одномерный массив
Функция получает вектор параметров системы (включая начальные условия ДУ).
Предложенная функция содержит вызов еще одной функции my_ls_func, выделенной для разделения функционала. Выглядит она следующим образом и вычисляет непосредственно решение системы ДУ при заданных параметрах:
def my_ls_func(self, x, teta):
"""
Определение функции, возвращающей значения решения ДУ в
процессе оценки параметров
x заданные (временные) точки, где известно решение
(экспериментальные данные)
teta -- массив с текущим значением оцениваемых параметров.
Первые self._y0_len элементов -- начальные условия,
остальные -- параметры ДУ
"""
# Для передачи функции создадим ламбда-выражение с подставленными
# параметрами
# Вычислим значения дифференциального уравнения в точках "x"
r = integrate.odeint(lambda y, t: self._f(y, t, teta[self._y0_len:]),
teta[0:self._y0_len], x)
# Возвращаем только наблюдаемые переменные
return r[:, 0:self.n_observed]
Таким образом, процедура оценки параметров работает следующим образом:
- Вычисляется решение системы ДУ при текущих (или начальных) значения параметров.
- Рассчитывается вектор ошибки между решением и экспериментальными данными.
- Производится корректировка значений параметров.
- Переход на шаг 1, если не достигнуто максимальное число итераций или минимальное рассогласование.
Визуализация
Для визуализации напишем следующую функцию:
def plot_result(self):
"""
Построить графическое представление результатов оценки параметров
"""
if self._c is None:
print("Parameter is not estimated.")
return
sol, t = self.calcODE((self._c[self._y0_len:],),
self._c[0:self._y0_len],
min(self._x_data),
max(self._x_data))
# Строим экспериментальные данные, как красные точки,
# а результаты моделирования, как синюю линию
pp.plot(self._x_data, self._y_data, '.r', t, sol, '-b')
pp.xlabel('xlabel', {"fontsize": 16})
pp.ylabel("ylabel", {"fontsize": 16})
pp.legend(('data', 'fit'), loc=0)
pp.show()
где calcODE — еще одна служебная функция для расчета системы ДУ:
def calcODE(self, args, y0, x0=0, xEnd=10, nt=101):
"""
Служебная функция для решения ДУ
"""
t = np.linspace(x0, xEnd, nt)
sol = odeint(self._f, y0, t, args)
return sol, t
Тестирование и пример использования
В качестве примера использования попробуем оценить параметры системы ДУ, описывающие маятник с трением.
\[ {d \theta \over dt } = \omega \\ {d \omega \over dt } = -b * \omega — c* \sin(\theta) \]
где \(\theta \) — угол, \(\omega\) — скорость.
Этот объект уже использовался в качестве иллюстрации к статье о фазовых портретах и частотному методу синтеза регуляторов.
Для получения «экспериментальных» данных воспользуемся решением системы ДУ с известными «истинными» параметрами:
def ode(y, t, k):
"""
Функция, реализующая систему ДУ маятника с трением
"""
theta, omega = y
b = k[0]
c = k[1]
dydt = [omega, -b*omega - c*np.sin(theta)]
return dydt
def calcODE(args, y0, dy0, ts=10, nt=101):
"""
Вспомогательная функция для получения решения систему ДУ
"""
y0 = [y0, dy0]
t = np.linspace(0, ts, nt)
sol = odeint(ode, y0, t, args)
return sol, t
# Зададим истинные значения параметров системы
b = 0.3
c = 5.0
args = ([b, c], )
y0 = 1
dy0 = 0
print("Real parameter: b = {}, c = {}".format(b, c))
print("Real initial condition: {} {}".format(y0, dy0))
sol, t = calcODE(args, y0, dy0)
Объявленная тут система будет использоваться и для оценки параметров, но уже без известных значений:
# Зададим истинные значения параметров системы
b = 0.3
c = 5.0
args = ([b, c], )
y0 = 1
dy0 = 0
print("Real parameter: b = {}, c = {}".format(b, c))
print("Real initial condition: {} {}".format(y0, dy0))
sol, t = calcODE(args, y0, dy0)
guess = [0.2, 0.3] # Начальные значения для параметров системы
y0 = [0, 1] # Стартовые начальные значения для системы ДУ
estimator = parameter_estimator(t, sol, ode)
est_par = estimator.estimate(y0, guess)
# Построим графики результатов оценки параметров
estimator.plot_result()
print("Estimated parameter: b={}, c={}".format(est_par[0][0], est_par[0][1]))
print("Estimated initial condition: {}".format(est_par[1]))
Получившиеся результаты являются достаточно точно оценкой:
Real parameter: b = 0.3, c = 5.0
Real initial condition: 1 0
Estimated parameter: b = 0.299993833612628, c = 5.000066486504925
Estimated initial condition: [ 9.99976783e-01 1.58491793e-04]
