mirror of
https://github.com/3b1b/manim.git
synced 2025-11-14 09:17:51 +00:00
Animations for some 18S191 lectures
This commit is contained in:
parent
25bab8c10e
commit
171042b8d7
4 changed files with 779 additions and 0 deletions
200
from_3b1b/active/18S191/convolutions.py
Normal file
200
from_3b1b/active/18S191/convolutions.py
Normal 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()
|
||||
295
from_3b1b/active/18S191/diffusion.py
Normal file
295
from_3b1b/active/18S191/diffusion.py
Normal 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,
|
||||
}
|
||||
252
from_3b1b/active/18S191/dynamic_prog.py
Normal file
252
from_3b1b/active/18S191/dynamic_prog.py
Normal 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),
|
||||
)
|
||||
32
from_3b1b/active/18S191/seam_carving.py
Normal file
32
from_3b1b/active/18S191/seam_carving.py
Normal 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
|
||||
Loading…
Add table
Reference in a new issue