Пример #1
0
    def construct(self):
        words, s = TextMobject(["Pseudo-Hilbert Curve", "s"]).split()
        pre_words = TextMobject("Order 1")
        pre_words.next_to(words, LEFT, buff = 0.5)
        s.next_to(words, RIGHT, buff = 0.05, aligned_edge = DOWN)
        cluster = Mobject(pre_words, words, s)
        cluster.center()
        cluster.scale(0.7)
        cluster.to_edge(UP, buff = 0.3)
        cluster.highlight(GREEN)
        grid1 = Grid(1, 1)
        grid2 = Grid(2, 2)
        curve = HilbertCurve(order = 1)

        self.add(words, s)
        self.dither()
        self.play(Transform(
            s, pre_words, 
            path_func = path_along_arc(-np.pi/3)
        ))
        self.dither()
        self.play(ShowCreation(grid1))
        self.dither()
        self.play(ShowCreation(grid2))
        self.dither()
        kwargs = {
            "run_time" : 5,
            "rate_func" : None
        }
        self.play(ShowCreation(curve, **kwargs))
        self.dither()
Пример #2
0
    def construct(self):
        n_terms = 4
        def func((x, y, ignore)):
            z = complex(x, y)                                    
            if (np.abs(x%1 - 0.5)<0.01 and y < 0.01) or np.abs(z)<0.01:
                return ORIGIN
            out_z = 1./(2*np.tan(np.pi*z)*(z**2))
            return out_z.real*RIGHT - out_z.imag*UP
        arrows = Mobject(*[
            Arrow(ORIGIN, np.sqrt(2)*point)
            for point in compass_directions(4, RIGHT+UP)
        ])
        arrows.highlight(YELLOW)
        arrows.ingest_submobjects()
        all_arrows = Mobject(*[
            arrows.copy().scale(0.3/(x)).shift(x*RIGHT)
            for x in range(1, n_terms+2)
        ])
        terms = TexMobject([
            "\\dfrac{1}{%d^2} + "%(x+1)
            for x in range(n_terms)
        ]+["\\cdots"])
        terms.shift(2*UP)
        plane = NumberPlane(color = BLUE_E)
        axes = Mobject(NumberLine(), NumberLine().rotate(np.pi/2))
        axes.highlight(WHITE)

        for term in terms.split():
            self.play(ShimmerIn(term, run_time = 0.5))
        self.dither()
        self.play(ShowCreation(plane), ShowCreation(axes))
        self.play(*[
            Transform(*pair)
            for pair in zip(terms.split(), all_arrows.split())
        ])
        self.play(PhaseFlow(
            func, plane,
            run_time = 5,
            virtual_time = 8
        ))
