Практическое введение в решение дифференциальных уравнений в Python [Николай Михайлович Ершов] (pdf) читать онлайн

Книга в формате pdf! Изображения и текст могут не отображаться!


 [Настройки текста]  [Cбросить фильтры]

Н. М. Ершов

Практическое
введение в решение
дифференциальных
уравнений в Рython

Москва, 2022

УДК 517.9
ББК 22.161.61
Е80

Е80

Ершов Н. М.
Практическое введение в решение дифференциальных уравнений
в Python. – М.: ДМК Пресс, 2022. – 176 с.: ил.
ISBN 978-5-93700-147-4
Книга посвящена вопросам практического применения символьных вычислений для решения различных прикладных задач, приводящих к дифференциальным уравнениям и их системам, с использованием библиотеки символьных
вычислений SymPy языка Python.
Издание ориентировано на школьников старших классов, студентов, преподавателей и всех, интересующихся тематикой математического моделирования.
Может служить дополнением к классическим учебникам по теории обыкновенных
дифференциальных уравнений.

УДК 517.9
ББК 22.161.61

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

ISBN 978-5-93700-147-4

© Н. М. Ершов, 2022
© Оформление, издание, ДМК Пресс, 2022

Оглавление
https://t.me/it_boooks

Введение. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
Глава

1

Глава

2

Глава

3

Глава

4

Глава

5

Глава

6

Глава

7

Глава

8

Глава

9

Глава

10

Вращение жидкости . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
Задача (7) Модель (9) Упражнения и замечания (18)

Водяные часы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
Задача (21) Модель (22) Упражнения и замечания (29)

Элементарные химические реакции . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
Закон действующих масс (33) Задача (35) Модель (35)
Упражнения и замечания (38)

Задача о четырех жуках . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
Кривые погони (41) Задача (41) Модель (43) Упражнения
и замечания (47)

Барометрическая формула . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
Задача (51) Модель (53) Упражнения и замечания (58)

Модели роста. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
Модель естественного роста (61) Модель Ферхюльста (62)
Модель (62) Упражнения и замечания (68)

Табулирование функций . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
Метод Эйлера (73) Задача (74) Модель (75) Упражнения
и замечания (81)

Ортогональные траектории . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
Ортогональные семейства кривых (85) Задача (85)
Модель (87) Упражнения и замечания (90)

Математическая вышивка . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95
Задача (95) Уравнение Клеро (96) Модель (97)
Упражнения и замечания (101)

Криволинейные зеркала . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105
Задача (105) Модель (106) Упражнения и замечания (112)

4

Оглавление
Глава

11

Глава

12

Глава

13

Глава

14

Глава

15

Глава

16

Из пушки на луну . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115
Задача (115) Модель (116) Упражнения и замечания (120)

Метроном . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123
Задача (123) Модель (124) Упражнения и замечания (130)

Пружинный маятник . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133
Задача (133) Модель (134) Упражнения и замечания (140)

Модель Лотки–Вольтерры . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 143
Задача (143) Модель (144) Упражнения и замечания (149)

Системы реакций первого порядка . . . . . . . . . . . . . . . . . . . . . . . . . . . 153
Задача (153) Преобразование Лапласа (154) Модель (156)
Упражнения и замечания (160)

Геодезические линии . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163
Задача (163) Уравнение Эйлера—Лагранжа (165)
Модель (166) Упражнения и замечания (171)

Введение
Настоящая книга посвящена вопросам практического применения символьных вычислений для решения
различных прикладных задач, приводящих к дифференциальным уравнениям и их системам. Как известно, решение дифференциальных уравнений — с одной
стороны, процесс многогранный и местами даже творческий, с другой — связан с выполнением больших
объемов и чисто рутинной работы: арифметика, алгебраические преобразования, вычисления производных и интегралов и т. д. Такая деятельность является
по большому счету чисто механической, поэтому она
относительно легко и эффективно алгоритмизуется и
может быть реализована программно на любом современном языке программирования.
Программные системы, позволяющие пользователю работать с математическими формулами, выполняя над ними те или иные символьные преобразования, называются системами символьных вычислений,
или системами компьютерной алгебры. Первые такие
системы появились еще в 60-х годах прошлого века.
В настоящее время пользователям доступны десятки подобного рода систем — от коммерческих (Maple,
Mathematica) до систем с открытым исходным кодом
(Reduce, Maxima), некоторые из которых способны работать уже и на мобильных устройствах.
Система SymPy, которой и посвящена данная книга,
является по сути обычной библиотекой языка Python.
Такой подход к построению систем символьных вычислений имеет ряд преимуществ. Во-первых, система
оказывается открытой и доступной всем без исключения пользователям. Во-вторых, работа с библиотекой SymPy в среде Jupyter Notebook позволяет проводить символьные вычисления прямо в браузере либо
локально, либо удаленно с помощью какого-нибудь об-

SymPy — библиотека
Python для символьных вычислений
РИС. 1

6
1

При таком сценарии пользователю вообще не нужно устанавливать локально никакое программное обеспечение.

2

Заметим, что автором не ставилась цель дать полное описание библиотеки SymPy. Такая задача, с одной стороны, является просто неподъемной, библиотека SymPy состоит из большого числа модулей, только часть из которых посвящена решению дифференциальных уравнений. Кроме того, данная библиотека все
еще активно развивается, ее функционал постоянно расширяется и
модифицируется. Более полную и
актуальную информацию по работе с библиотекой SymPy читателю рекомендуется находить либо на официальном сайте библиотеки https://www.sympy.org, либо на профильных ресурсах в сети Интернет, например на сайте
https://stackoverflow.com.

Введение

лачного сервиса1 , например Google Colab. В-третьих,
символьные вычисления в рамках данной библиотеки оказываются интегрированными в обычную программу на языке Python, при этом пользователю системы оказывается доступным весь функционал языка Python, а также вся его инфраструктура в виде бесчисленного набора разнообразнейших пакетов и библиотек.
Предлагаемая читателю книга устроена достаточно просто. Каждая ее глава посвящена рассмотрению
одной прикладной модели из физики, химии, биологии и т. д. После теоретического рассмотрения модели и вывода соответствующего ей дифференциального уравнения максимально детально описывается процесс формализации модели и решения возникающих
в ней дифференциальных уравнений с помощью библиотеки SymPy2 . Каждая глава сопровождается набором упражнений как технического характера — посчитать интеграл, решить уравнение, построить график,
так и исследовательского — построить и исследовать
по описанной схеме аналогичную модель.
Автор с благодарностью примет все конструктивные отзывы, комментарии и замечания относительно структуры и содержания настоящего издания, которые читатель может оставить или в телеграм-чате
https://t.me/odesinsympy, или прислать автору на электронную почту по адресу ershovnm@gmail.com.

ГЛАВА

1

Вращение жидкости
y
https://t.me/it_boooks

ω

Задача • В сосуд, имеющий форму прямого кру-

гового цилиндра, налита жидкость, например вода.
Сосуд вращается с постоянной угловой скоростью ω
относительно оси цилиндра (рис. 1). Требуется определить, какую форму примет поверхность жидкости,
если вращение продолжается достаточно долго1 . При
построении модели мы будем предполагать, что сосуд
достаточно широкий и глубокий, это позволит пренебречь разными поверхностными эффектами около боковых стенок сосуда.
Очевидно, что искомая поверхность должна быть
поверхностью вращения. Поэтому для нахождения ее
формы достаточно рассмотреть осевое сечение нашего сосуда и найти форму соответствующей кривой, из
которой потом мы сможем сформировать и саму поверхность.
Введем в модель систему координат, как это показано на рис. 1. Ось Oy направим по оси цилиндра,
ось Ox — перпендикулярно оси Oy вдоль основания
цилиндра, начало координат, таким образом, оказывается в центре основания цилиндра.
Рассмотрим малый объем ν жидкости на ее поверхности (точка P на рис. 2). Пусть его масса равна m. На
этот объем действуют две силы: сила тяжести F = mg
и сила реакции опоры N со стороны всей остальной
жидкости. Сила тяжести, как обычно, действует вертикально вниз, а реакция опоры — по нормали к нашей
искомой кривой, т. е. перпендикулярно касательной к
этой кривой в точке P .
Обозначим угол наклона данной касательной к оси
Ox через α и разложим силу N на горизонтальную Nx

x
Вращение сосуда с
жидкостью
РИС. 1

1

«Достаточно долго» понимается здесь в том смысле, что вся
жидкость в сосуде должна прийти
в стационарное состояние относительно самого сосуда, т. е. каждый
элементарный ее объем будет совершать только общее вращательное движение с заданной угловой
скоростью ω.

N

Ny
α

y

P

Nx

mg
y0
α
O

x

Силы, действующие
на малый объем жидкости ν
массы m в точке P (x, y)
РИС. 2

Глава 1

8
и вертикальную Ny составляющие (рис. 2):
Nx = N sin α, Ny = N cos α.

Запишем теперь 2-й закон Ньютона отдельно для каждой координаты. Вдоль вертикальной оси наш объем
ν находится в состоянии равновесия, поэтому суммарная сила, действующая на него в вертикальном направлении, должна быть равна нулю:
N cos α − mg = 0.
ω
O

a
x

P

Центростремительное
ускорение для точки P (вид
на сосуд сверху)
РИС. 3

(1)

Единственная сила, действующая на объем ν вдоль горизонтального направления, — это проекция реакции
опоры Nx . Так как данный объем совершает круговое
движение с радиусом вращения x, то он испытывает
центростремительное ускорение a, которое направлено горизонтально к оси вращения (рис. 3). Величина
этого ускорения вычисляется по формуле (квадрат угловой скорости на радиус вращения):
a = ω 2 x.

(2)

Таким образом, вдоль оси Ox второй закон Ньютона
записывается в следующем виде:
−N sin α = −ma = −mω 2 x,

(3)

обе части взяты с отрицательным знаком, потому что
и сила Nx , и ускорение a направлены против положительного направления оси Ox.
Запишем формулы (1) и (3) в виде системы двух
уравнений:
Ox : N sin α = mω 2 x,
(4)
Oy : N cos α = mg.
Поделим первое уравнение системы на второе:
tg α =

ω2 x
.
g

Теперь учтем тот факт, что согласно геометрическому смыслу производной функции y(x) величина производной в точке x0 равна тангенсу угла наклона касательной к соответствующей кривой y(x) в заданной
точке x0 . То есть мы можем заменить tg α на y 0 , что и
даст нам искомое дифференциальное уравнение:
y0 =

ω2 x
.
g

(5)

Вращение жидкости

9

Если дополнительно обозначить высоту жидкости
в точке x = 0 (на оси вращения) через y0 , то можно
поставить для полученного дифференциального уравнения задачу Коши:
ω2 x
,
g
y(0) = y0 .
y0 =

(6)

Модель • Рассмотрим теперь процесс решения поставленной начальной задачи с помощью библиотеки
SymPy. Сначала мы построим общее решение дифференциального уравнения (5), затем, используя начальное условие y(0) = y0 , решим начальную задачу (6).
Следующим шагом определим параметр y0 из условия
постоянства объема вращаемой жидкости, рассмотрев
при этом два случая — подкритический, когда поверхность вращаемой жидкости не касается дна цилиндра,
и надкритический, когда такое касание происходит.
1 Построение модели начинаем с подключения библиотеки SymPy и функции display, определенной в модуле IPython.display.
1
2

from sympy import *
from IPython.display import display
2 С помощью команды symbols создаем символы x,
g, ω, y0 и C1 , которые нам потребуются для задания и
решения дифференциального уравнения. Аргументом
этой команды является строка, содержащая вводимые
символы и определяющая, как эти символы будут выглядеть при печати. Возвращает эта команда кортеж
с объектами SymPy, которые нужно запомнить в соответствующих переменных для последующего использования. Так как параметр g, представляющий собой
ускорение свободного падения, является всегда строго положительным, то при создании соответствующего символа укажем этот факт, используя опциональный аргумент positive2 . Используем команду display,
чтобы посмотреть, как выглядят определенные нами
символы. Например, видно, что SymPy корректно отображает греческие буквы, а цифры после символов преобразует в нижние индексы этих символов3 .

1
2
3

x, omega, y0, C1 = symbols("x omega y0 C1")
g = symbols("g", positive = True)
display(x, g, omega, y0, C1)

2

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

Для краткости вывод команды
display в данном случае мы поместили в одну строку, на самом
деле эта команда выводит каждое
из переденных ей выражений в отдельной строке.

Глава 1

10
x g ω y0 C1
3 Наше дифференциальное уравнение

y0 =

ω2 x
g

является простейшим, это значит, что оно решается
непосредственным интегрированием его правой части.
Следовательно, для решения этого уравнения с помощью библиотеки SymPy нам достаточно определить
только его правую часть, а не все уравнение целиком4 .
Создадим переменную ode_rhs, в которую и запишем
правую часть решаемого уравнения.

4

Как это мы будем делать в дальнейшем при решении более сложных дифференциальных уравнений.
1
2

ode_rhs = omega ** 2 * x / g
display(ode_rhs)

ω2 x
g

5

4 Общее решение простейшего дифференциального
уравнения y 0 = f (x) — это неопределенный интеграл
от правой части f (x) этого уравнения. В библиотеке
SymPy неопределенные интегралы вычисляются с помощью команды integrate(f, x), где f — подынтегральное выражение, x — переменная интегрирования. Константа интегрирования C1 при этом автоматически командой integrate к найденному интегралу не прибавляется, это надо делать вручную5 , поэтому мы просто к результату работы функции integrate прибавим
символ C1. Найденное общее решение дифференциального уравнения сохраним в переменной dsol.

Если это необходимо.

1
2

dsol = integrate(ode_rhs, x) + C1
display(dsol)

C1 +

ω 2 x2
2g

5 Стандартный способ определения константы интегрирования C1 в общем решении дифференциального уравнения из начального условия заключается
в подстановке в это решение величин из начального
условия, что приводит к алгебраическому уравнению
для переменной C1 . Решив это уравнение и подставив

Вращение жидкости

11

найденное решение вместо C1 обратно в формулу общего решения, мы найдем решение соответствующей
начальной задачи для заданного дифференциального
уравнения. В нашем случае начальное условие имеет
вид y(0) = y0 , поэтому мы сначала подставляем в общее решение dsol вместо x значение 0, это делается с
помощью метода subs(f, g), где f — выражение, которое мы хотим заменить на выражение g. Полученное
в результате подстановки выражение приравниваем к
величине y0, используя команду Eq(lhs, rhs), которая
создает равенство вида lhs = rhs6 . Созданное уравнение запоминаем в переменной eq1.
1
2

eq1 = Eq(dsol.subs(x, 0), y0)
display(eq1)

6

Python не позволяет переопределить команду присваивания =,
поэтому для создания уравнений
приходится использовать специальную функцию Eq.

C1 = y0
6 Следующим шагом решаем построенное уравнение
с помощью команды solveset(eq, x), где первый аргумент eq — это само уравнение, второй аргумент x
— переменная, относительно которой данное уравнение должно решаться7 . Найденное решение сохраним
в переменной sol1.
1
2

sol1 = solveset(eq1, C1)
display(sol1)

{y0 }
7 Как мы видим, команда solveset выдает решения
уравнения в виде множества, чтобы выбрать какоето одно решение, нужно преобразовать это множество
в кортеж (tuple) или список (list) и выбрать соответствующий элемент, указав его индекс. В нашем случае
множество содержит всего одно решение, поэтому для
его извлечения используем нулевой индекс. Запоминаем найденное значение в переменной C2.
1
2

C2 = tuple(sol1)[0]
display(C2)

y0
8 Последним действием подставляем найденное значение С2 вместо символа C1 в общее решение dsol, что
и дает нам искомое решение начальной задачи. Сохраним это решение в переменной dsol_y0.
1
2

dsol_y0 = dsol.subs(C1, C2)
display(dsol_y0)

7

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

Глава 1

12

y0 +

ω 2 x2
2g

9 Как видно из последней формулы, найденная зависимость высоты y поверхности жидкости от расстояния до оси вращения x оказывается квадратичной,
т. е. интегральные кривые (графики решений дифференциального уравнения) должны быть параболами8 .
Убедимся в этом, построив графики решения. Библиотека SymPy имеет несколько простых функций для построения графиков9 , простейшей из которых является функция plot. Построим с помощью этой функции
график найденного нами решения начальной задачи
при следующих значениях входящих в это решение
констант:
y0 = 0, ω = π/2, g = 9.8.

8

Таким образом, искомая поверхность будет парабалоидом вращения.
9
Являющихся, по сути, надстройкой над существенно более
мощной графической библиотекой
Matplotlib.

Чтобы сделать в некоторой формуле сразу несколько
подстановок, можно создать из них словарь, ключами
которого будут заменяемые символы или выражения,
и подать этот словарь на вход методу subs.
1
2

ds = dsol_y0.subs({y0: 0, omega: pi/2, g: 9.8})
p1 = plot(ds)

Результат выполнения10 этого фрагмента кода показан на рис. 4.
10 Построим теперь графики семейства решений начальной задачи для различных значений угловой скорости ω ∈ [0, 2π] с шагом π/10. Для формирования
списка значений переменной omega будем использовать
функцию arange из библиотеки NumPy. Для построения
нескольких кривых на одном графике нужно сначала
создать пустой график и запомнить его в некоторой
переменной, в нашем случае это будет переменная p2.
Для каждой кривой следует создавать свой график
(переменная p) и затем добавлять его к общему графику p2 с помощью метода extend. По умолчанию любой
создаваемый график в среде Jupyter сразу же визуализируется, чтобы избежать этого, нужно в команде
plot использовать опцию show = False. Построенный
график можно визуализировать с помощью вызова метода show.

График решения начальной задачи
РИС. 4

10
Переменная pi является предопределенной константой в SymPy.

1
2
3

from numpy import arange
p2 = plot(show = False)
for om in arange(0, 2 * pi, pi / 10):

Вращение жидкости

4
5
6
7

13

ds = dsol_y0.subs({y0: 0, omega: om, g: 9.8})
p = plot(ds, (x, -1, 1), show = False)
p2.extend(p)
p2.show()

Результат выполнения этого кода приведен на рис. 5.
11 При построении графиков решения выше мы произвольно задавали значение параметра y0 равным нулю. Более естественный подход к определению этого
параметра заключается в вычислении объема вращающейся жидкости и учете очевидного условия, что
этот объем является постоянной величиной, не зависящей от скорости вращения ω. Пусть объем жидкости
равен V0 . Чтобы выразить величину y0 через V0 , нам
надо вычислить объем вращающейся жидкости, приравнять его к V0 и решить полученное уравнение относительно y0 . Заметим, что интересующий нас объем
получается вращением участка кривой y(x) на отрезке
[0, R] вокруг оси Oy, где R — радиус основания цилиндра (рис. 6). Следовательно, для его вычисления можно воспользоваться двойным интегралом в полярных
координатах11 :

V =

Z2πZR
0

V =

0

y(x)

y0

f (r) rdrdϕ.

y(x) xdxdϕ.

0

Данный двойной интеграл можно упростить с учетом
того, что подынтегральное выражение не зависит от
полярного угла ϕ:
ZR

y(x) xdx.

R
x

Вычисление объема
вращающейся жидкости

0

V = 2π

y

РИС.

Роль радиуса r в нашем случае играет переменная x,
а функция f (r) представлена решением начальной задачи y(x), следовательно,
Z2πZR

График семейства решений начальной задачи для
разных значений угловой скорости ω
РИС. 5

(7)

0

Введем два дополнительных символа для радиуса цилиндра и объема содержащейся в нем жидкости (оба
параметра являются строго положительными величинами), вычислим определенный интеграл (7) и запомним результат в переменной vol. Определенные интегралы в библиотеке SymPy вычисляются с помощью той

11

6

Наша поверхность является
поверхностью вращения, поэтому
функция f (r) не зависит от полярного угла ϕ.

Глава 1

14

же функции integrate, но вторым ее аргументом для
этого нужно указать кортеж из трех элементов: переменная интегрирования, нижний и верхний пределы
интегрирования.
1
2
3

V0, R = symbols("V0 R", positive = True)
vol = 2 * pi * integrate(dsol_y0 * x, (x, 0, R))
display(vol)





R4 ω 2
R 2 y0
+
8g
2



12 Приравниваем найденный объем к V0 и получаем
уравнение относительно переменной y0.
1
2

eq2 = Eq(vol, V0)
display(eq2)





R4 ω 2
R 2 y0
+
8g
2



= V0

13 Решаем полученное уравнение относительно y0.
1
2

sol2 = solveset(eq2, y0)
display(sol2)

(



R4 ω 2
g



4V0
π

4R2

)

14 Уравнение eq2 линейное, поэтому решение у него
единственное, для его извлечения используем нулевой индекс. Заодно упростим полученную формулу,
используя команду simplify.
1
2

y1 = simplify(tuple(sol2)[0])
display(y1)



R2 ω 2
V0
+
4g
πR2

15 Подставляем найденное выражение y1 вместо символа y0 в решение dsol_y0 начальной задачи. Получаем еще одно выражение для решения этой же начальной задачи, но теперь выраженное через заданный объем жидкости V0.
1
2

dsol_V0 = dsol_y0.subs(y0, y1)
display(dsol_V0)

Вращение жидкости



15

R2 ω 2
ω 2 x2
V0
+
+
4g
2g
πR2

16 Построим еще раз семейство итегральных кривых
нашей задачи для разных значений скорости вращения ω для случая V0 = 2 (рис. 7).
1
2
3
4
5
6

p3 = plot(show = False)
for om in arange(0, 2 * pi, pi / 10):
ds = dsol_V0.subs({V0:2, R:1, omega:om, g:9.8})
p = plot(ds, (x, -1, 1), show = False)
p3.extend(p)
p3.show()
17 Видно, что поведение интегральных кривых теперь стало более естественным — нижняя точка парабалоида постепенно опускается при увеличении скорости вращения. Однако при достижении дна цилиндра
(y = 0) это поведение становится нереалистичным —
поверхность жидкости в некоторой области оказывается ниже дна цилиндра, а интегрирование по этой
области дает отрицательный объем. То есть наши выкладки справедливы только при условии, что кривая
y(x) располагается строго над осью Ox. Найдем критическую скорость ω0 , при которой нижняя точка поверхности жидкости (при x = 0) касается дна цилиндра. Для этого подставим в решение x = 0, приравняем его к нулю и решим полученное уравнение относительно скорости вращения ω.

1
2
3

График семейства решений начальной задачи для
разных значений угловой скорости ω при фиксированном
объеме V0
РИС. 7

eq3 = Eq(dsol_V0.subs(x, 0), 0)
sol3 = solveset(eq3, omega)
display(sol3)



√ √
√ √ 
2 V0 g 2 V0 g
− √ 2 , √ 2
πR
πR

18 Команда solve в данном случае выдает два решения, отличающихся только знаком (т. е. направлением
вращения), выберем из них положительное (с индексом 1) и сохраним его в переменной omega0. Построим графики интегральных кривых для случая ω < ω0
(рис. 8). Этот случай назовем подкритическим. Для
удобства заранее создадим словарь par для выполнения дальнейших подстановок. Опции xlim и ylim команды plot определяют пределы изменения переменных по горизонтальной и вертикальной осям графика. Кривые рисуем синим цветом, используя опцию
line_color команды plot.

График семейства решений начальной задачи в
подкритическом случае
РИС. 8

Глава 1

16

1
2
3
4
5
6
7
8
9
10
11
12

omega0 = tuple(sol3)[1]
display(omega0)
p4 = plot(ylim = (0, 1.5), show = False)
par = {V0: 2, omega: 0, R: 1, g: 9.8}
om0 = omega0.subs(par)
for om in arange(0, 1.1 * om0, 0.1 * om0):
par[omega] = om
ds = dsol_V0.subs(par)
p = plot(ds, (x, -1, 1), line_color = "skyblue",
show = False)
p4.extend(p)
p4.show()

√ √
2 V0 g
√ 2
πR
19 Рассмотрим теперь по аналогичной схеме надкритический случай, когда скорость вращения превышает критическую, что приводит к образованию в центре
дна цилиндра области, не покрытой жидкостью. Первым шагом найдем радиус x0 этой области. Для этого
приравняем решение dsol_y0 к нулю и решим полученное уравнение относительно x.
1
2
3

)
( √ √ √
√ √ √
2 g −y0
2 g −y0
,

ω
ω

y
y(x)

x0
y0

eq4 = Eq(dsol_y0, 0)
sol4 = solveset(eq4, x)
display(sol4)

20 Из двух найденных решений выберем решение со
знаком плюс. И еще раз вычислим объем, занимаемый жидкостью. Единственное отличие от предыдущего случая состоит в том, что интегрирование теперь
выполняется от x0 до R, т. е. часть ниже оси Ox мы
не интегрируем (см. рис. 9):

R
x

РИС. 9 Вычисление объема
вращающейся жидкости в
надкритическом случае

V = 2π

ZR

y(x) xdx.

x0

1
2
3
4

x0 = tuple(sol4)[0]
display(x0)
vol1 = 2 * pi * integrate(dsol_y0 * x, (x, x0, R))
display(vol1)

Вращение жидкости

17

√ √ √
2 g −y0
ω
 4 2

R ω
R2 y0
gy02

+
+
8g
2
2ω 2
21 Приравниваем вычисленное выражение к V0 и решаем полученное уравнение относительно y0 .
1
2
3

eq5 = Eq(vol1, V0)
sol5 = solveset(eq5, y0)
display(sol5)








R2 ω 2
V0 ω
R2 ω 2
V0 ω
− √ √ ,−
+√ √
2g
2g
π g
π g

22 Получили два возможных значения параметра y0 .
Проверим, какое из них оказывается равным нулю при
подстановке вместо ω критического значения ω0 .
1
2

for s in tuple(sol5):
display(s.subs(omega, omega0))

0


4V0
πR2

23 Выбираем решение с индексом 0 и подставляем
его в решение начальной задачи dsol_y0 вместо символа y0, что дает нам уравнение поверхности для надкритического случая.
1
2
3

y2 = tuple(sol5)[0]
dsol_sup = dsol_y0.subs(y0, y2)
display(dsol_sup)


R2 ω 2
V0 ω
ω 2 x2

+√ √ +
2g
2g
π g

1
2
3
4
5
6
7
8

24 По аналогичной схеме строим семейство кривых
(красного цвета) найденного решения для скорости
ω ∈ [1.2ω0 , 3ω0 ] с шагом 0.2ω0 (рис. 10).

p5 = plot(ylim = (0, 1.5), show = False)
for om in arange(1.2 * om0, 3 * om0, 0.2 * om0):
par[omega] = om
ds = dsol_sup.subs(par)
p = plot(ds, (x, -1, 1), line_color = "red",
show = False)
p5.extend(p)
p5.show()

График семейства решений начальной задачи в
надкритическом случае
РИС. 10

Глава 1

18

25 Наконец, объединим графики двух рассмотренных нами случаев: подкритического (переменная p4)
и надкритического (переменная p5).
1
2
3
4

p6 = plot(ylim = (0, 1.5), show = False)
p6.extend(p4)
p6.extend(p5)
p6.show()

Итоговый график, включающий оба случая, показан
на рис. 11.
График семейства решений начальной задачи в
подкритическом (синие кривые) и надкритическом (красные кривые) случаях
РИС. 11

ТАБЛ. 1 Стандартные математические функции в SymPy

Функция

x
ex
ln x
sin x
cos x
tg x
ctg x
arcsin x
arccos x
arctg x
arcctg x

12

SymPy
sqrt(x)
exp(x)
log(x)
sin(x)
cos(x)
tan(x)
cot(x)
asin(x)
acos(x)
atan(x)
acot(x)

Библиотека SymPy частично
поддерживает работу с символом
бесконечности ∞, который определен в ней под именем oo.

УПРАЖНЕНИЯ И ЗАМЕЧАНИЯ
1 Решите с помощью SymPy следующие квадратные уравнения:
1) x2 − 4x + 3 = 0;
3) 64x2 + 16x + 1 = 0;
2
2) x − 5x + 1 = 0;
4) 2x2 − 3x + 2 = 0.
2 Решите с помощью SymPy следующие кубические уравнения, выведите на экран найденные положительные корни:
1) x3 − 3x2 + 3x − 1 = 0; 3) x3 − 2x2 − 7x − 4 = 0;
2) x3 − 7x + 6 = 0;
4) x3 + 7x2 + 14x + 8 = 0.
3 Дифференцирование выражений в SymPy выполняется
командой diff(f, x), где f — выражение, x — переменная
дифференцирования. Вычислите с помощью этой функции
производные по x следующих выражений (см. табл. 1):

3) ex · 4x + 2;
1) 5x4 + x2 − 4;
2) ax2 + bx + c;
4) arccos(a2 − x2 ).
4 Вычислите с помощью команды integrate следующие
неопределенные интегралы:
Z
Z
1)
(x2 − 2x + 3)dx;
3)
ses ds;
Z
Z
2)
y sin(y 2 )dy;
4)
e2t cos tdt.
5 Вычислите с помощью команды integrate следующие
определенные интегралы12 :
Z1
Z2π
1)
(x3 − 1)dx;
3)
sin 2ϕ dϕ;
2)

−1
Zb
a

0

y−1
dy;
y+1

4)

Z∞
e−3t dt.
1

Вращение жидкости

19

6 Найдите с помощью SymPy общее решение простейшего дифференциального уравнения и решите соответствующую начальную задачу:
1)
2)
3)
4)

1
, y(1) = −1;
x
y 0 = 2 cos2 x, y(π) = 0;

y0 =

1
1 + 2x2

, y(1) = − ;
5
x

y 0 = x ln x, y( e) = 1.
y0 =

Числовые дроби вида n/m с целыми n и m в SymPy задаются командой Rational(n, m).
7 Постройте семейство кривых решения начальной задачи dsol_y0 для значений параметра y0 из интервала [0, 2]
с шагом 0.1 при фиксированной скорости вращения ω = 1.
8 Постройте семейство кривых решения начальной задачи для разных значений ускорения свободного падения g,
список этих значений сформируйте из величин ускорения
свободного падения на поверхности различных космических тел Солнечной системы (Солнце, планеты, Луна).

y
H

9 Для подкритического случая найдите положение неподвижной точки x1 , в которой пересекаются все интегральные кривые рассматриваемого дифференциального уравнения. Может ли эта точка находиться вне цилиндра?
10 Напишите функцию для построения графика решения начальной задачи, автоматически определяющую, к
какому случаю относится решение при заданных значениях параметров V0 , R, g и ω.

x
O

R

Вращение жидкости в
закрытом сверху цилиндре
РИС. 12

11 Постройте модель вращения жидкости в сосуде, имеющем форму парабалоида вращения y = kx2 , k > 0.
12 Рассмотрите модель вращения жидкости в круговом
цилиндре высоты H, который закрыт сверху крышкой, в
предположении, что поверхность жидкости касается верхнего основания цилиндра при угловой скорости ω1 < ω0
(т. е. в подкритическом случае, см. рис. 12).
13 Рассмотрите случай вращения сосуда в форме тонкого прямоугольного параллелепипеда, толщина которого δ
настолько мала, что искривлением поверхности вдоль соответствующей стороны можно пренебречь (рис. 13). Вращение сосуда выполняется вокруг оси, проходящей через
его центр перпендикулярно основанию. Объем жидкости
во вращающемся сосуде в данном случае вычисляется по

δ
Вращение тонкого параллелепипеда
РИС. 13

Глава 1

20
более простой формуле:
ZR
V = 2δ y(x)dx,

(8)

0

где R — половина длинной стороны основания параллелепипеда. Так как объем V постоянен и δ является константой, то постоянным должен быть интеграл в формуле (8).
Найдите величину y0 в предположении, что объем жидкости равен V0 . Рассмотрите по аналогии с построенной нами
моделью подкритический и надкритический случаи.
14 Исследуйте модель вращения жидкости в тонком прямоугольном параллелепипеде (см. предыдущее упражнение) для случая, когда ось вращения смещена относительно центра параллелепипеда (см. рис. 14).
y

x
O
РИС. 14

a

b

Модель со смещенной осью вращения

ГЛАВА

2

Водяные часы
Задача • В 1641 году Торричелли сформулировал

закон вытекания жидкости из отверстий в стенке открытого сосуда и вывел формулу для определения скорости этого вытекания, называемую сейчас формулой
Торричелли. Этот закон утверждает, что скорость v
истечения жидкости через тонкое отверстие, находящееся на глубине h от поверхности жидкости, такая
же, как и у тела, свободно падающего с высоты h
(рис. 1). Если начальная скорость падения равна нулю, то за время t тело пролетит расстояние h = gt2 /2
и приобретет скорость v = gt (согласно формулам равноускоренного движения). Исключив из этих двух равенств время t, найдем, что скорость v тела связана с
расстоянием h (высотой падения) следующей формулой:
p
v = 2gh.
(1)

Пусть у нас имеется сосуд, заполненный водой до
высоты H, в нижней части которого сделано отверстие
площади σ (рис. 2). Требуется определить закон, по
которому изменяется со временем высота h(t) воды в
данном сосуде. Предполагается, что нам задана форма
сосуда в виде зависимости площади S сечения сосуда
от высоты h этого сечения.
Рассмотрим, как изменяется объем воды в сосуде
за время от t до t+∆t. С одной стороны, за указанный
промежуток времени ∆t из сосуда вытечет объем воды, равный ∆V = vσ∆t. Предполагается, что скорость
истечения воды за данный промежуток времени практически не меняется, и согласно
формуле Торричелли

эта скорость равна v = 2gh. Следовательно, объем

Эванджелиста Торричелли

Вытекание жидкости
из сосуда с разных высот
согласно формуле Торричелли (1)
РИС. 1

S
H
h
σ
v
Модель вытекания воды из сосуда
РИС. 2

Глава 2

22
воды снизится на величину
p
∆V = σ 2gh∆t.

(2)

∆V = −S(h)∆h,

(3)

С другой стороны, уровень воды за этот же промежуток времени снизится с h до h + ∆h (т. е. ∆h < 0). Это
значит, что ее объем уменьшится на величину

по аналогичным соображениям мы предполагаем, что
площадь S за рассматриваемый промежуток времени
также практически не меняется. Приравнивая величины (2) и (3) и выполняя предельный переход при
∆t → 0, приходим к дифференциальному уравнению
следующего вида:

РИС. 3

Водяные часы

S

РИС. 4

Цилиндрический сосуд

S(h)

РИС. 5

Сосуд в форме конуса

S(h)dh = −σ

p
p
dh
2ghdt ⇒ S(h)
= −σ 2gh.
dt

(4)

Решить в общем виде это уравнение мы, однако, пока
не можем, т. к. процесс этого решения существенным
образом зависит от вида функции S(h). Если же нам
эта функция задана, то, подставляя ее в уравнение (4),
мы получим уже полностью определенное дифференциальное уравнение, которое можно решать тем или
иным способом.
Решение h(t) уравнения (4), если мы сможем его
найти, позволит нам построить специальную шкалу,
с помощью которой можно по текущему уровню воды определять время, прошедшее с момента открытия сливного отверстия. По сути, это превращает наш
сосуд в широко применявшиеся (вплоть до XVII века) водяные часы, которые в Древней Греции были
известны под названием клепсидры (рис. 3).

Модель • Процесс решения построенного дифференциального уравнения (4) с помощью SymPy рассмотрим на примере сосудов двух видов — в форме кругового цилиндра (рис. 4), когда площадь любого горизонтального сечения является постоянной, и в форме
конуса (рис. 5), когда радиус поперечного сечения линейно убывает с высотой (т. е. площадь убывает с высотой квадратично).
1 Построение модели стандартно начинаем с подключения библиотек.

Водяные часы

1
2
3

23

from sympy import *
from IPython.display import display
from numpy import arange
2 Определяем символы σ, g, t и S 1 , входящие в диф-

ференциальное уравнение (4), каждый из которых является неотрицательной величиной.
1
2
3

1
Вместо S мы будем в дальнейшем подставлять разные выражения, соответствующие той или
иной форме сосуда.

sigma, g, t, S = symbols("sigma g t S",
positive = True)
display(sigma, g, t, S)

σgtS
3 Для создания и решения дифференциальных уравнений с помощью SymPy нам необходимо уметь определять объекты, представляющие собой неизвестные
функции. Такие объекты в SymPy создаются с помощью команды Function2 .
1
2

h = Function("h")(t)
display(h)

2

Если команды Symbol и symbols
вводят так называемые свободные
переменные, то команда Function
определяет зависимую переменную, т. е. функцию.

h(t)
4 Кроме того, нам потребуется производная нашей
неизвестной функции h(t), которую мы создадим с помощью команды Derivative3 . По принятому в SymPy
неформальному соглашению4 производная заданной
функции обозначается символом этой функции и символом подчеркивания.
1
2

h_ = Derivative(h, t)
display(h_)

d
h(t)
dt
5 Теперь мы можем определить наше дифференциальное уравнение в общей форме.
1
2

ode = Eq(S * h_, -sigma * sqrt(2 * g) * sqrt(h))
display(ode)

S

√ √ p
d
h(t) = − 2 gσ h(t)
dt

3

Команда diff вычисляет производную заданного выражения, а
команда Derivative создает формулу для производной.
4

Которого вы, в принципе, можете и не придерживаться.

Глава 2

24

6 Пусть наш сосуд имеет форму цилиндра с постоянной площадью поперечного сечения (рис. 4):

S(h) = S = const .
В этом случае введенный нами символ S становится просто параметром модели, а дифференциальное
уравнение ode оказывается полностью определенным.
В библиотеке SymPy общее решение дифференциального уравнения eq относительно неизвестной функции x
ищется с помощью команды dsolve(eq, x). Найдем общее решение нашего дифференциального уравнения и
сохраним результат в переменной dsol_cyl.
1
2

dsol_cyl = dsolve(ode, h)
display(dsol_cyl)



C12 − 2 2C1 gσt + 2gσ 2 t2
h(t) =
4S 2
7 Построенное общее решение зависит от константы
интегрирования C1 , значение которой можно определить из начального условия. Чтобы получить доступ
к этой константе, нужно определить соответствующий
символ C1. Для примера построим для различных значений константы C1 семейство интегральных кривых
нашего дифференциального уравнения при некоторых
значениях параметров модели (рис. 6). Заметим, что
решение, выдаваемое командой dsolve, является равенством, т. е. объектом типа Eq: чтобы извлечь его
правую часть (которая, собственно, и представляет искомое решение дифференциального уравнения), надо
использовать атрибут rhs5 объекта Eq (строка 6).

Семейство интегральных кривых дифференциального уравнения 4 для случая
S = const
РИС. 6

5

От первых букв словосочетания
right had side. Соответственно, левая часть равенства выделяется с
помощью атрибута lhs.

1
2
3
4
5
6
7
8
9

C1 = symbols("C1")
par = {g: 9.8, sigma: 0.1, S: 1.0, C1: 0.0}
p1 = plot(show = False)
for c in arange(-20, 21, 2):
par[C1] = c
ds = dsol_cyl.rhs.subs(par)
p = plot(ds, (t, -120, 120), show = False)
p1.extend(p)
p1.show()
8 Хотя построенные нами графики являются математически корректными, с точки зрения рассматриваемой физической модели они выглядят нереалистично
— высота со временем должна убывать (жидкость вытекает из сосуда), а наши графики показывают, что

Водяные часы

25

после полного вытекания жидкости из сосуда ее высота начинает расти. Кроме того, остается неясным
физический смысл константы C1 , намного более наглядным будет использование вместо этой константы
интегрирования какой-нибудь осмысленной величины,
например начальной высоты жидкости6 :
h(0) = H.

