Animations for some 18S191 lectures

This commit is contained in:
Grant Sanderson 2020-11-24 13:31:50 -08:00
parent 25bab8c10e
commit 171042b8d7
4 changed files with 779 additions and 0 deletions

View file

@ -0,0 +1,200 @@
from manimlib.imports import *
def box_blur(n):
return np.ones((n, n)) / (n**2)
class ConvolutionIntroduction(ThreeDScene):
def construct(self):
frame = self.camera.frame
# Setup the pixel grids
image = Image.open(get_full_raster_image_path("Mario"))
arr = np.array(image)
arr = arr[80::40, 0::40]
height, width = arr.shape[:2]
pixel_array = VGroup(*[
Square(fill_color=rgb_to_hex(arr[i, j] / 255), fill_opacity=1)
for i in range(height)
for j in range(width)
])
pixel_array.arrange_in_grid(height, width, buff=0)
pixel_array.set_height(6)
pixel_array.set_stroke(WHITE, 1)
pixel_array.to_edge(LEFT, buff=LARGE_BUFF)
new_array = pixel_array.copy()
new_array.next_to(pixel_array, RIGHT, buff=2)
new_array.set_fill(BLACK, 0)
self.add(pixel_array)
self.add(new_array)
# Setup kernel
def get_kernel_array(kernel, pixel_array=pixel_array, tex=None):
kernel_array = VGroup()
for row in kernel:
for x in row:
square = pixel_array[0].copy()
square.set_fill(BLACK, 0)
square.set_stroke(BLUE, 2)
if tex:
value = TexMobject(tex)
else:
value = DecimalNumber(x, num_decimal_places=3)
value.set_width(square.get_width() * 0.7)
value.set_stroke(BLACK, 1, background=True)
value.move_to(square)
square.add(value)
kernel_array.add(square)
kernel_array.arrange_in_grid(*kernel.shape, buff=0)
kernel_array.move_to(pixel_array[0])
return kernel_array
kernel = box_blur(3)
kernel_array = get_kernel_array(kernel, tex="1 / 9")
self.add(kernel_array)
# Define step
right_rect = new_array[0].copy()
right_rect.set_stroke(BLUE, 2)
self.add(right_rect)
def step(pos=0):
i = pos // width
j = pos % width
h, w = kernel.shape
pixels = np.array([
square.fill_rgbas[0]
for square in pixel_array
]).reshape((height, width, 4))
rgba = sum([
kernel[k, l] * pixels[i - k, j - l]
for k in range(-(w // 2), w // 2 + 1)
for l in range(-(h // 2), h // 2 + 1)
if (0 <= i - k < pixels.shape[0]) and (0 <= j - l < pixels.shape[1])
])
kernel_array.move_to(pixel_array[pos])
right_rect.move_to(new_array[pos])
new_array[pos].fill_rgbas[0] = rgba
def walk(start, stop, time=5, surface=None):
for n in range(start, stop):
step(n)
if surface:
surface.move_to(kernel_array, IN)
self.wait(time / (stop - start))
# Setup zooming
def zoom_to_kernel():
self.play(
frame.set_height, 1.5 * kernel_array.get_height(),
frame.move_to, kernel_array,
run_time=2
)
def zoom_to_new_pixel():
self.play(
frame.set_height, 1.5 * kernel_array.get_height(),
frame.move_to, right_rect,
run_time=2
)
def reset_frame():
self.play(
frame.to_default_state
)
# Example walking
# walk(0, 151, 15)
last_i = 0
next_i = 151
walk(last_i, next_i, 5)
self.wait()
zoom_to_kernel()
self.wait()
reset_frame()
zoom_to_new_pixel()
self.wait()
reset_frame()
# last_i = next_i
# next_i = 200
# walk(151, 200, 2)
# self.wait(0.5)
# zoom_to_kernel()
# self.wait()
# reset_frame()
# zoom_to_new_pixel()
# self.wait()
# reset_frame()
last_i = next_i
next_i = len(pixel_array)
walk(last_i, next_i, 10)
# self.wait()
# zoom_to_kernel()
# self.wait()
# reset_frame()
self.wait()
# Gauss kernel
gauss_kernel = np.array([
[0.00296902, 0.0133062, 0.0219382, 0.0133062, .00296902],
[0.0133062, 0.0596343, 0.0983203, 0.0596343, 0.0133062],
[0.0219382, 0.0983203, 0.162103, 0.0983203, 0.0219382],
[0.0133062, 0.0596343, 0.0983203, 0.0596343, 0.0133062],
[0.00296902, 0.0133062, 0.0219382, 0.0133062, 0.00296902],
]) # Oh good, hard coded, I hope you feel happy with yourself.
gauss_array = get_kernel_array(gauss_kernel)
kernel_array.set_submobjects(gauss_array)
kernel = gauss_kernel
new_array.set_fill(BLACK, 0)
walk(0, 200, time=5)
# walk(0, 200, time=10)
self.wait()
zoom_to_kernel()
self.wait()
# Gauss surface
gaussian = ParametricSurface(
lambda u, v: [u, v, np.exp(-(u**2) - v**2)],
u_range=(-3, 3),
v_range=(-3, 3),
resolution=(101, 101),
)
gaussian.set_color(BLUE, 0.8)
gaussian.match_width(kernel_array)
gaussian.stretch(2, 2)
def update_surface(surface, kernel_array=kernel_array):
surface.move_to(kernel_array, IN)
update_surface(gaussian)
self.play(
FadeIn(gaussian),
frame.set_phi, 70 * DEGREES,
frame.set_theta, 10 * DEGREES,
run_time=3
)
self.wait()
self.play(
frame.set_height, 8,
frame.set_theta, 0,
frame.set_x, 0,
run_time=3,
)
# More walking
walk(200, len(pixel_array), time=10, surface=gaussian)
self.wait()
self.play(frame.to_default_state, run_time=2)
self.wait()

View file

@ -0,0 +1,295 @@
from manimlib.imports import *
class Diffusion1D(Scene):
CONFIG = {
"n_dots": 100,
"y_range": (0, 100, 10),
"x_range": (-20, 20),
"show_y_axis": False,
"dot_radius": 0.07,
"dot_opacity": 0.5,
"dither_dots": True,
"total_steps": 100,
"clip_at_bounds": True,
}
def construct(self):
y_range = self.y_range
x_range = self.x_range
# Set up axes
axes = Axes(
x_range=x_range,
y_range=y_range,
axis_config={
"stroke_width": 2,
"include_tip": False,
},
width=FRAME_WIDTH,
height=FRAME_HEIGHT - 1,
)
axes.x_axis.add_numbers(
range(*x_range, 5)
)
if self.show_y_axis:
axes.y_axis.add_numbers(
np.arange(*y_range) + y_range[2],
number_config={'height': 0.2}
)
else:
axes.y_axis.scale(0, about_point=axes.c2p(0, 0))
axes.center()
x_unit = axes.c2p(1, 0)[0] - axes.c2p(0, 0)[0]
y_unit = axes.c2p(0, 1)[1] - axes.c2p(0, 0)[1]
self.add(axes)
# Set up time label
time_label = self.get_time_label()
self.add(time_label)
# Set up dots (make generalizable)
dots = self.get_dots()
dots.move_to(axes.c2p(0, 0))
self.adjust_initial_dot_positions(dots, x_unit)
self.add(dots)
# Set up bars
bars = VGroup()
epsilon = 1e-6
for x in range(x_range[0], x_range[1] + 1):
bar = Rectangle()
bar.x = x
bar.set_width(0.5 * x_unit)
bar.set_height(epsilon, stretch=True)
bar.move_to(axes.c2p(x, 0), DOWN)
bars.add(bar)
bars.set_fill(GREY, 0.8)
bars.set_stroke(LIGHT_GREY, 0.2)
def update_bars(bars, dots=dots, y_unit=y_unit, epsilon=epsilon):
for bar in bars:
count = 0
for dot in dots:
if dot.x == bar.x:
count += 1
bar.set_height(
count * y_unit + epsilon,
about_edge=bar.get_bottom(),
stretch=True
)
update_bars(bars, dots)
self.add(bars, dots)
# Include rule for updating
def step(dots=dots, bars=bars,
x_unit=x_unit, x_range=x_range,
time_label=time_label):
time_label[1].increment_value()
for dot in dots:
u = random.choice([-1, 1])
if self.clip_at_bounds:
# Boundary condition
if dot.x == x_range[0]:
u = max(0, u)
elif dot.x == x_range[1]:
u = min(0, u)
dot.shift(u * x_unit * RIGHT)
dot.x += u
update_bars(bars)
# Let it play out.
for t in range(self.total_steps):
if t < 6:
self.wait()
else:
self.wait(0.1)
step()
def get_time_label(self):
time_label = VGroup(
TextMobject("Time: "),
Integer(0),
)
time_label.arrange(RIGHT, aligned_edge=DOWN)
time_label.to_corner(UR)
time_label.shift(0.5 * LEFT)
return time_label
def get_dots(self):
dots = VGroup(*[Dot() for x in range(self.n_dots)])
dots.set_height(2 * self.dot_radius)
dots.set_fill(opacity=self.dot_opacity)
for dot in dots:
dot.x = 0
if self.dither_dots:
dot.shift(
self.dot_radius * random.random() * RIGHT,
self.dot_radius * random.random() * UP
)
dot.set_color(interpolate_color(
BLUE_B, BLUE_D, random.random()
))
return dots
def adjust_initial_dot_positions(self, dots, x_unit):
pass
class Diffusion1DWith1Dot(Diffusion1D):
CONFIG = {
"n_dots": 1,
"dot_radius": 0.1,
"dot_opacity": 1,
"dither_dots": False,
}
class Diffusion1DStepFunction(Diffusion1D):
CONFIG = {
"n_dots": 15000,
"initial_range": (-20, 0),
"y_range": (0, 1000, 100),
"total_steps": 100,
}
def adjust_initial_dot_positions(self, dots, x_unit):
initial_positions = list(range(*self.initial_range))
for n, dot in enumerate(dots):
x = initial_positions[n % len(initial_positions)]
dot.x = x
dot.shift(x * x_unit * RIGHT)
class Diffusion1DStepFunctionGraphed(Diffusion1DStepFunction):
CONFIG = {
"show_y_axis": True,
"total_steps": 500,
}
class DiffusionDeltaGraphed(Diffusion1D):
CONFIG = {
"n_dots": 1000,
"show_y_axis": True,
"y_range": (0, 1000, 100),
"total_steps": 200,
}
class DiffusionDeltaGraphedTripleStart(DiffusionDeltaGraphed):
CONFIG = {
"n_dots": 2000,
}
def adjust_initial_dot_positions(self, dots, x_unit):
for n, dot in enumerate(dots):
x = int(n % 4 - 1.5)
dot.x = x
dot.shift(x * x_unit * RIGHT)
class DiffusionDeltaGraphedShowingMean(DiffusionDeltaGraphed):
CONFIG = {
"n_dots": 10000,
"y_range": (0, 10000, 1000),
"clip_at_bounds": False,
"total_steps": 100,
}
def adjust_initial_dot_positions(self, dots, x_unit):
# Hack, just using this to add something new and updated
label = VGroup(
TexMobject("\\overline{x^2} = "),
DecimalNumber(0),
)
label.arrange(RIGHT)
label.to_corner(UL)
label.add_updater(lambda m: m[1].set_value(np.mean([
dot.x**2 for dot in dots
])))
self.add(label)
class Diffusion2D(Diffusion1D):
CONFIG = {
"n_dots": 100,
"dot_opacity": 0.5,
"dither_dots": True,
"grid_dimensions": (19, 35),
}
def construct(self):
grid_dimensions = self.grid_dimensions
# Setup grid
grid = VGroup(*[
Square()
for x in range(grid_dimensions[0])
for y in range(grid_dimensions[1])
])
grid.arrange_in_grid(*grid_dimensions, buff=0)
grid.set_height(FRAME_HEIGHT)
grid.set_stroke(LIGHT_GREY, 1)
self.add(grid)
step_size = get_norm(grid[1].get_center() - grid[0].get_center())
# Add time label
time_label = self.get_time_label()
br = BackgroundRectangle(time_label)
br.stretch(1.5, 0, about_edge=LEFT)
time_label.add_to_back(br)
self.add(time_label)
# Initialize dots
dots = self.get_dots()
self.add(dots)
# Rule for updating
def step(dots=dots, step_size=step_size, time_label=time_label):
for dot in dots:
vect = random.choice([
UP, DOWN, LEFT, RIGHT, ORIGIN
])
dot.shift(step_size * vect)
time_label[-1].increment_value()
# Let it play out
for t in range(self.total_steps):
if t < 6:
self.wait()
else:
self.wait(0.1)
step()
class Diffusion2D1Dot(Diffusion2D):
CONFIG = {
"n_dots": 1,
"dither_dots": False,
"dot_opacity": 1,
"total_steps": 50,
}
class Diffusion2D10KDots(Diffusion2D):
CONFIG = {
"n_dots": 10000,
"dot_opacity": 0.2,
"total_steps": 200,
}

View file

@ -0,0 +1,252 @@
from manimlib.imports import *
def get_value_grid(n_rows=6, n_cols=6):
random.seed(1)
boxes = VGroup(*[Square() for x in range(n_rows * n_cols)])
array = np.array(list(boxes)).reshape((n_rows, n_cols))
boxes.array = array
boxes.arrange_in_grid(n_rows, n_cols, buff=0)
boxes.set_height(5)
boxes.to_edge(DOWN)
boxes.shift(UP)
boxes.set_stroke(BLUE_D, 2)
for box in boxes:
value = DecimalNumber(
np.round(random.random(), 1),
num_decimal_places=1
)
box.set_fill(WHITE, opacity=0.8 * value.get_value())
box.value = value
value.match_height(box)
value.scale(0.3)
value.move_to(box)
box.add(value)
boxes.array[5, 1].value.set_value(0.4) # Tweaking for examples
return boxes
def highlight_box(box, opacity=0.5, color=PINK):
box.set_stroke(color, opacity)
box[1].set_stroke(BLACK, 2, background=True)
return box
def get_box_highlight(box, color=PINK, stroke_width=8, opacity=0.25):
highlight = SurroundingRectangle(box, buff=0)
highlight.set_fill(color, opacity)
highlight.set_stroke(color, stroke_width)
return highlight
class GreedyAlgorithm(Scene):
def construct(self):
n_rows, n_cols = 6, 6
boxes = get_value_grid(n_rows, n_cols)
self.add(boxes)
last_sum_term = boxes[0].value.copy()
last_sum_term.to_edge(UP)
last_sum_term.set_x(-5)
sum_terms = VGroup()
to_fade = VGroup()
(i, j) = (0, 3)
while i < n_rows:
box = boxes.array[i, j]
value = box.value
box_highlight = get_box_highlight(box)
sum_term = VGroup(
TexMobject("+"),
value.copy()
)
if i == 0:
sum_term[0].set_opacity(0)
sum_term.arrange(RIGHT, buff=0.2)
sum_term.next_to(last_sum_term, RIGHT, buff=0.2)
sum_terms.add(sum_term)
last_sum_term = sum_term
self.play(
FadeIn(box_highlight),
FadeIn(sum_term),
*map(FadeOut, to_fade)
)
i += 1
# Find new j
if i < n_rows:
next_boxes = VGroup()
for nj in range(j - 1, j + 2):
next_boxes.add(boxes.array[i, clip(nj, 0, n_cols - 1)])
next_highlights = VGroup()
for nb in next_boxes:
next_highlights.add(get_box_highlight(nb, stroke_width=4))
self.play(FadeIn(next_highlights))
min_box_index = np.argmin([b.value.get_value() for b in next_boxes])
j = j - 1 + min_box_index
j = clip(j, 0, n_cols - 1) # Am I doing this right?
to_fade = next_highlights
final_sum = VGroup(
TexMobject("="),
DecimalNumber(
sum([np.round(st[1].get_value(), 1) for st in sum_terms]),
height=sum_terms.get_height(),
num_decimal_places=1,
).set_color(YELLOW)
)
final_sum.arrange(RIGHT, buff=0.2)
final_sum.next_to(sum_terms, RIGHT, buff=0.2)
self.play(Write(final_sum))
self.wait()
class RecrusiveExhaustiveSearch(Scene):
def construct(self):
n_rows = 6
n_cols = 6
boxes = get_value_grid(n_rows, n_cols)
self.add(boxes)
seam = [
(i, 3)
for i in range(n_rows)
]
def get_seam_sum(seam):
terms = VGroup(*[boxes.array[i, j].value.copy() for (i, j) in seam])
row = VGroup()
for term in terms[:-1]:
row.add(term)
row.add(TexMobject("+"))
row.add(terms[-1])
row.add(TexMobject("="))
sum_term = DecimalNumber(sum([t.get_value() for t in terms]), num_decimal_places=1)
sum_term.match_height(terms[0])
sum_term.set_color(YELLOW)
row.add(sum_term)
row.arrange(RIGHT, buff=0.15)
row.to_edge(UP)
return row
def get_highlighted_seam(seam):
return VGroup(*[
get_box_highlight(boxes.array[i, j])
for (i, j) in seam
])
def get_all_seams(seam_starts, n_rows=n_rows, n_cols=n_cols):
if seam_starts[0][-1][0] == n_rows - 1:
return seam_starts
new_seams = []
for seam in seam_starts:
i, j = seam[-1]
# Adjacent j values below (i, j)
js = []
if j > 0:
js.append(j - 1)
js.append(j)
if j < n_cols - 1:
js.append(j + 1)
for j_prime in js:
new_seams.append([*seam, (i + 1, j_prime)])
return get_all_seams(new_seams, n_rows, n_cols)
all_seams = get_all_seams([[(0, 3)]])
curr_min_energy = np.inf
curr_best_seam = VGroup()
for seam in all_seams:
ss = get_seam_sum(seam)
hs = get_highlighted_seam(seam)
energy = ss[-1].get_value()
if energy < curr_min_energy:
curr_min_energy = energy
curr_best_seam = VGroup(ss, hs)
self.add(ss, hs)
self.wait(0.05)
self.remove(ss, hs)
self.add(curr_best_seam)
self.wait()
class DynamicProgrammingApproachSearch(Scene):
def construct(self):
n_rows = 6
n_cols = 6
boxes = get_value_grid(n_rows, n_cols)
boxes.to_corner(DL)
boxes.shift(UP)
self.add(boxes)
new_boxes = get_value_grid(n_rows, n_cols)
new_boxes.to_corner(DR)
new_boxes.shift(UP)
for box in new_boxes:
box.set_fill(opacity=0)
box.value.set_fill(opacity=0)
self.add(new_boxes)
left_title = TextMobject("Energy")
right_title = TextMobject("Minimal energy to bottom")
left_title.next_to(boxes, UP)
right_title.next_to(new_boxes, UP)
self.add(left_title, right_title)
op_factor = 0.5
movers = VGroup()
for j in range(n_cols):
box = boxes.array[n_rows - 1, j]
new_box = new_boxes.array[n_rows - 1, j]
new_box.generate_target()
new_box.target.match_style(box)
new_box.target.set_fill(TEAL, opacity=op_factor * new_box.value.get_value())
new_box.target[1].set_fill(WHITE, 1)
movers.add(new_box)
self.play(LaggedStartMap(MoveToTarget, movers))
self.wait()
for i in range(n_rows - 2, -1, -1):
for j in range(n_cols):
box = boxes.array[i, j]
new_box = new_boxes.array[i, j]
box_highlight = get_box_highlight(box)
new_box_highlight = get_box_highlight(new_box)
boxes_below = [
new_boxes.array[i + 1, j]
for j in range(j - 1, j + 2)
if 0 <= j < n_cols
]
index = np.argmin([bb.value.get_value() for bb in boxes_below])
low_box = boxes_below[index]
low_box_highlight = get_box_highlight(low_box, stroke_width=3)
low_box_highlight.fade(0.5)
new_box.value.set_value(new_box.value.get_value() + low_box.value.get_value())
new_box.generate_target()
new_box.target.set_fill(TEAL, opacity=op_factor * new_box.value.get_value())
new_box.target[1].set_fill(WHITE, 1)
self.play(
FadeIn(box_highlight),
FadeIn(new_box_highlight),
FadeIn(low_box_highlight),
MoveToTarget(new_box),
)
self.play(
FadeOut(box_highlight),
FadeOut(new_box_highlight),
FadeOut(low_box_highlight),
)

View file

@ -0,0 +1,32 @@
from manimlib.imports import *
def get_value_grid(n_rows=10, n_cols=10):
boxes = VGroup(*[Square() for x in range(n_rows * n_cols)])
boxes.arrange_in_grid(n_rows, n_cols, buff=0)
boxes.set_height(6)
boxes.set_style(GREY_B, 2)
for box in boxes:
value = DecimalNumber(random.random(), num_decimal_places=1)
box.value = value
value.set_height(0.7 * box.get_height())
value.move_to(box)
box.add(value)
return boxes
class GreedyAlgorithm(Scene):
def construct(self):
value_grid = get_value_grid()
self.add(value_grid)
class RecrusiveExhaustiveSearch(Scene):
def construct(self):
pass
class DynamicProgrammingApproachSearch(Scene):
def construct(self):
pass