3b1b-manim/from_3b1b/active/hamming.py
2020-08-12 13:24:40 -07:00

2952 lines
91 KiB
Python

from manimlib.imports import *
from from_3b1b.active.chess import string_to_bools
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 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):
return VGroup(*[bit for bit in block if get_bit_mob_value(bit) == 1])
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_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(*[
sm
for i, sm in enumerate(mob)
if bool(i & (1 << n)) ^ bool(1 - 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)
return TransformFromCopy(original, bit, path_arc=PI, **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
])
# 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[0].set_color(BLUE)
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[2].set_color(BLUE_D)
bits = VGroup(*[
Integer(i)
for i in [
1, 1, 0, 1, 1, 0, 0, 1,
0, 1, 1, 0, 1, 1, 1, 1,
]
])
# bits[0].set_opacity(0)
bits.arrange_in_grid(2, 8, buff=SMALL_BUFF)
bits.set_width(9)
bits.next_to(phrases[1], DOWN)
self.add(phrases)
self.add(bits)
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\\\\saved")
in_words.next_to(in_image, DOWN)
out_image = in_image.copy()
out_image.to_edge(RIGHT)
out_words = TextMobject("What is\\\\read")
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")
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)
# # Show some data
# full_timeline = VGroup(timeline, events)
# full_timeline.save_state()
# full_timeline.generate_target()
# full_timeline.target.scale(0.25)
# full_timeline.target.to_corner(DL)
# message = "You+I=:)"
# alt_message = "You+I=:("
# bits = get_bit_grid(8, 8, bits=string_to_bits(message))
# bits.shift(RIGHT)
# bits_rect = SurroundingRectangle(bits, buff=SMALL_BUFF)
# bits_rect.set_stroke(WHITE, 2)
# bits_arrow = Vector(1.5 * LEFT)
# bits_arrow.next_to(bits_rect, LEFT)
# bits_label = TextMobject("Data")
# bits_label.next_to(bits_arrow, UP)
# message_labels = VGroup(*[
# TextMobject(f"``{s}''")
# for s in (message, alt_message)
# ])
# message_labels.set_color(GREEN_B)
# message_labels.next_to(bits_arrow, LEFT)
# image = ImageMobject("Tom_In_Bowtie")
# sound = get_sound_wave()
# code = ImageMobject("Hamming_Code_Snippet")
# code.set_height(1.5)
# file_types = Group(image, sound, code)
# for file in file_types:
# file.next_to(bits_arrow, LEFT)
# ecc_bits = get_bit_grid(1, 6, height=bits[0].get_height())
# ecc_bits.next_to(bits[-1], RIGHT, MED_SMALL_BUFF)
# ecc_bits.set_color(YELLOW)
# ecc_rect = SurroundingRectangle(ecc_bits, buff=SMALL_BUFF)
# ecc_rect.set_stroke(YELLOW, 2)
# ecc_label = TextMobject("Some kind of\\\\redundancy")
# ecc_label.set_color(GREY_A)
# ecc_label.next_to(ecc_rect, DOWN)
# self.play(
# MoveToTarget(full_timeline, run_time=3),
# LaggedStartMap(FadeIn, bits, run_time=2),
# ShowCreation(bits_rect, run_time=2),
# Write(bits_label),
# )
# self.play(
# FadeIn(message_labels[0], RIGHT),
# GrowArrow(bits_arrow)
# )
# self.wait()
# curr_file = message_labels[0]
# files = [*file_types, message_labels[0]]
# anim_types = [image_reveal_animation, Write, FadeIn, FadeIn]
# for file, anim_type in zip(files, anim_types):
# anim = anim_type(file)
# self.add(anim.mobject, curr_file)
# self.play(
# anim,
# FadeOut(curr_file),
# run_time=1
# )
# self.wait()
# curr_file = file
# ecc_rect.save_state()
# ecc_rect.stretch(0, 0, about_edge=LEFT)
# self.play(
# LaggedStartMap(FadeIn, ecc_bits),
# Restore(ecc_rect),
# FadeIn(ecc_label, UP),
# )
# self.wait()
# # Show Error
# bolt = SVGMobject("lightning_bolt")
# bolt.set_height(0.5)
# bolt.set_color(RED)
# bolt.move_to(bits[-1].get_center(), DL)
# self.play(
# ShowCreation(bolt),
# ApplyMethod(bits[-1].set_color, RED),
# )
# self.play(
# FadeOut(bolt),
# toggle_bit_anim(bits[-1]),
# )
# self.play(
# FadeOut(message_labels[0]),
# FadeIn(message_labels[1]),
# )
# self.wait()
# # Show correction
# ecc_lines = VGroup(*[
# Line(ecc_rect.get_top(), bit.get_center())
# for bit in bits
# ])
# ecc_lines.set_stroke(YELLOW, 1)
# self.play(
# LaggedStartMap(ShowCreationThenFadeOut, ecc_lines, lag_ratio=0.01)
# )
# new_rect = SurroundingRectangle(bits[-1], buff=SMALL_BUFF)
# self.play(TransformFromCopy(ecc_rect, new_rect))
# self.play(toggle_bit_anim(bits[-1]))
# self.play(
# bits[-1].set_color, WHITE,
# FadeOut(message_labels[1]),
# FadeIn(message_labels[0]),
# )
# self.play(FadeOut(new_rect))
# self.wait()
# # Bring back time line
# data_pile = VGroup(
# message_labels[0],
# bits_arrow, bits_label, bits_rect, bits,
# ecc_bits, ecc_rect, ecc_label,
# )
# self.play(
# ApplyMethod(data_pile.scale, 0, {"about_edge": DR}, remover=True),
# Restore(full_timeline, run_time=2)
# )
# self.wait()
# 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))
)
words = TextMobject("How to invent")
words.match_height(hamming_word[0][0])
words.next_to(events[0], UP, buff=0.3)
words.set_color(BLUE)
self.play(Write(words))
hamming_word.add(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),
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 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):
def construct(self):
bits = get_bit_grid(
16, 16,
bits=string_to_bits("Claude Shannon was a total boss!")
)
bits.set_height(7)
bits.move_to(2 * RIGHT)
point = 2.5 * LEFT + 2.5 * DOWN
last_block = VMobject()
for x in range(10):
block = get_bit_grid(16, 16, height=7)
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=2, lag_factor=1,
show_robot=(x == 0)
)
self.play(
FadeIn(block, 6 * RIGHT),
FadeOut(last_block, 6 * LEFT),
)
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 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, r_boxes]
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.target, r_bits.target):
box.move_to(bit)
self.play(*[
MoveToTarget(mover, lag_ratio=0.2, run_time=3, path_arc=PI / 4)
for mover in movers
])
self.wait()
self.play(*[
Restore(mover, lag_ratio=0.2, run_time=3, path_arc=PI / 4)
for mover in movers
])
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.add(words)
self.add(pis)
# 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 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 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_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_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_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_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_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 HalfAsPowerful(TeacherStudentsScene):
def construct(self):
pass