(5)

6

Это выражение, очевидно, служит начальным условием для рассматриваемого дифференциального уравнения.

Итак, определим символ H (положительная величина)
и подставим начальное условие (5) в общее решение
dsol_cyl, что даст нам уравнение относительно переменной C1.
1
2
3

H = symbols("H", positive = True)
eq1 = dsol_cyl.subs({h: H, t: 0})
display(eq1)

H=

C12
4S 2

9 Решим это уравнение, решение сохраним в переменной sol1.
1
2

sol1 = solveset(eq1, C1)
display(sol1)

o
n √

−2 HS, 2 HS
10 Выберем решение со знаком плюс, запомним его
в переменной C27 . Подставим выбранное выражение в
общее решение dsol_cyl вместо символа C1, результат
подстановки (решение начальной задачи) сохраним в
переменной dsol_cyl_H.
1
2
3
4

C2 = tuple(sol1)[1]
display(C2)
dsol_cyl_H = dsol_cyl.subs(C1, C2)
display(dsol_cyl_H)


2 HS

√ √

−4 2 HS gσt + 4HS 2 + 2gσ 2 t2
h(t) =
4S 2

11 Вычислим время полного вытекания жидкости из
сосуда, заменив в найденном решении высоту h на ноль
и решив полученное алгебраическое уравнение относительно t.

7

При выборе решения из множества с помощью преобразования
этого множества в кортеж рекомендуется проверять, правильное
ли решение вы выбрали. Порядок
решений в кортеже может отличаться от того порядка, в котором
решения отображаются командой
display.

26

1
2
3

Глава 2

eq2 = dsol_cyl_H.subs(h, 0)
sol2 = solveset(eq2, t)
display(sol2)

)
(√ √
2 HS


12 Решение единственное, извлечем его и запомним в
переменной T1. Теперь мы можем построить графики
решения начальной задачи в интервале от нуля до T1
(предварительно выполнив в T1 подстановку числовых
параметров).
1
2
3
4
5
6
7
8
9
10

T1 = tuple(sol2)[0]
par = {g: 9.8, sigma: 0.1, S: 1.0, H: 0.0}
p2 = plot(show = False)
for ht in arange(1, 21, 1):
par[H] = ht
ds = dsol_cyl_H.rhs.subs(par)
t1 = T1.subs(par)
p = plot(ds, (t, 0, t1), show = False)
p2.extend(p)
p2.show()

Результат выполнения этого кода приведен на рис. 7.
13 Теперь перейдем ко второй модели, в которой рассмотрим вытекание жидкости из сосуда в форме прямого кругового конуса (рис. 5). В этом случае площадь
поперечного сечения является функцией от высоты h.
Предположим, что радиус R поперечного сечения равен высоте h этого сечения. Вычислим площадь сечения (площадь круга радиуса R) на данной высоте,
результат запомним в переменной s.

Семейство кривых решения начальной задачи для
случая S = const и разных
значений начальной высоты
h(0) = H
РИС. 7

1
2
3

R = h
s = pi * R ** 2
display(s)

πh2 (t)
14 Подставим в исходное дифференциальное уравнение вместо символа S найденную площадь s.
1
2

ode_cone = ode.subs(S, s)
display(ode_cone)

√ √ p
d
πh2 (t) dt
h(t) = − 2 gσ h(t)

Водяные часы

27

15 Решим построенное уравнение. В текущей версии
SymPy данное уравнение командой dsolve(ode_cone, h),

к сожалению, не решается. В таких случаях рекомендуется использовать так называемые подсказки (hints)
для команды dsolve. В нашем случае мы видим, что
уравнение ode_cone является уравнением с разделяющимися переменными, поэтому попробуем решить его,
используя подсказку separable, которая передается в
команду dsolve в виде опции hint.

1
2

dsol_cone = dsolve(ode_cone, h, hint = "separable")
display(dsol_cone)
[Eq(h(t),2**(3/5)*(C1-5*sqrt(2)*sqrt(g)*sigma*t)**(2/5)/(2*pi**(2
/5))), Eq(h(t),(C1-sqrt(2)*sqrt(g)*sigma*t)**(2/5)*(-2**(3/5)*5**
(9/10)-2**(3/5)*5**(2/5)-2**(1/10)*5**(9/10)*I*sqrt(sqrt(5)+5)+2*
*(1/10)*5**(2/5)*I*sqrt(sqrt(5)+5))/(8*pi**(2/5))), Eq(h(t),(C1-s
qrt(2)*sqrt(g)*sigma*t)**(2/5)*(-2**(3/5)*5**(9/10)-2**(3/5)*5**(
2/5)-2**(1/10)*5**(2/5)*I*sqrt(sqrt(5)+5)+2**(1/10)*5**(9/10)*I*s
qrt(sqrt(5)+5))/(8*pi**(2/5))), Eq(h(t),(C1-sqrt(2)*sqrt(g)*sigma
*t)**(2/5)*(-2**(3/5)*5**(2/5)+2**(3/5)*5**(9/10)+2**(1/10)*5**(2
/5)*I*sqrt(5-sqrt(5))+2**(1/10)*5**(9/10)*I*sqrt(5-sqrt(5)))/(8*p
i**(2/5))), Eq(h(t),(C1-sqrt(2)*sqrt(g)*sigma*t)**(2/5)*(-2**(3/5
)*5**(2/5)+2**(3/5)*5**(9/10)-2**(1/10)*5**(9/10)*I*sqrt(5-sqrt(5
))-2**(1/10)*5**(2/5)*I*sqrt(5-sqrt(5)))/(8*pi**(2/5)))]

16 Как видно, команда dsolve выдает список сразу
из пяти решений8 , последние четыре из которых, впрочем, оказываются комплексными, т. к. содержат в себе
мнимую единицу, которая в SymPy обозначается символом I9 . Выделим первое решение (с индексом ноль) и
запомним его в той же переменной dsol_cone.
1
2

dsol_cone = dsol_cone[0]
display(dsol_cone)

h(t) =

√ √ 2
3
2 5 C1 − 5 2 gσt 5
2

2π 5

17 Дальше действуем по схеме предыдущей модели.
Подставляем в найденное решение начальное условие
h(0) = H и решаем полученное алгебраическое уравнение относительно константы C1, результат запоминаем в sol3.
1
2
3

eq3 = dsol_cone.subs({h: H, t: 0})
sol3 = solveset(eq3,C1)
display(sol3)

8

Пять объектов Eq с левой частью
h(t).

9

Эти комплексные решения являются следствием извлечения корней пятой степени и того факта, что SymPy все свои вычисления
по умолчанию выполняет в комплексных числах.

Глава 2

28
(

5

2πH 2
5

)

18 Извлекаем из sol3 единственное решение и подставляем его в общее решение dsol_cone вместо символа C1, полученное частное решение сохраняем в переменной dsol_cone_H.
1
2
3

C3 = tuple(sol3)[0]
dsol_cone_H = dsol_cone.subs(C1, C3)
display(dsol_cone_H)

h(t) =


√ √  25
5
3
2 5 2πH 2 − 5 2 gσt
2

2π 5

19 Составляем и решаем уравнение для времени T2
полного вытекания жидкости из сосуда.
1
2
3

eq4 = dsol_cone_H.subs(h, 0)
sol4 = solveset(eq4, t)
display(sol4)

(√

5

2πH 2

5 gσ

)

20 Строим графики построенного решения начальной задачи для разных значений начальной высоты
жидкости H. Каждый отдельный график строим в
диапазоне времени от t = 0 (h = H) до t = T2 (h = 0).

Семейство кривых решения начальной задачи для
сосуда в форме конуса и разных значений начальной высоты жидкости h(0) = H
РИС. 8

1
2
3
4
5
6
7
8
9
10

T2 = tuple(sol4)[0]
par = {g: 9.8, sigma: 0.1, H: 0.0}
p3 = plot(show = False)
for ht in arange(1, 20, 1):
par[H] = ht
ds = dsol_cone_H.rhs.subs(par)
t2 = T2.subs(par)
p = plot(ds, (t, 0, t2), show = False)
p3.extend(p)
p3.show()

Результат построения приведен на рис. 5.

Водяные часы

29

УПРАЖНЕНИЯ И ЗАМЕЧАНИЯ
1 Найдите с помощью SymPy общее решение заданного
дифференциального уравнения с разделяющимися переменными:
1) y 0 = y 2 ;
3) xy + (x + 1)y 0 = 0;
2)

xy 0 ln x = y;

4)

y 0 + 1 = xy 2 + x − y 2 .

2 Решите с помощью SymPy следующие начальные задачи для дифференциальных уравнений с разделяющимися
переменными:
1)

y 0 = 2x2 y 2 , y(1) = 2;

2)

y 0 ctg x + y = 2, y(π/3) = 0;

3)

xy 0 − 2y = 0, y(1) = 1;

4)

y 0 = 1 − ax, y(2) = 0.

3 Библиотека SymPy всегда пытается найти решение заданного дифференциального уравнения в явной форме,
что далеко не всегда возможно. Если не получается найти
явное решение, то можно попытаться построить решение
в неявной форме в виде некоторого соотношения, связывающего неизвестную функцию и независимую переменную, используя опцию symplify = False команды dsolve.
Например, применение этой опции при решении дифференциального уравнения ode_cone приводит к следующему
результату:
1
2
3

dsol_cone = dsolve(ode_cone, h, hint = "separable",
simplify = False)
display(dsol_cone)
5

√ √
2πh 2 (t)
= C1 − 2 gσt
5
4 Начальные задачи в SymPy можно решать сразу с помощью команды dsolve, используя ее опциональный аргумент ics. Начальные условия задачи10 при этом оформляются в виде словаря, ключами которого служат левые
части этих условий, а значениями — их правые части. Например, для начального условия h(0) = H нашей задачи словарь ic будет состоять из единственного элемента с
ключом h(0) и значением H. Пример кода:
1
2
3
4
5

ic = {h.subs(t,0): H}
dsol_cone_H = dsolve(ode_cone, h, ics = ic,
hint = "separable",
simplify = False)
display(dsol_cone_H)

10

Одно — для уравнений первого
порядка, и несколько — для уравнений высших порядков.

Глава 2

30
5

5

√ √
2πh 2 (t)
2πH 2
=
− 2 gσt
5
5
5 Решите по описанной в предыдущем упражнении схеме
начальные задачи из упражнения 2.
6 Для построения графика найденного неявного решения (см. предыдущее упражнение) с помощью команды
plot11 надо предварительно преобразовать это решение в
явную форму, что не всегда возможно. В нашем случае более простым вариантом является преобразование решения
к виду t(h)12 и построение соответствующего графика, горизонтальная ось которого будет соответствовать высоте,
вертикальная — времени. Так как SymPy строит графики
выражений, зависящих от символов, а не от функций, то
предварительно в нашем неявном решении надо заменить
функцию h(t) на символ h (первые две строки кода), после этого выражаем время через высоту (третья строка)
и строим график решения для заданного набора параметров (пятая строка), указывая пределы изменения высоты
от нуля до начального значения H 13 :

11

Библиотека SymPy имеет специальную команду plot_implicit
для построения графиков неявных
функций, мы рассмотрим пример
работы этой команды в одной из
следующих глав.
12

Время выражаем через высоту.

13

При таком способе построения
графика нам не надо искать время полного вытекания, как это мы
делали при построении графиков
выше.

1
2
3
4
5

_h = symbols("h")
dsol_cone_h = dsol_cone_H.subs(h, _h)
_t = tuple(solveset(dsol_cone_h, t))[0]
par = {g: 9.8, sigma: 0.1, H: 10.0, h: hh}
p = plot(_t.subs(par), (_h, 0, par[H]))
Результат выполнения данного фрагмента кода приведен
на рис. 9.

РИС. 9 График зависимости
времени t от высоты h
14

Без использования команды
dsolve.

1
2
3

15

При описываемом подходе
используются только символы,
функции не нужны.

7 В некоторых случаях имеет смысл решать дифференциальные уравнения с разделяющимися переменными в
полностью ручном режиме14 . Рассмотрим для примера решение дифференциального уравнения xy 0 = xy + 2y. Определяем символы для переменной x, функции y 15 и ее производной y 0 (и символ для константы C1 ), после чего создаем дифференциальное уравнение.
x, y, y_, C1 = symbols("x y y’ C1")
ode = Eq(x * y_ - x * y - 2 * y, 0)
display(ode)
xy 0 = xy + 2y

1
2

Разрешаем полученное дифференциальное уравнение относительно производной.
rhs = tuple(solveset(ode, y_))[0]
display(Eq(y_, rhs))
y0 =

xy + 2y
x

Водяные часы

1
2

31

Раскладываем на множители правую часть, используя команду separatevars. Эта команда принимает на вход два
обязательных параметра — выражение и список переменных, по которым должно выполняться разделение. Опциональный параметр dict = True указывает, что результат
разделения должен быть представлен словарем, ключами
в котором являются разделяемые переменные, а все множители, которые не содержат указанные переменные, объединяются в элемент словаря с ключом ’coeff’.
dic = separatevars(rhs, [x, y], dict = True)
print(dic)
{x: (x + 2)/x, y: y, ’coeff’: 1}

3

Разделяем переменные.
p = dic[’coeff’] * dic[x]
q = dic[y]
display(Eq(Integral(1 / q, y), Integral(p, x)))
Z
Z
1
x+2
dy =
dx
y
x

1

Вычисляем интегралы и формируем из них общее решение
дифференциального уравнения в неявном виде16 .
Q = integrate(1 / q, y)
P = integrate(p, x)
dsol = Eq(Q, P + C1)
display(dsol)

1
2

2
3
4

16

При необходимости можно попытаться найти решение и в явной форме, используя команду
solveset(dsol, y).

log (y) = C1 + x + 2 log (x)
Решите по описанной схеме дифференциальные уравнения
из упражнения 1.
8 Обобщите построенные в настоящей главе модели на
случай сосудов, являющихся телами вращения, радиус сечений которых зависит от высоты следующим образом:
r(h) = r0 hk ,
где h — высота сечения, r0 и k — параметры. Например,
для цилиндра k = 0, а для конуса k = 1. Исследуйте
поведение интегральных кривых для других значений k:
k = 1/2 (параболоид вращения), k = 2 и т. д. При каком значении k скорость изменения высоты будет постоянной17 ? Что будет происходить с моделью при отрицательных значениях k?
9 Для форм из предыдущего упражнения постройте зависимость времени T полного вытекания жидкости из сосуда от ее начальной высоты H.

17

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

Глава 2

32

10 Рассмотрите перевернутые варианты форм из упражнения 8 с заданной высотой H. Сравните время полного
вытекания жидкости из двух вариантов каждой рассмотренной формы (прямой и перевернутой).
11 Постройте модель вытекания жидкости из сосудов в
форме: 1) усеченного конуса с заданными высотой и радиусами оснований; 2) верхней и нижней полусфер заданного
радиуса; 3) сферы заданного радиуса.
12 Постройте и исследуйте модель вытекания жидкости
из сосудов, в которых горизонтальными сечениями являются прямоугольники, у которых длина одной стороны a
фиксирована, а длина второй b меняется с высотой по степенному закону b = b0 hk .

РИС. 10 Формы с прямоугольными сечениями для
упражнения 12 (слева направо: k = 0.5, k = 1, k = 2)

13 Сравните время полного вытекания одинакового объема жидкости из сосудов разных форм.
14 Интересной и нереалистичной особенностью построенной нами модели вытекания жидкости из сосуда в форме
конуса является то, что скорость изменения высоты увеличивается неограниченно при уменьшении высоты:
dh
→ ∞ при h → 0.
dt
Это значит, что при достижении некоторой критической
высоты hc скорость изменения высоты превысит, например, скорость света. Найдите это значение hc .
15 Исследуйте зависимость времени полного вытекания
жидкости из сосуда заданной формы от ускорения свободного падения g. Где жидкость будет вытекать из одного и
того же сосуда быстрее — на Земле или на Луне?

ГЛАВА

3

Элементарные химические
реакции
Закон действующих масс • Элементарной хи-

мической реакцией называется реакция, протекающая
в одну стадию (без образования промежуточных веществ) при непосредственном взаимодействии молекул исходных веществ друг с другом. В таких реакциях, как правило, образуется или разрывается не более одной-двух связей между атомами взаимодействующих молекул, например
H2 + O → H2 O.

(1)

1

В сложных реакциях порядок,
вообще говоря, не равен молекулярности.

Уравнения вида (1) называются стехиометрическими. В общем виде они записываются следующим образом:
n1 A1 + n2 A2 + · · · → m1 X1 + m2 X2 + . . . ,

(2)

где Ai — реагенты, Xi — продукты реакции, ni и mi —
стехиометрические коэффициенты, которые показывают, сколько молекул каждого вида участвует в реакции с той или другой стороны.
Число реагирующих молекул называется молекулярностью реакции. Для элементарных реакций эта
величина также называется их порядком 1 . Элементарные реакции обычно имеют первый или второй порядок, существенно реже — третий. Элементарных реакций более высоких порядков не бывает в силу исчезающе малой вероятности одновременного столкновения
четырех и более молекул.
Закон действующих масс, открытый в 1864 году
норвежскими учеными Гульдбергом и Вааге, утверж-

Като Максимилиан
Гульдберг

Петер Вааге

Глава 3

34

дает, что скорость v протекания элементарной реакции (2), т. е. количество элементарных актов реакции
в единицу времени, пропорциональна концентрациям
реагирующих веществ в степенях, равных стехиометрическим коэффициентам:
v = kan1 1 an2 2 . . . ,
2

Количество молекул на единицу
объема, измеряется в моль/л.

(3)

где ai — концентрация2 молекул вида Ai , k — коэффициент пропорциональности (константа скорости реакции), специфичный для данной реакции и зависящий
также от условий проведения реакции, например от
температуры раствора.
Скорость реакции определяет скорость, с которой
изменяется концентрация каждого вещества, участвующего в этой реакции:
dai
= δi · v,
dt

(4)

где параметр δi показывает изменение числа молекул
рассматриваемого вида в результате одного элементарного акта реакции. Например, для реакции вида
A + 2B → 3C

(5)

скорость реакции равна
v = kab2 ,
3

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

(6)

где a и b — концентрации веществ A и B соответственно3 . Тогда скорости изменения концентраций веществ
A, B и C в данной реакции будут равны соответственно:
db
dc
da
= −v,
= −2v,
= 3v,
(7)
dt
dt
dt
т. к. в процессе одного элементарного акта рассматриваемой реакции (5) число молекул A уменьшается
на одну, поэтому δA = −1, число молекул B уменьшается на две, поэтому δB = −2, а число молекул C
увеличивается на три, поэтому δC = 3.
Дифференциальные уравнения вида (7) называются кинетическими. Основная задача химической кинетики заключается в определении того, как для заданной системы химических реакций меняются со временем концентрации реагирующих веществ.

Элементарные химические реакции

35

Задача • В реакциях второго порядка участвуют

две молекулы — либо одинаковые, либо разные. Поэтому такие реакции называют бимолекулярными. В качестве модельной рассмотрим следующую элементарную химическую реакцию второго порядка:
(8)

A + B → 2C.

Пусть соответствующие начальные концентрации равны a0 , b0 и c0 . Согласно закону действующих масс скорость реакции (8) равна
(9)

v = kab.

Тогда скорости изменения концентраций веществ A, B
и C равны
da
= −kab,
dt

db
= −kab,
dt

dc
= 2kab.
dt

(10)

Обозначим убыль концентрации a через x:
x(t) = a0 − a(t) ⇒ a(t) = a0 − x(t).

(11)

Тогда, согласно стехиометрическому уравнению реакции (8) убыль вещества B и прибыль вещества C равны x и 2x соответственно:
b(t) = b0 − x(t),

c(t) = c0 + 2x(t).

(12)

Подставляем выражения (11) и (12) в (10) и в начальное условие и приходим к одной и той же начальной
задаче относительно x(t):
dx
= −k(a0 − x)(b0 − x),
dt
x(0) = 0.



(13)

Модель • Рассмотрим процесс построения описанной выше модели реакции (8) в библиотеке SymPy. Причем в этот раз мы начнем не с решения дифференциального уравнения, а с его вывода, чтобы потом можно было обобщить рассматриваемую модель на случай
других химических реакций.
1 Подключаем необходимые библиотеки4 .

4

См. первые две модели.

36

Глава 3

2 Вводим символы для переменной t, константы скорости реакции k, начальных концентраций a0 , b0 и c0 .
Определяем функцию x(t). Функции a(t), b(t) и c(t)
как объекты SymPy нам не понадобятся, т. к. мы будем выражать их через x(t), для чего будет достаточно
обычных переменных. По этой же причине не нужно
определять производную функции x(t).
3 Определяем изменения δA , δB и δC числа молекул
наших трех веществ в одной химической реакции.
1
2

da, db, dc = -1, -1, 2
print(da, db, dc)

-1 -1 2

1
2

4 Функция x(t) обозначает убыль вещества a, т. е.
x(t) = a0 − a(t), следовательно, a(t) = a0 − x(t).
a = a0 - x
display(a)

a0 − x(t)
5 Теперь мы можем выразить функции b(t) и c(t) через убыль x(t). Для этого заметим, что на одну молекулу вещества A, расходуемую в одной реакции, расходуется δB /δA молекул вещества B и δC /δA молекул
вещества C. Если какое-то из этих отношений является положительным, то соответствующие молекулы
расходуются, если отрицательным — то создаются.
1
2
3

b = b0 - db * x / da
c = c0 - dc * x / da
display(b, c)

b0 − x(t) c0 + 2x(t)
1
2

6 Вычисляем скорость реакции v.
v = k * a * b
display(v)

k (a0 − x(t)) (b0 − x(t))
7 Записываем кинетическое уравнение ȧ = kv для
концентрации a(t), производную ȧ при этом вычисляем с помощью команды diff. Библиотека SymPy автоматически подставит вместо переменных a и v вычисленные ранее их выражения через функцию x, и в
результате мы получаем искомое дифференциальное
уравнение для неизвестной функции x(t).

Элементарные химические реакции

1
2

37

ode = Eq(diff(a, t), da * v)
display(ode)



d
x(t) = −k (a0 − x(t)) (b0 − x(t))
dt

8 Создаем нулевое начальное условие x(0) = 0 и решаем соответствующую начальную задачу.
1
2
3

ic = {x.subs(t, 0): 0}
dsol = dsolve(ode, x, ics = ic)
display(dsol)


x(t) =

−a0 e
e


b0 kt+



a0 kt+

log

a0 −b0
a0
b0
a0 −b0

log



( ab00 ) 

(





)





a0 kt+

+b0 e

−e


b0 kt+

log

a0 −b0

a0
b0
a0 −b0

log

(



( ab00 ) 


)





9 Подставим в найденное параметризованное решение конкретные значения константы скорости реакции
и начальных концентраций.
1
2
3

par = {k: 1, a0: 1, b0: 2, c0: 0}
ds = dsol.subs(par)
display(ds)

x(t) =

−4e2t + 4et
−4e2t + 2et

10 Построим график найденного решения (рис. 1) на
интервале t ∈ [0, T ] для значения T = 5.
1
2
3

T = 5
p1 = plot(ds.rhs, (t, 0, T), show = False)
p1.show()
11 Последним действием построим графики искомых
зависимостей a(t), b(t) и c(t). Все три кривые строятся
по одной и той же схеме (поэтому мы будем их строить в цикле): заменяем в соответствующем выражении
символ x на правую часть найденного решения dsol;
заменяем там же параметры на их числовые значения,
используя определенный выше словарь par; строим на
интервале t ∈ [0, T ] заданным цветом график полученной зависимости; добавляем построенную кривую
к общему графику p2.

Зависимость x(t) для
случая k = 1, a0 = 1, b0 = 2 и
c0 = 0
РИС. 1

Глава 3

38

1
2
3
4
5
6
7
8

p2 = plot(show = False)
L = [(a, "red"), (b, "blue"), (c, "green")]
for r, cl in L:
rs = r.subs(x, dsol.rhs).subs(par)
p = plot(rs, (t, 0, T),
line_color = cl, show = False)
p2.extend(p)
p2.show()

Результат выполнения приведенного кода показан на
рис. 2.
РИС. 2 Зависимость концентраций a(t), b(t) и c(t) для
случая k = 1, a0 = 1, b0 = 2 и
c0 = 0

5

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

УПРАЖНЕНИЯ И ЗАМЕЧАНИЯ
1 Если проанализировать решение dsol дифференциального уравнения ode, то оказывается, что оно было получено библиотекой SymPy в неявном предположении, что
a0 6= b0 . При a0 = b0 решением уравнения будет рациональная функция, а не комбинация экспонент. Если всетаки условие a0 = b0 5 требуется учесть, то это можно сделать двумя способами. Во-первых, можно явно заменить
b0 на a0 при создании дифференциального уравнения. Вовторых, можно сначала выполнить подстановку числовых
параметров, а затем уже решать конкретное непараметризованное уравнение.
2 Постройте и проанализируйте модели для элементарных химических реакций первого порядка:
1)
2)

A → B — реакция изомеризации;
A → B + C — реакция диссоциации.

3 Постройте модели для следующих элементарных химических реакций второго порядка:

Графики зависимостей a(t) (красная кривая)
и b(t) (голубая кривая) для
реакции (14), показывающие
влияние концентрации катализатора c0 на скорость реакции. Какие кривые соответствуют большим значениям c0 ?
РИС. 3

1)
2)
3)
4)

2A → B;
2A → A + B;
A + B → C + D;
A + B → C.

4 Постройте и решите кинетическое уравнение для реакции второго порядка с катализатором:
A + C → B + C.

(14)

Как следует из (14), катализатор C в реакции не расходуется, т. е. c(t) ≡ c0 , но влияет на ее скорость (рис. 3).
5 Исследуйте модель автокаталитической реакции следующего вида
A + C → C + C,
(15)

Элементарные химические реакции

39

в которой катализатор реакции C является также ее продуктом.
6 Рассмотрим обратимую реакцию первого порядка:
A ↔ B,

(16)

состоящую из двух отдельных реакций A → B и B → A,
протекающих со скоростями
v1 = k1 a и v2 = k2 b
соответственно. Чтобы найти скорости изменения концентраций a и b, можно воспользоваться принципом независимости: если в системе имеется несколько простых реакций, то каждая из них протекает по таким же кинетическим законам и с теми же скоростями, как и в отсутствие
других реакций. Следовательно,
da
= −k1 a + k2 b,
dt

db
= k1 a − k2 b.
dt

(17)

Так как в данном случае уравнение материального баланса
тривиально6 :
a + b = a0 + b0 ,
(18)
то систему также можно свести к одному дифференциальному уравнению для x(t) = a0 − a(t). Выполните такое построение в SymPy и решите полученное дифференциальное
уравнение.
7 Постройте и проанализируйте модель обратимой каталитической реакции:
A + C → B + C, B → A,

(19)

в которой константы скоростей равны k1 и k2 соответственно.
8 Во многих приложениях важным оказывается не столько решение кинетического уравнения, сколько нахождение
его предельного значения:
x∞ = lim x(t).
t→∞

Величина x∞ называется точкой покоя соответствующего
дифференцильного уравнения, с ее помощью можно найти предельные распределения уже концентраций всех веществ, участвующих в реакции (в системе реакций). В библиотеке SymPy пределы вычисляются с помощью команды
limit(f, x, a), где f — выражение, предел которого мы
ищем, x — переменная, a — значение, к которому стремится
переменная x. Причем вместо a можно использовать символы бесконечности oo и -oo. Найдите с помощью этой
команды предельные значения концентраций веществ для
реакций из упражнений 6 и 7.

6

Суммарное количество молекул
A и B остается постоянным.

Глава 3

40

9 Вычислите с помощью SymPy следующие пределы:
1)
2)

x−4
;
lim √
x−2
cos x
lim
;
x→1 sin2 πx

x→4

3)
4)

1

lim x(e x − 1);

x→∞

lim

x→0

1 − cos ax
.
x2

10 Выразите из найденного решения x(t) кинетического
дифференциального уравнения (для любой из рассмотренных вами моделей) переменную t через функцию x, например, с помощью команды solveset (предварительно
преобразовав символ функции в символ переменной, см.
упражнение 6 в предыдущей главе). Используя найденную
зависимость, ответьте на следующий вопрос: через какое
время убыль заданного вещества составит половину от его
исходного количества? Такое время является в некотором
смысле характеристикой скорости протекания реакции.
11 Постройте дифференциальное уравнение для элементарной химической реакции третьего порядка
A+B+C→D

(20)

с начальным условием a(0) = a0 , b(0) = b0 и c(0) = c0 .
Решите поставленную начальную задачу для различных
значений начальных концентраций.

ГЛАВА

4

Задача о четырех жуках
https://t.me/it_boooks
Кривые погони • Кривыми погони, или кривыми преследования, называются траектории движения
объектов, выполняющих преследование подвижных
или неподвижных целей согласно некоторому алгоритму. Если алгоритм описывается простым правилом,
например «держать направление движения строго на
цель», то кривая погони часто может быть построена
с помощью решения некоторого дифференциального
уравнения.
Классической задачей преследования является задача о четырех жуках1 . В начальный момент времени жуки располагаются в вершинах квадрата (рис. 1).
Они начинают одновременно двигаться с одинаковой
постоянной скоростью. Каждый жук ползет в направлении своего соседа против часовой стрелки. Требуется определить, по каким траекториям будут двигаться
жуки и какова будет длина этих траекторий от момента старта до момента встречи всех жуков в центре
квадрата, если скорость жуков постоянна и равна v.
Естественно, размерами жуков пренебрегаем, полагая
их безразмерными точками.
Задача • Рассмотрим сразу общую постановку задачи, в которой n жуков расположены в начальный
момент времени в вершинах правильного n-угольника
(рис. 2). Введем систему координат: начало координат
поместим в центр n-угольника, ось Ox направим на
первого жука, ось Oy — перпендикулярно оси Ox (тогда точка старта второго жука должна иметь положительную y-координату). Для краткости рассуждений
предположим, что расстояние от начала координат до
каждого из жуков равно 1.

Эдуард Люка
1

В оригинальном варианте этой
задачи, поставленной в 1877 году
французским математиком Эдуардом Люка, рассматривались три
собаки, расположенные в вершинах правильного треугольника.

РИС. 1

ках

Задача о четырех жу-

Глава 4

42

y
A3

A2
v

A4
O

1

A5

A1
x

A6

РИС. 2 Начальное состояние в
задаче об n жуках (для случая n = 6)

y
A2 (t)

A3 (t)

v
α
O

A4 (t)
A5 (t)

A1 (t)
1

x

A6 (t)

Взаимное расположение жуков в произвольный
момент времени t (n = 6)
РИС. 3

2

Коэффициент k равен отношению скорости v и длины вектора A1 A2 . Нетрудно убедиться, что
в этом случае вектор (ẋ, ẏ) сонаправлен вектору A1 A2 и имеет
длину v, что полностью соответствует условиям задачи.

Очевидно, что в силу симметрии задачи в каждый момент времени жуки располагаются в вершинах
некоторого правильного n-угольника с центром в начале координат. Следовательно, если координаты первого жука (точка A1 ) в некоторый момент времени
равны (x, y), то координаты (x1 , y1 ) его соседа против
часовой стрелки (точка A2 ) в тот же момент времени можно получить поворотом вектора OA1 на угол
α = 2π/n против часовой стрелки (см. рис. 3).
Воспользовавшись стандартными формулами линейной алгебры для поворотов в двумерном пространстве, выразим координаты точки A2 через координаты
(x, y) точки A1 и угол α:
x1 = x cos α − y sin α,
y1 = x sin α + y cos α.

(1)

По условию задачи скорость жука, находящегося в
точке (x, y), направлена на его соседа, т. е. сонаправлена вектору
(x1 − x, y1 − y).
Значит, компоненты скорости должны быть равны
dx
= k(x1 − x),
dt
dy
= k(y1 − y),
dt

(2)

где k — коэффициент, зависящий от скорости v и от
расстояния между жуками2 .
Поделим в системе (2) второе уравнение на первое,
рассматривая производные как отношения дифференциалов:
dy
y1 − y
x sin α + y cos α − y
=
=
.
dx
x1 − x
x cos α − y sin α − x

(3)

Получили дифференциальное уравнение, описывающее кривую преследования каждого из n жуков. Траектория движения конкретного жука определяется с
помощью начального условия: она должна проходить
через его точку старта. Например, для первого жука
начальное условие имеет следующий вид (рис. 2):
y(1) = 0.

(4)

Задача о четырех жуках

43

Модель • Рассмотрим процесс решения с помощью
библиотеки SymPy дифференциального уравнения (3) с
начальным условием (4). По ряду причин, которые будут указаны ниже, решать данное уравнение мы будем
с помощью перехода к полярной системе координат,
т. е. с помощью одновременной замены и неизвестной
функции, и независимой переменной.
1 Подключаем необходимые библиотеки.
2 Определяем символы x и alpha, а также функцию
y(x) и ее производную y_.
3 Вычисляем по формулам (1) координаты (x1 , y1 )
точки A2 , куда должна быть направлена скорость первого жука, находящегося в точке A1 с координатами
(x, y).
1
2
3

x1 = x * cos(alpha) - y * sin(alpha)
y1 = x * sin(alpha) + y * cos(alpha)
display(x1, y1)

x cos (α) − y(x) sin (α)

x sin (α) + y(x) cos (α)

4 Составляем дифференциальное уравнение (3), используя вычисленные координаты x1 и y1 .
1
2

ode = Eq(y_, (y1 - y) / (x1 - x))
display(ode)

x sin (α) + y(x) cos (α) − y(x)
d
y(x) =
dx
x cos (α) − x − y(x) sin (α)
5 Построенное уравнение относится к типу однородных дифференциальных уравнений первого порядка3 .
Проверим это с помощью команды classify_ode.
1

classify_ode(ode, y)
1st_homogeneous_coeff_best
1st_homogeneous_coeff_subs_indep_div_dep
1st_homogeneous_coeff_subs_dep_div_indep
1st_power_series
lie_group
1st_homogeneous_coeff_subs_indep_div_dep_Integral
1st_homogeneous_coeff_subs_dep_div_indep_Integral

3

Уравнение вида y 0 = f (x, y) называется однородным, если правая часть зависит только от отношения y/x: f (x, y) = F (y/x).

Глава 4

44

Как видно, SymPy правильно определяет тип уравнения (первые три строчки вывода соответствуют разным вариациям именно однородных дифференциальных уравнений). Но, к сожалению, решить это уравнение с помощью команды dsolve не получается. Причиной этого является наличие параметра α в уравнении. Само уравнение после соответствующей замены
сводится к уравнению с разделяющимися переменными, в процессе разделения переменных возникает рациональная функция с квадратичным знаменателем,
коэффициенты которого зависят от α. Анализ дискриминанта этого знаменателя (знак которого определяет способ интегрирования всей дроби) оказывается в
результате очень сложной задачей для случая произвольного α.
6 Так как исходная задача обладает очевидной поворотной симметрией (поворот всей конфигурации на
любой угол, кратный α, приводит к той же самой конфигурации, просто с другой нумерацией жуков), то
следует ожидать, что в полярной системе координат
эта задача должна решаться более естественно, чем в
декартовой системе координат. Выполним такую замену переменных в SymPy. Для этого сначала введем
новый символ для независимой переменной ϕ и определим новую неизвестную функцию r(ϕ).
1
2
3

phi = symbols("phi")
r = Function("r")(phi)
display(r)

r(ϕ)
7 Замене подлежат три величины: x, y и производная y 0 . Первые две величины выражаются через ϕ и
r с помощью стандартных формул, связывающих декартовы и полярные координаты заданной точки:

x = r cos ϕ,
y = r sin ϕ.
4

Используя формулы для производной сложной функции и для
производной обратной функции.

(5)

Для замены производной y 0 воспользуемся следующим
приемом4 :
y0 =

dy
dy dϕ
=
·
=
dx
dϕ dx

dy

dx


.

(6)

Задача о четырех жуках

45

Создадим три соответствующие переменные для каждой из перечисленных формул с учетом того, что r —
это функция угла ϕ.
1
2
3
4

zx = r * cos(phi)
zy = r * sin(phi)
zy_ = diff(zy, phi) / diff(zx, phi)
display(zx, zy, zy_)

r(ϕ) cos (ϕ)
r(ϕ) sin (ϕ)
d
r(ϕ) cos (ϕ) + sin (ϕ) dϕ
r(ϕ)
d
−r(ϕ) sin (ϕ) + cos (ϕ) dϕ
r(ϕ)

8 Выполняем замену, подставляя в уравнение ode выражения zx, zy и zy_ вместо символов x, y и y_ соответственно. Запомним новое дифференциальное уравнение в переменной ode1.
1
2

ode1 = ode.subs({y_: zy_, y: zy, x: zx})
display(ode1)
d
r(ϕ) cos (ϕ) + sin (ϕ) dϕ
r(ϕ)
d
−r(ϕ) sin (ϕ) + cos (ϕ) dϕ
r(ϕ)

=

=

r(ϕ) sin (α) cos (ϕ) + r(ϕ) sin (ϕ) cos (α) − r(ϕ) sin (ϕ)
−r(ϕ) sin (α) sin (ϕ) + r(ϕ) cos (α) cos (ϕ) − r(ϕ) cos (ϕ)

9 Визуально уравнение ode1 выглядит намного хуже,
чем исходное уравнение ode. Однако это уравнение в
отличие от исходного легко решается5 , и решение имеет очень простой вид. Решаем уравнение с учетом начального условия r(0) = 1: первый жук в начальный
момент времени располагается на оси Ox, т. е. ϕ = 0,
на расстоянии r = 1 от начала координат.
1
2
3

ic = {r.subs(phi, 0): 1}
dsol = dsolve(ode1, r, ics = ic)
display(dsol)

r(ϕ) = e

ϕ(cos (α)−1)
sin (α)

10 Построим график найденной траектории для случая n = 6. Библиотека SymPy не поддерживает построение графиков в полярной системе координат, поэтому
мы воспользуемся командой plot_parametric для построения графиков параметрически заданных функций. Первым аргументом этой команды указывается

5

Несмотря на наличие в уравнении нелинейных функций sin ϕ
и cos ϕ, они в конце концов
все сокращаются, и оказывается,
что уравнение является линейным
(см. замечание 3 к главе).

46

кортеж из двух выражений для x и y координат, выраженных через некоторый параметр (в нашем случае через параметр ϕ), вторым аргументом — кортеж,
содержащий символ параметра и диапазон его изменения. Сохраняем в переменной R правую часть найденного решения, подставив туда предварительно угол
α = 2π/n. Далее выражаем x и y через ϕ по формулам (5). Устанавливаем предел phi_max для угла ϕ.
Наконец, строим график, опция size задает размеры
изображения6 .

6

В данном случае существенно,
чтобы масштаб по обеим осям был
одинаковым, для этого мы используем по обеим осям одинаковые
пределы и одинаковые размеры.

Глава 4

1
2
3
4
5
6
7
8
9
10

n = 6
a = 2 * pi / n
R = dsol.rhs.subs(alpha, a)
X = R * cos(phi)
Y = R * sin(phi)
phi_max = 4 * pi
p1 = plot_parametric((X, Y), (phi, 0, phi_max),
xlim = (-1, 1), ylim = (-1, 1),
size=(6, 6), show = False)
p1.show()

