Команда Python for Devs подготовила перевод статьи о Manim — Python-инструменте для создания наглядных математических анимаций в стиле 3Blue1Brown. Разбираемся, как с помощью кода визуализировать уравнения, графики и абстрактные идеи так, чтобы они были понятны коллегам, менеджерам и студентам.
Мотивация
Случалось ли вам разбираться в математических концепциях, лежащих в основе алгоритмов машинного обучения, и в итоге обращаться к 3Blue1Brown как к учебному ресурсу? 3Blue1Brown — это популярный YouTube-канал о математике, созданный Грантом Сандерсоном, известный своими выдающимися объяснениями и впечатляющими анимациями.
А что если вы могли бы создавать похожие анимации, чтобы объяснять концепции data science своим коллегам по команде, менеджерам или подписчикам?
Грант разработал Python-пакет под названием Manim, который позволяет создавать математические анимации и изображения с помощью Python. В этой статье вы узнаете, как создавать красивые математические анимации с использованием Manim.
Полный исходный код и Jupyter Notebook для этого туториала доступны на GitHub. Склонируйте репозиторий, чтобы разбираться по ходу!
Что такое Manim?
Manim — это анимационный движок для создания точных и наглядных математических видео. Обратите внимание, что существует две версии Manim:
Поскольку версия Manim Community обновляется чаще и лучше протестирована, в этом туториале мы будем использовать именно её.
Чтобы установить зависимости пакета, перейдите к документации. После установки зависимостей выполните:
pip install manim
Создаём синий квадрат, растущий из центра
Начнём с создания синего квадрата, который растёт из центра:
from manim import * class GrowingSquare(Scene): def construct(self): square = Square(color=BLUE, fill_opacity=0.5) self.play(GrowFromCenter(square)) self.wait()
В этом коде используются четыре ключевых элемента Manim для создания анимации.
Scene: базовый класс, предоставляющий инфраструктуру для анимаций через construct() и play()
Square(color, fill_opacity): Mobject для создания квадратов с заданным цветом обводки и прозрачностью заливки
GrowFromCenter: анимация роста, начинающаяся из центра объекта
wait(): пауза в таймлайне анимации длительностью 1 секунду
Сохраните этот скрипт в файл start.py. Теперь выполните команду ниже, чтобы сгенерировать видео для этого скрипта:
manim -p -ql start.py GrowingSquare
В вашей локальной директории будет сохранено видео с именем GrowingSquare.mp4. Вы увидите синий квадрат, который растёт из центра.

Пояснение параметров:
p: воспроизвести видео после завершения генерации
ql: сгенерировать видео в низком качестве
Чтобы создать видео в высоком качестве, используйте -qh.
Чтобы создать GIF вместо видео, добавьте параметр --format=gif к команде:
manim -p -ql --format=gif start.py GrowingSquare
Превращаем квадрат в круг
Создать просто квадрат — не слишком интересно. Давайте превратим квадрат в круг с помощью анимации Transform.
from manim import * class SquareToCircle(Scene): def construct(self): circle = Circle() circle.set_fill(PINK, opacity=0.5) square = Square() square.rotate(PI / 4) self.play(Create(square)) self.play(Transform(square, circle)) self.play(FadeOut(square))

Этот код демонстрирует создание фигур, их стилизацию и применение различных анимационных эффектов.
Circle: создаёт круг
set_fill(color, opacity): задаёт цвет заливки и прозрачность (0.5 = 50% прозрачности)
rotate(angle): поворачивает объект на заданный угол (PI / 4 = 45 градусов)
Create: анимация, которая «рисует» фигуру на экране
Transform: плавно преобразует одну фигуру в другую
FadeOut: пос��епенно убирает объект с экрана
Полный список доступных фигур можно найти в документации Manim по геометрии.
Настройка Manim
Если вам не нужен чёрный фон, вы можете заменить его на белый с помощью свойства self.camera.background_color:
from manim import * class CustomBackground(Scene): def construct(self): self.camera.background_color = WHITE square = Square(color=BLUE, fill_opacity=0.5) circle = Circle(color=RED, fill_opacity=0.5) self.play(Create(square)) self.wait() self.play(Transform(square, circle)) self.wait()

Другие способы настройки Manim можно найти в документации по конфигурации.
Запись математических уравнений с движущейся рамкой
Вы можете создавать анимации, в которых математические уравнения «выписываются» на экран, используя класс MathTex:
from manim import * class WriteEquation(Scene): def construct(self): equation = MathTex(r"e^{i\pi} + 1 = 0") self.play(Write(equation)) self.wait()