Пример #3
0
class FluidFlow(Scene):
    DEFAULT_CONFIG = {
        "arrow_spacing" : 1,
        "dot_spacing" : 0.5,
        "dot_color" : BLUE_B,
        "text_color" : WHITE,
        "arrow_color" : GREEN_A,
        "points_height" : SPACE_HEIGHT,
        "points_width" : SPACE_WIDTH,
    }
    def use_function(self, function):
        self.function = function

    def get_points(self, spacing):
        x_radius, y_radius = [
            val-val%spacing
            for val in self.points_width, self.points_height
        ]
        return map(np.array, it.product(
            np.arange(-x_radius, x_radius+spacing, spacing),
            np.arange(-y_radius, y_radius+spacing, spacing),
            [0]
        ))


    def add_plane(self):
        self.add(NumberPlane().fade())

    def add_dots(self):
        points = self.get_points(self.dot_spacing)
        self.dots = Mobject(*map(Dot, points))
        self.dots.highlight(self.dot_color)
        self.play(ShowCreation(self.dots))
        self.dither()

    def add_arrows(self, true_length = False):
        if not hasattr(self, "function"):
            raise Exception("Must run use_function first")
        points = self.get_points(self.arrow_spacing)
        points = filter(
            lambda p : np.linalg.norm(self.function(p)) > 0.01,
            points
        )
        angles = map(angle_of_vector, map(self.function, points))
        prototype = Arrow(
            ORIGIN, RIGHT*self.arrow_spacing/2.,
            color = self.arrow_color, 
            tip_length = 0.1,
            buff = 0
        )
        arrows = []
        for point in points:
            arrow = prototype.copy()
            output = self.function(point)
            if true_length:
                arrow.scale(np.linalg.norm(output))
            arrow.rotate(angle_of_vector(output))
            arrow.shift(point)
            arrows.append(arrow)
        self.arrows = Mobject(*arrows)

        self.play(ShowCreation(self.arrows))
        self.dither()

    def add_paddle(self):
        pass

    def flow(self, **kwargs):
        if not hasattr(self, "function"):
            raise Exception("Must run use_function first")
        self.play(ApplyToCenters(
            PhaseFlow,
            self.dots.split(),
            function = self.function,
            **kwargs
        ))

    def label(self, text, time = 5):
        mob = TextMobject(text)
        mob.scale(1.5)
        mob.to_edge(UP)
        rectangle = region_from_polygon_vertices(*[
            mob.get_corner(vect) + 0.3*vect
            for vect in [
                UP+RIGHT,
                UP+LEFT,
                DOWN+LEFT,
                DOWN+RIGHT
            ]
        ])
        mob.highlight(self.text_color)
        rectangle = MobjectFromRegion(rectangle, "#111111")
        rectangle.point_thickness = 3
        self.add(rectangle, mob)
        self.dither(time)
        self.remove(mob, rectangle)