Результат построения показан на рис. 4.
11 Построим теперь на одном графике траектории
всех n жуков. Заметим, что траектория k-го жука получается поворотом траектории первого жука (которую мы нарисовали в предыдущем пункте) на угол kα
против часовой стрелки. Создаем пустой график p2.
Проходим в цикле по всем жукам. На k-й итерации
этого цикла делаем следующие действия: выполняем
поворот траектории R на угол kα с помощью замены
ϕ на ϕ − kα (строка 4); получаем параметризованное
описание траектории в декартовых координатах (строки 5 и 6); определяем пределы изменения угла ϕ для
k-го жука — он стартует с угла kα (строка 7); строим
траекторию и добавляем ее к общему графику (строки
8–10).

Траектория первого
жука для случая n = 6
РИС. 4

1
2
3

Траектории движения
всех жуков для случая n = 6
РИС. 5

4
5
6
7
8
9
10
11

p2 = plot(xlim = (-1, 1), ylim = (-1, 1),
size = (6, 6), show = False)
for k in range(n):
R2 = R.subs(phi, phi - k * a)
X = R2 * cos(phi)
Y = R2 * sin(phi)
phi1, phi2 = k * a, k * a + phi_max
p = plot_parametric((X, Y), (phi, phi1, phi2),
show = False)
p2.extend(p)
p2.show()

Окончательный результат показан на рис. 5.

Задача о четырех жуках

47

УПРАЖНЕНИЯ И ЗАМЕЧАНИЯ
1 Если воспользоваться известной формулой тангенса половинного аргумента
tg

7

Для окружности этот угол оказывается прямым.

1 − cos α
α
=
,
2
sin α

то найденное нами в пункте 9 решение дифференциального уравнения можно записать в более простом виде:
r(ϕ) = e−kϕ , где k = tg

α
.
2

(7)

2 Найденная нами кривая погони называется логарифмической спиралью. Впервые такие спирали были описаны
Декартом, искавшим кривую, обладающую свойством, подобным свойству окружности: касательная в каждой точке
должна образовывать с радиус-вектором в этой точке один
и тот же угол7 . Декарт показал, что это условие равносильно тому, что полярные углы для точек кривой пропорциональны логарифмам радиус-векторов, решение (7) как раз
и удовлетворяет этому свойству. Позже логарифмическая
спираль исследовалась Якобом Бернулли, который называл её «удивительной спиралью» — Spira mirabilis. Якоб
Бернулли хотел, чтобы на его могиле была выгравирована
логарифмическая спираль, но вместо этого по ошибке туда
поместили архимедову спираль (рис. 6).

Рене Декарт

Якоб Бернулли

3 Полученное нами дифференциальное уравнение в полярных координатах (см. пункт 8) можно упростить, разрешив его относительно производной dr/dϕ с помощью команды solveset:
1
2

rhs = solveset(ode1, diff(r, phi))
display(rhs)


(cos (α) − 1) r(ϕ)
sin (α)

 

r(ϕ) sin (ϕ)
\
cos (ϕ)

Получили одно решение (первая часть ответа), но с ограничением на область допустимых решений (вторая часть).
Если пренебречь ограничением, то можно записать исходное уравнение в более простой форме:
1
2

ode2 = Eq(diff(r, phi), rhs.args[0].args[0])
display(ode2)
(cos (α) − 1) r(ϕ)
d
r(ϕ) =

sin (α)

РИС. 6 Архимедова

спираль на
надгробии Якоба Бернулли

Глава 4

48

8

Таким образом, подобный способ можно использовать и для выделения нужных решений из множества решений, выдаваемых командой solveset, вместо преобразования этого множества в кортеж
или список.

Для извлечения части решения мы использовали стандартный атрибут args, в котором хранятся все части выражения (слагаемые, множители, элементы множества и т. д.).
Первое обращение args[0] к этому атрибуту выделяет из
rhs первую часть ответа (множество из одного элемента),
второе аналогичное обращение выделяет из этого выделенного множества первый его элемент8 .
4 Найдите траекторию k-го жука не поворотом траектории первого жука, как мы это делали выше, а напрямую
через решение начальной задачи для дифференциального
уравнения ode1 с начальным условием:

r (k − 1)α = 1, где α = 2π/n.
(8)

5 Логарифмическая спираль делает вокруг начала координат бесконечно много оборотов. Чтобы при построении
траекторий не подбирать вручную подходящее значение
параметра phi_max, можно его вычислять с помощью решения алгебраического уравнения r(ϕ) = r0 , где r(ϕ) —
решение ode1 дифференциального уравнения, r0 — минимальное расстояние от спирали до начала координат, например r0 = 0.01. Протестируйте этот прием сначала на
большом значении r0 = 0.5.
6 Относительно простым частным случаем, который разрешим в декартовых координатах, является классический
случай с n = 4 жуками. Подставьте в исходное дифференциальное уравнение ode (в декартовых координатах)
α = π/2 и решите полученное уравнение. Попробуйте построить график найденного решения в неявной форме с помощью команды plot_implicit. Сравните график в неявной форме с графиком в полярных координатах.
7 Дифференциальное уравнение для задачи с двумя жуками (n = 2) в полярных координатах не решается, но в
этот раз успешно решается в декартовых. Проверьте оба
этих утверждения. Какая кривая будет траекторией каждого из жуков в данном случае?
8 Работает ли наша модель для случая n = 1?
9 Проверьте, что следующие дифференциальные уравнения являются однородными (homogeneous), и решите их с
помощью SymPy:
1)
2)

x + yy 0 = 0;
2xyy 0 = y 2 − x2 ;

3)
4)

x2 y 0 = y 2 ;
(y + 2x)y 0 = 2y − x.

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

Задача о четырех жуках

49

11 Постройте графики следующих кривых в полярной
системе координат в указанных пределах угла ϕ:
ϕ
1) r(ϕ) = , ϕ ∈ [0, 8π];
4
2) r(ϕ) = 1 + cos ϕ, ϕ ∈ [0, 2π];

3) r(ϕ) = 1 + 2 sin2
, ϕ ∈ [0, 6π];
2
ϕ
4) r(ϕ) = tg , ϕ ∈ [−3π, 3π].
3
12 Постройте:
 график заданной параметрической функции x(t), y(t) в указанных пределах изменения параметра t для случая a = 1; семейство таких графиков для
a ∈ [0..2] с шагом 0.1.
1)
2)
3)
4)

x = a sin 2t, y = cos 3t, t ∈ [0, 2π];
x = a sin t, y = (1 + a) cos 3t, t ∈ [−2π, 0];
x = a sin 3t, y = cos t + cos 5t, t ∈ [−π, π];
x = cos t + a sin 2t, y = a sin 5t, t ∈ [0, π].

13 Хотя логарифмическая спираль в нашем случае и делает бесконечно много оборотов вокруг начала координат,
ее длина является конечной. Найдите длину найденной
траектории, используя формулу для длины кривой в полярной системе координат:
L=

Z∞p

r2 + r02 dϕ.

(9)

0

Пределы интеграла соответствуют пределам изменения полярного угла, в нашем случае (для первого жука) этот угол
изменяется от нуля до бесконечности. Для случая n = 6 вы
должны получить L = 2.
14 Рассмотрим дискретный вариант задачи об n жуках.
В каждый дискретный момент времени t = 0, 1, 2, . . . все
жуки синхронно делают один шаг в направлении своих
соседей против часовой стрелки, величина шага пропорциональна расстоянию до соответствующего соседа. Если
коэффициент пропорциональности δ мал, то мы получим
достаточно хорошее приближение к логарифмической спирали. Для демонстрации такой модели построим картинку, аналогичную той, которая использовалась на обложке журнала Scientific American в 1965 году (рис. 7). Так
как модель дискретна, то для построения графиков мы
будем использовать библиотеку Matplotlib. Подключаем
библиотеку и импортируем из модуля math константу pi
и функции sin и cos9 . Задаем число жуков n. Вычисляем
угол α. Создаем два списка с координатами жуков: первый

7 Обложка журнала
Scientific American за июль
1965 года
РИС.

9

Это стандартные объекты
Python, потому что в данной модели мы не работаем с символьными
вычислениями.

50

Глава 4

жук располагается в точке (1, 0), координаты всех остальных пока задаем нулями (строки 5 и 6). Координаты первого жука мы продублируем, чтобы при прорисовке получать замкнутые многоугольники.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

Визуализация дискретной модели n жуков (n = 6)
РИС. 8

import matplotlib.pyplot as plt
from math import pi, sin, cos
n = 6
a = 2 * pi / n
X = [1] + [0] * n
Y = [0] + [0] * n
for t in range(60):
for k in range(1, n + 1):
X[k] = X[0] * cos(a * k) - Y[0] * sin(a * k)
Y[k] = X[0] * sin(a * k) + Y[0] * cos(a * k)
plt.plot(X, Y, c="skyblue")
X[0] += 0.15 * (X[1] - X[0])
Y[0] += 0.15 * (Y[1] - Y[0])
plt.gca().set_aspect("equal")
plt.show()
Далее организуем цикл по времени (строки 7–13). На каждой его итерации сначала вычисляем координаты всех жуков, выполняя поворот координат первого жука на соответствующий угол (внутренний цикл по k). Далее соединяем линиями точки местоположения всех жуков с помощью
команды plot, опция c задает цвет линии. Последним действием вычисляем новые координаты первого жука, сдвигая его по направлению к его соседу с коэффициентом
δ = 0.15. При выходе из цикла задаем одинаковый масштаб по обеим осям графика (аспектное отношение, строка 14) и выводим график на экран. Результат работы этого
фрагмента кода для случая n = 6 показан на рис. 8.

ГЛАВА 5
Барометрическая формула
Задача • В покоящейся стационарной атмосфере
давление равно отношению веса вышележащего столба воздуха к площади его поперечного сечения. Поэтому вполне очевидно, что с увеличением высоты давление воздуха должно уменьшаться. В самом деле, например, на высоте Эвереста давление составляет только одну четверть от давления на уровне моря.
Интересной и практически важной, например для
авиации, задачей является определение точной зависимости давления воздуха P от высоты h над уровнем моря. В данной главе мы рассмотрим теоретическую модель стационарной изотермической атмосферы в гравитационном поле Земли.
Рассмотрим тонкий слой столба воздуха с площадью сечения S, заключенный между высотами h и
h+∆h (рис. 1). Так как атмосфера предполагается стационарной, то суммарная сила, действующая на выделенный слой воздуха, должна быть равна нулю. Запишем силы, действующие на этот объем в вертикальном
направлении.
Со стороны нижней части воздушного столба действует направленная вверх сила давления F (h), равная произведению давления P (h) на площадь сечения S. Со стороны верхней части этого же столба действует направленная вниз сила давления
F (h + ∆h) = P (h + ∆h) · S.
Наконец, на выделенный слой действует направленная вниз сила тяжести mg, где m — масса этого слоя.
Таким образом,
P (h + ∆h)S + mg = P (h)S ⇒ ∆P · S = −mg,

(1)

S

F (h)

h+∆h
h
mg
F (h+∆h)

0
уровень моря

Вертикальные силы,
действующие на малый объем воздуха
РИС. 1

Глава 5

52

где ∆P = P (h + ∆h) − P (h) — разность давлений
на верхнем и нижнем основаниях нашего воздушного слоя. Домножим обе части последнего равенства на
∆h и учтем, что S∆h — это объем V данного слоя:
∆P · V = −mg∆h.

Дмитрий Иванович Менделеев

Масса m в (2) зависит от высоты h (снизу воздух
плотней, сверху — разреженней), ее можно найти с помощью закона Менделеева—Клапейрона, устанавливающего связь между давлением, объемом и температурой идеального газа1 :
PV =

Бенуа Поль Эмиль Клапейрон
1

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

(2)

mRT
,
M

(3)

где R = 8.314 Дж/K·моль — универсальная газовая
постоянная, T — температура газа в кельвинах (K),
M — молярный объем, для воздуха M = 0.029 кг/моль.
Выражаем из формулы (3) массу m, подставляем ее
в (2) и после сокращения на V получаем следующее
соотношение:
M gP ∆h
∆P = −
.
(4)
RT
Делим обе части на ∆h, делаем предельный переход
∆h → 0 и получаем искомое дифференциальное уравнение для неизвестной функции P (h):
dP
Mg
=−
· P.
dh
RT

(5)

В качестве начального условия возьмем известное давление P0 = 101 325 Па на уровне моря:
P (0) = P0 .

2

Например, на высоте 100 км
ускорение свободного падения всего на 3 % меньше ускорения свободного падения у поверхности
Земли.
3

Известно, что до высоты 10 километров температура воздуха постепенно снижается со скоростью,
примерно равной 6.5 K/км.

(6)

В правой части уравнения (5) перед P имеется четыре коэффициента, два из которых (M и R) являются константами. Ускорение свободного падения g
очень слабо меняется на высотах до 100 км2 , поэтому его тоже можно считать практически постоянным.
А вот температура воздуха T , вообще говоря, зависит
от высоты h3 . В простейшем изотермическом случае
температура воздуха предполагается постоянной величиной, не зависящей от высоты h. Соответствующий
закон изменения давления от высоты, называемый теперь барометрической формулой, впервые сформулировал английский физик и астроном Галлей4 в 1686

Барометрическая формула

53

году, и только спустя более чем сто лет эта формула была уже математически выведена Лапласом. Как
мы увидим дальше, барометрическая формула неплохо описывает давление воздуха на высотах до 3километров.

Модель • Рассмотрим два варианта модели, основанных на дифференциальном уравнении (5). Первый
вариант соответствует изотермическому случаю, когда температура атмосферы считается одинаковой по
всей ее высоте. Второй, чуть более реалистичный вариант модели предполагает, что температура линейно
падает с высотой. Полученные результаты моделирования сравним с экспериментальными данными.

4

Именем Галлея названа известная комета, возвращение которой
в 1758 году в предсказанный Галлеем срок стало первым триумфальным подтверждением теории
тяготения Ньютона.

1 Подключаем необходимые библиотеки. В этот раз
помимо библиотеки SymPy нам потребуются (большей
частью для построения графиков) еще две библиотеки
— стандартная графическая библиотека Matplotlib и
математический пакет NumPy.
1
2
3
4

from sympy import *
from IPython.display import display
import numpy as np
import matplotlib.pyplot as plt
2 Определяем все символы, входящие в дифференциальное уравнение (5) и начальное условие (6). Все
эти величины, за исключением высоты h5 , являются
положительными.

1
2
3
4
5

h = symbols("h")
M, g, R, T, P0 = symbols("M g R T P0",
positive = True)
P = Function("P")(h)
display(P)

P (h)
3 Составляем дифференциальное уравнение (5) и задаем начальное условие (6).
1
2
3

Эдмунд Галлей

ode = Eq(diff(P, h), -M * g / (R * T) * P)
ic = {P.subs(h, 0): P0}
display(ode, ic)

d
M gP (h)
P (h) = −
dh
RT
{P(0): P0}

Пьер-Симон де Лаплас
5

Отрицательная высота h означает, что мы находимся ниже уровня
моря.

Глава 5

54
4 Решаем поставленную начальную задачу.
1
2

dsol = dsolve(ode, ics = ic)
display(dsol)

P (h) = P0 e−

Так как в процессе решения мы фактически предполагали, что все символы, входящие в уравнение, в том
числе температура T , являются постоянными величинами, то найденная нами формула соответствует изотермическому случаю T = const. Именно эта формула
и называется барометрической.

Давление P (Па) на
разных высотах h (м)

ТАБЛ. 1

h
0
1 000
2 000
3 000
4 000
5 000
6 000
7 000
8 000
9 000
10 000
11 000
12 000
13 000
14 000
15 000
16 000
17 000
18 000
19 000
20 000

P
101 325
89 876
79 501
70 121
61 660
54 048
47 217
41 105
35 651
30 800
26 499
22 699
19 399
16 579
14 170
12 111
10 352
8 849
7 565
6 467
5 529

M gh
RT

5 Сравним теперь предсказания нашей изотермической модели с экспериментально найденными значениями давления на разных высотах, приведенными
в табл. 1. Для этого сформируем из двух столбцов
данной таблицы два массива NumPy. Высота в таблице изменяется с постоянным шагом, поэтому соответствующий массив dataH заполним с помощью команды
arange.
1
2
3

dataH = np.arange(0, 21000, 1000)
dataP = np.array([101325, 89876, 79501, ...])
print(dataH[:3], dataP[:3])
[0 1000 2000] [101325 89876 79501]
6 Зададим параметры модели, соответствующие земной атмосфере. В качестве давления P0 на уровне моря возьмем первый элемент массива dataP. Подставим
эти параметры в решение dsol.

1
2
3

par = {R: 8.314, M: 0.029, g: 9.8, P0: dataP[0]}
ds = dsol.rhs.subs(par)
display(ds)

101325e−

0.0341833052682223h
T

Получили формулу, выражающую давление P через
высоту h и (постоянную) температуру T .
7 Чрезвычайно удобным инструментом библиотеки
SymPy является команда lambdify, преобразующая заданное символьное выражение SymPy в обычную функцию Python. Первым аргументом команды lambdify яв-

ляется переменная или список переменных, от которых зависит заданное выражение, вторым аргументом

Барометрическая формула

55

служит само выражение. Результатом работы этой команды является анонимная функция6 , зависящая от
указанных параметров, которую можно сохранить в
какой-нибудь переменной и использовать в дальнейшем как обычную функцию Python. Преобразуем с помощью этой команды правую часть выражения ds в
функцию fP двух переменных h и T и, для примера,
выведем на печать значение давления на высоте 10 км
при температуре воздуха T = 253 K (−20 ◦ C).
1
2

fP = lambdify([h, T], ds)
print(int(fP(10000, 253)))
26238

Видно, что найденная величина незначительно отличается от соответствующего значения 26 499 в табл. 1.
8 Функции, генерируемые командой lambdify, умеют
работать с массивами NumPy, поэтому мы можем применить функцию fP сразу ко всем высотам из массива
dataH. Вторым аргументом для fP укажем температуру T = 273 K. Результатом будет новый массив NumPy
с предсказанными моделью значениями давления. Запомним этот массив в переменной model. Выведем на
экран его первые три элемента, приведенные к целочисленному типу.
1
2

model = fP(dataH, 273)
print(model[:3].astype(int))
[101325 89399 78878]
9 Теперь мы можем визуализировать данные из массива model и сравнить их с экспериментальными данными. Для построения графиков используем команду plot библиотеки Matplotlib. Первым аргументом
этой команды указывается массив x-координат точек,
вторым — массив y-координат тех же точек (массивы должны иметь одинаковую длину), третий аргумент определяет вид кривой (этот аргумент необязательный, по умолчанию график строится в виде ломаной линии, соединяющей заданные точки). Остальные
параметры являются опциональными, например параметр label определяет метку для легенды, параметр c
задает цвет кривой. Первым графиком будет график
нашей модели, x-координаты точек берем из массива

6

Отсюда и происходит название
команды.

Глава 5

56

dataH, y-координаты — из массива model. Второй график соответствует экспериментальным данным (массивы dataH и dataP), которые мы изобразим с помощью
маркеров в форме кружков (за это отвечает третий
аргумент "o" команды plot в строке 2). В четвертой
строке создаем легенду. Последней строкой выводим
на экран построенный график (рис. 2).
1
2
3
4
5

plt.plot(dataH, model, label = "Isothermic")
plt.plot(dataH, dataP, "o", c = "red",
label = "Experiment")
plt.legend()
plt.show()
10 Видно, что две кривые демонстрируют хорошее
совпадение на небольших высотах, после чего они начинают расходиться. Переведем это наблюдение в числа, посчитав относительную погрешность (в процентах) модельных данных по формуле

Сравнение изотермической модели с экспериментальными данными

РИС. 2

E(h) =

PM (h) − P (h)
· 100 %,
P (h)

(7)

где PM (h) — давление на высоте h согласно модели,
P (h) — экспериментальное значение давления на той
же высоте. Применим эту формулу сразу ко всему
содержимому массивов model и dataP, на выходе получим новый массив погрешностей, который сохраним в переменной error. Построим график погешности (рис. 3), который и подтверждает сделанное нами наблюдение о хорошем совпадении модели и эксперимента на высотах до 5 км (ошибка менее одного
процента). Однако на больших высотах погрешность
начинает возрастать, и на высоте 20 км ошибка составляет уже порядка 50 %.
1
2
3
4

РИС. 3 Относительная погреш-

ность изотермической модели

error = np.abs((model - dataP) / dataP) * 100
plt.plot(dataH, error, label = "Isothermic")
plt.legend()
plt.show()
11 Как уже упоминалось выше, на высотах до 10 км
температура воздуха постепенно снижается. С хорошим приближением можно считать, что

T (h) = 288 − 0.0064h,

(8)

где h измеряется в метрах, а температура T — в градусах Кельвина. Построим в SymPy такую модель с линейной зависимостью температуры воздуха от высо-

Барометрическая формула

57

ты. Введем два дополнительных символа a и b и подставим в исходное дифференциальное уравнение ode7
вместо символа T выражение a + b * h.
1
2
3

a, b = symbols("a b")
ode2 = ode.subs(T, a + b * h)
display(ode2)

7

Заменять T на функцию от h
надо именно в дифференциальном
уравнении, а не в его решении!

M gP (h)
d
P (h) = −
dh
R (a + bh)
12 Решаем начальную задачу для нового уравнения
ode2 со старым начальным условием ic.
1
2

dsol2 = dsolve(ode2, P, ics = ic)
display(dsol2)

P (h) = P0 e

M g(− log (R)−log (a+bh))
Rb

e

M g log (R)
Rb

e

8

Символ T из новой модели нами
исключен.

M g log (a)
Rb

13 Далее повторяем шаги 6–10 для новой модели.
Подставляем параметры модели в решение, добавив
предварительно параметры

a = 288 и b = −0.0064
к словарю par, результат подстановки запоминаем в
переменной ds2. Преобразуем правую часть решения
в функцию fP2, зависящую теперь от единственной
переменной h8 . Применяем эту функцию к массиву
dataH, результат записываем в массив model2. Строим график новой зависимости давления от высоты,
указав метку "Linear" (см. рис. 4). Далее вычисляем
массив погрешностей error2 и строим соответствующий график (рис. 5). Предсказания новой модели с
линейным убыванием температуры на первом графике теперь визуально практически не отличимы от экспериментальных данных, второй график с погрешностью модели подтвеждает это наблюдение. Видно, что
новая модель очень хорошо согласуется с реальными
данными на высотах до 10–12 км, после чего погрешность начинает возрастать. Этот график, кстати, служит косвенным подтверждением того, что на высоте
около 10 км закон изменения температуры становится
каким-то другим (см. упражнение 7).
14 Для более наглядного сравнения двух построенных нами моделей объединим их графики (рис. 6).

Сравнение модели с линейным убыванием температуры с экспериментальными
данными
РИС. 4

РИС. 5 Относительная погреш-

ность модели с линейным
убыванием температуры

Глава 5

58

1
2
3
4
5
6

plt.plot(dataH, model, label = "Isothermic")
plt.plot(dataH, model2, label = "Linear")
plt.plot(dataH, dataP, "o", c = "red",
label = "Experiment")
plt.legend()
plt.show()
15 Аналогичным образом объединяем графики с погрешностями двух моделей (рис. 7).

1

Сравнение двух моделей друг с другом и с экспериментальными данными
РИС. 6

2
3
4

plt.plot(dataH, error, label = "Isothermic")
plt.plot(dataH, error2, label = "Linear")
plt.legend()
plt.show()

УПРАЖНЕНИЯ И ЗАМЕЧАНИЯ
1 Постройте графики зависимости давления P от высоты h в изотермической модели, используя разные значения
температуры T . Сравните между собой погрешности этих
моделей.

РИС. 7 Сравнение относительной погрешностей двух моделей

2 Характеристической высотой изотермической модели,
показывающей скорость изменения давления с высотой,
является высота h1/2 , на которой давление составляет половину давления на уровне моря. Вычислите значение такой высоты для атмосферы Земли.
3 Полученные нами формулы справедливы и для отрицательных высот. Оцените с их помощью давление воздуха
на глубине 1 км. На какой глубине давление воздуха превысит давление на поверхности P0 ровно в два раза?
4 Используя закон Менделеева—Клайперона (3) и решение dsol, найденное для изотермического случая, выразите плотность ρ = m/V воздуха через высоту h. Зная зависимость ρ(h), оцените примерную массу атмосферы Земли,
посчитав тройной интеграл по всему объему атмосферы,
т. е. по внешней части сферы радиуса, равного радиусу
Земли. В сферических координатах этот интеграл имеет
следующий вид:
Z2π Zπ/2
Z∞
mA = dϕ cos θdθ ρ(r − RE ) · r2 dr,
0

−π/2

(9)

RE

где RE — радиус Земли, r − RE = h — высота над уровнем
моря (над поверхностью Земли).

9

Также вам потребуются значения ускорения свободного падения
на поверхности данных планет.

5 Найдите параметры атмосфер других планет Солнечной системы — молярную массу M , температуру T и давление P0 на уровне поверхности9 — и постройте зависимость атмосферного давления от высоты для этих планет
в изотермическом приближении.

Барометрическая формула

59

6 Предположим, в далеком будущем человечество собирается колонизировать Луну, создав на ней искусственную
атмосферу, химически идентичную земной атмосфере. На
уровне поверхности Луны атмосферное давление должно
быть равным давлению P0 земной атмосферы на уровне
моря, а температура T = 20 ◦ C. Постройте зависимость
давления такой искусственной атмосферы от высоты. На
какой высоте давление будет в два раза меньше давления P0 ?
7 После 10 км температура воздуха перестает понижаться и до высоты 25 км остается примерно на одном и том
же уровне10 . То есть на этих высотах атмосфера является изотермической. Определите зависимость P (h) для такой кусочно-линейной температуры T (h). Для этого надо
с помощью решения dsol2 нашей второй модели вычислить давление P1 = P (10 000) на высоте 10 км и использовать это значение в качестве начального условия для дифференциального уравнения ode в изотермическом случае.
Сравните полученное решение на высотах от 10 до 20 км
с экспериментальными данными.

10

Тропопауза и нижний слой
стратосферы.

8 Составьте и решите дифференциальное уравнение для
случая изотермической атмосферы в предположении, что
ускорение свободного падения зависит от высоты h следующим образом:
GME
,
(10)
g(h) =
(RE + h)2
где G = 6.67408 · 1011 м3 ·с−2 ·кг−1 — гравитационная постоянная, ME ≈ 6 · 1024 кг — масса Земли, RE ≈ 6.4 · 106 м —
ее радиус.
9 Постройте модель для вычисления давления жидкости на глубине h в предположении, что на ее поверхности давление равно P0 . При построении такой модели надо
учесть, что жидкость в отличие от газа является несжимаемой, т. е. ее плотность не меняется с глубиной. Выведите
и решите дифференциальное уравнение для этой модели.
Оцените с помощью найденного решения давление воды
на глубине 10 м, если давление на ее поверхности равно
атмосферному давлению.
10 Устройство простейшего ртутного барометра (рис. 8)
основано на приниципе уравновешивания столба воздуха
и столба жидкости (в данном случае ртути). В стеклянную трубку, закрытую с одной стороны, наливается ртуть,
трубка переворачивается и помещается в кювету со ртутью. Часть ртути из трубки выливается в кювету, из-за
чего в верхней части трубки образуется ваккуум. Давление
ртути на ее уровне в кювете равно атмосферному давлению. С другой стороны такое же давление создает ртуть,

8 Схема простейшего
ртутного барометра
РИС.

Глава 5

60

оставшаяся в трубке. Высота ртути в трубке относительно высоты в кювете и служит мерой давления. Вычислите
с помощью модели из предыдущего упражнения высоту
ртутного столба для нормального атмосферного давления
P0 = 101 325 Па. Оцените аналогичную высоту столба воды в простейшем водяном барометре.
11 Оценить значение некоторого выражения SymPy при
заданных значениях входящих в него переменных можно
с помощью комбинации методов subs и evalf:
1
2

f = x * sin(x)
display(f.subs(x, 2).evalf())
1.81859485365136
Однако такой способ является весьма неэффективным, если применять его к одному и тому же выражению многократно11 , т. к. каждое одиночное оценивание выражения сводится к выполнению серии относительно трудоемких символьных преобразований. Существенно более эффективным и более наглядным является в данном случае
использование команды lambdify:

11

Например, при построении графика.

1
2

F = lambdify(x, f)
print(F(2))
1.81859485365136
Исследуйте и сравните время работы этих двух подходов в
зависимости от числа n оцениваний заданного выражения.
12 Дифференциальные уравнения вида p(x)y 0 +q(x)y = 0
называются линейными однородными. Решите с помощью
SymPy следующие уравнения данного типа:
1)
2)

y 0 + 4y = 0;
xy 0 = 2y;

3)
4)

y 0 + tgx y = 0;
x lnx y 0 = 2y.

Постройте с помощью Matplotlib графики семейств найденных общих решений.

ГЛАВА 6
Модели роста
Модель естественного роста • Богатым источником дифференциальных уравнений является популяционная экология, особенно в той ее части, где исследуется динамика различных популяционных процессов. Базовой и исторически первой моделью популяционной динамики является так называемая модель
естественного, или экспоненциального, роста. Впервые
эта модель была предложена в 1798 году английским
священником и ученым Томасом Мальтусом применительно к росту численности народонаселения.
Пусть у нас имеется популяция некоторых живых
организмов — бактерий, мух, кроликов, людей и т. п.
Размер популяции x предполагается достаточно большим, чтобы его можно было считать непрерывной величиной. Предположим, что в единицу времени на одну особь популяции приходится α рождений новых
особей1 и β смертей имеющихся особей. Параметры
α и β в модели естественного роста являются постоянными величинами. Требуется определить, как изменяется со временем размер такой популяции x(t).
Уравнение, описывающее динамику размера популяции, составляется по стандартной (бухгалтерской)
схеме: рассмотрим, как изменяется число особей в популяции за промежуток времени от t до t + ∆t:
x(t + ∆t) = x(t) + α · x(t) · ∆t − β · x(t) · ∆t .
было

родилось

(1)

умерло

Следовательно,
∆x
= (α − β) · x(t).
∆t

(2)

После выполнения предельного перехода ∆t → 0 по-

Томас Мальтус

1
Например, если α = 0.1, то в
популяции из миллиона особей в
указанную единицу времени рождается примерно 100 тысяч новых
особей.

Глава 6

62

лучаем искомое дифференциальное уравнение:
dx
= kx,
dt

(3)

где k = α − β — удельная скорость роста популяции.
В качестве начального условия традиционно используется известный размер популяции в начальный момент времени (обычно при t = 0):
x(0) = x0 .

(4)

Модель Ферхюльста • Модель Мальтуса, рас-

Пьер Франсуа Ферхюльст
2

Историки науки так и не установили, почему Ферхюльст назвал
свой закон «логистическим».

смотренная нами выше, считается адекватной только
для начальных стадий роста популяции, т. е. при условии неограниченности ресурсов и отсутствия внутривидового соперничества за эти ресурсы. Так как в любой реальной популяции количество ресурсов является конечным, то начиная с некоторого момента внутри популяции начинается борьба за эти ресурсы, что
приводит к уменьшению удельной скорости роста популяции.
Первой моделью, в которой учитывалась внутривидовая конкуренция за ресурсы, была модель логистического роста, предложенная в 1838 году бельгийским
математиком Пьером Ферхюльстом2 . В модели Ферхюльста предполагается, что скорость изменения размера популяции ẋ пропорциональна самому этому размеру x (как и в модели Мальтуса), а также пропорциональна количеству доступных популяции ресурсов.
При этом количество ресурсов должно убывать с ростом популяции.
В простейшем случае число ресурсов представляет
собой убывающую линейную функцию от размера популяции. При таком предположении дифференциальное уравнение, описывающее рост популяции в модели
Ферхюльста, записывается следующим образом:

dx
x
= kx 1 −
,
dt
M

(5)

где k > 0 — удельная скорость размножения, M — так
называемая поддерживающая емкость среды.

Модель • Построим в SymPy описанные выше модели
роста популяции — модель естественного роста (или

Модели роста

63

модель Мальтуса) и логистическую модель Ферхюльста. Используя полученные решения, попробуем применить эти две модели для оценки численности населения Земли, начиная с 1950 года.
1 Подключаем необходимые библиотеки, в том числе
библиотеку Matplotlib и пакет NumPy.
2 Определяем символы для переменной t и двух параметров k и x0 , а также функцию x(t).
3 Создаем дифференциальное уравнение (3) для модели естественного роста.
1
2

ode = Eq(diff(x, t), k * x)
display(ode)

d
x(t) = kx(t)
dt
4 Определяем начальное условие (4).
1
2

ic = {x.subs(t, 0): x0}
display(ic)
{x(0): x0}
5 Решаем уравнение ode с начальным условием ic.
Решение запоминаем в переменной dsol.

1
2

dsol = dsolve(ode, x, ics = ic)
display(dsol)

x(t) = x0 ekt
6 Найденное решение зависит от двух параметров —
начального размера популяции x0 и удельной скорости
роста k. Исследуем графически характер этих зависимостей. Сначала построим семейство интегральных
кривых для разных значений x0 при фиксированной
положительной скорости k = 0.04 (рис. 1).
1
2
3
4
5

p1 = plot(ylim = (0, 40), show = False)
for xx in np.arange(1, 40, 2):
ds = dsol.rhs.subs({k: 0.04, x0: xx})
p1.extend(plot(ds, (t, 0, 100), show = False))
p1.show()
7 Далее построим семейство интегральных кривых
уже для разных значений k (в том числе и отрицательных) при фиксированной начальной численности
x0 = 15 (рис. 2).

Поведение модели естественного роста для разных
значений параметра x0
РИС. 1

64

1
2
3
4
5

Глава 6

p2 = plot(ylim = (0, 40), show = False)
for kk in np.arange(-0.2, 0.2, 0.01):
ds = dsol.rhs.subs({k: kk, x0: 15})
p2.extend(plot(ds, (t, 0, 100), show = False))
p2.show()

Убывающие экспоненты на графике соответствуют отрицательным значениям k (популяция вымирает), возрастающие — положительным значениям k (популяция неограниченно растет). Критическим значением
является k = 0 (горизонтальная прямая x = x0 ), при
котором размер популяции не меняется со временем.
8 Попробуем теперь применить построенную модель
к данным ООН по численности населения Земли с
1950 по 2020 год (см. табл. 1). Для этого создадим и
заполним два массива NumPy данными из приведенной
таблицы.

Поведение модели естественного роста для разных
значений параметра k
РИС. 2

Численность населения Земли (в млрд человек)
в разные годы

ТАБЛ. 1

Год

Население

1950
1955
1960
1965
1970
1975
1980
1985
1990
1995
2000
2005
2010
2015
2020

2.53
2.76
3.02
3.33
3.69
4.06
4.44
4.85
5.29
5.71
6.11
6.51
6.91
7.30
7.67

1
2
3

years = np.arange(1950, 2021, 5)
pop = np.array([2.53, 2.76, 3.02, 3.33, ...])
print(years[:3], pop[:3])
[1950 1955 1960] [2.53 2.76 3.02]
9 Будем отсчитывать время (t = 0) в нашей модели
с 1950 года. Понятно, что параметр x0 — это численность населения Земли в 1950 году, т. е. вместо параметра x0 мы должны подставить в решение дифференциального уравнения величину pop[0]. Немного сложнее дело обстоит со вторым параметром модели k, значения которого в явном виде в наших данных
нет. Если предположить, что данные в табл. 1 являются точными и соответствующими модели естественного роста, то параметр k можно оценить по результатам ровно одного измерения x1 в любой момент времени t1 , не совпадающий с начальным моментом t0 .
Введем два дополнительных символа для параметров
x1 и t1 и подставим их в решение дифференциального
уравнения dsol вместо x и t. Получим алгебраическое
уравнение eq относительно символа k.

1
2
3

t1, x1 = symbols("t1 x1")
eq = dsol.subs({x: x1, t: t1})
display(eq)

x1 = x0 ekt1

Модели роста

65

10 Для решения полученного уравнения eq применим команду solve, которая возвращает в качестве результата список с найденными решениями3 . В данном
случае этот список состоит из единственного решения,
извлечем его с помощью нулевого индекса и сохраним
в переменной sol.
1
2

sol = solve(eq, k)[0]
display(sol)

log



x1
x0

t1



11 Теперь можем подставить найденное выражение
вместо символа k в решение dsol.
1
2

dsol2 = dsol.subs(k, sol)
display(dsol2)
t log

x(t) = x0 e

( xx10 )

t1

Получили решение уравнения, выраженное через три
параметра x0 , x1 и t1 , значения которых мы можем
взять, например, из массивов pop и years.
12 Построим с помощью Matplotlib график полученной модели и сравним его с реальными данными из
табл. 1. Выберем в качестве величины t1 значение 30
лет, что соответствует 1980 году, в массиве pop нужное нам значение x1 хранится под индексом 30/5 = 6.
Создаем словарь par с параметрами модели. Преобразуем правую часть решения с подставленными в нее
параметрами в функцию Python. Вычисляем предсказание prediction модели для каждого года из массива
years, т. к. в модели отсчет времени ведется с нуля,
то вычитаем из каждого года величину 1950. Строим
график с предсказанием и для сравнения график с исходными данными модели. Включаем показ легенды и
выводим график на экран.
1
2
3
4
5
6
7

par = {x0: pop[0], t1: 30, x1: pop[6]}
fP = lambdify(t, dsol2.rhs.subs(par))
prediction = fP(years - 1950)
plt.plot(years, prediction, label = "1980")
plt.plot(years, pop, "o", label = "ООН")
plt.legend()
plt.show()

3

Команда solve считается устаревшей командой SymPy, от которой разработчики библиотеки
планируют в будущем отказаться.
См. замечение 1 к главе для объяснения, почему мы не использовали
для решения данного алгебраического уравнения стандартный решатель solvesest.

Глава 6

66

Результат сравнения показан на рис. 3. Видно, что модель показывает адекватные результаты только на датах примерно до 1990 года, после чего предсказания
модели начинают существенно расходиться с реальными данными, т. е. рост населения Земли не подчиняется построенной нами модели Мальтуса.
Сравнение модели
Мальтуса
с
реальными
данными
РИС.

13 Построим теперь модель Ферхюльста по такой же
схеме. Определяем дополнительный символ M и составляем дифференциальное уравнение (5).

3

1
2
3

M = symbols("M")
ode2 = Eq(diff(x, t), k * x * (1 - x / M))
display(ode2)



x(t)
d
x(t) = k 1 −
x(t)
dt
M
14 Решаем уравнение, используя начальное условие
из модели Мальтуса. Упрощаем результат с помощью
команды symplify4 .

4

В неупрощенное решение нельзя
подставить критическое значение
x0 = M , проверьте!

1
2

dsol3 = simplify(dsolve(ode2, x, ics = ic))
display(dsol3)

x(t) =

M x0 ekt
M + x0 ekt − x0

15 Построим семейство кривых найденного решения
для разных значений x0 при фиксированных остальных параметрах модели (см. рис. 4).
4 Поведение
модели
Ферхюльста
для
разных
значений параметра x0
РИС.

1
2
3
4
5

p3 = plot(ylim = (-1, 40), show = False)
for xx in np.arange(1, 40, 2):
ds = dsol3.rhs.subs({k: 0.04, M: 21, x0: xx})
p3.extend(plot(ds, (t, 0, 100), show = False))
p3.show()