Этот код показывает, как записывать математические уравнения с помощью LaTeX.
MathTex: создаёт математические уравнения в нотации LaTeX (префикс r указывает, что используется raw-строка для LaTeX-команд)
Write: анимация, при которой текст появляется так, будто его пишут от руки
Также можно показывать пошаговое решение уравнений:
from manim import * class EquationSteps(Scene): def construct(self): step1 = MathTex(r"2x + 5 = 13") step2 = MathTex(r"2x = 8") step3 = MathTex(r"x = 4") self.play(Write(step1)) self.wait() self.play(Transform(step1, step2)) self.wait() self.play(Transform(step1, step3)) self.wait()

Разберём этот код:
Три этапа уравнения: отдельные объекты MathTex для каждого шага решения
Пошаговое преобразование: Transform последовательно преобразует первое уравнение в промежуточные и итоговое
Паузы по времени: wait() создаёт естественные паузы между шагами, подходящие для обучающего темпа
Вы также можете подсвечивать отдельные части уравнений с помощью рамок:
from manim import * class MovingFrame(Scene): def construct(self): # Write equations equation = MathTex("2x^2-5x+2", "=", "(x-2)(2x-1)") # Create animation self.play(Write(equation)) # Add moving frames framebox1 = SurroundingRectangle(equation[0], buff=.1) framebox2 = SurroundingRectangle(equation[2], buff=.1) # Create animations self.play(Create(framebox1)) self.wait() # Replace frame 1 with frame 2 self.play(ReplacementTransform(framebox1, framebox2)) self.wait()

В этом коде используются следующие элементы:
MathTex с несколькими аргументами: разбивает уравнение на отдельные части, к которым можно обращаться и подсвечивать их по отдельности
SurroundingRectangle(mobject, buff): создаёт рамку вокруг части уравнения (buff=0.1 задаёт расстояние между рамкой и содержимым)
ReplacementTransform: плавно перемещает и преобразует одну рамку в другую, заменяя первую второй
Движение и масштабирование камеры
Вы можете управлять камерой и выбирать, на какую часть уравнения приблизиться, используя класс, унаследованный от MovingCameraScene:
from manim import * class MovingCamera(MovingCameraScene): def construct(self): equation = MathTex( r"\frac{d}{dx}(x^2) = 2x" ) self.play(Write(equation)) self.wait() # Zoom in on the derivative self.play( self.camera.frame.animate.scale(0.5).move_to(equation[0]) ) self.wait()

Этот код показывает, как приближать отдельные элементы анимации.
MovingCameraScene: специальный тип сцены, который позволяет перемещать и масштабировать камеру
self.camera.frame: представляет область обзора камеры, которую можно перемещать и масштабировать
animate.scale(0.5): приближает изображение, уменьшая область обзора камеры на 50% (объекты выглядят в два раза больше)
move_to(equation[0]): центрирует камеру на первой части уравнения
Графики
С помощью Manim можно создавать аннотированные графики:
from manim import * class Graph(Scene): def construct(self): axes = Axes( x_range=[-3, 3, 1], y_range=[-5, 5, 1], x_length=6, y_length=6, ) # Add labels axes_labels = axes.get_axis_labels(x_label="x", y_label="f(x)") # Create a function graph graph = axes.plot(lambda x: x**2, color=BLUE) graph_label = axes.get_graph_label(graph, label="x^2") self.add(axes, axes_labels) self.play(Create(graph)) self.play(Write(graph_label)) self.wait()

Этот код демонстрирует создание графиков математических функций с подписями осей.
Axes: создаёт координатную сетку с заданными диапазонами (например, x от -3 до 3, y от -5 до 5) и размерами
get_axis_labels: добавляет подписи «x» и «f(x)» к осям координат
plot(lambda x: x**2): строит график математической функции (в данном случае x в квадрате) на координатных осях
get_graph_label: добавляет подпись с формулой функции рядом с построенной кривой
self.add: мгновенно отображает объекты на экране без анимации их появления
Если вам нужно получить изображение последнего кадра сцены, добавьте параметр -s к команде:
manim -p -qh -s more.py Graph
Вы также можете анимировать сам процесс построения осей:
manim -p -qh more.py Graph

Совместное перемещение объектов
Вы можете использовать VGroup, чтобы объединять разные объекты Manim и перемещать их как единое целое:
from manim import * class TracePath(Scene): def construct(self): dot = Dot(color=RED) # Create traced path path = TracedPath(dot.get_center, stroke_color=BLUE, stroke_width=4) self.add(path, dot) # Move the dot in a circular pattern self.play( MoveAlongPath(dot, Circle(radius=2)), rate_func=linear, run_time=4 ) self.wait()

