Лабораторная работа 3. Анимация с помощью функции FuncAnimation. Движения секущей прямой к заданной кривой

Вычислительная практика II, ММФ, БГУ

Лаврова О.А., апрель 2020

In [1]:
import numpy as np
In [2]:
import math as mth
In [3]:
import matplotlib.pyplot as plt
In [4]:
%matplotlib nbagg

Для построения анимации будем использовать модуль animation библиотеки matplotlib

In [5]:
import matplotlib.animation as anim

Непосредственно анимацию построим с помощью функции FuncAnimation

FuncAnimation(fig, func, frames=None, init_func=None, fargs=None, save_count=None, *, cache_frame_data=True, **kwargs)

Функция FuncAnimation имеет два обязательных аргумента fig и func.

fig -- графическое окно, в котором будет отображаться анимация.

func -- функция одного аргумента, которая будет вызываться в каждом кадре.

In [6]:
#help(anim.FuncAnimation)

Пример 1 (анимация построения линейной функции)

Построим анимацию линейной функции $y=x$ по значениям $x$, изменяющимся от $0$ до $10$ с шагом $1$.

Для начала создадим графическое окно для анимации

In [7]:
fig1 = plt.figure();

В графическом окне создадим графическую область и зададим пределы по осям

In [8]:
ax1 = plt.axes();
plt.axis([0, 11, 0, 11]);

В графической области создадим объект типа Line2D, координаты которого пока не определены

In [9]:
line, = ax1.plot([], [],'r'); 

Определим функцию одного аргумента at_frame1, которая будет вызываться в каждом кадре. Аргументом функции будет номер кадра. Функция добавляет к графическому объекту line из графической области для анимации точку с координатами $(t,t)$ и возвращает измененный графический объект line.

In [10]:
def at_frame1(t):
    """добавляет к графическому объекту line новую точку с координатами (t,t) и возвращает измененный графический объект
    
    Arguments:
    t -- номер кадра, неотрицательное целое число
    
    Returns: графический объект типа Line2D
    """
    x_coord = list(line.get_xdata());
    y_coord = list(line.get_ydata());
    x_coord += [t];
    y_coord += [t];
    line.set_data(x_coord, y_coord)
    
    return line

Строим анимацию. frames=11 означает, что всего будет 11 кадров, значения которых будут изменяться от 0 до 10. Для каждого кадра будет вызываться функция at_frame1 со значением аргумента, равным номеру кадра.

In [11]:
fig1 = plt.figure();
ax1 = plt.axes();
plt.axis([0, 11, 0, 11]);
line, = ax1.plot([], [],'r'); 

anim.FuncAnimation(fig1, at_frame1, frames=11, repeat=False)
Out[11]:
<matplotlib.animation.FuncAnimation at 0x40e5ac8>

Справочная информация