Видно, что при любых начальных условиях численность популяции стремится к предельной величине,
равной M . Кривые, стартующие ниже M , называются
сигмоидами.
16 Применим построенную нами модель Ферхюльста
к данным роста численности населения Земли. Теперь
модель включает в себя два дополнительных параметра k и M , значения которых можно определить с помощью двух дополнительных условий x1 = x(t1 ) и
x2 = x(t2 ). Получаемая при этом система двух нелинейных алгебраических уравнений относительно k и

Модели роста

67

M в случае произвольных t1 и t2 , однако, не решается
аналитически. Аналитическое решение возможно, например, при t2 /t1 = 2, в этом случае система сводится
к квадратному уравнению5 . Создадим новый символ
x2 и подставим в решение dsol3 условия x1 = x(t1 ) и
x2 = x(2t1 ). Сохраним полученные уравнения в переменных eq2 и eq3.
1
2
3
4

5

При условии, что t0 = 0. В общем случае условие разрешимости
имеет вид
t2 − t1 = t1 − t0 .

x2 = symbols("x2")
eq2 = dsol3.subs({x: x1, t: t1})
eq3 = dsol3.subs({x: x2, t: 2 * t1})
display(eq2, eq3)

M x0 ekt1
M + x0 ekt1 − x0
M x0 e2kt1
x2 =
M + x0 e2kt1 − x0

x1 =

17 Решим систему из этих двух уравнений с помощью команды solve6 . Первым аргументом этой команде в данном случае надо передать кортеж из двух
уравнений, вторым — кортеж из символов неизвестных. Решением является список кортежей. В нашем
случае решение одно, извлекаем его с помощью нулевого индекса и распаковываем в переменные sol2 и
sol3.
1
2

sol2, sol3 = solve((eq2, eq3), (k, M))[0]
display(sol2, sol3)

log



x2 (x0 −x1 )
x0 (x1 −x2 )



t1
x1 (−x0 x1 + 2x0 x2 − x1 x2 )
x0 x2 − x21
18 Подставляем эти два выражения вместо символов k и M в решение dsol3 дифференциального уравнения. Для вывода на экран полученное выражение
упрощаем с помощью команды logcombine, которая в
данном случае вносит все коэффициенты, стоящие перед логарифмами, под логарифмы как степени (опция
force=True) и упрощает получающиеся при этом экспоненты.
1
2

dsol4 = dsol3.subs({k: sol2, M: sol3})
display(logcombine(dsol4, force = True))

6

См. замечание 2 к главе.

Глава 6

68

x(t) =

 t

x2 (x0 −x1 ) t1
x0 x1
(−x0 x1 +2x0 x2 −x1 x2 )
x0 (x1 −x2 )



 t
x2 (x0 −x1 ) t1
x (−x0 x1 +2x0 x2 −x1 x2 ) 
2

−x0 + 1
(x0 x2 −x1 ) x0 x (x −x )
2
x0 x2 −x1
0 1
2

Получили в итоге решение x(t), выраженное через параметры x0 , x1 , x2 и t1 .
19 Применим построенную модель к предсказанию
роста населения Земли. Как и в предыдущей модели,
положим t1 = 30 (1980 год), следовательно, t2 = 60,
что соответствует 2010 году. Соответствующие данные в массиве pop хранятся под индексами 6 и 12. Все
остальные действия аналогичны пункту 12.
1
2
3
4
5
6
7

par = {x0: pop[0], t1: 30, x1: pop[6], x2: pop[12]}
fP2 = lambdify(t, dsol4.rhs.subs(par))
prediction = fP2(years - 1950)
plt.plot(years, prediction, label = "1980+2010")
plt.plot(years, pop, "o", label = "ООН")
plt.legend()
plt.show()
20 Наконец, оценим величину поддерживающей емкости среды M согласно последней построенной модели. В пункте 17 мы выразили этот параметр модели
через величины x0 , x1 , x2 и t1 и сохранили полученное
выражение в переменной sol3. Подставим в это выражение числовые параметры par последней модели.

РИС. 5 Сравнение
модели
Ферхюльста с реальными
данными

1

print(sol3.subs(par))
13.8276161878725

Так как поддерживающая емкость среды представляет собой предельное значение размера популяции в модели Ферхюльста, то получаем, что согласно построенной модели предельная численность населения Земли составляет примерно 14 миллиардов.

УПРАЖНЕНИЯ И ЗАМЕЧАНИЯ
1 Если мы попытаемся решить уравнения eq из пункта 10 с помощью команды solveset, то получим следующий неприятный результат, обусловленный тем, что SymPy
по умолчанию все решает в комплексных числах:
1
2

solk = solveset(eq, k)
display(solk)

Модели роста

69

 

 
 i 2nπ + arg xx1
+ log
0



1
2

1

t1

x1
x0



n∈Z





Чтобы найти решение в действительных числах, надо указать опцию domain = S.Reals:
solk = solveset(eq, k, domain = S.Reals)
display(solk)
 

 log xx1 
0
R∩


t1

Из такого выражения уже можно извлечь собственно решение, используя дважды атрибут args:
display(solk.args[1].args[0])
 
log xx10
t1

1
2
3
4

1
2
3

2 Для решения систем нелинейных уравнений в SymPy
имеется специальная команда nonlinsolve. Однако для решения системы уравнений (eq2, eq2) (см. пункт 16) эта
команда не работает. Данную систему можно решить в
ручном режиме с помощью нескольких применений команды solveset. Сначала разрешаем каждое из двух уравнений относительно M и приравниваем найденные выражения:
m1 = solveset(eq2, M).args[0].args[0]
m2 = solveset(eq3, M).args[0].args[0]
eqk = Eq(m1, m2)
display(eqk)



x0 x1 ekt1 − 1
x0 x2 ekt1 − 1 ekt1 + 1
=
x0 ekt1 − x1
x0 e2kt1 − x2
В полученном уравнении делаем замену ekt1 → K:
K = symbols("K")
eqK = eqk.subs(k, log(K) / t1)
display(eqK)
x0 x1 (K − 1)
x0 x2 (K − 1) (K + 1)
=
Kx0 − x1
K 2 x0 − x2

1
2

Это уравнение уже решается с помощью solveset:
solK = solveset(eqK, K).args[0].args[2]
display(solK)

Глава 6

70
x0 x2 − x1 x2
x0 x1 − x0 x2

Делаем обратную замену и подставляем полученное выражение вместо символа k в одно из выражений m1 или m2.
Это и дает нам искомое решение системы:
1
2
3

solk = log(solK) / t1
solM = m1.subs(k, solk)
display(solk, simplify(solM))
log



x0 x2 −x1 x2
x0 x1 −x0 x2



t1
x1 (−x0 x1 + 2x0 x2 − x1 x2 )
x0 x2 − x21
3 По аналогии с пунктом 12 постройте на одном графике предсказания численности населения Земли с помощью
модели Мальтуса с дополнительным условием x(t1 ) = x1
для значений времени t1 = 5, 10, . . . , 50.
4 Постройте графики предсказания численности населения Земли с помощью модели Ферхюльста для разных значений времени t1 = 5, 10, . . . , 35. Для каждого из этих значений оцените предельную численность M .

Конец света в модели
Ферхюльста
РИС. 6

Пол Ральф Эрлих

5 Если мы оценим параметры построенной в главе модели Ферхюльста для населения Земли, например, для случая t1 = 10 (т. е. по данным 1960 и 1970 годов), то обнаружим, что поддерживающая емкость среды M является
отрицательной. Это значит, что экспериментальные данные и модель не соответствуют друг другу. Если построить
график найденной зависимости x(t) в более более широком
диапазоне (см. рис. 6, красная линия соответствует отрицательной предельной численности населения), то мы увидим, что у этого графика имеется разрыв примерно в 2025
году. При приближении к этой дате модель предсказывает бесконечный рост населения Земли, что является абсолютно нереалистичным. Такой виртуальный апокалипсис
в теории популяционного моделирования с помощью дифференциальных уравнений называется doomsday (дословно: конец света).
6 По оценкам известного демографа Пола Эрлиха, 10 тысяч лет назад на Земле жило около 5 млн человек. В 2000
году — примерно 6 млрд человек. Оцените, исходя из этих
данных, примерное время «возникновения» человечества
согласно модели Мальтуса, т. е. когда его численность составляла ровно 2 человека. Попробуйте оценить это же
время с помощью данных из табл. 1.

Модели роста

71

7 Найдите в Интернете данные по численности населения по континентам и отдельным странам и постройте соответствующие модели Мальтуса и Ферхюльста.
8 Если проанализировать решение дифференциального
уравнения
M x0 ekt
x(t) =
(6)
M + x0 ekt − x0

для модели Ферхюльста, то окажется, что существует такая функция X(t), что x(t) < X(t) для любого начального
значения x0 > 0 (см. рис. 7, функция X(t) выделена красным цветом). Найдите эту функцию, используя команду
limit.

9 Постройте семейства графиков решения x(t) дифференциального уравнения в модели Ферхюльста для различных значений: а) параметра k; б) параметра M . Исследуйте поведение модели для отрицательных значений
этих параметров.

Предельное решение в
модели Ферхюльста
РИС. 7

10 Дифференциальное уравнение, возникающее в модели Мальтуса, принадлежит классу линейных однородных
уравнений первого порядка вида
y 0 = f (x)y.

(7)

Известно, что общее решение этого уравнения имеет вид7 :
y = CeF (x) , где F (x) =

Z

f (x)dx.

(8)

Напишите функцию lsolve(ode, y, x), которая бы решала с помощью формулы (8) заданное дифференциальное
уравнение относительно заданной неизвестной функции:
первым шагом разрешаем уравнение относительно производной — получаем выражение f (x)y; вторым шагом делим правую часть на неизвестную функцию — получаем
функцию f (x); последним шагом применяем формулу (8).
Протестируйте написанную функцию на решении следующих уравнений:
1)
2)

dy
− xy = 0;
dx
t + 1 dx
= t;
x dt

3)
4)

r(cos ϕ + 1)
dr
=
;

sin ϕ
dv
(u2 + 1)
= uv.
du

11 Дифференциальное уравнение в модели Ферхюльста
относится к классу уравнений Бернулли:
dx
= p(t)x + q(t)xn , где n 6= 0, 1.
dt

(9)

7

Эта формула элементарно выводится из решения данного уравнения методом разделения переменных.

Глава 6

72

Библиотека SymPy не всегда классифицирует уравнения такого типа, например ею не классифицируется и не решается уравнение (n = 100)
dx
= x100 + x.
dt

(10)

Стандартный алгоритм решения уравнений Бернулли заключается в замене неизвестной функции x(t) на новую
функцию y(t), приводящей к линейному уравнению:
1
1 − n dx
dy
=


xn−1
dt
xn dt
1 dx
1 dy
1 dy
⇒ n
=

= p(t)y + q(t).
x dt
1 − n dt
1 − n dt

y=

(11)

Применим этот подход к решению уравнения (10):
1
2
3
4
5
6
7
8

n = 100
ode = Eq(diff(x, t), x + x ** n)
y = Function("y")(t)
zy = 1 / x ** (n - 1)
zx_ = x ** n * diff(y, t) / (1 - n)
rhs = solve(ode.subs(x_, zx_), diff(y, t))[0]
zode = Eq(diff(y, t), rhs.subs(zy, y))
dsolve(zode, y).subs(y, zy)
1
= C1 e−99t − 1
x99 (t)

8

Крайне рекомендуется вывести
на экран результаты выполнения
всех приведенных команд, начиная с четвертой, и сравнить их с
формулами (11).
9

Для дробных n используйте команду Rational.

В первых трех строках явно задаем параметр n, определяем дифференциальное уравнение и вводим новую неизвестную функцию y(t). Далее определяем замену и вычисляем ее производную по t. В строке 6 выполняем подстановку производной в исходное уравнение и разрешаем это
уравнение относительно dy/dt. В полученном выражении
завершаем замену и формируем новое дифференциальное
уравнение. Решаем его и делаем обратную замену8 .
12 Убедитесь, что следующие дифференциальные уравнения являются уравнениями Бернулли, и решите их либо
с помощью команды dsolve, либо по описанной выше схеме (определив предварительно значение n9 ):
dx
dx
1)
+ 2x = x3 ;
3) x2
= 4x3 + 1;
dt
dt
√ 
dx
dx
2) t
+ x = t2 x50 ;
4)
= x + 5 x sin t.
dt
dt

ГЛАВА 7
Табулирование функций
Метод Эйлера • Далеко не все дифференциаль-

ные уравнения можно решить аналитически. Для решения таких «нерешаемых» уравнений1 были придуманы специальные приближенные методы, позволяющие находить искомое решение с некоторой (управляемой) погрешностью. Простейшим приближенным
методом решения дифференциальных уравнений является метод Эйлера.
Рассмотрим схему работы метода Эйлера решения
начальной задачи

1

А также уравнений, аналитическое решение которых хотя и возможно, но слишком трудоемко.

y 0 = f (x, y), y(x0 ) = y0 .
Согласно определению производной2 заменим в дифференциальном уравнении производную y 0 на разностное отношение
y(x + h) − y(x)
y 0 (x) ≈
,
(1)
h
где h — параметр, называемый шагом:
y(x + h) − y(x)
= f (x, y) ⇒
h
⇒ y(x + h) = y(x) + h · f (x, y). (2)

Полученная формула позволяет вычислить неизвестную функцию в точке x + h, если известно ее значение
в точке x. Подставляя в (2) значения из начального
условия x0 и y0 , находим
y1 = y(x0 + h) = y0 + h · f (x0 , y0 ).

Подставляем в (2) x1 = x0 + h и вычисленное значение
y1 и находим
y2 = y(x1 + h) = y1 + h · f (x1 , y1 ).

Леонард Эйлер
2

По определению производной
0

y (x) = lim

h→0

y(x + h) − y(x)
,
h

следовательно, для малых h
должно выполняться приближенное равенство
0

y (x) ≈

y(x + h) − y(x)
.
h

74

Глава 7

Продолжая этот процесс, находим значения неизвестной функции yi во всех точках вида xi = x0 + ih:
y0 = y(x0 ), yi+1 = yi + h · f (xi , yi ), i = 0, 1, . . .
y(xi+1 )

ε
yi+1
∆y

k1

yi

h

O

xi

xi+1

Геометрическая интерпретация метода Эйлера
РИС. 1

Одна из первых таблиц десятичных логарифмов,
1617 г.
РИС. 2

Чарльз Бэббидж

(3)

Геометрический смысл метода Эйлера вполне очевиден (см. рис. 1). Значение k1 = f (xi , yi ) определяет угловой коэффициент (тангенс угла наклона) касательной к искомой кривой в текущей точке (xi , yi ).
Если мы придаем приращение аргумента ∆x = h, то
приращение функции ∆y будет приблизительно равно h · k1 , что и приводит нас к формуле (3).

Задача • Табулированием функции называется со-

ставление таблицы ее значений в заданном диапазоне
изменения ее аргумента. До появления калькуляторов
и компьютеров, способных вычислять с нужной точностью тригонометрические, логарифмические и другие
элементарные функции, таблицы были единственным
средством практического использования таких функций. Например, тригонометрические функции активно применялись для навигации в мореплавании, логарифмические (рис. 2) — для ускорения арифметических вычислений (прежде всего для замены тяжелых
операций умножения и деления на простые сложение
и вычитание).
Составление таблиц функций — достаточно простая в алгоритмическом смысле задача, но требующая
выполнения больших объемов чисто вычислительной
работы, которая выполнялась вручную и приводила
по этой причине к большому количеству арифметических ошибок. Поэтому еще с XVIII века предпринимались попытки по механизации и автоматизации такого
рода деятельности, наиболее известной из которых может служить разностная машина Чарльза Бэббиджа,
предложенная (но так до конца и не достроенная) им
в 1822 году.
Проблема построения таблиц функций не потеряла
своей актуальности и в настоящее время. Многие алгоритмы вычисления значений элементарных функций
используют для ускорения расчетов таблицы предвычисленных значений этих функций. Кроме того, табулирование остается основным методом работы с многочисленными специальными функциями, поддержка
которых не встроена в аппаратную часть современных
компьютеров.

Табулирование функций

75

Рассмотрим подход к решению задачи табулирования функций, основанный на численном решении
обыкновенных дифференциальных уравнений. Основная идея предлагаемого подхода заключается в том,
что многие сложные функции (например, экспонента или логарифм) оказываются решениями начальных
задач для относительно простых дифференциальных
уравнений вида y 0 = f (x, y), в которых правая часть
является рациональной функцией, т. е. комбинацией
четырех арифметических операций над переменными
2
x и y. Например, функция y = ex является решением
начальной задачи
y 0 = 2xy, y(0) = 1.

(4)

Следовательно, чтобы составить таблицу значений такой функции, достаточно приближенно решить, например методом Эйлера, соответствующую начальную
задачу с заданным шагом h. Заметим, что если функция f (x, y) рациональна, то все вычисления будут выполняться только с помощью четырех арифметических операций.

Модель • Рассмотрим решение задачи табулирования на примере функции ошибок erf x, которая определяется как
2
erf x = √
π

Zx

2

e−t dt.

(5)

0

Известно, что эта функция является нечетной, поэтому достаточно построить таблицу ее значений только
для положительных x. Составление таблицы мы будем выполнять в три этапа: вычисление константы π;
вычисление квадратного корня из π; вычисление интеграла в (5). Каждый из этапов будет реализован через решение специально составленного дифференциального уравнения с рациональной правой частью с
помощью метода Эйлера3 .
1 Подключаем библиотеки. В этот раз нам не понадобится библиотека символьных вычислений, а функции модуля math будут нужны только для проверки
работоспособности нашей модели. Вычислим, для примера, значение erf(1).

3

Таким образом, для составления таблицы значений заданной
функции мы не будем использовать ни математические функции,
ни математические константы типа π и e, а только арифметику.

Глава 7

76

1
2
3
4

import numpy as np
import matplotlib.pyplot as plt
import math
print(math.erf(1))
0.8427007929497149
2 Наш первый шаг — приближенное вычисление числа π. Заметим, что
π
= arctg 1 ⇒ π = y(1), где y(x) = 4 arctg x.
(6)
4
Нужно придумать дифференциальное уравнение с рациональной правой частью, решением которого была бы функция y(x). В данном случае это сделать
несложно, т. к. производная y 0 сама по себе является рациональным выражением:

y0 =

4
.
1 + x2

(7)

Общим решением этого уравнения выступает функция
y(x) = 4 arctg x + C,
(8)

4
В данном случае функция f1 не
зависит от второго аргумента, но
в общем случае такая зависимость
есть.

чтобы исключить из него параметр C, надо придумать
подходящее начальное условие, при котором C обращается в ноль, например подойдет условие y(0) = 0.
Решим с помощью метода Эйлера поставленную начальную задачу на интервале x ∈ [0, 1]. Определяем
правую часть уравнения (7) в виде функции f1(x,y)4 .
1
2
3

def f1(x, y):
return 4 / (1 + x ** 2)
print(f1(1, 0))
2.0

5

3 Задаем начальное условие y0 = 0 и x0 = 0, а также
верхнюю границу x1 интервала, на котором мы будем
искать решение5 .

Нижняя граница — это x0 .
1
2

y0, x0, x1 = 0.0, 0.0, 1.0
print(y0, x0, x1)
0.0 0.0 1.0
4 Задаем число точек n для метода Эйлера и вычисляем шаг h.

1
2
3

n = 100000
h = (x1 - x0) / n
print(n, h)

Табулирование функций

77

100000 1e-05
5 Переходим собственно к методу Эйлера. Так как в
данном случае нас интересует только конечное значение yn ≈ π, то нам не нужно запоминать все остальные
промежуточные величины xi и yi . Поэтому мы можем
ограничиться хранением только их текущих значений
в переменных x и y.
1
2
3
4
5
6

y = y0
for i in range(n):
x = x0 + i * h
y = y + h * f1(x, y)
Pi = y
print(Pi, math.pi)
3.141602653573155
3.141592653589793

В первой строке приведенного фрагмента кода мы инициализируем переменную y начальным значениемy0.
Далее организуем цикл по всем точкам, на каждой его
итерации вычисляем текущее значение x (по прямой
формуле, чтобы не накапливать погрешность) и обновляем значение y по основной формуле метода Эйлера (3). После завершения цикла запоминаем последнее вычисленное значение в переменной Pi и выводим
на печать полученную приближенную оценку числа π
и «точное» значение этой константы из модуля math.
Видно, что погрешность нашей оценки примерно равна 10−5 .

6 Теперь вычислим π. Построим начальную зада√
чу, решением которой будет функция y(x) = x. Для
этого заметим, что
1
1
y0 = √ ⇒ y0 =
.
2y
2 x

(9)

Общим решением этого дифференциального
уравне√
ния6 является функция y(x) = C x. В данном случае константа C исключается с помощью начального
условия y(1) = 17 . Решаем данную начальную задачу
на интервале x ∈ [1, π], в качестве верхней границы
будем использовать наше приближенное значение Pi.
В остальном схема применения метода Эйлера остается той же самой.

6

С учетом положительности и x
и y в контексте решаемой задачи.
7

Подумайте, почему здесь не подходит нулевое начальное условие
y(0) = 0.

Глава 7

78

1
2
3
4
5
6
7
8
9
10

def f2(x, y):
return 1 / (2 * y)
y0, x0, x1 = 1.0, 1.0, Pi
h = (x1 - x0) / n
y = y0
for i in range(n):
x = x0 + i * h
y += h * f2(x, y)
sqrtPi = y
print(sqrtPi, math.sqrt(math.pi))
1.7724584007845334
1.7724538509055159


Запоминаем вычисленное приближенное значение π
в переменной sqrtPi и сравниваем его со значением,
вычисленным с помощью модуля math. Видно, что погрешность вычисления не увеличилась.
7 Переходим к табулированию функции, представленной интегралом в (5). Для этого сначала построим
таблицу значений подынтегральной функции
2

y(x) = e−x ,

(10)

которая называется гауссовой функцией. Вычисляем
производную этой функции и упрощаем полученное
выражение:
2

y 0 = −2xe−x ⇒ y 0 = −2xy.

8

Для значений x > 3 интересующая нас функция приблизительно
равна нулю.

(11)

Получили дифференциальное уравнение с рациональной правой частью. Подбираем начальное условие, при
котором решением данного уравнения будет искомая
функция (10): y(0) = 1. Решаем поставленную начальную задачу методом Эйлера. В этот раз, в отличие от
предыдущих случаев, нас интересуют значения функции во всех точках рассматриваемого интервала. Поэтому вычисленные приближенные значения будем запоминать в массиве Y. Кроме того, заранее вычислим
с помощью команды linspace и сохраним в отдельном массиве X координаты узлов сетки xi = x0 + ih
на интервале x ∈ [0, 3]8 . При использовании массивов вместо скаляров команда внутри цикла оказывается полностью идентична основной формуле (3). После завершения цикла построим график полученной
табличной функции (рис. 3), представляющий собой
половину гауссианы.

Табулирование функций

1
2
3
4
5
6
7
8
9
10
11

79

def f3(x, y):
return - 2 * x * y
y0, x0, x1 = 1.0, 0.0, 3.0
h = (x1 - x0) / n
X = np.linspace(x0, x1, num = n + 1, endpoint = True)
Y = np.zeros(n + 1)
Y[0] = y0
for i in range(n):
Y[i + 1] = Y[i] + h * f3(X[i], Y[i])
plt.plot(X, Y)
plt.show()
8 Выполним, наконец, табулирование искомой функции z(x) = erf x. По формуле Лейбница для дифференцирования интеграла (5), зависящего от параметра, находим производную искомой функции:
2
2
2
z 0 = √ e−x ⇒ z 0 = √ y,
π
π

График таблично заданной гауссовой функции
РИС. 3

(12)

где y(x) — гауссова функция, представленная нами в
табличном виде в форме массивов X и Y. Дифференциальное уравнение (12) является, таким образом, простейшим. Очевидным начальным условием будет нулевое условие z(0) = 0, т. к. при x = 0 пределы интеграла (5) совпадают и он обращается в ноль. Решаем
методом Эйлера начальную задачу для z(x), причем
используем тот же массив X значений переменной x,
а в качестве функции правой части используем массив√ Y значений функции y(x), умноженный на дробь
2/ π. Таблицу значений искомой функции запоминаем в массиве Z. Строим график полученной функции
(рис. 4).
1
2
3
4
5
6
7
8

y0 = 0.0
Z = np.zeros(n + 1)
Z[0] = y0
for i in range(n):
F = Y[i] * 2 / sqrtPi
Z[i + 1] = Z[i] + h * F
plt.plot(X, Z)
plt.show()
9 Сравним визуально график табулированной нами
функции ошибок с графиком той же функции, построенной с использованием модуля math. Второй график
построим с помощью маркеров, чтобы графики не накладывались друг на друга (рис. 5).

1
2
3

plt.plot(X, Z, label = "tabulated")
XX = np.linspace(x0, x1, 20, endpoint = True)
E = np.array([math.erf(x) for x in XX])

График таблично заданной функции ошибок
РИС. 4

Глава 7

80

4
5
6

10 Построим график модуля разности между табулированной функцией ошибок и ее реализацией в модуле math (рис. 6). Если считать, что функция math.erf
является точной, то указанная разность может считаться погрешностью нашего метода табулирования.
Из графика видно, что максимальная погрешность метода равна 2 · 10−5 (см. масштабный множитель 1e-5
слева вверху графика), что примерно совпадает с порядком величины выбранного нами шага h9 .

Сравнение табулированной функции ошибок и
функции erf модуля math

РИС. 5

1
2
3

ERF = np.array([math.erf(x) for x in X])
plt.plot(X, np.abs(Z - ERF), c = "red")
plt.show()
11 Если у нас есть подсчитанная с хорошей точностью табличная функция (xi , yi ), i = 0, 1, . . . , то мы
можем реализовать на ее основе обычную функцию
Python. По заданному аргументу x10 сначала найдем
пару элементов xi и xi+1 , между которыми располагается x: xi ≤ x < xi+1 . Если последовательность xi
сформирована с постоянным шагом h, то нужный нам
индекс i вычисляется по простой формуле


x − x0
i=
,
(13)
h

График погрешности
предложенного метода

РИС. 6

9

Во всех наших реализациях метода Эйлера использовалось одно
и то же число точек n, однако так
как длина интервала x1 − x0 была
разной, то разными были и значения шага h.

где внешние скобки означают операцию округления
вниз. Если h мало, то на интервале [xi , xi+1 ] функция
y(x) близка к линейной, поэтому ее значение в точке
x можно оценить по простой пропорции (см. рис. 7)

10

Предполагая, что x находится в
том же диапазоне, что и элементы
последовательности xi .

y(x) = yi + (yi+1 − yi )

yi+1

x − xi
.
h

(14)

Применим этот прием для создания своей собственной функции erf(x), работающей в определенном нами диапазоне (x0, x1). Табличные значения представлены вычисленными ранее массивами X и Z.

y
yi
xi

x

xi+1

Оценка функции y(x) в
точке x ∈ [xi , xi+1 )

РИС. 7

plt.plot(XX, E, "o", label = "math.erf")
plt.legend()
plt.show()

1
2
3
4

def erf(x):
i = math.floor((x - x0) / h)
z1, z2 = Z[i], Z[i + 1]
return z1 + (z2 - z1) * (x - X[i]) / h
12 Для примера вычислим значение erf(π/4) в точке,
которой точно нет в массиве X, и сравним его с аналогичным значением, вычисленным с помощью функции
erf из модуля math.

Табулирование функций

1
2

81

x = math.pi / 4
print(erf(x), math.erf(x))
0.7333236883334002
0.7333114255659122

Два найденных значения различаются в пятом знаке,
что соответствует полученной выше оценке точности
нашего метода.

УПРАЖНЕНИЯ И ЗАМЕЧАНИЯ
1 Перепишите рассмотренную выше функцию erf, чтобы она работала для всех x ∈ R. Для отрицательных x
используйте свойство нечетности, для x > x1 — свойство,
что erf x ≈ 1. Оцените величину x1 , так чтобы погрешность
функции erf(x) при x > x1 не превышала ее погрешности
на интервале x ∈ [0, x1 ].
2 Постройте графики погрешности ε для задачи табулирования функции ошибок методом Эйлера для разных
значений параметра n = 100, 1000, . . . . Если эти графики построить в логарифмическом масштабе по оси Oy, используя команду plt.yscale("log") (см. рис. 8), то можно
заметить, что погрешность ε уменьшается в 10 раз при увеличении n в 10 раз, т. е. при уменьшении шага h в 10 раз,
другими словами:
ε = O(h).
(15)
Говорят, что некоторый метод имеет k-й порядок точности при решении дифференциального уравнения, если его
погрешность ε на рассматриваемом интервале пропорциональна величине hk . Следовательно, метод Эйлера имеет
первый порядок точности11 .
3 Источнник появления погрешности в методе Эйлера
можно увидеть на рис. 1. Если вторая производная искомой функции не равна нулю, то интересующая нас интегральная кривая в точке xi+1 уйдет либо выше, либо ниже
касательной в точке xi . То есть на самом деле нам нужна не касательная, а секущая. Можно частично скомпенсировать возникающую ошибку с помощью так называемого метода предиктор—корректор. Сначала выполняется
предсказание нового значения yi+1 с помощью формулы
Эйлера (3):
ỹ = yi + h · k1 , где k1 = f (xi , yi ).

Погрешность метода
Эйлера для разных значений
параметра n
РИС. 8

11

Этот факт можно строго доказать методами математического
анализа.

Глава 7

82

Затем вычисляется угловой коэффициент в предсказанной
точке k2 = f (xi+1 , ỹ). Наконец, в качестве углового коэффициента нужной нам секущей берется среднее арифметическое коэффициентов k1 и k2 . Соответственно, в окончательном виде данный метод записывается следующим
образом:
k1 = f (xi , yi ),
k2 = f (xi + h, yi + h · k1 ),

(16)

yi+1 = yi + h · (k1 + k2 )/2.

Брук Тейлор

С помощью разложения в ряд Тэйлора можно строго показать, что такой метод имеет точность второго порядка.
Решите рассмотренную в главе задачу с помощью данного
метода. Сравните точность найденного решения с точностью метода Эйлера.
4 Стандартом де-факто приближенного решения дифференциальных уравнений является метод Рунге—Кутты
четвертого порядка, который вычисляет новое значение
искомой функции с помощью серии из нескольких предсказаний и коррекций:
k1 = f (xi , yi ),

Карл Давид Тольме Рунге

k2 = f (xi + h/2, yi + h/2 · k1 ),

k3 = f (xi + h/2, yi + h/2 · k2 ),

(17)

k4 = f (xi + h, yi + h · k3 ),

yi+1 = yi + h · (k1 + 2k2 + 2k3 + k4 )/6.

Мартин Вильгельм Кутта
12

Метод Эйлера иногда называют методом Рунге—Кутты
первого порядка точности, метод предиктор—корректор — методом Рунге—Кутты второго порядка точности.

Простой геометрической интерпретации этот метод не имеет, формулы (17) были найдены алгебраическим способом
из условия, чтобы данный метод имел четвертый порядок
точности12 .
5 Оформите метод Эйлера, метод предиктор—корректор
(см. упр. 3) и метод Рунге—Кутты 4-го порядка (см. упр. 4)
в виде функций Python, принимающих на вход: правую
часть дифференциального уравнения f (x, y); диапазон изменения переменной (x0 , x1 ); начальное значение y0 ; число
узлов сетки n. Каждая функция должна возвращать два
массива NumPy: массив координат узлов сетки и массив соответствующих приближенных значений решения дифференциального уравнения.
6 Решите с помощью какого-нибудь приближенного метода следующие начальные задачи:
1)
2)
3)
4)

y0
y0
y0
y0

= y(2 − y), y(0) = 1;
= y/x − 1, y(1) = 1;
= sin 2x − y, y(0) = 0;
= 2y 2 (x + 1), y(−1) = −1.

Табулирование функций

83

Сравните приближенное решение с аналитическим, найденным с помощью библиотеки SymPy.
7 Поставьте начальные задачи для вычисления следующих математических констант:
√ √
√ 1
3
2, 2, e, , eπ .
e
Выполните приближенные вычисления соответствующих
констант с помощью какого-нибудь из рассмотренных выше приближенных методов. Постройте графики зависимости погрешности оценки констант от величины шага h.
8 Поставьте начальную задачу, решением которой является заданная функция, и выполните табулирование этой
функции в указанном диапазоне изменения ее аргумента с
помощью одного из приближенных методов решения дифференциальных уравнений:

1 − x2 , x ∈ [0, 1];
1) e−x , x ∈ [0, 10];
3)
2)

ln(1 + x), x ∈ [0, 5];

4)

2x , x ∈ [0, 1].

9 Придумайте схему табулирования функции y −1 (x), обратной к заданной своей таблицей функции y(x). Используя эту схему, постройте таблицу значений функции Ламберта W (x), обратной к функции y(x) = xex .
10 Для табулирования функций sin и cos можно использовать начальную задачу для системы из двух обыкновенных дифференциальных уравнений13 :
y 0 = z, z 0 = −y, y(0) = 0, z(0) = 1.

(18)

Метод Эйлера обобщается для приближенного решения системы (18) тривиальным образом:
y0 = 0, z0 = 1,
yi+1 = yi + h · zi , zi+1 = zi − h · yi

(19)

i = 0, 1, . . .

Вычислите с помощью такой начальной задачи число sin 1.
11 Выполните табулирование обратных тригонометрических функций arcsin x и arccos x.
12 Придумайте способ приближенного решения дифференциального уравнения с заданным начальным условием
справа налево, т. е. в сторону уменьшения аргумента. Используйте этот способ, например, для составления таблицы функции ex (как решения начальной задачи y 0 = y,
y(0) = 1) в отрицательном диапазоне значений переменной x.

Иоганн Генрих Ламберт

13

Единственное решение этой системы y = sin x, z = cos x может
быть найдено аналитически.

Глава 7

84
13 Рассмотрим начальную задачу
y 0 = 1 + y 2 , y(0) = 0.

(20)

Хотя правая часть дифференциального уравнения не имеет никаких особенностей (не обращается в ноль, определена везде, т. е. не имеет никаких разрывов), решение данной
задачи y(x) = tg x имеет разрыв, например, при x = π/2.
Попробуйте решить эту задачу с помощью метода Эйлера
на интервале [0, 2]. Что происходит с приближенным решением при переходе через точку разрыва x = π/2?
14 Сравните время работы реализованной нами функции
erf с использованием предварительно составленной таблицы ее значений со временем работы аналогичной функции
в модуле math.

ГЛАВА

8

Ортогональные траектории
Ортогональные семейства кривых • Два од-

нопараметических семейства кривых называются взаимно ортогональными, если каждая кривая первого
семейства пересекает каждую кривую второго семейства под прямым углом1 .
Ортогональные траектории широко применяются
в разных разделах математики и физики. В прикладной математике такие семейства используются, например, в качестве криволинейных ортогональных систем
координат (рис. 1). В физике взаимно ортогональными являются, например, семейства силовых линий заданного электростатического поля и соответствующих
этому полю эквипотенциальных линий, на каждой из
которых все точки имеют одинаковое значение электростатического потенциала (рис. 2).
Обобщением эквипотенциальных линий является
понятие изолинии, в каждой точке которой измеряемая величина сохраняет одинаковое значение. Изолинии широко применяются в прикладных науках при
визуализации различных двумерных величин, например высот в топографии (рис. 3), давлений в метеорологии (изобары, рис. 4) и т. п. Важным свойством изолиний является то, что ортогональные к ним кривые
всегда показывают направление наибольшего возрастания (убывания) соответствующей величины.

Задача • Построение по заданному семейству кри-

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

1

Под углом между кривыми понимается угол между их касательными в точке пересечения.

Полярная система координат
РИС. 1

Силовые и эквипотенциальные линии (рисунок
Джеймса Максвелла)
РИС. 2

Глава 8

86

рого является это семейство; 2) преобразуем полученное уравнение в уравнение, соответствующее ортогональному семейству кривых; 3) решаем построенное
уравнение, найденное его общее решение описывает
искомое семейство ортогональных траекторий.
Если исходное семейство кривых задано «явным»
соотношением2
f (x, y) = C,
(1)
РИС. 3 Линии одинаковых высот, топографическая карта
Эльбруса

то для построения искомого дифференциального уравнения достаточно продифференцировать по переменной x обе части этого соотношения:
∂f dy
∂f
+
·
= 0.
∂x
∂y dx

(2)

Обозначим построенное таким образом дифференциальное уравнение как
Φ(x, y, y 0 ) = 0.

Изобары — линии одинакового давления
РИС. 4

2

См. упражнение 3 к главе для
общего «неявного» случая
F (x, y, C) = 0.

y(x)

P

y

ϕ

α
O

x

Связь между углами
наклона касательных к двум
взаимно ортогональным кривым y(x) и Y (x)

РИС. 5

Рассмотрим (рис. 5) теперь произвольную точку P
с координатами (x, y) и проходящую через нее интегральную кривую уравнения (3), являющуюся представителем исходного семейства кривых (1). Пусть искомая ортогональная кривая, проходящая через точку P , описывается функцией Y (x). Согласно определению угла между двумя кривыми, касательные к кривым y(x) и Y (x) в точке P перпендикулярны. Следовательно, для углов α и ϕ наклона этих касательных
к оси Ox справедливо следующее соотношение:
ϕ=

Y (x)

(3)

1
π
+ α ⇒ tg ϕ = − ctg α = −
.
2
tg α

(4)

Так как tg α = y 0 (x), а tg ϕ = Y 0 (x), то в силу (4)
производные функций y(x) и Y (x) оказываются связанными в точке x равенством
y 0 (x) = −

1
Y

0 (x)

.

(5)

Кроме того, y(x) = Y (x) в данной конкретной точке x.
Поэтому если функция y(x) удовлетворяет дифференциальному уравнению
Φ(x, y, y 0 ) = 0,

Ортогональные траектории

87

то искомая функция Y (x) должна удовлетворять уравнению


1
(6)
Φ x, Y, − 0 = 0.
Y

Заменив теперь в (6) Y на y (это всего лишь имя
функции), получим следующее правило: дифференциальное уравнение, описывающее семейство ортогональных кривых к семейству кривых, заданному дифференциальным уравнением (3), получается заменой в
этом уравнении производной y 0 на −1/y 0 :


1
0
Φ(x, y, y ) = 0
Φ x, y, − 0 = 0.
(7)
y
Общее решение построенного таким способом дифференциального уравнения, содержащее произвольный
параметр C, и будет определять искомое однопараметрическое семейство ортогональных траекторий.

Модель • В качестве примера рассмотрим задачу

построения ортогональных траекторий для семейства
степенных функций y = C1 xn при фиксированной степени n.
1 Подключаем библиотеки SymPy и NumPy и функцию
display.
2 Определяем символы x, C1 , n и функцию y(x). Задаем исходное семейство fam степенных функций.
1
2
3
4

x, C1, n = symbols("x C1 n")
y = Function("y")(x)
fam = Eq(y, C1 * x ** n)
display(fam)

y(x) = C1 xn
3 Визуализацию нашей модели рассмотрим в частном случае n = −1.
1
2

hyp = fam.subs(n, -1)
display(hyp)

y(x) =

C1
x

4 Построим семейство кривых hyp. Так как в общем
случае подобные семейства определяются в неявной

Глава 8

88

