From 5b228ba8dab83b40f91ea787d10a6a3a4bc6d546 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Fri, 7 Aug 2015 18:10:00 -0700 Subject: [PATCH] Maybe 2/3 through inventing math project --- animation/transform.py | 84 +++- constants.py | 5 +- mobject/image_mobject.py | 16 +- mobject/mobject.py | 34 +- mobject/simple_mobjects.py | 41 +- sample_script.py | 18 +- scene/__init__.py | 3 +- scene/arithmetic_scenes.py | 93 ++++ scene/graphs.py | 2 +- scene/scene.py | 5 +- scripts/ecf_graph_scenes.py | 6 +- scripts/inventing_math.py | 971 ++++++++++++++++++++++++++++++++++-- scripts/moser_main.py | 40 +- scripts/tau_poem.py | 16 +- 14 files changed, 1216 insertions(+), 118 deletions(-) create mode 100644 scene/arithmetic_scenes.py diff --git a/animation/transform.py b/animation/transform.py index 49e64006..cb441843 100644 --- a/animation/transform.py +++ b/animation/transform.py @@ -9,11 +9,34 @@ from mobject import Mobject, Point from constants import * from helpers import * +def straight_path(start_points, end_points, alpha): + return (1-alpha)*start_points + alpha*end_points + +def semi_circular_path(start_points, end_points, alpha, axis): + midpoints = (start_points + end_points) / 2 + angle = alpha * np.pi + rot_matrix = rotation_matrix(angle, axis)[:2, :2] + result = np.zeros(start_points.shape) + result[:,:2] = np.dot( + (start_points - midpoints)[:,:2], + np.transpose(rot_matrix) + ) + midpoints[:,:2] + result[:,2] = (1-alpha)*start_points[:,2] + alpha*end_points[:,2] + return result + +def clockwise_path(start_points, end_points, alpha): + return semi_circular_path(start_points, end_points, alpha, IN) + +def counterclockwise_path(start_points, end_points, alpha): + return semi_circular_path(start_points, end_points, alpha, OUT) + class Transform(Animation): def __init__(self, mobject1, mobject2, run_time = DEFAULT_TRANSFORM_RUN_TIME, + interpolation_function = straight_path, black_out_extra_points = False, *args, **kwargs): + self.interpolation_function = interpolation_function count1, count2 = mobject1.get_num_points(), mobject2.get_num_points() if count2 == 0: mobject2 = Point((SPACE_WIDTH, SPACE_HEIGHT, 0)) @@ -37,10 +60,14 @@ class Transform(Animation): self.non_redundant_m2_indices = indices def update_mobject(self, alpha): - Mobject.interpolate( - self.starting_mobject, - self.ending_mobject, - self.mobject, + self.mobject.points = self.interpolation_function( + self.starting_mobject.points, + self.ending_mobject.points, + alpha + ) + self.mobject.rgbs = straight_path( + self.starting_mobject.rgbs, + self.ending_mobject.rgbs, alpha ) @@ -59,23 +86,19 @@ class Transform(Animation): )[self.non_redundant_m2_indices] ) -class SemiCircleTransform(Transform): - def __init__(self, mobject1, mobject2, counterclockwise = True, - *args, **kwargs): - Transform.__init__(self, mobject1, mobject2, *args, **kwargs) - self.axis = (0, 0, 1) if counterclockwise else (0, 0, -1) +class ClockwiseTransform(Transform): + def __init__(self, mobject1, mobject2, **kwargs): + Transform.__init__( + self, mobject1, mobject2, + interpolation_function = clockwise_path, **kwargs + ) - def update_mobject(self, alpha): - sm, em = self.starting_mobject, self.ending_mobject - midpoints = (sm.points + em.points) / 2 - angle = alpha * np.pi - rot_matrix = rotation_matrix(angle, self.axis)[:2, :2] - self.mobject.points[:,:2] = np.dot( - (sm.points - midpoints)[:,:2], - np.transpose(rot_matrix) - ) + midpoints[:,:2] - self.mobject.points[:,2] = (1-alpha)*sm.points[:,2] + alpha*em.points[:,2] - self.mobject.rgbs = (1-alpha)*sm.rgbs + alpha*em.rgbs +class CounterclockwiseTransform(Transform): + def __init__(self, mobject1, mobject2, **kwargs): + Transform.__init__( + self, mobject1, mobject2, + interpolation_function = counterclockwise_path, **kwargs + ) class FadeToColor(Transform): def __init__(self, mobject, color, *args, **kwargs): @@ -119,12 +142,25 @@ class ApplyMethod(Transform): ) class ApplyFunction(Transform): - def __init__(self, mobject, function, run_time = DEFAULT_ANIMATION_RUN_TIME, - *args, **kwargs): + def __init__(self, function, mobject, **kwargs): + Transform.__init__( + self, + mobject, + function(copy.deepcopy(mobject)), + **kwargs + ) + self.name = "ApplyFunctionTo"+str(mobject) + + +class ApplyPointwiseFunction(Transform): + def __init__(self, mobject, function, + run_time = DEFAULT_ANIMATION_RUN_TIME, **kwargs): map_image = copy.deepcopy(mobject) map_image.points = np.array(map(function, map_image.points)) - Transform.__init__(self, mobject, map_image, run_time = run_time, - *args, **kwargs) + Transform.__init__( + self, mobject, map_image, + run_time = run_time, **kwargs + ) self.name = "".join([ "Apply", "".join([s.capitalize() for s in function.__name__.split("_")]), diff --git a/constants.py b/constants.py index 5a59e588..909e1f62 100644 --- a/constants.py +++ b/constants.py @@ -41,8 +41,8 @@ UP = np.array(( 0, 1, 0)) DOWN = np.array(( 0,-1, 0)) RIGHT = np.array(( 1, 0, 0)) LEFT = np.array((-1, 0, 0)) -IN = np.array(( 0, 0, 1)) -OUT = np.array(( 0, 0,-1)) +IN = np.array(( 0, 0,-1)) +OUT = np.array(( 0, 0, 1)) THIS_DIR = os.path.dirname(os.path.realpath(__file__)) FILE_DIR = os.path.join(THIS_DIR, "files") @@ -61,6 +61,7 @@ SIZE_TO_REPLACE = "SizeHere" TEX_TEXT_TO_REPLACE = "YourTextHere" TEMPLATE_TEX_FILE = os.path.join(TEX_DIR, "template.tex") TEMPLATE_TEXT_FILE = os.path.join(TEX_DIR, "text_template.tex") +MAX_LEN_FOR_HUGE_TEX_FONT = 25 LOGO_PATH = os.path.join(IMAGE_DIR, "logo.png") diff --git a/mobject/image_mobject.py b/mobject/image_mobject.py index 0aec87de..c3e93725 100644 --- a/mobject/image_mobject.py +++ b/mobject/image_mobject.py @@ -81,18 +81,26 @@ class VideoIcon(ImageMobject): self.scale(0.3) self.center() -def text_mobject(text, size = "\\Large"): +def text_mobject(text, size = None): + size = size or "\\Large" #TODO, auto-adjust? return tex_mobject(text, size, TEMPLATE_TEXT_FILE) def tex_mobject(expression, - size = "\\Huge", + size = None, template_tex_file = TEMPLATE_TEX_FILE): + if size == None: + if len("".join(expression)) < MAX_LEN_FOR_HUGE_TEX_FONT: + size = "\\Huge" + else: + size = "\\large" + #Todo, make this more sophisticated. images = tex_to_image(expression, size, template_tex_file) if isinstance(images, list): #TODO, is checking listiness really the best here? - return CompoundMobject(*map(ImageMobject, images)).center() + result = CompoundMobject(*map(ImageMobject, images)) else: - return ImageMobject(images).center() + result = ImageMobject(images) + return result.highlight("white").center() diff --git a/mobject/mobject.py b/mobject/mobject.py index a6cd6114..41eac731 100644 --- a/mobject/mobject.py +++ b/mobject/mobject.py @@ -146,11 +146,15 @@ class Mobject(object): center = self.get_center() return self.center().scale(scale_factor).shift(center) + def stretch(self, factor, dim): + self.points[:,dim] *= factor + return self + def stretch_to_fit(self, length, dim): center = self.get_center() old_length = max(self.points[:,dim]) - min(self.points[:,dim]) self.center() - self.points[:,dim] *= length/old_length + self.stretch(length/old_length, dim) self.shift(center) return self @@ -178,6 +182,9 @@ class Mobject(object): return self def replace(self, mobject, stretch = False): + if mobject.get_num_points() == 0: + raise Warning("Attempting to replace mobject with no points") + return self if stretch: self.stretch_to_fit_width(mobject.get_width()) self.stretch_to_fit_height(mobject.get_height()) @@ -231,11 +238,34 @@ class Mobject(object): ### Getters ### def get_num_points(self): - return self.points.shape[0] + return len(self.points) def get_center(self): + return (np.max(self.points, 0) + np.min(self.points, 0))/2.0 + + def get_center_of_mass(self): return np.apply_along_axis(np.mean, 0, self.points) + def get_border_point(self, direction): + return self.points[np.argmax(np.dot(self.points, direction))] + + def get_edge_center(self, dim, max_or_min_func): + result = self.get_center() + result[dim] = max_or_min_func(self.points[:,dim]) + return result + + def get_top(self): + return self.get_edge_center(1, np.max) + + def get_bottom(self): + return self.get_edge_center(1, np.min) + + def get_right(self): + return self.get_edge_center(0, np.max) + + def get_left(self): + return self.get_edge_center(0, np.min) + def get_width(self): return np.max(self.points[:, 0]) - np.min(self.points[:, 0]) diff --git a/mobject/simple_mobjects.py b/mobject/simple_mobjects.py index a7260d66..0cc2cf50 100644 --- a/mobject/simple_mobjects.py +++ b/mobject/simple_mobjects.py @@ -6,6 +6,7 @@ from constants import * from helpers import * class Point(Mobject): + DEFAULT_COLOR = "black" def __init__(self, point = (0, 0, 0), *args, **kwargs): Mobject.__init__(self, *args, **kwargs) self.points = np.array(point).reshape(1, 3) @@ -21,16 +22,18 @@ class Arrow(Mobject1D): length = 1, tip_length = 0.25, normal = (0, 0, 1), + density = DEFAULT_POINT_DENSITY_1D, *args, **kwargs): self.point = np.array(point) if tail is not None: direction = self.point - tail length = np.linalg.norm(direction) self.direction = np.array(direction) / np.linalg.norm(direction) + density *= max(length, 0.1) self.length = length self.normal = np.array(normal) self.tip_length = tip_length - Mobject1D.__init__(self, *args, **kwargs) + Mobject1D.__init__(self, density = density, **kwargs) def generate_points(self): self.add_points([ @@ -131,12 +134,34 @@ class CurvedLine(Line): class Circle(Mobject1D): DEFAULT_COLOR = "red" + def __init__(self, radius = 1.0, **kwargs): + self.radius = radius + Mobject1D.__init__(self, **kwargs) + def generate_points(self): self.add_points([ - (np.cos(theta), np.sin(theta), 0) - for theta in np.arange(0, 2 * np.pi, self.epsilon) + (self.radius*np.cos(theta), self.radius*np.sin(theta), 0) + for theta in np.arange(0, 2 * np.pi, self.epsilon/self.radius) ]) +class Rectangle(Mobject1D): + DEFAULT_COLOR = "yellow" + def __init__(self, height = 2.0, width = 2.0, **kwargs): + self.height, self.width = height, width + Mobject1D.__init__(self, **kwargs) + + def generate_points(self): + wh = [self.width/2.0, self.height/2.0] + self.add_points([ + (x, u, 0) if dim==0 else (u, x, 0) + for dim in 0, 1 + for u in wh[1-dim], -wh[1-dim] + for x in np.arange(-wh[dim], wh[dim], self.epsilon) + ]) + +class Square(Rectangle): + def __init__(self, side_length = 2.0, **kwargs): + Rectangle.__init__(self, side_length, side_length, **kwargs) class Bubble(Mobject): def __init__(self, direction = LEFT, index_of_tip = -1, center = ORIGIN): @@ -151,7 +176,7 @@ class Bubble(Mobject): return self.points[self.index_of_tip] def get_bubble_center(self): - return Mobject.get_center(self)+self.center_offset + return self.get_center()+self.center_offset def move_tip_to(self, point): self.shift(point - self.get_tip()) @@ -168,8 +193,7 @@ class Bubble(Mobject): def add_content(self, mobject): mobject.scale(0.75*self.get_width() / mobject.get_width()) mobject.shift(self.get_bubble_center()) - self.content = CompoundMobject(self.content, mobject) - self.add(self.content) + self.content = mobject return self def write(self, text): @@ -177,10 +201,7 @@ class Bubble(Mobject): return self def clear(self): - num_content_points = self.content.points.shape[0] - self.points = self.points[:-num_content_points] - self.rgbs = self.rgbs[:-num_content_points] - self.contents = Mobject() + self.content = Mobject() return self class SpeechBubble(Bubble): diff --git a/sample_script.py b/sample_script.py index 51f865ba..bd3f34c8 100644 --- a/sample_script.py +++ b/sample_script.py @@ -10,14 +10,24 @@ from animation import * from mobject import * from constants import * from region import * -from scene import Scene +from scene import Scene, RearrangeEquation from script_wrapper import command_line_create_scene -class SampleScene(Scene): +class SampleScene(RearrangeEquation): def construct(self): - tauy = TauCreature() - self.animate(ApplyMethod(tauy.make_sad)) + start_terms = "a + b = c".split(" ") + end_terms = "a = c - b + 0".split(" ") + index_map = { + 0 : 0, + 1 : 3, + 2 : 4, + 3 : 1, + 4 : 2, + } + RearrangeEquation.construct( + self, start_terms, end_terms, index_map + ) diff --git a/scene/__init__.py b/scene/__init__.py index 8e51a158..50c1fdf2 100644 --- a/scene/__init__.py +++ b/scene/__init__.py @@ -1,2 +1,3 @@ from scene import * -from sub_scenes import * \ No newline at end of file +from sub_scenes import * +from arithmetic_scenes import * \ No newline at end of file diff --git a/scene/arithmetic_scenes.py b/scene/arithmetic_scenes.py new file mode 100644 index 00000000..ca16acc6 --- /dev/null +++ b/scene/arithmetic_scenes.py @@ -0,0 +1,93 @@ +import numpy as np +import itertools as it + +from scene import Scene +from graphs import * + +from mobject import * +from animation import * +from region import * +from constants import * +from helpers import * + +class RearrangeEquation(Scene): + def construct( + self, + start_terms, + end_terms, + index_map, + size = None, + path = counterclockwise_path, + start_transform = None, + end_transform = None, + leave_start_terms = False, + transform_kwargs = {}, + ): + transform_kwargs["interpolation_function"] = path + start_mobs, end_mobs = self.get_mobs_from_terms( + start_terms, end_terms, size + ) + if start_transform: + start_mobs = start_transform(CompoundMobject(*start_mobs)).split() + if end_transform: + end_mobs = end_transform(CompoundMobject(*end_mobs)).split() + unmatched_start_indices = set(range(len(start_mobs))) + unmatched_end_indices = set(range(len(end_mobs))) + unmatched_start_indices.difference_update( + [n%len(start_mobs) for n in index_map] + ) + unmatched_end_indices.difference_update( + [n%len(end_mobs) for n in index_map.values()] + ) + mobject_pairs = [ + (start_mobs[a], end_mobs[b]) + for a, b in index_map.iteritems() + ]+ [ + (Point(end_mobs[b].get_center()), end_mobs[b]) + for b in unmatched_end_indices + ] + if not leave_start_terms: + mobject_pairs += [ + (start_mobs[a], Point(start_mobs[a].get_center())) + for a in unmatched_start_indices + ] + + self.add(*start_mobs) + if leave_start_terms: + self.add(CompoundMobject(*start_mobs)) + self.dither() + self.animate(*[ + Transform(*pair, **transform_kwargs) + for pair in mobject_pairs + ]) + self.dither() + + + def get_mobs_from_terms(self, start_terms, end_terms, size): + """ + Need to ensure that all image mobjects for a tex expression + stemming from the same string are point-for-point copies of one + and other. This makes transitions much smoother, and not look + like point-clouds. + """ + num_start_terms = len(start_terms) + all_mobs = np.array( + tex_mobject(start_terms, size = size).split() + \ + tex_mobject(end_terms, size = size).split() + ) + all_terms = np.array(start_terms+end_terms) + for term in set(all_terms): + matches = all_terms == term + if sum(matches) > 1: + base_mob = all_mobs[list(all_terms).index(term)] + all_mobs[matches] = [ + deepcopy(base_mob).replace(target_mob) + for target_mob in all_mobs[matches] + ] + return all_mobs[:num_start_terms], all_mobs[num_start_terms:] + + + + + + diff --git a/scene/graphs.py b/scene/graphs.py index f141aedb..348e65ad 100644 --- a/scene/graphs.py +++ b/scene/graphs.py @@ -247,7 +247,7 @@ class GraphScene(Scene): mobject.center() diameter = max(mobject.get_height(), mobject.get_width()) self.animate(*[ - SemiCircleTransform( + CounterclockwiseTransform( vertex, deepcopy(mobject).shift(vertex.get_center()) ) diff --git a/scene/scene.py b/scene/scene.py index b1db8998..86ce6558 100644 --- a/scene/scene.py +++ b/scene/scene.py @@ -213,7 +213,6 @@ class Scene(object): self.number = num_mob return self - def get_frame(self): frame = self.background for mob in self.mobjects: @@ -224,6 +223,10 @@ class Scene(object): self.frames += [self.get_frame()]*int(duration / self.frame_duration) return self + def repeat(self, num): + self.frames = self.frames*num + return self + def write_to_gif(self, name = None, end_dither_time = DEFAULT_DITHER_TIME): self.dither(end_dither_time) diff --git a/scripts/ecf_graph_scenes.py b/scripts/ecf_graph_scenes.py index db8397cb..65604044 100644 --- a/scripts/ecf_graph_scenes.py +++ b/scripts/ecf_graph_scenes.py @@ -165,7 +165,7 @@ class OldIntroduceGraphs(GraphScene): friends = text_mobject("Friends").scale(EDGE_ANNOTATION_SCALE_VAL) self.annotate_edges(friends.shift((0, friends.get_height()/2, 0))) self.animate(*[ - SemiCircleTransform(vertex, Dot(point)) + CounterclockwiseTransform(vertex, Dot(point)) for vertex, point in zip(self.vertices, self.points) ]+[ Transform(ann, line) @@ -558,7 +558,7 @@ class FacebookGraph(GraphScene): self.annotate_edges(friends) self.dither() self.animate(*[ - SemiCircleTransform(account, vertex) + CounterclockwiseTransform(account, vertex) for account, vertex in zip(accounts, self.vertices) ]) self.dither() @@ -1176,7 +1176,7 @@ class FinalSum(Scene): copy = plus if index == -2 else deepcopy(mob) copy.center().shift(lines[index].get_center()) copy.scale_in_place(lines[index].get_width()/mob.get_width()) - anims.append(SemiCircleTransform(copy, mob)) + anims.append(CounterclockwiseTransform(copy, mob)) self.clear() self.animate(*anims, run_time = 2.0) self.dither() diff --git a/scripts/inventing_math.py b/scripts/inventing_math.py index 518f65d0..16990af3 100644 --- a/scripts/inventing_math.py +++ b/scripts/inventing_math.py @@ -5,13 +5,14 @@ import itertools as it from copy import deepcopy import sys import operator as op +from random import sample from animation import * from mobject import * from constants import * from region import * -from scene import Scene +from scene import Scene, RearrangeEquation from script_wrapper import command_line_create_scene MOVIE_PREFIX = "inventing_math/" @@ -78,8 +79,11 @@ def divergent_sum(): def convergent_sum(): return tex_mobject(CONVERGENT_SUM_TEXT, size = "\\large").scale(2) -def underbrace(): - return tex_mobject("\\underbrace{%s}"%(8*"\\quad")) +def underbrace(left, right): + result = tex_mobject("\\underbrace{%s}"%(14*"\\quad")) + result.stretch_to_fit_width(right[0]-left[0]) + result.shift(left - result.points[0]) + return result def zero_to_one_interval(): interval = NumberLine( @@ -92,11 +96,26 @@ def zero_to_one_interval(): one = tex_mobject("1").shift(INTERVAL_RADIUS*RIGHT+DOWN) return CompoundMobject(interval, zero, one) -def draw_you(): +def draw_you(with_bubble = False): result = PiCreature() result.give_straight_face().highlight("grey") result.to_corner(LEFT+DOWN) result.rewire_part_attributes() + if with_bubble: + bubble = ThoughtBubble() + bubble.stretch_to_fit_width(12) + bubble.pin_to(result) + return result, bubble + return result + +def get_room_colors(): + return list(Color("yellow").range_to("red", 4)) + +def power_of_divisor(n, d): + result = 0 + while n%d == 0: + result += 1 + n /= d return result class FlipThroughNumbers(Animation): @@ -272,7 +291,7 @@ class DiscoverAndDefine(Scene): class YouAsMathematician(Scene): def construct(self): - you = draw_you() + you, bubble = draw_you(with_bubble = True) explanation = text_mobject( "You as a (questionably accurate portrayal of a) mathematician.", size = "\\small" @@ -281,9 +300,6 @@ class YouAsMathematician(Scene): arrow.nudge(you.get_width()) for mob in arrow, explanation: mob.highlight("yellow") - bubble = ThoughtBubble() - bubble.stretch_to_fit_width(10) - bubble.pin_to(you) equation = convergent_sum() bubble.add_content(equation) equation.shift(0.5*RIGHT) @@ -319,7 +335,7 @@ class YouAsMathematician(Scene): everything = CompoundMobject(*self.mobjects) self.clear() self.animate( - ApplyFunction( + ApplyPointwiseFunction( everything, lambda p : 3*SPACE_WIDTH*p/np.linalg.norm(p) ), @@ -376,18 +392,23 @@ class ZoomInOnInterval(Scene): self.dither() class DanceDotOnInterval(Scene): - def construct(self): + def construct(self, mode): + num_written_terms = NUM_WRITTEN_TERMS + prop = 0.5 + sum_terms = [ + "\\frac{1}{2}", + "\\frac{1}{4}", + "\\frac{1}{8}", + "\\frac{1}{16}", + ] num_height = 1.3*DOWN interval = zero_to_one_interval() dots = [ Dot(radius = 3*Dot.DEFAULT_RADIUS).shift(INTERVAL_RADIUS*x+UP) for x in LEFT, RIGHT ] - color_range = Color("green").range_to("yellow", NUM_WRITTEN_TERMS) - conv_sum = convergent_sum().split() - partial_sums = tex_mobject(PARTIAL_CONVERGENT_SUMS_TEXT, size = "\\small") - partial_sums.scale(1.5).to_edge(UP) - partial_sum_parts = partial_sums.split() + color_range = Color("green").range_to("yellow", num_written_terms) + conv_sum = tex_mobject(sum_terms, size = "\\large").split() self.add(interval) self.animate(*[ @@ -395,8 +416,8 @@ class DanceDotOnInterval(Scene): for dot in dots ]) self.dither() - for count in range(NUM_WRITTEN_TERMS): - shift_val = RIGHT*INTERVAL_RADIUS/(2.0**count) + for count in range(num_written_terms): + shift_val = 2*RIGHT*INTERVAL_RADIUS*(1-prop)*(prop**count) start = dots[0].get_center() line = Line(start, start + shift_val*RIGHT) line.highlight(color_range.next()) @@ -404,17 +425,31 @@ class DanceDotOnInterval(Scene): ApplyMethod(dots[0].shift, shift_val), ShowCreation(line) ) - num = conv_sum[count].scale(0.75) + num = conv_sum[count] num.shift(RIGHT*(line.get_center()[0]-num.get_center()[0])) - if num.get_width() > line.get_length(): - num.stretch_to_fit_width(line.get_length()) num.shift(num_height) + arrow = Mobject() + if num.get_width() > line.get_length(): + num.center().shift(3*DOWN+2*(count-2)*RIGHT) + arrow = Arrow( + line.get_center()+2*DOWN, + tail = num.get_center()+0.5*num.get_height()*UP + ) self.animate( ApplyMethod(line.shift, 2*DOWN), - FadeIn(num) + FadeIn(num), + FadeIn(arrow), ) - self.dither() + self.dither() + self.write_partial_sums() + self.dither() + + def write_partial_sums(self): + partial_sums = tex_mobject(PARTIAL_CONVERGENT_SUMS_TEXT, size = "\\small") + partial_sums.scale(1.5).to_edge(UP) + partial_sum_parts = partial_sums.split() partial_sum_parts[0].highlight("yellow") + for x in range(0, len(partial_sum_parts), 4): partial_sum_parts[x+2].highlight("yellow") self.animate(*[ @@ -442,7 +477,7 @@ class OrganizePartialSums(Scene): self.add(*partial_sum_parts) self.dither() self.animate(*[ - SemiCircleTransform(*pair, counterclockwise=False) + CounterclockwiseTransform(*pair, counterclockwise=False) for pair in zip(pure_sums, new_pure_sums) ]+[ FadeOut(mob) @@ -484,6 +519,25 @@ class SeeNumbersApproachOne(Scene): ) self.dither() +class OneAndInfiniteSumAreTheSameThing(Scene): + def construct(self): + one, equals, inf_sum = tex_mobject([ + "1", "=", "\\sum_{n=1}^\\infty \\frac{1}{2^n}" + ]).split() + point = Point(equals.get_center()).highlight("black") + + self.add(one.shift(LEFT)) + self.dither() + self.add(inf_sum.shift(RIGHT)) + self.dither() + self.animate( + ApplyMethod(one.shift, RIGHT), + ApplyMethod(inf_sum.shift, LEFT), + CounterclockwiseTransform(point, equals) + ) + self.dither() + + class HowDoYouDefineInfiniteSums(Scene): def construct(self): you = draw_you().center().rewire_part_attributes() @@ -508,20 +562,22 @@ class HowDoYouDefineInfiniteSums(Scene): self.add(sum_mob) self.dither() + class LessAboutNewThoughts(Scene): def construct(self): words = generating, new, thoughts, to, definitions = text_mobject([ "Generating", " new", " thoughts", "$\\rightarrow$", "useful definitions" ], size = "\\large").split() - gen_cross = ImageMobject("cross").highlight("red") + gen_cross = tex_mobject("\\hline").highlight("red") new_cross = deepcopy(gen_cross) for cross, mob in [(gen_cross, generating), (new_cross, new)]: - cross.replace(mob, stretch = True) - disecting = text_mobject("Disecting") - disecting.shift(generating.get_center() + 0.7*UP) - old = text_mobject("old") - old.shift(new.get_center()+0.7*DOWN) + cross.replace(mob) + cross.stretch_to_fit_height(0.03) + disecting = text_mobject("Disecting").highlight("green") + disecting.shift(generating.get_center() + 0.6*UP) + old = text_mobject("old").highlight("green") + old.shift(new.get_center()+0.6*UP) kwargs = {"run_time" : 0.25} self.add(*words) @@ -530,7 +586,7 @@ class LessAboutNewThoughts(Scene): self.animate(ShowCreation(new_cross, **kwargs)) self.dither() self.add(disecting) - self.dither(0.25) + self.dither(0.5) self.add(old) self.dither() @@ -564,6 +620,7 @@ class ListOfPartialSums(Scene): for number, finite_sum in zip(numbers, sums) ] ) + self.dither() self.animate(*[ ApplyMethod(s.highlight, "yellow", alpha_func = there_and_back) for s in sums @@ -581,13 +638,18 @@ class ShowDecreasingDistance(Scene): def construct(self, num): number_line = NumberLine(interval_size = 1).add_numbers() - vert_line0 = Line(0.5*UP, UP) - vert_line1 = Line(0.5*UP+num*RIGHT, UP+num*RIGHT) - horiz_line = Line(UP, UP+num*RIGHT) + vert_line0 = Line(0.5*UP+RIGHT, UP+RIGHT) + vert_line1 = Line(0.5*UP+2*num*RIGHT, UP+2*num*RIGHT) + horiz_line = Line(vert_line0.end, vert_line1.end) lines = [vert_line0, vert_line1, horiz_line] for line in lines: line.highlight("green") + dots = CompoundMobject(*[ + Dot().scale(1.0/(n+1)).shift((1+partial_sum(n))*RIGHT) + for n in range(10) + ]) + self.add(dots.split()[0]) self.add(number_line, *lines) self.dither() self.animate( @@ -596,24 +658,857 @@ class ShowDecreasingDistance(Scene): horiz_line, Line(vert_line0.end+RIGHT, vert_line1.end).highlight("green") ), + ShowCreation(dots), run_time = 2.5 ) self.dither() +class CircleZoomInOnOne(Scene): + def construct(self): + number_line = NumberLine(interval_size = 1).add_numbers() + dots = CompoundMobject(*[ + Dot().scale(1.0/(n+1)).shift((1+partial_sum(n))*RIGHT) + for n in range(10) + ]) + circle = Circle().shift(2*RIGHT) + + self.add(number_line, dots) + self.animate(Transform(circle, Point(2*RIGHT)), run_time = 5.0) + self.dither() + +class ZoomInOnOne(Scene): + def construct(self): + num_levels = 3 + scale_val = 5 + number_lines = [ + NumberLine( + interval_size = 1, + density = scale_val*DEFAULT_POINT_DENSITY_1D + ).scale(1.0/scale_val**x) + for x in range(num_levels) + ] + kwargs = {"alpha_func" : None} + self.animate(*[ + ApplyMethod(number_lines[x].scale, scale_val, **kwargs) + for x in range(1, num_levels) + ]+[ + ApplyMethod(number_lines[0].stretch, scale_val, 0, **kwargs), + ]) + + self.repeat(5) + + +class DefineInfiniteSum(Scene): + def construct(self): + self.put_expression_in_corner() + self.list_partial_sums() + self.dither() + + def put_expression_in_corner(self): + buff = 0.24 + define, infinite_sum = tex_mobject([ + "\\text{\\emph{Define} }", + "\\sum_{n = 0}^\\infty a_n = X" + ]).split() + define.highlight("skyblue") + expression = CompoundMobject(define, infinite_sum) + + self.add(expression) + self.dither() + self.animate(ApplyFunction( + lambda mob : mob.scale(0.5).to_corner(UP+LEFT, buff = buff), + expression + )) + bottom = (min(expression.points[:,1]) - buff)*UP + side = (max(expression.points[:,0]) + buff)*RIGHT + lines = [ + Line(SPACE_WIDTH*LEFT+bottom, side+bottom), + Line(SPACE_HEIGHT*UP+side, side+bottom) + ] + self.animate(*[ + ShowCreation(line.highlight("white")) + for line in lines + ]) + self.dither() + + def list_partial_sums(self): + num_terms = 10 + term_strings = reduce(op.add, [ + [ + "s_%d"%n, + "&=", + "+".join(["a_%d"%k for k in range(n+1)])+"\\\\" + ] + for n in range(num_terms) + ]) + terms = tex_mobject(term_strings, size = "\\large").split() + number_line = NumberLine() + ex_point = 2*RIGHT + ex = tex_mobject("X").shift(ex_point + LEFT + UP) + arrow = Arrow(ex_point, tail = ex.points[-1]).nudge() + + for term, count in zip(terms, it.count()): + self.add(term) + self.dither(0.1) + if count % 3 == 2: + self.dither(0.5) + self.dither() + esses = np.array(terms)[range(0, len(terms), 3)] + other_terms = filter(lambda m : m not in esses, terms) + self.animate(*[ + ApplyMethod(ess.highlight, "yellow") + for ess in esses + ]) + + def move_s(s, n): + s.center() + s.scale(1.0/(n+1)) + s.shift(ex_point-RIGHT*2.0/2**n) + return s + self.animate(*[ + FadeOut(term) + for term in other_terms + ]+[ + ApplyFunction(lambda s : move_s(s, n), ess) + for ess, n in zip(esses, it.count()) + ]+[ + FadeIn(number_line), + FadeIn(ex), + FadeIn(arrow) + ]) + + lines = [ + Line(x+0.25*DOWN, x+0.25*UP).highlight("white") + for y in [-1, -0.01, 1, 0.01] + for x in [ex_point+y*RIGHT] + ] + self.animate(*[ + Transform(lines[x], lines[x+1], run_time = 3.0) + for x in 0, 2 + ]) + + +class YouJustInventedSomeMath(Scene): + def construct(self): + text = text_mobject([ + "You ", "just ", "invented\\\\", "some ", "math" + ]).split() + for mob in text[:3]: + mob.shift(UP) + for mob in text[3:]: + mob.shift(1.3*DOWN) + you = draw_you().center().rewire_part_attributes() + + self.add(you) + for mob in text: + self.add(mob) + self.dither(0.2) + self.animate(BlinkPiCreature(you)) + + +class SeekMoreGeneralTruths(Scene): + def construct(self): + summands = [ + "\\frac{1}{3^n}", + "2^n", + "\\frac{1}{n^2}", + "\\frac{(-1)^n}{n}", + "\\frac{(-1)^n}{(2n)!}", + "\\frac{2\sqrt{2}}{99^2}\\frac{(4n)!}{(n!)^4} \\cdot \\frac{26390n + 1103}{396^{4k}}", + ] + sums = tex_mobject([ + "&\\sum_{n = 0}^\\infty" + summand + "= ? \\\\" + for summand in summands + ], size = "") + sums.stretch_to_fit_height(2*SPACE_HEIGHT-1) + sums.shift((SPACE_HEIGHT-0.5-max(sums.points[:,1]))*UP) + + for qsum in sums.split(): + self.add(qsum) + self.dither(0.5) + self.dither() + +class ChopIntervalInProportions(Scene): + args_list = [("9", ), ("p", )] + @staticmethod + def args_to_string(*args): + return args[0] + + def construct(self, mode): + if mode == "9": + prop = 0.1 + num_terms = 2 + left_terms, right_terms = [ + [ + tex_mobject("\\frac{%d}{%d}"%(k, (10**(count+1)))) + for count in range(num_terms) + ] + for k in 9, 1 + ] + if mode == "p": + num_terms = 3 + prop = 0.7 + left_terms = map(tex_mobject, ["(1-p)", "p(1-p)"]+[ + "p^%d(1-p)"%(count) + for count in range(2, num_terms) + ]) + right_terms = map(tex_mobject, ["p"] + [ + "p^%d"%(count+1) + for count in range(1, num_terms) + ]) + interval = zero_to_one_interval() + left = INTERVAL_RADIUS*LEFT + right = INTERVAL_RADIUS*RIGHT + curr = left.astype("float") + brace_to_replace = None + term_to_replace = None + + self.add(interval) + for lt, rt, count in zip(left_terms, right_terms, it.count()): + last = deepcopy(curr) + curr += 2*RIGHT*INTERVAL_RADIUS*(1-prop)*(prop**count) + braces = [ + underbrace(a, b).rotate(np.pi, RIGHT) + for a, b in [(last, curr), (curr, right)] + ] + for term, brace, count2 in zip([lt, rt], braces, it.count()): + term.scale(0.75) + term.shift(brace.get_center()+UP) + if term.get_width() > brace.get_width(): + term.shift(UP+RIGHT) + term.add(Arrow( + brace.get_center()+0.3*UP, + tail = term.get_center()+0.5*DOWN + )) + + if brace_to_replace is not None: + self.animate( + Transform( + brace_to_replace.repeat(2), + CompoundMobject(*braces) + ), + Transform( + term_to_replace, + CompoundMobject(lt, rt) + ) + ) + self.remove(brace_to_replace, term_to_replace) + self.add(lt, rt, *braces) + else: + self.animate(*[ + FadeIn(mob) + for mob in braces + [lt, rt] + ]) + self.dither() + brace_to_replace = braces[1] + term_to_replace = rt + self.dither() +class GeometricSum(RearrangeEquation): + def construct(self): + start_terms = "(1-p) + p (1-p) + p^2 (1-p) + p^3 (1-p) + \\cdots = 1" + end_terms = "1 + p + p^2 + p^3 + \\cdots = \\frac{1}{(1-p)}" + index_map = { + 0 : 0, + # 0 : -1, #(1-p) + 1 : 1, #+ + 2 : 2, #p + # 3 : -1, #(1-p) + 4 : 3, #+ + 5 : 4, #p^2 + # 6 : -1, #(1-p) + 7 : 5, #+ + 8 : 6, #p^3 + # 9 : -1, #(1-p) + 10: 7, #+ + 11: 8, #\\cdots + 12: 9, #= + 13: 10, #1 + } + def start_transform(mob): + return mob.scale(1.3).shift(2*UP) + def end_transform(mob): + return mob.scale(1.3).shift(DOWN) + + RearrangeEquation.construct( + self, + start_terms.split(" "), end_terms.split(" "), + index_map, size = "\\large", + path = counterclockwise_path, + start_transform = start_transform, + end_transform = end_transform, + leave_start_terms = True, + transform_kwargs = {"run_time" : 2.0} + ) + +class PointNineRepeating(RearrangeEquation): + def construct(self): + start_terms = [ + "\\frac{9}{10}", + "+", + "\\frac{9}{100}", + "+", + "\\frac{9}{1000}", + "+", + "\\cdots=1", + ] + end_terms = "0 . 9 9 9 \\cdots=1".split(" ") + index_map = { + 0 : 2, + 2 : 3, + 4 : 4, + 6 : 5, + } + for term in tex_mobject(start_terms).split(): + self.add(term) + self.dither(0.5) + self.clear() + RearrangeEquation.construct( + self, + start_terms, + end_terms, + index_map, + path = straight_path + ) + + +class PlugNumbersIntoRightside(Scene): + def construct(self): + scale_factor = 1.5 + lhs, rhs = tex_mobject( + ["1 + p + p^2 + p^3 + \\cdots = ", "\\frac{1}{1-p}"], + size = "\\large" + ).scale(scale_factor).split() + rhs = tex_mobject( + ["1 \\over 1 - ", "p"], + size = "\\large" + ).replace(rhs).split() + num_strings = [ + "0.5", "3", "\pi", "(-1)", "3.7", "2", + "0.2", "27", "i" + ] + nums = [ + tex_mobject(num_string, size="\\large") + for num_string in num_strings + ] + for num, num_string in zip(nums, num_strings): + num.scale(scale_factor) + num.shift(rhs[1].get_center()) + num.shift(0.1*RIGHT + 0.08*UP) + num.highlight("green") + if num_string == "(-1)": + num.shift(0.3*RIGHT) + right_words = text_mobject( + "This side makes sense for almost any value of $p$," + ).shift(2*UP) + left_words = text_mobject( + "even if it seems like this side will not." + ).shift(2*DOWN) + right_words.add(Arrow( + rhs[0].get_center(), + tail = right_words.get_center()+DOWN+RIGHT + ).nudge(0.5)) + left_words.add(Arrow( + lhs.get_center() + 0.3*DOWN, + tail = left_words.get_center() + 0.3*UP + )) + right_words.highlight("green") + left_words.highlight("yellow") + + + self.add(lhs, *rhs) + self.dither() + self.animate(FadeIn(right_words)) + curr = rhs[1] + for num, count in zip(nums, it.count()): + self.animate(CounterclockwiseTransform(curr, num)) + self.dither() + if count == 2: + self.animate(FadeIn(left_words)) + + +class PlugInNegativeOne(RearrangeEquation): + def construct(self): + num_written_terms = 4 + start_terms = reduce(op.add, [ + ["(-", "1", ")^%d"%n, "+"] + for n in range(num_written_terms) + ]) + ["\\cdots=", "\\frac{1}{1-(-1)}"] + end_terms = "1 - 1 + 1 - 1 + \\cdots= \\frac{1}{2}".split(" ") + index_map = dict([ + (4*n + 1, 2*n) + for n in range(num_written_terms) + ]+[ + (4*n + 3, 2*n + 1) + for n in range(num_written_terms) + ]) + index_map[-2] = -2 + index_map[-1] = -1 + RearrangeEquation.construct( + self, + start_terms, + end_terms, + index_map, + path = straight_path, + start_transform = lambda m : m.shift(2*UP), + leave_start_terms = True, + ) + +class PlugInTwo(RearrangeEquation): + def construct(self): + num_written_terms = 4 + start_terms = reduce(op.add, [ + ["2", "^%d"%n, "+"] + for n in range(num_written_terms) + ]) + ["\\cdots=", "\\frac{1}{1-2}"] + end_terms = "1 + 2 + 4 + 8 + \\cdots= -1".split(" ") + index_map = dict([ + (3*n, 2*n) + for n in range(num_written_terms) + ]+[ + (3*n + 2, 2*n + 1) + for n in range(num_written_terms) + ]) + index_map[-2] = -2 + index_map[-1] = -1 + RearrangeEquation.construct( + self, + start_terms, + end_terms, + index_map, + size = "\\Huge", + path = straight_path, + start_transform = lambda m : m.shift(2*UP), + leave_start_terms = True, + ) + +class ListPartialDivergentSums(Scene): + args_list = [ + ( + lambda n : "1" if n%2 == 0 else "(-1)", + lambda n : "1" if n%2 == 0 else "0" + ), + ( + lambda n : "2^%d"%n if n > 1 else ("1" if n==0 else "2"), + lambda n : str(2**(n+1)-1) + ) + ] + @staticmethod + def args_to_string(*args): + if args[0](1) == "(-1)": + return "Negative1" + else: + return args[0](1) + def construct(self, term_func, partial_sum_func): + num_lines = 8 + rhss = [ + partial_sum_func(n) + for n in range(num_lines) + ] + lhss = [ + "&=" + \ + "+".join([term_func(k) for k in range(n+1)]) + \ + "\\\\" + for n in range(num_lines) + ] + terms = tex_mobject( + list(it.chain.from_iterable(zip(rhss, lhss))) + ["\\vdots&", ""], + size = "\\large" + ).shift(RIGHT).split() + words = text_mobject("These numbers don't \\\\ approach anything") + words.to_edge(LEFT) + arrow = Arrow(3*DOWN+2*LEFT, direction = DOWN, length = 6) + + for x in range(0, len(terms), 2): + self.animate(FadeIn(terms[x]), FadeIn(terms[x+1])) + self.animate(FadeIn(words), ShowCreation(arrow)) + for x in range(0, len(terms), 2): + self.animate( + ApplyMethod(terms[x].highlight, "green"), + run_time = 0.1 + ) + self.dither() + + +class SumPowersOfTwoAnimation(Scene): + def construct(self): + dot = Dot(density = 3*DEFAULT_POINT_DENSITY_1D).scale(3) + dot_width = dot.get_width()*RIGHT + dot_buff = 0.4*RIGHT + left = (SPACE_WIDTH-1)*LEFT + right = left + 2*dot_width + dot_buff + top_brace_left = left+dot_width+dot_buff+0.3*DOWN + bottom_brace_left = left + 0.3*DOWN + circle = Circle().scale(dot_width[0]/2).shift(left+dot_width/2) + curr_dots = deepcopy(dot).shift(left+1.5*dot_width+dot_buff) + topbrace = underbrace(top_brace_left, right).rotate(np.pi, RIGHT) + bottombrace = underbrace(bottom_brace_left, right) + colors = Color("yellow").range_to("purple", 5) + curr_dots.highlight(colors.next()) + equation = tex_mobject( + "1+2+4+\\cdots+2^n=2^{n+1}" + ).shift(3*UP) + + self.add(equation) + self.dither() + self.add(circle, curr_dots, topbrace, bottombrace) + for n in range(1,5): + top_num = tex_mobject("+".join([ + str(2**k) + for k in range(n) + ])).shift(topbrace.get_center()+0.5*UP) + bottom_num = tex_mobject(str(2**n)) + bottom_num.shift(bottombrace.get_center()+0.5*DOWN) + self.add(top_num, bottom_num) + self.dither() + if n == 4: + continue + new_dot = deepcopy(dot).shift(circle.get_center()+2*UP) + self.animate(ApplyMethod( + new_dot.shift, 2*DOWN, + alpha_func = rush_into + )) + self.remove(new_dot) + shift_val = (2**n)*(dot_width+dot_buff) + right += shift_val + new_dots = CompoundMobject(new_dot, curr_dots) + target = deepcopy(new_dots).shift(shift_val) + target.highlight(colors.next()) + self.remove(top_num, bottom_num) + self.animate( + CounterclockwiseTransform( + new_dots, target, + alpha_func = rush_from + ), + Transform( + topbrace, + underbrace(top_brace_left, right).rotate(np.pi, RIGHT) + ), + Transform( + bottombrace, + underbrace(bottom_brace_left, right) + ) + ) + curr_dots = CompoundMobject(curr_dots, new_dots) + + + +class PretendTheyDoApproachNegativeOne(RearrangeEquation): + def construct(self): + you, bubble = draw_you(with_bubble = True) + start_terms = "1 , 3 , 7 , 15 , 31 , \\cdots\\rightarrow -1".split(" ") + end_terms = "2 , 4 , 8 , 16 , 32 , \\cdots\\rightarrow 0".split(" ") + def transform(mob): + bubble.add_content(mob) + return mob + index_map = dict([(a, a) for a in range(len(start_terms))]) + + self.add(you, bubble) + RearrangeEquation.construct( + self, start_terms, end_terms, index_map, + size = "\\Huge", + start_transform = transform, + end_transform = transform + ) + + +class NotTheOnlyWayToOrganize(Scene): + def construct(self): + self.animate(ShowCreation(NumberLine().add_numbers())) + self.dither() + words = "Is there any other reasonable way to organize numbers?" + self.animate(FadeIn(text_mobject(words).shift(2*UP))) + self.dither() +class RoomsAndSubrooms(Scene): + def construct(self): + colors = get_room_colors() + a_set = [3*RIGHT, 3*LEFT] + b_set = [1.5*UP, 1.5*DOWN] + c_set = [LEFT, RIGHT] + rectangle_groups = [ + [Rectangle(7, 12).highlight(colors[0])], + [ + Rectangle(6, 5).shift(a).highlight(colors[1]) + for a in a_set + ], + [ + Rectangle(2, 4).shift(a + b).highlight(colors[2]) + for a in a_set + for b in b_set + ], + [ + Rectangle(1, 1).shift(a+b+c).highlight(colors[3]) + for a in a_set + for b in b_set + for c in c_set + ] + ] + + for group in rectangle_groups: + mob = CompoundMobject(*group) + mob.sort_points(np.linalg.norm) + self.animate(ShowCreation(mob)) + + self.dither() + + +class RoomsAndSubroomsWithNumbers(Scene): + def construct(self): + zero_local = (SPACE_WIDTH-0.5)*LEFT + zero_one_width = SPACE_WIDTH-0.3 + + zero, power_mobs = self.draw_numbers(zero_local, zero_one_width) + self.dither() + rectangles = self.draw_first_rectangles(zero_one_width) + rect_clusters = self.draw_remaining_rectangles(rectangles) + self.adjust_power_mobs(zero, power_mobs, rect_clusters[-1]) + self.dither() + num_mobs = self.draw_remaining_numbers( + zero, power_mobs, rect_clusters + ) + self.dither() + self.add_negative_one(num_mobs) + self.dither() + self.show_distances(num_mobs, rect_clusters) + + + def draw_numbers(self, zero_local, zero_one_width): + num_numbers = 5 + zero = tex_mobject("0").shift(zero_local) + self.add(zero) + nums = [] + for n in range(num_numbers): + num = tex_mobject(str(2**n)) + num.scale(1.0/(n+1)) + num.shift( + zero_local+\ + RIGHT*zero_one_width/(2.0**n)+\ + LEFT*0.05*n+\ + (0.4*RIGHT if n == 0 else ORIGIN) #Stupid + ) + self.animate(FadeIn(num, run_time = 0.5)) + nums.append(num) + return zero, nums + + def draw_first_rectangles(self, zero_one_width): + side_buff = 0.05 + upper_buff = 0.5 + colors = get_room_colors() + rectangles = [] + for n in range(4): + rect = Rectangle( + 2*SPACE_HEIGHT-(n+2)*upper_buff, + zero_one_width/(2**n)-0.85*(n+1)*side_buff + ) + rect.sort_points(np.linalg.norm) + rect.to_edge(LEFT, buff = 0.2).shift(n*side_buff*RIGHT) + rect.highlight(colors[n]) + rectangles.append(rect) + for rect in rectangles: + self.animate(ShowCreation(rect)) + self.dither() + return rectangles + + def draw_remaining_rectangles(self, rectangles): + clusters = [] + centers = [ORIGIN] + map(Mobject.get_center, rectangles) + shift_vals = [ + 2*(c2 - c1)[0]*RIGHT + for c1, c2 in zip(centers[1:], centers) + ] + for rectangle, count in zip(rectangles, it.count(1)): + cluster = [rectangle] + for shift_val in shift_vals[:count]: + cluster += map( + lambda mob : mob.shift(shift_val), + deepcopy(cluster) + ) + clusters.append(cluster) + for rect in cluster[1:]: + self.animate(FadeIn(rect, run_time = 0.6**(count-1))) + return clusters + + def adjust_power_mobs(self, zero, power_mobs, small_rects): + new_zero = deepcopy(zero) + self.center_in_closest_rect(new_zero, small_rects) + new_power_mobs = deepcopy(power_mobs) + for mob, count in zip(new_power_mobs, it.count(1)): + self.center_in_closest_rect(mob, small_rects) + new_power_mobs[-1].shift(DOWN) + dots = tex_mobject("\\vdots") + dots.scale(0.5).shift(new_zero.get_center()+0.5*DOWN) + self.animate( + Transform(zero, new_zero), + FadeIn(dots), + *[ + Transform(old, new) + for old, new in zip(power_mobs, new_power_mobs) + ] + ) + + def draw_remaining_numbers(self, zero, power_mobs, rect_clusters): + small_rects = rect_clusters[-1] + max_width = 0.8*small_rects[0].get_width() + max_power = 4 + num_mobs = [None]*(2**max_power + 1) + num_mobs[0] = zero + powers = [2**k for k in range(max_power+1)] + for p, index in zip(powers, it.count()): + num_mobs[p] = power_mobs[index] + for power, count in zip(powers[1:-1], it.count(1)): + zero_copy = deepcopy(zero) + power_mob_copy = deepcopy(num_mobs[power]) + def transform(mob): + self.center_in_closest_rect(mob, small_rects) + mob.shift(UP) + return mob + self.animate(*[ + ApplyFunction(transform, mob) + for mob in zero_copy, power_mob_copy + ]) + last_left_mob = zero + for n in range(power+1, 2*power): + left_mob = num_mobs[n-power] + shift_val = left_mob.get_center()-last_left_mob.get_center() + self.animate(*[ + ApplyMethod(mob.shift, shift_val) + for mob in zero_copy, power_mob_copy + ]) + num_mobs[n] = tex_mobject(str(n)) + num_mobs[n].scale(1.0/(power_of_divisor(n, 2)+1)) + width_ratio = max_width / num_mobs[n].get_width() + if width_ratio < 1: + num_mobs[n].scale(width_ratio) + num_mobs[n].shift(power_mob_copy.get_center()+DOWN) + self.center_in_closest_rect(num_mobs[n], small_rects) + point = Point(power_mob_copy.get_center()) + self.animate(Transform(point, num_mobs[n])) + self.remove(point) + self.add(num_mobs[n]) + last_left_mob = left_mob + self.remove(zero_copy, power_mob_copy) + self.dither() + return num_mobs + + @staticmethod + def center_in_closest_rect(mobject, rectangles): + center = mobject.get_center() + diffs = [r.get_center()-center for r in rectangles] + mobject.shift(diffs[np.argmin(map(np.linalg.norm, diffs))]) + + def add_negative_one(self, num_mobs): + neg_one = tex_mobject("-1").scale(0.5) + shift_val = num_mobs[15].get_center()-neg_one.get_center() + neg_one.shift(UP) + self.animate(ApplyMethod(neg_one.shift, shift_val)) + + def show_distances(self, num_mobs, rect_clusters): + self.remove(*[r for c in rect_clusters for r in c]) + text = None + for cluster, count in zip(rect_clusters, it.count()): + if text is not None: + self.remove(text) + if count == 0: + dist_string = "1" + else: + dist_string = "$\\frac{1}{%d}$"%(2**count) + text = text_mobject( + "Any of these pairs are considered to be a distance " +\ + dist_string +\ + " away from each other" + ).shift(2*UP) + self.add(text) + self.clear_way_for_text(text, cluster) + self.add(*cluster) + pairs = filter( + lambda (a, b) : (a-b)%(2**count) == 0 and (a-b)%(2**(count+1)) != 0, + it.combinations(range(16), 2) + ) + for pair in sample(pairs, min(10, len(pairs))): + for index in pair: + num_mobs[index].highlight("green") + self.animate(*[ + ApplyMethod( + num_mobs[index].rotate_in_place, np.pi/10, + alpha_func = wiggle + ) + for index in pair + ]) + self.dither() + for index in pair: + num_mobs[index].highlight("white") + + @staticmethod + def clear_way_for_text(text, mobjects): + right, top, null = np.max(text.points, 0) + left, bottom, null = np.min(text.points, 0) + def filter_func((x, y, z)): + return x>left and xbottom and y