from big_ol_pile_of_manim_imports import * def apply_function_to_center(point_func, mobject): mobject.apply_function_to_position(point_func) def apply_function_to_submobjects(point_func, mobject): mobject.apply_function_to_submobject_positions(point_func) def apply_function_to_points(point_func, mobject): mobject.apply_function(point_func) def get_nested_one_plus_one_over_x(n_terms, bottom_term="x"): tex = "1+ {1 \\over" * n_terms + bottom_term + "}" * n_terms return TexMobject(tex, substrings_to_isolate=["1", "\\over", bottom_term]) def get_phi_continued_fraction(n_terms): return get_nested_one_plus_one_over_x(n_terms, bottom_term="1+\\cdots") def get_nested_f(n_terms, arg="x"): terms = ["f("] * n_terms + [arg] + [")"] * n_terms return TexMobject(*terms) # Scene types class NumberlineTransformationScene(ZoomedScene): CONFIG = { "input_line_zero_point": 0.5 * UP + (FRAME_X_RADIUS - 1) * LEFT, "output_line_zero_point": 2 * DOWN + (FRAME_X_RADIUS - 1) * LEFT, "number_line_config": { "include_numbers": True, "x_min": -3.5, "x_max": 3.5, "unit_size": 2, }, # These would override number_line_config "input_line_config": { "color": BLUE, }, "output_line_config": {}, "num_inserted_number_line_anchors": 20, "default_delta_x": 0.1, "default_sample_dot_radius": 0.07, "default_sample_dot_colors": [RED, YELLOW], "default_mapping_animation_config": { "run_time": 3, # "path_arc": 30 * DEGREES, }, "local_coordinate_num_decimal_places": 2, "zoom_factor": 0.1, "zoomed_display_height": 2.5, "zoomed_display_corner_buff": MED_SMALL_BUFF, "mini_line_scale_factor": 2, "default_coordinate_value_dx": 0.05, "zoomed_camera_background_rectangle_fill_opacity": 1.0, } def setup(self): ZoomedScene.setup(self) self.setup_number_lines() self.setup_titles() self.setup_zoomed_camera_background_rectangle() def setup_number_lines(self): number_lines = self.number_lines = VGroup() added_configs = (self.input_line_config, self.output_line_config) zero_opints = (self.input_line_zero_point, self.output_line_zero_point) for added_config, zero_point in zip(added_configs, zero_opints): full_config = dict(self.number_line_config) full_config.update(added_config) number_line = NumberLine(**full_config) number_line.main_line.insert_n_anchor_points( self.num_inserted_number_line_anchors ) number_line.shift(zero_point - number_line.number_to_point(0)) number_lines.add(number_line) self.input_line, self.output_line = number_lines self.add(number_lines) def setup_titles(self): input_title, output_title = self.titles = VGroup(*[ TextMobject(word) for word in "Inputs", "Outputs" ]) vects = [UP, DOWN] for title, line, vect in zip(self.titles, self.number_lines, vects): title.next_to(line, vect, aligned_edge=LEFT) title.shift_onto_screen() self.add(self.titles) def setup_zoomed_camera_background_rectangle(self): frame = self.zoomed_camera.frame frame.next_to(self.camera.frame, UL) self.zoomed_camera_background_rectangle = BackgroundRectangle( frame, fill_opacity=self.zoomed_camera_background_rectangle_fill_opacity ) self.zoomed_camera_background_rectangle_anim = UpdateFromFunc( self.zoomed_camera_background_rectangle, lambda m: m.replace(frame, stretch=True) ) self.zoomed_camera_background_rectangle_group = VGroup( self.zoomed_camera_background_rectangle, ) def get_sample_input_points(self, x_min=None, x_max=None, delta_x=None): x_min = x_min or self.input_line.x_min x_max = x_max or self.input_line.x_max delta_x = delta_x or self.default_delta_x return [ self.get_input_point(x) for x in np.arange(x_min, x_max + delta_x, delta_x) ] def get_sample_dots(self, x_min=None, x_max=None, delta_x=None, dot_radius=None, colors=None): dot_radius = dot_radius or self.default_sample_dot_radius colors = colors or self.default_sample_dot_colors dots = VGroup(*[ Dot(point, radius=dot_radius) for point in self.get_sample_input_points(x_min, x_max, delta_x) ]) dots.set_color_by_gradient(*colors) return dots def get_local_sample_dots(self, x, sample_radius=None, **kwargs): zoom_factor = self.get_zoom_factor() delta_x = kwargs.get("delta_x", self.default_delta_x * zoom_factor) dot_radius = kwargs.get("dot_radius", self.default_sample_dot_radius * zoom_factor) if sample_radius is None: unrounded_radius = self.zoomed_camera.frame.get_width() / 2 sample_radius = int(unrounded_radius / delta_x) * delta_x config = { "x_min": x - sample_radius, "x_max": x + sample_radius, "delta_x": delta_x, "dot_radius": dot_radius, } config.update(kwargs) return self.get_sample_dots(**config) def add_sample_dot_ghosts(self, sample_dots, fade_factor=0.5): self.sample_dot_ghosts = sample_dots.copy() self.sample_dot_ghosts.fade(fade_factor) self.add(self.sample_dot_ghosts, sample_dots) def get_local_coordinate_values(self, x, dx=None, n_neighbors=1): dx = dx or self.default_coordinate_value_dx return [ x + n * dx for n in range(-n_neighbors, n_neighbors + 1) ] # Mapping animations def get_mapping_animation(self, func, mobject, how_to_apply_func=apply_function_to_center, **kwargs): anim_config = dict(self.default_mapping_animation_config) anim_config.update(kwargs) point_func = self.number_func_to_point_func(func) mobject.generate_target(use_deepcopy=True) how_to_apply_func(point_func, mobject.target) return MoveToTarget(mobject, **anim_config) def get_line_mapping_animation(self, func, **kwargs): input_line_copy = self.input_line.deepcopy() self.moving_input_line = input_line_copy input_line_copy.remove(input_line_copy.numbers) # input_line_copy.set_stroke(width=2) input_line_copy.main_line.insert_n_anchor_points( self.num_inserted_number_line_anchors ) return AnimationGroup( self.get_mapping_animation( func, input_line_copy.main_line, apply_function_to_points ), self.get_mapping_animation( func, input_line_copy.tick_marks, apply_function_to_submobjects ), ) def get_sample_dots_mapping_animation(self, func, dots, **kwargs): return self.get_mapping_animation( func, dots, how_to_apply_func=apply_function_to_submobjects ) def get_zoomed_camera_frame_mapping_animation(self, func, x=None, **kwargs): frame = self.zoomed_camera.frame if x is None: point = frame.get_center() else: point = self.get_input_point(x) point_mob = VectorizedPoint(point) return AnimationGroup( self.get_mapping_animation(func, point_mob), UpdateFromFunc(frame, lambda m: m.move_to(point_mob)), ) def apply_function(self, func, apply_function_to_number_line=True, sample_dots=None, local_sample_dots=None, target_coordinate_values=None, added_anims=None, **kwargs ): zcbr_group = self.zoomed_camera_background_rectangle_group zcbr_anim = self.zoomed_camera_background_rectangle_anim frame = self.zoomed_camera.frame anims = [] if apply_function_to_number_line: anims.append(self.get_line_mapping_animation(func)) if hasattr(self, "mini_line"): # Test for if mini_line is in self? anims.append(self.get_mapping_animation( func, self.mini_line, how_to_apply_func=apply_function_to_center )) if sample_dots: anims.append( self.get_sample_dots_mapping_animation(func, sample_dots) ) if self.zoom_activated: zoom_anim = self.get_zoomed_camera_frame_mapping_animation(func) anims.append(zoom_anim) anims.append(zcbr_anim) zoom_anim.update(1) target_mini_line = Line(frame.get_left(), frame.get_right()) target_mini_line.scale(self.mini_line_scale_factor) target_mini_line.match_style(self.output_line.main_line) zoom_anim.update(0) zcbr_group.submobjects.insert(1, target_mini_line) if target_coordinate_values: coordinates = self.get_local_coordinates( self.output_line, *target_coordinate_values ) anims.append(FadeIn(coordinates)) zcbr_group.add(coordinates) self.local_target_coordinates = coordinates if local_sample_dots: anims.append( self.get_sample_dots_mapping_animation(func, local_sample_dots) ) zcbr_group.add(local_sample_dots) if added_anims: anims += added_anims anims.append(Animation(zcbr_group)) self.play(*anims, **kwargs) # Zooming def zoom_in_on_input(self, x, local_sample_dots=None, local_coordinate_values=None, pop_out=True, first_added_anims=None, first_anim_kwargs=None, second_added_anims=None, second_anim_kwargs=None, zoom_factor=None, ): first_added_anims = first_added_anims or [] first_anim_kwargs = first_anim_kwargs or {} second_added_anims = second_added_anims or [] second_anim_kwargs = second_anim_kwargs or {} input_point = self.get_input_point(x) # Decide how to move camera frame into place frame = self.zoomed_camera.frame frame.generate_target() frame.target.move_to(input_point) if zoom_factor: frame.target.scale_to_fit_height( self.zoomed_display.get_height() * zoom_factor ) movement = MoveToTarget(frame) zcbr = self.zoomed_camera_background_rectangle zcbr_group = self.zoomed_camera_background_rectangle_group zcbr_anim = self.zoomed_camera_background_rectangle_anim anims = [] if self.zoom_activated: anims.append(movement) anims.append(zcbr_anim) else: movement.update(1) zcbr_anim.update(1) anims.append(self.get_zoom_in_animation()) anims.append(FadeIn(zcbr)) # Make sure frame is in final place for anim in anims: anim.update(1) # Add miniature number_line mini_line = self.mini_line = Line(frame.get_left(), frame.get_right()) mini_line.scale(self.mini_line_scale_factor) mini_line.insert_n_anchor_points(self.num_inserted_number_line_anchors) mini_line.match_style(self.input_line.main_line) mini_line_copy = mini_line.copy() zcbr_group.add(mini_line_copy, mini_line) anims += [FadeIn(mini_line), FadeIn(mini_line_copy)] # Add tiny coordiantes if local_coordinate_values is None: local_coordinate_values = [x] local_coordinates = self.get_local_coordinates( self.input_line, *local_coordinate_values ) anims.append(FadeIn(local_coordinates)) zcbr_group.add(local_coordinates) self.local_coordinates = local_coordinates # Add tiny dots if local_sample_dots is not None: anims.append(LaggedStart(GrowFromCenter, local_sample_dots)) zcbr_group.add(local_sample_dots) if first_added_anims: anims += first_added_anims anims.append(Animation(zcbr_group)) if not pop_out: self.activate_zooming(animate=False) self.play(*anims, **first_anim_kwargs) if not self.zoom_activated and pop_out: self.activate_zooming(animate=False) added_anims = second_added_anims or [] self.play( self.get_zoomed_display_pop_out_animation(), *added_anims, **second_anim_kwargs ) def get_local_coordinates(self, line, *x_values, **kwargs): num_decimal_places = kwargs.get( "num_decimal_places", self.local_coordinate_num_decimal_places ) result = VGroup() result.tick_marks = VGroup() result.numbers = VGroup() result.add(result.tick_marks, result.numbers) for x in x_values: tick_mark = Line(UP, DOWN) tick_mark.scale_to_fit_height( 0.15 * self.zoomed_camera.frame.get_height() ) tick_mark.move_to(line.number_to_point(x)) result.tick_marks.add(tick_mark) number = DecimalNumber(x, num_decimal_places=num_decimal_places) number.scale(self.get_zoom_factor()) number.scale(0.5) # To make it seem small number.next_to(tick_mark, DOWN, buff=0.5 * number.get_height()) result.numbers.add(number) return result def get_mobjects_in_zoomed_camera(self, mobjects): frame = self.zoomed_camera.frame x_min = frame.get_left()[0] x_max = frame.get_right()[0] y_min = frame.get_bottom()[1] y_max = frame.get_top()[1] result = VGroup() for mob in mobjects: for point in mob.get_all_points(): if (x_min < point[0] < x_max) and (y_min < point[1] < y_max): result.add(mob) break return result # Helpers def get_input_point(self, x): return self.input_line.number_to_point(x) def get_output_point(self, fx): return self.output_line.number_to_point(fx) def number_func_to_point_func(self, number_func): input_line, output_line = self.number_lines def point_func(point): input_number = input_line.point_to_number(point) output_number = number_func(input_number) return output_line.number_to_point(output_number) return point_func class ExampleNumberlineTransformationScene(NumberlineTransformationScene): CONFIG = { "number_line_config": { "x_min": 0, "x_max": 5, "unit_size": 2.0 }, "output_line_config": { "x_max": 20, }, } def construct(self): func = lambda x: x**2 x = 3 dx = 0.05 sample_dots = self.get_sample_dots() local_sample_dots = self.get_local_sample_dots(x) self.play(LaggedStart(GrowFromCenter, sample_dots)) self.zoom_in_on_input( x, local_sample_dots=local_sample_dots, local_coordinate_values=[x - dx, x, x + dx], ) self.wait() self.apply_function( func, sample_dots=sample_dots, local_sample_dots=local_sample_dots, target_coordinate_values=[func(x) - dx, func(x), func(x) + dx], ) self.wait() # Scenes class WriteOpeningWords(Scene): def construct(self): raw_string1 = "Dear calculus student," raw_string2 = "You're about to go through your first course. Like " + \ "any new topic, it will take some hard work to understand," words1, words2 = [ TextMobject("\\Large", *rs.split(" ")) for rs in raw_string1, raw_string2 ] words1.next_to(words2, UP, aligned_edge=LEFT, buff=LARGE_BUFF) words = VGroup(*it.chain(words1, words2)) words.scale_to_fit_width(FRAME_WIDTH - 2 * LARGE_BUFF) words.to_edge(UP) letter_wait = 0.05 word_wait = 2 * letter_wait comma_wait = 5 * letter_wait for word in words: self.play(LaggedStart( FadeIn, word, run_time=len(word) * letter_wait, lag_ratio=1.5 / len(word) )) self.wait(word_wait) if word.get_tex_string()[-1] == ",": self.wait(comma_wait) class StartingCalc101(PiCreatureScene): CONFIG = { "camera_config": {"background_opacity": 1}, "image_frame_width": 3.5, "image_frame_height": 2.5, } def construct(self): self.show_you() self.show_images() self.show_mystery_topic() def show_you(self): randy = self.pi_creature title = self.title = Title("Calculus 101") you = TextMobject("You") arrow = Vector(DL, color=WHITE) arrow.next_to(randy, UR) you.next_to(arrow.get_start(), UP) self.play( Write(you), GrowArrow(arrow), randy.change, "erm", title ) self.wait() self.play(Write(title, run_time=1)) self.play(FadeOut(VGroup(arrow, you))) def show_images(self): randy = self.pi_creature images = self.get_all_images() modes = [ "pondering", # hard_work_image "pondering", # neat_example_image "hesitant", # not_so_neat_example_image "hesitant", # physics_image "horrified", # piles_of_formulas_image "horrified", # getting_stuck_image "thinking", # aha_image "thinking", # graphical_intuition_image ] for i, image, mode in zip(it.count(), images, modes): anims = [] if hasattr(image, "fade_in_anim"): anims.append(image.fade_in_anim) anims.append(FadeIn(image.frame)) else: anims.append(FadeIn(image)) if i >= 3: image_to_fade_out = images[i - 3] if hasattr(image_to_fade_out, "fade_out_anim"): anims.append(image_to_fade_out.fade_out_anim) else: anims.append(FadeOut(image_to_fade_out)) if hasattr(image, "continual_animations"): self.add(*image.continual_animations) anims.append(ApplyMethod(randy.change, mode)) self.play(*anims) self.wait() if i >= 3: if hasattr(image_to_fade_out, "continual_animations"): self.remove(*image_to_fade_out.continual_animations) self.remove(image_to_fade_out.frame) self.wait(3) self.remaining_images = images[-3:] def show_mystery_topic(self): images = self.remaining_images randy = self.pi_creature mystery_box = Rectangle( width=self.image_frame_width, height=self.image_frame_height, stroke_color=YELLOW, fill_color=DARK_GREY, fill_opacity=0.5, ) mystery_box.scale(1.5) mystery_box.next_to(self.title, DOWN, MED_LARGE_BUFF) rects = images[-1].rects.copy() rects.center() rects.scale_to_fit_height(FRAME_HEIGHT - 1) # image = rects.get_image() open_cv_image = cv2.imread(get_full_raster_image_path("alt_calc_hidden_image")) blurry_iamge = cv2.blur(open_cv_image, (50, 50)) array = np.array(blurry_iamge)[:, :, ::-1] im_mob = ImageMobject(array) im_mob.replace(mystery_box, stretch=True) mystery_box.add(im_mob) q_marks = TexMobject("???").scale(3) q_marks.space_out_submobjects(1.5) q_marks.set_stroke(BLACK, 1) q_marks.move_to(mystery_box) mystery_box.add(q_marks) for image in images: if hasattr(image, "continual_animations"): self.remove(*image.continual_animations) self.play( image.shift, DOWN, image.fade, 1, randy.change, "erm", run_time=1.5 ) self.remove(image) self.wait() self.play( FadeInFromDown(mystery_box), randy.change, "confused" ) self.wait(5) # Helpers def get_all_images(self): # Images matched to narration's introductory list images = VGroup( self.get_hard_work_image(), self.get_neat_example_image(), self.get_not_so_neat_example_image(), self.get_physics_image(), self.get_piles_of_formulas_image(), self.get_getting_stuck_image(), self.get_aha_image(), self.get_graphical_intuition_image(), ) colors = color_gradient([BLUE, YELLOW], len(images)) for i, image, color in zip(it.count(), images, colors): self.adjust_size(image) frame = Rectangle( width=self.image_frame_width, height=self.image_frame_height, color=color, stroke_width=2, ) frame.move_to(image) image.frame = frame image.add(frame) image.next_to(self.title, DOWN) alt_i = (i % 3) - 1 vect = (self.image_frame_width + LARGE_BUFF) * RIGHT image.shift(alt_i * vect) return images def adjust_size(self, group): group.scale_to_fit_width(min( group.get_width(), self.image_frame_width - 2 * MED_SMALL_BUFF )) group.scale_to_fit_height(min( group.get_height(), self.image_frame_height - 2 * MED_SMALL_BUFF )) return group def get_hard_work_image(self): new_randy = self.pi_creature.copy() new_randy.change_mode("telepath") bubble = new_randy.get_bubble(height=3.5, width=4) bubble.add_content(TexMobject("\\frac{d}{dx}(\\sin(\\sqrt{x}))")) bubble.add(bubble.content) # Remove? return VGroup(new_randy, bubble) def get_neat_example_image(self): filled_circle = Circle( stroke_width=0, fill_color=BLUE_E, fill_opacity=1 ) area = TexMobject("\\pi r^2") area.move_to(filled_circle) unfilled_circle = Circle( stroke_width=3, stroke_color=YELLOW, fill_opacity=0, ) unfilled_circle.next_to(filled_circle, RIGHT) circles = VGroup(filled_circle, unfilled_circle) circumference = TexMobject("2\\pi r") circumference.move_to(unfilled_circle) equation = TexMobject( "{d (\\pi r^2) \\over dx} = 2\\pi r", tex_to_color_map={ "\\pi r^2": BLUE_D, "2\\pi r": YELLOW, } ) equation.next_to(circles, UP) return VGroup( filled_circle, area, unfilled_circle, circumference, equation ) def get_not_so_neat_example_image(self): return TexMobject("\\int x \\cos(x) \\, dx") def get_physics_image(self): t_max = 6.5 r = 0.2 spring = ParametricFunction( lambda t: op.add( r * (np.sin(TAU * t) * RIGHT + np.cos(TAU * t) * UP), t * DOWN, ), t_min=0, t_max=t_max, color=WHITE, stroke_width=2, ) spring.color_using_background_image("grey_gradient") weight = Square() weight.set_stroke(width=0) weight.set_fill(opacity=1) weight.color_using_background_image("grey_gradient") weight.scale_to_fit_height(0.4) t_tracker = ValueTracker(0) group = VGroup(spring, weight) group.continual_animations = [ ContinualUpdateFromTimeFunc( t_tracker, lambda tracker, dt: tracker.set_value( tracker.get_value() + dt ) ), ContinualUpdateFromFunc( spring, lambda s: s.stretch_to_fit_height( 1.5 + 0.5 * np.cos(3 * t_tracker.get_value()), about_edge=UP ) ), ContinualUpdateFromFunc( weight, lambda w: w.move_to(spring.points[-1]) ) ] def update_group_style(alpha): spring.set_stroke(width=2 * alpha) weight.set_fill(opacity=alpha) group.fade_in_anim = UpdateFromAlphaFunc( group, lambda g, a: update_group_style(a) ) group.fade_out_anim = UpdateFromAlphaFunc( group, lambda g, a: update_group_style(1 - a) ) return group def get_piles_of_formulas_image(self): return TexMobject("(f/g)' = \\frac{gf' - fg'}{g^2}") def get_getting_stuck_image(self): creature = self.pi_creature.copy() creature.change_mode("angry") equation = TexMobject("\\frac{d}{dx}(x^x)") equation.scale_to_fit_height(creature.get_height() / 2) equation.next_to(creature, RIGHT, aligned_edge=UP) creature.look_at(equation) return VGroup(creature, equation) def get_aha_image(self): creature = self.pi_creature.copy() creature.change_mode("hooray") from old_projects.eoc.chapter3 import NudgeSideLengthOfCube scene = NudgeSideLengthOfCube( end_at_animation_number=7, skip_animations=True ) group = VGroup( scene.cube, scene.faces, scene.bars, scene.corner_cube, ) group.scale_to_fit_height(0.75 * creature.get_height()) group.next_to(creature, RIGHT) creature.look_at(group) return VGroup(creature, group) def get_graphical_intuition_image(self): gs = GraphScene() gs.setup_axes() graph = gs.get_graph( lambda x: 0.2 * (x - 3) * (x - 5) * (x - 6) + 4, x_min=2, x_max=8, ) rects = gs.get_riemann_rectangles( graph, x_min=2, x_max=8, stroke_width=0.5, dx=0.25 ) gs.add(graph, rects, gs.axes) group = VGroup(*gs.mobjects) self.adjust_size(group) group.next_to(self.title, DOWN, MED_LARGE_BUFF) group.rects = rects group.continual_animations = [ NormalAnimationAsContinualAnimation(Write(rects)), NormalAnimationAsContinualAnimation(ShowCreation(graph)), NormalAnimationAsContinualAnimation(FadeIn(gs.axes)), ] self.adjust_size(group) return group class GraphicalIntuitions(GraphScene): CONFIG = { "func": lambda x: 0.1 * (x - 2) * (x - 5) * (x - 7) + 4, "x_labeled_nums": range(1, 10), } def construct(self): self.setup_axes() axes = self.axes graph = self.get_graph(self.func) ss_group = self.get_secant_slope_group( x=2, graph=graph, dx=0.01, secant_line_length=6, secant_line_color=RED, ) rects = self.get_riemann_rectangles( graph, x_min=2, x_max=8, dx=0.01, stroke_width=0 ) deriv_text = TextMobject( "Derivative $\\rightarrow$ slope", tex_to_color_map={"slope": ss_group.secant_line.get_color()} ) deriv_text.to_edge(UP) integral_text = TextMobject( "Integral $\\rightarrow$ area", tex_to_color_map={"area": rects[0].get_color()} ) integral_text.next_to(deriv_text, DOWN) self.play( Succession(Write(axes), ShowCreation(graph, run_time=2)), self.get_graph_words_anim(), ) self.animate_secant_slope_group_change( ss_group, target_x=8, rate_func=there_and_back, run_time=5, added_anims=[ Write(deriv_text), VFadeIn(ss_group, run_time=2), ] ) self.play(FadeIn(integral_text)) self.play( LaggedStart( GrowFromEdge, rects, lambda r: (r, DOWN) ), Animation(axes), Animation(graph), ) self.wait() def get_graph_words_anim(self): words = VGroup( TextMobject("Graphs,"), TextMobject("graphs,"), TextMobject("non-stop graphs"), TextMobject("all day"), TextMobject("every day"), TextMobject("as if to visualize is to graph"), ) for word in words: word.add_background_rectangle() words.arrange_submobjects(DOWN) words.to_edge(UP) return LaggedStart( FadeIn, words, rate_func=there_and_back, run_time=len(words) - 1, lag_ratio=0.6, remover=True ) class Wrapper(Scene): CONFIG = { "title": "", "title_kwargs": {}, "screen_height": 6, "wait_time": 2, } def construct(self): rect = ScreenRectangle(height=self.screen_height) title = TextMobject(self.title, **self.title_kwargs) title.to_edge(UP) rect.next_to(title, DOWN) self.add(title) self.play(ShowCreation(rect)) self.wait(self.wait_time) class DomainColoringWrapper(Wrapper): CONFIG = { "title": "Complex $\\rightarrow$ Complex", } class ChangingVectorFieldWrapper(Wrapper): CONFIG = {"title": "$(x, y, t) \\rightarrow (x', y')$"} class ChangingVectorField(Scene): CONFIG = { "wait_time": 30, } def construct(self): plane = self.plane = NumberPlane() plane.set_stroke(width=2) plane.add_coordinates() self.add(plane) time_tracker = self.time_tracker = ValueTracker(0) self.add(ContinualGrowValue(time_tracker)) vectors = self.get_vectors() self.add(ContinualUpdateFromFunc( vectors, lambda vs: self.update_vectors(vs) )) self.wait(self.wait_time) def get_vectors(self): vectors = VGroup() x_max = int(np.ceil(FRAME_WIDTH)) y_max = int(np.ceil(FRAME_HEIGHT)) step = 0.5 for x in np.arange(-x_max, x_max + 1, step): for y in np.arange(-y_max, y_max + 1, step): point = x * RIGHT + y * UP vectors.add(Vector(RIGHT).shift(point)) vectors.set_color_by_gradient(YELLOW, RED) return vectors def update_vectors(self, vectors): time = self.time_tracker.get_value() for vector in vectors: point = vector.get_start() out_point = self.func(point, time) norm = np.linalg.norm(out_point) if norm == 0: out_point = RIGHT # Fake it vector.set_fill(opacity=0) else: out_point *= 0.5 color = interpolate_color(BLUE, RED, norm / np.sqrt(8)) vector.set_fill(color, opacity=1) vector.set_stroke(BLACK, width=1) new_x, new_y = out_point[:2] vector.put_start_and_end_on( point, point + new_x * RIGHT + new_y * UP ) def func(self, point, time): x, y, z = point return np.array([ np.sin(time + 0.5 * x + y), np.cos(time + 0.2 * x * y + 0.7), 0 ]) class MoreTopics(Scene): def construct(self): calculus = TextMobject("Calculus") calculus.next_to(LEFT, LEFT) calculus.set_color(YELLOW) others = VGroup( TextMobject("Multivariable calculus"), TextMobject("Complex analysis"), TextMobject("Differential geometry"), TextMobject("$\\vdots$") ) others.arrange_submobjects( DOWN, buff=MED_LARGE_BUFF, aligned_edge=LEFT, ) others.next_to(RIGHT, RIGHT) lines = VGroup(*[ Line(calculus.get_right(), word.get_left(), buff=MED_SMALL_BUFF) for word in others ]) rect = FullScreenFadeRectangle(fill_opacity=0.7) self.add(rect) self.add(calculus) self.play( LaggedStart(ShowCreation, lines), LaggedStart(Write, others), ) self.wait() self.calculus = calculus self.lines = lines self.full_screen_rect = rect self.other_topics = others class TransformationalViewWrapper(Wrapper): CONFIG = { "title": "Transformational view" } class SetTheStage(TeacherStudentsScene): def construct(self): ordinary = TextMobject("Ordinary visual") transformational = TextMobject("Transformational visual") for word in ordinary, transformational: word.move_to(self.hold_up_spot, DOWN) word.shift_onto_screen() self.teacher_holds_up( ordinary, added_anims=[self.get_student_changes(*3 * ["sassy"])] ) self.wait() self.play( ordinary.shift, UP, FadeInFromDown(transformational), self.teacher.change, "hooray", self.get_student_changes(*3 * ["erm"]) ) self.wait(3) class StandardDerivativeVisual(GraphScene): CONFIG = { "y_max": 8, "y_axis_height": 5, } def construct(self): self.add_title() self.show_function_graph() self.show_slope_of_graph() self.encourage_not_to_think_of_slope_as_definition() self.show_sensitivity() def add_title(self): title = self.title = TextMobject("Standard derivative visual") title.to_edge(UP) h_line = Line(LEFT, RIGHT) h_line.scale_to_fit_width(FRAME_WIDTH - 2 * LARGE_BUFF) h_line.next_to(title, DOWN) self.add(title, h_line) def show_function_graph(self): self.setup_axes() def func(x): x -= 5 return 0.1 * (x + 3) * (x - 3) * x + 3 graph = self.get_graph(func) graph_label = self.get_graph_label(graph, x_val=9.5) input_tracker = ValueTracker(4) def get_x_value(): return input_tracker.get_value() def get_y_value(): return graph.underlying_function(get_x_value()) def get_x_point(): return self.coords_to_point(get_x_value(), 0) def get_y_point(): return self.coords_to_point(0, get_y_value()) def get_graph_point(): return self.coords_to_point(get_x_value(), get_y_value()) def get_v_line(): return DashedLine(get_x_point(), get_graph_point(), stroke_width=2) def get_h_line(): return DashedLine(get_graph_point(), get_y_point(), stroke_width=2) input_triangle = RegularPolygon(n=3, start_angle=TAU / 4) output_triangle = RegularPolygon(n=3, start_angle=0) for triangle in input_triangle, output_triangle: triangle.set_fill(WHITE, 1) triangle.set_stroke(width=0) triangle.scale(0.1) input_triangle_update = ContinualUpdateFromFunc( input_triangle, lambda m: m.move_to(get_x_point(), UP) ) output_triangle_update = ContinualUpdateFromFunc( output_triangle, lambda m: m.move_to(get_y_point(), RIGHT) ) x_label = TexMobject("x") x_label_update = ContinualUpdateFromFunc( x_label, lambda m: m.next_to(input_triangle, DOWN, SMALL_BUFF) ) output_label = TexMobject("f(x)") output_label_update = ContinualUpdateFromFunc( output_label, lambda m: m.next_to( output_triangle, LEFT, SMALL_BUFF) ) v_line = get_v_line() v_line_update = ContinualUpdateFromFunc( v_line, lambda vl: Transform(vl, get_v_line()).update(1) ) h_line = get_h_line() h_line_update = ContinualUpdateFromFunc( h_line, lambda hl: Transform(hl, get_h_line()).update(1) ) graph_dot = Dot(color=YELLOW) graph_dot_update = ContinualUpdateFromFunc( graph_dot, lambda m: m.move_to(get_graph_point()) ) self.play( ShowCreation(graph), Write(graph_label), ) self.play( DrawBorderThenFill(input_triangle, run_time=1), Write(x_label), ShowCreation(v_line), GrowFromCenter(graph_dot), ) self.add_foreground_mobject(graph_dot) self.play( ShowCreation(h_line), Write(output_label), DrawBorderThenFill(output_triangle, run_time=1) ) self.add( input_triangle_update, x_label_update, graph_dot_update, v_line_update, h_line_update, output_triangle_update, output_label_update, ) self.play( input_tracker.set_value, 8, run_time=6, rate_func=there_and_back ) self.input_tracker = input_tracker self.graph = graph def show_slope_of_graph(self): input_tracker = self.input_tracker deriv_input_tracker = ValueTracker(input_tracker.get_value()) # Slope line def get_slope_line(): return self.get_secant_slope_group( x=deriv_input_tracker.get_value(), graph=self.graph, dx=0.01, secant_line_length=4 ).secant_line slope_line = get_slope_line() slope_line_update = ContinualUpdateFromFunc( slope_line, lambda sg: Transform(sg, get_slope_line()).update(1) ) def position_deriv_label(deriv_label): deriv_label.next_to(slope_line, UP) return deriv_label deriv_label = TexMobject( "\\frac{df}{dx}(x) =", "\\text{Slope}", "=" ) deriv_label.get_part_by_tex("Slope").match_color(slope_line) deriv_label_update = ContinualUpdateFromFunc( deriv_label, position_deriv_label ) slope_decimal = DecimalNumber(slope_line.get_slope()) slope_decimal.match_color(slope_line) slope_decimal_update = ContinualChangingDecimal( slope_decimal, lambda dt: slope_line.get_slope(), position_update_func=lambda m: m.next_to( deriv_label, RIGHT, SMALL_BUFF ).shift(0.2 * SMALL_BUFF * DOWN) ) self.play( ShowCreation(slope_line), Write(deriv_label), Write(slope_decimal), run_time=1 ) self.wait() self.add( slope_line_update, # deriv_label_update, slope_decimal_update, ) for x in 9, 2, 4: self.play( input_tracker.set_value, x, deriv_input_tracker.set_value, x, run_time=3 ) self.wait() self.deriv_input_tracker = deriv_input_tracker def encourage_not_to_think_of_slope_as_definition(self): morty = Mortimer(height=2) morty.to_corner(DR) self.play(FadeIn(morty)) self.play(PiCreatureSays( morty, "Don't think of \\\\ this as the definition", bubble_kwargs={"height": 2, "width": 4} )) self.play(Blink(morty)) self.wait() self.play( RemovePiCreatureBubble(morty), UpdateFromAlphaFunc( morty, lambda m, a: m.set_fill(opacity=1 - a), remover=True ) ) def show_sensitivity(self): input_tracker = self.input_tracker deriv_input_tracker = self.deriv_input_tracker self.wiggle_input() for x in 9, 7, 2: self.play( input_tracker.set_value, x, deriv_input_tracker.set_value, x, run_time=3 ) self.wiggle_input() ### def wiggle_input(self, dx=0.5, run_time=3): input_tracker = self.input_tracker x = input_tracker.get_value() x_min = x - dx x_max = x + dx y, y_min, y_max = map( self.graph.underlying_function, [x, x_min, x_max] ) x_line = Line( self.coords_to_point(x_min, 0), self.coords_to_point(x_max, 0), ) y_line = Line( self.coords_to_point(0, y_min), self.coords_to_point(0, y_max), ) x_rect, y_rect = rects = VGroup(Rectangle(), Rectangle()) rects.set_stroke(width=0) rects.set_fill(YELLOW, 0.5) x_rect.match_width(x_line) x_rect.stretch_to_fit_height(0.25) x_rect.move_to(x_line) y_rect.match_height(y_line) y_rect.stretch_to_fit_width(0.25) y_rect.move_to(y_line) self.play( ApplyMethod( input_tracker.set_value, input_tracker.get_value() + dx, rate_func=lambda t: wiggle(t, 6) ), FadeIn( rects, rate_func=squish_rate_func(smooth, 0, 0.33), remover=True, ), run_time=run_time, ) self.play(FadeOut(rects)) class EoCWrapper(Scene): def construct(self): title = Title("Essence of calculus") self.play(Write(title)) self.wait() class IntroduceTransformationView(NumberlineTransformationScene): CONFIG = { "func": lambda x: 0.5 * np.sin(2 * x) + x, "number_line_config": { "x_min": 0, "x_max": 6, "unit_size": 2.0 }, } def construct(self): self.add_title() self.show_animation_preview() self.indicate_indicate_point_densities() self.show_zoomed_transformation() def add_title(self): title = self.title = TextMobject("$f(x)$ as a transformation") title.to_edge(UP) self.add(title) def show_animation_preview(self): input_points = self.get_sample_input_points() output_points = map( self.number_func_to_point_func(self.func), input_points ) sample_dots = self.get_sample_dots() sample_dot_ghosts = sample_dots.copy().fade(0.5) arrows = VGroup(*[ Arrow(ip, op, buff=MED_SMALL_BUFF) for ip, op in zip(input_points, output_points) ]) arrows = arrows[1::3] arrows.set_stroke(BLACK, 1) self.play( LaggedStart(GrowFromCenter, sample_dots, run_time=1), LaggedStart(GrowArrow, arrows) ) self.add(sample_dot_ghosts) self.apply_function( self.func, sample_dots=sample_dots ) self.wait() self.play(LaggedStart(FadeOut, arrows, run_time=1)) self.sample_dots = sample_dots self.sample_dot_ghosts = sample_dot_ghosts def indicate_indicate_point_densities(self): lower_brace = Brace(Line(LEFT, RIGHT), UP) upper_brace = lower_brace.copy() input_tracker = ValueTracker(0.5) dx = 0.5 def update_upper_brace(brace): x = input_tracker.get_value() line = Line( self.get_input_point(x), self.get_input_point(x + dx), ) brace.match_width(line, stretch=True) brace.next_to(line, UP, buff=SMALL_BUFF) return brace def update_lower_brace(brace): x = input_tracker.get_value() line = Line( self.get_output_point(self.func(x)), self.get_output_point(self.func(x + dx)), ) brace.match_width(line, stretch=True) brace.next_to(line, UP, buff=SMALL_BUFF) return brace lower_brace_anim = UpdateFromFunc(lower_brace, update_lower_brace) upper_brace_anim = UpdateFromFunc(upper_brace, update_upper_brace) new_title = TextMobject( "$\\frac{df}{dx}(x)$ measures stretch/squishing" ) new_title.move_to(self.title, UP) stretch_factor = DecimalNumber(0, color=YELLOW) stretch_factor_anim = ChangingDecimal( stretch_factor, lambda a: lower_brace.get_width() / upper_brace.get_width(), position_update_func=lambda m: m.next_to(lower_brace, UP, SMALL_BUFF) ) self.play( GrowFromCenter(upper_brace), FadeOut(self.title), # FadeIn(new_title) Write(new_title, run_time=2) ) self.title = new_title self.play( ReplacementTransform(upper_brace.copy(), lower_brace), GrowFromPoint(stretch_factor, upper_brace.get_center()) ) self.play( input_tracker.set_value, self.input_line.x_max - dx, lower_brace_anim, upper_brace_anim, stretch_factor_anim, run_time=8, rate_func=bezier([0, 0, 1, 1]) ) self.wait() new_sample_dots = self.get_sample_dots() self.play( FadeOut(VGroup( upper_brace, lower_brace, stretch_factor, self.sample_dots, self.moving_input_line, )), FadeIn(new_sample_dots), ) self.sample_dots = new_sample_dots def show_zoomed_transformation(self): x = 2.75 local_sample_dots = self.get_local_sample_dots(x) self.zoom_in_on_input( x, local_sample_dots=local_sample_dots, local_coordinate_values=self.get_local_coordinate_values(x), ) self.wait() self.apply_function( self.func, sample_dots=self.sample_dots, local_sample_dots=local_sample_dots, target_coordinate_values=self.get_local_coordinate_values(self.func(x)) ) self.wait() class TalkThroughXSquaredExample(IntroduceTransformationView): CONFIG = { "func": lambda x: x**2, "number_line_config": { "x_min": 0, "x_max": 5, "unit_size": 1.25, }, "output_line_config": { "x_max": 25, }, "default_delta_x": 0.2 } def construct(self): self.add_title() self.show_specific_points_mapping() def add_title(self): title = self.title = TextMobject("$f(x) = x^2$") title.to_edge(UP, buff=MED_SMALL_BUFF) self.add(title) def show_specific_points_mapping(self): # First, just show integers as examples int_dots = self.get_sample_dots(1, 6, 1) int_dot_ghosts = int_dots.copy().fade(0.5) int_arrows = VGroup(*[ Arrow( # num.get_bottom(), self.get_input_point(x), self.get_output_point(self.func(x)), buff=MED_SMALL_BUFF ) for x, num in zip(range(1, 6), self.input_line.numbers[1:]) ]) point_func = self.number_func_to_point_func(self.func) numbers = self.input_line.numbers numbers.next_to(self.input_line, UP, SMALL_BUFF) self.titles[0].next_to(numbers, UP, MED_SMALL_BUFF, LEFT) # map(TexMobject.add_background_rectangle, numbers) # self.add_foreground_mobject(numbers) for dot, dot_ghost, arrow in zip(int_dots, int_dot_ghosts, int_arrows): arrow.match_color(dot) self.play(DrawBorderThenFill(dot, run_time=1)) self.add(dot_ghost) self.play( GrowArrow(arrow), dot.apply_function_to_position, point_func ) self.wait() # Show more sample_dots sample_dots = self.get_sample_dots() sample_dot_ghosts = sample_dots.copy().fade(0.5) self.play( LaggedStart(DrawBorderThenFill, sample_dots), LaggedStart(FadeOut, int_arrows), ) self.remove(int_dot_ghosts) self.add(sample_dot_ghosts) self.apply_function(self.func, sample_dots=sample_dots) self.remove(int_dots) self.wait() self.sample_dots = sample_dots self.sample_dot_ghosts = sample_dot_ghosts def get_stretch_words(self, factor, color=RED, less_than_one=False): factor_str = "$%s$" % str(factor) result = TextMobject( "Scale \\\\ by", factor_str, tex_to_color_map={factor_str: color} ) result.scale(0.7) la, ra = TexMobject("\\leftarrow \\rightarrow") if less_than_one: la, ra = ra, la if factor < 0: kwargs = { "path_arc": -np.pi, "use_rectangular_stem": False, } la = Arrow(DOWN, UP, **kwargs) ra = Arrow(UP, DOWN, **kwargs) for arrow in la, ra: arrow.pointwise_become_partial(arrow, 0, 0.9) arrow.tip.scale(2) VGroup(la, ra).match_height(result) la.next_to(result, LEFT) ra.next_to(result, RIGHT) result.add(la, ra) result.next_to( self.zoomed_display.get_top(), DOWN, SMALL_BUFF ) return result def get_deriv_equation(self, x, rhs, color=RED): deriv_equation = self.deriv_equation = TexMobject( "\\frac{df}{dx}(", str(x), ")", "=", str(rhs), tex_to_color_map={str(x): color, str(rhs): color} ) deriv_equation.next_to(self.title, DOWN, MED_LARGE_BUFF) return deriv_equation class ZoomInOnXSquaredNearOne(TalkThroughXSquaredExample): def setup(self): TalkThroughXSquaredExample.setup(self) self.force_skipping() self.add_title() self.show_specific_points_mapping() self.revert_to_original_skipping_status() def construct(self): zoom_words = TextMobject("Zoomed view \\\\ near 1") zoom_words.next_to(self.zoomed_display, DOWN) # zoom_words.shift_onto_screen() x = 1 local_sample_dots = self.get_local_sample_dots(x) local_coords = self.get_local_coordinate_values(x, dx=0.1) zcbr_anim = self.zoomed_camera_background_rectangle_anim zcbr_group = self.zoomed_camera_background_rectangle_group frame = self.zoomed_camera.frame self.zoom_in_on_input(x, local_sample_dots, local_coords) self.play(FadeIn(zoom_words)) self.wait() local_sample_dots.save_state() frame.save_state() self.mini_line.save_state() sample_dot_ghost_copies = self.sample_dot_ghosts.copy() self.apply_function( self.func, apply_function_to_number_line=False, local_sample_dots=local_sample_dots, target_coordinate_values=local_coords ) self.remove(sample_dot_ghost_copies) self.wait() # Go back self.play( frame.restore, self.mini_line.restore, local_sample_dots.restore, zcbr_anim, Animation(zcbr_group) ) self.wait() # Zoom in even more extra_zoom_factor = 0.3 one_group = VGroup( self.local_coordinates.tick_marks[1], self.local_coordinates.numbers[1], ) all_other_coordinates = VGroup( self.local_coordinates.tick_marks[::2], self.local_coordinates.numbers[::2], self.local_target_coordinates, ) self.play(frame.scale, extra_zoom_factor) new_local_sample_dots = self.get_local_sample_dots(x, delta_x=0.005) new_coordinate_values = self.get_local_coordinate_values(x, dx=0.02) new_local_coordinates = self.get_local_coordinates( self.input_line, *new_coordinate_values ) self.play( Write(new_local_coordinates), Write(new_local_sample_dots), one_group.scale, extra_zoom_factor, {"about_point": self.get_input_point(1)}, FadeOut(all_other_coordinates), *[ ApplyMethod(dot.scale, extra_zoom_factor) for dot in local_sample_dots ] ) self.remove(one_group, local_sample_dots) zcbr_group.remove( self.local_coordinates, self.local_target_coordinates, local_sample_dots ) # Transform new zoomed view stretch_by_two_words = self.get_stretch_words(2) self.add_foreground_mobject(stretch_by_two_words) sample_dot_ghost_copies = self.sample_dot_ghosts.copy() self.apply_function( self.func, apply_function_to_number_line=False, sample_dots=sample_dot_ghost_copies, local_sample_dots=new_local_sample_dots, target_coordinate_values=new_coordinate_values, added_anims=[FadeIn(stretch_by_two_words)] ) self.remove(sample_dot_ghost_copies) self.wait() # Write derivative deriv_equation = self.get_deriv_equation(1, 2, color=RED) self.play(Write(deriv_equation)) self.wait() class ZoomInOnXSquaredNearThree(ZoomInOnXSquaredNearOne): CONFIG = { "zoomed_display_width": 4, } def construct(self): zoom_words = TextMobject("Zoomed view \\\\ near 3") zoom_words.next_to(self.zoomed_display, DOWN) x = 3 local_sample_dots = self.get_local_sample_dots(x) local_coordinate_values = self.get_local_coordinate_values(x, dx=0.1) target_coordinate_values = self.get_local_coordinate_values(self.func(x), dx=0.1) color = self.sample_dots[len(self.sample_dots) / 2].get_color() sample_dot_ghost_copies = self.sample_dot_ghosts.copy() stretch_words = self.get_stretch_words(2 * x, color) deriv_equation = self.get_deriv_equation(x, 2 * x, color) self.add(deriv_equation) self.zoom_in_on_input( x, pop_out=False, local_sample_dots=local_sample_dots, local_coordinate_values=local_coordinate_values ) self.play(Write(zoom_words, run_time=1)) self.wait() self.add_foreground_mobject(stretch_words) self.apply_function( self.func, apply_function_to_number_line=False, sample_dots=sample_dot_ghost_copies, local_sample_dots=local_sample_dots, target_coordinate_values=target_coordinate_values, added_anims=[Write(stretch_words)] ) self.wait(2) class ZoomInOnXSquaredNearOneFourth(ZoomInOnXSquaredNearOne): CONFIG = { "zoom_factor": 0.01, "local_coordinate_num_decimal_places": 4, "zoomed_display_width": 4, "default_delta_x": 0.25, } def construct(self): # Much copy-pasting from previous scenes. Not great, but # the fastest way to get the ease-of-tweaking I'd like. zoom_words = TextMobject("Zoomed view \\\\ near $1/4$") zoom_words.next_to(self.zoomed_display, DOWN) x = 0.25 local_sample_dots = self.get_local_sample_dots( x, sample_radius=2.5 * self.zoomed_camera.frame.get_width(), ) local_coordinate_values = self.get_local_coordinate_values( x, dx=0.01, ) target_coordinate_values = self.get_local_coordinate_values( self.func(x), dx=0.01, ) color = RED sample_dot_ghost_copies = self.sample_dot_ghosts.copy() stretch_words = self.get_stretch_words("1/2", color, less_than_one=True) deriv_equation = self.get_deriv_equation("1/4", "1/2", color) one_fourth_point = self.get_input_point(x) one_fourth_arrow = Vector(0.5 * UP, color=WHITE) one_fourth_arrow.stem.stretch(0.75, 0) one_fourth_arrow.tip.scale(0.75, about_edge=DOWN) one_fourth_arrow.next_to(one_fourth_point, DOWN, SMALL_BUFF) one_fourth_label = TexMobject("0.25") one_fourth_label.match_height(self.input_line.numbers) one_fourth_label.next_to(one_fourth_arrow, DOWN, SMALL_BUFF) self.add(deriv_equation) self.zoom_in_on_input( x, local_sample_dots=local_sample_dots, local_coordinate_values=local_coordinate_values, pop_out=False, first_added_anims=[ FadeIn(one_fourth_label), GrowArrow(one_fourth_arrow), ] ) self.play(Write(zoom_words, run_time=1)) self.wait() self.add_foreground_mobject(stretch_words) self.apply_function( self.func, apply_function_to_number_line=False, sample_dots=sample_dot_ghost_copies, local_sample_dots=local_sample_dots, target_coordinate_values=target_coordinate_values, added_anims=[Write(stretch_words)] ) self.wait(2) class ZoomInOnXSquaredNearZero(ZoomInOnXSquaredNearOne): CONFIG = { "zoom_factor": 0.1, "zoomed_display_width": 4, "scale_by_term": "???", } def construct(self): zoom_words = TextMobject( "Zoomed %sx \\\\ near 0" % "{:,}".format(int(1.0 / self.zoom_factor)) ) zoom_words.next_to(self.zoomed_display, DOWN) x = 0 local_sample_dots = self.get_local_sample_dots( x, sample_radius=2 * self.zoomed_camera.frame.get_width() ) local_coordinate_values = self.get_local_coordinate_values( x, dx=self.zoom_factor ) # target_coordinate_values = self.get_local_coordinate_values( # self.func(x), dx=self.zoom_factor # ) color = self.sample_dots[len(self.sample_dots) / 2].get_color() sample_dot_ghost_copies = self.sample_dot_ghosts.copy() stretch_words = self.get_stretch_words( self.scale_by_term, color, less_than_one=True ) deriv_equation = self.get_deriv_equation(x, 2 * x, color) self.add(deriv_equation) self.zoom_in_on_input( x, pop_out=False, local_sample_dots=local_sample_dots, local_coordinate_values=local_coordinate_values ) self.play(Write(zoom_words, run_time=1)) self.wait() self.add_foreground_mobject(stretch_words) self.apply_function( self.func, apply_function_to_number_line=False, sample_dots=sample_dot_ghost_copies, local_sample_dots=local_sample_dots, # target_coordinate_values=target_coordinate_values, added_anims=[ Write(stretch_words), MaintainPositionRelativeTo( self.local_coordinates, self.zoomed_camera.frame ) ] ) self.wait(2) class ZoomInOnXSquared100xZero(ZoomInOnXSquaredNearZero): CONFIG = { "zoom_factor": 0.01 } class ZoomInOnXSquared1000xZero(ZoomInOnXSquaredNearZero): CONFIG = { "zoom_factor": 0.001, "local_coordinate_num_decimal_places": 3, } class ZoomInOnXSquared10000xZero(ZoomInOnXSquaredNearZero): CONFIG = { "zoom_factor": 0.0001, "local_coordinate_num_decimal_places": 4, "scale_by_term": "0", } class XSquaredForNegativeInput(TalkThroughXSquaredExample): CONFIG = { "input_line_config": { "x_min": -4, "x_max": 4, }, "input_line_zero_point": 0.5 * UP + 0 * LEFT, "output_line_config": {}, "default_mapping_animation_config": { "path_arc": 30 * DEGREES }, "zoomed_display_width": 4, } def construct(self): self.add_title() self.show_full_transformation() self.zoom_in_on_example() def show_full_transformation(self): sample_dots = self.get_sample_dots() self.play(LaggedStart(DrawBorderThenFill, sample_dots)) self.add_sample_dot_ghosts(sample_dots) self.apply_function(self.func, sample_dots=sample_dots) self.wait() def zoom_in_on_example(self): x = -2 local_sample_dots = self.get_local_sample_dots(x) local_coordinate_values = self.get_local_coordinate_values( x, dx=0.1 ) target_coordinate_values = self.get_local_coordinate_values( self.func(x), dx=0.1 ) deriv_equation = self.get_deriv_equation(x, 2 * x, color=BLUE) sample_dot_ghost_copies = self.sample_dot_ghosts.copy() scale_words = self.get_stretch_words(-4, color=BLUE) self.zoom_in_on_input( x, local_sample_dots=local_sample_dots, local_coordinate_values=local_coordinate_values, ) self.wait() self.play(Write(deriv_equation)) self.add_foreground_mobject(scale_words) self.play(Write(scale_words)) self.apply_function( self.func, sample_dots=sample_dot_ghost_copies, local_sample_dots=local_sample_dots, target_coordinate_values=target_coordinate_values ) self.wait() class HowDoesThisSolveProblems(TeacherStudentsScene): def construct(self): self.student_says( "Is this...useful?", target_mode="confused" ) self.change_student_modes("maybe", "confused", "sassy") self.play(self.teacher.change, "happy") self.wait(3) class IntroduceContinuedFractionPuzzle(PiCreatureScene): CONFIG = { "remove_initial_rhs": True, } def construct(self): self.ask_question() self.set_equal_to_x() # TODO, move this # self.plug_func_into_self() def create_pi_creatures(self): morty = Mortimer(height=2) morty.to_corner(DR) friend = PiCreature(color=GREEN, height=2) friend.to_edge(DOWN) friend.shift(0.5 * LEFT) group = VGroup(morty, friend) group.shift(2 * LEFT) return morty, friend def ask_question(self): morty, friend = self.pi_creatures frac = get_phi_continued_fraction(9) frac.scale(0.8) rhs = DecimalNumber( (1 - np.sqrt(5)) / 2.0, num_decimal_places=5, show_ellipsis=True, ) rhs.set_color(YELLOW) equals = TexMobject("=") equals.next_to(frac.get_part_by_tex("\\over"), RIGHT) rhs.next_to(equals, RIGHT) group = VGroup(frac, equals, rhs) group.scale(1.5) group.to_corner(UR) self.play( LaggedStart( Write, frac, run_time=15, lag_ratio=0.15, ), FadeInFromDown(equals), FadeInFromDown(rhs), PiCreatureSays( friend, "Would this be valid? \\\\ If not, why not?", target_mode="confused", look_at_arg=frac, bubble_kwargs={ "direction": RIGHT, "width": 4, "height": 3, } ), morty.change, "pondering", ) self.wait() anims = [ RemovePiCreatureBubble( friend, target_mode="pondering", look_at_arg=frac ), ] if self.remove_initial_rhs: anims += [ Animation(frac), FadeOut(equals), rhs.scale, 0.5, rhs.to_corner, DL, ] self.play(*anims) self.neg_one_over_phi = rhs self.equals = equals self.frac = frac def set_equal_to_x(self): frac = self.frac morty, friend = self.get_pi_creatures() inner_frac = frac[4:] inner_frac_rect = SurroundingRectangle( inner_frac, stroke_width=2, buff=0.5 * SMALL_BUFF ) inner_frac_group = VGroup(inner_frac, inner_frac_rect) equals = TexMobject("=") equals.next_to(frac[3], RIGHT) x, new_x = [TexMobject("x") for i in range(2)] xs = VGroup(x, new_x) xs.set_color(YELLOW) xs.scale(1.3) x.next_to(equals, RIGHT) new_x.next_to(frac[3], DOWN, 2 * SMALL_BUFF) fixed_point_words = VGroup( TextMobject("Fixed point of"), TexMobject( "f(x) = 1 + \\frac{1}{x}", tex_to_color_map={"x": YELLOW} ) ) fixed_point_words.arrange_submobjects(DOWN) self.play(Write(x), Write(equals)) self.wait() self.play(ShowCreation(inner_frac_rect)) self.wait() self.play( inner_frac_group.scale, 0.75, inner_frac_group.center, inner_frac_group.to_edge, LEFT, ReplacementTransform( x.copy(), new_x, path_arc=-90 * DEGREES ) ) self.wait() self.play( frac[3].stretch, 0.1, 0, {"about_edge": RIGHT}, MaintainPositionRelativeTo( VGroup(frac[2], new_x), frac[3] ), UpdateFromFunc( frac[:2], lambda m: m.next_to(frac[3], LEFT) ) ) self.wait() fixed_point_words.next_to(VGroup(frac[0], xs), DOWN, LARGE_BUFF) self.play( Write(fixed_point_words), morty.change, "hooray", friend.change, "happy" ) self.wait(3) class GraphOnePlusOneOverX(GraphScene): CONFIG = { "x_min": -6, "x_max": 6, "x_axis_width": 12, "y_min": -4, "y_max": 5, "y_axis_height": 8, "y_axis_label": None, "graph_origin": 0.5 * DOWN, "num_graph_anchor_points": 100, "func_graph_color": GREEN, "identity_graph_color": BLUE, } def construct(self): self.add_title() self.setup_axes() self.draw_graphs() self.show_solutions() def add_title(self): title = self.title = TexMobject( "\\text{Solve: }", "1 + \\frac{1}{x}", "=", "x", ) title.set_color_by_tex("x", self.identity_graph_color, substring=False) title.set_color_by_tex("frac", self.func_graph_color) title.to_corner(UL) self.add(title) def setup_axes(self): GraphScene.setup_axes(self) step = 2 self.x_axis.add_numbers(*range(-6, 0, step) + range(step, 7, step)) self.y_axis.label_direction = RIGHT self.y_axis.add_numbers(*range(-2, 0, step) + range(step, 4, step)) def draw_graphs(self, animate=True): lower_func_graph, upper_func_graph = func_graph = VGroup(*[ self.get_graph( lambda x: 1.0 + 1.0 / x, x_min=x_min, x_max=x_max, color=self.func_graph_color, ) for x_min, x_max in (-10, -0.1), (0.1, 10) ]) func_graph.label = self.get_graph_label( upper_func_graph, "y = 1 + \\frac{1}{x}", x_val=6, direction=UP, ) identity_graph = self.get_graph( lambda x: x, color=self.identity_graph_color ) identity_graph.label = self.get_graph_label( identity_graph, "y = x", x_val=3, direction=UL, buff=SMALL_BUFF ) if animate: for graph in func_graph, identity_graph: self.play( ShowCreation(graph), Write(graph.label), run_time=2 ) self.wait() else: self.add( func_graph, func_graph.label, identity_graph, identity_graph.label, ) self.func_graph = func_graph self.identity_graph = identity_graph def show_solutions(self): phi = 0.5 * (1 + np.sqrt(5)) phi_bro = 0.5 * (1 - np.sqrt(5)) lines = VGroup() for num in phi, phi_bro: line = DashedLine( self.coords_to_point(num, 0), self.coords_to_point(num, num), color=WHITE ) line_copy = line.copy() line_copy.set_color(YELLOW) line.fade(0.5) line_anim = ShowCreationThenDestruction( line_copy, submobject_mode="lagged_start", run_time=2 ) line.continual_anim = CycleAnimation(line_anim) lines.add(line) phi_line, phi_bro_line = lines decimal_kwargs = { "num_decimal_places": 3, "show_ellipsis": True, "color": YELLOW, } arrow_kwargs = { "buff": SMALL_BUFF, "color": WHITE, "tip_length": 0.15, "rectangular_stem_width": 0.025, } phi_decimal = DecimalNumber(phi, **decimal_kwargs) phi_decimal.next_to(phi_line, DOWN, LARGE_BUFF) phi_arrow = Arrow( phi_decimal[:4].get_top(), phi_line.get_bottom(), **arrow_kwargs ) phi_label = TexMobject("=", "\\varphi") phi_label.next_to(phi_decimal, RIGHT) phi_label.set_color_by_tex("\\varphi", YELLOW) phi_bro_decimal = DecimalNumber(phi_bro, **decimal_kwargs) phi_bro_decimal.next_to(phi_bro_line, UP, LARGE_BUFF) phi_bro_decimal.shift(0.5 * LEFT) phi_bro_arrow = Arrow( phi_bro_decimal[:6].get_bottom(), phi_bro_line.get_top(), **arrow_kwargs ) brother_words = TextMobject( "$\\varphi$'s little brother", tex_to_color_map={"$\\varphi$": YELLOW}, arg_separator="" ) brother_words.next_to( phi_bro_decimal[-2], UP, buff=MED_SMALL_BUFF, aligned_edge=RIGHT ) self.add(phi_line.continual_anim) self.play(ShowCreation(phi_line)) self.play( Write(phi_decimal), GrowArrow(phi_arrow), ) self.play(Write(phi_label)) self.wait(3) self.add(phi_bro_line.continual_anim) self.play(ShowCreation(phi_bro_line)) self.play( Write(phi_bro_decimal), GrowArrow(phi_bro_arrow), ) self.wait(4) self.play(Write(brother_words)) self.wait(8) class ThinkAboutWithRepeatedApplication(IntroduceContinuedFractionPuzzle): CONFIG = { "remove_initial_rhs": False, } def construct(self): self.force_skipping() self.ask_question() self.revert_to_original_skipping_status() self.obviously_not() self.plug_func_into_self() def obviously_not(self): morty, friend = self.get_pi_creatures() randy = Randolph() randy.match_height(morty) randy.to_corner(DL) frac = self.frac rhs = self.neg_one_over_phi plusses = frac[1::4] plus_rects = VGroup(*[ SurroundingRectangle(plus, buff=0) for plus in plusses ]) plus_rects.set_color(PINK) self.play(FadeIn(randy)) self.play( PiCreatureSays( randy, "Of course not!", bubble_kwargs={"width": 3, "height": 2}, target_mode="angry", run_time=1, ), morty.change, "guilty", friend.change, "hesitant" ) self.wait() self.play( Animation(frac), RemovePiCreatureBubble(randy, target_mode="sassy"), morty.change, "confused", friend.change, "confused", ) self.play(LaggedStart( ShowCreationThenDestruction, plus_rects, run_time=4, lag_ratio=0.35, )) self.play(WiggleOutThenIn(rhs)) self.wait(2) self.play( frac.scale, 0.7, frac.to_corner, UL, FadeOut(self.equals), rhs.scale, 0.5, rhs.center, rhs.to_edge, LEFT, FadeOut(randy), morty.change, "pondering", friend.change, "pondering", ) def plug_func_into_self(self, value=1, value_str="1"): morty, friend = self.pi_creatures def func(x): return 1 + 1.0 / x lines = VGroup() value_labels = VGroup() for n_terms in range(5): lhs = get_nested_f(n_terms, arg="c") equals = TexMobject("=") rhs = get_nested_one_plus_one_over_x(n_terms, bottom_term=value_str) equals.next_to(rhs[0], LEFT) lhs.next_to(equals, LEFT) lines.add(VGroup(lhs, equals, rhs)) value_label = TexMobject("= %.3f" % value) value = func(value) value_labels.add(value_label) lines.arrange_submobjects( DOWN, buff=MED_LARGE_BUFF, ) VGroup(lines, value_labels).scale(0.8) lines.to_corner(UR) buff = MED_LARGE_BUFF + MED_SMALL_BUFF + value_labels.get_width() lines.to_edge(RIGHT, buff=buff) for line, value_label in zip(lines, value_labels): value_label.move_to(line[1]).to_edge(RIGHT) top_line = lines[0] colors = [WHITE] + color_gradient([YELLOW, RED, PINK], len(lines) - 1) for n in range(1, len(lines)): color = colors[n] lines[n][0].set_color(color) lines[n][0][1:-1].match_style(lines[n - 1][0]) lines[n][2].set_color(color) lines[n][2][4:].match_style(lines[n - 1][2]) arrow = Vector(0.5 * DOWN, color=WHITE) arrow.next_to(value_labels[-1], DOWN) q_marks = TexMobject("???") q_marks.next_to(arrow, DOWN) self.play( FadeInFromDown(top_line), FadeInFromDown(value_labels[0]) ) for n in range(1, len(lines)): new_line = lines[n] last_line = lines[n - 1] value_label = value_labels[n] mover, target = [ VGroup( line[0][0], line[0][-1], line[1], line[2][:4], ) for line in lines[1], new_line ] anims = [ReplacementTransform( mover.copy().fade(1), target, path_arc=30 * DEGREES )] if n == 3: morty.generate_target() morty.target.change("horrified") morty.target.shift(2.5 * DOWN) anims.append(MoveToTarget(morty, remover=True)) self.play(*anims) self.wait() self.play( ReplacementTransform( last_line[0].copy(), new_line[0][1:-1] ), ReplacementTransform( last_line[2].copy(), new_line[2][4:] ), ) self.play(FadeIn(value_label)) self.wait() self.play( GrowArrow(arrow), Write(q_marks), friend.change, "confused" ) self.wait(3) class RepeatedApplicationWithPhiBro(ThinkAboutWithRepeatedApplication): def construct(self): self.force_skipping() self.ask_question() self.obviously_not() self.revert_to_original_skipping_status() self.plug_func_into_self( value=(1 - np.sqrt(5)) / 2, value_str="-1/\\varphi" ) class ShowRepeatedApplication(Scene): CONFIG = { "title_color": YELLOW, } def construct(self): self.add_func_title() self.show_repeated_iteration() def add_func_title(self): title = self.title = VGroup( TexMobject("f(", "x", ")"), TexMobject("="), get_nested_one_plus_one_over_x(1) ) title.arrange_submobjects(RIGHT) title.to_corner(UL) title.set_color(self.title_color) self.add(title) def show_repeated_iteration(self): line = VGroup() decimal_kwargs = { "num_decimal_places": 3, "show_ellipsis": True, } phi = (1 + np.sqrt(5)) / 2 def func(x): return 1.0 + 1.0 / x initial_term = DecimalNumber(2.71828, **decimal_kwargs) line.add(initial_term) last_term = initial_term def get_arrow(): arrow = TexMobject("\\rightarrow") arrow.stretch(1.5, 0) arrow.next_to(line[-1], RIGHT) tex = TexMobject("f(x)") tex.set_color(YELLOW) tex.match_width(arrow) tex.next_to(arrow, UP, SMALL_BUFF) return VGroup(arrow, tex) for x in range(2): arrow = get_arrow() line.add(arrow) new_term = DecimalNumber( func(last_term.number), **decimal_kwargs ) new_term.next_to(arrow[0], RIGHT) last_term = new_term line.add(new_term) line.add(get_arrow()) line.add(TexMobject("\\dots\\dots").next_to(line[-1][0], RIGHT)) num_phi_mob = DecimalNumber(phi, **decimal_kwargs) line.add(num_phi_mob.next_to(line[-1], RIGHT)) line.move_to(DOWN) rects = VGroup(*[ SurroundingRectangle(mob) for mob in line[0], line[1:-1], line[-1] ]) rects.set_stroke(BLUE, 2) braces = VGroup(*[ Brace(rect, DOWN, buff=SMALL_BUFF) for rect in rects ]) braces.set_color_by_gradient(GREEN, YELLOW) brace_texts = VGroup(*[ brace.get_text(text).scale(0.75, about_edge=UP) for brace, text in zip(braces, [ "Arbitrary \\\\ starting \\\\ value", "Repeatedly apply $f(x)$", "$\\varphi$ \\\\ ``Golden ratio''" ]) ]) var_phi_mob = brace_texts[2][0] var_phi_mob.scale(2, about_edge=UP).set_color(YELLOW) brace_texts[2][1:].next_to(var_phi_mob, DOWN, MED_SMALL_BUFF) # Animations self.add(line[0]) self.play( GrowFromCenter(braces[0]), Write(brace_texts[0]) ) self.wait() self.play( GrowFromEdge(line[1], LEFT), FadeIn(braces[1]), FadeIn(brace_texts[1]), ) self.play(ReplacementTransform(line[0].copy(), line[2])) self.wait() self.play(GrowFromEdge(line[3], LEFT)) self.play(ReplacementTransform(line[2].copy(), line[4])) self.wait() self.play(GrowFromEdge(line[5], LEFT)) self.play(LaggedStart(GrowFromCenter, line[6])) self.wait() self.play(FadeIn(line[7])) self.play( GrowFromCenter(braces[2]), FadeIn(brace_texts[2]), ) self.wait() class ShowPhiAsFixedPoint(ShowRepeatedApplication): CONFIG = { "title_color": WHITE, } def construct(self): self.add_func_title() self.show_fixed_point_formulas() def show_fixed_point_formulas(self): var_formula = TexMobject( "{1 \\over", "\\varphi}", "=", "\\varphi", "-", "1", ) var_formula.set_color_by_tex("\\varphi", YELLOW) var_formula.move_to(UP) alt_var_formula = TexMobject( "1", "+", "{1 \\over", "\\varphi}", "=", "\\varphi", ) alt_var_formula.set_color_by_tex("\\varphi", YELLOW) alt_var_formula.move_to(var_formula) num_formula = TexMobject( "{1 \\over 1.618\\dots} = 0.618\\dots", tex_to_color_map={ "1.618\\dots": YELLOW, "0.618\\dots": YELLOW, "=": WHITE, } ) num_formula.next_to(var_formula, DOWN, LARGE_BUFF) fixed_phi_formula = VGroup( TexMobject("f(", "\\varphi", ")"), TexMobject("="), TexMobject("\\quad", "\\varphi", "\\quad"), ) fixed_phi_formula.arrange_submobjects(RIGHT) for mob in fixed_phi_formula: mob.set_color_by_tex("\\varphi", YELLOW) fixed_phi_formula.next_to(alt_var_formula, UP, LARGE_BUFF) self.add(num_formula) self.wait() self.play(Write(var_formula)) self.wait() self.play(*[ ReplacementTransform( part, alt_var_formula.get_part_by_tex( tex_string if tex_string is not "-" else "+", substring=False ), path_arc=90 * DEGREES, run_time=2 ) for part in var_formula for tex_string in [part.get_tex_string()] ]) self.wait() self.play( ReplacementTransform( self.title[:2].copy(), fixed_phi_formula[:2], path_arc=90 * DEGREES ), ) self.play( ReplacementTransform( fixed_phi_formula[0][1].copy(), fixed_phi_formula[2][1], path_arc=-180 * DEGREES ) ) self.wait() group = VGroup( self.title, fixed_phi_formula, alt_var_formula, num_formula ) self.play( group.arrange_submobjects, DOWN, {"buff": LARGE_BUFF, "aligned_edge": LEFT}, group.to_corner, UL ) self.wait() class NumericalPlayFromOne(ExternallyAnimatedScene): pass class NumericalPlayFromTau(ExternallyAnimatedScene): pass class NumericalPlayFromNegPhi(ExternallyAnimatedScene): pass class NumericalPlayOnNumberLineFromOne(Scene): CONFIG = { "starting_value": 1, "n_jumps": 10, "func": lambda x: 1 + 1.0 / x, "number_line_config": { "x_min": 0, "x_max": 2, "unit_size": 6, "tick_frequency": 0.25, "numbers_with_elongated_ticks": [0, 1, 2] } } def construct(self): self.add_number_line() self.add_phi_label() self.add_title() self.bounce_around() def add_number_line(self): number_line = NumberLine(**self.number_line_config) number_line.move_to(2 * DOWN) number_line.add_numbers() self.add(number_line) self.number_line = number_line def add_phi_label(self): number_line = self.number_line phi_point = number_line.number_to_point( (1 + np.sqrt(5)) / 2 ) phi_dot = Dot(phi_point, color=YELLOW) arrow = Vector(DL) arrow.next_to(phi_point, UR, SMALL_BUFF) phi_label = TexMobject("\\varphi = 1.618\\dots") phi_label.set_color(YELLOW) phi_label.next_to(arrow.get_start(), UP, SMALL_BUFF) self.add(phi_dot, phi_label, arrow) def add_title(self): title = TexMobject("x \\rightarrow 1 + \\frac{1}{x}") title.to_corner(UL) self.add(title) def bounce_around(self): number_line = self.number_line value = self.starting_value point = number_line.number_to_point(value) dot = Dot(point) dot.set_fill(RED, opacity=0.8) arrow = Vector(DR) arrow.next_to(point, UL, buff=SMALL_BUFF) arrow.match_color(dot) start_here = TextMobject("Start here") start_here.next_to(arrow.get_start(), UP, SMALL_BUFF) start_here.match_color(dot) self.play( FadeIn(start_here), GrowArrow(arrow), GrowFromPoint(dot, arrow.get_start()) ) self.play( FadeOut(start_here), FadeOut(arrow) ) self.wait() for x in range(self.n_jumps): new_value = self.func(value) new_point = number_line.number_to_point(new_value) if new_value - value > 0: path_arc = -120 * DEGREES else: path_arc = -120 * DEGREES arc = Line( point, new_point, path_arc=path_arc, buff=SMALL_BUFF ) self.play( ShowCreationThenDestruction(arc, run_time=1.5), ApplyMethod( dot.move_to, new_point, path_arc=path_arc ), ) self.wait(0.5) value = new_value point = new_point class NumericalPlayOnNumberLineFromTau(NumericalPlayOnNumberLineFromOne): CONFIG = { "starting_value": TAU, "number_line_config": { "x_min": 0, "x_max": 7, "unit_size": 2, } } class NumericalPlayOnNumberLineFromMinusPhi(NumericalPlayOnNumberLineFromOne): CONFIG = { "starting_value": -0.61803, "number_line_config": { "x_min": -3, "x_max": 3, "unit_size": 2, "tick_frequency": 0.25, }, "n_jumps": 25, } def add_phi_label(self): NumericalPlayOnNumberLineFromOne.add_phi_label(self) number_line = self.number_line new_point = number_line.number_to_point( (1 - np.sqrt(5)) / 2 ) arrow = Vector(DR) arrow.next_to(new_point, UL, SMALL_BUFF) arrow.set_color(RED) new_label = TexMobject("-\\frac{1}{\\varphi} = -0.618\\dots") new_label.set_color(RED) new_label.next_to(arrow.get_start(), UP, SMALL_BUFF) new_label.shift(RIGHT) self.add(new_label, arrow) class RepeatedApplicationGraphically(GraphOnePlusOneOverX, PiCreatureScene): CONFIG = { "starting_value": 1, "n_jumps": 5, "n_times_to_show_identity_property": 2, } def setup(self): GraphOnePlusOneOverX.setup(self) PiCreatureScene.setup(self) def construct(self): self.setup_axes() self.draw_graphs(animate=False) self.draw_spider_web() def create_pi_creature(self): randy = Randolph(height=2) randy.flip() randy.to_corner(DR) return randy def get_new_randy_mode(self): randy = self.pi_creature if not hasattr(self, "n_mode_changes"): self.n_mode_changes = 0 else: self.n_mode_changes += 1 if self.n_mode_changes % 3 != 0: return randy.get_mode() return random.choice([ "confused", "erm", "maybe" ]) def draw_spider_web(self): randy = self.pi_creature func = self.func_graph[0].underlying_function x_val = self.starting_value curr_output = 0 dot = Dot(color=RED, fill_opacity=0.7) dot.move_to(self.coords_to_point(x_val, curr_output)) self.play(FadeInAndShiftFromDirection(dot, 2 * UR)) self.wait() for n in range(self.n_jumps): new_output = func(x_val) func_graph_point = self.coords_to_point(x_val, new_output) id_graph_point = self.coords_to_point(new_output, new_output) v_line = DashedLine(dot.get_center(), func_graph_point) h_line = DashedLine(func_graph_point, id_graph_point) curr_output = new_output x_val = new_output for line in v_line, h_line: line_end = line.get_end() self.play( ShowCreation(line), dot.move_to, line_end, randy.change, self.get_new_randy_mode() ) self.wait() if n < self.n_times_to_show_identity_property: x_point = self.coords_to_point(new_output, 0) y_point = self.coords_to_point(0, new_output) lines = VGroup(*[ Line(dot.get_center(), point) for point in x_point, y_point ]) lines.set_color(YELLOW) self.play(ShowCreationThenDestruction( lines, run_time=2 )) self.wait(0.25) class RepeatedApplicationGraphicallyFromNegPhi(RepeatedApplicationGraphically): CONFIG = { "starting_value": -0.61, "n_jumps": 13, "n_times_to_show_identity_property": 0, } class LetsSwitchToTheTransformationalView(TeacherStudentsScene): def construct(self): self.teacher_says( "Lose the \\\\ graphs!", target_mode="hooray" ) self.change_student_modes("hooray", "erm", "surprised") self.wait(5) class AnalyzeFunctionWithTransformations(NumberlineTransformationScene): CONFIG = { "input_line_zero_point": 0.5 * UP, "output_line_zero_point": 2 * DOWN, "func": lambda x: 1 + 1.0 / x, "num_initial_applications": 10, "num_repeated_local_applications": 7, "zoomed_display_width": 3.5, "zoomed_display_height": 2, "default_mapping_animation_config": {}, } def construct(self): self.add_function_title() self.repeatedly_apply_function() self.show_phi_and_phi_bro() self.zoom_in_on_phi() self.zoom_in_on_phi_bro() def setup_number_lines(self): NumberlineTransformationScene.setup_number_lines(self) for line in self.input_line, self.output_line: VGroup(line.main_line, line.tick_marks).set_stroke(width=2) def add_function_title(self): title = TexMobject("f(x)", "=", "1 + \\frac{1}{x}") title.to_edge(UP) self.add(title) self.title = title def repeatedly_apply_function(self): input_zero_point = self.input_line.number_to_point(0) output_zero_point = self.output_line.number_to_point(0) sample_dots = self.get_sample_dots( delta_x=0.05, dot_radius=0.05, x_min=-10, x_max=10, ) sample_dots.set_stroke(BLACK, 0.5) point_func = self.number_func_to_point_func(self.func) arrows = VGroup(*[ Arrow( c, point_func(c), buff=SMALL_BUFF, rectangular_stem_width=0.02, tip_length=0.15 ) for c in map(Mobject.get_center, sample_dots) if np.linalg.norm(c - input_zero_point) > 0.3 ]) arrows.set_fill(WHITE, 0.75) arrows.set_stroke(BLACK, 0.5) self.play(LaggedStart( FadeInAndShiftFromDirection, sample_dots, lambda m: (m, UP) )) self.play(LaggedStart(GrowArrow, arrows)) self.wait() for x in range(self.num_initial_applications): self.apply_function( self.func, apply_function_to_number_line=False, sample_dots=sample_dots ) self.wait() shift_vect = input_zero_point - output_zero_point shift_vect[0] = 0 lower_output_line = self.output_line.copy() upper_output_line = self.output_line.copy() lower_output_line.shift(-shift_vect) lower_output_line.fade(1) self.remove(self.output_line) self.play( ReplacementTransform(lower_output_line, self.output_line), upper_output_line.shift, shift_vect, upper_output_line.fade, 1, sample_dots.shift, shift_vect, ) self.remove(upper_output_line) self.wait() self.play(FadeOut(sample_dots)) self.all_arrows = arrows def show_phi_and_phi_bro(self): phi = (1 + np.sqrt(5)) / 2 phi_bro = (1 - np.sqrt(5)) / 2 input_phi_point = self.input_line.number_to_point(phi) output_phi_point = self.output_line.number_to_point(phi) input_phi_bro_point = self.input_line.number_to_point(phi_bro) output_phi_bro_point = self.output_line.number_to_point(phi_bro) tick = Line(UP, DOWN) tick.set_stroke(YELLOW, 3) tick.match_height(self.input_line.tick_marks) phi_tick = tick.copy().move_to(input_phi_point, DOWN) phi_bro_tick = tick.copy().move_to(input_phi_bro_point, DOWN) VGroup(phi_tick, phi_bro_tick).shift(SMALL_BUFF * DOWN) phi_label = TexMobject("1.618\\dots") phi_label.next_to(phi_tick, UP) phi_bro_label = TexMobject("-0.618\\dots") phi_bro_label.next_to(phi_bro_tick, UP) VGroup(phi_label, phi_bro_label).set_color(YELLOW) arrow_kwargs = { "buff": SMALL_BUFF, "rectangular_stem_width": 0.035, "tip_length": 0.2, } phi_arrow = Arrow(phi_tick, output_phi_point, **arrow_kwargs) phi_bro_arrow = Arrow(phi_bro_tick, output_phi_bro_point, **arrow_kwargs) self.play( LaggedStart( ApplyMethod, self.all_arrows, lambda m: (m.set_fill, {"opacity": 0.1}) ), FadeIn(phi_arrow), FadeIn(phi_bro_arrow), ) self.play( Write(phi_label), GrowFromCenter(phi_tick) ) self.play( Write(phi_bro_label), GrowFromCenter(phi_bro_tick) ) self.set_variables_as_attrs( input_phi_point, output_phi_point, input_phi_bro_point, output_phi_bro_point, phi_label, phi_tick, phi_bro_label, phi_bro_tick, phi_arrow, phi_bro_arrow ) def zoom_in_on_phi(self): phi = (1 + np.sqrt(5)) / 2 # phi_point = self.get_input_point(phi) local_sample_dots = self.get_local_sample_dots( phi, dot_radius=0.005, sample_radius=1 ) local_coordinate_values = [1.55, 1.6, 1.65, 1.7] # zcbr = self.zoomed_camera_background_rectangle zcbr_group = self.zoomed_camera_background_rectangle_group zcbr_group.add(self.phi_tick) title = self.title deriv_text = TexMobject( "|", "\\frac{df}{dx}(\\varphi)", "|", "< 1", tex_to_color_map={"\\varphi": YELLOW} ) deriv_text.get_parts_by_tex("|").match_height( deriv_text, stretch=True ) deriv_text.move_to(title, UP) approx_value = TexMobject("\\approx |%.2f|" % (-1 / phi**2)) approx_value.move_to(deriv_text) deriv_text_lhs = deriv_text[:-1] deriv_text_rhs = deriv_text[-1] self.zoom_in_on_input( phi, local_sample_dots=local_sample_dots, local_coordinate_values=local_coordinate_values ) self.wait() self.apply_function( self.func, apply_function_to_number_line=False, local_sample_dots=local_sample_dots, target_coordinate_values=local_coordinate_values, ) self.wait() self.play( FadeInFromDown(deriv_text_lhs), FadeInFromDown(deriv_text_rhs), title.to_corner, UL ) self.wait() self.play( deriv_text_lhs.next_to, approx_value, LEFT, deriv_text_rhs.next_to, approx_value, RIGHT, FadeIn(approx_value) ) self.wait() for n in range(self.num_repeated_local_applications): self.apply_function( self.func, apply_function_to_number_line=False, local_sample_dots=local_sample_dots, path_arc=60 * DEGREES, run_time=2 ) self.deriv_text = VGroup( deriv_text_lhs, deriv_text_rhs, approx_value ) def zoom_in_on_phi_bro(self): zcbr = self.zoomed_camera_background_rectangle # zcbr_group = self.zoomed_camera_background_rectangle_group zoomed_frame = self.zoomed_camera.frame phi_bro = (1 - np.sqrt(5)) / 2 # phi_bro_point = self.get_input_point(phi_bro) local_sample_dots = self.get_local_sample_dots(phi_bro) local_coordinate_values = [-0.65, -0.6, -0.55] deriv_text = TexMobject( "\\left| \\frac{df}{dx}\\left(\\frac{-1}{\\varphi}\\right) \\right|", "\\approx |%.2f|" % (-1 / (phi_bro**2)), "> 1" ) deriv_text.move_to(self.deriv_text, UL) deriv_text[0][10:14].set_color(YELLOW) self.play( zoomed_frame.scale_to_fit_height, 4, zoomed_frame.center, self.deriv_text.fade, 1, run_time=2 ) self.wait() zcbr.set_fill(opacity=0) self.zoom_in_on_input( phi_bro, local_sample_dots=local_sample_dots, local_coordinate_values=local_coordinate_values, zoom_factor=self.zoom_factor, first_anim_kwargs={"run_time": 2}, ) self.wait() self.play(FadeInFromDown(deriv_text)) self.wait() zcbr.set_fill(opacity=1) self.apply_function( self.func, apply_function_to_number_line=False, local_sample_dots=local_sample_dots, target_coordinate_values=local_coordinate_values, ) self.wait() for n in range(self.num_repeated_local_applications): self.apply_function( self.func, apply_function_to_number_line=False, local_sample_dots=local_sample_dots, path_arc=20 * DEGREES, run_time=2, ) class StabilityAndInstability(AnalyzeFunctionWithTransformations): CONFIG = { "num_initial_applications": 0, } def construct(self): self.force_skipping() self.add_function_title() self.repeatedly_apply_function() self.show_phi_and_phi_bro() self.revert_to_original_skipping_status() self.label_stability() def label_stability(self): self.title.to_corner(UL) stable_label = TextMobject("Stable fixed point") unstable_label = TextMobject("Unstable fixed point") labels = VGroup(stable_label, unstable_label) labels.scale(0.8) stable_label.next_to(self.phi_label, UP, aligned_edge=ORIGIN) unstable_label.next_to(self.phi_bro_label, UP, aligned_edge=ORIGIN) phi_point = self.input_phi_point phi_bro_point = self.input_phi_bro_point arrow_groups = VGroup() for point in phi_point, phi_bro_point: arrows = VGroup(*filter( lambda a: np.linalg.norm(a.get_start() - point) < 0.75, self.all_arrows )).copy() arrows.set_fill(PINK, 1) arrows.second_anim = LaggedStart( ApplyMethod, arrows, lambda m: (m.set_fill, YELLOW, 1), rate_func=there_and_back_with_pause, lag_ratio=0.7, run_time=2, ) arrows.anim = AnimationGroup(*map(GrowArrow, arrows)) arrow_groups.add(arrows) phi_arrows, phi_bro_arrows = arrow_groups self.add_foreground_mobjects(self.phi_arrow, self.phi_bro_arrow) self.play( Write(stable_label), phi_arrows.anim, ) self.play(phi_arrows.second_anim) self.play( Write(unstable_label), phi_bro_arrows.anim, ) self.play(phi_bro_arrows.second_anim) self.wait() class NotBetterThanGraphs(TeacherStudentsScene): def construct(self): self.student_says( "Um, yeah, I'll stick \\\\ with graphs thanks", target_mode="sassy", ) self.play( self.teacher.change, "guilty", self.get_student_changes("sad", "sassy", "hesitant") ) self.wait(2) self.play( RemovePiCreatureBubble(self.students[1]), self.teacher.change, "raise_right_hand" ) self.change_all_student_modes( "confused", look_at_arg=self.screen ) self.wait(3) self.teacher_says( "You must flex those \\\\ conceptual muscles", added_anims=[self.get_student_changes( *3 * ["thinking"], look_at_arg=self.teacher.eyes )] ) self.wait(3) class TopicsAfterSingleVariable(PiCreatureScene, MoreTopics): CONFIG = { "pi_creatures_start_on_screen": False, } def construct(self): MoreTopics.construct(self) self.show_horror() self.zero_in_on_complex_analysis() def create_pi_creatures(self): creatures = VGroup(*[ PiCreature(color=color) for color in [BLUE_E, BLUE_C, BLUE_D] ]) creatures.arrange_submobjects(RIGHT, buff=LARGE_BUFF) creatures.scale(0.5) creatures.to_corner(DR) return creatures def show_horror(self): creatures = self.get_pi_creatures() modes = ["horrified", "tired", "horrified"] for creature, mode in zip(creatures, modes): creature.generate_target() creature.target.change(mode, self.other_topics) creatures.fade(1) self.play(LaggedStart(MoveToTarget, creatures)) self.wait(2) def zero_in_on_complex_analysis(self): creatures = self.get_pi_creatures() complex_analysis = self.other_topics[1] self.other_topics.remove(complex_analysis) self.play( complex_analysis.scale, 1.25, complex_analysis.center, complex_analysis.to_edge, UP, LaggedStart(FadeOut, self.other_topics), LaggedStart(FadeOut, self.lines), FadeOut(self.calculus), *[ ApplyMethod(creature.change, "pondering") for creature in creatures ] ) self.wait(4) class ComplexAnalysisOverlay(Scene): def construct(self): words = TextMobject("Complex analysis") words.scale(1.25) words.to_edge(UP) words.add_background_rectangle() self.add(words) self.wait() class CompelxAnalyticFluidFlow(ComplexTransformationScene, MovingCameraScene): CONFIG = { "num_anchors_to_add_per_line": 200, "plane_config": {"y_radius": 8} } def setup(self): MovingCameraScene.setup(self) ComplexTransformationScene.setup(self) def construct(self): self.camera.frame.shift(2 * UP) self.camera.frame.scale(0.5, about_point=ORIGIN) plane = NumberPlane( x_radius=15, y_radius=25, y_unit_size=0.5, secondary_line_ratio=0, ) plane.next_to(ORIGIN, UP, buff=0.001) horizontal_lines = VGroup(*filter( lambda l: np.abs(l.get_center()[0]) < 0.1, list(plane.main_lines) + [plane.axes[0]] )) plane.set_stroke(MAROON_B, width=2) horizontal_lines.set_stroke(BLUE, width=2) for line in horizontal_lines: # To lag the paths of the droplets line.scale(1 + random.random()) self.prepare_for_transformation(plane) self.add_transformable_mobjects(plane) self.background.set_stroke(width=2) for label in self.background.coordinate_labels: label.set_stroke(width=0) label.scale(0.75, about_edge=UR) words = TextMobject("Flow near \\\\", "a wall") words.scale(0.75) words.add_background_rectangle_to_submobjects() words.next_to(0.75 * UP, LEFT, MED_LARGE_BUFF) equation = TexMobject("z \\rightarrow z^{1/2}") equation.scale(0.75) equation.add_background_rectangle() equation.next_to(words, UP) self.apply_complex_function( lambda x: x**(1. / 2), added_anims=[Write(equation)] ) self.play(Write(words, run_time=1)) dots = VGroup() num_dots_per_line = 50 for x in range(num_dots_per_line): for line in horizontal_lines: dot = Dot(radius=0.025) opacity = 1.0 - x / float(num_dots_per_line) dot.set_fill(opacity=opacity) dot.path = line dots.add(dot) dots.set_color_by_gradient(BLUE_B, BLUE_D) self.play(LaggedStart( MoveAlongPath, dots, lambda d: (d, d.path), run_time=3, lag_ratio=0.9 ))