Как с помощью Python создавать математическую мультипликацию типа 3Blue1Brown

Для чего нужна математическая мультипликация?

Вы когда-нибудь пытались освоить математические концепции алгоритма машинного обучения с помощью образовательного ресурса 3Blue1Brown? 3Blue1Brown  —  это знаменитый математический канал на YouTube, созданный Грантом Сандерсоном. Многим он полюбился из-за отличных объяснений Гранта и классной анимации.

Видео от 3Blue1Brown

А здесь этот же канал с переводами на русский.

JavaMentor
JavaMentor

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

К счастью, Грант сделал пакет на языке Python под названием manim, который позволяет создавать математическую мультипликацию или изображения с использованием Python. В этой статье вы узнаете, как с помощью manim создавать математическую мультипликацию, подобную той, что показана ниже.

Что такое Manim?

Manim  —  это движок точной анимации, предназначенный для создания образовательных математических видеороликов. Обратите внимание, что есть две версии manim: первая создана Грантом, а вторая дублировала первую и поддерживается сообществом Manim Community.

Будем использовать версию, поддерживаемую сообществом Manim, так как эта версия обновляется чаще и лучше тестируется, чем версия Гранта.

Чтобы установить зависимости для пакета, загляните в документацию. После установки зависимостей введите:

pip install manim

Приступим

Создадим синий увеличивающийся квадрат

Код для создания анимации находится внутри метода construct класса, определяемого в Scene .

from manim import * 

class PointMovingOnShapes(Scene):
    def construct(self):
        square = Square(color=BLUE) # Квадрат создается
        square.flip(RIGHT) # Квадрат транспонируется вправо
        square.rotate(-3 * TAU / 8) # Квадрат поворачивается -3/8 * 2*PI 

         # Воспроизводится анимация с увеличивающимся квадратом
        self.play(GrowFromCenter(square))

Сохраняем приведенный выше скрипт как start.py . Теперь запускаем команду для генерирования видео для скрипта:

$ manim -p -ql start.py PointMovingOnShapes

И видео под названием PointMovingOnShapes.mp4 будет сохранено у вас в локальном каталоге. Вот что вы должны увидеть:

Пояснения к приведенным выше параметрам:

  • -p: воспроизведение видео после того, как оно сгенерировано;
  • -ql: генерирование видео с низким качеством.

Для генерирования видео с высоким качеством используем -qh.

Для создания вместо видео гифки добавляем в команду -i:

$ manim -p -ql -i start.py PointMovingOnShapes

Превратим квадрат в окружность

Одного квадрата будет мало. Делаем из него окружность:

А вот и код для создания этой анимации:

from manim import * 

class PointMovingOnShapes(Scene):
    def construct(self):
        
        # Создается квадрат
        square = Square(color=BLUE)
        square.flip(RIGHT)
        square.rotate(-3 * TAU / 8)
        
        # Создается окружность
        circle = Circle()
        circle.set_fill(PINK, opacity=0.5) # задаются цвет и прозрачность
      
        # Создается анимация
        self.play(GrowFromCenter(square))
        self.play(Transform(square, circle))  # квадрат превращается в окружность
       
        self.wait() # несколько секунд ожидания

Полный список фигур смотрите здесь.

Настроим Manim под себя

Не хотите черный фон? Тогда превращаем его в серый

с помощью config.background_color:

from manim import * 

config.background_color = DARK_GRAY

Другие способы настройки manim смотрите здесь.

Для чего еще используют Manim?

Напишем математические уравнения с подвижной рамкой

Создаем анимацию, которая записывает математические уравнения с помощью подвижной рамки:

class MovingFrame(Scene):
     def construct(self):
        # Пишутся уравнения
        equation = MathTex("2x^2-5x+2", "=", "(x-2)(2x-1)")

        # Создается анимация
        self.play(Write(equation))

        # Добавляются подвижные рамки
        framebox1 = SurroundingRectangle(equation[0], buff=.1)
        framebox2 = SurroundingRectangle(equation[2], buff=.1)

        # Создается анимация
        self.play(Create(framebox1))  # с созданием рамки

        self.wait()
        # frame 2 сменяет frame 1
        self.play(ReplacementTransform(framebox1, framebox2))
    
        self.wait()

или записываем пошагово, как решить уравнение:

class MathematicalEquation(Scene):
    def construct(self):
    
        # Пишутся уравнения
        equation1 = MathTex("2x^2-5x+2")
        eq_sign_1 = MathTex("=")
        equation2 = MathTex("2x^2-4x-x+2")
        eq_sign_2 = MathTex("=")
        equation3 = MathTex("(x-2)(2x-1)")

        # Каждое уравнение или знак помещается в соответствующее положение
        equation1.next_to(eq_sign_1, LEFT)
        equation2.next_to(eq_sign_1, RIGHT)
        
        eq_sign_2.shift(DOWN)
        equation3.shift(DOWN)
        
        # Нижние уравнения приводятся в соответствие с верхними
        eq_sign_2.align_to(eq_sign_1, LEFT)
        equation3.align_to(equation2, LEFT)

        # Уравнения и знак группируются
        eq_group = VGroup(equation1, eq_sign_1, equation2, eq_sign_2, equation3)

        # Создается анимация
        self.play(Write(eq_group))
        
        self.wait()