форме, то для визуализации семейства hyp будем использовать команду plot_implicit для построения графиков неявных функций. Перед построением каждого графика нужно подставить в hyp вместо C1 соответствующее числовое значение и заменить символ функции y на предварительно определенный вспомогательный символ Y.
1
2
3
4
5
6
7
8
9

Y = symbols("y")
p1 = plot(xlim = (-5, 5), ylim = (-5, 5),
size = (5, 5), show = False)
for c in np.arange(-10, 10, 0.5):
f = hyp.subs({C1: c, y: Y})
p = plot_implicit(f, (x, -5, 5), (Y, -5, 5),
show = False)
p1.extend(p)
p1.show()

Результат выполнения данного фрагмента кода показан на рис. 6.
5 Приведем исходное уравнение fam к виду 1, разрешив данное уравнение относительно символа C1.
Исходное семейство
кривых для случая n = −1
РИС.

6

1
2

csol = solveset(fam, C1).args[0]
display(Eq(csol, C1))

x−n y(x) = C1
6 Составляем дифференциальное уравнение, описывающее исходное семейство кривых. Для этого дифференцируем выражение csol по x и приравниваем
вычисленную производную к нулю (производная константы в правой части в последнем соотношении).
1
2

ode = Eq(diff(csol, x), 0)
display(ode)



3

Попробуйте решить
уравнение без подсказок!

nx−n y(x)
d
+ x−n y(x) = 0
x
dx

7 Решим полученное дифференциальное уравнение,
чтобы убедиться, что оно в самом деле описывает исходное семейство кривых. Для решения используем
подсказку separable3 .

данное
1
2

dsol = dsolve(ode, y, hint = "separable")
display(dsol)

Ортогональные траектории

89

y(x) = C1 en log (x)
8 После упрощения полученного соотношения с помощью команды logcombine, как и предполагалось, получаем исходное уравнение fam.
1
2

dsol = logcombine(dsol, force = True)
display(dsol)

y(x) = C1 xn
9 По формуле (7) преобразуем уравнение ode к уравнению ode2, описывающему искомое ортогональное семейство кривых.
1
2

ode2 = ode.subs(diff(y, x), -1 / diff(y, x))
display(ode2)



x−n
nx−n y(x)
− d
=0
x
dx y(x)

10 Упростим полученное уравнение. Для этого сначала разрешим его относительно производной.
1
2

sol = solveset(ode2, diff(y, x))
display(sol)





x
ny(x)



\ {0}

11 Извлечем из полученного выражения собственно
решение — выражение в фигурных скобках слева4 .
1
2

rhs2 = sol.args[0].args[0]
display(rhs2)



x
ny(x)

12 Переопределяем уравнение ode2, приравнивая к
производной diff(y,x) найденное выражение rhs2.
1
2

ode2 = Eq(diff(y, x), rhs2)
display(ode2)

4

Выражение справа задает область допустимых значений для
данного уравнения, т. е. в нашем
случае y 0 6= 0.

Глава 8

90
d
x
y(x) = −
dx
ny(x)

13 Решаем дифференциальное уравнение ode2. Чтобы получить ответ в виде одного неявного выражения, используем для решения комбинацию опций hint
и simplify.
1
2
3

dsol2 = dsolve(ode2, y, hint = "separable",
simplify = False)
display(dsol2)

x2
y 2 (x)
= C1 −
2
2n

Семейство искомых ортогональных траекторий для
случая n = −1
РИС. 7

14 По описанной выше схеме строим график найденного семейства кривых dsol2 для частного случая
n = −1 (см. рис. 7).

1
2
3
4
5
6
7
8
9

p2 = plot(xlim = (-5, 5), ylim = (-5, 5),
size = (5, 5), show = False)
for c in np.arange(-10, 10, 0.5):
f = dsol2.subs({C1: c, y: Y, n: -1})
p = plot_implicit(f, (x, -5, 5), (Y, -5, 5),
line_color = "red",
show = False)
p2.extend(p)
p2.show()

15 Последним действием объединяем оба семейства
кривых на одном графике.
1
2
3
4
5

p3 = plot(xlim = (-5, 5), ylim = (-5, 5),
size = (5, 5), show = False)
p3.extend(p1)
p3.extend(p2)
p3.show()

Окончательный результат показан на рис. 8.
Два семейства взаимно ортогональных траекторий для случая n = −1
РИС. 8

УПРАЖНЕНИЯ И ЗАМЕЧАНИЯ
1 Постройте графики семейств взаимно ортогональных
траекторий в рассмотренной в главе модели для следующих частных случаев:
1)
2)

n = 1;
n = 2;

3)
4)

n = 1/3;
n = −2.

Ортогональные траектории

91

2 Постройте с помощью SymPy дифференциальные уравнения, описывающие заданные семейства кривых. Для разрешения уравнения относительно константы используйте
команду solve, если команда solveset не работает.
1)
2)

3)
4)

y = C · ln x;
y = eCx ;

y = sin(x − C);
y 2 + Cx2 = C 2 .

3 В более общем случае, когда семейство кривых задается в виде неявного относительно параметра C соотношения
F (x, y, C) = 0, это соотношение надо также продифференцировать по x, после чего из полученной системы
F (x, y, C) = 0,
∂F
∂F dy
+
·
=0
∂x
∂y dx

(8)

алгебраически исключить C. Найденное таким образом соотношение будет включать в себя x, y и y 0 , т. е. будет искомым дифференциальным уравнением. Постройте по такой
схеме дифференциальные уравнения, описывающие следующие семейства кривых:
1)
2)

y = Cx + C 2 ;
y = log(x − C) + C;

3)
4)

Cy = eCx ;
sin(Cy) + Cx = 0.

4 Постройте ортогональные траектории для следующих
семейств кривых:
1)
2)

x2 + (y − C)2 = C 2 ;
x − y = Ce ;
x

3)

cos y = C sin x;

4)

sin y = Cex .

2

Если в решении дифференциального уравнения появляются логарифмы, то перед построением графиков этого решения замените каждое выражение под логарифмом на
его модуль с помощью метода replace, как это сделано в
следующем примере:
1
2
3

e1 = log(x - 1) + log(x + 1)
e2 = e1.replace(log, lambda e: log(abs(e)))
display(e1, e2)
Ортогональные траектории для семейства кривых
y = 1/ sin x + C
РИС. 9

log (x − 1) + log (x + 1)

log (|x − 1|) + log (|x + 1|)

5 Для заданной функции f (x) постройте5 семейство ортогональных траекторий для семейства кривых, заданных
соотношением y = f (x) + C (см. рис. 9):
1)
2)

f (x) = x3 ;
f (x) = cos x;

3)
4)

f (x) = 1/(1 + x2 );
f (x) = 1/ sin x.

5

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

Глава 8

92

6 Для заданной функции f (x) постройте семейство ортогональных траекторий семейства кривых, заданного соотношением y = Cf (x) (см. рис. 10):
1)
2)

3)
4)

f (x) = ln x;
f (x) = 1 + 1/x;

f (x) = sin x;
f (x) = tg x.

7 Для заданной функции f (x) постройте семейство ортогональных траекторий семейства кривых, заданного соотношением y = f (x − C):
РИС. 10

1)
2)

6

8 Для заданной6 функции f (x) постройте семейство ортогональных траекторий семейства кривых, заданного соотношением y = f (Cx):

Ортогональные траектории для семейства кривых
y = C tg x
Функция arctg x в SymPy реализована командой atan(x), обратный
гиперболический косинус (ареакосинус) arch x — командой acosh(x).

1)
2)

3)
4)

f (x) = sin x;
f (x) = 1/x;

3)
4)

f (x) = 1/(1 + x);

f (x) = x + 1;

f (x) = log x;
f (x) = arccos x.

f (x) = arctg x;
f (x) = arch x.

9 Чтобы получить ортогональные траектории семейства
кривых, заданных дифференциальным уравнением
F (ϕ, r, r0 ) = 0

в полярной системе координат, нужно заменить в этом
уравнении производную r0 на выражение −r2 /r0 :

r(ϕ)

rdϕ

F (ϕ, r, r0 ) = 0
α
dr

P


ϕ

O
РИС. 11 Геометрический смысл

производной r0 (ϕ) в полярной
системе координат
7

(9)

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

F (ϕ, r, −r2 /r0 ) = 0.

(10)

Это обусловлено тем, что в полярных координатах тангенс
угла α наклона касательной к кривой r = r(ϕ) к радиусу в
данной точке равен r/r0 (см. рис. 11). При переходе к ортогональной траектории угол α меняется на α+π/2, поэтому
tg α меняется на −1/ tg α, т. е. r/r0 → −r0 /r, что и дает
указанную выше замену. Используя это правило, постройте ортогональные траектории следующих семейств кривых
в полярной системе координат (ϕ > 0):
1)
2)

r = C sin2 ϕ;
r = C cos ϕ;

3)
4)

r = Cϕ2 ;
r = C tg ϕ.

10 Кривая, которая пересекает заданное однопараметрическое семейство кривых под одним и тем же углом, называется изогональной траекторией данного семейства кривых. Метод построения семейства изогональных траекторий аналогичен построению семейств ортогональных траекторий. Сначала составляем дифференциальное уравнение, общим решением которого является исходное семейство кривых7 :
y 0 = f (x, y).
(11)

Ортогональные траектории

93

Пусть угол, под которым изогональные траектории должны пересекать наше семейство, равен γ, по определению,
это угол между соответствующими касательными. Выберем на одной из исходных кривых произвольную точку P
и рассмотрим 4ABP (рис. 12), образованный осью Ox и
двумя касательными: P A — к исходной кривой, угол ее
наклона равен α, P B — к искомой изогональной кривой,
угол ее наклона равен β. Так как β — внешний угол треугольника, то β = α + γ. Следовательно, tg β = tg(α + γ).
Применяем формулу тангенса суммы:
tg α + tg γ
.
1 − tg α tg γ

(12)

f (x, y) + k
,
1 − k · f (x, y)

(13)

tg β =

Тангенс угла β равен производной y 0 искомой функции,
описывающей изогональную траекторию, тангенс угла α
равен производной исходной функции, которая согласно
уравнению (11) равна f (x, y). Подставляя эти выражения
в (12), получаем следующее соотношение:
y0 =

γ
P

β

α
A

B

Соотношение между
углами α и β наклона двух
кривых к оси Ox и углом γ
между этими кривыми
РИС. 12

где k = tg γ. Так как соотношение (13) должно выполняться для всех точек всех кривых исходного семейства, то оно
и является искомым дифференциальным уравнением для
изогональных траекторий заданного семейства кривых.
11 Для следующих семейств кривых постройте их изогональные траектории с заданным значением параметра k:
1)
2)
3)
4)

y = x + C, k = 0.5;
y = x2 + C, k = 2;
y 2 = 4Cx, k = 1;
x2 + 2xy − y 2 = C 2 , k = 1.

12 Для построения семейств кривых, заданных неявным
соотношением F (x, y) = C, в некоторых случаях оказывается намного быстрее и удобнее использовать команду
contour из библиотеки Matplotlib, предназначенную для
построения линий уровня заданной функции двух переменных, а не команду plot_implicit из библиотеки SymPy.
Например, пусть имеется выражение8 SymPy (см. код ниже,
строка 2)
F (x, y) = sin(x + y) cos(y − x).
(14)
Необходимо построить семейство кривых вида
F (x, y) = C

(15)

для некоторого заданного набора значений параметра C.
Преобразуем выражение F в функцию f с помощью команды lambdify (строка 3). Создаем сетку в области построения графиков командой meshgrid библиотеки NumPy

8

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

94

Глава 8

(строки 4–6). Строим линии уровня функции f, используя
команду contour. Первые два ее аргумента задают сетку,
третий аргумент — сеточную функцию (по сути, матрицу значений рассматриваемого выражения в узлах сетки),
четвертый аргумент определяет список уровней (значений
параметра C), которые мы хотим визуализировать.
1
2
3
4
5
6
7

РИС. 13 Линии

уровня, построенные с помощью библиотеки
Matplotlib

8
9

x, y = symbols("x y")
F = sin(x + y) * cos(y - x)
f = lambdify([x, y], F)
xrange = np.arange(-3.0, 3.0, 0.01)
yrange = np.arange(-3.0, 3.0, 0.01)
X, Y = np.meshgrid(xrange, yrange)
plt.contour(X, Y, f(X, Y), np.arange(-1, 1, 0.2))
plt.gca().set_aspect(’equal’)
plt.show()
Результат выполнения этого фрагмента кода показан на
рис. 13. Заметим, что команда contour, в отличие от команды plot_implicit, имеет много опций, управляющих
внешним видом рисуемых ею кривых.

ГЛАВА 9
Математическая вышивка
Задача • Рассмотрим следующую геометрическую

задачу (см. рис. 1): найти кривую, касательная к которой в произвольной ее точке P пересекает координатные оси в таких точках A(a, 0) и B(0, b), что сумма
координат a и b является заданной постоянной величиной k:
a + b = k.
(1)

y
B
b

Уравнение касательной к кривой y = y(x) в точке
x = x0 , как известно, выглядит следующим образом:
y = y 0 (x0 ) · (x − x0 ) + y(x0 ).

(2)

Подставляя в эту формулу y = 0 и x = 0, найдем
координаты пересечения касательной с осями Ox и Oy
соответственно:
a = x0 −

y(x0 )
, b = y(x0 ) − x0 · y 0 (x0 ).
y 0 (x0 )

(3)

Заменяем в последних двух формулах x0 , y(x0 ) и y 0 (x0 )
на x, y и y 0 соответственно и подставляем полученные
выражения для a и b в условие задачи (1):
x − y/y 0 + y − y 0 · x = k.
| {z } | {z }
a

(4)

b

Выражаем в последнем равенстве y через x и y 0 и после
очевидных алгебраических преобразований получаем
так называемое дифференциальное уравнение Клеро:
y = xy 0 +

ky 0
.
−1

y0

(5)

O

P

a

A

x

Задача о постоянной
сумме координат пересечения
касательной с осями Ox и Oy
РИС. 1

Глава 9

96

Уравнение Клеро • Уравнением Клеро называется нелинейное дифференциальное уравнение вида
y = xy 0 + ϕ(y 0 ).

(6)

Уравнение Клеро, как и некоторые другие типы уравнений первого порядка, неразрешенные относительно
производной, решается методом введения параметра.
Обозначим y 0 = p, тогда
y = px + ϕ(p).

(7)

Продифференцируем последнее соотношение, учитывая, что p — это функция от x:
Алекси Клод Клеро

y 0 = p0 x + p + ϕ0 (p).

(8)

Заменяя слева y 0 на p, получаем после упрощения, что
p0 · (x + ϕ0 (p)) = 0.

(9)

Это значит, что либо p0 = 0, либо x = ϕ0 (p). В первом
случае p = C, подставляем это выражение в (7), это
дает нам общее решение уравнения 6:
y = Cx + ϕ(C).

(10)

То есть интегральными кривыми, соответствующими
общему решению уравнения Клеро, является семейство прямых линий.
Во втором случае x = −ϕ0 (p), подставляем это выражение в (7) и находим еще одно решение уравнения
Клеро уже в параметрической форме:
x = −ϕ0 (p),
y = −p · ϕ0 (p) + ϕ(p).

(11)

Последнее решение называется особым (или сингулярным). Особое решение не получается из общего решения того же дифференциального уравнения ни при
каких значениях константы C. Кроме того, особое решение само не содержит никаких констант, т. е. задает
только одну интегральную кривую.
Таким образом, полученное нами выше дифференциальное уравнение (5) является уравнением Клеро, в
котором ϕ(p) = kp/(p − 1).

Математическая вышивка

97

Модель • Решим поставленную задачу с помощью

библиотеки SymPy. Чтобы иметь возможность обобщить
применяемый подход к решению других аналогичных
задач, начнем процесс построения модели с вывода
дифференциального уравнения.
1 Подключаем библиотеки SymPy и NumPy и команду
display.
2 Определяем символ x и функцию y(x), которые мы
будем использовать для построения дифференциального уравнения.
3 Чтобы корректно задать уравнение касательной к
кривой y(x) в точке x, нам потребуется еще одна пара символов X и Y , с помощью которых перепишем
уравнение (2):

Y = y 0 (x)(X − x) + y(x).

(12)

Вводим новые символы и записываем уравнение касательной в SymPy.
1
2
3

X, Y = symbols("X Y")
tangent = Eq(Y, diff(y, x) * (X - x) + y)
display(tangent)
d
Y = (X − x) dx
y(x) + y(x)

4 Находим пересечение касательной с осью Ox, подставляя в уравнение касательной вместо Y ноль и решая полученное уравнение относительно X.
1
2

a = solveset(tangent.subs(Y, 0), X).args[0]
display(a)

x−

y(x)
d
dx y(x)

5 Выполняем аналогичные действия1 для нахожде-

ния точки пересечения с осью Oy.
1
2

b = solveset(tangent.subs(X, 0), Y).args[0]
display(b)

−x

d
y(x) + y(x)
dx

6 Вводим символ k и составляем дифференциальное
уравнение, используя соотношение (1).

1

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

Глава 9

98
1
2
3

k = symbols("k")
ode = Eq(a + b, k)
display(ode)

−x

d
y(x)
y(x) + x + y(x) − d
=k
dx
dx y(x)

7 Так как мы не собираемся решать полученное дифференциальное уравнение стандартным образом (с помощью команды dsolve), то сразу заменим в нем производную на параметр p, а уже потом перейдем к приведению этого уравнения к форме, стандартной для
уравнения Клеро.
1
2
3

p = symbols("p")
ode2 = ode.subs(diff(y, x), p)
display(ode2)

−px + x + y(x) −

1
2
3

y(x)
=k
p

8 Разрешаем уравнение относительно y(x).
rhs = solveset(ode2, y).args[0]
ode3 = Eq(y, rhs)
display(ode3)

y(x) =

p (k + px − x)
p−1

9 Проверяем, является ли это уравнение уравнением
Клеро. Для этого правая часть rhs должна линейно
зависеть от x, причем коэффициент перед x должен
быть равным p. Чтобы в этом убедиться, вычислим
производную по x правой части уравнения и сравним
ее с p.
1
2
3
4

if simplify(diff(rhs, x)) == p:
print("Clairaut’s equation")
else:
print("Not the Clairaut’s equation")
Clairaut’s equation
10 Подставим в правую часть rhs вместо символа
x ноль, полученное таким образом выражение будет

функцией ϕ(p).
1
2

phi = rhs.subs(x, 0)
display(phi)

Математическая вышивка

99

kp
p−1
11 Теперь мы можем записать наше уравнение в форме, стандартной для уравнения Клеро2 . Заметим, что
с точностью до порядка слагаемых полученное уравнение совпадает с выведенным нами аналитически уравнением (5).
1
2
3

y_ = symbols("y’")
ode4 = Eq(y, p*x+phi).subs(p, y_)
display(ode4)

y(x) =

ky 0
+ xy 0
y0 − 1

12 Выражение ode3 (см. пункт 8), как следует из описанной выше схемы решения уравнений Клеро, является его общим решением, в котором роль константы
интегрирования играет параметр p. Так как правая
часть rhs этого решения является линейной по x, то
интегральными кривыми общего решения будут прямые линии. Построим семейство этих линий. Для этого предварительно выразим параметр p через y0 — координату точки пересечения соответствующей прямой
с осью Oy. То есть фактически найдем решение, удовлетворяющее начальному условию y(0) = y0 . Сохраним найденное решение начальной задачи в переменной dsol.
1
2
3
4
5
6

y0 = symbols("y0")
sol = solveset(ode3.subs({x: 0, y: y0}), p)
display(sol)
p1 = sol.args[0].args[0]
dsol = ode3.subs(p, p1)
display(dsol)



y0
−k + y0



\ {1}


xy0
y0 k + −k+y

x
0


y(x) =
y0
(−k + y0 ) −k+y0 − 1

13 Построим семейство кривых общего решения dsol
для частного случая k = 1 (рис. 2).

2

Этот шаг не является обязательным, т. к. дальше мы будем работать исключительно с выражением phi.

Глава 9

100

1
2
3
4
5
6
7
8

pl1 = plot(xlim = (-1, 3), ylim = (-1, 3),
aspect_ratio = (1, 1), show = False)
for y_0 in np.arange(-6, 6, 0.1):
ds = dsol.subs({y0: y_0, k: 1})
pl = plot(ds.rhs, (x, -1, 3),
line_color = "lightgray", show = False)
pl1.extend(pl)
pl1.show()

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

Общее решение уравнения Клеро (5)
РИС. 2

14 Найдем особое решение нашего уравнения Клеро
согласно формулам (11).
1
2
3

solx = simplify(-diff(phi, p))
soly = simplify(p * solx + phi)
display(solx, soly)

k
(p − 1)

2

(p − 1)

2

kp2

Особое решение уравнения Клеро (5)

РИС. 3

15 Строим график особого решения в параметрическом виде (рис. 3).
1
2
3
4
5
6
7
8

pl2 = plot(xlim = (-1, 3), ylim = (-1, 3),
aspect_ratio = (1, 1), show = False)
sx, sy = solx.subs(k, 1), soly.subs(k, 1)
pl = plot_parametric((sx, sy), (p, -100, 100),
line_color = "red",
show = False)
pl2.extend(pl)
pl2.show()
16 Последним действием объединяем графики общего решения (семейство прямых серого цвета) и график
особого решения (кривая красного цвета) в общий график. Для большей выразительности построим график
без координатных осей, используя опцию axis = False
команды plot.

Общее и особое решения уравнения (5)
РИС. 4

1
2
3
4

pl3 = plot(xlim = (-1, 3), ylim = (-1, 3),
aspect_ratio = (1, 1), axis = False,
show = False)
pl3.extend(pl1)

Математическая вышивка

5
6

101

pl3.extend(pl2)
pl3.show()

Окончательный результат решения поставленной задачи показан на рис. 4. Заметим, что описанные в
пункте 13 действия (см. рис. 2), связанные с построением семейства прямых, соответствующих общему решению уравнения Клеро, легко выполнить руками, например, с помощью карандаша и линейки. Для этого
надо соединить каждую точку на оси Ox с целочисленной координатой x ∈ [1 . . . k] с точкой на оси Oy с
координатой y = k − x (рис. 5). Даже для небольших
значений k огибающая семейства построенных таким
образом отрезков выглядит достаточно «криволинейно». Такой прием широко применяется в специальной
технике вышивания, называемой математической вышивкой (англ. string art), например для заполнения
различных углов (рис. 6).

Основной прием математического вышивания
РИС. 5

УПРАЖНЕНИЯ И ЗАМЕЧАНИЯ
1 Постройте на одном графике семейство кривых особого
решения (11) уравнения (5) для разных значений параметра k ∈ [−4, 4].

РИС. 6

шивка

Математическая вы-

2 Проверьте с помощью SymPy, что любая прямая из найденного нами общего решения уравнения Клеро удовлетворяет условию задачи a+b = k, вычислив точки пересечения
этой прямой с осями координат.
3 Рассмотрите решение более общей задачи, в которой
касательная к кривой пересекается с двумя прямыми, описываемыми уравнениями y = nx и y = mx. Сумма расстояний от точек пересечения до начала координат должна
быть постоянной и равной k. Учтите, что расстояние a от
точки с координатой x = x0 , лежащей на прямой y = nx,
до начала координат равно
p
a = x0 1 + n2 .
(13)

Покажите, что и в такой конфигурации огибающей семейства прямых также будет парабола (рис. 7).

4 Постройте дифференциальное уравнение, общим решением которого является заданное семейство прямых линий:
1) y = k2 x + 2k;
3) y = e−k x + k2 ;
2) y = (k − 1)x − k3 ;
4) y = sin k · x + cos k.

Огибающая семейства
отрезков, построенных на
прямых y = nx и y = mx
РИС. 7

Глава 9

102

5 Найдите с помощью SymPy общее и особое решения следующих уравнений Клеро:
1)
2)

3

Если выражение в самом деле
линейно, в противном случае генерируется исключение.

1
2
3

y = xy 0 + y 02 ;
y = xy 0 + ln y 0 ;

3)
4)

y = xy 0 − sin y 0 ;
y = xy 0 + (1 + y 02 ).

6 Действия, которые мы выполняли в пунктах 9 и 10 для
извлечения коэффициентов линейного выражения, можно выполнить проще, используя команду linear_coeffs,
определенную в модуле sympy.solvers.solveset. Первым
аргументом этой команде передается выражение, которое
мы хотим представить в виде линейной функции от символов, перечисленных последующими аргументами. Команда возвращает список коэффициентов этого линейного выражения комбинации3 в том порядке, в котором перечислены символы. Последним элементом списка идет свободный
коэффициент, не зависящий ни от одного из этих символов.
Например, для выражения rhs эта команда выдаст следующий результат:
from sympy.solvers.solveset import linear_coeffs
coeffs = linear_coeffs(rhs, x)
display(coeffs)
[p, k*p/(p - 1)]
Видно, что коэффициент при x в данном случае равен p
(что соответствует уравнению Клеро), а свободный коэффициент (т. е. функция ϕ(p)) равен kp/(p − 1).

y

7 Напишите две функции для работы с дифференциальными уравнениями Клеро. Первая функция должна проверять, является ли заданное уравнение уравнением Клеро относительно заданной неизвестной функции, и возвращать в качестве результата функцию ϕ(p), если проверка
оказалась положительной, и значение None в противном
случае. Вторая функция должна выдавать общее и особое
решения уравнения Клеро, заданного функцией ϕ(p).

Q(cos 2α, sin 2α)

8 Пусть семейство прямых линий задано соотношением

P (cos α, sin α)

y = A(p)x + B(p).
α
α

O

1

x

Правило построения
хорды P Q для случая n = 2
РИС. 8

(14)

Для построения огибающей этого семейства не нужно каждый раз составлять уравнение Клеро и искать его особое
решение. Эти выкладки можно сделать один раз и получить формулы огибающей в параметрическом виде, выраженные через функции A(p) и B(p):
x=−

B 0 (p)
B 0 (p)A(p)
, y=−
+ B(p).
0
A (p)
A0 (p)

(15)

Примените эти формулы для построения огибающих семейств прямых из упражнения 4.

Математическая вышивка

103

9 Рассмотрим задачу построения огибающей семейства
хорд единичной окружности, соединяющих точки с полярными углами α и nα, где n — фиксированный параметр
(рис. 8). Координаты указанных точек равны
P (cos α, sin α) и Q(cos nα, sin nα).

(16)

Записываем уравнение прямой, проходящей через эти точки:
x − cos α
y − sin α
=
.
(17)
cos 2α − cos α
sin 2α − sin α
Разрешаем последнее равенство относительно y и приходим к уравнению семейства прямых (по параметру α)
y=

sin 2α − sin α
sin α
·x−
.
cos 2α − cos α
cos 2α − cos α

(18)

Примените к построенному семейству формулы (15) для
получения его огибающей. Постройте графики семейств
хорд и соответствующих им огибающих. Если n > 0, то
огибающей будет эпициклоида — кривая, образуемая фиксированной точкой окружности радиуса r, катящейся без
скольжения по внешней стороне другой окружности радиуса R = (n − 1)r. При n = 2 огибающая называется
кардиоидой, при n = 3 — нефроидой. Несколько примеров
огибающих-эпициклоид показано на рис. 9.
10 Если в предыдущем упражнении рассмотреть случай
отрицательных значений параметра n, то получим в качестве огибающих так называемые гипоциклоиды. Гипоциклоида — это кривая, образуемая фиксированной точкой окружности, катящейся без скольжения по внутренней стороне другой окружности. Огибающие в форме гипоциклоид оказываются расположенными вне единичной
окружности, на которой они строятся описанным методом,
поэтому для их визуализации надостроить не хорды, а
прямые, содержащие эти хорды (рис. 10).

Огибающие в форме
эпициклоид
РИС. 9

Огибающие в форме
гипоциклоид
РИС. 10

11 Огибающую семейства кривых F (x, y, C) = 0, если
она существует, можно найти исключением параметра C
из алгебраической системы
F (x, y, C) = 0, FC0 (x, y, C) = 0.

(19)

Примените этот метод к построению огибающих следующих семейств кривых:
1)
2)
3)
4)

(x − C)2
;
4
2
(x − C) + (y − C)2 = 1;

y =x−

y 2 = 2Cx − C 2 ;
x2
y2
+
= 1 (см. рис. 11).
C
1−C

Пример построения
огибающих семейства кривых
РИС. 11

Глава 9

104
12 Дифференциальное уравнение вида
y = xϕ(y 0 ) + ψ(y 0 )

(20)

называется уравнением Лагранжа. Решается это уравнение, как и уравнение Клеро, введением параметра y 0 = p,
тогда
y = xϕ(p) + ψ(p).
(21)
Дифференцируем это выражение по x, считая, что p —
функция от x, и заменяем y 0 слева на p4 :
p = ϕ(p) + xϕ0 p0 + ψ 0 p0 ⇒ p − ϕ(p) = (xϕ0 + ψ 0 )p0 .

Жозеф Луи Лагранж
4

Производные ϕ0 и ψ 0 вычисляются по p, а p0 — по x.

(22)

Если p0 — корень левой части p − ϕ(p) = 0, то p = p0
является решением последнего уравнения. Это дает нам
(возможно, особое) решение исходного уравнения
y = xϕ(p0 ) + ψ(p0 ).

(23)

Если p 6= ϕ(p), то переходим в (22) к обратной функции
x(p), заменяя dp/dx на 1/(dx/dp):
(p − ϕ(p))x0 = xϕ0 + ψ 0 .

(24)

Получили линейное дифференциальное уравнение первого порядка относительно x(p). Пусть его общее решение
равно x(p) = F (p, C). Подставляем это выражение в (21)
и находим, что y(p) = F (p, C)ϕ(p) + ψ(p). Вместе последняя пара функций определяет общее решение уравнения
Лагранжа в параметрической форме:
x = F (p, C), y = F (p, C)ϕ(p) + ψ(p).

(25)

Решите с помощью SymPy по описанной схеме следующие
дифференциальные уравнения и постройте графики всех
их решений:
1)
2)

y + xy 0 + y 02 = 0;
2yy 0 + 4y = xy 02 ;

3)
4)

y = xy 0 (y 0 + 2);
y = xy 02 + y 02 .

ГЛАВА

10

Криволинейные зеркала
Задача • Рассмотрим следующую задачу: требует-

ся найти кривую, после отражения от которой заданный пучок параллельных лучей сходится (фокусируется) в одной общей точке, называемой точкой фокуса. Пусть исходный пучок состоит из вертикальных
лучей. Введем систему координат, ось Oy которой направим против направления лучей, тогда лучи будут
падать на искомую кривую сверху вниз. Будем считать, что точка фокуса находится в начале координат,
т. е после отражения все лучи должны пройти через
точку O (рис. 1).
Пусть искомая кривая задана функцией y(x). Рассмотрим траекторию выделенного луча M AO с абсциссой x (рис. 2). Его отражение происходит в точке
A(x, y). По закону отражения угол падения ∠M AN
равен углу отражения ∠OAB. Следовательно,

x

O

Фокусирование отраженных от кривой лучей в начале координат
РИС. 1

M

(1)

Далее воспользуемся векторным способом. Условие (1) означает, что угол между векторами OA и BA
равен углу между векторами BA и CA. Координаты вектора OA находятся элементарно: OA = (x, y).
Далее заметим, что прямая BA является касательной к y(x), поэтому вектор BA должен быть сонаправлен вектору δ = (dx, dy) (см. рис. 2). Следовательно, при сравнении углов между векторами мы можем заменить вектор BA на вектор δ. А вместо вектора CA возьмем сонаправленный с ним единичный
вектор e = (0, 1).
Теперь применим формулу для косинуса угла меж-

N
dy

∠OAB = ∠M AN = ∠BAC.

y

A

y

dx

C
O

B

x

РИС. 2 Отражение луча M A от
искомой кривой

Глава 10

106
ду двумя векторами:

e·δ
OA · δ
=
.
(2)
|δ|
|δ|
|OA| · @
@ |e| · @
@
p
Длина вектора OA равна x2 + y 2 , вектор e единичный. Скалярные произведения вычислим, используя
известные координаты векторов:
OA · δ = xdx + ydy, e · δ = dy.
Подставляя все эти соотношения в (2), получаем дифференциальное уравнение, решение которого должно
описывать искомую кривую:
xdx + ydy
p
= dy.
x2 + y 2

Модель • Решим поставленную задачу в SymPy. Проверим найденное решение с помощью кода, который
строит семейство отраженных лучей от заданной кривой. Дополнительно рассмотрим понятие каустики отражения как огибающей кривой семейства прямых линий, представляющих лучи света.

ϕ
α

Вычисление угла наклона отраженного луча
РИС. 3

(3)

1 Подключаем стандартный набор библиотек.
2 Сначала выведем уравнение семейства лучей, отраженных от некоторой кривой. Для этого рассмотрим плоскость OXY , на этой плоскости — кривую
Y = y(X). Выберем на кривой точку с координатами (x, y(x)) и рассмотрим луч, падающий в эту точку
вертикально сверху вниз. Отражение луча происходит
относительно касательной к данной кривой в рассматриваемой точке. Пусть угол наклона касательной к оси
OX равен α, т. е.

k = tg α = y 0 (x).

(4)

Несложно показать (см. рис. 3), что отраженный от
такой касательной луч будет наклонен к оси OX под
углом ϕ = 90◦ + 2α, тогда
tg ϕ = tg(90◦ + 2α) = − ctg 2α =
=

k2 − 1
tg2 α − 1
=
. (5)
2 tg α
2k

Криволинейные зеркала

107

Таким образом, прямая, содержащая отраженный луч,
наклонена к OX под углом ϕ и проходит через точку
с координатами (x, y(x)), следовательно, ее уравнение
имеет вид
Y = tg ϕ · (X − x) + y(x) = Ax + B,

(6)

где

k2 − 1
, B = y(x) − Ax.
(7)
2k
Так как k зависит от x, то уравнение (6) представляет собой искомое параметрическое описание семейства
отраженных лучей, в котором роль параметра играет символ x. Повторим теперь эти выкладки в SymPy.
Определяем символы X и Y для описания семейства
прямых, параметр x и функцию y(x).
A = tg ϕ =

1
2

X, Y, x = symbols("X Y x")
y = Function("y")(x)
3 Вычисляем производную k = y 0 (x) и задаем коэф-

фициенты A и B согласно формулам (7).
1
2
3
4

k = diff(y, x)
A = (k ** 2 - 1) / (2 * k)
B = simplify(y - x * A)
display(A, B)

2
d

dx y(x)
d
2 dx y(x)
d
x dx
y(x)



+

2

1
x
+ y(x)
d
2 dx
y(x)

4 Записываем уравнение семейства отраженных лучей (6).
1
2

fam = Eq(Y, A * X + B)
display(fam)

X
Y =



2
d
dx y(x)
d
2 dx
y(x)


−1



d
x dx
y(x)
x
+ d
+ y(x)
2
2 dx y(x)

5 Для демонстрации полученных формул построим
семейство лучей, отраженных от нижней единичной
полуокружности. Задаем уравнение f1 такой кривой и
подставляем его вместо f в уравнение fam. Метод doit
применяется в данном случае для того, чтобы SymPy
вычислил все производные.

Глава 10

108

1
2
3

f1 = -sqrt(1 - x ** 2)
fam1 = simplify(fam.subs(y, f1).doit())
display(fam1)

Y =

Семейство лучей, отраженных от полуокружности

2Xx2 − X − x

2x 1 − x2

6 Строим график полученного семейства fam1 и поверх него исходную полуокружность (рис. 4).

РИС. 4

1
2
3
4
5
6
7
8
9

p1 = plot(xlim = (-1, 1), ylim = (-1, 0),
aspect_ratio = (1, 1), show = False)
for x0 in np.arange(-0.98, 1, 0.02):
l = fam1.rhs.subs(x, x0)
p = plot(l, (X, -1, 1), line_color = "gray",
show = False)
p1.extend(p)
p1.extend(plot(f1, (x, -1, 1), show = False))
p1.show()

Внутри окружности отчетливо различима кривая, образованная сгущением прямых линий. Подобные кривые в оптике называются каустиками. Каустика — это
особая линия или поверхность, вблизи которой резко возрастает интенсивность освещения за счет отражения или преломления потока световых лучей некоторой поверхностью или телом (рис. 5). Математически каустика представляет собой огибающую семейства прямых, поэтому мы можем использовать результаты предыдущей главы для нахождения уравнения
каустики в нашем случае.

РИС. 5

7 Так как у нас уже имеется уравнение семейства
прямых, то для построения их огибающей воспользуемся формулами (15) из упражнения 8 предыдущей
главы. Заменим в выражениях A и B символ функции
f на уравнение полуокружности f1 и вычислим согласно указанным формулам параметрическое уравнение
искомой огибающей1 .

Каустика отражения

1

Заметим, что в качестве параметра сейчас выступает символ x.
1
2
3
4
5

A1 = A.subs(y, f1).doit()
B1 = B.subs(y, f1).doit()
XC = simplify(- diff(B1, x) / diff(A1, x))
YC = simplify(A1 * XC + B1)
display(XC, YC)

x3



1
− 1 − x2 x2 +
2

Криволинейные зеркала

109

8 Добавим найденную каустику (XC, YC) к построенному ранее графику p1 (рис. 6).
1
2
3
4
5
6
7
8

p2 = plot(xlim = (-1, 1), ylim = (-1, 0),
aspect_ratio = (1, 1), show = False)
p = plot_parametric((XC, YC), (x, -1, 1),
line_color = "red",
show = False)
p2.extend(p1)
p2.extend(p)
p2.show()

Отметим, что найденная нами параметрическая кривая называется нефроидой и представляет собой частный случай эпициклоиды2 .
9 Вернемся теперь к основной задаче — нахождению
кривой y(x), при отражении от которой все лучи фокусируются в одной общей точке. Будем следовать описанной выше схеме. Определяем символы для дифференциалов3 dx и dy. Строим три вектора OA, δ и e,
используя кортежи SymPy.
1
2
3
4
5

dx, dy = symbols("dx dy")
OA = Tuple(x, y)
delta = Tuple(dx, dy)
e = Tuple(0, 1)
display(OA, delta, e)

(x, y(x)) (dx, dy) (0, 1)

1
2
3

10 Определяем функцию для скалярного произведения двух двумерных векторов. Тестируем ее на вычислении произведения OA · δ.
def dot(u, v):
return u[0] * v[0] + u[1] * v[1]
display(dot(OA, delta))

dx x + dy y(x)
11 Определяем функцию вычисления длины заданного вектора, тестируем ее на векторе OA.
1
2
3

def length(u):
return sqrt(dot(u, u))
display(length(OA))

p

x2 + y 2 (x)

12 Составляем искомое дифференциальное уравнение, используя равенство (2).

Каустика отражения
для полуокружности
РИС. 6

2

См. упражнение 9 предыдущей
главы.

3

Библиотека SymPy не умеет работать с дифференциалами, поэтому нам придется вводить их
вручную просто как символы.

Глава 10

110

1
2
3

ode = Eq(dot(OA, delta) / length(OA),
dot(e, delta) / length(e))
display(ode)

dx x + dy y(x)
p
= dy
x2 + y 2 (x)
13 Для перехода от уравнения в дифференциалах к
уравнению с производной формально нужно выполнить деление всего уравнения на dx и заменить отношение дифференциалов dy/dx на производную y 0 .
Однако того же результата можно добиться простой
заменой dx на 1, а dy — на y 0 .
1
2

