examples/attention_arcs_animation.py
"""
Attention Arcs Animation - Simple attention flow visualization
Shows how attention connects different positions with animated arcs.
Based on 3Blue1Brown's transformer visualizations.
Run: manimgl attention_arcs_animation.py AttentionArcsAnimation -o
"""
from manimlib import *
import numpy as np
import random
def random_bright_color(hue_range=(0.0, 1.0)):
"""Generate a random bright color within a hue range."""
hue = random.uniform(*hue_range)
return Color(hsl=(hue, 0.7, 0.6))
def value_to_color(
value,
low_positive_color=BLUE_E,
high_positive_color=BLUE_B,
low_negative_color=RED_E,
high_negative_color=RED_B,
min_value=0.0,
max_value=10.0
):
"""Map a value to a color based on its sign and magnitude."""
alpha = np.clip(float((abs(value) - min_value) / (max_value - min_value)), 0, 1)
if value >= 0:
return interpolate_color(low_positive_color, high_positive_color, alpha)
else:
return interpolate_color(low_negative_color, high_negative_color, alpha)
class SimpleEmbedding(VGroup):
"""A simple numeric embedding visualization."""
def __init__(self, length=7, height=2.0, **kwargs):
super().__init__(**kwargs)
# Create rectangles for entries
entries = VGroup()
for i in range(length):
value = random.uniform(-9.9, 9.9)
rect = Rectangle(width=0.3, height=height / length * 0.8)
color = value_to_color(value)
rect.set_fill(color, opacity=0.8)
rect.set_stroke(WHITE, 1)
entries.add(rect)
entries.arrange(DOWN, buff=0.05)
entries.set_height(height)
# Add brackets
lb = Tex(r"\left[", font_size=72)
rb = Tex(r"\right]", font_size=72)
lb.stretch_to_fit_height(height * 1.1)
rb.stretch_to_fit_height(height * 1.1)
lb.next_to(entries, LEFT, buff=0.05)
rb.next_to(entries, RIGHT, buff=0.05)
self.add(lb, entries, rb)
self.entries = entries
self.brackets = VGroup(lb, rb)
class AttentionArcsAnimation(Scene):
"""
Demonstrates attention mechanism through animated arcs connecting positions.
This visualization shows how each position attends to other positions,
with arc colors and widths representing attention weights.
"""
def construct(self):
# Create a row of embeddings
n_embeddings = 6
embeddings = VGroup(*(
SimpleEmbedding(length=8, height=3.0)
for _ in range(n_embeddings)
))
embeddings.arrange(RIGHT, buff=0.8)
embeddings.set_width(FRAME_WIDTH - 2)
embeddings.to_edge(DOWN, buff=1.5)
# Add position labels
labels = VGroup(*(
Text(f"Pos {i}", font_size=24)
for i in range(n_embeddings)
))
for label, emb in zip(labels, embeddings):
label.next_to(emb, DOWN, buff=0.2)
# Title
title = Text("Attention: How positions communicate", font_size=48)
title.to_edge(UP)
# Show initial setup
self.play(
Write(title),
LaggedStartMap(FadeIn, embeddings, shift=0.5 * UP, lag_ratio=0.1),
run_time=2
)
self.play(LaggedStartMap(FadeIn, labels, shift=0.2 * DOWN, lag_ratio=0.1))
self.wait()
# Create attention arcs for each position
self.play_attention_animation(embeddings, run_time=4)
self.wait()
# Show focused attention on one position
focus_label = Text("Each position gathers context from others", font_size=36)
focus_label.next_to(title, DOWN, buff=0.5)
self.play(FadeIn(focus_label, shift=DOWN))
self.play_focused_attention(embeddings, focus_index=3, run_time=3)
self.wait()
# Cleanup
self.play(
FadeOut(focus_label),
FadeOut(title),
FadeOut(labels),
FadeOut(embeddings),
)
def play_attention_animation(self, embeddings, run_time=5):
"""Play attention arcs between all positions."""
arc_groups = VGroup()
for _ in range(2): # Multiple rounds
for n, e1 in enumerate(embeddings):
arc_group = VGroup()
for e2 in embeddings[n + 1:]:
sign = (-1) ** int(e2.get_x() > e1.get_x())
arc = Line(
e1.get_top(), e2.get_top(),
path_arc=sign * PI / 3,
)
arc.set_stroke(
color=random_bright_color(hue_range=(0.1, 0.3)),
width=5 * random.random() ** 3,
)
arc_group.add(arc)
arc_group.shuffle()
if len(arc_group) > 0:
arc_groups.add(arc_group)
self.play(
LaggedStart(*(
AnimationGroup(
LaggedStartMap(VShowPassingFlash, arc_group.copy(), time_width=2, lag_ratio=0.15),
LaggedStartMap(ShowCreationThenFadeOut, arc_group, lag_ratio=0.15),
)
for arc_group in arc_groups
), lag_ratio=0.0),
run_time=run_time
)
def play_focused_attention(self, embeddings, focus_index=3, run_time=3):
"""Show attention arcs focused on one position."""
target = embeddings[focus_index]
# Highlight target
rect = SurroundingRectangle(target, buff=0.1)
rect.set_stroke(YELLOW, 3)
arcs = VGroup()
for i, emb in enumerate(embeddings):
if i == focus_index:
continue
sign = 1 if i < focus_index else -1
arc = Line(
emb.get_top(), target.get_top(),
path_arc=sign * PI / 3,
)
weight = random.random() ** 2
arc.set_stroke(
color=interpolate_color(BLUE_E, YELLOW, weight),
width=2 + 4 * weight,
)
arcs.add(arc)
self.play(ShowCreation(rect))
self.play(
LaggedStart(*(
ShowCreationThenFadeOut(arc, run_time=1.5)
for arc in arcs
), lag_ratio=0.2),
run_time=run_time
)
self.play(FadeOut(rect))
class AttentionArcs3D(Scene):
"""
3D version of attention arcs with camera movement.
"""
def construct(self):
frame = self.camera.frame
# Create 3D embeddings as colored columns
n_embeddings = 5
columns = Group()
for i in range(n_embeddings):
column = Group()
for j in range(8):
box = Cube(side_length=0.3)
box.set_color(value_to_color(random.uniform(-10, 10)))
box.set_opacity(0.8)
column.add(box)
column.arrange(OUT, buff=0.05)
columns.add(column)
columns.arrange(RIGHT, buff=1.0)
columns.center()
# Set up 3D camera
frame.set_euler_angles(phi=60 * DEGREES, theta=-30 * DEGREES)
self.add(columns)
# Create arcs in 3D
arcs = VGroup()
for i, c1 in enumerate(columns):
for c2 in columns[i + 1:]:
start = c1.get_top() + 0.2 * UP
end = c2.get_top() + 0.2 * UP
mid = (start + end) / 2 + UP
arc = VMobject()
arc.set_points_smoothly([start, mid, end])
arc.set_stroke(
random_bright_color(hue_range=(0.1, 0.4)),
width=2 + 3 * random.random()
)
arcs.add(arc)
# Animate
self.play(
frame.animate.set_euler_angles(phi=70 * DEGREES, theta=-45 * DEGREES),
run_time=2
)
self.play(
LaggedStartMap(ShowCreation, arcs, lag_ratio=0.1),
run_time=3
)
self.play(
frame.animate.increment_theta(60 * DEGREES),
LaggedStartMap(VShowPassingFlash, arcs, time_width=1.5, lag_ratio=0.05),
run_time=4
)
self.play(
FadeOut(arcs),
FadeOut(columns),
)
examples/attention_pattern_dots.py
"""
Attention Pattern Dots Visualization
Shows the attention pattern as a grid of varying-sized dots,
where dot size represents attention weight.
"""
from manimlib import *
import numpy as np
def softmax(logits, temperature=1.0):
"""Compute softmax of logits array."""
logits = np.array(logits, dtype=float)
# Mask future tokens (causal attention)
logits = logits - np.max(logits)
exps = np.exp(logits / temperature)
return exps / np.sum(exps)
class AttentionPatternDots(InteractiveScene):
def construct(self):
# Parameters
N = 8
np.random.seed(42)
# Create grid
grid = Square(side_length=0.8).get_grid(N, N, buff=0)
grid.set_stroke(GREY_A, 1)
grid.stretch(0.95, 0)
grid.stretch(0.85, 1)
grid.move_to(0.5 * DOWN)
self.add(grid)
# Create query/key labels
q_template = Tex(R"\vec{\textbf{Q}}_0", font_size=36).set_color(YELLOW)
k_template = Tex(R"\vec{\textbf{K}}_0", font_size=36).set_color(TEAL)
q_substr = q_template.make_number_changeable("0")
k_substr = k_template.make_number_changeable("0")
qs = VGroup()
ks = VGroup()
for n, square in enumerate(grid[:N], start=1):
q_substr.set_value(n)
q_template.next_to(square, UP, buff=SMALL_BUFF)
qs.add(q_template.copy())
for k, square in enumerate(grid[::N], start=1):
k_substr.set_value(k)
k_template.next_to(square, LEFT, buff=SMALL_BUFF)
ks.add(k_template.copy())
self.play(
LaggedStartMap(FadeIn, qs, shift=0.2 * DOWN, lag_ratio=0.05),
LaggedStartMap(FadeIn, ks, shift=0.2 * RIGHT, lag_ratio=0.05),
)
# Generate attention pattern (causal masking)
values = np.random.normal(0, 1, (N, N))
# Apply causal mask
for n, row in enumerate(values):
row[:n] = -np.inf
# Softmax each column
attention_pattern = np.zeros_like(values)
for k in range(N):
attention_pattern[:, k] = softmax(values[:, k])
# Create dots based on attention weights
dots = VGroup()
for n in range(N): # row (key)
row_dots = VGroup()
for k in range(N): # column (query)
weight = attention_pattern[n, k]
dot = Dot(radius=0.35 * weight**0.5)
dot.move_to(grid[n * N + k])
# Color based on whether it's diagonal or not
if n == k:
dot.set_fill(YELLOW, 0.9)
elif n < k:
dot.set_fill(GREY_C, 0.8)
else: # Masked (should be zero)
dot.set_fill(RED, 0.2)
row_dots.add(dot)
dots.add(row_dots)
flat_dots = VGroup(*it.chain(*dots))
self.play(
LaggedStartMap(GrowFromCenter, flat_dots, lag_ratio=0.01),
run_time=2
)
self.wait()
# Add title
title = Text("Attention Pattern", font_size=60)
title.to_edge(UP)
self.play(Write(title))
self.wait()
# Highlight causal structure - masked region
mask_label = Text("Masked\n(future tokens)", font_size=30)
mask_label.set_color(RED)
mask_label.to_corner(DL)
masked_region = VGroup()
for n in range(N):
for k in range(n):
square = grid[n * N + k].copy()
square.set_fill(RED, 0.15)
square.set_stroke(RED, 1)
masked_region.add(square)
self.play(
FadeIn(masked_region, lag_ratio=0.02),
FadeIn(mask_label),
)
self.wait()
# Highlight self-attention (diagonal)
diag_label = Text("Self-attention\n(diagonal)", font_size=30)
diag_label.set_color(YELLOW)
diag_label.to_corner(DR)
diag_dots = VGroup(dots[i][i] for i in range(N))
self.play(
FadeIn(diag_label),
LaggedStart(
(dot.animate.scale(1.3).set_fill(YELLOW) for dot in diag_dots),
lag_ratio=0.1,
),
)
self.play(
LaggedStart(
(dot.animate.scale(1/1.3) for dot in diag_dots),
lag_ratio=0.1,
),
)
self.wait(2)
examples/attention_scenes.py
"""
Attention Mechanism Scenes - ManimGL Examples
A collection of scenes from 3Blue1Brown's Attention video,
adapted to be self-contained without external image dependencies.
Run with: manimgl attention_scenes.py <SceneName> -w -l
Available scenes:
- ShowMasking
- ScalingAPattern
- LowRankTransformation
- ThinkAboutOverallMap
- CrossAttention
- TwoHarrysExample
- QueryMap
- MultiHeadedAttention
"""
from manimlib import *
import numpy as np
import re
import itertools as it
import random
import warnings
# ============================================================
# Helper Functions
# ============================================================
def softmax(logits, temperature=1.0):
"""Compute softmax of logits array."""
logits = np.array(logits)
with warnings.catch_warnings():
warnings.filterwarnings('ignore')
logits = logits - np.max(logits)
exps = np.exp(np.divide(logits, temperature, where=temperature != 0))
if np.isinf(exps).any() or np.isnan(exps).any() or temperature == 0:
result = np.zeros_like(logits)
result[np.argmax(logits)] = 1
return result
return exps / np.sum(exps)
def value_to_color(
value,
low_positive_color=BLUE_E,
high_positive_color=BLUE_B,
low_negative_color=RED_E,
high_negative_color=RED_B,
min_value=0.0,
max_value=10.0
):
"""Map a value to a color using interpolation."""
alpha = clip(float(inverse_interpolate(min_value, max_value, abs(value))), 0, 1)
if value >= 0:
colors = (low_positive_color, high_positive_color)
else:
colors = (low_negative_color, high_negative_color)
return interpolate_color_by_hsl(*colors, alpha)
def break_into_pieces(phrase_mob, offsets):
"""Break a Text mobject into pieces at given character offsets."""
phrase = phrase_mob.get_string()
lhs = offsets
rhs = [*offsets[1:], len(phrase)]
result = []
for lh, rh in zip(lhs, rhs):
substr = phrase[lh:rh]
start = phrase_mob.substr_to_path_count(phrase[:lh])
end = start + phrase_mob.substr_to_path_count(substr)
result.append(phrase_mob[start:end])
return VGroup(*result)
def break_into_words(phrase_mob):
"""Break a Text mobject into individual words."""
offsets = [m.start() for m in re.finditer(" ", phrase_mob.get_string())]
return break_into_pieces(phrase_mob, [0, *offsets])
def get_piece_rectangles(
phrase_pieces,
h_buff=0.05,
v_buff=0.1,
fill_opacity=0.15,
fill_color=None,
stroke_width=1,
stroke_color=None,
hue_range=(0.5, 0.6),
leading_spaces=False,
):
"""Create colored rectangles behind phrase pieces."""
rects = VGroup()
height = phrase_pieces.get_height() + 2 * v_buff
last_right_x = phrase_pieces.get_x(LEFT)
for piece in phrase_pieces:
left_x = last_right_x if leading_spaces else piece.get_x(LEFT)
right_x = piece.get_x(RIGHT)
fill = random_bright_color(hue_range) if fill_color is None else fill_color
stroke = fill if stroke_color is None else stroke_color
rect = Rectangle(
width=right_x - left_x + 2 * h_buff,
height=height,
fill_color=fill,
fill_opacity=fill_opacity,
stroke_color=stroke,
stroke_width=stroke_width
)
if leading_spaces:
rect.set_x(left_x, LEFT)
else:
rect.move_to(piece)
rect.set_y(0)
rects.add(rect)
last_right_x = right_x
rects.match_y(phrase_pieces)
return rects
class WeightMatrix(DecimalMatrix):
"""A matrix display for neural network weights."""
def __init__(
self,
values=None,
shape=(6, 8),
value_range=(-9.9, 9.9),
ellipses_row=-2,
ellipses_col=-2,
num_decimal_places=1,
bracket_h_buff=0.1,
decimal_config=dict(include_sign=True),
low_positive_color=BLUE_E,
high_positive_color=BLUE_B,
low_negative_color=RED_E,
high_negative_color=RED_B,
):
if values is not None:
shape = values.shape
self.shape = shape
self.value_range = value_range
self.low_positive_color = low_positive_color
self.high_positive_color = high_positive_color
self.low_negative_color = low_negative_color
self.high_negative_color = high_negative_color
self.ellipses_row = ellipses_row
self.ellipses_col = ellipses_col
if values is None:
values = np.random.uniform(*self.value_range, size=shape)
super().__init__(
values,
num_decimal_places=num_decimal_places,
bracket_h_buff=bracket_h_buff,
decimal_config=decimal_config,
ellipses_row=ellipses_row,
ellipses_col=ellipses_col,
)
self.set_entry_colors()
def set_entry_colors(self):
for entry in self.get_entries():
if isinstance(entry, DecimalNumber):
entry.set_color(value_to_color(
entry.get_value(),
self.low_positive_color,
self.high_positive_color,
self.low_negative_color,
self.high_negative_color,
min_value=0.0,
max_value=self.value_range[1],
))
return self
class NumericEmbedding(DecimalMatrix):
"""A vector display for embeddings."""
def __init__(
self,
values=None,
length=8,
value_range=(-9.9, 9.9),
num_decimal_places=1,
bracket_h_buff=0.1,
decimal_config=dict(include_sign=True),
ellipses_row=-2,
):
if values is None:
values = np.random.uniform(*value_range, (length, 1))
super().__init__(
values,
num_decimal_places=num_decimal_places,
bracket_h_buff=bracket_h_buff,
decimal_config=decimal_config,
ellipses_row=ellipses_row,
)
class ContextAnimation(LaggedStart):
"""Animation showing context flowing between tokens."""
def __init__(
self,
target,
sources,
direction=UP,
hue_range=(0.1, 0.3),
time_width=2,
min_stroke_width=0,
max_stroke_width=5,
lag_ratio=None,
strengths=None,
run_time=3,
fix_in_frame=False,
path_arc=PI / 2,
**kwargs,
):
arcs = VGroup()
if strengths is None:
strengths = np.random.random(len(sources))**2
for source, strength in zip(sources, strengths):
sign = direction[1] * (-1)**int(source.get_x() < target.get_x())
arcs.add(Line(
source.get_edge_center(direction),
target.get_edge_center(direction),
path_arc=sign * path_arc,
stroke_color=random_bright_color(hue_range=hue_range),
stroke_width=interpolate(
min_stroke_width,
max_stroke_width,
strength,
)
))
if fix_in_frame:
arcs.fix_in_frame()
arcs.shuffle()
lag_ratio = 0.5 / len(arcs) if lag_ratio is None else lag_ratio
super().__init__(
*(
VShowPassingFlash(arc, time_width=time_width)
for arc in arcs
),
lag_ratio=lag_ratio,
run_time=run_time,
**kwargs,
)
class RandomizeMatrixEntries(Animation):
"""Animation that randomizes matrix entries."""
def __init__(self, matrix, **kwargs):
self.matrix = matrix
super().__init__(matrix, **kwargs)
def interpolate_mobject(self, alpha):
if random.random() < 0.1:
for entry in self.matrix.get_entries():
if isinstance(entry, DecimalNumber):
new_val = random.uniform(-9.9, 9.9)
entry.set_value(new_val)
# ============================================================
# Scene Definitions
# ============================================================
class ShowMasking(Scene):
"""Demonstrates causal masking in attention."""
def construct(self):
# Set up two patterns
shape = (6, 6)
left_grid = Square().get_grid(*shape, buff=0)
left_grid.set_shape(5.5, 5)
left_grid.to_edge(LEFT)
left_grid.set_y(-0.5)
left_grid.set_stroke(GREY_B, 1)
right_grid = left_grid.copy()
right_grid.to_edge(RIGHT)
grids = VGroup(left_grid, right_grid)
arrow = Arrow(left_grid, right_grid)
sm_label = Text("softmax")
sm_label.next_to(arrow, UP)
titles = VGroup(
Text("Unnormalized\nAttention Pattern"),
Text("Normalized\nAttention Pattern"),
)
for title, grid in zip(titles, grids):
title.next_to(grid, UP, buff=MED_LARGE_BUFF)
values_array = np.random.normal(0, 2, shape)
font_size = 30
raw_values = VGroup(
DecimalNumber(
value,
include_sign=True,
font_size=font_size,
).move_to(square)
for square, value in zip(left_grid, values_array.flatten())
)
self.add(left_grid)
self.add(right_grid)
self.add(titles)
self.add(arrow)
self.add(sm_label)
self.add(raw_values)
# Highlight lower lefts (masking)
changers = VGroup()
for n, dec in enumerate(raw_values):
i = n // shape[1]
j = n % shape[1]
if i > j:
changers.add(dec)
neg_inf = Tex(R"-\infty", font_size=36)
neg_inf.move_to(dec)
neg_inf.set_fill(RED, border_width=1.5)
dec.target = neg_inf
values_array[i, j] = -np.inf
rects = VGroup(map(SurroundingRectangle, changers))
rects.set_stroke(RED, 3)
self.play(LaggedStartMap(ShowCreation, rects))
self.play(
LaggedStartMap(FadeOut, rects),
LaggedStartMap(MoveToTarget, changers)
)
self.wait()
# Normalized values
normalized_array = np.array([
softmax(col)
for col in values_array.T
]).T
normalized_values = VGroup(
DecimalNumber(value, font_size=font_size).move_to(square)
for square, value in zip(right_grid, normalized_array.flatten())
)
for n, value in enumerate(normalized_values):
value.set_fill(opacity=interpolate(0.5, 1, rush_from(value.get_value())))
if (n // shape[1]) > (n % shape[1]):
value.set_fill(RED, 0.75)
self.play(
LaggedStart(
(FadeTransform(v1.copy(), v2)
for v1, v2 in zip(raw_values, normalized_values)),
lag_ratio=0.05,
group_type=Group
)
)
self.wait()
class ScalingAPattern(Scene):
"""Shows a large attention pattern scaling up."""
def construct(self):
# Position grid
N = 50
grid = Square(side_length=1.0).get_grid(N, N, buff=0)
grid.set_stroke(GREY_A, 1)
grid.stretch(0.89, 0)
grid.stretch(0.70, 1)
grid.move_to(5.0 * LEFT + 2.5 * UP, UL)
self.add(grid)
# Dots representing attention weights
values = np.random.normal(0, 1, (N, N))
dots = VGroup()
for n, row in enumerate(values):
row[:n] = -np.inf
for k, col in enumerate(values.T):
for n, value in enumerate(softmax(col)):
dot = Dot(radius=0.3 * value**0.75)
dot.move_to(grid[n * N + k])
dots.add(dot)
dots.set_fill(GREY_C, 1)
self.add(dots)
# Add Q and K symbols
q_template = Tex(R"\vec{\textbf{Q}}_0").set_color(YELLOW)
k_template = Tex(R"\vec{\textbf{K}}_0").set_color(TEAL)
for template in [q_template, k_template]:
template.scale(0.75)
template.substr = template.make_number_changeable("0")
qs = VGroup()
ks = VGroup()
for n, square in enumerate(grid[:N], start=1):
q_template.substr.set_value(n)
q_template.next_to(square, UP, buff=SMALL_BUFF)
qs.add(q_template.copy())
for k, square in enumerate(grid[::N], start=1):
k_template.substr.set_value(k)
k_template.next_to(square, LEFT, buff=2 * SMALL_BUFF)
ks.add(k_template.copy())
self.add(qs, ks)
# Slowly zoom out
self.play(
self.frame.animate.reorient(0, 0, 0, (14.72, -14.71, 0.0), 38.06),
grid.animate.set_stroke(width=1, opacity=0.25),
dots.animate.set_fill(GREY_B, 1).set_stroke(GREY_B, 1),
run_time=20,
)
self.wait()
class LowRankTransformation(Scene):
"""Visualizes low-rank transformation in attention."""
def construct(self):
frame = self.frame
frame.set_field_of_view(10 * DEGREES)
all_axes = VGroup(
self.get_3d_axes(),
self.get_2d_axes(),
self.get_3d_axes(),
)
all_axes.arrange(RIGHT, buff=2.0)
all_axes.set_width(FRAME_WIDTH - 2)
all_axes.move_to(0.5 * DOWN)
dim_labels = VGroup(
Text("12,288 dims"),
Text("128 dims"),
Text("12,288 dims"),
)
dim_labels.scale(0.75)
dim_labels.set_fill(GREY_A)
for label, axes in zip(dim_labels, all_axes):
label.next_to(axes, UP, buff=MED_LARGE_BUFF)
map_arrows = Tex(R"\rightarrow", font_size=96).replicate(2)
map_arrows.set_color(YELLOW)
for arrow, vect in zip(map_arrows, [LEFT, RIGHT]):
arrow.next_to(all_axes[1], vect, buff=0.5)
axes_group = VGroup(all_axes, dim_labels)
self.add(axes_group)
self.add(map_arrows)
# Add vectors
all_coords = [
(4, 2, 1),
(2, 3),
(-3, 3, -2),
]
colors = [BLUE, RED_B, RED_C]
vects = VGroup(
Arrow(axes.get_origin(), axes.c2p(*coords), buff=0, stroke_color=color)
for axes, coords, color in zip(all_axes, all_coords, colors)
)
self.add(vects[0])
for v1, v2 in zip(vects, vects[1:]):
self.play(TransformFromCopy(v1, v2))
for axes, vect in zip(all_axes, vects):
axes.add(vect)
for axes in all_axes[0::2]:
axes.add_updater(lambda m, dt: m.rotate(2 * dt * DEGREES, axis=m.y_axis.get_vector()))
self.wait(3)
# Add title
big_rect = SurroundingRectangle(axes_group, buff=0.5)
big_rect.round_corners(radius=0.5)
big_rect.set_stroke(RED_B, 2)
title = Text("Low-rank transformation", font_size=72)
title.next_to(big_rect, UP, buff=MED_LARGE_BUFF)
self.play(
ShowCreation(big_rect),
FadeIn(title, shift=0.25 * UP)
)
self.wait(5)
def get_3d_axes(self, height=3):
result = ThreeDAxes((-4, 4), (-4, 4), (-4, 4))
result.set_height(height)
result.rotate(20 * DEGREES, DOWN)
result.rotate(5 * DEGREES, RIGHT)
return result
def get_2d_axes(self, height=2):
plane = NumberPlane(
(-4, 4), (-4, 4),
faded_line_ratio=0,
background_line_style=dict(
stroke_color=GREY_B,
stroke_width=1,
stroke_opacity=0.5
)
)
plane.set_height(height)
return plane
class ThinkAboutOverallMap(Scene):
"""Simple scene showing a reminder about overall maps."""
def construct(self):
rect = Rectangle(6.5, 2.75)
rect.round_corners(radius=0.5)
rect.set_stroke(RED_B, 2)
label = Text("Think about the\noverall map")
label.next_to(rect, UP, aligned_edge=LEFT)
label.shift(0.5 * RIGHT)
self.play(
ShowCreation(rect),
FadeIn(label, UP),
)
self.wait()
class CrossAttention(Scene):
"""Shows cross-attention between two languages."""
def construct(self):
# Show both language phrases
en_tokens = self.get_words("I do not want to pet it")
fr_tokens = self.get_words("Je ne veux pas le caresser", hue_range=(0.2, 0.3))
phrases = VGroup(en_tokens, fr_tokens)
phrases.arrange(DOWN, buff=2.0)
self.play(LaggedStartMap(FadeIn, en_tokens, scale=2, lag_ratio=0.25))
self.wait()
self.play(LaggedStartMap(FadeIn, fr_tokens, scale=2, lag_ratio=0.25))
self.wait()
# Create attention pattern
unnormalized_pattern = [
[3, 0, 0, 0, 0, 0],
[0, 1, 1.3, 1, 0, 0],
[0, 3, 0, 3, 0, 0],
[0, 0, 3, 0, 0, 0],
[0, 0, 0, 0, 0, 3],
[0, 0, 0, 0, 0, 3],
[0, 0, 0, 0, 3, 0],
]
attention_pattern = np.array([
softmax(col) for col in unnormalized_pattern
]).T
# Show connections
lines = VGroup()
for n, row in enumerate(attention_pattern.T):
for k, value in enumerate(row):
line = Line(en_tokens[n].get_bottom(), fr_tokens[k].get_top(), buff=0)
line.set_stroke(
color=[
en_tokens[n][0].get_color(),
fr_tokens[k][0].get_color(),
],
width=3,
opacity=value,
)
lines.add(line)
self.play(ShowCreation(lines, lag_ratio=0.01, run_time=2))
self.wait(2)
self.play(FadeOut(lines))
# Create grid
grid = Square().get_grid(len(fr_tokens), len(en_tokens), buff=0)
grid.stretch(1.2, 0)
grid.set_stroke(GREY_B, 1)
grid.set_height(5.0)
grid.to_edge(DOWN, buff=SMALL_BUFF)
grid.set_x(1)
# Create qk symbols
q_sym_generator = self.get_symbol_generator(R"\vec{\textbf{Q}}_0", color=YELLOW)
k_sym_generator = self.get_symbol_generator(R"\vec{\textbf{K}}_0", color=TEAL)
e_sym_generator = self.get_symbol_generator(R"\vec{\textbf{E}}_0", color=GREY_B)
f_sym_generator = self.get_symbol_generator(R"\vec{\textbf{F}}_0", color=BLUE)
q_syms = VGroup(q_sym_generator(n + 1) for n in range(len(en_tokens)))
k_syms = VGroup(k_sym_generator(n + 1) for n in range(len(fr_tokens)))
e_syms = VGroup(e_sym_generator(n + 1) for n in range(len(en_tokens)))
f_syms = VGroup(f_sym_generator(n + 1) for n in range(len(fr_tokens)))
VGroup(q_syms, k_syms, e_syms, f_syms).scale(0.65)
for q_sym, e_sym, square in zip(q_syms, e_syms, grid):
q_sym.next_to(square, UP, SMALL_BUFF)
e_sym.next_to(q_sym, UP, buff=0.65)
for k_sym, f_sym, square in zip(k_syms, f_syms, grid[::len(en_tokens)]):
k_sym.next_to(square, LEFT, SMALL_BUFF)
f_sym.next_to(k_sym, LEFT, buff=0.75)
q_arrows = VGroup(Arrow(*pair, buff=0.1) for pair in zip(e_syms, q_syms))
k_arrows = VGroup(Arrow(*pair, buff=0.1) for pair in zip(f_syms, k_syms))
e_arrows = VGroup(Vector(0.4 * DOWN).next_to(e_sym, UP, SMALL_BUFF) for e_sym in e_syms)
f_arrows = VGroup(Vector(0.5 * RIGHT).next_to(f_sym, LEFT, SMALL_BUFF) for f_sym in f_syms)
arrows = VGroup(q_arrows, k_arrows, e_arrows, f_arrows)
arrows.set_color(GREY_B)
wq_syms = VGroup(
Tex("W_Q", font_size=20, fill_color=YELLOW).next_to(arrow, RIGHT, buff=0.1)
for arrow in q_arrows
)
wk_syms = VGroup(
Tex("W_K", font_size=20, fill_color=TEAL).next_to(arrow, UP, buff=0.1)
for arrow in k_arrows
)
# Move tokens into place
en_tokens.target = en_tokens.generate_target()
fr_tokens.target = fr_tokens.generate_target()
for token, arrow in zip(en_tokens.target, e_arrows):
token.next_to(arrow, UP, SMALL_BUFF)
for token, arrow in zip(fr_tokens.target, f_arrows):
token.next_to(arrow, LEFT, SMALL_BUFF)
self.play(
MoveToTarget(en_tokens),
MoveToTarget(fr_tokens),
)
self.play(
LaggedStartMap(GrowArrow, e_arrows),
LaggedStartMap(GrowArrow, f_arrows),
LaggedStartMap(FadeIn, e_syms, shift=0.25 * DOWN),
LaggedStartMap(FadeIn, f_syms, shift=0.25 * RIGHT),
lag_ratio=0.25,
run_time=1.5,
)
self.play(
LaggedStartMap(GrowArrow, q_arrows),
LaggedStartMap(GrowArrow, k_arrows),
LaggedStartMap(FadeIn, wq_syms, shift=0.25 * DOWN),
LaggedStartMap(FadeIn, wk_syms, shift=0.25 * RIGHT),
LaggedStartMap(FadeIn, q_syms, shift=0.5 * DOWN),
LaggedStartMap(FadeIn, k_syms, shift=0.5 * RIGHT),
lag_ratio=0.25,
run_time=1.5,
)
self.play(FadeIn(grid, lag_ratio=1e-2), run_time=2)
self.wait()
# Show dot products
dot_prods = VGroup()
for q_sym in q_syms:
for k_sym in k_syms:
dot = Tex(".")
dot.match_x(q_sym)
dot.match_y(k_sym)
dot_prod = VGroup(q_sym.copy(), dot, k_sym.copy())
dot_prod.target = dot_prod.generate_target()
dot_prod.target.arrange(RIGHT, buff=SMALL_BUFF)
dot_prod.target.scale(0.7)
dot_prod.target.move_to(dot)
dot.set_opacity(0)
dot_prods.add(dot_prod)
self.play(
LaggedStartMap(MoveToTarget, dot_prods, lag_ratio=0.01),
run_time=3
)
self.wait()
# Show dots
dots = VGroup()
for square, value in zip(grid, attention_pattern.flatten()):
dot = Dot(radius=value * 0.4)
dot.set_fill(GREY_B, 1)
dot.move_to(square)
dots.add(dot)
self.play(
LaggedStartMap(GrowFromCenter, dots, lag_ratio=1e-2),
dot_prods.animate.set_fill(opacity=0.2).set_anim_args(lag_ratio=1e-3),
run_time=4
)
self.wait()
def get_words(self, text, hue_range=(0.5, 0.6)):
sent = Text(text)
tokens = break_into_words(sent)
rects = get_piece_rectangles(tokens, hue_range=hue_range)
return VGroup(VGroup(*pair) for pair in zip(rects, tokens))
def get_symbol_generator(self, raw_tex, subsrc="0", color=WHITE):
template = Tex(raw_tex)
template.set_color(color)
subscr = template.make_number_changeable(subsrc)
def get_sym(number):
subscr.set_value(number)
return template.copy()
return get_sym
class TwoHarrysExample(Scene):
"""Shows how context disambiguates 'Harry'."""
def construct(self):
s1, s2 = sentences = VGroup(
break_into_words(Text("... " + " ... ".join(words)))
for words in [
("wizard", "Hogwarts", "Hermione", "Harry"),
("Queen", "Sussex", "William", "Harry"),
]
)
sentences.arrange(DOWN, buff=2.0, aligned_edge=RIGHT)
sentences.to_edge(LEFT)
def context_anim(group):
self.play(
ContextAnimation(
group[-1],
VGroup(*it.chain(*group[1:-1:2])),
direction=DOWN,
path_arc=PI / 4,
run_time=5,
lag_ratio=0.025,
)
)
self.add(s1)
context_anim(s1)
self.wait()
self.play(FadeTransformPieces(s1.copy(), s2))
context_anim(s2)
class QueryMap(Scene):
"""Shows how embedding space maps to query/key space."""
map_tex = "W_Q"
map_color = YELLOW
src_name = "Creature"
pos_word = "position 4"
trg_name = "Any adjectives\nbefore position 4?"
in_vect_color = BLUE_B
in_vect_coords = (3, 2, -2)
out_vect_coords = (-2, -1)
def construct(self):
# Setup 3d axes
axes_3d = ThreeDAxes((-4, 4), (-3, 3), (-4, 4))
xz_plane = NumberPlane(
(-4, 4), (-4, 4),
background_line_style=dict(
stroke_color=GREY,
stroke_width=1,
),
faded_line_ratio=0
)
xz_plane.rotate(90 * DEGREES, RIGHT)
xz_plane.move_to(axes_3d)
xz_plane.axes.set_opacity(0)
axes_3d.add(xz_plane)
axes_3d.set_height(2.0)
self.set_floor_plane("xz")
frame = self.frame
frame.set_field_of_view(30 * DEGREES)
frame.reorient(-32, 0, 0, (2.13, 1.11, 0.27), 4.50)
frame.add_ambient_rotation(1 * DEGREES)
self.add(axes_3d)
# Set up target plane
plane = NumberPlane(
(-3, 3), (-3, 3),
faded_line_ratio=1,
background_line_style=dict(
stroke_color=BLUE,
stroke_width=1,
stroke_opacity=0.75
),
faded_line_style=dict(
stroke_color=BLUE,
stroke_width=1,
stroke_opacity=0.25,
)
)
plane.set_height(3.5)
plane.to_corner(DR)
arrow = Tex(R"\longrightarrow")
arrow.set_width(2)
arrow.stretch(0.75, 1)
arrow.next_to(plane, LEFT, buff=1.0)
arrow.set_color(self.map_color)
map_name = Tex(self.map_tex, font_size=72)
map_name.set_color(self.map_color)
map_name.next_to(arrow.get_left(), UR, SMALL_BUFF).shift(0.25 * RIGHT)
for mob in [plane, arrow, map_name]:
mob.fix_in_frame()
self.add(plane)
self.add(arrow)
self.add(map_name)
# Add titles
titles = VGroup(
Text("Embedding space"),
Text("Query/Key space"),
)
subtitles = VGroup(
Text("12,288-dimensional"),
Text("128-dimensional"),
)
subtitles.scale(0.75)
subtitles.set_fill(GREY_B)
x_values = [-frame.get_x() * FRAME_HEIGHT / frame.get_height(), plane.get_x()]
for title, subtitle, x_value in zip(titles, subtitles, x_values):
subtitle.next_to(title, DOWN, SMALL_BUFF)
title.add(subtitle)
title.next_to(plane, UP, MED_LARGE_BUFF)
title.set_x(x_value)
title.fix_in_frame()
self.add(titles)
# Show vector transformation
in_vect = Arrow(axes_3d.get_origin(), axes_3d.c2p(*self.in_vect_coords), buff=0)
in_vect.set_stroke(self.in_vect_color)
in_vect_label = TexText("``" + self.src_name + "''", font_size=24)
pos_label = Text(self.pos_word, font_size=16)
pos_label.next_to(in_vect_label, DOWN, SMALL_BUFF)
pos_label.set_opacity(0.75)
in_vect_label.add(pos_label)
in_vect_label.set_color(self.in_vect_color)
in_vect_label.next_to(in_vect.get_end(), UP, SMALL_BUFF)
out_vect = Arrow(plane.get_origin(), plane.c2p(*self.out_vect_coords), buff=0)
out_vect.set_stroke(self.map_color)
out_vect_label = Text(self.trg_name, font_size=30)
out_vect_label.next_to(out_vect.get_end(), DOWN, buff=0.2)
out_vect_label.set_backstroke(BLACK, 5)
VGroup(out_vect, out_vect_label).fix_in_frame()
self.play(
GrowArrow(in_vect),
FadeInFromPoint(in_vect_label, axes_3d.get_origin()),
)
self.wait(2)
self.play(
TransformFromCopy(in_vect, out_vect),
FadeTransform(in_vect_label.copy(), out_vect_label),
run_time=2,
)
self.wait(10)
self.play(FadeOut(out_vect_label))
self.wait(3)
class MultiHeadedAttention(Scene):
"""Demonstrates multi-headed attention with procedural patterns."""
def construct(self):
# Mention head
background_rect = FullScreenRectangle()
single_title = Text("Single head of attention")
multiple_title = Text("Multi-headed attention")
titles = VGroup(single_title, multiple_title)
for title in titles:
title.scale(1.25)
title.to_edge(UP)
# Create attention pattern instead of loading image
screen_rect = ScreenRectangle(height=6)
screen_rect.set_fill(BLACK, 1)
screen_rect.set_stroke(WHITE, 3)
screen_rect.next_to(titles, DOWN, buff=0.5)
head = single_title["head"][0]
self.add(background_rect)
self.add(single_title)
self.add(screen_rect)
self.wait()
self.play(
FlashAround(head, run_time=2),
head.animate.set_color(YELLOW),
)
self.wait()
# Change title
kw = dict(path_arc=45 * DEGREES)
self.play(
FadeTransform(single_title["Single"], multiple_title["Multi-"], **kw),
FadeTransform(single_title["head"], multiple_title["head"], **kw),
FadeIn(multiple_title["ed"], 0.25 * RIGHT),
FadeTransform(single_title["attention"], multiple_title["attention"], **kw),
FadeOut(single_title["of"])
)
self.add(multiple_title)
# Set up procedural attention pattern heads
n_heads = 15
heads = Group()
for n in range(n_heads):
# Create procedural attention pattern
pattern_grid = self.create_attention_pattern(seed=n * 7)
pattern_grid.set_opacity(1)
pattern_grid.shift(0.01 * OUT)
rect = SurroundingRectangle(pattern_grid, buff=0)
rect.set_fill(BLACK, 0.75)
rect.set_stroke(WHITE, 1, 1)
heads.add(Group(rect, pattern_grid))
# Show many parallel layers
self.set_floor_plane("xz")
frame = self.frame
multiple_title.fix_in_frame()
background_rect.fix_in_frame()
heads.set_height(4)
heads.arrange(OUT, buff=1.0)
heads.move_to(DOWN)
pre_head = self.create_attention_pattern(seed=0)
pre_head.replace(screen_rect)
pre_head_rect = SurroundingRectangle(pre_head, buff=0)
pre_head_rect.set_fill(BLACK, 0.75)
pre_head_rect.set_stroke(WHITE, 1, 1)
pre_head = Group(pre_head_rect, pre_head)
self.add(pre_head)
self.wait()
self.play(
frame.animate.reorient(41, -12, 0, (-1.0, -1.42, 1.09), 12.90).set_anim_args(run_time=2),
background_rect.animate.set_fill(opacity=0.75),
FadeTransform(pre_head, heads[-1], time_span=(1, 2)),
)
self.play(
frame.animate.reorient(48, -11, 0, (-1.0, -1.42, 1.09), 12.90),
LaggedStart(
(FadeTransform(heads[-1].copy(), image)
for image in heads),
lag_ratio=0.1,
group_type=Group,
),
run_time=4,
)
self.add(heads)
self.wait()
# Show matrices
colors = [YELLOW, TEAL, RED, PINK]
texs = ["W_Q", "W_K", R"\downarrow W_V", R"\uparrow W_V"]
n_shown = 9
wq_syms, wk_syms, wv_down_syms, wv_up_syms = sym_groups = VGroup(
VGroup(
Tex(tex + f"^{{({n})}}", font_size=36).next_to(image, UP, MED_SMALL_BUFF)
for n, image in enumerate(heads[:-n_shown - 1:-1], start=1)
).set_color(color).set_backstroke(BLACK, 5)
for tex, color in zip(texs, colors)
)
for group in wv_down_syms, wv_up_syms:
for sym in group:
sym[0].next_to(sym[1], LEFT, buff=0.025)
dots = Tex(R"\dots", font_size=90)
dots.rotate(PI / 2, UP)
sym_rot_angle = 70 * DEGREES
for syms in sym_groups:
syms.align_to(heads, LEFT)
for sym in syms:
sym.rotate(sym_rot_angle, UP)
dots.next_to(syms, IN, buff=0.5)
dots.match_style(syms[0])
syms.add(dots.copy())
up_shift = 0.75 * UP
self.play(
LaggedStartMap(FadeIn, wq_syms, shift=0.2 * UP, lag_ratio=0.25),
frame.animate.reorient(59, -7, 0, (-1.62, 0.25, 1.29), 14.18),
run_time=2,
)
for n in range(1, len(sym_groups)):
self.play(
LaggedStartMap(FadeIn, sym_groups[n], shift=0.2 * UP, lag_ratio=0.1),
sym_groups[:n].animate.shift(up_shift),
run_time=1,
)
self.wait()
# Count up 96 heads
depth = heads.get_depth()
brace = Brace(Line(LEFT, RIGHT).set_width(0.5 * depth), UP).scale(2)
brace_label = brace.get_text("96", font_size=96, buff=MED_SMALL_BUFF)
brace_group = VGroup(brace, brace_label)
brace_group.rotate(PI / 2, UP)
brace_group.next_to(heads, UP, buff=MED_LARGE_BUFF)
self.add(brace, brace_label, sym_groups)
self.play(
frame.animate.reorient(62, -6, 0, (-0.92, -0.08, -0.51), 14.18).set_anim_args(run_time=5),
GrowFromCenter(brace),
sym_groups.animate.set_fill(opacity=0.5).set_stroke(width=0),
FadeIn(brace_label, 0.5 * UP, time_span=(0.5, 1.5)),
)
self.wait(2)
def create_attention_pattern(self, n_rows=8, seed=0):
"""Create a procedural attention pattern grid."""
np.random.seed(seed)
grid = Square().get_grid(n_rows, 1, buff=0).get_grid(1, n_rows, buff=0)
grid.set_stroke(WHITE, 1, 0.5)
grid.set_height(3.0)
pattern = np.random.normal(0, 1, (n_rows, n_rows))
for n in range(len(pattern[0])):
pattern[:, n][n + 1:] = -np.inf
pattern[:, n] = softmax(pattern[:, n])
pattern = pattern.T
dots = VGroup()
for col, values in zip(grid, pattern):
for square, value in zip(col, values):
if value < 1e-3:
continue
dot = Dot(radius=0.4 * square.get_height() * value)
dot.move_to(square)
dots.add(dot)
dots.set_fill(GREY_B, 1)
grid.add(dots)
return grid
examples/attention_softmax_masking.py
"""
Attention Softmax with Masking Visualization
Shows how masking works in transformer attention - lower triangle gets -infinity
before softmax, producing zeros in the attention pattern.
"""
from manimlib import *
import numpy as np
def softmax(logits, temperature=1.0):
"""Compute softmax of logits array."""
logits = np.array(logits)
logits = logits - np.max(logits) # For numerical stability
exps = np.exp(logits / temperature)
if np.isinf(exps).any() or np.isnan(exps).any():
result = np.zeros_like(logits)
result[np.argmax(logits)] = 1
return result
return exps / np.sum(exps)
class AttentionSoftmaxMasking(InteractiveScene):
def construct(self):
# Set up two grids: raw scores and normalized
shape = (6, 6)
left_grid = Square().get_grid(*shape, buff=0)
left_grid.set_shape(5.5, 5)
left_grid.to_edge(LEFT)
left_grid.set_y(-0.5)
left_grid.set_stroke(GREY_B, 1)
right_grid = left_grid.copy()
right_grid.to_edge(RIGHT)
grids = VGroup(left_grid, right_grid)
arrow = Arrow(left_grid, right_grid)
sm_label = Text("softmax")
sm_label.next_to(arrow, UP)
titles = VGroup(
Text("Unnormalized\nAttention Pattern"),
Text("Normalized\nAttention Pattern"),
)
for title, grid in zip(titles, grids):
title.next_to(grid, UP, buff=MED_LARGE_BUFF)
# Create random values for attention scores
values_array = np.random.normal(0, 2, shape)
font_size = 30
raw_values = VGroup(
DecimalNumber(
value,
include_sign=True,
font_size=font_size,
).move_to(square)
for square, value in zip(left_grid, values_array.flatten())
)
self.add(left_grid)
self.add(right_grid)
self.add(titles)
self.add(arrow)
self.add(sm_label)
self.add(raw_values)
self.wait()
# Highlight lower triangle (future tokens - to be masked)
changers = VGroup()
for n, dec in enumerate(raw_values):
i = n // shape[1]
j = n % shape[1]
if i > j: # Below diagonal - future tokens
changers.add(dec)
neg_inf = Tex(R"-\infty", font_size=36)
neg_inf.move_to(dec)
neg_inf.set_fill(RED, border_width=1.5)
dec.target = neg_inf
values_array[i, j] = -np.inf
rects = VGroup(map(SurroundingRectangle, changers))
rects.set_stroke(RED, 3)
self.play(LaggedStartMap(ShowCreation, rects))
self.play(
LaggedStartMap(FadeOut, rects),
LaggedStartMap(MoveToTarget, changers)
)
self.wait()
# Compute and show normalized values
normalized_array = np.array([
softmax(col)
for col in values_array.T
]).T
normalized_values = VGroup(
DecimalNumber(value, font_size=font_size).move_to(square)
for square, value in zip(right_grid, normalized_array.flatten())
)
# Color by value and mark zeros
for n, value in enumerate(normalized_values):
val = value.get_value()
value.set_fill(opacity=interpolate(0.5, 1, min(val * 3, 1)))
if (n // shape[1]) > (n % shape[1]):
value.set_fill(RED, 0.75)
self.play(
LaggedStart(
(FadeTransform(v1.copy(), v2)
for v1, v2 in zip(raw_values, normalized_values)),
lag_ratio=0.05,
group_type=Group
)
)
self.wait(2)
examples/autoregressive_flow.py
"""
Autoregressive Flow Visualization
Demonstrates the flow of text through a transformer model,
showing how text enters and probability distributions emerge.
Run with: manimgl autoregressive_flow.py AutoregressiveFlow
"""
from manimlib import *
import numpy as np
def get_paragraph(words, line_len=40, font_size=48):
"""Handle word wrapping for text display."""
words = list(map(str.strip, words))
word_lens = list(map(len, words))
lines = []
lh, rh = 0, 0
while rh < len(words):
rh += 1
if sum(word_lens[lh:rh]) > line_len:
rh -= 1
lines.append(words[lh:rh])
lh = rh
lines.append(words[lh:])
text = "\n".join([" ".join(line).strip() for line in lines])
return Text(text, alignment="LEFT", font_size=font_size)
class AutoregressiveFlow(InteractiveScene):
"""
Shows how text flows through a transformer-like machine,
demonstrating the autoregressive generation process.
"""
def construct(self):
# Create the "machine" visualization
machine = self.get_transformer_drawing()
machine.set_height(3.5)
machine.to_edge(LEFT, buff=0.5)
# Input text
input_text = "The quick brown fox"
text_mob = Text(input_text, font_size=32)
text_mob.to_edge(UP, buff=1.0)
text_mob.set_color(BLUE_B)
# Sample predictions
predictions = [" jumps", " ran", " leaped", " went", " moved"]
probs = np.array([0.42, 0.28, 0.15, 0.10, 0.05])
# Build distribution
bar_groups = self.build_distribution(predictions, probs)
bar_groups.next_to(machine, RIGHT, buff=1.5)
bar_groups.align_to(machine, UP)
# Arrows
in_arrow = Arrow(text_mob.get_bottom(), machine[0][0].get_top(), buff=0.2)
in_arrow.set_color(BLUE)
out_arrow = Arrow(machine[0][-1].get_right(), bar_groups.get_left(), buff=0.3)
out_arrow.set_color(TEAL)
# Labels
input_label = Text("Input Context", font_size=24)
input_label.next_to(text_mob, LEFT)
output_label = Text("Output\nProbabilities", font_size=24, alignment="CENTER")
output_label.next_to(bar_groups, RIGHT)
# Animate
self.play(FadeIn(machine))
self.wait(0.5)
self.play(Write(text_mob), FadeIn(input_label))
self.play(GrowArrow(in_arrow))
# Animate text flowing into machine
text_copy = text_mob.copy()
self.play(
text_copy.animate.scale(0.5).move_to(machine[0][0].get_top()),
run_time=0.5
)
self.play(
FadeOut(text_copy, shift=DOWN),
self.animate_machine_processing(machine),
run_time=1.5
)
# Output emerges
self.play(GrowArrow(out_arrow))
self.play(
LaggedStart(
*(FadeIn(bg, shift=RIGHT) for bg in bar_groups),
lag_ratio=0.1,
run_time=1.5
),
FadeIn(output_label)
)
self.wait(2)
def get_transformer_drawing(self):
"""Create a 3D-like stack of blocks representing the transformer."""
blocks = VGroup(*(
VGroup(
Rectangle(2.5, 0.3).set_fill(GREY_D, 1).set_stroke(WHITE, 1),
)
for n in range(8)
))
blocks.arrange(DOWN, buff=0.05)
# Add "Transformer" label
label = Text("Transformer", font_size=28)
label.next_to(blocks, UP, buff=0.3)
return VGroup(blocks, label)
def animate_machine_processing(self, machine):
"""Animate the blocks lighting up in sequence."""
blocks = machine[0]
return LaggedStart(
*(
block[0].animate.set_fill(TEAL, 0.8).set_anim_args(
rate_func=there_and_back
)
for block in blocks
),
lag_ratio=0.15,
run_time=1.5
)
def build_distribution(self, words, probs, font_size=24, width_100p=2.0, bar_height=0.25):
"""Build bar chart visualization of token probabilities."""
labels = VGroup(*(Text(word, font_size=font_size) for word in words))
bars = VGroup(*(
Rectangle(prob * width_100p, bar_height)
for prob in probs
))
bars.arrange(DOWN, aligned_edge=LEFT, buff=0.4 * bar_height)
bars.set_fill(opacity=1)
bars.set_submobject_colors_by_gradient(TEAL, YELLOW)
bars.set_stroke(WHITE, 1)
bar_groups = VGroup()
for label, bar, prob in zip(labels, bars, probs):
prob_label = Integer(int(100 * prob), unit="%", font_size=0.75 * font_size)
prob_label.next_to(bar, RIGHT, buff=SMALL_BUFF)
label.next_to(bar, LEFT)
bar_groups.add(VGroup(label, bar, prob_label))
return bar_groups
class TextToMachineFlow(InteractiveScene):
"""
Simpler version showing text entering a machine block.
"""
def construct(self):
# Machine box
machine = Rectangle(3, 2)
machine.set_fill(GREY_D, 0.8)
machine.set_stroke(WHITE, 2)
machine_label = Text("LLM", font_size=36)
machine_label.move_to(machine)
machine_group = VGroup(machine, machine_label)
machine_group.center()
# Input text
input_words = ["The", "weather", "today", "is"]
word_mobs = VGroup(*(Text(w, font_size=28) for w in input_words))
word_mobs.arrange(RIGHT, buff=0.3)
word_mobs.next_to(machine, UP, buff=1.5)
word_mobs.set_color(BLUE_B)
# Output predictions
output_words = ["sunny", "rainy", "cloudy", "warm"]
output_probs = [0.45, 0.25, 0.20, 0.10]
output_mobs = VGroup()
for word, prob in zip(output_words, output_probs):
text = Text(f"{word}: {int(prob*100)}%", font_size=24)
output_mobs.add(text)
output_mobs.arrange(DOWN, aligned_edge=LEFT, buff=0.2)
output_mobs.next_to(machine, DOWN, buff=1.0)
output_mobs.set_color(TEAL)
# Arrows
in_arrow = Arrow(word_mobs.get_bottom(), machine.get_top(), buff=0.1)
out_arrow = Arrow(machine.get_bottom(), output_mobs.get_top(), buff=0.1)
# Animate
self.play(FadeIn(machine_group))
self.play(
LaggedStart(
*(FadeIn(w, shift=DOWN) for w in word_mobs),
lag_ratio=0.2
)
)
self.play(GrowArrow(in_arrow))
# Words flow in
self.play(
LaggedStart(
*(
w.animate.scale(0.3).move_to(machine.get_center())
for w in word_mobs.copy()
),
lag_ratio=0.1
),
machine.animate.set_fill(TEAL, 0.3).set_anim_args(rate_func=there_and_back),
run_time=1.5
)
# Output emerges
self.play(GrowArrow(out_arrow))
self.play(
LaggedStart(
*(FadeIn(o, shift=DOWN) for o in output_mobs),
lag_ratio=0.15
)
)
self.wait(2)
examples/basic_multihead.py
"""
Basic Multi-Head Attention - ManimGL (using Scene, not InteractiveScene)
Run with: manimgl basic_multihead.py MultiHeadBasic -w -l
"""
from manimlib import *
import numpy as np
def softmax(logits):
logits = np.array(logits)
logits = logits - np.max(logits)
exps = np.exp(logits)
return exps / np.sum(exps)
class AttentionGrid(VGroup):
"""Attention pattern grid."""
def __init__(self, n=6, seed=0, **kwargs):
super().__init__(**kwargs)
np.random.seed(seed)
cell = 0.35
grid = VGroup()
for i in range(n):
for j in range(n):
sq = Square(side_length=cell)
sq.set_stroke(WHITE, 0.5, 0.3)
sq.move_to([j * cell, -i * cell, 0])
grid.add(sq)
grid.center()
# Causal pattern
pattern = np.random.randn(n, n)
for col in range(n):
pattern[:, col][col + 1:] = -np.inf
valid = pattern[:, col][:col + 1]
pattern[:, col][:col + 1] = softmax(valid)
pattern[:, col][col + 1:] = 0
dots = VGroup()
for i in range(n):
for j in range(n):
v = pattern[i, j]
if v > 0.05:
d = Dot(radius=cell * 0.4 * v)
d.set_fill(GREY_B)
d.move_to(grid[i * n + j])
dots.add(d)
border = SurroundingRectangle(grid, buff=0.03)
border.set_stroke(WHITE, 2)
border.set_fill(BLACK, 0.9)
self.add(border, grid, dots)
class MultiHeadBasic(Scene):
"""Basic multi-head attention visualization."""
def construct(self):
# Title
title = Text("Multi-Head Attention")
title.to_edge(UP)
self.play(Write(title))
self.wait()
# Create multiple attention heads
heads = VGroup()
for i in range(6):
head = AttentionGrid(n=5, seed=i * 10)
head.set_height(1.5)
heads.add(head)
heads.arrange_in_grid(n_rows=2, n_cols=3, buff=0.5)
heads.next_to(title, DOWN, buff=0.5)
# Labels (using Text to avoid LaTeX dependency issues)
labels = VGroup()
for i, head in enumerate(heads):
label = Text(f"Head {i+1}", font_size=18)
label.set_color(YELLOW)
label.next_to(head, UP, buff=0.1)
labels.add(label)
# Show heads one by one
self.play(
LaggedStart(
*[FadeIn(h, scale=0.8) for h in heads],
lag_ratio=0.2
),
run_time=3
)
self.play(
LaggedStart(*[FadeIn(l) for l in labels], lag_ratio=0.1)
)
self.wait()
# Explanation
explanation = VGroup(
Text("Each head learns different patterns:", font_size=24),
Text("• Subject-verb relationships", font_size=20, color=BLUE),
Text("• Adjective-noun connections", font_size=20, color=GREEN),
Text("• Positional patterns", font_size=20, color=YELLOW),
)
explanation.arrange(DOWN, aligned_edge=LEFT, buff=0.15)
explanation.to_edge(DOWN, buff=0.5)
self.play(
LaggedStart(*[Write(e) for e in explanation], lag_ratio=0.3)
)
self.wait(2)
class MultiHead3D(Scene):
"""3D multi-head visualization using Scene (simpler)."""
def construct(self):
frame = self.camera.frame
# Title (fixed in frame)
title = Text("Multi-Head Attention in 3D")
title.to_edge(UP)
title.fix_in_frame()
self.add(title)
# Create heads
heads = Group()
for i in range(8):
head = AttentionGrid(n=5, seed=i * 7)
head.set_height(2)
heads.add(head)
# Arrange in depth
heads.arrange(OUT, buff=0.7)
heads.center()
# Start with one head
self.add(heads[-1])
self.wait()
# Rotate camera
self.play(
frame.animate.set_euler_angles(
phi=70 * DEGREES,
theta=-45 * DEGREES
),
run_time=2
)
# Show all heads
self.play(
LaggedStart(
*[FadeIn(h, shift=OUT * 0.3) for h in heads[:-1]],
lag_ratio=0.15
),
run_time=3
)
self.wait()
# Add labels (using Text to avoid LaTeX dependency)
wq_labels = VGroup()
for i, head in enumerate(list(heads)[::-1][:4]):
label = Text(f"H{i+1}", font_size=24, color=YELLOW)
label.next_to(head, UP, buff=0.2)
label.rotate(70 * DEGREES, RIGHT)
label.rotate(-45 * DEGREES, OUT)
wq_labels.add(label)
self.play(
LaggedStart(*[FadeIn(l, shift=UP * 0.2) for l in wq_labels], lag_ratio=0.2)
)
self.wait()
# Rotate around
self.play(
frame.animate.increment_theta(60 * DEGREES),
run_time=4
)
self.wait()
examples/bloch_sphere_3d.py
"""
Bloch Sphere 3D Visualization
=============================
Displays a quantum state vector in 3D space with a surrounding Bloch sphere.
The vector rotates and can be observed from different angles with ambient
camera rotation.
Key concepts demonstrated:
- ThreeDAxes for 3D coordinate system
- Sphere and SurfaceMesh for Bloch sphere visualization
- frame.add_ambient_rotation for continuous camera movement
- Vector with set_perpendicular_to_camera for billboard effect
"""
from manimlib import *
class BlochSphere3D(InteractiveScene):
"""Visualize a quantum state as a vector on the Bloch sphere."""
def construct(self):
frame = self.frame
# Set up 3D axes
axes = ThreeDAxes((-1, 1), (-1, 1), (-1, 1))
axes.scale(2.0)
# Add a subtle reference plane
plane = NumberPlane(
(-1, 1 - 1e-5),
(-1, 1 - 1e-5),
faded_line_ratio=5
)
plane.scale(2.0)
plane.background_lines.set_stroke(opacity=0.5)
plane.faded_lines.set_stroke(opacity=0.25)
plane.axes.set_stroke(opacity=0.25)
# Set up camera orientation and ambient rotation
frame.reorient(14, 76, 0)
frame.add_ambient_rotation(3 * DEG)
self.add(plane, axes)
# Create the state vector
vector = Vector(
2 * normalize([1, 1, 2]),
thickness=5,
fill_color=TEAL
)
vector.set_fill(border_width=2)
vector.always.set_perpendicular_to_camera(frame)
self.play(GrowArrow(vector))
self.wait(6)
# Rotate the vector randomly
for _ in range(3):
axis = normalize(np.random.uniform(-1, 1, 3))
angle = np.random.uniform(PI / 4, PI)
self.play(
Rotate(vector, angle, axis=axis, about_point=ORIGIN),
run_time=2
)
self.wait()
# Show the Bloch sphere
sphere = Sphere(radius=2)
sphere.always_sort_to_camera(self.camera)
sphere.set_color(BLUE, 0.25)
sphere_mesh = SurfaceMesh(sphere, resolution=(41, 21))
sphere_mesh.set_stroke(WHITE, 0.5, 0.5)
self.play(
ShowCreation(sphere),
Write(sphere_mesh, lag_ratio=1e-3),
run_time=3
)
# Add axis labels
labels = VGroup(
Tex(R"|0\rangle"),
Tex(R"|1\rangle"),
Tex(R"|+\rangle"),
)
labels.scale(0.6)
labels.set_backstroke(BLACK, 3)
# Position labels at key points
labels[0].rotate(90 * DEG, RIGHT)
labels[0].next_to(axes.c2p(0, 0, 1), OUT + RIGHT, buff=0.1)
labels[1].rotate(90 * DEG, RIGHT)
labels[1].next_to(axes.c2p(0, 0, -1), OUT + RIGHT, buff=0.1)
labels[2].rotate(90 * DEG, RIGHT)
labels[2].next_to(axes.c2p(1, 0, 0), RIGHT, buff=0.1)
self.play(LaggedStartMap(FadeIn, labels, lag_ratio=0.3))
# Let it rotate for observation
self.wait(10)
class StateVectorEvolution(InteractiveScene):
"""Shows a state vector evolving on the Bloch sphere with a tracing tail."""
def construct(self):
frame = self.frame
# Set up 3D environment
axes = ThreeDAxes((-1, 1), (-1, 1), (-1, 1))
axes.scale(2.0)
sphere = Sphere(radius=2)
sphere.always_sort_to_camera(self.camera)
sphere.set_color(BLUE, 0.15)
sphere_mesh = SurfaceMesh(sphere, resolution=(21, 11))
sphere_mesh.set_stroke(WHITE, 0.25, 0.25)
frame.reorient(20, 70, 0)
frame.add_ambient_rotation(2 * DEG)
self.add(axes, sphere, sphere_mesh)
# Create evolving vector
theta_tracker = ValueTracker(0)
phi_tracker = ValueTracker(PI / 4)
def get_vector_end():
theta = theta_tracker.get_value()
phi = phi_tracker.get_value()
return 2 * np.array([
np.sin(phi) * np.cos(theta),
np.sin(phi) * np.sin(theta),
np.cos(phi)
])
vector = Vector(get_vector_end(), thickness=5, fill_color=YELLOW)
vector.always.set_perpendicular_to_camera(frame)
vector.add_updater(
lambda m: m.put_start_and_end_on(ORIGIN, get_vector_end())
)
# Add tracing tail
tail = TracingTail(
lambda: vector.get_end(),
stroke_color=YELLOW,
stroke_width=2,
time_traced=5
)
self.add(vector, tail)
self.wait()
# Evolve the state
self.play(
theta_tracker.animate.set_value(2 * TAU),
phi_tracker.animate.set_value(3 * PI / 4),
run_time=10,
rate_func=linear
)
self.wait(3)
class QuantumStateCollapse(InteractiveScene):
"""Demonstrates the concept of quantum state collapse upon measurement."""
def construct(self):
frame = self.frame
# Simple 2D representation for clarity
plane = NumberPlane((-2, 2), (-2, 2), faded_line_ratio=5)
plane.scale(1.5)
# Basis state labels
zero_label = Tex(R"|0\rangle").scale(0.8)
zero_label.next_to(plane.c2p(1, 0), DR, SMALL_BUFF)
one_label = Tex(R"|1\rangle").scale(0.8)
one_label.next_to(plane.c2p(0, 1), UL, SMALL_BUFF)
# Unit circle
circle = Circle(radius=plane.c2p(1, 0)[0])
circle.set_stroke(GREY, 1, 0.5)
self.add(plane, circle, zero_label, one_label)
# Superposition state vector
theta = 45 * DEG
vector = Arrow(
plane.c2p(0, 0),
plane.c2p(np.cos(theta), np.sin(theta)),
buff=0,
thickness=5,
fill_color=TEAL
)
state_label = Tex(
R"\frac{1}{\sqrt{2}}(|0\rangle + |1\rangle)",
font_size=36
)
state_label.next_to(vector.get_end(), UR, SMALL_BUFF)
state_label.set_backstroke(BLACK, 3)
self.play(GrowArrow(vector), FadeIn(state_label))
self.wait()
# Measurement indicator
measurement_text = Text("Measurement", font_size=36, color=RED)
measurement_text.to_edge(UP)
self.play(Write(measurement_text))
# Flash effect
self.play(
Flash(vector.get_end(), color=WHITE, flash_radius=0.5),
run_time=0.5
)
# Collapse to |0> (50% case)
collapsed_vector = Arrow(
plane.c2p(0, 0),
plane.c2p(1, 0),
buff=0,
thickness=5,
fill_color=BLUE
)
result_label = Tex(R"|0\rangle", font_size=48, color=BLUE)
result_label.next_to(collapsed_vector.get_end(), RIGHT, MED_SMALL_BUFF)
self.play(
Transform(vector, collapsed_vector),
FadeOut(state_label),
FadeIn(result_label),
run_time=0.3
)
self.wait(2)
if __name__ == "__main__":
# To run: manimgl bloch_sphere_3d.py BlochSphere3D
pass
examples/block_collision_basic.py
"""
Basic block collision simulation demonstrating elastic collisions.
Based on the famous 3b1b pi-computing collision video.
"""
from manimlib import *
import math
LITTLE_BLOCK_COLOR = "#51463E"
class StateTracker(ValueTracker):
"""
Tracks the state of the block collision process as a 4d vector
[
x1 * sqrt(m1),
x2 * sqrt(m2),
v1 * sqrt(m1),
v2 * sqrt(m2),
]
"""
def __init__(self, blocks, initial_positions=[8, 5], initial_velocities=[-1, 0]):
sqrt_m1, sqrt_m2 = self.sqrt_mass_vect = np.sqrt([b.mass for b in blocks])
self.theta = math.atan2(sqrt_m2, sqrt_m1)
self.state0 = np.array([
*np.array(initial_positions) * self.sqrt_mass_vect,
*np.array(initial_velocities) * self.sqrt_mass_vect,
])
super().__init__(self.state0.copy())
def set_time(self, t):
pos0 = self.state0[0:2]
vel0 = self.state0[2:4]
self.set_value([*(pos0 + t * vel0), *vel0])
def rotate_2d(self, vect, angle):
"""Simple 2D rotation helper"""
c, s = math.cos(angle), math.sin(angle)
return np.array([c * vect[0] - s * vect[1], s * vect[0] + c * vect[1]])
def reflect_vect(self, vect):
n_reflections = self.get_n_collisions()
rot_angle = -2 * self.theta * ((n_reflections + 1) // 2)
result = self.rotate_2d(vect, rot_angle)
result[1] *= (-1)**(n_reflections % 2)
return result
def get_block_positions(self):
scaled_pos = self.get_value()[0:2]
rot_scaled_pos = self.reflect_vect(scaled_pos)
return rot_scaled_pos / self.sqrt_mass_vect
def get_scaled_block_velocities(self):
return self.reflect_vect(self.get_value()[2:4])
def get_block_velocities(self):
return self.get_scaled_block_velocities() / self.sqrt_mass_vect
def get_n_collisions(self):
state = self.get_value()
angle = math.atan2(state[1], state[0])
return int(angle / self.theta)
class BlockCollisionBasic(Scene):
"""
A simplified block collision demonstration.
Shows two blocks colliding elastically.
"""
initial_positions = [10, 7]
initial_velocities = [-2, 0]
masses = [100, 1]
widths = [1.0, 0.5]
colors = [BLUE_E, LITTLE_BLOCK_COLOR]
def construct(self):
# Create floor and wall
floor, wall = self.get_floor_and_wall()
self.add(floor, wall)
# Create blocks
blocks = self.get_blocks(floor)
self.add(blocks)
# Set up state tracking
state_tracker = StateTracker(blocks, self.initial_positions, self.initial_velocities)
time_tracker = ValueTracker(0)
state_tracker.add_updater(lambda m: m.set_time(time_tracker.get_value()))
# Bind blocks to state
min_x = floor.get_x(LEFT) + blocks[1].get_width()
def update_blocks(blocks):
pos = state_tracker.get_block_positions()
blocks[0].set_x(min_x + pos[0], LEFT)
blocks[1].set_x(min_x + pos[1], RIGHT)
blocks.add_updater(update_blocks)
self.add(state_tracker, time_tracker)
# Add collision counter
count_label = Tex(R"\# \text{Collisions} = 0")
count = count_label.make_number_changeable("0")
count.add_updater(lambda m: m.set_value(state_tracker.get_n_collisions()))
count_label.to_corner(UL)
self.add(count_label)
# Run the simulation
self.play(
time_tracker.animate.set_value(30),
run_time=15,
rate_func=linear,
)
self.wait()
def get_floor_and_wall(self, width=13, height=2, stroke_width=2, buff_to_bottom=0.75):
floor = Line(LEFT, RIGHT)
floor.set_width(width)
floor.to_edge(DOWN, buff=buff_to_bottom)
dl_point = floor.get_left()
wall = Line(ORIGIN, UP)
wall.set_height(height)
wall.move_to(dl_point, DOWN)
# Add tick marks to wall
ticks = VGroup()
tick_spacing = 0.5
tick_vect = 0.25 * DL
for y in np.arange(tick_spacing, height + tick_spacing, tick_spacing):
start = dl_point + y * UP
ticks.add(Line(start, start + tick_vect))
result = VGroup(floor, VGroup(wall, ticks))
result.set_stroke(WHITE, stroke_width)
return result
def get_blocks(self, floor):
blocks = Group()
for mass, color, width in zip(self.masses, self.colors, self.widths):
block = Square()
block.set_stroke(WHITE, 2)
block.set_fill(color, 1)
block.set_width(width)
block.next_to(floor, UP, buff=0.01)
block.mass = mass
mass_label = Tex(R"10 \, \text{kg}", font_size=24)
mass_label.make_number_changeable("10", edge_to_fix=RIGHT).set_value(mass)
mass_label.next_to(block, UP, buff=SMALL_BUFF)
block.add(mass_label)
block.mass_label = mass_label
blocks.add(block)
return blocks
# Alternative mass ratios for counting pi digits
class BlockCollision1e4(BlockCollisionBasic):
"""Mass ratio 10000:1 gives 314 collisions"""
masses = [10000, 1]
widths = [1.5, 0.5]
colors = [interpolate_color(BLUE_E, BLACK, 0.5), LITTLE_BLOCK_COLOR]
class BlockCollision1e6(BlockCollisionBasic):
"""Mass ratio 1000000:1 gives 3141 collisions"""
masses = [1000000, 1]
widths = [2.0, 0.5]
colors = [interpolate_color(BLUE_E, BLACK, 0.8), LITTLE_BLOCK_COLOR]
examples/blocks_3d.py
"""
3D block collision simulation with floor and wall.
Demonstrates 3D scene setup with physics simulation.
Based on the famous 3b1b pi-computing collision video.
"""
from manimlib import *
import math
LITTLE_BLOCK_COLOR = "#51463E"
class StateTracker(ValueTracker):
"""
Tracks the state of the block collision process.
"""
def __init__(self, blocks, initial_positions=[8, 5], initial_velocities=[-1, 0]):
sqrt_m1, sqrt_m2 = self.sqrt_mass_vect = np.sqrt([b.mass for b in blocks])
self.theta = math.atan2(sqrt_m2, sqrt_m1)
self.state0 = np.array([
*np.array(initial_positions) * self.sqrt_mass_vect,
*np.array(initial_velocities) * self.sqrt_mass_vect,
])
super().__init__(self.state0.copy())
def set_time(self, t):
pos0 = self.state0[0:2]
vel0 = self.state0[2:4]
self.set_value([*(pos0 + t * vel0), *vel0])
def rotate_2d(self, vect, angle):
c, s = math.cos(angle), math.sin(angle)
return np.array([c * vect[0] - s * vect[1], s * vect[0] + c * vect[1]])
def reflect_vect(self, vect):
n_reflections = self.get_n_collisions()
rot_angle = -2 * self.theta * ((n_reflections + 1) // 2)
result = self.rotate_2d(vect, rot_angle)
result[1] *= (-1)**(n_reflections % 2)
return result
def get_block_positions(self):
scaled_pos = self.get_value()[0:2]
rot_scaled_pos = self.reflect_vect(scaled_pos)
return rot_scaled_pos / self.sqrt_mass_vect
def get_block_velocities(self):
return self.reflect_vect(self.get_value()[2:4]) / self.sqrt_mass_vect
def get_n_collisions(self):
state = self.get_value()
angle = math.atan2(state[1], state[0])
return int(angle / self.theta)
class Blocks3D(Scene):
"""
3D visualization of colliding blocks with floor and wall.
"""
initial_positions = [10, 7]
initial_velocities = [-2, 0]
masses = [100, 1]
widths = [1.0, 0.5]
colors = [BLUE_E, LITTLE_BLOCK_COLOR]
floor_width = 15
floor_depth = 6
wall_height = 5
block_shading = (0.5, 0.5, 0)
def construct(self):
# Set up 3D camera
frame = self.frame
frame.set_field_of_view(10 * DEGREES)
frame.reorient(-10, 5, 0)
# Create 3D floor and wall
floor, wall = self.get_floor_and_wall_3d()
self.add(floor, wall)
# Create 3D blocks
blocks = self.get_blocks_3d(floor)
self.add(blocks)
# Set up state tracking
state_tracker = StateTracker(blocks, self.initial_positions, self.initial_velocities)
time_tracker = ValueTracker(0)
state_tracker.add_updater(lambda m: m.set_time(time_tracker.get_value()))
# Bind blocks to state
min_x = floor.get_x(LEFT) + blocks[1].get_width()
def update_blocks(blocks):
pos = state_tracker.get_block_positions()
blocks[0].set_x(min_x + pos[0], LEFT)
blocks[1].set_x(min_x + pos[1], RIGHT)
blocks.add_updater(update_blocks)
self.add(state_tracker, time_tracker)
# Add collision counter (fixed to frame)
count_label = Tex(R"\# \text{Collisions} = 0")
count = count_label.make_number_changeable("0")
count.add_updater(lambda m: m.set_value(state_tracker.get_n_collisions()))
count_label.to_corner(UL)
count_label.fix_in_frame()
self.add(count_label)
# Run simulation with camera movement
self.play(
time_tracker.animate.set_value(30),
frame.animate.reorient(-5, 3, 0),
run_time=15,
rate_func=linear,
)
self.wait()
def get_floor_and_wall_3d(self, buff_to_bottom=0.75, color=GREY_D, shading=(0.2, 0.2, 0.2)):
floor = Square3D(resolution=(20, 20))
floor.rotate(90 * DEGREES, LEFT)
floor.set_shape(self.floor_width, 0, self.floor_depth)
floor.to_edge(DOWN, buff=buff_to_bottom)
wall = Square3D()
wall.rotate(90 * DEGREES, UP)
wall.set_shape(0, self.wall_height, self.floor_depth)
wall.move_to(floor.get_left(), DOWN)
result = Group(floor, wall)
result.set_color(color)
result.set_shading(*shading)
result.to_corner(DL)
return result
def get_blocks_3d(self, floor, floor_buff=0.01):
blocks = Group()
for mass, color, width in zip(self.masses, self.colors, self.widths):
# Create 3D cube body
body = Cube()
body.set_color(color)
body.set_shading(*self.block_shading)
# Add wireframe shell
shell = VCube()
shell.set_fill(opacity=0)
shell.set_stroke(WHITE, width=1)
shell.replace(body)
shell.apply_depth_test()
block = Group(body, shell)
block.set_width(width)
block.next_to(floor, UP, buff=floor_buff)
block.mass = mass
# Mass label
mass_label = Tex(R"10 \, \text{kg}", font_size=24)
mass_label.make_number_changeable("10", edge_to_fix=RIGHT).set_value(mass)
mass_label.next_to(block, UP, buff=SMALL_BUFF)
mass_label.set_backstroke(BLACK, 1)
block.add(mass_label)
block.mass_label = mass_label
blocks.add(block)
return blocks
class PreviewClip3D(Blocks3D):
"""
Cinematic preview shot with camera movement.
"""
initial_velocities = [-0.75, 0]
masses = [100, 1]
widths = [2.0, 0.5]
initial_positions = [10, 7]
floor_depth = 2
wall_height = 2
def construct(self):
frame = self.frame
frame.set_field_of_view(15 * DEGREES)
# Create scene
floor, wall = self.get_floor_and_wall_3d()
self.add(floor, wall)
blocks = self.get_blocks_3d(floor)
self.add(blocks)
state_tracker = StateTracker(blocks, self.initial_positions, self.initial_velocities)
time_tracker = ValueTracker(0)
state_tracker.add_updater(lambda m: m.set_time(time_tracker.get_value()))
min_x = floor.get_x(LEFT) + blocks[1].get_width()
def update_blocks(blocks):
pos = state_tracker.get_block_positions()
blocks[0].set_x(min_x + pos[0], LEFT)
blocks[1].set_x(min_x + pos[1], RIGHT)
blocks.add_updater(update_blocks)
self.add(state_tracker, time_tracker)
# Counter
count_label = Tex(R"\# \text{Collisions} = 0")
count = count_label.make_number_changeable("0")
count.add_updater(lambda m: m.set_value(state_tracker.get_n_collisions()))
count_label.to_corner(UL)
count_label.fix_in_frame()
self.add(count_label)
# Start with dramatic angle
frame.reorient(-46, -6, 0, (0.41, -2.47, 1.07), 3.59)
# Automatic time update
time_tracker.add_updater(lambda m, dt: m.increment_value(dt))
# Cinematic camera movements
self.play(
frame.animate.reorient(-46, -4, 0, (-0.78, -2.2, -0.17), 5.41),
run_time=8
)
self.play(
frame.animate.reorient(-4, -4, 0, (-2.38, -1.95, -0.99), 6.58),
run_time=12,
)
self.wait()
examples/collision_phase_space.py
"""
Phase space visualization of elastic block collisions.
Shows how conservation laws constrain the state to a circle.
Based on the famous 3b1b pi-computing collision video.
"""
from manimlib import *
import math
LITTLE_BLOCK_COLOR = "#51463E"
class StateTracker(ValueTracker):
"""
Tracks the state of the block collision process.
"""
def __init__(self, blocks, initial_positions=[8, 5], initial_velocities=[-1, 0]):
sqrt_m1, sqrt_m2 = self.sqrt_mass_vect = np.sqrt([b.mass for b in blocks])
self.theta = math.atan2(sqrt_m2, sqrt_m1)
self.state0 = np.array([
*np.array(initial_positions) * self.sqrt_mass_vect,
*np.array(initial_velocities) * self.sqrt_mass_vect,
])
super().__init__(self.state0.copy())
def set_time(self, t):
pos0 = self.state0[0:2]
vel0 = self.state0[2:4]
self.set_value([*(pos0 + t * vel0), *vel0])
def rotate_2d(self, vect, angle):
c, s = math.cos(angle), math.sin(angle)
return np.array([c * vect[0] - s * vect[1], s * vect[0] + c * vect[1]])
def reflect_vect(self, vect):
n_reflections = self.get_n_collisions()
rot_angle = -2 * self.theta * ((n_reflections + 1) // 2)
result = self.rotate_2d(vect, rot_angle)
result[1] *= (-1)**(n_reflections % 2)
return result
def get_block_positions(self):
scaled_pos = self.get_value()[0:2]
rot_scaled_pos = self.reflect_vect(scaled_pos)
return rot_scaled_pos / self.sqrt_mass_vect
def get_scaled_block_velocities(self):
return self.reflect_vect(self.get_value()[2:4])
def get_block_velocities(self):
return self.get_scaled_block_velocities() / self.sqrt_mass_vect
def get_n_collisions(self):
state = self.get_value()
angle = math.atan2(state[1], state[0])
return int(angle / self.theta)
class CollisionPhaseSpace(Scene):
"""
Shows block collisions with a phase space diagram.
The state point traces a path on a circle as collisions occur.
"""
initial_positions = [9.5, 8]
initial_velocities = [-1, 0]
masses = [10, 1]
widths = [1.0, 0.5]
colors = [BLUE_E, LITTLE_BLOCK_COLOR]
def construct(self):
# Create floor and blocks (simplified)
floor = Line(13 * LEFT / 2, 13 * RIGHT / 2)
floor.to_edge(DOWN, buff=0.75)
floor.set_stroke(WHITE, 2)
blocks = self.get_blocks(floor)
self.add(floor, blocks)
# Set up state tracking
state_tracker = StateTracker(blocks, self.initial_positions, self.initial_velocities)
time_tracker = ValueTracker(0)
state_tracker.add_updater(lambda m: m.set_time(time_tracker.get_value()))
# Bind blocks to state
min_x = floor.get_x(LEFT) + blocks[1].get_width()
def update_blocks(blocks):
pos = state_tracker.get_block_positions()
blocks[0].set_x(min_x + pos[0], LEFT)
blocks[1].set_x(min_x + pos[1], RIGHT)
blocks.add_updater(update_blocks)
self.add(state_tracker, time_tracker)
# Create phase space plane
plane = NumberPlane((-4, 4, 1), (-4, 4, 1), faded_line_ratio=1)
plane.set_height(4.5)
plane.to_corner(UR, buff=0.5)
plane.axes.set_stroke(WHITE, 1)
plane.background_lines.set_stroke(BLUE, 1, 0.5)
plane.faded_lines.set_stroke(BLUE, 0.5, 0.25)
self.add(plane)
# Add axis labels
kw = dict(t2c={"v_1": RED, "v_2": RED}, font_size=24)
x_label = Tex("x = v_1", **kw)
y_label = Tex("y = v_2", **kw)
x_label.next_to(plane.x_axis.get_right(), UR, SMALL_BUFF)
y_label.next_to(plane.y_axis.get_top(), DR, SMALL_BUFF)
self.add(x_label, y_label)
# Create state point tracking velocity
marked_velocity = ValueTracker(state_tracker.get_block_velocities())
marked_velocity.add_updater(lambda m: m.set_value(state_tracker.get_block_velocities()))
self.add(marked_velocity)
state_point = Group(
TrueDot(radius=0.05).make_3d(),
GlowDot(radius=0.2),
)
state_point.set_color(RED)
state_point.add_updater(lambda m: m.move_to(plane.c2p(*marked_velocity.get_value())))
self.add(state_point)
# Add energy circle (ellipse before scaling)
ellipse = Circle(radius=plane.x_axis.get_unit_size())
ellipse.set_stroke(YELLOW, 2)
ellipse.stretch(math.sqrt(10), 1) # sqrt(m1/m2)
ellipse.move_to(plane.c2p(0, 0))
self.add(ellipse)
# Add traced path
traced_path = TracedPath(state_point.get_center, stroke_color=RED, stroke_width=1)
self.add(traced_path)
# Add collision counter
count_label = Tex(R"\# \text{Collisions} = 0", font_size=30)
count = count_label.make_number_changeable("0")
count.add_updater(lambda m: m.set_value(state_tracker.get_n_collisions()))
count_label.to_corner(UL)
self.add(count_label)
# Add energy equation
ke_equation = Tex(
R"\frac{1}{2} m_1 (v_1)^2 + \frac{1}{2}m_2 (v_2)^2 = E",
t2c={"m_1": BLUE, "m_2": BLUE, "v_1": RED, "v_2": RED},
font_size=28
)
ke_equation.next_to(count_label, DOWN, buff=0.5, aligned_edge=LEFT)
self.add(ke_equation)
# Run simulation
self.play(
time_tracker.animate.set_value(25),
run_time=15,
rate_func=linear,
)
self.wait()
def get_blocks(self, floor):
blocks = Group()
for mass, color, width in zip(self.masses, self.colors, self.widths):
block = Square()
block.set_stroke(WHITE, 2)
block.set_fill(color, 1)
block.set_width(width)
block.next_to(floor, UP, buff=0.01)
block.mass = mass
mass_label = Tex(R"10 \, \text{kg}", font_size=20)
mass_label.make_number_changeable("10", edge_to_fix=RIGHT).set_value(mass)
mass_label.next_to(block, UP, buff=SMALL_BUFF)
block.add(mass_label)
blocks.add(block)
return blocks
class CirclePuzzle(Scene):
"""
Shows the geometric puzzle: counting lines bouncing between a circle and a line.
This is the geometric interpretation of the collision counting.
"""
def construct(self):
# Add axes
axes = VGroup(Line(1.5 * LEFT, 1.5 * RIGHT), Line(UP, DOWN))
axes.set_stroke(WHITE, 2, 0.33)
axes.set_height(6)
self.add(axes)
# Add circle
circle = Circle(radius=2.5)
circle.set_stroke(YELLOW, 2)
self.play(ShowCreation(circle))
self.wait()
# Add state point
state_point = Group(
TrueDot(radius=0.05).make_3d(),
GlowDot(radius=0.2),
)
state_point.set_color(RED)
state_point.move_to(circle.get_left())
self.play(FadeIn(state_point, shift=0.5 * DR, scale=0.5))
self.wait()
# Add bouncing lines with slope = -sqrt(m1/m2)
slope = -math.sqrt(10) # For mass ratio 10:1
lines = self.get_bounce_lines(circle, slope)
# Animate each bounce
count_label = Tex(R"\# \text{Bounces} = 0", font_size=36)
count = count_label.make_number_changeable("0")
count_label.to_corner(UL)
self.add(count_label)
for i, line in enumerate(lines):
self.play(
ShowCreation(line),
state_point.animate.move_to(line.get_end()),
ChangeDecimalToValue(count, i + 1),
run_time=0.5
)
self.wait()
# Show end zone
theta = math.atan(1 / abs(slope))
endzone_line = Line(ORIGIN, 4 * np.array([math.cos(theta), math.sin(theta), 0]))
endzone_line.set_stroke(WHITE, 2)
endzone = Polygon(
endzone_line.get_end(),
ORIGIN,
4 * RIGHT,
)
endzone.set_fill(GREEN, 0.25)
endzone.set_stroke(width=0)
self.play(FadeIn(endzone), ShowCreation(endzone_line))
self.wait(2)
def get_bounce_lines(self, circle, slope, max_bounces=10):
"""Generate lines bouncing between circle and x-axis reflection"""
lines = VGroup()
point = circle.get_left()
direction = np.array([1, slope, 0])
direction = direction / np.linalg.norm(direction)
for i in range(max_bounces):
# Find intersection with circle or x-axis
if i % 2 == 0:
# Bounce off x-axis (reflect y)
t = -point[1] / direction[1] if abs(direction[1]) > 1e-6 else 1e6
next_point = point + t * direction
# Check if still inside circle
if np.linalg.norm(next_point[:2]) > circle.get_width() / 2:
break
else:
# Find circle intersection
# Solve |point + t*direction|^2 = r^2
r = circle.get_width() / 2
a = direction[0]**2 + direction[1]**2
b = 2 * (point[0] * direction[0] + point[1] * direction[1])
c = point[0]**2 + point[1]**2 - r**2
disc = b**2 - 4 * a * c
if disc < 0:
break
t = (-b + math.sqrt(disc)) / (2 * a)
next_point = point + t * direction
# Check end condition (first quadrant)
if next_point[0] > 0 and next_point[1] > 0:
lines.add(Line(point, next_point).set_stroke(WHITE, 2))
break
lines.add(Line(point, next_point).set_stroke(WHITE, 2))
point = next_point
# Reflect direction
if i % 2 == 0:
direction[1] = -direction[1] # Bounce off x-axis
else:
# Reflect off circle (tangent)
normal = point[:2] / np.linalg.norm(point[:2])
normal = np.array([*normal, 0])
direction = direction - 2 * np.dot(direction, normal) * normal
return lines
examples/complex_s_plane.py
"""
Complex S-Plane Visualization
Interactive visualization of exponential functions in the complex plane.
Shows how the parameter s affects growth, decay, and oscillation.
Run: manimgl complex_s_plane.py SPlaneVisualization -w
Preview: manimgl complex_s_plane.py SPlaneVisualization -p
Source: Adapted from 3b1b's Laplace transform video (2025)
"""
from manimlib import *
class SPlaneVisualization(InteractiveScene):
"""
Comprehensive s-plane visualization with:
- Complex s parameter with dot and label
- Output e^{st} on complex plane
- Real part graph over time
Key techniques:
- ComplexValueTracker for complex numbers
- Multiple synchronized planes
- Dynamic graph updating with bind_graph_to_func
- GlowDot for emphasis
"""
def construct(self):
# Trackers for s and t
s_tracker = ComplexValueTracker(-1)
t_tracker = ValueTracker(0)
get_s = s_tracker.get_value
get_t = t_tracker.get_value
# S-plane (input)
s_plane = self.create_s_plane()
s_dot, s_label = self.create_s_indicator(s_plane, get_s)
# Output plane (e^{st})
exp_plane = self.create_output_plane()
exp_label = self.create_output_label(exp_plane)
output_dot, output_label = self.create_output_indicator(exp_plane, get_s, get_t)
output_path = self.create_output_path(exp_plane, get_t, get_s)
# Graph of Re[e^{st}]
axes = self.create_graph_axes()
graph = self.create_dynamic_graph(axes, get_s)
v_line = self.create_graph_indicator(axes, get_t, get_s)
# Add everything
self.add(s_plane, s_dot, s_label)
self.add(exp_plane, exp_label, output_path, output_dot, output_label)
self.add(axes, graph, v_line)
# Store for later use
self.s_tracker = s_tracker
self.t_tracker = t_tracker
self.s_plane = s_plane
# Animate s exploration
self.explore_s_values()
def create_s_plane(self):
"""Create the s-plane (input plane)."""
plane = ComplexPlane((-2, 2), (-2, 2))
plane.set_width(7)
plane.to_edge(LEFT, buff=SMALL_BUFF)
plane.add_coordinate_labels(font_size=16)
return plane
def create_s_indicator(self, s_plane, get_s):
"""Create dot and label tracking s value."""
s_dot = Group(
Dot(radius=0.05, fill_color=YELLOW),
GlowDot(color=YELLOW),
)
s_dot.add_updater(lambda m: m.move_to(s_plane.n2p(get_s())))
s_label = Tex(R"s = +0.5", font_size=36)
s_rhs = s_label.make_number_changeable("+0.5")
s_rhs.f_always.set_value(get_s)
s_label.set_color(YELLOW)
s_label.set_backstroke(BLACK, 5)
s_label.always.next_to(s_dot[0], UR, SMALL_BUFF)
return Group(s_dot, s_label)
def create_output_plane(self):
"""Create the output plane showing e^{st}."""
plane = ComplexPlane((-2, 2), (-2, 2))
plane.background_lines.set_stroke(width=1)
plane.faded_lines.set_stroke(opacity=0.25)
plane.set_width(4)
plane.to_corner(DR).shift(0.5 * LEFT)
return plane
def create_output_label(self, exp_plane, font_size=60):
"""Label for output plane."""
label = Tex(R"e^{st}", font_size=font_size, t2c={"s": YELLOW, "t": BLUE})
label.set_backstroke(BLACK, 5)
label.next_to(exp_plane.get_corner(UL), DL, 0.2)
return label
def create_output_indicator(self, exp_plane, get_s, get_t):
"""Moving dot showing e^{st} value."""
output_dot = Group(
TrueDot(color=GREEN),
GlowDot(color=GREEN)
)
output_dot.add_updater(lambda m: m.move_to(
exp_plane.n2p(np.exp(get_s() * get_t()))
))
output_label = Tex(R"e^{s \cdot 0.00}", font_size=36, t2c={"s": YELLOW})
t_label = output_label.make_number_changeable("0.00")
t_label.set_color(BLUE)
t_label.f_always.set_value(get_t)
output_label.always.next_to(output_dot, UR, buff=SMALL_BUFF, aligned_edge=LEFT, index_of_submobject_to_align=0)
output_label.set_backstroke(BLACK, 3)
return Group(output_dot, output_label)
def create_output_path(self, exp_plane, get_t, get_s, delta_t=1/30, color=TEAL, stroke_width=2):
"""Traced path of e^{st} as t increases."""
path = VMobject()
path.set_points([ORIGIN])
path.set_stroke(color, stroke_width)
def get_path_points():
t_range = np.arange(0, get_t(), delta_t)
if len(t_range) == 0:
t_range = np.array([0])
values = np.exp(t_range * get_s())
return np.array([exp_plane.n2p(z) for z in values])
path.f_always.set_points_smoothly(get_path_points)
return path
def create_graph_axes(self):
"""Axes for plotting Re[e^{st}] over time."""
axes = Axes(
x_range=(0, 24),
y_range=(-2, 2),
width=15,
height=2
)
t_label = Tex(R"t", font_size=36, t2c={"t": BLUE})
y_label = Tex(R"\text{Re}\left[e^{st}\right]", font_size=36, t2c={"s": YELLOW, "t": BLUE})
t_label.next_to(axes.x_axis.get_right(), UP, buff=0.15)
y_label.next_to(axes.y_axis.get_top(), UP, SMALL_BUFF)
axes.add(t_label, y_label)
axes.next_to(ORIGIN, RIGHT, MED_LARGE_BUFF)
axes.to_edge(UP, buff=0.5)
return axes
def create_dynamic_graph(self, axes, get_s, stroke_color=TEAL, stroke_width=3):
"""Graph that updates based on current s value."""
graph = Line().set_stroke(stroke_color, stroke_width)
t_samples = np.arange(*axes.x_range[:2], 0.1)
def update_graph(graph):
s = get_s()
values = np.exp(s * t_samples)
xs = values.astype(np.complex128).real
graph.set_points_smoothly(axes.c2p(t_samples, xs))
graph.add_updater(update_graph)
return graph
def create_graph_indicator(self, axes, get_t, get_s):
"""Vertical line indicator on the graph."""
v_line = Line(DOWN, UP)
v_line.set_stroke(WHITE, 2)
v_line.f_always.put_start_and_end_on(
lambda: axes.c2p(get_t(), 0),
lambda: axes.c2p(get_t(), np.exp(get_s() * get_t()).real),
)
return v_line
def play_time_forward(self, duration, added_anims=[]):
"""Utility to animate time passing."""
self.t_tracker.set_value(0)
self.play(
self.t_tracker.animate.set_value(duration).set_anim_args(rate_func=linear),
*added_anims,
run_time=duration,
)
def explore_s_values(self):
"""Explore different s values and their effects."""
s_tracker = self.s_tracker
# Start with negative real (decay)
s_tracker.set_value(-1)
self.play(s_tracker.animate.set_value(0.2), run_time=4)
# Pure real = 0 (constant)
self.play(s_tracker.animate.set_value(0), run_time=2)
# Pure imaginary (oscillation)
self.play(s_tracker.animate.set_value(1j), run_time=3)
self.wait()
# Let time run
self.play_time_forward(3 * TAU)
self.wait()
# Reset time
self.play(self.t_tracker.animate.set_value(0), run_time=2)
# Complex with negative real (decaying oscillation)
self.play(s_tracker.animate.set_value(-0.2 + 1j), run_time=3)
self.play_time_forward(2 * TAU)
# Complex with positive real (growing oscillation)
self.t_tracker.set_value(0)
self.play(s_tracker.animate.set_value(0.1 + 1j), run_time=3)
self.play_time_forward(TAU)
class SPlaneRegions(InteractiveScene):
"""
Highlight different regions of the s-plane and their meaning:
- Right half: exponential growth
- Left half: exponential decay
- Imaginary axis: pure oscillation
"""
def construct(self):
# S-plane
plane = ComplexPlane((-3, 3), (-3, 3))
plane.set_height(6)
plane.add_coordinate_labels(font_size=20)
self.add(plane)
# Right half (growth)
right_half = Rectangle(width=plane.get_width()/2, height=plane.get_height())
right_half.set_fill(RED, 0.3)
right_half.set_stroke(width=0)
right_half.move_to(plane.n2p(1.5))
# Left half (decay)
left_half = Rectangle(width=plane.get_width()/2, height=plane.get_height())
left_half.set_fill(GREEN, 0.3)
left_half.set_stroke(width=0)
left_half.move_to(plane.n2p(-1.5))
# Imaginary axis highlight
imag_axis = Line(plane.n2p(-3j), plane.n2p(3j))
imag_axis.set_stroke(YELLOW, 4)
# Labels
growth_label = Text("Growth", color=RED)
growth_label.move_to(plane.n2p(1.5 + 2j))
decay_label = Text("Decay", color=GREEN)
decay_label.move_to(plane.n2p(-1.5 + 2j))
osc_label = Text("Oscillation", color=YELLOW)
osc_label.next_to(imag_axis, RIGHT)
osc_label.shift(UP)
# Animate
self.play(FadeIn(right_half), Write(growth_label))
self.wait()
self.play(FadeIn(left_half), Write(decay_label))
self.wait()
self.play(ShowCreation(imag_axis), Write(osc_label))
self.wait(2)
# Add sample points
sample_points = [
(1, RED, "Grows"),
(-1, GREEN, "Decays"),
(1j, YELLOW, "Oscillates"),
(-0.5 + 1j, TEAL, "Decays + Oscillates"),
]
dots = VGroup()
for s, color, label_text in sample_points:
dot = GlowDot(plane.n2p(s), color=color)
label = Text(label_text, font_size=24, color=color)
label.next_to(dot, UR, buff=0.1)
dots.add(VGroup(dot, label))
self.play(LaggedStartMap(FadeIn, dots, lag_ratio=0.5))
self.wait(2)
examples/cost_function.py
"""
Negative Log Loss (Cross-Entropy) cost function visualization.
Demonstrates: Graph plotting, labeled axes, mathematical expressions
"""
from manimlib import *
import numpy as np
class CostFunction(Scene):
def construct(self):
# Create axes
axes = Axes(
(0, 1, 0.1),
(0, 5, 1),
width=10,
height=6
)
axes.center().to_edge(LEFT)
axes.x_axis.add_numbers(num_decimal_places=1)
axes.y_axis.add_numbers(num_decimal_places=0, direction=LEFT)
# Add axis label
x_label = Tex("p")
x_label.next_to(axes.x_axis.get_right(), UR)
axes.add(x_label)
y_label = Text("Cost", font_size=36)
y_label.next_to(axes.y_axis.get_top(), RIGHT)
axes.add(y_label)
# Create the -log(p) graph
graph = axes.get_graph(
lambda x: -np.log(x) if x > 0.001 else 5,
x_range=(0.001, 1, 0.01)
)
graph.set_color(RED)
# Expression
expr = Tex(R"\text{Cost} = -\log(p)", font_size=60)
expr.to_edge(UP)
# Animate
self.play(FadeIn(axes))
self.wait(0.5)
self.play(
ShowCreation(graph, run_time=3),
Write(expr, run_time=2),
)
self.wait()
# Explanation labels
low_p_label = Text("Low probability\n= High cost", font_size=30, color=RED)
low_p_label.next_to(axes.i2gp(0.1, graph), RIGHT, buff=0.5)
high_p_label = Text("High probability\n= Low cost", font_size=30, color=GREEN)
high_p_label.next_to(axes.i2gp(0.8, graph), UP, buff=0.5)
self.play(FadeIn(low_p_label, shift=LEFT))
self.wait()
self.play(FadeIn(high_p_label, shift=DOWN))
self.wait()
# Show a moving dot on the curve
p_tracker = ValueTracker(0.5)
dot = Dot(color=YELLOW)
dot.f_always.move_to(lambda: axes.i2gp(p_tracker.get_value(), graph))
# Vertical line from x-axis to point
v_line = always_redraw(lambda: axes.get_line_from_axis_to_point(
0, axes.i2gp(p_tracker.get_value(), graph),
line_func=DashedLine
).set_stroke(YELLOW, 2))
# Horizontal line from y-axis to point
h_line = always_redraw(lambda: axes.get_line_from_axis_to_point(
1, axes.i2gp(p_tracker.get_value(), graph),
line_func=DashedLine
).set_stroke(YELLOW, 2))
# Value labels
p_label = VGroup(
Text("p = ", font_size=36),
DecimalNumber(p_tracker.get_value(), num_decimal_places=2, font_size=36)
)
p_label.arrange(RIGHT)
p_label.to_corner(UR)
p_label[1].f_always.set_value(p_tracker.get_value)
cost_label = VGroup(
Text("Cost = ", font_size=36),
DecimalNumber(-np.log(0.5), num_decimal_places=2, font_size=36)
)
cost_label.arrange(RIGHT)
cost_label.next_to(p_label, DOWN, aligned_edge=LEFT)
cost_label[1].f_always.set_value(lambda: -np.log(max(p_tracker.get_value(), 0.001)))
self.play(
FadeOut(low_p_label),
FadeOut(high_p_label),
FadeIn(dot),
FadeIn(v_line),
FadeIn(h_line),
FadeIn(p_label),
FadeIn(cost_label),
)
self.wait()
# Animate the dot moving
self.play(p_tracker.animate.set_value(0.1), run_time=2)
self.wait()
self.play(p_tracker.animate.set_value(0.9), run_time=3)
self.wait()
self.play(p_tracker.animate.set_value(0.05), run_time=2)
self.wait()
self.play(p_tracker.animate.set_value(0.5), run_time=2)
self.wait()
# Final message
message = Text(
"Goal: Maximize probability of correct answer",
font_size=36,
color=BLUE
)
message.to_edge(DOWN)
self.play(FadeIn(message, shift=UP))
self.wait(2)
examples/cube_projection_3d.py
"""
Visualization of 3D cube projection along the diagonal.
Shows how projecting a cube along the [1,1,1] direction creates a hexagonal pattern.
"""
from manimlib import *
import itertools as it
class CubeProjection3D(InteractiveScene):
"""
Demonstrates projecting a 3D cube along its main diagonal [1,1,1].
Shows:
1. Building the cube from vertices
2. Showing coordinates
3. Looking down the diagonal
4. The projected hexagonal pattern
5. Face projections
"""
def construct(self):
# Set axes
frame = self.frame
light_source = self.camera.light_source
frame.reorient(28, 68, 0, (0.99, 0.63, 0.66), 2.89)
light_source.move_to([3, 5, 7])
axes = ThreeDAxes(
(-3, 3), (-3, 3), (-3, 3),
axis_config=dict(tick_size=0.05)
)
axes.set_stroke(GREY_A, 1)
plane = NumberPlane((-3, 3), (-3, 3))
plane.axes.set_stroke(GREY_A, 1)
plane.background_lines.set_stroke(BLUE_E, 0.5)
plane.faded_lines.set_stroke(BLUE_E, 0.5, 0.25)
self.add(plane, axes)
# Add cube
vertices = np.array(list(it.product(*3 * [[0, 1]])))
vert_dots = DotCloud(vertices)
vert_dots.make_3d()
vert_dots.set_radius(0.025)
vert_dots.set_color(TEAL)
cube_shell = VGroup(
Line(vertices[i], vertices[j])
for i, p1 in enumerate(vertices)
for j, p2 in enumerate(vertices[i + 1:], start=i + 1)
if get_norm(p2 - p1) == 1
)
cube_shell.set_stroke(YELLOW, 1)
cube_shell.set_anti_alias_width(1)
cube_shell.set_width(1)
cube_shell.move_to(ORIGIN, [-1, -1, -1])
self.play(Write(cube_shell, lag_ratio=0.1, run_time=2))
self.wait()
# Show the coordinates
labels = VGroup()
for vert in vertices:
coords = vert.astype(int)
label = Tex(str(tuple(coords)), font_size=12)
label.next_to(vert, DR, buff=0.05)
label.rotate(45 * DEGREES, RIGHT, about_point=vert)
label.set_backstroke(BLACK, 2)
labels.add(label)
self.play(
LaggedStartMap(FadeIn, labels),
FadeIn(vert_dots),
frame.animate.reorient(10, 61, 0, (0.9, 0.51, 0.48), 2.44),
run_time=3,
)
self.wait()
# Show base and top square
edges = VGroup(*cube_shell)
edges.sort(lambda p: p[2])
self.play(
edges[4:].animate.set_stroke(width=0.5, opacity=0.25),
labels[1::2].animate.set_opacity(0.1)
)
self.wait()
self.play(
edges[8:].animate.set_stroke(width=2, opacity=1),
labels[1::2].animate.set_opacity(1),
edges[:4].animate.set_stroke(width=0.5, opacity=0.25),
labels[0::2].animate.set_opacity(0.1)
)
self.wait()
self.play(
edges.animate.set_stroke(width=1, opacity=1),
labels.animate.set_opacity(1)
)
self.play(FadeOut(labels))
# Orient to look down the corner
self.play(frame.animate.reorient(135.795, 55.795, 0, (-0.02, -0.08, 0.05), 3.61), run_time=4)
self.wait(2)
self.play(frame.animate.reorient(50, 68, 0, (-0.46, 0.29, 0.23), 3.45), run_time=4)
# Show the flat projection
diag_vect = Vector([1, 1, 1], thickness=2)
diag_vect.set_perpendicular_to_camera(frame)
diag_label = labels[-1].copy()
proj_mat = self.construct_proj_matrix()
proj_cube_shell = cube_shell.copy().apply_matrix(proj_mat)
proj_vert_dots = vert_dots.copy().apply_matrix(proj_mat)
self.play(
GrowArrow(diag_vect),
FadeIn(diag_label, shift=np.ones(3)),
cube_shell.animate.set_stroke(opacity=0.25),
)
self.wait()
self.play(
TransformFromCopy(cube_shell, proj_cube_shell),
TransformFromCopy(vert_dots, proj_vert_dots),
)
self.wait(3)
frame.save_state()
self.play(
frame.animate.reorient(134.75, 54.47, 0, (-0.46, 0.29, 0.23), 3.45).set_field_of_view(1 * DEGREES),
run_time=4
)
self.wait()
self.play(Restore(frame, run_time=3))
self.wait()
# Project more cubes down
cube_grid = VGroup(
cube_shell.copy().shift(vect)
for vect in it.product(*3 * [[0, 1, 2]])
)
cube_grid.remove(cube_grid[0])
proj_cube_grid = cube_grid.copy().apply_matrix(proj_mat)
proj_cube_grid.set_stroke(YELLOW, 2, 0.5)
ghost_cube = cube_shell.copy().set_opacity(0)
self.play(
LaggedStart(
(TransformFromCopy(ghost_cube, new_cube)
for new_cube in cube_grid),
lag_ratio=0.05,
),
frame.animate.reorient(40, 72, 0, (1.25, 1.69, 0.99), 5.10),
run_time=5
)
self.wait()
self.play(
TransformFromCopy(cube_grid, proj_cube_grid),
frame.animate.reorient(60, 68, 0, (0.81, 1.09, 0.94), 5.36),
run_time=3
)
self.wait()
self.play(
FadeOut(cube_grid),
FadeOut(proj_cube_grid),
FadeOut(diag_label),
FadeOut(diag_vect),
FadeOut(vert_dots),
FadeOut(proj_vert_dots),
frame.animate.reorient(42, 62, 0, (0.68, 0.48, 0.41), 2.34),
run_time=2,
)
# Show cube faces
cube = Cube()
cube.set_color(BLUE_E, 1)
cube.set_shading(0.75, 0.25, 0.5)
cube.replace(cube_shell)
cube.sort(lambda p: np.dot(p, np.ones(3)))
inner_faces = cube[:3]
for mob in [cube_shell, proj_cube_shell, plane]:
mob.apply_depth_test()
self.add(axes, cube, cube_shell, plane, proj_cube_shell)
self.play(
FadeIn(cube),
proj_cube_shell.animate.set_stroke(width=1, opacity=0.2),
)
self.wait(3)
def construct_proj_matrix(self):
diag = normalize(np.ones(3))
id3 = np.identity(3)
return np.array([self.project(basis, diag) for basis in id3]).T
def project(self, vect, unit_norm):
"""Project v1 onto the orthogonal subspace of norm"""
return vect - np.dot(unit_norm, vect) * unit_norm
examples/damped_solutions_splane.py
"""
Damped Spring Solutions on S-Plane
Visualization of how the damped harmonic oscillator solutions
move in the complex s-plane as parameters change.
Run: manimgl damped_solutions_splane.py DampedSolutionsDemo -w
Preview: manimgl damped_solutions_splane.py DampedSolutionsDemo -p
Source: Adapted from 3b1b's Laplace transform video (2025)
"""
from manimlib import *
class DampedSolutionsDemo(InteractiveScene):
"""
Interactive visualization of damped spring solutions on the s-plane.
The characteristic equation ms^2 + μs + k = 0 has roots that:
- Stay on imaginary axis when μ=0 (undamped oscillation)
- Move into left half-plane as μ increases (damped oscillation)
- Become real when μ^2 > 4mk (overdamped)
Key techniques:
- Custom slider creation
- GlowDot for interactive points
- Dynamic function binding for graphs
- Real-time root calculation
"""
def construct(self):
# Add the complex plane
plane = ComplexPlane((-3, 2), (-2, 2))
plane.set_height(5)
plane.background_lines.set_stroke(BLUE, 1)
plane.faded_lines.set_stroke(BLUE, 0.5, 0.25)
plane.add_coordinate_labels(font_size=24)
plane.move_to(DOWN)
plane.to_edge(RIGHT, buff=1.0)
self.add(plane)
# Parameter sliders
colors = [interpolate_color_by_hsl(RED, TEAL, a) for a in np.linspace(0, 1, 3)]
chars = ["m", R"\mu", "k"]
m_slider, mu_slider, k_slider = sliders = VGroup(
self.create_slider(char, color)
for char, color in zip(chars, colors)
)
m_tracker, mu_tracker, k_tracker = trackers = Group(
slider.value_tracker for slider in sliders
)
sliders.arrange(RIGHT, buff=MED_LARGE_BUFF)
sliders.next_to(plane, UP, aligned_edge=LEFT)
# Initial values: m=1, μ=0, k=3
m_tracker.set_value(1)
mu_tracker.set_value(0)
k_tracker.set_value(3)
self.add(trackers)
self.add(sliders[0], sliders[2]) # Start without damping slider
# Root calculation
def get_roots():
a = m_tracker.get_value()
b = mu_tracker.get_value()
c = k_tracker.get_value()
# Characteristic equation: as^2 + bs + c = 0
# s = (-b ± sqrt(b^2 - 4ac)) / 2a
discriminant = b**2 - 4*a*c
if discriminant >= 0:
radical = math.sqrt(discriminant)
else:
radical = 1j * math.sqrt(-discriminant)
m = -b / (2*a)
return (m + radical / (2*a), m - radical / (2*a))
# Dots showing the roots
root_dots = GlowDot().replicate(2)
root_dots.set_color(YELLOW)
def update_dots(dots):
roots = get_roots()
for dot, root in zip(dots, roots):
dot.move_to(plane.n2p(root))
root_dots.add_updater(update_dots)
self.add(root_dots)
# Lines from a reference point
s_rhs_point = Point((-4.09, -1.0, 0.0))
def update_lines(lines):
for line, dot in zip(lines, root_dots):
line.put_start_and_end_on(s_rhs_point.get_center(), dot.get_center())
lines = Line().replicate(2)
lines.set_stroke(YELLOW, 2, 0.35)
lines.add_updater(update_lines)
# Show the roots moving as k changes (undamped case)
self.play(ShowCreation(lines, lag_ratio=0, suspend_mobject_updating=True))
self.play(k_tracker.animate.set_value(1), run_time=2)
self.play(m_tracker.animate.set_value(4), run_time=2)
self.wait()
self.play(k_tracker.animate.set_value(3), run_time=2)
self.play(m_tracker.animate.set_value(1), run_time=2)
self.wait()
# Now add damping
self.play(
VFadeOut(lines),
VFadeIn(sliders[1])
)
self.wait()
# Increase damping - roots move left
self.play(mu_tracker.animate.set_value(3), run_time=5)
self.wait()
# Decrease damping - roots approach imaginary axis
self.play(mu_tracker.animate.set_value(0.5), run_time=3)
self.play(ShowCreation(lines, lag_ratio=0, suspend_mobject_updating=True))
self.wait()
# Add solution graph
frame = self.frame
axes = Axes((0, 10, 1), (-1, 1, 1), width=10, height=3.5)
axes.next_to(plane, DOWN, MED_LARGE_BUFF, aligned_edge=LEFT)
def solution_func(t):
roots = get_roots()
# Real part of e^{s1*t} + e^{s2*t} (divided by 2 for normalization)
return 0.5 * (np.exp(roots[0] * t) + np.exp(roots[1] * t)).real
graph = axes.get_graph(solution_func)
graph.set_stroke(TEAL, 3)
axes.bind_graph_to_func(graph, solution_func)
graph_label = Tex(R"\text{Re}[e^{st}]", t2c={"s": YELLOW}, font_size=72)
graph_label.next_to(axes.get_corner(UL), DL)
self.play(
frame.animate.set_height(12, about_point=4 * UP + 2 * LEFT),
FadeIn(axes, time_span=(1.5, 3)),
ShowCreation(graph, suspend_mobject_updating=True, time_span=(1.5, 3)),
Write(graph_label),
run_time=3
)
self.wait()
# More parameter exploration
self.play(k_tracker.animate.set_value(1), run_time=2)
self.play(k_tracker.animate.set_value(4), run_time=2)
self.wait()
self.play(mu_tracker.animate.set_value(2), run_time=3)
self.play(k_tracker.animate.set_value(2), run_time=2)
self.wait()
# Show overdamped case
self.play(mu_tracker.animate.set_value(3.5), run_time=3)
self.play(k_tracker.animate.set_value(5), run_time=2)
self.wait()
# Return to underdamped
self.play(
mu_tracker.animate.set_value(0.5),
m_tracker.animate.set_value(3),
run_time=3
)
self.wait(2)
def create_slider(self, char_name, color=WHITE, x_range=(0, 5), height=1.5, font_size=36):
"""Create a vertical slider for a parameter."""
tracker = ValueTracker(0)
number_line = NumberLine(x_range, width=height, tick_size=0.05)
number_line.rotate(90 * DEG)
indicator = ArrowTip(width=0.1, length=0.2)
indicator.rotate(PI)
indicator.add_updater(lambda m: m.move_to(number_line.n2p(tracker.get_value()), LEFT))
indicator.set_color(color)
label = Tex(Rf"{char_name} = 0.00", font_size=font_size)
label[char_name].set_color(color)
label.rhs = label.make_number_changeable("0.00")
label.always.next_to(indicator, RIGHT, SMALL_BUFF)
label.rhs.f_always.set_value(tracker.get_value)
slider = VGroup(number_line, indicator, label)
slider.value_tracker = tracker
return slider
class OverdampedVsUnderdamped(InteractiveScene):
"""
Side-by-side comparison of overdamped and underdamped behavior.
"""
def construct(self):
# Two planes side by side
plane_underdamped = ComplexPlane((-2, 1), (-2, 2))
plane_overdamped = ComplexPlane((-2, 1), (-2, 2))
for plane in [plane_underdamped, plane_overdamped]:
plane.set_width(5)
plane.add_coordinate_labels(font_size=16)
planes = VGroup(plane_underdamped, plane_overdamped)
planes.arrange(RIGHT, buff=1)
planes.to_edge(UP)
# Labels
underdamped_label = Text("Underdamped", font_size=36, color=BLUE)
underdamped_label.next_to(plane_underdamped, DOWN)
overdamped_label = Text("Overdamped", font_size=36, color=RED)
overdamped_label.next_to(plane_overdamped, DOWN)
self.add(planes, underdamped_label, overdamped_label)
# Roots for underdamped: complex conjugates
underdamped_roots = [-0.5 + 1.5j, -0.5 - 1.5j]
underdamped_dots = VGroup(
GlowDot(plane_underdamped.n2p(r), color=BLUE)
for r in underdamped_roots
)
# Roots for overdamped: both real
overdamped_roots = [-0.3, -1.7]
overdamped_dots = VGroup(
GlowDot(plane_overdamped.n2p(r), color=RED)
for r in overdamped_roots
)
self.play(
LaggedStartMap(FadeIn, underdamped_dots),
LaggedStartMap(FadeIn, overdamped_dots),
)
self.wait()
# Graphs below
axes_underdamped = Axes((0, 8), (-1, 1), width=5, height=2)
axes_overdamped = Axes((0, 8), (-1, 1), width=5, height=2)
axes_underdamped.next_to(underdamped_label, DOWN)
axes_overdamped.next_to(overdamped_label, DOWN)
# Underdamped solution: decaying oscillation
def underdamped_func(t):
s = underdamped_roots[0]
return (np.exp(s * t)).real
# Overdamped solution: pure decay
def overdamped_func(t):
s1, s2 = overdamped_roots
return 0.5 * (np.exp(s1 * t) + np.exp(s2 * t))
graph_under = axes_underdamped.get_graph(underdamped_func)
graph_under.set_stroke(BLUE, 3)
graph_over = axes_overdamped.get_graph(overdamped_func)
graph_over.set_stroke(RED, 3)
self.add(axes_underdamped, axes_overdamped)
self.play(
ShowCreation(graph_under),
ShowCreation(graph_over),
run_time=3
)
self.wait(2)
examples/dot_product_visualization.py
"""
Dot Product Visualization
Interactive demonstration of how dot products work with two vectors.
Based on: videos/_2024/transformers/embedding.py - DotProducts
"""
from manimlib import *
class DotProductVisualization(InteractiveScene):
"""
Shows dot product calculation between two vectors in 2D.
The result updates dynamically as vectors are rotated.
"""
def construct(self):
# Set up coordinate plane
plane = NumberPlane(
(-4, 4), (-4, 4),
background_line_style=dict(
stroke_width=2,
stroke_opacity=0.5,
stroke_color=BLUE,
),
faded_line_ratio=1
)
plane.set_height(6)
plane.to_edge(LEFT, buff=0)
# Create two vectors
vects = VGroup(
Vector(0.5 * RIGHT + 2 * UP).set_stroke(MAROON_B, 6),
Vector(1.0 * RIGHT + 0.5 * UP).set_stroke(YELLOW, 6),
)
vects.shift(plane.get_center())
def get_dot_product():
coords = np.array([plane.p2c(v.get_end()) for v in vects])
return np.dot(coords[0], coords[1])
self.add(plane)
self.add(vects)
# Vector labels
vect_labels = VGroup(*(
Tex(Rf"\vec{{\textbf{{ {char} }} }}")
for char in "vw"
))
for label, vect in zip(vect_labels, vects):
label.vect = vect
label.match_color(vect)
label.add_updater(lambda m: m.move_to(
m.vect.get_end() + 0.25 * normalize(m.vect.get_vector())
))
self.add(vect_labels)
# Coordinate expressions
vect_coords = VGroup(*(
TexMatrix(
[
[char + f"_{{{str(n)}}}"]
for n in [1, 2, 3, 4, "n"]
],
bracket_h_buff=0.1,
ellipses_row=-2,
)
for char in "vw"
))
vect_coords.arrange(RIGHT, buff=0.75)
vect_coords.next_to(plane, RIGHT, buff=1)
vect_coords.set_y(1)
for coords, vect in zip(vect_coords, vects):
coords.get_entries().match_color(vect)
dot = Tex(R"\cdot", font_size=72)
dot.move_to(vect_coords)
self.add(vect_coords, dot)
# Result display
rhs = Tex("= +0.00", font_size=60)
rhs.next_to(vect_coords, RIGHT)
result = rhs.make_number_changeable("+0.00", include_sign=True)
result.add_updater(lambda m: m.set_value(get_dot_product()))
self.add(rhs)
# Label
brace = Brace(vect_coords, DOWN, buff=0.25)
dp_label = brace.get_text("Dot product", buff=0.25)
self.add(brace, dp_label)
# Helper function for dual rotation
def dual_rotate(angle1, angle2, run_time=2):
self.play(
Rotate(vects[0], angle1 * DEGREES, about_point=plane.get_origin()),
Rotate(vects[1], angle2 * DEGREES, about_point=plane.get_origin()),
run_time=run_time
)
# Demonstrate various configurations
dual_rotate(-20, 20)
dual_rotate(50, -60)
dual_rotate(0, 80)
dual_rotate(20, -80)
# Show computation breakdown
equals = rhs[0].copy()
entry_pairs = VGroup(*(
VGroup(*pair)
for pair in zip(*[vc.get_columns()[0] for vc in vect_coords])
))
prod_terms = entry_pairs.copy()
for src_pair, trg_pair in zip(entry_pairs, prod_terms):
trg_pair.arrange(RIGHT, buff=0.1)
trg_pair.next_to(equals, RIGHT, buff=0.5)
trg_pair.match_y(src_pair)
prod_terms[-2].space_out_submobjects(1e-3)
prod_terms[-2].match_x(prod_terms)
prod_terms.target = prod_terms.generate_target()
prod_terms.target.space_out_submobjects(1.5).match_y(vect_coords)
plusses = VGroup(*(
Tex("+", font_size=48).move_to(midpoint(m1.get_bottom(), m2.get_top()))
for m1, m2 in zip(prod_terms.target, prod_terms.target[1:])
))
rhs.target = rhs.generate_target()
rhs.target[0].rotate(PI / 2)
rhs.target.arrange(DOWN)
rhs.target.next_to(prod_terms, DOWN)
self.add(equals)
self.play(
LaggedStart(*(
TransformFromCopy(m1, m2)
for m1, m2 in zip(entry_pairs, prod_terms)
), lag_ratio=0.1, run_time=2),
MoveToTarget(rhs)
)
self.wait()
self.play(
MoveToTarget(prod_terms),
rhs.animate.next_to(prod_terms.target, DOWN),
LaggedStartMap(Write, plusses),
)
self.wait()
# Show positive dot product
dual_rotate(-65, 65)
self.play(FlashAround(result, time_width=1.5, run_time=3))
self.wait()
# Show orthogonal (zero dot product)
elbow = Elbow(width=0.25, angle=vects[0].get_angle())
elbow.shift(plane.get_origin())
zero = DecimalNumber(0)
zero.replace(result, 1)
dual_rotate(
(vects[1].get_angle() + PI / 2 - vects[0].get_angle()) / DEGREES,
0,
)
self.remove(result)
self.add(zero)
self.play(ShowCreation(elbow))
self.wait()
self.remove(elbow, zero)
self.add(result)
# Show negative dot product
dual_rotate(20, -60)
self.play(FlashAround(result, time_width=1.5, run_time=3))
self.wait()
# Final animation
dual_rotate(75, -95, run_time=5)
examples/double_slit_interference.py
"""
Double Slit Interference Visualization
Demonstrates the classic double-slit experiment, showing how waves from two
slits interfere to create an interference pattern on a screen.
Based on 3Blue1Brown's diffraction visualizations.
Run: manimgl double_slit_interference.py DoubleSlitExperiment -w
"""
from manimlib import *
import numpy as np
class DoubleSlitExperiment(Scene):
"""
Visualizes the double-slit experiment with wave interference.
Shows plane wave hitting two slits and producing interference.
"""
def construct(self):
frame = self.camera.frame
# Create barrier with two slits
barrier_color = GREY_D
slit_separation = 2.0
slit_width = 0.15
# Create barrier pieces
barrier_y = -2
barrier_left = Rectangle(width=6, height=0.3, fill_color=barrier_color, fill_opacity=1)
barrier_left.set_stroke(WHITE, 1)
barrier_left.move_to([-(slit_separation/2 + 3 + slit_width), barrier_y, 0])
barrier_middle = Rectangle(width=slit_separation - 2*slit_width, height=0.3,
fill_color=barrier_color, fill_opacity=1)
barrier_middle.set_stroke(WHITE, 1)
barrier_middle.move_to([0, barrier_y, 0])
barrier_right = Rectangle(width=6, height=0.3, fill_color=barrier_color, fill_opacity=1)
barrier_right.set_stroke(WHITE, 1)
barrier_right.move_to([slit_separation/2 + 3 + slit_width, barrier_y, 0])
barrier = VGroup(barrier_left, barrier_middle, barrier_right)
# Slit positions
slit1_pos = np.array([-slit_separation/2, barrier_y, 0])
slit2_pos = np.array([slit_separation/2, barrier_y, 0])
# Mark the slits
slit1_marker = Dot(slit1_pos, color=RED, radius=0.1)
slit2_marker = Dot(slit2_pos, color=BLUE, radius=0.1)
# Screen to observe pattern
screen = Rectangle(width=0.2, height=6)
screen.set_fill(GREY_E, opacity=0.8)
screen.set_stroke(WHITE, 1)
screen.move_to([0, 4, 0])
# Wave parameters
wave_number = 2.0
frequency = 0.4
# Create incoming plane wave (simplified as horizontal lines)
def get_incoming_wave(time):
waves = VGroup()
for offset in np.arange(-10, 0, 0.5 / wave_number):
y = barrier_y - 1 + (time * frequency / wave_number + offset) % 3
if y < barrier_y - 0.2:
line = Line([-7, y, 0], [7, y, 0])
alpha = 1 - (barrier_y - y) / 3
line.set_stroke(TEAL, width=2, opacity=0.5 * alpha)
waves.add(line)
return waves
# Create outgoing waves from slits
def get_outgoing_waves(time):
rings = VGroup()
colors = [RED_B, BLUE_B]
positions = [slit1_pos, slit2_pos]
for pos, color in zip(positions, colors):
for phase_offset in np.arange(0, 12, 0.5 / wave_number):
radius = (time * frequency / wave_number + phase_offset)
if 0.1 < radius < 8:
# Only show upper semicircle
arc = Arc(
start_angle=0,
angle=PI,
radius=radius
)
arc.move_arc_center_to(pos)
amplitude = np.exp(-0.15 * radius)
arc.set_stroke(color, width=1.5 + 2 * amplitude, opacity=0.6 * amplitude)
rings.add(arc)
return rings
# Create intensity pattern on screen
def get_intensity_pattern(time):
dots = VGroup()
screen_y = 4
for x in np.linspace(-3, 3, 120):
point = np.array([x, screen_y, 0])
# Calculate path difference
r1 = np.linalg.norm(point - slit1_pos)
r2 = np.linalg.norm(point - slit2_pos)
# Interference
phase1 = TAU * (wave_number * r1 - frequency * time)
phase2 = TAU * (wave_number * r2 - frequency * time)
amp1 = np.cos(phase1) / np.sqrt(1 + 0.1 * r1)
amp2 = np.cos(phase2) / np.sqrt(1 + 0.1 * r2)
total_intensity = ((amp1 + amp2) / 2) ** 2
# Create dot
dot = Dot([x, screen_y - 0.1 + 0.2 * total_intensity, 0], radius=0.03)
brightness = 0.2 + 0.8 * total_intensity
dot.set_fill(interpolate_color(BLACK, WHITE, brightness), opacity=1)
dots.add(dot)
return dots
time_tracker = ValueTracker(0)
incoming = always_redraw(lambda: get_incoming_wave(time_tracker.get_value()))
outgoing = always_redraw(lambda: get_outgoing_waves(time_tracker.get_value()))
intensity = always_redraw(lambda: get_intensity_pattern(time_tracker.get_value()))
# Title
title = Text("Double Slit Interference", font_size=48)
title.to_corner(UL)
title.set_backstroke(BLACK, 5)
# Labels
incoming_label = Text("Incoming Wave", font_size=24)
incoming_label.next_to(barrier, DOWN, buff=0.5)
incoming_label.set_backstroke(BLACK, 3)
screen_label = Text("Detection Screen", font_size=24)
screen_label.next_to(screen, RIGHT)
screen_label.set_backstroke(BLACK, 3)
# Add elements
self.add(title)
self.add(barrier)
self.add(slit1_marker, slit2_marker)
self.add(screen)
self.add(incoming)
self.add(outgoing)
self.add(intensity)
self.add(incoming_label, screen_label)
# Animate
self.play(
time_tracker.animate.set_value(30),
run_time=15,
rate_func=linear
)
self.wait()
class PathDifferenceExplanation(Scene):
"""
Explains the path difference concept in interference.
Shows how different path lengths lead to phase differences.
"""
def construct(self):
# Two source points
source1 = Dot(2 * LEFT + 2 * DOWN, color=RED, radius=0.15)
source2 = Dot(2 * RIGHT + 2 * DOWN, color=BLUE, radius=0.15)
source1_label = Text("S1", font_size=24, color=RED).next_to(source1, DOWN)
source2_label = Text("S2", font_size=24, color=BLUE).next_to(source2, DOWN)
# Target point
target = Dot(UP, color=YELLOW, radius=0.15)
target_label = Text("P", font_size=24, color=YELLOW).next_to(target, UP)
# Path lines
path1 = Line(source1.get_center(), target.get_center(), color=RED)
path2 = Line(source2.get_center(), target.get_center(), color=BLUE)
# Distance labels
d1 = path1.get_length()
d2 = path2.get_length()
d1_label = Tex(f"d_1", color=RED, font_size=36)
d1_label.move_to(path1.get_center() + 0.5 * LEFT)
d2_label = Tex(f"d_2", color=BLUE, font_size=36)
d2_label.move_to(path2.get_center() + 0.5 * RIGHT)
# Title
title = Text("Path Difference and Interference", font_size=42)
title.to_edge(UP)
# Add elements
self.add(title)
self.play(
FadeIn(source1), FadeIn(source2),
Write(source1_label), Write(source2_label)
)
self.play(FadeIn(target), Write(target_label))
self.play(
ShowCreation(path1), ShowCreation(path2),
Write(d1_label), Write(d2_label)
)
self.wait()
# Path difference formula
formula = Tex(
R"\Delta d = d_2 - d_1",
font_size=48
)
formula.to_edge(DOWN)
formula.shift(UP)
self.play(Write(formula))
self.wait()
# Show constructive case
constructive_text = Text("Constructive: path diff = n * wavelength", font_size=32)
constructive_text.next_to(formula, DOWN)
constructive_text.set_color(GREEN)
self.play(Write(constructive_text))
self.wait(2)
# Show destructive case
destructive_text = Text("Destructive: path diff = (n + 1/2) * wavelength", font_size=32)
destructive_text.next_to(constructive_text, DOWN)
destructive_text.set_color(PINK)
self.play(Write(destructive_text))
self.wait(2)
class DiffractionGratingSimple(Scene):
"""
Simplified diffraction grating visualization showing multiple slits.
"""
def construct(self):
frame = self.camera.frame
# Parameters
n_slits = 8
slit_spacing = 0.8
barrier_y = -2
wave_number = 3.0
frequency = 0.3
# Create barrier with multiple slits
barrier_pieces = VGroup()
slit_positions = []
total_width = n_slits * slit_spacing
for i in range(n_slits + 1):
x_pos = -total_width / 2 + i * slit_spacing - slit_spacing / 4
piece = Rectangle(width=slit_spacing / 2, height=0.3)
piece.set_fill(GREY_D, opacity=1)
piece.set_stroke(WHITE, 1)
piece.move_to([x_pos, barrier_y, 0])
barrier_pieces.add(piece)
# Track slit positions (between pieces)
if i < n_slits:
slit_x = -total_width / 2 + i * slit_spacing + slit_spacing / 4
slit_positions.append(np.array([slit_x, barrier_y, 0]))
# Slit markers
slit_markers = VGroup(
Dot(pos, color=YELLOW, radius=0.05)
for pos in slit_positions
)
# Create outgoing waves from all slits
def get_grating_waves(time):
rings = VGroup()
for pos in slit_positions:
for phase_offset in np.arange(0, 8, 0.4 / wave_number):
radius = (time * frequency / wave_number + phase_offset)
if 0.1 < radius < 6:
arc = Arc(
start_angle=0,
angle=PI,
radius=radius
)
arc.move_arc_center_to(pos)
amplitude = np.exp(-0.2 * radius)
arc.set_stroke(BLUE, width=1 + amplitude, opacity=0.3 * amplitude)
rings.add(arc)
return rings
time_tracker = ValueTracker(0)
waves = always_redraw(lambda: get_grating_waves(time_tracker.get_value()))
# Title
title = Text("Diffraction Grating", font_size=48)
title.to_edge(UP)
title.set_backstroke(BLACK, 5)
# Spacing label
spacing_arrow = DoubleArrow(
slit_positions[0] + 0.5 * DOWN,
slit_positions[1] + 0.5 * DOWN,
buff=0
)
spacing_arrow.set_color(WHITE)
d_label = Tex("d", font_size=36)
d_label.next_to(spacing_arrow, DOWN, buff=0.1)
self.add(title)
self.add(barrier_pieces)
self.add(slit_markers)
self.add(waves)
self.add(spacing_arrow, d_label)
# Animate
self.play(
time_tracker.animate.set_value(25),
run_time=15,
rate_func=linear
)
self.wait()
examples/eigenvalue_equations.py
"""
Eigenvalue Equations
====================
Shows the key mathematical equations for eigenvalues and eigenvectors.
Demonstrates LaTeX typesetting with color coding for mathematical concepts.
Key concepts:
- Eigenvalue equation: Av = lambda * v
- Diagonalization: A = S * D * S^(-1)
- Change of basis transformation
"""
from manimlib import *
class EigenvalueEquations(Scene):
"""
Displays the fundamental eigenvalue/eigenvector equations
with proper color coding to highlight mathematical relationships.
"""
def construct(self):
# Title
title = Text("Eigenvalue Equations", font_size=48)
title.to_edge(UP)
self.play(Write(title))
# Main eigenvalue equation
eigen_eq = Tex(
R"A \vec{\mathbf{v}} = \lambda \vec{\mathbf{v}}",
font_size=60
)
eigen_eq.set_color_by_tex(R"\lambda", TEAL)
eigen_eq.set_color_by_tex(R"\vec{\mathbf{v}}", YELLOW)
# Description
eigen_desc = Text(
"Eigenvector is scaled by eigenvalue",
font_size=28
)
eigen_desc.set_color(GREY_B)
eigen_group = VGroup(eigen_eq, eigen_desc)
eigen_group.arrange(DOWN, buff=0.3)
eigen_group.next_to(title, DOWN, buff=0.8)
self.play(Write(eigen_eq))
self.play(FadeIn(eigen_desc, shift=UP * 0.3))
self.wait()
# Move up and show diagonalization
self.play(
eigen_group.animate.shift(UP * 0.5).scale(0.8)
)
# Diagonalization equation
diag_eq = Tex(
R"A = S \Lambda S^{-1}",
font_size=48
)
diag_eq.set_color_by_tex(R"\Lambda", TEAL)
diag_eq.set_color_by_tex("S", YELLOW)
# Where clause
where_clause = Tex(
R"\text{where } \Lambda = "
R"\begin{bmatrix} \lambda_1 & 0 \\ 0 & \lambda_2 \end{bmatrix}",
font_size=36
)
where_clause.set_color_by_tex(R"\lambda_1", TEAL)
where_clause.set_color_by_tex(R"\lambda_2", YELLOW)
# S matrix explanation
s_clause = Tex(
R"S = \begin{bmatrix} \vert & \vert \\ "
R"\vec{\mathbf{v}}_1 & \vec{\mathbf{v}}_2 \\ "
R"\vert & \vert \end{bmatrix}",
font_size=36
)
s_clause.set_color_by_tex(R"\vec{\mathbf{v}}_1", TEAL)
s_clause.set_color_by_tex(R"\vec{\mathbf{v}}_2", YELLOW)
diag_group = VGroup(diag_eq, where_clause, s_clause)
diag_group.arrange(DOWN, buff=0.4, aligned_edge=LEFT)
diag_group.next_to(eigen_group, DOWN, buff=0.6)
self.play(Write(diag_eq))
self.wait(0.5)
self.play(FadeIn(where_clause, shift=UP * 0.2))
self.wait(0.5)
self.play(FadeIn(s_clause, shift=UP * 0.2))
self.wait(2)
class DiagonalMatrixPowers(Scene):
"""
Shows the key insight: diagonal matrices are easy to raise to powers.
This makes computing A^n efficient when A is diagonalizable.
"""
def construct(self):
# Title
title = Text("Power of Diagonal Matrices", font_size=42)
title.to_edge(UP)
self.add(title)
# Show diagonal matrix power
diag_power = Tex(
R"\begin{bmatrix} \lambda_1 & 0 \\ 0 & \lambda_2 \end{bmatrix}^n = "
R"\begin{bmatrix} \lambda_1^n & 0 \\ 0 & \lambda_2^n \end{bmatrix}",
font_size=44,
t2c={R"\lambda_1": TEAL, R"\lambda_2": YELLOW}
)
diag_power.next_to(title, DOWN, buff=0.8)
self.play(Write(diag_power))
self.wait()
# Therefore A^n equation
therefore = Tex(
R"\therefore \quad A^n = S \Lambda^n S^{-1}",
font_size=40
)
therefore.next_to(diag_power, DOWN, buff=0.6)
self.play(Write(therefore))
self.wait()
# Example with Fibonacci matrix
fib_title = Text("Example: Fibonacci Matrix", font_size=32)
fib_title.next_to(therefore, DOWN, buff=0.8)
fib_matrix = Tex(
R"A = \begin{bmatrix} 0 & 1 \\ 1 & 1 \end{bmatrix}",
font_size=36
)
fib_matrix.next_to(fib_title, DOWN, buff=0.3)
fib_result = Tex(
R"A^n \begin{bmatrix} 0 \\ 1 \end{bmatrix} = "
R"\begin{bmatrix} F_n \\ F_{n+1} \end{bmatrix}",
font_size=36
)
fib_result.next_to(fib_matrix, DOWN, buff=0.3)
self.play(Write(fib_title))
self.play(Write(fib_matrix))
self.wait(0.5)
self.play(Write(fib_result))
self.wait(2)
class ChangeOfBasisVisualization(Scene):
"""
Shows how the change of basis matrix S transforms coordinates
between standard basis and eigenbasis.
"""
def construct(self):
# Title
title = Text("Change of Basis", font_size=42)
title.to_edge(UP)
# Main equation
cob_eq = Tex(
R"x \hat{\mathbf{i}} + y \hat{\mathbf{j}} = "
R"\tilde{x} \vec{\mathbf{v}}_1 + \tilde{y} \vec{\mathbf{v}}_2",
font_size=40,
t2c={
R"\hat{\mathbf{i}}": GREEN,
R"\hat{\mathbf{j}}": RED,
R"\vec{\mathbf{v}}_1": TEAL,
R"\vec{\mathbf{v}}_2": YELLOW,
}
)
cob_eq.next_to(title, DOWN, buff=0.6)
# Standard basis label
std_label = Text("Standard Basis", font_size=24, color=GREY_B)
std_label.next_to(cob_eq[:6], DOWN, buff=0.3)
# Eigenbasis label
eigen_label = Text("Eigenbasis", font_size=24, color=GREY_B)
eigen_label.next_to(cob_eq[7:], DOWN, buff=0.3)
# Show transformation
self.play(Write(title))
self.play(Write(cob_eq))
self.play(
FadeIn(std_label, shift=UP * 0.2),
FadeIn(eigen_label, shift=UP * 0.2),
)
self.wait()
# Simplified ODE in eigenbasis
ode_title = Text("ODE becomes simple in eigenbasis:", font_size=28)
ode_title.next_to(eigen_label, DOWN, buff=0.8)
ode_original = Tex(
R"\frac{d}{dt}\begin{bmatrix} x \\ y \end{bmatrix} = "
R"A \begin{bmatrix} x \\ y \end{bmatrix}",
font_size=32
)
ode_original.next_to(ode_title, DOWN, buff=0.3)
arrow = Tex(R"\Downarrow", font_size=40)
arrow.next_to(ode_original, DOWN, buff=0.3)
ode_simple = Tex(
R"\frac{d}{dt}\begin{bmatrix} \tilde{x} \\ \tilde{y} \end{bmatrix} = "
R"\begin{bmatrix} \lambda_1 & 0 \\ 0 & \lambda_2 \end{bmatrix}"
R"\begin{bmatrix} \tilde{x} \\ \tilde{y} \end{bmatrix}",
font_size=32,
t2c={R"\lambda_1": TEAL, R"\lambda_2": YELLOW}
)
ode_simple.next_to(arrow, DOWN, buff=0.3)
self.play(Write(ode_title))
self.play(Write(ode_original))
self.play(Write(arrow))
self.play(Write(ode_simple))
self.wait()
# Solution
solution = Tex(
R"\tilde{x}(t) = \tilde{x}_0 e^{\lambda_1 t}, \quad "
R"\tilde{y}(t) = \tilde{y}_0 e^{\lambda_2 t}",
font_size=32,
t2c={R"\lambda_1": TEAL, R"\lambda_2": YELLOW}
)
solution.next_to(ode_simple, DOWN, buff=0.5)
self.play(Write(solution))
self.wait(2)
examples/eigenvector_flow_field.py
"""
Eigenvector Flow Field
======================
Visualizes the flow of a linear dynamical system dx/dt = Ax.
The eigenvectors appear as special directions where flow stays on a line.
This demonstrates:
- VectorField for showing derivative directions
- StreamLines for animated flow
- Computing eigenvalues/eigenvectors with numpy
- Linear algebra visualization
"""
from manimlib import *
class EigenvectorFlowField(Scene):
"""
Shows the vector field for a linear ODE system dx/dt = Ax.
The eigenvectors are the special directions where trajectories
move straight outward or inward.
"""
def construct(self):
# Define the matrix for our linear system
mat = np.array([[1, 2], [3, 1]])
# Create coordinate plane
plane = NumberPlane((-4, 4), (-4, 4), faded_line_ratio=1)
plane.set_height(FRAME_HEIGHT)
plane.background_lines.set_stroke(BLUE, 1)
plane.faded_lines.set_stroke(BLUE, 0.5, 0.5)
plane.add_coordinate_labels(font_size=36)
self.add(plane)
# Define the derivative function for the linear system
def deriv_func(x, y):
"""Returns the derivative at a point: f(v) = Av"""
v = np.array([x, y])
result = 0.5 * np.dot(mat, v)
return result[0], result[1]
# Create vector field manually using arrows
vector_field = VGroup()
for x in np.linspace(-3.5, 3.5, 12):
for y in np.linspace(-3.5, 3.5, 12):
if abs(x) < 0.4 and abs(y) < 0.4:
continue # Skip origin area
dx, dy = deriv_func(x, y)
start = plane.c2p(x, y)
direction = np.array([dx, dy, 0])
norm = np.linalg.norm(direction)
if norm > 0.1:
# Normalize and scale for visibility
direction = direction / norm * min(0.5, norm * 0.3)
end = start + direction
arrow = Arrow(
start, end, buff=0,
stroke_width=2,
max_tip_length_to_length_ratio=0.3
)
# Color based on magnitude
alpha = min(1, norm / 3)
arrow.set_color(interpolate_color(BLUE, RED, alpha))
vector_field.add(arrow)
# Show vector field
self.play(
LaggedStartMap(GrowArrow, vector_field, lag_ratio=0.01),
run_time=2
)
self.wait(2)
# Calculate eigenvectors
eigenvalues, eigenvectors = np.linalg.eig(mat)
# Create eigenvalue lines (extended versions of eigenvectors)
eigenlines = VGroup()
eigen_labels = VGroup()
for i, (ev, eigval) in enumerate(zip(eigenvectors.T, eigenvalues)):
# Create the line
line = Line(-ev, ev)
line.set_length(15)
color = [TEAL, YELLOW][i]
line.set_stroke(color, 5)
eigenlines.add(line)
# Create label
label = Tex(
R"\lambda_" + str(i + 1) + f" = {eigval:.2f}",
font_size=30
)
label.set_color(color)
label.set_backstroke(width=5)
# Position label at end of eigenvector
label.next_to(plane.c2p(*(ev * 2)), RIGHT if ev[0] > 0 else LEFT)
eigen_labels.add(label)
# Show eigenvector lines with labels
self.play(
LaggedStartMap(ShowCreation, eigenlines, lag_ratio=0.3),
run_time=2
)
self.play(LaggedStartMap(FadeIn, eigen_labels, lag_ratio=0.3))
# Let it run for a while to see the flow
self.wait(8)
class LinearSystemPhasePortrait(Scene):
"""
Shows different types of equilibria based on eigenvalues:
- Both positive: unstable node (expanding)
- Both negative: stable node (contracting)
- Mixed signs: saddle point
"""
def construct(self):
# Create three small phase portraits
matrices = [
np.array([[2, 0], [0, 1]]), # Unstable node (both positive)
np.array([[-2, 0], [0, -1]]), # Stable node (both negative)
np.array([[2, 0], [0, -1]]), # Saddle point (mixed)
]
titles = [
"Unstable Node",
"Stable Node",
"Saddle Point",
]
subtitle_data = [
(r"\lambda_1 > 0, \lambda_2 > 0", GREEN),
(r"\lambda_1 < 0, \lambda_2 < 0", RED),
(r"\lambda_1 > 0, \lambda_2 < 0", YELLOW),
]
portraits = VGroup()
for mat, title, (subtitle, color) in zip(matrices, titles, subtitle_data):
portrait = self.create_phase_portrait(mat)
label = Text(title, font_size=24)
label.next_to(portrait, UP)
eigen_label = Tex(subtitle, font_size=20)
eigen_label.set_color(color)
eigen_label.next_to(portrait, DOWN)
group = VGroup(portrait, label, eigen_label)
portraits.add(group)
portraits.arrange(RIGHT, buff=0.5)
portraits.set_width(FRAME_WIDTH - 1)
main_title = Text("Phase Portraits by Eigenvalue Type", font_size=36)
main_title.to_edge(UP)
self.add(main_title)
self.play(LaggedStartMap(FadeIn, portraits, lag_ratio=0.3))
self.wait(3)
def create_phase_portrait(self, mat):
"""Create a small phase portrait for a given matrix."""
plane = NumberPlane(
(-2, 2), (-2, 2),
background_line_style={"stroke_width": 1, "stroke_opacity": 0.5}
)
plane.set_height(3)
def func(point):
v = np.array([point[0], point[1]])
result = mat @ v
return np.array([result[0], result[1], 0]) * 0.3
# Just show arrows, no animation for static display
arrows = VGroup()
for x in np.linspace(-1.5, 1.5, 5):
for y in np.linspace(-1.5, 1.5, 5):
if abs(x) < 0.3 and abs(y) < 0.3:
continue
start = plane.c2p(x, y)
deriv = func(np.array([x, y, 0]))
if np.linalg.norm(deriv) > 0.1:
deriv = deriv / np.linalg.norm(deriv) * 0.3
end = start + deriv
arrow = Arrow(start, end, buff=0, stroke_width=2, max_tip_length_to_length_ratio=0.3)
arrow.set_color(interpolate_color(BLUE, RED, (np.linalg.norm(deriv) / 0.5)))
arrows.add(arrow)
return VGroup(plane, arrows)
examples/eigenvector_matrix_transformation.py
"""
Eigenvector Matrix Transformation
=================================
Demonstrates how a matrix transformation looks in standard basis vs eigenbasis.
In the eigenbasis, the transformation becomes a simple scaling along each axis.
Key concepts demonstrated:
- Matrix transformation of a number plane
- Eigenvector computation with numpy
- Change of basis visualization
- Updated vectors that follow coordinate system changes
"""
from manimlib import *
class EigenvectorMatrixTransformation(Scene):
"""
Shows a matrix transformation in two perspectives:
1. Standard basis (i-hat, j-hat) - complex shearing transformation
2. Eigenbasis - simple scaling along eigenvector directions
"""
def construct(self):
# Define the transformation matrix
# This matrix has eigenvalues -1 and 3
mat = np.array([[1, 2], [3, 1]])
# Create ghost plane to show original grid
ghost_plane = NumberPlane(faded_line_ratio=0)
ghost_plane.set_stroke(GREY, 1)
# Create main plane that will be transformed
plane = self.get_plane()
# Create basis vectors that update with the plane
basis = VGroup(
self.get_updated_vector((1, 0), plane, GREEN),
self.get_updated_vector((0, 1), plane, RED),
)
# Add label
title = Text("Standard Basis Transformation", font_size=36)
title.to_corner(UL)
title.set_backstroke(width=5)
self.add(ghost_plane, plane, basis, title)
# Animate the transformation in standard basis
self.play(
plane.animate.apply_matrix(mat),
run_time=4
)
self.wait()
# Fade out standard basis view
self.play(FadeOut(VGroup(ghost_plane, plane, basis, title)))
# Now show the same transformation in eigenbasis
# Calculate eigenvectors
eigenvalues, eigenvectors = np.linalg.eig(mat)
# Create a plane already in the eigenbasis
eigenplane = self.get_plane()
eigenplane.apply_matrix(eigenvectors, about_point=ORIGIN)
# Create eigenbasis vectors
eigenbasis = VGroup(
self.get_updated_vector((1, 0), eigenplane, TEAL),
self.get_updated_vector((0, 1), eigenplane, YELLOW),
)
# Add new title
eigen_title = Text("Eigenbasis Transformation", font_size=36)
eigen_title.to_corner(UL)
eigen_title.set_backstroke(width=5)
# Show eigenvalue labels
eigen_labels = VGroup(
Tex(R"\lambda_1 = " + f"{eigenvalues[0]:.1f}", font_size=30).set_color(TEAL),
Tex(R"\lambda_2 = " + f"{eigenvalues[1]:.1f}", font_size=30).set_color(YELLOW),
)
eigen_labels.arrange(DOWN, aligned_edge=LEFT)
eigen_labels.to_corner(UR)
eigen_labels.set_backstroke(width=5)
self.add(eigenplane, eigenbasis, eigen_title, eigen_labels)
# In eigenbasis, transformation is just scaling by eigenvalues!
self.play(
eigenplane.animate.apply_matrix(mat),
run_time=4
)
self.wait()
def get_plane(self, x_range=(-16, 16), y_range=(-8, 8)):
"""Create a number plane for visualization."""
return NumberPlane(x_range, y_range, faded_line_ratio=1)
def get_updated_vector(self, coords, coord_system, color=YELLOW, thickness=4, **kwargs):
"""
Create a vector that automatically updates its position based on
the coordinate system it's attached to. This is useful for showing
how basis vectors transform with the plane.
"""
vect = Vector(RIGHT, fill_color=color, thickness=thickness, **kwargs)
vect.add_updater(lambda m: m.put_start_and_end_on(
coord_system.get_origin(),
coord_system.c2p(*coords),
))
return vect
class EigenvectorScaling(Scene):
"""
Shows that eigenvectors only get scaled by their eigenvalue.
Multiple vectors are shown - eigenvectors stay on their line,
other vectors rotate.
"""
def construct(self):
# Matrix with eigenvalues 4 and -1
mat = np.array([[1, 2], [3, 1]])
eigenvalues, eigenvectors = np.linalg.eig(mat)
# Create coordinate plane
plane = NumberPlane((-4, 4), (-4, 4))
plane.set_height(6)
plane.add_coordinate_labels(font_size=24)
# Create eigenvector lines (extended to infinity)
eigenlines = VGroup()
for i, ev in enumerate(eigenvectors.T):
line = Line(-ev * 5, ev * 5)
line.set_stroke([TEAL, YELLOW][i], 3, 0.5)
eigenlines.add(line)
# Create test vectors - some along eigenvectors, some not
test_vectors = VGroup()
colors = [TEAL, YELLOW, BLUE, RED, PURPLE]
directions = [
eigenvectors.T[0], # First eigenvector direction
eigenvectors.T[1], # Second eigenvector direction
np.array([1, 0]), # Standard basis i
np.array([0, 1]), # Standard basis j
np.array([1, 1]) / np.sqrt(2), # Diagonal
]
for direction, color in zip(directions, colors):
vect = Arrow(
plane.c2p(0, 0),
plane.c2p(*direction),
buff=0,
fill_color=color,
stroke_width=3,
)
test_vectors.add(vect)
# Labels
title = Text("Eigenvectors Stay on Their Line", font_size=36)
title.to_corner(UL)
title.set_backstroke(width=5)
self.add(plane, eigenlines, title)
self.play(LaggedStartMap(GrowArrow, test_vectors, lag_ratio=0.2))
self.wait()
# Transform all vectors
transformed_vectors = VGroup()
for i, (direction, color) in enumerate(zip(directions, colors)):
new_dir = mat @ direction
new_vect = Arrow(
plane.c2p(0, 0),
plane.c2p(*new_dir),
buff=0,
fill_color=color,
stroke_width=3,
)
transformed_vectors.add(new_vect)
self.play(
Transform(test_vectors, transformed_vectors),
run_time=3
)
self.wait(2)
examples/elastic_collision_vectors.py
"""
Elastic collision visualization with velocity vectors and conservation equations.
Shows how kinetic energy and momentum are conserved during collisions.
Based on the famous 3b1b pi-computing collision video.
"""
from manimlib import *
import math
LITTLE_BLOCK_COLOR = "#51463E"
class StateTracker(ValueTracker):
"""
Tracks the state of the block collision process.
"""
def __init__(self, blocks, initial_positions=[8, 5], initial_velocities=[-1, 0]):
sqrt_m1, sqrt_m2 = self.sqrt_mass_vect = np.sqrt([b.mass for b in blocks])
self.theta = math.atan2(sqrt_m2, sqrt_m1)
self.state0 = np.array([
*np.array(initial_positions) * self.sqrt_mass_vect,
*np.array(initial_velocities) * self.sqrt_mass_vect,
])
super().__init__(self.state0.copy())
def set_time(self, t):
pos0 = self.state0[0:2]
vel0 = self.state0[2:4]
self.set_value([*(pos0 + t * vel0), *vel0])
def rotate_2d(self, vect, angle):
c, s = math.cos(angle), math.sin(angle)
return np.array([c * vect[0] - s * vect[1], s * vect[0] + c * vect[1]])
def reflect_vect(self, vect):
n_reflections = self.get_n_collisions()
rot_angle = -2 * self.theta * ((n_reflections + 1) // 2)
result = self.rotate_2d(vect, rot_angle)
result[1] *= (-1)**(n_reflections % 2)
return result
def get_block_positions(self):
scaled_pos = self.get_value()[0:2]
rot_scaled_pos = self.reflect_vect(scaled_pos)
return rot_scaled_pos / self.sqrt_mass_vect
def get_block_velocities(self):
return self.reflect_vect(self.get_value()[2:4]) / self.sqrt_mass_vect
def get_kinetic_energy(self):
v1, v2 = self.get_value()[2:4]
return v1**2 + v2**2
def get_momentum(self):
v1, v2 = self.get_block_velocities()
m1, m2 = self.sqrt_mass_vect**2
return m1 * v1 + m2 * v2
def get_n_collisions(self):
state = self.get_value()
angle = math.atan2(state[1], state[0])
return int(angle / self.theta)
class ElasticCollisionVectors(Scene):
"""
Visualization of elastic collision with velocity vectors.
Shows conservation of kinetic energy and momentum.
"""
initial_positions = [10.5, 8]
initial_velocities = [-0.975, 0]
masses = [10, 1]
widths = [1.0, 0.5]
colors = [BLUE_E, LITTLE_BLOCK_COLOR]
def construct(self):
# Create floor and wall
floor, wall = self.get_floor_and_wall()
self.add(floor, wall)
# Create blocks
blocks = self.get_blocks(floor)
self.add(blocks)
# Set up state tracking
state_tracker = StateTracker(blocks, self.initial_positions, self.initial_velocities)
time_tracker = ValueTracker(0)
state_tracker.add_updater(lambda m: m.set_time(time_tracker.get_value()))
# Bind blocks to state
min_x = floor.get_x(LEFT) + blocks[1].get_width()
def update_blocks(blocks):
pos = state_tracker.get_block_positions()
blocks[0].set_x(min_x + pos[0], LEFT)
blocks[1].set_x(min_x + pos[1], RIGHT)
blocks.add_updater(update_blocks)
self.add(state_tracker, time_tracker)
# Set up equations
kw = dict(t2c={
"m_1": BLUE,
"m_2": BLUE,
"v_1": RED,
"v_2": RED,
})
ke_equation = Tex(R"\frac{1}{2} m_1 (v_1)^2 + \frac{1}{2}m_2 (v_2)^2 = E", **kw)
p_equation = Tex(R"m_1 v_1 + m_2 v_2 = P", **kw)
equations = VGroup(ke_equation, p_equation)
equations.arrange(DOWN, buff=0.5)
equations.to_corner(UL, buff=0.5)
self.add(equations)
# Create velocity vectors
velocity_vectors = VGroup(
self.get_velocity_vector(blocks[0], lambda: state_tracker.get_block_velocities()[0]),
self.get_velocity_vector(blocks[1], lambda: state_tracker.get_block_velocities()[1]),
)
self.add(velocity_vectors)
# Add collision counter
count_label = Tex(R"\# \text{Collisions} = 0", font_size=36)
count = count_label.make_number_changeable("0")
count.add_updater(lambda m: m.set_value(state_tracker.get_n_collisions()))
count_label.next_to(equations, DOWN, buff=0.5, aligned_edge=LEFT)
self.add(count_label)
# Run simulation
self.play(
time_tracker.animate.set_value(12),
run_time=12,
rate_func=linear,
)
self.wait()
# Show changing velocities
dec_equation = Tex(R"\frac{1}{2}(10)(+0.00)^2 + \frac{1}{2}(1)(+0.00)^2 = +0.00", font_size=36)
terms = dec_equation.make_number_changeable("+0.00", replace_all=True, include_sign=True)
dec_equation.next_to(ke_equation, DOWN, LARGE_BUFF)
dec_equation["(1)"].set_color(BLUE)
dec_equation["(10)"].set_color(BLUE)
terms[:2].set_color(RED)
terms[0].add_updater(lambda m: m.set_value(state_tracker.get_block_velocities()[0]))
terms[1].add_updater(lambda m: m.set_value(state_tracker.get_block_velocities()[1]))
terms[2].set_value(state_tracker.get_kinetic_energy())
self.add(dec_equation)
self.play(
time_tracker.animate.increment_value(10),
run_time=10,
rate_func=linear,
)
self.wait()
def get_floor_and_wall(self, width=13, height=2, stroke_width=2, buff_to_bottom=0.75):
floor = Line(LEFT, RIGHT)
floor.set_width(width)
floor.to_edge(DOWN, buff=buff_to_bottom)
dl_point = floor.get_left()
wall = Line(ORIGIN, UP)
wall.set_height(height)
wall.move_to(dl_point, DOWN)
ticks = VGroup()
tick_spacing = 0.5
tick_vect = 0.25 * DL
for y in np.arange(tick_spacing, height + tick_spacing, tick_spacing):
start = dl_point + y * UP
ticks.add(Line(start, start + tick_vect))
result = VGroup(floor, VGroup(wall, ticks))
result.set_stroke(WHITE, stroke_width)
return result
def get_blocks(self, floor):
blocks = Group()
for mass, color, width in zip(self.masses, self.colors, self.widths):
block = Square()
block.set_stroke(WHITE, 2)
block.set_fill(color, 1)
block.set_width(width)
block.next_to(floor, UP, buff=0.01)
block.mass = mass
mass_label = Tex(R"10 \, \text{kg}", font_size=24)
mass_label.make_number_changeable("10", edge_to_fix=RIGHT).set_value(mass)
mass_label.next_to(block, UP, buff=SMALL_BUFF)
block.add(mass_label)
block.mass_label = mass_label
blocks.add(block)
return blocks
def get_velocity_vector(
self,
block,
vel_function,
scale_factor=0.5,
max_width=1.0,
):
"""Create a velocity vector that follows a block."""
vector = Vector(RIGHT, thickness=2)
vector.set_fill(RED)
vector.set_backstroke(BLACK, 1)
def update_vector(vector):
start = block.get_top() + 0.1 * UP
vel = vel_function()
width = max_width * math.tanh(scale_factor * abs(vel))
if width > 0.05:
direction = RIGHT if vel > 0 else LEFT
vector.put_start_and_end_on(start, start + width * direction)
vector.set_opacity(1)
else:
vector.set_opacity(0)
return vector
vector.add_updater(update_vector)
label = DecimalNumber(0, num_decimal_places=2, font_size=18)
label.set_fill(RED)
label.set_backstroke(BLACK, 1)
label.add_updater(lambda m: m.set_value(vel_function()).next_to(
vector.get_start(), UP, buff=0.1
))
return VGroup(vector, label)
class MomentumConservation(ElasticCollisionVectors):
"""
Focuses on momentum conservation visualization.
"""
def construct(self):
# Create floor and wall
floor, wall = self.get_floor_and_wall()
self.add(floor, wall)
# Create blocks
blocks = self.get_blocks(floor)
self.add(blocks)
# Set up state tracking
state_tracker = StateTracker(blocks, self.initial_positions, self.initial_velocities)
time_tracker = ValueTracker(0)
state_tracker.add_updater(lambda m: m.set_time(time_tracker.get_value()))
# Bind blocks to state
min_x = floor.get_x(LEFT) + blocks[1].get_width()
def update_blocks(blocks):
pos = state_tracker.get_block_positions()
blocks[0].set_x(min_x + pos[0], LEFT)
blocks[1].set_x(min_x + pos[1], RIGHT)
blocks.add_updater(update_blocks)
self.add(state_tracker, time_tracker)
# Momentum equation
kw = dict(t2c={"m_1": BLUE, "m_2": BLUE, "v_1": RED, "v_2": RED})
p_equation = Tex(R"m_1 v_1 + m_2 v_2 = P", **kw)
p_equation.to_corner(UL)
self.add(p_equation)
# Numerical momentum
p_dec_equation = Tex(R"(10)(+0.00) + (1)(+0.00) = +0.00", font_size=42)
p_terms = p_dec_equation.make_number_changeable("+0.00", replace_all=True, include_sign=True)
p_terms[:2].set_color(RED)
p_dec_equation["(1)"].set_color(BLUE)
p_dec_equation["(10)"].set_color(BLUE)
p_dec_equation.next_to(p_equation, DOWN, buff=0.75)
p_terms[0].add_updater(lambda m: m.set_value(state_tracker.get_block_velocities()[0]))
p_terms[1].add_updater(lambda m: m.set_value(state_tracker.get_block_velocities()[1]))
p_terms[2].add_updater(lambda m: m.set_value(state_tracker.get_momentum()))
# Velocity vectors
velocity_vectors = VGroup(
self.get_velocity_vector(blocks[0], lambda: state_tracker.get_block_velocities()[0]),
self.get_velocity_vector(blocks[1], lambda: state_tracker.get_block_velocities()[1]),
)
self.add(velocity_vectors)
# Counter
count_label = Tex(R"\# \text{Collisions} = 0", font_size=36)
count = count_label.make_number_changeable("0")
count.add_updater(lambda m: m.set_value(state_tracker.get_n_collisions()))
count_label.next_to(p_dec_equation, DOWN, buff=0.5, aligned_edge=LEFT)
self.add(count_label)
self.add(p_dec_equation)
# Run simulation
self.play(
time_tracker.animate.set_value(20),
run_time=15,
rate_func=linear,
)
self.wait()
examples/embedding_matrix.py
"""
Embedding Matrix Visualization
Shows how words map to vectors via an embedding matrix lookup.
Based on: videos/_2024/transformers/embedding.py - IntroduceEmbeddingMatrix
"""
from manimlib import *
class EmbeddingMatrixScene(InteractiveScene):
"""
Visualizes the embedding matrix concept:
- Words as columns
- Each column is a word's vector representation
"""
def construct(self):
# Sample vocabulary
words = [
'aah', 'aardvark', 'aardwolf', 'aargh', 'ab',
'aback', 'abacterial', 'abacus', 'abalone', 'abandon',
'zygoid', 'zygomatic', 'zygomorphic', 'zygosis', 'zygote',
'zygotic', 'zyme', 'zymogen', 'zymosis', 'zzz'
]
# Create word list
dots = Tex(R"\vdots")
shown_words = VGroup(
*map(Text, words[:10]),
dots,
*map(Text, words[-10:]),
)
shown_words.arrange(DOWN, aligned_edge=LEFT)
dots.match_x(shown_words[:5])
shown_words.set_height(FRAME_HEIGHT - 1)
shown_words.move_to(LEFT)
shown_words.set_fill(border_width=0)
brace = Brace(shown_words, RIGHT)
brace_text = brace.get_tex(R"\text{All words}")
# Animate words appearing
self.play(
LaggedStartMap(FadeIn, shown_words, shift=0.5 * LEFT, lag_ratio=0.1, run_time=2),
GrowFromCenter(brace, time_span=(0.5, 2.0)),
FadeIn(brace_text, time_span=(0.5, 1.5)),
)
self.wait()
# Create embedding matrix
dots_index = shown_words.submobjects.index(dots)
matrix = WeightMatrix(
shape=(8, len(shown_words)),
ellipses_col=dots_index
)
matrix.set_width(13)
matrix.center()
columns = matrix.get_columns()
matrix_name = Text("Embedding Matrix", font_size=72)
matrix_name.next_to(matrix, DOWN, buff=0.5)
# Transform words to matrix columns
shown_words.target = shown_words.generate_target()
shown_words.target.rotate(PI / 2)
shown_words.target.next_to(matrix, UP)
for word, column in zip(shown_words.target, columns):
word.match_x(column)
word.rotate(-45 * DEGREES, about_edge=DOWN)
shown_words.target[dots_index].rotate(45 * DEGREES).move_to(
shown_words.target[dots_index - 1:dots_index + 2]
)
new_brace = Brace(shown_words.target, UP, buff=0.0)
# Create column highlight rectangles
column_rects = VGroup(*(
SurroundingRectangle(column, buff=0.05)
for column in columns
))
column_rects.set_stroke(WHITE, 1)
# Animate matrix formation
self.play(
MoveToTarget(shown_words),
brace.animate.become(new_brace),
brace_text.animate.next_to(new_brace, UP, buff=0.1),
LaggedStart(*(
Write(column, lag_ratio=0.01, stroke_width=1)
for column in columns
), lag_ratio=0.2, run_time=2),
LaggedStartMap(FadeIn, matrix.get_brackets(), scale=0.5, lag_ratio=0)
)
self.play(Write(matrix_name, run_time=1))
self.wait()
# Highlight columns one by one
last_rect = VMobject()
for index in range(min(8, len(columns))):
for group in shown_words, columns:
group.target = group.generate_target()
group.target.set_opacity(0.2)
group.target[index].set_opacity(1)
rect = column_rects[index]
self.play(
*map(MoveToTarget, [shown_words, columns]),
FadeIn(rect),
FadeOut(last_rect),
run_time=0.5
)
last_rect = rect
self.wait(0.25)
# Reset opacity
self.play(
FadeOut(last_rect),
shown_words.animate.set_opacity(1),
columns.animate.set_opacity(1),
)
# Add matrix label W_E
frame = self.frame
lhs = Tex("W_E = ", font_size=72)
lhs.next_to(matrix, LEFT)
self.play(
frame.animate.set_width(FRAME_WIDTH + 3, about_edge=RIGHT),
Write(lhs)
)
self.wait()
# Highlight a single word lookup
index = words.index("aardvark")
word = shown_words[index].copy()
vector = VGroup(
matrix.get_brackets()[0],
matrix.get_columns()[index],
matrix.get_brackets()[1],
).copy()
# Animate pulling out the vector
vector.target = vector.generate_target()
vector.target.arrange(RIGHT, buff=0.1)
vector.target.set_height(4)
vector.target.move_to(3 * RIGHT + DOWN)
word.target = word.generate_target()
word.target.rotate(-45 * DEGREES)
word.target.scale(2)
word.target.next_to(vector.target, LEFT, buff=1.5)
arrow = Arrow(word.target, vector.target)
# Scale down matrix and show lookup
matrix_group = VGroup(lhs, matrix, shown_words, matrix_name)
self.play(
matrix_group.animate.scale(0.5).to_corner(UL),
FadeOut(brace, UP),
FadeOut(brace_text, 0.5 * UP),
MoveToTarget(word),
MoveToTarget(vector),
GrowFromPoint(arrow, word.get_center()),
run_time=2
)
self.wait()
# Add lookup label
lookup_label = Text("Embedding Lookup", font_size=48)
lookup_label.to_edge(DOWN)
self.play(Write(lookup_label))
self.wait(2)
examples/equation_transforms.py
"""
Equation Transforms and Mathematical Derivations
Shows step-by-step equation manipulation with highlighting,
the hallmark of 3b1b's mathematical explanations.
Run: manimgl equation_transforms.py QuadraticFormula -w
Preview: manimgl equation_transforms.py QuadraticFormula -p
Source: Inspired by 3b1b's equation transformation style
"""
from manimlib import *
class QuadraticFormula(InteractiveScene):
"""
Derives the quadratic formula step by step with
color-coded terms and smooth transformations.
"""
def construct(self):
# Color scheme for terms
colors = {
"a": RED,
"b": GREEN,
"c": BLUE,
"x": YELLOW,
}
# Step 1: Start with general quadratic
eq1 = Tex(
r"ax^2 + bx + c = 0",
t2c={"a": RED, "b": GREEN, "c": BLUE, "x": YELLOW}
)
eq1.to_edge(UP, buff=1)
self.play(Write(eq1))
self.wait()
# Step 2: Divide by a
eq2 = Tex(
r"x^2 + \frac{b}{a}x + \frac{c}{a} = 0",
t2c={"a": RED, "b": GREEN, "c": BLUE, "x": YELLOW}
)
eq2.next_to(eq1, DOWN, buff=0.8)
step1_label = Text("Divide by a", font_size=24, color=GREY)
step1_label.next_to(eq2, LEFT, buff=0.5)
self.play(
TransformMatchingTex(eq1.copy(), eq2),
FadeIn(step1_label, LEFT),
)
self.wait()
# Step 3: Complete the square
eq3 = Tex(
r"\left(x + \frac{b}{2a}\right)^2 - \frac{b^2}{4a^2} + \frac{c}{a} = 0",
t2c={"a": RED, "b": GREEN, "c": BLUE, "x": YELLOW}
)
eq3.next_to(eq2, DOWN, buff=0.8)
step2_label = Text("Complete the square", font_size=24, color=GREY)
step2_label.next_to(eq3, LEFT, buff=0.5)
self.play(
TransformMatchingTex(eq2.copy(), eq3),
FadeIn(step2_label, LEFT),
)
self.wait()
# Step 4: Isolate the squared term
eq4 = Tex(
r"\left(x + \frac{b}{2a}\right)^2 = \frac{b^2 - 4ac}{4a^2}",
t2c={"a": RED, "b": GREEN, "c": BLUE, "x": YELLOW}
)
eq4.next_to(eq3, DOWN, buff=0.8)
step3_label = Text("Rearrange", font_size=24, color=GREY)
step3_label.next_to(eq4, LEFT, buff=0.5)
self.play(
TransformMatchingTex(eq3.copy(), eq4),
FadeIn(step3_label, LEFT),
)
self.wait()
# Step 5: Take square root
eq5 = Tex(
r"x + \frac{b}{2a} = \pm\frac{\sqrt{b^2 - 4ac}}{2a}",
t2c={"a": RED, "b": GREEN, "c": BLUE, "x": YELLOW}
)
eq5.next_to(eq4, DOWN, buff=0.8)
step4_label = Text("Square root", font_size=24, color=GREY)
step4_label.next_to(eq5, LEFT, buff=0.5)
self.play(
TransformMatchingTex(eq4.copy(), eq5),
FadeIn(step4_label, LEFT),
)
self.wait()
# Final formula with box
final = Tex(
r"x = \frac{-b \pm \sqrt{b^2 - 4ac}}{2a}",
t2c={"a": RED, "b": GREEN, "c": BLUE, "x": YELLOW},
font_size=60
)
final.next_to(eq5, DOWN, buff=1)
box = SurroundingRectangle(final, color=GOLD, buff=0.2)
self.play(
TransformMatchingTex(eq5.copy(), final),
)
self.play(ShowCreation(box))
self.wait(2)
class HighlightAndTransform(InteractiveScene):
"""
Demonstrates the technique of highlighting parts of equations
before transforming them. A core 3b1b pattern.
"""
def construct(self):
# Start with an equation
eq = Tex(r"(a + b)^2 = a^2 + 2ab + b^2", font_size=48)
eq.center()
self.play(Write(eq))
self.wait()
# Highlight LHS
lhs = eq[r"(a + b)^2"]
lhs_rect = SurroundingRectangle(lhs, color=YELLOW, buff=0.1)
self.play(ShowCreation(lhs_rect))
self.wait()
# Highlight RHS parts one by one
parts = [
(r"a^2", RED),
(r"2ab", GREEN),
(r"b^2", BLUE),
]
rects = []
for tex, color in parts:
part = eq[tex]
rect = SurroundingRectangle(part, color=color, buff=0.05)
self.play(ShowCreation(rect))
rects.append(rect)
self.wait(0.5)
# Fade out rectangles
self.play(
FadeOut(lhs_rect),
*[FadeOut(r) for r in rects]
)
# Show visual proof
self.play(eq.animate.to_edge(UP))
# Create squares
side = 2
a_frac = 0.6
a_side = side * a_frac
b_side = side * (1 - a_frac)
# The big square (a+b)^2
big_square = Square(side)
big_square.set_stroke(WHITE, 2)
big_square.center()
# Subdivisions
a_sq = Square(a_side)
a_sq.set_fill(RED, 0.5)
a_sq.set_stroke(WHITE, 1)
a_sq.align_to(big_square, UL)
b_sq = Square(b_side)
b_sq.set_fill(BLUE, 0.5)
b_sq.set_stroke(WHITE, 1)
b_sq.align_to(big_square, DR)
ab_rect1 = Rectangle(width=a_side, height=b_side)
ab_rect1.set_fill(GREEN, 0.5)
ab_rect1.set_stroke(WHITE, 1)
ab_rect1.next_to(a_sq, RIGHT, buff=0)
ab_rect2 = Rectangle(width=b_side, height=a_side)
ab_rect2.set_fill(GREEN, 0.5)
ab_rect2.set_stroke(WHITE, 1)
ab_rect2.next_to(a_sq, DOWN, buff=0)
squares = VGroup(a_sq, b_sq, ab_rect1, ab_rect2)
# Labels
a_label = Tex("a^2", color=RED, font_size=24)
a_label.move_to(a_sq)
b_label = Tex("b^2", color=BLUE, font_size=24)
b_label.move_to(b_sq)
ab_label1 = Tex("ab", color=GREEN, font_size=20)
ab_label1.move_to(ab_rect1)
ab_label2 = Tex("ab", color=GREEN, font_size=20)
ab_label2.move_to(ab_rect2)
self.play(ShowCreation(big_square))
self.play(
FadeIn(a_sq), Write(a_label),
FadeIn(ab_rect1), Write(ab_label1),
FadeIn(ab_rect2), Write(ab_label2),
FadeIn(b_sq), Write(b_label),
run_time=2
)
self.wait(2)
class BraceAnnotations(InteractiveScene):
"""
Uses braces to annotate and explain equation parts.
Another signature 3b1b technique.
"""
def construct(self):
# Main equation
eq = Tex(
r"F = ma",
font_size=96
)
eq.center()
self.play(Write(eq))
self.wait()
# Add braces with labels
F_brace = Brace(eq["F"], UP, color=BLUE)
F_label = F_brace.get_text("Force", font_size=30)
F_label.set_color(BLUE)
m_brace = Brace(eq["m"], DOWN, color=RED)
m_label = m_brace.get_text("Mass", font_size=30)
m_label.set_color(RED)
a_brace = Brace(eq["a"], DOWN, color=GREEN)
a_label = a_brace.get_text("Acceleration", font_size=30)
a_label.set_color(GREEN)
self.play(
GrowFromCenter(F_brace),
FadeIn(F_label, UP),
)
self.wait()
self.play(
GrowFromCenter(m_brace),
FadeIn(m_label, DOWN),
)
self.wait()
self.play(
GrowFromCenter(a_brace),
FadeIn(a_label, DOWN),
)
self.wait()
# Fade all and show rearrangement
all_braces = VGroup(F_brace, F_label, m_brace, m_label, a_brace, a_label)
eq2 = Tex(r"a = \frac{F}{m}", font_size=96)
eq2.center()
self.play(FadeOut(all_braces))
self.play(TransformMatchingTex(eq, eq2))
self.wait()
# New annotation
new_brace = Brace(eq2[r"\frac{F}{m}"], DOWN, color=YELLOW)
new_label = new_brace.get_text("Force per unit mass", font_size=24)
new_label.set_color(YELLOW)
self.play(
GrowFromCenter(new_brace),
FadeIn(new_label, DOWN),
)
self.wait(2)
class ColorCodedSubstitution(InteractiveScene):
"""
Shows variable substitution with color tracking.
Makes complex substitutions easy to follow.
"""
def construct(self):
# Define substitution
sub_def = Tex(
r"u = x^2 + 1",
t2c={"u": RED, "x": BLUE}
)
sub_def.to_edge(UP)
self.play(Write(sub_def))
self.wait()
# Original integral
integral1 = Tex(
r"\int 2x(x^2 + 1)^3 \, dx",
t2c={"x": BLUE},
font_size=48
)
integral1.center()
self.play(Write(integral1))
self.wait()
# Highlight the u part
u_part = integral1[r"(x^2 + 1)"]
u_rect = SurroundingRectangle(u_part, color=RED, buff=0.05)
self.play(ShowCreation(u_rect))
self.wait()
# Show du
du_def = Tex(
r"du = 2x \, dx",
t2c={"u": RED, "x": BLUE}
)
du_def.next_to(sub_def, DOWN)
# Highlight the 2x dx part
dx_part = integral1[r"2x"]
dx_rect = SurroundingRectangle(dx_part, color=GREEN, buff=0.05)
self.play(
Write(du_def),
ShowCreation(dx_rect),
)
self.wait()
# Transform to u integral
integral2 = Tex(
r"\int u^3 \, du",
t2c={"u": RED},
font_size=48
)
integral2.center()
self.play(
FadeOut(u_rect),
FadeOut(dx_rect),
TransformMatchingTex(integral1, integral2),
)
self.wait()
# Solve
solution = Tex(
r"= \frac{u^4}{4} + C",
t2c={"u": RED},
font_size=48
)
solution.next_to(integral2, DOWN, buff=0.5)
self.play(Write(solution))
self.wait()
# Substitute back
final = Tex(
r"= \frac{(x^2+1)^4}{4} + C",
t2c={"x": BLUE},
font_size=48
)
final.next_to(solution, DOWN, buff=0.5)
self.play(
TransformMatchingTex(solution.copy(), final),
)
self.wait(2)
examples/exponential_derivative.py
"""
Exponential Function and Its Derivative
Demonstrates the fundamental property that d/dt e^t = e^t
with tangent line visualization and moving point.
Run: manimgl exponential_derivative.py ExpDerivative -w
Preview: manimgl exponential_derivative.py ExpDerivative -p
Source: Adapted from 3b1b's Laplace transform video (2025)
"""
from manimlib import *
class ExpDerivative(InteractiveScene):
"""
Visual demonstration of the exponential function's defining property.
Key techniques:
- get_v_line_to_graph for vertical lines
- get_tangent_line for derivative visualization
- always updaters for dynamic positioning
- make_number_changeable for live value displays
"""
def construct(self):
# Set up graph
axes = Axes(
x_range=(-1, 4),
y_range=(0, 20),
width=10,
height=6
)
axes.to_edge(RIGHT)
# Axis label
t_label = Tex("t")
t_label.next_to(axes.x_axis.get_right(), UL, MED_SMALL_BUFF)
axes.add(t_label)
# The exponential graph
graph = axes.get_graph(np.exp)
graph.set_stroke(BLUE, 3)
# Title showing the defining property
title = Tex(
R"\frac{d}{dt} e^t = e^t",
t2c={"t": GREY_B},
font_size=60
)
title.to_edge(UP)
title.match_x(axes.c2p(1.5, 0))
self.add(axes, graph, title)
# Tracker for the point on the graph
t_tracker = ValueTracker(1)
get_t = t_tracker.get_value
# Vertical line showing height e^t
v_line = always_redraw(
lambda: axes.get_v_line_to_graph(get_t(), graph, line_func=Line)
.set_stroke(RED, 3)
)
# Height label
height_label = Tex(R"e^t", font_size=42)
height_label.always.next_to(v_line, RIGHT, SMALL_BUFF)
# Constrain label size when line is short
height_label_height = height_label.get_height()
height_label.add_updater(lambda m: m.set_height(
min(height_label_height, 0.7 * v_line.get_height())
))
# Animate the height visualization
self.play(
ShowCreation(v_line, suspend_mobject_updating=True),
FadeIn(height_label, UP, suspend_mobject_updating=True),
)
self.wait()
# Add tangent line showing the derivative
tangent_line = always_redraw(
lambda: axes.get_tangent_line(get_t(), graph, length=10)
.set_stroke(BLUE_A, 1)
)
# Show "1" run on the tangent line
unit_size = axes.x_axis.get_unit_size()
unit_line = Line(axes.c2p(0, 0), axes.c2p(1, 0))
unit_line.add_updater(lambda m: m.move_to(v_line.get_end(), LEFT))
unit_line.set_stroke(WHITE, 2)
unit_label = Integer(1, font_size=24)
unit_label.add_updater(lambda m: m.next_to(unit_line.pfp(0.6), UP, 0.5 * SMALL_BUFF))
# Vertical rise = slope * 1 = derivative value
tan_v_line = always_redraw(
lambda: v_line.copy().shift(v_line.get_vector() + unit_size * RIGHT)
)
# Label for the derivative (rise of tangent)
deriv_label = Tex(R"\frac{d}{dt} e^t = e^t", font_size=42)
deriv_label[R"\frac{d}{dt}"].scale(0.75, about_edge=RIGHT)
deriv_label_height = deriv_label.get_height()
deriv_label.add_updater(lambda m: m.set_height(
min(deriv_label_height, 0.8 * v_line.get_height())
))
deriv_label.always.next_to(tan_v_line, RIGHT, SMALL_BUFF)
# Show the tangent line
self.play(ShowCreation(tangent_line, suspend_mobject_updating=True))
# Show unit run and derivative rise
self.play(
VFadeIn(unit_line),
VFadeIn(unit_label),
VFadeIn(tan_v_line, suspend_mobject_updating=True),
TransformFromCopy(title, deriv_label),
)
# Animate the height = derivative correspondence
self.play(
ReplacementTransform(
v_line.copy().clear_updaters(),
tan_v_line,
path_arc=45 * DEG
),
FadeTransform(height_label.copy(), deriv_label["e^t"][1], path_arc=45 * DEG, remover=True),
)
self.wait()
# Move the point around to show consistency
for t in [2.35, 0, 1, 2]:
self.play(t_tracker.animate.set_value(t), run_time=4)
self.wait()
class ExpFamilyGraph(InteractiveScene):
"""
Show family of exponentials e^{st} for different values of s.
When s > 0: growth, s < 0: decay, s = 0: constant.
"""
def construct(self):
# Axes
axes = Axes(
x_range=(-1, 8),
y_range=(-1, 5),
width=FRAME_WIDTH - 2,
height=FRAME_HEIGHT - 1.5
)
axes.to_edge(DOWN)
# Parameter tracker
s_tracker = ValueTracker(0.5)
get_s = s_tracker.get_value
# Dynamic graph
graph = axes.get_graph(lambda t: np.exp(t))
graph.set_stroke(BLUE, 3)
axes.bind_graph_to_func(graph, lambda t: np.exp(get_s() * t))
# Label
label = Tex(R"e^{st}", font_size=90)
label.move_to(UP)
label["s"].set_color(YELLOW)
# s value display
s_label = Tex(R"s = 0.50", font_size=48)
s_label["s"].set_color(YELLOW)
s_value = s_label.make_number_changeable("0.50")
s_value.add_updater(lambda m: m.set_value(get_s()))
s_label.to_corner(UR)
self.add(axes, label, s_label)
# Draw initial graph
self.play(ShowCreation(graph, suspend_mobject_updating=True))
self.wait()
# Vary s through different regimes
self.play(
s_tracker.animate.set_value(-1),
graph.animate.set_color(YELLOW),
run_time=4
)
self.wait()
self.play(s_tracker.animate.set_value(0), run_time=2)
self.wait()
self.play(
s_tracker.animate.set_value(0.3),
graph.animate.set_color(GREEN),
run_time=2
)
self.wait()
self.play(s_tracker.animate.set_value(0.5), run_time=2)
self.wait(2)
class ComplexExpSpiral(InteractiveScene):
"""
Visualize e^{(a+bi)t} as a spiral in the complex plane.
Shows how real part controls growth/decay, imaginary controls rotation.
"""
def construct(self):
# Complex plane
plane = ComplexPlane(
x_range=(-3, 3),
y_range=(-3, 3),
background_line_style=dict(stroke_color=BLUE, stroke_width=1),
)
plane.set_height(6)
plane.to_edge(LEFT)
plane.add_coordinate_labels(font_size=20)
self.add(plane)
# s = a + bi tracker
s_tracker = ComplexValueTracker(-0.1 + 1j)
get_s = s_tracker.get_value
# Time tracker
t_tracker = ValueTracker(0)
get_t = t_tracker.get_value
# Moving point
dot = GlowDot(color=TEAL)
dot.add_updater(lambda m: m.move_to(plane.n2p(np.exp(get_s() * get_t()))))
# Traced path
path = TracedPath(dot.get_center, stroke_color=TEAL, stroke_width=2)
# Vector from origin
vector = Vector(fill_color=YELLOW)
vector.add_updater(lambda m: m.put_start_and_end_on(
plane.n2p(0),
plane.n2p(np.exp(get_s() * get_t()))
))
# s value display
s_label = Tex(R"s = -0.10 + 1.00i", font_size=36)
s_label.to_corner(UR)
# Expression
exp_label = Tex(R"e^{st}", font_size=60)
exp_label["s"].set_color(YELLOW)
exp_label.next_to(plane, UP)
self.add(exp_label, s_label)
self.add(vector, path, dot)
# Run the animation
t_tracker.add_updater(lambda m, dt: m.increment_value(dt))
self.add(t_tracker)
self.wait(8)
# Change s to show different spirals
t_tracker.clear_updaters()
path.clear_updaters()
path = TracedPath(dot.get_center, stroke_color=GREEN, stroke_width=2)
self.add(path)
t_tracker.set_value(0)
s_tracker.set_value(0.1 + 1.5j)
t_tracker.add_updater(lambda m, dt: m.increment_value(dt))
self.wait(8)
examples/fibonacci_eigenvalues.py
"""
Fibonacci Eigenvalues
=====================
Shows how eigenvalues/eigenvectors lead to the closed-form Fibonacci formula.
This is a classic application of diagonalization in linear algebra.
Key concepts:
- Fibonacci recurrence as matrix multiplication
- Golden ratio as eigenvalue
- Binet's formula derivation
"""
from manimlib import *
class FibonacciEigenvalues(Scene):
"""
Derives the closed-form Fibonacci formula using eigenvalues.
F_n = (phi^n - psi^n) / sqrt(5)
"""
def construct(self):
# Title
title = Text("Fibonacci via Eigenvalues", font_size=48)
title.to_edge(UP)
self.play(Write(title))
# Fibonacci recurrence
recurrence = Tex(
R"F_{n+1} = F_n + F_{n-1}",
font_size=40
)
recurrence.next_to(title, DOWN, buff=0.6)
self.play(Write(recurrence))
self.wait()
# Matrix form
matrix_form = Tex(
R"\begin{bmatrix} F_{n+1} \\ F_n \end{bmatrix} = "
R"\begin{bmatrix} 1 & 1 \\ 1 & 0 \end{bmatrix}"
R"\begin{bmatrix} F_n \\ F_{n-1} \end{bmatrix}",
font_size=36
)
matrix_form.next_to(recurrence, DOWN, buff=0.5)
self.play(Write(matrix_form))
self.wait()
# Label the matrix
a_label = Tex(R"A", font_size=36, color=BLUE)
a_label.next_to(matrix_form[10:16], UP, buff=0.1)
self.play(FadeIn(a_label, shift=DOWN * 0.2))
self.wait()
# Clear and show eigenvalue calculation
self.play(
FadeOut(recurrence),
FadeOut(matrix_form),
FadeOut(a_label),
)
# Characteristic equation
char_title = Text("Find eigenvalues:", font_size=32)
char_title.next_to(title, DOWN, buff=0.5)
char_eq = Tex(
R"\det(A - \lambda I) = 0",
font_size=36
)
char_eq.next_to(char_title, DOWN, buff=0.3)
expanded = Tex(
R"\det\begin{bmatrix} 1-\lambda & 1 \\ 1 & -\lambda \end{bmatrix} = 0",
font_size=36
)
expanded.next_to(char_eq, DOWN, buff=0.3)
polynomial = Tex(
R"\lambda^2 - \lambda - 1 = 0",
font_size=36
)
polynomial.next_to(expanded, DOWN, buff=0.3)
self.play(Write(char_title))
self.play(Write(char_eq))
self.wait(0.5)
self.play(Write(expanded))
self.wait(0.5)
self.play(Write(polynomial))
self.wait()
# Show eigenvalues (golden ratio!)
eigenvalues = Tex(
R"\lambda_1 = \phi = \frac{1 + \sqrt{5}}{2}, \quad "
R"\lambda_2 = \psi = \frac{1 - \sqrt{5}}{2}",
font_size=32,
t2c={R"\phi": TEAL, R"\psi": YELLOW, R"\lambda_1": TEAL, R"\lambda_2": YELLOW}
)
eigenvalues.next_to(polynomial, DOWN, buff=0.5)
golden_note = Text("(Golden Ratio!)", font_size=24, color=TEAL)
golden_note.next_to(eigenvalues, DOWN, buff=0.2)
self.play(Write(eigenvalues))
self.play(FadeIn(golden_note, shift=UP * 0.2))
self.wait()
# Clear and show final formula
self.play(
FadeOut(char_title),
FadeOut(char_eq),
FadeOut(expanded),
FadeOut(polynomial),
FadeOut(golden_note),
eigenvalues.animate.next_to(title, DOWN, buff=0.5)
)
# Binet's formula
binet_title = Text("Binet's Formula:", font_size=32)
binet_title.next_to(eigenvalues, DOWN, buff=0.5)
binet = Tex(
R"F_n = \frac{\phi^n - \psi^n}{\sqrt{5}}",
font_size=48,
t2c={R"\phi": TEAL, R"\psi": YELLOW}
)
binet.next_to(binet_title, DOWN, buff=0.3)
# Box around final formula
box = SurroundingRectangle(binet, buff=0.2, color=BLUE)
self.play(Write(binet_title))
self.play(Write(binet))
self.play(ShowCreation(box))
self.wait()
# Note about psi
note = Tex(
R"\text{Since } |\psi| < 1, \text{ for large } n: \quad "
R"F_n \approx \frac{\phi^n}{\sqrt{5}}",
font_size=28
)
note.next_to(box, DOWN, buff=0.5)
self.play(Write(note))
self.wait(2)
class FibonacciVisualization(Scene):
"""
Visual representation of Fibonacci spiral with golden ratio.
"""
def construct(self):
# Create Fibonacci squares
fibs = [1, 1, 2, 3, 5, 8, 13]
scale = 0.15
squares = VGroup()
current_pos = ORIGIN
directions = [RIGHT, UP, LEFT, DOWN] # Spiral pattern
for i, f in enumerate(fibs):
sq = Square(side_length=f * scale)
sq.set_stroke(BLUE, 2)
sq.set_fill(BLUE, 0.2)
if i == 0:
sq.move_to(current_pos)
else:
direction = directions[(i - 1) % 4]
prev_sq = squares[-1]
sq.next_to(prev_sq, direction, buff=0)
# Adjust position based on size difference
if direction == RIGHT or direction == LEFT:
sq.align_to(prev_sq, DOWN if i % 2 == 1 else UP)
else:
sq.align_to(prev_sq, LEFT if (i - 1) % 4 < 2 else RIGHT)
# Add number label
label = Tex(str(f), font_size=max(12, f * 3))
label.move_to(sq)
sq.add(label)
squares.add(sq)
squares.center()
squares.set_height(5)
title = Text("Fibonacci Spiral", font_size=42)
title.to_edge(UP)
golden_ratio = Tex(
R"\phi = \frac{1+\sqrt{5}}{2} \approx 1.618",
font_size=32
)
golden_ratio.to_edge(DOWN)
self.play(Write(title))
self.play(
LaggedStartMap(FadeIn, squares, lag_ratio=0.3),
run_time=3
)
self.play(Write(golden_ratio))
self.wait(2)
examples/gradient_descent_basic.py
"""
Basic gradient descent visualization on a 2D loss landscape.
Demonstrates: Surface plots, 3D camera, path animation, optimization concepts
"""
from manimlib import *
import numpy as np
class GradientDescentBasic(Scene):
def construct(self):
# Create a simple 2D loss landscape (contour view)
axes = Axes(
x_range=(-3, 3, 1),
y_range=(-3, 3, 1),
width=8,
height=8
)
axes.to_edge(LEFT)
# Loss function: simple quadratic bowl
def loss_func(x, y):
return 0.5 * x**2 + 0.8 * y**2 + 0.3 * x * y
# Create contour lines
contours = VGroup()
for level in np.linspace(0.5, 8, 8):
# Approximate contour as ellipse
a = np.sqrt(2 * level / 0.5) # x scale
b = np.sqrt(2 * level / 0.8) # y scale
ellipse = Ellipse(width=a, height=b)
ellipse.move_to(axes.get_origin())
ellipse.set_stroke(
color=interpolate_color(BLUE, RED, level / 8),
width=2,
opacity=0.7
)
contours.add(ellipse)
# Title
title = Text("Gradient Descent", font_size=60)
title.to_edge(UP)
# Labels
w1_label = Tex("w_1")
w1_label.next_to(axes.x_axis.get_right(), DOWN)
w2_label = Tex("w_2")
w2_label.next_to(axes.y_axis.get_top(), LEFT)
self.play(FadeIn(title))
self.play(FadeIn(axes), FadeIn(w1_label), FadeIn(w2_label))
self.play(LaggedStartMap(FadeIn, contours, lag_ratio=0.1))
self.wait()
# Add minimum marker
min_dot = Dot(axes.get_origin(), color=GREEN)
min_label = Text("Minimum", font_size=24, color=GREEN)
min_label.next_to(min_dot, DOWN)
self.play(FadeIn(min_dot, scale=2), FadeIn(min_label))
self.wait()
# Starting point
start_point = axes.c2p(2.5, -2)
current_dot = Dot(start_point, color=YELLOW)
current_dot.set_z_index(1)
start_label = Text("Start", font_size=24)
start_label.next_to(current_dot, UR, buff=0.1)
self.play(FadeIn(current_dot, scale=2), FadeIn(start_label))
self.wait()
# Gradient descent path
learning_rate = 0.2
path_points = [np.array([2.5, -2.0])]
current = path_points[0].copy()
for _ in range(20):
# Gradient of loss: [x + 0.15*y, 1.6*y + 0.15*x]
grad = np.array([
current[0] + 0.15 * current[1],
1.6 * current[1] + 0.15 * current[0]
])
current = current - learning_rate * grad
path_points.append(current.copy())
if np.linalg.norm(current) < 0.01:
break
# Create path
path = VMobject()
path.set_points_smoothly([axes.c2p(p[0], p[1]) for p in path_points])
path.set_stroke(YELLOW, 3)
# Animation info panel
info_panel = VGroup()
iter_text = Text("Iteration: 0", font_size=30)
loss_text = Text("Loss: {:.3f}".format(loss_func(*path_points[0])), font_size=30)
info_panel.add(iter_text, loss_text)
info_panel.arrange(DOWN, aligned_edge=LEFT)
info_panel.to_corner(UR)
self.play(FadeIn(info_panel), FadeOut(start_label))
# Animate gradient descent
path_so_far = VMobject()
path_so_far.set_stroke(YELLOW, 3)
for i, (p1, p2) in enumerate(zip(path_points[:-1], path_points[1:])):
# Draw gradient arrow
p1_screen = axes.c2p(p1[0], p1[1])
p2_screen = axes.c2p(p2[0], p2[1])
arrow = Arrow(
p1_screen, p2_screen,
buff=0,
stroke_width=3,
color=RED
)
# Update info
new_iter = Text(f"Iteration: {i + 1}", font_size=30)
new_loss = Text(f"Loss: {loss_func(*p2):.3f}", font_size=30)
new_info = VGroup(new_iter, new_loss)
new_info.arrange(DOWN, aligned_edge=LEFT)
new_info.move_to(info_panel)
self.play(
GrowArrow(arrow),
current_dot.animate.move_to(p2_screen),
Transform(info_panel, new_info),
run_time=0.5
)
self.play(FadeOut(arrow), run_time=0.2)
if i > 15:
break
self.wait()
# Final message
converged = Text("Converged!", font_size=48, color=GREEN)
converged.next_to(title, DOWN)
self.play(FadeIn(converged, scale=1.5))
self.wait()
# Show the full path
self.play(
ShowCreation(path),
run_time=2
)
self.wait(2)
class GradientDescent3D(ThreeDScene):
"""3D visualization of gradient descent on a loss surface."""
def construct(self):
# Set up 3D view
frame = self.camera.frame
frame.set_euler_angles(theta=30 * DEGREES, phi=70 * DEGREES)
# Create 3D axes
axes = ThreeDAxes(
x_range=(-3, 3, 1),
y_range=(-3, 3, 1),
z_range=(0, 5, 1),
width=8,
height=8,
depth=4
)
# Loss surface
def loss_func(x, y):
return 0.3 * x**2 + 0.4 * y**2
surface = axes.get_graph(
loss_func,
u_range=(-3, 3),
v_range=(-3, 3),
)
surface.set_color_by_gradient(BLUE, GREEN, YELLOW, RED)
surface.set_opacity(0.7)
# Labels
title = Text("Loss Landscape", font_size=48)
title.to_corner(UL)
title.fix_in_frame()
self.play(
FadeIn(axes),
FadeIn(surface),
FadeIn(title),
)
self.wait()
# Rotate view
self.play(
frame.animate.set_euler_angles(theta=-30 * DEGREES),
run_time=3
)
self.wait()
# Gradient descent ball
start = np.array([2.5, 2.0])
ball = Sphere(radius=0.15, color=YELLOW)
ball.move_to(axes.c2p(start[0], start[1], loss_func(*start)))
self.play(FadeIn(ball, scale=2))
# Animate descent
learning_rate = 0.15
current = start.copy()
for _ in range(15):
grad = np.array([0.6 * current[0], 0.8 * current[1]])
new_pos = current - learning_rate * grad
new_point = axes.c2p(new_pos[0], new_pos[1], loss_func(*new_pos))
self.play(
ball.animate.move_to(new_point),
run_time=0.4
)
current = new_pos
self.wait()
# Final rotation
self.play(
frame.animate.set_euler_angles(theta=60 * DEGREES, phi=60 * DEGREES),
run_time=3
)
self.wait(2)
examples/hexagon_cube_correspondence.py
"""
Visualization showing the correspondence between hexagonal tilings
and 3D cube stacking patterns.
"""
from manimlib import *
import math
class HexagonCubeCorrespondence(InteractiveScene):
"""
Shows how a hexagonal tiling corresponds to viewing 3D cube stacks from above.
Demonstrates:
1. Creating half-cube faces in 3D
2. Viewing them from the [1,1,1] direction
3. How rotation in 2D corresponds to adding/removing cubes in 3D
"""
n = 4
colors = [BLUE_B, BLUE_D, BLUE_E]
def construct(self):
# Set up axes and camera angle
self.frame.set_field_of_view(1 * DEGREES)
self.frame.reorient(135, 55, 0)
axes = ThreeDAxes((-5, 5), (-5, 5), (-5, 5))
# Add base half-cube
base_cube = self.get_half_cube(
side_length=self.n,
shared_corner=[-1, -1, -1],
grid=True
)
self.add(base_cube)
# Add cubes to build a stack
cubes = VGroup()
block_pattern = np.zeros((self.n, self.n, self.n))
# Build a pyramid-like structure
for x in range(self.n):
for y in range(self.n - x):
for z in range(self.n - x - y):
cube = self.get_half_cube((x, y, z))
cubes.add(cube)
block_pattern[x, y, z] = 1
self.play(
LaggedStart(
(FadeIn(cube, shift=0.25 * IN) for cube in cubes),
lag_ratio=0.02,
),
run_time=3
)
self.wait()
# Remove the base and color the cubes
self.play(FadeOut(base_cube))
cubes.set_fill(BLUE_D)
self.wait()
# Rotate to show hexagonal view
self.play(
self.frame.animate.reorient(135, 55, 0, ORIGIN, 8).set_field_of_view(1 * DEGREES),
run_time=2
)
self.wait(2)
def get_half_cube(self, coords=(0, 0, 0), side_length=1, colors=None, shared_corner=[1, 1, 1], grid=False):
"""Create three visible faces of a cube (half-cube) that would be seen from the [1,1,1] direction."""
if colors is None:
colors = self.colors
squares = Square(side_length).replicate(3)
if grid:
for square in squares:
grid_lines = Square(side_length=1).get_grid(side_length, side_length, buff=0)
grid_lines.move_to(square)
square.add(grid_lines)
axes = [OUT, DOWN, LEFT]
for square, color, axis in zip(squares, colors, axes):
square.set_fill(color, 1)
square.set_stroke(color, 0)
square.rotate(90.1 * DEGREES, axis)
square.move_to(ORIGIN, shared_corner)
squares.move_to(coords, np.array([-1, -1, -1]))
squares.set_stroke(WHITE, 2)
return squares
examples/integration_visualization.py
"""
Integration Visualization
Shows integration as accumulating area under a curve,
with animated filling and Riemann sum approximations.
Run: manimgl integration_visualization.py AreaUnderCurve -w
Preview: manimgl integration_visualization.py AreaUnderCurve -p
Source: Adapted from 3b1b's Laplace transform video (2025)
"""
from manimlib import *
import numpy as np
class AreaUnderCurve(InteractiveScene):
"""
Basic visualization of definite integral as area under curve.
Shows smooth accumulation of area from left to right.
"""
def construct(self):
# Create axes
axes = Axes(
x_range=(0, 5, 1),
y_range=(0, 3, 1),
width=10,
height=5,
axis_config={"include_tip": True}
)
axes.to_edge(DOWN, buff=1)
x_label = Tex("x", font_size=30)
x_label.next_to(axes.x_axis, RIGHT)
y_label = Tex("f(x)", font_size=30)
y_label.next_to(axes.y_axis, UP)
self.play(
ShowCreation(axes),
Write(x_label),
Write(y_label),
)
# Define a nice function
def f(x):
return 0.3 * x**2 - 0.5 * x + 1.5
# Draw the curve
curve = axes.get_graph(f, x_range=[0, 4.5], color=BLUE, stroke_width=3)
curve_label = Tex("f(x) = 0.3x^2 - 0.5x + 1.5", font_size=24)
curve_label.next_to(curve.get_end(), UR, buff=0.1)
self.play(ShowCreation(curve, run_time=2))
self.play(Write(curve_label))
self.wait()
# Show area accumulating
t_tracker = ValueTracker(0.1)
# Filled area using Polygon
def get_area_polygon():
t = max(0.1, t_tracker.get_value())
xs = np.linspace(0, t, 50)
points = [axes.c2p(x, f(x)) for x in xs]
points.append(axes.c2p(t, 0))
points.append(axes.c2p(0, 0))
poly = Polygon(*points)
poly.set_fill(BLUE_E, opacity=0.5)
poly.set_stroke(width=0)
return poly
area = always_redraw(get_area_polygon)
# Vertical line at current x
def get_v_line():
t = t_tracker.get_value()
return Line(
axes.c2p(t, 0),
axes.c2p(t, f(t)),
color=YELLOW,
stroke_width=2
)
v_line = always_redraw(get_v_line)
# Integral notation
integral = Tex(
r"\int_0^{x} f(t) \, dt",
font_size=48
)
integral.to_corner(UL)
self.play(
FadeIn(area),
FadeIn(v_line),
Write(integral),
)
# Animate accumulation
self.play(
t_tracker.animate.set_value(4),
run_time=5,
rate_func=linear
)
self.wait()
class RiemannSums(InteractiveScene):
"""
Shows Riemann sum approximation converging to true integral.
Rectangles get thinner and better approximate the area.
"""
def construct(self):
# Create axes
axes = Axes(
x_range=(0, 4, 1),
y_range=(0, 3, 1),
width=8,
height=4,
)
axes.center()
def f(x):
return 0.5 * np.sin(x) + 1.5
curve = axes.get_graph(f, x_range=[0.5, 3.5], color=BLUE, stroke_width=3)
self.play(ShowCreation(axes), ShowCreation(curve))
# Create rectangles for different n values
n_values = [4, 8, 16, 32]
current_rects = None
current_label = None
for n in n_values:
dx = 3 / n
rects = VGroup()
for i in range(n):
x = 0.5 + i * dx
height = f(x)
rect = Rectangle(
width=dx * axes.x_axis.get_unit_size(),
height=height * axes.y_axis.get_unit_size(),
stroke_color=WHITE,
stroke_width=1,
fill_color=BLUE_E,
fill_opacity=0.6,
)
rect.move_to(axes.c2p(x + dx/2, height/2))
rects.add(rect)
label = Tex(f"n = {n}", font_size=36)
label.to_corner(UR)
if current_rects is None:
self.play(
LaggedStartMap(FadeIn, rects, lag_ratio=0.05),
Write(label),
)
else:
self.play(
ReplacementTransform(current_rects, rects),
ReplacementTransform(current_label, label),
)
current_rects = rects
current_label = label
self.wait(0.5)
# Final message
converge_text = Tex(r"\text{As } n \to \infty, \text{ sum } \to \int", font_size=36)
converge_text.to_corner(UL)
self.play(Write(converge_text))
self.wait()
class ExponentialDecay(InteractiveScene):
"""
Visualize the integral of e^(-x) from 0 to infinity.
Shows that the total area is exactly 1.
"""
def construct(self):
# Create axes
axes = Axes(
x_range=(0, 6, 1),
y_range=(0, 1.2, 0.5),
width=10,
height=4,
)
axes.to_edge(DOWN, buff=1.5)
x_label = Tex("x", font_size=30).next_to(axes.x_axis, RIGHT)
self.play(ShowCreation(axes), Write(x_label))
# e^(-x) curve
curve = axes.get_graph(
lambda x: np.exp(-x),
x_range=[0, 5.5],
color=BLUE,
stroke_width=3
)
curve_label = Tex(r"e^{-x}", font_size=36, color=BLUE)
curve_label.next_to(curve.get_start(), UR)
self.play(ShowCreation(curve), Write(curve_label))
# Fill area progressively
t_tracker = ValueTracker(0.1)
def get_area():
t = max(0.1, t_tracker.get_value())
xs = np.linspace(0, t, 50)
points = [axes.c2p(x, np.exp(-x)) for x in xs]
points.append(axes.c2p(t, 0))
points.append(axes.c2p(0, 0))
poly = Polygon(*points)
poly.set_fill(BLUE_E, opacity=0.5)
poly.set_stroke(width=0)
return poly
area = always_redraw(get_area)
# Show integral formula
integral = Tex(
r"\int_0^{\infty} e^{-x} \, dx = 1",
font_size=48
)
integral.to_corner(UL)
# Current value tracker
value_label = Tex(r"\text{Area} \approx 0.00", font_size=30)
value_num = value_label.make_number_changeable("0.00")
value_num.add_updater(lambda m: m.set_value(1 - np.exp(-t_tracker.get_value())))
value_label.to_corner(UR)
self.play(
FadeIn(area),
Write(integral),
Write(value_label),
)
# Animate the fill
self.play(
t_tracker.animate.set_value(5.5),
run_time=6,
rate_func=linear
)
self.wait(2)
examples/laplace_integral.py
"""
Laplace Transform Integration Visualization
Demonstrates the integral of e^{-st} as area under the curve,
showing how squishing by 1/s preserves the area relationship.
Run: manimgl laplace_integral.py LaplaceIntegral -w
Preview: manimgl laplace_integral.py LaplaceIntegral -p
Source: Adapted from 3b1b's Laplace transform video (2025)
"""
from manimlib import *
class LaplaceIntegral(InteractiveScene):
"""
Visualize the integral ∫₀^∞ e^{-st} dt = 1/s
Key techniques:
- get_area_under_graph for shaded regions
- ValueTracker for parameter animation
- Dynamic function binding
- make_number_changeable for live updates
"""
def construct(self):
# Set up axes
max_x = 15
unit_size = 4
axes = Axes(
x_range=(0, max_x, 0.25),
y_range=(0, 1, 0.25),
unit_size=unit_size
)
axes.to_edge(DL, buff=1.0)
axes.add_coordinate_labels(num_decimal_places=2, font_size=20)
# Parameter s
s_tracker = ValueTracker(1)
get_s = s_tracker.get_value
# The exponential function
def exp_func(t):
return np.exp(-get_s() * t)
# Dynamic graph
graph = axes.get_graph(np.exp)
graph.set_stroke(BLUE, 3)
axes.bind_graph_to_func(graph, exp_func)
# Label
t2c = {"s": YELLOW}
graph_label = Tex(R"e^{-st}", t2c=t2c, font_size=72)
graph_label.next_to(axes.y_axis.get_top(), UR).shift(0.5 * RIGHT)
# Integral expression
integral = Tex(R"\int^\infty_0 e^{-st} dt", t2c=t2c)
integral.set_x(1)
integral.to_edge(UP)
self.add(axes, graph, graph_label, integral)
# Add a slider for s
s_slider = self.create_slider(s_tracker)
s_slider.to_edge(UP, buff=MED_LARGE_BUFF)
s_slider.align_to(axes.c2p(0, 0), LEFT)
self.add(s_slider)
# Vary s to show different decay rates
for value in [5, 0.25, 1]:
self.play(s_tracker.animate.set_value(value), run_time=4)
self.wait()
# Show integral as area
equals = Tex(R"=", font_size=72).rotate(90 * DEG)
equals.next_to(integral, DOWN)
area_word = Text("Area", font_size=60)
area_word.next_to(equals, DOWN)
area = axes.get_area_under_graph(graph)
def update_area(area):
area.become(axes.get_area_under_graph(graph))
arrow = Arrow(area_word.get_corner(DL), axes.c2p(0.75, 0.5), thickness=4)
self.play(
LaggedStart(
Animation(graph.copy(), remover=True),
Write(equals),
FadeIn(area_word, DOWN),
GrowArrow(arrow),
UpdateFromFunc(area, update_area),
lag_ratio=0.25
),
ShowCreation(graph, suspend_mobject_updating=True, run_time=3),
)
self.wait()
# Show that area = 1 when s = 1
simple_integral = Tex(R"\int^\infty_0 e^{-t} dt")
simple_integral.move_to(integral)
equals_one = Tex(R"= 1", font_size=60)
equals_one.next_to(area_word)
area_one_label = Tex(R"1", font_size=60)
area_one_label.move_to(axes.c2p(0.35, 0.35))
area_one_label.set_z_index(1)
self.play(
TransformMatchingTex(integral, simple_integral),
FadeOut(graph_label),
)
self.wait()
self.play(Write(equals_one))
self.play(TransformFromCopy(equals_one["1"], area_one_label))
self.wait()
# Show area squishing with s
area.clear_updaters()
area.add_updater(update_area)
rhs = Tex(R"= \frac{1}{s}", t2c=t2c, font_size=60)
rhs.next_to(area_word, RIGHT)
self.play(LaggedStart(
FadeOut(equals_one),
FadeOut(area_one_label),
FadeOut(simple_integral),
FadeIn(integral),
FadeIn(graph_label),
FadeOut(arrow),
lag_ratio=0.1
))
self.play(
s_tracker.animate.set_value(5).set_anim_args(run_time=8),
)
area_word.save_state()
self.play(
area_word.animate.move_to(axes.c2p(0.6, 0.33)),
Write(rhs),
FadeOut(equals),
)
self.wait()
# Show decimal approximation
dec_rhs = Tex(R"= 1.00", font_size=60)
dec_rhs.make_number_changeable("1.00").add_updater(lambda m: m.set_value(1 / get_s()))
dec_rhs.always.next_to(rhs, RIGHT)
self.play(
VFadeIn(dec_rhs),
s_tracker.animate.set_value(0.5).set_anim_args(run_time=8),
)
self.wait()
self.play(
s_tracker.animate.set_value(2),
run_time=4,
)
self.wait(2)
def create_slider(self, tracker, x_range=(0, 5), height=1.5, font_size=36):
"""Create a visual slider for the s parameter."""
number_line = NumberLine(x_range, width=height, tick_size=0.05)
number_line.rotate(90 * DEG)
indicator = ArrowTip(width=0.1, length=0.2)
indicator.rotate(PI)
indicator.add_updater(lambda m: m.move_to(number_line.n2p(tracker.get_value()), LEFT))
indicator.set_color(YELLOW)
label = Tex(R"s = 0.00", font_size=font_size)
label["s"].set_color(YELLOW)
label.rhs = label.make_number_changeable("0.00")
label.always.next_to(indicator, RIGHT, SMALL_BUFF)
label.rhs.f_always.set_value(tracker.get_value)
slider = VGroup(number_line, indicator, label)
return slider
class AverageValueInterpretation(InteractiveScene):
"""
Show that unit integrals equal the average value over that interval.
Helps build intuition for the Laplace transform.
"""
def construct(self):
# Set up axes
axes = Axes(
x_range=(0, 6),
y_range=(0, 1.2),
width=10,
height=4
)
axes.to_edge(DOWN, buff=1)
# Fixed s value
s = 0.5
def exp_func(t):
return np.exp(-s * t)
# Graph
graph = axes.get_graph(exp_func)
graph.set_stroke(BLUE, 3)
self.add(axes, graph)
# Unit interval [0, 1]
v_lines = VGroup(
DashedLine(axes.c2p(0, 0), axes.c2p(0, 1.2)),
DashedLine(axes.c2p(1, 0), axes.c2p(1, 1.2)),
)
v_lines.set_stroke(WHITE, 1)
# Area under [0, 1]
area = axes.get_area_under_graph(graph, x_range=(0, 1))
# Integral label
int_tex = Tex(R"\int^1_0 e^{-st} dt", t2c={"s": YELLOW}, font_size=48)
int_tex.move_to(v_lines, UP).shift(0.5 * UP)
self.play(
ShowCreation(v_lines),
FadeIn(area),
Write(int_tex),
)
self.wait()
# Show average value interpretation
avg_value = np.mean([exp_func(t) for t in np.linspace(0, 1, 1000)])
avg_rect = Rectangle(
width=axes.x_axis.get_unit_size(),
height=avg_value * axes.y_axis.get_unit_size()
)
avg_rect.set_fill(GREEN, 0.5)
avg_rect.set_stroke(GREEN, 2)
avg_rect.move_to(axes.c2p(0.5, avg_value/2))
avg_label = Text("Average height", font_size=24)
avg_label.next_to(avg_rect, RIGHT)
self.play(
area.animate.set_fill(opacity=0.3),
FadeIn(avg_rect),
Write(avg_label),
)
self.wait()
# Explanation
explanation = Tex(
R"\text{Area} = \text{Width} \times \text{Height}_{avg}",
font_size=36
)
explanation.to_edge(UP)
self.play(Write(explanation))
self.wait(2)
class IntegralAsSum(InteractiveScene):
"""
Show the full integral as a sum of unit interval averages.
"""
def construct(self):
# Axes
axes = Axes(
x_range=(0, 8),
y_range=(0, 1.2),
width=12,
height=4
)
axes.to_edge(DOWN, buff=1)
s = 0.75
def exp_func(t):
return np.exp(-s * t)
graph = axes.get_graph(exp_func)
graph.set_stroke(BLUE, 3)
self.add(axes, graph)
# Create stacked areas for each unit interval
areas = VGroup()
colors = color_gradient([BLUE_E, TEAL_E], 6)
for n, color in enumerate(colors):
area = axes.get_area_under_graph(graph, x_range=(n, n+1))
area.set_fill(color, 0.7)
areas.add(area)
# Labels for each interval
labels = VGroup()
for n in range(6):
label = Tex(f"[{n}, {n+1}]", font_size=24)
label.move_to(areas[n])
labels.add(label)
# Animate adding areas
self.play(LaggedStartMap(FadeIn, areas, lag_ratio=0.3))
self.play(LaggedStartMap(FadeIn, labels, lag_ratio=0.2))
self.wait()
# Show total integral
total = Tex(
R"\int^\infty_0 e^{-st} dt = \sum_{n=0}^{\infty} \int_n^{n+1} e^{-st} dt",
t2c={"s": YELLOW},
font_size=36
)
total.to_edge(UP)
self.play(Write(total))
self.wait()
# Highlight that it converges
result = Tex(R"= \frac{1}{s}", t2c={"s": YELLOW}, font_size=48)
result.next_to(total, DOWN)
self.play(Write(result))
self.wait(2)
examples/light_polarization.py
"""
Light Polarization and Quantum States
=====================================
Visualizes polarized light as a 3D electromagnetic wave, showing
how polarization states map to quantum states on a 2D plane.
Key concepts demonstrated:
- TimeVaryingVectorField for oscillating wave visualization
- 3D camera control with reorient
- Prism and ParametricSurface for 3D objects
- ValueTracker for controlling wave polarization angle
"""
from manimlib import *
class PolarizedLightWave(InteractiveScene):
"""Visualizes polarized light as an electromagnetic wave."""
def construct(self):
frame = self.frame
# Set up 3D view
frame.reorient(-60, 75, 0)
frame.add_ambient_rotation(DEG)
# Create axes
axes = ThreeDAxes((-1, 10), (-1, 1), (-1, 1))
axes.set_stroke(WHITE, 1, 0.5)
self.add(axes)
# Polarization angle tracker
theta_tracker = ValueTracker(45 * DEG)
# Wave parameters
wave_number = 1.5
frequency = 0.5
amplitude = 0.5
# Create the wave as a VGroup of vectors that update over time
# Note: TimeVaryingVectorField doesn't work directly with ThreeDAxes
sample_x = np.arange(0, 8, 0.15)
def get_wave_vectors(time=0):
"""Generate wave vectors at given time."""
vectors = VGroup()
theta = theta_tracker.get_value()
for x in sample_x:
phase = wave_number * x - TAU * frequency * time
magnitude = amplitude * np.cos(phase)
y_comp = np.cos(theta) * magnitude
z_comp = np.sin(theta) * magnitude
start = axes.c2p(x, 0, 0)
end = axes.c2p(x, y_comp, z_comp)
vec = Arrow(start, end, buff=0, thickness=2)
vec.set_color(BLUE)
vec.set_stroke(opacity=0.7)
vectors.add(vec)
return vectors
wave = get_wave_vectors()
# Add an updater to animate the wave
time_tracker = ValueTracker(0)
def update_wave(w):
new_wave = get_wave_vectors(time_tracker.get_value())
w.become(new_wave)
wave.add_updater(update_wave)
# Add a beam line
beam = Line(ORIGIN, 8 * RIGHT)
beam.set_stroke(GREEN, 2)
self.add(beam)
self.play(FadeIn(wave))
# Animate the wave for a few seconds
self.play(time_tracker.animate.set_value(3), run_time=3, rate_func=linear)
wave.clear_updaters() # Stop wave animation to change polarization
# Add polarization plane indicator
plane_indicator = Square(1.5)
plane_indicator.rotate(90 * DEG, RIGHT)
plane_indicator.rotate(theta_tracker.get_value(), RIGHT)
plane_indicator.move_to(4 * RIGHT)
plane_indicator.set_fill(BLUE, 0.2)
plane_indicator.set_stroke(BLUE, 1)
def update_plane(plane):
plane.rotate(
theta_tracker.get_value() - plane.get_angle(),
axis=RIGHT,
about_point=plane.get_center()
)
self.play(FadeIn(plane_indicator))
self.wait(2)
# Change polarization angle
self.play(
theta_tracker.animate.set_value(0),
run_time=3
)
self.wait(2)
self.play(
theta_tracker.animate.set_value(90 * DEG),
run_time=3
)
self.wait(2)
self.play(
theta_tracker.animate.set_value(45 * DEG),
run_time=2
)
self.wait(3)
class PolarizationTo2DState(InteractiveScene):
"""Shows how polarization maps to a 2D state vector."""
def construct(self):
frame = self.frame
# Title
title = Text("Polarization as Quantum State", font_size=48)
title.to_edge(UP)
self.add(title)
# Left side: 3D polarization representation
axes_3d = ThreeDAxes((-1, 1), (-1, 1), (-1, 1))
axes_3d.scale(1.5)
axes_3d.shift(3 * LEFT)
# Polarization vector (in yz plane at x=0)
theta_tracker = ValueTracker(45 * DEG)
def get_pol_vector():
theta = theta_tracker.get_value()
return Arrow(
axes_3d.c2p(0, 0, 0),
axes_3d.c2p(0, np.cos(theta), np.sin(theta)),
buff=0,
thickness=5,
fill_color=BLUE
)
pol_vector = always_redraw(get_pol_vector)
# Circle showing all possible polarizations
pol_circle = Circle(radius=1.5)
pol_circle.rotate(90 * DEG, UP)
pol_circle.move_to(axes_3d.c2p(0, 0, 0))
pol_circle.set_stroke(GREY, 1, 0.5)
# Labels
h_label = Tex("H", font_size=30, color=YELLOW)
h_label.rotate(90 * DEG, RIGHT)
h_label.next_to(axes_3d.c2p(0, 1, 0), UP + OUT, SMALL_BUFF)
v_label = Tex("V", font_size=30, color=GREEN)
v_label.rotate(90 * DEG, RIGHT)
v_label.next_to(axes_3d.c2p(0, 0, 1), OUT, SMALL_BUFF)
frame.reorient(-30, 70, 0, ORIGIN, 8)
self.add(axes_3d, pol_circle, pol_vector, h_label, v_label)
# Right side: 2D qubit representation
plane = NumberPlane((-2, 2), (-2, 2), faded_line_ratio=5)
plane.set_height(4)
plane.shift(3 * RIGHT)
zero_label = Tex(R"|H\rangle", font_size=30, color=YELLOW)
zero_label.next_to(plane.c2p(1, 0), DR, SMALL_BUFF)
one_label = Tex(R"|V\rangle", font_size=30, color=GREEN)
one_label.next_to(plane.c2p(0, 1), UL, SMALL_BUFF)
def get_state_vector():
theta = theta_tracker.get_value()
return Arrow(
plane.c2p(0, 0),
plane.c2p(np.cos(theta), np.sin(theta)),
buff=0,
thickness=4,
fill_color=TEAL
)
state_vector = always_redraw(get_state_vector)
# Unit circle on 2D plane
unit_circle = Circle(radius=plane.c2p(1, 0)[0] - plane.c2p(0, 0)[0])
unit_circle.move_to(plane.c2p(0, 0))
unit_circle.set_stroke(GREY, 1, 0.5)
self.add(plane, unit_circle, state_vector, zero_label, one_label)
# Arrow connecting the two representations
connection = Tex(R"\Leftrightarrow", font_size=72)
connection.move_to(ORIGIN)
self.play(Write(connection))
self.wait()
# Animate through different polarizations
for target_angle in [0, 90 * DEG, 30 * DEG, 60 * DEG, 45 * DEG]:
self.play(theta_tracker.animate.set_value(target_angle), run_time=2)
self.wait()
self.wait(2)
class BeamSplitterSimple(InteractiveScene):
"""Simplified beam splitter demonstration."""
def construct(self):
frame = self.frame
# Set up 3D view
frame.reorient(-70, 70, 0)
# Create the beam splitter cube
splitter = Cube()
splitter.set_color(WHITE)
splitter.set_opacity(0.3)
splitter.rotate(45 * DEG)
splitter.set_height(1)
splitter.move_to(ORIGIN)
# Input beam
input_beam = Line(4 * LEFT, ORIGIN)
input_beam.set_stroke(GREEN, 3)
# Output beams
output_h = Line(ORIGIN, 4 * RIGHT)
output_h.set_stroke(YELLOW, 3)
output_v = Line(ORIGIN, 4 * UP)
output_v.set_stroke(BLUE, 3)
# Labels
input_label = Tex(R"|\psi\rangle", font_size=36)
input_label.next_to(input_beam, UP)
input_label.rotate(90 * DEG, RIGHT)
h_label = Tex(R"|H\rangle", font_size=36, color=YELLOW)
h_label.next_to(output_h.get_end(), DOWN)
h_label.rotate(90 * DEG, RIGHT)
v_label = Tex(R"|V\rangle", font_size=36, color=BLUE)
v_label.next_to(output_v.get_end(), RIGHT)
v_label.rotate(90 * DEG, RIGHT)
self.add(splitter)
self.play(ShowCreation(input_beam), FadeIn(input_label))
self.wait()
# Split the beam
self.play(
ShowCreation(output_h),
ShowCreation(output_v),
FadeIn(h_label),
FadeIn(v_label),
)
self.wait()
# Add probability labels
cos_label = Tex(R"\cos(\theta)", font_size=24, color=YELLOW)
cos_label.next_to(output_h, DOWN, SMALL_BUFF)
cos_label.rotate(90 * DEG, RIGHT)
sin_label = Tex(R"\sin(\theta)", font_size=24, color=BLUE)
sin_label.next_to(output_v, LEFT, SMALL_BUFF)
sin_label.rotate(90 * DEG, RIGHT)
self.play(
FadeIn(cos_label),
FadeIn(sin_label)
)
# Animate the camera
self.play(
frame.animate.reorient(-30, 60, 0),
run_time=4
)
self.wait(2)
class WaveVectorComponents(InteractiveScene):
"""Shows decomposition of polarization into H and V components."""
def construct(self):
# 2D plane view
plane = NumberPlane((-2, 2), (-2, 2), faded_line_ratio=5)
plane.set_height(6)
# Labels
h_label = Tex(R"|H\rangle", color=YELLOW)
h_label.next_to(plane.c2p(1.2, 0), DR, SMALL_BUFF)
v_label = Tex(R"|V\rangle", color=BLUE)
v_label.next_to(plane.c2p(0, 1.2), UL, SMALL_BUFF)
self.add(plane, h_label, v_label)
# Main polarization vector
theta = 50 * DEG
main_vec = Arrow(
plane.c2p(0, 0),
plane.c2p(np.cos(theta), np.sin(theta)),
buff=0,
thickness=5,
fill_color=TEAL
)
# Component vectors
h_component = Arrow(
plane.c2p(0, 0),
plane.c2p(np.cos(theta), 0),
buff=0,
thickness=3,
fill_color=YELLOW
)
v_component = Arrow(
plane.c2p(np.cos(theta), 0),
plane.c2p(np.cos(theta), np.sin(theta)),
buff=0,
thickness=3,
fill_color=BLUE
)
# Dashed lines for projection
h_dashed = DashedLine(
plane.c2p(np.cos(theta), np.sin(theta)),
plane.c2p(np.cos(theta), 0)
)
h_dashed.set_stroke(YELLOW, 1)
v_dashed = DashedLine(
plane.c2p(np.cos(theta), np.sin(theta)),
plane.c2p(0, np.sin(theta))
)
v_dashed.set_stroke(BLUE, 1)
self.play(GrowArrow(main_vec))
self.wait()
# Show decomposition
self.play(
ShowCreation(h_dashed),
ShowCreation(v_dashed),
)
self.play(
GrowArrow(h_component),
GrowArrow(v_component),
)
# Equation
equation = Tex(
R"|\psi\rangle = \cos(\theta)|H\rangle + \sin(\theta)|V\rangle",
font_size=36
)
equation.to_edge(DOWN, buff=1.0)
self.play(Write(equation))
self.wait()
# Show angle
arc = Arc(0, theta, radius=0.5)
arc.move_to(plane.c2p(0, 0), LEFT + DOWN)
arc.set_stroke(WHITE, 2)
theta_label = Tex(R"\theta", font_size=36)
theta_label.next_to(arc.pfp(0.5), RIGHT, SMALL_BUFF)
self.play(
ShowCreation(arc),
Write(theta_label)
)
self.wait(2)
if __name__ == "__main__":
# To run: manimgl light_polarization.py PolarizedLightWave
pass
examples/linear_regression.py
"""
Linear Regression visualization showing data points and a fitted line.
Demonstrates: Axes, DotCloud, Line, ValueTracker, updaters
"""
from manimlib import *
import numpy as np
import random
class LinearRegression(Scene):
def construct(self):
# Set up axes
x_min, x_max = (-1, 12)
y_min, y_max = (-1, 10)
axes = Axes((x_min, x_max), (y_min, y_max), width=12, height=6)
axes.to_edge(DOWN)
self.add(axes)
# Add data points
n_data_points = 30
m = 0.75 # slope
y0 = 1 # y-intercept
np.random.seed(42)
data = np.array([
(x, y0 + m * x + 0.75 * np.random.normal(0, 1))
for x in np.random.uniform(2, x_max, n_data_points)
])
points = axes.c2p(data[:, 0], data[:, 1])
dots = DotCloud(points)
dots.set_color(YELLOW)
dots.set_glow_factor(1)
dots.set_radius(0.075)
self.add(dots)
# Title
title = Text("Linear Regression", font_size=72)
title.to_edge(UP)
# Create line with trackers for slope and y-intercept
m_tracker = ValueTracker(m)
y0_tracker = ValueTracker(y0)
line = Line()
line.set_stroke(TEAL, 2)
def update_line(line):
curr_y0 = y0_tracker.get_value()
curr_m = m_tracker.get_value()
line.put_start_and_end_on(
axes.c2p(0, curr_y0),
axes.c2p(x_max, curr_y0 + curr_m * x_max),
)
line.add_updater(update_line)
self.play(
FadeIn(title, UP),
ShowCreation(line),
)
self.wait()
# Label inputs and outputs
in_label = Text("Input")
in_label.next_to(axes.x_axis, DOWN, buff=0.1, aligned_edge=RIGHT)
out_label = Text("Output")
out_label.rotate(90 * DEGREES)
out_label.next_to(axes.y_axis, LEFT, aligned_edge=UP)
self.play(LaggedStart(
FadeIn(in_label, lag_ratio=0.1),
FadeIn(out_label, lag_ratio=0.1),
lag_ratio=0.5,
))
self.wait()
# Emphasize line
self.play(
VShowPassingFlash(
line.copy().set_stroke(BLUE, 8).scale(1.1).insert_n_curves(100),
time_width=1.5,
run_time=2
),
)
self.wait()
# Show parameter labels
m_label = VGroup(
Text("slope = "),
DecimalNumber(m_tracker.get_value()),
)
m_label.arrange(RIGHT)
m_label[1].f_always.set_value(m_tracker.get_value)
y0_label = VGroup(
Text("y-intercept = "),
DecimalNumber(y0_tracker.get_value()),
)
y0_label.arrange(RIGHT)
y0_label[1].f_always.set_value(y0_tracker.get_value)
labels = VGroup(m_label, y0_label)
labels.arrange(DOWN, aligned_edge=LEFT)
labels.next_to(axes.y_axis, RIGHT, buff=1.0)
labels.to_edge(UP)
self.play(
FadeOut(title, UP),
FadeIn(m_label, UP),
)
self.play(
m_tracker.animate.set_value(1.5),
run_time=2,
)
self.play(FadeIn(y0_label, UP))
self.play(
y0_tracker.animate.set_value(-2),
run_time=2
)
self.wait()
# Tweak line parameters to show fitting
for n in range(6):
alpha = random.random()
if alpha > 0.5:
alpha += 1
new_m = interpolate(m_tracker.get_value(), m, alpha)
new_y0 = interpolate(y0_tracker.get_value(), y0, alpha)
self.play(LaggedStart(
m_tracker.animate.set_value(new_m),
y0_tracker.animate.set_value(new_y0),
run_time=1.5,
lag_ratio=0.25,
))
self.wait(0.5)
examples/llm_prediction_pipeline.py
"""
LLM Prediction Pipeline Visualization
Demonstrates the complete flow of an LLM making predictions:
input context -> model processing -> probability distribution -> sampled output.
Run with: manimgl llm_prediction_pipeline.py LLMPredictionPipeline
"""
from manimlib import *
import numpy as np
def get_paragraph(words, line_len=40, font_size=48):
"""Handle word wrapping for text display."""
words = list(map(str.strip, words))
word_lens = list(map(len, words))
lines = []
lh, rh = 0, 0
while rh < len(words):
rh += 1
if sum(word_lens[lh:rh]) > line_len:
rh -= 1
lines.append(words[lh:rh])
lh = rh
lines.append(words[lh:])
text = "\n".join([" ".join(line).strip() for line in lines])
return Text(text, alignment="LEFT", font_size=font_size)
class LLMPredictionPipeline(InteractiveScene):
"""
Full visualization of the LLM prediction pipeline:
1. Input text is shown
2. Text flows into the model
3. Model processes (blocks light up)
4. Distribution appears
5. Token is sampled and added to text
"""
def construct(self):
# Initial setup
seed_text = "Michael Jordan plays the sport of"
# Create input text
input_text = get_paragraph(seed_text.split(), line_len=30, font_size=32)
input_text.to_edge(UP, buff=0.8)
input_text.set_color(BLUE_B)
# Create model visualization
model = self.create_llm_model()
model.set_height(3.0)
model.center()
model.shift(0.5 * DOWN)
# Create prediction data
predictions = [" basketball", " baseball", " golf", " tennis", " football"]
probs = np.array([0.65, 0.15, 0.08, 0.07, 0.05])
# Distribution visualization
bar_groups = self.build_distribution(predictions, probs)
bar_groups.to_edge(RIGHT, buff=0.5)
bar_groups.align_to(model, UP)
# Input arrow
in_arrow = Arrow(
input_text.get_bottom() + 0.2 * DOWN,
model.get_top() + 0.2 * UP,
buff=0
)
in_arrow.set_color(BLUE)
# Output arrow
out_arrow = Arrow(
model.get_right() + 0.2 * RIGHT,
bar_groups.get_left() + 0.2 * LEFT,
buff=0
)
out_arrow.set_color(TEAL)
# Step 1: Show input
step1 = Text("1. Input Context", font_size=24, color=YELLOW)
step1.to_corner(UL)
self.play(Write(step1))
self.play(Write(input_text))
self.wait(0.5)
# Step 2: Feed to model
step2 = Text("2. Feed to Model", font_size=24, color=YELLOW)
step2.next_to(step1, DOWN, aligned_edge=LEFT)
self.play(Write(step2))
self.play(FadeIn(model))
self.play(GrowArrow(in_arrow))
# Animate text flowing into model
text_copy = input_text.copy()
self.play(
text_copy.animate.scale(0.3).move_to(model.get_top()),
rate_func=rush_into,
run_time=0.8
)
self.play(FadeOut(text_copy, shift=DOWN, scale=0.5))
# Step 3: Model processes
step3 = Text("3. Process", font_size=24, color=YELLOW)
step3.next_to(step2, DOWN, aligned_edge=LEFT)
self.play(Write(step3))
self.play(self.animate_model_processing(model))
self.wait(0.3)
# Step 4: Output distribution
step4 = Text("4. Output Distribution", font_size=24, color=YELLOW)
step4.next_to(step3, DOWN, aligned_edge=LEFT)
self.play(Write(step4))
self.play(GrowArrow(out_arrow))
self.play(
LaggedStart(
*(FadeIn(bg, shift=LEFT) for bg in bar_groups),
lag_ratio=0.08
)
)
self.wait(0.5)
# Step 5: Sample and add
step5 = Text("5. Sample Token", font_size=24, color=YELLOW)
step5.next_to(step4, DOWN, aligned_edge=LEFT)
# Highlight top prediction
highlight = SurroundingRectangle(bar_groups[0], buff=0.05)
highlight.set_stroke(GREEN, 3)
highlight.set_fill(GREEN, 0.2)
self.play(Write(step5))
self.play(ShowCreation(highlight))
# Add word to text
new_word = Text(" basketball", font_size=32)
new_word.set_color(GREEN)
new_word.next_to(input_text, RIGHT, buff=0.1)
self.play(
FadeIn(new_word, shift=LEFT, scale=1.2),
)
self.wait(2)
def create_llm_model(self):
"""Create a visual representation of the LLM."""
# Stack of blocks
blocks = VGroup()
for i in range(6):
block = Rectangle(3.5, 0.35)
block.set_fill(GREY_D, 0.9)
block.set_stroke(WHITE, 1)
blocks.add(block)
blocks.arrange(DOWN, buff=0.08)
# Label
label = Text("Large Language Model", font_size=24)
label.next_to(blocks, UP, buff=0.2)
# Dials/parameters hint
dots = VGroup()
for block in blocks[:3]:
row_dots = VGroup(*(
Dot(radius=0.03).set_fill(random_bright_color(), 0.7)
for _ in range(8)
))
row_dots.arrange(RIGHT, buff=0.15)
row_dots.move_to(block)
dots.add(row_dots)
return VGroup(blocks, label, dots)
def animate_model_processing(self, model):
"""Animate the model blocks lighting up."""
blocks = model[0]
return LaggedStart(
*(
block.animate.set_fill(TEAL, 0.8).set_anim_args(
rate_func=there_and_back
)
for block in blocks
),
lag_ratio=0.15,
run_time=1.2
)
def build_distribution(self, words, probs, font_size=22, width_100p=2.0, bar_height=0.25):
"""Build probability distribution bars."""
bar_groups = VGroup()
for word, prob in zip(words, probs):
label = Text(word, font_size=font_size)
bar = Rectangle(prob * width_100p, bar_height)
bar.set_fill(interpolate_color(BLUE_E, TEAL, prob / max(probs)), opacity=0.9)
bar.set_stroke(WHITE, 1)
prob_label = Integer(int(100 * prob), unit="%", font_size=font_size * 0.8)
prob_label.next_to(bar, RIGHT, buff=SMALL_BUFF)
label.next_to(bar, LEFT)
bar_groups.add(VGroup(label, bar, prob_label))
bar_groups.arrange(DOWN, aligned_edge=LEFT, buff=0.2)
return bar_groups
class IterativeGeneration(InteractiveScene):
"""
Shows multiple iterations of token generation,
demonstrating the autoregressive nature of LLMs.
"""
def construct(self):
# Starting text
tokens = ["The", " sun", " rises"]
next_tokens = [" in", " the", " east"]
# Display area
text_display = VGroup()
for token in tokens:
t = Text(token, font_size=36)
t.set_color(BLUE_B)
text_display.add(t)
text_display.arrange(RIGHT, buff=0.05)
text_display.to_edge(UP, buff=1.5)
# Model box (simplified)
model_box = Rectangle(2.5, 1.5)
model_box.set_fill(GREY_D, 0.8)
model_box.set_stroke(WHITE, 2)
model_label = Text("LLM", font_size=28)
model_label.move_to(model_box)
model = VGroup(model_box, model_label)
model.center()
self.play(FadeIn(text_display, lag_ratio=0.2))
self.play(FadeIn(model))
self.wait(0.5)
# Generate tokens one by one
for i, next_token in enumerate(next_tokens):
# Arrow from text to model
in_arrow = Arrow(
text_display.get_bottom(),
model.get_top(),
buff=0.2
)
in_arrow.set_color(BLUE)
# Show input flowing
self.play(GrowArrow(in_arrow), run_time=0.4)
# Model processes
self.play(
model_box.animate.set_fill(TEAL, 0.5).set_anim_args(
rate_func=there_and_back
),
run_time=0.5
)
# New token emerges
new_token = Text(next_token, font_size=36)
new_token.set_color(GREEN)
new_token.next_to(text_display, RIGHT, buff=0.05)
out_arrow = Arrow(
model.get_top(),
new_token.get_bottom(),
buff=0.2,
path_arc=-60 * DEGREES
)
out_arrow.set_color(GREEN)
self.play(
GrowArrow(out_arrow),
FadeIn(new_token, scale=1.3),
run_time=0.6
)
# Add to display and clean up
text_display.add(new_token)
new_token.set_color(BLUE_B)
self.play(
FadeOut(in_arrow),
FadeOut(out_arrow),
run_time=0.3
)
# Final result
self.wait()
final_text = VGroup(*text_display).copy()
final_text.generate_target()
final_text.target.center()
final_text.target.shift(UP)
final_text.target.scale(1.2)
self.play(
FadeOut(model),
MoveToTarget(final_text)
)
result_label = Text("Generated Text", font_size=28)
result_label.next_to(final_text, DOWN, buff=0.5)
self.play(Write(result_label))
self.wait(2)
examples/lorenz_attractor.py
"""
Lorenz Attractor Visualization
Demonstrates the classic Lorenz strange attractor with multiple trajectories
showing sensitivity to initial conditions (chaos theory).
Run: manimgl lorenz_attractor.py LorenzAttractor
"""
from manimlib import *
from scipy.integrate import solve_ivp
def lorenz_system(t, state, sigma=10, rho=28, beta=8 / 3):
"""
The Lorenz system of differential equations.
These equations model atmospheric convection and exhibit
chaotic behavior for certain parameter values.
"""
x, y, z = state
dxdt = sigma * (y - x)
dydt = x * (rho - z) - y
dzdt = x * y - beta * z
return [dxdt, dydt, dzdt]
def ode_solution_points(function, state0, time, dt=0.01):
"""
Solve an ODE system and return the trajectory points.
Args:
function: The ODE system function
state0: Initial state [x0, y0, z0]
time: Total evolution time
dt: Time step for output points
Returns:
Array of shape (n_points, 3) with trajectory points
"""
solution = solve_ivp(
function,
t_span=(0, time),
y0=state0,
t_eval=np.arange(0, time, dt)
)
return solution.y.T
class LorenzAttractor(InteractiveScene):
"""
Visualizes the Lorenz attractor with multiple trajectories.
Shows how nearby initial conditions diverge over time,
demonstrating the butterfly effect in chaotic systems.
"""
def construct(self):
# Set up 3D axes
axes = ThreeDAxes(
x_range=(-50, 50, 5),
y_range=(-50, 50, 5),
z_range=(-0, 50, 5),
width=16,
height=16,
depth=8,
)
axes.set_width(FRAME_WIDTH)
axes.center()
# Set up camera rotation for 3D viewing
self.frame.reorient(43, 76, 1, IN, 10)
self.frame.add_updater(lambda m, dt: m.increment_theta(dt * 3 * DEGREES))
self.add(axes)
# Add the Lorenz equations
equations = Tex(
R"""
\begin{aligned}
\frac{\mathrm{d} x}{\mathrm{~d} t} & =\sigma(y-x) \\
\frac{\mathrm{d} y}{\mathrm{~d} t} & =x(\rho-z)-y \\
\frac{\mathrm{d} z}{\mathrm{~d} t} & =x y-\beta z
\end{aligned}
""",
t2c={
"x": RED,
"y": GREEN,
"z": BLUE,
},
font_size=30
)
equations.fix_in_frame()
equations.to_corner(UL)
equations.set_backstroke()
self.play(Write(equations))
# Compute trajectories with slightly different initial conditions
epsilon = 1e-5
evolution_time = 30
n_points = 10
states = [
[10, 10, 10 + n * epsilon]
for n in range(n_points)
]
colors = color_gradient([BLUE_E, BLUE_A], len(states))
# Create curves from solutions
curves = VGroup()
for state, color in zip(states, colors):
points = ode_solution_points(lorenz_system, state, evolution_time)
curve = VMobject().set_points_smoothly(axes.c2p(*points.T))
curve.set_stroke(color, 1, opacity=0.25)
curves.add(curve)
curves.set_stroke(width=2, opacity=1)
# Create glowing dots that follow the trajectories
dots = Group(GlowDot(color=color, radius=0.25) for color in colors)
def update_dots(dots, curves=curves):
for dot, curve in zip(dots, curves):
dot.move_to(curve.get_end())
dots.add_updater(update_dots)
# Add tracing tails for visual effect
tail = VGroup(
TracingTail(dot, time_traced=3).match_color(dot)
for dot in dots
)
self.add(dots)
self.add(tail)
curves.set_opacity(0)
# Animate the trajectories
self.play(
*(
ShowCreation(curve, rate_func=linear)
for curve in curves
),
run_time=evolution_time,
)
class LorenzSimple(Scene):
"""
A simpler version of the Lorenz attractor without equations overlay.
Good for demonstrations focused on the attractor itself.
"""
def construct(self):
frame = self.camera.frame
# Set up 3D axes
axes = ThreeDAxes(
x_range=(-50, 50, 10),
y_range=(-50, 50, 10),
z_range=(0, 50, 10),
width=12,
height=12,
depth=6,
)
axes.center()
self.add(axes)
# Set camera angle
frame.set_euler_angles(
phi=70 * DEGREES,
theta=-45 * DEGREES
)
# Add continuous rotation
frame.add_updater(lambda m, dt: m.increment_theta(dt * 2 * DEGREES))
# Compute single trajectory
evolution_time = 40
initial_state = [10, 10, 10]
points = ode_solution_points(lorenz_system, initial_state, evolution_time)
# Create the curve
curve = VMobject()
curve.set_points_smoothly(axes.c2p(*points.T))
curve.set_stroke(
color=color_gradient([BLUE, TEAL, GREEN, YELLOW, RED], 100),
width=2
)
# Animate drawing the curve
self.play(
ShowCreation(curve, rate_func=linear),
run_time=evolution_time,
)
self.wait(2)
examples/lozenge_tiling.py
"""
Visualization of lozenge (rhombus) tiling patterns.
Shows how lozenges can tile the plane in a honeycomb-like pattern.
"""
from manimlib import *
import math
def get_lozenge(side_length=1):
"""Create a lozenge (rhombus) shape with 60/120 degree angles."""
verts = [math.sqrt(3) * LEFT, UP, math.sqrt(3) * RIGHT, DOWN]
result = Polygon(*verts)
result.scale(side_length / get_norm(verts[0] - verts[1]))
return result
class LozengeTiling(InteractiveScene):
"""
Demonstrates lozenge tiling of the plane.
Shows:
1. A single lozenge with angle labels
2. How it tiles to create a row
3. How rows tile to fill the plane
4. The effect of stretching on the tiling
"""
def construct(self):
# Add Lozenge
lozenge = get_lozenge()
lozenge.scale(4)
lozenge.set_stroke(TEAL)
arc1 = Arc(-30 * DEGREES, 60 * DEGREES, arc_center=lozenge.get_left(), radius=0.75)
arc2 = Arc(-150 * DEGREES, 120 * DEGREES, arc_center=lozenge.get_top(), radius=0.5)
arc1_label = Tex(R"60^\circ")
arc1_label.next_to(arc1, RIGHT, MED_SMALL_BUFF)
arc2_label = Tex(R"120^\circ")
arc2_label.next_to(arc2, DOWN, MED_SMALL_BUFF)
angle_labels = VGroup(
arc1, arc1_label,
arc2, arc2_label,
)
angle_labels.set_z_index(1)
self.play(
ShowCreation(lozenge, time_span=(1, 2.5)),
VShowPassingFlash(lozenge.copy().insert_n_curves(20).set_stroke(width=5), time_width=2),
run_time=3
)
self.play(
Write(arc1_label),
ShowCreation(arc1),
)
self.play(
Write(arc2_label),
ShowCreation(arc2),
)
self.add(angle_labels)
self.wait()
# Tile the plane
verts = lozenge.get_anchors()[:4]
v1 = verts[1] - verts[0]
v2 = verts[-1] - verts[0]
row = VGroup(lozenge.copy().shift(x * v1) for x in range(-10, 11))
rows = VGroup(row.copy().shift(y * v2) for y in range(-10, 11))
tiles = VGroup(*rows.family_members_with_points())
tiles.sort(lambda p: get_norm(p))
for mob in row, rows:
mob.set_fill(GREY, 1)
mob.set_stroke(WHITE, 2)
mob.shift(-tiles[0].get_center())
self.play(
self.frame.animate.set_height(40),
lozenge.animate.set_fill(GREY, 1),
LaggedStart(
(TransformFromCopy(lozenge, tile, path_arc=30 * DEGREES) for tile in row),
lag_ratio=1.0 / len(row),
time_span=(1, 3),
),
run_time=4
)
self.play(
LaggedStart(
(TransformFromCopy(row, row2, path_arc=30 * DEGREES) for row2 in rows),
lag_ratio=1.0 / len(rows),
run_time=3,
),
)
self.clear()
self.add(rows, angle_labels)
# Squish it
self.play(FadeOut(angle_labels))
rows.save_state()
self.play(rows.animate.stretch(2, 0), run_time=2)
self.wait()
self.play(Restore(rows), run_time=2)
self.play(Write(angle_labels))
self.wait()
examples/max_random_process.py
"""
Visualization of max(rand(), rand()) process with animated tracking dots.
Shows how the maximum of two random uniform values behaves over time.
"""
from manimlib import *
import random
class Randomize(Animation):
"""Animation that randomizes a ValueTracker's value at a given frequency."""
def __init__(self, value_tracker, frequency=8, rand_func=random.random, final_value=None, **kwargs):
self.value_tracker = value_tracker
self.rand_func = rand_func
self.frequency = frequency
self.final_value = final_value if final_value is not None else rand_func()
self.last_alpha = 0
self.running_tally = 0
super().__init__(value_tracker, **kwargs)
def interpolate_mobject(self, alpha):
if not self.new_step(alpha):
return
value = self.rand_func() if alpha < 1 else self.final_value
self.value_tracker.set_value(value)
def new_step(self, alpha):
d_alpha = alpha - self.last_alpha
self.last_alpha = alpha
self.running_tally += self.frequency * d_alpha * self.run_time
if self.running_tally > 1:
self.running_tally = self.running_tally % 1
return True
return False
class TrackingDots(Animation):
"""Animation that leaves a trail of fading dots at specified positions."""
def __init__(self, point_func, fade_factor=0.95, radius=0.25, color=YELLOW, **kwargs):
self.point_func = point_func
self.fade_factor = fade_factor
self.dots = GlowDot(point_func(), color=color, radius=radius)
kwargs.update(remover=True)
super().__init__(self.dots, **kwargs)
def interpolate_mobject(self, alpha):
opacities = self.dots.get_opacities()
point = self.point_func()
if not np.isclose(self.dots.get_end(), point).all():
self.dots.add_point(point)
opacities = np.hstack([opacities, [1]])
opacities *= self.fade_factor
self.dots.set_opacity(opacities)
def get_random_var_label_group(axis, label_name, color=GREY, initial_value=None, font_size=36, direction=None):
"""Create a group with a tracker, arrow tip indicator, and label for a random variable on an axis."""
if initial_value is None:
initial_value = random.uniform(*axis.x_range[:2])
tracker = ValueTracker(initial_value)
tip = ArrowTip(angle=90 * DEGREES)
tip.set_height(0.15)
tip.set_fill(color)
tip.rotate(-axis.get_angle())
if direction is None:
direction = np.round(rotate_vector(UP, -axis.get_angle()), 1)
tip.add_updater(lambda m: m.move_to(axis.n2p(tracker.get_value()), direction))
label = Tex(label_name, font_size=font_size)
label.set_color(color)
label.set_backstroke(BLACK, 5)
label.always.next_to(tip, -direction, buff=0.1)
return Group(tracker, tip, label)
class MaxRandomProcess(InteractiveScene):
"""
Visualizes the max(rand(), rand()) process.
Shows three intervals:
- x1 = rand() (blue)
- x2 = rand() (yellow)
- max(x1, x2) (green)
Animated tracking dots show the distribution of values over time.
"""
def construct(self):
# Set up intervals
intervals = VGroup(UnitInterval() for _ in range(3))
intervals.set_width(3)
intervals.arrange(DOWN, buff=2.5)
intervals.shift(2 * LEFT)
intervals[1].shift(0.5 * UP)
for interval in intervals:
interval.add_numbers(np.arange(0, 1.1, 0.2), font_size=16, buff=0.1, direction=UP)
interval.numbers.set_opacity(0.75)
colors = [BLUE, YELLOW, GREEN]
x1_group, x2_group, max_group = groups = Group(
get_random_var_label_group(interval, "", color=color)
for interval, color in zip(intervals, colors)
)
x1_tracker, x1_tip, x1_label = x1_group
x2_tracker, x2_tip, x2_label = x2_group
max_tracker, max_tip, max_label = max_group
max_tracker.add_updater(lambda m: m.set_value(max(x1_tracker.get_value(), x2_tracker.get_value())))
self.add(intervals)
self.add(groups)
# Add labels
tex_to_color = {"x_1": BLUE, "x_2": YELLOW}
labels = VGroup(
Tex(tex + R"\rightarrow 0.00", t2c=tex_to_color)
for tex in [
R"x_1 = \text{rand}()",
R"x_2 = \text{rand}()",
R"\max(x_1, x_2)",
]
)
for label, group, interval in zip(labels, groups, intervals):
label.next_to(interval, RIGHT, buff=0.5)
num = label.make_number_changeable("0.00")
num.tracker = group[0]
num.add_updater(lambda m: m.set_value(m.tracker.get_value()))
self.add(labels)
# Add rectangles
top_rect = SurroundingRectangle(intervals[:2], buff=0.25)
top_rect.stretch(1.1, 1)
top_rect.set_stroke(WHITE, 2)
top_rect.set_fill(GREY_E, 1)
arrow = Vector(1.5 * DOWN, thickness=5)
arrow.next_to(top_rect, DOWN)
arrow_label = Text("max", font_size=60)
arrow_label.next_to(arrow, RIGHT)
self.add(top_rect, intervals, groups)
self.add(arrow, arrow_label)
# Line connecting max to its source
def get_line():
x1 = x1_tracker.get_value()
x2 = x2_tracker.get_value()
tip = x1_tip if x1 > x2 else x2_tip
line = DashedLine(max_tip.get_top(), tip.get_top())
line.set_stroke(GREY, 2, opacity=0.5)
return line
line = always_redraw(get_line)
self.add(line)
# Animate the random process
self.play(
Randomize(x1_tracker, frequency=4, run_time=15),
Randomize(x2_tracker, frequency=4, run_time=15),
TrackingDots(x1_tip.get_top, color=BLUE),
TrackingDots(x2_tip.get_top, color=YELLOW),
TrackingDots(max_tip.get_top, color=GREEN),
)
examples/mlp_forward_pass.py
"""
MLP Forward Pass Visualization
Shows data flowing through Linear -> ReLU -> Linear operations.
"""
from manimlib import *
import numpy as np
def value_to_color(value, max_value=10.0):
"""Maps a value to blue (positive) or red (negative)."""
alpha = clip(abs(value) / max_value, 0, 1)
if value >= 0:
return interpolate_color_by_hsl(BLUE_E, BLUE_B, alpha)
else:
return interpolate_color_by_hsl(RED_E, RED_B, alpha)
class MLPForwardPass(InteractiveScene):
"""
Shows the three-step MLP forward pass:
1. Linear transformation (matrix multiply + bias)
2. ReLU activation
3. Linear transformation (matrix multiply + bias)
Demonstrates: Sequential animations, data transformation visualization
"""
def construct(self):
# Title
title = Text("MLP Forward Pass", font_size=48)
title.to_edge(UP)
self.play(Write(title))
# Create the three arrows showing the pipeline
arrows = VGroup(
Arrow(ORIGIN, 1.8 * RIGHT) for _ in range(3)
)
arrows.arrange(RIGHT, buff=0.8)
arrows.move_to(ORIGIN)
# Labels for each stage
labels = VGroup(
Text("Linear", font_size=28),
Text("ReLU", font_size=28),
Text("Linear", font_size=28),
)
for label, arrow in zip(labels, arrows):
label.next_to(arrow, UP, buff=0.1)
# Position for vectors at each stage
# Input vector
input_values = np.array([1.5, -0.8, 2.1, -1.4, 0.6])
input_vect = self.create_vector(input_values, YELLOW)
input_vect.next_to(arrows[0], LEFT, buff=0.5)
# After first linear (expanded to 8 neurons)
mid1_values = np.array([2.3, -1.5, 0.8, -2.1, 1.9, -0.3, 0.1, -1.8])
mid1_vect = self.create_vector(mid1_values, None) # Will color by value
# After ReLU (negative values zeroed)
relu_values = np.maximum(mid1_values, 0)
relu_vect = self.create_vector(relu_values, None, zero_color=GREY)
# After second linear (back to 5 output neurons)
output_values = np.array([1.2, 0.5, -0.3, 1.8, 0.9])
output_vect = self.create_vector(output_values, GREEN)
# Position intermediate vectors
vects = [mid1_vect, relu_vect, output_vect]
positions = [
arrows[0].get_right() + 0.5 * RIGHT,
arrows[1].get_right() + 0.5 * RIGHT,
arrows[2].get_right() + 0.5 * RIGHT,
]
for vect, pos in zip(vects, positions):
vect.move_to(pos)
# Show input
self.play(FadeIn(input_vect, shift=LEFT))
self.wait()
# Show first linear arrow
self.play(
GrowArrow(arrows[0]),
FadeIn(labels[0])
)
# Animate transformation to mid1
self.play(
TransformFromCopy(input_vect, mid1_vect, run_time=1.5)
)
self.wait()
# Show ReLU arrow
self.play(
GrowArrow(arrows[1]),
FadeIn(labels[1])
)
# Show negative values being zeroed
neg_highlights = VGroup()
for i, val in enumerate(mid1_values):
if val < 0:
rect = SurroundingRectangle(mid1_vect[i], buff=0.05)
rect.set_stroke(RED, 2)
neg_highlights.add(rect)
self.play(ShowCreation(neg_highlights, lag_ratio=0.2))
self.wait(0.5)
# Transform to ReLU output
self.play(
TransformFromCopy(mid1_vect, relu_vect),
FadeOut(neg_highlights),
run_time=1.5
)
self.wait()
# Show second linear arrow
self.play(
GrowArrow(arrows[2]),
FadeIn(labels[2])
)
# Final transformation
self.play(
TransformFromCopy(relu_vect, output_vect, run_time=1.5)
)
self.wait(2)
def create_vector(self, values, color=None, zero_color=GREY):
"""Creates a vertical vector display with colored entries."""
entries = VGroup()
for val in values:
entry = DecimalNumber(
val,
num_decimal_places=1,
include_sign=True,
font_size=24
)
if color is not None:
entry.set_color(color)
elif val == 0:
entry.set_color(zero_color)
else:
entry.set_color(value_to_color(val, max_value=3))
entries.add(entry)
entries.arrange(DOWN, buff=0.15)
# Add brackets
left_b = Tex("[").stretch_to_fit_height(entries.get_height() * 1.1)
right_b = Tex("]").stretch_to_fit_height(entries.get_height() * 1.1)
left_b.next_to(entries, LEFT, buff=0.05)
right_b.next_to(entries, RIGHT, buff=0.05)
return VGroup(*entries, left_b, right_b)
class MLPBlockDiagram(InteractiveScene):
"""
Shows a high-level block diagram of an MLP.
Input -> [Up Projection] -> [Nonlinearity] -> [Down Projection] -> Output
Demonstrates: Block diagram style, text labels, arrows
"""
def construct(self):
# Title
title = Text("MLP Block Structure", font_size=48)
title.to_edge(UP)
# Create blocks
def create_block(text, color, width=2.5, height=1.5):
rect = Rectangle(width=width, height=height)
rect.set_fill(color, 0.3)
rect.set_stroke(color, 2)
label = Text(text, font_size=24)
label.move_to(rect)
return VGroup(rect, label)
up_proj = create_block("Up\nProjection", BLUE)
nonlin = create_block("ReLU", YELLOW, width=1.5)
down_proj = create_block("Down\nProjection", GREEN)
# Arrange blocks
blocks = VGroup(up_proj, nonlin, down_proj)
blocks.arrange(RIGHT, buff=1.0)
# Arrows between blocks
arrow1 = Arrow(up_proj.get_right(), nonlin.get_left(), buff=0.1)
arrow2 = Arrow(nonlin.get_right(), down_proj.get_left(), buff=0.1)
# Input/Output arrows
input_arrow = Arrow(up_proj.get_left() + LEFT, up_proj.get_left(), buff=0.1)
output_arrow = Arrow(down_proj.get_right(), down_proj.get_right() + RIGHT, buff=0.1)
# Input/Output labels
input_label = Tex(R"\vec{E}", font_size=36)
input_label.next_to(input_arrow, LEFT)
output_label = Tex(R"\vec{E}'", font_size=36)
output_label.next_to(output_arrow, RIGHT)
# Dimension labels
dim_in = Text("d", font_size=20, color=GREY)
dim_mid = Text("4d", font_size=20, color=GREY)
dim_out = Text("d", font_size=20, color=GREY)
dim_in.next_to(input_arrow, DOWN, buff=0.1)
dim_mid.next_to(arrow1, DOWN, buff=0.1)
dim_out.next_to(output_arrow, DOWN, buff=0.1)
# Build the scene
self.play(Write(title))
self.play(
FadeIn(input_label),
GrowArrow(input_arrow)
)
self.play(FadeIn(up_proj, shift=RIGHT))
self.play(
GrowArrow(arrow1),
FadeIn(dim_in)
)
self.play(FadeIn(nonlin, shift=RIGHT))
self.play(
GrowArrow(arrow2),
FadeIn(dim_mid)
)
self.play(FadeIn(down_proj, shift=RIGHT))
self.play(
GrowArrow(output_arrow),
FadeIn(output_label),
FadeIn(dim_out)
)
self.wait()
# Show data flow animation
data_dot = Dot(radius=0.1, color=ORANGE)
data_dot.move_to(input_arrow.get_start())
path = VMobject()
path.set_points_as_corners([
input_arrow.get_start(),
input_arrow.get_end(),
up_proj.get_center(),
arrow1.get_start(),
arrow1.get_end(),
nonlin.get_center(),
arrow2.get_start(),
arrow2.get_end(),
down_proj.get_center(),
output_arrow.get_start(),
output_arrow.get_end(),
])
self.play(
MoveAlongPath(data_dot, path, run_time=4),
)
self.wait()
# Highlight the residual connection concept
residual_label = Text(
"Output = Input + MLP(Input)",
font_size=28
)
residual_label.next_to(blocks, DOWN, buff=1.0)
plus_sign = Tex("+", font_size=48)
plus_sign.next_to(down_proj, RIGHT, buff=0.5)
skip_arrow = CurvedArrow(
input_arrow.get_end() + 0.2 * UP,
plus_sign.get_left() + 0.1 * LEFT,
angle=-TAU/4
)
skip_arrow.set_color(PINK)
self.play(
FadeIn(residual_label),
ShowCreation(skip_arrow),
Write(plus_sign)
)
self.wait(2)
examples/mlp_network_icon.py
"""
MLP Network Icon - A simple visualization of a multilayer perceptron structure
Shows dots arranged in layers with connecting lines between neurons.
"""
from manimlib import *
import random
class MLPNetworkIcon(InteractiveScene):
"""
Creates a classic MLP icon with three layers:
- Input layer
- Hidden layer (wider)
- Output layer
Demonstrates: VGroup organization, Line connections, random styling
"""
def construct(self):
# Create the MLP icon
network = self.get_mlp_icon(layer_buff=2.5, layer0_size=5)
# Animate the network appearing
self.play(Write(network, stroke_width=0.5, lag_ratio=1e-2, run_time=3))
self.wait()
# Show data propagating through the network
lines = VGroup(network[1].family_members_with_points()).copy()
for line in lines:
line.set_stroke(width=2 * line.get_width())
line.insert_n_curves(20)
self.play(
LaggedStartMap(
VShowPassingFlash,
lines,
time_width=1.5,
lag_ratio=5e-3,
run_time=3
)
)
self.wait()
def get_mlp_icon(self, dot_buff=0.15, layer_buff=1.5, layer0_size=5):
"""
Creates an MLP icon with three layers.
Args:
dot_buff: Spacing between neurons in a layer
layer_buff: Spacing between layers
layer0_size: Number of neurons in input/output layers
"""
# Create three layers of dots
layers = VGroup(
Dot().get_grid(layer0_size, 1, buff=dot_buff),
Dot().get_grid(2 * layer0_size, 1, buff=dot_buff), # Hidden layer is wider
Dot().get_grid(layer0_size, 1, buff=dot_buff),
)
layers.set_height(4)
layers.arrange(RIGHT, buff=layer_buff)
# Set random opacities for visual interest
for layer in layers:
for dot in layer:
dot.set_fill(opacity=random.random())
layers.set_stroke(WHITE, 0.5)
# Create connection lines between layers
lines = VGroup(
Line(
dot1.get_center(),
dot2.get_center(),
buff=dot1.get_width() / 2
)
for l1, l2 in zip(layers, layers[1:])
for dot1 in l1
for dot2 in l2
)
# Color and style the lines randomly
for line in lines:
line.set_stroke(
color=self.value_to_color(random.uniform(-10, 10)),
width=3 * random.random()**3
)
return VGroup(layers, lines)
def value_to_color(
self,
value,
low_positive_color=BLUE_E,
high_positive_color=BLUE_B,
low_negative_color=RED_E,
high_negative_color=RED_B,
min_value=0.0,
max_value=10.0
):
"""Maps a numeric value to a color based on its sign and magnitude."""
alpha = clip(float(inverse_interpolate(min_value, max_value, abs(value))), 0, 1)
if value >= 0:
colors = (low_positive_color, high_positive_color)
else:
colors = (low_negative_color, high_negative_color)
return interpolate_color_by_hsl(*colors, alpha)
examples/mlp_neuron_activation.py
"""
Neuron Activation Visualization
Shows neurons firing based on input patterns, with active/inactive states.
"""
from manimlib import *
import numpy as np
class NeuronActivationStates(InteractiveScene):
"""
Visualizes neurons as dots with varying activation levels.
Active neurons glow, inactive neurons are dim.
Demonstrates: Dot animations, opacity changes, highlighting
"""
def construct(self):
# Title
title = Text("Neuron Activations", font_size=48)
title.to_edge(UP)
# Create a column of neurons (dots)
neuron_values = [0.0, 0.8, 0.0, 0.5, 0.9, 0.0, 0.3, 0.0, 0.7]
neurons = VGroup()
for val in neuron_values:
neuron = Dot(radius=0.25)
neuron.set_stroke(WHITE, 2)
# Active neurons are bright, inactive are dim
if val > 0:
neuron.set_fill(BLUE, opacity=val)
else:
neuron.set_fill(GREY_D, opacity=0.3)
neurons.add(neuron)
neurons.arrange(DOWN, buff=0.15)
neurons.set_height(5)
neurons.move_to(ORIGIN)
# Add labels showing activation values
labels = VGroup()
for i, (neuron, val) in enumerate(zip(neurons, neuron_values)):
label = DecimalNumber(val, num_decimal_places=1, font_size=24)
label.next_to(neuron, RIGHT, buff=0.5)
if val > 0:
label.set_color(BLUE)
else:
label.set_color(GREY)
labels.add(label)
# Animate appearance
self.play(Write(title))
self.play(
LaggedStartMap(GrowFromCenter, neurons, lag_ratio=0.1)
)
self.play(
LaggedStartMap(FadeIn, labels, shift=LEFT, lag_ratio=0.1)
)
self.wait()
# Highlight active vs inactive
active_rect = SurroundingRectangle(
VGroup(neurons[1], neurons[4], neurons[6], neurons[8]),
buff=0.15
)
active_rect.set_stroke(GREEN, 3)
active_label = Text("Active", font_size=30, color=GREEN)
active_label.next_to(active_rect, LEFT, buff=0.5)
inactive_rect = SurroundingRectangle(
VGroup(neurons[0], neurons[2], neurons[5], neurons[7]),
buff=0.15
)
inactive_rect.set_stroke(RED, 3)
inactive_label = Text("Inactive", font_size=30, color=RED)
inactive_label.next_to(inactive_rect, LEFT, buff=0.5)
self.play(
ShowCreation(active_rect),
FadeIn(active_label)
)
self.wait()
self.play(
ShowCreation(inactive_rect),
FadeIn(inactive_label)
)
self.wait(2)
# Show activation changing
self.play(
FadeOut(active_rect),
FadeOut(active_label),
FadeOut(inactive_rect),
FadeOut(inactive_label)
)
# Animate neurons activating/deactivating
new_values = [0.9, 0.0, 0.6, 0.0, 0.0, 0.8, 0.0, 0.4, 0.0]
anims = []
for neuron, label, old_val, new_val in zip(neurons, labels, neuron_values, new_values):
if new_val > 0:
anims.append(neuron.animate.set_fill(BLUE, opacity=new_val))
else:
anims.append(neuron.animate.set_fill(GREY_D, opacity=0.3))
anims.append(ChangeDecimalToValue(label, new_val))
if new_val > 0:
anims.append(label.animate.set_color(BLUE))
else:
anims.append(label.animate.set_color(GREY))
self.play(*anims, run_time=2)
self.wait(2)
class ClassicNeuronDiagram(InteractiveScene):
"""
Shows the classic neural network diagram with connected nodes.
Inputs feed into hidden layer neurons which connect to outputs.
Demonstrates: VGroup, Line connections, network structure
"""
def construct(self):
# Title
title = Text("Neural Network Layer", font_size=42)
title.to_edge(UP)
# Create three layers
input_layer = VGroup(
Dot(radius=0.2) for _ in range(4)
)
input_layer.arrange(DOWN, buff=0.5)
input_layer.set_fill(YELLOW, 0.8)
input_layer.set_stroke(WHITE, 2)
hidden_layer = VGroup(
Dot(radius=0.2) for _ in range(6)
)
hidden_layer.arrange(DOWN, buff=0.35)
hidden_layer.set_stroke(WHITE, 2)
output_layer = VGroup(
Dot(radius=0.2) for _ in range(3)
)
output_layer.arrange(DOWN, buff=0.6)
output_layer.set_fill(GREEN, 0.8)
output_layer.set_stroke(WHITE, 2)
# Position layers
layers = VGroup(input_layer, hidden_layer, output_layer)
layers.arrange(RIGHT, buff=2.5)
# Create connections
def create_connections(layer1, layer2):
lines = VGroup()
for n1 in layer1:
for n2 in layer2:
line = Line(n1.get_center(), n2.get_center(), buff=0.2)
line.set_stroke(GREY, 1, opacity=0.5)
lines.add(line)
return lines
connections1 = create_connections(input_layer, hidden_layer)
connections2 = create_connections(hidden_layer, output_layer)
# Layer labels
input_label = Text("Input", font_size=28)
input_label.next_to(input_layer, DOWN)
hidden_label = Text("Hidden", font_size=28)
hidden_label.next_to(hidden_layer, DOWN)
output_label = Text("Output", font_size=28)
output_label.next_to(output_layer, DOWN)
# Animate construction
self.play(Write(title))
self.play(
LaggedStartMap(GrowFromCenter, input_layer, lag_ratio=0.2),
FadeIn(input_label)
)
self.play(
ShowCreation(connections1, lag_ratio=0.01, run_time=2),
LaggedStartMap(GrowFromCenter, hidden_layer, lag_ratio=0.1),
FadeIn(hidden_label)
)
self.play(
ShowCreation(connections2, lag_ratio=0.01, run_time=2),
LaggedStartMap(GrowFromCenter, output_layer, lag_ratio=0.2),
FadeIn(output_label)
)
self.wait()
# Show activation propagating
for i, neuron in enumerate(hidden_layer):
# Random activation
activation = np.random.random()
if activation > 0.5:
neuron.set_fill(BLUE, activation)
else:
neuron.set_fill(GREY_D, 0.3)
self.play(
LaggedStart(
*(
neuron.animate.set_fill(
BLUE if np.random.random() > 0.4 else GREY_D,
np.random.random() if np.random.random() > 0.4 else 0.3
)
for neuron in hidden_layer
),
lag_ratio=0.1
)
)
self.wait()
# Highlight signal flow with VShowPassingFlash
flash_lines = connections1.copy()
for line in flash_lines:
line.set_stroke(YELLOW, 3)
line.insert_n_curves(20)
self.play(
LaggedStartMap(
VShowPassingFlash,
flash_lines,
time_width=0.5,
lag_ratio=0.02,
run_time=2
)
)
flash_lines2 = connections2.copy()
for line in flash_lines2:
line.set_stroke(GREEN, 3)
line.insert_n_curves(20)
self.play(
LaggedStartMap(
VShowPassingFlash,
flash_lines2,
time_width=0.5,
lag_ratio=0.02,
run_time=2
)
)
self.wait(2)
examples/mlp_neurons_flow.py
"""
MLP/Feedforward Neurons Flow Visualization
Shows data flowing through neurons in an MLP/Feedforward layer.
Based on 3Blue1Brown's transformer visualizations.
Run: manimgl mlp_neurons_flow.py MLPNeuronsFlow -o
"""
from manimlib import *
import numpy as np
import random
import itertools as it
def value_to_color(
value,
low_positive_color=BLUE_E,
high_positive_color=BLUE_B,
low_negative_color=RED_E,
high_negative_color=RED_B,
min_value=0.0,
max_value=10.0
):
"""Map a value to a color based on its sign and magnitude."""
alpha = np.clip(float((abs(value) - min_value) / (max_value - min_value)), 0, 1)
if value >= 0:
return interpolate_color(low_positive_color, high_positive_color, alpha)
else:
return interpolate_color(low_negative_color, high_negative_color, alpha)
class MLPNeuronsFlow(Scene):
"""
Visualizes data flowing through MLP/Feedforward neurons.
Shows the expansion and contraction of data through the hidden layer.
"""
def construct(self):
frame = self.camera.frame
# Title
title = Text("Feedforward Layer: Neurons in Action", font_size=48)
title.to_edge(UP)
self.play(Write(title))
self.wait()
# Create input layer (small)
input_layer = self.create_layer(8, radius=0.15, color=BLUE)
input_layer.to_edge(LEFT, buff=2)
# Create hidden layer (large - 4x expansion)
hidden_layer = self.create_layer(24, radius=0.12, color=GREEN)
hidden_layer.center()
# Create output layer (same as input)
output_layer = self.create_layer(8, radius=0.15, color=BLUE)
output_layer.to_edge(RIGHT, buff=2)
# Labels
input_label = Text("Input\n(d dims)", font_size=24)
input_label.next_to(input_layer, DOWN)
hidden_label = Text("Hidden\n(4d dims)", font_size=24)
hidden_label.next_to(hidden_layer, DOWN)
output_label = Text("Output\n(d dims)", font_size=24)
output_label.next_to(output_layer, DOWN)
# Show layers
self.play(
FadeIn(input_layer, shift=RIGHT),
FadeIn(input_label),
)
self.wait()
self.play(
FadeIn(hidden_layer, scale=0.8),
FadeIn(hidden_label),
)
self.wait()
self.play(
FadeIn(output_layer, shift=LEFT),
FadeIn(output_label),
)
self.wait()
# Create connections (sparse for visibility)
connections_in = self.create_connections(input_layer, hidden_layer, density=0.15)
connections_out = self.create_connections(hidden_layer, output_layer, density=0.15)
self.play(
Write(connections_in, stroke_width=1),
run_time=2
)
self.play(
Write(connections_out, stroke_width=1),
run_time=2
)
self.wait()
# Animate data flow
self.play_data_flow(connections_in, connections_out)
# Show "this happens per token" note
note = Text("This happens independently for each token position", font_size=30)
note.next_to(title, DOWN, buff=0.5)
self.play(FadeIn(note, shift=DOWN))
self.wait(2)
# Cleanup
self.play(FadeOut(VGroup(
title, note,
input_layer, hidden_layer, output_layer,
input_label, hidden_label, output_label,
connections_in, connections_out
)))
def create_layer(self, n_neurons, radius=0.15, color=BLUE):
"""Create a vertical layer of neurons."""
neurons = VGroup()
for _ in range(n_neurons):
dot = Dot(radius=radius)
dot.set_fill(color, opacity=random.uniform(0.5, 1.0))
dot.set_stroke(WHITE, 1)
neurons.add(dot)
neurons.arrange(DOWN, buff=0.15)
neurons.set_height(5)
return neurons
def create_connections(self, layer1, layer2, density=0.2):
"""Create sparse connections between two layers."""
lines = VGroup()
for n1 in layer1:
for n2 in layer2:
if random.random() < density:
line = Line(
n1.get_center(), n2.get_center(),
buff=n1.get_width() / 2
)
line.set_stroke(
color=value_to_color(random.uniform(-10, 10)),
width=2 * random.random(),
opacity=0.6
)
lines.add(line)
return lines
def play_data_flow(self, connections_in, connections_out):
"""Animate data flowing through the network."""
for _ in range(2):
self.play(
LaggedStart(*(
VShowPassingFlash(line.copy().set_stroke(YELLOW, 3), time_width=0.5)
for line in connections_in
), lag_ratio=0.01),
run_time=1.5
)
self.play(
LaggedStart(*(
VShowPassingFlash(line.copy().set_stroke(YELLOW, 3), time_width=0.5)
for line in connections_out
), lag_ratio=0.01),
run_time=1.5
)
class NeuralNetworkBasic(Scene):
"""
Simple neural network visualization with multiple layers.
"""
def construct(self):
# Create network
layer_sizes = [6, 12, 6]
layers = VGroup()
for n in layer_sizes:
layer = VGroup(*(
Dot(radius=0.12).set_fill(WHITE, opacity=random.uniform(0.4, 1.0))
for _ in range(n)
))
layer.arrange(DOWN, buff=0.2)
layers.add(layer)
layers.arrange(RIGHT, buff=2.5)
layers.center()
# Create connections
all_connections = VGroup()
for l1, l2 in zip(layers[:-1], layers[1:]):
connections = VGroup()
for n1 in l1:
for n2 in l2:
line = Line(n1.get_center(), n2.get_center(), buff=0.12)
line.set_stroke(
value_to_color(random.uniform(-10, 10)),
width=2 * random.random() ** 2,
opacity=0.5
)
connections.add(line)
all_connections.add(connections)
# Layer labels
labels = VGroup(
Text("Input", font_size=30),
Text("Hidden", font_size=30),
Text("Output", font_size=30),
)
for label, layer in zip(labels, layers):
label.next_to(layer, DOWN, buff=0.5)
# Title
title = Text("Simple Neural Network", font_size=48)
title.to_edge(UP)
# Animate
self.play(Write(title))
self.play(LaggedStartMap(FadeIn, layers[0], shift=RIGHT, lag_ratio=0.1))
self.play(FadeIn(labels[0]))
for i, (connections, layer, label) in enumerate(zip(all_connections, layers[1:], labels[1:])):
self.play(
Write(connections, lag_ratio=0.01),
run_time=1.5
)
self.play(
LaggedStartMap(FadeIn, layer, shift=RIGHT, lag_ratio=0.1),
FadeIn(label),
)
self.wait()
# Animate forward pass
for _ in range(2):
for connections in all_connections:
self.play(
LaggedStart(*(
VShowPassingFlash(
line.copy().set_stroke(YELLOW, 4),
time_width=0.8
)
for line in connections
), lag_ratio=0.005),
run_time=1.5
)
self.wait()
# Cleanup
self.play(FadeOut(VGroup(title, layers, all_connections, labels)))
class MLPExpansion3D(Scene):
"""
3D visualization of MLP expansion from d to 4d dimensions.
"""
def construct(self):
frame = self.camera.frame
frame.set_euler_angles(phi=70 * DEGREES, theta=-30 * DEGREES)
# Title
title = Text("MLP: Dimension Expansion", font_size=48)
title.to_edge(UP)
title.fix_in_frame()
self.play(Write(title))
# Create 3D neuron clusters
input_neurons = self.create_3d_cluster(8, spread=0.3, color=BLUE)
input_neurons.shift(3 * LEFT)
hidden_neurons = self.create_3d_cluster(32, spread=0.8, color=GREEN)
output_neurons = self.create_3d_cluster(8, spread=0.3, color=BLUE)
output_neurons.shift(3 * RIGHT)
# Labels
for neurons, text in [(input_neurons, "d"), (hidden_neurons, "4d"), (output_neurons, "d")]:
label = Text(text, font_size=36)
label.next_to(neurons, DOWN, buff=0.5)
neurons.add(label)
# Show progression
self.play(FadeIn(input_neurons, scale=0.8))
self.wait()
self.play(
frame.animate.set_euler_angles(phi=65 * DEGREES, theta=-45 * DEGREES),
TransformFromCopy(input_neurons[:-1], hidden_neurons[:-1]),
FadeIn(hidden_neurons[-1]),
run_time=2
)
self.wait()
self.play(
frame.animate.set_euler_angles(phi=60 * DEGREES, theta=-60 * DEGREES),
TransformFromCopy(hidden_neurons[:-1], output_neurons[:-1]),
FadeIn(output_neurons[-1]),
run_time=2
)
self.wait()
# Rotate view
self.play(
frame.animate.increment_theta(90 * DEGREES),
run_time=3
)
self.wait()
# Cleanup
self.play(FadeOut(VGroup(title, input_neurons, hidden_neurons, output_neurons)))
def create_3d_cluster(self, n_points, spread=0.5, color=BLUE):
"""Create a 3D cluster of points/neurons."""
points = np.random.randn(n_points, 3) * spread
dots = VGroup()
for point in points:
dot = Dot3D(radius=0.08)
dot.move_to(point)
dot.set_color(color)
dot.set_opacity(random.uniform(0.6, 1.0))
dots.add(dot)
return dots
examples/mlp_relu_visualization.py
"""
ReLU Activation Function Visualization
Shows the ReLU and GELU activation functions used in neural networks.
"""
from manimlib import *
from scipy.stats import norm
class ReLUVisualization(InteractiveScene):
"""
Visualizes the ReLU (Rectified Linear Unit) activation function
and compares it with GELU.
Demonstrates: Axes, graph plotting, labels, transitions
"""
def construct(self):
# Create axes for the activation function
axes = Axes(
x_range=(-4, 4),
y_range=(-1, 4),
axis_config=dict(include_tip=True),
)
axes.set_width(8)
axes.add_coordinate_labels(font_size=20)
# Graph ReLU: f(x) = max(0, x)
relu_graph = axes.get_graph(
lambda x: max(0, x),
discontinuities=[0]
)
relu_graph.set_stroke(YELLOW, 4)
# Labels
relu_title = Text("Rectified Linear Unit (ReLU)", font_size=36)
relu_title.to_edge(UP)
relu_label = Text("ReLU", font_size=30)
relu_label.set_color(YELLOW)
relu_label.move_to(axes.c2p(2, 3))
# Formula
relu_formula = Tex(R"f(x) = \max(0, x)", font_size=36)
relu_formula.next_to(axes, DOWN)
# Animate building the scene
self.play(Write(axes))
self.play(
Write(relu_title),
ShowCreation(relu_graph, run_time=2)
)
self.play(
FadeIn(relu_label),
Write(relu_formula)
)
self.wait(2)
# Show GELU comparison
gelu_graph = axes.get_graph(lambda x: x * norm.cdf(x))
gelu_graph.set_stroke(GREEN, 4)
gelu_label = Text("GELU", font_size=30)
gelu_label.set_color(GREEN)
gelu_label.next_to(relu_label, DOWN, buff=0.5, aligned_edge=LEFT)
gelu_title = Text("Gaussian Error Linear Unit (GELU)", font_size=36)
gelu_title.to_edge(UP)
self.play(
relu_graph.animate.set_stroke(opacity=0.3),
relu_label.animate.set_fill(opacity=0.3),
FadeTransform(relu_title, gelu_title),
ShowCreation(gelu_graph),
FadeIn(gelu_label)
)
self.wait(2)
# Back to ReLU
self.play(
gelu_graph.animate.set_stroke(opacity=0.3),
gelu_label.animate.set_fill(opacity=0.3),
relu_graph.animate.set_stroke(opacity=1),
relu_label.animate.set_fill(opacity=1),
FadeTransform(gelu_title, relu_title)
)
self.wait(2)
class ReLUNeuronBehavior(InteractiveScene):
"""
Shows how ReLU affects neuron values - negative values become 0,
positive values pass through unchanged.
Demonstrates: DecimalNumber, color coding, visual feedback
"""
def construct(self):
# Create input values
input_values = [-3.5, -1.2, 0.5, 2.8, -0.7, 1.5, 4.2, -2.1, 0.0]
output_values = [max(0, v) for v in input_values]
# Create input column
input_entries = VGroup()
output_entries = VGroup()
for val in input_values:
entry = DecimalNumber(val, num_decimal_places=1, include_sign=True)
entry.set_color(BLUE if val >= 0 else RED)
input_entries.add(entry)
for val in output_values:
entry = DecimalNumber(val, num_decimal_places=1, include_sign=True)
entry.set_color(BLUE if val > 0 else GREY)
output_entries.add(entry)
input_entries.arrange(DOWN, buff=0.3)
output_entries.arrange(DOWN, buff=0.3)
# Add brackets
input_group = VGroup(
Tex("["),
input_entries,
Tex("]")
)
input_group[0].next_to(input_entries, LEFT)
input_group[2].next_to(input_entries, RIGHT)
output_group = VGroup(
Tex("["),
output_entries,
Tex("]")
)
output_group[0].next_to(output_entries, LEFT)
output_group[2].next_to(output_entries, RIGHT)
# Position groups
input_group.move_to(2 * LEFT)
output_group.move_to(2 * RIGHT)
# Arrow with ReLU label
arrow = Arrow(input_group.get_right(), output_group.get_left(), buff=0.3)
relu_label = Text("ReLU", font_size=36)
relu_label.next_to(arrow, UP)
# Title
title = Text("ReLU: Negative values become zero", font_size=36)
title.to_edge(UP)
# Animate
self.play(Write(title))
self.play(FadeIn(input_group, shift=LEFT))
self.play(
GrowArrow(arrow),
FadeIn(relu_label)
)
self.wait()
# Transform input to output with highlighting
for i, (inp, out) in enumerate(zip(input_entries, output_entries)):
inp_copy = inp.copy()
if input_values[i] < 0:
# Highlight negative -> zero transformation
self.play(
Transform(inp_copy, out),
Flash(inp, color=RED),
run_time=0.5
)
else:
self.play(
Transform(inp_copy, out),
run_time=0.3
)
output_group.add(inp_copy)
self.play(
FadeIn(output_group[0]),
FadeIn(output_group[2])
)
self.wait(2)
examples/mlp_vector_space.py
"""
Vector Space and Dot Products for MLPs
Shows how dot products can be used to detect features in embeddings.
"""
from manimlib import *
import numpy as np
class VectorDotProduct(InteractiveScene):
"""
Visualizes how the dot product between a feature direction
and an embedding determines neuron activation.
Demonstrates: NumberPlane, vectors, dot product projection
"""
def construct(self):
# Create 2D plane for visualization
unit_size = 2.0
plane = NumberPlane(
x_range=(-3, 3),
y_range=(-3, 3),
axis_config=dict(stroke_width=1),
background_line_style=dict(
stroke_color=BLUE_D,
stroke_width=1,
stroke_opacity=0.5
),
faded_line_ratio=1,
unit_size=unit_size,
)
plane.shift(DOWN * 0.5)
# Title
title = Text("Dot Product as Feature Detection", font_size=36)
title.to_edge(UP)
self.play(Write(title))
self.play(FadeIn(plane))
# Feature direction vector (represents what the neuron is looking for)
feature_angle = 60 * DEGREES
feature_vect = Vector(
unit_size * np.array([np.cos(feature_angle), np.sin(feature_angle), 0])
)
feature_vect.set_color(RED)
feature_vect.shift(plane.get_origin())
feature_label = Text("Feature\nDirection", font_size=20)
feature_label.set_color(RED)
feature_label.next_to(feature_vect.get_end(), UR, buff=0.1)
self.play(
GrowArrow(feature_vect),
FadeIn(feature_label)
)
self.wait()
# Embedding vector (the input we're testing)
emb_vect = Vector(unit_size * 1.5 * RIGHT)
emb_vect.set_color(YELLOW)
emb_vect.shift(plane.get_origin())
emb_label = Tex(R"\vec{E}", font_size=36)
emb_label.set_color(YELLOW)
emb_label.next_to(emb_vect.get_end(), DR, buff=0.1)
self.play(
GrowArrow(emb_vect),
FadeIn(emb_label)
)
self.wait()
# Show the projection (dot product visualization)
feature_unit = normalize(feature_vect.get_vector())
def get_projection_point():
emb_vec = emb_vect.get_end() - plane.get_origin()
proj_length = np.dot(emb_vec, feature_unit)
return plane.get_origin() + proj_length * feature_unit
proj_line = Line(plane.get_origin(), get_projection_point())
proj_line.set_stroke(PINK, 4)
dashed_line = DashedLine(emb_vect.get_end(), get_projection_point())
dashed_line.set_stroke(GREY, 2)
proj_dot = Dot(get_projection_point(), radius=0.1)
proj_dot.set_color(PINK)
self.play(
ShowCreation(proj_line),
ShowCreation(dashed_line),
GrowFromCenter(proj_dot)
)
# Dot product value
dp_value = DecimalNumber(
np.dot(emb_vect.get_end() - plane.get_origin(), feature_unit) / unit_size,
num_decimal_places=2,
font_size=30
)
dp_value.set_color(PINK)
dp_label = Text("Dot Product: ", font_size=24)
dp_display = VGroup(dp_label, dp_value).arrange(RIGHT)
dp_display.next_to(proj_dot, RIGHT, buff=0.3)
self.play(FadeIn(dp_display))
self.wait()
# Animate the embedding vector rotating
original_angle = 0
def update_emb_vect(mob, angle):
new_end = plane.get_origin() + unit_size * 1.5 * np.array([
np.cos(angle), np.sin(angle), 0
])
mob.put_start_and_end_on(plane.get_origin(), new_end)
emb_label.next_to(new_end, normalize(new_end - plane.get_origin()), buff=0.1)
def update_projection():
proj_pt = get_projection_point()
proj_line.put_start_and_end_on(plane.get_origin(), proj_pt)
dashed_line.put_start_and_end_on(emb_vect.get_end(), proj_pt)
proj_dot.move_to(proj_pt)
dp_val = np.dot(emb_vect.get_end() - plane.get_origin(), feature_unit) / unit_size
dp_value.set_value(dp_val)
dp_display.next_to(proj_dot, RIGHT, buff=0.3)
# Rotate through different angles
for target_angle in [45 * DEGREES, 90 * DEGREES, 150 * DEGREES, 220 * DEGREES, 300 * DEGREES, 0]:
self.play(
Rotate(
emb_vect,
target_angle - original_angle,
about_point=plane.get_origin()
),
UpdateFromFunc(proj_line, lambda m: update_projection()),
run_time=1.5
)
update_projection()
original_angle = target_angle
self.wait(0.5)
self.wait()
class FeatureDirectionThreshold(InteractiveScene):
"""
Shows how a threshold on the dot product creates a decision boundary.
Positive side = "Yes", Negative side = "No".
Demonstrates: Regions, decision boundaries, classification
"""
def construct(self):
# Create plane
unit_size = 2.0
plane = NumberPlane(
x_range=(-3, 3),
y_range=(-3, 3),
axis_config=dict(stroke_width=1),
background_line_style=dict(
stroke_color=BLUE_D,
stroke_width=1,
stroke_opacity=0.5
),
faded_line_ratio=1,
unit_size=unit_size,
)
# Title
title = Text("Decision Boundary from Dot Product", font_size=32)
title.to_edge(UP)
self.play(Write(title))
self.play(FadeIn(plane))
# Feature direction
feature_angle = 45 * DEGREES
feature_dir = np.array([np.cos(feature_angle), np.sin(feature_angle), 0])
feature_vect = Vector(unit_size * feature_dir)
feature_vect.set_color(WHITE)
# Decision boundary (perpendicular to feature direction)
perp_dir = np.array([-feature_dir[1], feature_dir[0], 0])
boundary_line = Line(
-4 * perp_dir * unit_size,
4 * perp_dir * unit_size
)
boundary_line.set_stroke(WHITE, 3)
self.play(GrowArrow(feature_vect))
self.play(ShowCreation(boundary_line))
# Create "Yes" and "No" regions
yes_region = Rectangle(width=8, height=8)
yes_region.set_fill(GREEN, 0.2)
yes_region.set_stroke(width=0)
yes_region.rotate(feature_angle)
yes_region.shift(2 * feature_dir * unit_size)
no_region = Rectangle(width=8, height=8)
no_region.set_fill(RED, 0.15)
no_region.set_stroke(width=0)
no_region.rotate(feature_angle)
no_region.shift(-2 * feature_dir * unit_size)
# Clip regions to visible area
yes_region.set_clip_path(Rectangle(width=12, height=8))
no_region.set_clip_path(Rectangle(width=12, height=8))
self.play(
FadeIn(yes_region),
FadeIn(no_region)
)
# Labels
yes_label = Text("Yes", font_size=36, color=GREEN)
yes_label.move_to(2.5 * feature_dir * unit_size)
no_label = Text("No", font_size=36, color=RED)
no_label.move_to(-2.5 * feature_dir * unit_size)
self.play(
FadeIn(yes_label),
FadeIn(no_label)
)
self.wait()
# Show example points
points_data = [
(1.5 * unit_size, 1.8 * unit_size, "Match", GREEN),
(-0.5 * unit_size, -1.0 * unit_size, "No Match", RED),
(2.0 * unit_size, 0.5 * unit_size, "Match", GREEN),
(-1.5 * unit_size, 0.2 * unit_size, "No Match", RED),
]
dots = VGroup()
for x, y, label_text, color in points_data:
dot = Dot(point=np.array([x, y, 0]), radius=0.15)
dot.set_color(color)
dots.add(dot)
self.play(
LaggedStartMap(GrowFromCenter, dots, lag_ratio=0.3)
)
self.wait(2)
# Show threshold adjustment
threshold_label = Text("Threshold can be adjusted with bias", font_size=24)
threshold_label.next_to(title, DOWN)
self.play(Write(threshold_label))
# Move boundary line (simulating bias adjustment)
for offset in [0.5, -0.5, 0]:
new_line = Line(
-4 * perp_dir * unit_size + offset * feature_dir * unit_size,
4 * perp_dir * unit_size + offset * feature_dir * unit_size
)
new_line.set_stroke(WHITE, 3)
self.play(Transform(boundary_line, new_line))
self.wait(0.5)
self.wait(2)
examples/mlp_weight_matrix.py
"""
Weight Matrix Visualization for MLPs
Shows a color-coded weight matrix with values mapped to colors.
"""
from manimlib import *
import numpy as np
def value_to_color(
value,
low_positive_color=BLUE_E,
high_positive_color=BLUE_B,
low_negative_color=RED_E,
high_negative_color=RED_B,
min_value=0.0,
max_value=10.0
):
"""Maps a numeric value to a color based on sign and magnitude."""
alpha = clip(float(inverse_interpolate(min_value, max_value, abs(value))), 0, 1)
if value >= 0:
colors = (low_positive_color, high_positive_color)
else:
colors = (low_negative_color, high_negative_color)
return interpolate_color_by_hsl(*colors, alpha)
class WeightMatrixVisualization(InteractiveScene):
"""
Visualizes a weight matrix with color-coded entries.
Blue = positive values, Red = negative values.
Brighter = larger magnitude.
Demonstrates: DecimalMatrix, color mapping, matrix operations
"""
def construct(self):
# Create weight matrix with random values
np.random.seed(42)
n_rows, n_cols = 6, 8
values = np.random.uniform(-9.9, 9.9, size=(n_rows, n_cols))
# Build the matrix display
matrix = self.create_weight_matrix(values)
matrix.set_height(4)
matrix.to_edge(LEFT, buff=1)
# Title
title = Text("Weight Matrix", font_size=48)
title.to_edge(UP)
# Legend
legend = self.create_color_legend()
legend.to_edge(RIGHT, buff=1)
# Animate
self.play(Write(title))
self.play(FadeIn(matrix, lag_ratio=0.02, run_time=2))
self.play(FadeIn(legend))
self.wait(2)
# Highlight a single row
row_idx = 2
row = matrix[row_idx]
row_rect = SurroundingRectangle(row, buff=0.1)
row_rect.set_stroke(YELLOW, 3)
row_label = Text(f"Row {row_idx}: one neuron's weights", font_size=24)
row_label.next_to(row_rect, DOWN)
self.play(ShowCreation(row_rect))
self.play(Write(row_label))
self.wait(2)
# Show dot product concept
self.play(FadeOut(row_rect), FadeOut(row_label))
self.wait()
def create_weight_matrix(self, values):
"""Creates a VGroup of DecimalNumbers arranged as a matrix."""
n_rows, n_cols = values.shape
entries = VGroup()
rows = VGroup()
for i in range(n_rows):
row = VGroup()
for j in range(n_cols):
val = values[i, j]
entry = DecimalNumber(
val,
num_decimal_places=1,
include_sign=True,
font_size=24
)
entry.set_color(value_to_color(val, max_value=9.9))
row.add(entry)
entries.add(entry)
row.arrange(RIGHT, buff=0.3)
rows.add(row)
rows.arrange(DOWN, buff=0.2)
# Add brackets
left_bracket = Tex(R"\left[", font_size=72)
right_bracket = Tex(R"\right]", font_size=72)
left_bracket.stretch_to_fit_height(rows.get_height() * 1.1)
right_bracket.stretch_to_fit_height(rows.get_height() * 1.1)
left_bracket.next_to(rows, LEFT, buff=0.1)
right_bracket.next_to(rows, RIGHT, buff=0.1)
return VGroup(*rows, left_bracket, right_bracket)
def create_color_legend(self):
"""Creates a color legend showing value-to-color mapping."""
legend = VGroup()
# Title
title = Text("Color Legend", font_size=24)
legend.add(title)
# Positive values
pos_example = DecimalNumber(5.0, include_sign=True, font_size=24)
pos_example.set_color(value_to_color(5.0))
pos_label = Text("Positive", font_size=20)
pos_row = VGroup(pos_example, pos_label).arrange(RIGHT, buff=0.3)
legend.add(pos_row)
# Negative values
neg_example = DecimalNumber(-5.0, include_sign=True, font_size=24)
neg_example.set_color(value_to_color(-5.0))
neg_label = Text("Negative", font_size=20)
neg_row = VGroup(neg_example, neg_label).arrange(RIGHT, buff=0.3)
legend.add(neg_row)
# Arrange vertically
legend.arrange(DOWN, buff=0.4, aligned_edge=LEFT)
return legend
class MatrixVectorProduct(InteractiveScene):
"""
Shows how a weight matrix multiplies with an input vector.
Demonstrates: Matrix-vector multiplication visualization
"""
def construct(self):
# Create a simple 4x3 matrix and 3x1 vector
np.random.seed(123)
matrix_values = np.random.uniform(-5, 5, size=(4, 3))
vector_values = np.random.uniform(-5, 5, size=(3,))
# Build matrix display
matrix_entries = VGroup()
for i in range(4):
row = VGroup()
for j in range(3):
val = matrix_values[i, j]
entry = DecimalNumber(val, num_decimal_places=1, include_sign=True, font_size=28)
entry.set_color(value_to_color(val, max_value=5))
row.add(entry)
row.arrange(RIGHT, buff=0.4)
matrix_entries.add(row)
matrix_entries.arrange(DOWN, buff=0.3)
# Add brackets
m_left = Tex("[").stretch_to_fit_height(matrix_entries.get_height() * 1.1)
m_right = Tex("]").stretch_to_fit_height(matrix_entries.get_height() * 1.1)
m_left.next_to(matrix_entries, LEFT, buff=0.05)
m_right.next_to(matrix_entries, RIGHT, buff=0.05)
matrix = VGroup(matrix_entries, m_left, m_right)
# Build vector display
vector_entries = VGroup()
for val in vector_values:
entry = DecimalNumber(val, num_decimal_places=1, include_sign=True, font_size=28)
entry.set_color(YELLOW)
vector_entries.add(entry)
vector_entries.arrange(DOWN, buff=0.3)
v_left = Tex("[").stretch_to_fit_height(vector_entries.get_height() * 1.1)
v_right = Tex("]").stretch_to_fit_height(vector_entries.get_height() * 1.1)
v_left.next_to(vector_entries, LEFT, buff=0.05)
v_right.next_to(vector_entries, RIGHT, buff=0.05)
vector = VGroup(vector_entries, v_left, v_right)
# Position matrix and vector
matrix.move_to(2.5 * LEFT)
vector.next_to(matrix, RIGHT, buff=0.5)
# Labels
matrix_label = Tex("W", font_size=48)
matrix_label.next_to(matrix, UP)
vector_label = Tex(R"\vec{x}", font_size=48).set_color(YELLOW)
vector_label.next_to(vector, UP)
# Title
title = Text("Matrix-Vector Product", font_size=42)
title.to_edge(UP)
# Show initial setup
self.play(Write(title))
self.play(
FadeIn(matrix),
FadeIn(vector),
Write(matrix_label),
Write(vector_label)
)
self.wait()
# Equals and result placeholder
equals = Tex("=", font_size=48)
equals.next_to(vector, RIGHT, buff=0.5)
# Compute result
result_values = matrix_values @ vector_values
result_entries = VGroup()
for val in result_values:
entry = DecimalNumber(val, num_decimal_places=1, include_sign=True, font_size=28)
entry.set_color(GREEN)
result_entries.add(entry)
result_entries.arrange(DOWN, buff=0.3)
r_left = Tex("[").stretch_to_fit_height(result_entries.get_height() * 1.1)
r_right = Tex("]").stretch_to_fit_height(result_entries.get_height() * 1.1)
r_left.next_to(result_entries, LEFT, buff=0.05)
r_right.next_to(result_entries, RIGHT, buff=0.05)
result = VGroup(result_entries, r_left, r_right)
result.next_to(equals, RIGHT, buff=0.5)
self.play(Write(equals))
# Animate row-by-row computation
for row_idx in range(4):
row = matrix_entries[row_idx]
row_rect = SurroundingRectangle(row, buff=0.05)
row_rect.set_stroke(PINK, 2)
vec_rect = SurroundingRectangle(vector_entries, buff=0.05)
vec_rect.set_stroke(PINK, 2)
self.play(
ShowCreation(row_rect),
ShowCreation(vec_rect),
run_time=0.5
)
self.play(
Write(result_entries[row_idx]),
run_time=0.5
)
self.play(
FadeOut(row_rect),
FadeOut(vec_rect),
run_time=0.3
)
self.play(FadeIn(r_left), FadeIn(r_right))
self.wait(2)
examples/multi_head_attention.py
"""
Multi-Head Attention Visualization - Native ManimGL
This is the proper ManimGL implementation using native 3D features.
Based on 3b1b's transformer visualization style.
Run with: manimgl multi_head_attention.py MultiHeadedAttention
Interactive: manimgl multi_head_attention.py MultiHeadedAttention -se 30
"""
from manimlib import *
import numpy as np
def softmax(logits, temperature=1.0):
"""Numerically stable softmax."""
logits = np.array(logits)
logits = logits - np.max(logits)
exps = np.exp(logits / max(temperature, 1e-10))
return exps / np.sum(exps)
class AttentionPatternGrid(VGroup):
"""A grid showing attention pattern with dots."""
def __init__(self, n_rows=8, seed=None, **kwargs):
super().__init__(**kwargs)
if seed is not None:
np.random.seed(seed)
cell_size = 0.4
# Create grid of squares
self.grid = VGroup()
for i in range(n_rows):
for j in range(n_rows):
cell = Square(side_length=cell_size)
cell.set_stroke(WHITE, 0.5, opacity=0.3)
cell.move_to(np.array([j * cell_size, -i * cell_size, 0]))
self.grid.add(cell)
self.grid.center()
# Generate causal attention pattern
pattern = np.random.normal(0, 1, (n_rows, n_rows))
for n in range(n_rows):
pattern[:, n][n + 1:] = -np.inf
valid = pattern[:, n][pattern[:, n] > -np.inf]
if len(valid) > 0:
pattern[:, n][:n + 1] = softmax(valid)
pattern[:, n][n + 1:] = 0
pattern = np.nan_to_num(pattern, nan=0.0)
# Add dots based on weights
self.dots = VGroup()
for i in range(n_rows):
for j in range(n_rows):
value = pattern[i, j]
if value > 0.05:
dot = Dot(radius=cell_size * 0.4 * value)
dot.set_fill(GREY_B, 1)
dot.move_to(self.grid[i * n_rows + j].get_center())
self.dots.add(dot)
# Border
self.border = SurroundingRectangle(self.grid, buff=0.05)
self.border.set_stroke(WHITE, 2)
self.border.set_fill(BLACK, 0.9)
self.add(self.border, self.grid, self.dots)
class MultiHeadedAttention(InteractiveScene):
"""
Multi-Head Attention visualization in native ManimGL.
Shows multiple attention heads in 3D space with camera movement.
"""
def construct(self):
# Background
background = FullScreenRectangle()
background.set_fill(GREY_E, 1)
background.fix_in_frame()
self.add(background)
# Title animation: Single head -> Multi-headed
single_title = Text("Single head of attention")
multiple_title = Text("Multi-headed attention")
for title in [single_title, multiple_title]:
title.scale(1.25)
title.to_edge(UP)
self.add(single_title)
self.wait()
# Flash around "head"
head = single_title["head"][0]
self.play(
FlashAround(head, run_time=2),
head.animate.set_color(YELLOW),
)
self.wait()
# Transform title
kw = dict(path_arc=45 * DEGREES)
self.play(
FadeTransform(single_title["Single"], multiple_title["Multi-"], **kw),
FadeTransform(single_title["head"], multiple_title["head"], **kw),
FadeIn(multiple_title["ed"], 0.25 * RIGHT),
FadeTransform(single_title["attention"], multiple_title["attention"], **kw),
FadeOut(single_title["of"])
)
self.add(multiple_title)
self.wait()
# Create attention pattern heads
n_heads = 15
heads = Group()
for n in range(n_heads):
pattern = AttentionPatternGrid(n_rows=6, seed=n * 42)
pattern.set_height(4)
heads.add(pattern)
# Arrange in 3D depth
self.set_floor_plane("xz")
frame = self.camera.frame
multiple_title.fix_in_frame()
heads.arrange(OUT, buff=1.0)
heads.move_to(DOWN)
# Show initial pattern
pre_head = heads[-1].copy()
pre_head.move_to(DOWN)
self.add(pre_head)
self.wait()
# Rotate camera to reveal 3D
self.play(
frame.animate.reorient(41, -12, 0, (-1.0, -1.42, 1.09), 12.90).set_anim_args(run_time=2),
background.animate.set_fill(opacity=0.75),
FadeTransform(pre_head, heads[-1], time_span=(1, 2)),
)
# Fan out all heads
self.play(
frame.animate.reorient(48, -11, 0, (-1.0, -1.42, 1.09), 12.90),
LaggedStart(
*(FadeTransform(heads[-1].copy(), image) for image in heads),
lag_ratio=0.1,
group_type=Group,
),
run_time=4,
)
self.add(heads)
self.wait()
# Add matrix labels W_Q, W_K, W_V for visible heads
colors = [YELLOW, TEAL, RED, PINK]
tex_labels = ["W_Q", "W_K", R"\downarrow W_V", R"\uparrow W_V"]
n_shown = 9
sym_groups = VGroup()
for tex, color in zip(tex_labels[:2], colors[:2]): # Just W_Q and W_K for now
syms = VGroup()
for n, image in enumerate(list(heads)[:-n_shown - 1:-1], start=1):
sym = Tex(tex + f"^{{({n})}}", font_size=36)
sym.next_to(image, UP, MED_SMALL_BUFF)
sym.set_color(color)
sym.set_backstroke(BLACK, 5)
syms.add(sym)
sym_groups.add(syms)
# Rotate labels to face camera
sym_rot_angle = 70 * DEGREES
for syms in sym_groups:
syms.align_to(heads, LEFT)
for sym in syms:
sym.rotate(sym_rot_angle, UP)
# Show W_Q labels
self.play(
LaggedStartMap(FadeIn, sym_groups[0], shift=0.2 * UP, lag_ratio=0.25),
frame.animate.reorient(59, -7, 0, (-1.62, 0.25, 1.29), 14.18),
run_time=2,
)
# Show W_K labels
self.play(
LaggedStartMap(FadeIn, sym_groups[1], shift=0.2 * UP, lag_ratio=0.1),
sym_groups[0].animate.shift(0.75 * UP),
run_time=1,
)
self.wait()
# Add brace showing "96 heads"
depth = heads.get_depth()
brace = Brace(Line(LEFT, RIGHT).set_width(0.5 * depth), UP).scale(2)
brace_label = brace.get_text("96", font_size=96, buff=MED_SMALL_BUFF)
brace_group = VGroup(brace, brace_label)
brace_group.rotate(PI / 2, UP)
brace_group.next_to(heads, UP, buff=MED_LARGE_BUFF)
self.add(brace, brace_label, sym_groups)
self.play(
frame.animate.reorient(62, -6, 0, (-0.92, -0.08, -0.51), 14.18).set_anim_args(run_time=5),
GrowFromCenter(brace),
sym_groups.animate.set_fill(opacity=0.5).set_stroke(width=0),
FadeIn(brace_label, 0.5 * UP, time_span=(0.5, 1.5)),
)
self.wait()
# Return to front view
self.play(
frame.animate.reorient(0, 0, 0, ORIGIN, FRAME_HEIGHT).set_anim_args(run_time=2),
FadeOut(multiple_title, UP),
FadeOut(brace_group),
FadeOut(sym_groups),
)
self.wait()
class SimpleMultiHead(InteractiveScene):
"""Simpler version for quick testing."""
def construct(self):
# Title
title = Text("Multi-Head Attention", font_size=48)
title.to_edge(UP)
title.fix_in_frame()
self.add(title)
# Create heads
heads = Group()
for i in range(8):
pattern = AttentionPatternGrid(n_rows=5, seed=i * 10)
pattern.set_height(2)
heads.add(pattern)
# Arrange in 3D
heads.arrange(OUT, buff=0.5)
heads.move_to(ORIGIN)
frame = self.camera.frame
# Show one, then fan out
self.add(heads[-1])
self.wait()
self.play(
frame.animate.reorient(50, -20, 0),
run_time=2
)
self.play(
LaggedStart(
*[FadeIn(h, shift=OUT * 0.3) for h in heads[:-1]],
lag_ratio=0.2
),
run_time=2
)
self.wait()
# Rotate around
self.play(
frame.animate.reorient(50, 60, 0),
run_time=4
)
self.wait()
examples/network_block_flow.py
"""
Network Block Flow - 3D visualization of data flowing through network blocks
Shows data moving through attention and MLP blocks as 3D cubes.
Based on 3Blue1Brown's transformer visualizations.
Run: manimgl network_block_flow.py NetworkBlockFlow3D -o
"""
from manimlib import *
import numpy as np
import random
def value_to_color(
value,
low_positive_color=BLUE_E,
high_positive_color=BLUE_B,
low_negative_color=RED_E,
high_negative_color=RED_B,
min_value=0.0,
max_value=10.0
):
"""Map a value to a color based on its sign and magnitude."""
alpha = np.clip(float((abs(value) - min_value) / (max_value - min_value)), 0, 1)
if value >= 0:
return interpolate_color(low_positive_color, high_positive_color, alpha)
else:
return interpolate_color(low_negative_color, high_negative_color, alpha)
def random_bright_color(hue_range=(0.0, 1.0)):
"""Generate a random bright color within a hue range."""
hue = random.uniform(*hue_range)
return Color(hsl=(hue, 0.7, 0.6))
class SimpleEmbeddingColumn(VGroup):
"""A simple 3D-style embedding column."""
def __init__(self, n_entries=8, height=3.0, width=0.4, **kwargs):
super().__init__(**kwargs)
entries = VGroup()
entry_height = height / n_entries * 0.85
for _ in range(n_entries):
value = random.uniform(-10, 10)
rect = Rectangle(width=width, height=entry_height)
rect.set_fill(value_to_color(value), opacity=0.9)
rect.set_stroke(WHITE, 1)
entries.add(rect)
entries.arrange(DOWN, buff=0.02)
self.add(entries)
self.entries = entries
def randomize_values(self):
for entry in self.entries:
value = random.uniform(-10, 10)
entry.set_fill(value_to_color(value), opacity=0.9)
return self
class NetworkBlockFlow3D(Scene):
"""
3D visualization of data flowing through transformer blocks.
Shows embeddings passing through attention and MLP blocks,
represented as 3D cubes that process the data.
"""
def construct(self):
frame = self.camera.frame
# Setup 3D view
frame.set_euler_angles(phi=65 * DEGREES, theta=-40 * DEGREES)
frame.set_z(2)
# Create input embeddings
n_tokens = 5
embeddings = VGroup(*(
SimpleEmbeddingColumn(n_entries=10, height=3.5, width=0.5)
for _ in range(n_tokens)
))
embeddings.arrange(RIGHT, buff=0.6)
embeddings.set_z(0)
# Title
title = Text("Data Flow Through Network Blocks", font_size=48)
title.to_edge(UP)
title.fix_in_frame()
self.play(
Write(title),
LaggedStartMap(FadeIn, embeddings, shift=0.5 * DOWN, lag_ratio=0.1),
run_time=2
)
self.wait()
# Create and show first block (Attention)
att_block = self.create_block(embeddings, "Attention", BLUE_E)
self.play(
frame.animate.reorient(-50, -15, 0).shift(2 * OUT),
FadeIn(att_block, scale=0.8),
run_time=2
)
# Flow through attention
new_embeddings = self.flow_through_block(embeddings, att_block)
# Create second block (MLP/Feedforward)
mlp_block = self.create_block(new_embeddings, "Feedforward", GREEN_E)
mlp_block.shift(3 * OUT)
self.play(
frame.animate.shift(2 * OUT),
FadeIn(mlp_block, scale=0.8),
run_time=2
)
# Flow through MLP
final_embeddings = self.flow_through_block(new_embeddings, mlp_block)
# Show "many more" indication
self.show_repetition_hint(mlp_block, frame)
# Cleanup
self.play(
FadeOut(VGroup(embeddings, new_embeddings, final_embeddings)),
FadeOut(att_block),
FadeOut(mlp_block),
FadeOut(title),
)
def create_block(self, layer, title_text, color):
"""Create a processing block (cube) next to the layer."""
body = Cube(color=color, opacity=0.7)
body.set_shading(0.5, 0.5, 0.0)
width = layer.get_width() + 1
height = layer.get_height() + 0.5
depth = 2.0
body.set_shape(width, height, depth)
body.next_to(layer, OUT, buff=1.0)
title = Text(title_text, font_size=60)
title.set_backstroke(BLACK, 3)
title.rotate(PI / 2, RIGHT)
title.next_to(body, UP, buff=0.2)
block = Group(body, title)
block.body = body
block.title = title
return block
def flow_through_block(self, embeddings, block):
"""Animate embeddings flowing through the block."""
# Create output embeddings
new_embeddings = VGroup(*(
SimpleEmbeddingColumn(n_entries=10, height=3.5, width=0.5)
for _ in range(len(embeddings))
))
new_embeddings.arrange(RIGHT, buff=0.6)
new_embeddings.move_to(block.body.get_center())
new_embeddings.set_z(block.body.get_z(OUT) + 1)
# Animate transformation
self.play(
TransformFromCopy(embeddings, new_embeddings),
run_time=2
)
return new_embeddings
def show_repetition_hint(self, last_block, frame):
"""Show indication of many more blocks."""
dots = Text("...", font_size=120)
dots.rotate(PI / 2, RIGHT)
dots.next_to(last_block, OUT, buff=1)
brace = Brace(Line(ORIGIN, 4 * OUT), RIGHT)
brace.rotate(PI / 2, RIGHT)
brace.next_to(dots, RIGHT)
label = Text("Many\nrepetitions", font_size=36)
label.rotate(PI / 2, RIGHT)
label.next_to(brace, RIGHT)
hint_group = VGroup(dots, brace, label)
self.play(
frame.animate.shift(2 * OUT),
FadeIn(dots),
GrowFromCenter(brace),
FadeIn(label),
run_time=2
)
self.wait(2)
self.play(FadeOut(hint_group))
class SimpleBlockTransition(Scene):
"""
Simpler 2D version showing block transitions.
"""
def construct(self):
# Create token representations
n_tokens = 6
tokens = VGroup()
for i in range(n_tokens):
token = VGroup()
# Colored rectangle
rect = Rectangle(width=0.8, height=2.5)
rect.set_fill(BLUE, opacity=0.3)
rect.set_stroke(BLUE, 2)
# Inner value indicators
for j in range(5):
small_rect = Rectangle(width=0.6, height=0.35)
small_rect.set_fill(value_to_color(random.uniform(-10, 10)), opacity=0.8)
small_rect.set_stroke(WHITE, 0.5)
token.add(small_rect)
token.arrange(DOWN, buff=0.05)
tokens.add(token)
tokens.arrange(RIGHT, buff=0.5)
tokens.to_edge(LEFT, buff=1)
# Block representations
att_block = self.create_2d_block("Attention", BLUE_D)
mlp_block = self.create_2d_block("Feedforward", GREEN_D)
att_block.next_to(tokens, RIGHT, buff=1.5)
mlp_block.next_to(att_block, RIGHT, buff=2)
# Arrows
arrow1 = Arrow(tokens.get_right(), att_block.get_left(), buff=0.2)
arrow2 = Arrow(att_block.get_right(), mlp_block.get_left(), buff=0.2)
# Labels
input_label = Text("Input\nEmbeddings", font_size=24)
input_label.next_to(tokens, DOWN)
# Animate
self.play(LaggedStartMap(FadeIn, tokens, shift=UP, lag_ratio=0.1))
self.play(FadeIn(input_label))
self.wait()
self.play(
GrowArrow(arrow1),
FadeIn(att_block, shift=RIGHT),
)
self.wait()
self.play(
GrowArrow(arrow2),
FadeIn(mlp_block, shift=RIGHT),
)
self.wait()
# Show data flow animation
for _ in range(2):
self.play(
VShowPassingFlash(arrow1.copy().set_stroke(YELLOW, 4), time_width=0.5),
VShowPassingFlash(arrow2.copy().set_stroke(YELLOW, 4), time_width=0.5),
run_time=1.5
)
self.wait()
def create_2d_block(self, label_text, color):
"""Create a 2D block representation."""
rect = RoundedRectangle(width=2.5, height=3, corner_radius=0.2)
rect.set_fill(color, opacity=0.5)
rect.set_stroke(color, 3)
label = Text(label_text, font_size=28)
label.move_to(rect)
return VGroup(rect, label)
examples/neural_network_basic.py
"""
Basic Neural Network visualization with animated connections and layers.
Demonstrates: Custom VGroup class, randomized styling, layer-based animation
"""
from manimlib import *
import numpy as np
import random
import itertools as it
def value_to_color(
value,
low_positive_color=BLUE_E,
high_positive_color=BLUE_B,
low_negative_color=RED_E,
high_negative_color=RED_B,
min_value=0.0,
max_value=10.0
):
"""Map a numeric value to a color gradient."""
alpha = clip(float(inverse_interpolate(min_value, max_value, abs(value))), 0, 1)
if value >= 0:
colors = (low_positive_color, high_positive_color)
else:
colors = (low_negative_color, high_negative_color)
return interpolate_color_by_hsl(*colors, alpha)
class NeuralNetwork(VGroup):
"""A simple neural network visualization with layers and connections."""
def __init__(
self,
layer_sizes=[6, 12, 6],
neuron_radius=0.1,
v_buff_ratio=1.0,
h_buff_ratio=7.0,
max_stroke_width=2.0,
stroke_decay=2.0,
):
self.max_stroke_width = max_stroke_width
self.stroke_decay = stroke_decay
# Create neuron layers
layers = VGroup(*(
Dot(radius=neuron_radius).get_grid(n, 1, v_buff_ratio=v_buff_ratio)
for n in layer_sizes
))
layers.arrange(RIGHT, buff=h_buff_ratio * layers[0].get_width())
# Create connections between layers
lines = VGroup(*(
VGroup(*(
Line(
n1.get_center(),
n2.get_center(),
buff=n1.get_width() / 2,
)
for n1, n2 in it.product(l1, l2)
))
for l1, l2 in zip(layers, layers[1:])
))
super().__init__(layers, lines)
self.layers = layers
self.lines = lines
self.randomize_layer_values()
self.randomize_line_style()
def randomize_layer_values(self):
"""Randomize the fill opacity of neurons."""
for layer in self.layers:
for dot in layer:
dot.set_stroke(WHITE, 1)
dot.set_fill(WHITE, random.random())
return self
def randomize_line_style(self):
"""Randomize connection colors and widths."""
for group in self.lines:
for line in group:
line.set_stroke(
value_to_color(random.uniform(-10, 10)),
self.max_stroke_width * random.random()**self.stroke_decay,
)
return self
class NeuralNetworkBasic(Scene):
def construct(self):
# Title
title = Text("Neural Network", font_size=60)
title.to_edge(UP)
# Create neural network
network = NeuralNetwork([5, 10, 5])
network.set_height(5)
network.center()
self.play(FadeIn(title, shift=DOWN))
self.wait(0.5)
# Animate layers appearing
self.play(
FadeIn(network.layers[0]),
ShowCreation(network.lines[0], lag_ratio=0.01),
FadeIn(network.layers[1], lag_ratio=0.5),
run_time=2
)
self.play(
ShowCreation(network.lines[1], lag_ratio=0.01),
FadeIn(network.layers[2], lag_ratio=0.5),
run_time=2
)
# Ambiently change the network
for _ in range(4):
self.play(
network.animate.randomize_line_style().randomize_layer_values(),
run_time=2,
lag_ratio=1e-4
)
# Add labels for layers
input_label = Text("Input", font_size=36)
hidden_label = Text("Hidden", font_size=36)
output_label = Text("Output", font_size=36)
input_label.next_to(network.layers[0], DOWN)
hidden_label.next_to(network.layers[1], DOWN)
output_label.next_to(network.layers[2], DOWN)
self.play(LaggedStart(
FadeIn(input_label, shift=UP),
FadeIn(hidden_label, shift=UP),
FadeIn(output_label, shift=UP),
lag_ratio=0.3
))
self.wait()
# Final animation
for _ in range(2):
self.play(
network.animate.randomize_line_style().randomize_layer_values(),
run_time=2,
)
self.wait()
examples/parallax_starfield.py
"""
Parallax Effect with 3D Starfield
Demonstrates the parallax effect - how nearby objects appear to move more
than distant objects when the observer moves. This is a fundamental concept
in astronomy for measuring distances to stars.
Run: manimgl parallax_starfield.py ParallaxStarfield -w
Preview: manimgl parallax_starfield.py ParallaxStarfield -p
Source: Adapted from 3b1b's cosmic_distance video (2025)
"""
from manimlib import *
import numpy as np
class ParallaxStarfield(InteractiveScene):
"""
A 3D scene showing parallax effect with stars at different distances.
Key techniques demonstrated:
- GlowDots for efficient star rendering
- 3D camera manipulation with frame.animate.reorient()
- VCube as a visual reference box
- Observer movement to demonstrate parallax
"""
def construct(self):
# Setup 3D environment
frame = self.frame
self.set_floor_plane("xz") # Set z as vertical axis
# Create a reference cube to help visualize 3D space
height = 4
cube = VCube(height)
cube.set_fill(opacity=0)
cube.set_stroke(BLUE, 2)
# Create stars as GlowDots - efficient for many point lights
n_stars = 200
# Random positions in a cube
star_positions = np.random.uniform(-1, 1, (n_stars, 3))
stars = GlowDots(star_positions)
stars.scale(height / 2) # Scale to fit within our cube
stars.set_color(WHITE)
stars.set_glow_factor(2)
# Vary star sizes for visual interest
stars.set_radii(np.random.uniform(0, 0.075, n_stars))
self.add(cube)
self.add(stars)
# Animate stars appearing
self.play(ShowCreation(stars, run_time=3))
# Add an observer (using a simple 3D sphere)
observer = Sphere(radius=0.3)
observer.set_color(BLUE_E)
observer.set_shading(0.5, 0.5, 0.5)
observer.next_to(cube, LEFT, buff=1)
# Add an arrow to show viewing direction
eye_arrow = Arrow(
observer.get_center(),
observer.get_center() + 1.5 * RIGHT,
buff=0,
stroke_color=YELLOW,
stroke_width=4,
)
eye_arrow.add_updater(lambda m: m.put_start_and_end_on(
observer.get_center(),
observer.get_center() + 1.5 * RIGHT
))
self.play(
FadeIn(observer),
ShowCreation(eye_arrow),
)
# Rotate camera for better 3D view
self.play(frame.animate.reorient(-40, -26, 0), run_time=2)
# Key demonstration: Move observer up and down
# Watch how nearby stars shift more than distant ones
for dy in [1.5, -3, 3, -3, 1.5]:
self.play(
observer.animate.shift(dy * IN), # IN = into screen = Z axis
run_time=3
)
self.wait()
class ParallaxFromObserverPOV(InteractiveScene):
"""
Same parallax demo but from the observer's point of view.
This variant shows what the observer would actually see -
the apparent motion of stars against the background.
"""
def construct(self):
frame = self.frame
self.set_floor_plane("xz")
# Create starfield
height = 4
cube = VCube(height)
cube.set_fill(opacity=0)
cube.set_stroke(BLUE, 2)
n_stars = 200
star_positions = np.random.uniform(-1, 1, (n_stars, 3))
stars = GlowDots(star_positions)
stars.scale(height / 2)
stars.set_color(WHITE)
stars.set_glow_factor(2)
stars.set_radii(np.random.uniform(0, 0.075, n_stars))
self.add(cube, stars)
self.play(ShowCreation(stars, run_time=2))
# Add observer as a tracking point
observer = Sphere(radius=0.3)
observer.set_color(BLUE_E)
observer.next_to(cube, LEFT, buff=1)
self.play(FadeIn(observer))
# Move camera to observer's perspective
self.play(
frame.animate.reorient(-89, -4, 0, (0.01, 0.21, 0.0), 3.05),
observer.animate.set_opacity(0),
cube.animate.set_stroke(width=5).set_anti_alias_width(10),
run_time=3,
)
# Camera follows observer's z position
frame.always.match_z(observer)
# Move observer - camera follows, showing parallax from their view
for dy in [1.5, -3, 3, -3, 1.5]:
self.play(observer.animate.shift(dy * IN), run_time=4)
self.wait()
class LayeredParallax(InteractiveScene):
"""
Demonstrates parallax with explicitly layered star planes.
Shows three distinct layers at different distances to make
the parallax effect more obvious and educational.
"""
def construct(self):
frame = self.frame
self.set_floor_plane("xz")
# Create three layers of stars at different distances
layers = []
colors = [RED, YELLOW, BLUE]
distances = [2, 5, 10] # Distance from origin
n_stars_per_layer = 50
for color, dist in zip(colors, distances):
# Create stars in an XY plane at distance Z
positions = np.random.uniform(-3, 3, (n_stars_per_layer, 3))
positions[:, 2] = dist # Set all Z to this layer's distance
layer = GlowDots(positions)
layer.set_color(color)
layer.set_glow_factor(1.5)
layer.set_radii(np.full(n_stars_per_layer, 0.05))
layers.append(layer)
all_stars = Group(*layers)
# Add distance labels
labels = VGroup()
for color, dist in zip(colors, distances):
label = Text(f"{dist} units away", color=color, font_size=24)
label.to_corner(UL)
label.shift(DOWN * (distances.index(dist) * 0.5))
labels.add(label)
self.add(all_stars, labels)
# Position camera to see all layers
frame.reorient(-30, -20, 0)
frame.set_height(12)
# Create observer dot
observer = Sphere(radius=0.2)
observer.set_color(GREEN)
observer.move_to(ORIGIN)
self.add(observer)
self.wait()
# Move observer laterally - watch the layers shift differently
for dx in [2, -4, 4, -2]:
self.play(
observer.animate.shift(dx * RIGHT),
run_time=3,
rate_func=smooth
)
self.wait()
examples/probability_distribution.py
"""
Probability Distribution Visualization
======================================
Visualizes how a quantum state vector maps to a probability distribution
through the Born rule (amplitude squared = probability).
Key concepts demonstrated:
- DecimalMatrix for state vector display
- Rectangle bars for probability visualization
- always_redraw for reactive updates
- LaggedStartMap for sequential animations
"""
from manimlib import *
class ProbabilityDistribution(InteractiveScene):
"""Shows how state vector amplitudes become probabilities."""
def construct(self):
# Title
title = Text("State Vector to Probability", font_size=48)
title.to_edge(UP)
self.add(title)
# Create a simple 4-state vector (2 qubit system)
state = normalize(np.array([1, 2, 0.5, 1.5]))
# State vector display
state_vector = DecimalMatrix(
state.reshape((4, 1)),
decimal_config=dict(include_sign=True, num_decimal_places=2)
)
state_vector.scale(0.8)
state_vector.shift(3 * LEFT)
vector_label = Text("State Vector", font_size=30)
vector_label.next_to(state_vector, UP)
# Bit string labels
bit_labels = VGroup(
Tex(R"|00\rangle", font_size=30),
Tex(R"|01\rangle", font_size=30),
Tex(R"|10\rangle", font_size=30),
Tex(R"|11\rangle", font_size=30),
)
bit_labels.set_color(GREY_B)
for bits, entry in zip(bit_labels, state_vector.get_entries()):
bits.next_to(state_vector, LEFT, buff=0.3)
bits.match_y(entry)
self.add(state_vector, vector_label, bit_labels)
# Arrow with transformation rule
arrow = Arrow(LEFT, RIGHT, thickness=5)
arrow.next_to(state_vector, RIGHT, buff=0.5)
rule = Tex(R"|\alpha|^2", font_size=36)
rule.next_to(arrow, UP, SMALL_BUFF)
self.play(GrowArrow(arrow), Write(rule))
# Probability bars
probs = state ** 2
max_bar_width = 3.0
bar_labels = VGroup(
Tex(R"|00\rangle", font_size=30),
Tex(R"|01\rangle", font_size=30),
Tex(R"|10\rangle", font_size=30),
Tex(R"|11\rangle", font_size=30),
)
bar_labels.arrange(DOWN, buff=0.5)
bar_labels.next_to(arrow, RIGHT, buff=1.0)
bars = VGroup()
prob_labels = VGroup()
for i, (label, prob) in enumerate(zip(bar_labels, probs)):
bar = Rectangle(
width=prob * max_bar_width,
height=0.4
)
bar.next_to(label, RIGHT, buff=0.2)
bar.set_fill(
interpolate_color(BLUE_D, GREEN, prob),
opacity=1
)
bar.set_stroke(WHITE, 1)
pct = Integer(int(100 * prob), unit=R"\%", font_size=24)
pct.next_to(bar, RIGHT, SMALL_BUFF)
bars.add(bar)
prob_labels.add(pct)
# Animate bars appearing
self.play(
FadeIn(bar_labels),
LaggedStart(
(GrowFromEdge(bar, LEFT)
for bar in bars),
lag_ratio=0.2
),
LaggedStartMap(FadeIn, prob_labels, lag_ratio=0.2),
run_time=2
)
# Add sum constraint
sum_eq = Tex(
R"\sum_i |\alpha_i|^2 = 1",
font_size=30
)
sum_eq.to_edge(DOWN, buff=1.0)
sum_eq.set_color(YELLOW)
self.play(Write(sum_eq))
self.wait(2)
class DynamicStateVector(InteractiveScene):
"""Shows state vector evolving and probabilities updating in real-time."""
def construct(self):
# Set up state tracker
n_states = 8
phase_trackers = [ValueTracker(np.random.uniform(0, TAU)) for _ in range(n_states)]
def get_state():
"""Generate a normalized state from phases."""
raw = np.array([
np.sin(tracker.get_value())
for tracker in phase_trackers
])
return normalize(raw + 0.1)
# Create layout
# Left: Quantum computer symbol
qc_symbol = VGroup(
Square(1.5).set_stroke(TEAL, 2).set_fill(GREY_E, 1),
Tex(R"|Q\rangle", color=TEAL).scale(0.8)
)
qc_symbol[1].move_to(qc_symbol[0])
qc_symbol.shift(4 * LEFT)
# Middle: State vector
state_vector = DecimalMatrix(
np.zeros((n_states, 1)),
decimal_config=dict(include_sign=True, num_decimal_places=2)
)
state_vector.scale(0.5)
state_vector.center()
def update_state_vector(matrix):
state = get_state()
for elem, val in zip(matrix.elements, state):
elem.set_value(val)
state_vector.add_updater(update_state_vector)
# Bit labels
bit_labels = VGroup(
Tex(R"|" + bin(n)[2:].zfill(3) + R"\rangle", font_size=20)
for n in range(n_states)
)
bit_labels.set_color(GREY_C)
def update_bit_labels(labels):
for bits, entry in zip(labels, state_vector.get_entries()):
bits.next_to(state_vector, LEFT, buff=0.15)
bits.match_y(entry)
bit_labels.add_updater(update_bit_labels)
# Right: Probability bars
qubit_labels = VGroup(
Tex(R"|" + bin(n)[2:].zfill(3) + R"\rangle", font_size=24)
for n in range(n_states)
)
qubit_labels.arrange(DOWN, buff=0.25)
qubit_labels.shift(2.5 * RIGHT)
def get_prob_bars():
probs = get_state() ** 2
bars = VGroup()
for qubit, prob in zip(qubit_labels, probs):
bar = Rectangle(
width=prob * 4,
height=qubit.get_height() * 0.8
)
bar.next_to(qubit, RIGHT, buff=0.15)
bar.set_fill(
interpolate_color(BLUE_D, GREEN, prob * 1.5),
opacity=1
)
bar.set_stroke(WHITE, 1)
bars.add(bar)
return bars
prob_bars = always_redraw(get_prob_bars)
# Arrow connecting state vector to probabilities
arrow = Arrow(state_vector.get_right() + 0.3 * RIGHT,
qubit_labels.get_left() + 0.3 * LEFT,
thickness=4)
arrow_label = Tex(R"|\cdot|^2", font_size=24)
arrow_label.next_to(arrow, UP, SMALL_BUFF)
# Add all elements
self.add(qc_symbol, state_vector, bit_labels)
self.add(arrow, arrow_label)
self.add(qubit_labels, prob_bars)
# Animate state evolution
animations = [
tracker.animate.set_value(tracker.get_value() + np.random.uniform(2, 5) * TAU)
for tracker in phase_trackers
]
self.play(
*animations,
run_time=10,
rate_func=linear
)
self.wait()
class BornRuleExplanation(InteractiveScene):
"""Explains the Born rule for quantum measurement."""
def construct(self):
# Title
title = Text("The Born Rule", font_size=60)
title.to_edge(UP)
self.add(title)
# The rule
rule = Tex(
R"P(i) = |\langle i | \psi \rangle|^2 = |\alpha_i|^2",
font_size=48
)
rule.next_to(title, DOWN, buff=1.0)
self.play(Write(rule))
self.wait()
# Explanation
explanation = VGroup(
Tex(R"\alpha_i \text{ = amplitude for state } |i\rangle", font_size=30),
Tex(R"|\alpha_i|^2 \text{ = probability of measuring } |i\rangle", font_size=30),
Tex(R"\sum_i |\alpha_i|^2 = 1 \text{ (normalization)}", font_size=30),
)
explanation.arrange(DOWN, aligned_edge=LEFT, buff=0.5)
explanation.next_to(rule, DOWN, buff=1.0)
for line in explanation:
self.play(FadeIn(line, shift=RIGHT))
self.wait(0.5)
self.wait()
# Visual example
example_title = Text("Example:", font_size=36)
example_title.next_to(explanation, DOWN, buff=1.0)
example_title.to_edge(LEFT, buff=1.0)
state = Tex(
R"|\psi\rangle = \frac{1}{\sqrt{2}}|0\rangle + \frac{1}{\sqrt{2}}|1\rangle",
font_size=36
)
state.next_to(example_title, RIGHT, buff=0.5)
probs = VGroup(
Tex(R"P(0) = \left|\frac{1}{\sqrt{2}}\right|^2 = \frac{1}{2}", font_size=30),
Tex(R"P(1) = \left|\frac{1}{\sqrt{2}}\right|^2 = \frac{1}{2}", font_size=30),
)
probs.arrange(DOWN, aligned_edge=LEFT, buff=0.3)
probs.next_to(state, DOWN, buff=0.5)
self.play(Write(example_title), Write(state))
self.wait()
self.play(LaggedStartMap(FadeIn, probs, lag_ratio=0.3))
self.wait(2)
class GroverAmplification(InteractiveScene):
"""Visualizes amplitude amplification in Grover's algorithm."""
def construct(self):
# Title
title = Text("Grover's Amplitude Amplification", font_size=48)
title.to_edge(UP)
self.add(title)
# Create bar chart for amplitudes
n_states = 8
target = 5 # The "marked" state
# Initial uniform state
initial_amps = np.ones(n_states) / np.sqrt(n_states)
def create_bars(amps, highlighted=None):
bars = VGroup()
labels = VGroup()
for i, amp in enumerate(amps):
bar = Rectangle(
width=0.5,
height=amp * 4
)
bar.set_fill(
YELLOW if i == highlighted else BLUE_D,
opacity=1
)
bar.set_stroke(WHITE, 1)
bars.add(bar)
label = Tex(R"|" + bin(i)[2:].zfill(3) + R"\rangle", font_size=16)
labels.add(label)
bars.arrange(RIGHT, buff=0.2, aligned_edge=DOWN)
bars.center().shift(DOWN)
for bar, label in zip(bars, labels):
label.next_to(bar, DOWN, SMALL_BUFF)
return VGroup(bars, labels)
# Show initial state
bar_chart = create_bars(initial_amps)
step_label = Text("Initial: Uniform Superposition", font_size=30)
step_label.next_to(bar_chart, UP, buff=0.5)
self.play(
LaggedStartMap(GrowFromEdge, bar_chart[0], edge=DOWN, lag_ratio=0.1),
FadeIn(bar_chart[1]),
Write(step_label)
)
self.wait()
# Grover iterations
amps = initial_amps.copy()
for iteration in range(3):
# Oracle: flip amplitude of target
amps[target] *= -1
# Show oracle step
new_bars = create_bars(np.abs(amps), target)
new_bars[0][target].set_fill(RED)
oracle_label = Text(f"Step {iteration * 2 + 1}: Oracle (flip target)", font_size=30)
oracle_label.next_to(bar_chart, UP, buff=0.5)
self.play(
Transform(bar_chart[0], new_bars[0]),
Transform(step_label, oracle_label)
)
self.wait()
# Diffusion: reflect about mean
mean = np.mean(amps)
amps = 2 * mean - amps
new_bars = create_bars(amps, target)
diffusion_label = Text(f"Step {iteration * 2 + 2}: Diffusion (amplify)", font_size=30)
diffusion_label.next_to(bar_chart, UP, buff=0.5)
self.play(
Transform(bar_chart[0], new_bars[0]),
Transform(step_label, diffusion_label)
)
self.wait()
# Final result
final_label = Text("Result: High probability for target state!", font_size=30, color=GREEN)
final_label.next_to(bar_chart, UP, buff=0.5)
self.play(Transform(step_label, final_label))
# Highlight target
rect = SurroundingRectangle(
VGroup(bar_chart[0][target], bar_chart[1][target]),
buff=0.1,
color=YELLOW
)
self.play(ShowCreation(rect))
self.wait(2)
if __name__ == "__main__":
# To run: manimgl probability_distribution.py ProbabilityDistribution
pass
examples/probability_output.py
"""
Probability Output Visualization
Shows how the network produces probability distributions over possible next tokens.
Based on 3Blue1Brown's transformer visualizations.
Run: manimgl probability_output.py ProbabilityOutput -o
"""
from manimlib import *
import numpy as np
import random
def value_to_color(
value,
low_positive_color=BLUE_E,
high_positive_color=BLUE_B,
low_negative_color=RED_E,
high_negative_color=RED_B,
min_value=0.0,
max_value=10.0
):
"""Map a value to a color based on its sign and magnitude."""
alpha = np.clip(float((abs(value) - min_value) / (max_value - min_value)), 0, 1)
if value >= 0:
return interpolate_color(low_positive_color, high_positive_color, alpha)
else:
return interpolate_color(low_negative_color, high_negative_color, alpha)
def softmax(logits, temperature=1.0):
"""Compute softmax of logits."""
logits = np.array(logits) / temperature
logits = logits - np.max(logits)
exps = np.exp(logits)
return exps / np.sum(exps)
class ProbabilityOutput(Scene):
"""
Demonstrates how the final layer outputs probability distributions.
Shows the transformation from embedding vector to probabilities over vocabulary.
"""
# Example predictions
possible_next_tokens = [
("the", 0.35),
("a", 0.25),
("an", 0.15),
("this", 0.10),
("that", 0.08),
("some", 0.04),
("my", 0.02),
("...", 0.01),
]
def construct(self):
# Title
title = Text("Network Output: Probability Distribution", font_size=44)
title.to_edge(UP)
self.play(Write(title))
self.wait()
# Show the prompt
prompt = Text('Input: "The cat sat on"', font_size=36)
prompt.next_to(title, DOWN, buff=0.5)
self.play(FadeIn(prompt, shift=DOWN))
self.wait()
# Create final embedding vector
vector = self.create_embedding_vector()
vector.to_edge(LEFT, buff=1.5)
vector.shift(0.5 * DOWN)
vector_label = Text("Final\nembedding", font_size=24)
vector_label.next_to(vector, DOWN)
self.play(
FadeIn(vector, shift=RIGHT),
FadeIn(vector_label),
)
self.wait()
# Arrow to probabilities
arrow = Arrow(vector.get_right(), vector.get_right() + 2 * RIGHT, buff=0.2)
arrow.set_color(YELLOW)
softmax_label = Text("softmax", font_size=28)
softmax_label.next_to(arrow, UP, buff=0.1)
self.play(
GrowArrow(arrow),
FadeIn(softmax_label),
)
# Create probability bars
prob_group = self.create_probability_bars()
prob_group.next_to(arrow, RIGHT, buff=0.5)
self.play(
LaggedStartMap(FadeIn, prob_group, shift=0.3 * RIGHT, lag_ratio=0.1),
run_time=2
)
self.wait()
# Highlight top prediction
highlight = SurroundingRectangle(prob_group[0], buff=0.1)
highlight.set_stroke(GREEN, 3)
prediction_label = Text('Prediction: "the"', font_size=36, color=GREEN)
prediction_label.next_to(prob_group, DOWN, buff=0.8)
self.play(ShowCreation(highlight))
self.play(FadeIn(prediction_label, shift=UP))
self.wait()
# Show this is a distribution
dist_note = Text("This is a probability distribution over ~50,000 tokens", font_size=28)
dist_note.next_to(prediction_label, DOWN, buff=0.5)
self.play(FadeIn(dist_note, shift=UP))
self.wait(2)
# Cleanup
self.play(FadeOut(VGroup(
title, prompt, vector, vector_label,
arrow, softmax_label, prob_group,
highlight, prediction_label, dist_note
)))
def create_embedding_vector(self, length=12, height=4.0):
"""Create a visual embedding vector."""
entries = VGroup()
entry_height = (height / length) * 0.85
for _ in range(length):
value = random.uniform(-9.9, 9.9)
rect = Rectangle(width=0.4, height=entry_height)
rect.set_fill(value_to_color(value), opacity=0.9)
rect.set_stroke(WHITE, 0.5)
entries.add(rect)
entries.arrange(DOWN, buff=0.02)
# Brackets
lb = Text("[", font_size=96)
rb = Text("]", font_size=96)
lb.stretch_to_fit_height(height * 1.1)
rb.stretch_to_fit_height(height * 1.1)
lb.set_color(GREY_B)
rb.set_color(GREY_B)
lb.next_to(entries, LEFT, buff=0.05)
rb.next_to(entries, RIGHT, buff=0.05)
return VGroup(lb, entries, rb)
def create_probability_bars(self):
"""Create probability bar chart."""
bars_group = VGroup()
for word, prob in self.possible_next_tokens:
# Bar
bar = Rectangle(
width=4 * prob,
height=0.4,
)
bar.set_fill(interpolate_color(BLUE_E, BLUE_B, prob), opacity=0.8)
bar.set_stroke(WHITE, 1)
# Word label
word_label = Text(word, font_size=24)
word_label.next_to(bar, LEFT, buff=0.2)
# Probability label
prob_label = Text(f"{prob:.0%}", font_size=20)
prob_label.next_to(bar, RIGHT, buff=0.1)
row = VGroup(word_label, bar, prob_label)
bars_group.add(row)
bars_group.arrange(DOWN, buff=0.15, aligned_edge=LEFT)
# Align bars
for row in bars_group:
row[1].align_to(bars_group[0][1], LEFT)
return bars_group
class SoftmaxVisualization(Scene):
"""
Shows the softmax transformation turning logits into probabilities.
"""
def construct(self):
# Title
title = Text("Softmax: Logits to Probabilities", font_size=48)
title.to_edge(UP)
self.play(Write(title))
# Create logits
logits = [2.5, 1.8, 1.2, 0.5, 0.1, -0.3, -1.0, -2.0]
probs = softmax(logits)
# Logits bars
logit_bars = self.create_bars(logits, max_val=3.0, color=RED)
logit_bars.to_edge(LEFT, buff=1)
logit_label = Text("Logits (raw scores)", font_size=28)
logit_label.next_to(logit_bars, DOWN)
# Probability bars
prob_bars = self.create_bars(probs * 10, max_val=10, color=BLUE)
prob_bars.to_edge(RIGHT, buff=1)
prob_label = Text("Probabilities", font_size=28)
prob_label.next_to(prob_bars, DOWN)
# Arrow with softmax
arrow = Arrow(logit_bars.get_right(), prob_bars.get_left(), buff=0.3)
softmax_text = Tex(r"\text{softmax}", font_size=36)
softmax_text.next_to(arrow, UP)
# Animate
self.play(FadeIn(logit_bars, shift=RIGHT))
self.play(FadeIn(logit_label))
self.wait()
self.play(
GrowArrow(arrow),
FadeIn(softmax_text),
)
self.play(TransformFromCopy(logit_bars, prob_bars))
self.play(FadeIn(prob_label))
self.wait()
# Show formula
formula = Tex(
r"\text{softmax}(x_i) = \frac{e^{x_i}}{\sum_j e^{x_j}}",
font_size=36
)
formula.next_to(arrow, DOWN, buff=1)
self.play(Write(formula))
self.wait(2)
# Cleanup
self.play(FadeOut(VGroup(
title, logit_bars, logit_label,
arrow, softmax_text, prob_bars, prob_label, formula
)))
def create_bars(self, values, max_val=1.0, color=BLUE):
"""Create a group of horizontal bars."""
bars = VGroup()
for val in values:
# Normalize width
width = max(0.1, abs(val) / max_val * 3)
bar = Rectangle(width=width, height=0.3)
if val >= 0:
bar.set_fill(color, opacity=0.7)
else:
bar.set_fill(RED, opacity=0.7)
bar.set_stroke(WHITE, 1)
bars.add(bar)
bars.arrange(DOWN, buff=0.1, aligned_edge=LEFT)
return bars
class VocabProjection(Scene):
"""
Shows the unembedding matrix projecting to vocabulary space.
"""
def construct(self):
# Title
title = Text("Projecting to Vocabulary Space", font_size=44)
title.to_edge(UP)
self.play(Write(title))
# Embedding vector (small)
emb_entries = VGroup(*(
Rectangle(width=0.3, height=0.25).set_fill(
value_to_color(random.uniform(-10, 10)), opacity=0.9
).set_stroke(WHITE, 0.5)
for _ in range(10)
))
emb_entries.arrange(DOWN, buff=0.02)
emb_bracket_l = Text("[", font_size=72).stretch_to_fit_height(emb_entries.get_height() * 1.1)
emb_bracket_r = Text("]", font_size=72).stretch_to_fit_height(emb_entries.get_height() * 1.1)
emb_bracket_l.next_to(emb_entries, LEFT, buff=0.05)
emb_bracket_r.next_to(emb_entries, RIGHT, buff=0.05)
embedding = VGroup(emb_bracket_l, emb_entries, emb_bracket_r)
embedding.scale(0.8)
embedding.to_edge(LEFT, buff=1)
embedding.shift(0.5 * DOWN)
emb_label = Text("Embedding\n(d dims)", font_size=24)
emb_label.next_to(embedding, DOWN)
# Matrix (wide)
matrix = self.create_matrix(rows=8, cols=10)
matrix.next_to(embedding, RIGHT, buff=1)
matrix_label = Text("Unembedding\nMatrix", font_size=24)
matrix_label.next_to(matrix, DOWN)
# Result (vocab sized)
result_entries = VGroup(*(
Rectangle(width=0.25, height=0.2).set_fill(
value_to_color(random.uniform(-10, 10)), opacity=0.9
).set_stroke(WHITE, 0.5)
for _ in range(8)
))
result_entries.arrange(DOWN, buff=0.02)
result_bracket_l = Text("[", font_size=72).stretch_to_fit_height(result_entries.get_height() * 1.1)
result_bracket_r = Text("]", font_size=72).stretch_to_fit_height(result_entries.get_height() * 1.1)
result_bracket_l.next_to(result_entries, LEFT, buff=0.05)
result_bracket_r.next_to(result_entries, RIGHT, buff=0.05)
result = VGroup(result_bracket_l, result_entries, result_bracket_r)
result.scale(0.8)
result.next_to(matrix, RIGHT, buff=0.8)
result_label = Text("Logits\n(~50k)", font_size=24)
result_label.next_to(result, DOWN)
# Multiply symbol
times = Tex(r"\times", font_size=48)
times.move_to(midpoint(embedding.get_right(), matrix.get_left()))
equals = Tex("=", font_size=48)
equals.move_to(midpoint(matrix.get_right(), result.get_left()))
# Animate
self.play(FadeIn(embedding, shift=RIGHT), FadeIn(emb_label))
self.play(FadeIn(times))
self.play(FadeIn(matrix, scale=0.9), FadeIn(matrix_label))
self.play(FadeIn(equals))
self.play(FadeIn(result, shift=LEFT), FadeIn(result_label))
self.wait()
# Formula
formula = Tex(r"W_U \cdot \text{emb} = \text{logits}", font_size=36)
formula.next_to(VGroup(embedding, matrix, result), UP, buff=0.8)
self.play(Write(formula))
self.wait(2)
# Cleanup
self.play(FadeOut(VGroup(
title, embedding, emb_label, times, matrix, matrix_label,
equals, result, result_label, formula
)))
def create_matrix(self, rows=6, cols=8):
"""Create a visual matrix."""
entries = VGroup()
for i in range(rows):
row = VGroup()
for j in range(cols):
value = random.uniform(-10, 10)
rect = Rectangle(width=0.25, height=0.25)
rect.set_fill(value_to_color(value), opacity=0.8)
rect.set_stroke(WHITE, 0.3)
row.add(rect)
row.arrange(RIGHT, buff=0.02)
entries.add(row)
entries.arrange(DOWN, buff=0.02)
# Brackets
lb = Text("[", font_size=72)
rb = Text("]", font_size=72)
lb.stretch_to_fit_height(entries.get_height() * 1.1)
rb.stretch_to_fit_height(entries.get_height() * 1.1)
lb.next_to(entries, LEFT, buff=0.05)
rb.next_to(entries, RIGHT, buff=0.05)
return VGroup(lb, entries, rb)
examples/quantum_gates.py
"""
Quantum Gates Visualization
===========================
Demonstrates quantum gate operations (H, X, Z) as reflections/rotations
of the state vector on the qubit plane.
Key concepts demonstrated:
- DashedLine for reflection axes
- Rotate animation with custom axis
- Gate labels and transitions
- Multiple gate applications
"""
from manimlib import *
class QuantumGatesVisualization(InteractiveScene):
"""Shows how quantum gates transform qubit states."""
def construct(self):
# Title
title = Text("Quantum Gates", font_size=60)
title.to_edge(UP)
self.add(title)
# Set up the qubit plane
plane = NumberPlane((-2, 2), (-2, 2), faded_line_ratio=5)
plane.set_height(5)
plane.center().shift(0.5 * DOWN)
# Qubit labels
zero_label = Tex(R"|0\rangle").scale(0.7)
zero_label.next_to(plane.c2p(1, 0), DR, SMALL_BUFF)
one_label = Tex(R"|1\rangle").scale(0.7)
one_label.next_to(plane.c2p(0, 1), UL, SMALL_BUFF)
# Unit circle
circle = Circle(radius=plane.c2p(1, 0)[0] - plane.c2p(0, 0)[0])
circle.move_to(plane.c2p(0, 0))
circle.set_stroke(GREY, 1, 0.5)
self.add(plane, circle, zero_label, one_label)
# Create the state vector
vector = Arrow(
plane.c2p(0, 0),
plane.c2p(1, 0),
buff=0,
thickness=5,
fill_color=TEAL
)
self.add(vector)
# Define gate reflection axes
# Z gate: reflection about x-axis (horizontal)
# H gate: reflection about 22.5 degree line
# X gate: reflection about 45 degree line (diagonal)
gate_info = [
("Z", 0, BLUE),
("H", PI / 8, YELLOW),
("X", PI / 4, RED),
]
gate_lines = VGroup()
gate_labels = VGroup()
for name, angle, color in gate_info:
line = DashedLine(2 * LEFT, 2 * RIGHT)
line.rotate(angle)
line.move_to(plane.c2p(0, 0))
line.set_stroke(color, 2)
label = Text(name + " gate", font_size=24, color=color)
label.next_to(plane.c2p(1, 1), DR)
gate_lines.add(line)
gate_labels.add(label)
# Apply gates in sequence
gate_sequence = [1, 0, 2, 1, 2, 1, 0, 1] # H, Z, X, H, X, H, Z, H
for i in gate_sequence:
name, angle, color = gate_info[i]
line = gate_lines[i]
label = gate_labels[i]
# Show the gate axis and label
self.play(
FadeIn(line),
FadeIn(label),
run_time=0.5
)
# Rotate vector by 180 degrees about the axis
axis = rotate_vector(RIGHT, angle)
axis_3d = np.array([axis[0], axis[1], 0])
self.play(
Rotate(
vector,
PI,
axis=axis_3d,
about_point=plane.c2p(0, 0)
),
run_time=1.5
)
# Hide the gate visualization
self.play(
FadeOut(line),
FadeOut(label),
run_time=0.3
)
self.wait()
class HadamardGateDetail(InteractiveScene):
"""Detailed visualization of the Hadamard gate transformation."""
def construct(self):
# Set up two planes: before and after
plane1 = NumberPlane((-2, 2), (-2, 2), faded_line_ratio=5)
plane1.set_height(4)
plane2 = plane1.copy()
planes = VGroup(plane1, plane2)
planes.arrange(RIGHT, buff=3)
planes.center()
# Labels
before_label = Text("Before H", font_size=36)
before_label.next_to(plane1, UP)
after_label = Text("After H", font_size=36)
after_label.next_to(plane2, UP)
# Arrow between planes
arrow = Arrow(plane1.get_right(), plane2.get_left(), thickness=5)
h_label = Text("H", font_size=48, color=YELLOW)
h_label.next_to(arrow, UP, SMALL_BUFF)
# Hadamard matrix
matrix_tex = Tex(
R"\frac{1}{\sqrt{2}} \begin{bmatrix} 1 & 1 \\ 1 & -1 \end{bmatrix}",
font_size=30
)
matrix_tex.set_fill(GREY_B)
matrix_tex.next_to(arrow, DOWN, SMALL_BUFF)
self.add(planes, before_label, after_label, arrow, h_label, matrix_tex)
# Add unit circles
for plane in planes:
circle = Circle(radius=plane.c2p(1, 0)[0] - plane.c2p(0, 0)[0])
circle.move_to(plane.c2p(0, 0))
circle.set_stroke(GREY, 1, 0.5)
self.add(circle)
# Create basis vectors
# |0> state
zero_vec = Arrow(
plane1.c2p(0, 0),
plane1.c2p(1, 0),
buff=0,
thickness=4,
fill_color=BLUE
)
zero_label = Tex(R"|0\rangle", font_size=30, color=BLUE)
zero_label.next_to(zero_vec.get_end(), UR, SMALL_BUFF)
# |1> state
one_vec = Arrow(
plane1.c2p(0, 0),
plane1.c2p(0, 1),
buff=0,
thickness=4,
fill_color=GREEN
)
one_label = Tex(R"|1\rangle", font_size=30, color=GREEN)
one_label.next_to(one_vec.get_end(), UL, SMALL_BUFF)
# H|0> = |+> = (|0> + |1>)/sqrt(2)
h_zero_vec = Arrow(
plane2.c2p(0, 0),
plane2.c2p(1, 1) / np.sqrt(2),
buff=0,
thickness=4,
fill_color=BLUE
)
h_zero_label = Tex(R"H|0\rangle = |+\rangle", font_size=24, color=BLUE)
h_zero_label.next_to(h_zero_vec.get_end(), UR, SMALL_BUFF)
# H|1> = |-> = (|0> - |1>)/sqrt(2)
h_one_vec = Arrow(
plane2.c2p(0, 0),
plane2.c2p(1, -1) / np.sqrt(2),
buff=0,
thickness=4,
fill_color=GREEN
)
h_one_label = Tex(R"H|1\rangle = |-\rangle", font_size=24, color=GREEN)
h_one_label.next_to(h_one_vec.get_end(), DR, SMALL_BUFF)
# Animate
self.play(
GrowArrow(zero_vec),
FadeIn(zero_label)
)
self.play(
TransformFromCopy(zero_vec, h_zero_vec, path_arc=-30 * DEG),
FadeIn(h_zero_label),
run_time=2
)
self.wait()
self.play(
GrowArrow(one_vec),
FadeIn(one_label)
)
self.play(
TransformFromCopy(one_vec, h_one_vec, path_arc=-30 * DEG),
FadeIn(h_one_label),
run_time=2
)
self.wait(2)
class GateComposition(InteractiveScene):
"""Shows how multiple gates compose to create quantum circuits."""
def construct(self):
# Create a simple quantum circuit visualization
wire = Line(4 * LEFT, 4 * RIGHT)
wire.set_stroke(WHITE, 2)
# Gate boxes
gates = VGroup()
gate_names = ["H", "X", "Z", "H"]
colors = [YELLOW, RED, BLUE, YELLOW]
for i, (name, color) in enumerate(zip(gate_names, colors)):
box = Square(0.8)
box.set_stroke(WHITE, 2)
box.set_fill(BLACK, 1)
box.move_to(wire.pfp((i + 1) / (len(gate_names) + 1)))
label = Text(name, font_size=36, color=color)
label.move_to(box)
gates.add(VGroup(box, label))
# Input and output labels
input_label = Tex(R"|0\rangle", font_size=48)
input_label.next_to(wire, LEFT)
output_label = Tex(R"|\psi\rangle", font_size=48)
output_label.next_to(wire, RIGHT)
circuit = VGroup(wire, gates, input_label, output_label)
circuit.center().shift(UP)
# Title
title = Text("Quantum Circuit", font_size=48)
title.to_edge(UP)
self.add(title)
self.play(
ShowCreation(wire),
FadeIn(input_label),
FadeIn(output_label)
)
# Show gates appearing one by one
for gate in gates:
self.play(FadeIn(gate, scale=1.2))
self.wait()
# Animate a "quantum state" passing through
glow = GlowDot(wire.get_start(), color=TEAL, radius=0.3)
glow.set_z_index(1)
self.play(
glow.animate.move_to(wire.get_end()),
rate_func=linear,
run_time=3
)
# Show final state
final_state = Tex(
R"|\psi\rangle = -|1\rangle",
font_size=36
)
final_state.next_to(circuit, DOWN, buff=1.0)
self.play(
FadeOut(glow),
FadeIn(final_state, shift=UP)
)
self.wait(2)
if __name__ == "__main__":
# To run: manimgl quantum_gates.py QuantumGatesVisualization
pass
examples/qubit_state_vector.py
"""
Qubit State Vector Visualization
================================
Shows a 2D plane representing a single qubit's state as a unit vector.
The vector rotates through different states while displaying probability
distribution for measuring |0> or |1>.
Key concepts demonstrated:
- NumberPlane for 2D visualization
- Vector with updaters tracking angle
- DecimalMatrix for live coordinate display
- Distribution bars showing measurement probabilities
"""
from manimlib import *
class QubitStateVector(InteractiveScene):
def construct(self):
# Set up the 2D plane for qubit visualization
plane = NumberPlane((-2, 2), (-2, 2), faded_line_ratio=5)
plane.set_height(6)
plane.to_edge(LEFT, buff=1.0)
# Create qubit labels |0> and |1>
zero_label = VGroup(Tex(R"|"), Integer(0), Tex(R"\rangle"))
zero_label.arrange(RIGHT, buff=0.05)
one_label = VGroup(Tex(R"|"), Integer(1), Tex(R"\rangle"))
one_label.arrange(RIGHT, buff=0.05)
qubit_labels = VGroup(zero_label, one_label)
qubit_labels.scale(0.6)
zero_label.next_to(plane.c2p(1, 0), DR, SMALL_BUFF)
one_label.next_to(plane.c2p(0, 1), DR, SMALL_BUFF)
self.add(plane, qubit_labels)
# Create the state vector
theta_tracker = ValueTracker(30 * DEG)
vector = Arrow(
plane.c2p(0, 0),
plane.c2p(1, 0),
buff=0,
thickness=6,
fill_color=TEAL
)
vector.add_updater(lambda m: m.set_angle(theta_tracker.get_value()))
vector.add_updater(lambda m: m.shift(plane.c2p(0, 0) - m.get_start()))
# Coordinate display
coord_display = DecimalMatrix(
[[1.0], [0.0]],
bracket_h_buff=0.1,
decimal_config=dict(include_sign=True, num_decimal_places=2)
)
coord_display.scale(0.6)
coord_display.add_background_rectangle()
coord_display.set_backstroke(BLACK, 5)
def get_state():
theta = theta_tracker.get_value()
return np.array([math.cos(theta), math.sin(theta)])
def update_coordinates(matrix):
for element, value in zip(matrix.elements, get_state()):
element.set_value(value)
def position_label(matrix):
x, y = get_state()
buff = SMALL_BUFF + 0.4 * interpolate(
matrix.get_width(), matrix.get_height(), x**2
)
vect = normalize(vector.get_vector())
matrix.move_to(vector.get_end() + buff * vect)
coord_display.add_updater(update_coordinates)
coord_display.add_updater(position_label)
self.add(vector, coord_display)
# Add probability display on the right
prob_title = Text("Measurement Probabilities", font_size=36)
prob_title.to_edge(RIGHT, buff=1.0)
prob_title.to_edge(UP, buff=1.0)
qubits = VGroup(
VGroup(Tex(R"|0\rangle"), Tex("")),
VGroup(Tex(R"|1\rangle"), Tex("")),
)
qubits.arrange(DOWN, buff=1.0)
qubits.next_to(prob_title, DOWN, buff=1.0)
# Probability bars
def get_prob_bars():
probs = get_state()**2
bars = VGroup()
for i, (qubit, prob) in enumerate(zip(qubits, probs)):
bar = Rectangle(
width=prob * 3,
height=0.4
)
bar.next_to(qubit[0], RIGHT, buff=0.3)
bar.set_fill(
interpolate_color(BLUE_D, GREEN, prob),
opacity=1
)
bar.set_stroke(WHITE, 1)
label = Integer(int(100 * prob), unit=R"\%", font_size=24)
label.next_to(bar, RIGHT, SMALL_BUFF)
bars.add(VGroup(bar, label))
return bars
prob_bars = always_redraw(get_prob_bars)
self.add(prob_title, qubits, prob_bars)
# Add unit circle
circle = Circle(radius=plane.c2p(1, 0)[0] - plane.c2p(0, 0)[0])
circle.move_to(plane.c2p(0, 0))
circle.set_stroke(YELLOW, 1, 0.5)
self.play(ShowCreation(circle))
# Animate the vector rotation
self.play(theta_tracker.animate.set_value(60 * DEG), run_time=2)
self.wait()
self.play(theta_tracker.animate.set_value(90 * DEG), run_time=2)
self.wait()
self.play(theta_tracker.animate.set_value(45 * DEG), run_time=2)
self.wait()
# Show the constraint x^2 + y^2 = 1
constraint = Tex(R"x^2 + y^2 = 1", font_size=48)
constraint.to_corner(UR, buff=1.0)
constraint.set_color(YELLOW)
self.play(Write(constraint))
self.wait()
# Full rotation
self.play(
theta_tracker.animate.set_value(theta_tracker.get_value() + TAU),
run_time=6
)
self.wait()
class QubitKetNotation(InteractiveScene):
"""Shows the relationship between vector coordinates and ket notation."""
def construct(self):
# Title
title = Text("Qubit State Representation", font_size=48)
title.to_edge(UP)
self.add(title)
# Vector form
vector_form = Tex(
R"\begin{bmatrix} x \\ y \end{bmatrix}",
font_size=72
)
vector_form.shift(2 * LEFT)
# Ket form
ket_form = Tex(
R"x|0\rangle + y|1\rangle",
font_size=72
)
ket_form.shift(2 * RIGHT)
# Equals sign
equals = Tex(R"\Leftrightarrow", font_size=72)
self.play(Write(vector_form))
self.wait()
self.play(Write(equals))
self.play(Write(ket_form))
self.wait()
# Constraint
constraint = Tex(
R"\text{where } x^2 + y^2 = 1",
font_size=36
)
constraint.next_to(VGroup(vector_form, equals, ket_form), DOWN, buff=1.0)
constraint.set_color(YELLOW)
self.play(FadeIn(constraint, shift=UP))
self.wait(2)
if __name__ == "__main__":
# To run: manimgl qubit_state_vector.py QubitStateVector
pass
examples/query_key_dot_products.py
"""
Query-Key Dot Products Grid Visualization
Shows how queries and keys produce a grid of dot products that form the attention pattern.
"""
from manimlib import *
import numpy as np
class QueryKeyDotProducts(InteractiveScene):
def construct(self):
# Create query and key symbols
n_tokens = 5
# Query template
q_template = Tex(R"\vec{\textbf{Q}}_0")
q_template[0].scale(1.5, about_edge=DOWN)
q_template.set_color(YELLOW)
q_subscript = q_template.make_number_changeable("0")
# Key template
k_template = Tex(R"\vec{\textbf{K}}_0")
k_template[0].scale(1.5, about_edge=DOWN)
k_template.set_color(TEAL)
k_subscript = k_template.make_number_changeable("0")
# Create query symbols along top
q_syms = VGroup()
for n in range(1, n_tokens + 1):
q_subscript.set_value(n)
q_syms.add(q_template.copy())
q_syms.arrange(RIGHT, buff=0.8)
q_syms.move_to(2 * UP)
# Create key symbols along left
k_syms = VGroup()
for n in range(1, n_tokens + 1):
k_subscript.set_value(n)
k_syms.add(k_template.copy())
k_syms.arrange(DOWN, buff=0.6)
k_syms.next_to(q_syms, DL, buff=0.8)
k_syms.shift(0.5 * LEFT)
self.play(
LaggedStartMap(FadeIn, q_syms, shift=0.5 * DOWN, lag_ratio=0.1),
LaggedStartMap(FadeIn, k_syms, shift=0.5 * RIGHT, lag_ratio=0.1),
)
self.wait()
# Draw grid lines
h_lines = VGroup()
for k in k_syms:
h_line = Line(LEFT, RIGHT).set_width(6)
h_line.next_to(k, DOWN, buff=0.3)
h_line.align_to(k_syms, LEFT)
h_lines.add(h_line)
v_lines = VGroup()
for q in q_syms:
v_line = Line(UP, DOWN).set_height(5)
v_line.next_to(q, DOWN, buff=0.3)
v_lines.add(v_line)
v_lines.add(v_lines[-1].copy().next_to(q_syms, RIGHT, buff=0.5))
grid_lines = VGroup(*h_lines, *v_lines)
grid_lines.set_stroke(GREY_A, 1)
self.play(
ShowCreation(h_lines, lag_ratio=0.2),
ShowCreation(v_lines, lag_ratio=0.2),
)
# Create dot products in each cell
dot_prods = VGroup()
for k_sym in k_syms:
for q_sym in q_syms:
square_center = np.array([q_sym.get_x(), k_sym.get_y(), 0])
dot = Tex(R"\cdot", font_size=48)
dot.move_to(square_center)
dot.set_fill(opacity=0)
dot_prod = VGroup(k_sym.copy(), dot, q_sym.copy())
dot_prod.target = dot_prod.generate_target()
dot_prod.target.arrange(RIGHT, buff=0.1)
dot_prod.target.scale(0.5)
dot_prod.target.move_to(square_center)
dot_prod.target.set_fill(opacity=1)
dot_prods.add(dot_prod)
self.play(
LaggedStartMap(MoveToTarget, dot_prods, lag_ratio=0.02, run_time=3)
)
self.wait()
# Show numerical values (random attention scores)
np.random.seed(42)
dots = VGroup(
VGroup(Dot().match_x(q_sym).match_y(k_sym) for q_sym in q_syms)
for k_sym in k_syms
)
# Set sizes based on "attention" - diagonal and some off-diagonal get bigger
for n, row in enumerate(dots):
for k, dot in enumerate(row):
base_size = 0.1 + 0.15 * np.random.random()
dot.set_width(base_size)
dot.set_fill(GREY_C, 0.8)
# Make diagonal stronger (self-attention)
if n == k:
dot.set_width(0.5 + 0.2 * np.random.random())
dot.set_fill(WHITE, 1)
flat_dots = VGroup(*it.chain(*dots))
self.play(
dot_prods.animate.set_fill(opacity=0.3),
LaggedStartMap(GrowFromCenter, flat_dots, lag_ratio=0.02)
)
self.wait()
# Label as attention pattern
pattern_label = Text("Attention Pattern", font_size=60)
pattern_label.to_edge(DOWN)
pattern_label.set_color(YELLOW)
self.play(Write(pattern_label))
self.wait(2)
examples/query_key_space_mapping.py
"""
Query/Key Space Mapping Visualization
Shows how embeddings in high-dimensional space get projected to a lower-dimensional
query/key space where dot products measure relevance.
"""
from manimlib import *
import numpy as np
class QueryKeySpaceMapping(InteractiveScene):
def construct(self):
# Set up 3D view
self.set_floor_plane("xz")
frame = self.frame
frame.set_field_of_view(30 * DEGREES)
frame.reorient(-30, -5, 0, (2, 1, 0), 4.5)
frame.add_ambient_rotation(1 * DEGREES)
# Create 3D axes representing embedding space
axes_3d = ThreeDAxes((-3, 3), (-3, 3), (-3, 3))
xz_plane = NumberPlane(
(-3, 3), (-3, 3),
background_line_style=dict(
stroke_color=GREY,
stroke_width=1,
),
faded_line_ratio=0
)
xz_plane.rotate(90 * DEGREES, RIGHT)
xz_plane.move_to(axes_3d)
xz_plane.axes.set_opacity(0)
axes_3d.add(xz_plane)
axes_3d.set_height(2.5)
self.add(axes_3d)
# Create target 2D plane (Query/Key space)
plane_2d = NumberPlane(
(-2.5, 2.5), (-2.5, 2.5),
faded_line_ratio=1,
background_line_style=dict(
stroke_color=BLUE,
stroke_width=1,
stroke_opacity=0.75
),
faded_line_style=dict(
stroke_color=BLUE,
stroke_width=1,
stroke_opacity=0.25,
)
)
plane_2d.set_height(3.0)
plane_2d.to_corner(DR)
plane_2d.fix_in_frame()
# Arrow showing the mapping
arrow = Tex(R"\longrightarrow", font_size=72)
arrow.set_width(1.5)
arrow.stretch(0.7, 1)
arrow.next_to(plane_2d, LEFT, buff=0.8)
arrow.set_color(YELLOW)
arrow.fix_in_frame()
# Label for the mapping
map_label = Tex("W_Q", font_size=60)
map_label.set_color(YELLOW)
map_label.next_to(arrow.get_left(), UR, SMALL_BUFF)
map_label.shift(0.2 * RIGHT)
map_label.fix_in_frame()
# Titles
titles = VGroup(
Text("Embedding space", font_size=30),
Text("Query/Key space", font_size=30),
)
subtitles = VGroup(
Text("12,288-dimensional", font_size=22),
Text("128-dimensional", font_size=22),
)
subtitles.set_fill(GREY_B)
for title, subtitle in zip(titles, subtitles):
subtitle.next_to(title, DOWN, SMALL_BUFF)
title.add(subtitle)
titles[0].to_edge(UL, buff=0.5)
titles[0].fix_in_frame()
titles[1].next_to(plane_2d, UP, MED_LARGE_BUFF)
titles[1].fix_in_frame()
self.add(plane_2d)
self.add(arrow)
self.add(map_label)
self.add(titles)
# Create a vector in 3D space
in_coords = (2, 2.5, 1)
in_vect = Arrow(axes_3d.get_origin(), axes_3d.c2p(*in_coords), buff=0)
in_vect.set_stroke(TEAL, 5)
in_label = Text("\"Creature\"", font_size=20)
in_label.set_color(TEAL)
in_label.next_to(in_vect.get_end(), UP, SMALL_BUFF)
# Create corresponding vector in 2D space
out_coords = (-1.5, -1)
out_vect = Arrow(plane_2d.get_origin(), plane_2d.c2p(*out_coords), buff=0)
out_vect.set_stroke(YELLOW, 4)
out_vect.fix_in_frame()
out_label = Text("Query:\nAny adjectives\nbefore me?", font_size=16)
out_label.next_to(out_vect.get_end(), DOWN, buff=0.15)
out_label.set_backstroke(BLACK, 3)
out_label.fix_in_frame()
# Animate the transformation
self.play(
GrowArrow(in_vect),
FadeInFromPoint(in_label, axes_3d.get_origin()),
run_time=1.5
)
self.wait(2)
self.play(
TransformFromCopy(in_vect, out_vect),
FadeTransform(in_label.copy(), out_label),
run_time=2,
)
self.wait()
# Show second vector (Key)
in_coords_2 = (-2, 1, 2)
in_vect_2 = Arrow(axes_3d.get_origin(), axes_3d.c2p(*in_coords_2), buff=0)
in_vect_2.set_stroke(BLUE, 5)
in_label_2 = Text("\"Fluffy\"", font_size=20)
in_label_2.set_color(BLUE)
in_label_2.next_to(in_vect_2.get_end(), UP, SMALL_BUFF)
out_coords_2 = (-1.2, -0.8)
out_vect_2 = Arrow(plane_2d.get_origin(), plane_2d.c2p(*out_coords_2), buff=0)
out_vect_2.set_stroke(TEAL, 4)
out_vect_2.fix_in_frame()
out_label_2 = Text("Key:\nAdjective at\nposition 1", font_size=16)
out_label_2.next_to(out_vect_2.get_end(), LEFT, buff=0.15)
out_label_2.set_backstroke(BLACK, 3)
out_label_2.fix_in_frame()
# Change map label to W_K
map_label_k = Tex("W_K", font_size=60)
map_label_k.set_color(TEAL)
map_label_k.move_to(map_label)
map_label_k.fix_in_frame()
self.play(
GrowArrow(in_vect_2),
FadeInFromPoint(in_label_2, axes_3d.get_origin()),
FadeTransform(map_label, map_label_k),
arrow.animate.set_color(TEAL),
run_time=1.5
)
self.wait(2)
self.play(
TransformFromCopy(in_vect_2, out_vect_2),
FadeTransform(in_label_2.copy(), out_label_2),
run_time=2,
)
self.wait()
# Show dot product in 2D space
dot_product_label = Tex(R"\vec{Q} \cdot \vec{K}", font_size=36)
dot_product_label.set_color(WHITE)
dot_product_label.next_to(plane_2d, DOWN, buff=0.3)
dot_product_label.fix_in_frame()
# Highlight the angle between vectors
angle_arc = Arc(
start_angle=out_vect.get_angle(),
angle=out_vect_2.get_angle() - out_vect.get_angle(),
radius=0.4,
arc_center=plane_2d.get_origin(),
)
angle_arc.set_stroke(WHITE, 2)
angle_arc.fix_in_frame()
high_score = Text("High score = relevant!", font_size=24)
high_score.set_color(GREEN)
high_score.next_to(dot_product_label, DOWN, SMALL_BUFF)
high_score.fix_in_frame()
self.play(
Write(dot_product_label),
ShowCreation(angle_arc),
)
self.play(Write(high_score))
self.wait(5)
examples/radial_wave_visualization.py
"""
Radial Wave Visualization
A beautiful visualization of a radial wave emanating from a point source,
demonstrating wave propagation and decay. Based on 3Blue1Brown's hologram/diffraction
visualizations.
Run: manimgl radial_wave_visualization.py RadialWaveDemo -w
"""
from manimlib import *
import numpy as np
class RadialWaveDemo(Scene):
"""
Demonstrates a radial wave visualization using procedural graphics.
Shows how waves propagate from a point source with decay.
"""
def construct(self):
# Setup
frame = self.camera.frame
frame.reorient(0, 0, 0)
# Create point source
source_point = Dot(ORIGIN, color=WHITE, radius=0.15)
source_glow = VGroup(
Circle(radius=r, stroke_color=WHITE, stroke_opacity=0.5 - 0.1 * r, stroke_width=2)
for r in [0.2, 0.3, 0.4, 0.5]
)
source = VGroup(source_point, source_glow)
# Wave parameters
wave_number = 2.0
frequency = 0.5
decay_factor = 0.3
max_radius = 8.0
# Create concentric wave rings that expand
def get_wave_rings(time):
rings = VGroup()
for phase_offset in np.arange(0, 8, 0.5):
radius = (time * frequency / wave_number + phase_offset) % (max_radius + 1)
if radius > 0.1 and radius < max_radius:
amplitude = np.exp(-decay_factor * radius)
ring = Circle(radius=radius)
ring.set_stroke(
color=BLUE,
width=2 + 3 * amplitude,
opacity=0.8 * amplitude
)
rings.add(ring)
return rings
# Initial state
time_tracker = ValueTracker(0)
wave_rings = always_redraw(lambda: get_wave_rings(time_tracker.get_value()))
# Add title
title = Text("Radial Wave Propagation", font_size=48)
title.to_edge(UP)
title.set_backstroke(BLACK, 5)
self.add(title)
self.add(source)
self.add(wave_rings)
# Animate wave propagation
self.play(
time_tracker.animate.set_value(20),
run_time=10,
rate_func=linear
)
# Show label for decay
decay_label = Text("Amplitude decays with distance", font_size=32)
decay_label.next_to(title, DOWN)
decay_label.set_backstroke(BLACK, 3)
self.play(Write(decay_label))
self.play(
time_tracker.animate.set_value(35),
run_time=8,
rate_func=linear
)
self.wait()
class WaveInterferencePattern(Scene):
"""
Shows interference pattern from two point sources.
Demonstrates constructive and destructive interference.
"""
def construct(self):
frame = self.camera.frame
# Two source points
separation = 3.0
source1_pos = separation / 2 * LEFT
source2_pos = separation / 2 * RIGHT
source1 = Dot(source1_pos, color=RED, radius=0.15)
source2 = Dot(source2_pos, color=BLUE, radius=0.15)
# Wave parameters
wave_number = 1.5
frequency = 0.5
max_radius = 10.0
# Create wave function that shows interference
def get_interference_field(time):
# Create a grid of points
x_range = np.linspace(-7, 7, 70)
y_range = np.linspace(-4, 4, 40)
dots = VGroup()
for x in x_range:
for y in y_range:
point = np.array([x, y, 0])
r1 = np.linalg.norm(point - source1_pos)
r2 = np.linalg.norm(point - source2_pos)
# Wave from source 1
phase1 = TAU * (wave_number * r1 - frequency * time)
amp1 = np.cos(phase1) / (1 + 0.3 * r1)
# Wave from source 2
phase2 = TAU * (wave_number * r2 - frequency * time)
amp2 = np.cos(phase2) / (1 + 0.3 * r2)
# Combined amplitude
total_amp = (amp1 + amp2) / 2
# Color based on amplitude
if total_amp > 0:
color = interpolate_color(BLACK, BLUE, min(total_amp, 1))
else:
color = interpolate_color(BLACK, RED, min(-total_amp, 1))
dot = Dot(point, radius=0.05, color=color)
dot.set_fill(opacity=0.3 + 0.7 * abs(total_amp))
dots.add(dot)
return dots
time_tracker = ValueTracker(0)
field = always_redraw(lambda: get_interference_field(time_tracker.get_value()))
# Title
title = Text("Two-Source Interference", font_size=48)
title.to_edge(UP)
title.set_backstroke(BLACK, 5)
# Labels for sources
label1 = Text("Source 1", font_size=24, color=RED)
label1.next_to(source1, DOWN)
label2 = Text("Source 2", font_size=24, color=BLUE)
label2.next_to(source2, DOWN)
self.add(title)
self.add(field)
self.add(source1, source2)
self.add(label1, label2)
# Animate
self.play(
time_tracker.animate.set_value(12),
run_time=12,
rate_func=linear
)
# Show constructive/destructive labels
constructive = Text("Constructive (bright)", font_size=28, color=BLUE)
destructive = Text("Destructive (dark)", font_size=28, color=RED)
labels = VGroup(constructive, destructive)
labels.arrange(DOWN, buff=0.5)
labels.to_edge(LEFT)
labels.set_backstroke(BLACK, 3)
self.play(Write(labels))
self.play(
time_tracker.animate.set_value(20),
run_time=8,
rate_func=linear
)
self.wait()
class WavePropagation3D(Scene):
"""
3D visualization of wave propagation from a point source.
Shows the wave as expanding spherical shells.
"""
def construct(self):
frame = self.camera.frame
frame.reorient(30, 70, 0)
# Parameters
wave_number = 1.0
frequency = 0.4
max_radius = 6.0
# Source point
source = Sphere(radius=0.15, color=WHITE)
source.move_to(ORIGIN)
# Create expanding wave shells
def get_wave_shells(time):
shells = Group()
for phase_offset in np.arange(0, 10, 1.0 / wave_number):
radius = (time * frequency / wave_number + phase_offset)
if 0.3 < radius < max_radius:
amplitude = np.exp(-0.2 * radius)
shell = Sphere(radius=radius)
shell.set_color(BLUE)
shell.set_opacity(0.15 * amplitude)
shells.add(shell)
return shells
time_tracker = ValueTracker(0)
shells = always_redraw(lambda: get_wave_shells(time_tracker.get_value()))
# Add axes for reference
axes = ThreeDAxes(
x_range=[-5, 5, 1],
y_range=[-5, 5, 1],
z_range=[-5, 5, 1],
)
axes.set_opacity(0.3)
self.add(axes)
self.add(shells)
self.add(source)
# Animate with camera rotation
self.play(
time_tracker.animate.set_value(15),
frame.animate.increment_theta(60 * DEGREES),
run_time=15,
rate_func=linear
)
self.play(
time_tracker.animate.set_value(25),
frame.animate.increment_theta(30 * DEGREES).set_phi(50 * DEGREES),
run_time=10,
rate_func=linear
)
self.wait()
examples/rotating_exponentials.py
"""
Rotating Exponentials and Complex Numbers
Visualizes e^(it) as a rotating vector in the complex plane,
showing how cosine emerges from combining two counter-rotating exponentials.
Run: manimgl rotating_exponentials.py RotatingExponential -w
Preview: manimgl rotating_exponentials.py RotatingExponential -p
Source: Adapted from 3b1b's Laplace transform video (2025)
"""
from manimlib import *
import numpy as np
class RotatingExponential(InteractiveScene):
"""
Shows e^(it) as a rotating vector on the complex plane.
The fundamental visualization of Euler's formula.
"""
def construct(self):
# Create complex plane
plane = ComplexPlane(
x_range=(-2, 2, 1),
y_range=(-2, 2, 1),
background_line_style={"stroke_opacity": 0.5}
)
plane.add_coordinate_labels(font_size=20)
# Title
title = Tex(r"e^{it}", font_size=60)
title.to_corner(UL)
self.play(FadeIn(plane), Write(title))
# Create rotating vector
omega = 1 # Angular frequency
time_tracker = ValueTracker(0)
# The vector
vector = Vector(RIGHT, color=YELLOW)
vector.add_updater(lambda v: v.put_start_and_end_on(
ORIGIN,
plane.n2p(np.exp(1j * time_tracker.get_value()))
))
# Dot at tip
tip_dot = Dot(color=YELLOW)
tip_dot.add_updater(lambda d: d.move_to(vector.get_end()))
# Traced path (the unit circle)
traced = TracedPath(
tip_dot.get_center,
stroke_color=BLUE,
stroke_width=2,
)
# Angle arc
angle_arc = always_redraw(lambda: Arc(
start_angle=0,
angle=time_tracker.get_value() % TAU,
radius=0.3,
color=GREEN
))
# Angle label
angle_label = Tex("t", font_size=30, color=GREEN)
angle_label.add_updater(lambda m: m.move_to(
0.5 * (np.cos(time_tracker.get_value() / 2) * RIGHT +
np.sin(time_tracker.get_value() / 2) * UP)
))
self.play(
GrowArrow(vector),
FadeIn(tip_dot),
FadeIn(angle_arc),
FadeIn(angle_label),
)
self.add(traced)
# Rotate through one full cycle
self.play(
time_tracker.animate.set_value(TAU),
run_time=4,
rate_func=linear
)
# Continue rotating
time_tracker.add_updater(lambda m, dt: m.increment_value(dt))
self.wait(4)
class CounterRotatingExponentials(InteractiveScene):
"""
Shows how e^(it) + e^(-it) = 2cos(t).
Two counter-rotating vectors that sum to give real cosine.
"""
def construct(self):
# Create complex plane
plane = ComplexPlane(
x_range=(-3, 3, 1),
y_range=(-2, 2, 1),
background_line_style={"stroke_opacity": 0.4}
)
plane.add_coordinate_labels(font_size=18)
self.play(FadeIn(plane))
# Time tracker
time_tracker = ValueTracker(0)
# e^(it) vector (counter-clockwise)
v1 = Vector(RIGHT, color=BLUE)
v1.add_updater(lambda v: v.put_start_and_end_on(
ORIGIN,
plane.n2p(np.exp(1j * time_tracker.get_value()))
))
# e^(-it) vector (clockwise)
v2 = Vector(RIGHT, color=RED)
v2.add_updater(lambda v: v.put_start_and_end_on(
ORIGIN,
plane.n2p(np.exp(-1j * time_tracker.get_value()))
))
# Sum vector (always real = 2cos(t))
v_sum = Vector(RIGHT, color=GREEN, stroke_width=6)
v_sum.add_updater(lambda v: v.put_start_and_end_on(
ORIGIN,
plane.n2p(2 * np.cos(time_tracker.get_value()))
))
# Labels
labels = VGroup(
Tex(r"e^{it}", color=BLUE, font_size=36),
Tex(r"e^{-it}", color=RED, font_size=36),
Tex(r"e^{it} + e^{-it} = 2\cos(t)", color=GREEN, font_size=36),
)
labels.arrange(DOWN, aligned_edge=LEFT)
labels.to_corner(UL)
# Traced paths
dot1 = Dot(color=BLUE, radius=0.05)
dot1.add_updater(lambda d: d.move_to(v1.get_end()))
trace1 = TracedPath(dot1.get_center, stroke_color=BLUE, stroke_width=1)
dot2 = Dot(color=RED, radius=0.05)
dot2.add_updater(lambda d: d.move_to(v2.get_end()))
trace2 = TracedPath(dot2.get_center, stroke_color=RED, stroke_width=1)
self.play(
GrowArrow(v1),
GrowArrow(v2),
Write(labels[0]),
Write(labels[1]),
)
self.add(dot1, dot2, trace1, trace2)
# Rotate to show counter-rotation
self.play(
time_tracker.animate.set_value(TAU),
run_time=4,
rate_func=linear
)
# Now show the sum
self.play(
GrowArrow(v_sum),
Write(labels[2]),
)
# Continue rotating to show sum is always real
time_tracker.add_updater(lambda m, dt: m.increment_value(dt))
self.wait(6)
class EulersFormula(InteractiveScene):
"""
The famous e^(i*pi) = -1 visualization.
Shows how rotating by pi radians lands at -1.
"""
def construct(self):
# Create plane
plane = ComplexPlane(
x_range=(-2, 2, 1),
y_range=(-1.5, 1.5, 1),
)
plane.add_coordinate_labels(font_size=20)
# Unit circle
circle = Circle(radius=1, color=BLUE_C, stroke_width=2)
self.play(FadeIn(plane), ShowCreation(circle))
# Start at 1
start_dot = Dot(plane.n2p(1), color=YELLOW)
start_label = Tex("1", font_size=30)
start_label.next_to(start_dot, DR, buff=0.1)
self.play(FadeIn(start_dot), Write(start_label))
# Show the formula building up
formula = Tex(r"e^{i\pi}", font_size=72)
formula.to_corner(UR)
self.play(Write(formula))
# Animate rotation from 1 to -1
rotating_dot = Dot(plane.n2p(1), color=GREEN)
rotating_vec = Vector(RIGHT, color=GREEN)
angle_tracker = ValueTracker(0)
rotating_vec.add_updater(lambda v: v.put_start_and_end_on(
ORIGIN,
plane.n2p(np.exp(1j * angle_tracker.get_value()))
))
rotating_dot.add_updater(lambda d: d.move_to(rotating_vec.get_end()))
# Arc to trace the path
traced_arc = TracedPath(rotating_dot.get_center, stroke_color=YELLOW, stroke_width=3)
self.play(GrowArrow(rotating_vec), FadeIn(rotating_dot))
self.add(traced_arc)
# Rotate to pi
self.play(
angle_tracker.animate.set_value(PI),
run_time=3,
rate_func=smooth
)
# Show = -1
end_dot = Dot(plane.n2p(-1), color=RED)
end_label = Tex("-1", font_size=30, color=RED)
end_label.next_to(end_dot, DL, buff=0.1)
equals = Tex(r"= -1", font_size=72)
equals.next_to(formula, RIGHT)
self.play(
FadeIn(end_dot),
Write(end_label),
Write(equals),
)
self.wait()
# Rearrange to famous form
famous = Tex(r"e^{i\pi} + 1 = 0", font_size=72)
famous.move_to(formula.get_center() + 0.5 * RIGHT)
self.play(
FadeOut(equals),
TransformMatchingTex(formula, famous),
)
self.wait(2)
class ComplexExponentialSpiral(InteractiveScene):
"""
Shows e^((a+bi)t) = e^(at) * e^(bit) as an exponential spiral.
When a < 0, we get a decaying spiral (damped oscillation).
"""
def construct(self):
# Create plane
plane = ComplexPlane(
x_range=(-4, 4, 1),
y_range=(-3, 3, 1),
background_line_style={"stroke_opacity": 0.3}
)
plane.scale(0.8)
self.play(FadeIn(plane))
# Parameters
a = -0.15 # Decay rate
b = 2 # Angular frequency
# Title showing the exponent
title = Tex(r"e^{(-0.15 + 2i)t}", font_size=48)
title.to_corner(UL)
self.play(Write(title))
# Time tracker
time_tracker = ValueTracker(0)
def get_position():
t = time_tracker.get_value()
return plane.n2p(np.exp((a + 1j * b) * t))
# Spiral tracer
dot = Dot(get_position(), color=YELLOW)
dot.add_updater(lambda d: d.move_to(get_position()))
spiral = TracedPath(
dot.get_center,
stroke_color=BLUE,
stroke_width=2,
)
# Vector from origin
vec = Vector(RIGHT, color=YELLOW)
vec.add_updater(lambda v: v.put_start_and_end_on(ORIGIN, get_position()))
self.play(FadeIn(dot), GrowArrow(vec))
self.add(spiral)
# Trace the spiral
self.play(
time_tracker.animate.set_value(15),
run_time=8,
rate_func=linear
)
self.wait()
# Show components
explanation = VGroup(
Tex(r"e^{at}", r"\text{ controls amplitude}", font_size=30),
Tex(r"e^{ibt}", r"\text{ controls rotation}", font_size=30),
)
explanation.arrange(DOWN, aligned_edge=LEFT)
explanation.to_corner(DR)
explanation[0][0].set_color(RED)
explanation[1][0].set_color(BLUE)
self.play(Write(explanation))
self.wait(2)
examples/semantic_similarity.py
"""
Semantic Similarity Visualization
Shows how similar words cluster together in embedding space.
Based on: videos/_2024/transformers/embedding.py - ShowNearestNeighbors
"""
from manimlib import *
class SemanticSimilarity(InteractiveScene):
"""
Demonstrates semantic clustering in word embedding space.
Similar words are shown as nearby vectors.
"""
def construct(self):
# Set up 3D scene
frame = self.frame
frame.reorient(-21, 87, 0, (2.18, 0.09, 0.72), 4)
frame.add_ambient_rotation(1 * DEGREES)
# Create axes
axes = ThreeDAxes(
x_range=(-5, 5, 1),
y_range=(-5, 5, 1),
z_range=(-4, 4, 1),
width=8,
height=8,
depth=6.4,
)
axes.set_stroke(width=2)
self.add(axes)
# Add reference plane
plane = NumberPlane(
axes.x_range[:2], axes.y_range[:2],
width=axes.get_width(),
height=axes.get_height(),
background_line_style=dict(
stroke_color=GREY,
stroke_width=1,
),
faded_line_style=dict(
stroke_opacity=0.25,
stroke_width=0.5,
),
faded_line_ratio=1,
)
self.add(plane)
# Seed word and its neighbors
seed_word = "tower"
seed_color = YELLOW
neighbor_words = [
"castle", "fortress", "building", "spire",
"monument", "cathedral", "skyscraper"
]
# Create seed vector
seed_pos = np.array([2, 0, 1])
def create_word_arrow(word, pos, color):
arrow = Arrow(
axes.get_origin(),
axes.c2p(*pos),
buff=0,
stroke_color=color,
stroke_width=4,
)
arrow.set_flat_stroke(False)
label = Text(word, font_size=24)
label.set_backstroke(BLACK, 3)
label.next_to(arrow.get_end(), normalize(arrow.get_vector()), buff=0.05)
# Keep label visible by fixing in frame
label.fix_in_frame()
return VGroup(arrow, label)
seed_vect = create_word_arrow(seed_word, seed_pos, seed_color)
self.add(seed_vect)
# Create title (fixed in frame)
title = Text(f"Words similar to '{seed_word}'", font_size=42)
title.fix_in_frame()
title.to_corner(UR)
underline = Underline(title)
underline.fix_in_frame()
self.add(title, underline)
# Create neighbor positions (clustered around seed)
np.random.seed(42)
neighbor_positions = [
seed_pos + np.random.uniform(-0.8, 0.8, 3)
for _ in neighbor_words
]
# Create list display
items = VGroup(*(
Text(f" {word}", font_size=30)
for word in neighbor_words
))
items.arrange(DOWN, aligned_edge=LEFT)
items.next_to(underline, DOWN, buff=0.5)
items.align_to(title, LEFT)
items.fix_in_frame()
# Animate neighbors appearing
neighbors = []
last_neighbor = VectorizedPoint()
for i, (word, pos, item) in enumerate(zip(neighbor_words, neighbor_positions, items)):
# Create slightly different colors for variety
hue = 0.55 + 0.1 * np.random.random()
color = Color(hsl=(hue, 0.6, 0.5))
neighbor = create_word_arrow(word, pos, color)
neighbors.append(neighbor)
# Fade previous neighbor
faded_neighbor = last_neighbor.copy()
faded_neighbor.set_opacity(0.3)
self.add(faded_neighbor, seed_vect, neighbor)
self.play(
FadeIn(item),
FadeIn(neighbor),
FadeOut(last_neighbor),
FadeIn(faded_neighbor),
run_time=0.5
)
last_neighbor = neighbor
self.wait(0.3)
# Fade last neighbor
self.play(last_neighbor.animate.set_opacity(0.3))
self.wait(2)
# Show all neighbors together
all_neighbors = VGroup(*neighbors)
self.play(all_neighbors.animate.set_opacity(1))
self.wait()
# Draw circle to show clustering
cluster_center = axes.c2p(*seed_pos)
cluster_circle = Circle(radius=1.2)
cluster_circle.move_to(cluster_center)
cluster_circle.set_stroke(YELLOW, 2)
cluster_circle.set_fill(YELLOW, 0.1)
self.play(ShowCreation(cluster_circle))
self.wait()
# Add clustering label
cluster_label = Text("Semantic cluster", font_size=36, color=YELLOW)
cluster_label.fix_in_frame()
cluster_label.next_to(items, DOWN, buff=1.0)
self.play(Write(cluster_label))
self.wait(3)
# Show contrasting words far away
contrast_words = ["banana", "running", "purple"]
contrast_positions = [
np.array([-3, -2, -1]),
np.array([-2, 3, 0]),
np.array([0, -3, 2]),
]
contrast_label = Text("Unrelated words", font_size=30, color=RED)
contrast_label.fix_in_frame()
contrast_label.next_to(cluster_label, DOWN, buff=0.3)
contrast_vects = VGroup()
for word, pos in zip(contrast_words, contrast_positions):
vect = create_word_arrow(word, pos, RED)
vect.set_opacity(0.6)
contrast_vects.add(vect)
self.play(
LaggedStartMap(FadeIn, contrast_vects, lag_ratio=0.3),
Write(contrast_label),
run_time=2
)
self.wait(3)
# Rotate scene to show 3D structure
frame.clear_updaters()
self.play(
frame.animate.reorient(-100, 60, 100, (0, 0, 0), 6),
run_time=5
)
self.wait(2)
examples/simple_test.py
"""
Simple ManimGL test without LaTeX
Run: PATH="/Library/TeX/texbin:$PATH" manimgl simple_test.py SimpleTest -w -l
"""
from manimlib import *
import numpy as np
class SimpleTest(Scene):
"""Basic shapes test - no LaTeX required."""
def construct(self):
# Title using Text (no LaTeX needed)
title = Text("ManimGL Test")
title.to_edge(UP)
self.play(Write(title))
self.wait()
# Create simple shapes
circle = Circle(color=BLUE)
circle.set_fill(BLUE, opacity=0.5)
square = Square(color=RED)
square.set_fill(RED, opacity=0.5)
triangle = Triangle(color=GREEN)
triangle.set_fill(GREEN, opacity=0.5)
shapes = VGroup(circle, square, triangle)
shapes.arrange(RIGHT, buff=1)
self.play(
LaggedStart(
*[ShowCreation(s) for s in shapes],
lag_ratio=0.3
)
)
self.wait()
# Transform
self.play(
circle.animate.shift(UP),
square.animate.rotate(PI/4),
triangle.animate.scale(1.5),
)
self.wait()
# Fade out
self.play(FadeOut(VGroup(shapes, title)))
class Simple3D(Scene):
"""Basic 3D test."""
def construct(self):
frame = self.camera.frame
# 3D axes
axes = ThreeDAxes()
self.add(axes)
# Sphere
sphere = Sphere(radius=1)
sphere.set_color(BLUE)
self.add(sphere)
# Rotate camera
self.play(
frame.animate.set_euler_angles(
phi=70 * DEGREES,
theta=-45 * DEGREES
),
run_time=2
)
self.wait()
# Rotate around
self.play(
frame.animate.increment_theta(90 * DEGREES),
run_time=3
)
self.wait()
examples/softmax_visualization.py
"""
Softmax function visualization showing probability distributions.
Demonstrates: BarChart, DecimalNumber, animations for probability concepts
"""
from manimlib import *
import numpy as np
def softmax(logits, temperature=1.0):
"""Compute softmax with optional temperature parameter."""
logits = np.array(logits)
logits = logits - np.max(logits) # For numerical stability
if temperature == 0:
result = np.zeros_like(logits, dtype=float)
result[np.argmax(logits)] = 1
return result
exps = np.exp(logits / temperature)
return exps / np.sum(exps)
class SoftmaxVisualization(Scene):
def construct(self):
# Example data - logits for different categories
categories = ['Cat', 'Dog', 'Bird', 'Fish', 'Rabbit', 'Hamster']
logits = np.array([-0.8, 2.5, 0.5, 1.5, 3.4, -2.3])
probs = softmax(logits)
# Create bar chart
chart = BarChart(probs, width=10, height=5)
chart.bars.set_stroke(width=1)
chart.to_edge(DOWN, buff=1)
# Add category labels
labels = VGroup()
for word, bar in zip(categories, chart.bars):
label = Text(word, font_size=30)
label.next_to(bar, DOWN)
labels.add(label)
# Add probability values above bars
prob_labels = VGroup()
for p, bar in zip(probs, chart.bars):
label = DecimalNumber(p, num_decimal_places=3, font_size=24)
label.next_to(bar, UP, buff=0.1)
prob_labels.add(label)
# Title
title = Text("Softmax: Converting Logits to Probabilities", font_size=48)
title.to_edge(UP)
# Animate
self.play(FadeIn(title))
self.wait(0.5)
# Show logits first
logit_text = Text("Input logits:", font_size=36)
logit_values = VGroup(*(
DecimalNumber(v, include_sign=True, font_size=30)
for v in logits
))
logit_values.arrange(RIGHT, buff=0.5)
logit_group = VGroup(logit_text, logit_values)
logit_group.arrange(RIGHT, buff=0.5)
logit_group.next_to(title, DOWN)
self.play(FadeIn(logit_group))
self.wait()
# Animate bars growing
chart.save_state()
for bar in chart.bars:
bar.stretch(0, 1, about_edge=DOWN)
chart.set_opacity(0)
self.play(
Restore(chart, lag_ratio=0.1),
LaggedStartMap(FadeIn, labels),
run_time=2
)
self.play(LaggedStartMap(FadeIn, prob_labels, shift=0.2 * UP))
self.wait()
# Show constraint: sum = 1
sum_text = Tex(R"\sum p_i = 1", font_size=48)
sum_text.next_to(chart, RIGHT, buff=1)
self.play(Write(sum_text))
self.wait()
# Show line at p=1
one_line = DashedLine(
chart.c2p(0, 1),
chart.c2p(len(categories), 1),
)
one_line.set_stroke(RED, 2)
self.play(ShowCreation(one_line))
self.wait()
# Demonstrate temperature effect
self.play(
FadeOut(one_line),
FadeOut(sum_text),
FadeOut(logit_group),
)
temp_label = VGroup(
Text("Temperature T = ", font_size=36),
DecimalNumber(1.0, font_size=36)
)
temp_label.arrange(RIGHT)
temp_label.next_to(title, DOWN)
temp_tracker = ValueTracker(1.0)
temp_label[1].f_always.set_value(temp_tracker.get_value)
self.play(FadeIn(temp_label))
self.wait()
# Update function for bars
def update_chart(chart):
t = temp_tracker.get_value()
new_probs = softmax(logits, t)
for bar, p, label in zip(chart.bars, new_probs, prob_labels):
target_height = p * chart.y_axis.get_unit_size()
bar.set_height(max(target_height, 0.01), stretch=True, about_edge=DOWN)
label.set_value(p)
label.next_to(bar, UP, buff=0.1)
chart.add_updater(update_chart)
prob_labels.add_updater(lambda m: None) # Keep visible
# Vary temperature
self.play(temp_tracker.animate.set_value(0.5), run_time=3)
self.wait()
self.play(temp_tracker.animate.set_value(2.0), run_time=3)
self.wait()
self.play(temp_tracker.animate.set_value(0.1), run_time=3)
self.wait()
# Low temperature = more confident
confident_text = Text("Low T = More confident", font_size=30, color=YELLOW)
confident_text.next_to(chart, RIGHT)
self.play(FadeIn(confident_text))
self.wait()
self.play(temp_tracker.animate.set_value(5.0), run_time=3)
self.play(FadeOut(confident_text))
uniform_text = Text("High T = More uniform", font_size=30, color=YELLOW)
uniform_text.next_to(chart, RIGHT)
self.play(FadeIn(uniform_text))
self.wait(2)
examples/solve_damped_ode.py
"""
Solving Damped Harmonic Oscillator ODE
Demonstrates animated equation solving for the damped spring-mass system.
Shows hypothesis substitution, algebraic manipulation, and quadratic formula.
Run: manimgl solve_damped_ode.py SolveDampedODE -w
Preview: manimgl solve_damped_ode.py SolveDampedODE -p
Source: Adapted from 3b1b's Laplace transform video (2025)
"""
from manimlib import *
def get_coef_colors(n_coefs=3):
"""Generate gradient colors for position, velocity, acceleration."""
return [
interpolate_color_by_hsl(TEAL, RED, a)
for a in np.linspace(0, 1, n_coefs)
]
class SolveDampedODE(InteractiveScene):
"""
Animated walkthrough of solving x'' + μx' + kx = 0
using the exponential hypothesis x(t) = e^{st}.
Key techniques demonstrated:
- TransformMatchingTex for equation transformations
- Animated arrows between derivatives
- SurroundingRectangle for highlighting
- Brace annotations
"""
def construct(self):
# Color scheme for derivatives
colors = get_coef_colors()
# Show x, x', x'' with labels
self.show_derivative_relationship(colors)
# Show F = ma equation
self.show_force_equation(colors)
# Hypothesis: x = e^{st}
self.show_exponential_hypothesis(colors)
# Solve for s
self.solve_for_s()
def show_derivative_relationship(self, colors):
"""Show position, velocity, acceleration and their relationships."""
pos, vel, acc = funcs = VGroup(
Tex(R"x(t)"),
Tex(R"x'(t)"),
Tex(R"x''(t)"),
)
funcs.arrange(DOWN, buff=MED_LARGE_BUFF, aligned_edge=LEFT)
labels = VGroup(
Text("Position").set_color(colors[0]),
Text("Velocity").set_color(colors[1]),
Text("Acceleration").set_color(colors[2]),
)
for line, label in zip(funcs, labels):
label.next_to(line, RIGHT, MED_LARGE_BUFF)
label.align_to(labels[0], LEFT)
VGroup(funcs, labels).to_corner(UR)
# Derivative arrows between terms
arrows = VGroup()
for l1, l2 in zip(funcs, funcs[1:]):
arrow = Line(l1.get_left(), l2.get_left(), path_arc=150 * DEG, buff=0.2)
arrow.add_tip(width=0.2, length=0.2)
arrow.set_color(GREY_B)
ddt = Tex(R"\frac{d}{dt}", font_size=30)
ddt.set_color(GREY_B)
ddt.next_to(arrow, LEFT, SMALL_BUFF)
arrow.add(ddt)
arrows.add(arrow)
# Animate
self.play(Write(funcs[0]), Write(labels[0]))
self.wait()
for func1, func2, label1, label2, arrow in zip(funcs, funcs[1:], labels, labels[1:], arrows):
self.play(LaggedStart(
GrowFromPoint(arrow, arrow.get_corner(UR), path_arc=30 * DEG),
TransformFromCopy(func1, func2, path_arc=30 * DEG),
FadeTransform(label1.copy(), label2),
lag_ratio=0.1
))
self.wait()
self.deriv_group = VGroup(funcs, labels, arrows)
self.funcs = funcs
self.colors = colors
def show_force_equation(self, colors):
"""Show F = ma formulation: mx'' = -kx - μx'"""
t2c = {
"x(t)": colors[0],
"x'(t)": colors[1],
"x''(t)": colors[2],
}
equation1 = Tex(R"{m} x''(t) = -k x(t) - \mu x'(t)", t2c=t2c)
equation1.to_corner(UL)
ma = equation1["{m} x''(t)"][0]
kx = equation1["-k x(t)"][0]
mu_v = equation1[R"- \mu x'(t)"][0]
# Braces for each term
ma_brace = Brace(ma, DOWN, buff=SMALL_BUFF)
ma_brace.add(ma_brace.get_tex(R"\textbf{F}"))
kx_brace = Brace(kx, DOWN, buff=SMALL_BUFF)
kx_brace.add(kx_brace.get_tex(R"\text{Spring force}"))
mu_v_brace = Brace(mu_v, DOWN, buff=SMALL_BUFF)
mu_v_brace.add(mu_v_brace.get_tex(R"\text{Damping}"))
pos, vel, acc = self.funcs
self.play(TransformFromCopy(acc, ma[1:], path_arc=-45 * DEG))
self.play(LaggedStart(
GrowFromCenter(ma_brace),
Write(ma[0]),
run_time=1,
lag_ratio=0.1
))
self.wait()
self.play(LaggedStart(
Write(equation1["= -k"][0]),
FadeTransformPieces(ma_brace, kx_brace),
TransformFromCopy(pos, equation1["x(t)"][0], path_arc=-45 * DEG),
))
self.wait()
self.play(LaggedStart(
FadeTransformPieces(kx_brace, mu_v_brace),
Write(equation1[R"- \mu"][0]),
TransformFromCopy(vel, equation1["x'(t)"][0], path_arc=-45 * DEG),
))
self.wait()
self.play(FadeOut(mu_v_brace))
# Rearrange to standard form
equation2 = Tex(R"{m} x''(t) + \mu x'(t) + k x(t) = 0", t2c=t2c)
equation2.move_to(equation1, UL)
self.play(TransformMatchingTex(equation1, equation2, path_arc=45 * DEG))
self.wait()
self.equation = equation2
def show_exponential_hypothesis(self, colors):
"""Show guess x(t) = e^{st} and plug it in."""
t2c = {"s": YELLOW, "x(t)": TEAL}
hyp_word, hyp_tex = hypothesis = VGroup(
Text("Hypothesis: "),
Tex("x(t) = e^{st}", t2c=t2c),
)
hypothesis.arrange(RIGHT)
hypothesis.to_corner(UR)
sub_hyp = TexText(R"(For some $s$)", t2c={"$s$": YELLOW}, font_size=36, fill_color=GREY_B)
sub_hyp.next_to(hyp_tex, DOWN)
pos = self.funcs[0]
self.play(LaggedStart(
FadeTransform(pos.copy(), hyp_tex[:4], path_arc=45 * DEG, remover=True),
FadeOut(self.deriv_group),
Write(hyp_word, run_time=1),
Write(hyp_tex[4:], time_span=(0.5, 1.5)),
))
self.add(hypothesis)
self.wait()
self.play(FadeIn(sub_hyp, 0.25 * DOWN))
self.wait()
self.hypothesis = hypothesis
self.sub_hyp = sub_hyp
def solve_for_s(self):
"""Plug in hypothesis and solve the characteristic equation."""
t2c = {"s": YELLOW}
# After substitution: m s^2 e^{st} + μ s e^{st} + k e^{st} = 0
equation3 = Tex(R"{m} s^2 e^{st} + \mu s e^{st} + k e^{st} = 0", t2c=t2c)
equation3.next_to(self.equation, DOWN, LARGE_BUFF)
self.play(FadeIn(equation3, 0.5 * DOWN))
self.wait()
# Factor out e^{st}
equation4 = Tex(R"e^{st} \left( ms^2 + \mu s + k \right) = 0", t2c=t2c)
equation4.next_to(equation3, DOWN, LARGE_BUFF)
self.play(
TransformMatchingTex(
equation3.copy(),
equation4,
matched_keys=[R"e^{st}"],
run_time=1.5,
path_arc=30 * DEG
)
)
self.wait()
# Highlight e^{st} ≠ 0
exp_rect = SurroundingRectangle(equation4[R"e^{st}"])
exp_rect.set_stroke(YELLOW, 2)
ne_0 = VGroup(Tex(R"\ne").rotate(90 * DEG), Integer(0))
ne_0.arrange(DOWN).next_to(exp_rect, DOWN)
self.play(ShowCreation(exp_rect))
self.play(Write(ne_0))
self.wait()
# Characteristic equation
equation5 = Tex(R"ms^2 + \mu s + k = 0", t2c=t2c)
equation5.next_to(equation4, DOWN, LARGE_BUFF)
self.play(
FadeOut(ne_0),
FadeOut(exp_rect),
Write(equation5),
)
self.wait()
# Quadratic formula result
equation6 = Tex(R"s = {{-\mu \pm \sqrt{\mu^2 - 4mk}} \over 2m}")
equation6["s"].set_color(YELLOW)
equation6.next_to(equation5, DOWN, LARGE_BUFF)
qf_words = Text("Quadratic Formula", font_size=30, fill_color=GREY_B)
qf_words.next_to(equation6, RIGHT, MED_LARGE_BUFF)
self.play(
FadeIn(equation6, 0.5 * DOWN),
FadeIn(qf_words),
)
self.wait(2)
class SimpleODEDemo(InteractiveScene):
"""
Simpler version showing just the undamped case: x'' + ωx = 0
Results in x = e^{±iωt}, demonstrating complex exponentials.
"""
def construct(self):
# Undamped equation
equation = Tex(R"x''(t) + \omega^2 x(t) = 0", font_size=60)
equation.to_edge(UP)
self.add(equation)
self.wait()
# Hypothesis
hypothesis = Tex(R"\text{Try } x(t) = e^{st}", font_size=48)
hypothesis["s"].set_color(YELLOW)
hypothesis.next_to(equation, DOWN, LARGE_BUFF)
self.play(Write(hypothesis))
self.wait()
# Result
result = Tex(R"s^2 + \omega^2 = 0 \implies s = \pm i\omega", font_size=48)
result["s"].set_color(YELLOW)
result.next_to(hypothesis, DOWN, LARGE_BUFF)
self.play(Write(result))
self.wait()
# Solutions
solutions = Tex(
R"x(t) = c_1 e^{i\omega t} + c_2 e^{-i\omega t}",
font_size=48
)
solutions.next_to(result, DOWN, LARGE_BUFF)
self.play(Write(solutions))
self.wait()
# Box the result
box = SurroundingRectangle(solutions, buff=0.2)
box.set_stroke(TEAL, 3)
self.play(ShowCreation(box))
self.wait(2)
examples/spring_mass_system.py
"""
Spring-Mass System with Live Graph
A physics simulation showing a spring-mass oscillator with real-time
position tracking on a graph. Demonstrates damped harmonic motion.
Run: manimgl spring_mass_system.py SpringMassDemo -w
Preview: manimgl spring_mass_system.py SpringMassDemo -p
Source: Adapted from 3b1b's Laplace transform video (2025)
"""
from manimlib import *
import math
class SpringMassSystem(VGroup):
"""
A reusable spring-mass system component with physics simulation.
This is a great example of 3b1b's approach: create a self-contained
VGroup subclass that handles its own physics and rendering.
"""
def __init__(
self,
x0=0, # Initial displacement from equilibrium
v0=0, # Initial velocity
k=3, # Spring constant
mu=0.1, # Damping coefficient
equilibrium_length=5, # Rest length of spring
equilibrium_position=ORIGIN,
direction=RIGHT,
spring_stroke_color=GREY_B,
spring_stroke_width=2,
spring_radius=0.25,
n_spring_curls=8,
mass_width=1.0,
mass_color=BLUE_E,
mass_label="m",
):
super().__init__()
self.equilibrium_position = equilibrium_position
self.fixed_spring_point = equilibrium_position - (equilibrium_length - 0.5 * mass_width) * direction
self.direction = direction
self.rot_off_horizontal = angle_between_vectors(RIGHT, direction)
# Create visual components
self.mass = self._create_mass(mass_width, mass_color, mass_label)
self.spring = self._create_spring(spring_stroke_color, spring_stroke_width, n_spring_curls, spring_radius)
self.add(self.spring, self.mass)
# Physics state
self.k = k
self.mu = mu
self.velocity = v0
self._is_running = True
# Set initial position
self.set_x(x0)
# Add physics updater
self.add_updater(lambda m, dt: m.time_step(dt))
def _create_spring(self, stroke_color, stroke_width, n_curls, radius):
"""Create a 3D helix spring using parametric curve."""
spring = ParametricCurve(
lambda t: [t, -radius * math.sin(TAU * t), radius * math.cos(TAU * t)],
t_range=(0, n_curls, 0.01),
stroke_color=stroke_color,
stroke_width=stroke_width,
)
spring.rotate(self.rot_off_horizontal)
return spring
def _create_mass(self, mass_width, mass_color, mass_label):
"""Create the mass block with label."""
mass = Square(mass_width)
mass.set_fill(mass_color, 1)
mass.set_stroke(WHITE, 1)
mass.set_shading(0.1, 0.1, 0.1)
label = Tex(mass_label)
label.set_max_width(0.5 * mass.get_width())
label.move_to(mass)
mass.add(label)
mass.label = label
return mass
def set_x(self, x):
"""Set displacement from equilibrium position."""
self.mass.move_to(self.equilibrium_position + x * self.direction)
# Stretch spring to connect fixed point to mass
spring_width = SMALL_BUFF + get_norm(self.mass.get_left() - self.fixed_spring_point)
self.spring.rotate(-self.rot_off_horizontal)
self.spring.set_width(spring_width, stretch=True)
self.spring.rotate(self.rot_off_horizontal)
self.spring.move_to(self.fixed_spring_point, -self.direction)
def get_x(self):
"""Get current displacement."""
return (self.mass.get_center() - self.equilibrium_position)[0]
def time_step(self, delta_t, dt_size=0.01):
"""Integrate physics using simple Euler method."""
if not self._is_running or delta_t == 0:
return
state = [self.get_x(), self.velocity]
sub_steps = max(int(delta_t / dt_size), 1)
true_dt = delta_t / sub_steps
for _ in range(sub_steps):
x, v = state
# Damped harmonic oscillator: x'' = -kx - μv
acceleration = -self.k * x - self.mu * v
state[0] += v * true_dt
state[1] += acceleration * true_dt
self.set_x(state[0])
self.velocity = state[1]
def pause(self):
self._is_running = False
def unpause(self):
self._is_running = True
def get_velocity_vector(self, scale_factor=0.5, v_offset=-0.25, color=GREEN):
"""Get a dynamic vector showing velocity."""
vector = Vector(RIGHT, fill_color=color, stroke_color=color)
v_shift = v_offset * UP
vector.add_updater(lambda m: m.put_start_and_end_on(
self.mass.get_center() + v_shift,
self.mass.get_center() + v_shift + scale_factor * self.velocity * RIGHT
))
return vector
def get_force_vector(self, scale_factor=0.5, v_offset=0.25, color=RED):
"""Get a dynamic vector showing net force."""
vector = Vector(RIGHT, fill_color=color, stroke_color=color)
v_shift = v_offset * UP
def get_force():
return -self.k * self.get_x() - self.mu * self.velocity
vector.add_updater(lambda m: m.put_start_and_end_on(
self.mass.get_center() + v_shift,
self.mass.get_center() + v_shift + scale_factor * get_force() * RIGHT
))
return vector
class SpringMassDemo(InteractiveScene):
"""
Main demonstration scene showing spring-mass oscillation.
"""
def construct(self):
# Create spring system with initial displacement
spring = SpringMassSystem(
x0=2,
mu=0.15,
k=3,
equilibrium_position=2 * LEFT,
equilibrium_length=5,
)
self.add(spring)
# Create number line to show position
number_line = NumberLine(x_range=(-4, 4, 1))
number_line.next_to(spring.equilibrium_position, DOWN, buff=2.0)
number_line.add_numbers(font_size=24)
# Arrow tip indicator on number line
arrow_tip = ArrowTip(length=0.2, width=0.1)
arrow_tip.rotate(-90 * DEG)
arrow_tip.set_fill(TEAL)
arrow_tip.add_updater(lambda m: m.move_to(number_line.n2p(spring.get_x()), DOWN))
# Let it oscillate for a moment
self.wait(2)
# Fade in tracking elements
self.play(
FadeIn(number_line),
FadeIn(arrow_tip),
)
self.wait(5)
# Add velocity vector
v_vect = spring.get_velocity_vector(color=GREEN, scale_factor=0.25)
self.play(FadeIn(v_vect))
self.wait(5)
# Add force vector
f_vect = spring.get_force_vector(color=RED, scale_factor=0.25)
self.play(FadeIn(f_vect))
self.wait(8)
class SpringWithGraph(InteractiveScene):
"""
Spring-mass system with real-time x(t) graph plotting.
Shows how position evolves over time.
"""
def construct(self):
# Create spring
spring = SpringMassSystem(
x0=2,
mu=0.2,
k=4,
equilibrium_position=3 * LEFT + DOWN,
equilibrium_length=4,
)
# Create axes for position-time graph
axes = Axes(
x_range=(0, 15, 1),
y_range=(-2.5, 2.5, 1),
width=10,
height=3,
axis_config={"stroke_color": GREY}
)
axes.next_to(spring.equilibrium_position, UP, buff=1.5)
axes.shift(RIGHT)
# Axis labels
t_label = Text("Time (t)", font_size=24)
t_label.next_to(axes.x_axis, RIGHT, buff=0.1)
x_label = Tex("x(t)", font_size=24)
x_label.next_to(axes.y_axis.get_top(), RIGHT, buff=0.1)
# Time tracker
time_tracker = ValueTracker(0)
time_tracker.add_updater(lambda m, dt: m.increment_value(dt))
# Tracking point for graph
tracking_point = Point()
tracking_point.add_updater(lambda p: p.move_to(
axes.c2p(time_tracker.get_value(), spring.get_x())
))
# Traced path creates the graph line
position_graph = TracedPath(
tracking_point.get_center,
stroke_color=BLUE,
stroke_width=3,
)
# Start paused to set up
spring.pause()
self.add(spring)
self.play(
FadeIn(axes),
Write(t_label),
Write(x_label),
)
# Start simulation and graphing
self.add(tracking_point, position_graph, time_tracker)
spring.unpause()
# Let it run and trace
self.wait(12)
class MultipleSprings(InteractiveScene):
"""
Multiple springs with different parameters side by side.
Great for comparing effects of mass, spring constant, damping.
"""
def construct(self):
# Create three springs with different damping
springs = VGroup()
damping_values = [0.0, 0.2, 0.5]
labels_text = ["No damping", "Light damping", "Heavy damping"]
colors = [BLUE, GREEN, RED]
for i, (mu, label_text, color) in enumerate(zip(damping_values, labels_text, colors)):
spring = SpringMassSystem(
x0=1.5,
mu=mu,
k=4,
equilibrium_position=4 * LEFT + (2 - i * 2) * UP,
equilibrium_length=4,
mass_color=color,
)
label = Text(label_text, font_size=24, color=color)
label.next_to(spring.mass, RIGHT, buff=2)
label.add_updater(lambda m, s=spring, t=label_text, c=color: m.become(
Text(t, font_size=24, color=c).next_to(s.mass, RIGHT, buff=2)
))
springs.add(spring)
self.add(label)
self.add(springs)
self.wait(12)
examples/sqrt_random_process.py
"""
Visualization of sqrt(rand()) process showing how the square root
transforms a uniform distribution.
"""
from manimlib import *
import random
import math
class Randomize(Animation):
"""Animation that randomizes a ValueTracker's value at a given frequency."""
def __init__(self, value_tracker, frequency=8, rand_func=random.random, final_value=None, **kwargs):
self.value_tracker = value_tracker
self.rand_func = rand_func
self.frequency = frequency
self.final_value = final_value if final_value is not None else rand_func()
self.last_alpha = 0
self.running_tally = 0
super().__init__(value_tracker, **kwargs)
def interpolate_mobject(self, alpha):
if not self.new_step(alpha):
return
value = self.rand_func() if alpha < 1 else self.final_value
self.value_tracker.set_value(value)
def new_step(self, alpha):
d_alpha = alpha - self.last_alpha
self.last_alpha = alpha
self.running_tally += self.frequency * d_alpha * self.run_time
if self.running_tally > 1:
self.running_tally = self.running_tally % 1
return True
return False
class TrackingDots(Animation):
"""Animation that leaves a trail of fading dots at specified positions."""
def __init__(self, point_func, fade_factor=0.95, radius=0.25, color=YELLOW, **kwargs):
self.point_func = point_func
self.fade_factor = fade_factor
self.dots = GlowDot(point_func(), color=color, radius=radius)
kwargs.update(remover=True)
super().__init__(self.dots, **kwargs)
def interpolate_mobject(self, alpha):
opacities = self.dots.get_opacities()
point = self.point_func()
if not np.isclose(self.dots.get_end(), point).all():
self.dots.add_point(point)
opacities = np.hstack([opacities, [1]])
opacities *= self.fade_factor
self.dots.set_opacity(opacities)
def get_random_var_label_group(axis, label_name, color=GREY, initial_value=None, font_size=36, direction=None):
"""Create a group with a tracker, arrow tip indicator, and label for a random variable on an axis."""
if initial_value is None:
initial_value = random.uniform(*axis.x_range[:2])
tracker = ValueTracker(initial_value)
tip = ArrowTip(angle=90 * DEGREES)
tip.set_height(0.15)
tip.set_fill(color)
tip.rotate(-axis.get_angle())
if direction is None:
direction = np.round(rotate_vector(UP, -axis.get_angle()), 1)
tip.add_updater(lambda m: m.move_to(axis.n2p(tracker.get_value()), direction))
label = Tex(label_name, font_size=font_size)
label.set_color(color)
label.set_backstroke(BLACK, 5)
label.always.next_to(tip, -direction, buff=0.1)
return Group(tracker, tip, label)
class SqrtRandomProcess(InteractiveScene):
"""
Visualizes the sqrt(rand()) process.
Shows two intervals:
- x = rand() (blue)
- sqrt(x) (teal)
Demonstrates that sqrt(rand()) has the same distribution as max(rand(), rand()).
"""
def construct(self):
# Set up intervals
intervals = VGroup(UnitInterval() for _ in range(2))
intervals.set_width(3)
intervals.arrange(DOWN, buff=3.5)
intervals.shift(2 * LEFT)
for interval in intervals:
interval.add_numbers(np.arange(0, 1.1, 0.2), font_size=16, buff=0.1, direction=UP)
interval.numbers.set_opacity(0.75)
colors = [BLUE, TEAL]
x_group, sqrt_group = groups = Group(
get_random_var_label_group(interval, "", color=color)
for interval, color in zip(intervals, colors)
)
x_tracker, x_tip, x_label = x_group
sqrt_tracker, sqrt_tip, sqrt_label = sqrt_group
sqrt_tracker.add_updater(lambda m: m.set_value(math.sqrt(x_tracker.get_value())))
self.add(intervals)
self.add(groups)
# Add labels
tex_to_color = {"x": BLUE}
labels = VGroup(
Tex(tex + R"\rightarrow 0.00", t2c=tex_to_color)
for tex in [
R"x = \text{rand}()",
R"\sqrt{x}",
]
)
for label, group, interval in zip(labels, groups, intervals):
label.next_to(interval, RIGHT, buff=0.5)
num = label.make_number_changeable("0.00")
num.tracker = group[0]
num.add_updater(lambda m: m.set_value(m.tracker.get_value()))
self.add(labels)
# Big arrow
arrow = Arrow(*intervals, buff=0.5, thickness=5)
label = Text(R"sqrt", font_size=60)
label.next_to(arrow, RIGHT)
self.add(arrow, label)
# Animate the random process
self.play(
Randomize(x_tracker, frequency=4, run_time=15),
TrackingDots(x_tip.get_top, color=colors[0]),
TrackingDots(sqrt_tip.get_top, color=colors[1]),
)
examples/superposition_effect.py
"""
Superposition Effect Visualization
==================================
Creates a visual "superposition" effect where multiple quantum states
appear to exist simultaneously with a glowing, oscillating appearance.
Key concepts demonstrated:
- Custom Group subclass with updaters
- ValueTracker for controlling animation parameters
- Glow effects using replicated objects with varying stroke widths
- Continuous animation with add_updater
"""
from manimlib import *
class Superposition(Group):
"""
A visual effect that makes multiple pieces appear to be in superposition.
The pieces jitter/oscillate around their center positions with a glowing
effect, simulating the uncertainty of a quantum superposition.
"""
def __init__(
self,
pieces,
offset_multiple=0.2,
max_rot_vel=3,
glow_color=TEAL,
glow_stroke_range=(1, 22, 4),
glow_stroke_opacity=0.05
):
self.pieces = pieces
self.center_points = Group(
Point(piece.get_center())
for piece in pieces
)
self.offset_multiplier = ValueTracker(offset_multiple)
# Initialize each piece with random offset and rotation velocity
for piece, point_mob in zip(pieces, self.center_points):
piece.center_point = point_mob
piece.offset_vect = rotate_vector(RIGHT, np.random.uniform(0, TAU))
piece.offset_vect_rot_vel = np.random.uniform(-max_rot_vel, max_rot_vel)
# Create glow layers with varying stroke widths
glow_strokes = np.arange(*glow_stroke_range)
glows = pieces.replicate(len(glow_strokes))
glows.set_fill(opacity=0)
glows.set_joint_type('no_joint')
for glow, sw in zip(glows, glow_strokes):
glow.set_stroke(glow_color, width=float(sw), opacity=glow_stroke_opacity)
self.glows = glows
super().__init__(glows, pieces, self.center_points, self.offset_multiplier)
self.add_updater(lambda m, dt: m.update_piece_positions(dt))
def update_piece_positions(self, dt):
"""Update positions with oscillating motion."""
offset_multiple = self.offset_multiplier.get_value()
for piece in self.pieces:
piece.offset_vect = rotate_vector(
piece.offset_vect,
dt * piece.offset_vect_rot_vel
)
piece.offset_radius = offset_multiple
piece.move_to(
piece.center_point.get_center() +
piece.offset_radius * piece.offset_vect
)
# Update glow positions to match pieces
for glow in self.glows:
for sm1, sm2 in zip(
glow.family_members_with_points(),
self.pieces.family_members_with_points()
):
sm1.match_points(sm2)
def set_offset_multiple(self, value):
"""Control the amount of jitter."""
self.offset_multiplier.set_value(value)
return self
def set_glow_opacity(self, opacity=0.1):
"""Control the glow intensity."""
self.glows.set_stroke(opacity=opacity)
return self
class SuperpositionDemo(InteractiveScene):
"""Demonstrates the superposition visual effect."""
def construct(self):
# Title
title = Text("Quantum Superposition", font_size=60)
title.to_edge(UP)
self.add(title)
# Create bit strings representing possible quantum states
def create_bit_string(value, length=4):
"""Create a visual bit string like |0101>"""
bits = bin(value)[2:].zfill(length)
bit_mobs = VGroup(
Integer(int(b)) for b in bits
)
bit_mobs.arrange(RIGHT, buff=0.1)
return bit_mobs
# Create ket notation
def create_ket(value, length=4):
bits = create_bit_string(value, length)
ket = VGroup(
Tex(R"|"),
bits,
Tex(R"\rangle")
)
ket[0].next_to(bits, LEFT, buff=0.05)
ket[2].next_to(bits, RIGHT, buff=0.05)
return ket
# Create multiple states
states = VGroup(
create_ket(n, 4)
for n in range(16)
)
states.arrange(DOWN, buff=0.2)
states.set_height(5)
states.center()
# Create superposition effect
superposition = Superposition(states, offset_multiple=0, glow_stroke_opacity=0)
superposition.update()
self.add(superposition)
# Animate the superposition emerging
self.play(
superposition.animate.set_offset_multiple(0.15).set_glow_opacity(0.08),
run_time=2
)
# Let it oscillate
self.wait(5)
# Collapse to a single state (measurement)
measurement_label = Text("Measurement", font_size=36, color=RED)
measurement_label.next_to(superposition, RIGHT, buff=1.0)
self.play(Write(measurement_label))
self.play(
Flash(states[7].get_center(), color=WHITE),
run_time=0.3
)
# Collapse effect
self.play(
superposition.animate.set_offset_multiple(0).set_glow_opacity(0),
run_time=0.5
)
# Highlight the measured state
rect = SurroundingRectangle(states[7], buff=0.1, color=YELLOW)
result_label = Text("Result: |0111>", font_size=36, color=YELLOW)
result_label.next_to(superposition, DOWN, buff=0.5)
self.play(
ShowCreation(rect),
FadeIn(result_label)
)
self.wait(2)
class BitStringVisualization(InteractiveScene):
"""Shows bit strings with ket notation."""
def construct(self):
# Create a grid of possible 4-qubit states
def create_ket(value, length=4):
bits_str = bin(value)[2:].zfill(length)
tex = Tex(
R"|" + bits_str + R"\rangle",
font_size=36
)
return tex
# Create grid
states = VGroup(
create_ket(n, 4)
for n in range(16)
)
states.arrange_in_grid(4, 4, buff=0.5)
states.center()
# Title
title = Text("4-Qubit Computational Basis States", font_size=48)
title.to_edge(UP)
self.add(title)
self.play(
LaggedStartMap(FadeIn, states, lag_ratio=0.1),
run_time=3
)
self.wait()
# Highlight the pattern: powers of 2
# |0000> = 0, |0001> = 1, |0010> = 2, etc.
decimal_labels = VGroup()
for i, state in enumerate(states):
label = Integer(i, font_size=24, color=YELLOW)
label.next_to(state, DOWN, SMALL_BUFF)
decimal_labels.add(label)
self.play(
LaggedStartMap(FadeIn, decimal_labels, lag_ratio=0.05),
run_time=2
)
self.wait(2)
class QuantumParallelism(InteractiveScene):
"""Visualizes the concept of quantum parallelism."""
def construct(self):
# Classical vs Quantum comparison
classical_title = Text("Classical", font_size=36)
quantum_title = Text("Quantum", font_size=36)
titles = VGroup(classical_title, quantum_title)
titles.arrange(RIGHT, buff=4)
titles.to_edge(UP, buff=1.0)
v_line = Line(UP, DOWN).set_height(5)
v_line.set_stroke(WHITE, 1)
self.add(titles, v_line)
# Classical: one input at a time
classical_inputs = VGroup(
Tex(R"|" + bin(n)[2:].zfill(4) + R"\rangle", font_size=30)
for n in range(8)
)
classical_inputs.arrange(DOWN, buff=0.2)
classical_inputs.next_to(classical_title, DOWN, buff=0.5)
# Quantum: superposition of all inputs
quantum_pieces = VGroup(
Tex(R"|" + bin(n)[2:].zfill(4) + R"\rangle", font_size=30)
for n in range(8)
)
quantum_pieces.arrange(DOWN, buff=0.2)
quantum_pieces.next_to(quantum_title, DOWN, buff=0.5)
# Create superposition effect for quantum side
superposition = Superposition(
quantum_pieces.copy(),
offset_multiple=0.1,
glow_color=TEAL
)
superposition.move_to(quantum_pieces)
# Classical: process one at a time
self.play(FadeIn(classical_inputs[0]))
for i in range(1, 4):
self.play(
classical_inputs[i - 1].animate.set_opacity(0.3),
FadeIn(classical_inputs[i])
)
# Show dots to indicate continuation
dots = Tex(R"\vdots", font_size=48)
dots.next_to(classical_inputs[3], DOWN)
self.play(FadeIn(dots))
# Quantum: all at once
quantum_label = Text("All states\nsimultaneously!", font_size=24, color=TEAL)
quantum_label.next_to(superposition, DOWN, buff=0.3)
self.play(FadeIn(superposition))
self.play(Write(quantum_label))
self.wait(5)
if __name__ == "__main__":
# To run: manimgl superposition_effect.py SuperpositionDemo
pass
examples/three_d_surfaces.py
"""
3D Surfaces and Camera Movement
Demonstrates 3D surface creation, parametric surfaces,
and camera manipulation in ManimGL.
Run: manimgl three_d_surfaces.py ParametricSurface3D -w
Preview: manimgl three_d_surfaces.py ParametricSurface3D -p
Source: Inspired by 3b1b's 3D visualizations
"""
from manimlib import *
import numpy as np
class ParametricSurface3D(InteractiveScene):
"""
Creates a beautiful 3D parametric surface with camera rotation.
"""
def construct(self):
frame = self.frame
# Create 3D axes
axes = ThreeDAxes(
x_range=(-3, 3, 1),
y_range=(-3, 3, 1),
z_range=(-2, 2, 1),
)
# Parametric surface: z = sin(x) * cos(y)
surface = ParametricSurface(
lambda u, v: [u, v, np.sin(u) * np.cos(v)],
u_range=(-3, 3),
v_range=(-3, 3),
resolution=(30, 30),
)
# Color by z value
surface.set_color(BLUE)
surface.set_opacity(0.8)
self.add(axes)
# Rotate camera to good initial position
frame.reorient(-30, 70, 0)
frame.set_height(10)
# Create surface
self.play(ShowCreation(surface, run_time=3))
self.wait()
# Rotate camera around
self.play(
frame.animate.reorient(30, 60, 0),
run_time=3
)
self.play(
frame.animate.reorient(-60, 80, 0),
run_time=3
)
self.wait()
class SphereSurface(InteractiveScene):
"""
Creates a sphere and demonstrates 3D transformations.
"""
def construct(self):
frame = self.frame
frame.reorient(-20, 70, 0)
# Create sphere
sphere = Sphere(radius=2)
sphere.set_color(BLUE)
sphere.set_opacity(0.7)
# Create latitude/longitude lines
lat_lines = VGroup(*[
ParametricCurve(
lambda t, phi=phi: 2 * np.array([
np.cos(t) * np.cos(phi),
np.sin(t) * np.cos(phi),
np.sin(phi)
]),
t_range=(0, TAU, 0.1),
color=WHITE,
stroke_width=1,
stroke_opacity=0.5,
)
for phi in np.linspace(-PI/2 + 0.3, PI/2 - 0.3, 6)
])
long_lines = VGroup(*[
ParametricCurve(
lambda t, theta=theta: 2 * np.array([
np.cos(theta) * np.cos(t),
np.sin(theta) * np.cos(t),
np.sin(t)
]),
t_range=(-PI/2, PI/2, 0.1),
color=WHITE,
stroke_width=1,
stroke_opacity=0.5,
)
for theta in np.linspace(0, TAU, 12, endpoint=False)
])
self.play(ShowCreation(sphere))
self.play(
ShowCreation(lat_lines, run_time=2),
ShowCreation(long_lines, run_time=2),
)
# Rotate
self.play(
Rotate(sphere, TAU, axis=OUT, run_time=4),
Rotate(lat_lines, TAU, axis=OUT, run_time=4),
Rotate(long_lines, TAU, axis=OUT, run_time=4),
)
self.wait()
class ConeUnfolding(InteractiveScene):
"""
A cone that unfolds into a flat sector.
Demonstrates surface transformation.
"""
def construct(self):
frame = self.frame
frame.reorient(-30, 70, 0)
frame.set_height(8)
# Create cone
height = 3
radius = 2
cone = ParametricSurface(
lambda u, v: [
v * radius / height * np.cos(u),
v * radius / height * np.sin(u),
height - v
],
u_range=(0, TAU),
v_range=(0, height),
resolution=(30, 10),
)
cone.set_color(BLUE_E)
cone.set_opacity(0.8)
self.play(ShowCreation(cone, run_time=2))
self.wait()
# Animate camera
self.play(
frame.animate.reorient(0, 0, 0).set_height(10),
run_time=2
)
self.wait()
class SaddleSurface(InteractiveScene):
"""
Hyperbolic paraboloid (saddle surface).
Classic example of negative Gaussian curvature.
"""
def construct(self):
frame = self.frame
frame.reorient(-40, 70, 0)
# Create saddle: z = x^2 - y^2
surface = ParametricSurface(
lambda u, v: [u, v, 0.3 * (u**2 - v**2)],
u_range=(-2, 2),
v_range=(-2, 2),
resolution=(20, 20),
)
# Color gradient based on z
surface.set_color(BLUE)
surface.set_opacity(0.9)
# Axes
axes = ThreeDAxes(
x_range=(-3, 3, 1),
y_range=(-3, 3, 1),
z_range=(-2, 2, 1),
)
self.play(ShowCreation(axes))
self.play(ShowCreation(surface, run_time=2))
# Show cross sections
x_section = ParametricCurve(
lambda t: [t, 0, 0.3 * t**2],
t_range=(-2, 2, 0.1),
color=RED,
stroke_width=4,
)
y_section = ParametricCurve(
lambda t: [0, t, -0.3 * t**2],
t_range=(-2, 2, 0.1),
color=BLUE,
stroke_width=4,
)
self.play(ShowCreation(x_section))
self.play(ShowCreation(y_section))
# Rotate view
self.play(
frame.animate.reorient(60, 60, 0),
run_time=4
)
self.wait()
class TorusSurface(InteractiveScene):
"""
Creates a torus (donut shape).
Classic example of parametric surface.
"""
def construct(self):
frame = self.frame
frame.reorient(-30, 70, 0)
# Torus parameters
R = 2 # Major radius
r = 0.7 # Minor radius
torus = ParametricSurface(
lambda u, v: [
(R + r * np.cos(v)) * np.cos(u),
(R + r * np.cos(v)) * np.sin(u),
r * np.sin(v)
],
u_range=(0, TAU),
v_range=(0, TAU),
resolution=(40, 20),
)
torus.set_color(BLUE_D)
torus.set_opacity(0.8)
self.play(ShowCreation(torus, run_time=3))
# Rotate the torus
self.play(
Rotate(torus, TAU, axis=UP, run_time=6, rate_func=linear),
)
# Camera orbit
self.play(
frame.animate.reorient(150, 50, 0),
run_time=4
)
self.wait()
examples/three_d_vector_space.py
"""
3D Vector Space Example
Demonstrates how coordinates create a point in 3D space with animated construction.
Based on: videos/_2024/transformers/embedding.py - ThreeDSpaceExample
"""
from manimlib import *
class ThreeDVectorSpace(InteractiveScene):
"""
Visualizes how 3D coordinates define a point in space.
Shows step-by-step construction along x, y, z axes.
"""
def construct(self):
# Set up 3D frame and axes
frame = self.frame
frame.reorient(-15, 78, 0, (1.07, 1.71, 1.41), 6.72)
frame.add_ambient_rotation(1 * DEGREES)
axes = ThreeDAxes((-5, 5), (-5, 5), (-4, 4))
plane = NumberPlane((-5, 5), (-5, 5))
plane.set_stroke(opacity=0.5)
self.add(plane)
self.add(axes)
# Target coordinates
x, y, z = coordinates = np.array([3, 1, 2])
colors = [RED, GREEN, BLUE]
# Create coordinate display (fixed in frame)
coords = DecimalMatrix(np.zeros((3, 1)), num_decimal_places=1)
coords.fix_in_frame()
coords.to_corner(UR)
coords.shift(1.5 * LEFT)
coords.get_entries().set_submobject_colors_by_gradient(*colors)
# Create path lines for x, y, z components
lines = VGroup(
Line(axes.c2p(0, 0, 0), axes.c2p(x, 0, 0)),
Line(axes.c2p(x, 0, 0), axes.c2p(x, y, 0)),
Line(axes.c2p(x, y, 0), axes.c2p(x, y, z)),
)
lines.set_flat_stroke(False)
lines.set_submobject_colors_by_gradient(*colors)
# Create axis labels
labels = VGroup(*map(Tex, "xyz"))
labels.rotate(89 * DEGREES, RIGHT)
directions = [OUT, OUT + RIGHT, RIGHT]
for label, line, direction in zip(labels, lines, directions):
label.next_to(line, direction, buff=SMALL_BUFF)
label.match_color(line)
# Glowing dot to track position
dot = GlowDot(color=WHITE)
dot.move_to(axes.get_origin())
# Final vector arrow
vect = Arrow(axes.get_origin(), axes.c2p(x, y, z), buff=0)
vect.set_flat_stroke(False)
# Show coordinate matrix
self.add(coords)
# Animate building the vector step by step
for entry, line, label, value in zip(coords.get_entries(), lines, labels, coordinates):
rect = SurroundingRectangle(entry)
rect.set_fill(line.get_color(), opacity=0.3)
rect.set_stroke(line.get_color(), width=2, opacity=1.0)
self.play(
ShowCreation(line),
FadeInFromPoint(label, line.get_start()),
FadeIn(rect, rate_func=there_and_back),
ChangeDecimalToValue(entry, value),
dot.animate.move_to(line.get_end()),
)
self.wait(0.5)
# Show the complete vector
self.play(ShowCreation(vect))
self.wait(3)
# Show many random points
points = GlowDots(np.random.uniform(-3, 3, size=(50, 3)), radius=0.1)
frame.clear_updaters()
self.play(
FadeOut(coords),
FadeOut(dot),
FadeOut(plane),
LaggedStartMap(FadeOut, VGroup(*lines, vect, *labels)),
frame.animate.reorient(-81, 61, 0, (-0.82, 0.6, 0.36), 8.95),
ShowCreation(points),
run_time=2,
)
frame.add_ambient_rotation(5 * DEGREES)
self.wait(5)
examples/token_embeddings_flow.py
"""
Token Embeddings Flow Visualization
Shows tokens being converted to embeddings and then updated through attention.
"""
from manimlib import *
import numpy as np
def value_to_color(
value,
low_positive_color=BLUE_E,
high_positive_color=BLUE_B,
low_negative_color=RED_E,
high_negative_color=RED_B,
min_value=0.0,
max_value=10.0
):
"""Map a value to a color based on sign and magnitude."""
alpha = clip(float(inverse_interpolate(min_value, max_value, abs(value))), 0, 1)
if value >= 0:
return interpolate_color(low_positive_color, high_positive_color, alpha)
else:
return interpolate_color(low_negative_color, high_negative_color, alpha)
class NumericEmbedding(DecimalMatrix):
"""A column vector (embedding) with color-coded entries."""
def __init__(
self,
values=None,
length=7,
value_range=(-9.9, 9.9),
ellipses_row=-2,
num_decimal_places=1,
bracket_h_buff=0.1,
**kwargs
):
if values is None:
values = np.random.uniform(*value_range, size=(length, 1))
elif len(values.shape) == 1:
values = values.reshape((values.shape[0], 1))
self.value_range = value_range
super().__init__(
values,
num_decimal_places=num_decimal_places,
bracket_h_buff=bracket_h_buff,
decimal_config=dict(include_sign=True),
ellipses_row=ellipses_row,
ellipses_col=None,
)
self.reset_entry_colors()
def reset_entry_colors(self):
for entry in self.get_entries():
entry.set_fill(color=value_to_color(
entry.get_value(),
low_positive_color=GREY_C,
high_positive_color=WHITE,
low_negative_color=GREY_C,
high_negative_color=WHITE,
min_value=0,
max_value=max(self.value_range),
))
return self
class TokenEmbeddingsFlow(InteractiveScene):
def construct(self):
# Create sentence
phrase = "a fluffy blue creature"
phrase_mob = Text(phrase, font_size=42)
phrase_mob.to_edge(UP, buff=1)
words = phrase.split()
word_mobs = VGroup()
for word in words:
word_mob = phrase_mob[word][0]
word_mobs.add(word_mob)
self.play(
LaggedStartMap(FadeIn, word_mobs, shift=0.5 * UP, lag_ratio=0.15)
)
self.wait()
# Create colored rectangles around words
colors = [GREY, TEAL, BLUE, ORANGE]
rects = VGroup()
for word_mob, color in zip(word_mobs, colors):
rect = SurroundingRectangle(word_mob, buff=0.1)
rect.set_stroke(color, 2)
rect.set_fill(color, 0.2)
rects.add(rect)
self.play(LaggedStartMap(DrawBorderThenFill, rects, lag_ratio=0.1))
self.wait()
# Create embeddings below each word
np.random.seed(42)
embeddings = VGroup(
NumericEmbedding(length=8).set_height(2.5)
for _ in word_mobs
)
embeddings.arrange(RIGHT, buff=0.6)
embeddings.next_to(rects, DOWN, buff=1.5)
# Arrows from words to embeddings
arrows = VGroup(
Arrow(rect.get_bottom(), emb.get_top(), buff=0.15)
for rect, emb in zip(rects, embeddings)
)
# Labels for embeddings
e_template = Tex(R"\vec{\textbf{E}}_0", font_size=36)
e_subscript = e_template.make_number_changeable("0")
e_labels = VGroup()
for n, emb in enumerate(embeddings, start=1):
e_subscript.set_value(n)
label = e_template.copy()
label.set_color(GREY_A)
label.next_to(emb, DOWN, buff=0.3)
e_labels.add(label)
self.play(
LaggedStartMap(GrowArrow, arrows, lag_ratio=0.1),
LaggedStartMap(FadeIn, embeddings, shift=0.5 * DOWN, lag_ratio=0.1),
LaggedStartMap(FadeIn, e_labels, shift=0.2 * DOWN, lag_ratio=0.1),
run_time=2
)
self.wait()
# Show attention arrows (adjectives -> noun)
# fluffy -> creature, blue -> creature
attention_arrows = VGroup(
Arrow(
embeddings[1].get_top() + 0.3 * UP,
embeddings[3].get_top() + 0.3 * UP,
path_arc=-120 * DEGREES,
buff=0.1
).set_stroke(TEAL, 3),
Arrow(
embeddings[2].get_top() + 0.3 * UP,
embeddings[3].get_top() + 0.3 * UP,
path_arc=-90 * DEGREES,
buff=0.1
).set_stroke(BLUE, 3),
)
attention_label = Text("Attention", font_size=30)
attention_label.next_to(attention_arrows, UP, buff=0.2)
attention_label.set_color(YELLOW)
self.play(
LaggedStartMap(ShowCreation, attention_arrows, lag_ratio=0.3),
FadeIn(attention_label, shift=0.2 * DOWN),
run_time=1.5
)
self.wait()
# Show updated embedding for "creature"
updated_emb = embeddings[3].copy()
updated_emb.set_color(YELLOW)
prime = Tex("'", font_size=48)
prime.next_to(e_labels[3], RIGHT, buff=0)
prime.shift(0.1 * UL)
prime.set_color(YELLOW)
update_label = Text("Updated with context!", font_size=28)
update_label.set_color(YELLOW)
update_label.next_to(embeddings[3], RIGHT, buff=0.5)
# Animate the update
self.play(
embeddings[3].animate.set_color(YELLOW),
FadeIn(prime),
Write(update_label),
run_time=1.5
)
self.wait()
# Final message
final_message = Text(
"Now 'creature' knows about 'fluffy' and 'blue'",
font_size=32
)
final_message.to_edge(DOWN)
self.play(Write(final_message))
self.wait(2)
examples/token_probability_distribution.py
"""
Token Probability Distribution Visualization
Demonstrates how to visualize next-token probability distributions
as animated bar charts - a key component of autoregressive generation.
Run with: manimgl token_probability_distribution.py TokenProbabilityDistribution
"""
from manimlib import *
import numpy as np
def get_paragraph(words, line_len=40, font_size=48):
"""Handle word wrapping for text display."""
words = list(map(str.strip, words))
word_lens = list(map(len, words))
lines = []
lh, rh = 0, 0
while rh < len(words):
rh += 1
if sum(word_lens[lh:rh]) > line_len:
rh -= 1
lines.append(words[lh:rh])
lh = rh
lines.append(words[lh:])
text = "\n".join([" ".join(line).strip() for line in lines])
return Text(text, alignment="LEFT", font_size=font_size)
class TokenProbabilityDistribution(InteractiveScene):
"""
Visualizes a probability distribution over next tokens.
Shows how language models output probabilities for each possible next word.
"""
def construct(self):
# Sample predictions with probabilities
predictions = [" habitat", " environment", " forest", " home", " land", " world", " area"]
probs = np.array([0.35, 0.25, 0.15, 0.10, 0.08, 0.05, 0.02])
# Input context
context = "Behold, a wild pi creature, foraging in its native"
context_mob = get_paragraph(context.split(" "), line_len=35, font_size=36)
context_mob.to_edge(UP, buff=0.5)
context_mob.set_color(BLUE_B)
# Next word indicator
next_word_line = Underline(context_mob[-6:])
next_word_line.set_stroke(TEAL, 2)
next_word_line.next_to(context_mob[-1], RIGHT, SMALL_BUFF, aligned_edge=DOWN)
# Build the distribution visualization
bar_groups = self.build_distribution(predictions, probs)
bar_groups.next_to(context_mob, DOWN, buff=1.0)
bar_groups.shift(RIGHT)
# Title
title = Text("Next Token Probabilities", font_size=42)
title.to_edge(UP, buff=0.1)
title.set_color(YELLOW)
# Animate
self.play(Write(title))
self.play(
FadeIn(context_mob, lag_ratio=0.02),
ShowCreation(next_word_line),
)
self.wait(0.5)
# Animate bars appearing
self.play(
LaggedStart(
*(FadeIn(bg, shift=LEFT) for bg in bar_groups),
lag_ratio=0.1,
run_time=2
)
)
self.wait()
# Highlight top prediction
highlight = SurroundingRectangle(bar_groups[0], buff=0.05)
highlight.set_stroke(YELLOW, 3)
highlight.set_fill(YELLOW, 0.2)
self.play(ShowCreation(highlight))
self.wait()
# Show that probabilities sum to 1
sum_label = Tex(R"\sum P = 1", font_size=36)
sum_label.next_to(bar_groups, RIGHT, buff=0.5)
self.play(Write(sum_label))
self.wait(2)
def build_distribution(
self,
words,
probs,
font_size=24,
width_100p=3.0,
bar_height=0.3
):
"""Build bar chart visualization of token probabilities."""
labels = VGroup(*(Text(word, font_size=font_size) for word in words))
bars = VGroup(*(
Rectangle(prob * width_100p, bar_height)
for prob in probs
))
bars.arrange(DOWN, aligned_edge=LEFT, buff=0.4 * bar_height)
bars.set_fill(opacity=1)
bars.set_submobject_colors_by_gradient(TEAL, YELLOW)
bars.set_stroke(WHITE, 1)
bar_groups = VGroup()
for label, bar, prob in zip(labels, bars, probs):
prob_label = Integer(int(100 * prob), unit="%", font_size=0.75 * font_size)
prob_label.next_to(bar, RIGHT, buff=SMALL_BUFF)
label.next_to(bar, LEFT)
bar_groups.add(VGroup(label, bar, prob_label))
# Add ellipsis to indicate more tokens
ellipses = Tex(R"\vdots", font_size=font_size)
ellipses.next_to(bar_groups[-1][0], DOWN)
bar_groups.add(ellipses)
return bar_groups
class AnimatedDistributionBars(InteractiveScene):
"""
Shows probability distribution bars animating as context changes.
Demonstrates how the distribution shifts based on input.
"""
def construct(self):
# Two different contexts
context1 = "The cat sat on the"
context2 = "The astronaut floated in"
# Different probability distributions for each context
predictions1 = [" mat", " floor", " chair", " bed", " couch"]
probs1 = np.array([0.40, 0.25, 0.15, 0.12, 0.08])
predictions2 = [" space", " air", " void", " capsule", " orbit"]
probs2 = np.array([0.45, 0.20, 0.18, 0.10, 0.07])
# Create context displays
ctx1_mob = Text(context1, font_size=32)
ctx1_mob.to_edge(UP, buff=1.0)
ctx1_mob.set_color(BLUE_B)
# Build first distribution
bar_groups1 = self.build_simple_distribution(predictions1, probs1)
bar_groups1.center()
bar_groups1.shift(0.5 * DOWN)
# Show first context and distribution
self.play(Write(ctx1_mob))
self.play(FadeIn(bar_groups1, lag_ratio=0.1))
self.wait()
# Transform to second context
ctx2_mob = Text(context2, font_size=32)
ctx2_mob.to_edge(UP, buff=1.0)
ctx2_mob.set_color(GREEN_B)
bar_groups2 = self.build_simple_distribution(predictions2, probs2)
bar_groups2.center()
bar_groups2.shift(0.5 * DOWN)
self.play(
ReplacementTransform(ctx1_mob, ctx2_mob),
ReplacementTransform(bar_groups1, bar_groups2),
run_time=2
)
self.wait(2)
def build_simple_distribution(self, words, probs, font_size=28, width_100p=4.0, bar_height=0.4):
"""Build a simple bar chart for probabilities."""
bar_groups = VGroup()
for word, prob in zip(words, probs):
label = Text(word, font_size=font_size)
bar = Rectangle(prob * width_100p, bar_height)
bar.set_fill(interpolate_color(RED, GREEN, prob), opacity=0.8)
bar.set_stroke(WHITE, 1)
prob_label = Integer(int(100 * prob), unit="%", font_size=font_size * 0.8)
prob_label.next_to(bar, RIGHT, buff=SMALL_BUFF)
label.next_to(bar, LEFT)
bar_groups.add(VGroup(label, bar, prob_label))
bar_groups.arrange(DOWN, aligned_edge=LEFT, buff=0.3)
return bar_groups
examples/token_sampling.py
"""
Token Sampling Animation
Demonstrates the random sampling process used in autoregressive generation,
where the next token is sampled from the probability distribution.
Run with: manimgl token_sampling.py TokenSamplingAnimation
"""
from manimlib import *
import numpy as np
import random
class TokenSamplingAnimation(InteractiveScene):
"""
Shows how tokens are randomly sampled from a probability distribution.
The highlight rectangle bounces between options before settling.
"""
def construct(self):
# Title
title = Text("Sampling from Distribution", font_size=36)
title.to_edge(UP, buff=0.5)
self.play(Write(title))
# Create distribution
words = [" habitat", " environment", " forest", " home", " land"]
probs = np.array([0.35, 0.28, 0.20, 0.12, 0.05])
probs = probs / probs.sum() # Normalize
bar_groups = self.build_distribution(words, probs)
bar_groups.center()
bar_groups.shift(0.5 * DOWN)
self.play(FadeIn(bar_groups, lag_ratio=0.1))
self.wait(0.5)
# Create highlight rectangle
highlight = SurroundingRectangle(bar_groups[0], buff=0.05)
highlight.set_stroke(YELLOW, 3)
highlight.set_fill(YELLOW, 0.25)
# Animate random sampling
seed = random.randint(0, 1000)
def highlight_randomly(rect, alpha):
np.random.seed(seed + int(15 * alpha))
index = np.random.choice(len(words), p=probs)
rect.surround(bar_groups[index], buff=0.05)
rect.stretch(1.05, 0)
self.play(FadeIn(highlight))
self.play(
UpdateFromAlphaFunc(
highlight,
lambda r, a: highlight_randomly(r, a)
),
run_time=2.5,
rate_func=linear
)
# Final selection
final_index = np.random.choice(len(words), p=probs)
final_highlight = SurroundingRectangle(bar_groups[final_index], buff=0.05)
final_highlight.set_stroke(GREEN, 4)
final_highlight.set_fill(GREEN, 0.3)
self.play(Transform(highlight, final_highlight))
# Show selected word
selected_word = Text(words[final_index].strip(), font_size=48)
selected_word.set_color(GREEN)
selected_word.next_to(bar_groups, RIGHT, buff=1.0)
selected_label = Text("Selected:", font_size=28)
selected_label.next_to(selected_word, UP)
self.play(
Write(selected_label),
FadeIn(selected_word, scale=1.5)
)
self.wait(2)
def build_distribution(self, words, probs, font_size=28, width_100p=4.0, bar_height=0.35):
"""Build bar chart visualization."""
bar_groups = VGroup()
for word, prob in zip(words, probs):
label = Text(word, font_size=font_size)
bar = Rectangle(prob * width_100p, bar_height)
bar.set_fill(interpolate_color(BLUE_E, TEAL, prob / max(probs)), opacity=0.9)
bar.set_stroke(WHITE, 1)
prob_label = Integer(int(100 * prob), unit="%", font_size=font_size * 0.8)
prob_label.next_to(bar, RIGHT, buff=SMALL_BUFF)
label.next_to(bar, LEFT)
bar_groups.add(VGroup(label, bar, prob_label))
bar_groups.arrange(DOWN, aligned_edge=LEFT, buff=0.25)
return bar_groups
class TemperatureSampling(InteractiveScene):
"""
Demonstrates how temperature affects the sampling distribution.
Higher temperature = more uniform, lower = more peaked.
"""
def construct(self):
def softmax(logits, temperature=1.0):
"""Compute softmax with temperature scaling."""
logits = np.array(logits, dtype=float)
logits = logits - np.max(logits)
if temperature == 0:
result = np.zeros_like(logits)
result[np.argmax(logits)] = 1.0
return result
exps = np.exp(logits / temperature)
return exps / np.sum(exps)
# Base logits (before softmax)
logits = np.array([2.5, 2.0, 1.5, 1.0, 0.5])
words = ["word1", "word2", "word3", "word4", "word5"]
# Different temperatures
temperatures = [0.5, 1.0, 2.0]
temp_labels = ["T = 0.5 (focused)", "T = 1.0 (normal)", "T = 2.0 (creative)"]
# Create three distributions side by side
dist_groups = VGroup()
for temp, label in zip(temperatures, temp_labels):
probs = softmax(logits, temp)
bars = self.build_mini_distribution(probs)
title = Text(label, font_size=22)
title.next_to(bars, UP, buff=0.3)
dist_groups.add(VGroup(title, bars))
dist_groups.arrange(RIGHT, buff=1.0)
dist_groups.center()
# Main title
main_title = Text("Temperature Effect on Sampling", font_size=36)
main_title.to_edge(UP, buff=0.5)
# Animate
self.play(Write(main_title))
self.play(
LaggedStart(
*(FadeIn(dg, shift=UP) for dg in dist_groups),
lag_ratio=0.3
)
)
self.wait()
# Highlight differences
arrows = VGroup()
for i, (dg, temp) in enumerate(zip(dist_groups, temperatures)):
if temp == 0.5:
note = Text("More deterministic", font_size=18, color=BLUE)
elif temp == 2.0:
note = Text("More random", font_size=18, color=RED)
else:
note = Text("Balanced", font_size=18, color=GREEN)
note.next_to(dg, DOWN, buff=0.3)
arrows.add(note)
self.play(FadeIn(arrows, lag_ratio=0.2))
self.wait(2)
def build_mini_distribution(self, probs, bar_width=1.5, bar_height=0.2):
"""Build a compact bar chart."""
bars = VGroup()
for prob in probs:
bar = Rectangle(prob * bar_width, bar_height)
bar.set_fill(interpolate_color(GREY_D, TEAL, prob), opacity=0.9)
bar.set_stroke(WHITE, 1)
bars.add(bar)
bars.arrange(DOWN, aligned_edge=LEFT, buff=0.1)
return bars
examples/token_to_embedding.py
"""
Token to Embedding Visualization
Shows the transformation from text tokens to vector embeddings.
Based on 3Blue1Brown's transformer visualizations.
Run: manimgl token_to_embedding.py TokenToEmbedding -o
"""
from manimlib import *
import numpy as np
import random
def value_to_color(
value,
low_positive_color=BLUE_E,
high_positive_color=BLUE_B,
low_negative_color=RED_E,
high_negative_color=RED_B,
min_value=0.0,
max_value=10.0
):
"""Map a value to a color based on its sign and magnitude."""
alpha = np.clip(float((abs(value) - min_value) / (max_value - min_value)), 0, 1)
if value >= 0:
return interpolate_color(low_positive_color, high_positive_color, alpha)
else:
return interpolate_color(low_negative_color, high_negative_color, alpha)
def random_bright_color(hue_range=(0.0, 1.0)):
"""Generate a random bright color within a hue range."""
hue = random.uniform(*hue_range)
return Color(hsl=(hue, 0.7, 0.6))
class SimpleNumericEmbedding(VGroup):
"""A simplified numeric embedding visualization."""
def __init__(self, length=8, height=2.5, width=0.5, bracket_color=GREY_B, **kwargs):
super().__init__(**kwargs)
entries = VGroup()
entry_height = (height / length) * 0.85
for _ in range(length):
value = random.uniform(-9.9, 9.9)
rect = Rectangle(width=width * 0.8, height=entry_height)
rect.set_fill(value_to_color(value), opacity=0.9)
rect.set_stroke(WHITE, 0.5)
entries.add(rect)
entries.arrange(DOWN, buff=0.02)
entries.set_height(height)
# Brackets
lb = Text("[", font_size=72)
rb = Text("]", font_size=72)
lb.stretch_to_fit_height(height * 1.1)
rb.stretch_to_fit_height(height * 1.1)
lb.set_color(bracket_color)
rb.set_color(bracket_color)
lb.next_to(entries, LEFT, buff=0.05)
rb.next_to(entries, RIGHT, buff=0.05)
self.add(lb, entries, rb)
self.entries = entries
self.brackets = VGroup(lb, rb)
class TokenToEmbedding(Scene):
"""
Demonstrates the conversion of text tokens into vector embeddings.
Shows how each word/token in a sentence gets converted into
a numerical vector representation.
"""
example_text = "The quick brown fox"
def construct(self):
# Show the phrase
phrase = Text(self.example_text, font_size=60)
phrase.to_edge(UP, buff=1)
self.play(Write(phrase))
self.wait()
# Split into words/tokens
word_strings = self.example_text.split()
colors = [BLUE, GREEN, YELLOW, RED]
word_groups = VGroup()
for i, word_str in enumerate(word_strings):
word = phrase[word_str][0]
rect = SurroundingRectangle(word, buff=0.1)
rect.set_stroke(colors[i % len(colors)], 2)
rect.set_fill(colors[i % len(colors)], 0.2)
word_groups.add(VGroup(rect, word.copy()))
# Animate word rectangles appearing
self.play(
LaggedStart(*(
DrawBorderThenFill(wg[0])
for wg in word_groups
), lag_ratio=0.2),
run_time=2
)
self.wait()
# Create embedding vectors
vectors = VGroup(*(
SimpleNumericEmbedding(length=10, height=3.0, width=0.6)
for _ in word_strings
))
vectors.arrange(RIGHT, buff=1.0)
vectors.set_width(FRAME_WIDTH - 2)
vectors.to_edge(DOWN, buff=1)
# Color code the brackets
for vec, color in zip(vectors, colors):
vec.brackets.set_color(color)
# Position token blocks above vectors
token_blocks = VGroup()
for i, (wg, vec) in enumerate(zip(word_groups, vectors)):
block = wg.copy()
block.set_width(vec.get_width() * 1.2)
block.next_to(vec, UP, buff=1.5)
token_blocks.add(block)
# Create arrows
arrows = VGroup(*(
Arrow(block.get_bottom(), vec.get_top(), stroke_width=3, buff=0.1)
for block, vec in zip(token_blocks, vectors)
))
for arrow, color in zip(arrows, colors):
arrow.set_color(color)
# Animate transformation
self.play(
ReplacementTransform(
VGroup(*(wg.copy() for wg in word_groups)),
token_blocks
),
self.frame.animate.shift(0.5 * DOWN) if hasattr(self, 'frame') else Wait(),
run_time=2
)
self.play(
LaggedStartMap(GrowArrow, arrows, lag_ratio=0.2),
LaggedStartMap(FadeIn, vectors, shift=0.5 * DOWN, lag_ratio=0.2),
run_time=2
)
self.wait()
# Add dimension label
dim_label = Text("Each vector has d dimensions", font_size=36)
dim_label.next_to(vectors, DOWN)
brace = Brace(vectors[0], RIGHT)
dim_num = brace.get_tex("d = 12288", font_size=30)
self.play(
FadeIn(dim_label),
GrowFromCenter(brace),
FadeIn(dim_num),
)
self.wait(2)
# Cleanup
self.play(
FadeOut(VGroup(
phrase, word_groups, token_blocks, arrows, vectors,
dim_label, brace, dim_num
))
)
class EmbeddingArrayVisualization(Scene):
"""
Shows multiple embeddings arranged as an array/matrix.
"""
def construct(self):
# Title
title = Text("Embedding Array", font_size=56)
title.to_edge(UP)
# Create array of embeddings
n_tokens = 7
n_dims = 10
# Create the embedding columns
columns = VGroup()
for i in range(n_tokens):
col = VGroup()
for j in range(n_dims):
value = random.uniform(-10, 10)
rect = Rectangle(width=0.5, height=0.35)
rect.set_fill(value_to_color(value), opacity=0.9)
rect.set_stroke(WHITE, 0.5)
col.add(rect)
col.arrange(DOWN, buff=0.02)
columns.add(col)
columns.arrange(RIGHT, buff=0.3)
# Add brackets
left_bracket = Tex(r"\left[", font_size=120)
right_bracket = Tex(r"\right]", font_size=120)
left_bracket.stretch_to_fit_height(columns.get_height() * 1.1)
right_bracket.stretch_to_fit_height(columns.get_height() * 1.1)
left_bracket.next_to(columns, LEFT, buff=0.1)
right_bracket.next_to(columns, RIGHT, buff=0.1)
array = VGroup(left_bracket, columns, right_bracket)
array.center()
# Token labels
token_labels = VGroup(*(
Text(f"t{i}", font_size=24)
for i in range(n_tokens)
))
for label, col in zip(token_labels, columns):
label.next_to(col, UP, buff=0.3)
# Dimension label
dim_brace = Brace(columns[0], LEFT)
dim_label = dim_brace.get_text("d", font_size=36)
# Animate
self.play(Write(title))
self.play(
LaggedStartMap(FadeIn, columns, shift=0.3 * DOWN, lag_ratio=0.1),
run_time=2
)
self.play(
FadeIn(left_bracket, shift=0.2 * LEFT),
FadeIn(right_bracket, shift=0.2 * RIGHT),
)
self.play(LaggedStartMap(FadeIn, token_labels, shift=0.2 * DOWN, lag_ratio=0.1))
self.play(
GrowFromCenter(dim_brace),
FadeIn(dim_label),
)
self.wait()
# Highlight one column
highlight_rect = SurroundingRectangle(columns[3], buff=0.1)
highlight_rect.set_stroke(YELLOW, 3)
self.play(ShowCreation(highlight_rect))
self.wait()
# Show context note
context_note = Text(
"Each column encodes one token's meaning + context",
font_size=30
)
context_note.next_to(array, DOWN, buff=1)
self.play(FadeIn(context_note, shift=UP))
self.wait(2)
# Cleanup
self.play(FadeOut(VGroup(
title, array, token_labels, dim_brace, dim_label,
highlight_rect, context_note
)))
examples/tokenization_demo.py
"""
Tokenization Visualization Demo
Shows how text gets broken into tokens with colored rectangles.
Based on: videos/_2024/transformers/embedding.py - LyingAboutTokens2
"""
from manimlib import *
def break_into_words(phrase_mob):
"""Break a Text mobject into individual word submobjects."""
import re
phrase = phrase_mob.get_string()
offsets = [m.start() for m in re.finditer(" ", phrase)]
return break_into_pieces(phrase_mob, [0, *offsets])
def break_into_pieces(phrase_mob, offsets):
"""Break a Text mobject at specified character offsets."""
phrase = phrase_mob.get_string()
lhs = offsets
rhs = [*offsets[1:], len(phrase)]
result = []
for lh, rh in zip(lhs, rhs):
substr = phrase[lh:rh]
start = phrase_mob.substr_to_path_count(phrase[:lh])
end = start + phrase_mob.substr_to_path_count(substr)
result.append(phrase_mob[start:end])
return VGroup(*result)
def random_bright_color(hue_range=(0.5, 0.6)):
"""Generate a random bright color within a hue range."""
import random
hue = random.uniform(*hue_range)
return Color(hsl=(hue, 0.8, 0.6))
def get_piece_rectangles(
phrase_pieces,
h_buff=0.05,
v_buff=0.1,
fill_opacity=0.15,
fill_color=None,
stroke_width=1,
stroke_color=None,
hue_range=(0.5, 0.6),
leading_spaces=False,
):
"""Create colored rectangles around text pieces."""
rects = VGroup()
height = phrase_pieces.get_height() + 2 * v_buff
last_right_x = phrase_pieces.get_x(LEFT)
for piece in phrase_pieces:
left_x = last_right_x if leading_spaces else piece.get_x(LEFT)
right_x = piece.get_x(RIGHT)
fill = random_bright_color(hue_range) if fill_color is None else fill_color
stroke = fill if stroke_color is None else stroke_color
rect = Rectangle(
width=right_x - left_x + 2 * h_buff,
height=height,
fill_color=fill,
fill_opacity=fill_opacity,
stroke_color=stroke,
stroke_width=stroke_width
)
if leading_spaces:
rect.set_x(left_x, LEFT)
else:
rect.move_to(piece)
rect.set_y(0)
rects.add(rect)
last_right_x = right_x
rects.match_y(phrase_pieces)
return rects
class TokenizationDemo(InteractiveScene):
"""
Demonstrates how text is broken into tokens/words with visual highlighting.
"""
def construct(self):
# Title
title = Text("Tokenization", font_size=72)
title.to_edge(UP)
self.play(Write(title))
self.wait()
# Show a phrase being tokenized
phrase = Text("The goal of our model is to predict the next word")
phrase.set_width(FRAME_WIDTH - 2)
phrase.next_to(title, DOWN, buff=1.0)
self.play(Write(phrase, run_time=2))
self.wait()
# Break into words
words = break_into_words(phrase)
rects = get_piece_rectangles(words, hue_range=(0.5, 0.6))
# Animate rectangles appearing
self.add(rects, phrase)
self.play(
LaggedStartMap(FadeIn, rects, lag_ratio=0.1),
LaggedStart(*(
word.animate.set_color(rect.get_color())
for word, rect in zip(words, rects)
), lag_ratio=0.1)
)
self.wait()
# Highlight last word as prediction target
last_rect = rects[-1]
q_marks = Text("???", font_size=48)
q_marks.next_to(last_rect, DOWN)
self.play(
last_rect.animate.set_color(YELLOW),
FadeIn(q_marks)
)
self.wait()
# Show arrow from context to prediction
context_rect = Rectangle()
context_rect.replace(rects[:-1], stretch=True)
context_rect.set_stroke(WHITE, 2)
arrow = Arrow(context_rect.get_top(), last_rect.get_top(), path_arc=-90 * DEGREES)
arrow.scale(0.6, about_edge=DR)
self.play(
FadeIn(context_rect),
GrowArrow(arrow),
)
self.wait()
# Transition to showing embedding concept
self.play(
FadeOut(title),
FadeOut(context_rect),
FadeOut(arrow),
FadeOut(q_marks),
)
# Show words becoming vectors
word_labels = VGroup(*(
Text(word.get_string().strip(), font_size=36)
for word in words[:-1]
))
# Create simple vector representations
vectors = VGroup(*(
VGroup(
Tex("["),
VGroup(*(
DecimalNumber(np.random.uniform(-1, 1), num_decimal_places=2)
for _ in range(4)
)).arrange(DOWN, buff=0.1),
Tex("]"),
).arrange(RIGHT, buff=0.05)
for _ in words[:-1]
))
for vector in vectors:
vector.scale(0.6)
# Arrange word-vector pairs
pairs = VGroup()
for word, vec, rect in zip(word_labels, vectors, rects[:-1]):
vec.get_brackets = lambda v=vec: VGroup(v[0], v[-1])
vec.get_brackets().match_color(rect.get_color())
pair = VGroup(word, vec)
pair.arrange(DOWN, buff=0.5)
pairs.add(pair)
pairs.arrange(RIGHT, buff=0.8)
pairs.set_width(FRAME_WIDTH - 1)
pairs.center()
# Animate transformation
self.play(
LaggedStart(*(
AnimationGroup(
ReplacementTransform(VGroup(rect, word), label),
FadeIn(vec, shift=DOWN),
)
for word, rect, label, vec in zip(words[:-1], rects[:-1], word_labels, vectors)
), lag_ratio=0.1),
FadeOut(rects[-1]),
FadeOut(words[-1]),
run_time=3
)
self.wait()
# Add title for embedding
embed_title = Text("Word Embeddings", font_size=60)
embed_title.to_edge(UP)
self.play(Write(embed_title))
self.wait(2)
examples/transit_animation.py
"""
Transit Animation
Simple but elegant animations showing objects crossing
in front of others. Useful for astronomical transits,
loading animations, or timing demonstrations.
Run: manimgl transit_animation.py TransitOfVenus -w
Preview: manimgl transit_animation.py TransitOfVenus -p
Source: Adapted from 3b1b's cosmic_distance video (2025)
"""
from manimlib import *
class TransitOfVenus(InteractiveScene):
"""
Venus (small dot) transiting across the Sun.
Shows how astronomers measured distances historically.
"""
def construct(self):
# Create the Sun (large yellow circle)
sun = Circle(radius=2.5)
sun.set_fill(YELLOW, opacity=0.8)
sun.set_stroke(ORANGE, width=3)
# Add some texture with a glow
sun_glow = Circle(radius=2.7)
sun_glow.set_fill(YELLOW, opacity=0.2)
sun_glow.set_stroke(width=0)
self.add(sun_glow, sun)
# Path for Venus transit
path = Line(3 * LEFT, 3 * RIGHT)
path.set_y(-0.5) # Slightly below center
# Venus as small black dot
venus = Dot(radius=0.08, color=BLACK)
venus.move_to(path.get_start())
venus.set_fill(BLACK, opacity=1)
self.add(venus)
# Show transit with periodic snapshots
velocity = 0.3
venus.add_updater(lambda m, dt: m.shift(dt * velocity * RIGHT))
# Collect snapshots
copies = VGroup()
self.add(copies)
wait_time = 0.8
n_snapshots = int(path.get_length() / velocity / wait_time)
for _ in range(n_snapshots):
self.wait(wait_time)
copy = venus.copy().clear_updaters()
copy.set_fill(BLACK, opacity=0.5)
copies.add(copy)
# Remove venus, show path
self.remove(venus)
path.set_stroke(BLACK, 2)
self.play(Transform(copies, VGroup(path)))
self.wait()
class OrbitalTransit(InteractiveScene):
"""
Shows a planet orbiting and periodically transiting
in front of its star from the viewer's perspective.
"""
def construct(self):
# Star
star = Circle(radius=1)
star.set_fill(YELLOW_E, opacity=1)
star.set_stroke(YELLOW, width=2)
# Orbit path (ellipse viewed at an angle)
orbit = Ellipse(width=5, height=1)
orbit.set_stroke(WHITE, 1, opacity=0.3)
self.add(orbit, star)
# Planet
planet = Dot(radius=0.15, color=BLUE)
planet.move_to(orbit.get_right())
# Orbit animation using angle tracker
angle = ValueTracker(0)
def update_planet(p):
a = angle.get_value()
x = 2.5 * np.cos(a)
y = 0.5 * np.sin(a)
p.move_to([x, y, 0])
# Depth effect: size changes based on y position
scale = 0.12 + 0.06 * np.sin(a)
p.set_width(2 * scale)
planet.add_updater(update_planet)
self.add(planet)
# Multiple orbits
self.play(
angle.animate.set_value(4 * TAU),
run_time=12,
rate_func=linear
)
class LoadingDots(InteractiveScene):
"""
Classic loading animation with dots.
Demonstrates phase-shifted periodic motion.
"""
def construct(self):
# Create three dots
n_dots = 3
dots = VGroup(*[
Dot(radius=0.15, color=BLUE)
for _ in range(n_dots)
])
dots.arrange(RIGHT, buff=0.5)
dots.center()
time = ValueTracker(0)
# Each dot oscillates with a phase shift
for i, dot in enumerate(dots):
phase = i * TAU / n_dots
original_y = dot.get_y()
dot.add_updater(
lambda m, o=original_y, p=phase: m.set_y(
o + 0.3 * np.sin(3 * time.get_value() + p)
)
)
self.add(dots)
# Animate
time.add_updater(lambda m, dt: m.increment_value(dt))
self.wait(5)
class WaveTransit(InteractiveScene):
"""
A wave propagating across the screen.
Good for demonstrating wave motion or signal propagation.
"""
def construct(self):
# Create axes
axes = Axes(
x_range=(-5, 5, 1),
y_range=(-2, 2, 1),
width=12,
height=4,
)
self.add(axes)
# Time tracker
t = ValueTracker(0)
# Wave function
def wave(x):
return np.sin(2 * x - 3 * t.get_value()) * np.exp(-0.1 * (x + 5 - t.get_value())**2)
# Wave curve
wave_curve = always_redraw(
lambda: axes.get_graph(wave, color=BLUE, stroke_width=3)
)
self.add(wave_curve)
# Propagate wave
self.play(
t.animate.set_value(10),
run_time=5,
rate_func=linear
)
self.wait()
class PendulumSwing(InteractiveScene):
"""
Simple pendulum animation.
Classic physics visualization.
"""
def construct(self):
# Pivot point
pivot = Dot(ORIGIN, color=WHITE)
# Pendulum parameters
length = 3
g = 10
omega = np.sqrt(g / length)
# Angle tracker (start displaced)
theta = ValueTracker(PI / 4)
# Bob
bob = Dot(radius=0.2, color=BLUE)
bob.add_updater(lambda m: m.move_to(
pivot.get_center() + length * np.array([
np.sin(theta.get_value()),
-np.cos(theta.get_value()),
0
])
))
# Rod
rod = Line(ORIGIN, DOWN)
rod.set_stroke(WHITE, 3)
rod.add_updater(lambda m: m.put_start_and_end_on(
pivot.get_center(),
bob.get_center()
))
# Trail
trail = TracedPath(
bob.get_center,
stroke_color=YELLOW,
stroke_width=1,
stroke_opacity=0.5
)
self.add(pivot, rod, bob, trail)
# Simple harmonic motion approximation
time = ValueTracker(0)
amplitude = PI / 4
def update_theta(m):
t = time.get_value()
m.set_value(amplitude * np.cos(omega * t) * np.exp(-0.05 * t))
theta.add_updater(update_theta)
time.add_updater(lambda m, dt: m.increment_value(dt))
self.wait(10)
examples/value_matrix_transform.py
"""
Value Matrix Transformation Visualization
Shows how the Value matrix transforms embeddings and how the weighted sum
of value vectors produces the output.
"""
from manimlib import *
import numpy as np
def value_to_color(
value,
low_positive_color=BLUE_E,
high_positive_color=BLUE_B,
low_negative_color=RED_E,
high_negative_color=RED_B,
min_value=0.0,
max_value=10.0
):
"""Map a value to a color based on sign and magnitude."""
alpha = clip(float(inverse_interpolate(min_value, max_value, abs(value))), 0, 1)
if value >= 0:
return interpolate_color(low_positive_color, high_positive_color, alpha)
else:
return interpolate_color(low_negative_color, high_negative_color, alpha)
class WeightMatrix(DecimalMatrix):
"""A matrix with color-coded entries based on value."""
def __init__(
self,
values=None,
shape=(5, 7),
value_range=(-9.9, 9.9),
ellipses_row=-2,
ellipses_col=-2,
num_decimal_places=1,
bracket_h_buff=0.1,
**kwargs
):
if values is None:
values = np.random.uniform(*value_range, size=shape)
self.shape = shape
self.value_range = value_range
self.ellipses_row = ellipses_row
super().__init__(
values,
num_decimal_places=num_decimal_places,
bracket_h_buff=bracket_h_buff,
decimal_config=dict(include_sign=True),
ellipses_row=ellipses_row,
ellipses_col=ellipses_col,
)
self.reset_entry_colors()
def reset_entry_colors(self):
for entry in self.get_entries():
entry.set_fill(color=value_to_color(
entry.get_value(),
min_value=0,
max_value=max(self.value_range),
))
return self
class ValueMatrixTransform(InteractiveScene):
def construct(self):
# Title
title = Text("Value Matrix: Creating Contextual Updates", font_size=42)
title.to_edge(UP)
self.play(Write(title))
# Create words with embeddings
words = ["fluffy", "blue", "creature"]
word_mobs = VGroup(Text(word, font_size=36) for word in words)
word_mobs.arrange(DOWN, buff=1.5)
word_mobs.shift(4 * LEFT + 0.5 * DOWN)
# Color code words
word_mobs[0].set_color(TEAL)
word_mobs[1].set_color(BLUE)
word_mobs[2].set_color(ORANGE)
# Embedding symbols
e_template = Tex(R"\vec{\textbf{E}}_0", font_size=36)
e_substr = e_template.make_number_changeable("0")
e_syms = VGroup()
e_arrows = VGroup()
for i, word in enumerate(word_mobs, start=1):
e_substr.set_value(i)
e_sym = e_template.copy()
e_sym.set_color(word.get_color())
arrow = Arrow(word.get_right(), word.get_right() + 0.8 * RIGHT, buff=0.1)
e_sym.next_to(arrow, RIGHT, buff=0.1)
e_syms.add(e_sym)
e_arrows.add(arrow)
self.play(
LaggedStartMap(FadeIn, word_mobs, shift=0.5 * RIGHT, lag_ratio=0.2),
)
self.play(
LaggedStartMap(GrowArrow, e_arrows, lag_ratio=0.2),
LaggedStartMap(FadeIn, e_syms, shift=0.5 * RIGHT, lag_ratio=0.2),
)
self.wait()
# Value matrix
np.random.seed(42)
matrix = WeightMatrix(shape=(5, 7))
matrix.set_height(2.5)
matrix.move_to(0.5 * DOWN)
mat_label = Tex("W_V", font_size=48)
mat_label.set_color(RED)
mat_label.next_to(matrix, UP)
# Value vectors
v_template = Tex(R"\vec{\textbf{V}}_0", font_size=36)
v_template.set_color(RED)
v_substr = v_template.make_number_changeable("0")
v_syms = VGroup()
v_arrows = VGroup()
for i, e_sym in enumerate(e_syms, start=1):
v_substr.set_value(i)
v_arrow = Arrow(ORIGIN, 0.8 * RIGHT, buff=0)
v_arrow.next_to(matrix, RIGHT, buff=0.3)
v_arrow.match_y(e_sym)
v_sym = v_template.copy()
v_sym.next_to(v_arrow, RIGHT, buff=0.1)
v_syms.add(v_sym)
v_arrows.add(v_arrow)
# Show transformation
self.play(
FadeIn(matrix, lag_ratio=0.01),
FadeIn(mat_label, shift=0.25 * UP),
)
self.wait()
# Transform each E to V
for e_sym, v_arrow, v_sym in zip(e_syms, v_arrows, v_syms):
self.play(
TransformFromCopy(e_sym, v_sym, path_arc=-30 * DEGREES),
GrowArrow(v_arrow),
run_time=0.7
)
self.wait()
# Show weighted sum
weighted_label = Text("Weighted Sum of Values", font_size=36)
weighted_label.to_edge(RIGHT)
weighted_label.shift(UP)
# Attention weights
weights = [0.6, 0.3, 0.1]
weight_labels = VGroup()
for w, v_sym in zip(weights, v_syms):
w_label = DecimalNumber(w, num_decimal_places=1, font_size=30)
w_label.next_to(v_sym, RIGHT, buff=0.3)
w_label.set_color(YELLOW)
weight_labels.add(w_label)
times_syms = VGroup(
Tex(R"\times", font_size=30).next_to(wl, LEFT, buff=0.1)
for wl in weight_labels
)
self.play(
Write(weighted_label),
LaggedStartMap(FadeIn, weight_labels, shift=0.2 * LEFT, lag_ratio=0.1),
LaggedStartMap(FadeIn, times_syms, lag_ratio=0.1),
)
self.wait()
# Show result
result_label = Tex(R"\Delta \vec{\textbf{E}}_3", font_size=42)
result_label.set_color(YELLOW)
result_label.next_to(weighted_label, DOWN, buff=1.0)
plus_syms = VGroup(Tex("+", font_size=30) for _ in range(2))
weighted_v = VGroup()
for i, (w, v_sym) in enumerate(zip(weight_labels, v_syms)):
term = VGroup(w.copy(), v_sym.copy())
weighted_v.add(term)
weighted_v.arrange(RIGHT, buff=0.3)
for plus, term in zip(plus_syms, weighted_v[1:]):
plus.next_to(term, LEFT, buff=0.1)
weighted_sum = VGroup(weighted_v[0], plus_syms[0], weighted_v[1], plus_syms[1], weighted_v[2])
weighted_sum.scale(0.8)
weighted_sum.next_to(result_label, UP, buff=0.5)
eq_sign = Tex("=", font_size=36)
eq_sign.next_to(result_label, LEFT, buff=0.2)
self.play(
LaggedStart(
(TransformFromCopy(VGroup(wl, vs), wv)
for wl, vs, wv in zip(weight_labels, v_syms, weighted_v)),
lag_ratio=0.2
),
LaggedStartMap(FadeIn, plus_syms, lag_ratio=0.3),
)
self.play(
FadeIn(eq_sign),
FadeIn(result_label, shift=0.2 * DOWN),
)
self.wait()
# Explanation
explanation = Text(
"This update adds context\nfrom attended tokens",
font_size=30
)
explanation.to_edge(DOWN)
self.play(Write(explanation))
self.wait(2)
examples/vector_fields.py
"""
Vector Fields and Flow Visualization
Demonstrates vector field rendering using arrows,
streamlines, and particle flow animations.
Run: manimgl vector_fields.py SimpleVectorField -w
Preview: manimgl vector_fields.py SimpleVectorField -p
Source: Inspired by 3b1b's vector field visualizations
"""
from manimlib import *
import numpy as np
class SimpleVectorField(InteractiveScene):
"""
Basic 2D vector field visualization using arrows.
Shows rotation field around origin.
"""
def construct(self):
# Create plane
plane = NumberPlane(
x_range=(-4, 4, 1),
y_range=(-3, 3, 1),
background_line_style={"stroke_opacity": 0.3}
)
self.add(plane)
# Create arrows manually for vector field
arrows = VGroup()
for x in np.arange(-3.5, 4, 0.7):
for y in np.arange(-2.5, 3, 0.7):
# Rotation field: F = (-y, x)
vx, vy = -y * 0.15, x * 0.15
if abs(vx) < 0.01 and abs(vy) < 0.01:
continue
arrow = Arrow(
start=[x, y, 0],
end=[x + vx, y + vy, 0],
buff=0,
stroke_width=2,
max_tip_length_to_length_ratio=0.3,
)
# Color by magnitude
mag = np.sqrt(vx**2 + vy**2)
arrow.set_color(interpolate_color(BLUE, YELLOW, mag / 0.5))
arrows.add(arrow)
self.play(LaggedStartMap(GrowArrow, arrows, lag_ratio=0.02, run_time=2))
self.wait()
# Add a particle that follows the field
dot = Dot(color=RED, radius=0.1)
dot.move_to(2 * RIGHT + UP)
def follow_field(mob, dt):
x, y = mob.get_center()[:2]
vx, vy = -y * 0.5, x * 0.5
mob.shift(np.array([vx, vy, 0]) * dt)
dot.add_updater(follow_field)
trail = TracedPath(dot.get_center, stroke_color=RED, stroke_width=2)
self.add(trail, dot)
self.wait(8)
class GradientFieldDemo(InteractiveScene):
"""
Shows gradient of a scalar field.
Arrows point toward steepest ascent.
"""
def construct(self):
# Create colored background showing scalar field
plane = NumberPlane(
x_range=(-4, 4, 1),
y_range=(-3, 3, 1),
background_line_style={"stroke_opacity": 0.2}
)
self.add(plane)
# Scalar field: f(x,y) = -(x^2 + y^2) (peak at origin)
# Gradient: (-2x, -2y) pointing toward origin
# Create dots colored by height
dots = VGroup()
for x in np.arange(-3.5, 4, 0.3):
for y in np.arange(-2.5, 3, 0.3):
val = -(x**2 + y**2)
t = (val + 25) / 25 # Normalize
color = interpolate_color(BLUE_E, RED, t)
dot = Dot([x, y, 0], radius=0.08, color=color)
dots.add(dot)
self.play(FadeIn(dots))
# Gradient vectors (pointing toward origin = uphill)
arrows = VGroup()
for x in np.arange(-3, 3.5, 0.8):
for y in np.arange(-2, 2.5, 0.8):
if abs(x) < 0.3 and abs(y) < 0.3:
continue
# Gradient direction (toward origin for this function)
gx, gy = -2*x, -2*y
length = np.sqrt(gx**2 + gy**2)
# Normalize and scale
scale = 0.3
gx, gy = gx/length * scale, gy/length * scale
arrow = Arrow(
start=[x, y, 0],
end=[x + gx, y + gy, 0],
buff=0,
stroke_width=2,
stroke_color=WHITE,
)
arrows.add(arrow)
self.play(LaggedStartMap(GrowArrow, arrows, lag_ratio=0.02, run_time=2))
# Label
label = Tex(r"\nabla f = (-2x, -2y)", font_size=36)
label.to_corner(UL)
label.set_backstroke(BLACK, 3)
self.play(Write(label))
self.wait()
class ParticleFlow(InteractiveScene):
"""
Multiple particles flowing through a vector field.
Great for visualizing fluid flow.
"""
def construct(self):
# Vortex field visualization
plane = NumberPlane(
x_range=(-5, 5, 1),
y_range=(-4, 4, 1),
background_line_style={"stroke_opacity": 0.2}
)
self.add(plane)
# Create particles
n_particles = 15
particles = VGroup()
trails = VGroup()
for i in range(n_particles):
# Start in a circle
angle = i * TAU / n_particles
start_pos = 2 * np.array([np.cos(angle), np.sin(angle), 0])
dot = Dot(start_pos, radius=0.1, color=YELLOW)
def make_updater():
def update(mob, dt):
x, y = mob.get_center()[:2]
r = np.sqrt(x**2 + y**2) + 0.1
vx, vy = -y/r, x/r
mob.shift(np.array([vx, vy, 0]) * dt * 0.8)
return update
dot.add_updater(make_updater())
trail = TracedPath(
dot.get_center,
stroke_color=BLUE,
stroke_width=1.5,
stroke_opacity=0.7,
)
particles.add(dot)
trails.add(trail)
self.add(trails, particles)
self.wait(10)
class ElectricDipole(InteractiveScene):
"""
Electric field from two point charges (dipole).
"""
def construct(self):
# Charge positions
q1_pos = np.array([-2, 0, 0])
q2_pos = np.array([2, 0, 0])
# Draw charges
q_plus = Dot(q1_pos, radius=0.25, color=RED)
q_plus_label = Tex("+", font_size=36, color=WHITE)
q_plus_label.move_to(q1_pos)
q_minus = Dot(q2_pos, radius=0.25, color=BLUE)
q_minus_label = Tex("-", font_size=36, color=WHITE)
q_minus_label.move_to(q2_pos)
self.add(q_plus, q_plus_label, q_minus, q_minus_label)
# Create field arrows
arrows = VGroup()
for x in np.arange(-4, 4.5, 0.6):
for y in np.arange(-3, 3.5, 0.6):
pos = np.array([x, y, 0])
# Skip near charges
if np.linalg.norm(pos - q1_pos) < 0.5:
continue
if np.linalg.norm(pos - q2_pos) < 0.5:
continue
# Electric field from both charges
r1 = pos - q1_pos
r2 = pos - q2_pos
d1 = np.linalg.norm(r1) + 0.1
d2 = np.linalg.norm(r2) + 0.1
# E = kq/r^2 in direction of r (positive) or -r (negative)
E1 = r1 / d1**3 # From positive charge
E2 = -r2 / d2**3 # From negative charge
E = E1 + E2
mag = np.linalg.norm(E)
if mag < 0.001:
continue
# Normalize and scale
E_norm = E / mag
length = min(0.4, mag * 2)
arrow = Arrow(
start=pos,
end=pos + E_norm * length,
buff=0,
stroke_width=2,
)
# Color by magnitude
color = interpolate_color(BLUE_E, YELLOW, min(mag * 5, 1))
arrow.set_color(color)
arrows.add(arrow)
self.play(LaggedStartMap(GrowArrow, arrows, lag_ratio=0.01, run_time=3))
self.wait()
examples/wave_amplitude_visualization.py
"""
Wave Amplitude and Phase Visualization
Demonstrates various ways to visualize electromagnetic waves,
including vector field representations and amplitude graphs.
Based on 3Blue1Brown's wave visualization techniques.
Run: manimgl wave_amplitude_visualization.py WaveAmplitudeDemo -w
"""
from manimlib import *
import numpy as np
class WaveAmplitudeDemo(Scene):
"""
Shows wave amplitude with oscillating vectors along a propagation line.
"""
def construct(self):
frame = self.camera.frame
# Title
title = Text("Wave Amplitude Visualization", font_size=42)
title.to_edge(UP)
title.set_backstroke(BLACK, 5)
self.add(title)
# Wave parameters
wave_number = 1.5
frequency = 0.5
amplitude = 1.0
# Create a line of points along which the wave propagates
n_points = 40
x_range = np.linspace(-6, 6, n_points)
# Wave function
def wave_value(x, time):
return amplitude * np.sin(TAU * (wave_number * x - frequency * time))
# Create oscillating vectors
def get_wave_vectors(time):
vectors = VGroup()
for x in x_range:
y_val = wave_value(x, time)
# Create vector from baseline
start = np.array([x, -2, 0])
end = np.array([x, -2 + y_val, 0])
vec = Arrow(start, end, buff=0, stroke_width=2, max_tip_length_to_length_ratio=0.15)
# Color based on displacement
if y_val > 0:
vec.set_color(interpolate_color(WHITE, BLUE, min(y_val / amplitude, 1)))
else:
vec.set_color(interpolate_color(WHITE, RED, min(-y_val / amplitude, 1)))
vectors.add(vec)
return vectors
# Create wave curve
def get_wave_curve(time):
curve = FunctionGraph(
lambda x: -2 + wave_value(x, time),
x_range=[-6, 6, 0.1],
color=TEAL
)
curve.set_stroke(width=3)
return curve
time_tracker = ValueTracker(0)
vectors = always_redraw(lambda: get_wave_vectors(time_tracker.get_value()))
curve = always_redraw(lambda: get_wave_curve(time_tracker.get_value()))
# Baseline
baseline = Line([-6, -2, 0], [6, -2, 0])
baseline.set_stroke(WHITE, 1, opacity=0.5)
# Labels
wavelength_brace = Brace(
Line([-2, -2 - 1.2, 0], [-2 + 1/wave_number, -2 - 1.2, 0]),
DOWN
)
lambda_label = Tex(R"\lambda", font_size=36)
lambda_label.next_to(wavelength_brace, DOWN)
amp_line = VGroup(
Arrow([-6.5, -2, 0], [-6.5, -2 + amplitude, 0], buff=0),
Arrow([-6.5, -2 + amplitude, 0], [-6.5, -2, 0], buff=0),
)
amp_line.set_color(YELLOW)
amp_label = Text("Amplitude", font_size=20, color=YELLOW)
amp_label.next_to(amp_line, LEFT)
self.add(baseline)
self.add(vectors)
self.add(curve)
# Animate wave motion
self.play(
time_tracker.animate.set_value(8),
run_time=8,
rate_func=linear
)
# Add labels
self.add(amp_line, amp_label)
self.play(
time_tracker.animate.set_value(12),
FadeIn(wavelength_brace),
FadeIn(lambda_label),
run_time=4,
rate_func=linear
)
self.wait()
class PhaseVisualization(Scene):
"""
Visualizes the phase of a wave using rotating phasors.
"""
def construct(self):
# Title
title = Text("Wave Phase as Rotating Phasor", font_size=42)
title.to_edge(UP)
title.set_backstroke(BLACK, 5)
self.add(title)
# Parameters
frequency = 0.3
# Phasor circle
circle = Circle(radius=1.5, color=GREY)
circle.move_to(LEFT * 3)
circle_center = circle.get_center()
# Phasor arrow
def get_phasor(time):
angle = TAU * frequency * time
end_point = circle_center + 1.5 * np.array([np.cos(angle), np.sin(angle), 0])
arrow = Arrow(circle_center, end_point, buff=0, color=BLUE)
return arrow
# Projection on vertical axis (wave value)
def get_projection_line(time):
angle = TAU * frequency * time
y_val = 1.5 * np.sin(angle)
line = DashedLine(
circle_center + 1.5 * np.array([np.cos(angle), np.sin(angle), 0]),
circle_center + np.array([0, y_val, 0]),
dash_length=0.1
)
line.set_stroke(YELLOW, 2)
return line
# Wave trace
def get_wave_trace(time, length=8):
wave = VGroup()
x_start = 0
for i in range(int(length * 30)):
x = x_start + i / 30
t = time - (x - x_start) / 2
y = 1.5 * np.sin(TAU * frequency * t)
dot = Dot([x, y, 0], radius=0.02, color=TEAL)
wave.add(dot)
return wave
time_tracker = ValueTracker(0)
phasor = always_redraw(lambda: get_phasor(time_tracker.get_value()))
projection = always_redraw(lambda: get_projection_line(time_tracker.get_value()))
wave_trace = always_redraw(lambda: get_wave_trace(time_tracker.get_value()))
# Center dot
center_dot = Dot(circle_center, color=WHITE, radius=0.08)
# Phase angle arc
def get_phase_arc(time):
angle = TAU * frequency * time % TAU
if angle > 0.1:
arc = Arc(0, angle, radius=0.5, arc_center=circle_center)
arc.set_stroke(GREEN, 2)
return arc
return VGroup()
phase_arc = always_redraw(lambda: get_phase_arc(time_tracker.get_value()))
# Labels
phasor_label = Text("Phasor", font_size=24)
phasor_label.next_to(circle, DOWN)
wave_label = Text("Wave amplitude = vertical projection", font_size=24)
wave_label.to_edge(DOWN)
self.add(circle, center_dot)
self.add(phasor)
self.add(projection)
self.add(phase_arc)
self.add(wave_trace)
self.add(phasor_label, wave_label)
# Animate
self.play(
time_tracker.animate.set_value(15),
run_time=15,
rate_func=linear
)
self.wait()
class TwoWaveSuperposition(Scene):
"""
Shows superposition of two waves with different phases.
"""
def construct(self):
# Title
title = Text("Wave Superposition", font_size=42)
title.to_edge(UP)
title.set_backstroke(BLACK, 5)
self.add(title)
# Parameters
wave_number = 1.0
frequency = 0.4
amplitude = 0.8
# Phase difference
phase_diff_tracker = ValueTracker(0)
# Wave functions
def wave1_value(x, time):
return amplitude * np.sin(TAU * (wave_number * x - frequency * time))
def wave2_value(x, time, phase_diff):
return amplitude * np.sin(TAU * (wave_number * x - frequency * time) + phase_diff)
def combined_value(x, time, phase_diff):
return wave1_value(x, time) + wave2_value(x, time, phase_diff)
# Wave curves
def get_wave1(time):
curve = FunctionGraph(
lambda x: 2 + wave1_value(x, time),
x_range=[-6, 6, 0.1],
color=RED
)
curve.set_stroke(width=2)
return curve
def get_wave2(time, phase_diff):
curve = FunctionGraph(
lambda x: wave2_value(x, time, phase_diff),
x_range=[-6, 6, 0.1],
color=BLUE
)
curve.set_stroke(width=2)
return curve
def get_combined(time, phase_diff):
curve = FunctionGraph(
lambda x: -2 + combined_value(x, time, phase_diff),
x_range=[-6, 6, 0.1],
color=GREEN
)
curve.set_stroke(width=3)
return curve
time_tracker = ValueTracker(0)
wave1 = always_redraw(lambda: get_wave1(time_tracker.get_value()))
wave2 = always_redraw(lambda: get_wave2(time_tracker.get_value(),
phase_diff_tracker.get_value()))
combined = always_redraw(lambda: get_combined(time_tracker.get_value(),
phase_diff_tracker.get_value()))
# Baselines
baseline1 = Line([-6, 2, 0], [6, 2, 0]).set_stroke(WHITE, 1, opacity=0.3)
baseline2 = Line([-6, 0, 0], [6, 0, 0]).set_stroke(WHITE, 1, opacity=0.3)
baseline3 = Line([-6, -2, 0], [6, -2, 0]).set_stroke(WHITE, 1, opacity=0.3)
# Labels
label1 = Text("Wave 1", font_size=24, color=RED).to_corner(UL).shift(DOWN)
label2 = Text("Wave 2", font_size=24, color=BLUE).next_to(label1, DOWN)
label_sum = Text("Sum", font_size=24, color=GREEN).next_to(label2, DOWN)
# Phase difference display
phase_display = always_redraw(lambda: Text(
f"Phase diff: {phase_diff_tracker.get_value() / PI:.2f}π",
font_size=28
).to_corner(DR))
self.add(baseline1, baseline2, baseline3)
self.add(wave1, wave2, combined)
self.add(label1, label2, label_sum)
self.add(phase_display)
# Show in-phase waves
self.play(
time_tracker.animate.set_value(6),
run_time=6,
rate_func=linear
)
# Transition to out-of-phase
in_phase_label = Text("In phase: Constructive", font_size=28, color=GREEN)
in_phase_label.to_edge(DOWN)
self.play(Write(in_phase_label))
self.play(
phase_diff_tracker.animate.set_value(PI),
time_tracker.animate.set_value(12),
run_time=6,
rate_func=linear
)
out_phase_label = Text("Out of phase: Destructive", font_size=28, color=PINK)
out_phase_label.next_to(in_phase_label, UP)
self.play(
Write(out_phase_label),
time_tracker.animate.set_value(18),
run_time=6,
rate_func=linear
)
self.wait()
class StandingWave(Scene):
"""
Visualization of a standing wave from two counter-propagating waves.
"""
def construct(self):
# Title
title = Text("Standing Wave", font_size=42)
title.to_edge(UP)
title.set_backstroke(BLACK, 5)
self.add(title)
# Parameters
wave_number = 2.0
frequency = 0.5
amplitude = 1.2
# Standing wave = 2A * sin(kx) * cos(wt)
def standing_wave_value(x, time):
return 2 * amplitude * np.sin(TAU * wave_number * x) * np.cos(TAU * frequency * time)
# Envelope
def envelope_upper(x):
return 2 * amplitude * abs(np.sin(TAU * wave_number * x))
def envelope_lower(x):
return -2 * amplitude * abs(np.sin(TAU * wave_number * x))
# Create wave and envelopes
def get_standing_wave(time):
curve = FunctionGraph(
lambda x: standing_wave_value(x, time),
x_range=[-5, 5, 0.1],
color=TEAL
)
curve.set_stroke(width=3)
return curve
upper_env = FunctionGraph(envelope_upper, x_range=[-5, 5, 0.1], color=YELLOW)
lower_env = FunctionGraph(envelope_lower, x_range=[-5, 5, 0.1], color=YELLOW)
upper_env.set_stroke(width=1, opacity=0.5)
lower_env.set_stroke(width=1, opacity=0.5)
time_tracker = ValueTracker(0)
wave = always_redraw(lambda: get_standing_wave(time_tracker.get_value()))
# Baseline
baseline = Line([-5, 0, 0], [5, 0, 0]).set_stroke(WHITE, 1, opacity=0.3)
# Node and antinode markers
nodes = VGroup()
antinodes = VGroup()
for i in range(-4, 5):
x = i / (2 * wave_number)
if i % 2 == 0:
node = Dot([x, 0, 0], color=RED, radius=0.08)
nodes.add(node)
else:
antinode = Dot([x, 0, 0], color=GREEN, radius=0.08)
antinodes.add(antinode)
# Labels
node_label = Text("Nodes (no motion)", font_size=24, color=RED)
node_label.to_corner(DL)
antinode_label = Text("Antinodes (max motion)", font_size=24, color=GREEN)
antinode_label.next_to(node_label, UP)
self.add(baseline)
self.add(upper_env, lower_env)
self.add(wave)
self.add(nodes, antinodes)
self.add(node_label, antinode_label)
# Animate
self.play(
time_tracker.animate.set_value(15),
run_time=15,
rate_func=linear
)
self.wait()
examples/weight_matrix_product.py
"""
Weight Matrix Product Visualization
Shows how a weight matrix transforms an embedding vector step by step,
demonstrating the row-by-vector dot product pattern.
"""
from manimlib import *
import numpy as np
def value_to_color(
value,
low_positive_color=BLUE_E,
high_positive_color=BLUE_B,
low_negative_color=RED_E,
high_negative_color=RED_B,
min_value=0.0,
max_value=10.0
):
"""Map a value to a color based on sign and magnitude."""
alpha = clip(float(inverse_interpolate(min_value, max_value, abs(value))), 0, 1)
if value >= 0:
return interpolate_color(low_positive_color, high_positive_color, alpha)
else:
return interpolate_color(low_negative_color, high_negative_color, alpha)
class WeightMatrix(DecimalMatrix):
"""A matrix with color-coded entries based on value."""
def __init__(
self,
values=None,
shape=(5, 7),
value_range=(-9.9, 9.9),
ellipses_row=-2,
ellipses_col=-2,
num_decimal_places=1,
bracket_h_buff=0.1,
**kwargs
):
if values is None:
values = np.random.uniform(*value_range, size=shape)
self.shape = shape
self.value_range = value_range
self.ellipses_row = ellipses_row
super().__init__(
values,
num_decimal_places=num_decimal_places,
bracket_h_buff=bracket_h_buff,
decimal_config=dict(include_sign=True),
ellipses_row=ellipses_row,
ellipses_col=ellipses_col,
)
self.reset_entry_colors()
def reset_entry_colors(self):
for entry in self.get_entries():
entry.set_fill(color=value_to_color(
entry.get_value(),
min_value=0,
max_value=max(self.value_range),
))
return self
class NumericEmbedding(WeightMatrix):
"""A column vector (embedding) with color-coded entries."""
def __init__(
self,
values=None,
length=7,
value_range=(-9.9, 9.9),
ellipses_row=-2,
**kwargs
):
if values is None:
shape = (length, 1)
else:
if len(values.shape) == 1:
values = values.reshape((values.shape[0], 1))
shape = values.shape
super().__init__(
values=values,
shape=shape,
value_range=value_range,
ellipses_row=ellipses_row,
ellipses_col=None,
**kwargs
)
class WeightMatrixProduct(InteractiveScene):
def construct(self):
# Create the weight matrix
np.random.seed(42)
matrix = WeightMatrix(shape=(5, 7))
matrix.set_height(3.5)
matrix.to_edge(LEFT, buff=1)
# Create input vector
in_vect = NumericEmbedding(length=7)
in_vect.match_height(matrix)
in_vect.next_to(matrix, RIGHT, buff=0.3)
# Labels
mat_brace = Brace(matrix, UP)
mat_label = Tex("W_Q", font_size=48)
mat_label.set_color(YELLOW)
mat_label.next_to(mat_brace, UP, SMALL_BUFF)
vect_label = Tex(R"\vec{E}", font_size=48)
vect_label.set_color(TEAL)
vect_label.next_to(in_vect, UP, buff=0.5)
self.play(
FadeIn(matrix, lag_ratio=0.01),
FadeIn(in_vect),
GrowFromCenter(mat_brace),
FadeIn(mat_label, shift=0.25 * UP),
FadeIn(vect_label, shift=0.25 * DOWN),
)
self.wait()
# Create result vector
eq = Tex("=", font_size=60)
eq.next_to(in_vect, RIGHT, buff=0.4)
result = NumericEmbedding(length=5)
result.match_height(matrix)
result.next_to(eq, RIGHT, buff=0.4)
result_label = Tex(R"\vec{Q}", font_size=48)
result_label.set_color(YELLOW)
result_label.next_to(result, UP, buff=0.5)
self.play(
FadeIn(eq),
FadeIn(result.get_brackets()),
)
# Animate row-by-vector products
rows = matrix.get_rows()
result_entries = result.get_entries()
vect_entries = in_vect.get_entries()
last_rects = VGroup()
for n, (row, entry) in enumerate(zip(rows, result_entries)):
if n == len(rows) - 2: # Skip ellipses row
self.add(entry)
continue
# Highlight current row and vector
row_rects = VGroup(SurroundingRectangle(r, buff=0.05) for r in row)
vect_rects = VGroup(SurroundingRectangle(v, buff=0.05) for v in vect_entries[:-2])
row_rects.set_stroke(YELLOW, 2)
vect_rects.set_stroke(YELLOW, 2)
# Compute actual dot product
row_vals = [r.get_value() for r in row if isinstance(r, DecimalNumber)]
vect_vals = [v.get_value() for v in vect_entries[:-2] if isinstance(v, DecimalNumber)]
dot_product = sum(a * b for a, b in zip(row_vals, vect_vals))
self.play(
ShowIncreasingSubsets(row_rects),
ShowIncreasingSubsets(vect_rects),
UpdateFromAlphaFunc(
entry,
lambda m, a, target=dot_product: m.set_value(target * a)
),
FadeOut(last_rects),
rate_func=linear,
run_time=0.8,
)
last_rects = VGroup(row_rects, vect_rects)
self.play(FadeOut(last_rects))
# Show result label
self.play(FadeIn(result_label, shift=0.25 * DOWN))
self.wait()
# Add explanation
explanation = Text(
"Each row produces one\nentry of the output",
font_size=36
)
explanation.to_edge(DOWN)
self.play(Write(explanation))
self.wait(2)
examples/weights_vs_data.py
"""
Visualization distinguishing between weights (model parameters) and data.
Demonstrates: DecimalMatrix, VGroup organization, Transform animations
"""
from manimlib import *
import numpy as np
import random
def value_to_color(
value,
low_positive_color=BLUE_E,
high_positive_color=BLUE_B,
low_negative_color=RED_E,
high_negative_color=RED_B,
min_value=0.0,
max_value=10.0
):
"""Map a numeric value to a color gradient."""
alpha = clip(float(inverse_interpolate(min_value, max_value, abs(value))), 0, 1)
if value >= 0:
colors = (low_positive_color, high_positive_color)
else:
colors = (low_negative_color, high_negative_color)
return interpolate_color_by_hsl(*colors, alpha)
class WeightMatrix(DecimalMatrix):
"""A matrix displaying weight values with color coding."""
def __init__(
self,
values=None,
shape=(4, 6),
value_range=(-9.9, 9.9),
num_decimal_places=1,
**kwargs
):
if values is None:
values = np.random.uniform(*value_range, size=shape)
self.value_range = value_range
super().__init__(
values,
num_decimal_places=num_decimal_places,
**kwargs
)
self.color_entries()
def color_entries(self):
for entry in self.get_entries():
entry.set_fill(color=value_to_color(
entry.get_value(),
min_value=0,
max_value=max(abs(self.value_range[0]), abs(self.value_range[1])),
))
return self
class NumericVector(DecimalMatrix):
"""A column vector displaying numeric values."""
def __init__(
self,
values=None,
length=6,
value_range=(-9.9, 9.9),
num_decimal_places=1,
**kwargs
):
if values is None:
values = np.random.uniform(*value_range, size=(length, 1))
elif len(values.shape) == 1:
values = values.reshape((-1, 1))
super().__init__(
values,
num_decimal_places=num_decimal_places,
**kwargs
)
# Color entries from dark to light based on value
for entry in self.get_entries():
alpha = clip(inverse_interpolate(
value_range[0], value_range[1], abs(entry.get_value())
), 0, 1)
entry.set_fill(interpolate_color(GREY_C, WHITE, alpha))
class WeightsVsData(Scene):
def construct(self):
# Create titles
weights_title = Text("Weights", font_size=60, color=BLUE)
data_title = Text("Data", font_size=60, color=GREY_B)
weights_title.set_x(-FRAME_WIDTH / 4)
data_title.set_x(FRAME_WIDTH / 4)
for title in [weights_title, data_title]:
title.to_edge(UP, buff=0.5)
underline = Underline(title, stretch_factor=1.5)
underline.match_color(title)
title.add(underline)
# Create vertical divider
v_line = Line(UP, DOWN).set_height(5)
v_line.set_stroke(GREY_A, 2)
v_line.next_to(weights_title, DOWN, buff=0.5)
v_line.set_x(0)
# Create weight matrices (model parameters)
matrices = VGroup(*(
WeightMatrix(shape=(4, 5))
for _ in range(2)
))
matrices.arrange(DOWN, buff=0.5)
matrices.set_height(4)
matrices.next_to(weights_title, DOWN, buff=0.75)
# Create data vectors (what flows through the network)
vectors = VGroup(*(
NumericVector(length=5)
for _ in range(4)
))
vectors.arrange(RIGHT, buff=0.3)
vectors.set_height(3)
vectors.next_to(data_title, DOWN, buff=0.75)
# Animation: scatter numbers first, then organize
all_mat_entries = VGroup(*(
entry
for mat in matrices
for entry in mat.get_entries()
))
all_vec_entries = VGroup(*(
entry
for vec in vectors
for entry in vec.get_entries()
))
# Save final positions
for entry in [*all_mat_entries, *all_vec_entries]:
entry.final_pos = entry.get_center().copy()
# Scatter to random positions
all_entries = VGroup(*all_mat_entries, *all_vec_entries)
all_entries.shuffle()
for entry in all_entries:
entry.move_to([
random.uniform(-7, 7),
random.uniform(-3, 3),
0
])
entry.set_height(0.15)
# Start animation
self.add(all_entries)
self.wait(0.5)
# Animate gathering
self.play(
LaggedStart(*(
entry.animate.move_to(entry.final_pos).set_height(0.25)
for entry in all_mat_entries
), lag_ratio=0.02),
ShowCreation(v_line),
run_time=2
)
self.play(
Write(weights_title),
*(FadeIn(mat.get_brackets()) for mat in matrices),
)
self.play(
LaggedStart(*(
entry.animate.move_to(entry.final_pos).set_height(0.25)
for entry in all_vec_entries
), lag_ratio=0.02),
run_time=2
)
self.play(
Write(data_title),
*(FadeIn(vec.get_brackets()) for vec in vectors),
)
self.wait()
# Add subtitles
weights_sub = Text("Fixed during inference", font_size=30)
weights_sub.next_to(matrices, DOWN, buff=0.3)
data_sub = Text("Flows through network", font_size=30)
data_sub.next_to(vectors, DOWN, buff=0.3)
self.play(
FadeIn(weights_sub, shift=UP),
FadeIn(data_sub, shift=UP),
)
self.wait()
# Show data flowing (animate vectors changing)
for _ in range(3):
new_vectors = VGroup(*(
NumericVector(length=5)
for _ in range(4)
))
new_vectors.arrange(RIGHT, buff=0.3)
new_vectors.set_height(3)
new_vectors.move_to(vectors)
self.play(
Transform(vectors, new_vectors),
run_time=1.5
)
self.wait(0.5)
# Final emphasis
weights_rect = SurroundingRectangle(matrices, color=BLUE, buff=0.2)
data_rect = SurroundingRectangle(vectors, color=GREY_B, buff=0.2)
self.play(
ShowCreation(weights_rect),
ShowCreation(data_rect),
)
self.wait(2)
examples/word_vector_analogy.py
"""
Word Vector Analogy Visualization
Demonstrates the famous king - man + woman = queen analogy in embedding space.
Based on: videos/_2024/transformers/embedding.py - KingQueenExample
"""
from manimlib import *
class WordVectorAnalogy(InteractiveScene):
"""
Visualizes word vector arithmetic in 3D space.
Shows how semantic relationships are encoded as directions.
"""
def construct(self):
# Set up 3D scene
frame = self.frame
frame.reorient(-20, 70, 0)
frame.add_ambient_rotation(2 * DEGREES)
# Create axes
axes = ThreeDAxes(
x_range=(-4, 4, 1),
y_range=(-4, 4, 1),
z_range=(-3, 3, 1),
width=8,
height=8,
depth=6,
)
axes.set_stroke(width=2)
self.add(axes)
# Add plane for reference
plane = NumberPlane(
axes.x_range[:2], axes.y_range[:2],
width=axes.get_width(),
height=axes.get_height(),
background_line_style=dict(
stroke_color=GREY,
stroke_width=1,
),
faded_line_style=dict(
stroke_opacity=0.25,
stroke_width=0.5,
),
faded_line_ratio=1,
)
plane.rotate(90 * DEGREES, LEFT)
self.add(plane)
# Define word positions (simplified for demo)
word_data = {
"man": {"pos": np.array([1, -1, 0.5]), "color": BLUE_B},
"woman": {"pos": np.array([1, 1, 0.5]), "color": RED_B},
"king": {"pos": np.array([-2, -1, 1.5]), "color": BLUE_D},
"queen": {"pos": np.array([-2, 1, 1.5]), "color": RED_D},
}
def create_labeled_arrow(word, pos, color):
"""Create an arrow with a word label."""
arrow = Arrow(
axes.get_origin(),
axes.c2p(*pos),
buff=0,
stroke_color=color,
stroke_width=4,
)
arrow.set_flat_stroke(False)
label = Text(word, font_size=30)
label.set_backstroke(BLACK, 3)
label.next_to(arrow.get_end(), normalize(arrow.get_vector()), buff=0.1)
label.rotate(90 * DEGREES, RIGHT) # Orient for 3D
return arrow, label
# Create all word vectors
vectors = {}
labels = {}
for word, data in word_data.items():
arrow, label = create_labeled_arrow(word, data["pos"], data["color"])
vectors[word] = arrow
labels[word] = label
# Show equation (fixed in frame)
equation = Tex(
R"\text{woman} - \text{man} \approx \text{queen} - \text{king}",
font_size=42
)
equation.fix_in_frame()
equation.to_corner(UR)
equation["woman"].set_color(RED_B)
equation["man"].set_color(BLUE_B)
equation["queen"].set_color(RED_D)
equation["king"].set_color(BLUE_D)
top_rect = FullScreenFadeRectangle().set_fill(BLACK, 0.7)
top_rect.set_height(1.2, about_edge=UP, stretch=True)
top_rect.fix_in_frame()
# Animate man and woman vectors
self.play(
GrowArrow(vectors["man"]),
FadeIn(labels["man"]),
GrowArrow(vectors["woman"]),
FadeIn(labels["woman"]),
run_time=2
)
self.wait()
# Show difference vector (man -> woman)
diff = Arrow(
vectors["man"].get_end(),
vectors["woman"].get_end(),
buff=0,
stroke_color=YELLOW,
stroke_width=4,
)
diff.set_flat_stroke(False)
self.play(GrowArrow(diff))
self.wait()
# Show equation
self.add(top_rect)
self.play(Write(equation))
self.wait()
# Add king and queen
self.play(
GrowArrow(vectors["king"]),
FadeIn(labels["king"]),
run_time=1.5
)
# Show the same difference applied to king
king_to_queen = diff.copy()
king_to_queen.shift(vectors["king"].get_end() - vectors["man"].get_end())
self.play(TransformFromCopy(diff, king_to_queen))
self.wait()
# Show queen at the tip
self.play(
GrowArrow(vectors["queen"]),
FadeIn(labels["queen"]),
)
self.wait()
# Rotate to show the relationship
frame.clear_updaters()
self.play(
frame.animate.reorient(-100, 20, 100),
run_time=4
)
frame.add_ambient_rotation(2 * DEGREES)
# Flash the gender direction
gender_dir = diff.get_vector()
lines = Line(ORIGIN, 1.5 * normalize(gender_dir)).replicate(100)
lines.insert_n_curves(20)
lines.set_stroke(YELLOW, 3)
for line in lines:
line.move_to(np.random.uniform(-2, 2, 3))
self.play(
LaggedStartMap(
VShowPassingFlash, lines,
lag_ratio=1 / len(lines),
run_time=3
)
)
# Add direction label
dir_label = Text("Gender direction", font_size=36, color=YELLOW)
dir_label.fix_in_frame()
dir_label.next_to(equation, DOWN, buff=0.5)
self.play(Write(dir_label))
self.wait(3)
# Show another example
new_eq = Tex(
R"\text{uncle} - \text{aunt} \approx \text{man} - \text{woman}",
font_size=36
)
new_eq.fix_in_frame()
new_eq.next_to(dir_label, DOWN, buff=0.3)
self.play(Write(new_eq))
self.wait(5)
examples/zone_plate_hologram.py
"""
Zone Plate / Fresnel Zone Plate Visualization
Demonstrates the creation and properties of a Fresnel zone plate,
which is the simplest form of hologram - recording interference
between a point source and a reference wave.
Based on 3Blue1Brown's hologram visualizations.
Run: manimgl zone_plate_hologram.py ZonePlateCreation -w
"""
from manimlib import *
import numpy as np
class ZonePlateCreation(Scene):
"""
Shows how a zone plate pattern emerges from interference
between a point source and a plane reference wave.
"""
def construct(self):
frame = self.camera.frame
# Title
title = Text("Fresnel Zone Plate", font_size=48)
title.to_edge(UP)
title.set_backstroke(BLACK, 5)
self.add(title)
# Parameters
source_distance = 4.0 # Distance of point source from plate
wavelength = 0.3
plate_size = 6.0
# Point source position (behind the plate plane)
source_pos = np.array([0, 0, source_distance])
# Create zone plate pattern
def get_zone_plate(resolution=200):
plate = VGroup()
# Sample grid on the plate
for i in range(resolution):
for j in range(resolution):
x = (i / resolution - 0.5) * plate_size
y = (j / resolution - 0.5) * plate_size
point = np.array([x, y, 0])
# Distance from point source
r = np.linalg.norm(point - source_pos)
# Phase from point source
phase_obj = (r / wavelength) % 1
# Phase from reference (plane wave from behind)
phase_ref = (source_distance / wavelength) % 1
# Interference pattern intensity
phase_diff = (phase_obj - phase_ref) * TAU
intensity = (1 + np.cos(phase_diff)) / 2
# Create small square
size = plate_size / resolution * 1.1
square = Square(side_length=size)
square.move_to([x, y, 0])
square.set_stroke(width=0)
square.set_fill(
interpolate_color(BLACK, WHITE, intensity),
opacity=1
)
plate.add(square)
return plate
# Create the pattern with increasing resolution
low_res = get_zone_plate(30)
self.play(FadeIn(low_res, lag_ratio=0.001))
self.wait()
# Show it's made of concentric rings
ring_explanation = Text("Concentric rings from interference", font_size=32)
ring_explanation.next_to(title, DOWN)
ring_explanation.set_backstroke(BLACK, 3)
self.play(Write(ring_explanation))
self.wait()
# Increase resolution
mid_res = get_zone_plate(60)
self.play(
ReplacementTransform(low_res, mid_res),
run_time=2
)
self.wait()
high_res = get_zone_plate(100)
self.play(
ReplacementTransform(mid_res, high_res),
run_time=2
)
self.wait(2)
class ZonePlateFromPointSource(Scene):
"""
Shows the geometry of how zone plates form from a point source.
"""
def construct(self):
frame = self.camera.frame
frame.reorient(20, 70, 0)
# 3D setup
axes = ThreeDAxes(
x_range=[-4, 4, 1],
y_range=[-4, 4, 1],
z_range=[0, 5, 1],
)
axes.set_opacity(0.3)
# Point source
source_z = 4.0
source = Sphere(radius=0.15, color=WHITE)
source.move_to([0, 0, source_z])
source_label = Text("Point Source", font_size=24)
source_label.rotate(PI/2, RIGHT)
source_label.next_to(source, OUT + UP, buff=0.3)
source_label.set_backstroke(BLACK, 3)
# Film plane
film = Square(side_length=6)
film.set_fill(GREY_E, opacity=0.5)
film.set_stroke(WHITE, 1)
film.move_to(ORIGIN)
film_label = Text("Film Plane", font_size=24)
film_label.next_to(film, DOWN)
film_label.set_backstroke(BLACK, 3)
# Wavefronts from point source (spherical shells)
def get_spherical_waves(time, n_waves=6):
waves = Group()
for i in range(n_waves):
radius = 0.5 + i * 0.8 + time * 0.2
if radius < 6:
sphere = Sphere(radius=radius)
sphere.move_to([0, 0, source_z])
sphere.set_color(BLUE)
sphere.set_opacity(0.15 * (1 - radius / 6))
waves.add(sphere)
return waves
time_tracker = ValueTracker(0)
waves = always_redraw(lambda: get_spherical_waves(time_tracker.get_value()))
# Reference wave fronts (planes)
def get_plane_waves(time, n_waves=8):
planes = Group()
for i in range(n_waves):
z = source_z - 0.5 - i * 0.8 + time * 0.2
if 0 < z < source_z:
plane = Square(side_length=8)
plane.set_fill(TEAL, opacity=0.1 * (z / source_z))
plane.set_stroke(TEAL, 1, opacity=0.3)
plane.move_to([0, 0, z])
planes.add(plane)
return planes
ref_waves = always_redraw(lambda: get_plane_waves(time_tracker.get_value()))
self.add(axes)
self.add(film, film_label)
self.add(source, source_label)
self.add(waves)
self.add(ref_waves)
# Animate waves
self.play(
time_tracker.animate.set_value(10),
frame.animate.increment_theta(30 * DEGREES),
run_time=10,
rate_func=linear
)
self.wait()
class ZonePlateAsLens(Scene):
"""
Demonstrates how a zone plate acts as a lens, focusing light.
"""
def construct(self):
# Title
title = Text("Zone Plate as Focusing Element", font_size=42)
title.to_edge(UP)
title.set_backstroke(BLACK, 5)
self.add(title)
# Zone plate representation
plate_x = -2
plate = VGroup()
n_rings = 12
for i in range(n_rings):
r_outer = 0.15 * (i + 1)
r_inner = 0.15 * i if i > 0 else 0
if i % 2 == 0:
ring = Annulus(inner_radius=r_inner, outer_radius=r_outer)
ring.set_fill(GREY_D, opacity=1)
ring.set_stroke(width=0)
plate.add(ring)
else:
ring = Annulus(inner_radius=r_inner, outer_radius=r_outer)
ring.set_fill(WHITE, opacity=0.8)
ring.set_stroke(width=0)
plate.add(ring)
plate.move_to([plate_x, 0, 0])
plate_label = Text("Zone Plate", font_size=24)
plate_label.next_to(plate, DOWN)
# Focal point
focal_x = 3
focal_point = Dot([focal_x, 0, 0], color=YELLOW, radius=0.15)
focal_label = Text("Focus", font_size=24, color=YELLOW)
focal_label.next_to(focal_point, DOWN)
# Incoming parallel rays
incoming_rays = VGroup()
ray_positions = np.linspace(-1.5, 1.5, 7)
for y in ray_positions:
ray = Arrow([-6, y, 0], [plate_x - 0.2, y, 0], buff=0, stroke_width=2)
ray.set_color(BLUE)
incoming_rays.add(ray)
# Diffracted rays converging to focus
diffracted_rays = VGroup()
for y in ray_positions:
ray = Line([plate_x + 0.2, y, 0], [focal_x, 0, 0])
ray.set_stroke(RED, 2)
diffracted_rays.add(ray)
self.add(plate, plate_label)
self.play(
LaggedStartMap(GrowArrow, incoming_rays, lag_ratio=0.1),
run_time=2
)
self.wait()
incoming_label = Text("Parallel Light", font_size=24, color=BLUE)
incoming_label.next_to(incoming_rays, UP)
self.play(Write(incoming_label))
self.wait()
# Show diffraction
self.play(
LaggedStartMap(ShowCreation, diffracted_rays, lag_ratio=0.1),
FadeIn(focal_point),
Write(focal_label),
run_time=2
)
self.wait()
# Explanation
explanation = Text(
"Zone plate diffracts light to focal point",
font_size=28
)
explanation.next_to(title, DOWN)
self.play(Write(explanation))
self.wait(2)
class InterferenceBands(Scene):
"""
Shows the intensity pattern resulting from two-wave interference.
A simplified representation of holographic recording.
"""
def construct(self):
# Title
title = Text("Interference Pattern on Film", font_size=42)
title.to_edge(UP)
title.set_backstroke(BLACK, 5)
self.add(title)
# Parameters
wavelength = 0.4
angle = 15 * DEGREES # Angle between reference and object beams
# Create interference pattern
def get_interference_bands(width=12, height=6, resolution=200):
bands = VGroup()
# The spacing of fringes depends on the angle between beams
fringe_spacing = wavelength / (2 * np.sin(angle / 2))
for i in range(resolution):
x = (i / resolution - 0.5) * width
# Intensity from interference
intensity = (1 + np.cos(TAU * x / fringe_spacing)) / 2
# Create vertical strip
strip = Rectangle(width=width / resolution * 1.05, height=height)
strip.move_to([x, 0, 0])
strip.set_stroke(width=0)
strip.set_fill(
interpolate_color(BLACK, WHITE, intensity),
opacity=1
)
bands.add(strip)
return bands
bands = get_interference_bands()
border = Rectangle(width=12, height=6)
border.set_stroke(WHITE, 2)
self.play(FadeIn(bands), ShowCreation(border))
self.wait()
# Labels
spacing_label = Text("Fringe spacing depends on beam angle", font_size=28)
spacing_label.next_to(border, DOWN, buff=0.5)
formula = Tex(
R"d = \frac{\lambda}{2\sin(\theta/2)}",
font_size=36
)
formula.next_to(spacing_label, DOWN)
self.play(Write(spacing_label))
self.play(Write(formula))
self.wait(2)
# Show changing angle effect
angle_label = Text("Decreasing angle = wider fringes", font_size=24)
angle_label.to_corner(DR)
for new_angle in [10 * DEGREES, 5 * DEGREES]:
wavelength_local = wavelength
fringe_spacing = wavelength_local / (2 * np.sin(new_angle / 2))
new_bands = VGroup()
for i in range(200):
x = (i / 200 - 0.5) * 12
intensity = (1 + np.cos(TAU * x / fringe_spacing)) / 2
strip = Rectangle(width=12 / 200 * 1.05, height=6)
strip.move_to([x, 0, 0])
strip.set_stroke(width=0)
strip.set_fill(
interpolate_color(BLACK, WHITE, intensity),
opacity=1
)
new_bands.add(strip)
self.play(
Transform(bands, new_bands),
FadeIn(angle_label) if new_angle == 10 * DEGREES else Animation(angle_label),
run_time=2
)
self.wait()
self.wait()
LICENSE.txt
Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0)
Copyright (c) 2025 3Blue1Brown (Grant Sanderson) - Original video code
Copyright (c) 2026 Adithya S Kolavi - Adapted examples and documentation
This work is adapted from and inspired by the 3Blue1Brown video repository:
https://github.com/3b1b/videos
You are free to:
- Share: copy and redistribute the material in any medium or format
- Adapt: remix, transform, and build upon the material
Under the following terms:
- Attribution: You must give appropriate credit to both 3Blue1Brown and the
adapter, provide a link to the license, and indicate if changes were made.
- NonCommercial: You may not use the material for commercial purposes.
- ShareAlike: If you remix, transform, or build upon the material, you must
distribute your contributions under the same license as the original.
No additional restrictions: You may not apply legal terms or technological
measures that legally restrict others from doing anything the license permits.
Full license text: https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode
---
ATTRIBUTION NOTICE:
The example code in this skill is adapted from 3Blue1Brown's video code
repository (https://github.com/3b1b/videos), which is licensed under
CC BY-NC-SA 4.0.
Original author: Grant Sanderson (3Blue1Brown)
Adapted by: Adithya S Kolavi
The reference documentation and skill structure are original work but
describe techniques from the adapted code.
references/equation_transforms.md
# Equation Transforms - Reference Guide
**Example file**: `examples/equation_transforms.py`
## User Query Scenarios
This example addresses queries like:
- "Show step-by-step equation derivation"
- "Animate the quadratic formula derivation"
- "Highlight parts of an equation"
- "Show variable substitution with color tracking"
- "Add braces to explain equation parts"
## Scene Thinking Process (3b1b Style)
### 1. Core Concept
**Mathematical Derivations**: Step-by-step equation manipulation is clearer when terms are color-coded and transformations are animated smoothly.
### 2. Technical Implementation
#### Color-Coded Terms with t2c
```python
eq = Tex(
r"ax^2 + bx + c = 0",
t2c={"a": RED, "b": GREEN, "c": BLUE, "x": YELLOW}
)
```
#### Smooth Equation Transformation
```python
eq1 = Tex(r"ax^2 + bx + c = 0", t2c=colors)
eq2 = Tex(r"x^2 + \frac{b}{a}x + \frac{c}{a} = 0", t2c=colors)
self.play(TransformMatchingTex(eq1.copy(), eq2))
```
**Key insight**: `TransformMatchingTex` matches characters between equations and morphs them smoothly.
#### Highlighting with SurroundingRectangle
```python
part = eq[r"a^2"] # Select by tex string
rect = SurroundingRectangle(part, color=RED, buff=0.05)
self.play(ShowCreation(rect))
```
#### Brace Annotations
```python
brace = Brace(eq["F"], UP, color=BLUE)
label = brace.get_text("Force", font_size=30)
self.play(GrowFromCenter(brace), FadeIn(label, UP))
```
### 3. Scene Variants
| Scene | Purpose |
|-------|---------|
| `QuadraticFormula` | Full derivation with step labels |
| `HighlightAndTransform` | Highlighting + visual proof |
| `BraceAnnotations` | F=ma with labeled parts |
| `ColorCodedSubstitution` | u-substitution with tracking |
## Key Patterns
### Pattern: Step Labels
```python
step_label = Text("Divide by a", font_size=24, color=GREY)
step_label.next_to(eq2, LEFT, buff=0.5)
self.play(FadeIn(step_label, LEFT))
```
### Pattern: Final Answer Box
```python
final = Tex(r"x = \frac{-b \pm \sqrt{b^2 - 4ac}}{2a}")
box = SurroundingRectangle(final, color=GOLD, buff=0.2)
self.play(ShowCreation(box))
```
### Pattern: Selecting Equation Parts
```python
# By tex substring
eq["x^2"] # Returns submobject matching "x^2"
eq[r"\frac{b}{a}"] # LaTeX commands work too
# By index
eq[0] # First character/group
```
## Run Commands
```bash
manimgl equation_transforms.py QuadraticFormula -w
manimgl equation_transforms.py HighlightAndTransform -w
manimgl equation_transforms.py BraceAnnotations -w
manimgl equation_transforms.py ColorCodedSubstitution -w
```
references/integration_visualization.md
# Integration Visualization - Reference Guide
**Example file**: `examples/integration_visualization.py`
## User Query Scenarios
This example addresses queries like:
- "Show the area under a curve"
- "Visualize Riemann sums converging to integral"
- "Animate definite integral accumulation"
- "Show integral of e^(-x) equals 1"
## Scene Thinking Process (3b1b Style)
### 1. Core Concept
**Definite Integral**: The integral ∫f(x)dx represents accumulated area under curve f(x). Riemann sums with shrinking rectangles converge to the true integral.
### 2. Technical Implementation
#### Animated Area Fill (Using Polygon)
```python
def get_area_polygon():
t = t_tracker.get_value()
xs = np.linspace(0, t, 50)
# Points along curve
points = [axes.c2p(x, f(x)) for x in xs]
# Close the polygon along x-axis
points.append(axes.c2p(t, 0))
points.append(axes.c2p(0, 0))
poly = Polygon(*points)
poly.set_fill(BLUE_E, opacity=0.5)
poly.set_stroke(width=0)
return poly
area = always_redraw(get_area_polygon)
```
**Key insight**: ManimGL doesn't have `axes.get_area()`, so build polygons manually from curve points.
#### Riemann Sum Rectangles
```python
for i in range(n):
x = start + i * dx
height = f(x)
rect = Rectangle(
width=dx * axes.x_axis.get_unit_size(),
height=height * axes.y_axis.get_unit_size(),
)
rect.move_to(axes.c2p(x + dx/2, height/2))
```
### 3. Scene Variants
| Scene | Purpose |
|-------|---------|
| `AreaUnderCurve` | Basic accumulating area animation |
| `RiemannSums` | Rectangles converging (n=4,8,16,32) |
| `ExponentialDecay` | ∫e^(-x)dx = 1 with live area counter |
## Key Patterns
### Pattern: Live Value Display
```python
value_label = Tex(r"\text{Area} \approx 0.00")
value_num = value_label.make_number_changeable("0.00")
value_num.add_updater(lambda m: m.set_value(computed_area))
```
### Pattern: Progressive Rectangle Refinement
```python
for n in [4, 8, 16, 32]:
new_rects = create_rectangles(n)
self.play(ReplacementTransform(current_rects, new_rects))
current_rects = new_rects
```
## Run Commands
```bash
manimgl integration_visualization.py AreaUnderCurve -w
manimgl integration_visualization.py RiemannSums -w
manimgl integration_visualization.py ExponentialDecay -w
```
references/parallax_starfield.md
# Parallax Starfield - Reference Guide
**Example file**: `examples/parallax_starfield.py`
## User Query Scenarios
This example addresses queries like:
- "Show how parallax works with stars"
- "Create a 3D scene demonstrating depth perception"
- "Animate an observer moving through a starfield"
- "Explain stellar parallax visually"
- "Show why nearby objects move more than distant ones when you move"
## Scene Thinking Process (3b1b Style)
### 1. Identify the Core Concept
**Parallax**: When an observer moves, nearby objects appear to shift more against the background than distant objects. This is how astronomers measure distances to nearby stars.
### 2. Visual Design Decisions
**Why stars/dots instead of complex objects?**
- Stars naturally exist at varying distances
- Dots are computationally efficient (GlowDots handles 200+ easily)
- The effect is clear without distraction from object shapes
**Why a reference cube?**
- Provides spatial context in 3D
- Helps viewer understand the volume where stars exist
- The wireframe doesn't obscure the stars
**Why use a Pi creature as observer?**
- Makes the scene relatable - you're watching someone observe
- Their movement is intuitive to understand
- Can show reactions with `observer.change("pondering")`
### 3. Technical Implementation
#### GlowDots for Efficient Star Rendering
```python
# Random 3D positions
star_positions = np.random.uniform(-1, 1, (n_stars, 3))
stars = GlowDots(star_positions)
stars.set_glow_factor(2) # Soft bloom effect
stars.set_radii(np.random.uniform(0, 0.075, n_stars)) # Varying sizes
```
**Key insight**: `GlowDots` is far more efficient than creating individual `Dot` objects. For 200+ points, this is essential.
#### 3D Camera Control
```python
frame = self.frame
self.set_floor_plane("xz") # Z is now vertical
# Smooth camera reorientation
self.play(frame.animate.reorient(-40, -26, 0), run_time=2)
```
**Why `set_floor_plane("xz")`?** In astronomy visualizations, we often want Z as the vertical axis. This call reconfigures the coordinate system.
#### Observer Movement Pattern
```python
for dy in [1.5, -3, 3, -3, 1.5]:
self.play(observer.animate.shift(dy * IN), run_time=3)
```
**Why this specific pattern?**
- `[1.5, -3, 3, -3, 1.5]` creates: up → down → up → down → center
- The viewer sees the full range of parallax shift
- Returns to starting position for clean looping if needed
### 4. Scene Variants
The example includes three variants showing progressive complexity:
| Scene | Purpose | When to Use |
|-------|---------|-------------|
| `ParallaxStarfield` | Basic effect, third-person view | General explanation |
| `ParallaxFromObserverPOV` | First-person perspective | "What would you see?" |
| `LayeredParallax` | Explicit distance layers | Teaching the concept clearly |
## Key Patterns Demonstrated
### Pattern: Frame Following an Object
```python
frame.always.match_z(observer)
```
The camera's Z position continuously matches the observer, creating a first-person view.
### Pattern: Layered Depth for Clarity
```python
colors = [RED, YELLOW, BLUE]
distances = [2, 5, 10]
```
Using distinct colors at specific distances makes the parallax effect unmistakably clear for educational purposes.
### Pattern: Smooth Lateral Movement
```python
self.play(
observer.animate.shift(dx * RIGHT),
run_time=3,
rate_func=smooth
)
```
Slow, smooth movement lets viewers track individual stars and observe the effect.
## Common Modifications
### Add More Stars
```python
n_stars = 500 # Increase count
stars.set_radii(np.random.uniform(0, 0.05, n_stars)) # Smaller radii for density
```
### Different Star Colors
```python
# Temperature-based star colors
colors = [RED, ORANGE, YELLOW, WHITE, BLUE_A]
for i, star in enumerate(stars):
star.set_color(random.choice(colors))
```
### Add Background Galaxy
```python
background = ImageMobject("milky_way.png")
background.set_height(20)
background.shift(50 * OUT) # Far behind stars
self.add(background)
```
## Output
When rendered, this produces:
- A 3D starfield within a blue wireframe cube
- An observer (Randolph) that moves up/down
- Stars appearing to shift differently based on distance
- Clear demonstration of the parallax principle
## Run Commands
```bash
# Full render
manimgl parallax_starfield.py ParallaxStarfield -w
# Preview (no file output)
manimgl parallax_starfield.py ParallaxStarfield -p
# All three scenes
manimgl parallax_starfield.py ParallaxStarfield ParallaxFromObserverPOV LayeredParallax -w
```
references/rotating_exponentials.md
# Rotating Exponentials - Reference Guide
**Example file**: `examples/rotating_exponentials.py`
## User Query Scenarios
This example addresses queries like:
- "Visualize e^(it) on the complex plane"
- "Show Euler's formula animation"
- "Demonstrate how cosine comes from rotating exponentials"
- "Create a complex plane with rotating vector"
- "Show e^(iπ) = -1 visually"
## Scene Thinking Process (3b1b Style)
### 1. Core Concept
**Euler's Formula**: `e^(it) = cos(t) + i·sin(t)` - a rotating unit vector in the complex plane. Two counter-rotating exponentials sum to give real cosine.
### 2. Visual Design Decisions
**Why use ComplexPlane?**
- Natural coordinate system for complex numbers
- Built-in grid and labels
- `n2p()` method converts complex to point
**Why show the traced path?**
- Reveals the unit circle emerges naturally
- Shows the relationship between angle and position
### 3. Technical Implementation
#### Rotating Vector with TracedPath
```python
time_tracker = ValueTracker(0)
vector = Vector(RIGHT, color=YELLOW)
vector.add_updater(lambda v: v.put_start_and_end_on(
ORIGIN,
plane.n2p(np.exp(1j * time_tracker.get_value()))
))
tip_dot = Dot(color=YELLOW)
tip_dot.add_updater(lambda d: d.move_to(vector.get_end()))
traced = TracedPath(tip_dot.get_center, stroke_color=BLUE)
```
#### Counter-Rotating for Cosine
```python
# e^(it) rotates counter-clockwise
v1.add_updater(lambda v: v.put_start_and_end_on(
ORIGIN, plane.n2p(np.exp(1j * t))
))
# e^(-it) rotates clockwise
v2.add_updater(lambda v: v.put_start_and_end_on(
ORIGIN, plane.n2p(np.exp(-1j * t))
))
# Sum is always real: 2cos(t)
```
### 4. Scene Variants
| Scene | Purpose |
|-------|---------|
| `RotatingExponential` | Basic e^(it) visualization |
| `CounterRotatingExponentials` | Shows e^(it) + e^(-it) = 2cos(t) |
| `EulersFormula` | Famous e^(iπ) = -1 |
| `ComplexExponentialSpiral` | Decaying spiral e^((a+bi)t) |
## Key Patterns
### Pattern: always_redraw for Arcs
```python
angle_arc = always_redraw(lambda: Arc(
start_angle=0,
angle=time_tracker.get_value() % TAU,
radius=0.3,
color=GREEN
))
```
### Pattern: Complex Number to Point
```python
# Using ComplexPlane.n2p() (number to point)
point = plane.n2p(1 + 2j) # Complex number
point = plane.n2p(np.exp(1j * theta)) # Euler form
```
## Run Commands
```bash
manimgl rotating_exponentials.py RotatingExponential -w
manimgl rotating_exponentials.py CounterRotatingExponentials -w
manimgl rotating_exponentials.py EulersFormula -w
manimgl rotating_exponentials.py ComplexExponentialSpiral -w
```
references/spring_mass_system.md
# Spring-Mass System - Reference Guide
**Example file**: `examples/spring_mass_system.py`
## User Query Scenarios
This example addresses queries like:
- "Create a spring animation with oscillation"
- "Show damped harmonic motion"
- "Visualize physics simulation with a mass on a spring"
- "Animate a spring-mass system with real-time graph"
- "Compare different damping coefficients"
## Scene Thinking Process (3b1b Style)
### 1. Identify the Core Concept
**Damped Harmonic Motion**: A mass attached to a spring oscillates, with amplitude decreasing over time due to friction/damping. The equation is: `x'' = -kx - μv`
### 2. Visual Design Decisions
**Why a parametric helix for the spring?**
- Looks realistic with 3D coils
- Stretches naturally when mass moves
- Uses `ParametricCurve` for smooth rendering
**Why track position on a number line?**
- Gives quantitative feedback
- Shows exact displacement values
- Easy to understand motion direction
### 3. Technical Implementation
#### Creating a Self-Contained Physics Component
```python
class SpringMassSystem(VGroup):
def __init__(self, x0=0, v0=0, k=3, mu=0.1, ...):
# Store physics state
self.k = k
self.mu = mu
self.velocity = v0
# Add physics updater
self.add_updater(lambda m, dt: m.time_step(dt))
```
**Key insight**: Encapsulate physics + visuals in one VGroup subclass. This makes it reusable and keeps animation code clean.
#### Physics Integration (Euler Method)
```python
def time_step(self, delta_t, dt_size=0.01):
state = [self.get_x(), self.velocity]
for _ in range(sub_steps):
x, v = state
acceleration = -self.k * x - self.mu * v
state[0] += v * true_dt
state[1] += acceleration * true_dt
```
#### Dynamic Velocity/Force Vectors
```python
def get_velocity_vector(self, scale_factor=0.5, color=GREEN):
vector = Vector(RIGHT, fill_color=color)
vector.add_updater(lambda m: m.put_start_and_end_on(
self.mass.get_center(),
self.mass.get_center() + scale_factor * self.velocity * RIGHT
))
return vector
```
### 4. Scene Variants
| Scene | Purpose |
|-------|---------|
| `SpringMassDemo` | Basic oscillation with velocity/force vectors |
| `SpringWithGraph` | Real-time x(t) graph using TracedPath |
| `MultipleSprings` | Compare different damping values |
## Key Patterns Demonstrated
### Pattern: Pausable Physics
```python
def pause(self):
self._is_running = False
def unpause(self):
self._is_running = True
```
### Pattern: TracedPath for Graphs
```python
tracking_point = Point()
tracking_point.add_updater(lambda p: p.move_to(
axes.c2p(time_tracker.get_value(), spring.get_x())
))
position_graph = TracedPath(tracking_point.get_center, stroke_color=BLUE)
```
## Run Commands
```bash
# Basic demo
manimgl spring_mass_system.py SpringMassDemo -w
# With real-time graph
manimgl spring_mass_system.py SpringWithGraph -w
# Compare damping
manimgl spring_mass_system.py MultipleSprings -w
```
references/three_d_surfaces.md
# 3D Surfaces - Reference Guide
**Example file**: `examples/three_d_surfaces.py`
## User Query Scenarios
This example addresses queries like:
- "Create a 3D surface visualization"
- "Show a parametric surface"
- "Animate camera rotation around object"
- "Create a torus/sphere/cone"
- "Show saddle surface"
## Scene Thinking Process (3b1b Style)
### 1. Core Concept
**Parametric Surfaces**: Define surfaces as functions (u,v) → (x,y,z). Camera movement reveals 3D structure.
### 2. Technical Implementation
#### Basic Parametric Surface
```python
surface = ParametricSurface(
lambda u, v: [u, v, np.sin(u) * np.cos(v)],
u_range=(-3, 3),
v_range=(-3, 3),
resolution=(30, 30),
)
surface.set_color(BLUE)
surface.set_opacity(0.8)
```
#### Camera Setup and Movement
```python
frame = self.frame
frame.reorient(-30, 70, 0) # phi, theta, gamma
frame.set_height(10)
# Animate camera
self.play(frame.animate.reorient(30, 60, 0), run_time=3)
```
#### Sphere with Latitude/Longitude Lines
```python
# Latitude lines
for phi in np.linspace(-PI/2 + 0.3, PI/2 - 0.3, 6):
line = ParametricCurve(
lambda t: radius * np.array([
np.cos(t) * np.cos(phi),
np.sin(t) * np.cos(phi),
np.sin(phi)
]),
t_range=(0, TAU),
)
```
#### Torus Parameterization
```python
R, r = 2, 0.7 # Major and minor radius
torus = ParametricSurface(
lambda u, v: [
(R + r * np.cos(v)) * np.cos(u),
(R + r * np.cos(v)) * np.sin(u),
r * np.sin(v)
],
u_range=(0, TAU),
v_range=(0, TAU),
)
```
### 3. Scene Variants
| Scene | Purpose |
|-------|---------|
| `ParametricSurface3D` | z = sin(x)cos(y) with camera orbit |
| `SphereSurface` | Sphere with grid lines, rotating |
| `ConeUnfolding` | 3D cone visualization |
| `SaddleSurface` | z = x² - y² with cross-sections |
| `TorusSurface` | Donut shape with rotation |
## Key Patterns
### Pattern: ThreeDAxes
```python
axes = ThreeDAxes(
x_range=(-3, 3, 1),
y_range=(-3, 3, 1),
z_range=(-2, 2, 1),
)
```
### Pattern: Rotating Objects
```python
self.play(
Rotate(surface, TAU, axis=UP, run_time=6, rate_func=linear),
)
```
### Pattern: Frame Reorientation
```python
# reorient(phi, theta, gamma, center, height)
frame.reorient(-30, 70, 0) # Just angles
frame.animate.reorient(60, 60, 0) # Animated
```
## Run Commands
```bash
manimgl three_d_surfaces.py ParametricSurface3D -w
manimgl three_d_surfaces.py SphereSurface -w
manimgl three_d_surfaces.py TorusSurface -w
manimgl three_d_surfaces.py SaddleSurface -w
```
references/transit_animation.md
# Transit Animations - Reference Guide
**Example file**: `examples/transit_animation.py`
## User Query Scenarios
This example addresses queries like:
- "Create a planet transit animation"
- "Show loading dots animation"
- "Animate a pendulum swing"
- "Create wave propagation"
- "Show orbital motion"
## Scene Thinking Process (3b1b Style)
### 1. Core Concept
**Transit/Periodic Motion**: Objects moving along paths, leaving traces, showing periodic behavior. Used for astronomical transits, loading indicators, physics demos.
### 2. Technical Implementation
#### Transit with Snapshots
```python
venus.add_updater(lambda m, dt: m.shift(dt * velocity * RIGHT))
copies = VGroup()
for _ in range(n_snapshots):
self.wait(wait_time)
copies.add(venus.copy().clear_updaters())
self.play(Transform(copies, VGroup(path))) # Collapse to line
```
#### Orbital Motion with Depth Effect
```python
def update_planet(p):
a = angle.get_value()
x = 2.5 * np.cos(a)
y = 0.5 * np.sin(a) # Compressed y = tilted orbit
p.move_to([x, y, 0])
# Size varies with "depth"
scale = 0.12 + 0.06 * np.sin(a)
p.set_width(2 * scale)
```
#### Phase-Shifted Oscillation (Loading Dots)
```python
for i, dot in enumerate(dots):
phase = i * TAU / n_dots
dot.add_updater(lambda m, p=phase: m.set_y(
original_y + 0.3 * np.sin(3 * time.get_value() + p)
))
```
#### Pendulum Physics
```python
omega = np.sqrt(g / length) # Natural frequency
amplitude = PI / 4
theta.add_updater(lambda m: m.set_value(
amplitude * np.cos(omega * time.get_value()) * np.exp(-0.05 * time.get_value())
))
```
### 3. Scene Variants
| Scene | Purpose |
|-------|---------|
| `TransitOfVenus` | Historical astronomical transit |
| `OrbitalTransit` | Exoplanet-style orbit with depth |
| `LoadingDots` | Classic loading animation |
| `WaveTransit` | Wave pulse propagation |
| `PendulumSwing` | Damped pendulum with trail |
## Key Patterns
### Pattern: Copy and Freeze
```python
copy = mobject.copy().clear_updaters() # Snapshot current state
```
### Pattern: Continuous Time Updater
```python
time = ValueTracker(0)
time.add_updater(lambda m, dt: m.increment_value(dt))
```
## Run Commands
```bash
manimgl transit_animation.py TransitOfVenus -w
manimgl transit_animation.py LoadingDots -w
manimgl transit_animation.py PendulumSwing -w
```
references/vector_fields.md
# Vector Fields - Reference Guide
**Example file**: `examples/vector_fields.py`
## User Query Scenarios
This example addresses queries like:
- "Create a vector field visualization"
- "Show particles flowing through a field"
- "Visualize electric field from charges"
- "Animate gradient descent"
- "Show fluid flow"
## Scene Thinking Process (3b1b Style)
### 1. Core Concept
**Vector Fields**: At each point in space, there's a vector showing direction and magnitude. Particles follow the field, revealing flow patterns.
### 2. Technical Implementation
#### Manual Arrow Field (Portable Approach)
```python
arrows = VGroup()
for x in np.arange(-3.5, 4, 0.7):
for y in np.arange(-2.5, 3, 0.7):
vx, vy = -y * 0.15, x * 0.15 # Rotation field
arrow = Arrow(
start=[x, y, 0],
end=[x + vx, y + vy, 0],
buff=0,
stroke_width=2,
)
# Color by magnitude
mag = np.sqrt(vx**2 + vy**2)
arrow.set_color(interpolate_color(BLUE, YELLOW, mag / 0.5))
arrows.add(arrow)
```
#### Particle Following Field
```python
def follow_field(mob, dt):
x, y = mob.get_center()[:2]
vx, vy = field_func(x, y)
mob.shift(np.array([vx, vy, 0]) * dt)
dot.add_updater(follow_field)
trail = TracedPath(dot.get_center, stroke_color=RED)
```
#### Electric Dipole Field
```python
def E_field(pos):
r1, r2 = pos - q1_pos, pos - q2_pos
d1, d2 = np.linalg.norm(r1), np.linalg.norm(r2)
E1 = r1 / d1**3 # From + charge
E2 = -r2 / d2**3 # From - charge
return E1 + E2
```
### 3. Scene Variants
| Scene | Purpose |
|-------|---------|
| `SimpleVectorField` | Rotation field with particle |
| `GradientFieldDemo` | Scalar field + gradient arrows |
| `ParticleFlow` | Multiple particles in vortex |
| `ElectricDipole` | Field from +/- charges |
## Key Patterns
### Pattern: Color by Magnitude
```python
mag = np.linalg.norm([vx, vy])
color = interpolate_color(BLUE, YELLOW, min(mag * scale, 1))
arrow.set_color(color)
```
### Pattern: LaggedStartMap for Many Arrows
```python
self.play(LaggedStartMap(GrowArrow, arrows, lag_ratio=0.02, run_time=2))
```
### Pattern: Closure for Updaters in Loops
```python
for i in range(n):
dot = Dot(...)
def make_updater(): # Closure captures current state
def update(mob, dt):
# use mob, not dot
...
return update
dot.add_updater(make_updater())
```
## Run Commands
```bash
manimgl vector_fields.py SimpleVectorField -w
manimgl vector_fields.py GradientFieldDemo -w
manimgl vector_fields.py ParticleFlow -w
manimgl vector_fields.py ElectricDipole -w
```
rules/3d.md
# 3D in ManimGL
ManimGL has powerful 3D capabilities with a flexible camera system. Unlike ManimCE, ManimGL doesn't require a special ThreeDScene class.
## Basic 3D Setup
### Creating 3D Objects
```python
from manimlib import *
class Basic3DScene(Scene):
def construct(self):
# Get camera frame
frame = self.camera.frame
# Set 3D orientation
frame.reorient(20, 70) # theta, phi in degrees
# Create 3D objects
sphere = Sphere(radius=2)
sphere.set_color(BLUE, opacity=0.7)
self.add(sphere)
```
## Camera Frame Control
### frame.reorient()
The `reorient()` method is the primary way to control 3D camera orientation.
```python
# frame.reorient(theta, phi, gamma=0, center=ORIGIN, height=8)
# Front view
frame.reorient(0, 0)
# Isometric view
frame.reorient(20, 70)
# Top-down view
frame.reorient(0, 90)
# Side view
frame.reorient(90, 90)
```
### Animating Camera Movement
```python
class AnimatedCamera(Scene):
def construct(self):
frame = self.camera.frame
# Create object
cube = Cube()
self.add(cube)
# Animate camera rotation
self.play(frame.animate.reorient(30, 60))
self.wait()
# Continuous rotation
frame.add_updater(lambda m, dt: m.increment_theta(0.2 * dt))
self.wait(10)
```
### Frame Methods
```python
# Set Euler angles
frame.set_euler_angles(theta=30*DEGREES, phi=70*DEGREES)
# Increment angles (useful for rotation)
frame.increment_theta(10*DEGREES)
frame.increment_phi(5*DEGREES)
frame.increment_gamma(2*DEGREES)
# Get current angles
theta = frame.get_theta()
phi = frame.get_phi()
```
## 3D Geometric Primitives
### Sphere
```python
sphere = Sphere(
radius=2,
resolution=(20, 20), # (u_resolution, v_resolution)
color=BLUE
)
sphere.set_opacity(0.7)
```
### Cube
```python
cube = Cube(
side_length=2,
color=GREEN,
fill_opacity=0.8
)
```
### Surface
```python
# Parametric surface
surface = Surface(
lambda u, v: np.array([u, v, u**2 + v**2]),
u_range=(-2, 2),
v_range=(-2, 2),
resolution=(20, 20)
)
```
### Torus
```python
torus = Torus(
r1=2, # Major radius
r2=0.5, # Minor radius
color=YELLOW
)
```
### Cylinder
```python
cylinder = Cylinder(
height=3,
radius=1,
color=RED
)
```
### 3D Lines and Shapes
```python
# 3D Line
line = Line3D(
start=[-2, -2, -2],
end=[2, 2, 2],
color=WHITE,
width=0.05
)
# 3D Disk
disk = Disk3D(radius=1.5, color=PURPLE)
# 3D Square
square = Square3D(side_length=2, color=ORANGE)
```
## Textured Surfaces
### Adding Textures to 3D Objects
```python
class TexturedSphere(Scene):
def construct(self):
frame = self.camera.frame
frame.reorient(20, 70)
# Create sphere with texture
sphere = Sphere(radius=2)
# Apply texture from URL or local file
textured_sphere = TexturedSurface(
surface=sphere,
# Can use URL or local path
image_file="path/to/texture.jpg"
)
self.add(textured_sphere)
# Rotate camera
self.play(frame.animate.increment_theta(360*DEGREES), run_time=10)
```
### Earth Example
```python
earth = Sphere(radius=2, resolution=(40, 40))
textured_earth = TexturedSurface(
earth,
"https://upload.wikimedia.org/wikipedia/commons/thumb/4/4d/Whole_world_-_land_and_oceans.jpg/1280px-Whole_world_-_land_and_oceans.jpg"
)
```
## 3D Axes
### ThreeDAxes
```python
axes = ThreeDAxes(
x_range=(-5, 5, 1),
y_range=(-5, 5, 1),
z_range=(-5, 5, 1),
width=10,
height=10,
depth=10
)
axes.add_coordinate_labels(font_size=20)
```
### NumberPlane in 3D
```python
# XY plane
xy_plane = NumberPlane(
x_range=(-5, 5),
y_range=(-5, 5)
)
# XZ plane (horizontal floor)
xz_plane = NumberPlane(
x_range=(-5, 5),
y_range=(-5, 5)
)
xz_plane.rotate(90*DEGREES, axis=RIGHT)
```
## Parametric Surfaces
### Creating Custom Surfaces
```python
# Paraboloid
paraboloid = Surface(
lambda u, v: np.array([
u,
v,
u**2 + v**2
]),
u_range=(-2, 2),
v_range=(-2, 2),
resolution=(30, 30),
color=BLUE
)
# Wave surface
wave = Surface(
lambda u, v: np.array([
u,
v,
np.sin(u) * np.cos(v)
]),
u_range=(-PI, PI),
v_range=(-PI, PI),
resolution=(40, 40)
)
```
### Surface Mesh
```python
# Create surface
surface = Sphere(radius=2)
# Add mesh overlay
mesh = SurfaceMesh(surface)
mesh.set_stroke(BLUE, width=1, opacity=0.5)
self.add(surface, mesh)
```
## Fixing Objects in Frame
### fix_in_frame()
Keep 2D elements (like labels) fixed while camera rotates.
```python
class FixedLabels(Scene):
def construct(self):
frame = self.camera.frame
frame.reorient(20, 70)
# 3D object that rotates with camera
cube = Cube()
self.add(cube)
# 2D title that stays fixed
title = Text("3D Cube", font_size=60)
title.to_edge(UP)
title.fix_in_frame() # Stays in screen space
self.add(title)
# Rotate camera
self.play(frame.animate.reorient(50, 80), run_time=3)
```
## Lighting
### Light Source
```python
# Access light source
light = self.camera.light_source
# Move light
light.move_to([10, 10, 10])
# Animate light movement
self.play(light.animate.move_to([0, 0, 10]), run_time=2)
```
### Gloss and Shadow
```python
# Add gloss to objects
sphere = Sphere(radius=2)
sphere.set_gloss(0.8) # 0 to 1
# Add shadow
sphere.set_shadow(0.5) # 0 to 1
```
## Complex 3D Scene Example
```python
class Complex3DScene(Scene):
def construct(self):
frame = self.camera.frame
frame.reorient(20, 75)
# Create axes
axes = ThreeDAxes(
x_range=(-3, 3),
y_range=(-3, 3),
z_range=(-3, 3)
)
# Create parametric surface
surface = Surface(
lambda u, v: np.array([
u,
v,
np.sin(np.sqrt(u**2 + v**2))
]),
u_range=(-3, 3),
v_range=(-3, 3),
resolution=(30, 30),
color=BLUE
)
surface.set_opacity(0.7)
# Add mesh
mesh = SurfaceMesh(surface)
mesh.set_stroke(WHITE, 0.5, opacity=0.3)
# Add title (fixed in frame)
title = Text("Sinc Function Surface", font_size=48)
title.to_edge(UP)
title.fix_in_frame()
title.set_backstroke(BLACK, 5)
# Build scene
self.add(axes, surface, mesh, title)
self.wait()
# Rotate camera smoothly
self.play(
frame.animate.reorient(45, 70),
run_time=3
)
self.wait()
# Add continuous rotation
frame.add_updater(lambda m, dt: m.increment_theta(20 * dt))
self.wait(10)
```
## Interactive 3D Controls
When running with `manimgl`, you can interact with 3D scenes:
- Press `d` + move mouse: Rotate camera
- Press `z` + scroll: Zoom in/out
- Press `r`: Reset camera to default position
- Press `q`: Exit interaction mode
## Best Practices
1. **Use reorient()**: Cleaner than setting Euler angles manually
2. **fix_in_frame() for labels**: Keep UI elements readable
3. **Appropriate resolution**: Higher resolution for final renders, lower for development
4. **Opacity for depth**: Set opacity < 1 to see through surfaces
5. **Lighting matters**: Adjust light source position for better visualization
6. **Continuous rotation**: Use updaters for smooth camera rotation
7. **Frame rate considerations**: 3D scenes may need lower frame rates during development
## Common Patterns
### Rotating camera around object
```python
# 360-degree rotation
self.play(
frame.animate.increment_theta(360*DEGREES),
run_time=10,
rate_func=linear
)
```
### Creating a floor plane
```python
floor = NumberPlane(
x_range=(-10, 10),
y_range=(-10, 10),
background_line_style={"stroke_color": GREY, "stroke_width": 1}
)
floor.rotate(90*DEGREES, RIGHT)
floor.shift(2*DOWN)
```
### Multiple viewpoints
```python
# Show from different angles
angles = [(0, 0), (20, 70), (45, 45), (90, 90)]
for theta, phi in angles:
self.play(frame.animate.reorient(theta, phi))
self.wait()
```
rules/animation-groups.md
# Animation Groups in ManimGL
Animation groups allow you to coordinate multiple animations, running them simultaneously, sequentially, or with staggered timing.
## AnimationGroup
Runs multiple animations together.
### Basic Usage
```python
from manimlib import *
class GroupExample(Scene):
def construct(self):
circle = Circle()
square = Square()
circle.shift(LEFT * 2)
square.shift(RIGHT * 2)
# Run both animations simultaneously
self.play(AnimationGroup(
ShowCreation(circle),
ShowCreation(square)
))
self.wait()
```
### Shorthand Syntax
```python
# Equivalent to AnimationGroup
self.play(
ShowCreation(circle),
ShowCreation(square)
)
```
## LaggedStart
Starts animations with a staggered delay.
### Basic LaggedStart
```python
class LaggedStartExample(Scene):
def construct(self):
circles = VGroup(*[
Circle(radius=0.5).shift(i * RIGHT)
for i in range(-3, 4)
])
# Staggered creation
self.play(LaggedStart(
*[ShowCreation(c) for c in circles],
lag_ratio=0.2, # Delay ratio between animations
run_time=3
))
self.wait()
```
### lag_ratio Parameter
```python
# lag_ratio controls the delay
# 0 = all at once (like AnimationGroup)
# 1 = completely sequential (like Succession)
# 0.5 = overlapping animations
# Subtle overlap
self.play(LaggedStart(*animations, lag_ratio=0.1))
# More pronounced stagger
self.play(LaggedStart(*animations, lag_ratio=0.5))
# Nearly sequential
self.play(LaggedStart(*animations, lag_ratio=0.9))
```
## Succession
Runs animations one after another.
```python
class SuccessionExample(Scene):
def construct(self):
shapes = VGroup(
Circle().shift(LEFT * 2),
Square(),
Triangle().shift(RIGHT * 2)
)
# One after another (no overlap)
self.play(Succession(
ShowCreation(shapes[0]),
ShowCreation(shapes[1]),
ShowCreation(shapes[2])
))
self.wait()
```
### Succession vs Sequential play() Calls
```python
# Using Succession (all in one play call)
self.play(Succession(
animation1,
animation2,
animation3
))
# Equivalent to separate play calls
self.play(animation1)
self.play(animation2)
self.play(animation3)
```
## Combining Animation Groups
### Nested Groups
```python
class NestedGroups(Scene):
def construct(self):
# Top row
top = VGroup(*[Circle().shift(i*RIGHT) for i in range(-2, 3)])
# Bottom row
bottom = VGroup(*[Square().shift(i*RIGHT + 2*DOWN) for i in range(-2, 3)])
# Stagger within each row, but rows appear simultaneously
self.play(
LaggedStart(*[ShowCreation(c) for c in top], lag_ratio=0.2),
LaggedStart(*[ShowCreation(s) for s in bottom], lag_ratio=0.2),
)
self.wait()
```
### Sequential Groups
```python
# First group, then second group
self.play(Succession(
LaggedStart(*[ShowCreation(t) for t in top], lag_ratio=0.2),
LaggedStart(*[ShowCreation(b) for b in bottom], lag_ratio=0.2)
))
```
## LaggedStartMap
Applies an animation constructor to mobjects with lag.
```python
class LaggedStartMapExample(Scene):
def construct(self):
dots = VGroup(*[
Dot().shift(i * RIGHT + j * UP)
for i in range(-3, 4)
for j in range(-2, 3)
])
# Apply FadeIn to all dots with lag
self.play(LaggedStartMap(
FadeIn, dots,
lag_ratio=0.05
))
self.wait()
```
## Timing Control
### run_time for Groups
```python
# Total time for all animations
self.play(LaggedStart(
*animations,
lag_ratio=0.2,
run_time=5 # Total duration
))
# Each animation's individual timing
self.play(LaggedStart(
ShowCreation(circle, run_time=2),
ShowCreation(square, run_time=1),
lag_ratio=0.3
))
```
### rate_func with Groups
```python
# Apply rate function to entire group
self.play(
LaggedStart(*animations, lag_ratio=0.2),
rate_func=smooth
)
# Different rate functions for each
self.play(
ShowCreation(circle, rate_func=linear),
ShowCreation(square, rate_func=rush_into),
ShowCreation(triangle, rate_func=rush_from)
)
```
## Practical Examples
### Text Appearance
```python
class TextReveal(Scene):
def construct(self):
title = Text("Animated Title", font_size=72)
subtitle = Text("With smooth appearance", font_size=40)
subtitle.next_to(title, DOWN)
# Title letters appear one by one
self.play(LaggedStart(
*[FadeIn(char, shift=UP) for char in title],
lag_ratio=0.05
))
self.wait(0.3)
# Subtitle fades in
self.play(FadeIn(subtitle, shift=DOWN))
self.wait()
```
### Grid Animation
```python
class GridAnimation(Scene):
def construct(self):
grid = VGroup(*[
Square(side_length=0.8).shift([i, j, 0])
for i in range(-3, 4)
for j in range(-2, 3)
])
# Ripple effect
self.play(LaggedStart(
*[ShowCreation(square) for square in grid],
lag_ratio=0.02,
run_time=4
))
self.wait()
```
### Wave Effect
```python
class WaveEffect(Scene):
def construct(self):
dots = VGroup(*[
Dot().shift(i * 0.5 * RIGHT)
for i in range(-10, 11)
])
# Wave up and down
def wave_animation(dot, delay):
return Succession(
Wait(delay),
dot.animate.shift(UP),
dot.animate.shift(DOWN)
)
self.add(dots)
self.play(*[
wave_animation(dot, i * 0.1)
for i, dot in enumerate(dots)
])
self.wait()
```
### Cascade Effect
```python
class CascadeEffect(Scene):
def construct(self):
squares = VGroup(*[
Square(side_length=1).shift(i * 1.5 * DOWN)
for i in range(-2, 3)
])
# Cascade from top to bottom
self.play(LaggedStart(
*[
AnimationGroup(
square.animate.shift(RIGHT * 3),
square.animate.set_color(random_color())
)
for square in squares
],
lag_ratio=0.3
))
self.wait()
```
## Simultaneous Transformations
### Multiple Object Transformations
```python
class SimultaneousTransforms(Scene):
def construct(self):
shapes = VGroup(
Circle().shift(LEFT * 3),
Square().shift(LEFT),
Triangle().shift(RIGHT),
Star().shift(RIGHT * 3)
)
self.play(LaggedStart(
*[ShowCreation(s) for s in shapes],
lag_ratio=0.2
))
self.wait()
# Transform all simultaneously with different targets
targets = [
Square().shift(LEFT * 3),
Circle().shift(LEFT),
Star().shift(RIGHT),
Triangle().shift(RIGHT * 3)
]
self.play(*[
Transform(s, t)
for s, t in zip(shapes, targets)
])
self.wait()
```
## Best Practices
1. **Use LaggedStart for visual rhythm**: Creates more dynamic animations
2. **lag_ratio tuning**:
- 0.1-0.3 for subtle effects
- 0.5 for balanced overlap
- 0.8-1.0 for nearly sequential
3. **Nested groups**: Combine for complex choreography
4. **Total run_time**: Set on the group for consistent timing
5. **Don't overuse**: Too many lagged animations can be distracting
## Common Patterns
### Fade out everything
```python
# Fade out all objects with lag
self.play(LaggedStart(
*[FadeOut(mob) for mob in self.mobjects],
lag_ratio=0.1
))
```
### Build complex figure
```python
# Build parts sequentially
self.play(Succession(
ShowCreation(axes),
ShowCreation(graph),
Write(labels),
FadeIn(legend)
))
```
### Reveal diagram
```python
# Reveal components with rhythm
components = [background, main_shape, decorations, labels]
self.play(LaggedStart(
*[FadeIn(c, scale=0.8) for c in components],
lag_ratio=0.4
))
```
### Synchronized movement
```python
# Move multiple objects together
objects = VGroup(circle, square, triangle)
self.play(*[
obj.animate.shift(RIGHT * 2)
for obj in objects
])
```
## Full Example
```python
class ComprehensiveGrouping(Scene):
def construct(self):
# Title
title = Text("Animation Groups", font_size=60)
title.to_edge(UP)
self.play(Write(title))
self.wait()
# Create grid of dots
dots = VGroup(*[
Dot(color=interpolate_color(BLUE, RED, i/20))
.shift([
(i % 7 - 3) * 0.8,
(i // 7 - 1.5) * 0.8,
0
])
for i in range(21)
])
# Lagged appearance
self.play(LaggedStart(
*[FadeIn(dot, scale=0.5) for dot in dots],
lag_ratio=0.05,
run_time=3
))
self.wait()
# Synchronized color change
self.play(*[
dot.animate.set_color(YELLOW)
for dot in dots
])
self.wait()
# Cascade disappearance
self.play(LaggedStart(
*[FadeOut(dot, shift=DOWN) for dot in dots],
lag_ratio=0.05,
run_time=2
))
# Clean up
self.play(FadeOut(title))
self.wait()
```
## Debugging Groups
### Print timing information
```python
# Check total duration
group = LaggedStart(*animations, lag_ratio=0.2)
print(f"Group duration: {group.get_run_time()}")
# Visualize timing
for i, anim in enumerate(animations):
print(f"Animation {i}: starts at {i * 0.2 * group.get_run_time()}")
```
### Test lag_ratio values
```python
# Try different values to find the right feel
for lag in [0.1, 0.3, 0.5, 0.7]:
self.play(LaggedStart(*animations, lag_ratio=lag))
self.wait()
```
rules/animations.md
# ManimGL Animations
## Animation System Overview
ManimGL's animation system is built around the `Animation` base class. Specialized subclasses handle creation, transformation, and indication effects.
## Playing Animations
```python
# Single animation
self.play(ShowCreation(circle))
# Multiple animations simultaneously
self.play(
ShowCreation(circle),
Write(text),
)
# With run_time
self.play(ShowCreation(circle), run_time=2)
# With rate function
self.play(ShowCreation(circle), rate_func=smooth)
```
## Creation Animations
| Animation | Description |
|-----------|-------------|
| `ShowCreation` | Draw a VMobject's path (NOT `Create` like in ManimCE) |
| `Write` | Write text or LaTeX |
| `DrawBorderThenFill` | Draw outline then fill |
| `FadeIn` | Fade in with optional direction |
| `FadeOut` | Fade out with optional direction |
| `GrowFromCenter` | Scale up from center |
| `GrowFromPoint` | Scale up from a point |
| `GrowArrow` | Specialized for arrows |
```python
# ShowCreation for paths
self.play(ShowCreation(circle))
# Write for text
self.play(Write(Tex(R"\pi")))
# FadeIn with direction
self.play(FadeIn(square, shift=UP))
```
## Transform Animations
| Animation | Description |
|-----------|-------------|
| `Transform` | Morph one mobject into another (modifies original) |
| `ReplacementTransform` | Replace source with target |
| `TransformMatchingShapes` | Match similar shapes |
| `TransformMatchingTex` | Match LaTeX parts |
| `FadeTransform` | Fade while transforming |
| `MoveToTarget` | Move to mobject's `.target` |
```python
# Transform (modifies circle, becomes square)
self.play(Transform(circle, square))
# ReplacementTransform (removes circle, adds square)
self.play(ReplacementTransform(circle, square))
# Using .target
circle.generate_target()
circle.target.shift(RIGHT * 2)
circle.target.set_color(RED)
self.play(MoveToTarget(circle))
```
## Indication Animations
| Animation | Description |
|-----------|-------------|
| `Indicate` | Flash/pulse to draw attention |
| `ShowPassingFlash` | Flash along a path |
| `Flash` | Burst of light |
| `Circumscribe` | Draw circle/rect around |
| `Wiggle` | Wiggle the mobject |
| `FlashAround` | Flash effect around object |
```python
self.play(Indicate(important_text))
self.play(FlashAround(equation, run_time=2))
```
## Movement Animations
```python
# Using .animate syntax
self.play(circle.animate.shift(RIGHT * 2))
self.play(circle.animate.scale(2).set_color(RED))
# Rotate
self.play(Rotate(square, PI/2))
self.play(Rotate(square, 90 * DEGREES)) # Same thing
# MoveAlongPath
path = Line(LEFT, RIGHT)
self.play(MoveAlongPath(dot, path))
```
## LaggedStart and Groups
```python
# Staggered animations
self.play(LaggedStart(
*[ShowCreation(mob) for mob in mobjects],
lag_ratio=0.2
))
# AnimationGroup for simultaneous
self.play(AnimationGroup(
ShowCreation(circle),
Write(text),
lag_ratio=0 # Simultaneous
))
# Succession for sequential
self.play(Succession(
ShowCreation(circle),
Write(text),
))
```
## Animation Parameters
Common parameters for all animations:
| Parameter | Description |
|-----------|-------------|
| `run_time` | Duration in seconds |
| `rate_func` | Easing function (smooth, linear, etc.) |
| `lag_ratio` | Stagger ratio for grouped animations |
| `remover` | Remove mobject after animation |
| `introducer` | Add mobject at animation start |
```python
self.play(
ShowCreation(circle),
run_time=3,
rate_func=there_and_back,
)
```
## Rate Functions
Common rate functions:
- `smooth` - Default smooth easing
- `linear` - Constant speed
- `rush_into` - Fast start, slow end
- `rush_from` - Slow start, fast end
- `there_and_back` - Go and return
- `double_smooth` - Extra smooth
## Waiting
```python
self.wait() # Default pause
self.wait(2) # 2 second pause
self.wait(0.5) # Half second
```
rules/camera.md
# Camera and Frame in ManimGL
ManimGL's camera system is centered around the `CameraFrame`, accessible via `self.camera.frame`. This provides powerful control over both 2D and 3D perspectives.
## Accessing the Camera Frame
```python
from manimlib import *
class CameraExample(Scene):
def construct(self):
# Get the camera frame
frame = self.camera.frame
# frame is a Mobject, so it has all Mobject methods
# move_to, shift, scale, rotate, etc.
```
## 2D Camera Movement
### Basic Movement
```python
# Shift the camera
self.play(frame.animate.shift(RIGHT * 2))
# Move to a specific position
self.play(frame.animate.move_to([3, 2, 0]))
# Scale (zoom)
self.play(frame.animate.scale(0.5)) # Zoom in
self.play(frame.animate.scale(2)) # Zoom out
```
### Following Objects
```python
class FollowObject(Scene):
def construct(self):
frame = self.camera.frame
dot = Dot(color=RED)
# Camera follows dot
frame.add_updater(lambda m: m.move_to(dot))
# Move dot around
self.add(dot)
self.play(dot.animate.shift(RIGHT * 5), run_time=3)
self.play(dot.animate.shift(UP * 3), run_time=2)
self.wait()
```
### Frame Dimensions
```python
# Set frame width/height
frame.set_width(10)
frame.set_height(6)
# Animate frame size
self.play(frame.animate.set_width(20), run_time=2)
```
## 3D Camera Orientation
### reorient() Method
The `reorient()` method is the primary way to set 3D camera orientation in ManimGL.
```python
# Signature:
# frame.reorient(theta, phi, gamma=0, center=ORIGIN, height=8)
# Parameters:
# - theta: Rotation around z-axis (azimuthal angle) in degrees
# - phi: Angle from z-axis (polar angle) in degrees
# - gamma: Roll angle in degrees (optional)
# - center: Point the camera looks at (optional)
# - height: Frame height (optional)
# Common views:
frame.reorient(0, 0) # Front view (XY plane)
frame.reorient(20, 70) # Isometric-like view
frame.reorient(0, 90) # Top-down view (XY plane from above)
frame.reorient(90, 90) # Side view (YZ plane)
frame.reorient(45, 45) # Diagonal view
```
### Euler Angles
```python
# Set angles individually
frame.set_theta(30 * DEGREES)
frame.set_phi(70 * DEGREES)
frame.set_gamma(0 * DEGREES)
# Set all at once
frame.set_euler_angles(
theta=30 * DEGREES,
phi=70 * DEGREES,
gamma=0 * DEGREES
)
# Get current angles
theta = frame.get_theta()
phi = frame.get_phi()
gamma = frame.get_gamma()
```
### Incremental Rotation
```python
# Increment angles (useful for animations)
frame.increment_theta(10 * DEGREES)
frame.increment_phi(5 * DEGREES)
frame.increment_gamma(2 * DEGREES)
# Animated increments
self.play(frame.animate.increment_theta(90 * DEGREES))
```
## Animating Camera
### Simple Camera Animations
```python
class AnimateCamera(Scene):
def construct(self):
frame = self.camera.frame
cube = Cube()
self.add(cube)
# Reorient to isometric view
self.play(frame.animate.reorient(20, 70), run_time=2)
self.wait()
# Rotate around object
self.play(frame.animate.increment_theta(360 * DEGREES), run_time=8)
self.wait()
```
### Continuous Camera Motion
```python
class ContinuousRotation(Scene):
def construct(self):
frame = self.camera.frame
frame.reorient(20, 70)
sphere = Sphere(radius=2, color=BLUE)
self.add(sphere)
# Add continuous rotation updater
frame.add_updater(lambda m, dt: m.increment_theta(20 * dt))
# Let it rotate for 10 seconds
self.wait(10)
# Stop rotation
frame.clear_updaters()
self.wait()
```
### Camera Zoom In/Out
```python
class ZoomEffect(Scene):
def construct(self):
frame = self.camera.frame
objects = VGroup(*[Square() for _ in range(5)])
objects.arrange(RIGHT, buff=1)
self.add(objects)
# Zoom out to see all objects
self.play(frame.animate.set_width(20), run_time=2)
self.wait()
# Zoom in on first object
self.play(
frame.animate.set_width(2).move_to(objects[0]),
run_time=2
)
self.wait()
```
## Fixing Mobjects in Frame
### fix_in_frame() Method
Keep 2D elements fixed in screen space while the camera moves.
```python
class FixedInFrame(Scene):
def construct(self):
frame = self.camera.frame
frame.reorient(20, 70)
# 3D object that moves with camera
cube = Cube(color=BLUE)
self.add(cube)
# 2D label that stays fixed
title = Text("Rotating Cube", font_size=60)
title.to_edge(UP)
title.fix_in_frame() # Fixes it to screen space
self.add(title)
# Rotate camera - cube rotates, title stays fixed
self.play(frame.animate.reorient(60, 80), run_time=3)
self.wait()
```
### Multiple Fixed Elements
```python
class MultipleFixed(Scene):
def construct(self):
frame = self.camera.frame
frame.reorient(30, 70)
# 3D content
surface = Sphere(radius=2, color=BLUE, opacity=0.7)
self.add(surface)
# Fixed UI elements
title = Text("3D Visualization", font_size=48)
title.to_edge(UP)
title.fix_in_frame()
subtitle = Text("Interactive Camera", font_size=30, color=GREY)
subtitle.next_to(title, DOWN)
subtitle.fix_in_frame()
controls = Text("Press 'd' to rotate", font_size=24)
controls.to_corner(DL)
controls.fix_in_frame()
self.add(title, subtitle, controls)
# Rotate camera
self.play(frame.animate.increment_theta(180 * DEGREES), run_time=6)
```
## Reset Camera
```python
# Reset to default state
frame.to_default_state()
# Animate reset
self.play(frame.animate.to_default_state())
```
## Camera Center
```python
# Set what the camera looks at
frame.set_center([2, 3, 0])
# Animate center change
self.play(frame.animate.set_center([0, 0, 2]))
# Get current center
center = frame.get_center()
```
## Advanced Camera Patterns
### Orbit Camera Around Object
```python
class OrbitCamera(Scene):
def construct(self):
frame = self.camera.frame
frame.reorient(30, 70)
# Central object
torus = Torus(r1=2, r2=0.5, color=YELLOW)
self.add(torus)
# Orbit 360 degrees
self.play(
frame.animate.increment_theta(360 * DEGREES),
run_time=10,
rate_func=linear
)
```
### Camera Following Path
```python
class CameraPath(Scene):
def construct(self):
frame = self.camera.frame
# Create path
path = Circle(radius=5)
self.add(path)
# Dot to follow
dot = Dot(color=RED)
dot.move_to(path.point_from_proportion(0))
# Camera follows dot
frame.add_updater(lambda m: m.move_to(dot))
# Move dot along path
self.play(
MoveAlongPath(dot, path),
run_time=8,
rate_func=linear
)
```
### Multiple Camera Positions
```python
class CameraTour(Scene):
def construct(self):
frame = self.camera.frame
# Create scene
objects = VGroup(
Square(side_length=2, color=RED).shift(LEFT * 3),
Circle(radius=1, color=BLUE),
Triangle(color=GREEN).shift(RIGHT * 3)
)
self.add(objects)
# Tour each object
for obj in objects:
self.play(
frame.animate.set_width(3).move_to(obj),
run_time=2
)
self.wait()
# Return to overview
self.play(
frame.animate.set_width(14).move_to(ORIGIN),
run_time=2
)
```
### Dynamic Camera with Updater
```python
class DynamicCamera(Scene):
def construct(self):
frame = self.camera.frame
# Moving object
dot = Dot(color=RED)
# Camera tracks and zooms based on distance from origin
def update_frame(frame):
frame.move_to(dot)
dist = np.linalg.norm(dot.get_center())
frame.set_width(max(8, dist * 2))
frame.add_updater(update_frame)
# Move dot around
self.add(dot)
self.play(dot.animate.shift(RIGHT * 5 + UP * 3), run_time=4)
self.play(dot.animate.shift(LEFT * 8 + DOWN * 2), run_time=4)
self.wait()
```
## Light Source
### Accessing and Moving Light
```python
class LightControl(Scene):
def construct(self):
frame = self.camera.frame
frame.reorient(20, 70)
# Get light source
light = self.camera.light_source
# Create 3D object
sphere = Sphere(radius=2, color=BLUE)
sphere.set_gloss(0.8)
self.add(sphere)
# Show light position (for debugging)
light_indicator = Dot(color=YELLOW)
light_indicator.add_updater(lambda m: m.move_to(light.get_center()))
self.add(light_indicator)
# Move light around
self.play(light.animate.move_to([5, 5, 5]), run_time=2)
self.wait()
self.play(light.animate.move_to([-5, -5, 5]), run_time=2)
self.wait()
```
## Best Practices
1. **Store frame reference**: `frame = self.camera.frame` at the start
2. **Use reorient() for 3D**: Cleaner than setting angles individually
3. **fix_in_frame() for UI**: Keep labels and titles readable
4. **Smooth transitions**: Use appropriate run_time for camera movements
5. **rate_func=linear**: For continuous rotations
6. **to_default_state()**: Reset camera when needed
7. **Updaters for following**: Use updaters to track moving objects
## Common Patterns
### Zoom and pan
```python
def zoom_to(self, mobject, scale_factor=1.5):
frame = self.camera.frame
self.play(
frame.animate
.set_width(mobject.get_width() * scale_factor)
.move_to(mobject),
run_time=2
)
```
### 360-degree showcase
```python
def showcase_3d(self, mobject):
frame = self.camera.frame
frame.reorient(20, 70)
self.play(
frame.animate.increment_theta(360 * DEGREES),
run_time=8,
rate_func=linear
)
```
### Picture-in-picture effect
```python
# Small inset camera view
small_frame = self.camera.frame.copy()
small_frame.set_width(4)
small_frame.to_corner(UR, buff=0.5)
small_frame.fix_in_frame()
```
rules/cli.md
# Command Line Interface in ManimGL
ManimGL uses the `manimgl` command for rendering scenes. It offers powerful flags for different workflows.
## Basic Usage
### Running a Scene
```bash
# Basic syntax
manimgl scene_file.py SceneName
# Example
manimgl my_animation.py SquareToCircle
```
### Auto-Select Scene
```bash
# If only one scene in file, it runs automatically
manimgl my_animation.py
# If multiple scenes, presents a menu to choose from
manimgl my_animations.py
```
## Common Flags
### Writing to File
```bash
# Write to file (no preview)
manimgl scene.py MyScene -w
# Write and open the file
manimgl scene.py MyScene -o
# Show final frame only
manimgl scene.py MyScene -s
# Save final frame as image and show
manimgl scene.py MyScene -so
```
### Interactive Mode
```bash
# Skip to line 15 and enter interactive mode
manimgl scene.py MyScene -se 15
# Interactive mode at specific line
manimgl scene.py MyScene --skip_animations --embed 20
```
### Display Options
```bash
# Fullscreen window
manimgl scene.py MyScene -f
# Custom window size
manimgl scene.py MyScene --resolution 1920,1080
# Hide progress bar
manimgl scene.py MyScene --quiet
```
## Quality and Resolution
### Resolution Presets
```bash
# Low quality (for testing)
manimgl scene.py MyScene -l
# Medium quality
manimgl scene.py MyScene -m
# High quality (1080p)
manimgl scene.py MyScene -h
# 4K quality
manimgl scene.py MyScene --uhd
# Custom resolution
manimgl scene.py MyScene --resolution 2560,1440
```
### Frame Rate
```bash
# Set frame rate (default is 60)
manimgl scene.py MyScene --frame_rate 30
# Lower frame rate for faster renders
manimgl scene.py MyScene --frame_rate 15
```
## Advanced Flags
### Skip to Specific Animation
```bash
# Skip to nth animation
manimgl scene.py MyScene -n 5
# Skip animations (instant mode)
manimgl scene.py MyScene --skip_animations
```
### Output Options
```bash
# Specify output file
manimgl scene.py MyScene -o output.mp4
# Save as GIF
manimgl scene.py MyScene --format gif
# Transparent background
manimgl scene.py MyScene --transparent
```
### Configuration
```bash
# Use custom config file
manimgl scene.py MyScene --config_file custom_config.yml
# Set specific config values
manimgl scene.py MyScene --config camera_config.frame_rate=30
```
## Interactive Development
### The -se Flag
The `-se` (skip and embed) flag is ManimGL's killer feature:
```bash
# Drop into interactive shell at line 15
manimgl scene.py MyScene -se 15
```
In the interactive shell:
```python
# Use abbreviated commands (no self.)
play(circle.animate.shift(RIGHT))
add(Square())
remove(circle)
wait(2)
# Copy code to clipboard, then:
checkpoint_paste() # Run with animations
checkpoint_paste(skip=True) # Run instantly
checkpoint_paste(record=True) # Record while running
# Interactive camera control
touch() # Press 'd' + mouse to rotate, 'z' + scroll to zoom
# Exit
exit()
```
## File Organization
### Running from Different Directories
```bash
# From same directory as manimlib/
manimgl project/scene.py MyScene
# With absolute path
manimgl /full/path/to/scene.py MyScene
# With relative path
manimgl ../other_project/scene.py MyScene
```
## Combining Flags
### Common Combinations
```bash
# High quality, write and open
manimgl scene.py MyScene -h -o
# Low quality, fullscreen, for testing
manimgl scene.py MyScene -l -f
# Skip animations, final frame only
manimgl scene.py MyScene -s --skip_animations
# Interactive at line 20, low quality
manimgl scene.py MyScene -l -se 20
# Save as GIF, high quality
manimgl scene.py MyScene -h --format gif -o
```
## Workflow Examples
### Development Workflow
```bash
# 1. Initial testing (low quality, fast)
manimgl scene.py MyScene -l
# 2. Interactive debugging at specific point
manimgl scene.py MyScene -l -se 25
# 3. Check final frame
manimgl scene.py MyScene -s
# 4. Final render (high quality, save and open)
manimgl scene.py MyScene -h -o
```
### Quick Preview Workflow
```bash
# Show final frame immediately
manimgl scene.py MyScene -s
# If it looks good, render full animation
manimgl scene.py MyScene -o
```
### Batch Rendering
```bash
# Render multiple scenes
for scene in Scene1 Scene2 Scene3; do
manimgl scenes.py $scene -h -w
done
```
## Debugging Flags
### Verbose Output
```bash
# Show detailed output
manimgl scene.py MyScene --verbose
# Show all debug info
manimgl scene.py MyScene --debug
```
### Profiling
```bash
# Show performance stats
manimgl scene.py MyScene --profile
# Detailed timing information
manimgl scene.py MyScene --timing
```
## Configuration Override
### Temporary Config Changes
```bash
# Override window size
manimgl scene.py MyScene --config window_config.size=fullscreen
# Override output directory
manimgl scene.py MyScene --config directories.output=/tmp/manim
# Multiple overrides
manimgl scene.py MyScene \
--config camera_config.frame_rate=30 \
--config camera_config.pixel_width=1280
```
## Help and Information
### Getting Help
```bash
# Show all available flags
manimgl --help
# Show version
manimgl --version
# List scenes in file without running
manimgl scene.py --list_scenes
```
## Full CLI Reference
### All Major Flags
```bash
# Quality/Resolution
-l, --low_quality # 480p, 15fps
-m, --medium_quality # 720p, 30fps
-h, --high_quality # 1080p, 60fps
--uhd # 4K, 60fps
--resolution WIDTHxHEIGHT # Custom resolution
# Output
-w, --write_file # Write to file
-o, --open # Write and open
-s, --show_last_frame # Show final frame
--format FORMAT # Output format (mp4, gif, png)
--transparent # Transparent background
# Playback
-f, --fullscreen # Fullscreen window
-n NUM, --skip_to NUM # Skip to animation number
--skip_animations # Skip all animations
# Interactive
-e, --embed # Drop into IPython shell
--skip_animations --embed # Interactive at end (skip animations)
-se LINE, --skip_and_embed # Interactive at line number
# Configuration
--config_file FILE # Custom config file
--config KEY=VALUE # Override config value
# Debugging
--verbose # Verbose output
--debug # Debug mode
--quiet # Minimize output
--profile # Performance profiling
# Other
--version # Show version
--help # Show help
--list_scenes # List scenes in file
```
## Best Practices
1. **Use -l for development**: Fast iteration with low quality
2. **Use -se for debugging**: Interactive mode at problem points
3. **Use -s for quick checks**: Verify final frame before full render
4. **Use -h -o for final**: High quality output when ready
5. **Combine flags wisely**: `-l -f` for fullscreen testing
6. **Custom configs**: Use different configs for different projects
7. **Script common commands**: Create shell aliases for frequent tasks
## Common Aliases
Add to `.bashrc` or `.zshrc`:
```bash
# Quick preview
alias mgl='manimgl -l'
# Final render
alias mgf='manimgl -h -o'
# Interactive debug
alias mgd='manimgl -l -se'
# Show final frame
alias mgs='manimgl -s'
```
## Troubleshooting
### Common Issues
```bash
# Scene not found
manimgl scene.py # Lists all scenes if you don't specify
# Can't find manimlib
# Ensure you're in the directory with manimlib/ or use full paths
# Window not showing
# Check window_config in custom_config.yml
# Poor performance
# Use -l flag, reduce frame_rate, or lower resolution
```
## Example Commands
```bash
# Simple preview
manimgl examples/basic_animations.py SquareToCircle
# High quality render
manimgl examples/basic_animations.py SquareToCircle -h -o
# Interactive debugging at line 30
manimgl examples/basic_animations.py SquareToCircle -se 30
# Save as GIF
manimgl examples/basic_animations.py SquareToCircle --format gif -o
# Custom resolution
manimgl examples/basic_animations.py SquareToCircle --resolution 2560,1440
# Skip to 5th animation and show
manimgl examples/basic_animations.py SquareToCircle -n 5
# Fullscreen, low quality for testing
manimgl examples/basic_animations.py SquareToCircle -l -f
```
rules/colors.md
# Colors in ManimGL
ManimGL provides extensive color support with built-in color constants, gradients, and color manipulation utilities.
## Color Constants
### Basic Colors
```python
# Primary colors
RED, GREEN, BLUE
YELLOW, CYAN, MAGENTA
# Grayscale
WHITE, GREY, GRAY, BLACK
# Common colors
ORANGE, PURPLE, PINK, BROWN
MAROON, TEAL, GOLD
```
### Color Variations
ManimGL provides color gradients with letter suffixes:
```python
# Blue variations (darkest to lightest)
BLUE_E # Darkest blue
BLUE_D
BLUE_C
BLUE_B
BLUE_A # Lightest blue
# Similarly for other colors:
RED_E, RED_D, RED_C, RED_B, RED_A
GREEN_E, GREEN_D, GREEN_C, GREEN_B, GREEN_A
YELLOW_E, YELLOW_D, YELLOW_C, YELLOW_B, YELLOW_A
```
### Usage Example
```python
from manimlib import *
class ColorExample(Scene):
def construct(self):
# Create circles with different color variations
circles = VGroup(*[
Circle(radius=0.5, color=color)
for color in [BLUE_E, BLUE_D, BLUE_C, BLUE_B, BLUE_A]
])
circles.arrange(RIGHT, buff=0.5)
self.add(circles)
```
## Setting Colors
### Basic Color Setting
```python
# At creation
circle = Circle(color=BLUE)
# After creation
square = Square()
square.set_color(RED)
# Multiple mobjects
group = VGroup(Circle(), Square(), Triangle())
group.set_color(GREEN)
```
### Animated Color Changes
```python
class ColorAnimation(Scene):
def construct(self):
circle = Circle(color=BLUE)
self.add(circle)
# Animate color change
self.play(circle.animate.set_color(RED))
self.wait()
# Another change
self.play(circle.animate.set_color(YELLOW))
self.wait()
```
## Gradients
### set_submobject_colors_by_gradient
```python
# Apply gradient to submobjects
text = Text("Gradient Text")
text.set_submobject_colors_by_gradient(BLUE, GREEN, YELLOW)
# Multiple objects with gradient
squares = VGroup(*[Square() for _ in range(10)])
squares.arrange(RIGHT)
squares.set_submobject_colors_by_gradient(RED, BLUE)
```
### Color Interpolation
```python
from manimlib.utils.color import interpolate_color
# Create color between two colors
mid_color = interpolate_color(RED, BLUE, 0.5) # Purple
# Create gradient programmatically
n_colors = 10
gradient = [
interpolate_color(RED, BLUE, alpha)
for alpha in np.linspace(0, 1, n_colors)
]
```
## Advanced Color Techniques
### set_color_by_code (GLSL)
ManimGL allows dynamic coloring using GLSL code:
```python
# Color based on position
square = Square()
square.set_color_by_code("""
color.r = x;
color.g = y;
color.b = 1.0;
""")
```
### set_color_by_xyz_func
```python
# Color based on 3D position
surface = Sphere(radius=2)
surface.set_color_by_xyz_func(
glsl_snippet="float value = sqrt(x*x + y*y + z*z); return value;",
min_value=0,
max_value=5,
colormap='viridis'
)
```
## Color for Text and LaTeX
### Coloring Text Parts
```python
# Color specific words
text = Text(
"Red, Green, and Blue",
t2c={"Red": RED, "Green": GREEN, "Blue": BLUE}
)
```
### Coloring LaTeX
```python
# Color math symbols
equation = Tex(
R"E = mc^2",
t2c={"E": BLUE, "m": GREEN, "c": YELLOW}
)
# Color by tex substring
formula = Tex(R"\int_0^1 x^2 dx")
formula.set_color_by_tex("x", BLUE)
formula.set_color_by_tex(R"\int", RED)
```
## RGB and Hex Colors
### Using RGB Values
```python
from manimlib.utils.color import rgb_to_color
# RGB values (0-1 range)
custom_color = rgb_to_color([0.5, 0.3, 0.8])
circle = Circle(color=custom_color)
# RGB from 0-255 range (convert to 0-1)
custom_color = rgb_to_color([128/255, 77/255, 204/255])
```
### Using Hex Colors
```python
from manimlib.utils.color import hex_to_rgb, rgb_to_color
# Hex color
hex_color = "#FF5733"
rgb = hex_to_rgb(hex_color)
color = rgb_to_color(rgb)
circle = Circle(color=color)
```
## Opacity and Transparency
### Setting Opacity
```python
# Transparent circle
circle = Circle(color=BLUE, fill_opacity=0.5)
# Change opacity
circle.set_opacity(0.7)
# Fill vs Stroke opacity
square = Square()
square.set_fill(BLUE, opacity=0.5)
square.set_stroke(WHITE, width=4, opacity=1.0)
```
## Color Utilities
### Getting Color from Mobject
```python
circle = Circle(color=BLUE)
# Get color
color = circle.get_color()
# Get fill color
fill_color = circle.get_fill_color()
# Get stroke color
stroke_color = circle.get_stroke_color()
```
### Color Matching
```python
# Match color from another mobject
circle = Circle(color=BLUE)
square = Square()
square.match_color(circle)
# Match fill color
square.match_fill(circle)
# Match stroke
square.match_stroke(circle)
```
## Color Schemes
### Creating Consistent Color Palettes
```python
# Define color scheme
COLOR_SCHEME = {
"background": "#1e1e1e",
"primary": BLUE_C,
"secondary": GREEN_C,
"accent": YELLOW_C,
"text": WHITE,
"highlight": RED_C
}
# Use in scene
class StyledScene(Scene):
def construct(self):
title = Text("Title", color=COLOR_SCHEME["primary"])
subtitle = Text("Subtitle", color=COLOR_SCHEME["secondary"])
highlight = Circle(color=COLOR_SCHEME["accent"])
self.add(title, subtitle, highlight)
```
### 3Blue1Brown Color Scheme
```python
# Grant's typical colors
BLUE_3B1B = BLUE_C
GREEN_3B1B = GREEN_C
YELLOW_3B1B = YELLOW_C
RED_3B1B = RED_C
# Background
BACKGROUND_COLOR = "#0a0a0a"
```
## Gloss and Visual Properties
### Adding Gloss (for 3D)
```python
# Add glossy appearance
sphere = Sphere(radius=2, color=BLUE)
sphere.set_gloss(0.8) # 0 to 1
# Get gloss value
gloss = sphere.get_gloss()
```
### Shadow
```python
# Add shadow (for 3D)
cube = Cube(color=RED)
cube.set_shadow(0.5) # 0 to 1
# Get shadow value
shadow = cube.get_shadow()
```
## Full Color Example
```python
class ComprehensiveColorExample(Scene):
def construct(self):
# Color variations showcase
blue_shades = VGroup(*[
Circle(radius=0.4, color=color)
for color in [BLUE_E, BLUE_D, BLUE_C, BLUE_B, BLUE_A]
])
blue_shades.arrange(RIGHT, buff=0.3)
blue_shades.to_edge(UP, buff=1)
# Gradient
squares = VGroup(*[Square(side_length=0.6) for _ in range(8)])
squares.arrange(RIGHT, buff=0.2)
squares.set_submobject_colors_by_gradient(RED, YELLOW, GREEN, BLUE)
# Custom RGB color
custom_circle = Circle(
radius=1,
color=rgb_to_color([0.8, 0.2, 0.6]),
fill_opacity=0.7
)
custom_circle.shift(DOWN * 2)
# Colored text
text = Text(
"Colorful Text",
font_size=48,
t2c={"Colorful": BLUE, "Text": GREEN}
)
text.next_to(custom_circle, UP, buff=0.5)
# Add everything
self.play(
FadeIn(blue_shades, lag_ratio=0.1),
FadeIn(squares, lag_ratio=0.1),
ShowCreation(custom_circle),
Write(text)
)
self.wait()
# Animate color changes
self.play(
squares.animate.set_submobject_colors_by_gradient(PURPLE, ORANGE),
custom_circle.animate.set_color(TEAL)
)
self.wait()
```
## Best Practices
1. **Use named constants**: Prefer `BLUE` over RGB values for readability
2. **Consistent color schemes**: Define color palettes for coherent visuals
3. **Gradients for emphasis**: Use gradients to show progression or relationships
4. **Opacity for layering**: Use transparency to show overlapping elements
5. **Color accessibility**: Ensure sufficient contrast for visibility
6. **t2c for LaTeX**: Color math expressions to highlight important parts
7. **Don't overdo it**: Too many colors can be distracting
## Common Patterns
### Rainbow gradient
```python
def rainbow_gradient(mobjects):
colors = [RED, ORANGE, YELLOW, GREEN, BLUE, PURPLE]
VGroup(*mobjects).set_submobject_colors_by_gradient(*colors)
```
### Fade to color animation
```python
self.play(
circle.animate.set_color(RED),
run_time=2
)
```
### Color cycling
```python
colors = [RED, ORANGE, YELLOW, GREEN, BLUE, PURPLE]
for color in colors:
self.play(circle.animate.set_color(color), run_time=0.5)
self.wait(0.2)
```
rules/config.md
# Configuration in ManimGL
ManimGL uses `custom_config.yml` files for configuration. These files control directories, camera settings, window properties, and more.
## Configuration File Location
### Default Locations
ManimGL looks for `custom_config.yml` in this order:
1. Current directory
2. Parent directories (recursively up to project root)
3. ManimGL installation directory
```
my_project/
├── custom_config.yml # Project-specific config
├── scenes/
│ ├── custom_config.yml # Scenes-specific config (overrides project config)
│ └── scene.py
└── manimlib/ # ManimGL installation
```
### Multiple Configs
```bash
# Use specific config file
manimgl scene.py MyScene --config_file /path/to/config.yml
# Project structure with multiple configs
project/
├── custom_config.yml # Default for project
├── experiments/
│ ├── custom_config.yml # Overrides for experiments
│ └── test_scene.py
└── final/
├── custom_config.yml # High quality settings
└── final_scene.py
```
## Basic Configuration
### Minimal custom_config.yml
```yaml
# Directories
directories:
output: "./media/videos"
raster_images: "./media/images"
vector_images: "./media/svg"
sounds: "./media/sounds"
data: "./media/data"
# Window configuration
window_config:
size: "default" # or "fullscreen"
# Camera settings
camera_config:
pixel_height: 1080
pixel_width: 1920
frame_rate: 60
```
## Detailed Configuration Options
### Directory Configuration
```yaml
directories:
# Where rendered videos are saved
output: "/path/to/output/videos"
# Where temporary files go
temporary_storage: "/tmp/manim"
# Image resources
raster_images: "./assets/images"
vector_images: "./assets/svg"
# Audio resources
sounds: "./assets/audio"
# Data files
data: "./assets/data"
# LaTeX templates
tex_templates: "./assets/tex_templates"
# Font directory
fonts: "./assets/fonts"
```
### Camera Configuration
```yaml
camera_config:
# Resolution
pixel_width: 1920
pixel_height: 1080
# Frame rate
frame_rate: 60
# Background color
background_color: "#000000"
# Frame settings
frame_height: 8.0
frame_width: 14.222222222222221 # 16:9 aspect ratio
# Quality presets
# These override pixel_width, pixel_height, frame_rate
quality:
low:
pixel_width: 854
pixel_height: 480
frame_rate: 15
medium:
pixel_width: 1280
pixel_height: 720
frame_rate: 30
high:
pixel_width: 1920
pixel_height: 1080
frame_rate: 60
ultra_high:
pixel_width: 3840
pixel_height: 2160
frame_rate: 60
```
### Window Configuration
```yaml
window_config:
# Window size: "default", "fullscreen", or [width, height]
size: "default"
# size: "fullscreen"
# size: [1280, 720]
# Window position on screen
position: "UR" # Upper right
# Options: UL, UR, DL, DR, TOP, BOTTOM, LEFT, RIGHT, CENTER
# Monitor to display on (for multi-monitor setups)
monitor: 0
# Window title
window_title: "ManimGL Preview"
# Show file name in title
show_file_name_in_title: true
```
### Style Configuration
```yaml
style:
# Default color constants
background_color: "#000000"
# Font settings
font: "Consolas"
tex_font: "Latin Modern Math"
# Default stroke width
stroke_width: 4
# Default animation run time
default_animation_run_time: 1.0
```
### Universal Import Configuration
```yaml
# Auto-import common modules
universal_import_line: |
from manimlib import *
import numpy as np
import itertools as it
```
## Quality Presets
### Command Line Override
```bash
# Use low quality preset
manimgl scene.py MyScene -l
# Use medium quality
manimgl scene.py MyScene -m
# Use high quality
manimgl scene.py MyScene -h
# Use 4K quality
manimgl scene.py MyScene --uhd
```
### Custom Quality Preset
```yaml
camera_config:
quality:
custom:
pixel_width: 2560
pixel_height: 1440
frame_rate: 120
```
## LaTeX Configuration
### TeX Configuration
```yaml
tex_config:
# TeX compiler
tex_compiler: "latex" # or "xelatex", "lualatex"
# TeX template
tex_template: "tex_template.tex"
# Additional packages
tex_packages:
- "amsmath"
- "amssymb"
- "mathtools"
# Text to LaTeX map
text_to_replace: {
# Replacements for common symbols
"pi": "\\pi",
"alpha": "\\alpha"
}
```
## Project-Specific Configuration
### Development Config (fast iteration)
```yaml
# dev_config.yml
directories:
output: "./output/dev"
camera_config:
pixel_height: 480
pixel_width: 854
frame_rate: 15
window_config:
size: [1280, 720]
position: "UR"
```
Usage:
```bash
manimgl scene.py MyScene --config_file dev_config.yml
```
### Production Config (high quality)
```yaml
# prod_config.yml
directories:
output: "./output/final"
camera_config:
pixel_height: 2160
pixel_width: 3840
frame_rate: 60
style:
default_animation_run_time: 1.5
```
## Runtime Configuration Override
### Command Line Override
```bash
# Override single value
manimgl scene.py MyScene --config camera_config.frame_rate=30
# Override multiple values
manimgl scene.py MyScene \
--config camera_config.frame_rate=30 \
--config camera_config.pixel_width=1280 \
--config camera_config.pixel_height=720
# Override output directory
manimgl scene.py MyScene --config directories.output=/tmp/manim_output
```
## Complete Example Configuration
### Full custom_config.yml
```yaml
# Directory Configuration
directories:
output: "./media/videos"
temporary_storage: "/tmp/manim"
raster_images: "./assets/images"
vector_images: "./assets/svg"
sounds: "./assets/audio"
data: "./assets/data"
tex_templates: "./assets/tex"
fonts: "./assets/fonts"
# Camera Configuration
camera_config:
pixel_width: 1920
pixel_height: 1080
frame_rate: 60
background_color: "#0a0a0a"
frame_height: 8.0
frame_width: 14.222222222222221
# Window Configuration
window_config:
size: "default"
position: "UR"
monitor: 0
window_title: "ManimGL Preview"
show_file_name_in_title: true
# Style Configuration
style:
background_color: "#0a0a0a"
font: "Consolas"
tex_font: "Latin Modern Math"
stroke_width: 4
default_animation_run_time: 1.0
# TeX Configuration
tex_config:
tex_compiler: "latex"
tex_template: "tex_template.tex"
tex_packages:
- "amsmath"
- "amssymb"
- "mathtools"
- "physics"
# Universal Imports
universal_import_line: |
from manimlib import *
import numpy as np
import itertools as it
import random
# Logging
log_level: "INFO" # DEBUG, INFO, WARNING, ERROR
```
## Best Practices
1. **Separate dev and prod configs**: Use different configs for development and final renders
2. **Project-level configs**: Keep `custom_config.yml` in project root
3. **Override for testing**: Use `--config` flag for temporary changes
4. **Version control**: Commit `custom_config.yml` to git
5. **Document custom settings**: Add comments to explain non-standard values
6. **Consistent paths**: Use relative paths for portability
7. **Quality presets**: Use built-in quality flags (-l, -m, -h) instead of manual resolution changes
## Common Configurations
### For YouTube Videos (1080p)
```yaml
camera_config:
pixel_width: 1920
pixel_height: 1080
frame_rate: 60
background_color: "#000000"
```
### For Quick Testing
```yaml
camera_config:
pixel_width: 854
pixel_height: 480
frame_rate: 15
```
### For 4K Production
```yaml
camera_config:
pixel_width: 3840
pixel_height: 2160
frame_rate: 60
```
### For Vertical Video (TikTok/Shorts)
```yaml
camera_config:
pixel_width: 1080
pixel_height: 1920
frame_rate: 60
frame_height: 14.222222222222221
frame_width: 8.0
```
## Troubleshooting
### Config Not Loading
```bash
# Check which config is being used
manimgl scene.py MyScene --verbose
# Specify config explicitly
manimgl scene.py MyScene --config_file ./custom_config.yml
```
### Invalid Configuration
- Ensure YAML syntax is correct (indentation, colons, etc.)
- Check for typos in configuration keys
- Verify paths exist and are accessible
- Use quotes around paths with spaces
### Performance Issues
```yaml
# Reduce quality for testing
camera_config:
pixel_width: 854
pixel_height: 480
frame_rate: 15
# Use temporary storage on SSD
directories:
temporary_storage: "/path/to/fast/storage"
```
rules/creation-animations.md
# Creation Animations in ManimGL
Creation animations bring mobjects into existence. ManimGL provides several animation classes for different creation effects.
## ShowCreation
**Note**: ManimGL uses `ShowCreation`, not `Create` (which is used in ManimCE).
### Basic Usage
```python
from manimlib import *
class CreationExample(Scene):
def construct(self):
circle = Circle()
# ShowCreation draws the object
self.play(ShowCreation(circle))
self.wait()
```
### Different Mobjects
```python
# Works with any VMobject
self.play(ShowCreation(Circle()))
self.play(ShowCreation(Square()))
self.play(ShowCreation(Line(LEFT, RIGHT)))
self.play(ShowCreation(Text("Hello")))
```
### Reverse Creation
```python
# Uncreate (reverse of ShowCreation)
circle = Circle()
self.add(circle)
self.play(ShowCreation(circle, reverse=True)) # Uncreates
```
## Write
The `Write` animation is specifically for text and LaTeX.
### Writing Text
```python
# Write text letter by letter
text = Text("Hello World", font_size=60)
self.play(Write(text))
# Write LaTeX
formula = Tex(R"\int_0^1 x^2 dx = \frac{1}{3}")
self.play(Write(formula))
```
### Write Speed
```python
# Control writing speed with run_time
text = Text("Fast", font_size=72)
self.play(Write(text), run_time=0.5)
text2 = Text("Slow", font_size=72)
self.play(Write(text2), run_time=3)
```
## FadeIn
Fade objects into view.
### Basic FadeIn
```python
circle = Circle()
self.play(FadeIn(circle))
```
### FadeIn with Shift
```python
# Fade in while shifting
text = Text("Appearing", font_size=60)
self.play(FadeIn(text, shift=UP))
# From different directions
self.play(FadeIn(circle, shift=DOWN))
self.play(FadeIn(square, shift=LEFT))
self.play(FadeIn(triangle, shift=RIGHT))
```
### FadeIn with Scale
```python
# Fade in while scaling
circle = Circle()
self.play(FadeIn(circle, scale=0.5)) # Starts at half size
# Shrink while fading in
square = Square()
self.play(FadeIn(square, scale=2)) # Starts at double size
```
## DrawBorderThenFill
Draws the border first, then fills the shape.
```python
class DrawBorderExample(Scene):
def construct(self):
square = Square()
square.set_fill(BLUE, opacity=0.7)
square.set_stroke(WHITE, width=4)
self.play(DrawBorderThenFill(square))
self.wait()
```
## GrowFromCenter
Grows object from its center.
```python
circle = Circle()
self.play(GrowFromCenter(circle))
# Control growth speed
square = Square()
self.play(GrowFromCenter(square), run_time=2)
```
## GrowFromEdge
Grows object from a specific edge.
```python
square = Square()
# Grow from different edges
self.play(GrowFromEdge(square, DOWN))
# or: UP, DOWN, LEFT, RIGHT
```
## GrowFromPoint
Grows object from a specific point.
```python
circle = Circle()
point = np.array([2, 2, 0])
self.play(GrowFromPoint(circle, point))
```
## SpinInFromNothing
Spins object into view while growing.
```python
star = Star()
self.play(SpinInFromNothing(star))
```
## AnimationGroup for Multiple Creations
### Simultaneous Creation
```python
class MultipleCreations(Scene):
def construct(self):
shapes = VGroup(
Circle().shift(LEFT * 2),
Square(),
Triangle().shift(RIGHT * 2)
)
# Create all simultaneously
self.play(*[ShowCreation(shape) for shape in shapes])
self.wait()
```
### Sequential Creation
```python
# One after another
for shape in shapes:
self.play(ShowCreation(shape))
self.wait(0.2)
```
## LaggedStart
Creates objects with a staggered delay.
```python
class LaggedCreation(Scene):
def construct(self):
circles = VGroup(*[
Circle(radius=0.5).shift(i * RIGHT)
for i in range(-3, 4)
])
# Staggered creation
self.play(LaggedStart(
*[ShowCreation(circle) for circle in circles],
lag_ratio=0.2 # Delay between each
))
self.wait()
```
## Comparison: Creation Animations
```python
class CreationComparison(Scene):
def construct(self):
methods = [
("ShowCreation", ShowCreation),
("FadeIn", FadeIn),
("GrowFromCenter", GrowFromCenter),
("DrawBorderThenFill", DrawBorderThenFill),
]
for name, AnimClass in methods:
# Create label
label = Text(name, font_size=30)
label.to_edge(UP)
# Create shape
square = Square()
square.set_fill(BLUE, opacity=0.7)
square.set_stroke(WHITE, width=3)
# Show animation
self.play(Write(label))
self.play(AnimClass(square))
self.wait()
self.play(FadeOut(VGroup(label, square)))
```
## Advanced Creation Patterns
### Partial Creation
```python
# Show only part of the creation
line = Line(LEFT * 3, RIGHT * 3)
self.play(
ShowCreation(line),
rate_func=lambda t: smooth(t * 0.5) # Only 50% created
)
```
### Reversed Rate Function
```python
# Create backwards
circle = Circle()
self.play(
ShowCreation(circle),
rate_func=lambda t: 1 - smooth(t) # Reverse
)
```
### Creation with Color Change
```python
class ColoredCreation(Scene):
def construct(self):
line = Line(LEFT * 3, RIGHT * 3)
line.set_color_by_gradient(BLUE, RED)
self.play(ShowCreation(line), run_time=2)
self.wait()
```
## Writing Mathematical Content
### Writing Equations
```python
class WriteEquation(Scene):
def construct(self):
equation = Tex(R"E = mc^2")
equation.scale(2)
self.play(Write(equation))
self.wait()
# Color parts
equation.set_color_by_tex("E", BLUE)
equation.set_color_by_tex("m", GREEN)
equation.set_color_by_tex("c", YELLOW)
self.wait()
```
### Writing Multi-line Content
```python
class MultiLineWrite(Scene):
def construct(self):
lines = VGroup(
Tex(R"a^2 + b^2 = c^2"),
Tex(R"e^{i\pi} + 1 = 0"),
Tex(R"\int_0^\infty e^{-x^2} dx = \frac{\sqrt{\pi}}{2}")
)
lines.arrange(DOWN, buff=0.5)
# Write line by line
for line in lines:
self.play(Write(line))
self.wait(0.5)
```
## Best Practices
1. **ShowCreation for shapes**: Use for geometric objects and paths
2. **Write for text**: Use for Text and Tex objects
3. **FadeIn for groups**: Good for bringing in multiple objects
4. **LaggedStart for sequences**: Creates visual rhythm
5. **Consistent timing**: Keep run_time similar for related objects
6. **Match animation to content**: Use appropriate animation for the context
## Common Patterns
### Create and highlight
```python
shape = Circle()
self.play(ShowCreation(shape))
self.play(shape.animate.set_color(YELLOW))
self.play(shape.animate.scale(1.5))
```
### Sequential text appearance
```python
title = Text("Title", font_size=72)
subtitle = Text("Subtitle", font_size=48)
self.play(Write(title))
self.wait(0.3)
self.play(FadeIn(subtitle, shift=UP))
```
### Grid creation
```python
grid = VGroup(*[
Square(side_length=0.5).shift([i, j, 0])
for i in range(-3, 4)
for j in range(-2, 3)
])
self.play(LaggedStart(
*[ShowCreation(square) for square in grid],
lag_ratio=0.01
))
```
## Full Example
```python
class ComprehensiveCreation(Scene):
def construct(self):
# Title
title = Text("Creation Animations", font_size=60)
title.to_edge(UP)
self.play(Write(title))
self.wait()
# Create shapes with different animations
circle = Circle(radius=1, color=BLUE)
circle.shift(LEFT * 3)
square = Square(side_length=2, color=GREEN)
square.set_fill(GREEN, opacity=0.5)
triangle = Triangle(color=YELLOW)
triangle.shift(RIGHT * 3)
# Staggered creation
self.play(
ShowCreation(circle),
FadeIn(square, scale=0.5),
GrowFromCenter(triangle),
run_time=2
)
self.wait()
# Add formula
formula = Tex(R"\sum_{n=1}^{\infty} \frac{1}{n^2} = \frac{\pi^2}{6}")
formula.next_to(title, DOWN, buff=1)
self.play(Write(formula))
self.wait(2)
# Clear scene
self.play(FadeOut(VGroup(title, circle, square, triangle, formula)))
```
rules/embedding.md
# Interactive Embedding in ManimGL
ManimGL's `self.embed()` feature drops you into an interactive IPython shell during scene execution, making debugging and experimentation incredibly powerful.
## Basic Usage
### Adding embed() to Your Scene
```python
from manimlib import *
class MyScene(Scene):
def construct(self):
circle = Circle()
self.play(ShowCreation(circle))
# Drop into interactive shell here
self.embed()
# Code continues after you exit the shell
self.play(circle.animate.shift(RIGHT))
self.wait()
```
### Running with Embed
```bash
# Run scene - will pause at embed() point
manimgl scene.py MyScene
```
## Interactive Commands
### Available in Shell
When `self.embed()` opens the IPython shell, you have access to:
```python
# Scene methods (abbreviated - no 'self.' needed)
play(animation) # Play animation
add(mobject) # Add mobject to scene
remove(mobject) # Remove mobject
wait(duration) # Wait for duration
clear() # Clear scene
# Camera/frame control
frame # Access camera frame
play(frame.animate.shift(RIGHT))
# All local variables from construct()
circle, square, text, etc. # Your mobjects
# Interactive camera
touch() # Enter touch mode (press 'q' to exit)
# Press 'd' + mouse to rotate
# Press 'z' + scroll to zoom
# Press 'r' to reset
# Exit shell and continue
exit() # Continue scene execution
```
## Practical Examples
### Debugging Animation
```python
class DebugScene(Scene):
def construct(self):
circle = Circle()
square = Square()
self.add(circle, square)
# Problem with this animation?
self.play(circle.animate.move_to(square))
# Debug it interactively
self.embed()
# In the shell:
# >>> play(circle.animate.set_color(RED))
# >>> circle.get_center()
# >>> square.get_center()
```
### Experimenting with Positioning
```python
class PositioningExperiment(Scene):
def construct(self):
shapes = VGroup(*[
Circle(radius=0.5) for _ in range(5)
])
# Try different arrangements interactively
self.add(shapes)
self.embed()
# In the shell, try:
# >>> play(shapes.animate.arrange(RIGHT, buff=1))
# >>> play(shapes.animate.arrange(DOWN, buff=0.5))
# >>> play(shapes.animate.arrange_in_grid(rows=2))
```
### Color and Style Exploration
```python
class StyleExploration(Scene):
def construct(self):
text = Text("Experiment", font_size=72)
self.add(text)
self.embed()
# In the shell:
# >>> play(text.animate.set_color(BLUE))
# >>> text.set_backstroke(BLACK, width=10)
# >>> play(text.animate.scale(2))
```
## Advanced embed() Usage
### Multiple Embed Points
```python
class MultipleEmbeds(Scene):
def construct(self):
# First checkpoint
circle = Circle()
self.play(ShowCreation(circle))
self.embed() # First pause
# Second checkpoint
square = Square()
self.play(ShowCreation(square))
self.embed() # Second pause
# Third checkpoint
self.play(FadeOut(VGroup(circle, square)))
self.embed() # Third pause
```
### Conditional Embedding
```python
class ConditionalEmbed(Scene):
def construct(self):
DEBUG = True
circle = Circle()
self.play(ShowCreation(circle))
if DEBUG:
self.embed() # Only embed in debug mode
self.play(circle.animate.shift(RIGHT))
```
## Using with -se Flag
### Skip and Embed
The `-se` flag skips to a specific line and embeds:
```python
class LargeScene(Scene):
def construct(self):
# Line 5
circle = Circle()
self.play(ShowCreation(circle))
# Line 10
square = Square()
self.play(ShowCreation(square))
# Line 15
text = Text("Hello")
self.play(Write(text))
# Line 20
self.play(FadeOut(VGroup(circle, square, text)))
```
```bash
# Skip directly to line 15 and embed
manimgl scene.py LargeScene -se 15
```
## checkpoint_paste()
### Interactive Code Execution
`checkpoint_paste()` runs code from your clipboard:
```python
class CheckpointScene(Scene):
def construct(self):
circle = Circle()
self.add(circle)
self.embed()
```
```bash
# Run the scene
manimgl scene.py CheckpointScene
```
In the shell:
```python
# Copy this code to clipboard first:
"""
square = Square()
play(ShowCreation(square))
play(square.animate.next_to(circle, RIGHT))
"""
# Then in the shell:
>>> checkpoint_paste() # Runs with animations
>>> checkpoint_paste(skip=True) # Runs instantly
>>> checkpoint_paste(record=True) # Records while running
```
## Saving and Restoring State
### save_state() and restore()
```python
class StateManagement(Scene):
def construct(self):
circle = Circle()
square = Square()
self.add(circle, square)
# Save current state
self.save_state()
# Make changes
self.play(circle.animate.shift(RIGHT * 3))
self.play(square.animate.shift(LEFT * 3))
self.embed()
# In the shell:
# >>> restore() # Revert to saved state
```
## Interactive 3D Exploration
### touch() Mode
```python
class Interactive3D(Scene):
def construct(self):
frame = self.camera.frame
frame.reorient(20, 70)
# Create 3D object
sphere = Sphere(radius=2, color=BLUE)
self.add(sphere)
self.embed()
# In the shell:
# >>> touch()
# Now you can:
# - Press 'd' and move mouse to rotate
# - Press 'z' and scroll to zoom
# - Press 'r' to reset camera
# - Press 'q' to exit touch mode
```
## Debugging Patterns
### Inspect Mobject Properties
```python
class InspectProperties(Scene):
def construct(self):
circle = Circle(radius=2, color=BLUE)
circle.shift(RIGHT * 3)
self.add(circle)
self.embed()
# In the shell:
# >>> circle.get_center()
# >>> circle.get_color()
# >>> circle.get_width()
# >>> circle.get_height()
# >>> circle.get_all_points()
```
### Test Animation Timing
```python
class TimingTest(Scene):
def construct(self):
circle = Circle()
self.add(circle)
self.embed()
# In the shell, test different timings:
# >>> play(circle.animate.shift(RIGHT), run_time=0.5)
# >>> play(circle.animate.shift(LEFT), run_time=2)
# >>> play(circle.animate.shift(UP), run_time=1, rate_func=smooth)
```
### Build Complex Scenes Iteratively
```python
class IterativeBuilding(Scene):
def construct(self):
self.embed()
# Build entire scene in the shell:
# >>> title = Text("My Animation")
# >>> title.to_edge(UP)
# >>> add(title)
#
# >>> circles = VGroup(*[Circle(radius=0.5) for _ in range(5)])
# >>> circles.arrange(RIGHT, buff=0.5)
# >>> play(LaggedStart(*[ShowCreation(c) for c in circles], lag_ratio=0.2))
#
# >>> formula = Tex(R"E = mc^2")
# >>> formula.next_to(circles, DOWN, buff=1)
# >>> play(Write(formula))
```
## Best Practices
1. **Use for debugging**: Add `self.embed()` when animations don't work as expected
2. **Experiment freely**: Try different approaches in the shell before adding to code
3. **save_state() before experimenting**: Easy to revert if something goes wrong
4. **Use -se for large scenes**: Jump to problem area instead of watching entire animation
5. **checkpoint_paste() for iteration**: Quickly test code snippets
6. **touch() for 3D**: Essential for finding the right camera angle
7. **Remove embed() for final render**: Don't forget to remove debugging embeds
## Common Patterns
### Quick experiment pattern
```python
# Add at problem point
self.embed()
# In shell, test fix
play(mobject.animate.scale(2)) # Test different values
# If it works, add to code
# exit()
```
### Interactive development pattern
```python
# Start with minimal setup
class Scene(Scene):
def construct(self):
self.embed()
# Build everything in the shell
# Copy successful commands back to code
```
### 3D camera setup pattern
```python
# Get to 3D scene
frame.reorient(20, 70)
add(sphere)
self.embed()
# Find perfect angle
touch() # Rotate with mouse
# Press 'q' when done
# Check frame.get_theta(), frame.get_phi()
# Add those values to code
```
## Troubleshooting
### embed() Not Working
- Ensure you're running with `manimgl` command
- Check that IPython is installed
- Verify no syntax errors before embed() point
### Can't Access Variables
- Variables must be defined before `self.embed()`
- Use `locals()` or `globals()` to inspect available variables
### Shell Exits Immediately
- Don't call `exit()` unless you want to continue
- Press Ctrl+D to exit and continue
- Use `quit()` or `exit()` to close shell
## Example: Full Interactive Development
```python
class InteractiveDevelopment(Scene):
def construct(self):
# Start with embed
self.embed()
# In the shell, build everything:
"""
# Create title
title = Text("Interactive Development", font_size=60)
title.to_edge(UP)
play(Write(title))
# Create content
circle = Circle(radius=1.5, color=BLUE)
circle.set_fill(BLUE, opacity=0.5)
circle.set_stroke(WHITE, width=3)
play(ShowCreation(circle))
# Add label
label = Text("Circle", font_size=36)
label.next_to(circle, DOWN)
play(FadeIn(label, shift=UP))
# Animate
play(
circle.animate.shift(RIGHT * 2),
label.animate.shift(RIGHT * 2)
)
wait(2)
# When happy, copy all this code to your construct method
exit()
"""
```
This makes ManimGL incredibly powerful for rapid prototyping and debugging!
rules/frame.md
# ManimGL Frame (Camera) Control
## CameraFrame
In ManimGL, camera control is done through `self.camera.frame` (CameraFrame is a Mobject):
```python
frame = self.camera.frame
# Set euler angles for 3D orientation
frame.set_euler_angles(
theta=-30 * DEGREES,
phi=70 * DEGREES,
)
```
**Note:** In InteractiveScene, you can also use `self.frame` as a shortcut.
## CameraFrame Methods (from official docs)
The CameraFrame inherits standard Mobject methods plus these specific ones:
- `.to_default_state()` - Reset camera
- `.set_euler_angles(theta, phi, gamma)` - Set all angles
- `.set_theta(theta)` - Horizontal rotation
- `.set_phi(phi)` - Vertical rotation
- `.set_gamma(gamma)` - Roll
- `.increment_theta(dtheta)` - Add to theta
- `.increment_phi(dphi)` - Add to phi
- `.increment_gamma(dgamma)` - Add to gamma
Also inherits: `.shift()`, `.scale()`, `.move_to()`
```python
# Look down at 45 degrees, rotated 30 degrees
self.frame.reorient(45, -30, 0, ORIGIN, 8)
# Animate the reorientation
self.play(
self.frame.animate.reorient(60, -45, 0, (1, 0, 0), 10),
run_time=3
)
```
## Common Camera Operations
### Zoom
```python
# Zoom in (smaller height = closer)
self.play(self.frame.animate.set_height(4))
# Zoom out
self.play(self.frame.animate.set_height(12))
```
### Pan
```python
# Move camera center
self.play(self.frame.animate.move_to(RIGHT * 3))
# Shift camera
self.play(self.frame.animate.shift(UP * 2))
```
### Combined Movement
```python
self.play(
self.frame.animate.reorient(50, -40, 0, (2, 1, 0), 6).set_anim_args(run_time=3)
)
```
## fix_in_frame()
Keep mobjects fixed in screen space during 3D camera movement:
```python
title = Text("My Title")
title.to_edge(UP)
title.fix_in_frame() # Call on the mobject, not the scene!
self.add(title)
# Title stays fixed while camera moves
self.play(self.frame.animate.reorient(60, -45, 0))
```
**Key difference from ManimCE:** In ManimCE you call `self.add_fixed_in_frame_mobjects(title)`. In ManimGL you call `title.fix_in_frame()`.
## set_floor_plane()
Set the floor plane orientation for 3D scenes:
```python
self.set_floor_plane("xz") # y is up, xz is floor
self.set_floor_plane("xy") # z is up, xy is floor (default)
```
## Frame Animation Syntax
```python
# Chain with set_anim_args for run_time
self.play(
self.frame.animate.reorient(45, -30, 0, ORIGIN, 8).set_anim_args(run_time=2)
)
# Multiple frame operations
self.play(
self.frame.animate.shift(RIGHT * 2).set_height(6),
run_time=1.5
)
```
## Background Rectangle
For scenes with 3D camera movement, add a background:
```python
background = FullScreenRectangle()
background.set_fill(BLACK, 1)
background.fix_in_frame()
self.add(background)
```
## Complete 3D Example
```python
class Camera3DDemo(InteractiveScene):
def construct(self):
# Background
bg = FullScreenRectangle()
bg.set_fill(GREY_E, 1)
bg.fix_in_frame()
self.add(bg)
# Title fixed in frame
title = Text("3D Demo")
title.to_edge(UP)
title.fix_in_frame()
self.add(title)
# 3D content
cube = Cube(side_length=2)
cube.set_color(BLUE)
self.add(cube)
# Animate camera
self.play(
self.frame.animate.reorient(60, -45, 0, ORIGIN, 8),
run_time=3
)
# Rotate around
self.play(
self.frame.animate.reorient(60, 45, 0),
run_time=4
)
```
rules/interactive.md
# ManimGL Interactive Development
ManimGL's killer feature is interactive development mode, allowing you to iterate rapidly without re-rendering the entire scene.
## Starting Interactive Mode
Use the `-se` (skip and embed) flag with a line number:
```bash
# Enter interactive mode at line 20
manimgl scene.py MyScene -se 20
# Enter at the beginning
manimgl scene.py MyScene -se 1
```
The scene runs up to that line, then drops into an IPython shell.
## checkpoint_paste()
The core workflow function. Copy code to your clipboard, then:
```python
# Run code from clipboard with full animations
checkpoint_paste()
# Run instantly without animations (for quick iteration)
checkpoint_paste(skip=True)
# Record animations while running
checkpoint_paste(record=True)
```
### Typical Workflow
1. Write your scene with placeholder line
2. Run with `-se` at that line
3. Copy animation code to clipboard
4. Call `checkpoint_paste()` to test
5. Iterate until satisfied
6. Move code into the actual file
## self.embed()
Drop into IPython shell programmatically:
```python
class MyScene(InteractiveScene):
def construct(self):
circle = Circle()
self.play(ShowCreation(circle))
self.embed() # Pause here, enter shell
# Code below runs after you exit the shell
self.play(FadeOut(circle))
```
In the shell, you have full access to:
- `self` - the scene
- All mobjects in scope
- All ManimGL functions
## Interactive Shell Commands
Once in the shell:
```python
# Inspect current mobjects
self.mobjects
# Add something new
square = Square()
self.play(ShowCreation(square))
# Clear and try again
self.clear()
# Exit shell and continue scene
exit()
# or Ctrl+D
```
## Quick Iteration Pattern
```python
class DevelopScene(InteractiveScene):
def construct(self):
# Setup that doesn't change often
axes = Axes()
self.add(axes)
# Breakpoint for development
self.embed()
# Code you're iterating on goes here
# (Or use checkpoint_paste() in the shell)
```
## Recording Mode
When you want to capture what you're doing interactively:
```python
# Start recording
checkpoint_paste(record=True)
# All animations are now recorded
# When done, video is saved
```
## Useful Shell Variables
```python
# Current frame (camera)
self.frame
# All mobjects
self.mobjects
# Specific mobjects by type
[m for m in self.mobjects if isinstance(m, Circle)]
# Frame center
self.frame.get_center()
```
## Debugging Tips
```python
# Print mobject info
print(circle.get_center())
print(circle.get_height())
print(circle.get_color())
# Highlight a mobject
circle.set_color(YELLOW)
self.wait(0.1)
# Check what's in the scene
print(len(self.mobjects))
```
## Exit and Continue
```python
# After interactive session, continue scene
exit() # or Ctrl+D
# The scene continues from where it left off
```
## Best Practices
1. **Use `-se` during development** - Much faster than re-rendering
2. **Keep setup code before the embed** - Reuse state
3. **Use `checkpoint_paste(skip=True)`** - For quick tests
4. **Use `checkpoint_paste(record=True)`** - When you've got it right
5. **Organize code into functions** - Easier to paste and test
rules/mobjects.md
# ManimGL Mobjects
## Mobject Hierarchy
```
Mobject (base class)
├── VMobject (vectorized - most common)
│ ├── VGroup
│ ├── Circle, Square, Rectangle, Line, Arrow
│ ├── Tex, Text, TexText
│ └── Axes, NumberPlane
├── Group (non-vectorized container)
├── ImageMobject
├── Point
└── 3D objects (Surface, ParametricSurface, etc.)
```
## Creating Mobjects
```python
# Geometric shapes
circle = Circle(radius=1, color=BLUE)
square = Square(side_length=2)
rect = Rectangle(width=3, height=2)
line = Line(LEFT, RIGHT)
arrow = Arrow(ORIGIN, UP)
# Text
text = Text("Hello")
math = Tex(R"\pi r^2")
# Groups
group = VGroup(circle, square)
```
## Positioning
```python
# Absolute position
circle.move_to(ORIGIN)
circle.move_to(RIGHT * 2 + UP)
# Relative to screen edges
circle.to_edge(UP)
circle.to_edge(LEFT, buff=1)
circle.to_corner(UL)
# Relative to other mobjects
square.next_to(circle, RIGHT)
square.next_to(circle, DOWN, buff=0.5)
# Alignment
group.align_to(other, UP)
group.align_to(other, LEFT)
# Shifting
circle.shift(RIGHT * 2)
circle.shift(UP + RIGHT)
```
## Styling
```python
# Fill
circle.set_fill(BLUE, opacity=0.5)
# Stroke (outline)
circle.set_stroke(WHITE, width=2)
circle.set_stroke(color=RED, width=4, opacity=0.8)
# Both
circle.set_style(
fill_color=BLUE,
fill_opacity=0.5,
stroke_color=WHITE,
stroke_width=2
)
# Color (affects both fill and stroke)
circle.set_color(RED)
# Backstroke (outline behind for readability)
text.set_backstroke(BLACK, 5)
```
## VGroup
Container for vectorized mobjects:
```python
# Create group
shapes = VGroup(circle, square, triangle)
# Arrange
shapes.arrange(RIGHT, buff=0.5)
shapes.arrange(DOWN, aligned_edge=LEFT)
shapes.arrange_in_grid(rows=2, cols=3)
# Apply to all
shapes.set_color(BLUE)
shapes.scale(0.5)
shapes.shift(UP)
# Access elements
shapes[0] # First element
shapes[-1] # Last element
shapes[1:3] # Slice
```
## Group vs VGroup
```python
# VGroup - for vectorized mobjects (VMobject subclasses)
vgroup = VGroup(Circle(), Square())
# Group - for any mobjects including images, 3D, etc.
group = Group(ImageMobject("photo.png"), Circle())
```
## Common Methods
| Method | Description |
|--------|-------------|
| `.move_to(point)` | Move center to point |
| `.shift(vector)` | Move by vector |
| `.scale(factor)` | Scale by factor |
| `.rotate(angle)` | Rotate by angle (radians) |
| `.next_to(mob, dir)` | Position next to another |
| `.align_to(mob, dir)` | Align edge with another |
| `.to_edge(dir)` | Move to screen edge |
| `.to_corner(corner)` | Move to screen corner |
| `.get_center()` | Get center point |
| `.get_width()` | Get width |
| `.get_height()` | Get height |
| `.copy()` | Create a copy |
## Generating Targets
For animating to a modified version:
```python
circle.generate_target()
circle.target.shift(RIGHT * 2)
circle.target.scale(2)
circle.target.set_color(RED)
self.play(MoveToTarget(circle))
```
## Saving and Restoring State
```python
circle.save_state()
self.play(circle.animate.shift(RIGHT).scale(2))
# Later...
self.play(Restore(circle))
```
## Updaters
Dynamic behavior:
```python
# Always follow another mobject
label.add_updater(lambda m: m.next_to(dot, UP))
# Time-based
circle.add_updater(lambda m, dt: m.rotate(dt))
# Value-based with ValueTracker
tracker = ValueTracker(0)
circle.add_updater(
lambda m: m.set_fill(opacity=tracker.get_value())
)
self.play(tracker.animate.set_value(1))
```
## Useful Shortcuts
```python
# f_always for common updater patterns
label.f_always.next_to(dot, UP)
# always (function form)
always(label.next_to, dot, UP)
```
rules/scenes.md
# ManimGL Scenes
## Scene Types
ManimGL provides several scene types:
### InteractiveScene (Recommended)
The default for most development. Supports interactive mode with `-se` flag.
```python
from manimlib import *
class MyScene(InteractiveScene):
def construct(self):
circle = Circle()
self.play(ShowCreation(circle))
self.wait()
```
### Scene (Base Class)
Basic scene without interactive features:
```python
class BasicScene(Scene):
def construct(self):
self.play(Write(Text("Hello")))
```
### ThreeDScene
For 3D animations with proper camera setup:
```python
from manimlib import *
class My3DScene(ThreeDScene):
def construct(self):
axes = ThreeDAxes()
self.add(axes)
self.camera.frame.reorient(-45*DEGREES, 75*DEGREES)
```
## The construct Method
All scene logic goes in `construct()`:
```python
class MyScene(InteractiveScene):
def construct(self):
# 1. Create mobjects
circle = Circle(color=BLUE)
square = Square(color=RED)
# 2. Position them
circle.shift(LEFT * 2)
square.shift(RIGHT * 2)
# 3. Animate
self.play(ShowCreation(circle), ShowCreation(square))
# 4. Wait for viewer
self.wait(2)
```
## Adding vs Playing
```python
# Static add (instant, no animation)
self.add(circle)
# Animated add
self.play(ShowCreation(circle))
self.play(FadeIn(square))
```
## Scene Methods
| Method | Description |
|--------|-------------|
| `self.play(*anims)` | Play animations |
| `self.wait(t)` | Wait t seconds |
| `self.add(*mobs)` | Add mobjects instantly |
| `self.remove(*mobs)` | Remove mobjects |
| `self.clear()` | Clear all mobjects |
| `self.embed()` | Drop into IPython shell |
## Interactive Mode
Run with `-se` flag to enter at a specific line:
```bash
manimgl scene.py MyScene -se 15
```
In the shell:
```python
checkpoint_paste() # Run clipboard code with animations
checkpoint_paste(skip=True) # Run instantly
checkpoint_paste(record=True) # Record while running
```
## Class Attributes
Define scene configuration as class attributes:
```python
class MyScene(InteractiveScene):
camera_class = ThreeDCamera # Use 3D camera
random_seed = 42 # For reproducibility
def construct(self):
...
```
rules/styling.md
# Styling in ManimGL
ManimGL provides comprehensive styling options for mobjects including fill, stroke, opacity, and special effects.
## Fill Properties
### Basic Fill
```python
from manimlib import *
# Set fill at creation
circle = Circle(fill_color=BLUE, fill_opacity=0.7)
# Set fill after creation
square = Square()
square.set_fill(RED, opacity=0.5)
```
### Fill Examples
```python
class FillExample(Scene):
def construct(self):
# Solid fill
solid = Circle(radius=1)
solid.set_fill(BLUE, opacity=1.0)
# Transparent fill
transparent = Circle(radius=1)
transparent.set_fill(GREEN, opacity=0.3)
# No fill (just outline)
outline = Circle(radius=1)
outline.set_fill(opacity=0)
outline.set_stroke(YELLOW, width=4)
VGroup(solid, transparent, outline).arrange(RIGHT, buff=1)
self.add(solid, transparent, outline)
```
## Stroke Properties
### Basic Stroke
```python
# Set stroke at creation
line = Line(stroke_color=WHITE, stroke_width=4)
# Set stroke after creation
circle = Circle()
circle.set_stroke(BLUE, width=3, opacity=0.8)
```
### Stroke Width
```python
# Different stroke widths
thin = Circle().set_stroke(width=1)
medium = Circle().set_stroke(width=4)
thick = Circle().set_stroke(width=10)
VGroup(thin, medium, thick).arrange(RIGHT, buff=0.5)
```
### Stroke Behind Fill
```python
# Draw stroke behind fill (useful for borders)
shape = Circle(fill_color=BLUE, fill_opacity=0.8)
shape.set_stroke(WHITE, width=6, opacity=1, background=True)
```
## Backstroke
The `backstroke` feature adds an outline behind text or shapes for better visibility.
```python
# Text with backstroke (black outline)
text = Text("Readable Text", font_size=60)
text.set_backstroke(BLACK, width=5)
# Works great over complex backgrounds
text.set_backstroke(BLACK, width=8, opacity=1.0)
```
### Backstroke Example
```python
class BackstrokeExample(Scene):
def construct(self):
# Create complex background
background = VGroup(*[
Circle(radius=2 * np.random.random(), color=random_color())
for _ in range(20)
])
background.set_opacity(0.3)
self.add(background)
# Text with backstroke stands out
text = Text("Clear and Readable", font_size=72, color=WHITE)
text.set_backstroke(BLACK, width=10)
self.add(text)
```
## Opacity Control
### Fill Opacity
```python
# Control fill transparency
circle = Circle()
circle.set_fill_opacity(0.5)
# Animate opacity
self.play(circle.animate.set_fill_opacity(1.0))
```
### Stroke Opacity
```python
# Control stroke transparency
square = Square()
square.set_stroke_opacity(0.7)
```
### Overall Opacity
```python
# Set both fill and stroke opacity
mobject = Circle()
mobject.set_opacity(0.5) # Affects both fill and stroke
```
## Gloss (3D)
### Adding Gloss to 3D Objects
```python
# Make objects glossy/shiny
sphere = Sphere(radius=2, color=BLUE)
sphere.set_gloss(0.8) # 0 (matte) to 1 (very glossy)
# Get gloss value
gloss_value = sphere.get_gloss()
```
## Shadow (3D)
### Adding Shadows
```python
# Add shadow to 3D objects
cube = Cube(color=RED)
cube.set_shadow(0.6) # 0 (no shadow) to 1 (strong shadow)
# Get shadow value
shadow_value = cube.get_shadow()
```
## Combined Styling
### Complete Styling Control
```python
class CompleteStyling(Scene):
def construct(self):
shape = Circle(radius=2)
# Set all properties
shape.set_fill(BLUE, opacity=0.7)
shape.set_stroke(WHITE, width=4, opacity=1.0)
shape.set_backstroke(BLACK, width=6)
self.add(shape)
```
## Style Matching
### Match Style from Another Mobject
```python
# Create styled source
source = Circle()
source.set_fill(BLUE, opacity=0.7)
source.set_stroke(WHITE, width=3)
# Match style
target = Square()
target.match_style(source) # Copies all styling
# Match specific properties
target2 = Triangle()
target2.match_fill(source) # Copy fill only
target2.match_stroke(source) # Copy stroke only
target2.match_color(source) # Copy color only
```
## Gradients and Color Transitions
### Gradient Fills
```python
# Gradient across submobjects
text = Text("Gradient")
text.set_submobject_colors_by_gradient(BLUE, GREEN, YELLOW)
# For shapes with submobjects
squares = VGroup(*[Square() for _ in range(10)])
squares.arrange(RIGHT)
squares.set_submobject_colors_by_gradient(RED, PURPLE)
```
## Visual Effects
### Glow Effect
```python
# Create glow effect with multiple strokes
def add_glow(mobject, color=YELLOW, radius=0.5):
glow_layers = VGroup(*[
mobject.copy().set_stroke(
color,
width=width,
opacity=0.3 / (i + 1)
)
for i, width in enumerate(range(2, 20, 2))
])
return VGroup(glow_layers, mobject)
# Usage
circle = Circle(color=BLUE)
glowing_circle = add_glow(circle)
```
### Neon Effect
```python
def neon_style(mobject, color=BLUE):
mobject.set_fill(color, opacity=0.2)
mobject.set_stroke(color, width=3)
mobject.set_backstroke(color, width=10, opacity=0.5)
return mobject
# Usage
neon_text = neon_style(Text("NEON", font_size=90), BLUE)
```
## Style Presets
### Creating Reusable Styles
```python
# Define style functions
def outline_style(mobject):
mobject.set_fill(opacity=0)
mobject.set_stroke(WHITE, width=3)
return mobject
def solid_style(mobject, color=BLUE):
mobject.set_fill(color, opacity=1.0)
mobject.set_stroke(color, width=0)
return mobject
def glass_style(mobject, color=BLUE):
mobject.set_fill(color, opacity=0.3)
mobject.set_stroke(WHITE, width=2, opacity=0.8)
mobject.set_gloss(0.9)
return mobject
# Usage
circle1 = outline_style(Circle())
circle2 = solid_style(Circle(), RED)
circle3 = glass_style(Circle(), GREEN)
```
## Animating Styles
### Style Transitions
```python
class StyleAnimation(Scene):
def construct(self):
square = Square()
square.set_fill(BLUE, opacity=0)
square.set_stroke(WHITE, width=1)
self.add(square)
self.wait()
# Animate style changes
self.play(
square.animate.set_fill(BLUE, opacity=0.7),
square.animate.set_stroke(WHITE, width=5)
)
self.wait()
# Change colors
self.play(
square.animate.set_fill(RED, opacity=0.9),
square.animate.set_stroke(YELLOW, width=3)
)
self.wait()
```
## Full Styling Example
```python
class ComprehensiveStyleExample(Scene):
def construct(self):
# Different styling approaches
shapes = VGroup()
# Filled shape
filled = Circle(radius=0.8)
filled.set_fill(BLUE, opacity=0.8)
filled.set_stroke(width=0)
shapes.add(filled)
# Outlined shape
outlined = Circle(radius=0.8)
outlined.set_fill(opacity=0)
outlined.set_stroke(WHITE, width=4)
shapes.add(outlined)
# Transparent with border
transparent = Circle(radius=0.8)
transparent.set_fill(GREEN, opacity=0.3)
transparent.set_stroke(GREEN, width=3)
shapes.add(transparent)
# With backstroke
backstroke = Circle(radius=0.8)
backstroke.set_fill(YELLOW, opacity=0.6)
backstroke.set_stroke(WHITE, width=2)
backstroke.set_backstroke(BLACK, width=5)
shapes.add(backstroke)
# Gradient (multiple submobjects)
gradient_circles = VGroup(*[
Circle(radius=0.15).shift(i * 0.3 * RIGHT)
for i in range(-2, 3)
])
gradient_circles.set_submobject_colors_by_gradient(RED, YELLOW)
shapes.add(gradient_circles)
# Arrange and display
shapes.arrange(RIGHT, buff=1)
self.play(LaggedStart(*[
FadeIn(shape)
for shape in shapes
], lag_ratio=0.2))
self.wait()
# Animate style transitions
self.play(
filled.animate.set_opacity(0.3),
outlined.animate.set_stroke(YELLOW, width=8),
transparent.animate.set_fill(RED, opacity=0.8)
)
self.wait()
```
## Best Practices
1. **Opacity for layering**: Use transparency to show overlapping elements
2. **Backstroke for readability**: Add backstroke to text over complex backgrounds
3. **Consistent stroke width**: Maintain visual hierarchy with consistent widths
4. **Fill vs stroke**: Use fill for areas, stroke for borders
5. **Gloss for realism**: Add gloss to 3D objects for more realistic appearance
6. **Match style for consistency**: Use style matching for consistent appearance
7. **Gradients for flow**: Use gradients to show transitions or relationships
## Common Patterns
### Outline style for emphasis
```python
def emphasize(mobject):
return mobject.set_stroke(YELLOW, width=8, opacity=1.0)
```
### Transparent overlay
```python
def overlay(mobject, color=BLUE):
return mobject.set_fill(color, opacity=0.2)
```
### Clean UI style
```python
def ui_style(mobject):
mobject.set_fill(BLUE_C, opacity=0.9)
mobject.set_stroke(WHITE, width=2)
return mobject
```
### Highlighted text
```python
text = Text("Important", font_size=60)
text.set_fill(YELLOW, opacity=1.0)
text.set_backstroke(BLACK, width=8)
text.set_stroke(WHITE, width=1)
```
rules/t2c.md
# Tex to Color Map (t2c) in ManimGL
The `t2c` parameter (tex_to_color_map) is a powerful feature for coloring specific parts of LaTeX expressions.
## Basic t2c Usage
### Coloring Math Symbols
```python
from manimlib import *
class T2CExample(Scene):
def construct(self):
# Color specific variables
equation = Tex(
R"E = mc^2",
t2c={"E": BLUE, "m": GREEN, "c": YELLOW}
)
self.add(equation)
```
### Coloring Substrings
```python
# Color parts of the formula
formula = Tex(
R"\int_0^1 x^2 \, dx = \frac{1}{3}",
t2c={
R"\int": BLUE,
"x": GREEN,
R"\frac{1}{3}": YELLOW
}
)
```
## Advanced t2c Patterns
### Coloring Multiple Instances
```python
# All instances of a variable get colored
series = Tex(
R"\sum_{n=1}^{\infty} \frac{1}{n^2} = \frac{\pi^2}{6}",
t2c={
"n": BLUE, # Colors all 'n's
R"\pi": RED,
R"\sum": GREEN
}
)
```
### Using isolate with t2c
```python
# Isolate specific parts for individual control
equation = Tex(
R"a^2 + b^2 = c^2",
isolate=["a", "b", "c", "^2"],
t2c={
"a": RED,
"b": GREEN,
"c": BLUE,
"^2": YELLOW
}
)
```
## Dynamic Coloring
### set_color_by_tex
```python
# Color after creation
formula = Tex(R"f(x) = x^2 + 2x + 1")
formula.set_color_by_tex("x", BLUE)
formula.set_color_by_tex("f", GREEN)
formula.set_color_by_tex("1", YELLOW)
```
### Gradient Coloring
```python
# Apply gradient to entire formula
formula = Tex(R"\nabla \times \vec{E} = -\frac{\partial \vec{B}}{\partial t}")
formula.set_submobject_colors_by_gradient(BLUE, GREEN, YELLOW)
```
## Text Coloring (Text class)
### t2c for Text Objects
```python
# Color words in Text
text = Text(
"The quick brown fox jumps",
t2c={
"quick": BLUE,
"brown": ORANGE,
"fox": GREEN
}
)
```
### Multiple Styling Options
```python
# Combine t2c, t2f, t2s, t2w
text = Text(
"Different styles and colors",
t2c={"Different": RED, "colors": BLUE},
t2f={"styles": "Consolas"},
t2s={"styles": ITALIC},
t2w={"Different": BOLD}
)
```
## Complex Examples
### Physics Equation with Color Coding
```python
class ColoredPhysicsEquation(Scene):
def construct(self):
# Maxwell's equation with color-coded components
maxwell = Tex(
R"\nabla \times \vec{E} = -\frac{\partial \vec{B}}{\partial t}",
t2c={
R"\nabla": BLUE,
R"\vec{E}": RED,
R"\vec{B}": GREEN,
"t": YELLOW
}
)
self.play(Write(maxwell))
self.wait()
```
### Step-by-Step Derivation
```python
class ColoredDerivation(Scene):
def construct(self):
# Initial equation
eq1 = Tex(
R"(a + b)^2 = a^2 + 2ab + b^2",
t2c={"a": BLUE, "b": GREEN}
)
# Expanded form
eq2 = Tex(
R"(a + b)^2 = (a + b)(a + b)",
t2c={"a": BLUE, "b": GREEN}
)
# Show transformation
self.play(Write(eq1))
self.wait()
self.play(TransformMatchingTex(eq1, eq2))
self.wait()
```
### Highlighting Specific Terms
```python
class HighlightTerms(Scene):
def construct(self):
# Quadratic formula with highlighted discriminant
formula = Tex(
R"x = \frac{-b \pm \sqrt{b^2 - 4ac}}{2a}",
t2c={
"x": WHITE,
"b": BLUE,
"a": GREEN,
"c": YELLOW,
R"b^2 - 4ac": RED # Discriminant in red
}
)
# Add label for discriminant
discriminant_label = Text("Discriminant", color=RED, font_size=30)
discriminant_label.next_to(formula, DOWN)
self.play(Write(formula))
self.play(FadeIn(discriminant_label, shift=UP))
self.wait()
```
## Coloring LaTeX Operators
```python
# Color different operator types
expression = Tex(
R"\int_0^\infty e^{-x^2} dx = \frac{\sqrt{\pi}}{2}",
t2c={
R"\int": BLUE, # Integral
"e": GREEN, # Exponential
R"\pi": RED, # Pi
"x": YELLOW, # Variable
"2": ORANGE # Exponent
}
)
```
## Best Practices
1. **Use raw strings with R**: Always use `R"..."` for LaTeX strings in ManimGL
2. **Test isolate first**: Use `isolate=` to verify what can be colored independently
3. **Consistent color scheme**: Use meaningful colors (e.g., variables in blue, constants in green)
4. **Don't over-color**: Too many colors can be distracting
5. **Color for emphasis**: Highlight the important parts you want viewers to focus on
## Common Patterns
### Creating a color scheme for math
```python
MATH_COLORS = {
"variables": BLUE,
"constants": GREEN,
"operators": YELLOW,
"results": RED
}
equation = Tex(
R"x^2 + y^2 = r^2",
t2c={
"x": MATH_COLORS["variables"],
"y": MATH_COLORS["variables"],
"r": MATH_COLORS["constants"]
}
)
```
### Animating color changes
```python
class AnimateColorChange(Scene):
def construct(self):
formula = Tex(R"f(x) = x^2")
# Start with one color
formula.set_color(BLUE)
self.add(formula)
self.wait()
# Animate to different colors
self.play(formula.animate.set_color_by_tex("x", RED))
self.wait()
```
## Troubleshooting
### If t2c doesn't work:
1. Check if the substring exists exactly in the LaTeX string
2. Use `isolate=` to separate the part you want to color
3. Remember that spacing matters in LaTeX
4. Use raw strings `R"..."` not regular strings
### Example of common issue:
```python
# This might not work if spaces don't match:
wrong = Tex(R"a+b", t2c={"a + b": RED}) # Won't match "a+b"
# This will work:
right = Tex(R"a + b", t2c={"a": RED, "b": BLUE})
```
rules/tex.md
# ManimGL LaTeX (Tex Class)
## Tex vs MathTex
**Important:** ManimGL uses `Tex` class (not `MathTex` like ManimCE).
```python
# ManimGL - use Tex with capital R raw strings
formula = Tex(R"\int_0^1 x^2 \, dx = \frac{1}{3}")
# NOT like ManimCE:
# formula = MathTex(r"\int...") # Wrong for ManimGL
```
## Raw Strings with Capital R
Always use capital `R` for raw strings to avoid escaping issues:
```python
# Good - capital R
Tex(R"\frac{a}{b}")
Tex(R"\vec{v}")
Tex(R"\sum_{n=1}^{\infty}")
# Also works but less readable
Tex("\\frac{a}{b}")
```
## Color Mapping with t2c
Use `t2c` (tex_to_color) parameter to color specific parts:
```python
equation = Tex(
R"E = mc^2",
t2c={"E": BLUE, "m": GREEN, "c": YELLOW}
)
```
For more complex coloring:
```python
formula = Tex(
R"\vec{F} = m\vec{a}",
t2c={
R"\vec{F}": BLUE,
R"\vec{a}": RED,
"m": GREEN,
}
)
```
## set_color_by_tex
Color parts after creation:
```python
formula = Tex(R"\sum_{n=1}^{\infty} \frac{1}{n^2}")
formula.set_color_by_tex("n", BLUE)
formula.set_color_by_tex(R"\infty", YELLOW)
```
## Isolating Substrings
Get parts of a formula for animation:
```python
formula = Tex(R"a^2 + b^2 = c^2")
# Access by index
a_squared = formula[0] # "a^2"
# Or use isolate parameter
formula = Tex(
R"a^2", "+", R"b^2", "=", R"c^2",
)
# Now formula[0] is "a^2", formula[1] is "+", etc.
```
## Text vs Tex
```python
# Regular text
text = Text("Hello World")
# LaTeX math
math = Tex(R"\pi \approx 3.14159")
# Mixed (use TexText for text in math context)
mixed = TexText("The value of ", R"$\pi$", " is important")
```
## TexText
For text that may contain inline math:
```python
sentence = TexText(
"The area is ", R"$\pi r^2$", ".",
t2c={R"$\pi r^2$": YELLOW}
)
```
## Aligned Equations
```python
equations = Tex(R"""
\begin{align*}
f(x) &= x^2 + 2x + 1 \\
&= (x + 1)^2
\end{align*}
""")
```
## Common LaTeX Symbols
```python
# Greek letters
Tex(R"\alpha, \beta, \gamma, \delta, \theta, \phi, \pi")
# Operators
Tex(R"\sum, \prod, \int, \oint, \partial")
# Relations
Tex(R"\leq, \geq, \neq, \approx, \equiv")
# Sets
Tex(R"\in, \subset, \cup, \cap, \emptyset")
# Arrows
Tex(R"\rightarrow, \leftarrow, \Rightarrow, \Leftrightarrow")
# Fractions
Tex(R"\frac{a}{b}, \dfrac{a}{b}")
# Roots
Tex(R"\sqrt{x}, \sqrt[3]{x}")
# Matrices
Tex(R"\begin{pmatrix} a & b \\ c & d \end{pmatrix}")
```
## Font Size
```python
# Use font_size parameter
small = Tex(R"\pi", font_size=24)
large = Tex(R"\pi", font_size=72)
# Or scale after creation
small.scale(1.5)
```
## Backstroke for Readability
When placing text over colored backgrounds:
```python
label = Tex(R"f(x)")
label.set_backstroke(BLACK, 5) # Black outline
```
## Debugging LaTeX
If LaTeX doesn't render:
1. Check for missing packages in your LaTeX installation
2. Try simpler expressions first
3. Check the intermediate `.tex` files in the output directory
4. Use `\text{}` for regular text inside math
rules/text.md
# Text in ManimGL
ManimGL provides powerful text rendering capabilities through the `Text` and `TexText` classes.
## Text Class
The `Text` class renders text using system fonts with extensive styling options.
### Basic Text Creation
```python
from manimlib import *
class TextExample(Scene):
def construct(self):
# Basic text
text = Text("Hello ManimGL")
self.add(text)
```
### Font Customization
```python
# Specify font and size
text = Text("Custom Font", font="Consolas", font_size=90)
# Different fonts for different parts
text = Text(
"Mixed fonts example",
t2f={"Mixed": "Consolas", "fonts": "Arial"}
)
```
### Text Coloring
```python
# Color entire text
text = Text("Colored Text", color=BLUE)
# Color specific words
text = Text(
"The quick brown fox",
t2c={"quick": BLUE, "brown": ORANGE, "fox": GREEN}
)
```
### Text Styling
```python
# Slant (italic)
text = Text(
"Italic and bold text",
t2s={"Italic": ITALIC},
t2w={"bold": BOLD}
)
# Combine color, font, slant, and weight
text = Text(
"Fully styled text",
font="Arial",
font_size=48,
t2c={"styled": RED},
t2s={"styled": ITALIC},
t2w={"text": BOLD},
t2f={"text": "Consolas"}
)
```
## TexText Class
`TexText` combines LaTeX rendering with text, useful for mixing text and math.
### Basic TexText
```python
# Text with LaTeX support
text = TexText("The integral $\\int_0^1 x^2 dx$ equals $\\frac{1}{3}$")
# With font size
text = TexText("Hello World", font_size=72)
# Isolate parts for coloring
text = TexText(
"Einstein's $E = mc^2$",
isolate=["E", "m", "c"]
)
text.set_color_by_tex("E", BLUE)
text.set_color_by_tex("m", GREEN)
text.set_color_by_tex("c", YELLOW)
```
## Text vs TexText
- **Text**: Uses system fonts, no LaTeX, better font control
- **TexText**: LaTeX support for math symbols, uses LaTeX's text rendering
### When to Use Each
```python
# Use Text for pure text with custom fonts
title = Text("Machine Learning", font="Helvetica", font_size=60)
# Use TexText when mixing text and inline math
description = TexText("The function $f(x) = x^2$ is convex")
# Use Tex for pure mathematical expressions
formula = Tex(R"\sum_{i=1}^n i = \frac{n(n+1)}{2}")
```
## Text Positioning
```python
# Basic positioning
text = Text("Top")
text.to_edge(UP)
# Arrange multiple texts
title = Text("Title")
subtitle = Text("Subtitle", font_size=36)
VGroup(title, subtitle).arrange(DOWN, buff=0.5)
# Set width
text = Text("Long text that needs to fit")
text.set_width(FRAME_WIDTH - 1)
```
## Text with Background
```python
# Set backstroke for readability
text = Text("With Background", font_size=60)
text.set_backstroke(BLACK, width=5)
# Background rectangle (manually)
from manimlib.mobject.svg.tex_mobject import BackgroundRectangle
text = Text("Text")
bg = BackgroundRectangle(text, color=BLACK, fill_opacity=0.8)
self.add(bg, text)
```
## VGroup for Text Layout
```python
# Group multiple text objects
line1 = Text("First line")
line2 = Text("Second line")
line3 = Text("Third line")
paragraph = VGroup(line1, line2, line3)
paragraph.arrange(DOWN, aligned_edge=LEFT, buff=0.3)
paragraph.to_edge(LEFT)
```
## Full Example
```python
class ComprehensiveTextExample(Scene):
def construct(self):
# Title with custom font
title = Text(
"Text Rendering in ManimGL",
font="Arial",
font_size=72,
color=BLUE
)
title.to_edge(UP)
# Description with mixed styling
desc = Text(
"Mix different fonts, colors, and styles",
font="Helvetica",
font_size=36,
t2c={"fonts": RED, "colors": GREEN, "styles": YELLOW},
t2w={"Mix": BOLD}
)
desc.next_to(title, DOWN, buff=0.5)
# Mathematical description using TexText
math_desc = TexText(
"For equations like $E = mc^2$, use Tex or TexText",
font_size=30
)
math_desc.next_to(desc, DOWN, buff=1)
# Add all with animations
self.play(Write(title))
self.play(FadeIn(desc, shift=DOWN))
self.play(Write(math_desc))
self.wait()
```
## Best Practices
1. **Font availability**: Ensure fonts are installed on the system
2. **Use Text for UI elements**: Better control over appearance
3. **Use TexText for mixed content**: When you need both text and math
4. **Backstroke for visibility**: Add backstroke to text over complex backgrounds
5. **VGroup for layout**: Group related text elements for easier positioning
6. **t2c/t2f/t2s/t2w**: Use dictionaries for per-word styling
## Common Patterns
### Creating a text label with background
```python
def create_label(text_str, color=WHITE):
label = Text(text_str, font_size=40, color=color)
label.set_backstroke(BLACK, width=5)
return label
```
### Multi-line text with alignment
```python
lines = [Text(line) for line in [
"Line 1",
"Longer line 2",
"Line 3"
]]
paragraph = VGroup(*lines)
paragraph.arrange(DOWN, aligned_edge=LEFT, buff=0.2)
```
### Highlighted text
```python
sentence = Text(
"This word is highlighted",
t2c={"highlighted": YELLOW},
t2w={"highlighted": BOLD}
)
```
rules/transform-animations.md
# Transform Animations in ManimGL
Transform animations morph one mobject into another or animate changes to mobject properties.
## Transform
The basic `Transform` changes one mobject to look like another.
### Basic Transform
```python
from manimlib import *
class BasicTransform(Scene):
def construct(self):
square = Square()
circle = Circle()
self.play(ShowCreation(square))
self.wait()
# Transform square into circle
self.play(Transform(square, circle))
self.wait()
# Note: After transform, square now looks like circle
# but it's still the square object
```
### Key Insight
After `Transform(A, B)`:
- Object A remains in the scene
- Object A now looks like B
- Object B is not added to the scene
## ReplacementTransform
`ReplacementTransform` replaces the source with the target.
```python
class ReplacementTransformExample(Scene):
def construct(self):
square = Square(color=BLUE)
circle = Circle(color=RED)
self.play(ShowCreation(square))
self.wait()
# Replace square with circle
self.play(ReplacementTransform(square, circle))
self.wait()
# After this, circle is in the scene, not square
```
### When to Use Each
```python
# Use Transform when:
# - You want to keep the same mobject reference
square.transform_into_circle = lambda: Transform(square, Circle())
# Use ReplacementTransform when:
# - You want to swap objects
# - The target object should remain
self.play(ReplacementTransform(old_text, new_text))
```
## TransformMatchingTex
Morphs LaTeX expressions by matching substrings.
```python
class TexTransformExample(Scene):
def construct(self):
eq1 = Tex(R"a^2 + b^2 = c^2")
eq2 = Tex(R"a^2 = c^2 - b^2")
self.play(Write(eq1))
self.wait()
# Matching parts smoothly transform
self.play(TransformMatchingTex(eq1, eq2))
self.wait()
```
### With Color Mapping
```python
class ColoredTexTransform(Scene):
def construct(self):
# Set up equations with colors
eq1 = Tex(
R"(a + b)^2 = a^2 + 2ab + b^2",
t2c={"a": BLUE, "b": GREEN}
)
eq2 = Tex(
R"(a + b)^2 = (a + b)(a + b)",
t2c={"a": BLUE, "b": GREEN}
)
self.play(Write(eq1))
self.wait()
self.play(TransformMatchingTex(eq1, eq2))
self.wait()
```
### With isolate Parameter
```python
# Isolate specific parts for better matching
eq1 = Tex(
R"x^2 + 2x + 1",
isolate=["x", "^2", "+", "1", "2"]
)
eq2 = Tex(
R"(x + 1)^2",
isolate=["x", "^2", "+", "1", "(", ")"]
)
self.play(Write(eq1))
self.wait()
self.play(TransformMatchingTex(eq1, eq2))
```
### With Key Mapping
```python
# Map specific substrings
eq1 = Tex(R"x^2 + y^2 = r^2")
eq2 = Tex(R"a^2 + b^2 = c^2")
self.play(Write(eq1))
self.wait()
self.play(TransformMatchingTex(
eq1, eq2,
key_map={
"x": "a",
"y": "b",
"r": "c"
}
))
```
## TransformMatchingShapes
Morphs objects by matching similar shapes.
```python
class ShapeTransform(Scene):
def construct(self):
# Source group
source = VGroup(
Circle(radius=0.5, color=BLUE),
Square(side_length=1, color=GREEN),
Triangle(color=YELLOW)
)
source.arrange(RIGHT, buff=0.5)
# Target group
target = VGroup(
Circle(radius=1, color=RED),
Square(side_length=0.5, color=PURPLE),
Triangle(color=ORANGE)
)
target.arrange(DOWN, buff=0.5)
self.play(ShowCreation(source))
self.wait()
self.play(TransformMatchingShapes(source, target))
self.wait()
```
## MoveToTarget
Set a target state for a mobject and animate to it.
```python
class MoveToTargetExample(Scene):
def construct(self):
circle = Circle()
self.play(ShowCreation(circle))
# Set target state
circle.generate_target()
circle.target.shift(RIGHT * 3)
circle.target.scale(2)
circle.target.set_color(YELLOW)
# Animate to target
self.play(MoveToTarget(circle))
self.wait()
```
### Multiple Targets
```python
# Set up multiple mobjects with targets
square = Square()
triangle = Triangle()
square.generate_target()
square.target.shift(LEFT * 2)
triangle.generate_target()
triangle.target.shift(RIGHT * 2)
self.play(
MoveToTarget(square),
MoveToTarget(triangle)
)
```
## FadeTransform
Cross-fades between two objects.
```python
class FadeTransformExample(Scene):
def construct(self):
text1 = Text("Hello", font_size=72)
text2 = Text("World", font_size=72)
self.play(Write(text1))
self.wait()
# Smooth cross-fade
self.play(FadeTransform(text1, text2))
self.wait()
```
## Rotate
Rotates a mobject.
```python
# Rotate by angle
square = Square()
self.play(Rotate(square, PI / 2)) # 90 degrees
# Rotate around a point
self.play(Rotate(square, PI, about_point=ORIGIN))
# Rotate around an axis (for 3D)
self.play(Rotate(cube, PI, axis=RIGHT))
```
## Rotating (Continuous)
Creates a continuous rotation.
```python
# Continuous rotation
square = Square()
self.play(Rotating(square, radians=2*PI, run_time=4))
# Infinite rotation with updater
square.add_updater(lambda m, dt: m.rotate(0.1 * dt))
self.wait(10)
```
## ScaleInPlace
Scales without changing center position.
```python
circle = Circle()
self.play(ScaleInPlace(circle, 2)) # Double size
# Scale around a point
self.play(ScaleInPlace(circle, 0.5, about_point=RIGHT))
```
## ApplyMethod
Animates any mobject method.
```python
# Using .animate syntax (preferred)
self.play(circle.animate.shift(RIGHT))
self.play(circle.animate.scale(2))
self.play(circle.animate.set_color(BLUE))
# Old syntax (still works)
self.play(ApplyMethod(circle.shift, RIGHT))
self.play(ApplyMethod(circle.scale, 2))
```
## Complex Transformations
### apply_complex_function
Transform using complex number operations.
```python
class ComplexTransform(Scene):
def construct(self):
plane = ComplexPlane()
plane.add_coordinate_labels(font_size=20)
# Create shape on complex plane
circle = Circle(radius=1, color=BLUE)
self.add(plane, circle)
self.wait()
# Apply complex function (e.g., z^2)
self.play(
circle.animate.apply_complex_function(lambda z: z**2),
run_time=3
)
self.wait()
```
### apply_function
Transform using arbitrary functions.
```python
# Apply custom transformation
grid = NumberPlane()
def wavy_transform(point):
x, y, z = point
return np.array([
x,
y + 0.5 * np.sin(2 * x),
z
])
self.play(
grid.animate.apply_function(wavy_transform),
run_time=3
)
```
## Transformation Sequences
### Multi-step Transformations
```python
class TransformSequence(Scene):
def construct(self):
shapes = [
Square(color=BLUE),
Circle(color=GREEN),
Triangle(color=YELLOW),
Star(color=RED)
]
current = shapes[0]
self.play(ShowCreation(current))
# Transform through each shape
for next_shape in shapes[1:]:
self.play(ReplacementTransform(current, next_shape))
current = next_shape
self.wait(0.3)
```
### Derivation Transformation
```python
class DerivationTransform(Scene):
def construct(self):
# Mathematical derivation
steps = [
Tex(R"x^2 - 4 = 0"),
Tex(R"x^2 = 4"),
Tex(R"x = \pm 2"),
]
current = steps[0]
self.play(Write(current))
self.wait()
for next_step in steps[1:]:
next_step.move_to(current)
self.play(TransformMatchingTex(current.copy(), next_step))
current = next_step
self.wait()
```
## Best Practices
1. **Transform vs ReplacementTransform**:
- Use `Transform` to keep the object reference
- Use `ReplacementTransform` to swap objects
2. **TransformMatchingTex**:
- Use `isolate=` to control matching
- Use `key_map=` for explicit mappings
- Color consistently for smooth transitions
3. **Timing**:
- Longer `run_time` for complex transformations
- Match timing to content importance
4. **.animate syntax**:
- Preferred for simple transformations
- More readable and concise
5. **Path arc**:
- Add `path_arc=90*DEGREES` for curved transformation paths
## Common Patterns
### Equation manipulation
```python
eq = Tex(R"2x + 4 = 10")
self.play(Write(eq))
eq2 = Tex(R"2x = 6")
eq2.move_to(eq)
self.play(TransformMatchingTex(eq.copy(), eq2))
eq3 = Tex(R"x = 3")
eq3.move_to(eq2)
self.play(TransformMatchingTex(eq2.copy(), eq3))
```
### Shape morphing
```python
shape = Circle()
self.play(ShowCreation(shape))
for new_shape in [Square(), Triangle(), Star(), Circle()]:
self.play(Transform(shape, new_shape))
self.wait(0.5)
```
### Text replacement
```python
text1 = Text("Before")
self.play(Write(text1))
text2 = Text("After")
text2.move_to(text1)
self.play(FadeTransform(text1, text2))
```
## Full Example
```python
class ComprehensiveTransform(Scene):
def construct(self):
# Title
title = Text("Transformations", font_size=60)
title.to_edge(UP)
self.play(Write(title))
# Shape transformations
shape = Square(color=BLUE)
self.play(ShowCreation(shape))
self.wait()
self.play(Transform(shape, Circle(color=GREEN)))
self.wait()
self.play(Transform(shape, Triangle(color=YELLOW)))
self.wait()
# Mathematical transformation
eq1 = Tex(R"a^2 + b^2 = c^2")
eq1.next_to(title, DOWN, buff=1)
self.play(
FadeOut(shape),
Write(eq1)
)
self.wait()
eq2 = Tex(R"c = \sqrt{a^2 + b^2}")
eq2.move_to(eq1)
self.play(TransformMatchingTex(eq1.copy(), eq2))
self.wait()
# Clean up
self.play(FadeOut(VGroup(title, eq2)))
```
SKILL.md
---
name: manimgl-best-practices
description: |
Trigger when: (1) User mentions "manimgl" or "ManimGL" or "3b1b manim", (2) Code contains `from manimlib import *`, (3) User runs `manimgl` CLI commands, (4) Working with InteractiveScene, self.frame, self.embed(), ShowCreation(), or ManimGL-specific patterns.
Best practices for ManimGL (Grant Sanderson's 3Blue1Brown version) - OpenGL-based animation engine with interactive development. Covers InteractiveScene, Tex with t2c, camera frame control, interactive mode (-se flag), 3D rendering, and checkpoint_paste() workflow.
NOT for Manim Community Edition (which uses `manim` imports and `manim` CLI).
---
## How to use
Read individual rule files for detailed explanations and code examples:
### Core Concepts
- [rules/scenes.md](rules/scenes.md) - InteractiveScene, Scene types, and construct method
- [rules/mobjects.md](rules/mobjects.md) - Mobject types, VMobject, Groups, and positioning
- [rules/animations.md](rules/animations.md) - Animation classes, playing animations, and timing
### Creation & Transformation
- [rules/creation-animations.md](rules/creation-animations.md) - ShowCreation, Write, FadeIn, DrawBorderThenFill
- [rules/transform-animations.md](rules/transform-animations.md) - Transform, ReplacementTransform, TransformMatchingTex
- [rules/animation-groups.md](rules/animation-groups.md) - LaggedStart, Succession, AnimationGroup
### Text & Math
- [rules/tex.md](rules/tex.md) - Tex class, raw strings R"...", and LaTeX rendering
- [rules/text.md](rules/text.md) - Text mobjects, fonts, and styling
- [rules/t2c.md](rules/t2c.md) - tex_to_color_map (t2c) for coloring math expressions
### Styling & Appearance
- [rules/colors.md](rules/colors.md) - Color constants, gradients, RGB, hex, GLSL coloring
- [rules/styling.md](rules/styling.md) - Fill, stroke, opacity, backstroke, gloss, shadow
### 3D & Camera
- [rules/3d.md](rules/3d.md) - 3D objects, surfaces, Sphere, Torus, parametric surfaces, lighting
- [rules/camera.md](rules/camera.md) - frame.reorient(), Euler angles, fix_in_frame(), camera animations
### Interactive Development
- [rules/interactive.md](rules/interactive.md) - Interactive mode with `-se` flag, checkpoint_paste()
- [rules/frame.md](rules/frame.md) - self.frame, camera control, reorient, and zooming
- [rules/embedding.md](rules/embedding.md) - self.embed() for IPython debugging, touch() mode
### Configuration & CLI
- [rules/cli.md](rules/cli.md) - manimgl command, flags (-w, -o, -se, -l, -h), rendering options
- [rules/config.md](rules/config.md) - custom_config.yml, directories, camera settings, quality presets
## Working Examples
Complete, tested example files demonstrating common patterns:
- [examples/basic_animations.py](examples/basic_animations.py) - Basic shapes, text, and animations
- [examples/math_visualization.py](examples/math_visualization.py) - LaTeX equations and mathematical content
- [examples/graph_plotting.py](examples/graph_plotting.py) - Axes, functions, and graphing
- [examples/3d_visualization.py](examples/3d_visualization.py) - 3D scenes with camera control and surfaces
- [examples/updater_patterns.py](examples/updater_patterns.py) - Dynamic animations with updaters
## Scene Templates
Copy and modify these templates to start new projects:
- [templates/basic_scene.py](templates/basic_scene.py) - Standard 2D scene template
- [templates/interactive_scene.py](templates/interactive_scene.py) - InteractiveScene with self.embed()
- [templates/3d_scene.py](templates/3d_scene.py) - 3D scene with frame.reorient()
- [templates/math_scene.py](templates/math_scene.py) - Mathematical derivations and equations
## Quick Reference
### Basic Scene Structure
```python
from manimlib import *
class MyScene(InteractiveScene):
def construct(self):
# Create mobjects
circle = Circle()
# Add to scene (static)
self.add(circle)
# Or animate
self.play(ShowCreation(circle)) # Note: ShowCreation, not Create
# Wait
self.wait(1)
```
### Render Command
```bash
# Render and preview
manimgl scene.py MyScene
# Interactive mode - drop into shell at line 15
manimgl scene.py MyScene -se 15
# Write to file
manimgl scene.py MyScene -w
# Low quality for testing
manimgl scene.py MyScene -l
```
### Key Differences from ManimCE
| Feature | ManimGL (3b1b) | Manim Community |
|---------|----------------|-----------------|
| Import | `from manimlib import *` | `from manim import *` |
| CLI | `manimgl` | `manim` |
| Math text | `Tex(R"\pi")` | `MathTex(r"\pi")` |
| Scene | `InteractiveScene` | `Scene` |
| Create anim | `ShowCreation` | `Create` |
| Camera | `self.frame` | `self.camera.frame` |
| Fix in frame | `mob.fix_in_frame()` | `self.add_fixed_in_frame_mobjects(mob)` |
| Package | `manimgl` (PyPI) | `manim` (PyPI) |
### Interactive Development Workflow
ManimGL's killer feature is interactive development:
```bash
# Start at line 20 with state preserved
manimgl scene.py MyScene -se 20
```
In interactive mode:
```python
# Copy code to clipboard, then run:
checkpoint_paste() # Run with animations
checkpoint_paste(skip=True) # Run instantly (no animations)
checkpoint_paste(record=True) # Record while running
```
### Camera Control (self.frame)
```python
# Get the camera frame
frame = self.frame
# Reorient in 3D (phi, theta, gamma, center, height)
frame.reorient(45, -30, 0, ORIGIN, 8)
# Animate camera movement
self.play(frame.animate.reorient(60, -45, 0))
# Fix mobjects to stay in screen space during 3D movement
title.fix_in_frame()
```
### LaTeX with Tex class
```python
# Use raw strings with capital R
formula = Tex(R"\int_0^1 x^2 \, dx = \frac{1}{3}")
# Color mapping with t2c
equation = Tex(
R"E = mc^2",
t2c={"E": BLUE, "m": GREEN, "c": YELLOW}
)
# Isolate substrings for animation
formula = Tex(R"\sum_{n=1}^{\infty} \frac{1}{n^2} = \frac{\pi^2}{6}")
formula.set_color_by_tex("n", BLUE)
```
### Common Patterns
#### Embedding for debugging
```python
def construct(self):
circle = Circle()
self.play(ShowCreation(circle))
self.embed() # Drops into IPython shell here
```
#### Set floor plane for 3D
```python
self.set_floor_plane("xz") # Makes xy the viewing plane
```
#### Backstroke for text readability
```python
text = Text("Label")
text.set_backstroke(BLACK, 5) # Black outline behind text
```
### Installation
```bash
# Install ManimGL
pip install manimgl
# Check installation
manimgl --version
```
### Common Pitfalls to Avoid
1. **Version confusion** - Ensure you're using `manimgl`, not `manim` (community version)
2. **ShowCreation vs Create** - ManimGL uses `ShowCreation`, not `Create`
3. **Tex vs MathTex** - ManimGL uses `Tex` with capital R raw strings
4. **self.frame vs self.camera.frame** - ManimGL uses `self.frame` directly
5. **fix_in_frame()** - Call on the mobject, not the scene
6. **Interactive mode** - Use `-se` flag for interactive development
## License & Attribution
This skill contains example code adapted from [3Blue1Brown's video repository](https://github.com/3b1b/videos) by Grant Sanderson.
**License:** [CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/)
- **Attribution required** - Credit both 3Blue1Brown and the adapter
- **NonCommercial** - Not for commercial use
- **ShareAlike** - Derivatives must use the same license
See [LICENSE.txt](LICENSE.txt) for full details.
templates/3d_scene.py
"""
3D Scene Template for ManimGL
Template for creating 3D animations with camera control.
Usage:
manimgl templates/3d_scene.py ThreeDSceneTemplate
manimgl templates/3d_scene.py ThreeDSceneTemplate -l # Low quality
manimgl templates/3d_scene.py ThreeDSceneTemplate -se 20 # Interactive at line 20
"""
from manimlib import *
class ThreeDSceneTemplate(Scene):
"""
Basic 3D scene template.
Key concepts:
- Use self.camera.frame (or self.frame) for camera control
- frame.reorient(theta, phi) sets camera orientation
- Use fix_in_frame() for 2D labels that don't rotate
- Press 'd' + mouse to rotate camera interactively (with touch())
"""
def construct(self):
# === CAMERA SETUP ===
frame = self.camera.frame
# Set initial 3D orientation
# theta: rotation around z-axis (azimuthal)
# phi: angle from z-axis (polar)
frame.reorient(20, 70) # Good default for isometric view
# === TITLE (Fixed in frame) ===
title = Text("3D Visualization", font_size=48)
title.to_edge(UP)
title.fix_in_frame() # Stays in screen space
title.set_backstroke(BLACK, width=5)
self.add(title)
# === 3D AXES ===
axes = ThreeDAxes(
x_range=(-3, 3, 1),
y_range=(-3, 3, 1),
z_range=(-3, 3, 1),
width=8,
height=8,
depth=8
)
axes.add_coordinate_labels(font_size=20)
self.play(ShowCreation(axes))
self.wait()
# === 3D OBJECTS ===
# Sphere
sphere = Sphere(radius=1.5, color=BLUE, resolution=(20, 20))
sphere.set_opacity(0.7)
sphere.shift(LEFT * 2)
# Cube
cube = Cube(side_length=2, color=GREEN)
cube.set_opacity(0.8)
cube.shift(RIGHT * 2)
# Create objects
self.play(
ShowCreation(sphere),
ShowCreation(cube)
)
self.wait()
# === CAMERA ANIMATION ===
# Rotate camera around scene
self.play(
frame.animate.reorient(45, 80),
run_time=3
)
self.wait()
# === CONTINUOUS ROTATION ===
# Add updater for continuous rotation
frame.add_updater(lambda m, dt: m.increment_theta(20 * dt))
self.wait(5)
frame.clear_updaters()
# === CLEANUP ===
self.play(
FadeOut(axes),
FadeOut(sphere),
FadeOut(cube),
FadeOut(title)
)
self.wait()
class ParametricSurfaceTemplate(Scene):
"""
Template for parametric 3D surfaces.
"""
def construct(self):
frame = self.camera.frame
frame.reorient(30, 75)
# Title
title = Text("Parametric Surface", font_size=48)
title.to_edge(UP)
title.fix_in_frame()
self.add(title)
# Create parametric surface
surface = ParametricSurface(
lambda u, v: np.array([
u,
v,
np.sin(np.sqrt(u**2 + v**2)) # Sinc function
]),
u_range=(-3, 3),
v_range=(-3, 3),
resolution=(30, 30)
)
surface.set_color(BLUE)
surface.set_opacity(0.7)
# Optional: Add mesh overlay
mesh = SurfaceMesh(surface)
mesh.set_stroke(WHITE, width=0.5, opacity=0.3)
# Show surface
self.play(
ShowCreation(surface),
ShowCreation(mesh),
run_time=3
)
self.wait()
# Rotate camera
self.play(
frame.animate.increment_theta(180 * DEGREES),
run_time=6
)
self.wait()
class Interactive3DTemplate(Scene):
"""
Template for interactive 3D exploration.
Use with: manimgl templates/3d_scene.py Interactive3DTemplate -se 30
Then use touch() in the shell to interact with the scene.
"""
def construct(self):
frame = self.camera.frame
frame.reorient(20, 70)
# Create 3D content
sphere = Sphere(radius=2, color=BLUE)
sphere.set_gloss(0.8)
cube = Cube(side_length=1.5, color=GREEN)
cube.shift(RIGHT * 3)
torus = Torus(r1=1.5, r2=0.5, color=YELLOW)
torus.shift(LEFT * 3)
self.play(
ShowCreation(sphere),
ShowCreation(cube),
ShowCreation(torus)
)
self.wait()
# === INTERACTIVE MODE ===
# Uncomment this to enter interactive mode
# self.embed()
# In the shell, try:
# >>> touch()
# Then use:
# 'd' + mouse to rotate
# 'z' + scroll to zoom
# 'r' to reset camera
# 'q' to quit touch mode
class TexturedSphereTemplate(Scene):
"""
Template for 3D objects with textures.
"""
def construct(self):
frame = self.camera.frame
frame.reorient(20, 70)
title = Text("Textured Sphere", font_size=48)
title.to_edge(UP)
title.fix_in_frame()
self.add(title)
# Create sphere
sphere = Sphere(radius=2, resolution=(40, 40))
# Apply texture (can use URL or local file)
# Example with Earth texture
textured_sphere = TexturedSurface(
sphere,
"https://upload.wikimedia.org/wikipedia/commons/thumb/4/4d/Whole_world_-_land_and_oceans.jpg/1280px-Whole_world_-_land_and_oceans.jpg"
)
self.add(textured_sphere)
self.wait()
# Rotate to show texture
self.play(
frame.animate.increment_theta(360 * DEGREES),
run_time=10,
rate_func=linear
)
class LightingTemplate(Scene):
"""
Template demonstrating lighting effects in 3D.
"""
def construct(self):
frame = self.camera.frame
frame.reorient(30, 70)
# Create glossy sphere
sphere = Sphere(radius=2, color=BLUE)
sphere.set_gloss(0.9) # High gloss for shininess
sphere.set_shadow(0.5) # Add shadow
self.add(sphere)
self.wait()
# Access and move light source
light = self.camera.light_source
# Animate light position
self.play(light.animate.move_to([5, 5, 5]), run_time=2)
self.wait()
self.play(light.animate.move_to([-5, -5, 5]), run_time=2)
self.wait()
# Rotate camera
self.play(
frame.animate.increment_theta(180 * DEGREES),
run_time=4
)
if __name__ == "__main__":
import os
os.system(f"manimgl {__file__} ThreeDSceneTemplate")
templates/basic_scene.py
"""
Basic Scene Template for ManimGL
This is a standard 2D scene template. Copy and modify for your animations.
Usage:
manimgl templates/basic_scene.py BasicSceneTemplate
manimgl templates/basic_scene.py BasicSceneTemplate -l # Low quality for testing
manimgl templates/basic_scene.py BasicSceneTemplate -w # Write to file
"""
from manimlib import *
class BasicSceneTemplate(Scene):
"""
A basic scene template showing common patterns.
Modify this template for your needs:
- Add your mobjects in construct()
- Create animations with self.play()
- Use self.wait() for pauses
"""
def construct(self):
# === TITLE ===
title = Text("My Animation", font_size=60)
title.to_edge(UP)
self.play(Write(title))
self.wait()
# === MAIN CONTENT ===
# Create your mobjects
circle = Circle(radius=1.5, color=BLUE)
circle.set_fill(BLUE, opacity=0.5)
circle.set_stroke(WHITE, width=3)
square = Square(side_length=2, color=GREEN)
square.set_fill(GREEN, opacity=0.5)
square.set_stroke(WHITE, width=3)
square.next_to(circle, RIGHT, buff=1)
# Animate creation
self.play(
ShowCreation(circle),
ShowCreation(square)
)
self.wait()
# === TRANSFORMATIONS ===
# Transform or animate
self.play(
circle.animate.shift(DOWN),
square.animate.shift(DOWN)
)
self.wait()
# === LABELS ===
# Add labels
circle_label = Text("Circle", font_size=36)
circle_label.next_to(circle, DOWN)
square_label = Text("Square", font_size=36)
square_label.next_to(square, DOWN)
self.play(
FadeIn(circle_label, shift=UP),
FadeIn(square_label, shift=UP)
)
self.wait()
# === CLEANUP ===
# Fade out everything
self.play(FadeOut(VGroup(
title, circle, square, circle_label, square_label
)))
self.wait()
class MinimalScene(Scene):
"""
Minimal scene template - just the essentials.
"""
def construct(self):
# Your code here
text = Text("Hello ManimGL!", font_size=72)
self.play(Write(text))
self.wait(2)
class AnimationShowcase(Scene):
"""
Template showing various animation types.
"""
def construct(self):
title = Text("Animation Types", font_size=48)
title.to_edge(UP)
self.add(title)
# ShowCreation
circle = Circle(color=BLUE)
self.play(ShowCreation(circle))
self.wait(0.5)
self.play(FadeOut(circle))
# Write (for text)
text = Text("Written Text", font_size=48)
self.play(Write(text))
self.wait(0.5)
self.play(FadeOut(text))
# FadeIn
square = Square(color=GREEN)
self.play(FadeIn(square, scale=0.5))
self.wait(0.5)
self.play(FadeOut(square))
# Transform
shape1 = Circle(color=YELLOW)
shape2 = Square(color=RED)
self.play(ShowCreation(shape1))
self.play(Transform(shape1, shape2))
self.wait(0.5)
self.play(FadeOut(VGroup(title, shape1)))
if __name__ == "__main__":
# This allows you to run: python basic_scene.py
# (though using manimgl is recommended)
import os
os.system(f"manimgl {__file__} BasicSceneTemplate")
templates/interactive_scene.py
"""
ManimGL Interactive Scene Template
Run with: manimgl scene.py MyScene
Interactive: manimgl scene.py MyScene -se 15
"""
from manimlib import *
class MyInteractiveScene(InteractiveScene):
def construct(self):
# Setup - runs before interactive mode
circle = Circle(color=BLUE)
circle.set_fill(BLUE, opacity=0.5)
square = Square(color=RED)
square.next_to(circle, RIGHT, buff=1)
self.play(ShowCreation(circle))
self.play(ShowCreation(square))
# Drop into interactive shell here
# Use: manimgl file.py MyInteractiveScene -se 15
self.embed()
# Code below runs after exiting shell
# Or paste code in shell with checkpoint_paste()
# Example animations to paste:
self.play(circle.animate.shift(LEFT * 2))
self.play(Transform(circle, square))
self.play(FadeOut(circle))
class My3DInteractiveScene(InteractiveScene):
def construct(self):
# Setup 3D
frame = self.camera.frame
frame.set_euler_angles(phi=70 * DEGREES, theta=-45 * DEGREES)
# Fixed elements
title = Text("3D Scene")
title.to_edge(UP)
title.fix_in_frame()
self.add(title)
# 3D content
axes = ThreeDAxes()
self.add(axes)
surface = Surface(
lambda u, v: np.array([u, v, np.sin(u) * np.cos(v)]),
u_range=[-3, 3],
v_range=[-3, 3],
)
surface.set_color(BLUE, opacity=0.8)
self.play(ShowCreation(surface))
# Interactive point
self.embed()
# Camera animation
self.play(
frame.animate.increment_theta(-30 * DEGREES),
run_time=3
)
templates/math_scene.py
"""
Mathematical Scene Template for ManimGL
Template for creating mathematical derivations and visualizations.
Usage:
manimgl templates/math_scene.py MathSceneTemplate
manimgl templates/math_scene.py MathSceneTemplate -l
"""
from manimlib import *
class MathSceneTemplate(Scene):
"""
Template for mathematical derivations and equations.
Key features:
- Use Tex(R"...") for LaTeX (note capital R)
- Use t2c for coloring parts of equations
- Use TransformMatchingTex for equation transformations
- Use isolate parameter for better control
"""
def construct(self):
# === TITLE ===
title = Text("Mathematical Derivation", font_size=60)
title.to_edge(UP)
self.play(Write(title))
self.wait()
# === INITIAL EQUATION ===
# Use Tex with raw strings (capital R)
eq1 = Tex(
R"(a + b)^2",
font_size=60
)
self.play(Write(eq1))
self.wait()
# === EXPANSION ===
eq2 = Tex(
R"(a + b)(a + b)",
font_size=60
)
eq2.move_to(eq1)
self.play(TransformMatchingTex(eq1, eq2))
self.wait()
# === FINAL FORM (with coloring) ===
eq3 = Tex(
R"a^2 + 2ab + b^2",
font_size=60,
t2c={"a": BLUE, "b": GREEN} # Color variables
)
eq3.move_to(eq2)
self.play(TransformMatchingTex(eq2, eq3))
self.wait(2)
# === CLEANUP ===
self.play(FadeOut(VGroup(title, eq3)))
class QuadraticFormulaTemplate(Scene):
"""
Template showing step-by-step derivation.
"""
def construct(self):
title = Text("Quadratic Formula Derivation", font_size=48)
title.to_edge(UP)
self.play(Write(title))
self.wait()
# Steps of derivation
steps = [
Tex(R"ax^2 + bx + c = 0"),
Tex(R"x^2 + \frac{b}{a}x + \frac{c}{a} = 0"),
Tex(R"x^2 + \frac{b}{a}x = -\frac{c}{a}"),
Tex(R"x^2 + \frac{b}{a}x + \frac{b^2}{4a^2} = \frac{b^2}{4a^2} - \frac{c}{a}"),
Tex(R"\left(x + \frac{b}{2a}\right)^2 = \frac{b^2 - 4ac}{4a^2}"),
Tex(R"x + \frac{b}{2a} = \pm\frac{\sqrt{b^2 - 4ac}}{2a}"),
Tex(R"x = \frac{-b \pm \sqrt{b^2 - 4ac}}{2a}"),
]
# Position first equation
current_eq = steps[0]
current_eq.next_to(title, DOWN, buff=1)
self.play(Write(current_eq))
self.wait()
# Transform through each step
for next_eq in steps[1:]:
next_eq.move_to(current_eq)
self.play(TransformMatchingTex(current_eq.copy(), next_eq))
current_eq = next_eq
self.wait()
self.wait(2)
class ColoredMathTemplate(Scene):
"""
Template for color-coded mathematical expressions.
"""
def construct(self):
title = Text("Color-Coded Math", font_size=48)
title.to_edge(UP)
self.play(Write(title))
# Color-code different parts
equation = Tex(
R"\int_0^1 x^2 \, dx = \left[\frac{x^3}{3}\right]_0^1 = \frac{1}{3}",
t2c={
R"\int": BLUE, # Integral sign
"x": GREEN, # Variable
R"\frac": YELLOW, # Fractions
"1": RED, # Constants
"3": RED
}
)
equation.next_to(title, DOWN, buff=1)
self.play(Write(equation))
self.wait(2)
# Highlight specific part
parts = equation.get_parts_by_tex(R"\frac{1}{3}")
self.play(
parts.animate.set_color(ORANGE).scale(1.3)
)
self.wait(2)
class AlignedEquationsTemplate(Scene):
"""
Template for aligned equations.
"""
def construct(self):
title = Text("System of Equations", font_size=48)
title.to_edge(UP)
self.play(Write(title))
# Create multiple equations
equations = VGroup(
Tex(R"2x + 3y = 7"),
Tex(R"x - y = 1"),
)
equations.arrange(DOWN, aligned_edge=LEFT, buff=0.5)
equations.next_to(title, DOWN, buff=1)
# Write equations
for eq in equations:
self.play(Write(eq))
self.wait(0.5)
self.wait()
# Solution
solution = VGroup(
Tex(R"x = 2"),
Tex(R"y = 1")
)
solution.arrange(DOWN, aligned_edge=LEFT, buff=0.3)
solution.next_to(equations, DOWN, buff=1)
self.play(FadeIn(solution, shift=UP))
self.wait(2)
class CalculusVisualizationTemplate(Scene):
"""
Template combining equations with visual elements.
"""
def construct(self):
# === EQUATION ===
integral = Tex(
R"\int_a^b f(x) \, dx",
font_size=60,
t2c={"f(x)": BLUE, "a": RED, "b": RED}
)
integral.to_edge(UP)
self.play(Write(integral))
self.wait()
# === VISUALIZATION ===
# Create axes
axes = Axes(
x_range=[-1, 5],
y_range=[-1, 5],
width=10,
height=6
)
axes.add_coordinate_labels(font_size=20)
# Function
graph = axes.get_graph(
lambda x: 0.2 * (x - 2)**2 + 1,
x_range=[1, 4],
color=BLUE
)
# Riemann rectangles
rects = axes.get_riemann_rectangles(
graph,
x_range=[1, 4],
dx=0.5,
color=BLUE,
fill_opacity=0.5
)
# Show visualization
self.play(
ShowCreation(axes),
ShowCreation(graph)
)
self.wait()
self.play(ShowCreation(rects))
self.wait(2)
class ComplexNumbersTemplate(Scene):
"""
Template for complex number visualization.
"""
def construct(self):
title = Text("Complex Numbers", font_size=48)
title.to_edge(UP)
self.play(Write(title))
# Euler's formula
euler = Tex(
R"e^{i\theta} = \cos\theta + i\sin\theta",
font_size=48,
t2c={
"e": BLUE,
R"\theta": GREEN,
R"\cos": YELLOW,
R"\sin": YELLOW,
"i": RED
}
)
euler.next_to(title, DOWN, buff=1)
self.play(Write(euler))
self.wait()
# Complex plane
plane = ComplexPlane(
x_range=[-2, 2],
y_range=[-2, 2]
)
plane.add_coordinate_labels(font_size=20)
plane.scale(1.5).shift(DOWN)
self.play(ShowCreation(plane))
self.wait()
# Unit circle
circle = Circle(radius=1.5, color=WHITE)
circle.move_to(plane.n2p(0))
# Point on circle
dot = Dot(color=RED)
dot.move_to(plane.n2p(1))
self.play(
ShowCreation(circle),
FadeIn(dot, scale=0.5)
)
self.wait()
# Rotate point
self.play(
Rotate(dot, PI, about_point=plane.n2p(0)),
run_time=3
)
self.wait()
class MatrixTemplate(Scene):
"""
Template for matrix operations.
"""
def construct(self):
title = Text("Matrix Multiplication", font_size=48)
title.to_edge(UP)
self.play(Write(title))
# Create matrices
matrix_a = Matrix([
["a", "b"],
["c", "d"]
])
matrix_b = Matrix([
["e", "f"],
["g", "h"]
])
equals = Tex("=")
matrix_result = Matrix([
["ae+bg", "af+bh"],
["ce+dg", "cf+dh"]
])
# Arrange
group = VGroup(matrix_a, matrix_b, equals, matrix_result)
group.arrange(RIGHT, buff=0.5)
group.next_to(title, DOWN, buff=1)
# Show step by step
self.play(Write(matrix_a))
self.wait(0.5)
self.play(Write(matrix_b))
self.wait(0.5)
self.play(Write(equals))
self.wait(0.5)
self.play(Write(matrix_result))
self.wait(2)
if __name__ == "__main__":
import os
os.system(f"manimgl {__file__} MathSceneTemplate")