Пример #4
0
class FormalDefinitionOfContinuity(Scene):
    def construct(self):
        self.setup()
        self.label_spaces()
        self.move_dot()
        self.label_jump()
        self.draw_circles()
        self.vary_circle_sizes()
        self.discontinuous_point()


    def setup(self):
        self.input_color = YELLOW_C
        self.output_color = RED
        def spiril(t):
            theta = 2*np.pi*t
            return t*np.cos(theta)*RIGHT+t*np.sin(theta)*UP

        self.spiril1 = ParametricFunction(
            lambda t : 1.5*RIGHT + DOWN + 2*spiril(t),
            density = 5*DEFAULT_POINT_DENSITY_1D,
        )
        self.spiril2 = ParametricFunction(
            lambda t : 5.5*RIGHT + UP - 2*spiril(1-t),
            density = 5*DEFAULT_POINT_DENSITY_1D,
        )
        Mobject.align_data(self.spiril1, self.spiril2)
        self.output = Mobject(self.spiril1, self.spiril2)
        self.output.ingest_submobjects()
        self.output.highlight(GREEN_A)

        self.interval = UnitInterval()
        self.interval.scale_to_fit_width(SPACE_WIDTH-1)
        self.interval.to_edge(LEFT)

        self.input_dot = Dot(color = self.input_color)
        self.output_dot = self.input_dot.copy().highlight(self.output_color)
        left, right = self.interval.get_left(), self.interval.get_right()
        self.input_homotopy = lambda (x, y, z, t) : (x, y, t) + interpolate(left, right, t)
        output_size = self.output.get_num_points()-1
        output_points = self.output.points        
        self.output_homotopy = lambda (x, y, z, t) : (x, y, z) + output_points[int(t*output_size)]

    def get_circles_and_points(self, min_input, max_input):
        input_left, input_right = [
            self.interval.number_to_point(num)
            for num in min_input, max_input
        ]
        input_circle = Circle(
            radius = np.linalg.norm(input_left-input_right)/2,
            color = WHITE
        )
        input_circle.shift((input_left+input_right)/2)

        input_points = Line(
            input_left, input_right, 
            color = self.input_color
        )
        output_points = Mobject(color = self.output_color)
        n = self.output.get_num_points()
        output_points.add_points(
            self.output.points[int(min_input*n):int(max_input*n)]
        )
        output_center = output_points.points[int(0.5*output_points.get_num_points())]
        max_distance = np.linalg.norm(output_center-output_points.points[-1])
        output_circle = Circle(
            radius = max_distance, 
            color = WHITE
        )
        output_circle.shift(output_center)
        return (
            input_circle, 
            input_points, 
            output_circle, 
            output_points
        )


    def label_spaces(self):
        input_space = TextMobject("Input Space")
        input_space.to_edge(UP)        
        input_space.shift(LEFT*SPACE_WIDTH/2)
        output_space = TextMobject("Output Space")
        output_space.to_edge(UP)
        output_space.shift(RIGHT*SPACE_WIDTH/2)
        line = Line(
            UP*SPACE_HEIGHT, DOWN*SPACE_HEIGHT, 
            color = WHITE
        )
        self.play(
            ShimmerIn(input_space),
            ShimmerIn(output_space),
            ShowCreation(line),
            ShowCreation(self.interval),
        )
        self.wait()

    def move_dot(self):
        kwargs = {
            "rate_func" : None,
            "run_time"  : 3
        }
        self.play(
            Homotopy(self.input_homotopy, self.input_dot, **kwargs),
            Homotopy(self.output_homotopy, self.output_dot, **kwargs),
            ShowCreation(self.output, **kwargs)
        )
        self.wait()

    def label_jump(self):
        jump_points = Mobject(
            Point(self.spiril1.points[-1]),
            Point(self.spiril2.points[0])
        )
        self.brace = Brace(jump_points, RIGHT)
        self.jump = TextMobject("Jump")
        self.jump.next_to(self.brace, RIGHT)
        self.play(
            GrowFromCenter(self.brace),
            ShimmerIn(self.jump)
        )
        self.wait()
        self.remove(self.brace, self.jump)


    def draw_circles(self):
        input_value = 0.45
        input_radius = 0.04
        for dot in self.input_dot, self.output_dot:
            dot.center()
        kwargs = {
            "rate_func" : lambda t : interpolate(1, input_value, smooth(t))
        }
        self.play(
            Homotopy(self.input_homotopy, self.input_dot, **kwargs),
            Homotopy(self.output_homotopy, self.output_dot, **kwargs)
        )

        A, B = map(Mobject.get_center, [self.input_dot, self.output_dot])
        A_text = TextMobject("A")
        A_text.shift(A+2*(LEFT+UP))
        A_arrow = Arrow(
            A_text, self.input_dot,
            color = self.input_color
        )
        B_text = TextMobject("B")
        B_text.shift(B+2*RIGHT+DOWN)
        B_arrow = Arrow(
            B_text, self.output_dot,
            color = self.output_color
        )
        tup = self.get_circles_and_points(
            input_value-input_radius, 
            input_value+input_radius
        )
        input_circle, input_points, output_circle, output_points = tup

        for text, arrow in [(A_text, A_arrow), (B_text, B_arrow)]:
            self.play(
                ShimmerIn(text),
                ShowCreation(arrow)
            )
            self.wait()
        self.remove(A_text, A_arrow, B_text, B_arrow)
        self.play(ShowCreation(input_circle))
        self.wait()
        self.play(ShowCreation(input_points))
        self.wait()
        input_points_copy = input_points.copy()
        self.play(
            Transform(input_points_copy, output_points),
            run_time = 2
        )
        self.wait()
        self.play(ShowCreation(output_circle))
        self.wait()
        self.wait()
        self.remove(*[
            input_circle, input_points, 
            output_circle, input_points_copy
        ])


    def vary_circle_sizes(self):
        input_value = 0.45
        radius = 0.04
        vary_circles = VaryCircles(
            self, input_value, radius, 
            run_time = 5,
        )
        self.play(vary_circles)
        self.wait()
        text = TextMobject("Function is ``Continuous at A''")
        text.shift(2*UP).to_edge(LEFT)
        arrow = Arrow(text, self.input_dot)
        self.play(
            ShimmerIn(text),
            ShowCreation(arrow)
        )
        self.wait()
        self.remove(vary_circles.mobject, text, arrow)

    def discontinuous_point(self):
        point_description = TextMobject(
            "Point where the function jumps"
        )
        point_description.shift(3*RIGHT)        
        discontinuous_at_A = TextMobject(
            "``Discontinuous at A''",
            size = "\\Large"
        )
        discontinuous_at_A.shift(2*UP).to_edge(LEFT)
        text = TextMobject("""
            Circle around ouput \\\\ 
            points can never \\\\
            be smaller than \\\\
            the jump
        """)
        text.scale(0.75)
        text.shift(3.5*RIGHT)

        input_value = 0.5
        input_radius = 0.04
        vary_circles = VaryCircles(
            self, input_value, input_radius, 
            run_time = 5,
        )
        for dot in self.input_dot, self.output_dot:
            dot.center()
        kwargs = {
            "rate_func" : lambda t : interpolate(0.45, input_value, smooth(t))
        }
        self.play(
            Homotopy(self.input_homotopy, self.input_dot, **kwargs),
            Homotopy(self.output_homotopy, self.output_dot, **kwargs)
        )
        discontinuous_arrow = Arrow(discontinuous_at_A, self.input_dot)
        arrow = Arrow(
            point_description, self.output_dot,
            buff = 0.05,
            color = self.output_color
        )
        self.play(
            ShimmerIn(point_description),
            ShowCreation(arrow)
        )
        self.wait()
        self.remove(point_description, arrow)

        tup = self.get_circles_and_points(
            input_value-input_radius, 
            input_value+input_radius
        )
        input_circle, input_points, output_circle, output_points = tup
        input_points_copy = input_points.copy()
        self.play(ShowCreation(input_circle))
        self.play(ShowCreation(input_points))
        self.play(
            Transform(input_points_copy, output_points),
            run_time = 2
        )
        self.play(ShowCreation(output_circle))
        self.wait()
        self.play(ShimmerIn(text))
        self.remove(input_circle, input_points, output_circle, input_points_copy)
        self.play(vary_circles)
        self.wait()
        self.play(
            ShimmerIn(discontinuous_at_A),
            ShowCreation(discontinuous_arrow)
        )
        self.wait(3)
        self.remove(vary_circles.mobject, discontinuous_at_A, discontinuous_arrow)

    def continuous_point(self):
        pass