ode1 = ode.subs({dx: 1, dy: diff(y, x)})
display(ode1)
d
x + y(x) dx
y(x)
d
p
=
y(x)
2
2
dx
x + y (x)

4

14 Проверим тип уравнения. Вторая строка вывода
показывает, что это уравнение в полных дифференциалах4 .

См. упражнения 1 и 2.
1

classify_ode(ode1, y)
factorable
1st_exact
1st_homogeneous_coeff_best
1st_homogeneous_coeff_subs_indep_div_dep
1st_homogeneous_coeff_subs_dep_div_indep
1st_power_series
lie_group
1st_exact_Integral
1st_homogeneous_coeff_subs_indep_div_dep_Integral
1st_homogeneous_coeff_subs_dep_div_indep_Integral
15 Решаем уравнение ode1 как уравнение в полных
дифференциалах, используя подсказку 1st_exact.

1
2

dsol = dsolve(ode1, y, hint = "1st_exact")
display(dsol)

y(x) = −

C1
x2
+
2
2C1

Криволинейные зеркала

111

16 Как видно, найденное решение оказалось квадратичной функцией, следовательно, искомыми кривыми, фокусирующими все отраженные лучи в одной
точке, являются параболы. Построим семейство таких
парабол (см. рис. 7). Так как все они имеют общую
точку фокуса, то данное семейство называется семейством конфокальных парабол5 .
1
2
3
4
5
6
7
8
9

C1 = symbols("C1")
p3 = plot(xlim = (-5, 5), ylim = (-5, 5),
aspect_ratio = (1, 1),
show = False)
for c in np.arange(-10, 10, 0.5):
p = plot(dsol.rhs.subs(C1, c), (x, -5, 5),
show = False)
p3.extend(p)
p3.show()
17 Выберем одну из найденных парабол (например,
для случая C1 = 1) и построим семейство отраженных
от нее лучей, чтобы проверить, что все эти лучи в самом деле проходят через начало координат. Определим сначала уравнение данной параболы и сохраним
его в переменной f2.

1
2

f2 = dsol.rhs.subs(C1, 1)
display(f2)

x2
1

2
2
18 Подставим эту формулу в выведенное выше общее
уравнение fam семейства отраженных лучей.
1
2

fam2 = fam.subs(y, f2).doit()
display(fam2)

Y =

X (x2 −1)
2x

19 Построим на одном графике параболу f2 и порожденное ею семейство прямых fam2.
1
2
3
4
5
6
7
8
9

p4 = plot(xlim = (-1, 1), ylim = (-1, 1),
aspect_ratio = (1, 1), show = False)
for x0 in np.arange(-0.95, 1, 0.05):
l = fam2.rhs.subs(x, x0)
p = plot(l, (X, -1, 1), line_color = "gray",
show = False)
p4.extend(p)
p4.extend(plot(f2, (x, -1, 1), show = False))
p4.show()

Семейство конфокальных парабол
РИС. 7

5

См. упражнение 5.

Глава 10

112

Результат построения, наглядно показывающий фокусирование отраженных лучей в начале координат, приведен на рис. 8.

УПРАЖНЕНИЯ И ЗАМЕЧАНИЯ

Семейство отраженных
от параболы лучей
РИС. 8

1 Определите в SymPy следующие уравнения, преобразуйте их к форме с производной, проверьте с помощью
команды classify_ode, что данные уравнения являются
уравнениями в полных дифференциалах, и решите их, используя подсказку 1st_exact, без упрощения результата
(опция simplify = False). Сравните найденное решение с
решениями, полученными с помощью других подсказок.
1)
2)
3)
4)

2xydx + (x2 − y 2 )dy = 0;
(2 − 9xy 2 )xdx + (4y 2 − 6x3 )ydy = 0;
e−y dx − (2y + xe−y )dy = 0;
(1 + y 2 sin 2x)dx − 2y cos2 xdy = 0.

2 Известно, что уравнение
P (x, y)dx + Q(x, y)dy = 0

(8)

является уравнением в полных дифференциалах, если для
него выполнено условие
∂P
∂Q
=
.
∂y
∂x
6

Обратите внимание, что во втором интеграле вместо x используется x0 !

(9)

Общее решение уравнения (8) можно найти, используя следующую формулу6 :
Zx
Zy
P (x, y)dx + Q(x0 , y)dy = C,
(10)
x0

y0

где (x0 , y0 ) — произвольная точка в области определения
функций P и Q. Напишите две функции, первая из которых проверяет, является ли заданное уравнение (в дифференциалах) уравнением в полных дифференциалах или
нет, а вторая — решает это уравнение описанным выше
способом. Протестируйте эти функции на уравнениях из
предыдущего упражнения.
Каустика, порожденная кривой y = x3 (упражнение 2)
РИС. 9

3 Постройте каустику отражения, порожденную заданной кривой y(x), для случая параллельного пучка лучей,
падающих на эту кривую сверху вниз.
1)
2)
3)
4)

y(x) = x3 (см. рис. 9);
y(x) = x4 ;
y(x) = sin x;

y(x) = 1 + x2 .

Криволинейные зеркала

113

4 Постройте каустику отражения вертикальных лучей,
порожденную полученным при построении модели уравнением параболы f2.
5 Рассмотрим семейство конфокальных парабол
y=−

C
x2
+
.
2
2C

(11)

Покажите, что два его подсемейства, различающихся знаком параметра C, являются взаимно ортогональными друг
другу (см. рис. 7).

1

6 Дифференциальное уравнение, описывающее полученное нами семейство парабол dsol, можно вывести и другим
способом. У нас есть уравнение семейства отраженных лучей Y = AX + B, порожденных кривой y(x). Потребуем,
чтобы все эти прямые проходили через начало координат,
для этого необходимо и достаточно, чтобы коэффициент
B равнялся нулю. Так как формулы для обоих коэффициентов A и B содержат в себе производную y 0 , то условие
B = 0 оказывается искомым дифференциальным уравнением:
display(Eq(B, 0))


d
x dx
y(x)
x
+ d
+ y(x) = 0
2
2 dx y(x)

В такой форме дифференциальное уравнение не является уравнением в полных дифференциалах. Однако если
разрешить его относительно y(x), то увидим, что данное
уравнение является уравнением Лагранжа. Решите его методом, описанным в упражнении 12 предыдущей главы.
7 Обобщите решенную в главе задачу на случай фокусирования отраженных лучей в произвольной точке плоскости.
8 Обобщите и решите рассмотренную в главе задачу на
случай, когда исходный пучок параллельных лучей наклонен к оси OX под заданным углом γ.
9 Решите по описанной в упражнении 6 схеме следующую задачу: определить форму кривой, после отражения
от которой вертикально падающие на нее лучи оказываются параллельными заданной прямой Y = aX.
10 Выполните вывод уравнения каустики в случае, если
лучи, падающие на внутреннюю часть единичной окружности с центром в начале координат, исходят из нижней
точки этой окружности (рис. 10). Каустикой в данном случае оказывается еще один эпициклоида, называемая кардиоидой.

Каустика отраженных
от окружности лучей, исходящих из нижней точки этой
окружности
РИС. 10

Глава 10

114

11 Выведите уравнение каустики для общего случая, когда лучи, исходящие из начала координат, падают снизу на
заданную кривую y(x). Примените полученную формулу
для получения каустик, порожденных следующими кривыми:
1) y(x) = 1 − x2 ;
3) y(x) = cos x;
2) y(x) = (x − 1)2 (x + 1); 4) y(x) = ln(1 + x2 ).
12 Решите задачу, обратную к рассмотренной в данной
главе: найти форму кривой, после отражения от которой
все лучи, исходящие из начала координат, оказываются направленными вертикально вверх.

y

F2

O

F1

x

Фокусирование после
отражения от кривой лучей,
исходящих из точки F1 , в точке F2
РИС. 11

13 Поставьте и решите с помощью SymPy задачу нахождения такой кривой y(x), после отражения от которой все лучи, исходящие из заданной точки F1 , проходят через другую заданную точку F2 (см. рис. 11).
14 Поставьте и решите с помощью SymPy задачу нахождения кривой y(x), после отражения от которой все лучи,
направленные в заданную точку F2 , фокусируются во второй точке F1 (см. рис. 12).
y

F2
РИС. 12

O

F1

x

Фокусирование после отражения от кривой лучей,
направленных в точку F2 , в точке F1

ГЛАВА

11

Из пушки на Луну
Задача • В данной главе мы рассмотрим следую-

щую классическую гравитационную задачу: некоторый снаряд выстреливается с заданной начальной скоростью v0 с поверхности Земли вертикально вверх в
направлении Луны, требуется определить минимальную начальную скорость v0 , которую необходимо придать снаряду, чтобы он достиг поверхности Луны.
Предполагается, что Земля и Луна неподвижны и
находятся на фиксированном расстоянии a друг от
друга1 . При решении этой задачи нами не будут учитываться никакие другие факторы и силы, кроме сил
притяжения, действующих на снаряд со стороны Земли и Луны. В частности, мы пренебрежем силой сопротивления воздуха, кстати, весьма значительной с
точки зрения практического решения данной задачи.
Физические характеристики рассматриваемой астрономической системы (G — гравитационная постоянная, R и M — радиус и масса Земли, r и m — радиус
и масса Луны, a — расстояние между их центрами),
необходимые для решения поставленной задачи, приведены в табл. 1.
Введем систему координат, ее начало O совместим
с центром Земли, а единственную ось Ox (движение
в нашей задаче очевидно является одномерным) направим на Луну. То есть снаряд начинает движение
из точки x = R, а закончить движение должен в точке x = a − r (рис. 1). Пусть масса снаряда равна m0 .
Тогда на него действуют две силы тяжести F1 и F2 со
стороны Земли и Луны соответственно:
F1 = −

GM m0
Gmm0
, F2 =
.
x2
(a − x)2

(1)

1

То есть a — это расстояние между центрами этих двух тел.

Физические характеристики задачи
ТАБЛ. 1

G
R
M
r
m
a

6.67 · 10−11 м3/с2 ·кг
6.38 · 106 м
6 · 1024 кг
1.73 · 106 м
7.3 · 1022 кг
4 · 108 м

Глава 11

116

Записываем второй закон Ньютона, в котором ẍ означает ускорение снаряда. После сокращения на m0 получаем дифференциальное уравнение второго порядка для неизвестной функции x(t):


m
M

.
(2)
ẍ = −G
x2
(a − x)2

x
r
m

Начальные условия для уравнения (2):

F2

x(0) = R, ẋ(0) = v0 .

a

Так как уравнение (2) не содержит в явном виде независимой переменной t, то его порядок можно понизить заменой неизвестной функции x(t) на функцию
v(x) = ẋ. Тогда

F1

M
O

РИС. 1

(3)

d(ẋ)
dv
dv dx
=
=
= v 0 v,
dt
dt
dx dt
и мы приходим к уравнению первого порядка


M
m
v · v 0 = −G

.
x2
(a − x)2
ẍ =

R

Конфигурация модели

(4)

(5)

Согласно условиям (3) в начальный момент времени x(0) = R, v(0) = v0 , следовательно, начальным
условием для уравнения (5) будет условие
v(R) = v0 .

(6)

Модель • Решим поставленную задачу с помощью
SymPy. Помимо ответа на вопрос о минимальной на-

чальной скорости v0 , необходимой для достижения поверхности Луны, мы найдем и общую зависимость скорости снаряда от его расстояния до центра Земли для
заданной скорости v0 .
1 Подключаем библиотеки SymPy и NumPy.
2 Определяем символ t и функцию x(t).
3 Вводим символы для всех параметров задачи, перечисленных в табл. 1.
4 Определяем словарь со значениями введенных в
предыдущем пункте параметров.
1
2
3
4

par = {G: 6.67 * 10 ** -11, M: 5.97 * 10 ** 24,
m: 7.36 * 10 ** 22, R: 6.380 * 10 ** 6,
r: 1.730 * 10 ** 6, a: 4.0 * 10 ** 8}
print(par)

Из пушки на Луну

117

G: 6.67e-11, M: 5.969999999999999e+24, m: 7.36e+22,
R: 6380000.0, r: 1730000.0, a: 400000000.0
5 Определяем силы F1 и F2 согласно формулам (1),
предполагая2 , что m0 = 1.
1
2
3
4

m0 = 1
F1 = - G * M * m0 / x ** 2
F2 = G * m * m0 / (a - x) ** 2
display(F1, F2)



2

Дифференциальное уравнение и
его решение от массы снаряда в
данной задачи никак не зависят.

GM
x2 (t)
Gm
2

(a − x(t))

6 Записываем исходное дифференциальное уравнение второго порядка (2).
1
2

ode = Eq(m0 * diff(x, t, t), F1 + F2)
display(ode)

d2
GM
Gm
x(t) = − 2
+
dt2
x (t) (a − x(t))2
7 Несложно убедиться, что полученное нами нелинейное дифференциальное уравнение второго порядка библиотекой SymPy не решается3 . Применим к нему
описанную выше замену x(t)
v(x) для понижения
порядка уравнения. Так как функция x(t) после замены становится переменной x, то по правилам SymPy мы
должны ввести для этой переменной новый символ.
Определяем функцию v от этой переменной и создаем
словарь с тремя ключами x, ẋ и ẍ, соответствующий
выполняемой замене.
1
2
3
4
5
6

X = symbols("x")
v = Function("v")(X)
Z = {diff(x, t, t): diff(v, X) * v,
diff(x, t): v,
x: X}
print(Z)
Derivative(x(t), t, 2): v(x) * Derivative(v(x), x),
Derivative(x(t), t): v(x), x(t): x

3

Это уравнение вообще не решается аналитически.

Глава 11

118

8 Выполняем замену, применяя подстановку Z к уравнению ode.
1
2

ode2 = ode.subs(Z)
display(ode2)

v(x)

d
GM
Gm
v(x) = − 2 +
2
dx
x
(a − x)

9 Вводим символ начальной скорости v0 , определяем
с его помощью начальное условие (6) и решаем поставленную начальную задачу в неявной форме.
1
2
3
4

v0 = symbols("v0", positive = True)
ic = {v.subs(X, R): v0}
dsol = dsolve(ode2, v, ics = ic, simplify = False)
display(dsol)

v 2 (x)
GM a + x (−GM + Gm)
=−
+
2
−ax + x2
2GM R − 2GM a − 2GRm − R2 v02 + Rav02
+
−2R2 + 2Ra
10 Для нахождения минимальной начальной скорости, при которой снаряд долетит до поверхности Луны, надо проанализировать скорость снаряда в так называемой точке либрации — точке равенства модулей
сил F1 и F2 . Несложно заметить, что если снаряд в
точке либрации имеет положительную скорость, то он
обязательно достигнет Луны. Критическим, очевидно,
будет случай, когда в точке либрации скорость снаряда будет в точности равна нулю4 . Сначала составим
и решим уравнение для нахождения расстояния L от
центра Земли до точки либрации.

4

Теоретически это будет означать, что снаряд останется в этой
точке навсегда. На практике, однако, любое малое возмущение
положения снаряда относительно
точки либрации приведет к падению снаряда или на Землю, или на
Луну.

1
2
3

eq = Eq(F1 + F2, 0)
sol = solve(eq, x)
display(sol)
[a*(M-sqrt(M*m))/(M-m), a*(M+sqrt(M*m))/(M-m)]

5

Объясните, почему вторая точка
не является точкой либрации.

11 Как видно, мы получили два возможных решения для расстояния L, однако нам подходит только
одно из них — то, которое меньше a5 . Подставим в оба
найденных решения набор параметров par и сравним
полученные значения с расстоянием a (из того же набора параметров).

Из пушки на Луну

1
2
3

119

for s in sol:
print(s.subs(par))
print(par[a])
360025337.331458
449960416.687944
400000000.0
12 То есть искомому значению L соответствует выражение sol[0]. Запомним его в переменной L. В переменной l сохраним значение этого выражения, вычисленное исходя из условий нашей задачи.

1
2
3

L = sol[0]
l = float(L.subs(par))
display(L, l)




a M − Mm
M −m

360025337.331458
13 Теперь мы можем найти искомую начальную скорость. Для этого заменим в решении dsol скорость v
на ноль, а расстояние X — на L. Полученное таким образом уравнение решим относительно символа v0 и в
качестве ответа выберем6 положительную величину.
1
2

VE = solve(dsol.subs({v: 0, X: L}), v0)[1]
display(VE)

v
u
√ u
u
2t−

6

Полученное выражение слишком велико, поэтому в выводе
показана только небольшая его
часть.




3
G M 2 R2 M m + · · · + a2 (M m) 2




Ra (R − a) −2M m + M M m + m M m

14 Вычислим значение критической начальной скорости (в метрах в секунду) для нашей задачи.
1
2

ve = float(VE.subs(par))
print(ve)
11063.20675112643

Интересно, что найденная величина скорости всего на
110 м/с (т. е. на 1 %) меньше второй космической скорости для Земли7 .

7

См. упражнение 8.

Глава 11

120

15 Выразим из найденного решения dsol скорость v,
взяв положительный корень уравнения, что соответствует движению снаряда вверх, и подставим в полученное выражение параметры par. Построим графики
найденной зависимости скорости снаряда от его положения для разных значений начальной скорости v0
(рис. 2). По оси абсцисс графики будем строить от
точки на поверхности Земли (x = R) до точки на поверхности Луны (x = a − r). Для скоростей, меньших
критической, цвет кривых сделаем синим, для критической скорости — красным, а для скоростей, больших
критической, — зеленым.
1
2
3
4
5
6
7
8
9
10
11
12

ds = solve(dsol, v)[1].subs(par)
p1 = plot(ylim = (0, 10000),
size = (6, 8), show = False)
xlim = (X, par[R], par[a] - par[r])
for u in np.arange(ve - 2100, ve + 2100, 70):
if u < ve: c = "blue"
elif u > ve: c = "green"
else: c = "red"
p = plot(ds.subs(v0, u), xlim,
line_color = c, show = False)
p1.extend(p)
p1.show()

УПРАЖНЕНИЯ И ЗАМЕЧАНИЯ
1 Оцените минимальную скорость, с которой снаряд, выпущенный с поверхности Земли, упадет на поверхность
Луны.

Зависимость v(x) скорости снаряда от его положения для разных значений начальной скорости v0
РИС. 2

1
2
3

2 Чтобы довести до конца решение исходного уравнения
ode, надо применить к найденному решению dsol обратную замену. Это можно сделать, используя словарь Z, поменяв в нем местами ключи и значения элементов:
Z2 = {v: k for k, v in Z.items()}
ode2 = dsol.subs(Z2)
display(ode2)
2
d
x(t)
dt
2

=

−GM a − (−GM + Gm) x(t)
+
−ax(t) + x2 (t)
2GM R − 2GM a − 2GRm − R2 v02 + Rav02
+
−2R2 + 2Ra

Выразите из найденного соотношения производную ẋ и решите приближенно полученное дифференциальное уравнение относительно x(t) с начальным условием x(0) = R.

Из пушки на Луну

121

С помощью этого решения найдите время полета снаряда
до поверхности Луны для заданной начальной скорости v0 .
3 Если начальная скорость снаряда невелика (например,
v0 = 10), то высота его полета будет небольшой и поэтому
силой притяжения со стороны Луны можно пренебречь.
При таких условиях снаряд должен двигаться практически
равноускоренно с ускорением g:
x(t) = v0 t −

gt2
.
2

(7)

Сравните численное решение уравнения ode, построенное
по схеме решения предыдущего упражнения, с приближенной оценкой (7) для v0 = 10.

Зависимость v(x) с
учетом отрицательных скоростей
РИС. 3

4 Численно можно решать и исходное уравнение (2), преобразовав его предварительно в систему двух дифференциальных уравнений первого порядка относительно двух
неизвестных функций x(t) и v(t):
ẋ = v, 

M
m
v̇ = −G

.
x2
(a − x)2

(8)
Жюль Верн

Преимуществом такого подхода является то, что не нужно отдельно рассматривать случаи, когда тело движется
вверх (ẋ > 0) и вниз (ẋ < 0), как это делалось в упражнении 2.
5 Постройте график зависимости максимальной высоты
полета снаряда от его начальной скорости v0 .
6 После того как снаряд достигает наивысшей точки полета (при условии ее существования), его скорость становится отрицательной, т. е. он начинает падать обратно на
Землю. Постройте и проанализируйте график зависимости
скорости снаряда от его положения в области отрицательных скоростей (см. рис. 3).
7 Рассмотренная нами модель достижения Луны была
положена в основу сюжета научно-фантастического романа Жюля Верна «С Земли на Луну прямым путём за 97
часов 20 минут»8 , написанного в 1865 году. Пушка, из которой выстреливался снаряд, имела длину ствола 274 метра.
Если предположить, что снаряд в стволе пушки разгонялся равноускоренно, то простые расчеты показывают, что
ускорение, необходимое для достижения найденной выше
скорости v0 , должно быть примерно равным 23 000g. Заметим, что максимальная зафиксированная перегрузка при
аварии гоночного автомобиля с выжившим водителем составляет 214g, т. е. у пассажиров снаряда в романе Жюля
Верна не было никаких шансов выжить при выстреле из

Иллюстрация из первого издания романа «Из пушки на Луну»
РИС. 4

8

В русскоязычных переводах, изданных в СССР, известен также
под названиями «Из пушки на Луну» и «От Земли до Луны».

Глава 11

122

9

В романе в качестве пушки
использовалась шахта в земле с
чугунными стенками диаметром
18.3 метра.

пушки. Рассчитайте, какой должна была быть длина ствола пушки из романа Жюля Верна, чтобы нужная скорость
достигалась с постоянным ускорением 10g. Сравните эту
длину с глубиной Кольской сверхглубокой скважины9 .
8 Постройте аналогичную модель движения снаряда в
поле тяготения только Земли. Используйте найденное решение для вычисления второй космической скорости — минимальной начальной скорости, при которой снаряд никогда не упадет обратно на Землю. Рассчитайте с помощью
полученной формулы величину второй космической скорости для различных тел Солнечной системы.
9 При уменьшении радиуса R тела, с которого выстреливается снаряд, при условии что масса тела остается постоянной и равной M , величина второй космической скорости неограниченно увеличивается и, начиная с некоторого критического значения R0 , становится больше скорости
света c, т. е. тело превращается в черную дыру. Критическая величина радиуса R0 называется гравитационным
радиусом, или радиусом Шварцшильда, данного тела. Вычислите, используя результат предыдущего упражнения,
гравитационный радиус Луны, Земли и Солнца.

Карл Шварцшильд

10 Решите с помощью рассмотренной выше (в главе и в
упражнении 2) замены z(y) = y 0 (x) следующие дифференциальные уравнения второго порядка :
1)
2)

yy 00 = y 02 − y 03 ;
y 02 + 2yy 00 = 0;

3)
4)

y 00 + y 02 = 0;
y 00 = 2yy 0 .

11 Порядок дифференциального уравнения вида
F (x, y 0 , y 00 ) = 0

(9)

может быть понижен с помощью замены z(x) = y 0 (x), применение которой приводит к уравнению первого порядка
вида
F (x, z, z 0 ) = 0.
(10)
Определите в SymPy такую замену и решите с ее помощью
следующие уравнения:
1)
2)

2xy 0 y 00 = y 02 − 1;
x2 y 00 = y 02 ;

3)
4)

y 00 = y 0 ctg x;
x ln x y 00 = y 0 − 1.

ГЛАВА

12

Метроном
Задача • Метроном — прибор, отмечающий корот-

кие промежутки времени равномерными ударами. Механический метроном (рис. 1) представляет собой маятник с дополнительным грузом, закрепленным выше
точки подвеса. Меняя положение этого груза, можно
настраивать нужную частоту ударов метронома.
Рассмотрим (рис. 2) простую математическую модель метронома. Пусть основной груз имеет массу M и
закреплен на невесомом стержне на расстоянии L ниже точки подвеса O, дополнительный груз имеет массу m и закреплен на том же стержне на высоте l выше
точки подвеса. Очевидно, что каждый груз метронома
совершает движение по дуге окружности своего радиуса с центром в точке крепления стержня. Поэтому
положение обоих грузов в любой момент времени однозначно определяется углом θ отклонения стержня
от вертикали.
Чтобы маятник не переворачивался, центр его масс
должен располагаться относительно точки подвеса с
той же стороны, что и основной груз:
M L > ml.

РИС. 1

ном

Механический метро-

D

v
Q

C

l
O

θ

(1)

Вывод дифференциального уравнения для функции θ(t) проведем с помощью закона сохранения механической энергии. Пусть в момент времени t нижний груз находится в точке P , верхний — в точке Q.
Потенциальная энергия системы равна сумме потенциальных энергий двух грузов, отсчитывать эту энергию будем относительно точки подвеса O:
EП = mgl cos θ − M gL cos θ = (ml − M L)g cos θ. (2)

L

B

P
A

V

Математическая модель метронома
РИС. 2

Глава 12

124

1

Линейная скорость тела, движущегося по окружности, равна произведению угловой скорости на
радиус окружности.

Линейные скорости нижнего и верхнего грузов равны1 Lθ̇ и lθ̇ соответственно. Следовательно, кинетическая энергия всей системы вычисляется по формуле
EК =

M L2 θ̇2
(M L2 + ml2 ) 2
ml2 θ̇2
+
=
· θ̇ .
2
2
2

(3)

В силу закона сохранения энергии:
EК + EП = E0 = const,

(4)

где E0 — полная механическая энергия рассматриваемой системы. Следовательно,
(M L2 + ml2 ) 2
· θ̇ + (ml − M L)g · cos θ = E0 .
2

(5)

Чтобы избавиться в последнем соотношении от неизвестной константы E0 , продифференцируем это соотношение по t. После упрощения полученного выражения приходим к следующему дифференциальному
уравнению второго порядка:
θ̈ +

M L − ml
· g · sin θ = 0.
M L2 + ml2

(6)

Обозначим коэффициент при синусе (по условию (1)
этот коэффициент положителен) через ω 2 :
θ̈ + ω 2 sin θ = 0.

(7)

Уравнение (7) является нелинейным и в элементарных функциях аналитически не решается. Если предположить, что угол θ мал: θ  1, то тогда мы можем
воспользоваться известным приближением sin θ ≈ θ и
заменить в (7) синус угла θ на сам угол, что приводит нас к линейному однородному уравнению второго
порядка:
θ̈ + ω 2 θ = 0,
(8)
называемому уравнением гармонических колебаний.

Модель • Рассмотрим вывод и решение уравнения
(8) с помощью библиотеки SymPy. Используя полученное решение, найдем зависимость частоты колебаний
метронома от расстояния l крепления его малого груза
и построим анимацию колебаний метронома.
1 Подключаем библиотеки SymPy и NumPy.

Метроном

125

2 Создаем символы, необходимые для построения модели, и определяем функцию θ(t).
1
2
3
4

t, l, L, m, M, g = symbols("t, l, L, m, M, g")
theta = Function(’theta’)(t)
theta_ = diff(theta, t)
display(theta, theta_)

θ(t)

d
θ(t)
dt

3 Вычисляем полную энергию системы E = EК + EП
согласно формулам (2) и (3).
1
2
3
4

EP = (m * l - M * L) * g * cos(theta)
EK = (m * l ** 2 + M * L ** 2) / 2 * theta_ ** 2
E = EP + EK
display(E)

g (−LM + lm) cos (θ(t)) +



L2 M
l2 m
+
2
2



d
θ(t)
dt

2

4 Энергия E является постоянной величиной, следовательно, ее производная по t должна равняться нулю.
Выполняем указанное дифференцирование и получаем дифференциальное уравнение в форме с неразрешенной второй производной.
1
2

ode = Eq(diff(E, t), 0)
display(ode)

d
−g (−LM + lm) sin (θ(t)) θ(t) +
dt

 2
L M
l2 m d
d2
+2
+
θ(t) 2 θ(t) = 0
2
2
dt
dt
5 Разрешаем полученное уравнение ode относительно второй производной и формируем новое дифференциальное уравнение.
1
2
3

sol = solveset(ode, diff(theta, t, t)).args[0]
ode2 = Eq(diff(theta, t, t), sol)
display(ode2)

d2
g (LM − lm) sin (θ(t))
θ(t) = −
dt2
L2 M + l2 m

Глава 12

126

6 Определяем замену дроби, стоящей перед синусом
справа (со знаком минус), на выражение ω 2 . Применяем эту замену к уравнению ode2 и получаем нелинейное уравнение (7).
1
2
3
4

omega = symbols("omega", positive = True)
W = {-ode2.rhs / sin(theta): omega ** 2}
ode3 = ode2.subs(W)
display(ode3)

d2
θ(t) = −ω 2 sin (θ(t))
dt2
7 Заменяем sin θ на θ согласно предположению о малости колебаний и приходим к искомому уравнению
гармонических колебаний (8).
1
2

ode4 = ode3.replace(sin, lambda x: x)
display(ode4)

d2
θ(t) = −ω 2 θ(t)
dt2
8 Решим уравнение ode4 с учетом начального условия2 θ(0) = θ0 , θ̇(0) = 0. Получаем решение в форме
чистого косинуса.

2

Начальное отклонение стержня
метронома на заданный угол θ0 и
нулевая начальная скорость.
1
2
3
4
5

theta0 = symbols("theta0")
ics = {theta.subs(t, 0): theta0,
diff(theta, t).subs(t, 0): 0}
dsol = dsolve(ode4, theta, ics = ics)
display(dsol)

θ(t) = θ0 cos (ωt)
9 Выполняем обратную замену параметра ω, обращая предварительно словарь W, и выражаем найденное решение dsol через исходные параметры модели.
1
2
3

W2 = {v: k for k, v in W.items()}
dsol2 = dsol.subs(W2)
display(dsol2)

θ(t) = θ0 cos t

r

g (LM − lm)
L2 M + l2 m

!

Метроном

127

10 Построим графики найденного решения для различных значений параметра l (это единственный настраиваемый параметр исходной модели, все остальные параметры фиксированы конструкцией метронома и не могут меняться). Для наглядности покажем
каждый график по отдельности, после чего объединим их на общем графике (см. рис. 3).
1
2
3
4
5
6
7
8

par1 = {theta0: pi / 6, m: 1, M: 10, L: 1, g: 9.8}
p1 = plot(aspect_ratio = (1, 1), show = False)
for l0 in np.arange(0.5, 4, 1):
par1[l] = l0
ds = dsol2.rhs.subs(par1)
p = plot(ds, (t, 0, 6), aspect_ratio = (1, 1))
p1.extend(p)
p1.show()
11 Введенный нами параметр ω называется собственной циклической частотой колебаний. Периодом колебаний называется величина T = 2π/ω — это минимальное время, через которое система возвращается в исходное состояние. Обратная периоду величина
ν = 1/T = ω/2π называется частотой колебаний, эта
величина измеряется в герцах (число колебаний в секунду). Выразим период колебаний и их частоту через
параметры модели.

1
2
3

Графики функции θ(t)
для разных значений параметра l

РИС. 3

T = (2 * pi / omega).subs(W2)
freq = 1 / T
display(T, freq)

r

r


g (LM − lm)
L2 M + l2 m
g (LM − lm)
L2 M + l2 m


12 Как следует из графиков на рис. 3, частота колебаний уменьшается с увеличением длины l. Найдем точную зависимость частоты от длины l и построим соответствующие графики для разных значений
массы основного груза M . Предварительно выразим
из соотношения (1) максимально допустимое значение
lmax параметра l, превышение которого должно приводить к переворачиванию маятника. Каждый график
будем строить в диапазоне от нуля3 до lmax .

3

Заметим, что при l = 0 метроном превращается в обычный подвесной маятник.

128

1
2
3
4
5
6
7
8
9
10

Глава 12

lmax = M * L / m
par2 = {m: 1, L: 1, g: 9.8}
p2 = plot(show = False)
for M0 in np.arange(0.1, 2, 0.1):
par2[M] = M0
f = freq.subs(par2)
lm = lmax.subs(par2)
p = plot(f, (l, 0, lm), show = False)
p2.extend(p)
p2.show()

Результат построения приведен на рис. 4. Видно, что
зависимость частоты колебаний метронома от расстояния l близка к линейной, что объясняет равномерность шкалы метронома на рис. 1.

Зависимость частоты
колебаний от длины l для
разных значений массы M
РИС. 4

13 Переходим к построению анимации колебаний нашего метронома. Определяем параметры модели, подставляем их в решение dsol и преобразуем правую
часть полученного решения в функцию Theta.
1
2
3
4

par = {theta0: pi / 6, m: 1, M: 9,
L: 0.2, l: 1.2, g: 9.8}
ds = dsol2.subs(par)
Theta = lambdify(t, ds.rhs)
14 Вычисляем параметры анимации: rate — количество кадров в секунду; T0 — длительность анимации
в секундах (один период колебаний); frames — число
кадров в анимации; dt — длительность одного кадра в
секундах.

1
2
3
4
5

rate = 10
T0 = float(T.subs(par))
frames = int(T0 * rate)
dt = 1 / rate
print(rate, T0, frames, dt)
10 3.4763817222630955 34 0.1

РИС. 5

ции

Шаблон кадра анима-

4
Каждый вызов функции plot
возвращает кортеж из одного элемента. Запятая после имени переменной, в которую сохраняется созданный объект, нужна для распаковки данного кортежа.

15 Создаем шаблон одного кадра анимации (рис. 5):
подключаем библиотеку Matplotlib; создаем поле для
кадра (строки 2 и 3); устанавливаем одинаковый масштаб по обеим осям. Далее создаем заготовки элементов, которые будем визуализировать4 : стержень S —
отрезок прямой линии, толщиной 6 пикселей, серого
цвета; нижний груз P — круг радиуса 25 пикселей,
красного цвета; верхний груз Q — радиус 15 пикселей, синего цвета; точка подвеса O — радиус 4 пикселя,
черного цвета. Координаты точки подвеса постоянны,

Метроном

129

поэтому мы их сразу указываем. Координаты остальных объектов будут вычисляться для каждого кадра
заново.
1
2
3
4
5
6
7
8

import matplotlib.pyplot as plt
fig = plt.figure()
ax = plt.axes(xlim = (-1, 1), ylim = (-0.5, 2))
ax.set_aspect(’equal’)
S, = ax.plot([], [], ’-’, lw = 6, c = "gray")
P, = plt.plot([], [], ’o’, ms = 25, c = ’red’)
Q, = plt.plot([], [], ’o’, ms = 15, c = ’blue’)
O, = plt.plot([0], [0], ’o’, ms = 4, c = ’black’)
16 Вычислим максимально возможную длину lmax
для установленных параметров модели. Эту длину будем использовать для прорисовки стержня метронома.

1
2

lm = float(lmax.subs(par))
print(lm)
1.8
17 Определяем функцию makeframe, которая должна вычислять координаты всех анимируемых объектов по номеру кадра i. Для этого сначала вычисляем
значение текущего угла θ(t), используя функцию Theta
и учитывая, что время t равно произведению номера
текущего кадра i на длительность кадра dt. Далее находим синус и косинус угла θ. После чего заполняем
координаты наших объектов: первый список содержит
x-координаты объекта, второй — y-координаты.

1
2
3
4
5
6
7
8

def makeframe(i):
th = Theta(i * dt)
s, c = np.sin(th), np.cos(th)
S.set_data([-lm * s, par[L] * s],
[lm * c, -par[L] * c])
P.set_data(par[L] * s, -par[L] * c)
Q.set_data(-par[l] * s, par[l] * c)
return S, P, Q
18 Последним действием создадим анимацию с помощью команды FuncAnimation, на вход которой передаются: поле кадра, функция прорисовки кадров, количество кадров, длительность одного кадра (в милисекундах). Для показа анимации используем команду
HTML, которая создает мини-проигрыватель и запускает в нем заданную анимацию.

Глава 12

130

1
2
3
4
5
6

from matplotlib.animation import FuncAnimation
from IPython.display import HTML
anim = FuncAnimation(fig, makeframe,
frames = frames,
interval = dt * 1000)
HTML(anim.to_html5_video())

Один кадр созданной анимации показан на рис. 6. Заметим, что по умолчанию показ анимации запускается
в бесконечном цикле, а так как мы сделали длительность анимации равной периоду колебаний, то визуально это будет выглядеть как бесконечно работающий метроном.

УПРАЖНЕНИЯ И ЗАМЕЧАНИЯ
РИС. 6

мации

1 Постройте по аналогии с рис. 3 семейство графиков
найденного решения θ(t) для разных значений параметров:
θ0 , m, g.

Кадр построенной ани-

2 Постройте график зависимости частоты колебаний ν
от длины l для различных значений ускорения свободного
падения, соответствующих разным телам Солнечной системы. Увеличивается или уменьшается частота колебаний
при уменьшении g?
3 Постройте графики зависимости периода колебаний T
от длины l для разных значений одного из параметров модели (M , m, L, g) при фиксированных знвачениях остальных параметров.
4 Решите начальную задачу для уравнения ode4 с начальными условиями общего вида:
θ(0) = θ0 , θ̇(0) = ξ0 .

(9)

При каких начальных условиях решением начальной задачи будет чистый синус?
5 Общим решением дифференциального уравнения ode4
является линейная комбинация косинуса и синуса:
1

display(dsolve(ode4, theta))
θ(t) = C1 sin (ωt) + C2 cos (ωt)
Эту линейную комбинацию можно преобразовать, используя метод вспомогательного аргумента:
θ(t) = A sin(ωt + ϕ),

(10)

Метроном

131

где A — амплитуда колебаний:
q
A = C12 + C22 ,

(11)

ϕ — начальная фаза колебаний, в качестве которой можно
взять любое решение системы уравнений
cos ϕ =

C2
C1
, sin ϕ =
.
A
A

(12)

Исследуйте зависимость амплитуды колебаний от параметров модели для случая ненулевой начальной угловой
скорости ξ0 6= 0.

6 Колебания подвесного маятника (в том числе и метронома) считаются малыми, если их амплитуда не превосходит 30◦ . Проанализируйте решение предыдущего упражнения для случая колебаний с амплитудой, большей 30◦ .
Насколько адекватным является найденное нами решение,
если амплитуда оказывается больше, чем 180◦ ?
7 Так как решение дифференциального уравнения ode2
найдено нами в так называемом линейном приближении
sin θ ≈ θ, то следует ожидать, что на этом решении полная энергия системы E не будет сохраняться. Чтобы убедиться в этом, постройте график зависимости энергии E от
времени t, подставив в соответствующую формулу вместо
функции thetaрешение dsol2.
8 Рассмотрите по аналогии с моделью метронома модель
простого подвесного маятника (рис. 7), решите полученное
дифференциальное уравнение в случае малых колебаний,
получите формулы частоты, периода и амплитуды колебаний, постройте анимацию движения маятника для какогонибудь одного заданного набора значений параметров модели.
9 Решите с помощью SymPy следующие начальные задачи для линейных однородных уравнений с постоянными
коэффициентами:
1)
2)
3)
4)

y 00
y 00
y 00
y 00

= −4y, y(0) = 0, y 0 (0) = 2;
= −y, y(0) = −2, y 0 (0) = 0;
= −2y, y(0) = 1, y 0 (0) = −1;
= −a2 y, y(0) = 3, y 0 (0) = 1.

10 Постройте анимацию найденного в главе решения θ(t)
по одному из следующих параметров модели: l, θ0 , m, g.
Каждый кадр анимации должен соответствовать своему
значению выбранного параметра (в некотором диапазоне
его изменения) при фиксированных значениях остальных
параметров.

θ
l

РИС. 7

ятник

mg
Простой подвесной ма-

