Библиотека классов для решения жестких

advertisement
Библиотека классов для решения жестких обыкновенных
дифференциальных уравнений
Основой для написания данной статьи стала практическая необходимость автора в
приложении, позволяющем гибкого формировать системы дифференциальных уравнений. В
результате была разработана оригинальная программная библиотека, описание
возможностей которой предлагается вниманию наших читателей.
Лет 25 назад студенты начинали изучение основ программирования, разрабатывая
программы, решающие те или иные математические задачи. Среди которых неизменно
повторялась задача численного решения дифференциального уравнения методом Рунге-Кутта. С
тех пор области применения программ несоизмеримо расширились, изменились подходы в
изучении программирования, но задача решения обыкновенных дифференциальных уравнений
(ОДУ) осталась актуальной, как и в прежние времена.
Как известно, методы численного решения обыкновенных дифференциальных уравнений
сформировались как законченный раздел математики к средине 80-х годов прошлого столетия.
Среди последних достижений в этой области – разработка алгоритмов решения жестких
уравнений с переменным шагом и переменным порядком, в частности – методы Гира [1, 2, 3],
называемые в западной литературе формулами дифференцирования назад (BDF).
Сегодня программы, решающие дифференциальные уравнения, составляют часть систем
автоматизированного проектирования технических устройств. Ими также оснащены
универсальные средства моделирования, например, изящная свободно доступная программа
Санкт-Петербургских авторов Model Vision Studium [1] или похожая программа AnT,
разработанная исследователями из Штудгардского университета в Германии [1]. Эти и
подобные им программы позволяют найти решение отдельной системы обыкновенных
дифференциальных уравнений, а также производить анализ свойств этих решений. Все же, если
необходимо многократно решать уравнения, автоматически формировать структуру уравнений
и вычислять их параметры, обрабатывать результаты решений, тогда применение готовых
средств моделирования весьма затруднительно.
Свободно доступных программ, содержащих известные математические алгоритмы
решения ОДУ, совсем немного. Вычислительные качества некоторых из них весьма скромны.
Или же они технологически и морально устарели, например до сих пор сохраняют
фортановский вид. Среди свободно доступных программ, решающих ОДУ, отличается
библиотека ODE++, разработанная М.Милде из Иенского университета в Германии [1].
Известно несколько программ решения ОДУ, разработанных ведущими научными
центрами, которые отличаются исключительно высоким качеством. Среди наиболее
распространенных таких программ это система программных вычислений MatLab, содержащая,
в частности, хорошо отлаженные программы решения жестких дифференциальных уравнений.
В файле mcpp1.rar, mcpp2.rar содержится два примера создания программ решения ОДУ на
языке программирования С++ с применением средств MatLab. Такие проекты дают очень
эффективные и надежные результаты, но, к сожалению, у них остается существенный
недостаток – это сложность внесения изменений в структуру решаемых уравнений.
Практическая необходимость разработки программ, позволяющих гибкого формировать
системы дифференциальных уравнений, привели к разработке программных библиотек, которые
описаны в этой статье.
PDF created with pdfFactory Pro trial version www.pdffactory.com
1. Магия формул плюс выразительность С++...
Предлагаемая библиотека классов предназначена для решения обыкновенных жестких
дифференциальных уравнений, записанных в виде dx(t)/dt=f(x,t); x(t0)=x0, где x(t) – искомый
вектор; f(x,t) – известная вектор-функция; t0, x0 – известные начальные условия.
Библиотека классов для решения ОДУ основывается на математических классах векторов
Vector, матриц Matrix, дискретных табличных зависимостей Tabl, о которых сообщалось в
предыдущих публикациях журнала [1,2].
Алгоритмы решения ОДУ размещены в классе C_ode. Данные этого класса содержат
название задачи char name[], размер системы ОДУ int size_ode, шаг интегрирования long
double hhh, начальное условия задачи Коши, то есть известные значение аргумента и решения
в узле начала интегрирования long double t_000, Vector x_000, конечное значение
интервала интегрирования long double t_max, шаг вывода long double h_out и
количество узлов выводимых решений int size_out. Аргументы найденных решений
хранятся в Vector output_solver_t, собственно решения хранятся в массиве векторов
Vector *output_solver_x размерностью int size_ode. Переменная int
continue_solver предназначена для внешнего прерывания алгоритмов решения ОДУ.
Объекты Vector val_ode_function и Matrix val_JakobyMatrix предназначены для
хранения значений вектор-функции, описывающей системы ОДУ и ее матрицы Якоби.
Класс C_ode оснащен конструктором задач указанного порядка C_ode(int), в котором
определяются размерности векторных и матричных объектов и вызывается функция выделения
памяти init_mem() для массива векторов, хранящих решения. Освобождает эту память
деструктор ~C_ode(void).
Функция void set_name(char*) присваивает название задачи. Функция void
set_hhh(long double) устанавливает значения шага интегрирования hhh. Явное указание
шага hhh необходимо для процедуры решения ОДУ с постоянным шагом. Процедуры с
автоматическим изменением шага начинают вычисления с текущего его значения, заданного по
умолчанию или указанного функцией set_hhh().
Функция решения системы ОДУ методом Гира переменного порядка с переменным
шагом умещена в программе void metod_Gear_ek_hk(void). Хотя объектные средства
позволяют существенно изменить вид программ, решающих ОДУ, функция
metod_Gear_ek_hk() оставлена в процедурном виде, что облегчает ее возможное
преобразование к другим формам или языкам программирования. Ее текст сопровождается
подробными комментариями со ссылками на литературу.
Интегрируя дифференциальные уравнения, необходимо проверять верность полученных
результатов. Снабдив класс C_ode двумя методами интегрирования, создаем простой способ
решить эту задачу, ведь вероятность получить два одинаково ошибочных решения ничтожно
мала. С этих соображений в классе C_ode оставлена функция void
prohnoz_AB3_korrekc_AM4(void) решения систем ОДУ методом прогноза-коррекции. Для
прогноза применен метод Адама-Башфорта 3-го порядка, для коррекции – метод АдамсаМултона 4-го порядка. Заметим, что накопление многих методов решения ОДУ ведет к
типичной ошибке, поскольку новый метод, лишь немного улучшает качество решающих
алгоритмов. Поэтому целесообразнее сосредоточить усилия на разработке одного, но
эффективного метода.
Многоточечные методы интегрирования ОДУ требуют так называемого «разгона», то
есть – нахождения нескольких решений, необходимых для начала процедуры вычисления
следующего решения на их основании. В классе C_ode для разгона применяется две функции.
Первая из них Vector ode_metod_Gear_1_one_iteration (const Vector&, long
double, long double) разработана по методу Гира 1-го порядка. Вторая Vector
RungeKyttaMmersona4_one_iteration (const Vector&, long double, long double)
– по методу Рунге-Кутта-Мерсона 4-го порядка. Обе эти функции, принимая входными
PDF created with pdfFactory Pro trial version www.pdffactory.com
параметрами значение решения xi и аргумента ti в предыдущей точке, а также величину шага h,
возвращают решение в следующей точке: xi+1 = …one_iteration(xi, ti, h). Разумеется, эти функции
пригодны для нахождения решения на всем отрезке интегрирования, но такие программы из
названных выше соображений в класс не включены. Наличие двух программ вычисления
решений в одной точке позволяет проверять их правильность. Для разгона многоточечного
алгоритма Гира применен одноточечный метод Гира, для разгона метода прогноза-коррекции
применен одноточечный метод Рунге-Кутта-Мерсона.
Для установки начальных условий задачи численного решения системы ОДУ и
присвоения параметров вывода предусмотрена функция void set_param(long double,
const Vector&, long double, long double). Ее параметры в порядке следования задают
значение аргумента t0 в точке начальных условий, значение решения в точке начальных условий
x(t0), правую границу отрезка интегрирования tmax, шаг вывода результатов Δt. Вызов этой
функции имеет вид: set_param(t0, x0, tmax, Δt).
Описание функции f(x,t), которая задает систему дифференциальных уравнений,
полностью умещено в функцию virtual Vector& calculate_ode_function(const
Vector&, long double). Её параметры принимают значение векторного аргумента x –
решения ОДУ и скалярного независимого аргумента t.
Вопрос, как программно описать функцию f(x,t) – остается камнем преткновения, об
который разбивалась переносимость процедурных программ, и который изрядно усложняет
использование современных объектных программ. О способах связи функции f(x,t),
определяющей дифференциальное уравнение, с универсальными вычислительными алгоритмы
его решения, речь пойдет ниже.
Для решения нелинейных алгебраических уравнений, которые получаются на каждом
шаге численного интегрирования, необходимо вычислять значение матрицы Якоби J(x, t),
состоящей из частных производных от вектор-функции f(x,t), то есть: Jij(xk,t)=∂fi(x,t)/∂xj, при
x=xk, t=tk. Матрицу Якоби определяют двумя способами. В общем случае её находят при
помощи численного определения приблизительных значений частных производных. Для
уравнений известной структуры матрицу Якоби находят аналитически. В классе C_ode матрицу
Якоби J(x, t) вычисляет функция Matrix& calculate_JakobyMatrix(const Vector&,
long double), которая принимает векторной и скалярный аргументы x, t, и возвращает
квадратную матрицу соответствующего размера, значения которой найдены численным
методом. Хотя этого достаточно для решения ОДУ, все же лучше, где это возможно, вычислять
матрицу Якоби аналитически. Для изменения метода вычисления матрицы Якоби годятся как
явная замена функции calculate_JakobyMatrix() в базовом классе, так и создание
порожденного класса с пепреопределенной в нем функции calculate_JakobyMatrix(). При
этом, разумеется, целесообразно также переопределить функцию
calculate_ode_function(), которая отражает f(x,t). Заметим, что обе эти функции
возвращают ссылку на существующие объекты, входящие в члены-данные класса C_ode,
благодаря чему сокращены расходы на создание и уничтожение возвращаемых объектов при
обращении к ним.
Оператор вывода позволяет вывести в поток найденные решения. Для прерывания
алгоритма интегрирования предусмотрена функция void break_solver(void), вызов
которой устанавливает признак принудительного прекращения решения. Для обращения к
найденным решениям как дискретным функциональным зависимостям служит функция Tabl
get_solver(int).
В предлагаемой библиотеке классов также разработаны классы матриц Паскаля и
векторов Нордсика. Матрица Паскаля – это правая треугольная матрица, элементы которой
определены соотношением ai+1 j+1= j!/((j-i)!i!), j>=i>=0. Матрицы Паскаля необходимы при
решении систем ОДУ многоточечными методами. Класс матриц Паскаля Matrix_Pascal
порожден от класса Matrix, в нем переопределены операции умножения на матрицы, векторы и
векторы Нордсика.
PDF created with pdfFactory Pro trial version www.pdffactory.com
Вектором Нордсика называют массив векторов, содержащих информацию о решениях
ОДУ в нескольких смежных точках. Запись многоточечных методов решения ОДУ в
представлении вектора Нордсика особенно удобна для автоматического управления величиной
шага. Создание класса векторов Нордсика VectN – отличительная особенность предложенной
библиотеки классов, благодаря этому алгоритмы решения ОДУ приобрели вид, тождественный
математическим записям.
Класс векторов Нордсика содержит массив векторов Vector *elem, в количестве int
size размерностью int size_vector, конструктор единичного размера для объявления
массивов векторов Нордсика VectN(void), конструктор вектора Нордсика указанных размеров
VectN(int порядок ОДУ, int порядок метода интегрирования), а также конструктор
копирования VectN(const VectN &) и оператор присвоения. Память под массив векторов
выделяет функция init_mem(), освобождает память деструктор ~VectN(void).
В классе VectN определены операции сложения и вычитания векторов Нордсика, их
умножения на скаляр, обычную матрицу и матрицу Паскаля, деления на скаляр а также
необычная операция, обозначенная символом &, которая создает вектор Нордсика, умножая
левый векторный операнд на элементы правого векторного операнда. В математическом
выражении ей соответствует операция обычного умножения. Эти операции необходимы для
создания программы по алгоритму Гира, и при том они сохраняют за программными
операторами вид математических выражений. Что придает программе решения ОДУ методом
Гира читабельности, которой трудно достичь другим путем.
Кроме того, в классе VectN введены функция преобразования вектора Нордсика,
соответствующая умножению шага интегрирования на константу void
multiple_diag_pow_matrix (long double), функции понижения void decremrnt(void)
и повышения void increment(const VectN&) размерности векторов Нордсика при
изменении порядка интегрирования. А также – оператор () индексного доступа к его элементам
и функции, возвращающие размерности.
2. ОДУ – это же просто!
Теперь рассмотрим пример решения дифференциального уравнения dx(t)/dt= - x,
оставленного по умолчанию в функции Vector& C_ode::calculate_ode_function().
Сначала задаем размерность уравнения n=3, создаем объект уравнения C_ode ode0(n) и
присваиваем ему название "dx/dt=-x".
int n = 3;
C_ode ode0(n);
ode0.set_name("dx/dt=-x");
// Порядок ОДУ.
// Объект ОДУ.
// Его название.
Потом формируем значение начальных условий: t0,= 0, x0 = (0, 1, 2).
// Начальные условия.
long double t0 = 0.0;
Vector x0(n); x0(0)=0.0; x0(1)=1.0;
x0(2)=2.0;
Определяем шаг и правую границу вывода.
long double t_max
= 10.0;
long double delta_t = 0.1;
// Правая граница.
// Шаг вывода
Устанавливаем начальные условия и параметры.
ode0.set_param(t0, x0, t_max, delta_t);
Вызываем программу решения ОДУ методом Гира переменного порядка и шага.
ode0.metod_Gear_ek_hk();
PDF created with pdfFactory Pro trial version www.pdffactory.com
Ввод полученных решений.
cout << ode0 << "\n";
Ввод найденных переменных состояния как дискретных функций.
cout << "Переменные состояния уравнения dx/dt=-x\n";
for (int i=0; i<n; i++)
{
cout << i << "-ая переменная состояния \n";
cout << ode0.get_solver(i) << "\n";
}
Решение этого же уравнения методом прогноза - коррекции.
ode0.set_hhh (1.0e-4);
ode0.prohnoz_AB3_korrekc_AM4();
cout << ode0 << "\n";
// Шаг решения.
// Решение.
// Задачи и решения.
В полном виде этот пример показан в Листинге 1.
Хотя изменение функций базового класса позволяет быстро получить необходимое
решение, но такой подход – не из лучших.
Рассмотрим другой подход, при котором описание уравнений содержится в производном
классе. Пусть необходимо решить уравнение аттрактора Лоренца:
x΄=sy-sx;
y΄=rx-y-y*z;
z΄=-bz+xy;
где s = 10, b = 8/3, r = 15.
Создадим класс Lorenz_ODE, производный от C_ode.
class Lorenz_ODE : public C_ode
{
private:
Vector& Lorenz_ODE::calculate_ode_function (const Vector&, long double);
public:
Lorenz_ODE (int n);
~Lorenz_ODE (void);
};
Переопределим в производном классе функцию calculate_ode_function (), так
чтобы три оператора присвоения отражали правые стороны уравнения Лоренца.
Vector& Lorenz_ODE::calculate_ode_function (const Vector &xxx, long double ttt)
{
long double s = 10;
long double b = 8/3;
long double r = 15;
val_ode_function(0) = s * xxx(1) - s * xxx(0);
val_ode_function(1) = r * xxx(0) xxx(1) - xxx(1)*xxx(2);
val_ode_function(2) = - b*xxx(2) + xxx(0)*xxx(1);
return val_ode_function;
}
PDF created with pdfFactory Pro trial version www.pdffactory.com
Этих операций достаточно для решения уравнения. Вот пример решения уравнения
Лоренца, писаного в производном классе Lorenz_ODE.
// Программа demo_1.cpp
int main(int cparam, char *par[])
{
// Пример 1.
// Уравнение аттрактора Лоренца
// описано в Lorenz_ODE::calculate_ode_function(...)
int n = 3;
// Порядок ОДУ.
Lorenz_ODE ode1(n);
// Объект ОДУ.
ode1.set_name("Атракор Лоренца");
// Его название.
// Начальные условия.
long double t0 = 0.0;
Vector x0(n); x0(0)=1.1; x0(1)=2.5;
x0(2)=7.1;
// Параметры вывода решения.
long double t_max = 7.0;
// Правая граница.
long double delta_t = 0.1; // Шаг вывода
// Установить начальные условия и параметры вывода решения ОДУ.
ode1.set_param(t0, x0, t_max, delta_t);
// Решение уравнения Лоренца методом Гира.
ode1.metod_Gear_ek_hk();
// Вывод задачи и решения уравнения Лоренца.
cout << ode1 << "\n";
return 0;
}
Приведенный пример иллюстрирует, как на основании класса C_ode создавать
пользовательский класс с описанием задачи Коши и получать его решение.
Хотя этого примера достаточно для решения поставленной задачи, все – же в
предлагаемой библиотеке предложен альтернативный вариант. Для класса C_ode создан полный
аналог – класс C_odep, в котором функция, описывающая дифференциальное уравнение,
вызывается через ссылку на функцию, принадлежащую к типу Vector
(*ODE_Function)(const Vector&, long double). Для определения f(x,t) необходимо
создать глобальную функцию, принадлежащую к типу ODE_Function и передать в объект
класса C_odep ссылку на её при помощи void C_odep::set_calculate_ode_function
(const ODE_Function&). Сказанное иллюстрирует следующий пример.
Рассмотрим уравнение Ван-дер-Поля:
x΄= y;
y΄= μ(1.0 – x2)y - x;
где μ = 100.
Этому уравнению отвечает следующая функция.
Vector f_vpd (const Vector &xxx, long double ttt)
{
Vector ret(xxx.get_size());
long double mju = 100;
ret(0) = xxx(1);
ret(1) = mju *(1.0 - xxx(0)*xxx(0))*xxx(1) - xxx(0);
return ret;
}
PDF created with pdfFactory Pro trial version www.pdffactory.com
Решение уравнения потребует передачи ссылки на функцию f_vpd().
int n = 2;
// Порядок ОДУ.
C_odep ode3(n);
// Объект ОДУ.
ode3.set_name("Уравнение Ван-дер-Поля"); // Его название.
long double t0 = 0.0;
// Начальные
Vector x0(n); x0(0)=2.1; x0(1)=0.0; //
long double t_max = 90.0;
long double delta_t = 0.1;
условия.
// Правая граница.
// Шаг вывода
// Установить начальные условия и параметры вывода решения ОДУ.
ode3.set_param(t0, x0, t_max, delta_t);
// Связать вектор-функцию ОДУ с решателем.
ode3.set_calculate_ode_function (&f_vpd);
// Решение уравнения Ван-дер-Поля методом Гира.
ode3.metod_Gear_ek_hk();
Второй метод описания функции f(x,t) менее эффективный, поскольку при каждом
обращении к этой функции создается и уничтожается объект вектора. Все же для быстрой
подготовки решения одного уравнения второй метод несколько проще.
3. Резюме
Библиотека классов и приведённые примеры содержатся в файле c_ode.rar
(http://www.argc-argv.relc.com/magazine_src.php).
Предлагаемая библиотека разработанная с целью решения жестких уравнений, структура
и порядок которых изменяется в ходе вычислений. Она проста в применении, на ее основе легко
создавать собственные программы решения ОДУ. В ней применен простой метод вывода
предупреждений и сообщений об ошибках в текстовый файл. Для большинства уравнений с ее
помощью удалось найти решения.
Сравнение предлагаемых программ с программами, разработанными при помощи
MatLab, показывает, что они уступают последним, в частности при обработке очень жестких
переходов и вблизи точек, где решение не существует. Все же, возможно, что благодаря новой
форме записи алгоритма Гира, близкой к математическим выражениям, предлагаемые
программы послужит подспорьем как при изучении применяемых численных методов, так и для
быстрого решения отдельных уравнений или же при разработке более крупных проектов.
4. Литература
1. Gear C.W. Numerical Initial Value Problem in Ordinary Differential Equations. Engle-wood
Cliffs, N.J.: Prentice-Hall, Inc., 1971.
2. Чуа Л.О., Лин П-М. Машинный анализ электронных схем (алгоритмы и
вычислительные методы). Пер. с англ. – М.: Энергия, 1980. – 640 с. С.416-241, 441-448,
458-474.
3. Арушунян О.Б., Залеткин С.Ф. Численное решение обыкновенных дифференциальных
уравнений на Фортране. – М.: Изд-во МГУ, 1990.
4. http://www.exponenta.ru.
5. http://www.AnT4669.de.
6. M.Milde. ODE ++ – a class library for ordinary differential equations. http://www.minet.unijena.de/www/fakultaet/iam/ode++. 1998.
7. Паучок В. Библиотека математических классов // Argc&Argv. – 2003, № 1. – С. 30-33. //
http://www.argc-argv.kiev.ua/SOURCES/1_2003/matlib.zip.
8. Паучок В. Классы статистического анализа // Argc&Argv. – 2004, № 4. // http://www.argcargv.kiev.ua/4_2004/article1.pdf.
Владимир Паучек
PDF created with pdfFactory Pro trial version www.pdffactory.com
Листинг 1. Программа demo_0.cpp
#include
#include
#include
#include
#include
#include
#include
#include
#include
"const.h"
"const_f.h"
"vector.h"
"matrix.h"
"matrixeo.h"
"matrixpa.h"
"vectn.h"
"rozv_lr.h"
"tabl.h"
#include "ode.h"
/*-----------------09.07.04 13:48------------------* demo_0.cpp
* Демонстрационная программа с примером решения * системы обыкновенных дифференциальных уравнений (ОДУ).
* Уравнения содержится в базовом классе.
* --------------------------------------------------*/
int main(int cparam, char *par[])
{
// Пример 0.
// Уравнения dx/dt=-x
// описано в C_ode::calculate_ode_function(...).
int n = 3;
// Порядок ОДУ.
C_ode ode0(n);
// Объект ОДУ.
ode0.set_name("dx/dt=-x"); // Его название.
// Начальные условия.
long double t0 = 0.0;
Vector x0(n); x0(0)=0.0; x0(1)=1.0;
x0(2)=2.0;
// Параметры вывода решения.
long double t_max
= 10.0; // Правая граница.
long double delta_t = 0.1; // Шаг вывода
// Установить начальные условия и параметры вывода решения ОДУ.
ode0.set_param(t0, x0, t_max, delta_t);
// Решение уравнения dx/dt=-x методом Гира.
ode0.metod_Gear_ek_hk();
// Вывод задачи и решения уравнения dx/dt=-x.
cout << ode0 << "\n";
cout << "Переменные состояния уравнения dx/dt=-x\n";
for (int i=0; i<n; i++)
{
cout << i << "-ая переменная состояния \n";
cout << ode0.get_solver(i) << "\n";
}
// Решение уравнения dx/dt=-x методом прогноза - коррекции.
ode0.set_hhh (1.0e-4);
// Шаг решения.
ode0.prohnoz_AB3_korrekc_AM4();
// Решение.
cout << ode0 << "\n";
// Задачи и решения.
return 0;
}
PDF created with pdfFactory Pro trial version www.pdffactory.com
Download