Этот код показывает, как группировать объекты и двигать их вместе как один элемент.
VGroup: объединяет несколько объектов, позволяя управлять ими одновременно (аналогично выделению нескольких элементов)
arrange(RIGHT, buff=1): выстраивает объекты горизонтально слева направо с расстоянием в 1 единицу между ними
shift(UP * 2): перемещает всю группу вверх на 2 единицы (умножение на 2 увеличивает расстояние перемещения вдвое)
Вы также можете создавать несколько групп и управлять ими независимо или совместно:
from manim import * class GroupCircles(Scene): def construct(self): # Create circles circle_green = Circle(color=GREEN) circle_blue = Circle(color=BLUE) circle_red = Circle(color=RED) # Set initial positions circle_green.shift(LEFT) circle_blue.shift(RIGHT) # Create 2 different groups gr = VGroup(circle_green, circle_red) gr2 = VGroup(circle_blue) self.add(gr, gr2) self.wait() # Shift 2 groups down self.play((gr + gr2).animate.shift(DOWN)) # Move only 1 group self.play(gr.animate.shift(RIGHT)) self.play(gr.animate.shift(UP)) # Shift 2 groups to the right self.play((gr + gr2).animate.shift(RIGHT)) self.play(circle_red.animate.shift(RIGHT)) self.wait()

Трассировка траектории
Вы можете использовать TracedPath, чтобы создать след движения объекта:
from manim import * class TracePath(Scene): def construct(self): dot = Dot(color=RED) # Create traced path path = TracedPath(dot.get_center, stroke_color=BLUE, stroke_width=4) self.add(path, dot) # Move the dot in a circular pattern self.play( MoveAlongPath(dot, Circle(radius=2)), rate_func=linear, run_time=4 ) self.wait()

Этот код показывает, как создать след, который повторяет движение объекта.
Dot: маленькая круглая точка, которую можно перемещать
TracedPath(dot.get_center): создаёт линию, которая рисуется по мере движения точки, повторяя её траекторию (как след от пера)
get_center: возвращает текущую позицию точки, чтобы TracedPath знал, где рисовать
MoveAlongPath(dot, Circle(radius=2)): перемещает точку по круговой траектории радиуса 2
rate_func=linear: обеспечивает равномерную скорость движения (без ускорений и замедлений)
run_time=4: анимация длится 4 секунды
Для более сложного примера можно создать катящийся круг, который рисует циклоидальную кривую:
from manim import * class RollingCircleTrace(Scene): def construct(self): # Create circle and dot circ = Circle(color=BLUE).shift(4*LEFT) dot = Dot(color=BLUE).move_to(circ.get_start()) # Group dot and circle rolling_circle = VGroup(circ, dot) trace = TracedPath(circ.get_start) # Rotate the circle rolling_circle.add_updater(lambda m: m.rotate(-0.3)) # Add trace and rolling circle to the scene self.add(trace, rolling_circle) # Shift the circle to 8*RIGHT self.play(rolling_circle.animate.shift(8*RIGHT), run_time=4, rate_func=linear)

Этот код создаёт анимацию катящегося колеса, которое рисует кривую по мере движения.
Настройка: создаётся синий круг слева и синяя точка, размещённая на краю круга
Группировка: VGroup объединяет круг и точку, чтобы они двигались вместе
Отслеживание пути: TracedPath следит за начальной точкой круга и рисует линию по её траектории
Непрерывное вращение: add_updater заставляет круг автоматически вращаться на каждом кадре, создавая эффект качения
Движение качения: при смещении группы вправо апдейтер вращения создаёт ощущение катящегося колеса
Циклоидальная кривая: трассируемый путь формирует волнообразную линию, показывающую математическую траекторию точки на катящемся колесе
Итоги
Поздравляем! Вы только что познакомились с Manim и его возможностями. Подведём итог: в Manim есть три основных типа сущностей:
Mobjects: объекты, которые можно отображать на экране, например Circle, Square, Matrix, Angle и другие
Scenes: «полотно» для анимаций, такие как Scene, MovingCameraScene и т. д.
Animations: анимации, применяемые к Mobjects, например Write, Create, GrowFromCenter, Transform и другие
Возможности Manim на этом далеко не заканчиваются — многое осталось за рамками этой статьи. Лучший способ разобраться глубже — практика, поэтому я рекомендую попробовать примеры из этой статьи и ознакомиться с туториалами Manim.
Русскоязычное сообщество про Python

Друзья! Эту статью подготовила команда Python for Devs — канала, где каждый день выходят самые свежие и полезные материалы о Python и его экосистеме. Подписывайтесь, чтобы ничего не пропустить!
