from helpers import * from mobject.tex_mobject import TexMobject from mobject import Mobject from mobject.image_mobject import ImageMobject from mobject.vectorized_mobject import * from animation.animation import Animation from animation.transform import * from animation.simple_animations import * from animation.playground import * from topics.geometry import * from topics.characters import * from topics.functions import * from topics.fractals import * from topics.number_line import * from topics.combinatorics import * from topics.numerals import * from topics.three_dimensions import * from scene import Scene from camera import Camera, ShadingCamera from mobject.svg_mobject import * from mobject.tex_mobject import * from eoc.graph_scene import GraphScene class Jewel(VMobject): CONFIG = { "color" : WHITE, "fill_opacity" : 0.75, "stroke_width" : 0, "propogate_style_to_family" : True, "height" : 0.5, "num_equator_points" : 5, "sun_vect" : OUT+LEFT+UP, } def generate_points(self): for vect in OUT, IN: compass_vects = list(compass_directions(self.num_equator_points)) if vect is IN: compass_vects.reverse() for vect_pair in adjascent_pairs(compass_vects): self.add(Polygon(vect, *vect_pair)) self.scale_to_fit_height(self.height) self.rotate(-np.pi/2-np.pi/24, RIGHT) self.rotate(-np.pi/12, UP) self.submobjects.sort(lambda m1, m2 : cmp(-m1.get_center()[2], -m2.get_center()[2])) return self class Necklace(VMobject): CONFIG = { "height" : 2*SPACE_WIDTH - 1, "jewel_buff" : MED_SMALL_BUFF, "chain_color" : GREY, "default_colors" : [(4, BLUE), (6, WHITE), (4, GREEN)] } def __init__(self, *colors, **kwargs): digest_config(self, kwargs, locals()) if len(colors) == 0: self.colors = self.get_default_colors() VMobject.__init__(self, **kwargs) def get_default_colors(self): result = list(it.chain(*[ num*[color] for num, color in self.default_colors ])) random.shuffle(result) return result def generate_points(self): jewels = VGroup(*[ Jewel(color = color) for color in self.colors ]) jewels.arrange_submobjects(buff = self.jewel_buff) jewels.scale_to_fit_width(self.width) jewels.center() j_to_j_dist = (jewels[1].get_center()-jewels[0].get_center())[0] chain = Line( jewels[0].get_center() + j_to_j_dist*LEFT/2, jewels[-1].get_center() + j_to_j_dist*RIGHT/2, color = self.chain_color, ) self.add(chain, *jewels) self.chain = chain self.jewels = jewels ################ class CheckOutMathologer(PiCreatureScene): CONFIG = { "logo_height" : 1.5, "screen_height" : 5, "channel_name" : "Mathologer", "logo_file" : "mathologer_logo", "logo_color" : None, } def construct(self): logo = ImageMobject(self.logo_file) logo.scale_to_fit_height(self.logo_height) logo.to_corner(UP+LEFT) if self.logo_color is not None: logo.highlight(self.logo_color) logo.stroke_width = 1 name = TextMobject(self.channel_name) name.next_to(logo, RIGHT) rect = Rectangle(height = 9, width = 16) rect.scale_to_fit_height(self.screen_height) rect.next_to(logo, DOWN) rect.to_edge(LEFT) logo.save_state() logo.shift(DOWN) logo.highlight(BLACK) self.play( logo.restore, self.pi_creature.change_mode, "hooray", ) self.play( ShowCreation(rect), Write(name) ) self.dither(2) self.change_mode("happy") self.dither(2) class IntroduceStolenNecklaceProblem(Scene): CONFIG = { "camera_class" : ShadingCamera, "jewel_colors" : [BLUE, GREEN, WHITE, RED], "num_per_jewel" : [8, 10, 4, 6], "num_shuffles" : 1, "necklace_center" : UP, "random_seed" : 2, "forced_binary_choices" : (0, 1, 0, 1, 0), } def construct(self): random.seed(self.random_seed) self.add_thieves() self.write_title() self.introduce_necklace() self.divvy_by_cutting_all() self.divvy_with_n_cuts() self.shuffle_jewels(self.necklace.jewels) self.divvy_with_n_cuts() def add_thieves(self): thieves = VGroup( Randolph(), Mortimer() ) thieves.arrange_submobjects(RIGHT, buff = 4*LARGE_BUFF) thieves.to_edge(DOWN) thieves[0].make_eye_contact(thieves[1]) self.add(thieves) self.thieves = thieves def write_title(self): title = TextMobject("Stolen necklace problem") title.to_edge(UP) self.play( Write(title), *[ ApplyMethod(pi.look_at, title) for pi in self.thieves ] ) self.title = title def introduce_necklace(self): necklace = self.get_necklace() jewels = necklace.jewels jewel_types = self.get_jewels_organized_by_type(jewels) enumeration_labels = VGroup() for jewel_type in jewel_types: num_mob = TexMobject(str(len(jewel_type))) jewel_copy = jewel_type[0].copy().scale(2) jewel_copy.next_to(num_mob) label = VGroup(num_mob, jewel_copy) enumeration_labels.add(label) enumeration_labels.arrange_submobjects(RIGHT, buff = LARGE_BUFF) enumeration_labels.to_edge(UP) self.play( FadeIn( necklace, submobject_mode = "lagged_start", run_time = 3 ), *it.chain(*[ [pi.change_mode, "conniving", pi.look_at, necklace] for pi in self.thieves ]) ) self.play(*[ ApplyMethod( jewel.rotate_in_place, np.pi/6, UP, rate_func = there_and_back ) for jewel in jewels ]) self.play(Blink(self.thieves[0])) self.dither() for x in range(self.num_shuffles): self.shuffle_jewels(jewels) self.play(FadeOut(self.title)) for jewel_type, label in zip(jewel_types, enumeration_labels): jewel_type.submobjects.sort(lambda m1, m2: cmp(m1.get_center()[0], m2.get_center()[0])) jewel_type.save_state() jewel_type.generate_target() jewel_type.target.arrange_submobjects() jewel_type.target.scale(2) jewel_type.target.move_to(2*UP) self.play( MoveToTarget(jewel_type), Write(label) ) self.play(jewel_type.restore) self.play(Blink(self.thieves[1])) self.enumeration_labels = enumeration_labels self.jewel_types = jewel_types def divvy_by_cutting_all(self): enumeration_labels = self.enumeration_labels necklace = self.necklace jewel_types = self.jewel_types thieves = self.thieves both_half_labels = VGroup() for thief, vect in zip(self.thieves, [LEFT, RIGHT]): half_labels = VGroup() for label in enumeration_labels: tex, jewel = label num = int(tex.get_tex_string()) half_label = VGroup( TexMobject(str(num/2)), jewel.copy() ) half_label.arrange_submobjects() half_labels.add(half_label) half_labels.arrange_submobjects(DOWN) half_labels.scale_to_fit_height(thief.get_height()) half_labels.next_to( thief, vect, buff = MED_LARGE_BUFF, aligned_edge = DOWN ) both_half_labels.add(half_labels) for half_labels in both_half_labels: self.play(ReplacementTransform( enumeration_labels.copy(), half_labels )) self.play(*[ApplyMethod(pi.change_mode, "pondering") for pi in thieves]) self.dither() for type_index, jewel_type in enumerate(jewel_types): jewel_type.save_state() jewel_type_copy = jewel_type.copy() n_jewels = len(jewel_type) halves = [ VGroup(*jewel_type_copy[:n_jewels/2]), VGroup(*jewel_type_copy[n_jewels/2:]), ] for half, thief, vect in zip(halves, thieves, [RIGHT, LEFT]): half.arrange_submobjects(DOWN) half.next_to( thief, vect, buff = SMALL_BUFF + type_index*half.get_width(), aligned_edge = DOWN ) self.play( Transform(jewel_type, jewel_type_copy), *[ ApplyMethod(thief.look_at, jewel_type_copy) for thief in thieves ] ) self.play(*it.chain(*[ [thief.change_mode, "happy", thief.look_at, necklace] for thief in thieves ])) self.dither() self.play(*[ jewel_type.restore for jewel_type in jewel_types ]) self.play(*it.chain(*[ [thief.change_mode, "confused", thief.look_at, necklace] for thief in thieves ])) def divvy_with_n_cuts( self, with_thieves = True, highlight_groups = True, show_matching_after_divvying = True, ): necklace = self.necklace jewel_types = self.jewel_types jewels = sorted( necklace.jewels, lambda m1, m2 : cmp(m1.get_center()[0], m2.get_center()[0]) ) slice_indices, binary_choices = self.find_slice_indices(jewels, jewel_types) subgroups = [ VGroup(*jewels[i1:i2]) for i1, i2 in zip(slice_indices, slice_indices[1:]) ] buff = (jewels[1].get_left()[0]-jewels[0].get_right()[0])/2 v_lines = VGroup(*[ DashedLine(UP, DOWN).next_to(group, RIGHT, buff = buff) for group in subgroups[:-1] ]) strand_groups = [VGroup(), VGroup()] for group, choice in zip(subgroups, binary_choices): strand = Line( group[0].get_center(), group[-1].get_center(), color = necklace.chain.get_color() ) strand.add(*group) strand_groups[choice].add(strand) self.add(strand) self.play(ShowCreation(v_lines)) self.play( FadeOut(necklace.chain), *it.chain(*[ map(Animation, group) for group in strand_groups ]) ) for group in strand_groups: group.save_state() self.play( strand_groups[0].shift, UP/2., strand_groups[1].shift, DOWN/2., ) if with_thieves: self.play(*it.chain(*[ [thief.change_mode, "happy", thief.look_at, self.necklace] for thief in thieves ])) self.play(Blink(thieves[1])) else: self.dither() if highlight_groups: for group in strand_groups: box = Rectangle( width = group.get_width()+2*SMALL_BUFF, height = group.get_height()+2*SMALL_BUFF, stroke_width = 0, fill_color = YELLOW, fill_opacity = 0.3, ) box.move_to(group) self.play(FadeIn(box)) self.dither() self.play(FadeOut(box)) self.dither() if show_matching_after_divvying: for jewel_type in jewel_types: self.play( *[ ApplyMethod(jewel.scale_in_place, 1.5) for jewel in jewel_type ], rate_func = there_and_back, run_time = 2 ) self.dither() self.play( FadeOut(v_lines), FadeIn(necklace.chain), *[ group.restore for group in strand_groups ] ) self.remove(*strand_groups) self.add(necklace) ######## def get_necklace(self, **kwargs): colors = reduce(op.add, [ num*[color] for num, color in zip(self.num_per_jewel, self.jewel_colors) ]) self.necklace = Necklace(*colors, **kwargs) self.necklace.shift(self.necklace_center) return self.necklace def get_jewels_organized_by_type(self, jewels): return [ VGroup(*filter(lambda m : m.get_color() == color, jewels)) for color in map(Color, self.jewel_colors) ] def shuffle_jewels(self, jewels, run_time = 2, path_arc = np.pi/2, **kwargs): shuffled_indices = range(len(jewels)) random.shuffle(shuffled_indices) target_group = VGroup(*[ jewel.copy().move_to(jewels[shuffled_indices[i]]) for i, jewel in enumerate(jewels) ]) self.play(Transform( jewels, target_group, run_time = run_time, path_arc = path_arc, **kwargs )) def find_slice_indices(self, jewels, jewel_types): def jewel_to_type_number(jewel): for i, jewel_type in enumerate(jewel_types): if jewel in jewel_type: return i raise Exception("Not in any jewel_types") type_numbers = map(jewel_to_type_number, jewels) n_types = len(jewel_types) for slice_indices in it.combinations(range(1, len(jewels)), n_types): slice_indices = [0] + list(slice_indices) + [len(jewels)] if self.forced_binary_choices is not None: all_binary_choices = [self.forced_binary_choices] else: all_binary_choices = it.product(*[range(2)]*(n_types+1)) for binary_choices in all_binary_choices: subsets = [ type_numbers[i1:i2] for i1, i2 in zip(slice_indices, slice_indices[1:]) ] left_sets, right_sets = [ [ subset for subset, index in zip(subsets, binary_choices) if index == target_index ] for target_index in range(2) ] flat_left_set = np.array(list(it.chain(*left_sets))) flat_right_set = np.array(list(it.chain(*right_sets))) match_array = [ sum(flat_left_set == n) == sum(flat_right_set == n) for n in range(n_types) ] if np.all(match_array): return slice_indices, binary_choices raise Exception("No fair division found") class FiveJewelCase(IntroduceStolenNecklaceProblem): CONFIG = { "jewel_colors" : [BLUE, GREEN, WHITE, RED, YELLOW], "num_per_jewel" : [6, 4, 4, 2, 8], "forced_binary_choices" : (0, 1, 0, 1, 0, 1), } def construct(self): random.seed(self.random_seed) self.add(self.get_necklace()) jewels = self.necklace.jewels self.shuffle_jewels(jewels, run_time = 0) self.jewel_types = self.get_jewels_organized_by_type(jewels) self.add_title() self.add_thieves() for thief in self.thieves: ApplyMethod(thief.change_mode, "pondering").update(1) thief.look_at(self.necklace) self.divvy_with_n_cuts() def add_title(self): n_cuts = len(self.jewel_colors) title = TextMobject( "%d jewel types, %d cuts"%(n_cuts, n_cuts) ) title.to_edge(UP) self.add(title) class SixJewelCase(FiveJewelCase): CONFIG = { "jewel_colors" : [BLUE, GREEN, WHITE, RED, YELLOW, MAROON_B], "num_per_jewel" : [6, 4, 4, 2, 2, 6], "forced_binary_choices" : (0, 1, 0, 1, 0, 1, 0), } class DiscussApplicability(TeacherStudentsScene): def construct(self): self.teacher_says(""" Minize sharding, allocate resources evenly """) self.change_student_modes(*["pondering"]*3) self.dither(2) class ThreeJewelCase(FiveJewelCase): CONFIG = { "jewel_colors" : [BLUE, GREEN, WHITE], "num_per_jewel" : [6, 4, 8], "forced_binary_choices" : (0, 1, 0, 1), } class RepeatedShuffling(IntroduceStolenNecklaceProblem): CONFIG = { "num_shuffles" : 5, "random_seed" : 3, "show_matching_after_divvying" : False, } def construct(self): random.seed(self.random_seed) self.add(self.get_necklace()) jewels = self.necklace.jewels self.jewel_types = self.get_jewels_organized_by_type(jewels) self.add_thieves() for thief in self.thieves: ApplyMethod(thief.change_mode, "pondering").update(1) thief.look_at(self.necklace) for x in range(self.num_shuffles): self.shuffle_jewels(jewels) self.divvy_with_n_cuts( show_matching_after_divvying = False ) class NowForTheTopology(TeacherStudentsScene): def construct(self): self.teacher_says("Now for the \\\\ topology") self.change_student_modes(*["hooray"]*3) self.dither(3) class ExternallyAnimatedScene(Scene): def construct(self): raise Exception("Don't actually run this class.") class SphereOntoPlaneIn3D(ExternallyAnimatedScene): pass class DiscontinuousSphereOntoPlaneIn3D(ExternallyAnimatedScene): pass class WriteWords(Scene): CONFIG = { "words" : "", "color" : WHITE, } def construct(self): words = TextMobject(self.words) words.highlight(self.color) words.scale_to_fit_width(2*SPACE_WIDTH-1) words.to_edge(DOWN) self.play(Write(words)) self.dither(2) class WriteNotAllowed(WriteWords): CONFIG = { "words" : "Not allowed", "color" : RED, } class NonAntipodalCollisionIn3D(ExternallyAnimatedScene): pass class AntipodalCollisionIn3D(ExternallyAnimatedScene): pass class WriteBorsukUlam(WriteWords): CONFIG = { "words" : "Borsuk-Ulam Theorem", } class WriteAntipodal(WriteWords): CONFIG = { "words" : "``Antipodal''", "color" : MAROON_B, } class ProjectOntoEquatorIn3D(ExternallyAnimatedScene): pass class ProjectOntoEquatorWithPolesIn3D(ExternallyAnimatedScene): pass class ProjectAntipodalNonCollisionIn3D(ExternallyAnimatedScene): pass class ShearThenProjectnOntoEquatorPolesMissIn3D(ExternallyAnimatedScene): pass class ShearThenProjectnOntoEquatorAntipodalCollisionIn3D(ExternallyAnimatedScene): pass class ClassicExample(TeacherStudentsScene): def construct(self): self.teacher_says("The classic example...") self.change_student_modes(*["happy"]*3) self.dither(2) class AntipodalEarthPoints(ExternallyAnimatedScene): pass class RotatingEarth(ExternallyAnimatedScene): pass class TemperaturePressurePlane(GraphScene): CONFIG = { "x_labeled_nums" : [], "y_labeled_nums" : [], "x_axis_label" : "Temperature", "y_axis_label" : "Pressure", "graph_origin" : 2.5*DOWN + 2*LEFT, "corner_square_width" : 4, "example_point_coords" : (2, 5), } def construct(self): self.setup_axes() self.draw_corner_square() self.add_example_coordinates() self.wander_continuously() def draw_corner_square(self): square = Square( side_length = self.corner_square_width, stroke_color = WHITE, stroke_width = 2 ) square.to_corner(UP+LEFT, buff = 0) arrow = Arrow( square.get_right(), self.coords_to_point(*self.example_point_coords) ) self.play(ShowCreation(square)) self.play(ShowCreation(arrow)) def add_example_coordinates(self): dot = Dot(self.coords_to_point(*self.example_point_coords)) dot.highlight(YELLOW) tex = TexMobject("(25^\\circ\\text{C}, 101 \\text{ kPa})") tex.next_to(dot, UP+RIGHT, buff = SMALL_BUFF) self.play(ShowCreation(dot)) self.play(Write(tex)) self.dither() self.play(FadeOut(tex)) def wander_continuously(self): path = VMobject().set_points_smoothly([ ORIGIN, 2*UP+RIGHT, 2*DOWN+RIGHT, 5*RIGHT, 4*RIGHT+UP, 3*RIGHT+2*DOWN, DOWN+LEFT, 2*RIGHT ]) point = self.coords_to_point(*self.example_point_coords) path.shift(point) path.highlight(GREEN) self.play(ShowCreation(path, run_time = 10, rate_func = None)) self.dither() class AlternateSphereSquishing(ExternallyAnimatedScene): pass class AlternateAntipodalCollision(ExternallyAnimatedScene): pass class AskWhy(TeacherStudentsScene): def construct(self): self.student_says("But...why?") self.change_student_modes("pondering", None, "thinking") self.play(self.get_teacher().change_mode, "happy") self.dither(3) class PointOutVSauce(CheckOutMathologer): CONFIG = { "channel_name" : "", "logo_file" : "Vsauce_logo", "logo_height" : 1, "logo_color" : GREY, } class WalkEquatorPostTransform(GraphScene): CONFIG = { "x_labeled_nums" : [], "y_labeled_nums" : [], "graph_origin" : 2.5*DOWN + 2*LEFT, "curved_arrow_color" : WHITE, "curved_arrow_radius" : 3, "num_great_arcs" : 10, } def construct(self): self.setup_axes() self.add_curved_arrow() self.great_arc_images = self.get_great_arc_images() self.walk_equator() self.walk_tilted_equator() self.draw_transverse_curve() self.walk_transverse_curve() def add_curved_arrow(self): arc = Arc( start_angle = 2*np.pi/3, angle = -np.pi/2, radius = self.curved_arrow_radius, color = self.curved_arrow_color ) arc.add_tip() arc.move_to(self.coords_to_point(0, 7)) self.add(arc) def walk_equator(self): equator = self.great_arc_images[0] dots = VGroup(Dot(), Dot()) dots.highlight(MAROON_B) dot_movement = self.get_arc_walk_dot_movement(equator, dots) dot_movement.update(0) self.play(ShowCreation(equator, run_time = 3)) self.play(FadeIn(dots[0])) dots[1].set_fill(opacity = 0) self.play(dot_movement) self.play(dots[1].set_fill, None, 1) self.play(dot_movement) self.play(dot_movement) proportion = equator.collision_point_proportion self.play(self.get_arc_walk_dot_movement( equator, dots, rate_func = lambda t : 2*proportion*smooth(t) )) v_line = DashedLine(SPACE_HEIGHT*UP, SPACE_HEIGHT*DOWN) v_line.shift(dots.get_center()[0]*RIGHT) self.play(ShowCreation(v_line)) self.dither() self.play(FadeOut(v_line)) dots.save_state() equator.save_state() self.play( equator.fade, dots.fade ) self.first_dots = dots def walk_tilted_equator(self): equator = self.great_arc_images[0] tilted_eq = self.great_arc_images[1] dots = VGroup(Dot(), Dot()) dots.highlight(MAROON_B) dot_movement = self.get_arc_walk_dot_movement(tilted_eq, dots) dot_movement.update(0) self.play(ReplacementTransform(equator.copy(), tilted_eq)) self.dither() self.play(FadeIn(dots)) self.play(dot_movement) proportion = tilted_eq.collision_point_proportion self.play(self.get_arc_walk_dot_movement( tilted_eq, dots, rate_func = lambda t : 2*proportion*smooth(t) )) v_line = DashedLine(SPACE_HEIGHT*UP, SPACE_HEIGHT*DOWN) v_line.shift(dots.get_center()[0]*RIGHT) self.play(ShowCreation(v_line)) self.dither() self.play(FadeOut(v_line)) self.play(*map(FadeOut, [tilted_eq, dots])) def draw_transverse_curve(self): transverse_curve = self.get_transverse_curve(self.great_arc_images) dots = self.first_dots equator = self.great_arc_images[0] self.play(dots.restore) equator.restore() self.great_arc_images.fade() target_arcs = list(self.great_arc_images[1:]) target_dots = [] for arc in target_arcs: new_dots = dots.copy() for dot, point in zip(new_dots, arc.x_collision_points): dot.move_to(point) target_dots.append(new_dots) alt_eq = equator.copy() alt_eq.points = np.array(list(reversed(alt_eq.points))) alt_dots = dots.copy() alt_dots.submobjects.reverse() target_arcs += [alt_eq, alt_eq.copy()] target_dots += [alt_dots, alt_dots.copy()] equator_transform = Succession(*[ Transform(equator, arc, rate_func = None) for arc in target_arcs ]) dots_transform = Succession(*[ Transform(dots, target, rate_func = None) for target in target_dots ]) self.play( ShowCreation(transverse_curve, submobject_mode = "all_at_once"), equator_transform, dots_transform, run_time = 10, rate_func = None, ) self.dither(2) def walk_transverse_curve(self): transverse_curve = self.get_transverse_curve(self.great_arc_images) dots = self.first_dots def dot_update(dots, alpha): for dot, curve in zip(dots, transverse_curve): dot.move_to(curve.point_from_proportion(alpha)) return dots for x in range(2): self.play( UpdateFromAlphaFunc(dots, dot_update), run_time = 4 ) self.play( UpdateFromAlphaFunc(dots, dot_update), run_time = 4, rate_func = lambda t : 0.455*smooth(t) ) self.play( dots.highlight, YELLOW, dots.scale_in_place, 1.2, rate_func = there_and_back ) self.dither() ####### def get_arc_walk_dot_movement(self, arc, dots, **kwargs): def dot_update(dots, alpha): dots[0].move_to(arc.point_from_proportion(0.5*alpha)) dots[1].move_to(arc.point_from_proportion(0.5+0.5*alpha)) return dots if "run_time" not in kwargs: kwargs["run_time"] = 5 return UpdateFromAlphaFunc(dots, dot_update, **kwargs) def sphere_to_plane(self, point): x, y, z = point return np.array([ x - 2*x*z + y + 1, y+0.5*y*np.cos(z*np.pi), 0 ]) def sphere_point(self, portion_around_equator, equator_tilt = 0): theta = portion_around_equator*2*np.pi point = np.cos(theta)*RIGHT + np.sin(theta)*UP phi = equator_tilt*np.pi return rotate_vector(point, phi, RIGHT) def get_great_arc_images(self): curves = VGroup(*[ ParametricFunction( lambda t : self.sphere_point(t, s) ).apply_function(self.sphere_to_plane) for s in np.arange(0, 1, 1./self.num_great_arcs) # for s in [0] ]) curves.highlight(YELLOW) curves[0].highlight(RED) for curve in curves: antipodal_x_diff = lambda x : \ curve.point_from_proportion(x+0.5)[0]-\ curve.point_from_proportion(x)[0] last_x = 0 last_sign = np.sign(antipodal_x_diff(last_x)) for x in np.linspace(0, 0.5, 100): sign = np.sign(antipodal_x_diff(x)) if sign != last_sign: mean = np.mean([last_x, x]) curve.x_collision_points = [ curve.point_from_proportion(mean), curve.point_from_proportion(mean+0.5), ] curve.collision_point_proportion = mean break last_x = x last_sign = sign return curves def get_transverse_curve(self, gerat_arc_images): points = list(it.chain(*[ [ curve.x_collision_points[i] for curve in gerat_arc_images ] for i in 0, 1 ])) full_curve = VMobject(close_new_points = True) full_curve.set_points_smoothly(points + [points[0]]) full_curve.highlight(MAROON_B) first_half = full_curve.copy().pointwise_become_partial( full_curve, 0, 0.5 ) second_half = first_half.copy().rotate_in_place(np.pi, RIGHT) broken_curve = VGroup(first_half, second_half) return broken_curve class WalkAroundEquatorPreimage(ExternallyAnimatedScene): pass class WalkTiltedEquatorPreimage(ExternallyAnimatedScene): pass class FormLoopTransverseToEquator(ExternallyAnimatedScene): pass class AntipodalWalkAroundTransverseLoop(ExternallyAnimatedScene): pass class MentionGenerality(TeacherStudentsScene): CONFIG = { "camera_class" : ShadingCamera, } def construct(self): necklace = Necklace(width = SPACE_WIDTH) necklace.shift(2*UP) necklace.to_edge(RIGHT) arrow = TexMobject("\\Leftrightarrow") arrow.scale(2) arrow.next_to(necklace, LEFT) q_marks = TexMobject("???") q_marks.next_to(arrow, UP) arrow.add(q_marks) formula = TexMobject("f(\\textbf{x}) = f(-\\textbf{x})") formula.next_to(self.get_students(), UP, buff = LARGE_BUFF) formula.to_edge(LEFT, buff = LARGE_BUFF) self.play( self.teacher.change_mode, "raise_right_hand", self.teacher.look_at, arrow ) self.play( FadeIn(necklace, run_time = 2, submobject_mode = "lagged_start"), Write(arrow), *[ ApplyMethod(pi.look_at, arrow) for pi in self.get_everyone() ] ) self.change_student_modes("pondering", "erm", "confused") self.dither() self.play(*[ ApplyMethod(pi.look_at, arrow) for pi in self.get_everyone() ]) self.play(Write(formula)) self.dither(3) class SimpleSphere(ExternallyAnimatedScene): pass class PointsIn3D(Scene): CONFIG = { # "colors" : [RED, GREEN, BLUE], "colors" : color_gradient([GREEN, BLUE], 3), } def construct(self): sphere_def = TextMobject( "\\doublespacing Sphere in 3D: All", "$(x_1, x_2, x_3)$\\\\", "such that", "$x_1^2 + x_2^2 + x_3^2 = 1$", alignment = "", ) sphere_def.next_to(ORIGIN, DOWN) for index, subindex_list in (1, [1, 2, 4, 5, 7, 8]), (3, [0, 2, 4, 6, 8, 10]): colors = np.repeat(self.colors, 2) for subindex, color in zip(subindex_list, colors): sphere_def[index][subindex].highlight(color) point_ex = TextMobject( "For example, ", "(", "0.41", ", ", "-0.58", ", ", "0.71", ")", arg_separator = "" ) for index, color in zip([2, 4, 6], self.colors): point_ex[index].highlight(color) point_ex.scale(0.8) point_ex.next_to( sphere_def[1], UP+RIGHT, buff = 1.5*LARGE_BUFF ) point_ex.shift_onto_screen() arrow = Arrow(sphere_def[1].get_top(), point_ex.get_bottom()) self.play(Write(sphere_def[1])) self.play(ShowCreation(arrow)) self.play(Write(point_ex)) self.dither() self.play( Animation(sphere_def[1].copy(), remover = True), Write(sphere_def), ) self.dither() class AntipodalPairToBeGivenCoordinates(ExternallyAnimatedScene): pass class WritePointCoordinates(Scene): CONFIG = { "colors" : color_gradient([GREEN, BLUE], 3), "corner" : DOWN+RIGHT, } def construct(self): coords = self.get_coords() arrow = Arrow( -self.corner, self.corner, stroke_width = 8, color = MAROON_B ) x_component = self.corner[0]*RIGHT y_component = self.corner[1]*UP arrow.next_to( coords.get_edge_center(y_component), y_component, aligned_edge = -x_component, buff = MED_SMALL_BUFF ) group = VGroup(coords, arrow) group.scale(2) group.to_corner(self.corner) self.play(FadeIn(coords)) self.play(ShowCreation(arrow)) self.dither() def get_coords(self): coords = TexMobject( "(", "0.41", ", ", "-0.58", ", ", "0.71", ")", arg_separator = "" ) for index, color in zip([1, 3, 5], self.colors): coords[index].highlight(color) return coords class WriteAntipodalCoordinates(WritePointCoordinates): CONFIG = { "corner" : UP+LEFT, "sign_color" : RED, } def get_coords(self): coords = TexMobject( "(", "-", "0.41", ", ", "+", "0.58", ", ", "-", "0.71", ")", arg_separator = "" ) for index, color in zip([2, 5, 8], self.colors): coords[index].highlight(color) coords[index-1].highlight(self.sign_color) return coords class GeneralizeBorsukUlam(Scene): CONFIG = { "n_dims" : 3, "boundary_colors" : [GREEN_B, BLUE], "output_boundary_color" : [MAROON_B, YELLOW], "negative_color" : RED, } def setup(self): self.colors = color_gradient(self.boundary_colors, self.n_dims) def construct(self): sphere_set = self.get_sphere_set() arrow = Arrow(LEFT, RIGHT) f = TexMobject("f") output_space = self.get_output_space() equation = self.get_equation() sphere_set.to_corner(UP+LEFT) arrow.next_to(sphere_set, RIGHT) f.next_to(arrow, UP) output_space.next_to(arrow, RIGHT) equation.to_edge(RIGHT) lhs = VGroup(*equation[:2]) eq = equation[2] rhs = VGroup(*equation[3:]) self.play(FadeIn(sphere_set)) self.dither() self.play( ShowCreation(arrow), Write(f) ) self.play(Write(output_space)) self.dither() self.play(FadeIn(lhs)) self.play( ReplacementTransform(lhs.copy(), rhs), Write(eq) ) self.dither() def get_condition(self): squares = map(TexMobject, [ "x_%d^2"%d for d in range(1, 1+self.n_dims) ]) for square, color in zip(squares, self.colors): square[0].highlight(color) square[-1].highlight(color) plusses = [TexMobject("+") for x in range(self.n_dims-1)] plusses += [TexMobject("=1")] condition = VGroup(*it.chain(*zip(squares, plusses))) condition.arrange_submobjects(RIGHT) return condition def get_tuple(self): mid_parts = list(it.chain(*[ ["x_%d"%d, ","] for d in range(1, self.n_dims) ])) tup = TexMobject(*["("] + mid_parts + ["x_%d"%self.n_dims, ")"]) for index, color in zip(it.count(1, 2), self.colors): tup[index].highlight(color) return tup def get_negative_tuple(self): mid_parts = list(it.chain(*[ ["-", "x_%d"%d, ","] for d in range(1, self.n_dims) ])) tup = TexMobject(*["("] + mid_parts + ["-", "x_%d"%self.n_dims, ")"]) for index, color in zip(it.count(1, 3), self.colors): tup[index].highlight(self.negative_color) tup[index+1].highlight(color) return tup def get_output_space(self): return TextMobject("%dD space"%(self.n_dims-1)) # n_dims = self.n_dims-1 # colors = color_gradient(self.output_boundary_color, n_dims) # mid_parts = list(it.chain(*[ # ["y_%d"%d, ","] # for d in range(1, n_dims) # ])) # tup = TexMobject(*["("] + mid_parts + ["y_%d"%n_dims, ")"]) # for index, color in zip(it.count(1, 2), colors): # tup[index].highlight(color) # return tup def get_equation(self): tup = self.get_tuple() neg_tup = self.get_negative_tuple() f1, f2 = [TexMobject("f") for x in range(2)] equals = TexMobject("=") equation = VGroup(f1, tup, equals, f2, neg_tup) equation.arrange_submobjects(RIGHT, buff = SMALL_BUFF) return equation def get_sphere_set(self): tup = self.get_tuple() such_that = TextMobject("such that") such_that.next_to(tup, RIGHT) condition = self.get_condition() condition.next_to( tup, DOWN, buff = MED_LARGE_BUFF, aligned_edge = LEFT ) group = VGroup(tup, such_that, condition) l_brace = Brace(group, LEFT) r_brace = Brace(group, RIGHT) group.add(l_brace, r_brace) return group # class FourDBorsukUlam(GeneralizeBorsukUlam): # CONFIG = { # "n_dims" : 4, # } # class FiveDBorsukUlam(GeneralizeBorsukUlam): # CONFIG = { # "n_dims" : 5, # } class MakeTwoJewelCaseContinuous(IntroduceStolenNecklaceProblem): CONFIG = { "camera_class" : ShadingCamera, "jewel_colors" : [BLUE, GREEN], "num_per_jewel" : [8, 10], "random_seed" : 2, "forced_binary_choices" : (0, 1, 0), "show_matching_after_divvying" : True, "necklace_center" : ORIGIN, "necklace_width" : 2*SPACE_WIDTH - 3, "random_seed" : 0, "num_continuous_division_searches" : 4, } def construct(self): random.seed(self.random_seed) self.introduce_necklace() self.divvy_with_n_cuts() self.identify_necklace_with_unit_interval() self.color_necklace() self.find_continuous_fair_division() self.show_continuous_fair_division() self.highlight_continuous_groups() self.mention_equivalence_to_discrete_case() self.shift_divide_off_tick_marks() def introduce_necklace(self): self.get_necklace( width = self.necklace_width, ) self.play(FadeIn( self.necklace, submobject_mode = "lagged_start" )) self.shuffle_jewels(self.necklace.jewels) jewel_types = self.get_jewels_organized_by_type( self.necklace.jewels ) self.dither() self.count_jewel_types(jewel_types) self.dither() self.jewel_types = jewel_types def count_jewel_types(self, jewel_types): enumeration_labels = VGroup() for jewel_type in jewel_types: num_mob = TexMobject(str(len(jewel_type))) jewel_copy = jewel_type[0].copy() # jewel_copy.scale_to_fit_height(num_mob.get_height()) jewel_copy.next_to(num_mob) label = VGroup(num_mob, jewel_copy) enumeration_labels.add(label) enumeration_labels.arrange_submobjects(RIGHT, buff = LARGE_BUFF) enumeration_labels.to_edge(UP) for jewel_type, label in zip(jewel_types, enumeration_labels): jewel_type.submobjects.sort(lambda m1, m2: cmp(m1.get_center()[0], m2.get_center()[0])) jewel_type.save_state() jewel_type.generate_target() jewel_type.target.arrange_submobjects() jewel_type.target.move_to(2*UP) self.play( MoveToTarget(jewel_type), Write(label) ) self.play(jewel_type.restore) def divvy_with_n_cuts(self): IntroduceStolenNecklaceProblem.divvy_with_n_cuts( self, with_thieves = False, highlight_groups = False, show_matching_after_divvying = True, ) def identify_necklace_with_unit_interval(self): interval = UnitInterval( tick_frequency = 1./sum(self.num_per_jewel), tick_size = 0.2, numbers_with_elongated_ticks = [], ) interval.stretch_to_fit_width(self.necklace.get_width()) interval.move_to(self.necklace) tick_marks = interval.tick_marks tick_marks.set_stroke(WHITE, width = 2) brace = Brace(interval) brace_text = brace.get_text("Length = 1") self.play( GrowFromCenter(brace), Write(brace_text) ) self.dither() self.play( ShowCreation(interval.tick_marks), ) self.dither() self.tick_marks = interval.tick_marks self.length_brace = VGroup(brace, brace_text) def color_necklace(self): example_index = len(self.necklace.jewels)/2 jewels = self.necklace.jewels chain = self.necklace.chain self.remove(self.necklace) self.add(chain, jewels) jewels.submobjects.sort( lambda m1, m2 : cmp(m1.get_center()[0], m2.get_center()[0]) ) remaining_indices = range(len(jewels)) remaining_indices.remove(example_index) example_segment = self.color_necklace_by_indices(example_index) remaining_segments = self.color_necklace_by_indices(*remaining_indices) self.remove(chain) segments = VGroup(example_segment[0], *remaining_segments) segments.submobjects.sort( lambda m1, m2 : cmp(m1.get_center()[0], m2.get_center()[0]) ) segment_types = VGroup(*[ VGroup(*filter( lambda m : m.get_color() == Color(color), segments )) for color in self.jewel_colors ]) for group in segment_types: length_tex = TexMobject("\\frac{%d}{%d}"%( len(group), len(jewels) )) length_tex.next_to(group, UP) length_tex.shift(UP) self.play( group.shift, UP, Write(length_tex, run_time = 1), ) self.dither() self.play( group.shift, DOWN, FadeOut(length_tex) ) self.play(FadeOut(self.length_brace)) self.segments = segments def color_necklace_by_indices(self, *indices): chain = self.necklace.chain jewels = VGroup(*[ self.necklace.jewels[i] for i in indices ]) n_jewels = len(self.necklace.jewels) segments = VGroup(*[ Line( chain.point_from_proportion(index/float(n_jewels)), chain.point_from_proportion((index+1)/float(n_jewels)), color = jewel.get_color() ) for index, jewel in zip(indices, jewels) ]) for jewel in jewels: jewel.save_state() self.play(jewels.shift, jewels.get_height()*UP) self.play(ReplacementTransform( jewels, segments, submobject_mode = "lagged_start", run_time = 2 )) self.dither() return segments def find_continuous_fair_division(self): chain = self.necklace.chain n_jewels = len(self.necklace.jewels) slice_indices, ignore = self.find_slice_indices( self.necklace.jewels, self.jewel_types ) cut_proportions = [ sorted([random.random(), random.random()]) for x in range(self.num_continuous_division_searches) ] cut_proportions.append([ float(i)/n_jewels for i in slice_indices[1:-1] ]) cut_points = [ map(chain.point_from_proportion, pair) for pair in cut_proportions ] v_lines = VGroup(*[DashedLine(UP, DOWN) for x in range(2)]) for line, point in zip(v_lines, cut_points[0]): line.move_to(point) self.play(ShowCreation(v_lines)) self.dither() for target_points in cut_points[1:]: self.play(*[ ApplyMethod(line.move_to, point) for line, point in zip(v_lines, target_points) ]) self.dither() self.slice_indices = slice_indices self.v_lines = v_lines def show_continuous_fair_division(self): slice_indices = self.slice_indices groups = [ VGroup( VGroup(*self.segments[i1:i2]), VGroup(*self.tick_marks[i1:i2]), ) for i1, i2 in zip(slice_indices, slice_indices[1:]) ] groups[-1].add(self.tick_marks[-1]) vects = [[UP, DOWN][i] for i in self.forced_binary_choices] self.play(*[ ApplyMethod(group.shift, 0.5*vect) for group, vect in zip(groups, vects) ]) self.dither() self.groups = groups def highlight_continuous_groups(self): top_group = VGroup(self.groups[0], self.groups[2]) bottom_group = self.groups[1] boxes = VGroup() for group in top_group, bottom_group: box = Rectangle( width = group.get_width()+2*SMALL_BUFF, height = group.get_height()+SMALL_BUFF, stroke_width = 0, fill_color = WHITE, fill_opacity = 0.25, ) box.move_to(group) boxes.add(box) weight_description = VGroup(*[ VGroup( TexMobject("\\frac{%d}{%d}"%( len(jewel_type)/2, len(self.segments) )), Jewel(color = jewel_type[0].get_color()) ).arrange_submobjects() for jewel_type in self.jewel_types ]) weight_description.arrange_submobjects(buff = LARGE_BUFF) weight_description.next_to(boxes, UP, aligned_edge = LEFT) self.play(FadeIn(boxes)) self.play(Write(weight_description)) self.dither() self.highlight_box = boxes self.weight_description = weight_description def mention_equivalence_to_discrete_case(self): morty = Mortimer() morty.flip() morty.to_edge(DOWN) morty.shift(LEFT) self.play(FadeIn(morty)) self.play(PiCreatureSays( morty, """This is equivalent to the discrete case. """, bubble_kwargs = { "height" : 3, "direction" : LEFT, } )) self.play(Blink(morty)) self.dither() self.play(*map(FadeOut, [ morty, morty.bubble, morty.bubble.content ])) def shift_divide_off_tick_marks(self): groups = self.groups slice_indices = self.slice_indices v_lines = self.v_lines left_segment = groups[1][0][0] left_tick = groups[1][1][0] right_segment = groups[-1][0][0] right_tick = groups[-1][1][0] segment_width = left_segment.get_width() for mob in left_segment, right_segment: mob.parts = VGroup( mob.copy().pointwise_become_partial(mob, 0, 0.5), mob.copy().pointwise_become_partial(mob, 0.5, 1), ) self.remove(mob) self.add(mob.parts) restorers = [left_segment.parts, left_tick, right_segment.parts, right_tick] for mob in restorers: mob.save_state() self.play(v_lines.shift, segment_width*RIGHT/2) self.play(*[ ApplyMethod(mob.shift, vect) for mob, vect in [ (left_segment.parts[0], UP), (left_tick, UP), (right_segment.parts[0], DOWN), (right_tick, DOWN), ] ]) self.dither() words = TextMobject("Cut part way through segment") words.to_edge(RIGHT) words.shift(2*UP) arrow1 = Arrow(words.get_bottom(), left_segment.parts[0].get_right()) arrow2 = Arrow(words.get_bottom(), right_segment.parts[1].get_left()) VGroup(words, arrow1, arrow2).highlight(RED) self.play(Write(words), ShowCreation(arrow1)) self.dither() self.play(ShowCreation(arrow2)) self.dither() self.play(*map(FadeOut, [words, arrow1, arrow2])) for line in v_lines: self.play(line.shift, segment_width*LEFT/2) self.play(*[mob.restore for mob in restorers]) self.remove(left_segment.parts, right_segment.parts) self.add(left_segment, right_segment) class ThinkAboutTheChoices(TeacherStudentsScene): def construct(self): self.teacher_says(""" Think about the choices behind a division... """) self.change_student_modes( *["pondering"]*3, look_at_arg = SPACE_WIDTH*RIGHT+SPACE_HEIGHT*DOWN ) self.dither(3) class ChoicesInNecklaceCutting(Scene): CONFIG = { "num_continuous_division_searches" : 4, "final_num_pair" : [1./6, 1./2], "necklace_center" : DOWN, "thief_box_offset" : 1.2, } def construct(self): self.add_necklace() self.choose_places_to_cut() self.show_three_numbers_adding_to_one() self.make_binary_choice() def add_necklace(self): width, colors, num_per_color = [ MakeTwoJewelCaseContinuous.CONFIG[key] for key in [ "necklace_width", "jewel_colors", "num_per_jewel" ] ] color_list = list(it.chain(*[ num*[color] for num, color in zip(num_per_color, colors) ])) random.shuffle(color_list) interval = UnitInterval( tick_frequency = 1./sum(num_per_color), tick_size = 0.2, numbers_with_elongated_ticks = [], ) interval.stretch_to_fit_width(width) interval.shift(self.necklace_center) tick_marks = interval.tick_marks tick_marks.set_stroke(WHITE, width = 2) segments = VGroup() for l_tick, r_tick, color in zip(tick_marks, tick_marks[1:], color_list): segment = Line( l_tick.get_center(), r_tick.get_center(), color = color ) segments.add(segment) self.necklace = VGroup(segments, tick_marks) self.add(self.necklace) self.interval = interval def choose_places_to_cut(self): v_lines = VGroup(*[DashedLine(UP, DOWN) for x in range(2)]) num_pairs = [ sorted([random.random(), random.random()]) for x in range(self.num_continuous_division_searches) ] + [self.final_num_pair] point_pairs = [ map(self.interval.number_to_point, num_pair) for num_pair in num_pairs ] for line, point in zip(v_lines, point_pairs[0]): line.move_to(point) self.play(ShowCreation(v_lines)) for point_pair in point_pairs[1:]: self.dither() self.play(*[ ApplyMethod(line.move_to, point) for line, point in zip(v_lines, point_pair) ]) self.dither() self.division_points = list(it.chain( [self.interval.get_left()], point_pairs[-1], [self.interval.get_right()] )) self.v_lines = v_lines def show_three_numbers_adding_to_one(self): points = self.division_points braces = [ Brace(Line(p1+SMALL_BUFF*RIGHT/2, p2+SMALL_BUFF*LEFT/2)) for p1, p2 in zip(points, points[1:]) ] for char, denom, brace in zip("abc", [6, 3, 2], braces): brace.label = brace.get_text("$%s$"%char) brace.concrete_label = brace.get_text("$\\frac{1}{%d}$"%denom) VGroup( brace.label, brace.concrete_label ).highlight(YELLOW) words = TextMobject( "1) Choose", "$a$, $b$, $c$", "so that", "$a+b+c = 1$" ) words[1].highlight(YELLOW) words[3].highlight(YELLOW) words.to_corner(UP+LEFT) self.play(*it.chain(*[ [GrowFromCenter(brace), Write(brace.label)] for brace in braces ])) self.play(Write(words)) self.dither(2) self.play(*[ ReplacementTransform(brace.label, brace.concrete_label) for brace in braces ]) self.dither() self.wiggle_v_lines() self.dither() self.play(*map(FadeOut, list(braces) + [ brace.concrete_label for brace in braces ])) self.choice_one_words = words def make_binary_choice(self): groups = self.get_groups() boxes, labels = self.get_boxes_and_labels() arrow_pairs, curr_arrows = self.get_choice_arrow_pairs(groups) words = TextMobject("2) Make a binary choice for each segment") words.next_to( self.choice_one_words, DOWN, buff = MED_LARGE_BUFF, aligned_edge = LEFT ) self.play(Write(words)) self.play(*map(FadeIn, [boxes, labels])) for binary_choices in it.product(*[[0, 1]]*3): self.play(*[ ApplyMethod(group.move_to, group.target_points[choice]) for group, choice in zip(groups, binary_choices) ] + [ Transform( curr_arrow, arrow_pair[choice], path_arc = np.pi ) for curr_arrow, arrow_pair, choice in zip( curr_arrows, arrow_pairs, binary_choices ) ]) self.dither() ###### def get_groups(self): segments, tick_marks = self.necklace n_segments = len(segments) indices = [0, n_segments/6, n_segments/2, n_segments] groups = [ VGroup( VGroup(*segments[i1:i2]), VGroup(*tick_marks[i1:i2]), ) for i1, i2 in zip(indices, indices[1:]) ] for group, index in zip(groups, indices[1:]): group.add(tick_marks[index].copy()) groups[-1][-1].add(tick_marks[-1]) for group in groups: group.target_points = [ group.get_center() + self.thief_box_offset*vect for vect in UP, DOWN ] return groups def get_boxes_and_labels(self): box = Rectangle( height = self.necklace.get_height()+SMALL_BUFF, width = self.necklace.get_width()+2*SMALL_BUFF, stroke_width = 0, fill_color = WHITE, fill_opacity = 0.25 ) box.move_to(self.necklace) boxes = VGroup(*[ box.copy().shift(self.thief_box_offset*vect) for vect in UP, DOWN ]) labels = VGroup(*[ TextMobject( "Thief %d"%(i+1) ).next_to(box, UP, aligned_edge = RIGHT) for i, box in enumerate(boxes) ]) return boxes, labels def get_choice_arrow_pairs(self, groups): arrow = TexMobject("\\uparrow") arrow_pairs = [ [arrow.copy(), arrow.copy().rotate(np.pi)] for group in groups ] pre_arrow_points = [ VectorizedPoint(group.get_center()) for group in groups ] for point, arrow_pair in zip(pre_arrow_points, arrow_pairs): for arrow, color in zip(arrow_pair, [GREEN, RED]): arrow.highlight(color) arrow.move_to(point.get_center()) return arrow_pairs, pre_arrow_points def wiggle_v_lines(self): self.play( *it.chain(*[ [ line.rotate_in_place, np.pi/12, vect, line.highlight, RED ] for line, vect in zip(self.v_lines, [OUT, IN]) ]), rate_func = wiggle ) class CompareThisToSphereChoice(TeacherStudentsScene): def construct(self): self.teacher_says(""" Compare this to choosing a point on the sphere. """) self.change_student_modes( *["pondering"]*3, look_at_arg = SPACE_WIDTH*RIGHT+SPACE_HEIGHT*DOWN ) self.dither(3) class ChoicesForSpherePoint(GeneralizeBorsukUlam): def construct(self): self.add_sphere_set() self.initialize_words() self.play(Write(self.choice_one_words)) self.dither() self.show_example_choices() self.show_binary_choices() def get_tuple(self): tup = TexMobject("(x, y, z)") for i, color in zip([1, 3, 5], self.colors): tup[i].highlight(color) return tup def get_condition(self): condition = TexMobject("x^2+y^2+z^2 = 1") for i, color in zip([0, 3, 6], self.colors): VGroup(*condition[i:i+2]).highlight(color) return condition def add_sphere_set(self): sphere_set = self.get_sphere_set() sphere_set.scale(0.7) sphere_set.to_edge(RIGHT) sphere_set.shift(UP) self.add(sphere_set) def initialize_words(self): choice_one_words = TextMobject( "1) Choose", "$x^2$, $y^2$, $z^2$", "so that", "$x^2+y^2+z^2 = 1$" ) for i in 1, 3: for j, color in zip([0, 3, 6], self.colors): VGroup(*choice_one_words[i][j:j+2]).highlight(color) choice_one_words.to_corner(UP+LEFT) choice_two_words = TextMobject( "2) Make a binary choice for each one" ) choice_two_words.next_to( choice_one_words, DOWN, buff = MED_LARGE_BUFF, aligned_edge = LEFT ) self.choice_one_words = choice_one_words self.choice_two_words = choice_two_words def show_example_choices(self): choices = VGroup(*[ TexMobject(*tex).highlight(color) for color, tex in zip(self.colors, [ ("x", "^2 = ", "1/6"), ("y", "^2 = ", "1/3"), ("z", "^2 = ", "1/2"), ]) ]) choices.arrange_submobjects( DOWN, buff = LARGE_BUFF, aligned_edge = LEFT ) choices.scale_to_fit_height(SPACE_HEIGHT) choices.to_edge(LEFT) choices.shift(DOWN) self.play(FadeIn( choices, run_time = 2, submobject_mode = "lagged_start" )) self.dither() self.choices = choices def show_binary_choices(self): for choice in self.choices: var_tex = choice.expression_parts[0] frac_tex = choice.expression_parts[2] sqrts = VGroup(*[ TexMobject( var_tex + "=" + sign + \ "\\sqrt{%s}"%frac_tex) for sign in ["+", "-"] ]) for sqrt in sqrts: sqrt.scale(0.6) sqrts.arrange_submobjects(DOWN) sqrts.next_to(choice, RIGHT, buff = LARGE_BUFF) sqrts.highlight(choice.get_color()) arrows = VGroup(*[ Arrow( choice.get_right(), sqrt.get_left(), color = WHITE, tip_length = 0.1, buff = SMALL_BUFF ) for sqrt in sqrts ]) self.play(ShowCreation(arrows)) self.play(FadeIn(sqrts, submobject_mode = "lagged_start")) self.play(Write(self.choice_two_words)) self.dither() class NecklaceDivisionSphereAssociation(ChoicesInNecklaceCutting): CONFIG = { "xyz_colors" : color_gradient([GREEN_B, BLUE], 3), "necklace_center" : DOWN, "thief_box_offset" : 1.6, "denoms" : [6, 3, 2], } def construct(self): self.add_necklace() self.add_sphere_point_label() self.choose_places_to_cut() self.add_braces() self.add_boxes_and_labels() self.show_binary_choice_association() self.ask_about_antipodal_pairs() def add_sphere_point_label(self): label = TextMobject( "$(x, y, z)$", "such that", "$x^2 + y^2 + z^2 = 1$" ) for i, j_list in (0, [1, 3, 5]), (2, [0, 3, 6]): for j, color in zip(j_list, self.xyz_colors): label[i][j].highlight(color) label.to_corner(UP+RIGHT) ghost_sphere_point = VectorizedPoint() ghost_sphere_point.to_corner(UP+LEFT, buff = LARGE_BUFF) ghost_sphere_point.shift(2*RIGHT) arrow = Arrow( label.get_left(), ghost_sphere_point, color = WHITE ) self.add(label, arrow) self.sphere_point_label = label def add_braces(self): points = self.division_points braces = [ Brace( Line(p1+SMALL_BUFF*RIGHT/2, p2+SMALL_BUFF*LEFT/2), UP ) for p1, p2 in zip(points, points[1:]) ] for char, brace, color, denom in zip("xyz", braces, self.xyz_colors, self.denoms): brace.label = brace.get_text( "$%s^2$"%char, "$= 1/%d$"%denom, buff = SMALL_BUFF ) brace.label.highlight(color) if brace.label.get_right()[0] > brace.get_right()[0]: brace.label.next_to( brace, UP, buff = SMALL_BUFF, aligned_edge = RIGHT ) self.play(*it.chain( map(GrowFromCenter, braces), [Write(brace.label) for brace in braces] )) self.dither() self.braces = braces def add_boxes_and_labels(self): boxes, labels = self.get_boxes_and_labels() self.play(*map(FadeIn, [boxes, labels])) self.dither() def show_binary_choice_association(self): groups = self.get_groups() self.swapping_anims = [] final_choices = [1, 0, 1] quads = zip(self.braces, self.denoms, groups, final_choices) for brace, denom, group, final_choice in quads: char = brace.label.args[0][1] choices = [ TexMobject( char, "=", sign, "\\sqrt{\\frac{1}{%d}}"%denom ) for sign in "+", "-" ] for choice, color in zip(choices, [GREEN, RED]): # choice[0].highlight(brace.label.get_color()) choice[2].highlight(color) choice.scale(0.8) choice.move_to(group) if choice.get_width() > 0.8*group.get_width(): choice.next_to(group.get_right(), LEFT, buff = MED_SMALL_BUFF) original_choices = [m.copy() for m in choices] self.play( ReplacementTransform( VGroup(brace.label[0], brace, brace.label[1]), choices[0] ), group.move_to, group.target_points[0] ) self.dither() self.play( Transform(*choices), group.move_to, group.target_points[1] ) self.dither() if final_choice == 0: self.play( Transform(choices[0], original_choices[0]), group.move_to, group.target_points[0] ) self.swapping_anims += [ Transform(choices[0], original_choices[1-final_choice]), group.move_to, group.target_points[1-final_choice] ] def ask_about_antipodal_pairs(self): question = TextMobject("What do antipodal points signify?") question.move_to(self.sphere_point_label, LEFT) question.highlight(MAROON_B) antipodal_tex = TexMobject( "(x, y, z) \\rightarrow (-x, -y, -z)" ) antipodal_tex.next_to(question, DOWN, aligned_edge = LEFT) self.play(FadeOut(self.sphere_point_label)) self.play(FadeIn(question)) self.dither() self.play(Write(antipodal_tex)) self.dither() self.wiggle_v_lines() self.dither() self.play(*self.swapping_anims) self.dither() class TotalLengthOfEachJewelEquals(NecklaceDivisionSphereAssociation): CONFIG = { "random_seed" : 2, "hard_coded_fair_division_indices" : [], } def construct(self): random.seed(self.random_seed) self.add_necklace() self.add_boxes_and_labels() self.find_fair_division() def find_fair_division(self): pass