Пример #5
0
class PiCreature(Mobject):
    DEFAULT_CONFIG = {
        "color" : BLUE_E
    }
    PART_NAMES = [
        'arm', 
        'body', 
        'left_eye', 
        'right_eye',
        'left_leg',
        'right_leg',            
        'mouth', 
    ]
    WHITE_PART_NAMES = ['left_eye', 'right_eye', 'mouth']
    MOUTH_NAMES = ["smile", "frown", "straight_mouth"]

    def __init__(self, **kwargs):
        Mobject.__init__(self, **kwargs)
        for part_name in self.PART_NAMES:
            mob = ImageMobject(
                part_name_to_directory(part_name),
                should_center = False
            )
            if part_name not in self.WHITE_PART_NAMES:
                mob.highlight(self.color)
            setattr(self, part_name, mob)
        self.eyes = Mobject(self.left_eye, self.right_eye)
        self.legs = Mobject(self.left_leg, self.right_leg)
        mouth_center = self.get_mouth_center()
        self.mouth.center()
        self.smile = self.mouth
        self.frown = self.mouth.copy().rotate(np.pi, RIGHT)
        self.straight_mouth = TexMobject("-").scale(0.7)
        for mouth in self.smile, self.frown, self.straight_mouth:
            mouth.sort_points(lambda p : p[0])
            mouth.highlight(self.color) ##to blend into background
            mouth.shift(mouth_center)
        self.digest_mobject_attrs()
        self.give_smile()
        self.add(self.mouth)
        self.scale(PI_CREATURE_SCALE_VAL)


    def get_parts(self):
        return [getattr(self, pn) for pn in self.PART_NAMES]

    def get_white_parts(self):
        return [
            getattr(self, pn) 
            for pn in self.WHITE_PART_NAMES+self.MOUTH_NAMES
        ]

    def get_mouth_center(self):
        result = self.body.get_center()
        result[0] = self.eyes.get_center()[0]
        return result
        # left_center  = self.left_eye.get_center()
        # right_center = self.right_eye.get_center()
        # l_to_r = right_center-left_center
        # eyes_to_mouth = rotate_vector(l_to_r, -np.pi/2, OUT)
        # eyes_to_mouth /= np.linalg.norm(eyes_to_mouth)
        # return left_center/2 + right_center/2 + \
        #        PI_CREATURE_MOUTH_TO_EYES_DISTANCE*eyes_to_mouth

    def highlight(self, color, condition = None):
        for part in set(self.get_parts()).difference(self.get_white_parts()):
            part.highlight(color, condition)
        return self

    def move_to(self, destination):
        self.shift(destination-self.get_bottom())
        return self

    def change_mouth_to(self, mouth_name):
        #TODO, This is poorly implemented
        self.mouth = getattr(self, mouth_name) 
        self.sub_mobjects = list_update(
            self.sub_mobjects, 
            self.get_parts()
        )
        self.mouth.highlight(WHITE)
        return self

    def give_smile(self):
        return self.change_mouth_to("smile")

    def give_frown(self):
        return self.change_mouth_to("frown")

    def give_straight_face(self):
        return self.change_mouth_to("straight_mouth")

    def get_eye_center(self):
        return self.eyes.get_center()

    def make_mean(self):
        eye_x, eye_y = self.get_eye_center()[:2]
        def should_delete((x, y, z)):
            return y - eye_y > 0.3*abs(x - eye_x)
        self.eyes.highlight("black", should_delete)
        self.give_straight_face()
        return self

    def make_sad(self):
        eye_x, eye_y = self.get_eye_center()[:2]
        eye_y += 0.15
        def should_delete((x, y, z)):
            return y - eye_y > -0.3*abs(x - eye_x)
        self.eyey.highlight("black", should_delete)
        self.give_frown()
        return self

    def get_step_intermediate(self, pi_creature):
        vect = pi_creature.get_center() - self.get_center()
        result = self.copy().shift(vect / 2.0)
        left_forward = vect[0] > 0
        if self.right_leg.get_center()[0] < self.left_leg.get_center()[0]:
            #For Mortimer's case
            left_forward = not left_forward
        if left_forward:
            result.left_leg.wag(vect/2.0, DOWN)
            result.right_leg.wag(-vect/2.0, DOWN)
        else:
            result.right_leg.wag(vect/2.0, DOWN)
            result.left_leg.wag(-vect/2.0, DOWN)
        return result

    def blink(self):
        bottom = self.eyes.get_bottom()
        self.eyes.apply_function(
            lambda (x, y, z) : (x, bottom[1], z)
        )
        return self

    def shift_eyes(self):
        for eye in self.left_eye, self.right_eye:
            eye.rotate_in_place(np.pi, UP)
        return self

    def to_symbol(self):
        Mobject.__init__(
            self,
            *list(set(self.get_parts()).difference(self.get_white_parts()))
        )