Style Guide for Python Code (https://www.python.org/dev/peps/pep-0008/)

Строки документирования (documentation strings, docstrings)

  • Пишите строки документирования для всех модулей, функций, классов и методов. Строки документирования должны создаваться в начале функции после def-строки.
  • Используйте """тройные двойные кавычки""" вокруг строк документирования.
  • Не оставлйте пустые строки до и после строк документирования.
  • Строки документирования могут быть однострочными и многострочными.
  • Для однострочных строк документирования:
    • открывающие и закрывающие кавычки располагаются в одной строке;
    • предпочтительная форма """делает ... и возвращает ...""".
  • Для многострочных строк документирования:
    • состоит из строки вида: делает ... и возвращает ..., далее следует пустая строка, затем развернутое описание;
    • должны быть указаны аргументы, возвращаемые значения, обработка исключений, ограничения на аргументы, необязательные аргументы;
    • каждый аргумент описывается в отдельной строке;
    • закрывающие кавычки должны располагаться в отдельной строке.

Пример 2 (анимация движения тела по траектории)

В контексте постановки задачи из Лабраторной работы 1 необходимо построить анимацию движения тела по траектории $(s_{x}(t),s_{y}(t))$ для $t \in [0,T]$.

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

In [12]:
h_start = 10 # высота положения тела в момент запуска
T = 2.5 # время полета
s_end = 5.5e1 # горизонтальное перемещение тела за время полета
h_end = 1.2E+1 # высота положения тела в конечный момент движения
g = 9.807 # ускорение свободного падения

v0_x = s_end/T
v0_y = (h_end-h_start+g/2*T**2)/T

t = np.arange(0,T,0.01)
s_x = v0_x*t
s_y = h_start+v0_y*t-g*t**2/2

Создадим матрицу $s$ из двух столбцов для хранения координат траектории движения

In [13]:
s = np.transpose(np.array([s_x, s_y]))
Cоздадим графическое окно для анимации. В графическом окне создадим графическую область и зададим пределы по осям. В графической области создадим два графических объекта типа Line2D, координаты которого пока не определены.
In [14]:
fig2 = plt.figure();

ax2 = plt.axes();
plt.axis([0, s_end+1, 0, h_end+10]);

line1, = ax2.plot([], [],'k');  # отображение траектории
line2, = ax2.plot([], [],'ro'); # отображение тела
In [15]:
coords = list(line1.get_data())
coords
Out[15]:
[array([], dtype=float64), array([], dtype=float64)]

Определим функцию одного аргумента at_frame2, которая будет вызываться в каждом кадре. Аргументом функции будет массив из $x$-ой и $у$-ой кординаты точки траектории. Функция добавляет к графическому объекту line1 из графической области для анимации точку с координатами $(x,y)$. Такжу функция задает графический объект line2 одной точкой с текущими координатами $(x,y)$.

In [16]:
def at_frame2(t):
    """добавляет к графическому объекту line1 новую точку с координатами (t[0],t[1]) и 
    задает графичекий объект line2 точкой с координатами (t[0],t[1])
    
    Arguments:
    t -- массив из двух элементов
    
    Returns: два графических объета типа Line2D
    """
    x_coord = list(line1.get_xdata());
    y_coord = list(line1.get_ydata());
    x_coord += [t[0]];
    y_coord += [t[1]];
    line1.set_data(x_coord, y_coord)
    line2.set_data(t)
    
    return line1, line2

Строим анимацию. frames=s означает, что всего будет столько кадров, сколько строк матрицы $s$. Для каждого кадра будет вызываться функция at_frame2 со значением аргумента, равным массиву со значениями в текущей строке матрицы.

In [17]:
fig2 = plt.figure();

ax2 = plt.axes();
plt.axis([0, s_end+1, 0, h_end+10]);

line1, = ax2.plot([], [],'k');  # отображение траектории
line2, = ax2.plot([], [],'ro'); # отображение тела

anim.FuncAnimation(fig2, at_frame2, frames=s, repeat=False, interval=10)
Out[17]:
<matplotlib.animation.FuncAnimation at 0x83ba948>

Задание (анимация движения секущей прямой к заданной кривой)

Кривая задана траекторией движения тела из Лабораторной работы 1 как $(s_{x}(t),s_{y}(t))$ для $t \in [0,T]$. Начальная точка траектории с координатами $(s_{x}(0),s_{y}(0))$ является неподвижной точкой, обозначим ее через $A$. Подвижная точка $B$ движется последовательно по кривой от конечной точки траектории $(s_{x}(T),s_{y}(T))$ к начальной точке $A$.

Необходимо построить анимацию движения секущей прямой, проходящей через точки $A$ и $B$ до момента, когда секущая прямая становится касательной прямой к заданной кривой при совпадении координат точек $A$ и $B$.

Реализация

Перед началом анимации графическая область должна содержать следующие графические объекты:

  • линию, которая задает траекторию движения подвижной точки $B$;
  • неподвижную точку $A$;
  • подвижную точку $B$;
  • cекущую прямую, проходящую через точки $A$ и $B$.

Начальное состояние графической области реализуем с помощью пользовательской функции init(), которая будет передана в качестве значения аргумента init_func функции FuncAnimation.

In [18]:
fig3 = plt.figure(); # создание графического окна для анимации
ax3 = plt.axes();    # создание графической области для анимации

s = np.transpose(np.array([s_x, s_y]))

def init():
    """Создает начальное состояние графической области перед началом анимации"""
    curve, = ax3.plot(s[:,0], s[:,1],'k'); # траектория движения
    
    A = np.array([s[0,0], s[0,1]]); 
    point_A, = ax3.plot(A[0],A[1],'ro'); # неподвижная точка
    
    B = np.array([s[-1,0], s[-1,1]]);    
    point_B, = ax3.plot(B[0],B[1],'bo'); # подвижная точка
    
    secant_p = [A + (B - A)*t for t in [-2, 2]]; # векторно-параметрическое уравнение прямой 
    secant_p = np.array(secant_p)
    secant_line, = ax3.plot(secant_p[:,0], secant_p[:,1],'g'); # секущая прямая
    
    plt.legend(['Curve','Unmovable point A','Мovable point B','Secant line through A and B'])
    plt.axis([-5, s_end+5, 0, h_end+10]);
    return None

def at_frame3(t):
    """Should be defined"""
    return None
    
anim.FuncAnimation(fig3, at_frame3, frames=s[::-1], init_func=init, repeat=False, interval=10)    
Out[18]:
<matplotlib.animation.FuncAnimation at 0x8409088>

Напишите пользовательскую функцию at_frame3(t), которая будет вызываться в каждом кадре анимации, полагая, что t является массивом текущей координаты подвижной точки $B$. Обратите внимание, что при совпадении координат точек $A$ и $B$ уравнение для задания секущей прямой через две точки возвращает только точку $A$ и уравнение должно быть заменено на уравнение касательной в точке $A$. Для функции at_frame3(t) напишите строки документирования.