Глава 12

132

11 Постройте анимации следующих графиков (указаны
уравнение кривой, параметр анимации, пределы рисования графика):
1)
2)
3)
4)

x2 + a, a ∈ [−3, 3], x ∈ [−3, 3], y ∈ [−3, 3];
(x − a)2 , a ∈ [−3, 3], x ∈ [−3, 3], y ∈ [−3, 3];
cos(x − a), a ∈ [0, 2π], x ∈ [−2π, 2π], y ∈ [−1, 1];
sin(a) cos(x), a ∈ [0, 2π], x ∈ [−π, π], y ∈ [−1, 1].

12 Нелинейное уравнение (7) в общем виде (без начального условия) аналитически не решается. Но его можно решить численно, с помощью одного из приближенных методов, например с помощью метода Эйлера. Для этого предварительно надо преобразовать данное уравнение второго
порядка к системе двух уравнений первого порядка введением дополнительной неизвестной функции — угловой
скорости ξ(t):


= ξ,
= −ω 2 sin θ.
dt
dt

(13)

Постройте с помощью такого приближенного решения графики зависимости периода колебаний метронома от величины начального отклонения θ0 (при нулевой начальной
угловой скорости), сравните эти графики с соответствующими графиками из упражнения 3.
Фазовый портрет уравнения (13), по горизонтали
отложен угол θ, по вертикали
— угловая скорость ξ = θ̇
РИС. 8

13 Перепишем соотношение (5), из которого была выведена система (13), с использованием введенного обозначения угловой скорости ξ = θ̇ и полученного ранее выражения для собственной частоты ω:
ξ 2 − 2ω 2 cos θ = C,

(14)

где C — константа, зависящая от параметров модели и начального условия (начальной энергии E0 ). То есть в любой
момент времени угол θ и угловая скорость ξ оказываются
связанными формулой (14). Соответствующее этой формуле семейство кривых на плоскости Oθξ (см. рис. 8) называется фазовым портретом системы (13), сама плоскость
называется фазовой, а каждая отдельная кривая — фазовой траекторией. Выполните указанное построение фазового портрета с помощью SymPy.

ГЛАВА 13
Пружинный маятник
Задача • Рассмотрим простую модель пружинно-

го маятника, представляющего собой подвешенный с
помощью пружины груз массы m (см. рис. 1). В такой модели на груз маятника действуют как минимум
две силы — сила тяжести F1 , направленная вниз, и
сила упругости F2 со стороны сжатой или растянутой пружины. Величина силы упругости определяется законом Гука1 : величина силы упругости пружины пропорциональна изменению длины этой пружины (относительно ее естественной длины). Коэффициент пропорциональности k в законе Гука называется жесткостью пружины.
Также будем предполагать, что на груз маятника,
кроме двух указанных выше сил, могут действовать
еще две дополнительные силы. Первая из них — это
сила сопротивления F3 со стороны среды, в которую
помещен груз маятника. Если в качестве среды выступает какая-нибудь жидкость, например вода или
масло, то сила сопротивления оказывается пропорциональной скорости груза v и направленной против этой
скорости2 . Второй дополнительной силой может быть
внешняя (то есть не зависящая ни от положения, ни
от скорости груза) периодическая сила F4 , величина
которой определяется формулой:
F4 = f0 sin ω0 t,

(1)

где f0 — амплитуда данной силы, а ω0 — ее циклическая частота.
Введем одномерную систему координат (рис. 1),
ось Ox которой направим вертикально вверх, а начало координат O поместим в точку, соответствующую недеформированному состоянию пружины (т. е.

1

Роберт Гук — английский
естествоиспытатель и изобретатель XVII века. В настоящее
время не существует ни одного
подтвержденного его портрета.

x

O
m

x(t)
v(t)

Простейший пружинный маятник
РИС. 1

2

Если средой является воздух,
то сила сопротивления пропорциональна квадрату скорости, что на
самом деле существенно усложняет рассматриваемую модель.

Глава 13

134

в точке O сила упругости равна нулю). Функция x(t)
определяет положение груза в момент времени t. Тогда скорость груза равна v = ẋ. Таким образом, сила
тяжести равна F1 = −mg, сила упругости F2 = −kx,
а сила сопротивления F3 = −rv = −rẋ, где r — коэффициент пропорциональности, определяемый свойствами среды и геометрией груза маятника.
Записываем второй закон Ньютона применительно
к нашей системе:
mẍ = −mg − kx − rẋ + f0 sin ω0 t,

(2)

где вторая производная ẍ, как обычно, соответствует
ускорению груза. Перегруппируем слагаемые и поделим все уравнение на m:
ẍ +

k
f0
r
ẋ + x = −g +
sin ω0 t.
m
m
m

(3)

Полученное соотношение является линейным неоднородным дифференциальным уравнением второго порядка с постоянными коээфициентами. Для его однозначного разрешения требуются два дополнительных
условия. Будем предполагать, что начальное отклонение груза равно x0 , а его начальная скорость равна
нулю:
x(0) = x0 , ẋ(0) = 0.
(4)

Модель • Построим описанную модель средствами библиотеки SymPy. Рассмотрим три важных частных случая этой модели: свободные колебания (r = 0,
f0 = 0), затухающие колебания (r 6= 0, f0 = 0) и вынужденные колебания (r = 0, f0 6= 0), приводящие, в
частности, к явлению резонанса.
1 Подключаем библиотеки SymPy, NumPy и Matplotlib.
2 Создаем символы m, k, r, f0, omega0, g для всех параметров, входящих в уравнение (2), указывая, что все
они являются неотрицательными величинами. Определяем символ t и вводим функцию x(t).
3 Задаем дифференциальное уравнение (2) в максимально общем виде, т. е. с учетом всех четырех рассмотренных выше сил.
1
2
3

ode = Eq(m * diff(x, t, t), -m * g - k * x r * diff(x, t) + f0 * sin(omega0 * t))
display(ode)

Пружинный маятник

m

135

d
d2
x(t) = f0 sin (ω0 t) − gm − kx(t) − r x(t)
2
dt
dt

4 Определяем символ x0 и задаем начальные условия (4).
1
2

x0 = symbols("x0")
ic = {x.subs(t, 0): x0, diff(x, t).subs(t, 0): 0}
5 Рассмотрим первый частный случай свободных колебаний, когда на груз действуют только сила тяжести
и сила упругости. Для этого обнулим параметры r и f0
в дифференциальном уравнении и решим его.

1
2
3

par1 = {r: 0, f0: 0}
dsol1 = dsolve(ode.subs(par1), x, ics = ic)
display(dsol1)

gm
x(t) = −
+
k

(gm+kx0 ) cos
k

√

√kt
m



6 Видно, что найденная зависимость x(t) является
косинусоидальной, при этом колебания происходят относительно величины −gm/k. Сохраним эту величину
в переменной x1.
1
2

x1 = dsol1.rhs.replace(cos, lambda x: 0)
display(x1)



gm
k

7 Построим график семейства интегральных кривых

найденного решения для различных значений параметра k при фиксированных значениях остальных параметров модели для начального условия x0 = 0.
1
2
3
4
5
6
7

p1 = plot(size = (8, 4), show = False)
for k_ in np.arange(2.0, 8.0, 0.5):
ds = dsol1.rhs.subs({m: 1, k: k_, x0: 0,
g: 9.8})
p = plot(ds, (t, 0, 2 * pi), show = False)
p1.extend(p)
p1.show()

Из построенного графика (см. рис. 2) видно, что чем
меньше жесткость пружины, тем большей является
амплитуда колебаний и тем ниже располагается точка равновесия системы, относительно которой происходят ее колебания.

Свободные колебания
пружинного маятника для
различных значений параметра k
РИС. 2

136

Глава 13

8 Для сравнения построим график того же семейства кривых для разных значений параметра x0 , который явно показывает, что колебания являются гармоническими (рис. 3).
1
2
3
4
5
6
7

РИС. 3 Свободные колебания
пружинного маятника для
различных значений параметра x0

p2 = plot(size = (8, 4), show = False)
for x0_ in np.arange(-7.0, 2.1, 0.5):
ds = dsol1.rhs.subs({m: 1, k: 4, x0: x0_,
g: 9.8})
p = plot(ds, (t, 0, 2 * pi), show = False)
p2.extend(p)
p2.show()
9 Перейдем теперь к случаю затухающих колебаний.
Для этого нужно в уравнении ode обнулить только коэффициент f0. Так как решение получающегося после
такой замены уравнения в общем случае оказывается
очень громоздким, сразу подставим в уравнение числовые значения параметров k и m, а в найденное решение — ноль вместо x0. Получаем решение, зависящее
от параметров g и r.

1
2
3
4

par2 = {m: 1, k: 4, f0: 0}
dsol2 = dsolve(ode.subs(par2), x, ics = ic)
dsol2 = dsol2.subs(x0, 0)
display(dsol2)





t(−r− r−4 r+4)
g
gr
g
2


x(t) = − + −
+
e
+
4
8 r−4 r+4 8




t(−r+ r−4 r+4)
g
gr
2


+
+
e
8 r−4 r+4 8
10 Известно, что имеется два основных режима затухающих колебаний. В подкритическем режиме, когда
сила сопротивления (т. е. параметр r) относительно
невелика, колебания маятника происходят, но их амплитуда постепенно уменьшается до нуля. В надкритическом режиме (при большой силе сопротивления)
колебания как таковые не происходят, маятник просто возвращается в положение своего равновесия. Для
пружинного маятника критическим значением,
√ разделяющим эти два режима, является r0 = 2 km. Для
выбранного нами в предыдущем пункте набора параметров r0 = 4. При r < 4 в решении dsol2 подкоренные
выражения в показателях экспонент оказываются отрицательными, т. е. сами показатели — комплексными.
Это значит, что после применения к данным экспонентам формулы Эйлера в решении появятся синусы

Пружинный маятник

137

и косинусы. Так как SymPy умеет работать с комплексными числами, то мы можем построить графики решения dsol2 даже для подкритических значений параметра r. Подкритические кривые выделим оранжевым
цветом, надкритические — голубым цветом.
1
2
3
4
5
6
7
8
9
10
11
12

p3 = plot(size = (8, 6), show = False)
for r_ in np.arange(0.5, 8.0, 0.5):
ds = dsol2.rhs.subs({r: r_, x0: 0, g: 9.8})
c = "skyblue" if r_ < 4 else "orange"
p = plot(ds, (t, 0, 2 * pi), line_color = c,
show = False)
p3.extend(p)
X1 = x1.subs(par2).subs(g, 9.8)
p = plot(X1, (t, 0, 2 * pi), line_color = "black",
show = False)
p3.extend(p)
p3.show()

Результат построения показан на рис. 4. Хорошо видны затухающие колебания в подкритическом режиме.
В надкритическом режиме кривые являются монотонно убывающими к положению равновесия x = x1 (прямая черного цвета).

Затухающие колебания
пружинного маятника
РИС. 4

11 Из графика 4 видно, что между оранжевыми и
голубыми кривыми есть зазор, в котором должно располагаться так называемое критическое решение для
случая r = r0 = 4. Прямая подстановка этого значения
в решение dsol2 приводит к делению на ноль, поэтому
SymPy игнорирует построение данной кривой. Чтобы
построить недостающую кривую, найдем критическое
решение в явном виде.
1
2
3

par3 = {m: 1, k: 4, r: 4, f0: 0}
dsol3 = dsolve(ode.subs(par3), x, ics = ic)
display(dsol3)

g


g g
x(t) = − +
+t
+ 2x0 + x0 e−2t
4
4
2
12 Добавим найденное критическое решение dsol3 к
построенному ранее графику p3, выделив это решение
красным цветом (см. рис. 5).
1
2
3
4
5
6
7

p4 = plot(size = (8, 6), show = False)
p4.extend(p3)
ds = dsol3.rhs.subs({x0: 0, g: 9.8})
p = plot(ds, (t, 0, 2 * pi), line_color = "red",
show = False)
p4.extend(p)
p4.show()

Критический режим
(красная кривая) затухающих колебаний пружинного
маятника
РИС. 5

Глава 13

138

13 Последний случай, который мы должны рассмотреть, — это случай вынужденных колебаний под действием внешней периодической силы. В этот раз мы
должны обнулить параметр r в уравнении ode. Хотя в
общем виде такое уравнение и решается библиотекой
SymPy, для большей наглядности мы сразу подставим
в уравнение параметры m = 1, k = 4, f0 = 1.
1
2
3

par5 = {m: 1, k: 4, r: 0, f0: 1}
dsol5 = dsolve(ode.subs(par5), x, ics = ic)
display(dsol5)

x(t) = − g4 +

ω0 sin (2t)
2ω02 −8

+

g
4


+ x0 cos (2t) −

sin (ω0 t)
ω02 −4

В полученном решении первое слагаемое соответствует точке покоя x1 , второе и третье — колебаниям маятника с его собственной частотой ω, а последнее слагаемое — колебаниям с частотой внешней силы ω0 .
14 Построим графики найденного решения для разных значений частоты ω0 . В качестве начального условия x0 выберем точку покоя маятника X13 .

3

Вычисленную нами при тех же
параметрах модели в пункте 10.

РИС. 6

1
2
3
4
5
6
7
8

Вынужденные колебания пружинного маятника

p5 = plot(size = (8, 4), show = False)
for om in np.arange(1.25, 3, 0.25):
ds = dsol5.rhs.subs({omega0: om, x0: X1, g: 9.8})
p = plot(ds, (t, 0, 8 * pi), show = False,
label = om)
p5.extend(p)
p5.legend = True
p5.show()

Результат построения приведен на рис. 6. Видно, что
колебания стали более сложными, их амплитуда меняется со временем и зависит от частоты ω0 внешней

Пружинный маятник

139

силы. Известно, что критическим значением частоты
ω0 является собственная частота ω свободных колебаний того же маятника. В нашем случае4 критическая
частота равна 2. При приближении параметра ω0 к
этому значению амплитуда колебаний начинает возрастать, такое явление называется резонансом.
15 При точном совпадении двух указанных частот
(ω0 = ω) решение нашего дифференциального уравнения меняется кардинально. На рис. 6 это решение
отсутствует, т. к. подстановка ω0 = 2 в решение dsol5
приводит к делению на ноль. Найдем резонансное решение явным образом.
1
2
3

par6 = {m: 1, k: 4, r: 0, f0: 1, omega0: 2}
dsol6 = dsolve(ode.subs(par6), x, ics = ic)
display(dsol6)

g
x(t) = − +
4




g
t
sin (2t)
− + x0 cos (2t) +
4 4
8

Коэффициент перед косинусом линейно зависит от t,
что и приводит к неограниченному росту амплитуды
колебаний при t → ∞ — это явление называется математическим резонансом.
16 Последним действием добавим к общему графику
p5 найденное резонансное решение dsol6, выделив его

красным цветом (см. рис. 7).

1
2
3
4
5
6
7

p6 = plot(size = (8, 6), show = False)
p6.extend(p5)
ds = dsol6.rhs.subs({x0: X1, g: 9.8})
p = plot(ds, (t, 0, 8 * pi), line_color = "red",
show = False)
p6.extend(p)
p6.show()

РИС. 7

Явление математического резонанса (красная
кривая)

4

См. знаменатель во втором и
четвертом слагаемых в решении
dsol5.

Глава 13

140

УПРАЖНЕНИЯ И ЗАМЕЧАНИЯ

Схема одного кадра
анимации

РИС. 8

1 Постройте по аналогии с моделью из предыдущей главы анимацию поведения пружинного маятника, описываемого дифференциальным уравнением ode с заданным набором параметров и заданным начальным условием. Каждый кадр такой анимации должен содержать пять объектов (см. рис. 8): горизонтальную линию, представляющую
поверхность, к которой прикреплена пружина; горизонтальную пунктирную линию, показывающую положение
равновесия маятника; точку подвеса пружины; груз маятника; пружину, которую можно изобразить простой линией. Переменными вертикальными координатами обладают
только груз маятника и нижний конец пружины. Протестируйте анимацию на колебаниях различных типов.
2 Постройте по аналогии с рис. 2 графики решения dsol1
(случай свободных колебаний) для разных значений параметров g и m. Как влияет на частоту и амплитуду колебаний увеличение или уменьшение этих двух параметров?
3 Постройте и визуализируйте решение всех трех рассмотренных в главе случаев (свободные колебания, затухающие колебания, вынужденные колебания) для начального условия x(0) = 0, ẋ(0) = v0 .
4 Рассмотрите общий случай, когда на груз маятника
действуют все четыре силы. Интересной особенностью такой модели является то, что в ней оказывается невозможным явление математического резонанса, т. е. амплитуда
колебаний при наличии силы сопротивления не может возрастать неограниченно.
5 Постройте анимированный вариант графика на рис. 6,
в котором параметром анимации является частота внешней силы ω0 .
6 Постройте фазовые портреты свободных и затухающих колебаний. Для этого надо взять решение x(t) соответствующего дифференциального уравнения, вычислить
скорость v(t) = ẋ(t) и построить семейство графиков v(x)
для различных значений x0 в параметрической форме, используя в качестве параметра время t.

РИС. 9 Фазовые

портреты: свободные колебания, затухающие подкритические и надкритические колебания

7 Напишите функцию, которая принимает на вход линейное однородное дифференциальное уравнение с постоянными коэффициентами относительно функции y(x)
y (n) + an−1 y (n−1) + · · · + a1 y 0 + a0 y = 0

(5)

и составляет соответствующее ему так называемое характеристическое уравнение относительно параметра λ
λn + an−1 λn−1 + · · · + a1 λ + a0 = 0.

(6)

Пружинный маятник

141

Корни уравнения (6) называются собственными значениями дифференциального уравнения (5) и полностью определяют его общее решение. Для построения характеристического уравнения нужно выполнить в дифференциальном
уравнении замену x(t) = eλt , вычислить все производные
и сократить полученное соотношение на eλt .
8 Решите следующие линейные однородные уравнения:
1)
2)

y 000 − 2y 00 = 0;
y

000

+ 8y = 0;

3)
4)

y (4) − y = 0;

y (4) + 5y 00 + 4y = 0.

9 Решите следующие начальные задачи для линейных
неоднородных уравнений:
1)
2)
3)
4)

y 00 − 2y 0 + y = x2 − 1, y(0) = 1, y 0 (0) = −1;
y 00 + 3y 0 + 2y = 5 sin x, y(0) = −2, y 0 (0) = 0;
y 00 + 4y = e−2x , y(0) = −1, y 0 (0) = 2;
y 00 − 3y 0 = 4e3x , y(0) = 0, y 0 (0) = 1.

10 Постройте модель движения груза, соединенного пружинами с заданной жесткостью с двумя неподвижными
стенками (рис. 10). Решите и визуализируйте полученное
дифференциальное уравнение. Трением в системе пренебречь.
11 Постройте модель движения двух грузов с массами
m1 и m2 , соединенных пружиной с жесткостью k (рис. 11).
Неизвестными в такой модели должны быть две функции x1 (t) и x2 (t), определяющие положение грузов в момент времени t. Задайте начальные положения и скорости грузов, решите построенное дифференциальное уравнение, визуализируйте найденное решение. Трением в системе пренебречь.
12 Определение собственных частот колебательной системы является важной практической задачей. Если система описывается линейным дифференциальным уравнением высокого порядка, то аналитическое нахождение собственных частот оказывается невозможным. Однако собственные частоты системы, заданной линейным однородным дифференциальным уравнением, можно найти численно с помощью какого-нибудь приближенного метода решения дифференциальных уравнений, например с помощью метода Эйлера. Идея подхода основана на моделировании явления резонанса. Рассмотрим для примера простейшую колебательную систему, описываемую дифференциальным уравнением второго порядка
ẍ + 4x = 0,

(7)

k1

k2
m

Маятник с двумя пружинами
РИС. 10

m1
РИС. 11

ми

k

m2

Модель с двумя груза-

Глава 13

142

собственная частота колебаний которой равна ω = 2. Добавим к системе внешнюю периодическую силу, действующую с частотой ω0 :
ẍ + 4x = sin ω0 t,

(8)

и преобразуем уравнение в систему:
ẋ = v, v̇ = −4x + sin ω0 t.

РИС. 12 Зависимость максимальной амплитуды от частоты ω0

(9)

Теперь будем решать полученную систему численно с нулевыми начальными условиями для разных значений ω0
на одном и том же интервале времени t ∈ [0, T ]. Для каждого найденного решения запомним его максимальную (по
модулю) амплитуду, после чего построим график полученной зависимости максимальной амплитуды колебаний от
частоты ω0 . Максимумы на этом графике и будут соответствовать приближенным значениям собственных частот рассматриваемой системы. Например, для системы (7)
описанная схема дает график, показанный на рис. (12), из
которого хорошо видно, что пик приходится как раз на
собственную частоту ω = 2.
13 Примените подход, описанный в предыдущем упражнении, для численного определения собственных частот
двойного пружинного маятника, показанного на рис. 13.

k2
m2
k1
m1
РИС. 13

Двойной пружинный маятник

ГЛАВА

14

Модель Лотки—Вольтерры
Задача • В 1925 году Альфред Лотка и на год позже
в 1926 году Вито Вольтерра независимо друг от друга предложили простую математическую модель взаимодействия двух видов, в которой один вид (жертвы)
служит источником пищи для второго вида (хищники).
Пусть x(t) обозначает размер популяции хищников
в момент времени t, а y(t) — размер популяции жертв.
Предполагается, что каждая популяция в отсутствие
другой популяции подчиняется закону естественного
роста1 , причем в популяции жертв коэффициент роста (удельная скорость роста популяции) положителен,
т. е. популяция жертв неограниченно растет, а в популяции хищников этот коэффициент отрицателен, т. е.
в отсутствие жертв хищники вымирают:
ẋ = −αx, ẏ = γy, α, γ > 0.

(1)

Взаимодействие популяций (т. е. поедание жертв хищниками) ведет к уменьшению числа жертв и увеличению числа хищников. В простейшем случае изменение
размера каждой популяции будет пропорциональным
частоте встреч двух видов, т. е. произведению xy размеров их популяций. Добавляя это взаимодействие к
уравнениям (1), мы приходим к системе дифференциальных уравнений
ẋ = −αx + βxy,
ẏ = γy − δxy,

Альфред Джеймс Лотка

(2)

называемой моделью Лотки—Вольтерры, или моделью
«хищник—жертва». Все параметры модели α, β, γ и δ
являются строго положительными.

Вито Вольтерра
1

См. главу 6.

144

Глава 14

Модель • Система уравнений (2) является нелиней-

ной, и известно, что она не решается аналитически.
Поэтому мы рассмотрим несколько подходов к приближенному, в том числе к графическому, решению
этой системы с помощью библиотек SymPy и Matplotlib.
1 Подключаем библиотеки SymPy, NumPy и Matplotlib.
2 Создаем символы для положительных параметров
модели α, β, γ и δ. Так как мы начнем с приближенного решения системы (2), то функции x(t) и y(t) объявим пока тоже как символы, а не как функции.
1
2
3

al, bt, gm, dl = symbols("alpha beta gamma delta",
positive = True)
x, y = symbols("x y")
3 Создадим и запомним в переменных dxdt и dydt
выражения, стоящие в правых частях системы (2).

1
2
3

dxdt = -al * x + bt * x * y
dydt = gm * y - dl * x * y
display(dxdt, dydt)

−αx + βxy
−δxy + γy

4 Для приближенного решения системы (2) воспользуемся командой odeint библиотеки SciPy. Для этого
предварительно преобразуем выражения dxdt и dydt в
функции Python.
1
2

fx = lambdify([x, y, al, bt], dxdt)
fy = lambdify([x, y, gm, dl], dydt)
5 Определим функцию dsdt, которая принимает на
вход вектор s, составленный из двух переменных x и y,
переменную t, от которой зависят x и y 2 , и вспомогательные параметры α, β, γ и δ, а возвращает вектор
из двух производных ẋ и ẏ, вычисленных согласно системе (2). В первой строке этой функции мы распаковываем вектор в скаляры, а во второй — вычисляем
правые части системы.

2

В нашем случае правая часть
системы не зависит явно от t, но
эту переменную в списке аргументов надо все-таки указывать для
совместимости с общим случаем.

1
2
3

def dsdt(s, t, a, b, g, d):
x, y = s
return [fx(x, y, a, b), fy(x, y, g, d)]

Модель Лотки—Вольтерры

145

6 Подключаем команду odeint. Фиксируем параметры модели: α = β = 0.5, γ = δ = 1. Определяем сетку
T по переменной t, на которой будем искать приближенное решение заданной системы дифференциальных уравнений. Наконец, вызываем команду odeint.
Первым ее аргументом идет функция, вычисляющая
правые части всех уравнений системы. Опциональный
аргумент y0 задает начальное условие x(0) = 0.25,
y(0) = 1. Аргумент t задает сетку, на которой будут выполняться вычисления. В аргументе args указываются в форме кортежа все дополнительные параметры, которые будут переданы в функцию dsdt (см.
определение этой функции в пункте 5).
1
2
3
4
5
6

from scipy.integrate import odeint
par = (0.5, 0.5, 1.0, 1.0)
T = np.linspace(0, 50, 1000)
dsol = odeint(dsdt, y0 = [0.25, 1.0],
t = T, args = par)
print(dsol)
[[0.25
[0.25011893
[0.25048208
...
[0.37753404
[0.37077453
[0.36421712

1.
]
1.0382489 ]
1.07794783]
0.27373435]
0.28244464]
0.29152923]]

7 Результатом работы команды odeint, как видно,
является двумерный массив, столбцы которого соответствуют неизвестным функциям, а строки — переменной, в нашем случае времени t. Построим графики
найденного решения, соответствующие двум столбцам
массива dsol. Команда в первой строке создает рисунок (figure) и график (axes), команды во второй и третьей строках строят на графике две кривые x(t) и y(t),
в четвертой строке добавляем к графику легенду на
основе указанных меток кривых.
1
2
3
4
5

fig, ax = plt.subplots()
ax.plot(T, dsol[:, 0], label = "хищники")
ax.plot(T, dsol[:, 1], label = "жертвы")
ax.legend()
plt.show()

Результат построения показан на рис. 1. Очевидным
является колебательный характер этих кривых.

Приближенное решение системы (2)
РИС. 1

Глава 14

146

8 Используя те же данные, можем построить фазовую траекторию на плоскости Oxy.
1
2
3
4
5
6

Фазовая траектория,
соответствующая
решению
на рис. 1
РИС. 2

y0
O

P

Q
δx

δx = ẋ(x0 , y0 ) и δy = ẏ(x0 , y0 )

x0

РИС. 3 Построение указателя в
точке P
3

Построение большого числа указателей — это весьма трудоемкая
операция, поэтому ее выполнение
обычно доверяют компьютерам.

Поле направлений системы (2)
РИС. 4

Траектория оказалась замкнутой, что также согласуется с колебательным поведением решения на рис. 1.
9 Приближенным и очень простым методом построения фазового портрета любой автономной системы
второго порядка, например системы (2), является построение поля направлений этой системы. Выберем на
фазовой плоскости произвольную точку P = (x0 , y0 )
(рис. 3) и подставим ее координаты в правые части
системы (2), это даст нам значения производных

δy
α

fig, ax = plt.subplots()
ax.plot(dsol[:, 0], dsol[:, 1],
"skyblue", lw = 3)
ax.set_xlabel(’хищники’)
ax.set_ylabel(’жертвы’)
plt.show()

(3)

в точке P . Построим теперь направленный отрезок,
называемый указателем, который соединяет точку P
с точкой Q(x0 + δx, y0 + δy). Так как любой указатель
является фактически отрезком касательной к фазовой траектории в заданной точке, то, применив описанную процедуру для большего числа точек, равномерно распределенных по некоторой области фазовой
плоскости, мы получим приближенный фазовый портрет исходной системы дифференциальных уравнений
в данной области3 . Так как длины указателей могут
варьироваться в широком диапазоне, то их, как правило, некоторым способом нормируют, например можно
просто использовать указатели фиксированной длины. В библиотеке Matplotlib имеется специальная команда quiver для построения векторных полей, воспользуемся ею для построения поля направлений системы (2). Сначала с помощью команды meshgrid задаем сетку, по которой будут строиться указатели. Затем
вычисляем в каждой точке сетки значения производных, используя функции fx и fy. В седьмой строке
строим искомое поле направлений: первые два аргумента команды quiver задают сетку, следующие два
— координаты векторов. Результат построения показан на рис. 4.

Модель Лотки—Вольтерры

1
2
3
4
5
6
7
8

147

fig, ax = plt.subplots(figsize = (6, 6))
xrange = np.arange(0.1, 4.05, 0.4)
yrange = np.arange(0.1, 4.05, 0.4)
X, Y = np.meshgrid(xrange, yrange)
U = fx(X, Y, 0.5, 0.5)
V = fy(X, Y, 1.0, 1.0)
ax.quiver(X, Y, U, V, color = "orangered")
plt.show()
10 Команда quiver автоматически выполняет нормировку длин указателей к некоторому среднему значению. В нашем случае разброс длин очень большой,
поэтому часть указателей фактически превратились в
точки. Выполним свою нормировку (строки 2 и 3), поделив каждый элемент массивов U и V, т. е. координат
указателей, на длину соответствующего указателя. Результат построения приведен на рис. 5.

1
2
3
4
5

fig, ax = plt.subplots(figsize = (6, 6))
U1 = U / np.sqrt(U ** 2 + V ** 2)
V1 = V / np.sqrt(U ** 2 + V ** 2)
ax.quiver(X, Y, U1, V1, color = "orangered")
plt.show()
11 Совместим (рис. 6) построенное поле направлений
с вычисленной ранее в пункте 8 фазовой траекторией
dsol.

1
2
3
4
5

Поле направлений системы (2) с указателями равной длины
РИС. 5

fig, ax = plt.subplots(figsize = (6, 6))
ax.quiver(X, Y, U1, V1, color = "orangered")
ax.plot(dsol.T[0][:216], dsol.T[1][:216],
"skyblue", lw = 3)
plt.show()
12 Хотя сама система (2) относительно неизвестных
функций x(t) и y(t) и не решается аналитически, дифференциальное уравнение для ее фазовых траекторий
может быть решено аналитически в неявном виде. Поделим второе уравнение системы (2) на первое и заменим отношение ẏ/ẋ на производную dy/dx:

y(γ − δx)
dy
=
.
dx
x(βy − α)

(4)

Получили дифференциальное уравнение первого порядка с разделяющимися переменными относительно
неизвестной функции y(x). Решим это уравнение с помощью SymPy. Для этого введем новый символ yf для
функции y(x), так как в данный момент символ y является символом переменной. Составляем дифференциальное уравнение (4), используя выражения dxdt и
dydt и выполняя замену y на yf.

Поле направлений и
фазовая траектория системы (2)
РИС. 6

148

1
2
3
4

Глава 14

yf = Function("y")(x)
ode = Eq(diff(yf, x), (dydt / dxdt).subs(y, yf))
display(ode)
plt.show()

d
−δxy(x) + γy(x)
y(x) =
dx
−αx + βxy(x)
13 Решаем уравнение ode, как уравнение с разделяющимися переменными, без упрощения результата, т. е.
в неявной форме.
1
2
3

dsol2 = dsolve(ode, yf, hint = "separable",
simplify = False)
display(dsol2)

−α log (y(x)) + βy(x) = C1 − δx + γ log (x)
14 Найденное решение является уравнением фазовых траекторий исходной системы. Для построения
этих траекторий, в принципе, можно использовать команду plot_implicit библиотеки SymPy, но более качественно и быстрее это делается с помощью команды
contour библиотеки Matplotlib. Для этого выразим из
найденного решения константу C1 и запомним полученное выражение в переменной csol.
1
2
3
4

C1 = symbols("C1")
csol = solveset(dsol2, C1).args[0]
csol = csol.subs(yf, y)
display(Eq(csol, C1))

−α log (y) + βy + δx − γ log (x) = C1
Видно, что теперь фазовая траектория для некоторого
значения C1 является соответствующей линией уровня выражения csol. Построив семейство линий уровня этого выражения, мы получим искомый фазовый
портрет.
15 Преобразуем сначала список параметров модели
par в словарь с соответствующими ключами. Подставим этот набор параметров в выражение csol и преобразуем его в функцию Python. Далее определяем но-

вую сетку (существенно более подробную, чем та, которую мы использовали для построения поля направлений). Вычисляем в каждом узле сетки выражение

Модель Лотки—Вольтерры

149

csol. Создаем график и строим на нем семейство линий уровня, параметр levels определяет количество

линий.

1
2
3
4
5
6
7
8
9
10

par2 = dict(zip([al, bt, gm, dl], par))
cf = lambdify([x, y], csol.subs(par2))
xrange = np.arange(0.01, 4, 0.01)
yrange = np.arange(0.01, 4, 0.01)
X1, Y1 = np.meshgrid(xrange, yrange)
C = cf(X1, Y1)
fig, ax = plt.subplots(figsize = (6, 6))
ax.contour(X1, Y1, C, levels = 50,
colors = "skyblue")
plt.show()

Результат построения показан на рис. 7.

Фазовый портрет системы (2)
РИС. 7

16 Последним действием объединим фазовый портрет с полем направлений (см. рис. 8).
1
2
3
4
5

fig, ax = plt.subplots(figsize = (6, 6))
ax.contour(X1, Y1, C, levels = 50,
colors = "skyblue")
ax.quiver(X, Y, U1, V1, color = "orangered")
plt.show()

УПРАЖНЕНИЯ И ЗАМЕЧАНИЯ
1 Решите с помощью команды odeint заданную систему
дифференциальных уравнений с указанными начальными
условиями. Визуализируйте найденное решение в форме:
интегральных кривых x(t) и y(t); фазовой траектории на
плоскости Oxy.
1)
2)
3)
4)

ẋ = 2y − x, ẏ = x − y, x0 = 1, y0 = 1;
ẋ = −x2 y, ẏ = 2x − y, x0 = −1, y0 = 1;
ẋ = sin y − x, ẏ = x2 − y, x0 = 2, y0 = −2;
ẋ = y 2 − y 4 , ẏ = 3x − 2y, x0 = 1, y0 = 0.

2 Постройте с помощью команды quiver поле направлений заданной системы дифференциальных уравнений:
1)
2)
3)
4)

ẋ = 2x + y, ẏ = x − 2y;
ẋ = x2 + y 2 , ẏ = x2 − y 2 ;
ẋ = sin x + cos y, ẏ = cos x + sin y;
ẋ = sin(x + y), ẏ = y − x.

3 Составьте и решите дифференциальное уравнение для
нахождения фазовых траекторий заданной системы. Используя найденное решение, постройте с помощью команды contour фазовый портрет системы.

Фазовый портрет системы (2), совмещенный с полем направлений
РИС. 8

Глава 14

150
1)
2)
3)
4)

ẋ = y 2 − y, ẏ = x + x2 ;
ẋ = y + 1, ẏ = xy;
ẋ = sin x cos y, ẏ = tg x tg y;
sin y
cos x
ẋ =
, ẏ =
.
x
y

4 Преобразуйте заданное дифференциальное уравнение
второго порядка относительно неизвестной функции x(t)
к системе дифференциальных уравнений первого порядка
относительно двух неизвестных функций x(t) и v(t) = ẋ(t)
и постройте ее фазовый портрет по аналогии с предыдущим упражнением:
1)
2)
3)
4)

ẍ = x − x2 ;
ẍẋ2 = x;
ẍ cos ẋ = sin x;
ẍ = (1 − ẋ3 ) sin x.

5 Постройте фазовый портрет системы (2) на всей фазовой плоскости Oxy, а не только в первой ее четверти, т. е.
при x > 0 и y > 0.

4

Тривиальной точкой покоя является точка (0, 0).

6 Найдите нетривиальную точку равновесия (x0 , y0 ) системы (2), лежащую в первой четверти фазовой плоскости4 . В точке равновесия производные ẋ и ẏ должны быть
равны нулю, это значит, что для нахождения таких точек
нужно приравнять правые части dxdt и dydt системы (2) к
нулю и решить полученную алгебраическую систему относительно x и y, например с помощью команды solve. Для
рассмотренного в главе варианта модели искомая точка
равновесия имеет координаты (1, 1).
7 Постройте фазовый портрет системы (2) с помощью
приближенного ее решения (например, командой odeint).
Для этого на интервале [0, x0 ] надо расположить равномерно n точек (см. рис. 9):
xi =

ix0
, i = 1, . . . , n.
n+1

(5)

Для каждой выбранной точки построить приближенное
решение системы с начальным условием
Схема построения фазового портрета с помощью
приближенного решения системы (2), красным маркером отмечена точка равновесия (x0 , y0 )
РИС. 9

x(0) = xi , y(0) = y0

(6)

на достаточно большом интервале времени, чтобы фазовая траектория замкнулась. Используя найденное решение, найти минимальный момент времени (период колебаний), когда траектория замыкается. Этот момент легко
отслеживается сравнением координаты y решения с величиной y0 . После этого надо построить фазовую траекторию
на ее периоде.

Модель Лотки—Вольтерры

151

8 Используя результат выполнения предыдущего упражнения, постройте график зависимости периода колебаний
системы от величины x(0) при условии, что y(0) = y0 .
9 С помощью приближенного решения модели Лотки—
Вольтерры постройте анимацию движения сразу нескольких точек вдоль соответствующих фазовых траекторий на
плоскости Oxy. Используйте начальные условия из упражнения 7 (см. рис. 9). Включите в каждый кадр анимации в
качестве фонового неменяющегося изображения: а) семейство фазовых траекторий, по которым движутся точки;
б) поле направлений системы.
10 Более правдоподобной моделью роста одной популяции является модель Ферхюльста, учитывающая внутривидовую конкуренцию за ресурсы. Если мы модифицируем
соответствующим образом второе уравнение (для жертв) в
модели Лотки—Вольтерры:
ẏ = γy · (1 −

y
) − δxy,
M

(7)

то получим систему, фазовыми траекториями которой являются спирали, стремящиеся к единственной устойчивой
точке равновесия (рис. 10). Постройте и визуализируйте
такую модель средствами библиотек SymPy и Matplotlib
по аналогии с рассмотренной в главе моделью.
11 Рассмотрим простую математическую модель межвидовой конкуренции. Пусть имеется две популяции, размеры которых равны x(t) и y(t). Оба вида зависят от одного
и того же ресурса, доступное количество (объем) r(t) которого в момент времени t задается формулой
r(t) = r0 − ax − by,

(8)

где r0 — естественное количество ресурса (при отсутствии
популяций), a и b — удельные объемы потребления данного
ресурса особями обоих видов. Дифференциальные уравнения, описывающие динамику роста рассматриваемых популяций, имеют одинаковый вид:
ẋ = (α1 r − β1 )x,
ẏ = (α2 r − β2 )y,

(9)

где α1,2 — эффективности потребления ресурсов двумя видами, β1,2 — коэффициенты смертности, определяющие
скорости естественной убыли популяций в случае отсутствия ресурсов. То есть скорость роста популяции пропорциональна ее размеру, причем коэффициент пропорциональности (удельная скорость роста) определяется количеством доступного ресурса и коэффициентом смертности.
Подставляя в (9) формулу для r(t), приходим к системе

РИС. 10 Фазовый

портрет в модели с внутривидовой конкуренцией в популяции жертв

152

Глава 14

дифференциальных уравнений относительно функций x(t)
и y(t):
ẋ = (α1 (r0 − ax − by) − β1 )x,
(10)
ẏ = (α2 (r0 − ax − by) − β2 )y.

