diff --git a/from_3b1b/active/hamming.py b/from_3b1b/active/hamming.py new file mode 100644 index 00000000..66f3ab23 --- /dev/null +++ b/from_3b1b/active/hamming.py @@ -0,0 +1,2952 @@ +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