3b1b-manim/active_projects/windmill.py
2019-08-01 10:40:44 -07:00

3400 lines
98 KiB
Python

from manimlib.imports import *
import json
class IntroduceIMO(Scene):
CONFIG = {
"num_countries": 130,
"use_real_images": True,
# "use_real_images": False,
"include_labels": False,
"camera_config": {"background_color": DARKER_GREY},
"random_seed": 6,
"year": 2019,
"n_flag_rows": 10,
}
def construct(self):
self.add_title()
self.show_flags()
self.show_students()
self.move_title()
self.isolate_usa()
def add_title(self):
title = TextMobject(
"International ", "Mathematical ", "Olympiad",
)
title.scale(1.25)
logo = ImageMobject("imo_logo")
logo.set_height(1)
group = Group(logo, title)
group.arrange(RIGHT)
group.to_edge(UP, buff=MED_SMALL_BUFF)
self.add(title, logo)
self.title = title
self.logo = logo
def show_flags(self):
flags = self.get_flags()
flags.set_height(6)
flags.to_edge(DOWN)
random_flags = Group(*flags)
random_flags.shuffle()
self.play(
LaggedStartMap(
FadeInFromDown, random_flags,
run_time=2,
lag_ratio=0.03,
)
)
self.remove(random_flags)
self.add(flags)
self.wait()
self.flags = flags
def show_students(self):
flags = self.flags
student_groups = VGroup()
all_students = VGroup()
for flag in flags:
group = self.get_students(flag)
student_groups.add(group)
for student in group:
student.preimage = VectorizedPoint()
student.preimage.move_to(flag)
all_students.add(student)
all_students.shuffle()
student_groups.generate_target()
student_groups.target.arrange_in_grid(
n_rows=self.n_flag_rows,
buff=SMALL_BUFF,
)
student_groups.target[-9:].align_to(student_groups.target[0], LEFT)
student_groups.target.match_height(flags)
student_groups.target.match_y(flags)
student_groups.target.to_edge(RIGHT, buff=1)
self.play(LaggedStart(
*[
ReplacementTransform(
student.preimage, student
)
for student in all_students
],
run_time=2,
lag_ratio=0.2,
))
self.wait()
self.play(
MoveToTarget(student_groups),
flags.space_out_submobjects, 0.8,
flags.to_edge, LEFT, MED_SMALL_BUFF,
)
self.wait()
self.student_groups = student_groups
def move_title(self):
title = self.title
logo = self.logo
new_title = TextMobject("IMO")
new_title.match_height(title)
logo.generate_target()
group = Group(logo.target, new_title)
group.arrange(RIGHT, buff=SMALL_BUFF)
group.match_y(title)
group.match_x(self.student_groups, UP)
title.generate_target()
for word, letter in zip(title.target, new_title[0]):
for nl in word:
nl.move_to(letter)
word.set_opacity(0)
word[0].set_opacity(1)
word[0].become(letter)
self.play(
MoveToTarget(title),
MoveToTarget(logo),
)
self.wait()
def isolate_usa(self):
flags = self.flags
student_groups = self.student_groups
us_flag = flags[0]
random_flags = Group(*flags[1:])
random_flags.shuffle()
old_height = us_flag.get_height()
us_flag.label.set_width(0.8 * us_flag.get_width())
us_flag.label.next_to(
us_flag, DOWN,
buff=0.2 * us_flag.get_height(),
)
us_flag.label.set_opacity(0)
us_flag.add(us_flag.label)
us_flag.generate_target()
us_flag.target.scale(1 / old_height)
us_flag.target.to_corner(UL)
us_flag.target[1].set_opacity(1)
self.remove(us_flag)
self.play(
LaggedStart(
*[
FadeOutAndShift(flag, DOWN)
for flag in random_flags
],
lag_ratio=0.05,
run_time=1.5
),
MoveToTarget(us_flag),
student_groups[1:].fade, 0.9,
)
self.wait()
#
def get_students(self, flag):
dots = VGroup(*[Dot() for x in range(6)])
dots.arrange_in_grid(n_cols=2, buff=SMALL_BUFF)
dots.match_height(flag)
dots.next_to(flag, RIGHT, SMALL_BUFF)
dots[flag.n_students:].set_opacity(0)
if isinstance(flag, ImageMobject):
rgba = random.choice(random.choice(flag.pixel_array))
if np.all(rgba < 100):
rgba = interpolate(rgba, 256 * np.ones(len(rgba)), 0.5)
color = rgba_to_color(rgba / 256)
else:
color = random_bright_color()
dots.set_color(color)
return dots
def get_flags(self):
year = self.year
file = "{}_imo_countries.json".format(year)
with open(os.path.join("assets", file)) as fp:
countries_with_counts = json.load(fp)
with open(os.path.join("assets", "country_codes.json")) as fp:
country_codes = json.load(fp)
country_to_code2 = dict([
(country.lower(), code2.lower())
for country, code2, code3 in country_codes
])
country_to_code3 = dict([
(country.lower(), code3.lower())
for country, code2, code3 in country_codes
])
images = Group()
for country, count in countries_with_counts:
country = country.lower()
alt_names = [
("united states of america", "united states"),
("people's republic of china", "china"),
("macau", "macao"),
("syria", "syrian arab republic"),
("north macedonia", "macedonia, the former yugoslav republic of"),
("tanzania", "united republic of tanzania"),
("vietnam", "viet nam"),
("ivory coast", "cote d'ivoire")
]
for n1, n2 in alt_names:
if country == n1:
country = n2
if country not in country_to_code2:
print("Can't find {}".format(country))
continue
short_code = country_to_code2[country]
try:
image = ImageMobject(os.path.join("flags", short_code))
image.set_width(1)
label = VGroup(*[
TextMobject(l)
for l in country_to_code3[country].upper()
])
label.arrange(RIGHT, buff=0.05, aligned_edge=DOWN)
label.set_height(0.25)
if not self.use_real_images:
rect = SurroundingRectangle(image, buff=0)
rect.set_stroke(WHITE, 1)
image = rect
image.label = label
image.n_students = count
images.add(image)
except OSError:
print("Failed on {}".format(country))
n_rows = self.n_flag_rows
images.arrange_in_grid(
n_rows=n_rows,
buff=1.25,
)
images[-(len(images) % n_rows):].align_to(images[0], LEFT)
sf = 1.7
images.stretch(sf, 0)
for i, image in enumerate(images):
image.set_height(1)
image.stretch(1 / sf, 0)
image.label.next_to(image, DOWN, SMALL_BUFF)
if self.include_labels:
image.add(image.label)
images.set_width(FRAME_WIDTH - 1)
if images.get_height() > FRAME_HEIGHT - 1:
images.set_height(FRAME_HEIGHT - 1)
images.center()
return images
class FootnoteToIMOIntro(Scene):
def construct(self):
words = TextMobject("$^*$Based on data from 2019 test")
self.play(FadeInFrom(words, UP))
self.wait()
class ShowTest(Scene):
def construct(self):
self.introduce_test()
def introduce_test(self):
test = self.get_test()
test.generate_target()
test.target.to_edge(UP)
# Time label
time_labels = VGroup(
TextMobject("Day 1", ": 4.5 hours"),
TextMobject("Day 2", ": 4.5 hours"),
)
time_labels.scale(1.5)
day_labels = VGroup()
hour_labels = VGroup()
for label, page in zip(time_labels, test.target):
label.next_to(page, DOWN)
label[0].save_state()
label[0].next_to(page, DOWN)
label[1][1:].set_color(YELLOW)
day_labels.add(label[0])
hour_labels.add(label[1])
# Problem desciptions
problem_rects = self.get_problem_rects(test.target[0])
proof_words = VGroup()
for rect in problem_rects:
word = TextMobject("Proof")
word.scale(2)
word.next_to(rect, RIGHT, buff=3)
word.set_color(BLUE)
proof_words.add(word)
proof_words.space_out_submobjects(2)
proof_arrows = VGroup()
for rect, word in zip(problem_rects, proof_words):
arrow = Arrow(word.get_left(), rect.get_right())
arrow.match_color(word)
proof_arrows.add(arrow)
scores = VGroup()
for word in proof_words:
score = VGroup(TexMobject("/"), Integer(0))
score.arrange(RIGHT, buff=SMALL_BUFF)
score.scale(2)
score.next_to(word, RIGHT, buff=1.5)
scores.add(score)
# Introduce test
self.play(
LaggedStart(
FadeInFrom(test[0], 2 * RIGHT),
FadeInFrom(test[1], 2 * LEFT),
lag_ratio=0.3,
)
)
self.wait()
self.play(
MoveToTarget(test, lag_ratio=0.2),
FadeInFrom(day_labels, UP, lag_ratio=0.2),
)
self.wait()
self.play(
*map(Restore, day_labels),
FadeInFrom(hour_labels, LEFT),
)
self.wait()
# Discuss problems
self.play(
FadeOut(test[1]),
FadeOut(time_labels[1]),
LaggedStartMap(ShowCreation, problem_rects),
run_time=1,
)
self.play(
LaggedStart(*[
FadeInFrom(word, LEFT)
for word in proof_words
]),
LaggedStart(*[
GrowArrow(arrow)
for arrow in proof_arrows
]),
)
self.wait()
self.play(FadeIn(scores))
self.play(
LaggedStart(*[
ChangeDecimalToValue(score[1], 7)
for score in scores
], lag_ratio=0, rate_func=rush_into)
)
self.wait()
self.scores = scores
self.proof_arrows = proof_arrows
self.proof_words = proof_words
self.problem_rects = problem_rects
self.test = test
self.time_labels = time_labels
def get_test(self):
group = Group(
ImageMobject("imo_2011_p1"),
ImageMobject("imo_2011_p2"),
)
group.set_height(6)
group.arrange(RIGHT, buff=LARGE_BUFF)
for page in group:
rect = SurroundingRectangle(page, buff=0.01)
rect.set_stroke(WHITE, 1)
page.add(rect)
# page.pixel_array[:, :, :3] = 255 - page.pixel_array[:, :, :3]
return group
def get_problem_rects(self, page):
pw = page.get_width()
rects = VGroup(*[Rectangle() for x in range(3)])
rects.set_stroke(width=2)
rects.set_color_by_gradient([BLUE_E, BLUE_C, BLUE_D])
rects.set_width(pw * 0.75)
for factor, rect in zip([0.095, 0.16, 0.1], rects):
rect.set_height(factor * pw, stretch=True)
rects.arrange(DOWN, buff=0.08)
rects.move_to(page)
rects.shift(0.09 * pw * DOWN)
return rects
class USProcess(IntroduceIMO):
CONFIG = {
}
def construct(self):
self.add_flag_and_label()
self.show_tests()
self.show_imo()
def add_flag_and_label(self):
flag = ImageMobject("flags/us")
flag.set_height(1)
flag.to_corner(UL)
label = VGroup(*map(TextMobject, "USA"))
label.arrange(RIGHT, buff=0.05, aligned_edge=DOWN)
label.set_width(0.8 * flag.get_width())
label.next_to(flag, DOWN, buff=0.2 * flag.get_height())
self.add(flag, label)
self.flag = flag
def show_tests(self):
tests = VGroup(
self.get_test(
["American ", "Mathematics ", "Contest"],
n_questions=25,
time_string="75 minutes",
hours=1.25,
n_students=250000,
),
self.get_test(
["American ", "Invitational ", "Math ", "Exam"],
n_questions=15,
time_string="3 hours",
hours=3,
n_students=12000,
),
self.get_test(
["U", "S", "A ", "Math ", "Olympiad"],
n_questions=6,
time_string="$2 \\times 4.5$ hours",
hours=4.5,
n_students=500,
),
self.get_test(
["Mathematical ", "Olympiad ", "Program"],
n_questions=None,
time_string="3 weeks",
hours=None,
n_students=60
)
)
amc, aime, usamo, mop = tests
amc.to_corner(UR)
top_point = amc.get_top()
last_arrow = VectorizedPoint()
last_arrow.to_corner(DL)
next_anims = []
for test in tests:
test.move_to(top_point, UP)
test.shift_onto_screen()
self.play(
Write(test.name),
*next_anims,
run_time=1,
)
self.wait()
self.animate_name_abbreviation(test)
self.wait()
if isinstance(test.nq_label[0], Integer):
int_mob = test.nq_label[0]
n = int_mob.get_value()
int_mob.set_value(0)
self.play(
ChangeDecimalToValue(int_mob, n),
FadeIn(test.nq_label[1:])
)
else:
self.play(FadeIn(test.nq_label))
self.play(
FadeIn(test.t_label)
)
self.wait()
test.generate_target()
test.target.scale(0.575)
test.target.next_to(last_arrow, RIGHT, buff=SMALL_BUFF)
test.target.shift_onto_screen()
next_anims = [
MoveToTarget(test),
GrowArrow(last_arrow),
]
last_arrow = Vector(0.5 * RIGHT)
last_arrow.set_color(WHITE)
last_arrow.next_to(test.target, RIGHT, SMALL_BUFF)
self.play(*next_anims)
self.tests = tests
def show_imo(self):
tests = self.tests
logo = ImageMobject("imo_logo")
logo.set_height(1)
name = TextMobject("IMO")
name.scale(2)
group = Group(logo, name)
group.arrange(RIGHT)
group.to_corner(UR)
group.shift(2 * LEFT)
students = VGroup(*[
PiCreature()
for x in range(6)
])
students.arrange_in_grid(n_cols=3, buff=LARGE_BUFF)
students.set_height(2)
students.next_to(group, DOWN)
colors = it.cycle([RED, LIGHT_GREY, BLUE])
for student, color in zip(students, colors):
student.set_color(color)
student.save_state()
student.move_to(tests[-1])
student.fade(1)
self.play(FadeInFromDown(group))
self.play(
LaggedStartMap(
Restore, students,
run_time=3,
lag_ratio=0.3,
)
)
self.play(
LaggedStart(*[
ApplyMethod(student.change, "hooray")
for student in students
])
)
for x in range(3):
self.play(Blink(random.choice(students)))
self.wait()
#
def animate_name_abbreviation(self, test):
name = test.name
short_name = test.short_name
short_name.move_to(name, LEFT)
name.generate_target()
for p1, p2 in zip(name.target, short_name):
for letter in p1:
letter.move_to(p2[0])
letter.set_opacity(0)
p1[0].set_opacity(1)
self.add(test.rect, test.name, test.ns_label)
self.play(
FadeIn(test.rect),
MoveToTarget(name),
FadeIn(test.ns_label),
)
test.remove(name)
test.add(short_name)
self.remove(name)
self.add(short_name)
def get_test(self, name_parts, n_questions, time_string, hours, n_students):
T_COLOR = GREEN_B
Q_COLOR = YELLOW
name = TextMobject(*name_parts)
short_name = TextMobject(*[np[0] for np in name_parts])
if n_questions:
nq_label = VGroup(
Integer(n_questions),
TextMobject("questions")
)
nq_label.arrange(RIGHT)
else:
nq_label = TextMobject("Lots of training")
nq_label.set_color(Q_COLOR)
if time_string:
t_label = TextMobject(time_string)
t_label.set_color(T_COLOR)
else:
t_label = Integer(0).set_opacity(0)
clock = Clock()
clock.hour_hand.set_opacity(0)
clock.minute_hand.set_opacity(0)
clock.set_stroke(WHITE, 2)
if hours:
sector = Sector(
start_angle=TAU / 4,
angle=-TAU * (hours / 12),
outer_radius=clock.get_width() / 2,
arc_center=clock.get_center()
)
sector.set_fill(T_COLOR, 0.5)
sector.set_stroke(T_COLOR, 2)
clock.add(sector)
if hours == 4.5:
plus = TexMobject("+").scale(2)
plus.next_to(clock, RIGHT)
clock_copy = clock.copy()
clock_copy.next_to(plus, RIGHT)
clock.add(plus, clock_copy)
else:
clock.set_opacity(0)
clock.set_height(1)
clock.next_to(t_label, RIGHT, buff=MED_LARGE_BUFF)
t_label.add(clock)
ns_label = TextMobject("$\\sim${:,} students".format(n_students))
result = VGroup(
name,
nq_label,
t_label,
ns_label,
)
result.arrange(
DOWN,
buff=MED_LARGE_BUFF,
aligned_edge=LEFT,
)
rect = SurroundingRectangle(result, buff=MED_SMALL_BUFF)
rect.set_width(
result[1:].get_width() + MED_LARGE_BUFF,
about_edge=LEFT,
stretch=True,
)
rect.set_stroke(WHITE, 2)
rect.set_fill(BLACK, 1)
result.add_to_back(rect)
result.name = name
result.short_name = short_name
result.nq_label = nq_label
result.t_label = t_label
result.ns_label = ns_label
result.rect = rect
result.clock = clock
return result
class Describe2011IMO(IntroduceIMO):
CONFIG = {
"year": 2011,
"use_real_images": True,
"n_flag_rows": 10,
"student_data": [
[1, "Lisa Sauermann", "de", [7, 7, 7, 7, 7, 7]],
[2, "Jeck Lim", "sg", [7, 5, 7, 7, 7, 7]],
[3, "Lin Chen", "cn", [7, 3, 7, 7, 7, 7]],
[4, "Jun Jie Joseph Kuan", "sg", [7, 7, 7, 7, 7, 1]],
[4, "David Yang", "us", [7, 7, 7, 7, 7, 1]],
[6, "Jie Jun Ang", "sg", [7, 7, 7, 7, 7, 0]],
[6, "Kensuke Yoshida", "jp", [7, 6, 7, 7, 7, 1]],
[6, "Raul Sarmiento", "pe", [7, 7, 7, 7, 7, 0]],
[6, "Nipun Pitimanaaree", "th", [7, 7, 7, 7, 7, 0]],
],
}
def construct(self):
self.add_title()
self.add_flags_and_students()
self.comment_on_primality()
self.show_top_three_scorers()
def add_title(self):
year = TexMobject("2011")
logo = ImageMobject("imo_logo")
imo = TextMobject("IMO")
group = Group(year, logo, imo)
group.scale(1.25)
logo.set_height(1)
group.arrange(RIGHT)
group.to_corner(UR, buff=MED_SMALL_BUFF)
group.shift(LEFT)
self.add(group)
self.play(FadeInFrom(year, RIGHT))
self.title = group
def add_flags_and_students(self):
flags = self.get_flags()
flags.space_out_submobjects(0.8)
sf = 0.8
flags.stretch(sf, 0)
for flag in flags:
flag.stretch(1 / sf, 0)
flags.set_height(5)
flags.to_corner(DL)
student_groups = VGroup(*[
self.get_students(flag)
for flag in flags
])
student_groups.arrange_in_grid(
n_rows=self.n_flag_rows,
buff=SMALL_BUFF,
)
student_groups[-1].align_to(student_groups, LEFT)
student_groups.set_height(6)
student_groups.next_to(self.title, DOWN)
flags.align_to(student_groups, UP)
all_students = VGroup(*it.chain(*[
[
student
for student in group
if student.get_fill_opacity() > 0
]
for group in student_groups
]))
# Counters
student_counter = VGroup(
Integer(0),
TextMobject("Participants"),
)
student_counter.set = all_students
student_counter.next_to(self.title, LEFT, MED_LARGE_BUFF)
student_counter.right_edge = student_counter.get_right()
def update_counter(counter):
counter[0].set_value(len(counter.set))
counter.arrange(RIGHT)
counter[0].align_to(counter[1][0][0], DOWN)
counter.move_to(counter.right_edge, RIGHT)
student_counter.add_updater(update_counter)
flag_counter = VGroup(
Integer(0),
TextMobject("Countries")
)
flag_counter.set = flags
flag_counter.next_to(student_counter, LEFT, buff=0.75)
flag_counter.align_to(student_counter[0], DOWN)
flag_counter.right_edge = flag_counter.get_right()
flag_counter.add_updater(update_counter)
self.add(student_counter)
self.play(
ShowIncreasingSubsets(all_students),
run_time=3,
)
self.wait()
self.add(flag_counter)
self.play(
ShowIncreasingSubsets(flags),
run_time=3,
)
self.wait()
self.student_counter = student_counter
self.flag_counter = flag_counter
self.all_students = all_students
self.student_groups = student_groups
self.flags = flags
def comment_on_primality(self):
full_rect = FullScreenFadeRectangle(opacity=0.9)
numbers = VGroup(
self.title[0],
self.student_counter[0],
self.flag_counter[0],
)
lines = VGroup(*[
Line().match_width(number).next_to(number, DOWN, SMALL_BUFF)
for number in numbers
])
lines.set_stroke(TEAL, 2)
morty = Mortimer()
morty.flip()
morty.to_corner(DL)
morty.look_at(numbers)
self.add(full_rect, numbers)
self.play(
FadeIn(full_rect),
morty.change, "sassy",
VFadeIn(morty),
)
self.play(
ShowCreation(lines),
morty.change, "pondering",
)
self.play(Blink(morty))
self.play(
PiCreatureBubbleIntroduction(
morty,
"${1 \\over \\ln(101)}"
"{1 \\over \\ln(563)}"
"{1 \\over \\ln(2011)}$",
bubble_class=ThoughtBubble,
target_mode="thinking",
)
)
self.play(Blink(morty))
self.play(
FadeOut(morty),
FadeOut(morty.bubble),
FadeOut(morty.bubble.content),
)
self.play(FadeOut(full_rect), FadeOut(lines))
def show_top_three_scorers(self):
student_groups = self.student_groups
all_students = self.all_students
flags = self.flags
student_counter = self.student_counter
flag_counter = self.flag_counter
student = student_groups[10][0]
flag = flags[10]
students_to_fade = VGroup(*filter(
lambda s: s is not student,
all_students
))
flags_to_fade = Group(*filter(
lambda s: s is not flag,
flags
))
grid = self.get_score_grid()
grid.shift(3 * DOWN)
title_row = grid.rows[0]
top_row = grid.rows[1]
self.play(
LaggedStartMap(FadeOutAndShiftDown, students_to_fade),
LaggedStartMap(FadeOutAndShiftDown, flags_to_fade),
ChangeDecimalToValue(student_counter[0], 1),
FadeOut(flag_counter),
run_time=2
)
student_counter[1][0][-1].fade(1)
self.play(
Write(top_row[0]),
ReplacementTransform(student, top_row[1][1]),
flag.replace, top_row[1][0],
)
self.remove(flag)
self.add(top_row[1])
self.play(
LaggedStartMap(FadeIn, title_row[2:]),
LaggedStartMap(FadeIn, top_row[2:]),
)
self.wait()
self.play(
LaggedStart(*[
FadeInFrom(row, UP)
for row in grid.rows[2:4]
]),
LaggedStart(*[
ShowCreation(line)
for line in grid.h_lines[:2]
]),
lag_ratio=0.5,
)
self.wait()
self.play(
ShowCreationThenFadeAround(
Group(title_row[3], grid.rows[3][3]),
)
)
self.wait()
student_counter.clear_updaters()
self.play(
FadeOutAndShift(self.title, UP),
FadeOutAndShift(student_counter, UP),
grid.rows[:4].shift, 3 * UP,
grid.h_lines[:3].shift, 3 * UP,
)
remaining_rows = grid.rows[4:]
remaining_lines = grid.h_lines[3:]
Group(remaining_rows, remaining_lines).shift(3 * UP)
self.play(
LaggedStartMap(
FadeInFrom, remaining_rows,
lambda m: (m, UP),
),
LaggedStartMap(ShowCreation, remaining_lines),
lag_ratio=0.3,
run_time=2,
)
self.wait()
def get_score_grid(self):
data = self.student_data
ranks = VGroup(*[
Integer(row[0])
for row in data
])
# Combine students with flags
students = VGroup(*[
TextMobject(row[1])
for row in data
])
flags = Group(*[
ImageMobject("flags/{}.png".format(row[2])).set_height(0.3)
for row in data
])
students = Group(*[
Group(flag.next_to(student, LEFT, buff=0.2), student)
for flag, student in zip(flags, students)
])
score_rows = VGroup(*[
VGroup(*map(Integer, row[3]))
for row in data
])
colors = color_gradient([RED, YELLOW, GREEN], 8)
for score_row in score_rows:
for score in score_row:
score.set_color(colors[score.get_value()])
titles = VGroup(*[
VectorizedPoint(),
VectorizedPoint(),
*[
TextMobject("P{}".format(i))
for i in range(1, 7)
]
])
titles.arrange(RIGHT, buff=MED_LARGE_BUFF)
titles[2:].shift(students.get_width() * RIGHT)
rows = Group(titles, *[
Group(rank, student, *score_row)
for rank, flag, student, score_row in zip(
ranks, flags, students, score_rows
)
])
rows.arrange(DOWN)
rows.to_edge(UP)
for row in rows:
for i, e1, e2 in zip(it.count(), titles, row):
if i < 2:
e2.align_to(e1, LEFT)
else:
e2.match_x(e1)
ranks.next_to(students, LEFT)
h_lines = VGroup()
for r1, r2 in zip(rows[1:], rows[2:]):
line = Line()
line.set_stroke(WHITE, 0.5)
line.match_width(r2)
line.move_to(midpoint(r1.get_bottom(), r2.get_top()))
line.align_to(r2, LEFT)
h_lines.add(line)
grid = Group(rows, h_lines)
grid.rows = rows
grid.h_lines = h_lines
return grid
class AskWhatsOnTest(ShowTest, MovingCameraScene):
def construct(self):
self.force_skipping()
self.introduce_test()
self.revert_to_original_skipping_status()
self.ask_about_questions()
def ask_about_questions(self):
scores = self.scores
arrows = self.proof_arrows
proof_words = self.proof_words
question = TextMobject("What kind \\\\ of problems?")
question.scale(1.5)
question.move_to(proof_words, LEFT)
research = TextMobject("Research-lite")
research.scale(1.5)
research.move_to(question, LEFT)
research.shift(MED_SMALL_BUFF * RIGHT)
research.set_color(BLUE)
arrows.generate_target()
for arrow in arrows.target:
end = arrow.get_end()
start = arrow.get_start()
arrow.put_start_and_end_on(
interpolate(question.get_left(), start, 0.1),
end
)
self.play(
FadeOut(scores),
FadeOut(proof_words),
MoveToTarget(arrows),
Write(question),
)
self.wait()
self.play(
FadeInFrom(research, DOWN),
question.shift, 2 * UP,
)
self.wait()
# Experience
randy = Randolph(height=2)
randy.move_to(research.get_corner(UL), DL)
randy.shift(SMALL_BUFF * RIGHT)
clock = Clock()
clock.set_height(1)
clock.next_to(randy, UR)
self.play(
FadeOut(question),
FadeIn(randy),
FadeInFromDown(clock),
)
self.play(
randy.change, "pondering",
ClockPassesTime(clock, run_time=5, hours_passed=5),
)
self.play(
ClockPassesTime(clock, run_time=2, hours_passed=2),
VFadeOut(clock),
Blink(randy),
VFadeOut(randy),
LaggedStartMap(
FadeOut,
VGroup(
research,
*arrows,
*self.problem_rects,
self.time_labels[0]
)
),
)
# Second part
big_rect = FullScreenFadeRectangle()
lil_rect = self.problem_rects[1].copy()
lil_rect.reverse_points()
big_rect.append_vectorized_mobject(lil_rect)
frame = self.camera_frame
frame.generate_target()
frame.target.scale(0.35)
frame.target.move_to(lil_rect)
self.play(
FadeInFromDown(self.test[1]),
)
self.wait()
self.play(
FadeIn(big_rect),
MoveToTarget(frame, run_time=3),
)
self.wait()
class ReadQuestions(Scene):
def construct(self):
background = ImageMobject("AskWhatsOnTest_final_image")
background.set_height(FRAME_HEIGHT)
self.add(background)
lines = SVGMobject("imo_2011_2_underline-01")
lines.set_width(FRAME_WIDTH - 1)
lines.move_to(0.1 * DOWN)
lines.set_stroke(TEAL, 3)
clump_sizes = [1, 2, 3, 2, 1, 2]
partial_sums = list(np.cumsum(clump_sizes))
clumps = VGroup(*[
lines[i:j]
for i, j in zip(
[0] + partial_sums,
partial_sums,
)
])
faders = []
for clump in clumps:
rects = VGroup()
for line in clump:
rect = Rectangle()
rect.set_stroke(width=0)
rect.set_fill(TEAL, 0.25)
rect.set_width(line.get_width() + SMALL_BUFF)
rect.set_height(0.35, stretch=True)
rect.move_to(line, DOWN)
rects.add(rect)
self.play(
ShowCreation(clump, run_time=2),
FadeIn(rects),
*faders,
)
self.wait()
faders = [
FadeOut(clump),
FadeOut(rects),
]
self.play(*faders)
self.wait()
# Windmill scenes
class WindmillScene(Scene):
CONFIG = {
"dot_config": {
"fill_color": LIGHT_GREY,
"radius": 0.05,
"background_stroke_width": 2,
"background_stroke_color": BLACK,
},
"windmill_style": {
"stroke_color": RED,
"stroke_width": 2,
"background_stroke_width": 3,
"background_stroke_color": BLACK,
},
"windmill_length": 2 * FRAME_WIDTH,
"windmill_rotation_speed": 0.25,
# "windmill_rotation_speed": 0.5,
# "hit_sound": "pen_click.wav",
"hit_sound": "pen_click.wav",
"leave_shadows": False,
}
def get_random_point_set(self, n_points=11, width=6, height=6):
return np.array([
[
-width / 2 + np.random.random() * width,
-height / 2 + np.random.random() * height,
0
]
for n in range(n_points)
])
def get_dots(self, points):
return VGroup(*[
Dot(point, **self.dot_config)
for point in points
])
def get_windmill(self, points, pivot=None, angle=TAU / 4):
line = Line(LEFT, RIGHT)
line.set_length(self.windmill_length)
line.set_angle(angle)
line.set_style(**self.windmill_style)
line.point_set = points
if pivot is not None:
line.pivot = pivot
else:
line.pivot = points[0]
line.rot_speed = self.windmill_rotation_speed
line.add_updater(lambda l: l.move_to(l.pivot))
return line
def get_pivot_dot(self, windmill, color=YELLOW):
pivot_dot = Dot(color=YELLOW)
pivot_dot.add_updater(lambda d: d.move_to(windmill.pivot))
return pivot_dot
def start_leaving_shadows(self):
self.leave_shadows = True
self.add(self.get_windmill_shadows())
def get_windmill_shadows(self):
if not hasattr(self, "windmill_shadows"):
self.windmill_shadows = VGroup()
return self.windmill_shadows
def next_pivot_and_angle(self, windmill):
curr_angle = windmill.get_angle()
pivot = windmill.pivot
non_pivots = list(filter(
lambda p: not np.all(p == pivot),
windmill.point_set
))
angles = np.array([
-(angle_of_vector(point - pivot) - curr_angle) % PI
for point in non_pivots
])
# Edge case for 2 points
tiny_indices = angles < 1e-6
if np.all(tiny_indices):
return non_pivots[0], PI
angles[tiny_indices] = np.inf
index = np.argmin(angles)
return non_pivots[index], angles[index]
def rotate_to_next_pivot(self, windmill, max_time=None, added_anims=None):
"""
Returns animations to play following the contact, and total run time
"""
new_pivot, angle = self.next_pivot_and_angle(windmill)
change_pivot_at_end = True
if added_anims is None:
added_anims = []
run_time = angle / windmill.rot_speed
if max_time is not None and run_time > max_time:
ratio = max_time / run_time
rate_func = (lambda t: ratio * t)
run_time = max_time
change_pivot_at_end = False
else:
rate_func = linear
for anim in added_anims:
if anim.run_time > run_time:
anim.run_time = run_time
self.play(
Rotate(
windmill,
-angle,
rate_func=rate_func,
run_time=run_time,
),
*added_anims,
)
if change_pivot_at_end:
self.handle_pivot_change(windmill, new_pivot)
# Return animations to play
return [self.get_hit_flash(new_pivot)], run_time
def handle_pivot_change(self, windmill, new_pivot):
windmill.pivot = new_pivot
self.add_sound(self.hit_sound)
if self.leave_shadows:
new_shadow = windmill.copy()
new_shadow.fade(0.5)
new_shadow.set_stroke(width=1)
new_shadow.clear_updaters()
shadows = self.get_windmill_shadows()
shadows.add(new_shadow)
def let_windmill_run(self, windmill, time):
# start_time = self.get_time()
# end_time = start_time + time
# curr_time = start_time
anims_from_last_hit = []
while time > 0:
anims_from_last_hit, last_run_time = self.rotate_to_next_pivot(
windmill,
max_time=time,
added_anims=anims_from_last_hit,
)
time -= last_run_time
# curr_time = self.get_time()
def add_dot_color_updater(self, dots, windmill, **kwargs):
for dot in dots:
dot.add_updater(lambda d: self.update_dot_color(
d, windmill, **kwargs
))
def update_dot_color(self, dot, windmill, color1=BLUE, color2=GREY_BROWN):
perp = rotate_vector(windmill.get_vector(), TAU / 4)
dot_product = np.dot(perp, dot.get_center() - windmill.pivot)
if dot_product > 0:
dot.set_color(color1)
# elif dot_product < 0:
else:
dot.set_color(color2)
# else:
# dot.set_color(WHITE)
dot.set_stroke(
# interpolate_color(dot.get_fill_color(), WHITE, 0.5),
WHITE,
width=2,
background=True
)
def get_hit_flash(self, point):
flash = Flash(
point,
line_length=0.1,
flash_radius=0.2,
run_time=0.5,
remover=True,
)
flash_mob = flash.mobject
for submob in flash_mob:
submob.reverse_points()
return Uncreate(
flash.mobject,
run_time=0.25,
lag_ratio=0,
)
def get_pivot_counters(self, windmill, counter_height=0.25, buff=0.2, color=WHITE):
points = windmill.point_set
counters = VGroup()
for point in points:
counter = Integer(0)
counter.set_color(color)
counter.set_height(counter_height)
counter.next_to(point, UP, buff=buff)
counter.point = point
counter.windmill = windmill
counter.is_pivot = False
counter.add_updater(self.update_counter)
counters.add(counter)
return counters
def update_counter(self, counter):
dist = get_norm(counter.point - counter.windmill.pivot)
counter.will_be_pivot = (dist < 1e-6)
if (not counter.is_pivot) and counter.will_be_pivot:
counter.increment_value()
counter.is_pivot = counter.will_be_pivot
def get_orientation_arrows(self, windmill, n_tips=20):
tips = VGroup(*[
ArrowTip(start_angle=0)
for x in range(n_tips)
])
tips.stretch(0.75, 1)
tips.scale(0.5)
tips.rotate(windmill.get_angle())
tips.match_color(windmill)
tips.set_stroke(BLACK, 1, background=True)
for tip, a in zip(tips, np.linspace(0, 1, n_tips)):
tip.shift(
windmill.point_from_proportion(a) - tip.points[0]
)
return tips
def get_left_right_colorings(self, windmill, opacity=0.3):
rects = VGroup(VMobject(), VMobject())
rects.const_opacity = opacity
def update_regions(rects):
p0, p1 = windmill.get_start_and_end()
v = p1 - p0
vl = rotate_vector(v, 90 * DEGREES)
vr = rotate_vector(v, -90 * DEGREES)
p2 = p1 + vl
p3 = p0 + vl
p4 = p1 + vr
p5 = p0 + vr
rects[0].set_points_as_corners([p0, p1, p2, p3])
rects[1].set_points_as_corners([p0, p1, p4, p5])
rects.set_stroke(width=0)
rects[0].set_fill(BLUE, rects.const_opacity)
rects[1].set_fill(GREY_BROWN, rects.const_opacity)
return rects
rects.add_updater(update_regions)
return rects
class IntroduceWindmill(WindmillScene):
CONFIG = {
"final_run_time": 60,
"windmill_rotation_speed": 0.5,
}
def construct(self):
self.add_points()
self.exclude_colinear()
self.add_line()
self.switch_pivots()
self.continue_and_count()
def add_points(self):
points = self.get_random_point_set(8)
points[-1] = midpoint(points[0], points[1])
dots = self.get_dots(points)
dots.set_color(YELLOW)
dots.set_height(3)
braces = VGroup(
Brace(dots, LEFT),
Brace(dots, RIGHT),
)
group = VGroup(dots, braces)
group.set_height(4)
group.center().to_edge(DOWN)
S, eq = S_eq = TexMobject("\\mathcal{S}", "=")
S_eq.scale(2)
S_eq.next_to(braces, LEFT)
self.play(
FadeIn(S_eq),
FadeInFrom(braces[0], RIGHT),
FadeInFrom(braces[1], LEFT),
)
self.play(
LaggedStartMap(FadeInFromLarge, dots)
)
self.wait()
self.play(
S.next_to, dots, LEFT,
{"buff": 2, "aligned_edge": UP},
FadeOut(braces),
FadeOut(eq),
)
self.S_label = S
self.dots = dots
def exclude_colinear(self):
dots = self.dots
line = Line(dots[0].get_center(), dots[1].get_center())
line.scale(1.5)
line.set_stroke(WHITE)
words = TextMobject("Not allowed!")
words.scale(2)
words.set_color(RED)
words.next_to(line.get_center(), RIGHT)
self.add(line, dots)
self.play(
ShowCreation(line),
FadeInFrom(words, LEFT),
dots[-1].set_color, RED,
)
self.wait()
self.play(
FadeOut(line),
FadeOut(words),
)
self.play(
FadeOutAndShift(
dots[-1], 3 * RIGHT,
path_arc=-PI / 4,
rate_func=running_start,
)
)
dots.remove(dots[-1])
self.wait()
def add_line(self):
dots = self.dots
points = np.array(list(map(Mobject.get_center, dots)))
p0 = points[0]
windmill = self.get_windmill(points, p0, angle=60 * DEGREES)
pivot_dot = self.get_pivot_dot(windmill)
l_label = TexMobject("\\ell")
l_label.scale(1.5)
p_label = TexMobject("P")
l_label.next_to(
p0 + 2 * normalize(windmill.get_vector()),
RIGHT,
)
l_label.match_color(windmill)
p_label.next_to(p0, RIGHT)
p_label.match_color(pivot_dot)
arcs = VGroup(*[
Arc(angle=-45 * DEGREES, radius=1.5)
for x in range(2)
])
arcs[1].rotate(PI, about_point=ORIGIN)
for arc in arcs:
arc.add_tip(tip_length=0.2)
arcs.rotate(windmill.get_angle())
arcs.shift(p0)
self.add(windmill, dots)
self.play(
GrowFromCenter(windmill),
FadeInFrom(l_label, DL),
)
self.wait()
self.play(
TransformFromCopy(pivot_dot, p_label),
GrowFromCenter(pivot_dot),
dots.set_color, WHITE,
)
self.wait()
self.play(*map(ShowCreation, arcs))
self.wait()
# Rotate to next pivot
next_pivot, angle = self.next_pivot_and_angle(windmill)
self.play(
*[
Rotate(
mob, -0.99 * angle,
about_point=p0,
rate_func=linear,
)
for mob in [windmill, arcs, l_label]
],
VFadeOut(l_label),
)
self.add_sound(self.hit_sound)
self.play(
self.get_hit_flash(next_pivot)
)
self.wait()
self.pivot2 = next_pivot
self.pivot_dot = pivot_dot
self.windmill = windmill
self.p_label = p_label
self.arcs = arcs
def switch_pivots(self):
windmill = self.windmill
pivot2 = self.pivot2
p_label = self.p_label
arcs = self.arcs
q_label = TexMobject("Q")
q_label.set_color(YELLOW)
q_label.next_to(pivot2, DR, buff=SMALL_BUFF)
self.rotate_to_next_pivot(windmill)
self.play(
FadeInFrom(q_label, LEFT),
FadeOut(p_label),
FadeOut(arcs),
)
self.wait()
flashes, run_time = self.rotate_to_next_pivot(windmill)
self.remove(q_label)
self.add_sound(self.hit_sound)
self.play(*flashes)
self.wait()
self.let_windmill_run(windmill, 10)
def continue_and_count(self):
windmill = self.windmill
pivot_dot = self.pivot_dot
p_label = TexMobject("P")
p_label.match_color(pivot_dot)
p_label.next_to(pivot_dot, DR, buff=0)
l_label = TexMobject("\\ell")
l_label.scale(1.5)
l_label.match_color(windmill)
l_label.next_to(
windmill.get_center() + -3 * normalize(windmill.get_vector()),
DR,
buff=SMALL_BUFF,
)
self.play(FadeInFrom(p_label, UL))
self.play(FadeInFrom(l_label, LEFT))
self.wait()
self.add(
windmill.copy().fade(0.75),
pivot_dot.copy().fade(0.75),
)
pivot_counters = self.get_pivot_counters(windmill)
self.add(pivot_counters)
windmill.rot_speed *= 2
self.let_windmill_run(windmill, self.final_run_time)
class ContrastToOtherOlympiadProblems(AskWhatsOnTest):
def construct(self):
self.zoom_to_other_questions()
def zoom_to_other_questions(self):
test = self.get_test()
rects = self.get_all_rects()
big_rects = VGroup()
for rect in rects:
big_rect = FullScreenFadeRectangle()
rect.reverse_points()
big_rect.append_vectorized_mobject(rect)
big_rects.add(big_rect)
frame = self.camera_frame
frame.generate_target()
frame.target.scale(0.35)
frame.target.move_to(rects[1])
big_rect = big_rects[1].copy()
self.add(test)
self.play(
FadeIn(big_rect),
MoveToTarget(frame, run_time=3),
)
self.wait()
for i in [2, 0, 3, 5]:
self.play(
frame.move_to, rects[i],
Transform(big_rect, big_rects[i])
)
self.wait()
def get_all_rects(self, test):
rects = self.get_problem_rects(test[0])
new_rects = VGroup(rects[1], rects[0], rects[2]).copy()
new_rects[0].stretch(0.85, 1)
new_rects[1].stretch(0.8, 1)
new_rects[2].stretch(0.8, 1)
new_rects.arrange(DOWN, buff=0.08)
new_rects.move_to(test[1])
new_rects.align_to(rects, UP)
rects.add(*new_rects)
return rects
class WindmillExample30Points(WindmillScene):
CONFIG = {
"n_points": 30,
"random_seed": 0,
"run_time": 60,
"counter_config": {
"counter_height": 0.15,
"buff": 0.1,
},
}
def construct(self):
points = self.get_random_point_set(self.n_points)
points[:, 0] *= 1.5
sorted_points = sorted(list(points), key=lambda p: p[1])
sorted_points[4] += RIGHT
dots = self.get_dots(points)
windmill = self.get_windmill(points, sorted_points[5], angle=PI / 4)
pivot_dot = self.get_pivot_dot(windmill)
# self.add_dot_color_updater(dots, windmill)
self.add(windmill)
self.add(dots)
self.add(pivot_dot)
self.add(self.get_pivot_counters(
windmill, **self.counter_config
))
self.let_windmill_run(windmill, self.run_time)
class WindmillExample15Points(WindmillExample30Points):
CONFIG = {
"n_points": 15,
"run_time": 60,
"random_seed": 2,
"counter_config": {
"counter_height": 0.25,
"buff": 0.1,
},
}
class TheQuestion(Scene):
def construct(self):
words = TextMobject(
"Will each point be hit infinitely many times?"
)
words.set_width(FRAME_WIDTH - 1)
words.to_edge(UP)
self.add(words)
class SpiritOfIMO(PiCreatureScene):
def construct(self):
randy = self.pi_creature
problems = VGroup(*[
TextMobject("P{})".format(i))
for i in range(1, 7)
])
problems.arrange_in_grid(n_cols=2, buff=LARGE_BUFF)
problems.scale(1.5)
problems[3:].shift(1.5 * RIGHT)
problems.to_corner(UR, buff=LARGE_BUFF)
problems.shift(2 * LEFT)
light_bulbs = VGroup()
lights = VGroup()
for problem in problems:
light_bulb = Lightbulb()
light_bulb.base = light_bulb[:3]
light_bulb.light = light_bulb[3:]
light_bulb.set_height(1)
light_bulb.next_to(problem, RIGHT)
light_bulbs.add(light_bulb)
light = self.get_light(light_bulb.get_center())
lights.add(light)
self.play(
LaggedStartMap(FadeInFromDown, problems)
)
self.play(
LaggedStartMap(
FadeIn, light_bulbs,
run_time=1,
),
LaggedStartMap(
LaggedStartMap, lights,
lambda l: (VFadeInThenOut, l),
run_time=3
),
randy.change, "thinking"
)
self.wait()
self.pi_creature_thinks(
"Oh, I've\\\\seen this...",
target_mode="surprised",
)
self.wait(3)
def get_light(self, point):
radii = np.arange(0, 5, 0.1)
result = VGroup(*[
Annulus(
inner_radius=r1,
outer_radius=r2,
arc_center=point,
fill_opacity=(1 / (r1 + 1)**2),
fill_color=YELLOW,
)
for r1, r2 in zip(radii[1:], radii[2:])
])
return result
# TODO
class HowToPrepareForThis(Scene):
def construct(self):
pass
class HarderThanExpected(TeacherStudentsScene):
def construct(self):
title = TextMobject("Unusual aspect \\#2")
title.scale(1.5)
title.to_edge(UP)
line = Line(LEFT, RIGHT)
line.match_width(title)
line.next_to(title, DOWN)
words = TextMobject("Harder than expected")
words.set_color(RED)
words.scale(1.5)
words.next_to(line, DOWN, LARGE_BUFF)
self.play(
FadeInFromDown(title),
ShowCreation(line),
self.teacher.change, "raise_right_hand",
self.get_student_changes("pondering", "confused", "sassy")
)
self.wait()
self.play(
FadeInFrom(words, UP),
self.get_student_changes(*3 * ["horrified"]),
)
self.wait(3)
class TraditionalDifficulty(ContrastToOtherOlympiadProblems):
def construct(self):
test = self.get_test()
rects = self.get_all_rects(test)
for rect in rects:
rect.reverse_points()
big_rects = VGroup(*[
FullScreenFadeRectangle()
for x in range(3)
])
for br, r1, r2 in zip(big_rects, rects, rects[3:]):
br.append_vectorized_mobject(r1)
br.append_vectorized_mobject(r2)
big_rect = big_rects[0].copy()
p_labels = VGroup()
for i, rect in enumerate(rects):
p_label = TextMobject("P{}".format(i + 1))
p_label.next_to(rect, LEFT)
p_labels.add(p_label)
arrow = Vector(3 * DOWN)
arrow.next_to(test[0], RIGHT)
arrow.match_y(rects)
harder_words = TextMobject("Get harder")
harder_words.scale(2)
harder_words.next_to(arrow, RIGHT)
harder_words.set_color(RED)
p_words = VGroup(
TextMobject("Doable", color=GREEN),
TextMobject("Challenging", color=YELLOW),
TextMobject("Brutal", color=RED),
)
p_words.add(*p_words.copy())
for rect, word, label in zip(rects, p_words, p_labels):
word.next_to(rect, UP)
label.match_color(word)
self.add(test[0])
self.play(
FadeIn(harder_words),
GrowArrow(arrow),
LaggedStart(*[FadeInFrom(p, UP) for p in p_labels[:3]]),
LaggedStartMap(ShowCreation, rects[:3]),
)
self.wait()
self.play(
FadeIn(test[1]),
FadeIn(p_labels[3:]),
FadeIn(rects[3:]),
FadeOut(harder_words),
FadeOut(arrow),
)
self.wait()
self.add(big_rect, p_labels[0], p_labels[3])
self.play(
FadeIn(big_rect),
FadeOut(rects),
FadeOut(p_labels[1:3]),
FadeOut(p_labels[4:]),
FadeInFromDown(p_words[0::3]),
)
self.wait()
self.play(
Transform(big_rect, big_rects[1]),
FadeOut(p_labels[0::3]),
FadeIn(p_labels[1::3]),
FadeOutAndShift(p_words[0::3], DOWN),
FadeInFrom(p_words[1::3], UP),
)
self.wait()
self.play(
Transform(big_rect, big_rects[2]),
FadeOut(p_labels[1::3]),
FadeIn(p_labels[2::3]),
FadeOutAndShift(p_words[1::3], DOWN),
FadeInFrom(p_words[2::3], UP),
)
self.wait()
class PerfectScoreData(Describe2011IMO):
CONFIG = {
"n_students": 563,
"n_perfect_scores_per_problem": [
345, 22, 51, 267, 170, 6,
],
"full_bar_width": 7,
}
def construct(self):
self.add_title()
self.show_total_number_of_students()
self.add_subtitle()
self.show_data()
self.analyze_data()
def add_title(self):
self.force_skipping()
super().add_title()
self.revert_to_original_skipping_status()
self.title.center().to_edge(UP)
def show_total_number_of_students(self):
title = self.title
bar = self.get_bar(self.n_students, ORIGIN)
bar.next_to(title, DOWN, buff=0.3)
counter = self.get_bar_counter(bar)
counter_label = TextMobject("Students")
counter_label.add_updater(
lambda m: m.next_to(counter, RIGHT)
)
self.add(counter, counter_label)
self.play(
self.get_bar_growth_anim(bar),
run_time=2,
)
self.wait()
def add_subtitle(self):
title = self.title
subtitle = TextMobject(
"Number of perfect scores on each problem:"
)
subtitle.scale(1.25)
subtitle.set_color(GREEN)
subtitle.next_to(title, DOWN, buff=LARGE_BUFF)
problems = VGroup(*[
TextMobject("P{})".format(i))
for i in range(1, 7)
])
problems.arrange_in_grid(n_cols=2, buff=LARGE_BUFF)
problems[3:].shift(5 * RIGHT)
problems.next_to(subtitle, DOWN, LARGE_BUFF)
problems.to_edge(LEFT)
self.play(
FadeInFromDown(subtitle),
LaggedStartMap(FadeInFromDown, problems),
)
self.problems = problems
def show_data(self):
problems = self.problems
bars = VGroup(*[
self.get_bar(n, p.get_right() + SMALL_BUFF * RIGHT)
for n, p in zip(
self.n_perfect_scores_per_problem,
problems,
)
])
counters = VGroup(*map(self.get_bar_counter, bars))
self.play(
VFadeIn(counters),
*[
self.get_bar_growth_anim(bar)
for bar in bars
],
)
counters.set_fill(WHITE, 1)
self.wait()
self.problem_bars = bars
self.problem_counters = counters
def analyze_data(self):
problems = VGroup(*[
VGroup(p, pb, pc)
for p, pb, pc in zip(
self.problems,
self.problem_bars,
self.problem_counters,
)
])
rects = VGroup(*[
SurroundingRectangle(p, color=p[1].get_color())
for p in problems
])
rect = rects[1].copy()
self.play(ShowCreation(rect))
self.wait()
self.play(TransformFromCopy(rect, rects[4]))
self.wait()
self.play(TransformFromCopy(rect, rects[2]))
self.wait()
self.play(
ReplacementTransform(rect, rects[5]),
ReplacementTransform(rects[4], rects[5]),
ReplacementTransform(rects[2], rects[5]),
)
self.wait()
#
def get_bar(self, number, left_side):
bar = Rectangle()
bar.set_stroke(width=0)
bar.set_fill(WHITE, 1)
bar.set_height(0.25)
bar.set_width(
self.full_bar_width * number / self.n_students,
stretch=True
)
bar.move_to(left_side, LEFT)
def update_bar_color(bar):
frac = bar.get_width() / self.full_bar_width
if 0 < frac <= 0.25:
alpha = 4 * frac
bar.set_color(interpolate_color(RED, YELLOW, alpha))
elif 0.25 < frac <= 0.5:
alpha = 4 * (frac - 0.25)
bar.set_color(interpolate_color(YELLOW, GREEN, alpha))
else:
alpha = 2 * (frac - 0.5)
bar.set_color(interpolate_color(GREEN, BLUE, alpha))
bar.add_updater(update_bar_color)
return bar
def get_bar_growth_anim(self, bar):
bar.save_state()
bar.stretch(0, 0, about_edge=LEFT)
return Restore(
bar,
suspend_mobject_updating=False,
run_time=2,
)
def get_bar_counter(self, bar):
counter = Integer()
counter.add_updater(
lambda m: m.set_value(
self.n_students * bar.get_width() / self.full_bar_width
)
)
counter.add_updater(lambda m: m.next_to(bar, RIGHT, SMALL_BUFF))
return counter
class SixOnSix(Describe2011IMO):
CONFIG = {
"student_data": [
[1, "Lisa Sauermann", "de", [7, 7, 7, 7, 7, 7]],
[2, "Jeck Lim", "sg", [7, 5, 7, 7, 7, 7]],
[3, "Lin Chen", "cn", [7, 3, 7, 7, 7, 7]],
[14, "Mina Dalirrooyfard", "ir", [7, 0, 2, 7, 7, 7]],
[202, "Georgios Kalantzis", "gr", [7, 0, 1, 1, 2, 7]],
[202, "Chi Hong Chow", "hk", [7, 0, 3, 1, 0, 7]],
],
}
def construct(self):
grid = self.get_score_grid()
grid.to_edge(DOWN, buff=LARGE_BUFF)
for row in grid.rows:
row[0].set_opacity(0)
grid.h_lines.stretch(0.93, 0, about_edge=RIGHT)
sf = 1.25
title = TextMobject("Only 6 solved P6")
title.scale(sf)
title.to_edge(UP, buff=MED_SMALL_BUFF)
subtitle = TextMobject("P2 evaded 5 of them")
subtitle.set_color(YELLOW)
subtitle.scale(sf)
subtitle.next_to(title, DOWN)
six_rect, two_rect = [
SurroundingRectangle(VGroup(
grid.rows[0][index],
grid.rows[-1][index],
))
for index in [7, 3]
]
self.play(
Write(title),
LaggedStart(*[FadeInFrom(row, UP) for row in grid.rows]),
LaggedStart(*[ShowCreation(line) for line in grid.h_lines]),
)
self.play(ShowCreation(six_rect))
self.wait()
self.play(
ReplacementTransform(six_rect, two_rect),
FadeInFrom(subtitle, UP)
)
self.wait()
class AlwaysStartSimple(TeacherStudentsScene):
def construct(self):
self.teacher_says("Always start\\\\simple")
self.change_all_student_modes("pondering")
self.wait(3)
class TryOutSimplestExamples(WindmillScene):
CONFIG = {
"windmill_rotation_speed": TAU / 8,
}
def construct(self):
self.two_points()
self.add_third_point()
self.add_fourth_point()
self.move_starting_line()
def two_points(self):
points = [1.5 * LEFT, 1.5 * RIGHT]
dots = self.dots = self.get_dots(points)
windmill = self.windmill = self.get_windmill(points, angle=TAU / 8)
pivot_dot = self.pivot_dot = self.get_pivot_dot(windmill)
self.play(
ShowCreation(windmill),
LaggedStartMap(
FadeInFromLarge, dots,
scale_factor=10,
run_time=1,
lag_ratio=0.4,
),
GrowFromCenter(pivot_dot),
)
self.let_windmill_run(windmill, 8)
def add_third_point(self):
windmill = self.windmill
new_point = 2 * DOWN
new_dot = self.get_dots([new_point])
windmill.point_set.append(new_point)
self.add(new_dot, self.pivot_dot)
self.play(FadeInFromLarge(new_dot, scale_factor=10))
self.let_windmill_run(windmill, 8)
def add_fourth_point(self):
windmill = self.windmill
dot = self.get_dots([ORIGIN])
dot.move_to(DOWN + 2 * RIGHT)
words = TextMobject("Never hit!")
words.set_color(RED)
words.scale(0.75)
words.move_to(0.7 * DOWN, DOWN)
self.add(dot, self.pivot_dot)
self.play(
FadeInFromLarge(dot, scale_factor=10)
)
windmill.point_set.append(dot.get_center())
windmill.rot_speed = TAU / 4
self.let_windmill_run(windmill, 4)
# Shift point
self.play(
dot.next_to, words, DOWN,
FadeInFrom(words, RIGHT),
)
windmill.point_set[3] = dot.get_center()
self.let_windmill_run(windmill, 4)
self.wait()
self.dots.add(dot)
self.never_hit_words = words
def move_starting_line(self):
windmill = self.windmill
dots = self.dots
windmill.suspend_updating()
self.play(
windmill.move_to, dots[-1],
FadeOut(self.never_hit_words),
)
windmill.pivot = dots[-1].get_center()
windmill.resume_updating()
counters = self.get_pivot_counters(windmill)
self.play(
LaggedStart(*[
FadeInFrom(counter, DOWN)
for counter in counters
])
)
self.wait()
windmill.rot_speed = TAU / 8
self.let_windmill_run(windmill, 16)
highlight = windmill.copy()
highlight.set_stroke(YELLOW, 4)
self.play(
ShowCreationThenDestruction(highlight),
)
self.let_windmill_run(windmill, 8)
class FearedCase(WindmillScene):
CONFIG = {
"n_points": 25,
"windmill_rotation_speed": TAU / 16,
}
def construct(self):
points = self.get_random_point_set(self.n_points)
sorted_points = sorted(list(points), key=lambda p: p[1])
dots = self.get_dots(points)
windmill = self.get_windmill(
points,
sorted_points[self.n_points // 2],
angle=0,
)
pivot_dot = self.get_pivot_dot(windmill)
# self.add_dot_color_updater(dots, windmill)
counters = self.get_pivot_counters(
windmill,
counter_height=0.15,
buff=0.1
)
self.add(windmill)
self.add(dots)
self.add(pivot_dot)
self.add(counters)
self.let_windmill_run(windmill, 32)
windmill.pivot = sorted_points[0]
self.let_windmill_run(windmill, 32)
class WhereItStartsItEnds(WindmillScene):
CONFIG = {
"n_points": 11,
"windmill_rotation_speed": TAU / 8,
"random_seed": 1,
"points_shift_val": 2 * LEFT,
}
def construct(self):
self.show_stays_in_middle()
self.ask_about_proof()
def show_stays_in_middle(self):
points = self.get_random_point_set(self.n_points)
points += self.points_shift_val
sorted_points = sorted(list(points), key=lambda p: p[1])
dots = self.get_dots(points)
windmill = self.get_windmill(
points,
sorted_points[self.n_points // 2],
angle=0
)
pivot_dot = self.get_pivot_dot(windmill)
sf = 1.25
start_words = TextMobject("Starts in the ", "``middle''")
start_words.scale(sf)
start_words.next_to(windmill, UP, MED_SMALL_BUFF)
start_words.to_edge(RIGHT)
end_words = TextMobject("Stays in the ", "``middle''")
end_words.scale(sf)
end_words.next_to(windmill, DOWN, MED_SMALL_BUFF)
end_words.to_edge(RIGHT)
start_words.match_x(end_words)
self.add(dots)
self.play(
ShowCreation(windmill),
GrowFromCenter(pivot_dot),
FadeInFrom(start_words, LEFT),
)
self.wait()
self.start_leaving_shadows()
self.add(windmill, dots, pivot_dot)
half_time = PI / windmill.rot_speed
self.let_windmill_run(windmill, time=half_time)
self.play(FadeInFrom(end_words, UP))
self.wait()
self.let_windmill_run(windmill, time=half_time)
self.wait()
self.start_words = start_words
self.end_words = end_words
self.windmill = windmill
self.dots = dots
self.pivot_dot = pivot_dot
def ask_about_proof(self):
sf = 1.25
middle_rects = self.get_middle_rects()
middle_words = TextMobject("Can you formalize this?")
middle_words.scale(sf)
middle_words.next_to(middle_rects, DOWN, MED_LARGE_BUFF)
middle_words.to_edge(RIGHT)
middle_words.match_color(middle_rects)
proof_words = TextMobject("Can you prove this?")
proof_words.next_to(
self.end_words.get_left(),
DL,
buff=2,
)
proof_words.shift(RIGHT)
proof_words.scale(sf)
proof_arrow = Arrow(
proof_words.get_top(),
self.end_words.get_corner(DL),
buff=SMALL_BUFF,
)
proof_words2 = TextMobject("Then prove the result?")
proof_words2.scale(sf)
proof_words2.next_to(middle_words, DOWN, MED_LARGE_BUFF)
proof_words2.to_edge(RIGHT)
VGroup(proof_words, proof_words2, proof_arrow).set_color(YELLOW)
self.play(
Write(proof_words),
GrowArrow(proof_arrow),
run_time=1,
)
self.wait()
self.play(
FadeOut(proof_arrow),
FadeOut(proof_words),
LaggedStartMap(ShowCreation, middle_rects),
Write(middle_words),
)
self.wait()
self.play(FadeInFrom(proof_words2, UP))
self.wait()
self.let_windmill_run(self.windmill, time=10)
def get_middle_rects(self):
middle_rects = VGroup(*[
SurroundingRectangle(words[1])
for words in [
self.start_words,
self.end_words
]
])
middle_rects.set_color(TEAL)
return middle_rects
class AltWhereItStartsItEnds(WhereItStartsItEnds):
CONFIG = {
"n_points": 9,
"random_seed": 3,
}
class FormalizeMiddle(WhereItStartsItEnds):
CONFIG = {
"random_seed": 2,
"points_shift_val": 3 * LEFT,
}
def construct(self):
self.show_stays_in_middle()
self.problem_solving_tip()
self.define_colors()
self.mention_odd_case()
self.ask_about_numbers()
def problem_solving_tip(self):
mid_words = VGroup(
self.start_words,
self.end_words,
)
mid_words.save_state()
sf = 1.25
pst = TextMobject("Problem-solving tip:")
pst.scale(sf)
underline = Line(LEFT, RIGHT)
underline.match_width(pst)
underline.move_to(pst.get_bottom())
pst.add(underline)
pst.to_corner(UR)
# pst.set_color(YELLOW)
steps = VGroup(
TextMobject("Vague idea"),
TextMobject("Put numbers to it"),
TextMobject("Ask about those numbers"),
)
steps.scale(sf)
steps.arrange(DOWN, buff=LARGE_BUFF)
steps.next_to(pst, DOWN, buff=MED_LARGE_BUFF)
steps.shift_onto_screen()
pst.match_x(steps)
colors = color_gradient([BLUE, YELLOW], 3)
for step, color in zip(steps, colors):
step.set_color(color)
arrows = VGroup()
for s1, s2 in zip(steps, steps[1:]):
arrow = Arrow(s1.get_bottom(), s2.get_top(), buff=SMALL_BUFF)
arrows.add(arrow)
self.play(Write(pst), run_time=1)
self.wait()
self.play(
mid_words.scale, 0.75,
mid_words.set_opacity, 0.25,
mid_words.to_corner, DL,
FadeInFromDown(steps[0]),
)
self.wait()
for arrow, step in zip(arrows, steps[1:]):
self.play(
FadeInFrom(step, UP),
GrowArrow(arrow),
)
self.wait()
steps.generate_target()
steps.target.scale(0.75)
steps.target.arrange(DOWN, buff=0.2)
steps.target.to_corner(UR)
self.play(
FadeOut(pst),
MoveToTarget(steps),
Restore(mid_words),
FadeOut(arrows)
)
self.wait()
self.tip_words = steps
self.mid_words = mid_words
def define_colors(self):
windmill = self.windmill
mid_words = self.mid_words
tip_words = self.tip_words
shadows = self.windmill_shadows
self.leave_shadows = False
full_time = TAU / windmill.rot_speed
self.play(FadeOut(shadows))
self.add(windmill, tip_words, mid_words, self.dots, self.pivot_dot)
self.let_windmill_run(windmill, time=full_time / 4)
windmill.rotate(PI)
self.wait()
# Show regions
rects = self.get_left_right_colorings(windmill)
rects.suspend_updating()
rects.save_state()
rects.stretch(0, 0, about_point=windmill.get_center())
counters = VGroup(Integer(0), Integer(0))
counters.scale(2)
counters[0].set_stroke(BLUE, 3, background=True)
counters[1].set_stroke(GREY_BROWN, 3, background=True)
new_dots = self.dots.copy()
new_dots.set_color(WHITE)
for dot in new_dots:
dot.scale(1.25)
new_dots.sort(lambda p: p[0])
k = self.n_points // 2
dot_sets = VGroup(new_dots[:k], new_dots[-k:])
label_sets = VGroup()
for dot_set, direction in zip(dot_sets, [LEFT, RIGHT]):
label_set = VGroup()
for i, dot in zip(it.count(1), dot_set):
label = Integer(i)
label.set_height(0.15)
label.next_to(dot, direction, SMALL_BUFF)
label_set.add(label)
label_sets.add(label_set)
for counter, dot_set in zip(counters, dot_sets):
counter.move_to(dot_set)
counter.to_edge(UP)
self.add(rects, *self.get_mobjects())
self.play(
Restore(rects),
FadeIn(counters),
)
for counter, dot_set, label_set in zip(counters, dot_sets, label_sets):
self.play(
ShowIncreasingSubsets(dot_set),
ShowIncreasingSubsets(label_set),
ChangingDecimal(counter, lambda a: len(dot_set)),
rate_func=linear,
)
self.wait()
self.wait()
self.remove(self.dots)
self.dots = new_dots
# Show orientation
tips = self.get_orientation_arrows(windmill)
self.play(ShowCreation(tips))
windmill.add(tips)
self.wait()
self.add_dot_color_updater(new_dots, windmill)
rects.suspend_updating()
for rect in rects:
self.play(rect.set_opacity, 1)
self.play(rect.set_opacity, rects.const_opacity)
rects.resume_updating()
self.wait()
self.play(
counters.space_out_submobjects, 0.8,
counters.next_to, mid_words, DOWN, LARGE_BUFF,
FadeOut(label_sets),
)
eq = TexMobject("=")
eq.scale(2)
eq.move_to(counters)
self.play(FadeIn(eq))
self.wait()
self.counters = counters
self.colored_regions = rects
rects.resume_updating()
def mention_odd_case(self):
dots = self.dots
counters = self.counters
sf = 1.0
words = TextMobject(
"Assume odd \\# points"
)
words.scale(sf)
words.to_corner(UL)
example = VGroup(
TextMobject("Example:"),
Integer(0)
)
example.arrange(RIGHT)
example.scale(sf)
example.next_to(words, DOWN)
example.align_to(words, LEFT)
k = self.n_points // 2
dot_rects = VGroup()
for i, dot in zip(it.count(1), dots):
dot_rect = SurroundingRectangle(dot)
dot_rect.match_color(dot)
dot_rects.add(dot_rect)
self.play(FadeInFrom(words, DOWN))
self.wait()
self.play(
ShowCreationThenFadeAround(dots[k]),
self.pivot_dot.set_color, WHITE,
)
self.play(FadeInFrom(example, UP))
self.play(
ShowIncreasingSubsets(dot_rects),
ChangingDecimal(
example[1],
lambda a: len(dot_rects)
),
rate_func=linear
)
self.wait()
self.remove(dot_rects)
self.play(
ShowCreationThenFadeOut(dot_rects[:k]),
ShowCreationThenFadeOut(
SurroundingRectangle(counters[0], color=BLUE)
),
)
self.play(
ShowCreationThenFadeOut(dot_rects[-k:]),
ShowCreationThenFadeOut(
SurroundingRectangle(counters[1], color=GREY_BROWN)
),
)
self.wait()
self.play(
FadeOut(words),
FadeOut(example),
)
def ask_about_numbers(self):
self.windmill.rot_speed *= 0.5
self.add(self.dots, self.pivot_dot)
self.let_windmill_run(self.windmill, 20)
class SecondColoringExample(WindmillScene):
CONFIG = {
"run_time": 30,
"n_points": 9,
}
def construct(self):
points = self.get_random_point_set(self.n_points)
points += RIGHT
sorted_points = sorted(list(points), key=lambda p: p[0])
dots = self.get_dots(points)
windmill = self.get_windmill(
points,
pivot=sorted_points[self.n_points // 2],
angle=PI / 2
)
pivot_dot = self.get_pivot_dot(windmill)
pivot_dot.set_color(WHITE)
rects = self.get_left_right_colorings(windmill)
self.add_dot_color_updater(dots, windmill)
counts = VGroup(
TextMobject("\\# Blues = 4"),
TextMobject("\\# Browns = 4"),
)
counts.arrange(DOWN, aligned_edge=LEFT, buff=MED_LARGE_BUFF)
counts.to_corner(UL)
counts[0].set_color(interpolate_color(BLUE, WHITE, 0.25))
counts[1].set_color(interpolate_color(GREY_BROWN, WHITE, 0.5))
counts[0].set_stroke(BLACK, 5, background=True)
counts[1].set_stroke(BLACK, 5, background=True)
const_words = TextMobject("Stay constant$\\dots$why?")
const_words.next_to(counts, RIGHT, buff=1.5, aligned_edge=UP)
arrows = VGroup(*[
Arrow(
const_words.get_left(),
count.get_right(),
buff=SMALL_BUFF,
max_tip_length_to_length_ratio=0.15,
max_stroke_width_to_length_ratio=3,
)
for count in counts
])
self.add(rects, windmill, dots, pivot_dot)
self.add(counts, const_words, arrows)
self.let_windmill_run(windmill, time=self.run_time)
class TalkThroughPivotChange(WindmillScene):
CONFIG = {
"windmill_rotation_speed": 0.2,
}
def construct(self):
self.setup_windmill()
self.ask_about_pivot_change()
self.show_above_and_below()
self.change_pivot()
def setup_windmill(self):
points = self.points = np.array([
DR, UR, UL, DL, 0.5 * LEFT
])
points *= 3
self.dots = self.get_dots(points)
self.windmill = self.get_windmill(points, points[-1])
self.pivot_dot = self.get_pivot_dot(self.windmill)
self.pivot_dot.set_color(WHITE)
self.add_dot_color_updater(self.dots, self.windmill)
self.rects = self.get_left_right_colorings(self.windmill)
self.add(
self.rects,
self.windmill,
self.dots,
self.pivot_dot,
)
def ask_about_pivot_change(self):
windmill = self.windmill
new_pivot, angle = self.next_pivot_and_angle(windmill)
words = TextMobject("Think about\\\\pivot change")
words.next_to(new_pivot, UP, buff=2)
words.to_edge(LEFT)
arrow = Arrow(words.get_bottom(), new_pivot, buff=0.2)
self.play(
Rotate(
windmill, -0.9 * angle,
run_time=3,
rate_func=linear
),
Write(words, run_time=1),
ShowCreation(arrow),
)
self.wait()
self.question = words
self.question_arrow = arrow
def show_above_and_below(self):
windmill = self.windmill
vect = normalize(windmill.get_vector())
angle = windmill.get_angle()
tips = self.get_orientation_arrows(windmill)
top_half = Line(windmill.get_center(), windmill.get_end())
low_half = Line(windmill.get_center(), windmill.get_start())
top_half.set_stroke(YELLOW, 3)
low_half.set_stroke(PINK, 3)
halves = VGroup(top_half, low_half)
top_words = TextMobject("Above pivot")
low_words = TextMobject("Below pivot")
all_words = VGroup(top_words, low_words)
for words, half in zip(all_words, halves):
words.next_to(ORIGIN, DOWN)
words.rotate(angle, about_point=ORIGIN)
words.shift(half.point_from_proportion(0.15))
words.match_color(half)
self.play(ShowCreation(tips))
self.wait()
self.add(top_half, tips)
self.play(
ShowCreationThenFadeOut(top_half),
FadeInFrom(top_words, -vect),
)
self.add(low_half, tips)
self.play(
ShowCreationThenFadeOut(low_half),
FadeInFrom(low_words, vect),
)
self.wait()
windmill.add(tips)
self.above_below_words = all_words
def change_pivot(self):
windmill = self.windmill
dots = self.dots
arrow = self.question_arrow
blue_rect = SurroundingRectangle(dots[3])
blue_rect.set_color(BLUE)
new_pivot_word = TextMobject("New pivot")
new_pivot_word.next_to(blue_rect, LEFT)
old_pivot_word = TextMobject("Old pivot")
old_pivot = windmill.pivot
old_pivot_word.next_to(
old_pivot, LEFT,
buff=SMALL_BUFF + MED_SMALL_BUFF
)
self.play(
FadeOut(self.above_below_words),
ReplacementTransform(
self.question,
new_pivot_word,
),
ReplacementTransform(arrow, blue_rect),
)
self.wait()
anims, time = self.rotate_to_next_pivot(windmill)
self.play(
*anims,
Rotate(
windmill,
angle=-windmill.rot_speed,
rate_func=linear,
)
)
self.wait()
self.play(
TransformFromCopy(new_pivot_word, old_pivot_word),
blue_rect.move_to, old_pivot,
)
self.wait(2)
# Hit new point
brown_rect = SurroundingRectangle(dots[1])
brown_rect.set_color(GREY_BROWN)
self.play(TransformFromCopy(blue_rect, brown_rect))
self.play(
blue_rect.move_to, windmill.pivot,
blue_rect.set_color, GREY_BROWN,
old_pivot_word.move_to, new_pivot_word,
FadeOutAndShift(new_pivot_word, DL)
)
self.let_windmill_run(windmill, 1)
self.wait()
self.play(
FadeOut(old_pivot_word),
FadeOut(blue_rect),
FadeOut(brown_rect),
)
self.let_windmill_run(windmill, 20)
class InsightNumber1(Scene):
def construct(self):
words = TextMobject(
"Key insight 1: ",
"\\# Points on either side is constant"
)
words[0].set_color(YELLOW)
words.set_width(FRAME_WIDTH - 1)
self.play(FadeInFromDown(words))
self.wait()
class Rotate180Argument(WindmillScene):
CONFIG = {
"n_points": 21,
"random_seed": 3,
}
def construct(self):
self.setup_windmill()
self.add_total_rotation_label()
self.rotate_180()
self.show_parallel_lines()
self.rotate_180()
self.rotate_180()
def setup_windmill(self):
n = self.n_points
points = self.get_random_point_set(n)
points[:, 0] *= 1.5
points += RIGHT
points = sorted(points, key=lambda p: p[0])
mid_point = points[n // 2]
points[n // 2 - 1] += 0.2 * LEFT
self.points = points
self.dots = self.get_dots(points)
self.windmill = self.get_windmill(points, mid_point)
self.pivot_dot = self.get_pivot_dot(self.windmill)
self.pivot_dot.set_color(WHITE)
self.add_dot_color_updater(self.dots, self.windmill)
self.rects = self.get_left_right_colorings(self.windmill)
p_label = TexMobject("P_0")
p_label.next_to(mid_point, RIGHT, SMALL_BUFF)
self.p_label = p_label
self.add(
self.rects,
self.windmill,
self.dots,
self.pivot_dot,
self.p_label,
)
def add_total_rotation_label(self):
windmill = self.windmill
words = TextMobject("Total rotation:")
counter = Integer(0, unit="^\\circ")
title = VGroup(words, counter)
title.arrange(RIGHT)
title.to_corner(UL)
rot_arrow = Vector(UP)
rot_arrow.set_color(RED)
rot_arrow.next_to(title, DOWN)
circle = Circle()
circle.replace(rot_arrow, dim_to_match=1)
circle.set_stroke(WHITE, 1)
rot_arrow.add_updater(
lambda m: m.set_angle(windmill.get_angle())
)
rot_arrow.add_updater(
lambda m: m.move_to(circle)
)
def update_count(c):
new_val = 90 - windmill.get_angle() * 360 / TAU
while abs(new_val - c.get_value()) > 90:
new_val += 360
c.set_value(new_val)
counter.add_updater(update_count)
rect = SurroundingRectangle(
VGroup(title, circle),
buff=MED_LARGE_BUFF,
)
rect.set_fill(BLACK, 0.8)
rect.set_stroke(WHITE, 1)
title.shift(MED_SMALL_BUFF * LEFT)
self.rotation_label = VGroup(
rect, words, counter, circle, rot_arrow
)
self.add(self.rotation_label)
def rotate_180(self):
windmill = self.windmill
self.let_windmill_run(
windmill,
PI / windmill.rot_speed,
)
self.wait()
def show_parallel_lines(self):
points = self.points
rotation_label = self.rotation_label
dots = self.dots
windmill = self.windmill
lines = VGroup()
for point in points:
line = Line(DOWN, UP)
line.set_height(2 * FRAME_HEIGHT)
line.set_stroke(RED, 1, opacity=0.5)
line.move_to(point)
lines.add(line)
lines.shuffle()
self.add(lines, dots, rotation_label)
self.play(
ShowCreation(lines, lag_ratio=0.5, run_time=3)
)
self.wait()
self.rects.suspend_updating()
for rect in self.rects:
self.play(
rect.set_opacity, 0,
rate_func=there_and_back,
run_time=2
)
self.rects.resume_updating()
self.wait()
pivot_tracker = VectorizedPoint(windmill.pivot)
pivot_tracker.save_state()
def update_pivot(w):
w.pivot = pivot_tracker.get_center()
windmill.add_updater(update_pivot)
for x in range(4):
point = random.choice(points)
self.play(
pivot_tracker.move_to, point
)
self.wait()
self.play(Restore(pivot_tracker))
self.play(FadeOut(lines))
windmill.remove_updater(update_pivot)
self.wait()
class EvenCase(Rotate180Argument):
CONFIG = {
"n_points": 10,
"dot_config": {"radius": 0.075},
}
def construct(self):
self.ask_about_even_number()
self.choose_halfway_point()
self.add_total_rotation_label()
self.rotate_180()
self.rotate_180()
self.show_parallel_lines()
self.rotate_180()
self.rotate_180()
def ask_about_even_number(self):
n = self.n_points
points = self.get_random_point_set(n)
points[:, 0] *= 2
points += DOWN
points = sorted(points, key=lambda p: p[0])
dots = self.get_dots(points)
windmill = self.get_windmill(points, points[3])
region_rects = self.rects = self.get_left_right_colorings(windmill)
pivot_dot = self.get_pivot_dot(windmill)
pivot_dot.set_color(WHITE)
dot_rects = VGroup(*map(SurroundingRectangle, dots))
question = TextMobject("What about an even number?")
# question.to_corner(UL)
question.to_edge(UP)
counter_label = TextMobject("\\# Points", ":")
counter = Integer(0)
counter_group = VGroup(counter_label, counter)
counter_group.arrange(RIGHT)
counter.align_to(counter_label[1], DOWN)
counter_group.next_to(question, DOWN, MED_LARGE_BUFF)
counter_group.set_color(YELLOW)
# counter_group.align_to(question, LEFT)
self.add(question, counter_label)
self.add(windmill, dots, pivot_dot)
self.add_dot_color_updater(dots, windmill)
self.add(region_rects, question, counter_group, windmill, dots, pivot_dot)
self.play(
ShowIncreasingSubsets(dot_rects),
ChangingDecimal(counter, lambda a: len(dot_rects)),
rate_func=linear
)
self.play(FadeOut(dot_rects))
self.wait()
# region_rects.suspend_updating()
# self.play(
# FadeIn(region_rects),
# FadeOut(dot_rects),
# )
# region_rects.resume_updating()
# self.wait()
# Count by color
blue_rects = dot_rects[:3]
blue_rects.set_color(BLUE)
brown_rects = dot_rects[4:]
brown_rects.set_color(GREY_BROWN)
pivot_rect = dot_rects[3]
pivot_rect.set_color(GREY_BROWN)
blues_label = TextMobject("\\# Blues", ":")
blues_counter = Integer(len(blue_rects))
blues_group = VGroup(blues_label, blues_counter)
blues_group.set_color(BLUE)
browns_label = TextMobject("\\# Browns", ":")
browns_counter = Integer(len(brown_rects))
browns_group = VGroup(browns_label, browns_counter)
browns_group.set_color(interpolate_color(GREY_BROWN, WHITE, 0.5))
groups = VGroup(blues_group, browns_group)
for group in groups:
group.arrange(RIGHT)
group[-1].align_to(group[0][-1], DOWN)
groups.arrange(DOWN, aligned_edge=LEFT)
groups.next_to(counter_group, DOWN, aligned_edge=LEFT)
self.play(
FadeInFrom(blues_group, UP),
ShowCreation(blue_rects),
)
self.play(
FadeInFrom(browns_group, UP),
ShowCreation(brown_rects),
)
self.wait()
# Pivot counts as brown
pivot_words = TextMobject("Pivot counts as brown")
arrow = Vector(LEFT)
arrow.next_to(pivot_dot, RIGHT, SMALL_BUFF)
pivot_words.next_to(arrow, RIGHT, SMALL_BUFF)
self.play(
FadeInFrom(pivot_words, LEFT),
ShowCreation(arrow),
)
self.play(
ShowCreation(pivot_rect),
ChangeDecimalToValue(browns_counter, len(brown_rects) + 1),
FadeOut(pivot_dot),
)
self.wait()
self.play(
FadeOut(dot_rects),
FadeOut(pivot_words),
FadeOut(arrow),
)
self.wait()
blues_counter.add_updater(
lambda c: c.set_value(len(list(filter(
lambda d: d.get_fill_color() == Color(BLUE),
dots
))))
)
browns_counter.add_updater(
lambda c: c.set_value(len(list(filter(
lambda d: d.get_fill_color() == Color(GREY_BROWN),
dots
))))
)
self.windmill = windmill
self.dots = dots
self.points = points
self.question = question
self.counter_group = VGroup(
counter_group,
blues_group,
browns_group,
)
def choose_halfway_point(self):
windmill = self.windmill
points = self.points
n = self.n_points
p_label = TexMobject("P_0")
p_label.next_to(points[n // 2], RIGHT, SMALL_BUFF)
pivot_tracker = VectorizedPoint(windmill.pivot)
def update_pivot(w):
w.pivot = pivot_tracker.get_center()
windmill.add_updater(update_pivot)
self.play(
pivot_tracker.move_to, points[n // 2],
run_time=2
)
self.play(FadeInFrom(p_label, LEFT))
self.wait()
windmill.remove_updater(update_pivot)
def add_total_rotation_label(self):
super().add_total_rotation_label()
self.rotation_label.scale(0.8, about_edge=UL)
self.play(
FadeOut(self.question),
FadeIn(self.rotation_label),
self.counter_group.to_edge, UP,
)
class WindmillEndScreen(PatreonEndScreen):
CONFIG = {
"specific_patrons": [
"Juan Benet",
"Kurt Dicus",
"Vassili Philippov",
"Davie Willimoto",
"Burt Humburg",
"Hardik Meisheri",
"L. Z.",
"Matt Russell",
"Scott Gray",
"soekul",
"Tihan Seale",
"D. Sivakumar",
"Richard Barthel",
"Ali Yahya",
"Arthur Zey",
"dave nicponski",
"Joseph Kelly",
"Kaustuv DeBiswas",
"kkm",
"Lambda AI Hardware",
"Lukas Biewald",
"Mark Heising",
"Nicholas Cahill",
"Peter Mcinerney",
"Quantopian",
"Roy Larson",
"Scott Walter, Ph.D.",
"Tauba Auerbach",
"Yana Chernobilsky",
"Yu Jun",
"Jordan Scales",
"Lukas -krtek.net- Novy",
"Britt Selvitelle",
"David Gow",
"J",
"Jonathan Wilson",
"Joseph John Cox",
"Magnus Dahlström",
"Randy C. Will",
"Ryan Atallah",
"Luc Ritchie",
"1stViewMaths",
"Adrian Robinson",
"Aidan Shenkman",
"Alex Mijalis",
"Alexis Olson",
"Andreas Benjamin Brössel",
"Andrew Busey",
"Ankalagon",
"Antoine Bruguier",
"Antonio Juarez",
"Arjun Chakroborty",
"Art Ianuzzi",
"Austin Goodman",
"Awoo",
"Ayan Doss",
"AZsorcerer",
"Barry Fam",
"Bernd Sing",
"Boris Veselinovich",
"Bradley Pirtle",
"Brian Staroselsky",
"Charles Southerland",
"Charlie N",
"Chris Connett",
"Christian Kaiser",
"Clark Gaebel",
"Cooper Jones",
"Danger Dai",
"Daniel Pang",
"Dave B",
"Dave Kester",
"David B. Hill",
"David Clark",
"DeathByShrimp",
"Delton Ding",
"Dheeraj Vepakomma",
"eaglle",
"Empirasign",
"emptymachine",
"Eric Younge",
"Ero Carrera",
"Eryq Ouithaqueue",
"Federico Lebron",
"Fernando Via Canel",
"Gero Bone-Winkel",
"Giovanni Filippi",
"Hal Hildebrand",
"Hitoshi Yamauchi",
"Isaac Jeffrey Lee",
"Ivan Sorokin",
"j eduardo perez",
"Jacob Harmon",
"Jacob Hartmann",
"Jacob Magnuson",
"Jameel Syed",
"Jason Hise",
"Jeff Linse",
"Jeff Straathof",
"John C. Vesey",
"John Griffith",
"John Haley",
"John V Wertheim",
"Jonathan Eppele",
"Jordan A Purcell",
"Josh Kinnear",
"Joshua Claeys",
"Kai-Siang Ang",
"Kanan Gill",
"Kartik Cating-Subramanian",
"L0j1k",
"Lee Redden",
"Linh Tran",
"Ludwig Schubert",
"Magister Mugit",
"Mark B Bahu",
"Martin Price",
"Mathias Jansson",
"Matt Langford",
"Matt Roveto",
"Matthew Bouchard",
"Matthew Cocke",
"Michael Faust",
"Michael Hardel",
"Mirik Gogri",
"Mustafa Mahdi",
"Márton Vaitkus",
"Nero Li",
"Nikita Lesnikov",
"Omar Zrien",
"Owen Campbell-Moore",
"Patrick Lucas",
"Peter Ehrnstrom",
"RedAgent14",
"rehmi post",
"Ripta Pasay",
"Rish Kundalia",
"Roman Sergeychik",
"Roobie",
"Ryan Williams",
"Sebastian Garcia",
"Solara570",
"Steven Siddals",
"Stevie Metke",
"Tal Einav",
"Ted Suzman",
"Thomas Tarler",
"Tianyu Ge",
"Tom Fleming",
"Tyler VanValkenburg",
"Valeriy Skobelev",
"Vinicius Reis",
"Xuanji Li",
"Yavor Ivanov",
"YinYangBalance.Asia",
"Zach Cardwell",
],
}
class Thumbnail(WindmillScene):
CONFIG = {
"dot_config": {
"radius": 0.15,
"stroke_width": 1,
},
"random_seed": 7,
}
def construct(self):
points = self.get_random_point_set(11)
points[:, 0] *= 1.7
points += 0.5 * LEFT
points[1] += DR + 0.5 * DOWN
points[10] += LEFT
points[6] += 3 * RIGHT
windmill = self.get_windmill(
points, points[1],
angle=45 * DEGREES,
)
dots = self.get_dots(points)
rects = self.get_left_right_colorings(windmill)
pivot_dot = self.get_pivot_dot(windmill)
pivot_dot.scale(2)
pivot_dot.set_color(WHITE)
new_pivot = points[5]
new_pivot2 = points[3]
flash = Flash(pivot_dot, flash_radius=0.5)
wa = windmill.get_angle()
arcs = VGroup(*[
Arc(
start_angle=wa + a,
angle=90 * DEGREES,
radius=1.5,
stroke_width=10,
).add_tip(tip_length=0.7)
for a in [0, PI]
])
arcs.move_to(windmill.pivot)
arcs.set_color([LIGHT_GREY, WHITE])
self.add(rects[0], windmill, dots, pivot_dot)
self.add(arcs)
self.add(flash.mobject)
self.add_dot_color_updater(dots, windmill, color2=WHITE)
words = TextMobject("Next\\\\", "pivot")
words2 = TextMobject("Next ", "next\\\\", "pivot", alignment="")
words.scale(2)
words2.scale(2)
# words.next_to(windmill.pivot, RIGHT)
words.to_edge(UR)
words2.to_corner(DL)
arrow = Arrow(words[1].get_left(), new_pivot, buff=0.6)
arrow.set_stroke(width=10)
arrow.set_color(YELLOW)
arrow2 = Arrow(words2[-1].get_right(), new_pivot2, buff=0.6)
arrow2.match_style(arrow)
self.add(words, arrow)
self.add(words2, arrow2)
# for i, dot in enumerate(dots):
# self.add(Integer(i).move_to(dot))
class Thumbnail2(Scene):
def construct(self):
words = TextMobject("Olympics\\\\", "for\\\\", "math", alignment="")
# words.arrange(DOWN, aligned_edge=LEFT)
words.set_height(FRAME_HEIGHT - 1.5)
words.to_edge(LEFT)
logo = ImageMobject("imo_logo")
logo.set_height(4.5)
logo.to_corner(DR, buff=LARGE_BUFF)
rect = FullScreenFadeRectangle()
rect.set_fill([GREY, BLACK], 1)
self.clear()
self.add(rect)
self.add(words)
self.add(logo)