Преобразуем эти уравнения к более простому виду: вынесем за скобки коэффициенты α1,2 и обозначим r0 −β1,2 /α1,2
через γ1,2 :
ẋ = α1 (γ1 − ax − by)x,
(11)
ẏ = α2 (γ2 − ax − by)y.

Георгий Францевич Гаузе

Все коэффициенты в (11) являются строго положительными. Проведите качественный анализ данной модели и покажите, что независимо от начального условия ровно один из
двух рассматриваемых в модели видов обязательно вымирает. Этот факт является частным случаем более общего
принципа конкурентного исключения Гаузе: если два вида
занимают одну и ту же экологическую нишу, то или один
вид вымирает, или виды эволюционируют с разделением
своих экологических ниш. Таким образом, если два таких
вида сосуществуют, то между ними должно быть какое-то
экологическое различие.

ГЛАВА 15
Системы реакций первого
порядка
Задача • Кинетика отдельных химических реакций

описывается дифференциальными уравнениями первого порядка. При этом уравнение материального баланса позволяет свести описание всей реакции со многими реагентами к одному дифференциальному уравнению относительно убыли одного из реагентов.
В случае когда вещества-реагенты участвуют сразу в нескольких реакциях, моделирование кинетики
этих реакций приводит уже к системам дифференциальных уравнений относительно нескольких неизвестных функций, даже с учетом уравнения материального баланса. Если к тому же все реакции рассматриваемой системы являются элементарными реакциями
первого порядка, т. е. имеют вид1
A → продукты,
то описывающая этот набор реакций система дифференциальных уравнений будет линейной и однородной.
Примером такого типа системы реакций является так
называемая параллельная реакция с обратимостью в
одной стадии:
A

k1
k2

k

3
B −→
C,

(1)

в которой вещество B превращается (параллельно) в
вещества A и C, при этом первая стадия является обратимой — вещество A превращается обратно в B. Коэффициенты ki в (1) — константы скоростей соответствующих реакций.
Система дифференциальных уравнений, описывающих набор реакций (1), строится с помощью прин-

1

Продуктов реакции может быть
несколько, а реагент — только
один.

Глава 15

154

ципа независимости — для каждого реагента находим скорость изменения его концентрации в каждой
из реакций, после чего складываем найденные скорости. Например, для вещества A скорость изменения
концентрации a согласно реакции A → B равна −k1 a,
согласно реакции B → A эта скорость равна k2 b, в
третьей реакции B → C вещество A не участвует, т. е.
скорость равна нулю. Складывая эти три скорости,
получаем дифференциальное уравнение для a(t):
ȧ = −k1 a + k2 b.

(2)

Выполняя описанные действия для двух других реагентов B и C с концентрациями b и c соответственно, приходим к искомой системе дифференциальных
уравнений:
ȧ = −k1 a + k2 b,
(3)
ḃ = k1 a − (k2 + k3 )b,
ċ = k3 b.
Используем традиционное начальное условие:
a(0) = a0 , b(0) = b0 и c(0) = c0 .

(4)

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

Преобразование Лапласа • Пусть задана некоторая функция x(t), определенная при t ≥ 0. Рассмотрим несобственный интеграл с параметром s следующего вида:
Z


x(t)e−st dt.

(5)

0

2

Это предположение справедливо для достаточно широкого класса функций — синусов и косинусов, многочленов, экспонент вида
eat при a < s, а также произведений этих функций. В общем случае сходимость интеграла (5) надо
доказывать специально.

Во всех последующих выкладках будем предполагать,
что все встречающиеся несобственные интегралы являются сходящимися2 . Интеграл (5) для каждого допустимого значения s будет равен некоторому числу,
т. е. он представляет собой некоторую функцию X(s).
Эта функция и называется преобразованием Лапласа
функции x:
X(s) = L [x(t)].
(6)

Системы реакций первого порядка

155

В формуле (6) функция X называется образом функции x, а функция x — прообразом функции X.
Для примера найдем образ функции eat :
Z

L [eat ] =



0

Z

eat · e−st dt =



Преобразования Лапласа некоторых функций
ТАБЛ. 1

e(a−s)t dt =

x(t)

0

=

e(a−s)t
a−s


0

=

1
, (7)
s−a

a
tn

т. к. верхняя подстановка t = ∞ обращает экспоненту
в ноль (в силу сходимости несобственного интеграла),
а нижняя t = 0 — в единицу. Следовательно, образом
функции eat является функция 1/(s − a). Преобразования Лапласа некоторых других функций приведены
в табл. 1.
Преобразование Лапласа является линейным, т. е.
образ линейной комбинации двух функций является
линейной комбинацией с теми же коэффициентами образов этих функций:
L [a1 x1 + a2 x2 ] = a1 L [x1 ] + a2 L [x2 ].

e

teat
tn eat
sin at
cos at
x sin at

(8)

x cos at

Это свойство непосредственно вытекает из аналогичного свойства интегралов.
Вычислим теперь пребразование Лапласа производной функции x(t), для чего воспользуемся формулой интегрирования по частям3 :
L [ẋ] =

Z

0



ẋ · e−st dt =

= x(t)e

−st


0

+s

at

eat sin bt
eat cos bt

X(s) = L [x]
a
s
n!
sn+1
1
s−a
1
(s − a)2
n!
(s − a)n+1
a
s2 + a2
s
s2 + a2
2as
(s2 + a2 )2
s2 − a2
(s2 + a2 )2
b
(s − a)2 + b2
s−a
(s − a)2 + b2

3

Z

0



Для этого в формуле интегрирования по частям
Z
Z
udv = uv − vdu

x(t) · e−st dt =
= −x(0) + sL [x]. (9)

Аналогичным образом можно вывести формулы для
производных любого порядка.
Известно, что преобразование Лапласа g = L [x]
является взаимно однозначным, т. е. существует и обратное преобразование, переводящее функцию X(s)
обратно в функцию x(t)4 :
x = L −1 [X].

(10)

Несложно показать, что обратное преобразование,
как и прямое, также обладает свойством линейности.

полагаем u = e−st , dv = ẋdt, тогда du = −se−st dt, v = x.

4

Примеры обратного преобразования Лапласа можно получить из
той же табл. 1.

156

Глава 15

Модель • Рассмотрим процесс решения системы (3)

с помощью последовательного применения прямого и
обратного преобразований Лапласа, поддержка которых включена в библиотеку SymPy.
1 Подключаем библиотеки SymPy и Matplotlib.
2 Продемонстрируем правила работы с преобразованием Лапласа в SymPy на простом примере. Cоздадим
два положительных символа t и s. Определим выражение x = e−2t .
1
2
3

t, s = symbols("t s", positive = True)
x = exp(-2 * t)
display(x)

e−2t
3 Прямое преобразование Лапласа выполняется командой laplace_transform, которая принимает на вход
три аргумента: преобразуемую функцию; переменную,
от которой зависит эта функция; переменную, от которой должна зависетьпреобразованная функция (образ). Результатом работы данной команды является
кортеж, содержащий найденное преобразование и набор условий, при которых это преобразование является корректным.
1

laplace_transform(x, t, s)
(1/(s + 2), -oo, True)
4 Если нас интересует только образ заданной функции, то для этого можно использовать опцию noconds
команды laplace_transform5 . Сохраним образ выражения x в переменной X.

5

Или использовать индексацию:
laplace_transform(x, t, s)[0].
1
2

X = laplace_transform(x, t, s, noconds = True)
display(X)

1
s+2
5 Для большей компактности и ясности кода определим короткую версию команды laplace_transform и
применим ее для преобразования выражения t2 .

Системы реакций первого порядка
1
2
3
4
5

157

def L(x):
return laplace_transform(x, t, s,
noconds = True)
X = L(t ** 2)
display(X)

2
s3
6 Обратное преобразование выполняется командой
inverse_laplace_transform, принимающей на вход так-

же три аргумента: функцию и две переменные. Эта команда возвращает только прообраз заданной функции
без дополнительных условий. Сразу создадим короткий вариант этой команды и применим его к найденному выше выражению X, на выходе получаем исходное
выражение t2 .
1
2
3

def invL(X):
return inverse_laplace_transform(X, s, t)
display(invL(X))

t2
7 Перейдем теперь к решению рассматриваемой модели. Определим функции a(t), b(t) и c(t), представляющие концентрации реагентов A, B и C.
1
2

a, b, c = (Function(f)(t) for f in "abc")
display(a, b, c)

a(t)
b(t)
c(t)

1
2

8 Создаем символы для начальных концентраций.
a0, b0, c0 = [symbols(f + "0") for f in "abc"]
display(a0, b0, c0)

a0
b0
c0
9 Задаем константы k1 , k2 и k3 скоростей химических реакций.
1
2

k1, k2, k3 = 2, 1, 2
print(k1, k2, k3)

158

Глава 15

2 1 2

1
2
3
4

10 Определяем правые части системы (3).
rhsa = - k1 * a + k2 * b
rhsb = k1 * a - (k2 + k3) * b
rhsc = k3 * b
display(rhsa, rhsb, rhsc)

−2a(t) + b(t)
2a(t) − 3b(t)
2b(t)

11 Выпишем в явном виде построенную систему дифференциальных уравнений.
1
2
3
4

odea = Eq(diff(a, t), rhsa)
odeb = Eq(diff(b, t), rhsb)
odec = Eq(diff(c, t), rhsc)
display(odea, odeb, odec)

d
a(t) = −2a(t) + b(t)
dt
d
b(t) = 2a(t) − 3b(t)
dt
d
c(t) = 2b(t)
dt

6
Обратите внимание, как SymPy
обозначает преобразование Лапласа неопределенной функции.

12 Теперь мы должны применить к каждому уравнению построенной системы преобразование Лапласа.
Команда laplace_transform, однако, не умеет преобразовывать производные неизвестных функций. Поэтому нам нужно самостоятельно определить команду,
соответствующую формуле (9). Применим эту команду к функции a(t)6 .
1
2
3

def Ld(x, x0):
return s * L(x) - x0
display(Ld(a, a0))

−a0 + sLt [a(t)] (s)
13 Применим преобразование Лапласа к каждому из
трех уравнений системы. Для преобразования левых
частей (производные) используем команду Ld с указанием соответствующих начальных условий, для преобразования правых частей — команду L. В результате
получаем систему алгебраических уравнений относительно образов функций a(t), b(t) и c(t).

Системы реакций первого порядка

1
2
3
4

159

eqa = Eq(Ld(a, a0), L(rhsa))
eqb = Eq(Ld(b, b0), L(rhsb))
eqc = Eq(Ld(c, c0), L(rhsc))
display(eqa, eqb, eqc)

−a0 + sLt [a(t)] (s) = −2Lt [a(t)] (s) + Lt [b(t)] (s)
−b0 + sLt [b(t)] (s) = 2Lt [a(t)] (s) − 3Lt [b(t)] (s)

−c0 + sLt [c(t)] (s) = 2Lt [b(t)] (s)

14 Для упрощения полученных формул введем специальные символы A(s), B(s) и C(s), обозначающие
образы функций a(t), b(t) и c(t).
1
2

A, B, C = [Function(F)(s) for F in "ABC"]
display(A, B, C)

A(s)
B(s)
C(s)
15 Сформируем замену вида Lt [x(t)] → X(s) и применим ее к уравнениям eqa, eqb и eqc.
1
2
3
4
5

Z = {L(a): A, L(b): B, L(c): C}
eqa2 = eqa.subs(Z)
eqb2 = eqb.subs(Z)
eqc2 = eqc.subs(Z)
display(eqa2, eqb2, eqc2)

−a0 + sA(s) = −2A(s) + B(s)

−b0 + sB(s) = 2A(s) − 3B(s)

−c0 + sC(s) = 2B(s)

16 Решим с помощью команды solve построенную
алгебраическую систему. Результатом работы данной
команды является словарь, ключами которого служат
имена переменных.
1
2

sol = solve((eqa2, eqb2, eqc2), (A, B, C))
print(sol)
A(s):(a0*s+3*a0+b0)/(s**2+5*s+4),
B(s):(2*a0+b0*s+2*b0)/(s**2+5*s+4),
C(s):(4*a0+2*b0*s+4*b0+c0*s**2+5*c0*s+4*c0)/(s**3+5*s**2+4*s)

Глава 15

160

17 Извлекаем из словаря отдельные решения.
1
2

sola, solb, solc = sol[A], sol[B], sol[C]
display(sola, solb, solc)

a0 s + 3a0 + b0
s2 + 5s + 4
2a0 + b0 s + 2b0
s2 + 5s + 4
4a0 + 2b0 s + 4b0 + c0 s2 + 5c0 s + 4c0
s3 + 5s2 + 4s
18 Применяем обратное преобразование Лапласа к
каждому из трех последних выражений и получаем
решение исходной системы дифференциальных уравнений.
1
2
3
4

dsa = invL(sola)
dsb = invL(solb)
dsc = invL(solc)
display(dsa, dsb, dsc)


a0 − b0 + (2a0 + b0 ) e3t e−4t
3

−2a0 + 2b0 + (2a0 + b0 ) e3t e−4t
3

a0 − b0 − 2 (2a0 + b0 ) e3t + 3 (a0 + b0 + c0 ) e4t e−4t
3
19 Построим графики найденного решения для начального условия a0 = 1, b0 = c0 = 0.
Динамика системы (3)
при условии, что в начальный
момент времени в растворе присутствует только вещество A
РИС. 1

1
2
3
4
5
6

par1 = {a0: 1, b0: 0, c0: 0}
p = plot(show = False, legend = True)
for ds, lab in zip([dsa, dsb, dsc], "ABC"):
p.extend(plot(ds.subs(par1), (t, 0, 3),
label = lab, show = False))
p.show()

Результат построения показан на рис. 1.

УПРАЖНЕНИЯ И ЗАМЕЧАНИЯ
1 Выполните с помощью SymPy преобразование Лапласа
функций, перечисленных в табл. 1.

Системы реакций первого порядка

161

2 Решите в SymPy с помощью преобразования Лапласа
систему уравнений (3) для заданного набора параметров
модели:
1)
2)
3)
4)

k1
k1
k1
k1

= 3, k2 = 1, k3 = 4, a0 = 0, b0 = 1, c0 = 0;
= 1, k2 = 2, k3 = 1, a0 = 1, b0 = 2, c0 = 0;
= 1, k2 = 20, k3 = 10, a0 = 1, b0 = 1, c0 = 0;
= 25, k2 = 5, k3 = 1, a0 = 2, b0 = 1, c0 = 0.

Динамика последней системы показана на рис. 2.
3 В системе (3) первые два уравнения не зависят от функции c(t), поэтому их можно решать отдельно, как систему
из двух уравнений:
ȧ = −k1 a + k2 b,
ḃ = k1 a − (k2 + k3 )b.

Динамика системы (1)
для последнего набора параметров из упражнения 2
РИС. 2

(11)

Постройте фазовый портрет такой редуцированной системы на фазовой плоскости Oab для заданного набора параметров k1 и k2 (см. рис. 3).
4 Постройте системы дифференциальных уравнений для
заданных наборов реакций первого порядка и решите их с
помощью преобразования Лапласа, используя начальные
условия a0 = 1, b0 = 0 и c0 = 0 (константы скоростей для
всех указанных реакций считайте равными k = 1):
1)
2)

A → B;
A  B;

3)
4)

A → B → C;
A → B → C → A.

5 Решите с помощью преобразования Лапласа следующие неоднородные дифференциальные уравнения:
1)
2)
3)
4)

y0
y0
y0
y0

= 3y + 2, y(0) = 0;
= −y + x, y(0) = −1;
= y + ex , y(0) = 1;
= 2y + sin x, y(0) = 2.

6 Выведите рекуррентную формулу для преобразования
Лапласа k-й производной функции x(t). Модифицируйте
соответствующим образом введенную нами выше функцию Ld, добавив к ней параметр k и заменив начальное
условие x0 на список из k начальных условий.
7 Решите, используя результаты предыдущего упражнения, следующие начальные задачи для дифференциальных уравнений второго порядка:
1)
2)
3)
4)

y 00 + 3y 0 y + 2y = 0, y(0) = 1, y 0 (0) = 0;
y 00 − 2y 0 + y = 0, y(0) = 0, y 0 (0) = −1;
y 00 + y = 0, y(0) = −1, y 0 (0) = 0;
y 00 − 2y 0 + 2y = 0, y(0) = 0, y 0 (0) = 1.

Фазовый портрет системы (11) для случая k1 = 2,
k2 = 1
РИС. 3

Глава 15

162

8 Разработайте вариант функции L(x), который был бы
применим не только к выражениям, но и к уравнениям,
а также корректно обрабатывал бы производные любого
порядка, входящие в качестве слагаемых в заданное выражение (уравнение).
9 Обратное преобразование Лапласа рациональных выражений выполняется по следующей схеме: сначала дробь
раскладывается в линейную комбинацию простых дробей7 ;
после этого для каждой полученной простой дроби в таблице преобразований ищется ее прообраз; все найденные
прообразы складываются с коэффициентами исходной линейной комбинации:

7

Этот же прием используется при
вычислении интегралов от рациональных функций.

2
1
s−3
=

→ 2e−t − et .
s2 − 1
s+1
s−1

(12)

В SymPy разложение рациональной функции в сумму простых дробей выполняется с помощью команды apart, первым аргументом которой указывается рациональное выражение, вторым — переменная, по которой выполняется
разложение:
1
2

out = apart((s - 3) / (s ** 2 - 1), s)
display(out)
2
1

s+1
s−1
10 Разложите с помощью команды apart следующие рациональные выражения в суммы простейших дробей.
1)
2)

x
;
x3 − 1
8
;
x4 − 16

3)
4)

x2 + 1
;
x3 − x
1 − 2x
.
x2 − 4x + 3

ГЛАВА

16

Геодезические линии
Задача • Классической задачей геодезии является нахождение кривых наименьшей длины, соединяющих две заданные точки на поверхности Земли. Такие кривые называются геодезическими линиями, или
просто геодезическими.
В случае небольших расстояний1 поверхность Земли можно считать практически плоской2 . Тогда кратчайшей кривой между двумя точками будет, очевидно,
отрезок соединяющей их прямой линии (рис. 1). Для
больших расстояний нужно учитывать искривленную
форму поверхности Земли. Если предположить, что
этой формой является сфера, то кратчайшей кривой
будет ортодрома — короткая дуга большой окружности, проходящей через две заданные точки (рис. 2).
На практике, однако, для построения геодезических кривых требуется учитывать как перепад высот
в рассматриваемой местности, например наличие возвышенностей или впадин, так и тот факт, что реальной формой Земли является не сфера, а так называемый геоид. Оба этих фактора существенно усложняют
процесс нахождения геодезических кривых на поверхности Земли.
В настоящей главе мы рассмотрим общий подход к
построению геодезических для заданной поверхности
в трехмерном пространстве, результатом применения
которого является дифференциальное уравнение, описывающее семейство всех геодезических линий на данной поверхности.
Пусть поверхность задана параметрически системой соотношений
x = x(u, v), y = y(u, v), z = z(u, v).

(1)

1

За этот случай отвечает специальный раздел геодезии, называемый топографией.
2

Конечно, при условии отсутствия в данном месте значительных перепадов высот, что справедливо, например, для водных поверхностей — озер, морей и т. д.

Кратчайший путь на
плоскости
РИС. 1

РИС. 2

сфере

Кратчайший путь на

Глава 16

164

Рассмотрим на этой поверхности две точки A и B,
которым соответствуют значения параметров (u1 , v1 )
и (u2 , v2 ), и кривую, соединяющую указанные точки,
определяемую некоторой функцией v(u). Длина такой
кривой равна следующему интегралу вдоль нее:
L=

ZB

ds,

(2)

A

где ds — длина бесконечно малого элемента (дуги)
данной кривой. Согласно теореме Пифагора
p
(3)
ds = dx2 + dy 2 + dz 2 .

Выразим из (1) дифференциалы dx, dy и dz через дифференциалы du и dv:
∂x
∂x
du +
dv,
∂u
∂v
∂y
∂y
du +
dv,
dy =
∂u
∂v
∂z
∂z
dz =
du +
dv,
∂u
∂v
dx =

подставим эти выражения в (3):
p
ds = P · du2 + Q · dudv + R · dv 2 ,

(4)

(5)

где P , Q и R — некоторые функции переменных u и v,
однозначно определяемые уравнением рассматриваемой поверхности (1):
2  2  2
∂y
∂z
∂x
+
+
,
P (u, v) =
∂u
∂u
∂u


∂x ∂x ∂y ∂y
∂z ∂z
Q(u, v) = 2
+
+
,
∂u ∂v
∂u ∂v
∂u ∂v
 2  2  2
∂x
∂y
∂z
R(u, v) =
+
+
.
∂v
∂v
∂v


(6)

Вынесем в (5) дифференциал du из-под корня и заменим отношение dv/du на производную v 0 :
p
ds = P + Qv 0 + Rv 02 du.
(7)

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

Геодезические линии

165

такую функцию v(u), удовлетворяющую граничным
условиям
v(u1 ) = v1 , v(u2 ) = v2 ,
(8)
которая бы доставляла минимум интегралу
L[v] =

Zu2 p

P + Qv 0 + Rv 02 du

(9)

u1

для заданных функций P (u, v), Q(u, v) и R(u, v).

Уравнение Эйлера—Лагранжа • Интеграл вида (9) называется функционалом, неформально говоря — функцией от функции. Задача поиска функции,
доставляющей экстремум заданному функционалу, является стандартной задачей вариационного исчисления. Рассмотрим такую задачу для функционала следующего вида:
L[y] =

Zx2

F (x, y, y 0 )dx,

(10)

x1

значение которого зависит от заданной функции y(x)
и ее производной y 0 (x)3 . Дополнительно потребуем,
чтобы на концах интервала интегрирования функция
y(x) принимала заданные значения y1 и y2 соответственно:
y(x1 ) = y1 , y(x2 ) = y2 .
(11)
Из теории вариационного исчисления известно, что
функция является экстремалью функционала (10), если она удовлетворяет уравнению Эйлера—Лагранжа:
d ∂F
∂F

= 0.
∂y
dx ∂y 0

(12)

Уравнение (12) является дифференциальным уравнением второго порядка, вместе с дополнительными граничными условиями (11) оно составляет так называемую краевую задачу.
Важным частным случаем является задача минимизации функционала (10), подыинтегральная функция F в котором не зависит явным образом от переменной x:
Zx2
L[y] = F (y, y 0 )dx.
(13)
x1

3

Всюду ниже мы будем предполагать нужную степень гладкости рассматриваемых функций,
как минимум их дифференцируемость.

Глава 16

166

Можно показать, что для функционалов такого вида
уравнение Эйлера—Лагранжа сводится к более простому дифференциальному уравнению первого порядка, называемому тождеством Бельтрами:
F − y0

∂F
= C1 ,
∂y 0

(14)

где C1 — некоторая произвольная константа.

Модель • Рассмотрим задачу построения геодези-

ческих кривых на поверхности прямого кругового конуса с помощью библиотеки SymPy. Поверхность такого конуса в цилиндрических координатах описывается
уравнением
z = γr,
(15)

Эудженио Бельтрами

где коэффициент γ > 0 определяет угол раствора конуса. Следовательно, мы можем параметризовать коническую поверхность с помощью двух полярных координат r и ϕ:
x = r cos ϕ, y = r sin ϕ, z = γr.

(16)

Для краткости изложения рассмотрим фиксированное
значение параметра γ = 4.
1 Подключаем библиотеки SymPy, NumPy и Matplotlib.
2 Создаем символы r и ϕ, задаем значение параметра γ и определяем формулы (16).
1
2
3
4
5
6

r, phi = symbols("r phi")
gamma = 4
x = r * cos(phi)
y = r * sin(phi)
z = gamma * r
display(x, y, z)

r cos (ϕ)
r sin (ϕ)
4r
3 Вычисляем частные производные выражений (16)
по ϕ и r, с помощью которых согласно формулам (6)
определяем функции P , Q и R. В качестве переменной
u в нашем случае выступает параметр ϕ, в качестве
переменной v — параметр r.

Геодезические линии

1
2
3
4
5
6
7

dxdr, dxdp = diff(x, r),
dydr, dydp = diff(y, r),
dzdr, dzdp = diff(z, r),
P = dxdp ** 2 + dydp **2
Q = dxdr * dxdp + dydr *
R = dxdr ** 2 + dydr **2
display(P, Q, R)

167
diff(x, phi)
diff(y, phi)
diff(z, phi)
+ dzdp ** 2
dydp + dzdr * dzdp
+ dzdr ** 2

r2 sin2 (ϕ) + r2 cos2 (ϕ)
0
sin2 (ϕ) + cos2 (ϕ) + 16
4 Введем символ rf для обозначения положительной функции r(ϕ) и символ rf_ для производной этой
функции. Определим подынтегральное выражение F
согласно (9), заменив в этом выражении символ переменной r на символ функции rf, и упростим полученное выражение4 .
1
2
3
4
5

rf = Function("r", positive = True)(phi)
rf_ = diff(rf, phi)
F = sqrt(P + Q * rf_ + R * rf_ ** 2)
F = F.subs(r, rf).simplify()
display(F)

s

r2 (ϕ) + 17



2
d
r(ϕ)


5 Введем два параметра (две константы интегрирования) k и α, первый из которых будем использовать
вместо параметра C1 в тождестве Бельтрами (14), а
второй — при решении соответствующего дифференциального уравнения. Создаем, упрощаем и сохраняем
в переменной B уравнение Бельтрами.
1
2
3

k, alpha = symbols("k alpha", positive = True)
B = Eq(F - rf_ * diff(F, rf_), k).simplify()
display(B)

k=r

r2 (ϕ)

2
d
r2 (ϕ) + 17 dϕ
r(ϕ)

6 Разрешаем полученное соотношение относительно
производной rf_.

4

Нетрудно показать, что «волшебная» константа 17 в полученном выражении равна 1 + γ 2 .

Глава 16

168
1
2

sol = solve(B, rf_)
display(sol)
-sqrt(17)*sqrt((-k**2+r(phi)**2)*r(phi)**2)/(17*k)
sqrt(17)*sqrt(-(k**2-r(phi)**2)*r(phi)**2)/(17*k)
7 Из двух найденных решений выбираем решение со
знаком плюс и составляем искомое дифференциальное
уравнение.

1
2

ode = Eq(rf_, sol[1])
display(ode)

d
r(ϕ) =


p
−17k 2 + 17r2 (ϕ)r(ϕ)
17k

8 Находим общее решение построенного дифференциального уравнения.
1
2

dsol = dsolve(ode, rf)
display(dsol)

r(ϕ) =




k √


17ϕ
cos C1 k− 17

NaN

for k >

k √


17ϕ
cos C1 k− 17

otherwise

9 Используя несколько раз атрибут args, извлекаем
из выражения dsol функцию, представляющую решение дифференциального уравнения.
1
2

rsol = dsol.args[1].args[0].args[0]
display(rsol)



k

cos C1 k −



17ϕ
17



10 Заменим в найденном решении константу C1 на
отношение α/k (оба параметра являются произвольными константами, т. е. и их отношение также будет
произвольной константой) и выпишем окончательное
решение в терминах символов r и ϕ.
1
2
3

C1 = symbols("C1")
rsol = rsol.subs(C1, alpha / k)
display(Eq(r, rsol))

Геодезические линии

r=



k

cos α −



17ϕ
17

169



11 Построим график полученной зависимости r(ϕ)
при фиксированных значениях параметров k = 1 и
α = 0 (см. рис. 3).
1
2
3

par = {k: 1, alpha: 0}
rs = rsol.subs(par)
plot(rs, (phi, -8 * pi, 8 * pi), ylim = (-10, 10))

Заметим, что параметр r по своему определению (полярный радиус) является положительной величиной,
поэтому геометрический смысл имеют только части
графика 3, расположенные в его верхней полуплоскости.
12 Построим кривую r(ϕ) в полярных координатах
на плоскости Oxy в диапазоне от −ϕ0 до ϕ0 , где ϕ0
задает положение первой положительной асимптоты
графика на рис. 3. Несложно убедиться, что период T
функции r(ϕ) равен периоду ее знаменателя, т. е.
p
T = 2π 1 + γ 2 .
(17)

Тогда

π
T
=
ϕ0 =
4
1
2
3
4
5

p

1 + γ2
.
2

(18)

2
3

График зависимости
r(ϕ) на плоскости Oxy для
ϕ ∈ (−ϕ0 , ϕ0 )
РИС. 4

phi0 = pi * sqrt(1 + gamma ** 2) / 2
xs, ys = x.subs(r, rs), y.subs(r, rs)
plot_parametric((xs, ys), (phi, -phi0, phi0),
xlim = (-5, 5), ylim = (-5, 5),
aspect_ratio = (1, 1))
13 Так как отношение периода функции r(ϕ) к «естественному» периоду 2π полярной системы координат
является иррациональным, то никакие из ветвей графика на рис. 3 при их визуализации на плоскости Oxy
не должны совпадать друг с другом. Построим на одном графике несколько таких кривых (рис. 5). Чтобы избежать прорисовки прямых линий, соединяющих
разные ветви, изменим пределы a и b каждого периода
на небольшую величину ±0.01.

1

График зависимости
r(ϕ), вертикальные линии соответствуют асимптотам
РИС. 3

p1 = plot(xlim = (-5, 5), ylim = (-5, 5),
aspect_ratio = (1, 1), show = False)
for i in range(-17, 17, 2):

Кривые, описываемые
выражением rsol при k = 1,
α=0
РИС. 5

Глава 16

170

4
5
6
7
8
9
10

a =
b =
xs,
p =

i * phi0 + 0.01
(i + 2) * phi0 - 0.01
ys = x.subs(r, rs), y.subs(r, rs)
plot_parametric((xs, ys), (phi, a, b),
show = False)
p1.extend(p)
p1.show()
14 Перейдем теперь к визуализации найденных геодезических кривых на поверхности конуса с помощью
библиотеки Matplotlib. Для этого сначала преобразуем выражения x, y и z в функции Python, подставив в
них вместо символа r частное решение rs дифференциального уравнения.

1
2
3
4

X = lambdify([phi], x.subs(r, rs))
Y = lambdify([phi], y.subs(r, rs))
Z = lambdify([phi], z.subs(r, rs))
print(X(0), Y(0), Z(0))
1.0 0.0 4.0

РИС. 6

15 Построим график поверхности рассматриваемого
конуса (см. рис. 6). Для этого перепишем уравнение
поверхности конуса z = γr в декартовых координатах:
p
z = γ x2 + y 2 .
(19)

Поверхность конуса

В первой строке подключаем библиотеку для работы
с трехмерными графиками. Далее создаем сетку SX,
SY и на этой сетке вычисляем z-координаты точек согласно (19). После этого создаем трехмерный график
и строим поверхность, заданную массивами SX, SY и
SZ. Параметр alpha определяет прозрачность поверхности. Команда view_init в предпоследней строке задает трехмерную ориентацию графика5 .

5

Аргументами данной команды
служат два угла поворота в градусах.

1
2
3
4
5
6
7
8
9

from mpl_toolkits.mplot3d import Axes3D
xrange = np.arange(-3.0, 3.0, 0.05)
SX, SY = np.meshgrid(xrange, xrange)
SZ = gamma * np.sqrt(SX ** 2 + SY ** 2)
fig = plt.figure(figsize = (8, 8))
ax = fig.add_subplot(111, projection = "3d")
ax.plot_surface(SX, SY, SZ, alpha = 0.3)
ax.view_init(10, -30)
plt.show()
16 Теперь построим трехмерную кривую, заданную
параметрически функциями X, Y и Z. Чтобы рисуемая

Геодезические линии

171

кривая не вышла за пределы графика, найдем минимальный угол ϕ1 , при котором функция z(ϕ) принимает значение, равное максимальному элементу в массиве SZ (т. е. верхнему пределу по оси z нашего графика). Далее определяем диапазон Phi значений угла
ϕ (от −ϕ1 до ϕ1 ), создаем трехмерный график и строим искомую кривую с помощью стандартной команды
plot.
1
2
3
4
5
6
7
8
9

g = float(sqrt(1 + gamma ** 2))
phi1 = g * np.arccos(gamma / np.max(SZ))
Phi = np.linspace(-phi1, phi1, 200)
fig = plt.figure(figsize = (8, 8))
ax = fig.add_subplot(111, projection = "3d")
ax.plot(X(Phi), Y(Phi), Z(Phi),
color = "red", lw = 2)
ax.view_init(10, -30)
plt.show()

Результат построения приведен на рис. 7.
17 Последним шагом объединим на одном рисунке
построенные выше графики поверхности конуса и геодезической кривой (см. рис. 8).
1
2
3
4
5
6
7

РИС. 7

Геодезическая кривая

fig = plt.figure(figsize = (8, 8))
ax = fig.add_subplot(111, projection = "3d")
ax.plot_surface(SX, SY, SZ, alpha = 0.25)
ax.plot(X(Phi), Y(Phi), Z(Phi),
color = "red", lw = 2)
ax.view_init(10, -30)
plt.show()

Геодезическая кривая
на поверхности конуса
РИС. 8

УПРАЖНЕНИЯ И ЗАМЕЧАНИЯ
1 Найденное нами в пункте 10 уравнение геодезической
кривой зависит от двух произвольных параметров (констант интегрирования) k и α. Очевидно, что параметр k
является масштабным множителем, а параметр α задает поворот всей кривой относительно оси Oz. Это значит,
что для заданной поверхности (при фиксированном значении γ) все геодезические линии являются подобными,
они отличаются друг от друга только размером и углом
поворота. Конкретные значения данных двух параметров
могут быть найдены из дополнительных граничных условий, что искомая геодезическая кривая проходит через две
заданные точки:
r(ϕ1 ) = r1 , r(ϕ2 ) = r2 .

(20)

Глава 16

172

Для рассматриваемой конической поверхности, однако, соответствующая алгебраическая система в общем случае не
решается аналитически, а может быть решена только приближенными методами.
2 Исследуйте, как влияет на внешний вид геодезических
кривых изменение параметра γ.
3 При построении трехмерных графиков с помощью библиотеки Matplotlib в системе Jupyter Notebook может оказаться более удобным интерактивный режим их показа,
когда пользователь может прямо при просмотре графика
менять углы его поворота. Для этого надо первой строкой
кода указать так называемую «магическую» команду
%matplotlib qt
и перезапустить ядро блокнота. После этого все графики
будут строиться в отдельных окнах (рис. (9)), а для трехмерных графиков появится возможность их вращения.

РИС. 9 Окно интерактивного
показа трехмерных графиков
в Jupyter Notebook

4 Для заданной функции F (x, y 0 ) постройте и решите
краевую задачу для уравнения Эйлера—Лагранжа с учетом указанных граничных условий. Так как подынтегральная функция не зависит явным образом от y, то уравнение
Эйлера—Лагранжа сразу может быть сведено к дифференциальному уравнению первого порядка:


d ∂F
∂F
=0⇒
= C1 .
dx ∂y 0
∂y 0

1)

F (x, y 0 ) = x2 y 02 , y(1) = 1, y(2) = 2;

2)

F (x, y 0 ) = y 0 + xy 02 , y(1) = 0, y(e) = 1;
p
F (x, y 0 ) = x2 + y 02 , y(0) = 1, y(1) = 2;

3)
4)

(21)

F (x, y 0 ) = y 02 + y 0 sin x, y(0) = 0, y(π/2) = 1.

5 Найдите общее решения уравнение Эйлера—Лагранжа,
записанного в форме тождества Бельтрами, для заданной
функции F (y, y 0 ):
1)

F (y, y 0 ) = yy 02 ;

2)

F (y, y 0 ) = ey y 03 ;

3)
4)

y 02
F (y, y 0 ) = √ ;
y
2y
+1
F (y, y 0 ) =
.
y0

6 Найдите общее решения уравнение Эйлера—Лагранжа
для заданного функционала L[y]:

Геодезические линии

1)

173

L[y] =

Zb

(y 2 + y sin x − y 02 )dx;

L[y] =

Zb

(xex y 0 + y 2 + 4y 02 )dx;

L[y] =

Zb

(y ln x + y 02 )dx;

L[y] =

Zb

(y 03 − xy 0 + y)dx.

a

2)

a

3)

a

4)

a

7 Докажите с помощью решения уравнения Эйлера—Лагранжа, что геодезическими кривыми на плоскости являются прямые линии.
8 Постройте уравнение семейства геодезических кривых
на поверхности прямого кругового цилиндра радиуса 1,
уравнение которой в цилиндрических координатах тривиально: r = 1 (рис. 10). Поэтому для параметризации данной поверхности можно использовать полярный угол ϕ и
высоту z:
x = cos ϕ, y = sin ϕ, z = z.
(22)

Цилиндрическая поверхность
РИС. 10

9 Выведите уравнение геодезических кривых на сферической поверхности единичного радиуса с центром в начале координат. Для построения функционала используйте
уравнение сферы в сферических координатах (рис. 11)

z
A

r=1
и соотношения между сферическими и декартовыми координатами:
x = r sin θ cos ϕ,
y = r sin θ sin ϕ,
z = r cos θ.
Покажите, что полученное уравнение геодезической кривой в самом деле соответствует большим окружностям сферы.
10 Для поверхности, заданной уравнением z = f (x, y),
постройте семейство ее геодезических линий, исходящих
из заданной точки (x0 , y0 ). Параметризация (1) такой поверхности тривиальна:
x = x, y = y, z = f (x, y).

(23)

Преобразуйте соответствующее дифференциальное уравнение Эйлера—Лагранжа (второго порядка) к системе двух

θ

r

O
ϕ

y

x
Сферические координаты (r, ϕ, θ) точки A
РИС. 11

Глава 16

174

дифференциальных уравнений первого порядка и решите эту систему с помощью какого-нибудь приближенного метода. Заметим, что искомое семейство геодезических
кривых является однопараметрическим, его параметром k
служит начальное значение производной: y 0 (x0 ) = k.
1)
2)

z = y2 ;
z = x2 + y 2 ;

3)
4)

z = sin x;
z = y + x3 .

11 Постройте с помощью Matplotlib графики следующих поверхностей:
1)
2)
3)
4)

z = (x2 + y 2 − 3)2 ;
z = x2 sin y;
1
z=
;
1 + x2 + y 2
z = cos(x + y) + 2 cos(x − y).

Книги издательства «ДМК ПРЕСС»
можно купить оптом и в розницу
в книготорговой компании «Галактика»
(представляет интересы издательств
«ДМК ПРЕСС», «СОЛОН ПРЕСС», «КТК Галактика»).
Адрес: г. Москва, пр. Андропова, 38;
тел.: (499) 782-38-89, электронная почта: books@alians-kniga.ru.
При оформлении заказа следует указать адрес (полностью),
по которому должны быть высланы книги;
фамилию, имя и отчество получателя.
Желательно также указать свой телефон и электронный адрес.
Эти книги вы можете заказать и в интернет-магазине: www.a-planeta.ru.

Ершов Николай Михайлович

Практическое введение
в решение дифференциальных уравнений в Python
Главный редактор

Мовчан Д. А.

dmkpress@gmail.com

Зам. главного редактора
Корректор
Верстка
Дизайн обложки

Сенченкова Е. А.
Синяева Г. И.
Ершов Н. М.
Мовчан А. Г.

Гарнитура Computer Modern LaTeX. Печать цифровая.
Усл. печ. л. 14,3. Тираж 200 экз.
Веб-сайт издательства: www.dmkpress.com