Команда 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 и его экосистеме. Подписывайтесь, чтобы ничего не пропустить!