Пример #6
0
class PiCreature(Mobject):
    DEFAULT_CONFIG = {"color": BLUE_E}
    PART_NAMES = [
        'arm',
        'body',
        'left_eye',
        'right_eye',
        'left_leg',
        'right_leg',
        'mouth',
    ]
    WHITE_PART_NAMES = ['left_eye', 'right_eye', 'mouth']
    MOUTH_NAMES = ["smile", "frown", "straight_mouth"]

    def __init__(self, **kwargs):
        Mobject.__init__(self, **kwargs)
        for part_name in self.PART_NAMES:
            mob = ImageMobject(part_name_to_directory(part_name),
                               should_center=False)
            if part_name not in self.WHITE_PART_NAMES:
                mob.highlight(self.color)
            setattr(self, part_name, mob)
        self.eyes = Mobject(self.left_eye, self.right_eye)
        self.legs = Mobject(self.left_leg, self.right_leg)
        mouth_center = self.get_mouth_center()
        self.mouth.center()
        self.smile = self.mouth
        self.frown = self.mouth.copy().rotate(np.pi, RIGHT)
        self.straight_mouth = TexMobject("-").scale(0.7)
        for mouth in self.smile, self.frown, self.straight_mouth:
            mouth.sort_points(lambda p: p[0])
            mouth.highlight(self.color)  ##to blend into background
            mouth.shift(mouth_center)
        self.digest_mobject_attrs()
        self.give_smile()
        self.add(self.mouth)
        self.scale(PI_CREATURE_SCALE_VAL)

    def get_parts(self):
        return [getattr(self, pn) for pn in self.PART_NAMES]

    def get_white_parts(self):
        return [
            getattr(self, pn)
            for pn in self.WHITE_PART_NAMES + self.MOUTH_NAMES
        ]

    def get_mouth_center(self):
        result = self.body.get_center()
        result[0] = self.eyes.get_center()[0]
        return result
        # left_center  = self.left_eye.get_center()
        # right_center = self.right_eye.get_center()
        # l_to_r = right_center-left_center
        # eyes_to_mouth = rotate_vector(l_to_r, -np.pi/2, OUT)
        # eyes_to_mouth /= np.linalg.norm(eyes_to_mouth)
        # return left_center/2 + right_center/2 + \
        #        PI_CREATURE_MOUTH_TO_EYES_DISTANCE*eyes_to_mouth

    def highlight(self, color, condition=None):
        for part in set(self.get_parts()).difference(self.get_white_parts()):
            part.highlight(color, condition)
        return self

    def move_to(self, destination):
        self.shift(destination - self.get_bottom())
        return self

    def change_mouth_to(self, mouth_name):
        #TODO, This is poorly implemented
        self.mouth = getattr(self, mouth_name)
        self.sub_mobjects = list_update(self.sub_mobjects, self.get_parts())
        self.mouth.highlight(WHITE)
        return self

    def give_smile(self):
        return self.change_mouth_to("smile")

    def give_frown(self):
        return self.change_mouth_to("frown")

    def give_straight_face(self):
        return self.change_mouth_to("straight_mouth")

    def get_eye_center(self):
        return self.eyes.get_center()

    def make_mean(self):
        eye_x, eye_y = self.get_eye_center()[:2]

        def should_delete((x, y, z)):
            return y - eye_y > 0.3 * abs(x - eye_x)

        self.eyes.highlight("black", should_delete)
        self.give_straight_face()
        return self

    def make_sad(self):
        eye_x, eye_y = self.get_eye_center()[:2]
        eye_y += 0.15

        def should_delete((x, y, z)):
            return y - eye_y > -0.3 * abs(x - eye_x)

        self.eyey.highlight("black", should_delete)
        self.give_frown()
        return self

    def get_step_intermediate(self, pi_creature):
        vect = pi_creature.get_center() - self.get_center()
        result = self.copy().shift(vect / 2.0)
        left_forward = vect[0] > 0
        if self.right_leg.get_center()[0] < self.left_leg.get_center()[0]:
            #For Mortimer's case
            left_forward = not left_forward
        if left_forward:
            result.left_leg.wag(vect / 2.0, DOWN)
            result.right_leg.wag(-vect / 2.0, DOWN)
        else:
            result.right_leg.wag(vect / 2.0, DOWN)
            result.left_leg.wag(-vect / 2.0, DOWN)
        return result

    def blink(self):
        bottom = self.eyes.get_bottom()
        self.eyes.apply_function(lambda (x, y, z): (x, bottom[1], z))
        return self

    def shift_eyes(self):
        for eye in self.left_eye, self.right_eye:
            eye.rotate_in_place(np.pi, UP)
        return self

    def to_symbol(self):
        Mobject.__init__(
            self,
            *list(set(self.get_parts()).difference(self.get_white_parts())))
