Решение краевой задачи баллистики¶
Рассмотрим задачу толкания ядра на олимпийских играх.
- Даны:
- время полёта $(t_0,\:t_f)$
- начальные положения $x(t_0)$ и $y(t_0)$
- конечные положения $x(t_f)$ и $y(t_f)$
Определить начальную скорость ядра и угол его броска $v_x(t_0)$ и $v_y(t_0)$
Вывод уравнения баллистики¶
Для решения задачи необходимо составить уравнения движения брошенного предмета:
Исходя из рисунка, составим уравнения движения баллистики: $$ \begin{split} &m \ddot{x} = 0\\ &m \ddot{y}=-mg \end{split} $$
Сократив $m$, и перейдя к системе ОДУ первого порядка, получим: $$ \begin{split} &\dot{x} = v_x \\ &\dot{y} = v_y \\ &\dot{v}_x = 0 \\ &\dot{v}_y = -g \end{split} $$
Добавим (Pkg.add
) и подключим библиотеки:
BoundaryValueDiffEq
для решения краевой задачи дифференциальных уравненийPlots
- для отображения графика решенийLinearAlgebra: norm
для нахождения нормыPhysicalConstants
поззволит использовать значение стандартного ускорения свободного падения
using Pkg
Pkg.add(["BoundaryValueDiffEq","LinearAlgebra","PhysicalConstants"])
using BoundaryValueDiffEq
using Plots
import LinearAlgebra: norm # используем не весь модуль, а только определённую функцию
import PhysicalConstants.CODATA2018: g_n
g_n
представляет собой не просто число, а физическую константу (значение, размерность, неопределённость величины). Чтобы получить "чистое" значение - нужно воспользоваться float(g_n)
. Она вернёт 9.80665 m s^-2
. Чтобы получить только значение, нужно обратиться к полю val
g_n
const g = float(g_n).val
Теперь рассмотрим приём, который позволяет вставлять "заглушки"
_,b,_,d = [1 2 3 4]
print("b=$b, d=$d")
Запишем нашу систему уравнений
$$ \begin{split}
&\dot{x} = v_x \\
&\dot{y} = v_y \\
&\dot{v}_x = 0 \\
&\dot{v}_y = -g
\end{split} $$
в виде функции ballistic!(du,u,p,t)
.
p
- параметры системы, t
- время (но наша система является автономной (стацианарной), т.е. явно не зависит от t
).
Не бойтесь вводить новые переменные dx, ..., v_y
ради повышения читабельности. Чаще всего это не влияет на производительность.
function ballistic!(du, u, p, t)
_, _, v_x, v_y = u
du[1] = dx = v_x
du[2] = dy = v_y
du[3] = dv_x = 0
du[4] = dv_y = -g
end
Определение краевых условий задачи¶
Теперь введём граничные условия (boundary conditions) в начальный (a) и конечный (b) моменты времени.
Эти ограничения вводятся в следующем виде: $g(u)=0$.
Т.е. если условие $y(0)=2$, то $g(u)=y(0)-2=0$
Сами значения задачи будем брать, исходя из мирового рекорда по толканию ядра:
- $x(0)=0$
- $y(0)=2.1$ - исходя из стр 13 исследования
- $x(t_f)=23.1$ - олимпийский рекорд на 2023 год
- $y(t_f)$ = 0
- $(t_0,t_f)=(0,2)$ (исходя из кадрирования видео shot_put.mp4 самого рекорда)
function bca!(resid_a, u_a, p)
resid_a[1] = u_a[1] # x(0) = 0
resid_a[2] = u_a[2] - 2.1 # y(0) = 2
end
function bcb!(resid_b, u_b, p)
resid_b[1] = u_b[1] - 23.1 # x(t_f) = 95
resid_b[2] = u_b[2] # y(t_f) = 0
end
tspan = (0., 2)
Сделаем предположения о начальных условиях
α = deg2rad(45)
x₀ = 0
y₀ = 2.1
v₀ = 10
v_x_0 = v₀ * cos(α)
v_y_0 = v₀ * sin(α)
u₀ = [x₀, y₀, v_x_0, v_y_0]
Решение двухточечной краевой задачи¶
Зададим двухточечную краевую задачу (TwoPointBVProblem
).
Эта проблема состоит из системы дифференциальных уравнений, краевых условий, начальных предположений и временного интервала интегрирования.
Ключевое слово bcresid_prototype
позволяет задать вид начальных предположений (у нас может быть 4 условия в точке a
и 4 условия в точке b
. Но мы указали только по 2 ограничения, так как скорости и в начале и конце не ограничивали.
bvp = TwoPointBVProblem(ballistic!, (bca!, bcb!), u₀, tspan;
bcresid_prototype=(zeros(2), zeros(2)))
Теперь осталось решить задачу, выбрав в качетсве метода - Shooting
, с решателем Verns7()
. Подробнее про алгоритмы решателей краевой задачи можно прочитать в документации.
sol = solve(bvp, Shooting(Vern7()), saveat=0.1)
gr() # используем статическое отображение графиков
plt_record = plot( sol[1, :], sol[2, :],
aspect_ratio=1.0, xlabel="x, м",ylabel="y, м", label = "траектория")
scatter!(plt_record, [0,23.1],[2.1, 0],label="краевые условия")
Как видно из рисунка, решение системы ОДУ действительно удовлетворяет краевым условиям.
Теперь узнаем значения модуля и угла вектора скорости в момент броска $t=0$.
norm([3 4]) # √(3²+4²)
norm([sol[1][3],sol[1][4]]) #√(v_x²+v_y²)
x |> f
эквивалентно f(x)
atan(sol[1][4]/sol[1][3]) |> rad2deg
Таким образом, скорость броска ≈ 14.5 м/с
, и угол броска ≈ 37.16°
Определение угла броска, который позволяет достигать цели при наименьшей начальной скорости.¶
Теперь рассмотрим набор траекторий, и убедимся в том, что угол в 45° позволяет достигнуть цели при наименьшей начальной скорости.
Функции, позволяющие изменять цвет траектории, в зависимости от начальной скорости¶
Перед тем как построить траектории, определим функции, которые позволят отображать цвет линии в зависимости от значения начальной скорости.
Напишем функцию map_range
, пропорционально отображающую число из отрезка [a,b]
в отрезок [A,B]
.
В качестве параметров она содержит:
- кортеж из двух чисел
(a,b)
- исходный отрезок, из которого планируется отображение - кортеж из двух чисел
(A,B)
- отрезок, в который планируется отображение - $x\in[a,b]$ - число из
[a,b]
, которое планируется отобразить в[A,B]
Если $x\notin[a,b]$, то функция выбрасывает исключение. В нашем случае тип этого исключения - DomainError, так как x
выходит из области определения функции.
map_range((a, b), (A, B), x) = a < x < b ?
A + (x - a) / (b - a) * (B - A) :
throw(DomainError(x, "x must be in domain ($a, $b)"))
map_range((14,18),(0,1),15)
# |14---15___16___17___18| |0---0.25___0.5___0.75___1|
# map_range((14,18),(0,1),19) # раскомментируйте, чтобы проверить работу исключений
Для того чтобы работать с цветами и использовать определённые наборы цветов необходимо подключить библиотеки Colors
и ColorSchemes
Pkg.add(["Colors","ColorSchemes"])
using Colors
using ColorSchemes
Создадим набор из 20 цветов, расположеных от синего к красному:
COLORS_NUMBER = 20
ndr = ColorScheme(get(ColorSchemes.darkrainbow, range(0.0, 1.0, length=COLORS_NUMBER)))
[ndr[1],ndr[end]]
Предположим, что у нас будет набор траекторий, начальные скорости которых будут меняться от 14 до 17. И мы хотим найти цвет, соответствующий скорости 15, если синий - наименьшая скорость, красный - наибольшая.
round(Int,3.5)
ndr[round(Int,map_range((14,17),(1,COLORS_NUMBER),15))]
Добавим пустые графики, на которые будем в каждой итерации добавлять новые данные:
plt_trajectories = plot()
scatter_v_0_α = scatter();
for tspan in map(x -> (0, x), 1.5:0.15:3)
# решение краевой задачи для заданного (t_0,t_f)
bvp = TwoPointBVProblem(ballistic!, (bca!, bcb!), u₀, tspan;
bcresid_prototype=(zeros(2), zeros(2)))
sol = solve(bvp, Shooting(Vern7()), saveat=0.1)
# определение v_0 и α
v_0 = round(norm([sol[1][3],sol[1][4]]),digits=2) # (round, π, digits=4) = 3.1416
α = atan(sol[1][4]/sol[1][3]) |> rad2deg
# построение графиков
## построение траекторий
plot!( plt_trajectories, sol[1, :], sol[2, :], aspect_ratio=1.0,
color=ndr[round(Int,map_range((14,17), (1,COLORS_NUMBER),v_0))],
label=nothing, w=3,xlabel="x",ylabel="y", title="траектории толкания ядра")
## для построения зависимости v_0(α)
scatter!(scatter_v_0_α,[α],[v_0],label=nothing,
xlabel="α", ylabel="v_0",title="Зависимость начальной скорости от угла толкания",
color=ndr[round(Int,map_range((14,17), (1,COLORS_NUMBER),v_0))])
end
display(plt_trajectories)
display(scatter_v_0_α)
Выводы¶
Можем сделать вывод, что действительно, для достижения поставленной задачи (метания ядра на определённое расстояние) угол в 45° обеспечивает минимальную начальную скорость, при которой этот бросок возможен. Иными словами, угол в 45° обеспечивает максимальное расстояние, которое может преодолеть ядро, если заданная начальная скорость фиксирована.
В результате выполения поставленной задачи были рассмотрены:
- решение двухточечной краевой задачи
- использование физических констант
- создание функций, выбрасывающих исключения при выходе за границы области определения функции
- пользовательское изменение цвета траекторий в зависимости от данных, соответствующих траектории (v_0, в нашем случае)