3b1b-videos/_2020/chess.py
2021-01-01 20:10:38 -08:00

4873 lines
152 KiB
Python

from manim_imports_ext import *
def boolian_linear_combo(bools):
return reduce(op.xor, [b * n for n, b in enumerate(bools)], 0)
def string_to_bools(message):
# For easter eggs on the board
as_int = int.from_bytes(message.encode(), 'big')
bits = "{0:b}".format(as_int)
bits = (len(message) * 8 - len(bits)) * '0' + bits
return [bool(int(b)) for b in bits]
def layer_mobject(mobject, nudge=1e-6):
for i, sm in enumerate(mobject.family_members_with_points()):
sm.shift(i * nudge * OUT)
def int_to_bit_coords(n, min_dim=3):
bits = "{0:b}".format(n)
bits = (min_dim - len(bits)) * '0' + bits
return np.array(list(map(int, bits)))
def bit_coords_to_int(bits):
return sum([(2**n) * b for n, b in enumerate(reversed(bits))])
def get_vertex_sphere(height=0.4, color=GREY, resolution=(21, 21)):
sphere = Sphere(resolution=resolution)
sphere.set_height(height)
sphere.set_color(color)
return sphere
def get_bit_string(bit_coords):
result = VGroup(*[Integer(int(b)) for b in bit_coords])
result.arrange(RIGHT, buff=SMALL_BUFF)
result.set_stroke(BLACK, 4, background=True)
return result
class Chessboard(SGroup):
CONFIG = {
"shape": (8, 8),
"height": 7,
"depth": 0.25,
"colors": [LIGHT_GREY, DARKER_GREY],
"gloss": 0.2,
"square_resolution": (3, 3),
"top_square_resolution": (5, 5),
}
def __init__(self, **kwargs):
super().__init__(**kwargs)
nr, nc = self.shape
cube = Cube(square_resolution=self.square_resolution)
# Replace top square with something slightly higher res
top_square = Square3D(resolution=self.top_square_resolution)
top_square.replace(cube[0])
cube.replace_submobject(0, top_square)
self.add(*[cube.copy() for x in range(nc * nr)])
self.arrange_in_grid(nr, nc, buff=0)
self.set_height(self.height)
self.set_depth(self.depth, stretch=True)
for i, j in it.product(range(nr), range(nc)):
color = self.colors[(i + j) % 2]
self[i * nc + j].set_color(color)
self.center()
self.set_gloss(self.gloss)
class Coin(Group):
CONFIG = {
"disk_resolution": (4, 51),
"height": 1,
"depth": 0.1,
"color": GOLD_D,
"tails_color": RED,
"include_labels": True,
"numeric_labels": False,
}
def __init__(self, **kwargs):
super().__init__(**kwargs)
res = self.disk_resolution
self.top = Disk3D(resolution=res, gloss=0.2)
self.bottom = self.top.copy()
self.top.shift(OUT)
self.bottom.shift(IN)
self.edge = Cylinder(height=2, resolution=(res[1], 2))
self.add(self.top, self.bottom, self.edge)
self.rotate(90 * DEGREES, OUT)
self.set_color(self.color)
self.bottom.set_color(RED)
if self.include_labels:
chars = "10" if self.numeric_labels else "HT"
labels = VGroup(*[TextMobject(c) for c in chars])
for label, vect in zip(labels, [OUT, IN]):
label.shift(1.02 * vect)
label.set_height(0.8)
labels[1].rotate(PI, RIGHT)
labels.apply_depth_test()
labels.set_stroke(width=0)
self.add(*labels)
self.labels = labels
self.set_height(self.height)
self.set_depth(self.depth, stretch=True)
def is_heads(self):
return self.top.get_center()[2] > self.bottom.get_center()[2]
def flip(self, axis=RIGHT):
super().flip(axis)
return self
class CoinsOnBoard(Group):
CONFIG = {
"proportion_of_square_height": 0.7,
"coin_config": {},
}
def __init__(self, chessboard, **kwargs):
super().__init__(**kwargs)
prop = self.proportion_of_square_height
for cube in chessboard:
coin = Coin(**self.coin_config)
coin.set_height(prop * cube.get_height())
coin.next_to(cube, OUT, buff=0)
self.add(coin)
def flip_at_random(self, p=0.5):
bools = np.random.random(len(self)) < p
self.flip_by_bools(bools)
return self
def flip_by_message(self, message):
self.flip_by_bools(string_to_bools(message))
return self
def flip_by_bools(self, bools):
for coin, head in zip(self, bools):
if coin.is_heads() ^ head:
coin.flip()
return self
def get_bools(self):
return [coin.is_heads() for coin in self]
class Key(SVGMobject):
CONFIG = {
"file_name": "key",
"fill_color": YELLOW_D,
"fill_opacity": 1,
"stroke_color": YELLOW_D,
"stroke_width": 0,
"gloss": 0.5,
"depth_test": True,
}
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.rotate(PI / 2, OUT)
class FlipCoin(Animation):
CONFIG = {
"axis": RIGHT,
"run_time": 1,
"shift_dir": OUT,
}
def __init__(self, coin, **kwargs):
super().__init__(coin, **kwargs)
self.shift_vect = coin.get_height() * self.shift_dir / 2
def interpolate_mobject(self, alpha):
coin = self.mobject
for sm, start_sm in self.families:
sm.points[:] = start_sm.points
coin.rotate(alpha * PI, axis=self.axis)
coin.shift(4 * alpha * (1 - alpha) * self.shift_vect)
return coin
# Scenes
class IntroducePuzzle(Scene):
CONFIG = {
"camera_class": ThreeDCamera,
}
def construct(self):
# Setup
frame = self.camera.frame
chessboard = Chessboard()
chessboard.move_to(ORIGIN, OUT)
grid = NumberPlane(
x_range=(0, 8), y_range=(0, 8),
faded_line_ratio=0
)
grid.match_height(chessboard)
grid.next_to(chessboard, OUT, 1e-8)
low_grid = grid.copy()
low_grid.next_to(chessboard, IN, 1e-8)
grid.add(low_grid)
grid.set_stroke(GREY, width=2)
grid.set_gloss(0.5)
grid.prepare_for_nonlinear_transform(0)
coins = CoinsOnBoard(chessboard)
coins.set_gloss(0.2)
coins_to_flip = Group()
head_bools = string_to_bools('3b1b :)')
for coin, heads in zip(coins, head_bools):
if not heads:
coins_to_flip.add(coin)
coins_to_flip.shuffle()
count_label = VGroup(
Integer(0, edge_to_fix=RIGHT),
TextMobject("Coins")
)
count_label.arrange(RIGHT, aligned_edge=DOWN)
count_label.to_corner(UL)
count_label.fix_in_frame()
# Draw board and coins
frame.set_rotation(-25 * DEGREES, 70 * DEGREES, 0)
self.play(
FadeIn(chessboard),
ShowCreationThenDestruction(grid, lag_ratio=0.01),
frame.set_theta, 0,
frame.set_phi, 45 * DEGREES,
run_time=3,
)
self.wait()
self.add(count_label)
self.play(
ShowIncreasingSubsets(coins),
UpdateFromFunc(count_label[0], lambda m, c=coins: m.set_value(len(c))),
rate_func=bezier([0, 0, 1, 1]),
run_time=2,
)
self.wait()
self.play(LaggedStartMap(FlipCoin, coins_to_flip, run_time=6, lag_ratio=0.1))
self.add(coins)
self.wait()
# Show key
key = Key()
key.rotate(PI / 4, RIGHT)
key.move_to(3 * OUT)
key.scale(0.8)
key.to_edge(LEFT, buff=1)
k = boolian_linear_combo(head_bools) ^ 63 # To make the flip below the actual solution
key_cube = Cube(square_resolution=(6, 6))
key_cube.match_color(chessboard[k])
key_cube.replace(chessboard[k], stretch=True)
chessboard.replace_submobject(k, key_cube)
key_square = key_cube[0]
chessboard.generate_target()
chessboard.save_state()
for i, cube in enumerate(chessboard.target):
if i == k:
cube[0].set_color(MAROON_E)
else:
cube.set_color(interpolate_color(cube.get_color(), BLACK, 0.8))
key.generate_target()
key.target.rotate(PI / 4, LEFT)
key.target.set_width(0.7 * key_square.get_width())
key.target.next_to(key_square, IN, buff=SMALL_BUFF)
self.play(FadeIn(key, LEFT))
self.wait()
self.play(
FadeOut(coins, lag_ratio=0.01),
MoveToTarget(chessboard),
)
ks_top = key_square.get_top()
self.play(
Rotate(key_square, PI / 2, axis=LEFT, about_point=ks_top),
MoveToTarget(key),
frame.set_phi, 60 * DEGREES,
run_time=2,
)
self.play(
Rotate(key_square, PI / 2, axis=RIGHT, about_point=ks_top),
run_time=2,
)
chessboard.saved_state[k][0].match_color(key_square)
self.play(
chessboard.restore,
FadeIn(coins),
frame.set_phi, 0 * DEGREES,
frame.move_to, 2 * LEFT,
run_time=3
)
# State goal
goal = TextMobject(
"Communicate where\\\\the key is",
" by turning\\\\over one coin.",
alignment=""
)
goal.next_to(count_label, DOWN, LARGE_BUFF, LEFT)
goal.fix_in_frame()
goal[1].set_color(YELLOW)
self.play(FadeIn(goal[0]))
self.wait()
self.play(FadeIn(goal[1]))
self.wait()
coin = coins[63]
rect = SurroundingRectangle(coin, color=TEAL)
rect.set_opacity(0.5)
rect.save_state()
rect.replace(chessboard)
rect.set_stroke(width=0)
rect.set_fill(opacity=0)
self.play(Restore(rect, run_time=2))
self.add(coin, rect)
self.play(FlipCoin(coin), FadeOut(rect))
class PrisonerPuzzleSetting(PiCreatureScene):
CONFIG = {
"camera_config": {
"background_color": GREY_E
}
}
def create_pi_creatures(self):
p1 = PiCreature(color=BLUE_C, height=2)
p2 = PiCreature(color=RED, height=2)
warden = PiCreature(color=GREY_BROWN, height=2.5)
warden.flip()
result = VGroup(p1, p2, warden)
result.arrange(RIGHT, buff=2, aligned_edge=DOWN)
warden.shift(RIGHT)
result.center().to_edge(DOWN, buff=1.5)
return result
def construct(self):
pis = self.pi_creatures
p1, p2, warden = self.pi_creatures
names = VGroup(
TextMobject("Prisoner 1\\\\(you)"),
TextMobject("Prisoner 2"),
TextMobject("Warden"),
)
for name, pi in zip(names, pis):
name.match_color(pi.body)
name.next_to(pi, DOWN)
question = TextMobject(
"Why do mathematicians\\\\always set their puzzles\\\\in prisons?",
alignment=""
)
question.to_corner(UR)
self.remove(warden)
warden.look_at(p2.eyes)
self.play(
LaggedStartMap(FadeIn, pis[:2], run_time=1.5, lag_ratio=0.3),
LaggedStartMap(FadeIn, names[:2], run_time=1.5, lag_ratio=0.3),
)
self.play(
p1.change, "sad",
p2.change, "pleading", warden.eyes
)
self.play(
FadeIn(warden),
FadeIn(names[2]),
)
self.play(warden.change, "conniving", p2.eyes)
self.wait()
self.play(FadeIn(question, lag_ratio=0.1))
self.wait(3)
self.play(FadeOut(question))
self.wait(2)
class FromCoinToSquareMaps(ThreeDScene):
CONFIG = {
"messages": [
"Please, ",
"go watch",
"Stand-up",
"Maths on",
"YouTube."
],
"flip_lag_ratio": 0.05,
}
def construct(self):
messages = self.messages
board1 = Chessboard()
board1.set_width(5.5)
board1.to_corner(DL)
board2 = board1.copy()
board2.to_corner(DR)
coins = CoinsOnBoard(board1)
bools = string_to_bools(messages[0])
for coin, head in zip(coins, bools):
if not head:
coin.flip(RIGHT)
for cube in board2:
cube.original_color = cube.get_color()
arrow = Arrow(board1.get_right(), board2.get_left())
arrow.tip.set_stroke(width=0)
title1 = TextMobject("Pattern of coins")
title2 = TextMobject("Individual square")
for title, board in [(title1, board1), (title2, board2)]:
title.scale(0.5 / title[0][0].get_height())
title.next_to(board, UP, MED_LARGE_BUFF)
title2.align_to(title1, UP)
def get_special_square(coins=coins, board=board2):
bools = [coin.is_heads() for coin in coins]
return board[boolian_linear_combo(bools)]
self.add(board1)
self.add(title1)
self.add(coins)
self.play(
GrowArrow(arrow),
FadeIn(board2, 2 * LEFT)
)
square = get_special_square()
rect = SurroundingRectangle(square, buff=0)
rect.set_color(PINK)
rect.next_to(square, OUT, buff=0.01)
self.play(
square.set_color, MAROON_C,
ShowCreation(rect),
FadeIn(title2)
)
for message in messages[1:]:
new_bools = string_to_bools(message)
coins_to_flip = Group()
for coin, to_heads in zip(coins, new_bools):
if coin.is_heads() ^ to_heads:
coins_to_flip.add(coin)
coins_to_flip.shuffle()
self.play(LaggedStartMap(
FlipCoin, coins_to_flip,
lag_ratio=self.flip_lag_ratio,
run_time=1,
))
new_square = get_special_square()
self.play(
square.set_color, square.original_color,
new_square.set_color, MAROON_C,
rect.move_to, new_square, OUT,
rect.shift, 0.01 * OUT,
)
square = new_square
self.wait()
class FromCoinToSquareMapsSingleFlips(FromCoinToSquareMaps):
CONFIG = {
"messages": [
"FlipBits",
"BlipBits",
"ClipBits",
"ChipBits",
"ChipBats",
"ChipRats",
]
}
class DiagramOfProgression(ThreeDScene):
def construct(self):
# Setup panels
P1_COLOR = BLUE_C
P2_COLOR = RED
rect = Rectangle(4, 2)
rect.set_fill(GREY_E, 1)
panels = Group()
for x in range(4):
panels.add(Group(rect.copy()))
panels.arrange_in_grid(buff=1)
panels[::2].shift(0.5 * LEFT)
panels.set_width(FRAME_WIDTH - 2)
panels.center().to_edge(DOWN)
p1_shift = panels[1].get_center() - panels[0].get_center()
panels[1].move_to(panels[0])
chessboard = Chessboard()
chessboard.set_height(0.9 * panels[0].get_height())
coins = CoinsOnBoard(
chessboard,
coin_config={
"disk_resolution": (2, 25),
}
)
coins.flip_by_message("Tau > Pi")
for panel in panels[1:]:
cb = chessboard.copy()
co = coins.copy()
cb.next_to(panel.get_right(), LEFT)
co.next_to(cb, OUT, 0)
panel.chessboard = cb
panel.coins = co
panel.add(cb, co)
kw = {
"tex_to_color_map": {
"Prisoner 1": P1_COLOR,
"Prisoner 2": P2_COLOR,
}
}
titles = VGroup(
TextMobject("Prisoners conspire", **kw),
TextMobject("Prisoner 1 sees key", **kw),
TextMobject("Prisoner 1 flips coin", **kw),
TextMobject("Prisoner 2 guesses key square", **kw),
)
for panel, title in zip(panels, titles):
title.next_to(panel, UP)
panel.title = title
panel.add(title)
# Darken first chessboard
for coin in panels[1].coins:
coin.remove(coin.edge)
if coin.is_heads():
coin.remove(coin.bottom)
coin.remove(coin.labels[1])
else:
coin.remove(coin.top)
coin.remove(coin.labels[0])
coin.set_opacity(0.25)
# Add characters
prisoner1 = PiCreature(color=P1_COLOR)
prisoner2 = PiCreature(color=P2_COLOR)
pis = VGroup(prisoner1, prisoner2)
pis.arrange(RIGHT, buff=1)
pis.set_height(1.5)
p0_pis = pis.copy()
p0_pis.set_height(2.0, about_edge=DOWN)
p0_pis[1].flip()
p0_pis.next_to(panels[0].get_bottom(), UP, SMALL_BUFF)
p0_pis[0].change("pondering", p0_pis[1].eyes)
p0_pis[1].change("speaking", p0_pis[0].eyes)
panels[0].add(p0_pis)
p1_pi = pis[0].copy()
p1_pi.next_to(panels[1].get_corner(DL), UR, SMALL_BUFF)
p1_pi.change("happy")
key = Key()
key.set_height(0.5)
key.next_to(p1_pi, UP)
key.set_color(YELLOW)
key_cube = panels[1].chessboard[18]
key_square = Square()
key_square.replace(key_cube)
key_square.set_stroke(width=3)
key_square.match_color(key)
p1_pi.look_at(key_square)
key_arrow = Arrow(
key.get_right() + SMALL_BUFF * UP,
key_square.get_corner(UL),
path_arc=-45 * DEGREES,
buff=SMALL_BUFF
)
key_arrow.tip.set_stroke(width=0)
key_arrow.match_color(key)
panels[1].add(p1_pi, key)
p2_pi = pis[0].copy()
p2_pi.next_to(panels[2].get_corner(DL), UR, SMALL_BUFF)
p2_pi.change("tease")
flip_coin = panels[2].coins[38]
panels[3].coins[38].flip()
flip_square = Square()
flip_square.replace(panels[2].chessboard[38])
flip_square.set_stroke(BLUE, 5)
for coin in panels[2].coins:
if coin is not flip_coin:
coin.remove(coin.edge)
if coin.is_heads():
coin.remove(coin.bottom)
coin.remove(coin.labels[1])
else:
coin.remove(coin.top)
coin.remove(coin.labels[0])
coin.set_opacity(0.25)
panels[2].add(p2_pi)
p3_pi = pis[1].copy()
p3_pi.next_to(panels[3].get_corner(DL), UR, SMALL_BUFF)
p3_pi.shift(MED_LARGE_BUFF * RIGHT)
p3_pi.change("confused")
panels[3].add(p3_pi)
# Animate each panel in
self.play(FadeIn(panels[1], DOWN))
self.play(
ShowCreation(key_arrow),
FadeInFromLarge(key_square),
)
panels[1].add(key_arrow, key_square)
self.wait()
self.play(FadeIn(panels[2], UP))
self.play(
ShowCreation(flip_square),
FlipCoin(flip_coin),
p2_pi.look_at, flip_coin,
)
self.wait()
self.play(FadeIn(panels[3], LEFT))
self.wait()
self.play(
FadeIn(panels[0], LEFT),
panels[1].shift, p1_shift,
)
self.wait()
class ImpossibleVariations(FromCoinToSquareMaps):
CONFIG = {
"messages": [
"FlipBits",
"BlipBits",
"ClipBits",
"ChipBits",
"ChipBats",
"ChipRats",
"ChipVats",
"ChipFats",
"ChapFats",
]
}
def construct(self):
# Definitions
frame = self.camera.frame
title = TextMobject("Describe any square\\\\with one flip")
title.set_height(1.2)
title.to_edge(UP)
title.fix_in_frame()
messages = it.cycle(self.messages)
left_board = Chessboard()
right_board = Chessboard()
for board, vect in (left_board, LEFT), (right_board, RIGHT):
board.set_width(4.5)
board.to_corner(DOWN + vect, buff=LARGE_BUFF)
coins = CoinsOnBoard(left_board)
coins.flip_by_message(next(messages))
arrow = Arrow(left_board.get_right(), right_board.get_left())
# Prepare for colorings
for cube in right_board:
cube.original_color = cube.get_color()
def get_special_square(board=right_board, coins=coins):
return board[boolian_linear_combo(coins.get_bools())]
frame.set_phi(45 * DEGREES)
# Introduce boards
self.add(title)
group = Group(*left_board, *coins, *right_board)
self.play(
LaggedStartMap(FadeInFromLarge, group, lambda m: (m, 1.3), lag_ratio=0.01),
GrowArrow(arrow)
)
# Flip one at a time
square = Square()
square.set_stroke(TEAL, 3)
square.replace(right_board[0])
square.move_to(right_board[0], OUT)
self.moving_square = square
self.colored_square = right_board[0]
for x in range(8):
self.set_board_message(next(messages), left_board, coins, get_special_square)
self.wait()
# To 6x6
to_fade = Group()
for grid in left_board, right_board, coins:
for n, mob in enumerate(grid):
row = n // 8
col = n % 8
if not ((0 < row < 7) and (0 < col < 7)):
to_fade.add(mob)
cross = Cross(title)
cross.fix_in_frame()
cross.set_stroke(RED, 8)
cross.shift(2 * LEFT)
imp_words = TextMobject("Impossible!")
imp_words.fix_in_frame()
imp_words.next_to(title, RIGHT, buff=1.5)
imp_words.shift(2 * LEFT)
imp_words.set_height(0.7)
imp_words.set_color(RED)
self.play(to_fade.set_opacity, 0.05)
self.play(
title.shift, 2 * LEFT,
FadeIn(cross, 2 * RIGHT),
FadeIn(imp_words, LEFT)
)
self.wait()
self.play(to_fade.set_opacity, 1)
# Remove a square
to_remove = Group(
left_board[63], right_board[63], coins[63]
)
remove_words = TextMobject("Remove one\\\\square")
remove_words.set_color(RED)
remove_words.to_corner(DOWN, buff=1.5)
remove_words.fix_in_frame()
self.play(
FadeIn(remove_words, DOWN),
FadeOut(to_remove, 3 * IN),
)
def set_board_message(self, message, left_board, coins, get_special_square):
new_bools = string_to_bools(message)
coins_to_flip = Group()
for coin, to_heads in zip(coins, new_bools):
if coin.is_heads() ^ to_heads:
coins_to_flip.add(coin)
coins_to_flip.shuffle()
self.play(LaggedStartMap(
FlipCoin, coins_to_flip,
lag_ratio=self.flip_lag_ratio,
run_time=1,
))
new_colored_square = get_special_square()
self.play(
new_colored_square.set_color, BLUE,
self.colored_square.set_color, self.colored_square.original_color,
self.moving_square.move_to, get_special_square(), OUT,
)
self.colored_square = new_colored_square
class ErrorCorrectionMention(Scene):
def construct(self):
# Setup board
message = "Do math!"
error_message = "Do meth!"
board = Chessboard()
board.set_width(5)
board.to_corner(DL)
coins = CoinsOnBoard(board)
coins.flip_by_message(message)
bools = coins.get_bools()
right_board = board.copy()
right_board.to_corner(DR)
right_board.set_opacity(0.5)
right_board[boolian_linear_combo(bools)].set_color(BLUE, 1)
arrow = Arrow(board.get_right(), right_board.get_left())
words = TextMobject("Feels a bit like ", "Error correction codes", "$\\dots$")
words.scale(1.2)
words.to_edge(UP)
self.add(board, coins, right_board)
self.add(arrow)
self.add(words)
# Go from board diagram to bit string
bits = VGroup()
for coin in coins:
bit = Integer(1 if coin.is_heads() else 0)
bit.replace(coin, dim_to_match=1)
bits.add(bit)
coin.generate_target()
coin.target.rotate(90 * DEGREES, RIGHT)
coin.target.set_opacity(0)
bits_rect = SurroundingRectangle(bits, buff=MED_SMALL_BUFF)
bits_rect.set_stroke(YELLOW, 2)
data_label = TextMobject("Data")
data_label.next_to(bits_rect, UP)
data_label.set_color(YELLOW)
meaning_label = TextMobject(f"``{message}''")
error_meaning_label = TextMobject(f"``{error_message}''")
for label in meaning_label, error_meaning_label:
label.scale(1.5)
label.next_to(arrow, RIGHT)
error_meaning_label[0][5].set_color(RED)
message_label = TextMobject("Message")
message_label.set_color(BLUE)
message_label.next_to(meaning_label, UP, buff=1.5)
message_label.to_edge(RIGHT, LARGE_BUFF)
message_arrow = Arrow(
message_label.get_bottom(),
meaning_label.get_top(),
)
message_arrow = Arrow(
message_label.get_left(),
meaning_label.get_top(),
path_arc=70 * DEGREES,
)
self.play(
LaggedStartMap(MoveToTarget, coins),
LaggedStartMap(FadeOut, board),
Write(bits),
run_time=3
)
self.play(
words[1].set_x, 0,
FadeOut(words[0], LEFT),
FadeOut(words[2], LEFT),
)
self.play(
ShowCreation(bits_rect),
FadeIn(data_label, DOWN)
)
self.play(
FadeOut(right_board),
FadeIn(message_label, DOWN),
ShowCreation(message_arrow),
FadeIn(meaning_label)
)
self.wait()
# Describe ECC
error_index = 8 * 4 + 5
error_bit = bits[error_index]
error_bit.unlock_triangulation()
error_bit_rect = SurroundingRectangle(error_bit)
error_bit_rect.set_stroke(RED, 2)
self.play(
FadeInFromLarge(error_bit_rect),
error_bit.set_value, 1 - error_bit.get_value(),
error_bit.set_color, RED,
)
meaning_label.save_state()
meaning_label.unlock_triangulation()
self.play(
Transform(meaning_label, error_meaning_label)
)
self.wait()
# Ask about correction
question = TextMobject("How can you\\\\detect the error?")
question.next_to(bits_rect, RIGHT, aligned_edge=UP)
self.play(Write(question))
self.wait(2)
ecc = VGroup()
for bit in int_to_bit_coords(boolian_linear_combo(bools), 6):
ecc.add(Integer(bit).match_height(bits[0]))
ecc.arrange(RIGHT, buff=0.2)
ecc.set_color(GREEN)
ecc.next_to(bits, RIGHT, MED_LARGE_BUFF, aligned_edge=DOWN)
ecc_rect = SurroundingRectangle(ecc, buff=MED_SMALL_BUFF)
ecc_rect.set_stroke(GREEN, 2)
ecc_name = words[1]
ecc_name.generate_target()
ecc_name.target[-1].set_opacity(0)
ecc_name.target.scale(0.75)
ecc_name.target.next_to(ecc_rect)
ecc_name.target.match_color(ecc)
frame = self.camera.frame
self.play(
MoveToTarget(ecc_name),
ShowIncreasingSubsets(ecc),
ShowCreation(ecc_rect),
ApplyMethod(frame.move_to, DOWN, run_time=2)
)
self.wait()
# Show correction at play
lines = VGroup()
for bit in bits:
line = Line(ecc_rect.get_top(), bit.get_center())
line.set_stroke(GREEN, 1, opacity=0.7)
lines.add(line)
alert = TexMobject("!!!")[0]
alert.arrange(RIGHT, buff=SMALL_BUFF)
alert.scale(1.5)
alert.set_color(RED)
alert.next_to(ecc_rect, UP)
self.play(LaggedStartMap(
ShowCreationThenFadeOut, lines,
lag_ratio=0.02, run_time=3
))
self.play(FadeIn(alert, DOWN, lag_ratio=0.2))
self.wait()
self.play(ShowCreation(lines, lag_ratio=0))
for line in lines:
line.generate_target()
line.target.become(lines[error_index])
self.play(LaggedStartMap(MoveToTarget, lines, lag_ratio=0, run_time=1))
self.play(
error_bit.set_value, 0,
Restore(meaning_label)
)
self.play(
FadeOut(lines),
FadeOut(alert),
FadeOut(error_bit_rect),
error_bit.set_color, WHITE,
)
self.wait()
# Hamming name
hamming_label = TextMobject("e.g. Hamming codes")
hamming_label.move_to(ecc_name, LEFT)
self.play(
Write(hamming_label),
FadeOut(ecc_name, DOWN)
)
self.wait()
class StandupMathsWrapper(Scene):
CONFIG = {
"title": "Solution on Stand-up Maths"
}
def construct(self):
fsr = FullScreenFadeRectangle()
fsr.set_fill(GREY_E, 1)
self.add(fsr)
title = TextMobject(self.title)
title.scale(1.5)
title.to_edge(UP)
rect = ScreenRectangle(height=6)
rect.set_stroke(WHITE, 2)
rect.set_fill(BLACK, 1)
rect.next_to(title, DOWN)
rb = AnimatedBoundary(rect)
self.add(rect, rb)
self.play(Write(title))
self.wait(30)
class ComingUpWrapper(StandupMathsWrapper):
CONFIG = {
"title": "Coming up"
}
class TitleCard(Scene):
def construct(self):
n = 6
board = Chessboard(shape=(n, n))
for square in board:
square.set_color(interpolate_color(square.get_color(), BLACK, 0.25))
# board[0].set_opacity(0)
grid = NumberPlane(
x_range=(0, n),
y_range=(0, n),
faded_line_ratio=0
)
grid.match_height(board)
grid.next_to(board, OUT, 1e-8)
low_grid = grid.copy()
low_grid.next_to(board, IN, 1e-8)
grid.add(low_grid)
grid.set_stroke(GREY, width=1)
grid.set_gloss(0.5)
grid.prepare_for_nonlinear_transform(0)
grid.rotate(PI, RIGHT)
frame = self.camera.frame
frame.set_phi(45 * DEGREES)
text = TextMobject("The impossible\\\\chessboard puzzle")
# text.set_width(board.get_width() - 0.5)
text.set_width(FRAME_WIDTH - 2)
text.set_stroke(BLACK, 10, background=True)
text.fix_in_frame()
self.play(
ApplyMethod(frame.set_phi, 0, run_time=5),
ShowCreationThenDestruction(grid, lag_ratio=0.02, run_time=3),
LaggedStartMap(FadeIn, board, run_time=3, lag_ratio=0),
Write(text, lag_ratio=0.1, run_time=3, stroke_color=BLUE_A),
)
self.wait(2)
class WhatAreWeDoingHere(TeacherStudentsScene):
def construct(self):
self.student_says(
"Wait, what are we\\\\doing here then?",
target_mode="sassy",
added_anims=[self.get_student_changes("hesitant", "angry", "sassy")],
run_time=2
)
self.play(self.teacher.change, "tease")
self.wait(6)
class HowCanWeVisualizeSolutions(TeacherStudentsScene):
def construct(self):
self.teacher_says(
"How can we\\\\visualize solutions",
bubble_kwargs={
"height": 3,
"width": 4,
"fill_opacity": 0,
},
added_anims=[self.get_student_changes("pondering", "thinking", "pondering")]
)
self.look_at(self.screen),
self.wait(1)
self.change_student_modes("thinking", "erm", "confused")
self.wait(5)
class TwoSquareCase(ThreeDScene):
CONFIG = {
"coin_names": ["c_0", "c_1"]
}
def construct(self):
frame = self.camera.frame
# Transition to just two square
chessboard = Chessboard()
chessboard.shift(2 * IN + UP)
coins = CoinsOnBoard(chessboard)
coins.flip_by_message("To 2 bits")
to_remove = Group(*it.chain(*zip(chessboard[:1:-1], coins[:1:-1])))
small_board = chessboard[:2]
coin_pair = coins[:2]
small_group = Group(small_board, coin_pair)
frame.set_phi(45 * DEGREES)
two_square_words = TextMobject("What about a 2-square board?")
two_square_words.fix_in_frame()
two_square_words.set_height(0.5)
two_square_words.center().to_edge(UP)
self.add(chessboard, coins)
self.play(
Write(two_square_words, run_time=1),
LaggedStartMap(
FadeOut, to_remove,
lambda m: (m, DOWN),
run_time=2,
lag_ratio=0.01
),
small_group.center,
small_group.set_height, 1.5,
frame.set_phi, 10 * DEGREES,
run_time=2
)
self.wait(3)
coins = coin_pair
# Show two locations for the key
key = Key()
key.set_width(0.7 * small_board[0].get_width())
key.move_to(small_board[0])
key.shift(0.01 * OUT)
coin_pair.shift(0.04 * OUT)
s0_top = small_board[0][0].get_top()
s1_top = small_board[1][0].get_top()
s0_rot_group = Group(small_board[0][0], coin_pair[0])
s1_rot_group = Group(small_board[1][0], coin_pair[1])
self.add(key)
angle = 170 * DEGREES
self.play(
Rotate(s0_rot_group, angle, LEFT, about_point=s0_top),
Rotate(s1_rot_group, angle, LEFT, about_point=s1_top),
frame.set_phi, 45 * DEGREES,
)
self.wait()
self.play(
key.match_x, small_board[1],
path_arc=PI,
path_arc_axis=UP,
)
self.wait()
self.play(
Rotate(s0_rot_group, angle, RIGHT, about_point=s0_top),
Rotate(s1_rot_group, angle, RIGHT, about_point=s1_top),
frame.set_phi, 0,
run_time=2,
)
self.wait()
# Show four states pointing to two message
states = VGroup(*[
TextMobject(letters, tex_to_color_map={"H": GOLD, "T": RED_D})
for letters in ["TT", "HT", "TH", "HH"]
])
states.set_height(0.8)
states.arrange(DOWN, buff=1)
states.to_edge(LEFT)
self.play(
FadeOut(two_square_words),
FlipCoin(coins[1]),
FadeIn(states[0])
)
self.play(
FlipCoin(coins[0]),
FadeIn(states[1])
)
self.play(
FlipCoin(coins[0]),
FlipCoin(coins[1]),
FadeIn(states[2])
)
self.play(
FlipCoin(coins[0]),
FadeIn(states[3])
)
self.wait()
key_copy = key.copy()
key_copy.match_x(small_board[0])
small_board_copy = small_board.copy()
small_boards = Group(small_board_copy, small_board)
for board, vect in zip(small_boards, [UP, DOWN]):
board.generate_target()
board.target.set_opacity(0.7)
board.target.shift(2 * vect)
board.target.set_depth(0.01, stretch=True)
self.add(key, key_copy, *small_boards)
self.play(
FadeOut(coins),
MoveToTarget(small_board),
MaintainPositionRelativeTo(key, small_board),
MoveToTarget(small_board_copy),
MaintainPositionRelativeTo(key_copy, small_board_copy),
)
self.add(*small_boards, key, key_copy)
arrows = VGroup()
for i in range(4):
arrows.add(Arrow(states[i].get_right(), small_boards[i // 2].get_left()))
arrows.set_opacity(0.75)
self.play(LaggedStartMap(GrowArrow, arrows, lag_ratio=0.3))
self.wait()
# Show one flip changing interpretation
coins.next_to(states, LEFT, buff=1.5)
def get_state(coins=coins):
bools = [c.is_heads() for c in coins]
return sum([b * (2**n) for n, b in enumerate(reversed(bools))])
n = 3
state_rect = SurroundingRectangle(states[n])
board_rects = VGroup()
for board in small_boards:
br = SurroundingRectangle(board, buff=0)
br.move_to(board, OUT)
br.set_stroke(YELLOW, 3)
board_rects.add(br)
self.play(
ApplyMethod(frame.shift, 4.5 * LEFT, run_time=1),
FadeIn(coins),
FadeIn(state_rect),
FadeIn(board_rects[1]),
arrows[n].set_color, YELLOW,
arrows[n].set_opacity, 1,
)
self.wait()
self.play(
FlipCoin(coins[1]),
FadeOut(board_rects[1]),
FadeIn(board_rects[0]),
state_rect.move_to, states[1],
arrows[3].match_style, arrows[0],
arrows[1].match_style, arrows[3],
)
self.wait()
self.play(
FlipCoin(coins[0]),
state_rect.move_to, states[0],
arrows[0].match_style, arrows[1],
arrows[1].match_style, arrows[3],
)
self.wait()
self.play(
FlipCoin(coins[0]),
state_rect.move_to, states[1],
arrows[0].match_style, arrows[1],
arrows[1].match_style, arrows[0],
)
self.wait()
# Erase H and T, replace with 1 and 0
bin_states = VGroup(*[
TextMobject(letters, tex_to_color_map={"1": GOLD, "0": RED_D})
for letters in ["00", "10", "01", "11"]
])
for bin_state, state in zip(bin_states, states):
for bit, letter in zip(bin_state, state):
bit.replace(letter, dim_to_match=1)
bin_coins = CoinsOnBoard(small_board, coin_config={"numeric_labels": True})
bin_coins[1].flip()
bin_coins.move_to(coins)
self.play(
FadeOut(coins, IN),
FadeIn(bin_coins),
)
self.play(
LaggedStartMap(GrowFromCenter, Group(*bin_states.family_members_with_points())),
LaggedStartMap(ApplyMethod, Group(*states.family_members_with_points()), lambda m: (m.scale, 0)),
)
self.wait()
# Add labels
c_labels = VGroup(*[
TexMobject(name)
for name in self.coin_names
])
arrow_kw = {
"tip_config": {
"width": 0.2,
"length": 0.2,
},
"buff": 0.1,
"color": GREY_B,
}
# s_label_arrows = VGroup()
# for high_square, low_square, label in zip(*small_boards, s_labels):
# label.move_to(Group(high_square, low_square))
# label.arrows = VGroup(
# Arrow(label.get_bottom(), low_square.get_top(), **arrow_kw),
# Arrow(label.get_top(), high_square.get_bottom(), **arrow_kw),
# )
# s_label_arrows.add(*label.arrows)
# self.play(
# FadeIn(label),
# *map(GrowArrow, label.arrows)
# )
c_label_arrows = VGroup()
for label, coin in zip(c_labels, bin_coins):
label.next_to(coin, UP, LARGE_BUFF)
arrow = Arrow(label.get_bottom(), coin.get_top(), **arrow_kw)
c_label_arrows.add(arrow)
self.play(
FadeIn(label),
GrowArrow(arrow)
)
self.wait()
# Coin 1 communicates location
bit1_rect = SurroundingRectangle(
VGroup(
bin_states[0][1],
bin_states[-1][1],
),
buff=SMALL_BUFF,
)
coin1_rect = SurroundingRectangle(
Group(c_labels[1], bin_coins[1]),
buff=SMALL_BUFF,
)
for rect in bit1_rect, coin1_rect:
rect.insert_n_curves(100)
nd = int(12 * get_norm(rect.get_area_vector()))
rect.become(DashedVMobject(rect, num_dashes=nd))
rect.set_stroke(WHITE, 2)
kw = {
"stroke_width": 2,
"stroke_color": YELLOW,
"buff": 0.05,
}
zero_rects, one_rects = [
VGroup(
SurroundingRectangle(bin_states[0][1], **kw),
SurroundingRectangle(bin_states[1][1], **kw),
),
VGroup(
SurroundingRectangle(bin_states[2][1], **kw),
SurroundingRectangle(bin_states[3][1], **kw),
),
]
self.play(
ShowCreation(bit1_rect),
ShowCreation(coin1_rect),
FadeOut(state_rect),
)
self.wait()
self.play(board_rects[0].stretch, 0.5, 0, {"about_edge": LEFT})
self.play(ShowCreation(zero_rects))
self.wait()
self.play(
FadeOut(board_rects[0]),
FadeOut(zero_rects),
FadeIn(board_rects[1])
)
self.play(
board_rects[1].stretch, 0.5, 0, {"about_edge": RIGHT}
)
self.play(
FlipCoin(bin_coins[1]),
arrows[1].match_style, arrows[0],
arrows[3].match_style, arrows[1],
)
self.play(ShowCreation(one_rects[1]))
self.wait()
# Talk about null bit
null_word = TextMobject("Null bit")
null_word.next_to(bin_coins[0], DOWN, buff=1.5, aligned_edge=LEFT)
null_arrow = Arrow(null_word.get_top(), bin_coins[0].get_bottom())
self.play(
Write(null_word),
GrowArrow(null_arrow)
)
self.wait()
for i in (0, 1, 0):
self.play(
FlipCoin(bin_coins[0]),
arrows[3 - i].match_style, arrows[0],
arrows[2 + i].match_style, arrows[3 - i],
FadeOut(one_rects[1 - i]),
FadeIn(one_rects[i]),
)
self.wait()
# Written mathematically
frame.generate_target()
frame.target.set_height(10, about_edge=DOWN)
rule_words = TextMobject("Rule: Just look at coin 1")
rule_words.set_height(0.6)
rule_words.next_to(frame.target.get_corner(UL), DR, buff=0.5)
rule_arrow = Vector(1.5 * RIGHT)
rule_arrow.next_to(rule_words, RIGHT)
rule_arrow.set_color(BLUE)
rule_equation = TexMobject("K", "=", self.coin_names[1])
rule_equation_long = TexMobject(
"K", "=", "0", "\\cdot",
self.coin_names[0], "+", "1", "\\cdot",
self.coin_names[1],
)
for equation in rule_equation, rule_equation_long:
equation.set_color_by_tex("S", YELLOW)
equation.set_height(0.7)
equation.next_to(rule_arrow, RIGHT)
s_labels = VGroup(
TexMobject("K", "= 0"),
TexMobject("K", "= 1"),
)
for label, board in zip(s_labels, small_boards):
label.set_height(0.5)
label.next_to(board, UP)
label.set_color_by_tex("S", YELLOW)
self.play(
MoveToTarget(frame),
FadeIn(rule_words, 2 * DOWN)
)
self.wait()
for label in s_labels:
self.play(Write(label))
self.wait()
self.play(
GrowArrow(rule_arrow),
FadeIn(rule_equation, LEFT),
)
self.wait()
mid_equation = rule_equation_long[2:-1]
mid_equation.save_state()
mid_equation.scale(0, about_edge=LEFT)
self.play(
Transform(rule_equation[:2], rule_equation_long[:2]),
Transform(rule_equation[2], rule_equation_long[-1]),
Restore(mid_equation),
)
self.wait()
self.remove(bin_coins)
for mob in self.mobjects:
for submob in mob.get_family():
if isinstance(submob, TexSymbol):
submob.set_stroke(BLACK, 8, background=True)
self.add(bin_coins)
class TwoSquaresAB(TwoSquareCase):
CONFIG = {
"coin_names": ["a", "b"]
}
class IGotThis(TeacherStudentsScene):
def construct(self):
self.student_says(
"Pssh, I got this",
target_mode="tease",
look_at_arg=self.screen,
added_anims=[self.teacher.change, "happy", self.screen],
run_time=2,
)
self.change_student_modes(
"thinking", "pondering",
look_at_arg=self.screen
)
self.wait(6)
class WalkingTheSquare(ThreeDScene):
def construct(self):
# Setup objects
plane = NumberPlane(
x_range=(-2, 2, 1),
y_range=(-2, 2, 1),
height=15,
width=15,
faded_line_ratio=3,
axis_config={"include_tip": False}
)
plane.move_to(1.5 * DOWN)
plane.add_coordinate_labels()
plane.x_axis.add_numbers([0])
board = Chessboard(shape=(1, 2))
board.set_height(1.5)
board.move_to(plane.c2p(-0.75, 0.75))
coins = CoinsOnBoard(
board,
coin_config={"numeric_labels": True}
)
coins.flip(RIGHT)
coords = [(0, 0), (0, 1), (1, 0), (1, 1)]
coord_labels = VGroup()
dots = VGroup()
for x, y in coords:
label = TexMobject(f"({x}, {y})")
point = plane.c2p(x, y)
label.next_to(point, UR, buff=0.25)
dot = Dot(point, radius=0.075)
dot.set_color(GREY)
dots.add(dot)
coord_labels.add(label)
active_dot = Dot(radius=0.15, color=YELLOW)
active_dot.move_to(plane.c2p(0, 0))
# Walk around square
self.play(Write(plane))
self.play(
FadeIn(board),
FadeIn(coins),
FadeIn(active_dot),
FadeIn(coord_labels[0]),
)
edges = VGroup()
for i, j, c in [(0, 1, 1), (1, 3, 0), (3, 2, 1), (2, 0, 0)]:
edge = Line(plane.c2p(*coords[i]), plane.c2p(*coords[j]))
edge.set_stroke(PINK, 3)
edges.add(edge)
anims = [
FlipCoin(coins[c]),
ShowCreation(edge),
ApplyMethod(active_dot.move_to, dots[j])
]
if j != 0:
anims += [
FadeInFromPoint(coord_labels[j], coord_labels[i].get_center()),
]
self.add(edge, dots[i], active_dot)
self.play(*anims)
self.add(edges, dots, active_dot)
self.wait()
# Show a few more flips
self.play(
FlipCoin(coins[0]),
active_dot.move_to, dots[2],
)
self.play(
FlipCoin(coins[1]),
active_dot.move_to, dots[3],
)
self.play(
FlipCoin(coins[1]),
active_dot.move_to, dots[2],
)
self.wait()
# Circles illustrating scheme
low_rect = SurroundingRectangle(
VGroup(edges[3], coord_labels[0], coord_labels[2], plane.x_axis.numbers[-1]),
buff=0.25,
)
low_rect.round_corners()
low_rect.insert_n_curves(30)
low_rect.set_stroke(YELLOW, 3)
high_rect = low_rect.copy()
high_rect.shift(dots[1].get_center() - dots[0].get_center())
key = Key()
key.set_color(YELLOW)
key.set_gloss(0)
key.match_width(board[0])
key.next_to(board[0], UP, SMALL_BUFF)
s_labels = VGroup(
TexMobject("\\text{Key} = 0").next_to(low_rect, UP, SMALL_BUFF),
TexMobject("\\text{Key} = 1").next_to(high_rect, UP, SMALL_BUFF),
)
self.play(
ShowCreation(low_rect),
)
self.play(
FadeIn(key, DOWN),
FadeIn(s_labels[0], DOWN),
)
self.play(
FlipCoin(coins[0]),
active_dot.move_to, dots[0],
)
self.wait(0.5)
self.play(
FlipCoin(coins[0]),
active_dot.move_to, dots[2],
)
self.wait()
self.play(
TransformFromCopy(low_rect, high_rect),
FadeIn(s_labels[1], DOWN),
low_rect.set_stroke, GREY, 1,
FlipCoin(coins[1]),
active_dot.move_to, dots[3],
key.match_x, board[1],
)
self.wait()
self.play(
FlipCoin(coins[0]),
active_dot.move_to, dots[1],
)
self.wait()
self.play(
FlipCoin(coins[1]),
active_dot.move_to, dots[0],
key.match_x, board[0],
high_rect.match_style, low_rect,
low_rect.match_style, high_rect,
)
self.wait()
class ThreeSquareCase(ThreeDScene):
CONFIG = {
"coin_names": ["c_0", "c_1", "c_2"]
}
def construct(self):
# Show sequence of boards
boards = Group(
Chessboard(shape=(1, 2), height=0.25 * 1),
Chessboard(shape=(1, 3), height=0.25 * 1),
Chessboard(shape=(2, 2), height=0.25 * 2),
Chessboard(shape=(8, 8), height=0.25 * 8),
)
dots = TexMobject("\\dots")
group = Group(*boards[:3], dots, boards[3])
group.arrange(RIGHT)
group.set_width(FRAME_WIDTH - 1)
board_groups = Group()
for board in boards:
board.coins = CoinsOnBoard(board, coin_config={"numeric_labels": True})
board_groups.add(Group(board, board.coins))
boards[0].coins.flip_at_random()
boards[1].coins.flip_by_bools([False, True, False])
boards[2].coins.flip_at_random()
boards[3].coins.flip_by_message("3 Fails!")
def get_board_transform(i, bgs=board_groups):
return TransformFromCopy(
bgs[i], bgs[i + 1],
path_arc=PI / 2,
run_time=2,
)
frame = self.camera.frame
frame.save_state()
frame.scale(0.5)
frame.move_to(boards[:2])
self.add(board_groups[0])
self.play(get_board_transform(0))
turn_animation_into_updater(Restore(frame, run_time=4))
self.add(frame)
self.play(get_board_transform(1))
self.play(
Write(dots),
get_board_transform(2),
)
self.wait()
# Isolate 3 square board
board_group = board_groups[1]
board = boards[1]
coins = board.coins
title = TextMobject("Three square case")
title.set_height(0.7)
title.to_edge(UP)
board_group.generate_target()
board_group.target.set_width(4)
board_group.target.move_to(DOWN, OUT)
self.save_state()
self.play(
MoveToTarget(board_group, rate_func=squish_rate_func(smooth, 0.5, 1)),
Write(title),
LaggedStartMap(FadeOut, Group(
board_groups[0],
board_groups[2],
dots,
board_groups[3],
), lambda m: (m, DOWN)),
run_time=2,
)
self.wait()
# Try 0*c0 + 1*c1 + 2*c2
s_sum = TexMobject(
"0", "\\cdot", self.coin_names[0], "+",
"1", "\\cdot", self.coin_names[1], "+",
"2", "\\cdot", self.coin_names[2],
)
s_sum.set_height(0.6)
c_sum = s_sum.copy()
s_sum.center().to_edge(UP)
c_sum.next_to(s_sum, DOWN, LARGE_BUFF)
coin_copies = Group()
for i in range(3):
part = c_sum.get_part_by_tex(self.coin_names[i], substring=False)
coin_copy = coins[i].copy()
coin_copy.set_height(1.2 * c_sum[0].get_height())
coin_copy.move_to(part)
coin_copy.align_to(c_sum, UP)
part.set_opacity(0)
coin_copies.add(coin_copy)
self.play(
FadeOut(title),
FadeIn(s_sum[:3]),
FadeIn(c_sum[:2]),
)
self.play(TransformFromCopy(coins[0], coin_copies[0]))
self.wait()
self.play(
FadeIn(s_sum[3:7]),
FadeIn(c_sum[3:6]),
)
self.play(TransformFromCopy(coins[1], coin_copies[1]))
self.wait()
self.play(
FadeIn(s_sum[7:11]),
FadeIn(c_sum[7:10]),
)
self.play(TransformFromCopy(coins[2], coin_copies[2]))
self.wait()
self.add(s_sum, c_sum, coin_copies)
rhs = VGroup(TexMobject("="), Integer(1))
rhs.arrange(RIGHT)
rhs[1].set_color(YELLOW)
rhs.match_height(c_sum[0])
rhs.next_to(c_sum, RIGHT, aligned_edge=UP)
braces = VGroup(
Brace(c_sum[0:3], DOWN),
Brace(c_sum[0:7], DOWN),
Brace(c_sum[0:11], DOWN),
)
for brace, n in zip(braces, [0, 1, 1]):
brace.add(brace.get_tex(n))
brace.unlock_triangulation()
self.play(GrowFromCenter(braces[0]))
self.wait()
self.play(ReplacementTransform(braces[0], braces[1]))
self.wait()
self.play(ReplacementTransform(braces[1], braces[2]))
self.play(
TransformFromCopy(braces[2][-1], rhs[1], path_arc=-PI / 2),
Write(rhs[0]),
)
self.play(FadeOut(braces[2]))
self.wait()
# Show values of S
s_labels = VGroup(*[
TexMobject(f"K = {n}")
for n in range(3)
])
for label, square in zip(s_labels, board):
label.next_to(square, UP)
label.set_width(0.8 * square.get_width())
label.set_color(YELLOW)
key = Key(depth_test=False)
key.set_stroke(BLACK, 3, background=True)
key.set_width(0.8 * board[0].get_width())
key.move_to(board[0])
self.play(
coins.next_to, board, DOWN,
coins.match_z, coins,
board.set_opacity, 0.75,
FadeIn(key),
FadeIn(s_labels[0])
)
self.wait(0.5)
for i in (1, 2):
self.play(
ApplyMethod(key.move_to, board[i], path_arc=-45 * DEGREES),
s_labels[i - 1].set_fill, GREY, 0.25,
FadeIn(s_labels[i]),
)
self.wait(0.5)
# Mod 3 label
mod3_label = TextMobject("(mod 3)")
mod3_label.match_height(s_sum)
mod3_label.set_color(BLUE)
mod3_label.next_to(s_sum, RIGHT, buff=0.75)
rhs_rhs = TexMobject("\\equiv 0")
rhs_rhs.match_height(rhs)
rhs_rhs.next_to(rhs, RIGHT)
self.play(Write(mod3_label))
self.wait()
rhs[1].unlock_triangulation()
self.play(
FlipCoin(coins[2]),
FlipCoin(coin_copies[2]),
rhs[1].set_value, 3,
)
self.wait()
self.play(Write(rhs_rhs))
self.wait()
self.play(
rhs[1].set_value, 0,
FadeOut(rhs_rhs)
)
# Show a few flips
for i in [2, 1, 0, 2, 1, 2, 0]:
bools = coins.get_bools()
bools[i] = not bools[i]
new_sum = sum([n * b for n, b in enumerate(bools)]) % 3
self.play(
FlipCoin(coins[i]),
FlipCoin(coin_copies[i]),
rhs[1].set_value, new_sum,
)
self.wait()
# Show general sum
general_sum = TexMobject(r"\sum ^{63}_{n=0}n\cdot c_n")
mod_64 = TextMobject("(mod 64)")
mod_64.next_to(general_sum, DOWN)
general_sum.add(mod_64)
general_sum.to_corner(UL)
self.play(FadeIn(general_sum))
self.wait()
self.play(FadeOut(general_sum))
# Walk through 010 example
board.flip_by_bools([False, False, True])
self.play(
s_labels[2].set_fill, GREY, 0.25,
s_labels[0].set_fill, YELLOW, 1,
ApplyMethod(key.move_to, board[0], path_arc=30 * DEGREES)
)
self.wait()
self.play(
FlipCoin(coins[1]),
FlipCoin(coin_copies[1]),
rhs[1].set_value, 0,
)
self.wait()
square = Square()
square.set_stroke(YELLOW, 3)
square.replace(board[0])
square[0].move_to(board[0], OUT)
self.play(ShowCreation(square))
self.wait()
self.play(FadeOut(square))
# Walk through alternate flip on 010 example
self.play(
FlipCoin(coins[1]),
FlipCoin(coin_copies[1]),
rhs[1].set_value, 1,
)
morty = Mortimer(height=1.5, mode="hooray")
morty.to_corner(DR)
bubble = SpeechBubble(height=2, width=2)
bubble.pin_to(morty)
bubble.write("There's another\\\\way!")
self.play(
FadeIn(morty),
ShowCreation(bubble),
Write(bubble.content, run_time=1),
)
self.play(Blink(morty))
self.play(
FadeOut(VGroup(morty, bubble, bubble.content))
)
self.play(
FlipCoin(coins[2]),
FlipCoin(coin_copies[2]),
rhs[1].set_value, 3,
)
self.wait()
self.play(rhs[1].set_value, 0)
self.wait()
class ThreeSquaresABC(ThreeSquareCase):
CONFIG = {
"coin_names": ["a", "b", "c"]
}
class FailedMod3Addition(Scene):
def construct(self):
coin = Coin(height=0.5, numeric_labels=True)
csum = Group(
TexMobject("0 \\cdot"),
coin.deepcopy().flip(),
TexMobject(" + 1 \\cdot"),
coin.deepcopy().flip(),
TexMobject("+ 2 \\cdot"),
coin.deepcopy(),
TexMobject("="),
Integer(2, color=YELLOW),
)
csum.arrange(RIGHT, buff=SMALL_BUFF)
csum[-1].unlock_triangulation()
csum[-1].shift(SMALL_BUFF * RIGHT)
coins = csum[1:7:2]
csum[-1].add_updater(lambda m, coins=coins: m.set_value(coins[1].is_heads() + 2 * coins[2].is_heads()))
self.add(csum)
for coin in coins[::-1]:
rect = SurroundingRectangle(coin)
self.play(ShowCreation(rect))
self.play(FlipCoin(coin))
self.wait()
self.play(FlipCoin(coin), FadeOut(rect))
self.wait()
self.embed()
class TreeOfThreeFlips(ThreeDScene):
def construct(self):
# Setup sums
csum = Group(
TexMobject("0 \\cdot"),
Coin(numeric_labels=True),
TexMobject("+\\,1 \\cdot"),
Coin(numeric_labels=True),
TexMobject("+\\,2 \\cdot"),
Coin(numeric_labels=True),
TexMobject("="),
Integer(0)
)
csum.coins = csum[1:7:2]
csum.coins.set_height(1.5 * csum[0].get_height())
csum.coins.flip(RIGHT)
csum.coins[1].flip(RIGHT)
csum.arrange(RIGHT, buff=0.1)
csum[-1].align_to(csum[0], DOWN)
csum[-1].shift(SMALL_BUFF * RIGHT)
csum.to_edge(LEFT)
csum_rect = SurroundingRectangle(csum, buff=SMALL_BUFF)
csum_rect.set_stroke(WHITE, 1)
# Set rhs values
def set_rhs_target(cs, colors=[RED, GREEN, BLUE]):
bools = [c.is_heads() for c in cs.coins]
value = sum([n * b for n, b in enumerate(bools)]) % 3
cs[-1].unlock_triangulation()
cs[-1].generate_target()
cs[-1].target.set_value(value)
cs[-1].target.set_color(colors[value])
return cs[-1]
rhs = set_rhs_target(csum)
rhs.become(rhs.target)
# Create copies
new_csums = Group()
for i in range(3):
new_csum = csum.deepcopy()
new_csum.coins = new_csum[1:7:2]
new_csums.add(new_csum)
new_csums.arrange(DOWN, buff=1.5)
new_csums.next_to(csum, RIGHT, buff=3)
# Arrows
arrows = VGroup()
for i, ncs in enumerate(new_csums):
arrow = Arrow(csum_rect.get_right(), ncs.get_left())
label = TextMobject(f"Flip coin {i}")
label.set_height(0.3)
label.set_fill(GREY_A)
label.set_stroke(BLACK, 3, background=True)
label.next_to(ORIGIN, UP, buff=0)
label.rotate(arrow.get_angle(), about_point=ORIGIN)
label.shift(arrow.get_center())
arrow.label = label
arrows.add(arrow)
arrows.set_color(GREY)
# Initial state label
is_label = TextMobject(
"Initial state: 010",
tex_to_color_map={"0": RED_D, "1": GOLD_D}
)
is_label.set_height(0.4)
is_label.next_to(csum_rect, UP, aligned_edge=LEFT)
# Show three flips
self.add(csum)
self.add(csum_rect)
self.add(is_label)
self.wait()
anims = []
for i, arrow, ncs in zip(it.count(), arrows, new_csums):
anims += [
GrowArrow(arrow),
FadeIn(arrow.label, lag_ratio=0.2),
]
self.play(LaggedStart(*anims))
for indices in [[0], [1, 2]]:
self.play(*[
TransformFromCopy(csum, new_csums[i], path_arc=30 * DEGREES, run_time=2)
for i in indices
])
self.wait()
for i in indices:
ncs = new_csums[i]
ncs.coins[i].flip()
rhs = set_rhs_target(ncs)
ncs.coins[i].flip()
self.play(
FlipCoin(ncs.coins[i]),
MoveToTarget(rhs)
)
# Put key in square 2
board = Chessboard(shape=(1, 3), square_resolution=(5, 5))
board.set_gloss(0.5)
board.set_width(3)
board.set_depth(0.25, stretch=True)
board.space_out_submobjects(factor=1.001)
board.next_to(ORIGIN, LEFT)
board.to_edge(UP)
board.shift(IN)
board.rotate(60 * DEGREES, LEFT)
opening_square = board[2][0]
opening_square_top = opening_square.get_corner(UP + IN)
key = Key()
key.to_corner(UL, buff=LARGE_BUFF)
key.shift(OUT)
key.generate_target()
key.target.scale(0.3)
key.target.rotate(60 * DEGREES, LEFT)
key.target.move_to(board[2][0])
self.play(
FadeIn(board, DOWN),
FadeIn(key)
)
self.play(
MoveToTarget(key, path_arc=30 * DEGREES),
Rotate(opening_square, 90 * DEGREES, LEFT, about_point=opening_square_top),
)
self.play(
Rotate(opening_square, 90 * DEGREES, RIGHT, about_point=opening_square_top),
key.next_to, board[1], RIGHT, buff=0.01,
)
self.wait()
self.remove(key)
self.play(Rotate(board, 0 * DEGREES, RIGHT, run_time=0))
self.play(Rotate(board, 60 * DEGREES, RIGHT))
# Put coins on
coins = csum.coins.copy()
for coin, cube in zip(coins, board):
coin.generate_target()
coin.target.next_to(cube, OUT, buff=0)
self.play(LaggedStartMap(MoveToTarget, coins, run_time=2))
self.wait()
class SeventyFivePercentChance(Scene):
def construct(self):
# Setup column
rows = []
n_shown = 5
coins = Group()
nums = VGroup()
for n in it.chain(range(n_shown), range(64 - n_shown, 64)):
coin = Coin(numeric_labels=True)
coin.set_height(0.7)
if (random.random() < 0.5 or (n == 2)) and (n != 62):
coin.flip()
num = Integer(n)
row = Group(
coin,
TexMobject("\\cdot"),
num,
TexMobject("+"),
)
VGroup(*row[1:]).set_stroke(BLACK, 3, background=True)
row.arrange(RIGHT, buff=MED_SMALL_BUFF)
rows.append(row)
coins.add(coin)
nums.add(num)
vdots = TexMobject("\\vdots")
rows = Group(*rows[:n_shown], vdots, *rows[n_shown:])
rows.arrange(DOWN, buff=MED_SMALL_BUFF, aligned_edge=LEFT)
vdots.match_x(rows[0][2])
rows.set_height(7)
rows.to_edge(RIGHT)
rows[-1][-1].set_opacity(0)
nums = VGroup(*nums[:n_shown], vdots, *nums[n_shown:])
self.play(Write(nums))
self.wait()
self.play(
LaggedStartMap(FadeIn, rows, lag_ratio=0.1, run_time=3),
Animation(nums.copy(), remover=True),
)
self.wait()
# Show desired sums
brace = Brace(rows, LEFT)
b_label = brace.get_text("Sum mod 64")
sum_label = TextMobject("=\\, 53 (say)")
sum_label.next_to(b_label, DOWN)
want_label = TextMobject("Need to encode 55 (say)")
want_label.next_to(sum_label, DOWN, buff=0.25, aligned_edge=RIGHT)
want_label.set_color(YELLOW)
need_label = TextMobject("Must add 2")
need_label.next_to(want_label, DOWN, buff=0.25)
need_label.set_color(BLUE)
for label in b_label, sum_label, want_label, need_label:
label.set_stroke(BLACK, 7, background=True)
self.play(
GrowFromCenter(brace),
FadeIn(b_label, RIGHT)
)
self.wait(2)
self.play(FadeIn(sum_label, 0.25 * UP))
self.wait(2)
self.play(LaggedStart(
FadeIn(want_label, UP),
FadeIn(need_label, UP),
lag_ratio=0.3
))
self.wait()
# Show attempts
s_rect = SurroundingRectangle(rows[2])
self.play(ShowCreation(s_rect))
self.wait()
self.play(FlipCoin(rows[2][0]))
self.wait(2)
self.play(
s_rect.move_to, rows[-2],
s_rect.stretch, 1.1, 0,
)
self.wait()
self.play(FlipCoin(rows[-2][0]))
self.wait()
class ModNStrategy(ThreeDScene):
def construct(self):
# Board
n_shown = 5
board = Chessboard()
coins = CoinsOnBoard(board, coin_config={"numeric_labels": True})
coins.flip_by_message(r"75% odds")
nums = VGroup()
for n, square in enumerate(board):
num = Integer(n)
num.set_height(0.4 * square.get_height())
num.next_to(square, OUT, buff=0)
nums.add(num)
nums.set_stroke(BLACK, 3, background=True)
coins.generate_target()
for coin in coins.target:
coin.set_opacity(0.2)
coin[-2:].set_opacity(0)
self.add(board)
self.add(coins)
self.wait()
self.play(
MoveToTarget(coins),
FadeIn(nums, lag_ratio=0.1)
)
self.wait()
# # Compress
# square_groups = Group(*[
# Group(square, coin, num)
# for square, coin, num in zip(board, coins, nums)
# ])
# segments = Group(
# square_groups[:n_shown],
# square_groups[n_shown:-n_shown],
# square_groups[-n_shown:],
# )
# segments.generate_target()
# dots = TexMobject("\\cdots")
# dots.center()
# segments.target[0].next_to(dots, LEFT)
# segments.target[2].next_to(dots, RIGHT)
# segments.target[1].scale(0)
# segments.target[1].move_to(dots)
# self.play(
# Write(dots),
# MoveToTarget(segments),
# )
# self.wait()
# self.remove(segments[1])
# # Raise coins
# coins = Group(*coins[:n_shown], *coins[-n_shown:])
# nums = VGroup(*nums[:n_shown], *nums[-n_shown:])
# board = Group(*board[:n_shown], *board[-n_shown:])
# self.play(
# coins.shift, UP,
# coins.set_opacity, 1,
# )
# Setup sum
mid_coins = coins[n_shown:-n_shown]
mid_nums = nums[n_shown:-n_shown]
coins = Group(*coins[:n_shown], *coins[-n_shown:])
nums = VGroup(*nums[:n_shown], *nums[-n_shown:])
nums.generate_target()
coins.generate_target()
coins.target.set_opacity(1)
full_sum = Group()
to_fade_in = VGroup()
for num, coin in zip(nums.target, coins.target):
coin.set_height(0.7)
num.set_height(0.5)
summand = Group(
coin,
TexMobject("\\cdot"),
num,
TexMobject("+"),
)
to_fade_in.add(summand[1], summand[3])
VGroup(*summand[1:]).set_stroke(BLACK, 3, background=True)
summand.arrange(RIGHT, buff=MED_SMALL_BUFF)
full_sum.add(summand)
dots = TexMobject("\\dots")
full_sum = Group(*full_sum[:n_shown], dots, *full_sum[n_shown:])
full_sum.arrange(RIGHT, buff=MED_SMALL_BUFF)
full_sum.set_width(FRAME_WIDTH - 1)
full_sum[-1][-1].scale(0, about_edge=LEFT)
full_sum.move_to(UP)
brace = Brace(full_sum, DOWN)
s_label = VGroup(
TextMobject("Sum (mod 64) = "),
Integer(53),
)
s_label[1].set_color(BLUE)
s_label[1].match_height(s_label[0][0][0])
s_label.arrange(RIGHT)
s_label[1].align_to(s_label[0][0][0], DOWN)
s_label.next_to(brace, DOWN)
words = TextMobject("Can't know if a flip will add or subtract")
words.to_edge(UP)
for mob in mid_coins, mid_nums:
mob.generate_target()
mob.target.move_to(dots)
mob.target.scale(0)
mob.target.set_opacity(0)
self.play(
FadeOut(board, IN),
MoveToTarget(mid_coins, remover=True),
MoveToTarget(mid_nums, remover=True),
MoveToTarget(nums),
MoveToTarget(coins),
Write(dots),
FadeIn(to_fade_in, lag_ratio=0.1),
run_time=2
)
self.play(
GrowFromCenter(brace),
FadeIn(s_label, 0.25 * UP)
)
self.wait()
self.play(Write(words, run_time=1))
self.wait()
# Do some flips
s_label[1].add_updater(lambda m: m.set_value(m.get_value() % 64))
for x in range(10):
n = random.randint(-n_shown, n_shown - 1)
coin = coins[n]
n = n % 64
diff_label = Integer(n, include_sign=True)
if not coin.is_heads():
diff_label.set_color(GREEN)
else:
diff_label.set_color(RED)
diff_label.set_value(-diff_label.get_value())
diff_label.next_to(coin, UP, aligned_edge=LEFT)
self.play(
ChangeDecimalToValue(
s_label[1],
s_label[1].get_value() + n,
rate_func=squish_rate_func(smooth, 0.5, 1)
),
FlipCoin(coin),
FadeIn(diff_label, 0.5 * DOWN)
)
self.play(FadeOut(diff_label))
self.wait()
class ShowCube(ThreeDScene):
def construct(self):
# Camera stuffs
frame = self.camera.frame
light = self.camera.light_source
light.move_to([-10, -10, 20])
# Plane and axes
plane = NumberPlane(
x_range=(-2, 2, 1),
y_range=(-2, 2, 1),
height=15,
width=15,
faded_line_ratio=3,
axis_config={"include_tip": False}
)
plane.add_coordinate_labels()
plane.coordinate_labels.set_stroke(width=0)
axes = ThreeDAxes(
x_range=(-2, 2, 1),
y_range=(-2, 2, 1),
z_range=(-2, 2, 1),
height=15,
width=15,
depth=15,
)
axes.apply_depth_test()
# Vertices and edges
vert_coords = [
(n % 2, (n // 2) % 2, (n // 4) % 2)
for n in range(8)
]
verts = []
coord_labels = VGroup()
coord_labels_2d = VGroup()
for coords in vert_coords:
vert = axes.c2p(*coords)
verts.append(vert)
x, y, z = coords
label = TexMobject(f"({x}, {y}, {z})")
label.set_height(0.3)
label.next_to(vert, UR, SMALL_BUFF)
label.rotate(89 * DEGREES, RIGHT, about_point=vert)
coord_labels.add(label)
if z == 0:
label_2d = TexMobject(f"({x}, {y})")
label_2d.set_height(0.3)
label_2d.next_to(vert, UR, SMALL_BUFF)
coord_labels_2d.add(label_2d)
edge_indices = [
(0, 1), (0, 2), (1, 3), (2, 3),
(0, 4), (1, 5), (2, 6), (3, 7),
(4, 5), (4, 6), (5, 7), (6, 7),
]
# Vertex and edge drawings
spheres = SGroup()
for vert in verts:
sphere = Sphere(
radius=0.1,
resolution=(9, 9),
)
sphere.set_gloss(0.3)
sphere.set_color(GREY)
sphere.move_to(vert)
spheres.add(sphere)
edges = SGroup()
for i, j in edge_indices:
edge = Line3D(
verts[i], verts[j],
resolution=(5, 51),
width=0.04,
gloss=0.5,
)
edge.set_color(GREY_BROWN)
edges.add(edge)
# Setup highlight animations
def highlight(n, spheres=spheres, coord_labels=coord_labels):
anims = []
for k, sphere, cl in zip(it.count(), spheres, coord_labels):
if k == n:
sphere.save_state()
cl.save_state()
sphere.generate_target()
cl.generate_target()
cl.target.set_fill(YELLOW)
sphere.target.set_color(YELLOW)
Group(cl.target, sphere.target).scale(1.5, about_point=sphere.get_center())
anims += [
MoveToTarget(sphere),
MoveToTarget(cl),
]
elif sphere.get_color() == Color(YELLOW):
anims += [
Restore(sphere),
Restore(cl),
]
return AnimationGroup(*anims)
# Setup 2d case
frame.move_to(1.5 * UP)
self.add(plane)
self.play(
LaggedStartMap(FadeIn, coord_labels_2d),
LaggedStartMap(GrowFromCenter, spheres[:4]),
LaggedStartMap(GrowFromCenter, edges[:4]),
)
self.wait()
# Transition to 3d case
frame.generate_target()
frame.target.set_rotation(-25 * DEGREES, 70 * DEGREES)
frame.target.move_to([1, 2, 0])
frame.target.set_height(10)
to_grow = Group(*edges[4:], *spheres[4:], *coord_labels[4:])
to_grow.save_state()
to_grow.set_depth(0, about_edge=IN, stretch=True)
rf = squish_rate_func(smooth, 0.5, 1)
self.play(
MoveToTarget(frame),
ShowCreation(axes.z_axis),
Restore(to_grow, rate_func=rf),
FadeOut(coord_labels_2d, rate_func=rf),
*[
FadeInFromPoint(cl, cl2.get_center(), rate_func=squish_rate_func(smooth, 0.5, 1))
for cl, cl2 in zip(coord_labels[:4], coord_labels_2d)
],
run_time=3
)
frame.start_time = self.time
frame.scene = self
frame.add_updater(lambda m: m.set_theta(
-25 * DEGREES * math.cos((m.scene.time - m.start_time) * PI / 60)
))
self.add(axes.z_axis)
self.add(edges)
self.add(spheres)
self.play(
LaggedStart(*[Indicate(s, color=GREEN) for s in spheres], run_time=2, lag_ratio=0.1),
LaggedStart(*[Indicate(c, color=GREEN) for c in coord_labels], run_time=2, lag_ratio=0.1),
)
# Add chessboard
board = Chessboard(
shape=(1, 3), height=1,
square_resolution=(5, 5),
)
board.move_to(plane.c2p(-1, 0), DOWN + IN)
coins = CoinsOnBoard(board, coin_config={"numeric_labels": True})
self.play(
FadeIn(board),
FadeIn(coins),
highlight(7)
)
# Walk along a few edges
for ci in [1, 2, 0, 1, 2, 1, 0, 1]:
coin = coins[ci]
curr_n = sum([(2**k) * c.is_heads() for k, c in enumerate(coins)])
coin.flip()
new_n = sum([(2**k) * c.is_heads() for k, c in enumerate(coins)])
coin.flip()
line = Line(verts[curr_n], verts[new_n])
line.set_stroke(YELLOW, 3)
self.play(
FlipCoin(coin),
highlight(new_n),
ShowCreationThenDestruction(line)
)
self.wait()
# Color the corners
self.play(
highlight(-1),
edges.set_color, GREY, 0.5,
)
colors = [RED, GREEN, BLUE_D]
title = TextMobject("Strategy", "\\, $\\Leftrightarrow$ \\,", "Coloring")
title[2].set_submobject_colors_by_gradient(*colors)
title.set_stroke(BLACK, 5, background=True)
title.set_height(0.7)
title.to_edge(UP)
title.shift(LEFT)
title.fix_in_frame()
color_label_templates = [
TexMobject(char, color=color).rotate(PI / 2, RIGHT).match_depth(coord_labels[0])
for char, color in zip("RGB", colors)
]
coord_labels.color_labels = VGroup(*[VMobject() for cl in coord_labels])
def get_coloring_animation(ns,
spheres=spheres,
coord_labels=coord_labels,
colors=colors,
color_label_templates=color_label_templates,
):
anims = []
new_color_labels = VGroup()
for n, sphere, coord_label, old_color_label in zip(ns, spheres, coord_labels, coord_labels.color_labels):
color = colors[int(n)]
sphere.generate_target()
coord_label.generate_target()
sphere.target.set_color(color)
coord_label.target.set_fill(color)
color_label = color_label_templates[n].copy()
color_label.next_to(coord_label, RIGHT, SMALL_BUFF)
anims += [
MoveToTarget(sphere),
MoveToTarget(coord_label),
FadeIn(color_label, 0.25 * IN),
FadeOut(old_color_label, 0.25 * OUT),
]
new_color_labels.add(color_label)
coord_labels.color_labels = new_color_labels
return LaggedStart(*anims, run_time=2)
self.play(
FadeIn(title, DOWN),
get_coloring_animation(np.random.randint(0, 3, 8)),
)
self.wait()
for x in range(4):
self.play(get_coloring_animation(np.random.randint(0, 3, 8)))
self.wait()
# Some specific color examples
S0 = TexMobject("\\text{Key} = 0")
S0.to_edge(LEFT)
S0.shift(UP)
S0.fix_in_frame()
self.play(
FadeIn(S0, DOWN),
get_coloring_animation([0] * 8)
)
self.wait(5)
bit_sum = TexMobject("\\text{Key} = \\,&c_0 + c_1")
bit_sum.scale(0.8)
bit_sum.to_edge(LEFT)
bit_sum.shift(UP)
bit_sum.fix_in_frame()
self.play(
FadeIn(bit_sum, DOWN),
FadeOut(S0, UP),
get_coloring_animation([sum(coords[:2]) for coords in vert_coords])
)
self.wait(6)
bit_sum_with_coefs = TexMobject(
"\\text{Key} = \\,&(0\\cdot c_0 + 1\\cdot c_1 + 2\\cdot c_2) \\\\ &\\quad \\mod 3"
)
bit_sum_with_coefs.scale(0.8)
bit_sum_with_coefs.move_to(bit_sum, LEFT)
bit_sum_with_coefs.fix_in_frame()
self.play(
FadeIn(bit_sum_with_coefs, DOWN),
FadeOut(bit_sum, UP),
get_coloring_animation([np.dot(coords, [0, 1, 2]) % 3 for coords in vert_coords])
)
self.wait(4)
# Focus on (0, 0, 0)
self.play(
FlipCoin(coins),
coord_labels[1:].set_opacity, 0.2,
coord_labels.color_labels[1:].set_opacity, 0.2,
spheres[1:].set_opacity, 0.2,
)
self.wait(2)
lines = VGroup()
for n in [1, 2, 4]:
line = Line(verts[0], verts[n], buff=0.1)
line.set_stroke(YELLOW, 3)
coin = coins[int(np.log2(n))]
self.play(
ShowCreationThenDestruction(line),
spheres[n].set_opacity, 1,
coord_labels[n].set_opacity, 1,
coord_labels.color_labels[n].set_opacity, 1,
FlipCoin(coin)
)
line.reverse_points()
self.add(line, coord_labels)
self.play(
FlipCoin(coin),
ShowCreation(line)
)
lines.add(line)
self.wait(10)
# Focus on (0, 1, 0)
self.play(
FlipCoin(coins[1]),
Uncreate(lines[1]),
FadeOut(lines[::2]),
Group(
spheres[0], coord_labels[0], coord_labels.color_labels[0],
spheres[1], coord_labels[1], coord_labels.color_labels[1],
spheres[4], coord_labels[4], coord_labels.color_labels[4],
).set_opacity, 0.2,
)
self.wait(3)
lines = VGroup()
curr_n = 2
for n in [1, 2, 4]:
new_n = n ^ curr_n
line = Line(verts[curr_n], verts[new_n], buff=0.1)
line.set_stroke(YELLOW, 3)
coin = coins[int(np.log2(n))]
self.play(
ShowCreationThenDestruction(line),
spheres[new_n].set_opacity, 1,
coord_labels[new_n].set_opacity, 1,
coord_labels.color_labels[new_n].set_opacity, 1,
FlipCoin(coin)
)
line.reverse_points()
self.add(line, coord_labels)
self.play(
FlipCoin(coin),
ShowCreation(line)
)
lines.add(line)
self.wait(10)
self.play(
LaggedStartMap(Uncreate, lines),
spheres.set_opacity, 1,
coord_labels.set_opacity, 1,
coord_labels.color_labels.set_opacity, 1,
FadeOut(bit_sum_with_coefs),
)
self.wait()
for x in range(8):
self.play(get_coloring_animation(np.random.randint(0, 3, 8)))
self.wait()
# Count all strategies
count = TextMobject("$3^8$ total strategies")
count64 = TextMobject("$64^{(2^{64})}$ total strategies")
for words in count, count64:
words.to_edge(LEFT, buff=MED_SMALL_BUFF)
words.shift(UP)
words.fix_in_frame()
full_board = Chessboard()
full_board.set_height(6)
full_board.next_to(axes.c2p(0, 0, 0), np.array([-1, 1, 1]), buff=0)
full_board.shift(SMALL_BUFF * UP + LEFT)
full_coins = CoinsOnBoard(full_board, coin_config={"numeric_labels": True})
full_coins.flip_by_message("64^ 2^64")
self.play(FadeIn(count, DOWN))
self.wait(4)
self.remove(board, coins)
frame.clear_updaters()
frame.generate_target()
frame.target.set_rotation(0, 45 * DEGREES)
frame.target.shift(2 * UP)
self.play(
count.shift, UP,
count.set_opacity, 0.5,
ShowIncreasingSubsets(full_board, run_time=4),
ShowIncreasingSubsets(full_coins, run_time=4),
FadeIn(count64, DOWN),
MoveToTarget(frame, run_time=5)
)
messages = [
"Or, use ",
"Burnside",
"to count",
"modulo ",
"symmetry",
]
for message in messages:
bools = string_to_bools(message)
to_flip = Group()
for head, coin in zip(bools, full_coins):
if head ^ coin.is_heads():
to_flip.add(coin)
self.play(
LaggedStartMap(FlipCoin, to_flip, run_time=1)
)
self.wait(0.5)
frame.generate_target()
frame.target.shift(2 * DOWN)
frame.target.set_rotation(-15 * DEGREES, 70 * DEGREES)
self.play(
MoveToTarget(frame, run_time=3),
LaggedStartMap(FadeOut, full_board),
LaggedStartMap(FadeOut, full_coins),
FadeOut(count),
FadeOut(count64),
)
frame.add_updater(lambda m, dt: m.increment_theta(0.01 * dt))
self.wait(30)
class CubeSupplement(ThreeDScene):
CONFIG = {
"try_different_strategies": False,
}
def construct(self):
# Map 8 states to square choices
boards = Group(*[Chessboard(shape=(1, 3)) for x in range(8)])
boards.arrange(DOWN, buff=0.5 * boards[0].get_height())
boards.set_height(7)
boards.to_edge(LEFT)
coin_sets = Group(*[
CoinsOnBoard(board, coin_config={"numeric_labels": True})
for board in boards
])
vert_coords = [[n // 4, (n // 2) % 2, n % 2] for n in range(7, -1, -1)]
for coords, coins in zip(vert_coords, coin_sets):
coins.flip_by_bools(coords)
def get_choice_boards(values, boards):
choices = VGroup()
for value, board in zip(values, boards):
choice = VGroup(*[Square() for x in range(3)])
choice.arrange(RIGHT, buff=0)
choice.match_height(board)
choice.next_to(board, RIGHT, buff=1.25)
choice.set_fill(GREY_D, 1)
choice.set_stroke(WHITE, 1)
choice[value].set_fill(TEAL)
choices.add(choice)
return choices
colors = [RED, GREEN, BLUE_D]
color_words = ["Red", "Green", "Blue"]
s_values = [sum([n * v for n, v in enumerate(cs)]) % 3 for cs in vert_coords]
choice_boards = get_choice_boards(s_values, boards)
c_labels = VGroup()
s_arrows = VGroup()
for value, board, choice_board in zip(s_values, boards, choice_boards):
arrow = Vector(RIGHT)
arrow.next_to(board, RIGHT, SMALL_BUFF)
c_label = TextMobject(color_words[value], color=colors[value])
c_label.next_to(choice_board, RIGHT)
c_labels.add(c_label)
s_arrows.add(arrow)
choice_board.generate_target()
choice_board.target[value].set_fill(colors[value])
self.play(
LaggedStartMap(FadeIn, boards, lag_ratio=0.25),
LaggedStartMap(FadeIn, coin_sets, lag_ratio=0.25),
run_time=3
)
self.play(
LaggedStartMap(GrowArrow, s_arrows, lag_ratio=0.25),
LaggedStartMap(FadeIn, choice_boards, lambda m: (m, LEFT), lag_ratio=0.25),
)
self.wait()
# Fork
if self.try_different_strategies:
for x in range(5):
values = list(np.arange(8) % 3)
random.shuffle(values)
new_cboards = get_choice_boards(values, boards)
self.play(
LaggedStartMap(FadeOut, choice_boards, lambda m: (m, 0.25 * UP)),
LaggedStartMap(FadeIn, new_cboards, lambda m: (m, 0.25 * DOWN)),
)
choice_boards = new_cboards
self.wait(2)
else:
# Associate choices with colors
self.play(
LaggedStartMap(MoveToTarget, choice_boards),
LaggedStartMap(FadeIn, c_labels),
)
self.wait()
class TryDifferentCaseThreeStrategies(CubeSupplement):
CONFIG = {
"try_different_strategies": True,
}
class CubeEdgeDescription(Scene):
CONFIG = {
"camera_config": {"background_color": GREY_E}
}
def construct(self):
bits = VGroup(*[
VGroup(*[
Integer(int(b))
for b in string_to_bools(char)
]).arrange(RIGHT, buff=SMALL_BUFF)
for char in "hi"
])
bits.arrange(DOWN, buff=LARGE_BUFF)
arrow = Arrow(
bits[0][7].get_bottom(),
bits[1][7].get_top(),
buff=SMALL_BUFF,
tip_config={"length": 0.15, "width": 0.15}
)
arrow.set_color(BLUE)
words = TextMobject("Bit flip")
words.set_color(BLUE)
words.next_to(arrow, LEFT)
bf_group = VGroup(bits, arrow, words)
parens = TexMobject("()")[0]
parens.scale(2)
parens.match_height(bf_group, stretch=True)
parens[0].next_to(bf_group, LEFT, SMALL_BUFF)
parens[1].next_to(bf_group, RIGHT, SMALL_BUFF)
bf_group.add(parens)
bf_group.to_edge(UP)
cube_words = TextMobject("Edge of an\\\\n-dimensional cube")
top_group = VGroup(
bf_group,
Vector(RIGHT),
cube_words
)
top_group.arrange(RIGHT)
top_group.to_edge(UP)
self.add(bf_group)
bits.unlock_triangulation()
self.play(
TransformFromCopy(*bits),
GrowArrow(arrow),
FadeIn(words, 0.25 * UP)
)
self.wait()
self.play(
GrowArrow(top_group[1]),
FadeIn(cube_words, LEFT)
)
self.wait()
class EdgeColoringExample(Scene):
def construct(self):
words = VGroup(
TextMobject(
"Color edges\\\\red or blue",
tex_to_color_map={"red": RED, "blue": BLUE}
),
TextMobject("Prove there is a\\\\monochromatic triangle", alignment=""),
)
words.arrange(DOWN, buff=LARGE_BUFF, aligned_edge=LEFT)
words.to_edge(RIGHT)
words.to_edge(UP, buff=LARGE_BUFF)
def get_graph(words=words):
points = compass_directions(6)
points *= 3
verts = VGroup(*[Dot(p, radius=0.1) for p in points])
verts.set_fill(GREY_B, 1)
edges = VGroup(*[
Line(p1, p2, color=random.choice([RED, BLUE]))
for p1, p2 in it.combinations(points, 2)
])
graph = VGroup(verts, edges)
graph.set_height(6)
graph.next_to(words, LEFT, LARGE_BUFF)
graph.set_y(0)
graph.set_stroke(background=True)
return graph
graph = get_graph()
self.add(words)
self.add(graph)
self.wait()
for x in range(2):
new_graph = get_graph()
self.play(
ShowCreation(
new_graph, lag_ratio=0.1,
run_time=3,
),
ApplyMethod(
graph[1].set_stroke, None, 0,
run_time=2,
)
)
graph = new_graph
self.wait(4)
class GrahamsConstantAlt(Scene):
def construct(self):
# lhs = TexMobject("g_{64}", "=")
# lhs[0][1:].scale(0.7, about_edge=DL)
lhs = TexMobject("")
lhs.scale(2)
rhs = VGroup()
for ndots in [1, 3, 6, 7, 9, 12]:
row = VGroup(*[
TexMobject("2"),
TexMobject("\\uparrow\\uparrow"),
VGroup(*[
TexMobject("\\cdot") for x in range(ndots)
]).arrange(RIGHT, buff=0.2),
TexMobject("\\uparrow\\uparrow"),
TexMobject("3"),
])
row.arrange(RIGHT, buff=MED_SMALL_BUFF)
if ndots == 1:
rc = row.get_center()
row[:2].move_to(rc, RIGHT)
row[2].set_opacity(0)
row[3:].move_to(rc, LEFT)
row.add(Brace(row[1:-1], DOWN, buff=SMALL_BUFF))
rhs.add(row)
rhs.replace_submobject(0, Integer(12))
# rhs[0][-1].set_opacity(0)
rhs.replace_submobject(3, TexMobject("\\vdots"))
rhs.arrange(UP)
rhs.next_to(lhs, RIGHT)
rbrace = Brace(rhs[1:], RIGHT)
rbrace_tex = rbrace.get_text("7 times")
equation = VGroup(lhs, rhs, rbrace, rbrace_tex)
equation.center().to_edge(LEFT, buff=LARGE_BUFF)
self.add(lhs, rhs[0])
self.play(TransformFromCopy(rhs[0], rhs[1]),)
self.play(TransformFromCopy(rhs[1], rhs[2]))
self.play(
Write(rhs[3]),
TransformFromCopy(rhs[2], rhs[4]),
)
self.play(
TransformFromCopy(rhs[4], rhs[5]),
GrowFromCenter(rbrace),
Write(rbrace_tex)
)
self.wait()
class ThinkAboutNewTrick(PiCreatureScene, ThreeDScene):
def construct(self):
randy = self.pi_creature
board = Chessboard(shape=(1, 3))
board.set_height(1.5)
coins = CoinsOnBoard(board)
coins.flip_at_random()
self.add(board, coins)
self.play(randy.change, "confused", board)
for x in range(4):
self.play(FlipCoin(random.choice(coins)))
if x == 1:
self.play(randy.change, "maybe")
else:
self.wait()
class AttemptAColoring(ThreeDScene):
def construct(self):
# Setup cube
short_vert_height = 0.3
tall_vert_height = 0.4
vert_coords = np.array(list(map(int_to_bit_coords, range(8))))
vert_coords = vert_coords - 0.5
vert_coords = vert_coords * 4
vert_coords[:, 2] *= 1.25 # Stretch in the z
cube = Group()
cube.verts = SGroup()
cube.edges = VGroup()
cube.add(cube.verts, cube.edges)
for n, coords in enumerate(vert_coords):
vert = Sphere(resolution=(21, 21))
vert.set_height(short_vert_height)
vert.rotate(90 * DEGREES, RIGHT)
vert.move_to(coords)
cube.verts.add(vert)
vert.edges = VGroup()
for m, coords2 in enumerate(vert_coords):
if sum(int_to_bit_coords(n ^ m)) == 1:
edge = Line(coords, coords2)
cube.edges.add(edge)
vert.edges.add(edge)
vert.edges.apply_depth_test()
cube.edges.set_color(GREY)
cube.edges.apply_depth_test()
cube.rotate(30 * DEGREES, DOWN)
cube.to_edge(RIGHT)
cube.set_height(4)
self.play(
ShowCreation(cube.edges, lag_ratio=0.1),
LaggedStartMap(FadeInFromLarge, cube.verts, lambda m: (m, 0.2)),
run_time=2,
)
# Setup cube color
def get_colored_vertices(values, verts=cube.verts):
color_choices = [RED, GREEN, BLUE_D]
color_label_choices = ["R", "G", "B"]
vert_targets = SGroup()
labels = VGroup()
for n, vert in zip(values, verts):
color = color_choices[n]
v_target = vert.copy()
if n == -1:
v_target.set_height(short_vert_height)
v_target.set_color(GREY)
label = VectorizedPoint()
else:
v_target.set_color(color)
v_target.set_height(tall_vert_height)
label = TexMobject(color_label_choices[n])
label.set_color(color)
label.set_stroke(BLACK, 3, background=True)
label.next_to(vert, UR, buff=0)
vert_targets.add(v_target)
labels.add(label)
return vert_targets, labels
new_verts, color_labels = get_colored_vertices(np.arange(0, 8) % 3)
for vert, label in zip(cube.verts, color_labels):
vert.label = label
self.play(
Transform(cube.verts, new_verts),
Write(color_labels),
run_time=2,
)
self.wait()
def get_color_change_animations(values, verts=cube.verts, labels=color_labels, gcv=get_colored_vertices):
new_verts, new_labels = gcv(values)
old_labels = labels.copy()
labels.become(new_labels)
return [
Transform(verts, new_verts),
LaggedStartMap(FadeOut, old_labels, lambda m: (m, 0.5 * UP), lag_ratio=0.03),
LaggedStartMap(FadeIn, labels, lambda m: (m, 0.5 * DOWN), lag_ratio=0.03),
]
# Prepare a few colorings
mod3_strategy = [
np.dot(int_to_bit_coords(n), [0, 1, 2]) % 3
for n in range(8)
]
sum_bits = [sum(int_to_bit_coords(n)) % 3 for n in range(8)]
self.play(*get_color_change_animations(sum_bits))
self.wait()
self.play(*get_color_change_animations(mod3_strategy))
self.wait()
# Pull out vertices with their neighbors
# first just one, then all of them.
trees = Group()
tree_targets = Group()
for n, vert in enumerate(cube.verts):
tree = Group()
tree.root = vert.copy()
tree.root.origin = tree.root.get_center()
tree.edges = VGroup()
tree.leafs = Group()
tree.labels = Group()
for mask in [1, 2, 4]:
leaf = cube.verts[n ^ mask]
leaf.origin = leaf.get_center()
label = leaf.label.copy()
label.original = leaf.label
tree.edges.add(Line(vert.get_center(), leaf.get_center()))
tree.leafs.add(leaf.copy())
tree.labels.add(label)
tree.edges.apply_depth_test()
tree.edges.match_style(vert.edges)
tree.edges.save_state()
tree.add(tree.root, tree.edges, tree.leafs, tree.labels)
trees.add(tree)
tree.generate_target(use_deepcopy=True)
for edge, leaf, label, y in zip(tree.target.edges, tree.target.leafs, tree.target.labels, [0.4, 0, -0.4]):
start = vert.get_center()
end = start + RIGHT + y * UP
edge.set_points_as_corners([start, end])
leaf.move_to(edge.get_end())
label.next_to(leaf, RIGHT, buff=SMALL_BUFF)
label.scale(0.7)
tree_targets.add(tree.target)
tree_targets.arrange_in_grid(4, 2, buff=LARGE_BUFF)
tree_targets[1::2].shift(0.5 * RIGHT)
tree_targets.set_height(6)
tree_targets.center()
tree_targets.to_corner(DL)
self.play(
MoveToTarget(trees[0]),
run_time=3,
)
self.wait()
self.play(
LaggedStartMap(
MoveToTarget, trees[1:],
lag_ratio=0.3,
),
run_time=6,
)
self.add(trees)
self.wait()
# Show what we want
want_rect = SurroundingRectangle(trees, buff=MED_SMALL_BUFF)
want_rect.set_stroke(WHITE, 1)
want_label = TextMobject("What we want")
want_label.next_to(want_rect, UP)
trees.save_state()
anims = []
for tree in trees:
anims.append(ApplyMethod(tree.root.set_color, GREY))
colors = [RED, GREEN, BLUE_D]
letters = ["R", "G", "B"]
for color, letter, leaf, label in zip(colors, letters, tree.leafs, tree.labels):
new_label = TextMobject(letter)
new_label.set_fill(color)
new_label.replace(label, dim_to_match=1)
old_label = label.copy()
label.become(new_label)
anims += [
FadeIn(label, 0.1 * LEFT),
FadeOut(old_label, 0.1 * RIGHT),
ApplyMethod(leaf.set_color, color),
]
cube.verts.generate_target()
cube.verts.save_state()
cube.verts.target.set_color(GREY)
for vert in cube.verts.target:
vert.scale(0.75)
self.play(
ShowCreation(want_rect),
Write(want_label),
LaggedStart(*anims, lag_ratio=0.001, run_time=3),
FadeOut(color_labels),
MoveToTarget(cube.verts),
)
self.add(trees)
self.wait()
# Try to fit these back onto the cube
# First attempt
def restore_tree(tree, **kwargs):
anims = []
for mob in [tree.root, *tree.leafs]:
anims.append(ApplyMethod(mob.move_to, mob.origin))
for label in tree.labels:
label.generate_target()
label.target.replace(label.original, dim_to_match=1)
anims.append(MoveToTarget(label))
anims.append(Restore(tree.edges))
return AnimationGroup(*anims, **kwargs)
tree_copies = trees.deepcopy()
self.play(restore_tree(tree_copies[0], run_time=2))
self.wait()
self.play(restore_tree(tree_copies[1], run_time=2))
self.wait()
frame = self.camera.frame
self.play(
UpdateFromAlphaFunc(
frame,
lambda m, a: m.move_to(0.1 * wiggle(a, 6) * RIGHT),
),
FadeOut(tree_copies[0]),
FadeOut(tree_copies[1]),
)
# Second attempt
def restore_vertex(n, verts=cube.verts, labels=color_labels):
return AnimationGroup(
Transform(verts[n], verts.saved_state[n]),
FadeIn(labels[n], DOWN)
)
for i in [0, 4, 2, 1]:
self.play(restore_vertex(i))
self.wait()
self.play(ShowCreationThenFadeAround(cube.verts[4]))
for i in [6, 5]:
self.play(restore_vertex(i))
self.wait()
q_marks = VGroup(*[TexMobject("???") for x in range(2)])
q_marks[0].next_to(cube.verts[7], UP, SMALL_BUFF)
q_marks[1].next_to(cube.verts[3], UP, SMALL_BUFF)
self.play(Write(q_marks))
self.wait()
# Mention it'll never work
nv_label = TextMobject("It'll never work!")
nv_label.set_height(0.5)
nv_label.next_to(cube, UP, buff=0.75)
cube_copy = cube.deepcopy()
self.remove(cube)
self.add(cube_copy)
new_verts, new_labels = get_colored_vertices([-1] * 8)
self.play(
Transform(cube_copy.verts, new_verts),
FadeOut(q_marks),
FadeOut(color_labels[:3]),
FadeOut(color_labels[4:7]),
)
self.add(cube_copy)
self.play(FadeIn(nv_label, DOWN))
for vert in cube_copy.verts:
vert.generate_target()
vert.target.scale(0.01)
vert.target.set_opacity(0)
self.play(
LaggedStartMap(Uncreate, cube_copy.edges),
LaggedStartMap(MoveToTarget, cube_copy.verts),
)
# Highlight symmetry
rects = VGroup()
for tree in trees:
t_rect = SurroundingRectangle(
Group(tree.leafs, tree.labels),
buff=SMALL_BUFF
)
t_rect.set_stroke(YELLOW, 2)
rects.add(t_rect)
self.play(LaggedStartMap(ShowCreationThenFadeOut, rects, lag_ratio=0.025, run_time=3))
self.wait()
# Show implication
implies = TexMobject("\\Rightarrow")
implies.set_height(0.7)
implies.next_to(want_rect, RIGHT)
number_labels = VGroup(*[
TextMobject("Number of ", f"{color} vertices")
for color in ["red", "green", "blue"]
])
for color, label in zip(colors, number_labels):
label[1].set_color(color)
number_labels.set_height(0.5)
number_labels.arrange(DOWN, buff=1.5, aligned_edge=LEFT)
number_labels.next_to(implies, RIGHT, MED_LARGE_BUFF)
vert_eqs = VGroup(*[TexMobject("=") for x in range(2)])
vert_eqs.scale(1.5)
vert_eqs.rotate(90 * DEGREES)
vert_eqs[0].move_to(number_labels[0:2])
vert_eqs[1].move_to(number_labels[1:3])
rhss = VGroup()
for label in number_labels:
rhs = TexMobject("= \\frac{8}{3}")
rhs.scale(1.25)
rhs.next_to(label, RIGHT)
rhss.add(rhs)
self.play(
Write(implies),
FadeOut(nv_label),
)
self.play(
GrowFromCenter(vert_eqs),
FadeIn(number_labels[0], DOWN),
FadeIn(number_labels[1]),
FadeIn(number_labels[2], UP),
)
self.wait()
self.play(Write(rhss))
self.wait(2)
self.play(
LaggedStartMap(
FadeOut, VGroup(*number_labels, *vert_eqs, *rhss, *implies),
),
ShowCreation(cube.edges, lag_ratio=0.1),
LaggedStartMap(FadeInFromLarge, cube.verts, lambda m: (m, 0.2)),
)
self.add(cube)
new_verts, color_labels = get_colored_vertices(mod3_strategy)
true_trees = trees.saved_state
self.play(
Transform(cube.verts, new_verts),
FadeIn(color_labels),
FadeOut(trees),
FadeOut(want_label)
)
self.play(FadeIn(true_trees))
self.wait()
# Count colors
for edge in cube.edges:
edge.insert_n_curves(10)
red_total = Integer(height=0.6)
red_total.next_to(want_rect, UP)
red_total.set_color(RED)
self.play(FadeIn(red_total))
all_label_rects = VGroup()
for n in range(8):
tree = true_trees[n]
vert = cube.verts[n]
neighbor_highlights = VGroup()
new_edges = VGroup()
label_rects = VGroup()
for mask, label in zip([1, 2, 4], tree.labels):
neighbor = cube.verts[n ^ mask]
edge = Line(vert, neighbor, buff=0)
edge.set_stroke(YELLOW, 5)
edge.insert_n_curves(10)
new_edges.add(edge)
if neighbor.get_color() == Color(RED):
circ = Circle()
circ.set_stroke(YELLOW, 3)
circ.replace(neighbor)
neighbor_highlights.add(circ)
rect = SurroundingRectangle(label, buff=0.025)
rect.set_stroke(YELLOW, 2)
label_rects.add(rect)
new_edges.apply_depth_test()
new_edges.shift(0.01 * OUT)
new_tree_edges = tree.edges.copy()
new_tree_edges.set_stroke(YELLOW, 3)
new_tree_edges.shift(0.01 * OUT)
self.play(
*map(ShowCreation, [*new_edges, *new_tree_edges]),
)
for highlight, rect in zip(neighbor_highlights, label_rects):
self.play(
FadeInFromLarge(highlight, 1.2),
FadeInFromLarge(rect, 1.2),
run_time=0.25
)
red_total.increment_value()
self.wait(0.25)
self.play(
FadeOut(neighbor_highlights),
FadeOut(new_edges),
FadeOut(new_tree_edges),
)
all_label_rects.add(*label_rects)
self.wait()
# Show count to 8
new_verts = get_colored_vertices([-1] * 8)[0]
self.play(
FadeOut(true_trees),
FadeOut(all_label_rects),
FadeOut(red_total),
FadeOut(color_labels),
Transform(cube.verts, new_verts),
)
self.play(FadeIn(trees))
label_rects = VGroup()
for tree in trees:
rect = SurroundingRectangle(tree.labels[0], buff=0.025)
rect.match_style(all_label_rects[0])
label_rects.add(rect)
self.play(
ShowIncreasingSubsets(label_rects, rate_func=linear),
UpdateFromFunc(
red_total, lambda m, lr=label_rects: m.set_value(len(lr))
)
)
self.wait()
# Show red corners
r_verts = SGroup(cube.verts[3], cube.verts[4]).copy()
r_labels = VGroup()
r_edge_groups = VGroup()
for r_vert in r_verts:
r_label = TexMobject("R")
r_label.set_color(RED)
r_label.next_to(r_vert, UR, buff=0)
r_labels.add(r_label)
r_vert.set_height(tall_vert_height)
r_vert.set_color(RED)
edges = VGroup()
for edge in r_vert.edges:
to_r_edge = edge.copy()
to_r_edge.reverse_points()
to_r_edge.set_stroke(YELLOW, 3)
to_r_edge.shift(0.01 * OUT)
edges.add(to_r_edge)
edges.apply_depth_test()
r_edge_groups.add(edges)
self.play(
LaggedStartMap(FadeInFromLarge, r_verts),
LaggedStartMap(FadeInFromLarge, r_labels),
run_time=1,
)
self.wait()
for edges in r_edge_groups:
self.play(ShowCreationThenDestruction(edges, lag_ratio=0.1))
self.wait()
rhs = TexMobject("=", "3", "\\, (\\text{\\# Red corners})")
rhs[2].set_color(RED)
rhs.match_height(red_total)
rhs[:2].match_height(red_total, about_edge=RIGHT)
rhs.next_to(red_total, RIGHT)
self.play(Write(rhs))
self.wait()
three = rhs[1]
three.generate_target()
three.target.move_to(red_total, RIGHT)
over = TexMobject("/")
over.match_height(three)
over.next_to(three.target, LEFT, MED_SMALL_BUFF)
self.play(
MoveToTarget(three, path_arc=90 * DEGREES),
red_total.next_to, over, LEFT, MED_SMALL_BUFF,
FadeIn(over, UR),
rhs[2].move_to, three, LEFT,
)
self.wait()
np_label = TextMobject("Not possible!")
np_label.set_height(0.6)
np_label.next_to(rhs, RIGHT, LARGE_BUFF)
self.play(Write(np_label))
self.wait()
class TryTheProofYourself(TeacherStudentsScene):
def construct(self):
self.teacher_says(
"Can you predict\\\\the proof?",
target_mode="hooray",
bubble_kwargs={
"height": 3,
"width": 3,
},
)
self.teacher.bubble.set_fill(opacity=0)
self.change_student_modes(
"pondering", "thinking", "confused",
look_at_arg=self.screen,
)
self.wait(3)
self.change_student_modes("thinking", "pondering", "erm", look_at_arg=self.screen)
self.wait(4)
self.change_student_modes("tease", "pondering", "thinking", look_at_arg=self.screen)
self.wait(5)
class HighDimensionalCount(ThreeDScene):
def construct(self):
# Definitions
N = 6
colors = [RED, GREEN, BLUE_D, YELLOW, PINK, TEAL]
coords = np.array([0, 1, 1, 1, 0, 0])
# Add chess board
board = Chessboard(shape=(2, 3))
board.set_height(2)
board.to_corner(UL)
grid = NumberPlane(
x_range=(0, 3), y_range=(0, 2),
faded_line_ratio=0
)
grid.match_height(board)
grid.match_width(board, stretch=True)
grid.next_to(board, OUT, 1e-8)
grid.set_gloss(0.5)
coins = CoinsOnBoard(board, coin_config={"numeric_labels": True})
coins.flip_by_bools(coords)
coin_labels = VGroup()
for i, coin in zip(coords, coins):
coin_labels.add(coin.labels[1 - i])
self.play(
ShowCreationThenFadeOut(grid, lag_ratio=0.1),
FadeIn(board),
FadeIn(coins, lag_ratio=0.1),
run_time=2
)
# Setup corners
def get_vert(height=0.4, color=RED):
return get_vertex_sphere(height, color)
def get_vert_label(coords):
args = ["("]
for coord in coords:
args.append(str(coord))
args.append(",")
args[-1] = ")"
return TexMobject(*args)
def get_board_with_highlights(n, height=1, N=N, colors=colors):
board = VGroup(*[Square() for x in range(N)])
board.arrange_in_grid(2, 3, buff=0)
board.set_fill(GREY_E, 1)
board.set_stroke(WHITE, 1)
board.set_height(height)
board[n].set_fill(colors[n])
return board
vert = get_vert()
vert_label = get_vert_label(coords)
vert_board = get_board_with_highlights(0)
vert_label.next_to(vert, LEFT)
vert_board.next_to(vert_label, DOWN, MED_LARGE_BUFF)
neighbors = SGroup()
for color in colors:
neighbors.add(get_vert(color=color))
neighbors.arrange(DOWN, buff=0.75)
neighbors.next_to(vert, RIGHT, buff=2)
neighbor_labels = VGroup()
edges = VGroup()
neighbor_boards = VGroup()
for n, neighbor in enumerate(neighbors):
edge = Line(
vert.get_center(),
neighbor.get_center(),
buff=vert.get_height() / 2,
)
new_coords = list(coords)
new_coords[n] ^= 1
label = get_vert_label(new_coords)
label.next_to(neighbor, RIGHT)
label.add(SurroundingRectangle(label[2 * n + 1], buff=0.05))
n_board = get_board_with_highlights(n, height=0.7)
n_board.next_to(label, RIGHT)
neighbor_boards.add(n_board)
edges.add(edge)
neighbor_labels.add(label)
vertex_group = Group(
vert_board, vert_label, vert,
edges,
neighbors, neighbor_labels, neighbor_boards
)
vertex_group.to_corner(DL)
# Show coords with states
cl_mover = coin_labels.copy()
cl_mover.generate_target()
for m1, m2 in zip(cl_mover.target, vert_label[1::2]):
m1.replace(m2)
self.play(
MoveToTarget(cl_mover),
)
self.play(
FadeIn(vert_label),
FadeOut(cl_mover)
)
self.play(FadeIn(vert_board))
self.wait()
self.play(
ShowIncreasingSubsets(neighbor_labels),
ShowCreation(edges),
)
self.wait()
self.play(LaggedStartMap(
TransformFromCopy, neighbor_boards,
lambda m, b=vert_board: (b, m)
))
self.wait()
# Show one vertex
self.play(FadeInFromLarge(vert))
self.play(LaggedStartMap(
TransformFromCopy, neighbors,
lambda m, v=vert: (v, m)
))
self.wait()
# Isolate vertex
edges.apply_depth_test()
tree = Group(vert, edges, neighbors)
tree.generate_target()
tree.target[0].scale(0.5)
tree.target[2].scale(0.5)
tree.target[2].arrange(DOWN, buff=0)
tree.target[2].next_to(vert, RIGHT, MED_LARGE_BUFF)
for edge, nv in zip(tree.target[1], tree.target[2]):
new_edge = Line(
vert.get_center(),
nv.get_center(),
)
edge.become(new_edge)
edge.set_stroke(WHITE, 2)
tree.target.rotate(-90 * DEGREES)
tree.target.center()
short_label = vert_label[1::2]
short_label.generate_target()
short_label.target.arrange(RIGHT, buff=SMALL_BUFF)
short_label.target.match_width(tree.target)
short_label.target.next_to(tree.target, UP)
short_label.target.set_fill(GREY_A)
self.play(
MoveToTarget(tree),
MoveToTarget(short_label),
LaggedStartMap(FadeOut, Group(
vert_label[0::2],
vert_board,
*neighbor_labels,
*neighbor_boards,
*board,
*coins,
)),
run_time=2,
)
tree.add(short_label)
# Show all vertices
def get_bit_string(n, template=short_label):
bits = VGroup(*map(Integer, int_to_bit_coords(n, min_dim=6)))
bits.arrange(RIGHT, buff=0.075)
bits.match_height(template)
bits.set_color(GREY_A)
return bits
new_trees = Group()
for n in [0, 1, 62, 63]:
new_tree = tree.copy()
bits = get_bit_string(n)
bits.move_to(new_tree[3])
new_tree.replace_submobject(3, bits)
new_trees.add(new_tree)
for new_tree, color in zip(new_trees, [YELLOW, GREEN, RED, BLUE_D]):
new_tree[0].set_color(color)
new_trees.arrange(RIGHT, buff=MED_LARGE_BUFF)
new_trees.move_to(tree)
new_trees[:2].to_edge(LEFT)
new_trees[2:].to_edge(RIGHT)
dots = VGroup(*[TexMobject("\\dots") for x in range(2)])
dots.scale(2)
dots[0].move_to(Group(new_trees[1], tree))
dots[1].move_to(Group(new_trees[2], tree))
top_brace = Brace(new_trees, UP, buff=MED_LARGE_BUFF)
total_label = top_brace.get_text("$2^n$ total vertices", buff=MED_LARGE_BUFF)
low_brace = Brace(tree, DOWN)
neighbors_label = low_brace.get_text("n neighbors")
self.play(
GrowFromCenter(low_brace),
Write(neighbors_label, run_time=1)
)
self.wait()
self.play(
GrowFromCenter(dots),
GrowFromCenter(top_brace),
LaggedStartMap(
TransformFromCopy, new_trees,
lambda m, t=tree: (t, m)
),
Write(total_label, run_time=1),
run_time=2,
)
self.wait()
# Count red neighbors
middle_tree = tree
frame = self.camera.frame
self.play(frame.move_to, UP)
count = Integer(1)
count.set_color(RED)
count.scale(1.5)
count.next_to(total_label, UP, LARGE_BUFF, aligned_edge=LEFT)
two_to_n_label = TexMobject("2^n")
two_to_n_label.scale(1.5)
two_to_n_label.set_color(RED)
two_to_n_label.move_to(count, LEFT)
n_arrows = VGroup()
for tree in [*new_trees[:2], middle_tree, *new_trees[2:]]:
arrow = Vector(
[-1, -2, 0],
tip_config={"width": 0.2, "length": 0.2}
)
arrow.match_height(tree[1])
arrow.next_to(tree[2][0], UR, buff=0)
arrow.set_color(RED)
n_arrows.add(arrow)
self.add(n_arrows[0], count)
self.wait()
self.add(n_arrows[1])
count.increment_value()
self.wait()
self.play(
ChangeDecimalToValue(count, 63, rate_func=rush_into),
LaggedStartMap(FadeIn, n_arrows[2:], lag_ratio=0.5),
run_time=3
)
self.remove(count)
self.add(two_to_n_label)
self.wait()
rhs = TexMobject("=", "n", "\\cdot", "(\\text{\\# Red vertices})")
rhs.scale(1.5)
rhs.next_to(two_to_n_label, RIGHT)
rhs.shift(0.05 * DOWN)
rhs.set_color_by_tex("Red", RED)
highlighted_edges = VGroup(*middle_tree[1], new_trees[2][1]).copy()
highlighted_edges.set_stroke(YELLOW, 3)
highlighted_edges.shift(0.01 * OUT)
edge_anim = ShowCreationThenFadeOut(
highlighted_edges, lag_ratio=0.3
)
self.play(edge_anim)
self.play(Write(rhs), run_time=1)
self.play(edge_anim)
self.wait(2)
# Conclusion
pairs = VGroup(VGroup(TexMobject("n"), TexMobject("2^n")))
pairs.set_color(YELLOW)
for n in range(1, 10):
pairs.add(VGroup(Integer(n), Integer(2**n)))
for pair in pairs:
pair.arrange(RIGHT, buff=0.75, aligned_edge=DOWN)
line = Line(LEFT, RIGHT)
line.set_stroke(WHITE, 1)
line.set_width(2)
line.next_to(pair, DOWN, aligned_edge=LEFT)
line.shift(SMALL_BUFF * LEFT)
pair.add(line)
pairs.add(pair)
pairs.arrange(DOWN, aligned_edge=LEFT, buff=0.25)
pairs.set_height(7)
pairs.to_edge(LEFT)
pairs.shift(UP)
marks = VGroup()
for n, pair in zip(it.count(1), pairs[1:]):
if sum(int_to_bit_coords(n)) == 1:
mark = Checkmark()
else:
mark = Exmark()
mark.move_to(pair[1], LEFT)
mark.shift(RIGHT)
marks.add(mark)
v_line = Line(UP, DOWN)
v_line.set_height(7)
v_line.set_stroke(WHITE, 1)
v_line.set_x((pairs[0][0].get_right() + pairs[0][1].get_left())[0] / 2)
v_line.match_y(pairs)
pairs.add(v_line)
new_trees.generate_target()
new_trees.target[:2].move_to(middle_tree, RIGHT)
shift_vect = new_trees.target[0].get_center() - new_trees[0].get_center()
self.play(
MoveToTarget(new_trees),
top_brace.match_width, new_trees.target, {"about_edge": RIGHT},
total_label.shift, shift_vect * 0.5,
n_arrows[:2].shift, shift_vect,
FadeOut(middle_tree, RIGHT),
FadeOut(n_arrows[2], RIGHT),
FadeOut(dots[0], 2 * RIGHT),
Write(pairs)
)
self.play(LaggedStartMap(
FadeIn, marks,
lambda m: (m, 0.2 * LEFT),
lag_ratio=0.4,
run_time=5,
))
self.wait()
class SimpleRect(Scene):
def construct(self):
rect = SurroundingRectangle(
VGroup(Integer(4), Integer(16), Integer(0)).arrange(RIGHT, MED_LARGE_BUFF),
)
self.play(ShowCreation(rect))
self.wait(2)
self.play(FadeOut(rect))
class WhenIsItHopeless(Scene):
def construct(self):
boards = Group(
Chessboard(shape=(1, 3)),
Chessboard(shape=(2, 2)),
Chessboard(shape=(2, 3)),
Chessboard(shape=(2, 3)),
Chessboard(shape=(2, 4)),
Chessboard(shape=(2, 4)),
Chessboard(shape=(3, 3)),
Chessboard(shape=(3, 4)),
Chessboard(shape=(3, 4)),
Chessboard(shape=(3, 4)),
)
last_board = None
last_coins = None
last_words = None
for n, board in zip(it.count(3), boards):
board.scale(1 / board[0].get_height())
coins = CoinsOnBoard(board)
coins.flip_at_random()
diff = len(board) - n
if diff > 0:
board[-diff:].set_opacity(0)
coins[-diff:].set_opacity(0)
if sum(int_to_bit_coords(n)) == 1:
words = TextMobject("Maybe possible")
words.set_color(GREEN)
else:
words = TextMobject("Futile!")
words.set_color(RED)
words.scale(1.5)
words.next_to(board, UP, MED_LARGE_BUFF)
if n == 3:
self.play(
FadeIn(board),
FadeIn(coins),
FadeIn(words, DOWN),
)
else:
self.play(
ReplacementTransform(last_board, board),
ReplacementTransform(last_coins, coins),
FadeOut(last_words),
FadeIn(words, DOWN),
)
self.wait()
last_board = board
last_coins = coins
last_words = words
class FourDCubeColoringFromTrees(ThreeDScene):
def construct(self):
# Camera stuffs
frame = self.camera.frame
light = self.camera.light_source
light.move_to([-25, -20, 20])
# Setup cube
colors = [RED, GREEN, BLUE_D, YELLOW]
cube = self.get_hypercube()
for n, vert in enumerate(cube.verts):
code = boolian_linear_combo(int_to_bit_coords(n, 4))
cube.verts[n].set_color(colors[code])
# Create trees
trees = Group()
original_trees = Group()
for vert in cube.verts:
tree = Group(
vert,
vert.edges,
vert.neighbors,
).copy()
original = tree.copy()
original[0].set_color(GREY)
original[0].scale(0)
original_trees.add(original)
trees.add(tree)
for tree in trees:
tree[0].set_color(GREY)
tree[0].rotate(90 * DEGREES, LEFT)
sorted_verts = Group(*tree[2])
sorted_verts.submobjects.sort(key=lambda m: m.get_color().hex)
sorted_verts.arrange(DOWN, buff=SMALL_BUFF)
sorted_verts.next_to(tree[0], RIGHT, buff=0.75)
for edge, neighbor in zip(tree[1], tree[2]):
edge.become(Line3D(
tree[0].get_center(),
neighbor.get_center(),
resolution=edge.resolution,
))
neighbor.rotate(90 * DEGREES, LEFT)
trees.arrange_in_grid(4, 4, buff=MED_LARGE_BUFF)
for i in range(4):
trees[i::4].shift(0.5 * i * RIGHT)
trees.center()
trees.set_height(6)
trees.rotate(PI / 2, RIGHT)
trees.move_to(10 * LEFT, LEFT)
frame.set_phi(90 * DEGREES)
frame.move_to(5 * LEFT)
self.add(trees)
self.wait()
# Show transition
anims = []
for tree, original in zip(trees, original_trees):
anims.append(Transform(tree, original))
self.play(
frame.set_rotation, 20 * DEGREES, 70 * DEGREES,
frame.move_to, ORIGIN,
LaggedStart(*anims, lag_ratio=0.2),
run_time=8,
)
self.remove(trees)
self.add(cube)
frame.add_updater(lambda m, dt: m.increment_theta(2 * dt * DEGREES))
self.wait(30)
def get_hypercube(self, dim=4, width=4):
hc_points = self.get_hypercube_points(dim, width)
cube = Group()
cube.verts = SGroup()
cube.edges = SGroup()
cube.add(cube.verts, cube.edges)
for point in hc_points:
vert = get_vertex_sphere(resolution=(25, 13))
vert.rotate(PI / 2, UP)
vert.move_to(point)
cube.verts.add(vert)
vert.edges = SGroup()
vert.neighbors = SGroup()
for n in range(2**dim):
for power in range(dim):
k = n ^ (1 << power)
edge = Line3D(
hc_points[n],
hc_points[k],
width=0.05,
resolution=(31, 31)
)
cube.edges.add(edge)
cube.verts[n].edges.add(edge)
cube.verts[n].neighbors.add(cube.verts[k])
return cube
def get_hypercube_points(self, dim=4, width=4):
all_coords = [
int_to_bit_coords(n, dim).astype(float)
for n in range(2**dim)
]
vertex_holder = Mobject()
vertex_holder.set_points([
sum([c * v for c, v in zip(reversed(coords), [RIGHT, UP, OUT])])
for coords in all_coords
])
vertex_holder.center()
if dim == 4:
vertex_holder.points[8:] *= 2
vertex_holder.set_width(width)
return vertex_holder.points
class IntroduceHypercube(FourDCubeColoringFromTrees):
def construct(self):
# Camera stuffs
frame = self.camera.frame
light = self.camera.light_source
light.move_to([-25, -20, 20])
# Setup cubes
cubes = [
self.get_hypercube(dim=d)
for d in range(5)
]
def reconnect_edges(cube):
for vert in cube.verts:
for edge, neighbor in zip(vert.edges, vert.neighbors):
edge.become(Line3D(
vert.get_center(),
neighbor.get_center(),
resolution=edge.resolution
))
# Show increasing dimensions
label = VGroup(Integer(0), TexMobject("D"))
label.arrange(RIGHT, buff=SMALL_BUFF)
label.scale(1.5)
label.to_edge(UP)
label.fix_in_frame()
def get_cube_intro_anim(n, cubes=cubes, reconnect_edges=reconnect_edges, label=label):
if n == 0:
return GrowFromCenter(cubes[n])
self.remove(cubes[n - 1])
cubes[n].save_state()
for v1, v2 in zip(cubes[n].verts, it.cycle(cubes[n - 1].verts)):
v1.move_to(v2)
reconnect_edges(cubes[n])
if n == 1:
cubes[n].edges.scale(0)
return AnimationGroup(
Restore(cubes[n]),
ChangeDecimalToValue(label[0], n),
)
self.play(
FadeIn(label, DOWN),
get_cube_intro_anim(0)
)
self.wait()
for n in [1, 2]:
self.play(get_cube_intro_anim(n))
self.wait()
self.play(
get_cube_intro_anim(3),
ApplyMethod(
frame.set_rotation, -20 * DEGREES, 75 * DEGREES,
run_time=3
)
)
frame.add_updater(lambda m, dt: m.increment_theta(dt * DEGREES))
self.wait(4)
# Flatten cube
flat_cube = self.get_hypercube(3)
for n, vert in enumerate(flat_cube.verts):
point = vert.get_center()
if n < 4:
point *= 1.5
else:
point *= 0.75
point[2] = 0
vert.move_to(point)
reconnect_edges(flat_cube)
plane = NumberPlane(x_range=(-10, 10), y_range=(-10, 10), faded_line_ratio=0)
plane.set_opacity(0.25)
plane.apply_depth_test()
plane.axes.shift(0.01 * OUT)
plane.shift(0.02 * IN)
cubes[3].save_state()
self.add(cubes[3], plane)
self.play(
FadeIn(plane, run_time=2),
Transform(cubes[3], flat_cube, run_time=2),
)
self.wait(7)
self.play(
Restore(cubes[3], run_time=2),
FadeOut(plane)
)
self.play(get_cube_intro_anim(4), run_time=3)
self.wait(10)
# Highlight some neighbor groups
colors = [RED, GREEN, BLUE_D, YELLOW]
for x in range(6):
vert = random.choice(cubes[4].verts)
neighbors = vert.neighbors.copy()
neighbors.save_state()
neighbors.generate_target()
new_edges = VGroup()
for neighbor, color in zip(neighbors.target, colors):
neighbor.set_color(color)
edge = Line(
vert.get_center(),
neighbor.get_center(),
buff=vert.get_height() / 2,
)
edge.set_stroke(color, 5)
new_edges.add(edge)
self.remove(vert.neighbors)
self.play(
ShowCreation(new_edges, lag_ratio=0.2),
MoveToTarget(neighbors),
)
self.wait(1)
self.play(
FadeOut(new_edges),
Restore(neighbors),
)
self.remove(neighbors)
self.add(vert.neighbors)
# Show valid coloring
cubes[4].generate_target()
for n, vert in enumerate(cubes[4].target[0]):
code = boolian_linear_combo(int_to_bit_coords(n, 4))
vert.set_color(colors[code])
self.play(MoveToTarget(cubes[4], lag_ratio=0.2, run_time=3))
self.wait(15)
# Animations for Matt
class WantAdditionToBeSubtraction(ThreeDScene):
def construct(self):
# Add sum
coins = CoinsOnBoard(
Chessboard(shape=(1, 4)),
coin_config={"numeric_labels": True},
)
for coin in coins[0], coins[2]:
coin.flip()
coefs = VGroup(*[TexMobject(f"X_{i}") for i in range(len(coins))])
full_sum = Group()
to_fade = VGroup()
for coin, coef in zip(coins, coefs):
coin.set_height(0.7)
coef.set_height(0.5)
summand = Group(coin, TexMobject("\\cdot"), coef, TexMobject("+"))
to_fade.add(*summand[1::2])
summand.arrange(RIGHT, buff=0.2)
full_sum.add(summand)
full_sum.add(TexMobject("\\dots"))
full_sum.arrange(RIGHT, buff=0.2)
to_fade.add(full_sum[-1])
some_label = TextMobject("Some kind of ``numbers''")
some_label.next_to(full_sum, DOWN, buff=2)
arrows = VGroup(*[
Arrow(some_label.get_top(), coef.get_bottom())
for coef in coefs
])
for coin in coins:
coin.save_state()
coin.rotate(90 * DEGREES, UP)
coin.set_opacity(0)
self.play(
LaggedStartMap(Restore, coins, lag_ratio=0.3),
run_time=1
)
self.play(
FadeIn(to_fade),
LaggedStartMap(FadeInFromPoint, coefs, lambda m: (m, some_label.get_top())),
LaggedStartMap(GrowArrow, arrows),
Write(some_label, run_time=1)
)
self.wait()
self.play(FadeOut(some_label), FadeOut(arrows))
# Show a flip
add_label = TexMobject("+X_2", color=GREEN)
sub_label = TexMobject("-X_2", color=RED)
for label in add_label, sub_label:
label.next_to(coins[2], UR)
label.match_height(coefs[2])
self.play(
FlipCoin(coins[2]),
FadeIn(label, 0.5 * DOWN)
)
self.play(FadeOut(label))
# What we want
want_label = TextMobject("Want: ", "$X_i = -X_i$")
eq = TextMobject("$X_i + X_i = 0$")
want_label.next_to(full_sum, DOWN, LARGE_BUFF)
eq.next_to(want_label[1], DOWN, aligned_edge=LEFT)
self.play(FadeIn(want_label))
self.wait()
self.play(FadeIn(eq, UP))
self.wait()
class BitVectorSum(ThreeDScene):
def construct(self):
# Setup
board = Chessboard(shape=(1, 4))
board.set_height(1)
coins = CoinsOnBoard(board, coin_config={"numeric_labels": True})
coins[2].flip()
all_coords = [np.array([b0, b1]) for b0, b1 in it.product(range(2), range(2))]
bit_vectors = VGroup(*[
IntegerMatrix(coords.reshape((2, 1))).set_height(1)
for coords in all_coords
])
bit_vectors.arrange(RIGHT, buff=2)
bit_vectors.to_edge(UP)
bit_vectors.set_stroke(BLACK, 4, background=True)
arrows = VGroup(
Arrow(board[0].get_corner(UL), bit_vectors[0].get_corner(DR)),
Arrow(board[1].get_corner(UP), bit_vectors[1].get_corner(DOWN)),
Arrow(board[2].get_corner(UP), bit_vectors[2].get_corner(DOWN)),
Arrow(board[3].get_corner(UR), bit_vectors[3].get_corner(DL)),
)
# Show vectors
self.add(board)
self.add(coins)
for arrow, vector in zip(arrows, bit_vectors):
self.play(
GrowArrow(arrow),
FadeInFromPoint(vector, arrow.get_start()),
)
self.wait()
# Move coins
coin_copies = coins.copy()
cdots = VGroup()
plusses = VGroup()
for cc, vector in zip(coin_copies, bit_vectors):
dot = TexMobject("\\cdot")
dot.next_to(vector, LEFT, MED_SMALL_BUFF)
cdots.add(dot)
plus = TexMobject("+")
plus.next_to(vector, RIGHT, MED_SMALL_BUFF)
plusses.add(plus)
cc.next_to(dot, LEFT, MED_SMALL_BUFF)
plusses[-1].set_opacity(0)
for coin, cc, dot, plus in zip(coins, coin_copies, cdots, plusses):
self.play(
TransformFromCopy(coin, cc),
Write(dot),
)
self.play(Write(plus))
self.wait()
# Show sum
eq = TexMobject("=")
eq.move_to(plusses[-1])
def get_rhs(coins=coins, bit_vectors=bit_vectors, all_coords=all_coords, eq=eq):
bit_coords = sum([
(b * coords)
for coords, b in zip(all_coords, coins.get_bools())
]) % 2
n = bit_coords_to_int(bit_coords)
result = bit_vectors[n].copy()
result.next_to(eq, RIGHT)
result.n = n
return result
def get_rhs_anim(rhs, bit_vectors=bit_vectors):
bv_copies = bit_vectors.copy()
bv_copies.generate_target()
for bv in bv_copies.target:
bv.move_to(rhs)
bv.set_opacity(0)
bv_copies.target[rhs.n].set_opacity(1)
return AnimationGroup(
MoveToTarget(bv_copies, remover=True),
ShowIncreasingSubsets(Group(rhs), int_func=np.floor)
)
rhs = get_rhs()
mod2_label = TextMobject("(Add mod 2)")
mod2_label.next_to(rhs, DOWN, MED_LARGE_BUFF)
mod2_label.to_edge(RIGHT)
self.play(
Write(eq),
get_rhs_anim(rhs),
FadeIn(mod2_label),
FadeOut(board),
FadeOut(coins),
FadeOut(arrows),
)
self.wait(2)
# Show some flips
for x in range(8):
i = random.randint(0, 3)
rect = SurroundingRectangle(Group(coin_copies[i], bit_vectors[i]))
old_rhs = rhs
coins[i].flip()
rhs = get_rhs()
self.play(
ShowCreation(rect),
FlipCoin(coin_copies[i]),
FadeOut(old_rhs, RIGHT),
FadeIn(rhs, LEFT),
)
self.play(FadeOut(rect))
self.wait(2)
class ExampleSquareAsBinaryNumber(Scene):
def construct(self):
# Setup
board = Chessboard()
nums = VGroup()
bin_nums = VGroup()
for n, square in enumerate(board):
bin_num = VGroup(*[
Integer(int(b))
for b in int_to_bit_coords(n, min_dim=6)
])
bin_num.arrange(RIGHT, buff=SMALL_BUFF)
bin_num.set_width((square.get_width() * 0.8))
num = Integer(n)
num.set_height(square.get_height() * 0.4)
for mob in num, bin_num:
mob.move_to(square, OUT)
mob.set_stroke(BLACK, 4, background=True)
num.generate_target()
num.target.replace(bin_num, stretch=True)
num.target.set_opacity(0)
bin_num.save_state()
bin_num.replace(num, stretch=True)
bin_num.set_opacity(0)
nums.add(num)
bin_nums.add(bin_num)
# Transform to binary
self.add(board, nums)
self.wait()
original_nums = nums.copy()
self.play(LaggedStart(*[
AnimationGroup(MoveToTarget(num), Restore(bin_num))
for num, bin_num in zip(nums, bin_nums)
]), lag_ratio=0.1)
self.remove(nums)
nums = original_nums
self.wait(2)
self.play(
bin_nums.set_stroke, None, 0,
bin_nums.set_opacity, 0.1,
)
self.wait()
# Count
n = 43
self.play(
board[n].set_color, MAROON_E,
Animation(bin_nums[n]),
)
last = VMobject()
shown_nums = VGroup()
for k in [0, 8, 16, 24, 32, 40, 41, 42, 43]:
nums[k].set_fill(YELLOW)
self.add(nums[k])
self.play(last.set_fill, WHITE, run_time=0.5)
last = nums[k]
shown_nums.add(last)
if k == 40:
self.wait()
self.wait()
self.play(LaggedStartMap(FadeOut, shown_nums[:-1]))
self.wait()
self.play(
FadeOut(last),
bin_nums[n].set_opacity, 1,
bin_nums[n].set_fill, YELLOW
)
self.wait()
class SkipSkipYesYes(Scene):
def construct(self):
board = Chessboard()
board.next_to(ORIGIN, DOWN)
words = VGroup(
TextMobject("Skip"),
TextMobject("Skip"),
TextMobject("Yes"),
TextMobject("Yes"),
)
words.add(*words.copy())
words.set_width(board[0].get_width() * 0.8)
for word, square in zip(words, board):
word.move_to(square)
word.set_y(0, UP)
for group in words[:4], words[4:]:
self.play(ShowIncreasingSubsets(group, rate_func=double_smooth, run_time=2))
self.play(FadeOut(group))
self.wait()
class ShowCurrAndTarget(Scene):
CONFIG = {
"bit_strings": [
"011010",
"110001",
"101011",
]
}
def construct(self):
words = VGroup(
TextMobject("Current: "),
TextMobject("Need to\\\\change:"),
TextMobject("Target: "),
)
words.arrange(DOWN, buff=0.75, aligned_edge=RIGHT)
words.to_corner(UL)
def get_bit_aligned_bit_string(bit_coords):
result = VGroup(*[Integer(int(b)) for b in bit_coords])
for i, bit in enumerate(result):
bit.move_to(ORIGIN, LEFT)
bit.shift(i * RIGHT * 0.325)
result.set_stroke(BLACK, 4, background=True)
return result
bit_strings = VGroup(*[
get_bit_aligned_bit_string(bs)
for bs in self.bit_strings
])
for word, bs in zip(words, bit_strings):
bs.next_to(word.family_members_with_points()[-1], RIGHT, aligned_edge=DOWN)
words[1].set_fill(YELLOW)
bit_strings[1].set_fill(YELLOW)
self.add(words[::2])
self.add(bit_strings[::2])
self.wait()
self.play(FadeIn(words[1]))
curr_rect = None
for n in reversed(range(6)):
rect = SurroundingRectangle(Group(
bit_strings[0][n],
bit_strings[2][n],
buff=0.05,
))
rect.stretch(0.9, 0)
rect.set_stroke(WHITE, 1)
if curr_rect is None:
curr_rect = rect
self.play(ShowCreation(curr_rect))
else:
self.play(Transform(curr_rect, rect, run_time=0.25))
self.wait(0.75)
self.play(FadeIn(bit_strings[1][n]))
self.play(FadeOut(curr_rect))
class ShowCurrAndTargetAlt(ShowCurrAndTarget):
CONFIG = {
"bit_strings": [
"110100",
"010101",
"100001",
]
}
class EulerDiagram(Scene):
def construct(self):
colors = [RED, GREEN, BLUE]
vects = compass_directions(3, UP)
circles = VGroup(*[
Circle(
radius=2,
fill_color=color,
stroke_color=color,
fill_opacity=0.5,
stroke_width=3,
).shift(1.2 * vect)
for vect, color in zip(vects, colors)
])
bit_coords = list(map(int_to_bit_coords, range(8)))
bit_strings = VGroup(*map(get_bit_string, bit_coords))
bit_strings.center()
r1 = 2.2
r2 = 1.4
bit_strings[0].next_to(circles[0], LEFT).shift(UP)
bit_strings[1].shift(r1 * vects[0])
bit_strings[2].shift(r1 * vects[1])
bit_strings[3].shift(r2 * (vects[0] + vects[1]))
bit_strings[4].shift(r1 * vects[2])
bit_strings[5].shift(r2 * (vects[0] + vects[2]))
bit_strings[6].shift(r2 * (vects[1] + vects[2]))
self.add(circles)
for circle in circles:
circle.save_state()
for coords, bstring in zip(bit_coords[1:], bit_strings[1:]):
for circ, coord in zip(circles, reversed(coords)):
circ.generate_target()
if coord:
circ.target.become(circ.saved_state)
else:
circ.target.set_opacity(0.1)
self.play(
FadeIn(bstring),
*map(MoveToTarget, circles),
run_time=0.25,
)
self.wait(0.75)
self.wait()
self.play(FadeIn(bit_strings[0], DOWN))
self.wait()
class ShowBoardRegions(ThreeDScene):
def construct(self):
# Setup
board = Chessboard()
nums = VGroup()
pre_bin_nums = VGroup()
bin_nums = VGroup()
for n, square in enumerate(board):
bin_num = VGroup(*[
Integer(int(b), fill_color=GREY_A)
for b in int_to_bit_coords(n, min_dim=6)
])
bin_num.arrange(RIGHT, buff=SMALL_BUFF)
bin_num.set_width((square.get_width() * 0.8))
num = Integer(n)
num.set_height(square.get_height() * 0.4)
for mob in num, bin_num:
mob.move_to(square, OUT)
mob.set_stroke(BLACK, 4, background=True)
bin_num.align_to(square, DOWN)
bin_num.shift(SMALL_BUFF * UP)
pre_bin_num = num.copy()
pre_bin_num.generate_target()
pre_bin_num.target.replace(bin_num, stretch=True)
pre_bin_num.target.set_opacity(0)
num.generate_target()
num.target.scale(0.7)
num.target.align_to(square, UP)
num.target.shift(SMALL_BUFF * DOWN)
bin_num.save_state()
bin_num.replace(num, stretch=True)
bin_num.set_opacity(0)
nums.add(num)
bin_nums.add(bin_num)
pre_bin_nums.add(pre_bin_num)
# Transform to binary
self.add(board)
self.play(
ShowIncreasingSubsets(nums, run_time=4, rate_func=bezier([0, 0, 1, 1]))
)
self.wait()
self.play(
LaggedStart(*[
AnimationGroup(
MoveToTarget(num),
MoveToTarget(pbn),
Restore(bin_num),
)
for num, pbn, bin_num in zip(nums, pre_bin_nums, bin_nums)
], lag_ratio=1.5 / 64),
)
self.remove(pre_bin_nums)
self.wait(2)
# Build groups to highlight
one_groups = VGroup()
highlights = VGroup()
for i in reversed(range(6)):
one_group = VGroup()
highlight = VGroup()
for bin_num, square in zip(bin_nums, board):
boundary_square = Square()
# boundary_square.set_stroke(YELLOW, 4)
boundary_square.set_stroke(BLUE, 4)
boundary_square.set_fill(BLUE, 0.5)
boundary_square.replace(square)
boundary_square.move_to(square, OUT)
bit = bin_num[i]
if bit.get_value() == 1:
one_group.add(bit)
highlight.add(boundary_square)
one_group.save_state()
one_groups.add(one_group)
highlights.add(highlight)
# Highlight hit_groups
curr_highlight = None
for one_group, highlight in zip(one_groups, highlights):
one_group.generate_target()
one_group.target.set_fill(YELLOW)
one_group.target.set_stroke(YELLOW, 2)
if curr_highlight is None:
self.play(MoveToTarget(one_group))
self.wait()
self.play(LaggedStartMap(DrawBorderThenFill, highlight, lag_ratio=0.1, run_time=3))
curr_highlight = highlight
else:
self.add(one_group, curr_highlight)
self.play(
MoveToTarget(one_group),
Transform(curr_highlight, highlight)
)
self.wait()
self.play(Restore(one_group))
self.wait()
self.play(FadeOut(curr_highlight))
class ShowFinalStrategy(Scene):
CONFIG = {
"show_with_lines": False,
}
def construct(self):
# Setup board and such
board = Chessboard()
board.to_edge(RIGHT)
coins = CoinsOnBoard(board, coin_config={"numeric_labels": True})
coins.flip_by_message("3b1b :)")
encoding_lines = VGroup(*[Line(ORIGIN, 0.5 * RIGHT) for x in range(6)])
encoding_lines.arrange(LEFT, buff=SMALL_BUFF)
encoding_lines.next_to(board, LEFT, LARGE_BUFF)
encoding_lines.shift(UP)
code_words = TextMobject("Encoding")
code_words.next_to(encoding_lines, DOWN)
add_words = TextMobject("Check the parity\\\\of these coins")
add_words.next_to(board, LEFT, LARGE_BUFF, aligned_edge=UP)
self.add(board, coins)
self.add(encoding_lines)
self.add(code_words)
# Set up groups
fade_groups = Group()
line_groups = VGroup()
mover_groups = VGroup()
count_mobs = VGroup()
one_groups = VGroup()
bits = VGroup()
for i in range(6):
bit = Integer(0)
bit.next_to(encoding_lines[i], UP, SMALL_BUFF)
bits.add(bit)
count_mob = Integer(0)
count_mob.set_color(RED)
count_mob.next_to(add_words, DOWN, MED_SMALL_BUFF)
count_mobs.add(count_mob)
line_group = VGroup()
fade_group = Group()
mover_group = VGroup()
one_rect_group = VGroup()
count = 0
for n, coin in enumerate(coins):
if bool(n & (1 << i)):
line_group.add(Line(
coin.get_center(),
bit.get_center(),
))
mover_group.add(coin.labels[1 - int(coin.is_heads())].copy())
if coin.is_heads():
one_rect_group.add(SurroundingRectangle(coin))
count += 1
else:
fade_group.add(coin)
bit.set_value(count % 2)
fade_group.save_state()
line_group.set_stroke(BLUE, width=1, opacity=0.5)
fade_groups.add(fade_group)
line_groups.add(line_group)
mover_groups.add(mover_group)
one_groups.add(one_rect_group)
# Animate
for lines, fades, movers, og, cm, bit in zip(line_groups, fade_groups, mover_groups, one_groups, count_mobs, bits):
self.play(
FadeIn(add_words),
fades.set_opacity, 0.1,
)
if self.show_with_lines:
for mover in movers:
mover.generate_target()
mover.target.replace(bit)
mover.target.set_opacity(0)
bit.save_state()
bit.replace(movers[0])
bit.set_opacity(0)
self.play(
LaggedStartMap(ShowCreation, lines, run_time=2),
LaggedStartMap(MoveToTarget, movers, lag_ratio=0.01),
Restore(bit)
)
self.remove(movers)
self.add(bit)
self.play(
FadeOut(lines)
)
else:
self.play(
ShowIncreasingSubsets(og),
UpdateFromFunc(cm, lambda m: m.set_value(len(og)))
)
self.play(FadeInFromPoint(bit, cm.get_center()))
self.play(
FadeOut(og),
FadeOut(cm),
)
self.play(
FadeOut(add_words),
Restore(fades),
)
self.remove(fades)
self.add(coins)
self.wait()
class ShowFinalStrategyWithFadeLines(ShowFinalStrategy):
CONFIG = {
"show_with_lines": True,
}
class Thumbnail(ThreeDScene):
def construct(self):
# Board
board = Chessboard(
shape=(8, 8),
# shape=(6, 6),
square_resolution=(5, 5),
top_square_resolution=(7, 7),
)
board.set_gloss(0.5)
coins = CoinsOnBoard(
board,
coin_config={
"disk_resolution": (8, 51),
}
)
coins.flip_by_message("A colab!")
# bools = np.array(string_to_bools("A colab!"))
# bools = bools.reshape((6, 8))[:, 2:]
# coins.flip_by_bools(bools.flatten())
# board[0].set_opacity(0)
# coins[0].set_opacity(0)
# k = boolian_linear_combo(coins.get_bools())
k = 6
board[k].set_color(YELLOW)
self.add(board)
self.add(coins)
# Move them
Group(board, coins).shift(DOWN + 2 * RIGHT)
frame = self.camera.frame
frame.set_rotation(phi=50 * DEGREES)
# Title
title = TextMobject("Impossible?")
title.fix_in_frame()
title.set_width(8)
title.to_edge(UP)
title.set_stroke(BLACK, 6, background=True)
# self.add(title)
# Instructions
message = TextMobject(
"Flip one coin\\\\to describe a\\\\",
"unique square",
alignment="",
)
message[1].set_color(YELLOW)
message.scale(1.25)
message.to_edge(LEFT)
message.shift(1.25 * DOWN)
message.fix_in_frame()
arrow = Arrow(
message.get_corner(UR),
message.get_corner(UR) + 3 * RIGHT + UP,
path_arc=-90 * DEGREES,
)
arrow.fix_in_frame()
arrow.shift(1.5 * LEFT)
arrow.set_color(YELLOW)
self.add(message)
self.add(arrow)
class ChessEndScreen(PatreonEndScreen):
CONFIG = {
"scroll_time": 25,
}