mirror of
https://github.com/3b1b/manim.git
synced 2025-04-13 09:47:07 +00:00
2085 lines
64 KiB
Python
2085 lines
64 KiB
Python
#!/usr/bin/env python
|
|
# -*- coding: utf-8 -*-
|
|
|
|
from manimlib.imports import *
|
|
from old_projects.efvgt import ConfettiSpiril
|
|
|
|
#revert_to_original_skipping_status
|
|
|
|
|
|
class HappyHolidays(TeacherStudentsScene):
|
|
def construct(self):
|
|
hats = VGroup(*list(map(
|
|
self.get_hat, self.pi_creatures
|
|
)))
|
|
self.add(self.get_snowflakes())
|
|
self.change_student_modes(
|
|
*["hooray"]*3,
|
|
look_at_arg = FRAME_Y_RADIUS*UP,
|
|
added_anims = [self.teacher.change, "hooray"]
|
|
)
|
|
self.play(LaggedStartMap(
|
|
DrawBorderThenFill, hats
|
|
), Animation(self.pi_creatures))
|
|
self.change_student_modes(
|
|
"happy", "wave_2", "wave_1",
|
|
look_at_arg = FRAME_Y_RADIUS*UP,
|
|
)
|
|
self.look_at(self.teacher.get_corner(UP+LEFT))
|
|
self.wait(2)
|
|
self.play(self.teacher.change, "happy")
|
|
self.wait(2)
|
|
|
|
def get_hat(self, pi):
|
|
hat = SVGMobject(
|
|
file_name = "santa_hat",
|
|
height = 0.5*pi.get_height()
|
|
)
|
|
hat.rotate(-np.pi/12)
|
|
vect = RIGHT
|
|
if not pi.is_flipped():
|
|
hat.flip()
|
|
vect = LEFT
|
|
hat.set_fill(RED_D)
|
|
hat[0].remove(hat[0][1])
|
|
hat[0].set_fill("#EEE")
|
|
hat[2].set_fill(WHITE)
|
|
hat.add(hat[0])
|
|
hat.next_to(pi.body, UP, buff = SMALL_BUFF)
|
|
hat.shift(SMALL_BUFF*vect)
|
|
return hat
|
|
|
|
def get_snowflakes(self, n_flakes = 50):
|
|
snowflakes = VGroup(*[
|
|
SVGMobject(
|
|
file_name = "snowflake",
|
|
height = 0.5,
|
|
stroke_width = 0,
|
|
fill_opacity = 0.75,
|
|
fill_color = WHITE,
|
|
).rotate(np.pi/12, RIGHT)
|
|
for x in range(n_flakes)
|
|
])
|
|
def random_confetti_spiral(mob, **kwargs):
|
|
return ConfettiSpiril(
|
|
mob, x_start = 2*random.random()*FRAME_X_RADIUS - FRAME_X_RADIUS,
|
|
**kwargs
|
|
)
|
|
snowflake_spirils = LaggedStartMap(
|
|
random_confetti_spiral, snowflakes,
|
|
run_time = 10,
|
|
rate_func = lambda x : x,
|
|
)
|
|
return turn_animation_into_updater(snowflake_spirils)
|
|
|
|
class UtilitiesPuzzleScene(Scene):
|
|
CONFIG = {
|
|
"object_height" : 0.75,
|
|
"h_distance" : 2,
|
|
"v_distance" : 2,
|
|
"line_width" : 4,
|
|
}
|
|
def setup_configuration(self):
|
|
houses = VGroup()
|
|
for x in range(3):
|
|
house = SVGMobject(file_name = "house")
|
|
house.set_height(self.object_height)
|
|
house.set_fill(LIGHT_GREY)
|
|
house.move_to(x*self.h_distance*RIGHT)
|
|
houses.add(house)
|
|
houses.move_to(self.v_distance*UP/2)
|
|
|
|
utilities = VGroup(*[
|
|
self.get_utility(u, c).move_to(x*self.h_distance*RIGHT)
|
|
for x, u, c in zip(
|
|
it.count(),
|
|
["fire", "electricity", "water"],
|
|
[RED_D, YELLOW_C, BLUE_D]
|
|
)
|
|
])
|
|
utilities.move_to(self.v_distance*DOWN/2)
|
|
objects = VGroup(houses, utilities)
|
|
bounding_box = SurroundingRectangle(
|
|
objects,
|
|
buff = MED_LARGE_BUFF,
|
|
stroke_width = 0
|
|
)
|
|
objects.add(bounding_box)
|
|
self.add_foreground_mobjects(objects)
|
|
self.set_variables_as_attrs(
|
|
houses, utilities, objects, bounding_box
|
|
)
|
|
|
|
def get_utility(self, name, color):
|
|
circle = Circle(
|
|
fill_color = color,
|
|
fill_opacity = 1,
|
|
stroke_width = 0,
|
|
)
|
|
utility = SVGMobject(
|
|
file_name = name,
|
|
height = 0.65*circle.get_height(),
|
|
fill_color = WHITE,
|
|
)
|
|
if color == YELLOW:
|
|
utility.set_fill(DARK_GREY)
|
|
utility.move_to(circle)
|
|
circle.add(utility)
|
|
circle.set_height(self.object_height)
|
|
return circle
|
|
|
|
def get_line(
|
|
self, utility_index, house_index,
|
|
*midpoints,
|
|
**kwargs
|
|
):
|
|
prop = kwargs.pop("prop", 1.0)
|
|
utility = self.utilities[utility_index]
|
|
points = [utility.get_center()]
|
|
points += list(midpoints)
|
|
points += [self.houses[house_index].get_center()]
|
|
line = Line(
|
|
points[0], points[-1],
|
|
color = utility[0].get_color(),
|
|
stroke_width = self.line_width
|
|
)
|
|
line.set_points_smoothly(points)
|
|
line.pointwise_become_partial(line, 0, prop)
|
|
return line
|
|
|
|
def get_almost_solution_lines(self):
|
|
bb = self.bounding_box
|
|
return VGroup(
|
|
VGroup(
|
|
self.get_line(0, 0),
|
|
self.get_line(0, 1),
|
|
self.get_line(0, 2, bb.get_top()),
|
|
),
|
|
VGroup(
|
|
self.get_line(1, 0, bb.get_corner(DOWN+LEFT)),
|
|
self.get_line(1, 1),
|
|
self.get_line(1, 2, bb.get_corner(DOWN+RIGHT)),
|
|
),
|
|
VGroup(
|
|
self.get_line(2, 0, bb.get_top()),
|
|
self.get_line(2, 1),
|
|
self.get_line(2, 2),
|
|
),
|
|
)
|
|
|
|
def get_straight_lines(self):
|
|
return VGroup(*[
|
|
VGroup(*[self.get_line(i, j) for j in range(3)])
|
|
for i in range(3)
|
|
])
|
|
|
|
def get_no_crossing_words(self):
|
|
arrow = Vector(DOWN)
|
|
arrow.next_to(self.bounding_box.get_top(), UP, SMALL_BUFF)
|
|
words = TextMobject("No crossing!")
|
|
words.next_to(arrow, UP, buff = SMALL_BUFF)
|
|
result = VGroup(words, arrow)
|
|
result.set_color("RED")
|
|
return result
|
|
|
|
def get_region(self, *bounding_edges):
|
|
region = VMobject(mark_paths_closed = True)
|
|
for i, edge in enumerate(bounding_edges):
|
|
new_edge = edge.copy()
|
|
if i%2 == 1:
|
|
new_edge.points = new_edge.points[::-1]
|
|
region.append_vectorized_mobject(new_edge)
|
|
region.set_stroke(width = 0)
|
|
region.set_fill(WHITE, opacity = 1)
|
|
return region
|
|
|
|
def convert_objects_to_dots(self, run_time = 2):
|
|
group = VGroup(*it.chain(self.houses, self.utilities))
|
|
for mob in group:
|
|
mob.target = Dot(color = mob.get_color())
|
|
mob.target.scale(2)
|
|
mob.target.move_to(mob)
|
|
self.play(LaggedStartMap(MoveToTarget, group, run_time = run_time))
|
|
|
|
class PauseIt(PiCreatureScene):
|
|
def construct(self):
|
|
morty = self.pi_creatures
|
|
morty.center().to_edge(DOWN)
|
|
self.pi_creature_says(
|
|
"Pause it!", target_mode = "surprised"
|
|
)
|
|
self.wait(2)
|
|
|
|
class AboutToyPuzzles(UtilitiesPuzzleScene, TeacherStudentsScene, ThreeDScene):
|
|
def construct(self):
|
|
self.remove(self.pi_creatures)
|
|
self.lay_out_puzzle()
|
|
self.point_to_abstractions()
|
|
self.show_this_video()
|
|
|
|
def lay_out_puzzle(self):
|
|
self.setup_configuration()
|
|
houses, utilities = self.houses, self.utilities
|
|
lines = VGroup(*it.chain(*self.get_almost_solution_lines()))
|
|
no_crossing_words = self.get_no_crossing_words()
|
|
|
|
self.remove(self.objects)
|
|
self.play(
|
|
ReplacementTransform(
|
|
VGroup(houses[1], houses[1]).copy().fade(1),
|
|
VGroup(houses[0], houses[2]),
|
|
rate_func = squish_rate_func(smooth, 0.35, 1),
|
|
run_time = 2,
|
|
),
|
|
FadeIn(houses[1]),
|
|
LaggedStartMap(DrawBorderThenFill, utilities, run_time = 2)
|
|
)
|
|
self.play(
|
|
LaggedStartMap(
|
|
ShowCreation, lines,
|
|
run_time = 3
|
|
),
|
|
Animation(self.objects)
|
|
)
|
|
self.play(
|
|
Write(no_crossing_words[0]),
|
|
GrowArrow(no_crossing_words[1]),
|
|
)
|
|
self.wait()
|
|
|
|
self.objects.add_to_back(lines, no_crossing_words)
|
|
|
|
def point_to_abstractions(self):
|
|
objects = self.objects
|
|
objects.generate_target()
|
|
objects.target.scale(0.5)
|
|
objects.target.move_to(
|
|
(FRAME_Y_RADIUS*DOWN + FRAME_X_RADIUS*LEFT)/2
|
|
)
|
|
|
|
eulers = TexMobject(*"V-E+F=2")
|
|
eulers.set_color_by_tex_to_color_map({
|
|
"V" : RED,
|
|
"E" : GREEN,
|
|
"F" : BLUE,
|
|
})
|
|
eulers.to_edge(UP, buff = 2)
|
|
|
|
cube = Cube()
|
|
cube.set_stroke(WHITE, 2)
|
|
cube.set_height(0.75)
|
|
cube.pose_at_angle()
|
|
cube.next_to(eulers, UP)
|
|
|
|
tda = TextMobject("Topological \\\\ Data Analysis")
|
|
tda.move_to(DOWN + 4*RIGHT)
|
|
|
|
arrow_from_eulers = Arrow(
|
|
eulers.get_bottom(), tda.get_corner(UP+LEFT),
|
|
color = WHITE
|
|
)
|
|
|
|
self.play(
|
|
objects.scale, 0.5,
|
|
objects.move_to, DOWN + 4*LEFT,
|
|
)
|
|
arrow_to_eulers = Arrow(
|
|
self.houses[2].get_corner(UP+LEFT),
|
|
eulers.get_bottom(),
|
|
color = WHITE
|
|
)
|
|
always_rotate(cube, axis=UP)
|
|
self.play(
|
|
GrowArrow(arrow_to_eulers),
|
|
Write(eulers),
|
|
FadeIn(cube)
|
|
)
|
|
self.wait(5)
|
|
self.play(
|
|
GrowArrow(arrow_from_eulers),
|
|
Write(tda)
|
|
)
|
|
self.wait(2)
|
|
|
|
self.set_variables_as_attrs(
|
|
eulers, cube, tda,
|
|
arrows = VGroup(arrow_to_eulers, arrow_from_eulers),
|
|
)
|
|
|
|
def show_this_video(self):
|
|
screen_rect = FullScreenFadeRectangle(
|
|
stroke_color = WHITE,
|
|
stroke_width = 2,
|
|
fill_opacity = 0,
|
|
)
|
|
everything = VGroup(
|
|
self.objects, self.arrows, self.eulers,
|
|
self.cube, self.tda,
|
|
screen_rect,
|
|
)
|
|
|
|
self.teacher.save_state()
|
|
self.teacher.fade(1)
|
|
|
|
self.play(
|
|
everything.scale, 0.5,
|
|
everything.next_to, self.teacher, UP+LEFT,
|
|
self.teacher.restore,
|
|
self.teacher.change, "raise_right_hand", UP+LEFT,
|
|
LaggedStartMap(FadeIn, self.students)
|
|
)
|
|
self.change_student_modes(
|
|
*["pondering"]*3, look_at_arg = everything
|
|
)
|
|
self.wait(5)
|
|
|
|
class ThisPuzzleIsHard(UtilitiesPuzzleScene, PiCreatureScene):
|
|
def construct(self):
|
|
self.introduce_components()
|
|
self.failed_attempts()
|
|
self.ask_meta_puzzle()
|
|
|
|
def introduce_components(self):
|
|
randy = self.pi_creature
|
|
|
|
try_it = TextMobject("Try it yourself!")
|
|
try_it.to_edge(UP)
|
|
|
|
self.setup_configuration()
|
|
houses, utilities = self.houses, self.utilities
|
|
self.remove(self.objects)
|
|
house = houses[0]
|
|
|
|
puzzle_words = TextMobject("""
|
|
Puzzle: Connect each house to \\\\
|
|
each utility without crossing lines.
|
|
""")
|
|
# puzzle_words.next_to(self.objects, UP)
|
|
puzzle_words.to_edge(UP)
|
|
|
|
self.add(try_it)
|
|
self.play(Animation(try_it))
|
|
self.play(
|
|
LaggedStartMap(DrawBorderThenFill, houses),
|
|
LaggedStartMap(GrowFromCenter, utilities),
|
|
try_it.set_width, house.get_width(),
|
|
try_it.fade, 1,
|
|
try_it.move_to, house,
|
|
self.pi_creature.change, "happy",
|
|
)
|
|
self.play(LaggedStartMap(FadeIn, puzzle_words))
|
|
|
|
self.add_foreground_mobjects(self.objects)
|
|
self.set_variables_as_attrs(puzzle_words)
|
|
|
|
def failed_attempts(self):
|
|
bb = self.bounding_box
|
|
utilities = self.utilities
|
|
houses = self.houses
|
|
randy = self.pi_creature
|
|
|
|
line_sets = [
|
|
[
|
|
self.get_line(0, 0),
|
|
self.get_line(1, 1),
|
|
self.get_line(2, 2),
|
|
self.get_line(0, 1),
|
|
self.get_line(2, 1),
|
|
self.get_line(0, 2, bb.get_corner(UP+LEFT)),
|
|
self.get_line(
|
|
2, 0, bb.get_corner(DOWN+LEFT),
|
|
prop = 0.85,
|
|
),
|
|
self.get_line(
|
|
2, 0, bb.get_corner(UP+RIGHT), bb.get_top(),
|
|
prop = 0.73,
|
|
),
|
|
],
|
|
[
|
|
self.get_line(0, 0),
|
|
self.get_line(1, 1),
|
|
self.get_line(2, 2),
|
|
self.get_line(0, 1),
|
|
self.get_line(1, 2),
|
|
self.get_line(
|
|
2, 0,
|
|
bb.get_bottom(),
|
|
bb.get_corner(DOWN+LEFT)
|
|
),
|
|
self.get_line(0, 2, bb.get_top()),
|
|
self.get_line(
|
|
2, 1,
|
|
utilities[1].get_bottom() + MED_SMALL_BUFF*DOWN,
|
|
utilities[1].get_left() + MED_SMALL_BUFF*LEFT,
|
|
),
|
|
self.get_line(
|
|
1, 0, houses[2].get_corner(UP+LEFT) + MED_LARGE_BUFF*LEFT,
|
|
prop = 0.49,
|
|
),
|
|
self.get_line(
|
|
1, 2, bb.get_right(),
|
|
prop = 0.25,
|
|
),
|
|
],
|
|
[
|
|
self.get_line(0, 0),
|
|
self.get_line(0, 1),
|
|
self.get_line(0, 2, bb.get_top()),
|
|
self.get_line(1, 0, bb.get_corner(DOWN+LEFT)),
|
|
self.get_line(1, 1),
|
|
self.get_line(1, 2, bb.get_corner(DOWN+RIGHT)),
|
|
self.get_line(2, 1),
|
|
self.get_line(2, 2),
|
|
self.get_line(2, 0, bb.get_top(), prop = 0.45),
|
|
self.get_line(2, 0, prop = 0.45),
|
|
],
|
|
]
|
|
modes = ["confused", "sassy", "angry"]
|
|
|
|
for line_set, mode in zip(line_sets, modes):
|
|
good_lines = VGroup(*line_set[:-2])
|
|
bad_lines = line_set[-2:]
|
|
self.play(LaggedStartMap(ShowCreation, good_lines))
|
|
for bl in bad_lines:
|
|
self.play(
|
|
ShowCreation(
|
|
bl,
|
|
rate_func = bezier([0, 0, 0, 1, 1, 1, 1, 1, 0.3, 1, 1]),
|
|
run_time = 1.5
|
|
),
|
|
randy.change, mode,
|
|
)
|
|
self.play(ShowCreation(
|
|
bl, rate_func = lambda t : smooth(1-t),
|
|
))
|
|
self.remove(bl)
|
|
self.play(LaggedStartMap(FadeOut, good_lines))
|
|
|
|
def ask_meta_puzzle(self):
|
|
randy = self.pi_creature
|
|
group = VGroup(
|
|
self.puzzle_words,
|
|
self.objects,
|
|
)
|
|
rect = SurroundingRectangle(group, color = BLUE, buff = MED_LARGE_BUFF)
|
|
group.add(rect)
|
|
group.generate_target()
|
|
group.target.scale(0.75)
|
|
group.target.shift(DOWN)
|
|
group[-1].set_stroke(width = 0)
|
|
|
|
meta_puzzle_words = TextMobject("""
|
|
Meta-puzzle: Prove that this\\\\
|
|
is impossible.
|
|
""")
|
|
meta_puzzle_words.next_to(group.target, UP)
|
|
meta_puzzle_words.set_color(BLUE)
|
|
|
|
self.play(
|
|
MoveToTarget(group),
|
|
randy.change, "pondering"
|
|
)
|
|
self.play(Write(meta_puzzle_words))
|
|
self.play(randy.change, "confused")
|
|
|
|
straight_lines = self.get_straight_lines()
|
|
almost_solution_lines = self.get_almost_solution_lines()
|
|
self.play(LaggedStartMap(
|
|
ShowCreation, straight_lines,
|
|
run_time = 2,
|
|
lag_ratio = 0.8
|
|
), Blink(randy))
|
|
self.play(Transform(
|
|
straight_lines, almost_solution_lines,
|
|
run_time = 3,
|
|
lag_ratio = 0.5
|
|
))
|
|
self.wait()
|
|
|
|
######
|
|
|
|
def create_pi_creature(self):
|
|
return Randolph().to_corner(DOWN+LEFT)
|
|
|
|
class IntroduceGraph(PiCreatureScene):
|
|
def construct(self):
|
|
pi_creatures = self.pi_creatures
|
|
dots = VGroup(*[
|
|
Dot(color = pi.get_color()).scale(2).move_to(pi)
|
|
for pi in pi_creatures
|
|
])
|
|
lines = VGroup(*[
|
|
Line(pi1.get_center(), pi2.get_center())
|
|
for pi1, pi2 in it.combinations(pi_creatures, 2)
|
|
])
|
|
|
|
graph_word = TextMobject("``", "", "Graph", "''", arg_separator = "")
|
|
graph_word.to_edge(UP)
|
|
planar_graph_word = TextMobject("``", "Planar", " graph", "''", arg_separator = "")
|
|
planar_graph_word.move_to(graph_word)
|
|
|
|
vertices_word = TextMobject("Vertices")
|
|
vertices_word.to_edge(RIGHT, buff = LARGE_BUFF)
|
|
vertices_word.set_color(YELLOW)
|
|
|
|
vertex_arrows = VGroup(*[
|
|
Arrow(vertices_word.get_left(), dot)
|
|
for dot in dots[-2:]
|
|
])
|
|
|
|
edge_word = TextMobject("Edge")
|
|
edge_word.next_to(lines, LEFT, LARGE_BUFF)
|
|
edge_arrow = Arrow(
|
|
edge_word, lines, buff = SMALL_BUFF,
|
|
color = WHITE
|
|
)
|
|
|
|
self.play(LaggedStartMap(GrowFromCenter, pi_creatures))
|
|
self.play(
|
|
LaggedStartMap(ShowCreation, lines),
|
|
LaggedStartMap(
|
|
ApplyMethod, pi_creatures,
|
|
lambda pi : (pi.change, "pondering", lines)
|
|
)
|
|
)
|
|
self.play(Write(graph_word))
|
|
self.play(ReplacementTransform(
|
|
pi_creatures, dots,
|
|
run_time = 2,
|
|
lag_ratio = 0.5
|
|
))
|
|
self.add_foreground_mobjects(dots)
|
|
self.play(
|
|
FadeIn(vertex_arrows),
|
|
FadeIn(vertices_word),
|
|
)
|
|
self.wait()
|
|
self.play(LaggedStartMap(
|
|
ApplyMethod, lines,
|
|
lambda l : (l.rotate_in_place, np.pi/12),
|
|
rate_func = wiggle
|
|
))
|
|
self.play(
|
|
FadeIn(edge_word),
|
|
GrowArrow(edge_arrow),
|
|
)
|
|
self.wait(2)
|
|
line = lines[2]
|
|
self.play(
|
|
line.set_points_smoothly, [
|
|
line.get_start(),
|
|
dots.get_left() + MED_SMALL_BUFF*LEFT,
|
|
dots.get_corner(DOWN+LEFT) + MED_SMALL_BUFF*(DOWN+LEFT),
|
|
dots.get_bottom() + MED_SMALL_BUFF*DOWN,
|
|
line.get_end(),
|
|
],
|
|
VGroup(edge_word, edge_arrow).shift, MED_LARGE_BUFF*LEFT,
|
|
)
|
|
self.wait()
|
|
self.play(ReplacementTransform(graph_word, planar_graph_word))
|
|
self.wait(2)
|
|
|
|
###
|
|
|
|
def create_pi_creatures(self):
|
|
pis = VGroup(
|
|
PiCreature(color = BLUE_D),
|
|
PiCreature(color = GREY_BROWN),
|
|
PiCreature(color = BLUE_C).flip(),
|
|
PiCreature(color = BLUE_E).flip(),
|
|
)
|
|
pis.scale(0.5)
|
|
pis.arrange_in_grid(buff = 2)
|
|
return pis
|
|
|
|
class IsK33Planar(UtilitiesPuzzleScene):
|
|
def construct(self):
|
|
self.setup_configuration()
|
|
self.objects.shift(MED_LARGE_BUFF*DOWN)
|
|
|
|
straight_lines = self.get_straight_lines()
|
|
almost_solution_lines = self.get_almost_solution_lines()
|
|
|
|
question = TextMobject("Is", "this graph", "planar?")
|
|
question.set_color_by_tex("this graph", YELLOW)
|
|
question.to_edge(UP)
|
|
brace = Brace(question.get_part_by_tex("graph"), DOWN, buff = SMALL_BUFF)
|
|
fancy_name = brace.get_text(
|
|
"``Complete bipartite graph $K_{3, 3}$''",
|
|
buff = SMALL_BUFF
|
|
)
|
|
fancy_name.set_color(YELLOW)
|
|
|
|
self.add(question)
|
|
self.convert_objects_to_dots()
|
|
self.play(LaggedStartMap(ShowCreation, straight_lines))
|
|
self.play(
|
|
GrowFromCenter(brace),
|
|
LaggedStartMap(FadeIn, fancy_name),
|
|
)
|
|
self.play(ReplacementTransform(
|
|
straight_lines, almost_solution_lines,
|
|
run_time = 3,
|
|
lag_ratio = 0.5
|
|
))
|
|
self.wait(2)
|
|
|
|
class TwoKindsOfViewers(PiCreatureScene, UtilitiesPuzzleScene):
|
|
def construct(self):
|
|
self.setup_configuration()
|
|
objects = self.objects
|
|
objects.remove(self.bounding_box)
|
|
lines = self.get_straight_lines()
|
|
objects.add_to_back(lines)
|
|
objects.scale(0.75)
|
|
objects.next_to(ORIGIN, RIGHT, LARGE_BUFF)
|
|
self.remove(objects)
|
|
|
|
pi1, pi2 = self.pi_creatures
|
|
words = TextMobject(
|
|
"$(V-E+F)$", "kinds of viewers"
|
|
)
|
|
words.to_edge(UP)
|
|
eulers = words.get_part_by_tex("V-E+F")
|
|
eulers.set_color(GREEN)
|
|
non_eulers = VGroup(*[m for m in words if m is not eulers])
|
|
|
|
self.add(words)
|
|
self.wait()
|
|
self.play(
|
|
pi1.shift, 2*LEFT,
|
|
pi2.shift, 2*RIGHT,
|
|
)
|
|
|
|
know_eulers = TextMobject("Know about \\\\ Euler's formula")
|
|
know_eulers.next_to(pi1, DOWN)
|
|
know_eulers.set_color(GREEN)
|
|
dont = TextMobject("Don't")
|
|
dont.next_to(pi2, DOWN)
|
|
dont.set_color(RED)
|
|
|
|
self.play(
|
|
FadeIn(know_eulers),
|
|
pi1.change, "hooray",
|
|
)
|
|
self.play(
|
|
FadeIn(dont),
|
|
pi2.change, "maybe", eulers,
|
|
)
|
|
self.wait()
|
|
self.pi_creature_thinks(
|
|
pi1, "",
|
|
bubble_kwargs = {"width" : 3, "height" : 2},
|
|
target_mode = "thinking"
|
|
)
|
|
self.play(pi2.change, "confused", eulers)
|
|
self.wait()
|
|
|
|
### Out of thin air
|
|
self.play(*list(map(FadeOut, [
|
|
non_eulers, pi1, pi2, pi1.bubble,
|
|
know_eulers, dont
|
|
])))
|
|
self.play(eulers.next_to, ORIGIN, LEFT, LARGE_BUFF)
|
|
arrow = Arrow(eulers, objects, color = WHITE)
|
|
self.play(
|
|
GrowArrow(arrow),
|
|
LaggedStartMap(DrawBorderThenFill, VGroup(*it.chain(*objects)))
|
|
)
|
|
self.wait()
|
|
self.play(
|
|
objects.move_to, eulers, RIGHT,
|
|
eulers.move_to, objects, LEFT,
|
|
path_arc = np.pi,
|
|
run_time = 1.5,
|
|
)
|
|
self.wait(2)
|
|
|
|
###
|
|
|
|
def create_pi_creatures(self):
|
|
group = VGroup(Randolph(color = BLUE_C), Randolph())
|
|
group.scale(0.7)
|
|
group.shift(MED_LARGE_BUFF*DOWN)
|
|
return group
|
|
|
|
class IntroduceRegions(UtilitiesPuzzleScene):
|
|
def construct(self):
|
|
self.setup_configuration()
|
|
houses, utilities = self.houses, self.utilities
|
|
objects = self.objects
|
|
lines, line_groups, regions = self.get_lines_line_groups_and_regions()
|
|
back_region = regions[0]
|
|
front_regions = VGroup(*regions[1:])
|
|
|
|
self.convert_objects_to_dots(run_time = 0)
|
|
self.play(LaggedStartMap(
|
|
ShowCreation, lines,
|
|
run_time = 3,
|
|
))
|
|
self.add_foreground_mobjects(lines, objects)
|
|
self.wait()
|
|
for region in front_regions:
|
|
self.play(FadeIn(region))
|
|
self.play(
|
|
FadeIn(back_region),
|
|
Animation(front_regions),
|
|
)
|
|
self.wait()
|
|
self.play(FadeOut(regions))
|
|
|
|
##Paint bucket
|
|
paint_bucket = SVGMobject(
|
|
file_name = "paint_bucket",
|
|
height = 0.5,
|
|
)
|
|
paint_bucket.flip()
|
|
paint_bucket.move_to(8*LEFT + 5*UP)
|
|
|
|
def click(region):
|
|
self.play(
|
|
UpdateFromAlphaFunc(
|
|
region,
|
|
lambda m, a : m.set_fill(opacity = int(2*a)),
|
|
),
|
|
ApplyMethod(
|
|
paint_bucket.scale_in_place, 0.5,
|
|
rate_func = there_and_back,
|
|
),
|
|
run_time = 0.25,
|
|
)
|
|
|
|
self.play(
|
|
paint_bucket.next_to, utilities, DOWN+LEFT, SMALL_BUFF
|
|
)
|
|
click(regions[1])
|
|
self.play(paint_bucket.next_to, utilities[1], UP+RIGHT, SMALL_BUFF)
|
|
click(regions[2])
|
|
self.play(paint_bucket.next_to, houses[1], RIGHT)
|
|
click(regions[3])
|
|
self.play(paint_bucket.move_to, 4*LEFT + 2*UP)
|
|
self.add_foreground_mobjects(front_regions, lines, objects)
|
|
click(back_region)
|
|
self.remove_foreground_mobjects(front_regions)
|
|
self.wait()
|
|
self.play(
|
|
FadeOut(back_region),
|
|
FadeOut(front_regions[0]),
|
|
FadeOut(paint_bucket),
|
|
*list(map(Animation, front_regions[1:]))
|
|
)
|
|
|
|
#Line tries to escape
|
|
point_sets = [
|
|
[
|
|
VGroup(*houses[1:]).get_center(),
|
|
houses[2].get_top() + MED_SMALL_BUFF*UP,
|
|
],
|
|
[
|
|
houses[1].get_top() + SMALL_BUFF*UP,
|
|
utilities[0].get_center(),
|
|
],
|
|
[VGroup(houses[1], utilities[1]).get_center()],
|
|
[
|
|
utilities[2].get_center() + 0.75*(DOWN+RIGHT)
|
|
],
|
|
]
|
|
escape_lines = VGroup(*[
|
|
Line(LEFT, RIGHT).set_points_smoothly(
|
|
[utilities[2].get_center()] + point_set
|
|
)
|
|
for point_set in point_sets
|
|
])
|
|
|
|
self.wait()
|
|
for line in escape_lines:
|
|
self.play(ShowCreation(line,
|
|
rate_func = lambda t : 0.8*smooth(t)
|
|
))
|
|
self.play(ShowCreation(line,
|
|
rate_func = lambda t : smooth(1 - t)
|
|
))
|
|
|
|
def get_lines_line_groups_and_regions(self):
|
|
lines = self.get_almost_solution_lines()
|
|
flat_lines = VGroup(*it.chain(*lines))
|
|
flat_lines.remove(lines[2][0])
|
|
|
|
line_groups = [
|
|
VGroup(*[lines[i][j] for i, j in ij_set])
|
|
for ij_set in [
|
|
[(0, 0), (1, 0), (1, 1), (0, 1)],
|
|
[(1, 1), (2, 1), (2, 2), (1, 2)],
|
|
[(0, 2), (2, 2), (2, 1), (0, 1)],
|
|
[(0, 0), (1, 0), (1, 2), (0, 2)],
|
|
]
|
|
]
|
|
regions = VGroup(*[
|
|
self.get_region(*line_group)
|
|
for line_group in line_groups
|
|
])
|
|
back_region = FullScreenFadeRectangle(fill_opacity = 1 )
|
|
regions.submobjects.pop()
|
|
regions.submobjects.insert(0, back_region)
|
|
front_regions = VGroup(*regions[1:])
|
|
|
|
back_region.set_color(BLUE_E)
|
|
front_regions.set_color_by_gradient(GREEN_E, MAROON_E)
|
|
|
|
return flat_lines, line_groups, regions
|
|
|
|
class FromLastVideo(Scene):
|
|
def construct(self):
|
|
title = TextMobject("From last video")
|
|
title.to_edge(UP)
|
|
rect = ScreenRectangle(height = 6)
|
|
rect.next_to(title, DOWN)
|
|
|
|
self.add(title)
|
|
self.play(ShowCreation(rect))
|
|
self.wait(2)
|
|
|
|
class AskAboutRegions(IntroduceRegions):
|
|
def construct(self):
|
|
self.setup_configuration()
|
|
houses, utilities = self.houses, self.utilities
|
|
self.convert_objects_to_dots(run_time = 0)
|
|
objects = self.objects
|
|
lines, line_groups, regions = self.get_lines_line_groups_and_regions()
|
|
back_region = regions[0]
|
|
front_regions = VGroup(*regions[1:])
|
|
missing_lines = VGroup(
|
|
self.get_line(2, 0, self.objects.get_top()),
|
|
self.get_line(
|
|
2, 0,
|
|
self.objects.get_bottom() + DOWN,
|
|
self.objects.get_corner(DOWN+LEFT) + DOWN+LEFT,
|
|
)
|
|
)
|
|
missing_lines.set_stroke(width = 5)
|
|
|
|
front_regions.save_state()
|
|
front_regions.generate_target()
|
|
front_regions.target.scale(0.5)
|
|
front_regions.target.arrange(RIGHT, buff = LARGE_BUFF)
|
|
front_regions.target.to_edge(UP)
|
|
|
|
self.add(front_regions)
|
|
self.add_foreground_mobjects(lines, objects)
|
|
self.wait()
|
|
self.play(MoveToTarget(front_regions))
|
|
self.play(LaggedStartMap(
|
|
ApplyMethod, front_regions,
|
|
lambda m : (m.rotate_in_place, np.pi/12),
|
|
rate_func = wiggle,
|
|
lag_ratio = 0.75,
|
|
run_time = 1
|
|
))
|
|
self.play(front_regions.restore)
|
|
self.wait()
|
|
|
|
#Show missing lines
|
|
for line in missing_lines:
|
|
self.play(ShowCreation(
|
|
line,
|
|
rate_func = there_and_back,
|
|
run_time = 2,
|
|
))
|
|
|
|
#Count regions
|
|
count = TexMobject("1")
|
|
count.scale(1.5)
|
|
count.to_edge(UP)
|
|
self.play(
|
|
FadeIn(back_region),
|
|
FadeIn(count),
|
|
Animation(front_regions)
|
|
)
|
|
last_region = None
|
|
for n, region in zip(it.count(2), front_regions):
|
|
new_count = TexMobject(str(n))
|
|
new_count.replace(count, dim_to_match = 1)
|
|
self.remove(count)
|
|
self.add(new_count)
|
|
count = new_count
|
|
|
|
region.save_state()
|
|
anims = [ApplyMethod(region.set_color, YELLOW)]
|
|
if last_region:
|
|
anims.append(ApplyMethod(last_region.restore))
|
|
anims.append(Animation(front_regions))
|
|
self.play(*anims, run_time = 0.25)
|
|
self.wait(0.5)
|
|
last_region = region
|
|
self.play(last_region.restore)
|
|
self.wait()
|
|
self.play(FadeOut(count))
|
|
|
|
#Count edges per region
|
|
fade_rect = FullScreenFadeRectangle(opacity = 0.8)
|
|
line_group = line_groups[0].copy()
|
|
region = front_regions[0].copy()
|
|
self.foreground_mobjects = []
|
|
def show_lines(line_group):
|
|
lg_copy = line_group.copy()
|
|
lg_copy.set_stroke(WHITE, 6)
|
|
self.play(LaggedStartMap(
|
|
FadeIn, lg_copy,
|
|
run_time = 3,
|
|
rate_func = there_and_back,
|
|
lag_ratio = 0.4,
|
|
remover = True,
|
|
))
|
|
|
|
self.play(
|
|
FadeIn(fade_rect),
|
|
Animation(region),
|
|
Animation(line_group),
|
|
)
|
|
show_lines(line_group)
|
|
last_line_group = line_group
|
|
last_region = region
|
|
for i in range(1, 3):
|
|
line_group = line_groups[i].copy()
|
|
region = front_regions[i].copy()
|
|
self.play(
|
|
FadeOut(last_region),
|
|
FadeOut(last_line_group),
|
|
FadeIn(region),
|
|
FadeIn(line_group),
|
|
)
|
|
show_lines(line_group)
|
|
last_line_group = line_group
|
|
last_region = region
|
|
self.play(
|
|
FadeOut(fade_rect),
|
|
FadeOut(last_region),
|
|
FadeOut(last_line_group),
|
|
)
|
|
self.wait()
|
|
|
|
class NewRegionClosedOnlyForNodesWithEdges(UtilitiesPuzzleScene):
|
|
def construct(self):
|
|
self.setup_configuration()
|
|
self.convert_objects_to_dots(run_time = 0)
|
|
objects = self.objects
|
|
houses, utilities = self.houses, self.utilities
|
|
|
|
bb = self.bounding_box
|
|
lines = VGroup(
|
|
self.get_line(2, 1),
|
|
self.get_line(0, 1),
|
|
self.get_line(0, 2,
|
|
bb.get_corner(UP+LEFT),
|
|
bb.get_top() + MED_LARGE_BUFF*UP,
|
|
),
|
|
self.get_line(2, 2),
|
|
)
|
|
lit_line = lines[2].copy()
|
|
lit_line.points = lit_line.points[::-1]
|
|
lit_line.set_stroke(WHITE, 5)
|
|
|
|
region = self.get_region(*lines)
|
|
region.set_fill(MAROON_E)
|
|
|
|
arrow = Vector(DOWN+LEFT, color = WHITE)
|
|
arrow.next_to(houses[2], UP+RIGHT, buff = SMALL_BUFF)
|
|
words = TextMobject("Already has \\\\ an edge")
|
|
words.next_to(arrow.get_start(), UP, SMALL_BUFF)
|
|
|
|
for line in lines[:-1]:
|
|
self.play(ShowCreation(line))
|
|
lines[-1].pointwise_become_partial(lines[-1], 0, 0.92)
|
|
lines[-1].save_state()
|
|
self.wait()
|
|
self.play(ShowCreation(lines[-1]))
|
|
self.add(region, lines, objects)
|
|
self.wait()
|
|
self.remove(region)
|
|
self.play(ShowCreation(lines[-1],
|
|
rate_func = lambda t : smooth(1-2*t*(1-t))
|
|
))
|
|
self.add(region, lines, objects)
|
|
self.wait()
|
|
self.remove(region)
|
|
self.play(
|
|
ShowCreation(lines[-1],
|
|
rate_func = lambda t : smooth(1-0.5*t)
|
|
),
|
|
FadeIn(words),
|
|
GrowArrow(arrow),
|
|
)
|
|
for x in range(2):
|
|
self.play(ShowCreationThenDestruction(lit_line))
|
|
self.play(lines[-1].restore)
|
|
self.add(region, lines, objects)
|
|
self.wait(2)
|
|
|
|
class LightUpNodes(IntroduceRegions):
|
|
CONFIG = {
|
|
"vertices_word" : "Lit vertices",
|
|
}
|
|
def construct(self):
|
|
self.setup_configuration()
|
|
self.setup_regions()
|
|
self.setup_counters()
|
|
self.describe_one_as_lit()
|
|
self.show_rule_for_lighting()
|
|
|
|
def setup_configuration(self):
|
|
IntroduceRegions.setup_configuration(self)
|
|
self.convert_objects_to_dots(run_time = 0)
|
|
self.objects.shift(DOWN)
|
|
|
|
def setup_regions(self):
|
|
lines, line_groups, regions = self.get_lines_line_groups_and_regions()
|
|
back_region = regions[0]
|
|
front_regions = VGroup(*regions[1:])
|
|
self.set_variables_as_attrs(
|
|
lines, line_groups, regions,
|
|
back_region, front_regions,
|
|
)
|
|
|
|
def setup_counters(self):
|
|
titles = [
|
|
TextMobject("\\# %s"%self.vertices_word),
|
|
TextMobject("\\# Edges"),
|
|
TextMobject("\\# Regions"),
|
|
]
|
|
for title, vect in zip(titles, [LEFT, ORIGIN, RIGHT]):
|
|
title.shift(FRAME_X_RADIUS*vect/2)
|
|
title.to_edge(UP)
|
|
underline = Line(LEFT, RIGHT)
|
|
underline.stretch_to_fit_width(title.get_width())
|
|
underline.next_to(title, DOWN, SMALL_BUFF)
|
|
title.add(underline)
|
|
self.add(title)
|
|
self.count_titles = titles
|
|
self.v_count, self.e_count, self.f_count = self.counts = list(map(
|
|
Integer, [1, 0, 1]
|
|
))
|
|
for count, title in zip(self.counts, titles):
|
|
count.next_to(title, DOWN)
|
|
self.add(count)
|
|
|
|
def describe_one_as_lit(self):
|
|
houses, utilities = self.houses, self.utilities
|
|
vertices = VGroup(*it.chain(houses, utilities))
|
|
dim_arrows = VGroup()
|
|
for vertex in vertices:
|
|
arrow = Vector(0.5*(DOWN+LEFT), color = WHITE)
|
|
arrow.next_to(vertex, UP+RIGHT, SMALL_BUFF)
|
|
vertex.arrow = arrow
|
|
dim_arrows.add(arrow)
|
|
lit_vertex = utilities[0]
|
|
lit_arrow = lit_vertex.arrow
|
|
lit_arrow.rotate(np.pi/2, about_point = lit_vertex.get_center())
|
|
dim_arrows.remove(lit_arrow)
|
|
lit_word = TextMobject("Lit up")
|
|
lit_word.next_to(lit_arrow.get_start(), UP, SMALL_BUFF)
|
|
dim_word = TextMobject("Dim")
|
|
dim_word.next_to(dim_arrows[1].get_start(), UP, MED_LARGE_BUFF)
|
|
|
|
dot = Dot().move_to(self.v_count)
|
|
|
|
self.play(
|
|
vertices.set_fill, None, 0,
|
|
vertices.set_stroke, None, 1,
|
|
)
|
|
self.play(ReplacementTransform(dot, lit_vertex))
|
|
self.play(
|
|
FadeIn(lit_word),
|
|
GrowArrow(lit_arrow)
|
|
)
|
|
self.play(*self.get_lit_vertex_animations(lit_vertex))
|
|
self.play(
|
|
FadeIn(dim_word),
|
|
LaggedStartMap(GrowArrow, dim_arrows)
|
|
)
|
|
self.wait()
|
|
self.play(*list(map(FadeOut, [
|
|
lit_word, lit_arrow, dim_word, dim_arrows
|
|
])))
|
|
|
|
def show_rule_for_lighting(self):
|
|
lines = self.lines
|
|
regions = self.regions
|
|
line_groups = self.line_groups
|
|
objects = self.objects
|
|
houses, utilities = self.houses, self.utilities
|
|
|
|
#First region, lines 0, 1, 4, 3
|
|
lines[4].rotate_in_place(np.pi)
|
|
region = regions[1]
|
|
|
|
self.play(ShowCreation(lines[0]))
|
|
self.play(*self.get_count_change_animations(0, 1, 0))
|
|
self.play(*it.chain(
|
|
self.get_lit_vertex_animations(houses[0]),
|
|
self.get_count_change_animations(1, 0, 0)
|
|
))
|
|
self.wait()
|
|
for line, vertex in (lines[1], houses[1]), (lines[4], utilities[1]):
|
|
self.play(
|
|
ShowCreation(line),
|
|
*self.get_count_change_animations(0, 1, 0)
|
|
)
|
|
self.play(*it.chain(
|
|
self.get_lit_vertex_animations(vertex),
|
|
self.get_count_change_animations(1, 0, 0),
|
|
))
|
|
self.wait()
|
|
self.play(
|
|
ShowCreation(lines[3], run_time = 2),
|
|
*self.get_count_change_animations(0, 1, 0)
|
|
)
|
|
self.add_foreground_mobjects(line_groups[0])
|
|
self.add_foreground_mobjects(objects)
|
|
self.play(
|
|
FadeIn(region),
|
|
*self.get_count_change_animations(0, 0, 1)
|
|
)
|
|
self.wait()
|
|
|
|
#Next region, lines 2, 7, 8
|
|
region = regions[3]
|
|
lines[6].rotate_in_place(np.pi)
|
|
|
|
for line, vertex in (lines[2], houses[2]), (lines[6], utilities[2]):
|
|
self.play(ShowCreation(line), *it.chain(
|
|
self.get_lit_vertex_animations(
|
|
vertex,
|
|
run_time = 2,
|
|
squish_range = (0.5, 1),
|
|
),
|
|
self.get_count_change_animations(1, 1, 0)
|
|
))
|
|
self.wait()
|
|
self.play(
|
|
ShowCreation(lines[7]),
|
|
*self.get_count_change_animations(0, 1, 1)
|
|
)
|
|
self.add_foreground_mobjects(line_groups[2])
|
|
self.add_foreground_mobjects(objects)
|
|
self.play(FadeIn(region))
|
|
self.wait()
|
|
|
|
####
|
|
|
|
def get_count_change_animations(self, *changes):
|
|
anims = []
|
|
for change, count in zip(changes, self.counts):
|
|
if change == 0:
|
|
continue
|
|
new_count = Integer(count.number + 1)
|
|
new_count.move_to(count)
|
|
anims.append(Transform(
|
|
count, new_count,
|
|
run_time = 2,
|
|
rate_func = squish_rate_func(smooth, 0.5, 1)
|
|
))
|
|
count.number += 1
|
|
anims.append(self.get_plus_one_anim(count))
|
|
|
|
return anims
|
|
|
|
def get_plus_one_anim(self, count):
|
|
plus_one = TexMobject("+1")
|
|
plus_one.set_color(YELLOW)
|
|
plus_one.move_to(count)
|
|
plus_one.next_to(count, DOWN)
|
|
plus_one.generate_target()
|
|
plus_one.target.move_to(count)
|
|
plus_one.target.set_fill(opacity = 0)
|
|
move = MoveToTarget(plus_one, remover = True)
|
|
grow = GrowFromCenter(plus_one)
|
|
return UpdateFromAlphaFunc(
|
|
plus_one,
|
|
lambda m, a : (
|
|
(grow if a < 0.5 else move).update(2*a%1)
|
|
),
|
|
remover = True,
|
|
rate_func = double_smooth,
|
|
run_time = 2
|
|
)
|
|
|
|
def get_lit_vertex_animations(self, vertex, run_time = 1, squish_range = (0, 1)):
|
|
line = Line(
|
|
LEFT, RIGHT,
|
|
stroke_width = 0,
|
|
stroke_color = BLACK,
|
|
)
|
|
line.set_width(0.5*vertex.get_width())
|
|
line.next_to(ORIGIN, buff = 0.75*vertex.get_width())
|
|
lines = VGroup(*[
|
|
line.copy().rotate(angle)
|
|
for angle in np.arange(0, 2*np.pi, np.pi/4)
|
|
])
|
|
lines.move_to(vertex)
|
|
random.shuffle(lines.submobjects)
|
|
return [
|
|
LaggedStartMap(
|
|
ApplyMethod, lines,
|
|
lambda l : (l.set_stroke, YELLOW, 4),
|
|
rate_func = squish_rate_func(there_and_back, *squish_range),
|
|
lag_ratio = 0.75,
|
|
remover = True,
|
|
run_time = run_time
|
|
),
|
|
ApplyMethod(
|
|
vertex.set_fill, None, 1,
|
|
run_time = run_time,
|
|
rate_func = squish_rate_func(smooth, *squish_range)
|
|
),
|
|
]
|
|
|
|
class ShowRule(TeacherStudentsScene):
|
|
def construct(self):
|
|
new_edge = TextMobject("New edge")
|
|
new_vertex = TextMobject("New (lit) vertex")
|
|
new_vertex.next_to(new_edge, UP+RIGHT, MED_LARGE_BUFF)
|
|
new_region = TextMobject("New region")
|
|
new_region.next_to(new_edge, DOWN+RIGHT, MED_LARGE_BUFF)
|
|
VGroup(new_vertex, new_region).shift(RIGHT)
|
|
arrows = VGroup(*[
|
|
Arrow(
|
|
new_edge.get_right(), mob.get_left(),
|
|
color = WHITE,
|
|
buff = SMALL_BUFF
|
|
)
|
|
for mob in (new_vertex, new_region)
|
|
])
|
|
for word, arrow in zip(["Either", "or"], arrows):
|
|
word_mob = TextMobject(word)
|
|
word_mob.scale(0.65)
|
|
word_mob.next_to(ORIGIN, UP, SMALL_BUFF)
|
|
word_mob.rotate(arrow.get_angle())
|
|
word_mob.shift(arrow.get_center())
|
|
word_mob.set_color(GREEN)
|
|
arrow.add(word_mob)
|
|
new_vertex.set_color(YELLOW)
|
|
new_edge.set_color(BLUE)
|
|
new_region.set_color(RED)
|
|
rule = VGroup(new_edge, arrows, new_vertex, new_region)
|
|
rule.center().to_edge(UP)
|
|
|
|
nine_total = TextMobject("(9 total)")
|
|
nine_total.next_to(new_edge, DOWN)
|
|
|
|
self.play(
|
|
Animation(rule),
|
|
self.teacher.change, "raise_right_hand"
|
|
)
|
|
self.change_student_modes(
|
|
*["confused"]*3,
|
|
look_at_arg = rule
|
|
)
|
|
self.wait(2)
|
|
self.play(
|
|
Write(nine_total),
|
|
self.teacher.change, "happy",
|
|
)
|
|
self.change_student_modes(
|
|
*["thinking"]*3,
|
|
look_at_arg = rule
|
|
)
|
|
self.wait(3)
|
|
|
|
class ConcludeFiveRegions(LightUpNodes):
|
|
def construct(self):
|
|
self.setup_configuration()
|
|
self.setup_regions()
|
|
self.setup_counters()
|
|
|
|
self.describe_start_setup()
|
|
self.show_nine_lines_to_start()
|
|
self.show_five_to_lit_up_nodes()
|
|
self.relate_four_lines_to_regions()
|
|
self.conclude_about_five_regions()
|
|
|
|
def describe_start_setup(self):
|
|
to_dim = VGroup(*it.chain(self.houses, self.utilities[1:]))
|
|
to_dim.set_stroke(width = 1)
|
|
to_dim.set_fill(opacity = 0)
|
|
|
|
full_screen_rect = FullScreenFadeRectangle(
|
|
fill_color = LIGHT_GREY,
|
|
fill_opacity = 0.25,
|
|
)
|
|
|
|
self.play(
|
|
Indicate(self.v_count),
|
|
*self.get_lit_vertex_animations(self.utilities[0])
|
|
)
|
|
self.play(
|
|
FadeIn(
|
|
full_screen_rect,
|
|
rate_func = there_and_back,
|
|
remover = True,
|
|
),
|
|
Indicate(self.f_count),
|
|
*list(map(Animation, self.mobjects))
|
|
)
|
|
self.wait()
|
|
|
|
def show_nine_lines_to_start(self):
|
|
line_sets = self.get_straight_lines()
|
|
line_sets.target = VGroup()
|
|
for lines in line_sets:
|
|
lines.generate_target()
|
|
for line in lines.target:
|
|
line.rotate(-line.get_angle())
|
|
line.set_width(1.5)
|
|
lines.target.arrange(DOWN)
|
|
line_sets.target.add(lines.target)
|
|
line_sets.target.arrange(DOWN)
|
|
line_sets.target.center()
|
|
line_sets.target.to_edge(RIGHT)
|
|
|
|
for lines in line_sets:
|
|
self.play(LaggedStartMap(ShowCreation, lines, run_time = 1))
|
|
self.play(MoveToTarget(lines))
|
|
self.wait()
|
|
|
|
ghost_lines = line_sets.copy()
|
|
ghost_lines.fade(0.9)
|
|
self.add(ghost_lines, line_sets)
|
|
self.side_lines = VGroup(*it.chain(*line_sets))
|
|
|
|
def show_five_to_lit_up_nodes(self):
|
|
side_lines = self.side_lines
|
|
lines = self.lines
|
|
vertices = VGroup(*it.chain(self.houses, self.utilities))
|
|
line_indices = [0, 1, 4, 6, 7]
|
|
vertex_indices = [0, 1, 4, 5, 2]
|
|
|
|
for li, vi in zip(line_indices, vertex_indices):
|
|
side_line = side_lines[li]
|
|
line = lines[li]
|
|
vertex = vertices[vi]
|
|
self.play(ReplacementTransform(side_line, line))
|
|
self.play(*it.chain(
|
|
self.get_count_change_animations(1, 1, 0),
|
|
self.get_lit_vertex_animations(vertex),
|
|
))
|
|
|
|
def relate_four_lines_to_regions(self):
|
|
f_rect = SurroundingRectangle(
|
|
VGroup(self.count_titles[-1], self.f_count)
|
|
)
|
|
on_screen_side_lines = VGroup(*[m for m in self.side_lines if m in self.mobjects])
|
|
side_lines_rect = SurroundingRectangle(on_screen_side_lines)
|
|
side_lines_rect.set_color(WHITE)
|
|
|
|
self.play(ShowCreation(side_lines_rect))
|
|
self.wait()
|
|
self.play(ReplacementTransform(side_lines_rect, f_rect))
|
|
self.play(FadeOut(f_rect))
|
|
self.wait()
|
|
|
|
def conclude_about_five_regions(self):
|
|
lines = self.lines
|
|
side_lines = self.side_lines
|
|
regions = self.regions[1:]
|
|
line_groups = self.line_groups
|
|
line_indices = [3, 5, 2]
|
|
objects = self.objects
|
|
|
|
for region, line_group, li in zip(regions, line_groups, line_indices):
|
|
self.play(ReplacementTransform(
|
|
side_lines[li], lines[li]
|
|
))
|
|
self.play(
|
|
FadeIn(region),
|
|
Animation(line_group),
|
|
Animation(objects),
|
|
*self.get_count_change_animations(0, 1, 1)
|
|
)
|
|
self.wait()
|
|
|
|
#Conclude
|
|
words = TextMobject("Last line must \\\\ introduce 5th region")
|
|
words.scale(0.8)
|
|
words.set_color(BLUE)
|
|
rect = SurroundingRectangle(self.f_count)
|
|
rect.set_color(BLUE)
|
|
words.next_to(rect, DOWN)
|
|
randy = Randolph().flip()
|
|
randy.scale(0.5)
|
|
randy.next_to(words, RIGHT, SMALL_BUFF, DOWN)
|
|
self.play(ShowCreation(rect), Write(words))
|
|
self.play(FadeIn(randy))
|
|
self.play(randy.change, "pondering")
|
|
self.play(Blink(randy))
|
|
self.wait(2)
|
|
|
|
class WhatsWrongWithFive(TeacherStudentsScene):
|
|
def construct(self):
|
|
self.student_says(
|
|
"What's wrong with \\\\ 5 regions?",
|
|
target_mode = "maybe"
|
|
)
|
|
self.wait(2)
|
|
|
|
class CyclesHaveAtLeastFour(UtilitiesPuzzleScene):
|
|
def construct(self):
|
|
self.setup_configuration()
|
|
houses, utilities = self.houses, self.utilities
|
|
vertices = VGroup(
|
|
houses[0], utilities[0],
|
|
houses[1], utilities[1], houses[0],
|
|
)
|
|
lines = [
|
|
VectorizedPoint(),
|
|
self.get_line(0, 0),
|
|
self.get_line(0, 1),
|
|
self.get_line(1, 1),
|
|
self.get_line(1, 0, self.objects.get_corner(DOWN+LEFT)),
|
|
]
|
|
for line in lines[1::2]:
|
|
line.points = line.points[::-1]
|
|
arrows = VGroup()
|
|
for vertex in vertices:
|
|
vect = vertices.get_center() - vertex.get_center()
|
|
arrow = Vector(vect, color = WHITE)
|
|
arrow.next_to(vertex, -vect, buff = 0)
|
|
vertex.arrow = arrow
|
|
arrows.add(arrow)
|
|
word_strings = [
|
|
"Start at a house",
|
|
"Go to a utility",
|
|
"Go to another house",
|
|
"Go to another utility",
|
|
"Back to the start",
|
|
]
|
|
words = VGroup()
|
|
for word_string, arrow in zip(word_strings, arrows):
|
|
vect = arrow.get_vector()[1]*UP
|
|
word = TextMobject(word_string)
|
|
word.next_to(arrow.get_start(), -vect)
|
|
words.add(word)
|
|
|
|
count = Integer(-1)
|
|
count.fade(1)
|
|
count.to_edge(UP)
|
|
|
|
last_word = None
|
|
last_arrow = None
|
|
|
|
for line, word, arrow in zip(lines, words, arrows):
|
|
anims = []
|
|
for mob in last_word, last_arrow:
|
|
if mob:
|
|
anims.append(FadeOut(mob))
|
|
new_count = Integer(count.number + 1)
|
|
new_count.move_to(count)
|
|
anims += [
|
|
FadeIn(word),
|
|
GrowArrow(arrow),
|
|
ShowCreation(line),
|
|
FadeOut(count),
|
|
FadeIn(new_count),
|
|
]
|
|
self.play(*anims)
|
|
self.wait()
|
|
last_word = word
|
|
last_arrow = arrow
|
|
count = new_count
|
|
self.wait(2)
|
|
|
|
class FiveRegionsFourEdgesEachGraph(Scene):
|
|
CONFIG = {
|
|
"v_color" : WHITE,
|
|
"e_color" : YELLOW,
|
|
"f_colors" : (BLUE, RED_E, BLUE_E),
|
|
"n_edge_double_count_examples" : 6,
|
|
"random_seed" : 1,
|
|
}
|
|
def construct(self):
|
|
self.draw_squares()
|
|
self.transition_to_graph()
|
|
self.count_edges_per_region()
|
|
self.each_edges_has_two_regions()
|
|
self.ten_total_edges()
|
|
|
|
def draw_squares(self):
|
|
words = VGroup(
|
|
TextMobject("5", "regions"),
|
|
TextMobject("4", "edges each"),
|
|
)
|
|
words.arrange(DOWN)
|
|
words.to_edge(UP)
|
|
words[0][0].set_color(self.f_colors[0])
|
|
words[1][0].set_color(self.e_color)
|
|
|
|
squares = VGroup(*[Square() for x in range(5)])
|
|
squares.scale(0.5)
|
|
squares.set_stroke(width = 0)
|
|
squares.set_fill(opacity = 1)
|
|
squares.set_color_by_gradient(*self.f_colors)
|
|
squares.arrange(RIGHT, buff = MED_LARGE_BUFF)
|
|
squares.next_to(words, DOWN, LARGE_BUFF)
|
|
all_edges = VGroup()
|
|
all_vertices = VGroup()
|
|
for square in squares:
|
|
corners = square.get_anchors()[:4]
|
|
square.edges = VGroup(*[
|
|
Line(c1, c2, color = self.e_color)
|
|
for c1, c2 in adjacent_pairs(corners)
|
|
])
|
|
square.vertices = VGroup(*[
|
|
Dot(color = self.v_color).move_to(c)
|
|
for c in corners
|
|
])
|
|
all_edges.add(*square.edges)
|
|
all_vertices.add(*square.vertices)
|
|
|
|
self.play(
|
|
FadeIn(words[0]),
|
|
LaggedStartMap(FadeIn, squares, run_time = 1.5)
|
|
)
|
|
self.play(
|
|
FadeIn(words[1]),
|
|
LaggedStartMap(ShowCreation, all_edges),
|
|
LaggedStartMap(GrowFromCenter, all_vertices),
|
|
)
|
|
self.wait()
|
|
|
|
self.add_foreground_mobjects(words)
|
|
self.set_variables_as_attrs(words, squares)
|
|
|
|
def transition_to_graph(self):
|
|
squares = self.squares
|
|
words = self.words
|
|
|
|
points = np.array([
|
|
UP+LEFT,
|
|
UP+RIGHT,
|
|
DOWN+RIGHT,
|
|
DOWN+LEFT,
|
|
3*(UP+RIGHT),
|
|
3*(DOWN+LEFT),
|
|
3*(DOWN+RIGHT),
|
|
])
|
|
points *= 0.75
|
|
|
|
regions = VGroup(*[
|
|
Square().set_points_as_corners(points[indices])
|
|
for indices in [
|
|
[0, 1, 2, 3],
|
|
[0, 4, 2, 1],
|
|
[5, 0, 3, 2],
|
|
[5, 2, 4, 6],
|
|
[6, 4, 0, 5],
|
|
]
|
|
])
|
|
regions.set_stroke(width = 0)
|
|
regions.set_fill(opacity = 1)
|
|
regions.set_color_by_gradient(*self.f_colors)
|
|
|
|
all_edges = VGroup()
|
|
all_movers = VGroup()
|
|
for region, square in zip(regions, squares):
|
|
corners = region.get_anchors()[:4]
|
|
region.edges = VGroup(*[
|
|
Line(c1, c2, color = self.e_color)
|
|
for c1, c2 in adjacent_pairs(corners)
|
|
])
|
|
all_edges.add(*region.edges)
|
|
region.vertices = VGroup(*[
|
|
Dot(color = self.v_color).move_to(c)
|
|
for c in corners
|
|
])
|
|
mover = VGroup(
|
|
square, square.edges, square.vertices,
|
|
)
|
|
mover.target = VGroup(
|
|
region, region.edges, region.vertices
|
|
)
|
|
all_movers.add(mover)
|
|
|
|
back_region = FullScreenFadeRectangle()
|
|
back_region.set_fill(regions[-1].get_color(), 0.5)
|
|
regions[-1].set_fill(opacity = 0)
|
|
back_region.add(regions[-1].copy().set_fill(BLACK, 1))
|
|
back_region.edges = regions[-1].edges
|
|
|
|
self.play(
|
|
FadeIn(
|
|
back_region,
|
|
rate_func = squish_rate_func(smooth, 0.7, 1),
|
|
run_time = 3,
|
|
),
|
|
LaggedStartMap(
|
|
MoveToTarget, all_movers,
|
|
run_time = 3,
|
|
replace_mobject_with_target_in_scene = True,
|
|
),
|
|
)
|
|
self.wait(2)
|
|
|
|
self.set_variables_as_attrs(
|
|
regions, all_edges, back_region,
|
|
graph = VGroup(*[m.target for m in all_movers])
|
|
)
|
|
|
|
def count_edges_per_region(self):
|
|
all_edges = self.all_edges
|
|
back_region = self.back_region
|
|
regions = self.regions
|
|
graph = self.graph
|
|
all_vertices = VGroup(*[r.vertices for r in regions])
|
|
|
|
ghost_edges = all_edges.copy()
|
|
ghost_edges.set_stroke(LIGHT_GREY, 1)
|
|
|
|
count = Integer(0)
|
|
count.scale(2)
|
|
count.next_to(graph, RIGHT, buff = 2)
|
|
count.set_fill(YELLOW, opacity = 0)
|
|
|
|
last_region = VGroup(back_region, *regions[1:])
|
|
last_region.add(all_edges)
|
|
|
|
for region in list(regions[:-1]) + [back_region]:
|
|
self.play(
|
|
FadeIn(region),
|
|
Animation(ghost_edges),
|
|
FadeOut(last_region),
|
|
Animation(count),
|
|
Animation(all_vertices),
|
|
)
|
|
for edge in region.edges:
|
|
new_count = Integer(count.number + 1)
|
|
new_count.replace(count, dim_to_match = 1)
|
|
new_count.set_color(count.get_color())
|
|
self.play(
|
|
ShowCreation(edge),
|
|
FadeOut(count),
|
|
FadeIn(new_count),
|
|
run_time = 0.5
|
|
)
|
|
count = new_count
|
|
last_region = VGroup(region, region.edges)
|
|
self.wait()
|
|
self.add_foreground_mobjects(count)
|
|
self.play(
|
|
FadeOut(last_region),
|
|
Animation(ghost_edges),
|
|
Animation(all_vertices),
|
|
)
|
|
|
|
self.set_variables_as_attrs(count, ghost_edges, all_vertices)
|
|
|
|
def each_edges_has_two_regions(self):
|
|
regions = list(self.regions[:-1]) + [self.back_region]
|
|
back_region = self.back_region
|
|
self.add_foreground_mobjects(self.ghost_edges, self.all_vertices)
|
|
|
|
edge_region_pair_groups = []
|
|
for r1, r2 in it.combinations(regions, 2):
|
|
for e1 in r1.edges:
|
|
for e2 in r2.edges:
|
|
diff = e1.get_center()-e2.get_center()
|
|
if get_norm(diff) < 0.01:
|
|
edge_region_pair_groups.append(VGroup(
|
|
e1, r1, r2
|
|
))
|
|
|
|
for x in range(self.n_edge_double_count_examples):
|
|
edge, r1, r2 = random.choice(edge_region_pair_groups)
|
|
if r2 is back_region:
|
|
#Flip again, maybe you're still unlucky, maybe not
|
|
edge, r1, r2 = random.choice(edge_region_pair_groups)
|
|
self.play(ShowCreation(edge))
|
|
self.add_foreground_mobjects(edge)
|
|
self.play(FadeIn(r1), run_time = 0.5)
|
|
self.play(FadeIn(r2), Animation(r1), run_time = 0.5)
|
|
self.wait(0.5)
|
|
self.play(*list(map(FadeOut, [r2, r1, edge])), run_time = 0.5)
|
|
self.remove_foreground_mobjects(edge)
|
|
|
|
def ten_total_edges(self):
|
|
double_count = self.count
|
|
brace = Brace(double_count, UP)
|
|
words = brace.get_text("Double-counts \\\\ edges")
|
|
regions = self.regions
|
|
|
|
edges = VGroup(*it.chain(
|
|
regions[0].edges,
|
|
regions[-1].edges,
|
|
[regions[1].edges[1]],
|
|
[regions[2].edges[3]],
|
|
))
|
|
|
|
count = Integer(0)
|
|
count.scale(2)
|
|
count.set_fill(WHITE, 0)
|
|
count.next_to(self.graph, LEFT, LARGE_BUFF)
|
|
|
|
self.play(
|
|
GrowFromCenter(brace),
|
|
Write(words)
|
|
)
|
|
self.wait()
|
|
for edge in edges:
|
|
new_count = Integer(count.number + 1)
|
|
new_count.replace(count, dim_to_match = 1)
|
|
self.play(
|
|
ShowCreation(edge),
|
|
FadeOut(count),
|
|
FadeIn(new_count),
|
|
run_time = 0.5
|
|
)
|
|
count = new_count
|
|
self.wait()
|
|
|
|
class EulersFormulaForGeneralPlanarGraph(LightUpNodes, ThreeDScene):
|
|
CONFIG = {
|
|
"vertices_word" : "Vertices"
|
|
}
|
|
def construct(self):
|
|
self.setup_counters()
|
|
self.show_creation_of_graph()
|
|
self.show_formula()
|
|
self.transform_into_cube()
|
|
|
|
def show_creation_of_graph(self):
|
|
points = np.array([
|
|
UP+LEFT,
|
|
UP+RIGHT,
|
|
DOWN+RIGHT,
|
|
DOWN+LEFT,
|
|
3*(UP+LEFT),
|
|
3*(UP+RIGHT),
|
|
3*(DOWN+RIGHT),
|
|
3*(DOWN+LEFT),
|
|
])
|
|
points *= 0.75
|
|
points += DOWN
|
|
vertices = VGroup(*list(map(Dot, points)))
|
|
vertices.set_color(YELLOW)
|
|
edges = VGroup(*[
|
|
VGroup(*[
|
|
Line(p1, p2, color = WHITE)
|
|
for p2 in points
|
|
])
|
|
for p1 in points
|
|
])
|
|
regions = self.get_cube_faces(points)
|
|
regions.set_stroke(width = 0)
|
|
regions.set_fill(opacity = 1)
|
|
regions.set_color_by_gradient(GREEN, RED, BLUE_E)
|
|
regions[-1].set_fill(opacity = 0)
|
|
|
|
pairs = [
|
|
(edges[0][1], vertices[1]),
|
|
(edges[1][2], vertices[2]),
|
|
(edges[2][6], vertices[6]),
|
|
(edges[6][5], vertices[5]),
|
|
(edges[5][1], regions[2]),
|
|
(edges[0][4], vertices[4]),
|
|
(edges[4][5], regions[1]),
|
|
(edges[0][3], vertices[3]),
|
|
(edges[3][2], regions[0]),
|
|
(edges[4][7], vertices[7]),
|
|
(edges[7][3], regions[4]),
|
|
(edges[7][6], regions[3]),
|
|
]
|
|
|
|
self.add_foreground_mobjects(vertices[0])
|
|
self.wait()
|
|
for edge, obj in pairs:
|
|
anims = [ShowCreation(edge)]
|
|
if obj in vertices:
|
|
obj.save_state()
|
|
obj.move_to(edge.get_start())
|
|
anims.append(ApplyMethod(obj.restore))
|
|
anims += self.get_count_change_animations(1, 1, 0)
|
|
self.add_foreground_mobjects(obj)
|
|
else:
|
|
anims = [FadeIn(obj)] + anims
|
|
anims += self.get_count_change_animations(0, 1, 1)
|
|
self.play(*anims)
|
|
self.add_foreground_mobjects(edge)
|
|
self.wait()
|
|
|
|
self.set_variables_as_attrs(edges, vertices, regions)
|
|
|
|
def show_formula(self):
|
|
counts = VGroup(*self.counts)
|
|
count_titles = VGroup(*self.count_titles)
|
|
groups = [count_titles, counts]
|
|
|
|
for group in groups:
|
|
group.symbols = VGroup(*list(map(TexMobject, ["-", "+", "="])))
|
|
group.generate_target()
|
|
line = VGroup(*it.chain(*list(zip(group.target, group.symbols))))
|
|
line.arrange(RIGHT)
|
|
line.to_edge(UP, buff = MED_SMALL_BUFF)
|
|
VGroup(counts.target, counts.symbols).shift(0.75*DOWN)
|
|
for mob in count_titles.target:
|
|
mob[-1].fade(1)
|
|
count_titles.symbols.shift(0.5*SMALL_BUFF*UP)
|
|
twos = VGroup(*[
|
|
TexMobject("2").next_to(group.symbols, RIGHT)
|
|
for group in groups
|
|
])
|
|
twos.shift(0.5*SMALL_BUFF*UP)
|
|
|
|
words = TextMobject("``Euler's characteristic formula''")
|
|
words.next_to(counts.target, DOWN)
|
|
words.shift(MED_LARGE_BUFF*RIGHT)
|
|
words.set_color(YELLOW)
|
|
|
|
for group in groups:
|
|
self.play(
|
|
MoveToTarget(group),
|
|
Write(group.symbols)
|
|
)
|
|
self.wait()
|
|
self.play(Write(twos))
|
|
self.wait()
|
|
self.play(Write(words))
|
|
self.wait()
|
|
|
|
self.top_formula = VGroup(count_titles, count_titles.symbols, twos[0])
|
|
self.bottom_formula = VGroup(counts, counts.symbols, twos[1])
|
|
|
|
def transform_into_cube(self):
|
|
regions = self.regions
|
|
points = np.array([
|
|
UP+LEFT,
|
|
UP+RIGHT,
|
|
DOWN+RIGHT,
|
|
DOWN+LEFT,
|
|
UP+LEFT+2*IN,
|
|
UP+RIGHT+2*IN,
|
|
DOWN+RIGHT+2*IN,
|
|
DOWN+LEFT+2*IN,
|
|
])
|
|
cube = self.get_cube_faces(points)
|
|
cube.shift(OUT)
|
|
cube.rotate_in_place(np.pi/12, RIGHT)
|
|
cube.rotate_in_place(np.pi/6, UP)
|
|
cube.shift(MED_LARGE_BUFF*DOWN)
|
|
shade_in_3d(cube)
|
|
|
|
for face, region in zip(cube, regions):
|
|
face.set_fill(region.get_color(), opacity = 0.8)
|
|
|
|
self.remove(self.edges)
|
|
regions.set_stroke(WHITE, 3)
|
|
cube.set_stroke(WHITE, 3)
|
|
|
|
new_formula = TexMobject("V - E + F = 2")
|
|
new_formula.to_edge(UP, buff = MED_SMALL_BUFF)
|
|
new_formula.align_to(self.bottom_formula, RIGHT)
|
|
|
|
self.play(FadeOut(self.vertices))
|
|
self.play(ReplacementTransform(regions, cube, run_time = 2))
|
|
cube.sort(lambda p : -p[2])
|
|
always_rotate(cube, axis=UP, about_point=ORIGIN)
|
|
self.add(cube)
|
|
self.wait(3)
|
|
self.play(
|
|
FadeOut(self.top_formula),
|
|
FadeIn(new_formula)
|
|
)
|
|
self.wait(10)
|
|
|
|
|
|
###
|
|
|
|
def get_cube_faces(self, eight_points):
|
|
return VGroup(*[
|
|
Square().set_points_as_corners(eight_points[indices])
|
|
for indices in [
|
|
[0, 1, 2, 3],
|
|
[0, 4, 5, 1],
|
|
[1, 5, 6, 2],
|
|
[2, 6, 7, 3],
|
|
[3, 7, 4, 0],
|
|
[4, 5, 6, 7],
|
|
]
|
|
])
|
|
|
|
class YouGaveFriendsAnImpossiblePuzzle(TeacherStudentsScene):
|
|
def construct(self):
|
|
self.student_says(
|
|
"You gave friends \\\\ an impossible puzzle?",
|
|
target_mode = "sassy",
|
|
)
|
|
self.change_student_modes(
|
|
"angry", "sassy", "angry",
|
|
added_anims = [self.teacher.change, "happy"]
|
|
)
|
|
self.wait(2)
|
|
|
|
class FunnyStory(TeacherStudentsScene):
|
|
def construct(self):
|
|
self.teacher_says("Funny story", target_mode = "hooray")
|
|
self.wait()
|
|
self.change_student_modes(
|
|
*["happy"]*3,
|
|
added_anims = [RemovePiCreatureBubble(
|
|
self.teacher,
|
|
target_mode = "raise_right_hand"
|
|
)],
|
|
look_at_arg = UP+2*RIGHT
|
|
)
|
|
self.wait(5)
|
|
|
|
class QuestionWrapper(Scene):
|
|
def construct(self):
|
|
question = TextMobject(
|
|
"Where", "\\emph{specifically}", "does\\\\",
|
|
"this proof break down?",
|
|
)
|
|
question.to_edge(UP)
|
|
question.set_color_by_tex("specifically", YELLOW)
|
|
screen_rect = ScreenRectangle(height = 5.5)
|
|
screen_rect.next_to(question, DOWN)
|
|
|
|
self.play(ShowCreation(screen_rect))
|
|
self.wait()
|
|
for word in question:
|
|
self.play(LaggedStartMap(
|
|
FadeIn, word,
|
|
run_time = 0.05*len(word)
|
|
))
|
|
self.wait(0.05)
|
|
self.wait()
|
|
|
|
class Homework(TeacherStudentsScene):
|
|
def construct(self):
|
|
self.teacher_says("Consider this \\\\ homework")
|
|
self.change_student_modes(*["pondering"]*3)
|
|
self.wait(2)
|
|
self.student_says(
|
|
"$V-E+F=0$ on \\\\ a torus!",
|
|
target_mode = "hooray"
|
|
)
|
|
self.wait()
|
|
self.teacher_says("Not good enough!", target_mode = "surprised")
|
|
self.change_student_modes(*["confused"]*3)
|
|
self.wait(2)
|
|
|
|
class WantToLearnMore(Scene):
|
|
def construct(self):
|
|
text = TextMobject("Want to learn more?")
|
|
self.play(Write(text))
|
|
self.wait()
|
|
|
|
class PatreonThanks(PatreonEndScreen):
|
|
CONFIG = {
|
|
"specific_patrons" : [
|
|
"Randall Hunt",
|
|
"Desmos",
|
|
"Burt Humburg",
|
|
"CrypticSwarm",
|
|
"Juan Benet",
|
|
"David Kedmey",
|
|
"Ali Yahya",
|
|
"Mayank M. Mehrotra",
|
|
"Lukas Biewald",
|
|
"Yana Chernobilsky",
|
|
"Kaustuv DeBiswas",
|
|
"Kathryn Schmiedicke",
|
|
"Yu Jun",
|
|
"Dave Nicponski",
|
|
"Damion Kistler",
|
|
"Jordan Scales",
|
|
"Markus Persson",
|
|
"Egor Gumenuk",
|
|
"Yoni Nazarathy",
|
|
"Ryan Atallah",
|
|
"Joseph John Cox",
|
|
"Luc Ritchie",
|
|
"Onuralp Soylemez",
|
|
"John Bjorn Nelson",
|
|
"Yaw Etse",
|
|
"David Barbetta",
|
|
"Julio Cesar Campo Neto",
|
|
"Waleed Hamied",
|
|
"Oliver Steele",
|
|
"George Chiesa",
|
|
"supershabam",
|
|
"James Park",
|
|
"Samantha D. Suplee",
|
|
"Delton Ding",
|
|
"Thomas Tarler",
|
|
"Jonathan Eppele",
|
|
"Isak Hietala",
|
|
"1stViewMaths",
|
|
"Jacob Magnuson",
|
|
"Mark Govea",
|
|
"Dagan Harrington",
|
|
"Clark Gaebel",
|
|
"Eric Chow",
|
|
"Mathias Jansson",
|
|
"David Clark",
|
|
"Michael Gardner",
|
|
"Mads Elvheim",
|
|
"Erik Sundell",
|
|
"Awoo",
|
|
"Dr. David G. Stork",
|
|
"Tianyu Ge",
|
|
"Ted Suzman",
|
|
"Linh Tran",
|
|
"Andrew Busey",
|
|
"John Haley",
|
|
"Ankalagon",
|
|
"Eric Lavault",
|
|
"Boris Veselinovich",
|
|
"Julian Pulgarin",
|
|
"Jeff Linse",
|
|
"Cooper Jones",
|
|
"Ryan Dahl",
|
|
"Robert Teed",
|
|
"Jason Hise",
|
|
"Meshal Alshammari",
|
|
"Bernd Sing",
|
|
"James Thornton",
|
|
"Mustafa Mahdi",
|
|
"Mathew Bramson",
|
|
"Jerry Ling",
|
|
"Shimin Kuang",
|
|
"Rish Kundalia",
|
|
"Achille Brighton",
|
|
"Ripta Pasay",
|
|
]
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|