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

6270 lines
No EOL
193 KiB
Python

from manim_imports_ext import *
from _2020.chess import string_to_bools
def get_background(color=GREY_E):
background = FullScreenRectangle()
background.set_fill(color, 1)
background.set_stroke(width=0)
return background
def get_bit_grid(n_rows, n_cols, bits=None, buff=MED_SMALL_BUFF, height=4):
bit_pair = VGroup(Integer(0), Integer(1))
bit_mobs = VGroup(*[
bit_pair.copy()
for x in range(n_rows * n_cols)
])
bit_mobs.arrange_in_grid(n_rows, n_cols, buff=buff)
bit_mobs.set_height(height)
if bits is None:
bits = np.random.randint(0, 2, len(bit_mobs))
for bit_mob, bit in zip(bit_mobs, bits):
bit_mob[1 - bit].set_opacity(0)
bit_mobs.n_rows = n_rows
bit_mobs.n_cols = n_cols
return bit_mobs
def get_bit_mob_value(bit_mob):
return int(bit_mob[1].get_fill_opacity() > bit_mob[0].get_fill_opacity())
def bit_grid_to_bits(bit_grid):
return list(map(get_bit_mob_value, bit_grid))
def toggle_bit(bit):
for sm in bit:
sm.set_fill(opacity=1 - sm.get_fill_opacity())
return bit
def hamming_syndrome(bits):
return reduce(
lambda i1, i2: i1 ^ i2,
[i for i, b in enumerate(bits) if b],
0,
)
def string_to_bits(message):
return [int(b) for b in string_to_bools(message)]
def int_to_bit_string(number, n_bits=None):
result = "{:b}".format(number)
if n_bits is not None:
result = (n_bits - len(result)) * "0" + result
return result
def get_image_bits(image, bit_height=0.15, buff=MED_SMALL_BUFF):
bit = Integer(0)
small_buff = (buff / bit.get_height()) * bit_height
bit.set_height(bit_height)
bits = get_bit_grid(
n_rows=int(image.get_height() / (bit.get_height() + small_buff)),
n_cols=int(image.get_width() / (bit.get_width() + small_buff)),
buff=buff
)
bits.replace(image)
return bits
def get_sound_wave():
sound = VGroup(*[
Line(DOWN, UP).set_height(
(0.3 + 0.8 * random.random()) * abs(np.sin(x))
)
for x in np.linspace(0, 3 * PI, 100)
])
sound.arrange(RIGHT, buff=0.05)
return sound
def get_sender_and_receiver(height=2):
sender = Randolph(height=height)
receiver = Mortimer(height=height)
sender.name = TextMobject("Sender")
receiver.name = TextMobject("Receiver")
pis = VGroup(sender, receiver)
names = VGroup(sender.name, receiver.name)
sender.to_corner(DL)
receiver.to_corner(DR)
pis.shift(UP)
for name, pi in zip(names, pis):
name.next_to(pi, DOWN)
return pis, names
def get_ones(block):
result = VGroup()
for bit in block:
if isinstance(bit, Integer):
value = bit.get_value()
else:
value = get_bit_mob_value(bit)
if value == 1:
result.add(bit)
return result
def get_one_rects(block, buff=SMALL_BUFF):
rects = VGroup()
for bit in get_ones(block):
rect = SurroundingRectangle(bit, buff=buff)
rect.set_stroke(YELLOW, 3)
rect.set_fill(YELLOW, 0.2)
rects.add(rect)
return rects
def get_ones_counter(label, one_rects, buff=MED_LARGE_BUFF):
counter = Integer()
counter.match_color(one_rects[0])
counter.match_height(label[1])
counter.next_to(label[1], RIGHT, buff=buff, aligned_edge=DOWN)
f_always(counter.set_value, lambda: len(one_rects))
return counter
def get_bit_grid_boxes(bit_grid, color=GREY_B, stroke_width=2):
width = get_norm(bit_grid[1].get_center() - bit_grid[0].get_center())
height = get_norm(bit_grid[bit_grid.n_cols].get_center() - bit_grid[0].get_center())
return VGroup(*[
Rectangle(
height=height,
width=width,
stroke_color=color,
stroke_width=stroke_width,
).move_to(bit)
for bit in bit_grid
])
def get_grid_position_labels(boxes, height=0.25):
labels = VGroup()
for n, box in enumerate(boxes):
label = Integer(n)
label.set_height(height)
label.move_to(box, DR)
label.shift(SMALL_BUFF * UL)
labels.add(label)
return labels
def get_bit_n_sublist(input_list, n, bit_value=1):
return [
elem
for i, elem in enumerate(input_list)
if bool(i & (1 << n)) ^ bool(1 - bit_value)
]
def get_bit_n_subgroup(mob, n, bit_value=1):
"""
If we enumerate mob, this returns a subgroup of all elements
whose index has a binary representation with the n'th bit
equal to bit_value
"""
return VGroup(*get_bit_n_sublist(mob, n, bit_value))
# Special animations
def image_reveal_animation(image, bit_height=0.1):
box = SurroundingRectangle(image)
box.set_fill(BLACK, 1)
box.set_stroke(width=0)
bits = get_image_bits(image, bit_height=bit_height)
return AnimationGroup(
Animation(image),
ApplyMethod(
box.stretch, 0, 1, {"about_edge": DOWN}, remover=True,
rate_func=linear,
),
LaggedStartMap(
VFadeInThenOut, bits,
run_time=1,
lag_ratio=3 / len(bits)
)
)
def toggle_bit_anim(bit, target_color=None, **kwargs):
original = bit.copy()
original[1 - get_bit_mob_value(original)].rotate(PI)
toggle_bit(bit)
if target_color is not None:
bit.set_color(target_color)
if "path_arc" not in kwargs:
kwargs["path_arc"] = PI
return TransformFromCopy(original, bit, **kwargs)
def zap_anim(bit, bolt_height=0.75):
bolt = SVGMobject("lightning_bolt")
bolt[0].add_line_to(bolt[0].get_start())
bolt.set_fill(RED_B, 1)
bolt.set_stroke(width=0)
bolt.set_height(bolt_height)
bolt.move_to(bit.get_center(), DL)
outline = bolt.deepcopy()
outline.set_stroke(RED_D, 2)
outline.set_fill(opacity=0)
return AnimationGroup(
Succession(
GrowFromPoint(bolt, bolt.get_corner(UR), rate_func=rush_into),
FadeOut(bolt, run_time=0.5),
),
Succession(
ShowCreation(outline),
FadeOut(outline),
),
run_time=1,
)
def scan_anim(point, bits, final_stroke_width=0, run_time=3, lag_factor=3, show_robot=True, robot_height=0.5):
lines = VGroup(*[
Line(
point, bit.get_center(),
stroke_color=[GREEN, BLUE][get_bit_mob_value(bit)],
stroke_width=1,
)
for bit in bits
])
anims = [
LaggedStartMap(
lambda m, **kw: Succession(
ShowCreation(m),
ApplyMethod(m.set_stroke, None, final_stroke_width),
**kw
),
lines,
lag_ratio=lag_factor / len(bits),
run_time=run_time,
remover=bool(final_stroke_width)
)
]
if show_robot:
robot = SVGMobject("robot")
robot.set_stroke(width=0)
robot.set_color(GREY)
robot.set_gloss(1)
robot.set_height(robot_height)
robot.move_to(point, UP)
anims.append(FadeIn(robot))
return AnimationGroup(*anims)
def focus_scan_anim_lines(scanim, point, final_stroke_width=1):
lines = scanim.mobject[0]
for line in lines:
line.generate_target()
line.target.put_start_and_end_on(line.get_start(), point)
line.target.set_stroke(width=final_stroke_width)
return AnimationGroup(*[
MoveToTarget(line)
for line in lines
])
def get_xor(height=0.35, color=BLUE_B, stroke_width=4):
xor = VGroup(
Line(UP, DOWN),
Line(LEFT, RIGHT),
Circle(),
)
xor.set_stroke(color, stroke_width)
xor.set_height(height)
return xor
# Scenes
class Thumbnail(Scene):
def construct(self):
phrases = VGroup(
TextMobject("One Bit is Wrong"),
TextMobject("(according to an extended Hamming Code)"),
TextMobject("Can you tell which?"),
)
for phrase in phrases:
phrase.set_width(12)
phrases[0].to_edge(UP)
phrases[1].set_width(10)
phrases[1].set_color(GREY_C)
phrases[1].next_to(phrases[0], DOWN, SMALL_BUFF)
phrases[2].to_edge(DOWN, buff=MED_SMALL_BUFF)
phrases[0].set_color(BLUE_B)
phrases[2].set_color(BLUE_C)
bit_values = [
1, 1, 0, 1, 1, 0, 0, 1,
0, 1, 1, 0, 1, 1, 1, 1,
]
bits = get_bit_grid(2, 8, bits=bit_values, buff=MED_LARGE_BUFF)
boxes = get_bit_grid_boxes(bits)
pos_labels = get_grid_position_labels(boxes)
pos_labels.set_color(GREY_B)
for bit, box in zip(bits, boxes):
bit.set_height(0.7 * box.get_height())
group = VGroup(boxes, bits, pos_labels)
group.set_width(10)
group.next_to(phrases[1], DOWN)
self.add(phrases)
self.add(group)
class DiskOfBits(Scene):
def construct(self):
# Setup disc
bits = get_bit_grid(2**5, 2**6, height=6)
inner_r = 1
outer_r = 3
annulus = Annulus(
inner_radius=inner_r * 0.93,
outer_radius=outer_r * 1.02,
fill_color=GREY_D,
)
annulus.set_gloss(0.5)
for bit in bits:
point = bit.get_center()
norm = get_norm(point)
to_inner = inner_r - norm
to_outer = norm - outer_r
sdf = 10 * max(to_inner, to_outer)
if 0 < sdf < 0.5:
bit.scale(1 - sdf)
elif sdf > 0:
bits.remove(bit)
disc = VGroup(annulus, bits)
# Setup scratch
scratch_line = Line(disc.get_top(), disc.get_right())
scratch_line.set_stroke(RED, width=2, opacity=0.75)
flipped_bits = VGroup()
for bit in bits:
point = bit.get_center()
norm = abs(point[0] + point[1] - 3)
alpha = 1 - clip(norm / 0.2, 0, 1)
if alpha > 0.25:
bit.generate_target()
bit.target.set_stroke(width=1)
bit.target.set_color(RED)
flipped_bits.add(bit)
# Add disc
light = self.camera.light_source
light.move_to([0, -10, 10])
random_bits = bits.copy()
random_bits.sort(lambda p: -get_norm(p))
self.add(annulus)
self.play(
LaggedStartMap(FadeIn, random_bits, lag_ratio=10 / len(bits)),
light.move_to, [-10, 10, 10],
run_time=1,
)
self.clear()
self.add(disc)
# Show scratch
self.play(
LaggedStartMap(MoveToTarget, flipped_bits),
ShowCreationThenDestruction(scratch_line),
run_time=2,
)
# Flip 'em'
self.play(LaggedStartMap(toggle_bit_anim, flipped_bits))
self.wait()
# Show image in and image out
disc.generate_target()
disc.target.scale(0.5)
in_image = ImageMobject("Mona_Lisa")
in_image.to_edge(LEFT)
in_image.shift(UP)
in_words = TextMobject("What was\\\\encoded")
in_words.next_to(in_image, DOWN)
out_image = in_image.copy()
out_image.to_edge(RIGHT)
out_words = TextMobject("What is\\\\decoded")
out_words.next_to(out_image, DOWN)
in_arrow = Arrow(
in_image.get_right(),
disc.target.get_left() + 0.2 * UP,
buff=MED_SMALL_BUFF,
)
out_arrow = Arrow(
disc.target.get_right() + 0.2 * UP,
out_image.get_left(),
buff=MED_SMALL_BUFF,
)
for arrow in in_arrow, out_arrow:
arrow.set_fill(GREY_B)
self.play(
MoveToTarget(disc),
GrowArrow(out_arrow),
)
in_bits = get_bit_grid(40, 33)
in_bits.replace(in_image, stretch=True)
in_bits.set_fill(GREY_B)
out_bits = in_bits.copy()
out_bits.move_to(out_image)
out_image_blocker = SurroundingRectangle(out_image)
out_image_blocker.set_fill(BLACK, 1)
out_image_blocker.set_stroke(width=0)
self.add(out_image, out_image_blocker, out_bits, out_words)
self.play(
LaggedStart(
*[
Succession(
GrowFromPoint(
out_bit,
random.choice(bits).get_center(),
),
FadeOut(out_bit),
)
for out_bit in out_bits
],
lag_ratio=3 / len(out_bits),
run_time=12,
),
ApplyMethod(
out_image_blocker.stretch, 0, 1, {"about_edge": DOWN},
run_time=12,
rate_func=bezier([0, 0, 1, 1]),
),
FadeIn(out_words, UP),
)
self.wait()
self.play(
TransformFromCopy(out_image, in_image),
ReplacementTransform(
in_words.copy().replace(out_words).set_opacity(0),
in_words,
),
)
self.play(
LaggedStart(
*[
Succession(
FadeIn(in_bit),
Transform(in_bit, random.choice(bits)),
FadeOut(in_bit),
)
for in_bit in in_bits
],
lag_ratio=3 / len(in_bits),
run_time=12,
),
GrowArrow(in_arrow),
)
self.wait()
# Pi Creature
randy = Randolph()
randy.set_height(1.5)
randy.to_edge(DOWN)
randy.shift(2 * LEFT)
self.play(FadeIn(randy))
self.play(randy.change, "maybe")
self.play(Blink(randy))
self.play(randy.change, "confused")
self.wait(2)
self.play(Blink(randy))
self.wait()
class TripleRedundancy(Scene):
def construct(self):
# Show different file types
image = ImageMobject("Claude_Shannon")
image.set_height(6)
image.to_edge(DOWN)
video = ImageMobject("ZoeyInGrass")
video.set_opacity(0)
sound = get_sound_wave()
text = TextMobject(
"""
Fourscore and seven years ago\\\\
our fathers brought forth, on this\\\\
continent, a new nation, conceived\\\\
in liberty, and dedicated to the\\\\
proposition that all men are created\\\\
equal. Now we are engaged$\\dots$
""",
alignment=""
)
code = ImageMobject("Hamming_Code_Snippet")
files = Group(video, sound, text, code, image)
for file in files:
file.match_width(image)
file.move_to(image, UP)
brace = Brace(image, UP)
file_word = TextMobject("Original File")
file_word.set_height(0.5)
file_word.next_to(brace, UP)
self.play(
GrowFromCenter(brace),
FadeIn(file_word),
image_reveal_animation(video),
)
video.set_opacity(0)
self.wait(2)
for f1, f2 in zip(files, files[1:]):
self.play(FadeOut(f1), FadeIn(f2))
self.wait()
# Show bits
bits = get_image_bits(image)
for bit, value in zip(bits, string_to_bits(";)Hi")):
bit[1 - value].set_opacity(0)
bit[value].set_opacity(1)
bits.generate_target()
bits.target.arrange(RIGHT)
bits.target.set_height(0.5)
bits.target.arrange(RIGHT, buff=SMALL_BUFF)
bits.target.to_edge(LEFT)
bits.target.shift(UP)
last_shown_bit_index = 31
dots = TextMobject("\\dots")
dots.next_to(bits.target[last_shown_bit_index], RIGHT, aligned_edge=DOWN, buff=SMALL_BUFF)
new_brace = Brace(VGroup(bits.target[0], dots), UP)
file_rect = SurroundingRectangle(VGroup(bits.target[0], dots))
file_rect.set_stroke(BLUE, 4)
self.play(
FadeOut(image),
FadeIn(bits, lag_ratio=1 / len(bits))
)
self.play(
MoveToTarget(bits),
Transform(brace, new_brace),
file_word.next_to, new_brace, UP, SMALL_BUFF,
run_time=3,
)
self.play(
FadeOut(bits[last_shown_bit_index + 1:last_shown_bit_index + 5]),
FadeIn(dots),
FadeOut(brace),
FadeIn(file_rect),
file_word.next_to, file_rect, UP,
)
self.remove(*bits[last_shown_bit_index + 1:])
bits.remove(*bits[last_shown_bit_index + 1:])
self.add(bits)
self.wait()
# Show redundant copies
bits.add(dots)
copies = VGroup(bits.copy(), bits.copy())
copies.arrange(DOWN, buff=MED_SMALL_BUFF)
copies.next_to(bits, DOWN, buff=MED_SMALL_BUFF)
copies.set_color(BLUE)
copies_rect = SurroundingRectangle(copies, buff=SMALL_BUFF)
copies_rect.set_stroke(BLUE_E, 4)
copies_word = TextMobject("Redundant copies")
copies_word.scale(file_word[0][0].get_height() / copies_word[0][0].get_height())
copies_word.match_color(copies)
copies_word.next_to(copies_rect, DOWN)
self.play(LaggedStart(*[
TransformFromCopy(bits, bits_copy)
for bits_copy in copies
], lag_ratio=0.5))
self.play(
ShowCreation(copies_rect),
FadeIn(copies_word, UP),
copies.set_color, WHITE,
)
self.wait()
# Show a correction
flipper_index = 7
flipper = bits[flipper_index]
self.play(
zap_anim(flipper),
toggle_bit_anim(flipper),
)
self.play()
self.wait()
bit_groups = [bits, *copies]
scan_rect = SurroundingRectangle(
VGroup(*[group[0] for group in bit_groups])
)
scan_rect.set_stroke(GREEN, 5)
self.add(scan_rect)
for i in range(flipper_index + 1):
self.play(
scan_rect.match_x, bits[i],
run_time=0.25
)
self.wait(0.25)
bangs = TextMobject("!!!")[0]
bangs.scale(1.5)
bangs.set_color(RED)
bangs.next_to(scan_rect, UP)
self.play(
LaggedStartMap(
FadeIn, bangs,
lambda m: (m, DOWN),
run_time=1,
),
scan_rect.set_color, RED
)
self.wait()
flipper_rect = SurroundingRectangle(flipper, buff=SMALL_BUFF)
flipper_rect.set_stroke(GREEN, 4)
flipper_rect.set_fill(GREEN, 0.5)
bangs.unlock_triangulation()
self.play(
ReplacementTransform(bangs, flipper_rect)
)
self.play(
toggle_bit_anim(flipper),
FadeOut(flipper_rect),
scan_rect.set_color, GREEN,
)
for i in range(flipper_index + 1, flipper_index + 5):
self.play(
scan_rect.match_x, bits[i],
run_time=0.25
)
self.wait(0.25)
self.play(FadeOut(scan_rect, 0.2 * RIGHT, run_time=0.5))
# Show 2/3 of transmission block
rects = VGroup(file_rect, copies_rect)
rects.generate_target()
for rect, width in zip(rects.target, [1, 2]):
rect.set_width(width, stretch=True)
rect.set_height(0.75, stretch=True)
rect.set_fill(rect.get_stroke_color(), opacity=1)
rects.target[0].set_color(BLUE)
rects.target[1].set_color(BLUE_E)
rects.target.set_stroke(width=0)
rects.target.arrange(RIGHT, buff=0)
rects.target.set_width(12, stretch=True)
rects.target.move_to(UP)
braces = VGroup(*[Brace(rect, UP, buff=SMALL_BUFF) for rect in rects.target])
frac_labels = VGroup(*[
DecimalNumber(100 * frac, unit="\\%", num_decimal_places=1)
for frac in [1 / 3, 2 / 3]
])
for rect, label in zip(rects.target, frac_labels):
label.move_to(rect)
copies_word.unlock_triangulation()
redundancy_word = TextMobject("Redundancy")
redundancy_word.match_height(copies_word)
redundancy_word.match_color(copies_word)
redundancy_word.next_to(braces[1], UP, SMALL_BUFF)
self.play(
bits.replace, rects.target[0], {"stretch": True},
bits.set_opacity, 0,
copies.replace, rects.target[1], {"stretch": True},
copies.set_opacity, 0,
MoveToTarget(rects),
LaggedStartMap(GrowFromCenter, braces),
file_word.next_to, braces[0], UP, SMALL_BUFF,
ReplacementTransform(copies_word, redundancy_word),
run_time=2,
)
self.play(Write(frac_labels))
self.wait()
# Show failure for some two-bit errors
bits = get_bit_grid(3, 1, [0, 0, 0], height=3, buff=SMALL_BUFF)
bits.next_to(rects, DOWN, buff=MED_LARGE_BUFF)
self.play(FadeIn(bits))
self.play(
LaggedStart(zap_anim(bits[0]), zap_anim(bits[2]), lag_ratio=0.5),
LaggedStart(toggle_bit_anim(bits[0]), toggle_bit_anim(bits[2]), lag_ratio=0.5),
run_time=1.5,
)
self.wait()
self.play(FadeOut(bits))
# Shrink bars
rects.generate_target()
p = 9 / 256
rects.target[0].set_width(1 - p, stretch=True)
rects.target[1].set_width(p, stretch=True)
rects.target.arrange(RIGHT, buff=0)
rects.target.match_width(rects, stretch=True)
rects.target.move_to(rects)
braces.generate_target()
for rect, brace in zip(rects.target, braces.target):
brace.become(Brace(rect, UP, buff=SMALL_BUFF, min_num_quads=1))
f_always(frac_labels[0].move_to, rects[0].get_center)
f_always(frac_labels[1].move_to, rects[1].get_center)
rl_width = frac_labels[1].get_width()
f_always(frac_labels[1].set_width, lambda: min(0.95 * rects[1].get_width(), rl_width))
self.play(
MoveToTarget(rects),
MoveToTarget(braces),
ChangeDecimalToValue(frac_labels[0], 100 * (1 - p)),
ChangeDecimalToValue(frac_labels[1], 100 * p),
file_word.next_to, braces.target[0], UP, SMALL_BUFF,
redundancy_word.next_to, braces.target[1], UP, SMALL_BUFF,
redundancy_word.to_edge, RIGHT,
run_time=3,
)
self.wait()
ratio_group = VGroup(rects, braces, frac_labels, file_word, redundancy_word)
ratio_group.clear_updaters()
# Show space division for a (256, 247) Hamming code
bits = get_bit_grid(
16, 16,
bits=string_to_bits("There are 10 kinds of people...."),
buff=0.2, height=5
)
bits.to_edge(DOWN, buff=MED_SMALL_BUFF)
bits.shift(2 * LEFT)
ecc_bits = bits[-9:]
ecc_rect = SurroundingRectangle(ecc_bits, buff=0.05)
ecc_rect.set_stroke(BLUE_E, 2)
ecc_rect.set_fill(BLUE_E, 0.5)
block_label = TextMobject("256 bit block")
ecc_label = TextMobject("9 redundancy bits")
message_label = TextMobject("247 message bits")
block_label.next_to(bits, RIGHT, LARGE_BUFF)
block_label.shift(UP)
ecc_label.next_to(ecc_bits, RIGHT, LARGE_BUFF)
ecc_label.to_edge(DOWN, buff=MED_SMALL_BUFF)
ecc_label.set_color(BLUE)
message_label.move_to(VGroup(block_label, ecc_label))
message_label.align_to(block_label, LEFT)
message_label.set_color(YELLOW)
self.play(
ratio_group.to_edge, UP,
LaggedStartMap(FadeIn, bits, lag_ratio=1 / len(bits)),
Write(block_label),
)
self.wait()
self.play(
ReplacementTransform(
SurroundingRectangle(bits, color=BLUE, stroke_opacity=0),
ecc_rect
),
Write(ecc_label)
)
self.wait()
self.play(
LaggedStartMap(
VFadeInThenOut,
bits[:-9].copy().set_fill(color=YELLOW),
lag_ratio=0.1 / len(bits),
run_time=3,
),
Write(message_label),
)
self.wait()
# Show correction
flipper = random.choice(bits)
self.play(
zap_anim(flipper),
toggle_bit_anim(flipper),
)
self.play(flipper.set_color, RED)
point = bits.get_corner(DL) + 1.5 * LEFT + UP
scanim = scan_anim(point, bits, final_stroke_width=0.2)
lines, robot = scanim.mobject
self.play(scanim)
self.play(*[
Succession(
ApplyMethod(line.put_start_and_end_on, point, flipper.get_center()),
ApplyMethod(line.set_stroke, None, 1)
)
for line in lines
])
self.play(
toggle_bit_anim(flipper),
FadeOut(lines),
FadeOut(robot),
)
self.play(flipper.set_color, WHITE)
# Show two errors
two_error_label = TextMobject("Two errors")
two_error_label.next_to(bits, LEFT, buff=MED_LARGE_BUFF, aligned_edge=UP)
two_error_label.set_color(RED)
flippers = VGroup(*random.sample(list(bits), 2))
self.play(
FadeIn(two_error_label),
LaggedStart(*[zap_anim(f) for f in flippers], lag_ratio=0.5),
LaggedStartMap(toggle_bit_anim, flippers, lag_ratio=0.5),
)
self.play(flippers.set_color, RED)
self.wait()
scanim = scan_anim(point, bits, final_stroke_width=0.2)
bangs = TextMobject("!!!")[0]
bangs.set_color(RED)
bangs.next_to(point, UL, SMALL_BUFF)
q_marks = TextMobject("???")[0]
q_marks.replace(bangs, dim_to_match=1)
q_marks.match_style(bangs)
self.play(scanim)
self.play(
LaggedStartMap(
FadeIn, bangs,
lambda m: (m, 0.25 * DOWN),
lag_ratio=0.2,
run_time=1,
),
)
self.wait()
bangs.unlock_triangulation()
self.play(ReplacementTransform(bangs, q_marks, lag_ratio=0.2))
self.wait()
class TimeLine(Scene):
def construct(self):
# Time line
decades = list(range(1920, 2030, 10))
timeline = NumberLine(
(decades[0], decades[-1], 2),
numbers_with_elongated_ticks=decades,
width=13
)
timeline.add_numbers(
decades,
number_config={
"group_with_commas": False,
"height": 0.2,
}
)
timeline.numbers.set_stroke(BLACK, 4, background=True)
# Events
def get_event(timeline, date, words, direction=UP):
arrow = Vector(-direction)
arrow.set_fill(GREY_A, 0.75)
arrow.shift(timeline.n2p(date) - arrow.get_end())
arrow.shift(SMALL_BUFF * direction)
label = TextMobject(words)
label.scale(0.7)
label.next_to(arrow.get_start(), np.sign(direction[1]) * UP, SMALL_BUFF)
label.set_color(GREY_A)
label.set_stroke(BLACK, 4, background=True)
event = VGroup(label, arrow)
return event
events = VGroup(
get_event(timeline, 1947, "Hamming codes", 1.5 * UP),
get_event(timeline, 1948, "Shannon's paper\\\\on information theory", DOWN + 0.2 * LEFT),
# get_event(timeline, 1949, "Gorlay codes", 0.7 * UP),
get_event(timeline, 1960, "Reed-Solomon\\\\codes", 0.7 * UP),
get_event(timeline, 1993, "Turbo codes", UP),
get_event(timeline, 1995, "Shor codes\\\\(quantum)", DOWN),
)
# Title
title = TextMobject("Error correction codes")
title.set_color(YELLOW)
title.set_height(0.5)
title.to_edge(UP)
title_underline = Underline(title)
title_underline.match_color(title)
title.add(title_underline)
title.fix_in_frame()
# Introduce time line
frame = self.camera.frame
frame.save_state()
frame.scale(0.5, about_point=timeline.n2p(1945))
frame.shift(0.5 * UP)
self.play(
Write(timeline),
Write(timeline.numbers.copy(), remover=True),
LaggedStart(
*[Animation(Mobject()) for x in range(1)],
*[
AnimationGroup(
Write(event[0], run_time=1),
GrowArrow(event[1]),
)
for event in events
],
lag_ratio=0.75,
),
FadeIn(title, DOWN),
Restore(frame, run_time=6),
)
self.wait(2)
# Isolate Hamming
hamming_word = events[0][0]
rs_word = events[2][0]
self.play(
hamming_word.scale, 2, {"about_edge": DL},
hamming_word.set_fill, WHITE,
LaggedStart(*[
ApplyMethod(mob.set_opacity, 0.5)
for mob in events[1:]
]),
FadeOut(VGroup(title, title_underline))
)
invent_words = TextMobject("How to invent")
invent_words.match_height(hamming_word[0][0])
invent_words.next_to(events[0], UP, buff=0.3)
invent_words.set_color(BLUE)
self.play(Write(invent_words))
self.wait()
hamming_word.generate_target()
hamming_word.target.scale(0.5, about_edge=UL)
hamming_word.target.set_opacity(0.5)
hamming_arrow = events[0][1]
self.play(
MoveToTarget(hamming_word),
FadeOut(invent_words),
hamming_arrow.put_start_and_end_on, hamming_word.target.get_bottom(), hamming_arrow.get_end(),
hamming_arrow.set_opacity, 0.5,
rs_word.scale, 1.5, {"about_edge": DOWN},
rs_word.set_opacity, 1,
events[2][1].set_opacity, 1,
)
self.wait()
class WhatCDsActuallyUse(Scene):
def construct(self):
arrow = Vector(2 * RIGHT + UP)
words = TextMobject("What CDs/DVDs\\\\actually use")
words.next_to(arrow.get_end(), RIGHT)
arrow.set_color(YELLOW)
words.set_color(YELLOW)
self.play(
GrowFromPoint(words, arrow.get_start()),
GrowArrow(arrow)
)
self.wait()
class ListOfRelevantMathTopics(Scene):
def construct(self):
topics = VGroup(
TextMobject("$L^1$ norm"),
TextMobject("Sphere packing"),
TextMobject("Finite sporadic groups (see Golay codes)"),
TextMobject("Finite fields"),
TextMobject("Galois extensions"),
TextMobject("Lagrange interpolation (see Reed-Solomon)"),
TextMobject("Discrete Fourier Transform"),
TexMobject("\\dots")
)
topics.arrange(RIGHT, buff=LARGE_BUFF)
brown = interpolate_color(GREY_BROWN, WHITE, 0.25)
for topic, color in zip(topics, it.cycle([BLUE_C, BLUE_D, BLUE_B, brown])):
topic.set_color(color)
topics.move_to(ORIGIN, LEFT)
topics.to_edge(UP)
self.play(topics.shift, (topics.get_width() - 5) * LEFT, run_time=12)
self.wait()
class Reinvention(TeacherStudentsScene):
def construct(self):
self.play(
self.teacher.change, "raise_right_hand",
self.get_student_changes(*3 * ["pondering"]),
)
self.wait(3)
self.student_says(
"I see where\\\\this is going",
student_index=0,
target_mode="tease",
)
self.look_at(self.students[0].bubble)
self.play(self.students[0].change, "thinking")
self.wait(6)
class EaterWrapper(Scene):
def construct(self):
bg_rect = FullScreenRectangle()
bg_rect.set_fill(GREY_E, 1)
bg_rect.set_stroke(BLACK, 0)
self.add(bg_rect)
title = TextMobject("Ben Eater implementing Hamming codes")
title.set_width(FRAME_WIDTH - 2)
title.to_edge(UP)
self.add(title)
screen_rect = ScreenRectangle()
screen_rect.set_fill(BLACK, 1)
screen_rect.set_height(6)
screen_rect.next_to(title, DOWN, MED_LARGE_BUFF)
self.add(screen_rect)
self.add(AnimatedBoundary(screen_rect))
self.wait(16)
class DataGettingZapped(Scene):
CONFIG = {
"random_seed": 1,
}
def construct(self):
# Setup bit array
# bits = get_bit_grid(50, 100)
bits = get_bit_grid(25, 50)
bits.set_color(GREY_B)
bits.set_height(FRAME_HEIGHT - 0.25)
image = ImageMobject("LowResMandelbrot")
image.replace(bits, dim_to_match=0)
threshold = 0.1
for bit in bits:
try:
rgb = image.point_to_rgb(bit.get_center())
if get_norm(rgb) > threshold:
value = 1
else:
value = 0
bit[value].set_opacity(1)
bit[1 - value].set_opacity(0)
except Exception:
pass
self.add(bits)
# Zippity zap
bolt = SVGMobject("lightning_bolt")
bolt[0].add_line_to(bolt[0].get_start())
bolt.set_fill(RED_B, 1)
bolt.set_stroke(width=0)
bolt.set_height(0.5)
def strike_anim(bit, bolt=bolt, **kwargs):
bolt = bolt.copy()
bolt.move_to(bit.get_center(), DL)
bits.remove(bit)
bit.generate_target()
bit.target.set_color(RED)
bit.target.set_stroke(RED, 1)
for sm in bit.target:
sm.set_opacity(1 - sm.get_fill_opacity())
outline = bolt.deepcopy()
outline.set_stroke(RED_D, 2)
outline.set_fill(opacity=0)
return AnimationGroup(
Succession(
GrowFromPoint(bolt, bolt.get_corner(UR), rate_func=rush_into),
FadeOut(bolt, run_time=0.5),
),
Succession(
ShowCreation(outline),
FadeOut(outline),
),
MoveToTarget(bit),
)
for count in range(20):
self.play(LaggedStart(*[
strike_anim(random.choice(bits))
for x in range(int(random.expovariate(0.5)) + 1)
], lag_ratio=0.25))
self.wait(random.expovariate(2))
class AmbientErrorCorrection(Scene):
CONFIG = {
"N": 8,
"bit_grid_height": 7,
}
def construct(self):
N = self.N
size = (2**(N // 2), 2**(N // 2))
bits = get_bit_grid(
*size,
bits=string_to_bits("Claude Shannon was a total boss!"),
height=self.bit_grid_height,
)
bits.move_to(2 * RIGHT)
point = 2.5 * LEFT + 2.5 * DOWN
last_block = VMobject()
for x in range(10):
block = get_bit_grid(*size, height=self.bit_grid_height)
block.move_to(2 * RIGHT)
syndrome = hamming_syndrome(bit_grid_to_bits(block))
if random.random() < 0.5:
syndrome = 0
toggle_bit(block[syndrome])
scanim = scan_anim(
point, bits,
final_stroke_width=0.2, run_time=3, lag_factor=1,
show_robot=(x == 0),
)
self.play(
FadeIn(block, 6 * RIGHT),
FadeOut(last_block, 6 * LEFT),
run_time=2
)
self.play(scanim, run_time=1)
if syndrome:
flipper = block[syndrome]
bangs = TexMobject("!!!")
bangs.scale(2)
bangs.next_to(point, UL)
bangs.set_color(RED)
self.play(
Write(bangs, run_time=0.5),
focus_scan_anim_lines(scanim, flipper.get_center()),
flipper.set_color, RED,
)
self.play(
toggle_bit_anim(flipper, target_color=WHITE),
ApplyMethod(scanim.mobject[0].set_stroke, None, 0, 0, remover=True),
FadeOut(bangs),
)
else:
check = Checkmark()
check.scale(2)
check.next_to(point, UL)
self.play(FadeIn(check, 0.5 * DOWN))
self.play(FadeOut(check), FadeOut(scanim.mobject[0]))
last_block = block
class AmbientErrorCorrection6(AmbientErrorCorrection):
CONFIG = {
"N": 6
}
class AmbientErrorCorrection4(AmbientErrorCorrection):
CONFIG = {
"N": 4,
"bit_grid_height": 5,
}
class ImpossibleToReasonable(Scene):
def construct(self):
group = VGroup(
TextMobject("Impossible"),
Vector(RIGHT, color=GREY_B),
TextMobject("Utterly reasonable"),
)
group.arrange(RIGHT)
group.scale(1.5)
group.to_edge(UP)
line = Line(LEFT, RIGHT)
line.set_width(FRAME_WIDTH)
line.set_stroke(GREY, 2)
line.next_to(group, DOWN, SMALL_BUFF)
self.add(line)
self.play(FadeIn(group[0], 0.5 * UP))
self.wait()
self.play(
GrowArrow(group[1]),
FadeIn(group[2], LEFT),
)
self.wait()
class HammingAtBell(Scene):
def construct(self):
# Setup
hamming_image = ImageMobject("Richard_Hamming")
hamming_name = TextMobject("Richard Hamming")
hamming_name.match_width(hamming_image)
hamming_name.next_to(hamming_image, DOWN, MED_SMALL_BUFF)
hamming = Group(hamming_image, hamming_name)
hamming.to_corner(DR)
hamming.shift(2 * LEFT)
bell_logo = ImageMobject("BellSystemLogo")
bell_logo.set_height(3)
bell_logo.next_to(hamming, LEFT, buff=2)
bell_logo.to_edge(UP)
bell_logo_outline = SVGMobject("BellSystemLogo")
bell_logo_outline.match_height(bell_logo)
bell_logo_outline.set_stroke(GREY_B, 1)
bell_logo_outline.set_fill(BLACK, 0)
bell_logo_outline.move_to(bell_logo)
punchcard = SVGMobject("punchcard")
punchcard.set_stroke(width=0)
punchcard.set_fill(GREY_B, 1)
punchcard.next_to(bell_logo, DOWN, LARGE_BUFF)
punchcard.remove(*punchcard[23:])
years = TextMobject("1940s")
years.scale(2)
years.to_edge(UP)
# Introductions
self.play(Write(years))
self.play(FadeIn(hamming[0], RIGHT))
self.play(Write(hamming[1]))
self.play(
FadeOut(years),
ShowCreationThenFadeOut(bell_logo_outline, lag_ratio=0.1, run_time=4),
FadeIn(bell_logo, run_time=3),
)
self.play(
FadeIn(punchcard[0]),
Write(punchcard[1:], lag_ratio=0.5, run_time=4)
)
self.add(punchcard)
self.wait()
# Zap some bits
random.seed(3)
bits = random.sample(list(punchcard[1:]), 4)
for bit in bits:
bit.generate_target()
bit.target.set_color(RED)
if random.random() < 0.5:
bit.target.stretch(0.2, 1, about_edge=DOWN)
else:
bit.target.shift(1.2 * bit.get_width() * LEFT)
self.play(
MoveToTarget(bit),
zap_anim(bit)
)
self.wait()
# Frustration
curse = TextMobject("\\$*@\\#*!!?!")[0]
curse.set_color(RED)
curse.next_to(hamming, UP)
self.play(ShowIncreasingSubsets(curse))
self.wait()
class MultiplePerspectives(Scene):
def construct(self):
# Background
background = VGroup(*[
Rectangle().set_fill(color, 1)
for color in [GREY_E, BLACK, GREY_E]
])
background.set_stroke(width=0)
background.arrange(RIGHT, buff=0)
background.set_height(FRAME_HEIGHT)
background.set_width(FRAME_WIDTH, stretch=True)
self.add(background)
# Names
names = VGroup(
TextMobject("Parity checks"),
TextMobject("Xor of indices"),
TextMobject("Matrix"),
)
names.set_height(0.6)
for name, rect in zip(names, background):
name.match_x(rect)
names[0].shift(SMALL_BUFF * DOWN)
names.to_edge(DOWN, buff=1)
# Objects
parity_groups = VGroup()
for n in range(4):
pg = VGroup(*[Square() for x in range(16)])
pg.arrange_in_grid(4, 4, buff=0)
pg.set_height(0.7)
pg.set_stroke(GREY_A, 2)
get_bit_n_subgroup(pg, n).set_fill(BLUE, 0.8)
parity_groups.add(pg)
parity_groups.arrange_in_grid(2, 2)
code = ImageMobject("Hamming_Code_Snippet")
ints = list(random.sample(list(range(16)), 4))
ints.sort()
xor_sum = reduce(op.xor, ints)
bits = [int_to_bit_string(n, n_bits=4) for n in [*ints, xor_sum]]
column = Group(*map(TextMobject, bits))
column.arrange(DOWN, SMALL_BUFF)
column[-1].set_color(YELLOW)
column[-1].shift(MED_SMALL_BUFF * DOWN)
line = Line(LEFT, RIGHT)
line.set_stroke(GREY_B)
line.set_width(column.get_width() + 0.75)
line.move_to(column[-2:], RIGHT)
xor = get_xor()
xor.next_to(line, UP, SMALL_BUFF, LEFT)
column.add(line, xor)
code.set_width(2 * column.get_width())
code.next_to(column, UP)
column.add(code)
matrix = IntegerMatrix(
[
[1, 1, 0, 1],
[1, 0, 1, 1],
[1, 0, 0, 0],
[0, 1, 1, 1],
[0, 1, 0, 0],
[0, 0, 1, 0],
[0, 0, 0, 1],
],
v_buff=0.6,
h_buff=0.75,
)
objs = Group(parity_groups, column, matrix)
for name, obj in zip(names, objs):
obj.match_width(names[0])
obj.next_to(name, UP, LARGE_BUFF)
matrix.scale(0.7, about_edge=DOWN)
# Introduce
anims = []
for name, obj in zip(names, objs):
anims.append(AnimationGroup(
FadeIn(name, 0.25 * UP),
FadeIn(obj, lag_ratio=0, run_time=2),
))
self.play(LaggedStart(*anims, lag_ratio=0.4))
self.wait()
for name, obj in zip(names, objs):
obj.add(name)
self.add(background[0])
self.play(
FadeOut(background),
objs[0].set_x, 0,
FadeOut(objs[1], 5 * RIGHT),
FadeOut(objs[2], 2 * RIGHT),
run_time=2
)
self.wait()
class SetupSixteenBitExample(Scene):
def construct(self):
# Title
title = TextMobject("Reinventing Hamming Codes")
title.scale(1.5)
title.to_edge(UP)
title.set_color(BLUE)
self.play(Write(title))
# Simple but not too simple
block = get_bit_grid(4, 4, bits=string_to_bits(":)"))
block.move_to(0.5 * DOWN)
top_row = block[:4]
top_row.save_state()
top_row.center()
simple_words = TextMobject("Simple\\\\", "but not too\\\\simple")
simple_words.scale(1.5)
simple_words.next_to(block[4:12], LEFT, buff=LARGE_BUFF)
top_simp = simple_words[0]
top_simp.save_state()
top_simp.set_y(0)
self.play(
FadeIn(top_simp),
ShowIncreasingSubsets(top_row),
)
self.play(
Restore(top_simp),
Restore(top_row),
FadeIn(simple_words[1:]),
LaggedStartMap(FadeIn, block[4:])
)
self.add(block)
self.add(simple_words)
boxes = get_bit_grid_boxes(block, color=GREEN)
bits_word = TextMobject("bits")
bits_word.set_height(0.7)
bits_word.next_to(boxes, LEFT, buff=LARGE_BUFF)
counter = Integer(0, edge_to_fix=RIGHT)
counter.match_height(bits_word)
counter.next_to(bits_word, LEFT, buff=0.35)
bit_count_group = VGroup(bits_word, counter)
bit_count_group.set_color(YELLOW)
self.play(
FadeOut(simple_words),
FadeIn(bits_word),
UpdateFromAlphaFunc(
counter,
lambda m, a: m.set_fill(opacity=a)
),
UpdateFromFunc(
counter,
lambda c: c.set_value(len(boxes))
),
ShowIncreasingSubsets(boxes, run_time=2)
)
# Enumerate bits
block.generate_target()
block.target.space_out_submobjects(1.5)
block.target.center()
new_boxes = get_bit_grid_boxes(block.target, color=GREY_B)
h_buff = get_norm(block.target[0].get_center() - block.target[4].get_center())
for box in new_boxes:
box.set_height(h_buff, stretch=True)
p_labels = VGroup()
for n, box in enumerate(new_boxes):
label = Integer(n)
label.set_height(0.25)
label.set_color(YELLOW)
label.move_to(box, DR)
label.shift(SMALL_BUFF * UL)
p_labels.add(label)
self.play(
MoveToTarget(block),
Transform(boxes, new_boxes),
FadeOut(title, UP),
FadeOut(VGroup(counter, bits_word), LEFT),
)
self.play(FadeIn(p_labels, lag_ratio=0.07, run_time=3))
self.wait()
# Data bits
r_boxes = VGroup(*[boxes[2**n] for n in range(4)]).copy()
r_bits = VGroup(*[block[2**n] for n in range(4)])
d_bits = VGroup(*[b for b in block if b not in r_bits])
d_label = TextMobject("12 bits\\\\of data")
d_label.set_height(1.5)
d_label.next_to(boxes, RIGHT, aligned_edge=UP, buff=LARGE_BUFF)
d_label.set_color(BLUE)
d_lines = VGroup(*[
Line(
d_label.get_left(), bit.get_center(),
color=(TEAL if get_bit_mob_value(bit) else YELLOW)
)
for bit in d_bits
])
d_lines.set_stroke(width=1)
self.play(
FadeOut(r_bits),
d_bits.set_color, BLUE,
FadeIn(d_label),
p_labels.set_color, WHITE,
)
self.play(LaggedStartMap(ShowCreationThenFadeOut, d_lines))
self.wait()
# Redundancy bits
r_label = TextMobject("4 bits for\\\\", "redundancy")
r_label.match_height(d_label)
r_label.next_to(boxes, LEFT, aligned_edge=UP, buff=MED_LARGE_BUFF)
r_label.set_color(GREEN)
r_boxes.set_fill(GREEN, 0.5)
self.add(r_boxes, p_labels)
self.play(
FadeIn(r_label, 0.2 * RIGHT),
LaggedStartMap(FadeIn, r_boxes, run_time=2)
)
self.wait()
# Can't cram in copies
d_bit_copies = d_bits.copy()
d_bit_copies.generate_target()
for n, box in enumerate(r_boxes):
group = d_bit_copies.target[3 * n:3 * (n + 1)]
group.arrange_in_grid(2, 2, buff=SMALL_BUFF)
group.set_width(0.4 * box.get_width())
group.move_to(box)
group.set_color(YELLOW)
self.play(ShowCreationThenDestruction(Underline(r_label[1])))
self.play(MoveToTarget(d_bit_copies, lag_ratio=0.1, run_time=4, rate_func=linear))
self.wait()
self.play(
UpdateFromAlphaFunc(
d_bit_copies, lambda m, a: m.shift(0.03 * np.sin(7 * TAU * a) * RIGHT).fade(a),
remover=True
)
)
# Set true redundancy bits
highlights = boxes.copy()
highlights.set_stroke(YELLOW, 6)
for n in range(4):
h_group = get_bit_n_subgroup(highlights, n)
bit = r_bits[n]
if get_bit_mob_value(bit) == 1:
toggle_bit(bit)
self.play(
FadeIn(h_group, lag_ratio=0.2),
FadeIn(bit)
)
anims = [FadeOut(h_group)]
if n in [2, 3]:
anims.append(toggle_bit_anim(bit))
self.play(*anims)
# Might expect them to come at the end
movers = [d_bits, r_bits]
for mover in movers:
mover.save_state()
mover.generate_target()
for b1, b2 in zip(it.chain(d_bits.target, r_bits.target), block):
b1.move_to(b2)
for box, bit in zip(r_boxes, r_bits):
box.bit = bit
box.add_updater(lambda m: m.move_to(m.bit))
self.add(*r_boxes)
self.play(
*[
MoveToTarget(mover, lag_ratio=0.1, run_time=3, path_arc=20 * DEGREES)
for mover in movers
],
)
self.wait()
self.play(*[
Restore(mover, lag_ratio=0.1, run_time=3, path_arc=20 * DEGREES)
for mover in movers
])
r_boxes.clear_updaters()
self.wait()
power_of_2_rects = VGroup(*[
SurroundingRectangle(p_labels[2**n])
for n in range(4)
])
self.play(LaggedStartMap(ShowCreationThenFadeOut, power_of_2_rects))
# Correct to 11 bits
cross = Cross(d_label[0][:2])
c_label = TextMobject("Er...11 bits")
c_label.match_width(d_label)
c_label.next_to(d_label, DOWN, buff=MED_LARGE_BUFF)
c_label.set_color(RED)
q_mark = TexMobject("?")
q_mark.replace(block[0])
q_mark.set_color(RED)
self.play(ShowCreation(cross))
self.play(Write(c_label))
self.wait()
self.play(
FadeOut(block[0]),
Write(q_mark),
)
randy = Randolph()
randy.to_corner(DL)
self.play(
VFadeIn(randy),
randy.change, "confused", q_mark
)
self.play(Blink(randy))
self.wait(2)
self.play(Blink(randy))
self.wait()
def old_functions(self):
# Show redundancy and message
redundancy_words = TextMobject("Separate some for\\\\redundancy")
redundancy_words.next_to(block[-4:], LEFT, buff=LARGE_BUFF, aligned_edge=UP)
redundancy_words.set_color(BLUE)
vect = redundancy_words.get_center() - bit_count_group.get_center()
self.play(
FadeOut(bit_count_group, vect),
FadeIn(redundancy_words, -vect),
ApplyMethod(boxes[-4:].set_color, BLUE, lag_ratio=0.2),
ApplyMethod(block[-4:].set_color, BLUE, lag_ratio=0.2),
)
self.wait()
message_words = TextMobject("Leave the rest\\\\for a message")
message_words.next_to(block[:-4], LEFT)
message_words.match_x(redundancy_words)
message_words.set_color(YELLOW)
message_words.save_state()
message_words.replace(redundancy_words)
message_words.set_opacity(0)
self.play(
Restore(message_words),
boxes[:-4].set_color, YELLOW,
)
for x in range(10):
bits = list(block[:-4])
random.shuffle(bits)
for bit in bits:
if random.random() < 0.5:
toggle_bit(bit)
self.wait(0.2 / 12)
self.wait()
# Show correction
flipper = random.choice(block)
scanim = scan_anim(
block.get_corner(DR) + 2 * RIGHT, block,
final_stroke_width=0.5,
lag_factor=1,
)
self.play(
zap_anim(flipper),
toggle_bit_anim(flipper, target_color=RED)
)
self.wait()
self.play(scanim)
self.play(focus_scan_anim_lines(scanim, flipper.get_center()))
self.play(
toggle_bit_anim(flipper, target_color=WHITE),
FadeOut(scanim.mobject)
)
class SenderReceiverDynamic(PiCreatureScene):
def construct(self):
# Sender and receiver
sender_word = TextMobject("Sender")
receiver_word = TextMobject("Receiver")
words = VGroup(sender_word, receiver_word)
words.scale(1.5)
words.set_y(-2)
sender_word.to_edge(LEFT)
receiver_word.to_edge(RIGHT)
sender, receiver = pis = self.pi_creatures
for pi, word in zip(pis, words):
pi.set_height(2)
pi.next_to(word, UP)
self.clear()
for pi in pis:
self.play(
VFadeIn(pi),
pi.change, "pondering", ORIGIN,
)
for word in words:
self.play(Write(word, run_time=1))
self.wait()
# Message
block = get_bit_grid(4, 4)
block.set_height(1.5)
block.next_to(pis[0].get_corner(UR), UR)
self.play(
sender.change, "raise_right_hand",
FadeIn(block, DOWN, lag_ratio=0.01),
)
self.add(sender, block)
block.save_state()
self.play(
block.move_to, sender,
block.scale, 0.5,
sender.change, "gracious", sender,
)
self.play(LaggedStart(
ApplyMethod(block[1].set_color, GREEN),
toggle_bit_anim(block[2], target_color=GREEN),
ApplyMethod(block[4].set_color, GREEN),
toggle_bit_anim(block[8], target_color=GREEN),
))
self.wait()
block.generate_target()
block.target.scale(2)
block.target.next_to(receiver, UL)
self.play(
MoveToTarget(block, run_time=3, path_arc=-30 * DEGREES),
receiver.change, "tease", receiver.get_corner(UL) + UP,
sender.change, "happy", receiver.get_corner(UL) + UP,
)
self.play(scan_anim(receiver.get_corner(UL), block, lag_factor=1, show_robot=False))
self.wait()
# Replace with machines
underlines = VGroup(*[Underline(word) for word in words])
underlines.set_color(YELLOW)
servers = VGroup(*[SVGMobject("server_stack") for x in range(2)])
servers.set_color(GREY)
servers.set_stroke(BLACK, 2)
servers.set_gloss(0.5)
for server, pi in zip(servers, pis):
server.move_to(pi)
self.play(
LaggedStartMap(ShowCreationThenFadeOut, underlines, lag_ratio=0.7),
*[
ApplyMethod(pi.change, "thinking", pi)
for pi in pis
],
)
self.wait()
self.play(
*it.chain(*[
[
pi.change, "horrified",
pi.shift, 2 * UP,
pi.set_opacity, 0
]
for pi in pis
]),
FadeIn(servers, 2 * DOWN)
)
self.remove(pis)
bits_copy = block.copy()
bits_copy.arrange(RIGHT, buff=SMALL_BUFF)
bits_copy.set_width(0.7 * servers[1].get_width())
bits_copy.move_to(servers[1], LEFT)
bits_copy.shift(SMALL_BUFF * RIGHT)
self.play(
LaggedStart(*[
TransformFromCopy(b1, b2)
for b1, b2 in zip(block, bits_copy)
], run_time=3),
)
# Sent and received
sent_block = block.copy()
sent_block.next_to(servers[0], UR)
sent_block.match_y(block)
wire = VGroup(
SurroundingRectangle(sent_block),
Line(sent_block.get_right(), block.get_left(), buff=SMALL_BUFF / 2),
SurroundingRectangle(block),
)
wire.set_stroke(GREY, 2)
noise_word = TextMobject("Potential noise")
noise_word.scale(0.75)
noise_word.set_color(RED)
noise_word.next_to(wire[1], UP, SMALL_BUFF)
small_words = VGroup(
TextMobject("sent"),
TextMobject("received"),
)
for word, mob in zip(small_words, [sent_block, block]):
word.next_to(mob, UP, buff=0.35)
self.play(
ShowCreation(wire[0]),
FadeIn(small_words[0], 0.5 * DOWN),
FadeIn(sent_block),
)
self.play(
Transform(sent_block.copy(), block, remover=True, lag_ratio=0.01),
ShowCreation(wire[1]),
FadeIn(noise_word, lag_ratio=0.1)
)
self.play(
ShowCreation(wire[2]),
FadeIn(small_words[1], 0.5 * DOWN)
)
# Past and future
cross_lines = VGroup(*[
Line(
word.get_left(), word.get_right(),
stroke_width=5,
stroke_color=RED,
)
for word in words
])
time_words = VGroup(
TextMobject("Past"),
TextMobject("Future"),
)
time_words.set_color(BLUE)
for w1, w2 in zip(words, time_words):
w2.match_height(w1)
w2.next_to(w1, DOWN)
self.play(
LaggedStartMap(ShowCreation, cross_lines, lag_ratio=0.7),
LaggedStartMap(ApplyMethod, words, lambda m: (m.set_opacity, 0.75), lag_ratio=0.7),
run_time=1,
)
self.play(
LaggedStartMap(FadeIn, time_words, lambda m: (m, UP), lag_ratio=0.3)
)
self.wait(2)
def create_pi_creatures(self):
return VGroup(Randolph(), Mortimer())
class ParityChecks(Scene):
def construct(self):
# Show detection
block = get_bit_grid(4, 4, bits=string_to_bits(":)"))
block.move_to(3 * LEFT)
point = block.get_corner(DR) + 2 * RIGHT + UP
scanim = scan_anim(point, block, final_stroke_width=0.5, run_time=2, lag_factor=2)
lines, robot = scanim.mobject
detection_words = TextMobject("Error detected!")
detection_words.set_color(RED)
detection_subwords = TextMobject("But I have no idea where!")
detection_words.next_to(robot.get_top(), UR, SMALL_BUFF)
detection_subwords.move_to(detection_words, DL)
self.add(block)
self.play(scanim)
self.play(
GrowFromPoint(detection_words, robot.get_top())
)
self.wait()
self.play(
detection_words.shift, 0.75 * UP,
FadeIn(detection_subwords, 0.5 * DOWN),
)
self.wait()
title = TextMobject("Parity Check")[0]
title.set_color(YELLOW)
title.set_stroke(BLACK, 3, background=True)
title.add(Underline(title).shift(0.05 * UP))
title.scale(1.5)
title.to_edge(UP)
self.play(Write(title))
self.wait()
self.play(
LaggedStart(
FadeOut(scanim.mobject),
FadeOut(detection_words),
FadeOut(detection_subwords),
),
block.move_to, DOWN,
)
# Single bit vs. message
pb_rect = SurroundingRectangle(block[0])
pb_rect.set_stroke(GREEN, 3)
ms_rect = SurroundingRectangle(block)
ms_blob = Polygon(
pb_rect.get_corner(UR) + 0.05 * RIGHT,
ms_rect.get_corner(UR),
ms_rect.get_corner(DR),
ms_rect.get_corner(DL),
pb_rect.get_corner(DL) + 0.05 * DOWN,
pb_rect.get_corner(DR) + 0.05 * DR,
)
ms_blob.set_stroke(BLUE, 3)
pb_words = TextMobject("Reserve one special bit")
pb_words.next_to(pb_rect, LEFT)
pb_words.match_color(pb_rect)
ms_words = TextMobject("The rest carry\\\\a message", alignment="")
ms_words.next_to(ms_blob, RIGHT, aligned_edge=UP)
ms_words.align_to(pb_words, UP)
ms_words.match_color(ms_blob)
self.play(
FadeIn(pb_words),
ShowCreation(pb_rect),
)
self.wait()
self.play(
FadeIn(ms_words),
ShowCreation(ms_blob),
)
# Wave of flips
k = 3
for n in range(1, len(block) + k):
if n < len(block):
toggle_bit(block[n])
if 1 <= n - k < len(block):
toggle_bit(block[n - k])
self.wait(0.05)
# Count 1's
number_ones_label = TextMobject("\\# ", "of 1's: ")
number_ones_label.set_height(0.7)
number_ones_label.to_edge(UP)
number_ones_label.shift(LEFT)
one_rects = get_one_rects(block)
one_counter = get_ones_counter(number_ones_label, one_rects)
self.play(
FadeIn(number_ones_label, DOWN),
FadeOut(title, UP),
)
self.add(one_counter)
self.play(ShowIncreasingSubsets(one_rects, run_time=1, rate_func=bezier([0, 0, 1, 1])))
self.wait()
# Show need to flip
want_even_label = TextMobject("Want this\\\\to be even")
want_even_label.next_to(one_counter, RIGHT, buff=1.5)
want_even_arrow = Arrow(
want_even_label.get_left(), one_counter.get_right()
)
want_even_label.shift_onto_screen()
self.play(
GrowArrow(want_even_arrow),
FadeIn(want_even_label),
)
self.wait()
self.play(
LaggedStartMap(
Indicate, one_rects,
scale_factor=1.1,
color=RED,
lag_ratio=0.2,
run_time=2,
)
)
self.wait()
pb = block[0]
pb_center = pb.get_center()
self.play(pb.next_to, pb_words, DOWN)
self.play(toggle_bit_anim(pb))
self.play(pb.move_to, pb_center)
one_rects.become(get_one_rects(block))
self.play(DrawBorderThenFill(one_rects[0]))
# Show case with no need to flip
self.play(
FadeOut(block),
FadeOut(one_rects),
FadeOut(one_counter),
)
new_block = get_bit_grid(4, 4, bits=string_to_bits("<3"))
new_block.replace(block)
block = new_block
self.play(ShowIncreasingSubsets(block))
one_rects = get_one_rects(block)
one_counter = get_ones_counter(number_ones_label, one_rects)
self.add(one_counter)
self.play(ShowIncreasingSubsets(one_rects))
check = Checkmark()
check.scale(2)
check.next_to(pb, UP)
self.play(Write(check, run_time=0.5))
self.play(FadeOut(check))
self.wait()
# Sender and receiver
self.play(LaggedStart(*map(FadeOut, [
number_ones_label, one_counter,
want_even_arrow, want_even_label,
pb_words, pb_rect,
ms_words, ms_blob,
one_rects
])))
pis, names = get_sender_and_receiver()
sender, receiver = pis
block.generate_target()
block.target.scale(0.5)
block.target.next_to(sender, UR)
r_block = block.target.copy()
r_block.next_to(receiver, UL)
n_block = r_block.copy()
n_block.move_to(VGroup(block.target, r_block))
arrows = VGroup(
Arrow(block.target.get_right(), n_block.get_left()),
Arrow(n_block.get_right(), r_block.get_left()),
)
noise_word = TextMobject("Noise")
noise_arrow = Vector(0.7 * DOWN)
noise_arrow.next_to(n_block, UP)
noise_word.next_to(noise_arrow, UP)
noise_label = VGroup(noise_word, noise_arrow)
noise_label.set_color(RED)
flipper_index = 7
self.play(
ApplyMethod(sender.change, "raise_right_hand", block.target),
VFadeIn(sender),
FadeIn(names),
FadeIn(receiver),
MoveToTarget(block),
)
self.play(
TransformFromCopy(block, n_block),
GrowArrow(arrows[0]),
FadeIn(noise_label),
)
self.play(
toggle_bit_anim(n_block[flipper_index], target_color=RED),
zap_anim(n_block[flipper_index]),
)
toggle_bit(r_block[flipper_index])
self.play(
TransformFromCopy(n_block, r_block),
GrowArrow(arrows[1]),
receiver.change, "pondering", r_block,
)
# Recount the 1's
blocks = (block, r_block)
one_rects_pair = VGroup(*[get_one_rects(b, buff=0.05) for b in blocks])
label_pair = VGroup(*[
TextMobject("\\#", " 1's:").next_to(b, UP, buff=MED_LARGE_BUFF)
for b in blocks
])
one_counter_pair = VGroup(*[
get_ones_counter(label, rect, buff=MED_SMALL_BUFF)
for label, rect in zip(label_pair, one_rects_pair)
])
self.add(label_pair, one_counter_pair)
self.play(*[
ShowIncreasingSubsets(group)
for group in one_rects_pair
])
self.wait()
bangs = TexMobject("!!!")
bangs.set_color(RED)
bangs.next_to(receiver, UP)
self.play(
FadeIn(bangs, DOWN),
receiver.change, "horrified", r_block,
)
self.play(Blink(receiver))
self.wait()
# Define parity and parity bit
parity_rects = VGroup(
SurroundingRectangle(VGroup(block, one_counter_pair[0]), buff=MED_SMALL_BUFF),
SurroundingRectangle(VGroup(r_block, one_counter_pair[1]), buff=MED_SMALL_BUFF),
)
parity_rects[0].set_stroke(BLUE, 2)
parity_rects[1].set_stroke(RED, 2)
parity_labels = VGroup(*[
TextMobject(
"Parity: ", word,
tex_to_color_map={word: color}
).next_to(rect, UP)
for word, color, rect in zip(
["Even", "Odd"],
[BLUE, RED],
parity_rects,
)
])
for label, rect in zip(parity_labels, parity_rects):
self.play(
FadeIn(label, DOWN),
ShowCreation(rect),
)
self.play(LaggedStart(*[
ShowCreationThenFadeOut(Underline(label[0], color=YELLOW))
for label in parity_labels
], lag_ratio=0.3))
self.wait()
self.play(Blink(sender))
self.play(Blink(receiver))
for bit, label, rect in zip([0, 1], parity_labels, parity_rects):
bit_mob = get_bit_grid(1, 1, bits=[bit])[0]
bit_mob.match_height(label[1])
bit_mob.match_color(label[1])
bit_mob.move_to(label[1], LEFT)
x_shift = (rect.get_center() - VGroup(label[0], bit_mob).get_center()) * RIGHT
bit_mob.shift(x_shift)
self.play(
label[1].replace, bit_mob, {"stretch": True},
label[1].set_opacity, 0,
FadeIn(bit_mob),
label[0].shift, x_shift,
)
label.replace_submobject(1, bit_mob)
pb_rect = SurroundingRectangle(block[0], buff=0.05)
pb_rect.set_stroke(GREEN, 3)
pb_label = TextMobject("Parity bit")
pb_label.move_to(pb_rect)
pb_label.to_edge(LEFT, buff=MED_SMALL_BUFF)
pb_label.shift(UP)
pb_label.match_color(pb_rect)
pb_arrow = Arrow(
pb_label.get_bottom() + SMALL_BUFF * DOWN,
pb_rect.get_left(),
buff=SMALL_BUFF,
fill_color=GREEN
)
self.play(
Write(pb_label, run_time=1),
GrowArrow(pb_arrow),
ShowCreation(pb_rect),
)
for color in [RED, BLUE]:
self.play(
toggle_bit_anim(block[0]),
toggle_bit_anim(parity_labels[0][1], target_color=color),
parity_rects[0].set_color, color,
)
self.wait()
parity_descriptors = VGroup(
parity_rects, parity_labels,
pb_rect, pb_label, pb_arrow,
)
self.play(FadeOut(parity_descriptors, lag_ratio=0.1))
# More than 1 error
new_filpper_indices = [8, 13]
n_flippers = VGroup(*[n_block[fi] for fi in new_filpper_indices])
r_flippers = VGroup(*[r_block[fi] for fi in new_filpper_indices])
for bit in r_flippers:
toggle_bit(bit)
new_one_rects_pair = VGroup(*[get_one_rects(b, buff=0.05) for b in blocks])
new_one_counter_pair = VGroup(*[
get_ones_counter(label, rect, buff=MED_SMALL_BUFF)
for label, rect in zip(label_pair, new_one_rects_pair)
])
for bit in r_flippers:
toggle_bit(bit)
self.play(
LaggedStartMap(
toggle_bit_anim, n_flippers,
target_color=RED,
),
LaggedStartMap(toggle_bit_anim, r_flippers),
LaggedStart(*map(zap_anim, n_flippers)),
receiver.change, "maybe",
FadeOut(one_rects_pair[1]),
FadeOut(one_counter_pair[1]),
)
self.remove(one_rects_pair, one_counter_pair)
one_rects_pair = new_one_rects_pair
one_counter_pair = new_one_counter_pair
self.add(one_rects_pair, one_counter_pair)
self.play(
ShowIncreasingSubsets(one_rects_pair[1])
)
for x in range(2):
self.play(Blink(receiver))
self.play(Blink(sender))
self.wait()
# Even number of errors
self.play(
FadeOut(one_rects_pair[1]),
FadeOut(one_counter_pair[1]),
FadeOut(bangs),
receiver.change, "pondering", r_block,
)
temp_rect = SurroundingRectangle(
n_flippers[1],
buff=0.05,
color=GREEN,
)
temp_rect.flip()
self.play(
ShowCreationThenFadeOut(temp_rect),
toggle_bit_anim(n_flippers[1], target_color=WHITE),
toggle_bit_anim(r_flippers[1]),
)
self.wait()
self.play(Blink(receiver))
one_counter_pair.set_opacity(1)
self.add(one_counter_pair)
one_rects_pair[1].set_submobjects(
get_one_rects(r_block, buff=0.05).submobjects
)
self.play(
ShowIncreasingSubsets(one_rects_pair[1])
)
self.play(receiver.change, "hooray", r_block)
self.wait()
self.play(receiver.change, "erm")
self.wait()
class ChangeAnywhereToOneBit(Scene):
CONFIG = {
"random_seed": 3,
}
def construct(self):
title = VGroup(
TextMobject("Change anywhere"),
Vector(RIGHT),
TextMobject("One bit of information"),
)
title.arrange(RIGHT)
title.set_width(FRAME_WIDTH - 1)
title.to_edge(UP)
self.add(title)
grid = get_bit_grid(4, 4)
grid.set_height(4)
grid.match_x(title[0])
grid.set_y(-1)
self.add(grid)
one_rects = get_one_rects(grid)
self.add(one_rects)
parity_words = VGroup(
TextMobject("Even \\# of 1s", color=BLUE_B),
TextMobject("Odd \\# of 1s", color=TEAL_D),
)
parity_words.scale(1.5)
parity_words.arrange(DOWN, buff=MED_LARGE_BUFF, aligned_edge=RIGHT)
parity_words.match_x(title[2])
parity_words.match_y(grid)
self.add(parity_words)
def get_parity_rect(n, words=parity_words):
return SurroundingRectangle(words[n % 2])
p_rect = get_parity_rect(len(one_rects))
self.add(p_rect)
# Random changes
for x in range(10):
bit = random.choice(grid)
self.play(toggle_bit_anim(bit))
one_rects.set_submobjects(get_one_rects(grid))
p_rect.become(get_parity_rect(len(one_rects)))
self.wait()
class OddNumberCountTo101(Scene):
def construct(self):
group = VGroup(
*[Integer(2 * n + 1) for n in range(1, 50)],
)
group.set_color(RED)
group.scale(2)
for mob in group[:2]:
self.add(mob)
self.wait()
self.remove(mob)
self.play(ShowSubmobjectsOneByOne(group[2:]), run_time=6, rate_func=bezier([0, 0, 1, 1]))
self.wait()
class ComplainAboutParityCheckWeakness(TeacherStudentsScene):
def construct(self):
self.embed()
self.screen.set_fill(BLACK, 1)
self.add(self.screen)
self.play(
PiCreatureSays(
self.students[1],
"Wait, it fails\\\\for two flips?",
target_mode="sassy",
bubble_kwargs={
"height": 3,
"width": 3,
}
),
self.teacher.change, "guilty",
)
self.wait()
self.play(
PiCreatureSays(
self.students[2], "Weak!",
target_mode="angry",
bubble_kwargs={"direction": LEFT}
),
self.students[0].change, "hesitant"
)
self.wait()
self.play(self.teacher.change, "tease")
self.wait()
self.change_student_modes(
"thinking", "pondering", "pondering",
look_at_arg=self.screen,
added_anims=[
FadeOut(self.students[1].bubble),
FadeOut(self.students[1].bubble.content),
FadeOut(self.students[2].bubble),
FadeOut(self.students[2].bubble.content),
]
)
self.wait()
class ArrayOfValidMessages(Scene):
def construct(self):
# Messages
title = TextMobject("All possible messages")
title.to_edge(UP)
nr = 22
nc = 46
dots = VGroup(*[Dot() for x in range(nr * nc)])
dots.arrange_in_grid(nr, nc)
dots.set_color(GREY_C)
dots.set_height(6)
dots.to_edge(DOWN)
shuffled_dots = dots.copy()
shuffled_dots.shuffle()
self.add(title)
self.play(Write(shuffled_dots, remover=True, run_time=6, lag_ratio=5 / len(dots)))
self.add(dots)
self.wait()
# Valid messages
subset = TexMobject("\\subset")
subset.set_height(0.4)
subset.to_edge(UP)
valid_label = TextMobject("Valid messages")
valid_label.set_color(YELLOW)
valid_label.next_to(subset, LEFT)
valid_dots = VGroup()
for row in range(0, nr, 3):
for col in range(0, nc, 3):
valid_dot = dots[row * nc + col]
valid_dot.generate_target()
valid_dot.target.scale(2)
valid_dot.target.set_color(YELLOW)
valid_dots.add(valid_dot)
self.play(
LaggedStartMap(MoveToTarget, valid_dots, run_time=3),
Write(subset),
FadeIn(valid_label, LEFT),
title.next_to, subset, RIGHT,
)
self.wait()
# Words analogy
example_words = VGroup(
TextMobject("Hello world", color=YELLOW),
TextMobject("Helho world", color=GREY_B),
)
example_words.scale(1.25)
index = 12 * nc + 21
example_dots = VGroup(dots[index], dots[index + 1]).copy()
example_groups = VGroup()
for word, dot in zip(example_words, example_dots):
arrow = Vector(0.7 * DOWN)
arrow.next_to(dot, UP, SMALL_BUFF)
word.next_to(arrow, UP, SMALL_BUFF)
example_group = VGroup(word, arrow, dot)
example_group.unlock_triangulation()
example_groups.add(example_group)
fade_rect = SurroundingRectangle(dots)
fade_rect.set_stroke(BLACK, 0)
fade_rect.set_fill(BLACK, 0.7)
self.play(
FadeIn(fade_rect),
FadeIn(example_groups[0])
)
self.wait()
self.play(
zap_anim(example_words[0][0][3:5]),
Transform(*example_groups),
)
self.wait()
self.play(FadeOut(example_groups[0]), FadeOut(fade_rect))
# Corrections
valid_centers = [vd.get_center() for vd in valid_dots]
lines = VGroup()
for dot in dots:
dc = dot.get_center()
norms = [get_norm(dc - vc) for vc in valid_centers]
line = Line(dc, valid_centers[np.argmin(norms)])
line.set_stroke(WHITE, 1)
lines.add(line)
shuffled_lines = VGroup(*lines)
shuffled_lines.shuffle()
self.play(ShowCreation(shuffled_lines, lag_ratio=10 / len(lines), run_time=5))
self.wait()
# Mandering path between valid messages
self.add(fade_rect, valid_dots)
self.play(FadeIn(fade_rect))
path = [RIGHT, UP, UP, RIGHT, RIGHT, RIGHT, UP, UP, RIGHT, RIGHT, DOWN]
dist = get_norm(dots[1].get_center() - dots[0].get_center())
curr = dots[index].get_center()
arrows = VGroup()
for vect in path:
new = curr + dist * vect
arrows.add(Arrow(curr, new, buff=0, fill_color=RED))
curr = new
for arrow in arrows:
self.play(GrowArrow(arrow), run_time=0.5)
self.wait()
class RobustForLessThanNErrors(Scene):
def construct(self):
words = TextMobject(
"Robust for ", "$\\le N$", " errors",
)
words.to_edge(UP)
words[1].set_color(YELLOW)
words[2].shift(0.15 * RIGHT)
N = words[1][-1]
num = Integer(1)
num.set_color(YELLOW)
num.move_to(N, LEFT)
num.set_value(1)
self.play(Write(words, run_time=2))
self.wait()
self.remove(N)
self.add(num)
self.play(ChangeDecimalToValue(num, 20, run_time=3))
self.remove(num)
self.add(N)
self.wait()
class TwentyQuestions(Scene):
def construct(self):
# Hamming's insight
bits = [
1, 0, 0, 1,
0, 1, 0, 0,
0, 1, 0, 1,
1, 0, 1, 1,
]
block = get_bit_grid(4, 4, bits=bits)
hamming = ImageMobject("Richard_Hamming")
hamming.set_height(3)
hamming.to_corner(DL)
bulb = Lightbulb()
bulb.next_to(hamming.get_corner(UR), UP)
self.add(block)
self.play(FadeIn(hamming, 0.5 * RIGHT))
self.play(Write(bulb, run_time=1, stroke_width=5))
self.wait()
# Preview parity checks
parity_word = TextMobject("Parity check")
parity_word.set_height(0.7)
parity_word.set_color(BLUE)
parity_word.move_to(block, UP)
parity_word.set_opacity(0)
back_rects = VGroup(*[
SurroundingRectangle(bit, buff=MED_SMALL_BUFF / 2).scale(0)
for bit in get_bit_n_subgroup(block, 0)
])
back_rects.set_stroke(width=0)
back_rects.set_fill(BLUE_E, 1)
last_one_rects = VMobject()
self.add(back_rects, block)
for n, vect in zip(it.count(), [UP, UP, RIGHT, RIGHT]):
block.generate_target()
block.target.set_color(WHITE)
color_group = get_bit_n_subgroup(block.target, n)
color_group.set_color(BLUE)
one_rects = get_one_rects(color_group)
rects = VGroup(*[
SurroundingRectangle(bit, buff=MED_SMALL_BUFF / 2)
for bit in color_group
])
rects.match_style(back_rects)
self.play(
FadeOut(last_one_rects),
MoveToTarget(block),
parity_word.next_to, color_group, vect, MED_LARGE_BUFF,
parity_word.set_opacity, 1,
Transform(back_rects, rects),
)
self.play(ShowIncreasingSubsets(one_rects))
last_one_rects = one_rects
self.play(
FadeOut(back_rects),
FadeOut(last_one_rects),
FadeOut(parity_word),
block.set_color, WHITE,
)
# Expand to grid
block.generate_target()
block.target.space_out_submobjects(1.5)
block.target.to_edge(LEFT, buff=LARGE_BUFF)
boxes = get_bit_grid_boxes(block.target)
p_labels = get_grid_position_labels(boxes)
p_labels.set_color(GREY_C)
self.play(
FadeOut(hamming, LEFT),
FadeOut(bulb, 1.5 * LEFT),
MoveToTarget(block),
)
self.play(
ShowCreation(boxes, lag_ratio=0.2),
FadeIn(p_labels, lag_ratio=0.2),
)
# Set up questions
q_labels = VGroup(*[
TextMobject(f"Q{n}:")
for n in range(1, 5)
])
q_labels.set_height(0.5)
q_labels.arrange(DOWN, buff=1.25)
q_labels.next_to(boxes, RIGHT, buff=2)
questions = VGroup()
for n, q_label in enumerate(q_labels):
m_grids = VGroup(boxes.copy(), boxes.copy())
m_grids.set_height(1)
m_grids.set_width(1, stretch=True)
colors = [GREY_BROWN, BLUE]
for k, m_grid in enumerate(m_grids):
get_bit_n_subgroup(m_grid, n, k).set_fill(colors[k], 0.75)
vs = TextMobject("or")
question = VGroup(m_grids[0], vs, m_grids[1])
question.arrange(RIGHT)
question.next_to(q_label, RIGHT, buff=MED_SMALL_BUFF)
question.add_to_back(q_label)
questions.add(question)
questions.to_edge(RIGHT, buff=MED_LARGE_BUFF)
for question in questions:
question.save_state()
q_center = question.get_center()
for mob in question:
if mob is not question[0]:
mob.move_to(q_center)
mob.set_fill(opacity=0)
mob.set_stroke(width=0)
self.play(
LaggedStartMap(Restore, questions, lag_ratio=0.25),
run_time=3,
)
self.wait()
questions.save_state()
# Focus on question 1
h_rects = boxes.copy()
h_rects.set_fill(BLUE, 0.5)
h_groups = VGroup(*[
get_bit_n_subgroup(h_rects, n)
for n in range(4)
])
pc_words = TextMobject("Parity check\\\\these 8 bits")
pc_words.next_to(boxes, RIGHT, aligned_edge=UP)
pc_words.shift(DOWN)
pc_words.set_color(BLUE)
block.save_state()
self.add(h_groups[0], block, p_labels)
self.play(
questions[1:].fade, 0.9,
FadeIn(h_groups[0], lag_ratio=0.3, run_time=3),
get_bit_n_subgroup(block, 0, 0).fade, 0.9,
Write(pc_words, run_time=1)
)
self.wait()
# Scan first group
def get_sub_scanim(n, boxes=boxes, block=block, **kwargs):
return scan_anim(
boxes.get_corner(DR) + UR,
get_bit_n_subgroup(block, n),
lag_factor=0.5,
run_time=2,
**kwargs
)
scanim = get_sub_scanim(0)
robot = scanim.mobject[-1]
bangs = TexMobject("!!!")
bangs.set_color(RED)
bangs.next_to(robot, UR, buff=SMALL_BUFF)
check = Checkmark()
check.next_to(robot, UR, buff=SMALL_BUFF)
for mob in (bangs, check):
mob.scale(1.5, about_edge=DL)
q1_rect = SurroundingRectangle(questions[0][3])
self.play(scanim)
self.play(FadeIn(bangs, 0.1 * DOWN, lag_ratio=0.2))
self.wait()
self.play(ShowCreation(q1_rect))
self.wait()
self.play(
get_sub_scanim(0, show_robot=False),
FadeOut(bangs)
)
self.play(FadeIn(check, 0.2 * DOWN))
self.wait()
self.play(
q1_rect.move_to, questions[0][1],
)
self.play(
LaggedStartMap(
ShowCreationThenFadeOut,
get_bit_n_subgroup(h_rects, 0, 0).copy().set_fill(GREY_BROWN, 0.5)
)
)
self.wait()
self.play(LaggedStart(*map(FadeOut, [
robot, check, q1_rect,
])))
# Comment over in pi creature scene
pass
# Highlight parity bit
frame = self.camera.frame
frame.save_state()
ecc_rects = VGroup(*[
h_group[0]
for h_group in h_groups
])
pb_label = TextMobject("Parity bit")
pb_label.next_to(ecc_rects[0], UP, MED_LARGE_BUFF)
pb_arrow = Arrow(
pb_label.get_bottom() + MED_SMALL_BUFF * LEFT,
block[1].get_top(),
buff=0.1
)
pb_label.set_color(GREEN)
pb_arrow.set_color(GREEN)
pb_arrow.set_stroke(BLACK, 5, background=True)
h_groups[0].remove(ecc_rects[0])
self.add(ecc_rects[0], block, p_labels)
self.play(
frame.set_height, 9, {"about_edge": DOWN},
FadeIn(pb_label, 0.5 * DOWN),
GrowArrow(pb_arrow),
ecc_rects[0].set_color, GREEN,
)
self.wait()
self.play(ShowCreationThenFadeAround(p_labels[1]))
self.wait()
one_rects = get_one_rects(get_bit_n_subgroup(block, 0))
counter = get_ones_counter(boxes[-2:], one_rects)
counter.scale(0.5)
self.add(counter)
self.play(ShowIncreasingSubsets(one_rects))
self.wait()
self.play(toggle_bit_anim(block[1]))
one_rects.set_submobjects(
get_one_rects(get_bit_n_subgroup(block, 0)).submobjects
)
self.wait()
# Back to all questions
self.play(
LaggedStart(*map(FadeOut, [
one_rects, counter,
pb_arrow, pb_label,
pc_words, h_groups[0]
])),
frame.restore,
)
toggle_bit(block.saved_state[1]) # Dumb hack
self.play(
questions.restore,
block.restore,
)
# Focus on question 2
self.play(
questions[0].fade, 0.9,
questions[2:].fade, 0.9,
get_bit_n_subgroup(block, 1, 0).fade, 0.9,
ecc_rects[0].fade, 0.5,
)
self.add(h_groups[1], block, p_labels)
self.play(FadeIn(h_groups[1], lag_ratio=0.2, run_time=2))
pb_label.next_to(boxes[2], UP, SMALL_BUFF)
h_groups[1].remove(ecc_rects[1])
self.add(ecc_rects[1], block, p_labels)
self.play(
FadeIn(pb_label, 0.25 * LEFT),
ecc_rects[1].set_color, GREEN,
)
self.wait()
# Apply second parity check
one_rects = get_one_rects(get_bit_n_subgroup(block, 1))
counter = get_ones_counter(boxes[-2:], one_rects)
counter.scale(0.5)
self.add(counter)
self.play(
ShowIncreasingSubsets(one_rects)
)
self.wait()
self.play(LaggedStart(*map(FadeOut, [
one_rects, counter, pb_label,
])))
# Find error in right half
scanim = get_sub_scanim(1)
robot = scanim.mobject[-1]
self.play(
zap_anim(block[6]),
toggle_bit_anim(block[6], target_color=RED)
)
self.play(scanim)
self.play(FadeIn(bangs, 0.1 * DOWN, lag_ratio=0.1))
self.wait()
q2_rect = q1_rect.copy()
q2_rect.move_to(questions[1][3])
self.play(ShowCreation(q2_rect))
self.wait()
self.play(
toggle_bit_anim(block[6], target_color=WHITE),
FadeOut(bangs),
)
self.play(get_sub_scanim(1, show_robot=False))
self.play(FadeIn(check, 0.2 * DOWN))
self.wait()
self.play(q2_rect.move_to, questions[1][1])
self.play(
ShowCreationThenFadeOut(
get_bit_n_subgroup(boxes.copy(), 1, 0).set_fill(GREY_BROWN, 0.5),
lag_ratio=0.5,
run_time=2,
)
)
self.wait()
self.play(
FadeOut(h_groups[1]),
FadeOut(robot),
FadeOut(check),
FadeOut(q2_rect),
block.restore,
Transform(questions[0], questions.saved_state[0]),
ecc_rects[0].set_fill, GREEN, 0.5,
)
# Mention two errors?
# How to use Q1 with Q2
q1_rect.move_to(questions[0][3])
q2_rect.move_to(questions[1][3])
q1_highlight, q2_highlight = [
get_bit_n_subgroup(h_rects, n).copy().set_fill(BLUE, 0.5)
for n in [0, 1]
]
q2_highlight.set_fill(opacity=0)
q2_highlight.set_stroke(YELLOW, 7)
self.add(q1_highlight, block, p_labels)
self.play(
FadeIn(q1_highlight, lag_ratio=0.2),
ShowCreation(q1_rect),
get_bit_n_subgroup(block, 0, 0).fade, 0.9,
*map(FadeOut, ecc_rects[:2]),
)
self.wait()
self.play(
FadeIn(q2_highlight, lag_ratio=0.2),
ShowCreation(q2_rect),
get_bit_n_subgroup(block, 1, 0)[1::2].fade, 0.9,
)
self.wait(2)
self.play(
q1_rect.move_to, questions[0][1],
q1_highlight.move_to, boxes, LEFT,
q1_highlight.set_fill, GREY_BROWN,
block.restore,
FadeOut(q2_highlight),
)
self.play(get_bit_n_subgroup(block, 0).fade, 0.9)
self.wait()
self.play(
FadeIn(q2_highlight, lag_ratio=0.2),
block[::4].fade, 0.9,
)
self.wait(2)
self.play(
q1_highlight.move_to, boxes, RIGHT,
q1_highlight.set_fill, BLUE,
q2_highlight.move_to, boxes, LEFT,
block.restore,
q1_rect.move_to, questions[0][3],
q2_rect.move_to, questions[1][1],
)
self.play(
block[::4].fade, 0.9,
get_bit_n_subgroup(block, 1).fade, 0.9,
)
self.wait(2)
self.play(
q1_rect.move_to, questions[0][1],
q1_highlight.move_to, boxes, LEFT,
q1_highlight.set_fill, GREY_BROWN,
block[1::4].fade, 0.9,
Transform(block[0::4], block.saved_state[0::4]),
)
self.wait()
morty = Mortimer(height=2)
morty.flip()
morty.next_to(boxes, RIGHT, buff=0.5, aligned_edge=DOWN)
words = TextMobject("Or no error\\\\at all!")
words.next_to(morty, UP)
self.play(
VFadeIn(morty),
morty.change, "shruggie", questions[0],
FadeIn(words, DOWN)
)
self.play(Blink(morty))
self.wait(2)
self.play(
LaggedStart(*map(FadeOut, [
q1_highlight, q2_highlight[1::2],
words, morty,
])),
block.restore,
)
self.wait()
column_highlight = q2_highlight[0::2]
# Choosing a column
self.play(
q1_rect.move_to, questions[0][3],
column_highlight.move_to, boxes[1], UP,
)
self.wait()
self.play(
q1_rect.move_to, questions[0][1],
q2_rect.move_to, questions[1][3],
column_highlight.move_to, boxes[2], UP,
)
self.wait()
self.play(
q1_rect.move_to, questions[0][3],
column_highlight.move_to, boxes[3], UP,
)
self.wait()
# Bring back the parity highlights
self.add(*ecc_rects[:2], block, p_labels)
self.play(
LaggedStart(*map(FadeOut, [
column_highlight, q1_rect, q2_rect
])),
*map(FadeIn, ecc_rects[:2]),
)
self.wait()
for n in [2, 3]:
self.play(Transform(questions[n], questions.saved_state[n]))
self.wait()
# Question 3
self.add(h_groups[2], block, p_labels)
self.play(
get_bit_n_subgroup(block, 2, 0).fade, 0.9,
FadeIn(h_groups[2], lag_ratio=0.2, run_time=2),
questions[:2].fade, 0.9,
questions[3].fade, 0.9,
)
self.wait()
h_groups[2].remove(ecc_rects[2])
self.add(ecc_rects[2], block, p_labels)
self.play(
ecc_rects[2].set_color, GREEN,
ShowCreationThenFadeOut(SurroundingRectangle(boxes[4], buff=0))
)
self.wait()
one_rects = get_one_rects(get_bit_n_subgroup(block, 2))
self.play(ShowIncreasingSubsets(one_rects))
self.wait()
temp_check = Checkmark()
temp_check.set_height(0.4)
temp_check.next_to(block[4], UL, buff=0)
self.play(Write(temp_check))
self.play(FadeOut(temp_check))
self.play(FadeOut(one_rects))
self.play(
block.restore,
FadeOut(h_groups[2]),
)
self.wait()
# Question 4
self.add(h_groups[3], block, p_labels)
self.play(
questions[2].fade, 0.9,
Transform(questions[3], questions.saved_state[3]),
get_bit_n_subgroup(block, 3, 0).fade, 0.9,
FadeIn(h_groups[3], lag_ratio=0.2, run_time=2),
)
self.wait()
h_groups[3].remove(ecc_rects[3])
self.add(ecc_rects[3], block, p_labels)
self.play(
ecc_rects[3].set_color, GREEN,
ShowCreationThenFadeOut(SurroundingRectangle(boxes[8], buff=0))
)
one_rects = get_one_rects(get_bit_n_subgroup(block, 3))
self.play(ShowIncreasingSubsets(one_rects))
self.wait()
self.play(toggle_bit_anim(block[8]))
toggle_bit(block.saved_state[8])
one_rects.set_submobjects(get_one_rects(get_bit_n_subgroup(block, 3)))
self.wait()
self.play(FadeOut(one_rects, lag_ratio=0.2))
self.play(
block.restore,
questions.restore,
FadeOut(h_groups[3], lag_ratio=0.2),
)
self.wait()
# Point out rolls of questions
braces = VGroup(
Brace(questions[:2], LEFT),
Brace(questions[2:], LEFT),
)
for brace, text in zip(braces, ["column", "row"]):
brace.words = brace.get_text(f"Which\\\\{text}?")
for brace in braces:
self.play(
GrowFromCenter(brace),
FadeIn(brace.words, 0.25 * RIGHT)
)
self.wait()
# Example with error at 3
q_rects = VGroup()
for question, answer in zip(questions, [1, 1, 0, 0]):
q_rects.add(SurroundingRectangle(question[1 + 2 * answer]))
self.play(
zap_anim(block[3]),
toggle_bit_anim(block[3], target_color=RED),
)
self.wait()
target_square = SurroundingRectangle(boxes[3::4], buff=0, stroke_width=5)
for n in range(4):
highlight = get_bit_n_subgroup(h_rects.copy(), n)
one_rects = get_one_rects(get_bit_n_subgroup(block, n))
self.add(highlight, block, p_labels)
self.play(
FadeIn(highlight),
ShowCreation(q_rects[n]),
ShowIncreasingSubsets(one_rects),
)
self.wait()
self.play(
FadeOut(highlight),
FadeOut(one_rects),
)
if n == 1:
self.play(FadeIn(target_square))
elif n == 3:
self.play(target_square.replace, boxes[3], {"stretch": True})
self.wait()
self.play(
toggle_bit_anim(block[3], target_color=WHITE)
)
self.wait()
# Binary counting
for n in range(1, 16):
target_square.generate_target()
target_square.target.move_to(boxes[n])
for k, q_rect in enumerate(q_rects):
index = 3 if((1 << k) & n) else 1
q_rect.generate_target()
q_rect.target.move_to(questions[k][index])
self.play(*map(MoveToTarget, [target_square, *q_rects]), run_time=0.5)
self.wait()
# Bit 0
for q_rect, question in zip(q_rects, questions):
q_rect.generate_target()
q_rect.target.move_to(question[1])
self.play(
target_square.move_to, boxes[0],
LaggedStartMap(MoveToTarget, q_rects),
)
self.wait()
for n in range(4):
group = get_bit_n_subgroup(h_rects.copy(), n)
self.play(FadeIn(group, lag_ratio=0.2))
self.wait(0.5)
self.play(FadeOut(group))
self.wait()
randy = Randolph(height=2)
randy.next_to(boxes, RIGHT, buff=MED_LARGE_BUFF, aligned_edge=DOWN)
ne_word = TextMobject("No error?")
ne_word.next_to(randy, UP, buff=MED_LARGE_BUFF)
ne_word.shift(0.2 * RIGHT)
ez_word = TextMobject("or error\\\\at bit 0?")
ez_word.set_color(RED)
ez_word.move_to(ne_word, DOWN)
self.play(
VFadeIn(randy),
randy.change, 'pondering', questions,
FadeOut(braces),
*[
FadeOut(brace.words)
for brace in braces
]
)
self.play(FadeIn(ne_word, 0.5 * DOWN))
self.play(Blink(randy))
self.wait()
self.play(
ne_word.next_to, ez_word, UP, MED_LARGE_BUFF,
FadeIn(ez_word, 0.25 * DOWN),
randy.change, "confused", target_square,
)
self.play(Blink(randy))
for x in range(2):
self.play(toggle_bit_anim(block[0]))
self.play(randy.change, "pondering", questions)
# Count through again
for n in [*range(0, 16), 0]:
target_square.move_to(boxes[n])
for k, q_rect in enumerate(q_rects):
index = 3 if((1 << k) & n) else 1
q_rect.move_to(questions[k][index])
randy.look_at(target_square)
self.wait(0.5)
# Highlight no error
ne_rect = SurroundingRectangle(ne_word)
ne_rect.set_stroke(GREY_B, 2)
ne_comment = TextMobject("17th outcome")
ne_comment.set_color(GREY_B)
ne_comment.next_to(ne_rect, UP)
self.play(
ShowCreation(ne_rect),
FadeIn(ne_comment, 0.25 * DOWN),
randy.change, "maybe", ne_comment,
)
for x in range(2):
self.play(Blink(randy))
self.wait(2)
# Nix bit 0
zero_rect = SurroundingRectangle(boxes[0], buff=0)
zero_rect.set_fill(BLACK, 1)
zero_rect.set_stroke(BLACK, 0)
zero_rect.scale(0.98)
self.play(
GrowFromCenter(zero_rect),
boxes[0].set_stroke, {"width": 0},
FadeOut(target_square),
randy.change, "raise_left_hand", zero_rect,
)
ne_word.generate_target()
ne_word.target[0][-1].set_opacity(0)
ne_word.target.next_to(randy, UP, MED_LARGE_BUFF)
ne_word.target.set_color(YELLOW)
self.play(
MoveToTarget(ne_word),
LaggedStart(*map(FadeOut, [ne_rect, ne_comment, ez_word]))
)
for n in range(4):
highlight = get_bit_n_subgroup(h_rects.copy(), n)
one_rects = get_one_rects(get_bit_n_subgroup(block, n))
self.add(highlight, block, p_labels, zero_rect, one_rects)
self.wait()
self.remove(highlight, one_rects)
# (15, 11) setup
stat_words = VGroup(
TextMobject("15", "-bit block"),
TextMobject("11", " bits of\\\\message", alignment=""),
TextMobject("4 bits of\\\\redundancy", alignment=""),
)
stat_words.arrange(DOWN, buff=MED_LARGE_BUFF, aligned_edge=LEFT)
stat_words[0].set_color(YELLOW)
stat_words[2].set_color(GREEN)
stat_words.next_to(boxes, RIGHT, buff=MED_LARGE_BUFF, aligned_edge=UP)
stat_words.shift(0.5 * DOWN)
self.play(
LaggedStart(*map(FadeOut, [*q_rects, ne_word])),
randy.change, "tease", stat_words,
FadeIn(stat_words[0], 0.5 * LEFT)
)
self.play(Blink(randy))
self.play(
FadeIn(stat_words[1], 0.5 * LEFT),
LaggedStart(*[
toggle_bit_anim(bit)
for i, bit in enumerate(block)
if i not in [0, 1, 2, 4, 8]
])
)
self.play(
LaggedStart(*[
toggle_bit_anim(bit)
for i, bit in enumerate(block)
if i not in [0, 1, 2, 4, 8]
])
)
self.wait()
self.play(
FadeIn(stat_words[2], 0.5 * LEFT),
LaggedStart(*map(ShowCreationThenFadeAround, [
block[2**n]
for n in range(4)
]))
)
self.play(Blink(randy))
code_name = TextMobject("(", "15", ", ", "11", ")", " Hamming code")
code_name.set_height(0.8)
code_name.to_edge(UP)
code_name.shift(UP)
self.play(
ApplyMethod(frame.set_height, 9, {"about_edge": DOWN}, run_time=2),
TransformFromCopy(stat_words[0][0], code_name[1]),
TransformFromCopy(stat_words[1][0], code_name[3]),
LaggedStart(*map(FadeIn, [
code_name[i] for i in [0, 2, 4, 5]
])),
)
self.wait()
# Bring back bit zero
self.play(
LaggedStart(*map(FadeOut, [
*stat_words, randy,
*questions, code_name,
])),
ApplyMethod(frame.match_x, block),
run_time=2
)
old_zero_rect = zero_rect
zero_rect = old_zero_rect.copy()
zero_rect.set_stroke(YELLOW, 2)
zero_rect.set_fill(YELLOW, 0.5)
zero_rect.save_state()
zero_rect.stretch(0, 1, about_edge=UP)
zero_words = TextMobject("Can we put this bit to work?")
zero_words.set_height(0.7)
zero_words.next_to(zero_rect, UP, MED_LARGE_BUFF)
zero_words.match_x(block)
zero_words.set_color(YELLOW)
self.add(zero_rect, block, p_labels)
block[0].set_opacity(0)
self.play(
FadeOut(old_zero_rect),
Restore(zero_rect),
Write(zero_words)
)
self.wait()
# Parity check the whole block
pc_words = TextMobject("Parity check\\\\ \\emph{the whole block}")
pc_words.set_height(1.2)
pc_words.next_to(boxes, LEFT, buff=MED_LARGE_BUFF)
one_rects = get_one_rects(block)
counter = get_ones_counter(boxes[-2:], one_rects)
counter.scale(0.5)
self.play(
Write(pc_words, run_time=1),
ShowCreationThenFadeOut(h_rects.copy()),
)
self.wait()
ecc_bits = VGroup(*[block[2**n] for n in range(4)])
for x in range(2):
self.play(LaggedStartMap(toggle_bit_anim, ecc_bits, lag_ratio=0.25, run_time=1))
self.wait()
self.add(counter)
self.play(ShowIncreasingSubsets(one_rects))
self.wait()
block[0][1].set_opacity(1)
self.play(Write(block[0]))
one_rects.set_submobjects(get_one_rects(block))
self.wait()
self.play(*map(FadeOut, [one_rects, counter]))
# Walk through single and two bit errors
self.play(
zap_anim(block[9]),
toggle_bit_anim(block[9], target_color=RED),
)
one_rects.set_submobjects(get_one_rects(block))
counter.set_opacity(1)
self.add(counter)
self.play(ShowIncreasingSubsets(one_rects))
self.wait()
self.play(
zap_anim(block[5]),
toggle_bit_anim(block[5], target_color=RED),
)
one_rects.set_submobjects(get_one_rects(block))
self.wait()
self.play(FadeOut(one_rects), FadeOut(counter))
for n in [2, 3]:
group = get_bit_n_subgroup(h_rects.copy(), n)
group.set_fill(opacity=0)
group.set_stroke(BLUE, 10)
self.play(ShowCreation(group, lag_ratio=0.5))
self.play(FadeOut(group))
self.wait()
scanim = scan_anim(boxes.get_corner(DR) + 2 * UR, block, lag_factor=0.5, run_time=2)
robot = scanim.mobject[-1]
robot.set_color(GREY_B)
robot.scale(2, about_edge=UP)
ded_words = TextMobject("At least\\\\2 errors!")
ded_words.set_color(RED)
ded_words.next_to(robot, UR, buff=SMALL_BUFF)
self.play(scanim)
self.play(FadeIn(ded_words, 0.5 * DOWN))
self.wait()
# Extended name
new_title = TextMobject("Extended Hamming Code")
new_title.replace(zero_words, dim_to_match=1)
self.play(
FadeIn(new_title, DOWN),
FadeOut(zero_words, UP),
FadeOut(pc_words),
)
self.wait()
class WhatIfTheresAndArrowInECCBits(TeacherStudentsScene):
def construct(self):
self.student_says(
"What if an\\\\error-correction bit\\\\needs to be corrected?",
bubble_kwargs={'width': 5, 'height': 4, "direction": LEFT},
added_anims=[self.teacher.change, "happy"]
)
self.change_student_modes("confused", "confused")
self.look_at(self.screen)
self.wait(2)
self.teacher_says("Try it!", target_mode="hooray")
self.change_student_modes(*3 * ["pondering"], look_at_arg=self.screen)
self.wait(2)
self.change_student_modes(*3 * ["thinking"], look_at_arg=self.screen)
self.wait(8)
class ErrorAtECCBit(Scene):
def construct(self):
bits = get_bit_grid(4, 4, height=6)
toggle_bit(bits[1])
toggle_bit(bits[4])
boxes = get_bit_grid_boxes(bits)
pos_labels = get_grid_position_labels(boxes)
ecc_boxes = VGroup(*[boxes[2**n] for n in range(4)])
ecc_boxes.set_fill(GREEN, 0.5)
bangs = TextMobject("!!!")
bangs.set_color(RED)
bangs.next_to(boxes[2], UP, SMALL_BUFF)
self.add(boxes, ecc_boxes, pos_labels, bits)
self.wait()
self.play(LaggedStartMap(Rotate, ecc_boxes, lambda m: (m, PI)))
self.wait()
self.play(
zap_anim(bits[2]),
toggle_bit_anim(bits[2], target_color=RED),
)
self.play(Write(bangs))
self.wait()
for bit in bits:
bit.remove(bit[1 - get_bit_mob_value(bit)])
for n in range(4):
bits.generate_target()
bits.target.set_opacity(1)
get_bit_n_subgroup(bits.target, n, 0).set_opacity(0)
self.play(MoveToTarget(bits))
self.wait()
self.play(bits.set_opacity, 1)
self.wait()
class HalfAsPowerful(TeacherStudentsScene):
def construct(self):
self.student_says(
"Shouldn't that be\\\\only half as good?",
target_mode="sassy",
added_anims=[self.teacher.change, "happy"]
)
self.change_student_modes(
"pondering", "pondering", look_at_arg=self.screen,
added_anims=[self.teacher.change, "tease"]
)
self.look_at(self.screen)
self.wait(8)
class WhatAboutTwoErrors(TeacherStudentsScene):
def construct(self):
self.student_says(
"What about\\\\2 errors?",
)
self.play(self.teacher.change, "guilty")
self.look_at(self.screen)
self.change_student_modes("erm", "confused")
self.look_at(self.screen)
self.wait(4)
class BlockSize256(Scene):
def construct(self):
N = 8
frame = self.camera.frame
# Bit block
bits = string_to_bits("You decoded an easter egg. Nice!")
block = get_bit_grid(2**(N // 2), 2**(N // 2), bits=bits)
block.set_height(6)
block.to_edge(LEFT, buff=LARGE_BUFF)
boxes = get_bit_grid_boxes(block)
parity_boxes = VGroup(*[boxes[2**k] for k in range(N)])
parity_boxes.set_fill(GREEN, 0.8)
bit_boxes = VGroup(*[VGroup(box, bit) for bit, box in zip(block, boxes)])
to_fade = VGroup()
to_keep = VGroup()
for i, bb in enumerate(bit_boxes):
if i >= 64 or (i % 16) >= 4:
to_fade.add(bb.copy())
else:
to_keep.add(bb.copy())
to_fade.save_state()
to_fade.fade(1)
frame.save_state()
frame.replace(to_keep, dim_to_match=1)
frame.scale(1.2)
self.add(to_keep)
self.play(
Restore(frame),
Restore(to_fade, lag_ratio=0.1),
run_time=5,
)
self.clear()
self.add(boxes)
self.add(block)
# Add title
title = TextMobject("$256 = 2^8$ bits")
title.set_height(0.7)
title.next_to(boxes, UP, MED_LARGE_BUFF)
title.set_x(0)
self.play(
frame.move_to, 0.5 * UP,
Write(title)
)
self.wait()
# Parity groups
parity_groups = VGroup()
for k in range(N):
group = boxes.copy()
group.set_fill(BLACK, opacity=0)
group.set_stroke(GREY_B, 1)
group.set_height(1.5)
get_bit_n_subgroup(group, k).set_fill(BLUE_E, 1)
parity_groups.add(group)
parity_groups.arrange_in_grid(2, 4, buff=MED_LARGE_BUFF)
parity_groups.set_width(7)
VGroup(parity_groups[:4], parity_groups[4:]).arrange(DOWN, buff=1.5)
parity_groups.to_edge(RIGHT)
# Question labels
q_labels = VGroup(*[TextMobject(f"Q{i + 1}") for i in range(N)])
for label, group in zip(q_labels, parity_groups):
label.set_height(0.3)
label.next_to(group, UP, SMALL_BUFF)
# Add questions
self.play(
LaggedStartMap(FadeIn, q_labels, lambda m: (m, DOWN), lag_ratio=0.3, run_time=5),
LaggedStartMap(FadeIn, parity_groups, lambda m: (m, DOWN), lag_ratio=0.3, run_time=5),
)
self.wait()
# Isolate one square
pos = 69 # Why not?
bits = "{0:b}".format(pos)
bits = (N - len(bits)) * '0' + bits
bits = bits[::-1]
yes_no_group = VGroup()
boxes.save_state()
possible_positions = list(range(2**N))
for k, bit, group in zip(it.count(), bits, parity_groups):
bit_value = int(bit)
if bit_value:
word = TextMobject("Yes", color=GREEN)
else:
word = TextMobject("No", color=RED)
word.next_to(group, DOWN)
yes_no_group.add(word)
intersect_positions = get_bit_n_sublist(range(2**N), k, bit_value)
globals()['intersect_positions'] = intersect_positions
possible_positions = list(filter(
lambda i: i in intersect_positions,
possible_positions,
))
boxes.generate_target()
boxes.target.match_style(boxes.saved_state)
globals()['possible_positions'] = possible_positions
globals()['boxes'] = boxes
VGroup(*[boxes.target[i] for i in possible_positions]).set_fill(BLUE, 1)
self.play(
FadeIn(word),
MoveToTarget(boxes)
)
self.wait()
# Highlight parity bits
parity_bits_label = TextMobject("8 parity bits")
parity_bits_label.next_to(boxes, UP, aligned_edge=LEFT)
parity_bits_label.set_color(GREEN)
self.play(
FadeIn(parity_bits_label, DOWN),
title.to_edge, RIGHT
)
self.add(parity_boxes, block)
self.play(
LaggedStartMap(Rotate, parity_boxes, lambda m: (m, TAU)),
)
self.add(boxes, block)
# Un-highlight isolated point
self.play(
boxes[pos].set_fill, BLACK, 0,
FadeOut(yes_no_group)
)
# Message bits
message_bits = VGroup(*[
bit
for i, bit in enumerate(block)
if i not in [2**k for k in range(N)]
])
message_bits.shuffle()
self.play(
LaggedStart(*[
toggle_bit_anim(bit)
for bit in message_bits
], lag_ratio=0.01, run_time=3)
)
self.wait()
# Write redundant
redundant_label = TextMobject("``Redundant''")
redundant_label.set_color(GREEN)
redundant_label.next_to(parity_bits_label[-1][-1], RIGHT, MED_LARGE_BUFF, DOWN)
self.play(Write(redundant_label))
block.save_state()
for k in range(N):
block.target = block.saved_state.copy()
get_bit_n_subgroup(block.target, k, 0).set_fill(opacity=0)
self.play(MoveToTarget(block))
self.wait(0.5)
self.play(Restore(block))
self.wait()
class WellAlmost(TeacherStudentsScene):
def construct(self):
self.teacher_says("Well...\\\\almost", target_mode="hesitant")
self.change_student_modes("angry", "sassy", "confused")
self.wait(3)
class ChecksSpellOutPositionInBinary(Scene):
def construct(self):
N = 4
pos = 7
# Setup block
random.seed(0)
bits = [random.choice([0, 1]) for n in range(2**N)]
bits[1] = 0
bits[2] = 1
bits[4] = 0
bits[8] = 0
block = get_bit_grid(2**(N // 2), 2**(N // 2), bits=bits)
block.set_height(5)
block.to_edge(LEFT, buff=LARGE_BUFF)
boxes = get_bit_grid_boxes(block)
VGroup(*[boxes[2**n] for n in range(N)]).set_fill(GREY_D, 1)
pos_labels = VGroup(*map(Integer, range(2**N)))
pos_labels.set_height(0.2)
for label, box, bit_label in zip(pos_labels, boxes, block):
label.move_to(box, DR)
label.shift(0.1 * UL)
label.set_color(GREY_A)
bit_label.scale(0.8)
self.add(boxes)
self.add(block)
self.add(pos_labels)
self.play(
zap_anim(block[pos]),
toggle_bit_anim(block[pos]),
)
# Setup questions
parity_groups = VGroup()
for n in range(N):
group = boxes.copy()
group.set_height(1)
group.set_width(1, stretch=True)
group.set_fill(BLACK, 0)
get_bit_n_subgroup(group, n).set_fill(BLUE_D, 1)
parity_groups.add(group)
parity_groups.arrange(DOWN, buff=MED_LARGE_BUFF)
parity_groups.set_height(6)
parity_groups.to_edge(RIGHT, buff=3)
q_labels = VGroup(*[TextMobject(f"Q{n + 1}:") for n in range(N)])
for label, group in zip(q_labels, parity_groups):
label.next_to(group, LEFT, MED_SMALL_BUFF)
self.play(
FadeIn(parity_groups),
FadeIn(q_labels),
)
# Binary search down
bits_word = "{0:b}".format(pos)
bits_word = (N - len(bits_word)) * '0' + bits_word
bits = list(map(int, bits_word[::-1]))
boxes.save_state()
yes_no_words = VGroup()
possible_positions = list(range(2**N))
for n, bit, group in zip(it.count(), bits, parity_groups):
if bit:
word = TextMobject("Yes", color=GREEN)
else:
word = TextMobject("No", color=RED)
word.next_to(group, RIGHT)
yes_no_words.add(word)
possible_positions = list(filter(
lambda i: i in get_bit_n_sublist(range(2**N), n, bit_value=bit),
possible_positions,
))
boxes.target = boxes.saved_state.copy()
VGroup(*[boxes.target[i] for i in possible_positions]).set_fill(BLUE_D, 0.8)
self.play(
FadeIn(word, 0.5 * LEFT),
MoveToTarget(boxes),
)
self.wait()
# Spell answer in binary
binary_answers = VGroup(*[
Integer(bit).move_to(word).match_color(word)
for bit, word in zip(bits, yes_no_words)
])
self.play(
LaggedStartMap(GrowFromCenter, binary_answers),
LaggedStartMap(ApplyMethod, yes_no_words, lambda m: (m.scale, 0), remover=True),
)
# Show value of 7
frame = self.camera.frame
binary_pos_label = binary_answers.copy()
binary_pos_label.generate_target()
binary_pos_label.target.arrange(LEFT, buff=SMALL_BUFF, aligned_edge=DOWN)
equation = VGroup(
Integer(7, color=BLUE),
TexMobject("\\rightarrow"),
binary_pos_label.target,
)
equation.arrange(RIGHT)
equation.to_edge(UP, buff=0)
arrow = Arrow(equation.get_left(), equation.get_right(), buff=0)
arrow.next_to(equation, DOWN, SMALL_BUFF)
trans_words = TextMobject("Decimal to binary")
trans_words.match_width(arrow)
trans_words.next_to(arrow, DOWN, SMALL_BUFF)
self.play(
MoveToTarget(binary_pos_label),
frame.move_to, 0.5 * UP
)
self.play(
Write(equation[:-1]),
Write(trans_words),
GrowArrow(arrow),
run_time=1,
)
self.wait()
bin_group = VGroup(*equation[:-1], binary_pos_label, arrow, trans_words)
# Spell out binary
bin_equation = TexMobject(
"{7} = {0} \\cdot 8 + {1} \\cdot 4 + {1} \\cdot 2 + {1} \\cdot 1",
tex_to_color_map={
"{0}": RED,
"{1}": GREEN,
"{7}": BLUE,
}
)
bin_equation.move_to(bin_group, UP)
bit_parts = list(it.chain(*[
bin_equation.get_parts_by_tex(f"{{{d}}}")
for d in [0, 1]
]))
self.play(
bin_group.next_to, bin_equation, DOWN, LARGE_BUFF,
*[
ApplyMethod(m1.copy().replace, m2, {"dim_to_match": 1}, remover=True, run_time=1.5)
for m1, m2 in zip(binary_pos_label[::-1], bit_parts)
],
ApplyMethod(equation[0].copy().replace, bin_equation[0], remover=True, run_time=1.5)
)
self.add(bin_equation[0], *bit_parts)
self.play(FadeIn(VGroup(*[
part
for part in bin_equation
if part.get_tex_string() not in ["{0}", "{1}", "{7}"]
]), lag_ratio=0.1))
self.add(bin_equation)
self.wait()
# Error at 7
toggle_bit(block[pos])
self.play(
zap_anim(block[pos]),
toggle_bit_anim(block[pos], target_color=RED),
)
self.wait()
# Four parity checks
for n, label, group, word in zip(it.count(), q_labels, parity_groups, yes_no_words):
boxes.target = boxes.saved_state.copy()
get_bit_n_subgroup(boxes.target, n).set_fill(BLUE, 0.8)
rect = SurroundingRectangle(VGroup(label, group, word), buff=MED_SMALL_BUFF)
one_rects = get_one_rects(get_bit_n_subgroup(block, n))
self.play(
MoveToTarget(boxes),
ShowCreation(rect),
ShowIncreasingSubsets(one_rects)
)
self.play(
FadeOut(one_rects),
FadeOut(rect),
)
self.wait(0.5)
self.play(Restore(boxes))
self.wait()
# Other examples
toggle_bit(block[7])
block[7].set_color(WHITE)
bit_parts = VGroup(*bit_parts)
to_save = VGroup(
equation[0], bin_equation[0],
bit_parts, binary_pos_label, binary_answers,
)
to_save.save_state()
ns = random.sample(list(range(16)), 10)
for n in ns:
toggle_bit(block[n])
block[n].set_color(YELLOW)
nc1 = Integer(n)
nc1.replace(equation[0], 1)
nc1.match_color(equation[0])
equation[0].set_opacity(0)
nc2 = nc1.copy()
nc2.replace(bin_equation[0], 1)
bin_equation[0].set_fill(0)
new_bits = int_to_bit_string(n, 4)
new_bit_mobs = VGroup()
for b1, b2, b3, value in zip(reversed(bit_parts), binary_pos_label, binary_answers, reversed(new_bits)):
new_mob = TexMobject(value)
new_mob.set_color(GREEN if int(value) else RED)
for b in (b1, b2, b3):
nmc = new_mob.copy()
nmc.replace(b, 1)
b.set_opacity(0)
new_bit_mobs.add(nmc)
self.play(*[
Animation(mob, remover=True, run_time=1)
for mob in [new_bit_mobs, nc1, nc2]
])
toggle_bit(block[n])
block[n].set_color(WHITE)
to_save.restore()
self.wait()
# Remove 7 stuff
self.play(
FadeOut(VGroup(
*bin_group, *bin_equation, *binary_answers
), lag_ratio=0.1),
block[pos].set_color, WHITE,
)
question_group = VGroup(q_labels, parity_groups)
# Show numbers 0 through 15
pos_labels_movers = pos_labels.copy()
bin_pos_groups = VGroup()
arrows = VGroup()
bin_labels = VGroup()
for n, label in enumerate(pos_labels_movers):
label.scale(2)
arrow = TexMobject("\\rightarrow")
bits_word = "{0:b}".format(n)
bits_word = (N - len(bits_word)) * '0' + bits_word
bin_label = VGroup(*[TexMobject(b) for b in bits_word])
bin_label.arrange(RIGHT, buff=SMALL_BUFF, aligned_edge=DOWN)
pos_group = VGroup(label, arrow, bin_label)
pos_group.arrange(RIGHT, buff=MED_SMALL_BUFF)
bin_pos_groups.add(pos_group)
arrows.add(pos_group[1])
bin_labels.add(bin_label)
bin_pos_groups.arrange_in_grid(8, 2, fill_rows_first=False)
bin_pos_groups.set_height(7)
bin_pos_groups.to_edge(RIGHT)
bin_pos_groups.set_y(0.5)
self.play(
FadeOut(question_group),
FadeIn(arrows, lag_ratio=0.02),
TransformFromCopy(pos_labels, pos_labels_movers),
)
self.play(ShowIncreasingSubsets(bin_labels, run_time=3, rate_func=bezier([0, 0, 1, 1])))
self.wait()
# Put bin labels in boxes
for label, box in zip(bin_labels, boxes):
label.generate_target()
label.target.set_width(0.7 * box.get_width())
label.target.move_to(box, DOWN)
label.target.shift(SMALL_BUFF * UP)
for bit in block:
bit.generate_target()
bit.target.scale(0.5, about_edge=UP),
bit.target.fade(0.5)
kw = {
"run_time": 5,
"lag_ratio": 0.3,
}
self.play(
LaggedStartMap(MoveToTarget, bin_labels, **kw),
LaggedStartMap(FadeOut, arrows, **kw),
LaggedStartMap(FadeOut, pos_labels_movers, **kw),
LaggedStartMap(FadeOut, pos_labels, **kw),
LaggedStartMap(MoveToTarget, block, **kw),
)
self.wait()
# Show confusion
randy = Randolph()
randy.flip()
randy.to_corner(DR, buff=LARGE_BUFF)
self.play(
VFadeIn(randy),
randy.change, "maybe", boxes,
)
self.play(PiCreatureBubbleIntroduction(
randy, "Wait...",
target_mode="confused",
bubble_class=ThoughtBubble,
look_at_arg=boxes.get_top(),
))
self.play(Blink(randy))
self.wait()
self.play(LaggedStart(*map(ShowCreationThenFadeAround, bin_labels), lag_ratio=0))
self.play(randy.change, "maybe")
self.play(
LaggedStart(*[ShowCreationThenFadeOut(SurroundingRectangle(b, color=GREEN)) for b in block], lag_ratio=0),
randy.look_at, boxes.get_bottom(),
)
self.play(Blink(randy))
self.play(
randy.change, 'pondering', boxes,
FadeOut(randy.bubble),
FadeOut(randy.bubble.content),
)
for x in range(2):
self.wait()
self.play(Blink(randy))
self.play(randy.change, "thinking")
self.play(FadeOut(randy))
# Go through parity group 1 (and setup others)
bit_arrow_groups = VGroup()
for n in range(N):
arrow_group = VGroup()
for bin_label in bin_labels:
char = bin_label[-(n + 1)]
arrow = Triangle(start_angle=-PI / 2)
arrow.stretch(0.8, 0)
arrow.set_height(0.8 * char.get_height())
arrow.next_to(char, UP, buff=0.05)
arrow.set_stroke(width=0)
if char.get_tex_string() == '0':
arrow.set_fill(GREY, 1)
else:
arrow.set_fill(BLUE, 1)
arrow_group.add(arrow)
bit_arrow_groups.add(arrow_group)
highlight_groups = VGroup()
for n in range(N):
highlight_group = boxes.copy()
highlight_group.set_fill(BLACK, 0)
get_bit_n_subgroup(highlight_group, n).set_fill(BLUE, 0.5)
highlight_groups.add(highlight_group)
questions = VGroup()
for n in range(N):
chars = ["\\underline{\\phantom{0}}" for x in range(4)]
chars[-(n + 1)] = "\\underline{1}"
question = TextMobject(
f"""
If there's an error, does\\\\
its position look like\\\\
""",
" ".join(chars),
"?"
)
question.scale(1.25)
question[1:].scale(1.5, about_edge=UP)
question[1:].shift(SMALL_BUFF * DOWN)
question[1].set_color(BLUE)
question.next_to(boxes, RIGHT, LARGE_BUFF)
questions.add(question)
self.play(
LaggedStartMap(FadeIn, bit_arrow_groups[0], lag_ratio=0.3, run_time=3),
FadeOut(block),
)
self.play(Transform(boxes, highlight_groups[0]))
self.wait()
self.play(Write(questions[0]))
self.wait()
# Go through parity groups 2-4
bit_arrows = bit_arrow_groups[0]
for n in range(1, N):
self.play(boxes.set_fill, BLACK, 0)
self.play(
Transform(bit_arrows, bit_arrow_groups[n]),
FadeOut(questions[n - 1])
)
self.wait()
self.play(
Transform(boxes, highlight_groups[n]),
FadeIn(questions[n])
)
self.wait()
# Organize questions
questions.generate_target()
questions.target.arrange(DOWN, buff=LARGE_BUFF)
questions.target.set_height(FRAME_HEIGHT - 1)
questions.target.next_to(boxes, RIGHT, buff=1.5)
questions.target.match_y(frame)
questions[:N - 1].set_opacity(0)
self.play(
MoveToTarget(questions),
Restore(boxes),
FadeOut(bit_arrows),
)
fade_anims = []
for n in range(N):
rect = SurroundingRectangle(questions[n])
rect.set_stroke(BLUE, 2)
self.play(
FadeIn(highlight_groups[n]),
FadeIn(rect),
*fade_anims
)
fade_anims = [
FadeOut(highlight_groups[n]),
FadeOut(rect),
]
self.play(*fade_anims)
# Note power of 2 points
parity_arrows = VGroup(Vector(DOWN), Vector(DOWN), Vector(RIGHT), Vector(RIGHT))
parity_arrows[0].next_to(boxes[1], UP, SMALL_BUFF)
parity_arrows[1].next_to(boxes[2], UP, SMALL_BUFF)
parity_arrows[2].next_to(boxes[4], LEFT, SMALL_BUFF)
parity_arrows[3].next_to(boxes[8], LEFT, SMALL_BUFF)
parity_arrows.set_color(GREEN)
parity_groups = VGroup()
for n, question in enumerate(questions):
pg = boxes.copy()
pg.set_width(pg.get_height(), stretch=True)
pg.set_height(0.8 * question.get_height())
pg.set_fill(BLACK, 0)
get_bit_n_subgroup(pg, n).set_fill(BLUE, 0.8)
pg.next_to(question, RIGHT, LARGE_BUFF)
parity_groups.add(pg)
self.play(
LaggedStartMap(GrowArrow, parity_arrows),
ApplyMethod(frame.set_x, -1, run_time=2)
)
self.wait()
rects = VGroup(*[boxes[2**n].copy() for n in range(N)])
rects.set_fill(BLUE, 0.8)
self.add(rects, bin_label)
self.play(
LaggedStartMap(VFadeInThenOut, rects, lag_ratio=0.5, run_time=5),
LaggedStartMap(FadeIn, parity_groups, lag_ratio=0.5, run_time=5),
)
self.wait()
self.add(rects, bin_label)
self.play(
LaggedStartMap(VFadeInThenOut, rects, lag_ratio=0.5, run_time=5),
)
self.wait()
class PowerOfTwoPositions(Scene):
def construct(self):
block = get_bit_grid(4, 4)
block.set_height(5)
block.to_edge(LEFT, buff=LARGE_BUFF)
boxes = get_bit_grid_boxes(block)
numbers = VGroup(*[
Integer(n).move_to(box)
for n, box in enumerate(boxes)
])
self.add(numbers)
self.wait()
for n in range(4):
numbers[2**n].scale(1.5)
numbers[2**n].set_color(YELLOW)
self.wait(0.25)
self.wait()
class OneGroupPerParityBit(Scene):
def construct(self):
N = 4
block = get_bit_grid(2**(N // 2), 2**(N // 2))
block.set_height(5)
block.to_edge(LEFT, buff=LARGE_BUFF)
boxes = get_bit_grid_boxes(block)
pos_labels = get_grid_position_labels(boxes)
for bit in block:
bit.scale(0.7)
parity_boxes = VGroup(*[boxes[2**n] for n in range(N)])
parity_boxes.set_fill(GREEN, 0.8)
block.save_state()
self.add(boxes, pos_labels, block)
for n in range(4):
block.restore()
get_bit_n_subgroup(block, n, 0).set_fill(opacity=0)
self.wait(1.5)
class LetsWalkThroughAnExample(TeacherStudentsScene):
def construct(self):
self.student_says(
"Can we walk through\\\\a full example?",
student_index=1,
added_anims=[self.teacher.change, "happy"]
)
self.change_student_modes("hooray", None, "hooray")
self.wait(5)
self.teacher_says(
"But of\\\\course!",
target_mode="tease"
)
self.change_student_modes("happy", "coin_flip_1", "happy")
self.wait(4)
class FullExampleWithNewEnd(Scene):
CONFIG = {
"random_seed": 3,
}
def construct(self):
# Pull bits out of an image
image = ImageMobject("Tom_In_Bowtie")
image.set_height(6)
image.to_edge(LEFT, buff=LARGE_BUFF)
bits = get_image_bits(image)
bits.match_height(image)
bits.generate_target()
bits.target.arrange_in_grid(n_cols=11, h_buff=SMALL_BUFF, v_buff=MED_SMALL_BUFF)
bits.target.next_to(image, RIGHT, LARGE_BUFF, UP)
for bit, bt in zip(bits, bits.target):
bit.save_state()
bit.target = bt
bit.fade(0.9)
words = TextMobject("11-bit\\\\chunks")
words.scale(1.5)
words.to_edge(RIGHT)
lines = VGroup()
for bit in bits.target[10:200:11]:
line = Line(RIGHT, LEFT)
line.set_stroke(BLUE, 1)
line.bit = bit
line.word = words[0][0]
line.add_updater(lambda m: m.put_start_and_end_on(
m.word.get_left() + SMALL_BUFF * LEFT,
m.bit.get_right() + SMALL_BUFF * RIGHT,
))
lines.add(line)
self.add(image)
self.add(bits)
self.save_state()
self.play(
LaggedStartMap(
Succession, bits,
lambda m: (Restore(m), MoveToTarget(m)),
lag_ratio=3 / len(bits),
),
Write(words, rate_func=squish_rate_func(smooth, 0.5, 0.7)),
ShowCreation(lines, lag_ratio=0.1, rate_func=squish_rate_func(smooth, 0.5, 1)),
run_time=8
)
for line, bit in zip(lines, bits[10::11]):
line.bit = bit
for bit in bits:
if bit.get_center()[1] < -FRAME_HEIGHT / 2:
bits.remove(bit)
self.wait()
# Show many 16 bit blocks
bits.generate_target()
bits.target.scale(1.5)
bits.target.arrange_in_grid(n_cols=11, h_buff=SMALL_BUFF, v_buff=LARGE_BUFF)
bits.target.move_to(bits, UR)
box_groups = VGroup()
box_arrows = VGroup()
for bit in bits.target[0:77:11]:
boxes = VGroup(*[Square() for x in range(16)])
boxes.arrange_in_grid(4, 4, buff=0)
boxes.set_height(0.8)
boxes.next_to(bit, LEFT, buff=1.5)
boxes.set_stroke(GREY_B, 2)
arrow = Arrow(bit.get_left(), boxes.get_right())
box_arrows.add(arrow)
box_groups.add(boxes)
self.play(
FadeOut(image, 2 * LEFT),
MoveToTarget(bits, run_time=2),
LaggedStartMap(FadeIn, box_groups, run_time=4),
LaggedStartMap(GrowArrow, box_arrows, run_time=4),
)
self.wait()
# Isolate to one box
first_bits = bits[:11]
first_boxes = box_groups[0]
first_bits.generate_target()
first_bits.target.set_height(0.6)
first_bits.target.to_edge(UP)
first_bits.target.set_x(-3)
first_boxes.generate_target()
first_boxes.target.set_height(5)
first_boxes.target.next_to(first_bits.target, DOWN, LARGE_BUFF)
self.play(
MoveToTarget(first_bits),
MoveToTarget(first_boxes),
LaggedStart(*map(FadeOut, [
*bits[11:], box_arrows, box_groups[1:],
*lines, words,
]), lag_ratio=0.01),
run_time=2
)
bits = first_bits
boxes = first_boxes
# Try it yourself
morty = Mortimer()
morty.to_edge(DR)
self.play(
PiCreatureSays(morty, "Try it\\\\yourself", target_mode="hooray"),
VFadeIn(morty)
)
self.play(Blink(morty))
self.wait(2)
self.play(LaggedStart(
FadeOut(morty),
FadeOut(morty.bubble),
FadeOut(morty.bubble.content),
))
# Fill block
N = 4
ecc_boxes = VGroup(*[boxes[2**n] for n in range(N)])
message_boxes = VGroup(*[
box for box in boxes[1:]
if box not in ecc_boxes
])
pos_labels = get_grid_position_labels(boxes, height=0.2)
pos_labels.set_color(GREY_B)
self.play(
ecc_boxes.set_fill, GREEN, 0.7,
boxes[0].set_fill, YELLOW, 0.5,
FadeIn(pos_labels, lag_ratio=0.1)
)
self.wait()
self.play(LaggedStart(*[
ApplyMethod(bit.move_to, box)
for bit, box in zip(bits, message_boxes)
], run_time=4, lag_ratio=0.3))
self.wait()
# Organize bits properly
bit_template = bits[0].copy()
if get_bit_mob_value(bit_template) == 1:
toggle_bit(bit_template)
new_bits = [None] * 16
for i in [0, 1, 2, 4, 8]:
new_bits[i] = bit_template.copy()
new_bits[i].move_to(boxes[i])
bits_iter = iter(bits)
for i, new_bit in enumerate(new_bits):
if new_bit is None:
new_bits[i] = next(bits_iter)
bits = VGroup(*new_bits)
# Show parity groups
boxes.save_state()
self.add(boxes, pos_labels, bits)
for bit in bits:
for part in bit:
if part.get_fill_opacity() > 0:
part.set_fill(opacity=1)
bit.save_state()
VGroup(*bits[:3], bits[4], bits[8]).set_opacity(0)
for n in range(N):
boxes.generate_target()
boxes.target.set_fill(BLACK, 0)
get_bit_n_subgroup(boxes.target, n).set_fill(BLUE, 0.8)
for k in range(n):
boxes.target[2**k].set_fill(GREEN, 0.5)
one_rects = get_one_rects(get_bit_n_subgroup(bits, n))
counter = get_ones_counter(boxes[10:12], one_rects, buff=1.5)
counter.match_height(bits[0])
self.play(MoveToTarget(boxes))
self.add(counter)
self.play(ShowIncreasingSubsets(one_rects))
self.wait()
self.play(Restore(bits[2**n]))
if counter.get_value() % 2 == 1:
rect_copy = one_rects[0].copy()
rect_copy.move_to(bits[2**n])
one_rects.add(rect_copy)
bits[2**n][0].set_opacity(1)
toggle_bit(bits[2**n])
self.add(rect_copy)
self.wait()
self.play(
LaggedStartMap(FadeOut, VGroup(*one_rects, counter), run_time=1),
)
self.play(Restore(boxes))
# Final parity check
one_rects = get_one_rects(bits)
counter = get_ones_counter(boxes[10:12], one_rects, buff=1.5)
counter.match_height(bits[0])
self.add(counter)
self.play(ShowIncreasingSubsets(one_rects))
self.wait()
self.play(Restore(bits[0]))
self.play(LaggedStartMap(FadeOut, VGroup(*one_rects, counter)))
self.wait()
# Send as a message
block_group = VGroup(boxes, pos_labels, bits)
pis, names = get_sender_and_receiver()
randy, morty = pis
line = Line(randy.get_corner(UR), morty.get_corner(UL), buff=MED_SMALL_BUFF)
line.add(
Dot().move_to(line.get_start(), RIGHT),
Dot().move_to(line.get_end(), LEFT),
)
line.set_stroke(GREY, 2)
line_label = TextMobject("Noisy channel")
line_label.next_to(line, DOWN, SMALL_BUFF)
line_label.set_color(RED)
self.play(
VFadeIn(pis),
FadeIn(names, DOWN),
randy.change, "raise_right_hand",
block_group.set_height, 1,
block_group.move_to, randy.get_corner(UR), DOWN,
block_group.shift, 0.1 * UP,
)
self.play(
ShowCreation(line),
Write(line_label)
)
self.play(Blink(randy))
self.play(
block_group.match_x, line,
randy.change, "happy", morty.eyes,
)
# Possible changes
black_box = SurroundingRectangle(block_group, buff=0)
black_box.set_fill(GREY_D, 1)
black_box.set_stroke(WHITE, 2)
change_words = VGroup(
TextMobject("Maybe flip 0 bits"),
TextMobject("Maybe flip 1 bit"),
TextMobject("Maybe flip 2 bits"),
)
colors = [WHITE, RED_B, RED]
for word, color in zip(change_words, colors):
word.next_to(black_box, UP)
word.set_color(color)
morty_arrow = Vector(DOWN)
morty_arrow.next_to(morty, UP)
self.play(
GrowArrow(morty_arrow),
morty.change, "pondering", morty_arrow
)
self.play(ShowCreationThenFadeAround(morty))
self.play(Blink(morty))
self.wait()
self.play(
morty_arrow.next_to, block_group, UP,
FadeIn(black_box),
morty.look_at, black_box,
)
self.play(Blink(randy))
self.wait()
self.play(Blink(randy))
self.play(
FadeIn(change_words[0], 0.25 * DOWN),
FadeOut(morty_arrow, UP),
morty.change, "confused", change_words[0],
)
for i in [1, 2]:
self.play(
FadeIn(change_words[i], 0.25 * DOWN),
change_words[:i].shift, UP,
)
self.wait()
self.play(Blink(morty))
error_pos = 10
toggle_bit(bits[error_pos])
self.add(block_group, black_box)
self.play(
FadeOut(black_box),
FadeOut(change_words),
)
self.play(
block_group.set_x, line.get_end()[0],
morty.change, "pondering", line.get_end() + UP,
)
self.play(
Uncreate(line),
FadeOut(line_label, DOWN),
FadeOut(randy, DL),
FadeOut(morty, DR),
FadeOut(names, DOWN),
block_group.set_height, 5,
block_group.set_y, 0,
block_group.to_edge, RIGHT,
)
self.wait()
# Try it!
try_it_words = TextMobject("Try it\\\\yourself!")
try_it_words.scale(2)
try_it_words.next_to(boxes, LEFT, buff=2)
self.play(FadeIn(try_it_words, RIGHT))
self.wait()
self.play(FadeOut(try_it_words, LEFT))
# Do parity checks
working_grid = boxes.copy()
working_grid.to_edge(LEFT)
working_grid_words = TextMobject("Possibilities")
working_grid_words.set_color(BLUE)
working_grid_words.next_to(working_grid, UP)
working_grid.set_fill(BLUE, 0.7)
working_pos_labels = get_grid_position_labels(working_grid)
counter = Integer(0)
counter.set_height(0.7)
counter.set_color(YELLOW)
counter.next_to(boxes, LEFT, MED_LARGE_BUFF)
counter.counted = VGroup()
counter.add_updater(lambda m: m.set_value(len(m.counted)))
for n in range(N):
off_bits = get_bit_n_subgroup(bits, n, 0)
on_bits = get_bit_n_subgroup(bits, n, 1)
rects = get_one_rects(on_bits)
counter.counted = rects
self.play(FadeOut(off_bits))
self.add(counter)
self.play(ShowIncreasingSubsets(rects))
to_fade = get_bit_n_subgroup(working_grid, n, 1 - (len(rects) % 2))
if n == 0:
to_fade.set_fill(BLACK, 0)
self.play(
FadeIn(working_grid),
FadeIn(working_pos_labels),
FadeIn(working_grid_words),
)
else:
self.play(to_fade.set_fill, BLACK, 0)
self.wait()
self.play(
FadeOut(counter),
FadeOut(rects),
FadeIn(off_bits),
)
# Move working grid
self.add(working_grid, block_group)
self.play(
ApplyMethod(working_grid.move_to, boxes, run_time=2),
FadeOut(working_grid_words),
FadeOut(working_pos_labels),
)
# Full parity check
rects = get_one_rects(bits)
counter.counted = rects
self.add(counter)
self.play(ShowIncreasingSubsets(rects))
self.wait()
self.play(FadeOut(rects), FadeOut(counter))
# Correct error bit
self.play(toggle_bit_anim(bits[error_pos]))
self.play(FadeOut(working_grid))
self.wait()
# Show 11 message bits
block_group.generate_target()
block_group.target.center()
block_group.target.to_edge(DOWN)
VGroup(*[
block_group.target[2][i]
for i in [0, 1, 2, 4, 8]
]).set_color(GREY_C)
self.play(MoveToTarget(block_group))
message_bits = VGroup(*[
bit for i, bit in enumerate(bits)
if i not in [0, 1, 2, 4, 8]
])
message_bits.generate_target()
message_bits.target.arrange(RIGHT, buff=SMALL_BUFF)
message_bits.target.to_edge(UP)
for mb, mt in zip(message_bits, message_bits.target):
mb.target = mt
self.play(LaggedStartMap(
MoveToTarget, message_bits,
lag_ratio=0.3,
run_time=4,
))
self.wait()
def old_parity_checks(self):
questions = VGroup(*[boxes.copy() for x in range(4)])
questions.set_height(1)
questions.arrange(DOWN, buff=0.5)
questions.set_height(6)
questions.to_edge(LEFT, buff=1)
counter = Integer(0, color=YELLOW)
counter.match_height(bits[0])
counter.next_to(boxes, LEFT, LARGE_BUFF)
boxes.save_state()
self.add(boxes, pos_labels, bits)
results = VGroup()
for n, question in enumerate(questions):
question.set_fill(BLACK, 0)
get_bit_n_subgroup(question, n).set_fill(BLUE, 0.7)
one_rects = get_one_rects(get_bit_n_subgroup(bits, n))
counter.set_value(len(one_rects))
boxes.generate_target()
boxes.target.match_style(question)
self.play(MoveToTarget(boxes))
self.play(FadeIn(one_rects))
if counter.get_value() % 2 == 0:
result = Integer(0, color=GREEN)
else:
result = Integer(1, color=RED)
result.next_to(question, RIGHT)
results.add(result)
counter_mover = counter.copy()
counter_mover.generate_target()
counter_mover.target.replace(result, stretch=True)
counter_mover.target.fade(1)
result.save_state()
result.replace(counter, stretch=True)
result.fade(1)
self.play(
ReplacementTransform(boxes.copy().set_fill(opacity=0), question),
MoveToTarget(counter_mover, remover=True),
Restore(result),
FadeOut(one_rects),
FadeOut(counter),
)
self.wait()
one_rects = get_one_rects(bits)
counter.set_value(len(one_rects))
words = TextMobject("Likely one error")
words.next_to(counter, DOWN, LARGE_BUFF, aligned_edge=RIGHT)
self.play(
boxes.set_fill, BLACK, 0,
FadeIn(counter),
FadeIn(one_rects),
)
self.wait()
self.play(FadeIn(words, 0.1 * UP))
self.wait()
self.play(
FadeOut(VGroup(counter, words, one_rects), lag_ratio=0.2)
)
# Read result
final_result = results.copy()
final_result.arrange(LEFT, buff=SMALL_BUFF)
equation = VGroup(
final_result,
TexMobject("\\rightarrow"),
Integer(10)
)
equation.arrange(RIGHT)
equation.to_edge(UP)
self.play(TransformFromCopy(results, final_result, run_time=3, lag_ratio=0.3))
self.play(
Write(equation[1]),
FadeIn(equation[2], LEFT),
)
self.wait()
boxes.generate_target()
boxes.target[10].set_fill(BLUE, 0.7)
self.play(MoveToTarget(boxes))
self.play(toggle_bit_anim(bits[10]))
self.wait()
self.play(
LaggedStartMap(
FadeOut, VGroup(*equation, *questions, *results),
lambda m: (m, DOWN),
),
Restore(boxes),
)
class ByHandVsSoftwareVsHardware(Scene):
def construct(self):
self.add(get_background(GREY_E))
rects = VGroup(*[ScreenRectangle() for x in range(3)])
rects.set_stroke(GREY_B)
rects.set_fill(BLACK, 1)
rects.arrange(RIGHT, buff=MED_LARGE_BUFF)
rects.set_width(FRAME_WIDTH - 1)
rects[0].shift(UP)
rects[2].shift(DOWN)
self.add(rects)
labels = VGroup(
TextMobject("By hand"),
TextMobject("In software"),
TextMobject("In hardware"),
)
for label, rect in zip(labels, rects):
label.next_to(rect, DOWN)
randy = Randolph(height=1.25)
randy.next_to(rects[0], UP, SMALL_BUFF)
self.play(
LaggedStartMap(
FadeIn, labels,
lambda m: (m, 0.5 * UP),
lag_ratio=0.4,
),
randy.change, 'thinking', rects[0],
)
self.play(Blink(randy))
self.wait()
randy.generate_target()
randy.target.next_to(rects[1], UP, SMALL_BUFF)
randy.target.change("hooray", rects[1])
self.play(
MoveToTarget(randy, path_arc=-45 * DEGREES)
)
self.play(Blink(randy))
self.play(randy.change, 'thinking', rects[1])
self.play(Blink(randy))
self.wait()
class EndScreen(Scene):
def construct(self):
self.add(get_background(GREY_E))
rects = VGroup(*[ScreenRectangle() for x in range(2)])
rects.set_stroke(WHITE, 1)
rects.set_fill(BLACK, 1)
rects.set_height(3)
rects.arrange(RIGHT, buff=1)
rects.shift(UP)
self.add(rects)
labels = VGroup(
TextMobject("Part 2\\\\", "the elegance of it all"),
TextMobject("Ben Eater\\\\", "doing this on breadboards"),
)
for label, rect in zip(labels, rects):
label[0].scale(1.5, about_edge=DOWN)
label.scale(0.9)
label.next_to(rect, DOWN)
self.add(labels)
self.add(AnimatedBoundary(rects[0]))
self.wait()
self.add(AnimatedBoundary(rects[1]))
self.wait(19)
# Part 2
class Thumbnail2(Scene):
def construct(self):
code = ImageMobject("HammingCodeOneLine")
code.set_width(FRAME_WIDTH - 3)
self.add(code)
words = VGroup(
# TextMobject("Why one line\\\\"),
# TextMobject("finds bit errors"),
TextMobject("Hamming codes\\\\"),
TextMobject("in one(ish) line"),
)
words.set_width(FRAME_WIDTH - 3)
words[0].to_edge(UP, buff=0.5)
words[1].to_edge(DOWN, buff=0.5)
words[0].set_color(BLUE_C)
words[1].set_color(BLUE_B)
self.add(words)
class Part1Wrapper(Scene):
def construct(self):
self.add(get_background())
rect = ScreenRectangle()
rect.set_fill(BLACK, 1)
rect.set_stroke(GREY_B, 2)
rect.set_height(6)
rect.to_edge(DOWN)
title = TextMobject("Part 1")
title.set_height(0.7)
title.to_edge(UP)
self.add(rect)
self.add(AnimatedBoundary(rect, max_stroke_width=2, cycle_rate=0.25))
self.play(Write(title))
self.wait(35)
class AskHowItsImplemented(TeacherStudentsScene):
def construct(self):
self.student_says("How do you\\\\implement this?")
self.play(
self.teacher.change, "happy",
self.get_student_changes("pondering", "confused"),
)
self.look_at(self.screen)
self.wait(6)
class ScaleUp(Scene):
def construct(self):
square_template = Square()
square_template.set_stroke(GREY_B, 2)
square_template.set_height(1)
zero = Integer(0)
one = Integer(1)
last_grid = None
last_parity_groups = None
last_words = None
for N in range(4, 13):
grid = VGroup(*[square_template.copy() for x in range(2**N)])
grid.arrange_in_grid(
2**int(math.floor(N / 2)),
2**int(math.ceil(N / 2)),
buff=0
)
grid.set_width(5.5)
if N > 8:
grid.set_stroke(width=1)
elif N > 10:
grid.set_stroke(width=0.25)
grid.set_stroke(background=True)
grid[0].set_fill(YELLOW, 0.6)
for n in range(N):
grid[2**n].set_fill(GREEN, 0.7)
parity_groups = VGroup()
for n in range(N):
group = grid.copy()
group.set_fill(BLACK, 0)
get_bit_n_subgroup(group, n).set_fill(BLUE, 0.8)
parity_groups.add(group)
parity_groups.arrange_in_grid(n_cols=2, buff=1.5)
parity_groups.set_width(5)
max_height = 7
if parity_groups.get_height() > max_height:
parity_groups.set_height(max_height)
parity_groups.to_edge(RIGHT, buff=0.5)
random.seed(0)
for square in grid:
bit = random.choice([zero, one]).copy()
bit.replace(square, dim_to_match=1)
bit.scale(0.5)
square.add(bit)
redun = "{:.3}".format((N + 1) / (2**N))
words = TexMobject(
f"""
{{ {{{N + 1}}} \\text{{ parity bits}}
\\over
{2**N} \\text{{ bits per block}} }}
\\approx {redun}
""",
tex_to_color_map={
f"{{{N + 1}}}": GREEN,
f"{2**N}": WHITE,
f"{redun}": YELLOW,
},
fill_color=GREY_A
)
words.to_corner(UL, buff=MED_SMALL_BUFF)
grid.next_to(words, DOWN, aligned_edge=LEFT)
if last_grid is None:
self.add(grid)
self.add(parity_groups)
self.add(words)
else:
self.play(
ReplacementTransform(last_grid, grid[:2**(N - 1)]),
FadeIn(grid[2**(N - 1):], lag_ratio=0.1, run_time=2),
FadeOut(last_parity_groups),
LaggedStartMap(FadeIn, parity_groups, lag_ratio=0.2, run_time=2),
FadeOut(last_words, UP),
FadeIn(words, DOWN),
)
self.wait()
last_grid = grid
last_parity_groups = parity_groups
last_words = words
class MillionRatio(Scene):
def construct(self):
# Largely copied from above
N = 20
words = TexMobject(
"""
{21 \\text{ parity bits}
\\over
1{,}048{,}576 \\text{ bits per block} }
\\approx 0.00002
""",
tex_to_color_map={
"21": GREEN,
"1{,}048{,}576": WHITE,
"0.00002": YELLOW,
},
fill_color=GREY_A
)
words.to_corner(UL, buff=MED_SMALL_BUFF)
self.add(words)
st = Square()
st.set_stroke(GREY, 0.5)
grid = VGroup(*[st.copy() for x in range(2**(16))])
grid.arrange_in_grid(buff=0)
grid.set_height(6)
grid.next_to(words, DOWN, aligned_edge=LEFT)
self.add(grid)
k = 17
positions = VGroup(*[
TexMobject(int_to_bit_string(n, n_bits=20))
for n in [*range(k), *range(2**N - k // 2, 2**N)]
])
positions.replace_submobject(-k // 2, TexMobject("\\vdots"))
positions.arrange(DOWN)
positions.set_height(7)
positions.to_edge(RIGHT)
for n in [0, 1, 2, 4, 8, 16]:
positions[n].set_color(GREEN_B)
brace = Brace(positions, LEFT, buff=SMALL_BUFF)
p_label = TextMobject("$2^{20}$\\\\positions")
p_label.next_to(brace, LEFT, SMALL_BUFF)
self.play(
ShowIncreasingSubsets(positions, run_time=3),
GrowFromPoint(brace, brace.get_top(), run_time=3, rate_func=squish_rate_func(smooth, 0.5, 1)),
FadeIn(p_label, 0.5 * RIGHT, run_time=3, rate_func=squish_rate_func(smooth, 0.5, 1)),
)
self.wait()
grid.set_stroke(background=True)
for n in range(16):
grid.set_fill(BLACK, 0)
get_bit_n_subgroup(grid, n).set_fill(BLUE, 0.8)
self.wait(0.5)
class BurstErrors(Scene):
def construct(self):
# Setup
bl = 8
nb = 4
bits = get_bit_grid(1, bl * nb, bits=string_to_bits("3b1b"))
bits.set_height(0.5)
bits.arrange(RIGHT, buff=SMALL_BUFF)
bits.move_to(DOWN)
self.add(bits)
colors = [BLUE, YELLOW, MAROON_B, TEAL]
block_words = VGroup(*[
TextMobject(f"Block {n}", fill_color=color)
for n, color in zip(range(nb), colors)
])
block_words.set_height(0.5)
block_words.arrange(RIGHT, buff=LARGE_BUFF)
block_words.move_to(2 * UP)
self.add(block_words)
# Add lines
lines = VGroup()
for n, bit in enumerate(bits):
words = block_words[n // bl]
line = Line()
line.match_color(words)
line.set_stroke(width=2)
line.words = words
line.bit = bit
bit.line = line
underline = Underline(bit)
underline.set_stroke(words.get_color(), 4)
bit.add(underline)
line.add_updater(lambda m: m.put_start_and_end_on(
m.words.get_bottom() + SMALL_BUFF * DOWN,
m.bit.get_top(),
))
lines.add(line)
self.play(LaggedStartMap(ShowCreation, lines, suspend_mobject_updating=True))
self.wait()
# Show burst error
error_bits = bits[9:13]
error_words = TextMobject("Burst of errors")
error_words.next_to(error_bits, DOWN)
error_words.set_color(RED)
ruined_words = TextMobject("Ruined")
ruined_words.set_color(RED)
ruined_words.next_to(block_words[1], UP)
strike = Line(LEFT, RIGHT)
strike.replace(block_words[1])
strike.set_color(RED)
self.play(
LaggedStartMap(toggle_bit_anim, error_bits, target_color=RED),
LaggedStart(*map(zap_anim, error_bits)),
Write(error_words)
)
self.play(
ShowCreation(strike),
FadeIn(ruined_words, 0.5 * DOWN)
)
self.wait()
self.play(
FadeOut(ruined_words, 0.2 * UP),
FadeOut(error_words, 0.2 * DOWN),
FadeOut(strike),
LaggedStartMap(toggle_bit_anim, error_bits, target_color=WHITE),
run_time=1,
)
for bit in error_bits:
bit[-1].set_color(YELLOW)
# Rearrange
new_order = VGroup()
for i in range(bl):
for j in range(nb):
new_order.add(bits[bl * j + i])
new_order.generate_target()
new_order.target.arrange(RIGHT, buff=SMALL_BUFF)
new_order.target.replace(bits)
self.play(MoveToTarget(new_order, run_time=3, path_arc=30 * DEGREES))
self.wait()
# New burst
error_bits = new_order[9:13]
self.play(
LaggedStartMap(toggle_bit_anim, error_bits, target_color=RED),
LaggedStart(*map(zap_anim, error_bits)),
Write(error_words),
)
non_error_lines = VGroup()
for line in lines:
if line.bit not in error_bits:
non_error_lines.add(line)
self.play(non_error_lines.set_stroke, {"width": 1, "opacity": 0.5})
self.wait()
error_words = VGroup(*[TextMobject("1 error", fill_color=GREEN) for x in range(4)])
for ew, bw in zip(error_words, block_words):
ew.next_to(bw, UP, MED_LARGE_BUFF)
self.play(LaggedStartMap(FadeIn, error_words, lambda m: (m, DOWN)))
self.wait()
class BinaryCounting(Scene):
def construct(self):
def get_bit_grids(bit_values):
left_bits = get_bit_grid(4, 1, buff=LARGE_BUFF, bits=bit_values, height=6)
left_bits.move_to(3 * LEFT)
right_bits = get_bit_grid(1, 4, buff=SMALL_BUFF, bits=bit_values, height=0.6)
right_bits.set_submobjects(list(reversed(right_bits)))
right_bits.move_to(RIGHT + 2 * UP)
return VGroup(left_bits, right_bits)
bit_grids = get_bit_grids([0, 0, 0, 0])
brace = Brace(bit_grids[1], UP)
counter = Integer(0, edge_to_fix=ORIGIN)
counter.match_height(bit_grids[1])
counter.set_color(BLUE)
counter.next_to(brace, UP)
boxes = VGroup(*[Square() for x in range(16)])
boxes.arrange_in_grid(4, 4, buff=0)
boxes.set_height(4)
boxes.next_to(bit_grids[1], DOWN, LARGE_BUFF)
boxes.set_stroke(GREY_B, 2)
pos_labels = get_grid_position_labels(boxes)
self.add(bit_grids)
self.add(brace)
self.add(counter)
self.add(boxes)
self.add(pos_labels)
for n in range(16):
bit_values = list(map(int, int_to_bit_string(n, n_bits=4)))
boxes.generate_target()
boxes.target.set_fill(BLACK, 0)
boxes.target[n].set_fill(BLUE, 0.8)
anims = [
ChangeDecimalToValue(counter, n),
MoveToTarget(boxes)
]
for grid in bit_grids:
for bit, bv in zip(grid, reversed(bit_values)):
if get_bit_mob_value(bit) != bv:
anims.append(toggle_bit_anim(bit))
self.play(*anims, run_time=0.5)
self.wait()
class ReviewOfXOR(Scene):
CONFIG = {
"random_seed": 2,
}
def construct(self):
# Setup equations
xor = get_xor()
equations = VGroup()
for n in range(4):
bits = list(map(int, int_to_bit_string(n, n_bits=2)))
equation = VGroup(
Integer(bits[0]),
xor.copy(),
Integer(bits[1]),
TexMobject("="),
Integer(op.xor(*bits)),
)
equation.set_height(0.6)
equation.arrange(RIGHT)
equations.add(equation)
equations.arrange(DOWN, buff=LARGE_BUFF)
# Intro xor
equation = equations[1]
equation.save_state()
equation.center()
equation[3:].set_opacity(0)
arrow = Vector(0.7 * DOWN)
arrow.next_to(equation[1], UP, SMALL_BUFF)
xor_word = TextMobject("xor")
xor_word_long = TextMobject("``exclusive or''")
xor_words = VGroup(xor_word, xor_word_long)
xor_words.scale(1.5)
xor_words.match_color(xor)
xor_words.next_to(arrow, UP)
self.add(equation)
self.play(
FadeIn(equation[1], 0.5 * UP),
GrowArrow(arrow),
Write(xor_word),
)
self.wait()
self.play(
xor_word.next_to, xor_word_long, UP, MED_SMALL_BUFF,
FadeIn(xor_word_long, DOWN),
)
self.wait()
self.play(
FadeOut(arrow),
xor_words.to_corner, UL,
Restore(equation),
FadeIn(equations[2], UP),
)
self.wait()
self.play(
FadeIn(equations[0], DOWN),
FadeIn(equations[3], UP),
)
self.wait()
# Parity of two bits
parity_words = TextMobject("Parity of\\\\two bits")
parity_words.set_color(YELLOW)
parity_words.scale(1.5)
parity_words.to_edge(RIGHT, buff=MED_LARGE_BUFF)
arrows = VGroup()
for equation in equations:
globals()['equation'] = equation
new_arrows = VGroup(*[
Arrow(equation[i].get_top(), equation[4].get_top(), path_arc=-60 * DEGREES)
for i in [0, 2]
])
new_arrows.set_color(YELLOW)
arrows.add(new_arrows)
self.play(
LaggedStartMap(DrawBorderThenFill, arrows),
FadeIn(parity_words)
)
self.wait()
# Addition mod 2
mod2_words = TextMobject("Addition\\\\mod 2")
mod2_words.scale(1.5)
mod2_words.move_to(parity_words, RIGHT)
mod2_words.set_color(BLUE)
self.play(
FadeIn(mod2_words, DOWN),
FadeOut(parity_words, UP),
FadeOut(arrows)
)
self.wait()
# xor of two bit strings
row_len = 8
bit_strings = VGroup(*[
Integer(random.choice([0, 1]), edge_to_fix=ORIGIN)
for x in range(row_len * 3)
])
bit_strings.arrange_in_grid(
3, row_len,
h_buff=SMALL_BUFF,
v_buff=MED_LARGE_BUFF,
fill_rows_first=False
)
bit_strings.scale(equation[0][0].get_height() / bit_strings[0].get_height())
rows = VGroup(*[bit_strings[i::3] for i in range(3)])
rows[0].next_to(rows[1], UP, buff=0.25)
line = Line(LEFT, RIGHT)
line.set_width(bit_strings.get_width() + 1)
line.move_to(bit_strings[-2:], RIGHT)
line.set_stroke(GREY_B, 3)
line_xor = xor.copy()
line_xor.set_height(0.5)
line_xor.next_to(line, UP, aligned_edge=LEFT)
for b1, b2, b3 in zip(*[row for row in rows]):
b3.set_value(b1.get_value() ^ b2.get_value())
b2.match_x(b1)
b3.match_x(b1)
equations.generate_target()
for n, eq in enumerate(equations.target):
for k, b1 in enumerate(eq[0::2]):
target_bit = bit_strings[3 * n + k]
target_bit.set_value(b1.get_value())
b1.become(target_bit)
eq[1].become(line_xor)
eq[3].fade(1)
self.play(
MoveToTarget(equations, run_time=2),
FadeOut(mod2_words),
FadeIn(bit_strings[12:], run_time=2, rate_func=squish_rate_func(smooth, 0.5, 1), lag_ratio=0.1),
ShowCreation(line, run_time=2, rate_func=squish_rate_func(smooth, 0.5, 1))
)
self.remove(equations)
self.add(bit_strings, line_xor)
self.wait()
# Highlight columns
last_rect = VMobject()
for b1, b2, b3 in zip(*[row for row in rows]):
rect = SurroundingRectangle(VGroup(b1, b2, b3), buff=SMALL_BUFF)
rect.set_stroke(BLUE, 2)
self.play(FadeIn(rect), FadeOut(last_rect))
last_rect = rect
self.play(FadeOut(last_rect))
# Add more rows
new_rows = VGroup(*[rows[0].copy() for x in range(3)])
new_rows.arrange(DOWN, buff=0.2)
new_rows.next_to(rows, UP, buff=0.2)
for row in new_rows:
for bit in row:
bit.set_value(random.choice([0, 1]))
self.play(
LaggedStartMap(FadeIn, new_rows, lambda m: (m, DOWN)),
FadeOut(xor_words),
FadeOut(rows[2])
)
# Compute parities
parity_words = TextMobject("Computes\\\\parity\\\\of each\\\\column", alignment="")
parity_words.set_color(YELLOW)
parity_words.to_corner(UL)
self.add(parity_words)
for tup in zip(*[*new_rows, *rows]):
rects = VGroup()
for bit in tup[:-1]:
if bit.get_value() == 1:
rect = SurroundingRectangle(bit)
rect.set_stroke(YELLOW, 2)
rect.set_fill(YELLOW, 0.5)
rects.add(rect)
tup[-1].set_value(len(rects) % 2)
tup[-1].set_color(YELLOW)
self.add(rects, *tup)
self.wait()
self.remove(rects)
self.wait()
# Simpler sum
self.play(FadeOut(
VGroup(new_rows, rows[0][4:], rows[1][4:], rows[2][4:]),
lag_ratio=0.1,
))
for b1, b2, b3 in zip(*[row[:4] for row in rows]):
b3.set_value(b1.get_value() ^ b2.get_value())
self.wait(0.25)
arrows = VGroup()
integers = VGroup()
for row, n in zip(rows, [3, 5, 6]):
arrow = Vector(0.75 * RIGHT)
arrow.next_to(row[:4], RIGHT, buff=0.2)
integer = Integer(n)
integer.match_height(row[0])
integer.match_color(row[0])
integer.next_to(arrow, RIGHT, buff=0.2)
self.play(
GrowArrow(arrow),
FadeIn(integer, LEFT)
)
arrows.add(arrow)
integers.add(integer)
class ButWhy(TeacherStudentsScene):
def construct(self):
self.student_says(
"Hang on...\\\\why?", target_mode="confused",
added_anims=[self.teacher.change, "tease"]
)
self.change_student_modes(
"maybe", "erm", "confused",
look_at_arg=self.screen,
)
self.wait(6)
class WhyPointToError(Scene):
def construct(self):
rect = SurroundingRectangle(TextMobject("0000").scale(2))
rect.to_edge(RIGHT)
rect.set_stroke(RED, 3)
words = TextMobject("Why do these\\\\point to an error?")
arrow = Vector(0.7 * RIGHT)
arrow.next_to(rect, LEFT)
words.next_to(arrow, LEFT)
words.set_color(RED)
bit = Integer(0).scale(2)
bit.next_to(words, UP, buff=0.45)
bit.shift(1.11 * words.get_width() * LEFT)
self.play(
Write(words),
ShowCreation(rect),
)
self.play(GrowArrow(arrow))
self.wait()
self.play(
rect.become, SurroundingRectangle(bit).match_style(rect),
FadeOut(arrow)
)
self.wait()
class SimplePointer(Scene):
def construct(self):
arrow = Arrow(ORIGIN, [-4, 1.5, 0])
arrow.center()
arrow.set_fill(GREY_B)
self.play(DrawBorderThenFill(arrow))
self.wait()
class ArrowPair(Scene):
def construct(self):
arrows = VGroup(
Vector(LEFT),
Vector(LEFT),
)
arrows.scale(1.5)
arrows.arrange(DOWN, buff=2)
arrows[0].shift(4 * LEFT)
arrows.set_fill(YELLOW)
self.play(*map(GrowArrow, arrows))
self.wait()
class PythonXorExample(ExternallyAnimatedScene):
pass
class HammingCodesWithXOR(Scene):
def construct(self):
# Setup
bits = get_bit_grid(4, 4, bits=string_to_bits(":)"))
bits.to_edge(LEFT, buff=1.5)
boxes = get_bit_grid_boxes(bits)
block = VGroup(boxes, bits)
block.set_height(6)
for bit in bits:
bit.set_height(0.7)
bin_pos_labels = VGroup()
dec_pos_labels = VGroup()
for n, box, bit in zip(it.count(), boxes, bits):
bin_label = VGroup(
*[Integer(int(c)) for c in int_to_bit_string(n, n_bits=4)]
)
bin_label.arrange(RIGHT, buff=SMALL_BUFF, aligned_edge=DOWN)
bin_label.set_color(GREY_B)
bin_label.set_width(0.7 * box.get_width())
bin_label.move_to(box, DOWN)
bin_label.shift(SMALL_BUFF * UP)
bin_pos_labels.add(bin_label)
dec_label = Integer(n)
dec_label.match_height(bin_label)
dec_label.match_style(bin_label[0])
dec_label.move_to(bin_label, DR)
dec_pos_labels.add(dec_label)
bit.generate_target()
bit.target.scale(0.9)
bit.target.move_to(box, UP)
bit.target.shift(MED_SMALL_BUFF * DOWN)
# Enumerate
self.add(block)
kw = {"lag_ratio": 0.3, "run_time": 2}
self.play(LaggedStartMap(FadeIn, dec_pos_labels, **kw))
self.wait()
kw["lag_ratio"] = 0.1
self.play(
LaggedStartMap(FadeOut, dec_pos_labels, **kw),
LaggedStartMap(FadeIn, bin_pos_labels, **kw),
LaggedStartMap(MoveToTarget, bits, **kw),
)
self.wait()
# Highlight ones
summands = VGroup()
for bit, box, label in zip(bits, boxes, bin_pos_labels):
for mob in bit, box, label:
mob.save_state()
mob.generate_target()
if get_bit_mob_value(bit) == 1:
box.target.set_fill(BLUE, 0.7)
summands.add(label.copy())
else:
bit.target.fade(0.5)
label.target.fade(0.5)
self.play(*[
LaggedStartMap(MoveToTarget, mob, lag_ratio=0.02)
for mob in [boxes, bits, bin_pos_labels]
])
self.wait()
# Arrange sum
summands.generate_target()
summands.target.arrange(DOWN, buff=SMALL_BUFF)
summands.target.set_width(1.5)
summands.target.set_color(WHITE)
summands.target.to_edge(RIGHT, buff=1.5)
summands.target.to_edge(UP)
line = Line(LEFT, RIGHT)
xor = get_xor()
line.set_width(summands.target.get_width() + 0.75)
line.next_to(summands.target, DOWN, aligned_edge=RIGHT, buff=SMALL_BUFF)
xor.next_to(line, UP, aligned_edge=LEFT)
self.play(
MoveToTarget(summands, run_time=2),
ShowCreation(line),
ShowCreation(xor),
)
self.wait()
# Perform xor
result = VGroup()
rect_columns = VGroup()
for tup in zip(*summands):
rects = get_one_rects(tup)
result_bit = get_bit_grid(1, 1, bits=[len(rects) % 2])[0]
result_bit.replace(tup[-1], dim_to_match=1)
result_bit.shift(1.5 * result_bit.get_height() * DOWN)
result_bit.set_color(YELLOW)
result.add(result_bit)
rect_columns.add(rects)
pre_result = VGroup()
for summand in summands:
pr = result.copy()
pr.save_state()
pr.move_to(summand)
pr.fade(1)
pre_result.add(pr)
self.play(LaggedStartMap(Restore, pre_result, lag_ratio=0.05, remover=True))
self.add(result)
self.wait()
self.play(ShowCreationThenFadeOut(SurroundingRectangle(result, stroke_color=BLUE)))
self.wait()
for n, rects, result_bit in zip(it.count(), reversed(rect_columns), reversed(result)):
faders = VGroup()
for bit_vect in [*summands, result]:
for k, bit in enumerate(reversed(bit_vect)):
if k != n:
bit.generate_target()
bit.target.fade(0.7)
faders.add(bit)
for group in bits, bin_pos_labels:
sg = get_bit_n_subgroup(group, n, 0)
sg.generate_target()
sg.target.fade(1)
faders.add(sg)
faders.save_state()
self.play(LaggedStartMap(MoveToTarget, faders, lag_ratio=0, run_time=1))
new_rects = VGroup()
for bit, pos in zip(get_bit_n_subgroup(bits, n), get_bit_n_subgroup(bin_pos_labels, n)):
if get_bit_mob_value(bit) == 1:
nr = SurroundingRectangle(pos[3 - n], buff=0.05)
nr.set_fill(YELLOW, 0.25)
new_rects.add(nr)
self.play(
ShowIncreasingSubsets(rects),
ShowIncreasingSubsets(new_rects),
)
self.wait()
self.play(
faders.restore,
FadeOut(rects),
FadeOut(new_rects),
)
self.wait()
# Sender manipulations (Doing more by hand than should here...sorry)
parity_highlights = VGroup(*[boxes[2**n].copy() for n in range(4)])
parity_highlights.set_stroke(GREEN, 8)
parity_highlights.set_fill(BLACK, 0)
self.play(ShowCreation(parity_highlights))
words = TextMobject("Try to make\\\\this 0000")
words.set_color(GREEN)
words.next_to(boxes, RIGHT, MED_LARGE_BUFF)
words.to_edge(DOWN, buff=1)
arrow = Arrow(words.get_right(), result.get_left(), buff=0.1)
arrow.get_lp = words.get_right
arrow.get_rp = result.get_left
arrow.add_updater(lambda m: m.put_start_and_end_on(
m.get_lp() + SMALL_BUFF * RIGHT,
m.get_rp() + SMALL_BUFF * LEFT,
))
self.play(
Write(words),
DrawBorderThenFill(arrow),
)
self.wait()
strike = Cross(summands[0])
strike.set_stroke(RED, 8)
self.play(ShowCreation(strike))
self.add(summands[0], strike)
bits[2].generate_target()
toggle_bit(bits[2].target)
bits[2].target.fade(0.5)
self.play(
boxes[2].set_fill, BLACK, 0,
MoveToTarget(bits[2]),
bin_pos_labels[2].fade, 0.5,
toggle_bit_anim(result[2]),
FadeOut(summands[0]),
FadeOut(strike),
)
summands.remove(summands[0])
self.wait()
toggle_bit(bits[8].saved_state)
self.play(
boxes[8].set_fill, BLUE, 0.7,
Restore(bits[8]),
Restore(bin_pos_labels[8]),
)
new_term = bin_pos_labels[8].copy()
new_term.generate_target()
new_term.target.set_color(WHITE)
new_term.target.replace(summands[2])
self.play(
MoveToTarget(new_term),
summands[:3].move_to, summands[1], DOWN,
)
self.play(toggle_bit_anim(result[0]))
self.wait()
summands.set_submobjects([*summands[:3], new_term, *summands[3:]])
self.play(FadeOut(words), FadeOut(arrow), FadeOut(parity_highlights))
# Show 0 -> 1 error
pos = 11
self.play(
Restore(bits[pos]),
Restore(bin_pos_labels[pos]),
)
self.play(toggle_bit_anim(bits[pos], target_color=RED))
self.wait()
new_term = bin_pos_labels[pos].copy()
new_term.generate_target()
new_term.target.set_color(RED)
new_term.target.replace(summands[5])
bottom_group = VGroup(summands[5:], xor, line, result)
bottom_group.save_state()
self.play(
MoveToTarget(new_term),
bottom_group.move_to, summands[6], UR,
)
self.wait(0.25)
nt_copy = new_term.copy()
self.play(
nt_copy.replace, result,
nt_copy.fade, 1,
*[
toggle_bit_anim(b1, path_arc=0)
for b1, b2 in zip(result, new_term)
if b2.get_value() == 1
]
)
self.remove(nt_copy)
self.wait()
self.play(
Restore(bottom_group),
FadeOut(new_term),
toggle_bit_anim(bits[pos], target_color=WHITE)
)
self.play(
bits[pos].fade, 0.5,
bin_pos_labels[pos].fade, 0.5,
)
# Show 1 -> 0 error
pos = 6
self.play(
toggle_bit_anim(bits[pos], target_color=RED),
zap_anim(bits[pos]),
)
self.wait()
new_term = bin_pos_labels[pos].copy()
new_term.generate_target()
new_term.target.set_color(RED)
new_term.target.replace(summands[3])
bottom_group = VGroup(summands[3:], xor, line, result)
bottom_group.save_state()
self.play(
MoveToTarget(new_term),
bottom_group.move_to, summands[4], UR,
)
for bit in result:
bit[0].set_opacity(1)
bit[1].set_opacity(0)
nt_copy = new_term.copy()
self.play(
nt_copy.move_to, result,
nt_copy.fade, 1,
*[
toggle_bit_anim(b1, path_arc=0)
for b1, b2 in zip(result, new_term)
if b2.get_value() == 1
]
)
self.remove(nt_copy)
self.wait()
brace = Brace(VGroup(summands[2], new_term), LEFT, buff=SMALL_BUFF)
brace_bits = summands[2].copy()
for bb in brace_bits:
bb.set_value(0)
brace_bits.next_to(brace, LEFT)
self.play(GrowFromCenter(brace))
self.play(
Transform(summands[2].copy().unlock_triangulation(), brace_bits, remover=True),
ReplacementTransform(new_term.copy().unlock_triangulation(), brace_bits),
)
self.wait()
arrow = Arrow(result.get_left(), bin_pos_labels[pos].get_corner(DR), path_arc=-30 * DEGREES, buff=0.1)
self.play(DrawBorderThenFill(arrow))
self.wait()
self.play(toggle_bit_anim(bits[pos], target_color=WHITE))
self.play(
Restore(bottom_group),
FadeOut(new_term),
FadeOut(brace),
FadeOut(brace_bits),
FadeOut(arrow),
)
self.wait()
class HammingSyndromePython(ExternallyAnimatedScene):
pass
class WhatAboutTwoBitDetection(TeacherStudentsScene):
def construct(self):
self.student_says(
"What about\\\\detecting\\\\two bit errors?"
)
self.play(
self.get_student_changes("angry", "maybe", "raise_left_hand"),
self.teacher.change, "guilty",
)
self.look_at(self.screen)
self.wait(4)
self.play(self.teacher.change, "happy")
self.change_student_modes("confused", "erm", "pondering")
self.wait(3)
class ConflictingViewsOnXor(TeacherStudentsScene):
def construct(self):
self.clear()
self.add(self.pi_creatures)
self.student_says(
"Um...can you\\\\say that again?",
target_mode="confused",
student_index=2,
added_anims=[self.teacher.change, "guilty"]
)
self.change_student_modes("pondering", "pondering", look_at_arg=self.screen)
self.wait(2)
self.student_says(
"Why didn't you\\\\just use xors\\\\from the start?",
target_mode="sassy",
student_index=1,
)
self.look_at(self.students[1].bubble)
self.wait(5)
class CompareXorToParityChecks(Scene):
def construct(self):
# Title
bg_rect = FullScreenRectangle()
bg_rect.set_fill(GREY_E, 1)
bg_rect.set_stroke(width=0)
self.add(bg_rect)
title = TextMobject("One algorithm, multiple perspectives")
title.scale(1.5)
title.to_edge(UP)
title.add_to_back(Underline(title))
self.add(title)
# Options
rects = VGroup(*[ScreenRectangle() for x in range(3)])
rects.set_fill(BLACK, 1)
rects.set_stroke(GREY_B, 3)
rects.set_height(3)
rects.arrange(RIGHT, buff=1)
rects.shift(-rects[:2].get_center())
labels = VGroup(
TextMobject("Multiple parity checks"),
TextMobject("One big xor"),
TextMobject("Matrix product"),
)
for label, rect in zip(labels, rects):
label.next_to(rect, DOWN)
labels.set_color(BLUE)
self.add(rects[:2])
self.add(labels[:2])
self.play(Write(title, run_time=2))
self.wait(2)
# Hardware/software labels
hw_label = TextMobject("(nicer for hardware)")
sw_label = TextMobject("(nicer for software)")
for l1, l2 in zip(labels, [hw_label, sw_label]):
l2.next_to(l1, DOWN)
self.play(FadeIn(hw_label, 0.25 * UP))
self.wait()
self.play(FadeIn(sw_label, 0.25 * UP))
self.wait()
# Third view
icons = Group(
ImageMobject("ParityCheckIcon"),
ImageMobject("XorViewIcon"),
)
for icon, rect in zip(icons, rects):
icon.replace(rect)
icon.scale(0.95)
groups = Group(
Group(rects[0], labels[0], icons[0], hw_label),
Group(rects[1], labels[1], icons[1], sw_label),
Group(rects[2], labels[2]),
)
groups.generate_target()
groups.target[:2].arrange(DOWN, buff=LARGE_BUFF)
groups.target[:2].match_height(groups.target[2])
groups.target[2].next_to(groups.target[:2], RIGHT, LARGE_BUFF)
groups.target.set_height(5)
groups.target.center()
groups.target.to_edge(DOWN, buff=1)
groups[2].set_opacity(0)
self.play(MoveToTarget(groups))
self.wait()
class LogTitle(Scene):
def construct(self):
title = TextMobject("$\\text{log}_2(256) = 8$ parity checks")
title.set_height(0.7)
title.to_edge(UP)
underline = Underline(title[0][:4])
underline.set_color(YELLOW)
self.add(title)
self.wait()
self.play(ShowCreationThenFadeOut(underline))
self.wait()
class MatrixProduct(Scene):
def construct(self):
title = TextMobject("(7, 4) Hamming code")
title.set_height(0.7)
title.to_edge(UP)
title.set_color(GREY_A)
encoder_matrix = np.array([
[1, 1, 0, 1],
[1, 0, 1, 1],
[1, 0, 0, 0],
[0, 1, 1, 1],
[0, 1, 0, 0],
[0, 0, 1, 0],
[0, 0, 0, 1],
])
message_matrix = np.array([1, 0, 1, 1]).reshape((4, 1))
result_matrix = np.dot(encoder_matrix, message_matrix) % 2
kw = {"v_buff": 0.6, "h_buff": 0.75}
encoder, message, result = [
IntegerMatrix(matrix, **kw)
for matrix in [encoder_matrix, message_matrix, result_matrix]
]
equation = VGroup(
encoder, message, TexMobject("="), result
)
equation.arrange(RIGHT, buff=MED_LARGE_BUFF)
equation.to_edge(LEFT, buff=2)
# Labels
message_label = TextMobject("Content")
message_label.move_to(message)
message_label.to_edge(DOWN)
message_arrow = Arrow(
message_label.get_top(),
message.get_bottom(),
)
message_label.set_color(YELLOW)
message_arrow.set_color(YELLOW)
brace = Brace(result, RIGHT)
brace_text = brace.get_text("Error-resistant\\\\block")
brace.set_color(BLUE)
brace_text.set_color(BLUE)
# Animate
self.add(title)
self.add(equation)
self.play(FadeIn(message_label, UP), GrowArrow(message_arrow))
self.play(GrowFromCenter(brace), FadeIn(brace_text, LEFT))
equation.set_fill(opacity=0.8)
for n in range(encoder.mob_matrix.shape[0]):
row = VGroup(*encoder.mob_matrix[n, :]).copy()
col = VGroup(*message.elements).copy()
rhs = result.mob_matrix[n, 0].copy()
mult_group = VGroup(row, col, rhs)
mult_group.set_fill(YELLOW, 1)
self.play(
ShowIncreasingSubsets(row),
ShowIncreasingSubsets(col),
FadeIn(rhs),
)
self.wait()
self.remove(mult_group)
class TooManyErrorsTripUpHamming(Scene):
def construct(self):
title = TextMobject(
"$>2$ Errors"," $\\Rightarrow$ ", "Invalid decoding"
)
title.set_height(0.7)
title[2].set_color(RED)
title.to_edge(UP)
self.add(title)
block = get_bit_grid(8, 8, bits=string_to_bits("EpicFail"), height=5.5)
block.next_to(title, DOWN, MED_LARGE_BUFF)
self.add(block)
# Animations
errors = random.sample(list(range(64)), 3)
kw = {"lag_ratio": 0.5}
self.play(
LaggedStart(*[
zap_anim(block[pos])
for pos in errors
], **kw),
LaggedStart(*[
toggle_bit_anim(block[pos], target_color=RED)
for pos in errors
], **kw),
)
scanim = scan_anim(block.get_corner(DR) + UR, block, run_time=5, lag_factor=1)
self.play(scanim)
self.play(ShowCreationThenFadeOut(Underline(title[2], color=RED)))
self.wait()
class LouisPasteurQuote(Scene):
def construct(self):
quote = TextMobject("``Luck favors a\\\\prepared mind''")
quote.scale(2)
quote.set_stroke(BLACK, 8, background=True)
self.play(Write(quote))
self.wait()
class ReedSolomonPreview(Scene):
def construct(self):
# Setup
title = TextMobject("Reed-Solomon basic idea")
title.set_height(0.5)
title.to_edge(UP, buff=MED_SMALL_BUFF)
axes = Axes(
x_range=(-1, 10, 1), y_range=(-1, 8, 1),
width=12,
height=7,
)
axes.to_edge(DOWN, buff=SMALL_BUFF)
axes.set_color(GREY_B)
cubic = axes.get_graph(
lambda x: -0.05 * x * (x - 2) * (x - 4) * (x - 8) + 2
)
cubic.set_stroke(TEAL, 3)
self.add(title)
self.add(axes)
# Data
dots = VGroup(*[
Dot(axes.input_to_graph_point(x, cubic))
for x in range(0, 8)
])
dots[:4].set_color(YELLOW)
dots[4:].set_color(BLUE)
# Input words
input_words = TextMobject("Input data")
poly_words = TextMobject("Polynomial\\\\fit")
redundant_words = TextMobject("Redundancy")
input_words.next_to(dots[:4], UP, buff=2)
input_words.set_color(YELLOW)
input_arrows = VGroup(*[
Arrow(input_words.get_bottom(), dot.get_center())
for dot in dots[:4]
])
input_arrows.set_fill(YELLOW)
poly_words.next_to(dots[5], LEFT)
poly_words.shift(0.5 * UR)
poly_words.match_color(cubic)
redundant_words.move_to(dots[4:].get_center(), LEFT)
redundant_words.shift(2 * DR + DOWN)
redundant_words.set_color(BLUE)
redundant_arrows = VGroup(*[
Arrow(redundant_words.get_corner(UL), dot.get_center())
for dot in dots[4:]
])
redundant_arrows.set_color(BLUE)
# Animations
kw = {"lag_ratio": 0.5}
self.play(
FadeIn(input_words),
LaggedStartMap(FadeIn, dots[:4], lambda m: (m, UP), **kw),
LaggedStartMap(GrowArrow, input_arrows, **kw),
)
self.add(cubic, *dots[:4])
self.play(
ShowCreation(cubic),
Write(poly_words)
)
self.wait()
self.play(
FadeIn(redundant_words),
LaggedStartMap(FadeIn, dots[4:], lambda m: (m, DR), **kw),
LaggedStartMap(GrowArrow, redundant_arrows, **kw),
)
self.wait()
self.play(LaggedStartMap(
FadeOut, VGroup(*reversed(self.mobjects)),
lambda m: (m, 0.2 * normalize(m.get_center())),
lag_ratio=0.1,
run_time=2,
))
self.wait()
class HammingThinking(Scene):
def construct(self):
hamming = ImageMobject("Richard_Hamming")
hamming.set_height(3)
hamming.to_corner(DL)
randy = Randolph()
randy.set_opacity(0)
randy.move_to(hamming)
self.add(hamming)
self.wait()
self.play(PiCreatureBubbleIntroduction(
randy, "What's the most efficient\\\\I could conceivably be?",
bubble_class=ThoughtBubble,
))
self.wait()
class RandomWalks(Scene):
CONFIG = {
"random_seed": 1,
}
def construct(self):
# Setup
N_PATHS = 25
frame = self.camera.frame
frame.set_height(2 * FRAME_HEIGHT)
frame.shift(2 * RIGHT)
idea_spot = 10 * RIGHT + 3 * UP
idea_dot = Dot(idea_spot)
idea_dot.set_color(YELLOW)
bulb = Lightbulb()
bulb.next_to(idea_dot, UP)
start_point = 7 * LEFT + 3 * DOWN
start_dot = Dot(start_point, color=WHITE)
start_dot.scale(2)
self.add(idea_dot, bulb, start_dot)
# Paths
paths = VGroup(*[VGroup() for n in range(N_PATHS)])
for path in paths:
path.add(Line(start_point, start_point))
path.set_stroke(WHITE, 3, 0.5)
path_dots = VGroup()
for path in paths:
# dot = Randolph(
# mode="thinking", height=0.25,
# # color=random.choice([BLUE_B, BLUE_C, BLUE_D, GREY_BROWN])
# )
dot = Dot(color=BLUE)
dot.set_stroke(BLACK, 3, background=True)
dot.path = path
dot.add_updater(lambda m: m.move_to(m.path[-1].get_end()))
path_dots.add(dot)
self.add(paths)
self.add(path_dots)
# Perform search
magic_path = None
while magic_path is None:
new_segments = VGroup()
for path in paths:
start = path[-1].get_end()
# Choose random direction based on loosely sniffing out idea spot
R_vect = start - idea_spot
R_vect = rotate_vector(10 * R_vect, TAU * random.random())
point = idea_spot + R_vect
to_point = point - start
angle = angle_of_vector(to_point)
if -3 * PI / 2 < angle <= -PI / 2:
vect = DOWN
elif -PI / 2 < angle <= PI / 2:
vect = RIGHT
elif PI / 2 < angle <= 3 * PI / 2:
vect = UP
else:
vect = LEFT
end = start + vect
new_segment = Line(start, end)
new_segment.match_style(path)
new_segments.add(new_segment)
path.add(new_segment)
if np.isclose(end, idea_spot).all():
magic_path = path.copy()
self.play(
LaggedStartMap(ShowCreation, new_segments, lag_ratio=10 / N_PATHS),
run_time=0.5,
)
self.add(paths)
# Highlight magic path
magic_path.set_stroke(YELLOW, 5, 1)
self.play(
paths.fade, 0.7,
ShowCreation(magic_path, run_time=2),
)
self.wait()
fake_path = VMobject()
fake_path.start_new_path(start_point)
for segment in magic_path:
fake_path.add_line_to(segment.get_end())
fake_path.match_style(magic_path)
line = Line(start_point, idea_spot)
line.match_style(magic_path)
line.set_stroke(TEAL, 8)
self.add(fake_path, start_dot)
self.play(
ApplyMethod(magic_path.set_opacity, 0.5),
Transform(fake_path, line, run_time=2),
paths.fade, 0.5,
path_dots.fade, 0.5,
)
self.wait(4)
class ThinkingInTermsOfBits(Scene):
def construct(self):
word = TextMobject("Information")
word.scale(2)
word.next_to(ORIGIN, LEFT, buff=0.7)
bits = get_bit_grid(11, 8, bits=string_to_bits("Information"))
bits.set_height(6)
bits.next_to(ORIGIN, RIGHT, buff=0.7)
bits.set_color(GREY_A)
for bit in bits:
toggle_bit(bit)
bits.shuffle()
self.add(word)
self.add(bits)
self.play(LaggedStartMap(
toggle_bit_anim, bits,
lag_ratio=5 / len(bits),
run_time=3,
))
self.wait(2)
class SimpleHoldUpBackground(TeacherStudentsScene):
def construct(self):
self.play(self.teacher.change, "raise_right_hand", 3 * UP)
self.change_student_modes("pondering", "thinking", "tease", look_at_arg=3 * UP)
self.wait(4)
self.change_student_modes("tease", "hesitant", "happy", look_at_arg=3 * UP)
self.wait(5)
class HammingEndScreen(PatreonEndScreen):
CONFIG = {
"scroll_time": 25
}
# Extras
class TwoErrorGrids(Scene):
def construct(self):
grid = VGroup(*[Square() for x in range(16)])
grid.arrange_in_grid(buff=0)
grid.set_stroke(WHITE, 1)
grid.set_height(1)
grids = VGroup(*[grid.copy() for x in range(16)])
grids.arrange_in_grid(buff=MED_LARGE_BUFF)
grids.set_height(7)
grids.to_edge(RIGHT)
self.add(grids)
vects = [
np.array(tup)
for tup in it.product(*[[0, 1]] * 4)
]
def vect_to_int(vect):
return sum([b * (1 << i) for i, b in enumerate(reversed(vect))])
for vect in vects:
label = VGroup(*map(Integer, vect))
label.arrange(RIGHT, buff=SMALL_BUFF)
label.to_edge(LEFT)
self.add(label)
error_int = vect_to_int(vect)
for i, grid in enumerate(grids):
grid[i].set_fill(YELLOW, 1)
grid[i ^ error_int].set_fill(TEAL, 1)
self.wait()
grids.set_fill(opacity=0)
self.remove(label)