Пример #7
0
class FormalDefinitionOfContinuity(Scene):
    def construct(self):
        self.setup()
        self.label_spaces()
        self.move_dot()
        self.label_jump()
        self.draw_circles()
        self.vary_circle_sizes()
        self.discontinuous_point()


    def setup(self):
        self.input_color = YELLOW_C
        self.output_color = RED
        def spiril(t):
            theta = 2*np.pi*t
            return t*np.cos(theta)*RIGHT+t*np.sin(theta)*UP

        self.spiril1 = ParametricFunction(
            lambda t : 1.5*RIGHT + DOWN + 2*spiril(t),
            density = 5*DEFAULT_POINT_DENSITY_1D,
        )
        self.spiril2 = ParametricFunction(
            lambda t : 5.5*RIGHT + UP - 2*spiril(1-t),
            density = 5*DEFAULT_POINT_DENSITY_1D,
        )
        Mobject.align_data(self.spiril1, self.spiril2)
        self.output = Mobject(self.spiril1, self.spiril2)
        self.output.ingest_sub_mobjects()
        self.output.highlight(GREEN_A)

        self.interval = UnitInterval()
        self.interval.scale_to_fit_width(SPACE_WIDTH-1)
        self.interval.to_edge(LEFT)

        self.input_dot = Dot(color = self.input_color)
        self.output_dot = self.input_dot.copy().highlight(self.output_color)
        left, right = self.interval.get_left(), self.interval.get_right()
        self.input_homotopy = lambda (x, y, z, t) : (x, y, t) + interpolate(left, right, t)
        output_size = self.output.get_num_points()-1
        output_points = self.output.points        
        self.output_homotopy = lambda (x, y, z, t) : (x, y, z) + output_points[int(t*output_size)]

    def get_circles_and_points(self, min_input, max_input):
        input_left, input_right = [
            self.interval.number_to_point(num)
            for num in min_input, max_input
        ]
        input_circle = Circle(
            radius = np.linalg.norm(input_left-input_right)/2,
            color = WHITE
        )
        input_circle.shift((input_left+input_right)/2)

        input_points = Line(
            input_left, input_right, 
            color = self.input_color
        )
        output_points = Mobject(color = self.output_color)
        n = self.output.get_num_points()
        output_points.add_points(
            self.output.points[int(min_input*n):int(max_input*n)]
        )
        output_center = output_points.points[int(0.5*output_points.get_num_points())]
        max_distance = np.linalg.norm(output_center-output_points.points[-1])
        output_circle = Circle(
            radius = max_distance, 
            color = WHITE
        )
        output_circle.shift(output_center)
        return (
            input_circle, 
            input_points, 
            output_circle, 
            output_points
        )


    def label_spaces(self):
        input_space = TextMobject("Input Space")
        input_space.to_edge(UP)        
        input_space.shift(LEFT*SPACE_WIDTH/2)
        output_space = TextMobject("Output Space")
        output_space.to_edge(UP)
        output_space.shift(RIGHT*SPACE_WIDTH/2)
        line = Line(
            UP*SPACE_HEIGHT, DOWN*SPACE_HEIGHT, 
            color = WHITE
        )
        self.play(
            ShimmerIn(input_space),
            ShimmerIn(output_space),
            ShowCreation(line),
            ShowCreation(self.interval),
        )
        self.dither()

    def move_dot(self):
        kwargs = {
            "rate_func" : None,
            "run_time"  : 3
        }
        self.play(
            Homotopy(self.input_homotopy, self.input_dot, **kwargs),
            Homotopy(self.output_homotopy, self.output_dot, **kwargs),
            ShowCreation(self.output, **kwargs)
        )
        self.dither()

    def label_jump(self):
        jump_points = Mobject(
            Point(self.spiril1.points[-1]),
            Point(self.spiril2.points[0])
        )
        self.brace = Brace(jump_points, RIGHT)
        self.jump = TextMobject("Jump")
        self.jump.next_to(self.brace, RIGHT)
        self.play(
            GrowFromCenter(self.brace),
            ShimmerIn(self.jump)
        )
        self.dither()
        self.remove(self.brace, self.jump)


    def draw_circles(self):
        input_value = 0.45
        input_radius = 0.04
        for dot in self.input_dot, self.output_dot:
            dot.center()
        kwargs = {
            "rate_func" : lambda t : interpolate(1, input_value, smooth(t))
        }
        self.play(
            Homotopy(self.input_homotopy, self.input_dot, **kwargs),
            Homotopy(self.output_homotopy, self.output_dot, **kwargs)
        )

        A, B = map(Mobject.get_center, [self.input_dot, self.output_dot])
        A_text = TextMobject("A")
        A_text.shift(A+2*(LEFT+UP))
        A_arrow = Arrow(
            A_text, self.input_dot,
            color = self.input_color
        )
        B_text = TextMobject("B")
        B_text.shift(B+2*RIGHT+DOWN)
        B_arrow = Arrow(
            B_text, self.output_dot,
            color = self.output_color
        )
        tup = self.get_circles_and_points(
            input_value-input_radius, 
            input_value+input_radius
        )
        input_circle, input_points, output_circle, output_points = tup

        for text, arrow in [(A_text, A_arrow), (B_text, B_arrow)]:
            self.play(
                ShimmerIn(text),
                ShowCreation(arrow)
            )
            self.dither()
        self.remove(A_text, A_arrow, B_text, B_arrow)
        self.play(ShowCreation(input_circle))
        self.dither()
        self.play(ShowCreation(input_points))
        self.dither()
        input_points_copy = input_points.copy()
        self.play(
            Transform(input_points_copy, output_points),
            run_time = 2
        )
        self.dither()
        self.play(ShowCreation(output_circle))
        self.dither()
        self.dither()
        self.remove(*[
            input_circle, input_points, 
            output_circle, input_points_copy
        ])


    def vary_circle_sizes(self):
        input_value = 0.45
        radius = 0.04
        vary_circles = VaryCircles(
            self, input_value, radius, 
            run_time = 5,
        )
        self.play(vary_circles)
        self.dither()
        text = TextMobject("Function is ``Continuous at A''")
        text.shift(2*UP).to_edge(LEFT)
        arrow = Arrow(text, self.input_dot)
        self.play(
            ShimmerIn(text),
            ShowCreation(arrow)
        )
        self.dither()
        self.remove(vary_circles.mobject, text, arrow)

    def discontinuous_point(self):
        point_description = TextMobject(
            "Point where the function jumps"
        )
        point_description.shift(3*RIGHT)        
        discontinuous_at_A = TextMobject(
            "``Discontinuous at A''",
            size = "\\Large"
        )
        discontinuous_at_A.shift(2*UP).to_edge(LEFT)
        text = TextMobject("""
            Circle around ouput \\\\ 
            points can never \\\\
            be smaller than \\\\
            the jump
        """)
        text.scale(0.75)
        text.shift(3.5*RIGHT)

        input_value = 0.5
        input_radius = 0.04
        vary_circles = VaryCircles(
            self, input_value, input_radius, 
            run_time = 5,
        )
        for dot in self.input_dot, self.output_dot:
            dot.center()
        kwargs = {
            "rate_func" : lambda t : interpolate(0.45, input_value, smooth(t))
        }
        self.play(
            Homotopy(self.input_homotopy, self.input_dot, **kwargs),
            Homotopy(self.output_homotopy, self.output_dot, **kwargs)
        )
        discontinuous_arrow = Arrow(discontinuous_at_A, self.input_dot)
        arrow = Arrow(
            point_description, self.output_dot,
            buff = 0.05,
            color = self.output_color
        )
        self.play(
            ShimmerIn(point_description),
            ShowCreation(arrow)
        )
        self.dither()
        self.remove(point_description, arrow)

        tup = self.get_circles_and_points(
            input_value-input_radius, 
            input_value+input_radius
        )
        input_circle, input_points, output_circle, output_points = tup
        input_points_copy = input_points.copy()
        self.play(ShowCreation(input_circle))
        self.play(ShowCreation(input_points))
        self.play(
            Transform(input_points_copy, output_points),
            run_time = 2
        )
        self.play(ShowCreation(output_circle))
        self.dither()
        self.play(ShimmerIn(text))
        self.remove(input_circle, input_points, output_circle, input_points_copy)
        self.play(vary_circles)
        self.dither()
        self.play(
            ShimmerIn(discontinuous_at_A),
            ShowCreation(discontinuous_arrow)
        )
        self.dither(3)
        self.remove(vary_circles.mobject, discontinuous_at_A, discontinuous_arrow)

    def continuous_point(self):
        pass