Перемещение и наезд камеры

Настраиваем камеру и выбираем, какую часть уравнений увеличить с помощью класса, наследуемого от объекта MovingCameraScene.

class MovingAndZoomingCamera(MovingCameraScene):
    def construct(self):
        # Пишутся уравнения
        equation = MathTex("2x^2-5x+2", "=", "(x-2)(2x-1)")

        self.add(equation)
        self.play(self.camera.frame.animate.move_to(equation[0]).set(width=equation[0].width*2))
        self.wait(0.3)
        self.play(self.camera.frame.animate.move_to(equation[2]).set(width=equation[2].width*2))

Графики

Используем manim для создания аннотированного графика:

class Graph(GraphScene):
    def __init__(self, **kwargs):
        GraphScene.__init__(
            self,
            x_min=-3.5,
            x_max=3.5,
            y_min=-5,
            y_max=5,
            graph_origin=ORIGIN,
            axes_color=BLUE,
            x_labeled_nums=range(-4, 4, 2), # x тикеры
            y_labeled_nums=range(-5, 5, 2), # y тикеры
            **kwargs
        )
    
    def construct(self):
        self.setup_axes(animate=False)

        # Рисуются графики
        func_graph_cube = self.get_graph(lambda x: x**3, RED)
        func_graph_ncube = self.get_graph(lambda x: -x**3, GREEN)

        # Создаются метки
        graph_lab = self.get_graph_label(func_graph_cube, label="x^3")
        graph_lab2 = self.get_graph_label(func_graph_ncube, label="-x^3", x_val=-3)

        # Создается вертикальная линия
        vert_line = self.get_vertical_line_to_graph(1.5, func_graph_cube, color=YELLOW)
        label_coord = self.input_to_graph_point(1.5, func_graph_cube)
        text = MathTex(r"x=1.5")
        text.next_to(label_coord)
       
        self.add(func_graph_cube, func_graph_ncube, graph_lab, graph_lab2, vert_line, text)
        self.wait()

Если надо получить изображение последней рамки scene, добавляем к команде -s:

manim -p -qh -s more.py Graph

Анимируем процесс настройки осей координат, установив значение animate=True:

def construct(self):
        self.setup_axes(animate=True)
        ################### Ниже то же самое, что и выше ###################
$ manim -p -qh more.py Graph

Перемещение объектов

Задействуем VGroup для группировки различных объектов Manim и их совместного перемещения:

class GroupCircles(Scene):
    def construct(self):

        # Создаются окружности
        circle_green = Circle(color=GREEN)
        circle_blue = Circle(color=BLUE)
        circle_red = Circle(color=RED)
        
        # Задаются исходные позиции
        circle_green.shift(LEFT)
        circle_blue.shift(RIGHT)
        
        # Создаются две разные группы
        gr = VGroup(circle_green, circle_red)
        gr2 = VGroup(circle_blue)
        self.add(gr, gr2) # две группы добавляются в scene
        self.wait()

        self.play((gr + gr2).animate.shift(DOWN)) # две группы сдвигаются вниз
        
        self.play(gr.animate.shift(RIGHT)) # перемещается только одна группа
        self.play(gr.animate.shift(UP))

        self.play((gr + gr2).animate.shift(RIGHT)) # две группы сдвигаются вправо
        self.play(circle_red.animate.shift(RIGHT))
        self.wait()

Trace Path

Используем TracedPath для создания следа движущегося объекта:

class TracedPathExample(Scene):
    def construct(self):
        # Создается окружность и точка
        circ = Circle(color=BLUE).shift(4*LEFT)
        dot = Dot(color=BLUE).move_to(circ.get_start())

        # Точка и окружность группируются
        rolling_circle = VGroup(circ, dot)
        trace = TracedPath(circ.get_start)

        rolling_circle.add_updater(lambda m: m.rotate(-0.3))  # Окружность поворачивается

        self.add(trace, rolling_circle) # след и вращающаяся окружность добавляются в scene

        # Окружность сдвигается на 8*RIGHT
        self.play(rolling_circle.animate.shift(8*RIGHT), run_time=4, rate_func=linear)

Подведем итоги

Поздравляем! Вы только что научились использовать manim и узнали, на что он способен. Напомним, что manim работает с тремя видами объектов.

  • Mobjects. Объекты, отображающиеся на экране, такие как Circle (окружность), Square (квадрат), Matrix (матрица), Angle (угол) и т. д.
  • Scenes. Холст для анимации, такой как Scene (сцена), MovingCameraScene (сцена с движущейся камерой) и т. д.
  • Animations. Действия, применяемые к объектам Mobjects для создания анимации, например Write (написание), Create (создание), GrowFromCenter (увеличение), Transform (преобразование) и т. д.

Manim способен на гораздо большее, чем то, о чем нам удалось рассказать. Лучший способ научиться  —  практиковаться, поэтому рекомендую попробовать примеры из этой статьи и заглянуть в руководство по manim.

Исходный код находится здесь.

Читайте также:

Читайте нас в TelegramVK и Яндекс.Дзен


Перевод статьи Khuyen Tran: How to Create Mathematical Animations like 3Blue1Brown Using Python

Предыдущая статья5 советов для начинающих программистов
Следующая статьяКак создавать доступные веб-приложения для дальтоников с помощью Chrome DevTools