from manimlib.imports import * # Quick note to anyone coming to this file with the # intent of recreating animations from the video. Some # of these, especially those involving AnimatedStreamLines, # can take an extremely long time to run, but much of the # computational cost is just for giving subtle little effects # which don't matter too much. Switching the line_anim_class # to ShowPassingFlash will give significant speedups, as will # increasing the values of delta_x and delta_y in sampling for # the stream lines. Certainly while developing, things were not # run at production quality. FOX_COLOR = "#DF7F20" RABBIT_COLOR = "#C6D6EF" # Warning, this file uses ContinualChangingDecimal, # which has since been been deprecated. Use a mobject # updater instead # Helper functions def joukowsky_map(z): if z == 0: return 0 return z + fdiv(1, z) def inverse_joukowsky_map(w): u = 1 if w.real >= 0 else -1 return (w + u * np.sqrt(w**2 - 4)) / 2 def derivative(func, dt=1e-7): return lambda z: (func(z + dt) - func(z)) / dt def negative_gradient(potential_func, dt=1e-7): def result(p): output = potential_func(p) dx = dt * RIGHT dy = dt * UP dz = dt * OUT return -np.array([ (potential_func(p + dx) - output) / dt, (potential_func(p + dy) - output) / dt, (potential_func(p + dz) - output) / dt, ]) return result def divergence(vector_func, dt=1e-7): def result(point): value = vector_func(point) return sum([ (vector_func(point + dt * RIGHT) - value)[i] / dt for i, vect in enumerate([RIGHT, UP, OUT]) ]) return result def two_d_curl(vector_func, dt=1e-7): def result(point): value = vector_func(point) return op.add( (vector_func(point + dt * RIGHT) - value)[1] / dt, -(vector_func(point + dt * UP) - value)[0] / dt, ) return result def cylinder_flow_vector_field(point, R=1, U=1): z = R3_to_complex(point) # return complex_to_R3(1.0 / derivative(joukowsky_map)(z)) return complex_to_R3(derivative(joukowsky_map)(z).conjugate()) def cylinder_flow_magnitude_field(point): return get_norm(cylinder_flow_vector_field(point)) def vec_tex(s): return "\\vec{\\textbf{%s}}" % s def four_swirls_function(point): x, y = point[:2] result = (y**3 - 4 * y) * RIGHT + (x**3 - 16 * x) * UP result *= 0.05 norm = get_norm(result) if norm == 0: return result # result *= 2 * sigmoid(norm) / norm return result def get_force_field_func(*point_strength_pairs, **kwargs): radius = kwargs.get("radius", 0.5) def func(point): result = np.array(ORIGIN) for center, strength in point_strength_pairs: to_center = center - point norm = get_norm(to_center) if norm == 0: continue elif norm < radius: to_center /= radius**3 elif norm >= radius: to_center /= norm**3 to_center *= -strength result += to_center return result return func def get_charged_particles(color, sign, radius=0.1): result = Circle( stroke_color=WHITE, stroke_width=0.5, fill_color=color, fill_opacity=0.8, radius=radius ) sign = TexMobject(sign) sign.set_stroke(WHITE, 1) sign.set_width(0.5 * result.get_width()) sign.move_to(result) result.add(sign) return result def get_proton(radius=0.1): return get_charged_particles(RED, "+", radius) def get_electron(radius=0.05): return get_charged_particles(BLUE, "-", radius) def preditor_prey_vector_field(point): alpha = 30.0 beta = 1.0 gamma = 30.0 delta = 1.0 x, y = point[:2] result = 0.05 * np.array([ alpha * x - beta * x * y, delta * x * y - gamma * y, 0, ]) return rotate(result, 1 * DEGREES) # Mobjects # TODO, this is untested after turning it from a # ContinualAnimation into a VGroup class JigglingSubmobjects(VGroup): CONFIG = { "amplitude": 0.05, "jiggles_per_second": 1, } def __init__(self, group, **kwargs): VGroup.__init__(self, **kwargs) for submob in group.submobjects: submob.jiggling_direction = rotate_vector( RIGHT, np.random.random() * TAU, ) submob.jiggling_phase = np.random.random() * TAU self.add(submob) self.add_updater(lambda m, dt: m.update(dt)) def update(self, dt): for submob in self.submobjects: submob.jiggling_phase += dt * self.jiggles_per_second * TAU submob.shift( self.amplitude * submob.jiggling_direction * np.sin(submob.jiggling_phase) * dt ) # Scenes class Introduction(MovingCameraScene): CONFIG = { "stream_lines_config": { "start_points_generator_config": { "delta_x": 1.0 / 8, "delta_y": 1.0 / 8, "y_min": -8.5, "y_max": 8.5, } }, "vector_field_config": {}, "virtual_time": 3, } def construct(self): # Divergence def div_func(p): return p / 3 div_vector_field = VectorField( div_func, **self.vector_field_config ) stream_lines = StreamLines( div_func, **self.stream_lines_config ) stream_lines.shuffle() div_title = self.get_title("Divergence") self.add(div_vector_field) self.play( LaggedStartMap(ShowPassingFlash, stream_lines), FadeIn(div_title[0]), *list(map(GrowFromCenter, div_title[1])) ) # Curl def curl_func(p): return rotate_vector(p / 3, 90 * DEGREES) curl_vector_field = VectorField( curl_func, **self.vector_field_config ) stream_lines = StreamLines( curl_func, **self.stream_lines_config ) stream_lines.shuffle() curl_title = self.get_title("Curl") self.play( ReplacementTransform(div_vector_field, curl_vector_field), ReplacementTransform( div_title, curl_title, path_arc=90 * DEGREES ), ) self.play(ShowPassingFlash(stream_lines, run_time=3)) self.wait() def get_title(self, word): title = TextMobject(word) title.scale(2) title.to_edge(UP) title.add_background_rectangle() return title class ShowWritingTrajectory(TeacherStudentsScene): def construct(self): self.add_screen() self.show_meandering_path() self.show_previous_video() def add_screen(self): self.screen.scale(1.4, about_edge=UL) self.add(self.screen) def show_meandering_path(self): solid_path = VMobject().set_points_smoothly([ 3 * UP + 2 * RIGHT, 2 * UP + 4 * RIGHT, 3.5 * UP + 3.5 * RIGHT, 2 * UP + 3 * RIGHT, 3 * UP + 7 * RIGHT, 3 * UP + 5 * RIGHT, UP + 6 * RIGHT, 2 * RIGHT, 2 * UP + 2 * RIGHT, self.screen.get_right() ]) step = 1.0 / 80 dashed_path = VGroup(*[ VMobject().pointwise_become_partial( solid_path, x, x + step / 2 ) for x in np.arange(0, 1 + step, step) ]) arrow = Arrow( solid_path.points[-2], solid_path.points[-1], buff=0 ) dashed_path.add(arrow.tip) dashed_path.set_color_by_gradient(BLUE, YELLOW) self.play( ShowCreation( dashed_path, rate_func=bezier([0, 0, 1, 1]), run_time=5, ), self.teacher.change, "raise_right_hand", self.get_student_changes(*["sassy"] * 3) ) self.play( LaggedStartMap( ApplyMethod, dashed_path, lambda m: (m.scale, 0), remover=True ), self.teacher.change, "tease", self.get_student_changes( *["pondering"] * 3, look_at_arg=self.screen ) ) def show_previous_video(self): screen = self.screen arrow = Vector(LEFT, color=WHITE) arrow.next_to(screen, RIGHT) prev_words = TextMobject("Previous video") prev_words.next_to(arrow, RIGHT) screen.generate_target() screen.target.set_height(3.75) screen.target.to_corner(UR) complex_words = TextMobject("Complex derivatives") complex_words.next_to( screen.target, LEFT, buff=2 * SMALL_BUFF + arrow.get_length() ) self.play( GrowArrow(arrow), Write(prev_words) ) self.wait(3) self.play( arrow.flip, arrow.next_to, screen.target, LEFT, SMALL_BUFF, MoveToTarget(screen), FadeOut(prev_words), Write(complex_words), self.teacher.change, "raise_right_hand", path_arc=30 * DEGREES ) self.change_student_modes("erm", "sassy", "confused") self.look_at(screen) self.wait(2) self.change_student_modes("pondering", "confused", "sassy") self.wait(2) bubble = self.teacher.get_bubble( bubble_class=SpeechBubble, height=3, width=5 ) complex_words.generate_target() complex_words.target.move_to(bubble.get_bubble_center()) # self.play( # FadeOut(screen), # FadeOut(arrow), # ShowCreation(bubble), # self.teacher.change, "hooray", # MoveToTarget(complex_words), # ) s0 = self.students[0] s0.target_center = s0.get_center() def update_s0(s0, dt): s0.target_center += dt * LEFT * 0.5 s0.move_to(s0.target_center) self.add(Mobject.add_updater(s0, update_s0)) self.change_student_modes("tired", "horrified", "sad") self.play(s0.look, LEFT) self.wait(4) class TestVectorField(Scene): CONFIG = { "func": cylinder_flow_vector_field, "flow_time": 15, } def construct(self): lines = StreamLines( four_swirls_function, virtual_time=3, min_magnitude=0, max_magnitude=2, ) self.add(AnimatedStreamLines( lines, line_anim_class=ShowPassingFlash )) self.wait(10) class CylinderModel(Scene): CONFIG = { "production_quality_flow": True, "vector_field_func": cylinder_flow_vector_field, } def construct(self): self.add_plane() self.add_title() self.show_numbers() self.show_contour_lines() self.show_flow() self.apply_joukowsky_map() def add_plane(self): self.plane = ComplexPlane() self.plane.add_coordinates() self.plane.coordinate_labels.submobjects.pop(-1) self.add(self.plane) def add_title(self): title = TextMobject("Complex Plane") title.to_edge(UP, buff=MED_SMALL_BUFF) title.add_background_rectangle() self.title = title self.add(title) def show_numbers(self): run_time = 5 unit_circle = self.unit_circle = Circle( radius=self.plane.unit_size, fill_color=BLACK, fill_opacity=0, stroke_color=YELLOW ) dot = Dot() dot_update = UpdateFromFunc( dot, lambda d: d.move_to(unit_circle.point_from_proportion(1)) ) exp_tex = TexMobject("e^{", "0.00", "i}") zero = exp_tex.get_part_by_tex("0.00") zero.fade(1) exp_tex_update = UpdateFromFunc( exp_tex, lambda et: et.next_to(dot, UR, SMALL_BUFF) ) exp_decimal = DecimalNumber( 0, num_decimal_places=2, include_background_rectangle=True, color=YELLOW ) exp_decimal.replace(zero) exp_decimal_update = ChangeDecimalToValue( exp_decimal, TAU, position_update_func=lambda mob: mob.move_to(zero), run_time=run_time, ) sample_numbers = [ complex(-5, 2), complex(2, 2), complex(3, 1), complex(-5, -2), complex(-4, 1), ] sample_labels = VGroup() for z in sample_numbers: sample_dot = Dot(self.plane.number_to_point(z)) sample_label = DecimalNumber( z, num_decimal_places=0, include_background_rectangle=True, ) sample_label.next_to(sample_dot, UR, SMALL_BUFF) sample_labels.add(VGroup(sample_dot, sample_label)) self.play( ShowCreation(unit_circle, run_time=run_time), VFadeIn(exp_tex), UpdateFromAlphaFunc( exp_decimal, lambda ed, a: ed.set_fill(opacity=a) ), dot_update, exp_tex_update, exp_decimal_update, LaggedStartMap( FadeIn, sample_labels, remover=True, rate_func=there_and_back, run_time=run_time, ) ) self.play( FadeOut(exp_tex), FadeOut(exp_decimal), FadeOut(dot), unit_circle.set_fill, BLACK, {"opacity": 1}, ) self.wait() def show_contour_lines(self): warped_grid = self.warped_grid = self.get_warpable_grid() h_line = Line(3 * LEFT, 3 * RIGHT, color=WHITE) # Hack func_label = self.get_func_label() self.remove(self.plane) self.add_foreground_mobjects(self.unit_circle, self.title) self.play( warped_grid.apply_complex_function, inverse_joukowsky_map, Animation(h_line, remover=True) ) self.play(Write(func_label)) self.add_foreground_mobjects(func_label) self.wait() def show_flow(self): stream_lines = self.get_stream_lines() stream_lines_copy = stream_lines.copy() stream_lines_copy.set_stroke(YELLOW, 1) stream_lines_animation = self.get_stream_lines_animation( stream_lines ) tiny_buff = 0.0001 v_lines = VGroup(*[ Line( UP, ORIGIN, path_arc=0, n_arc_anchors=20, ).shift(x * RIGHT) for x in np.linspace(0, 1, 5) ]) v_lines.match_background_image_file(stream_lines) fast_lines, slow_lines = [ VGroup(*[ v_lines.copy().next_to(point, vect, tiny_buff) for point, vect in it.product(h_points, [UP, DOWN]) ]) for h_points in [ [0.5 * LEFT, 0.5 * RIGHT], [2 * LEFT, 2 * RIGHT], ] ] for lines in fast_lines, slow_lines: lines.apply_complex_function(inverse_joukowsky_map) self.add(stream_lines_animation) self.wait(7) self.play( ShowCreationThenDestruction( stream_lines_copy, lag_ratio=0, run_time=3, ) ) self.wait() self.play(ShowCreation(fast_lines)) self.wait(2) self.play(ReplacementTransform(fast_lines, slow_lines)) self.wait(3) self.play( FadeOut(slow_lines), VFadeOut(stream_lines_animation.mobject) ) self.remove(stream_lines_animation) def apply_joukowsky_map(self): shift_val = 0.1 * LEFT + 0.2 * UP scale_factor = get_norm(RIGHT - shift_val) movers = VGroup(self.warped_grid, self.unit_circle) self.unit_circle.insert_n_curves(50) stream_lines = self.get_stream_lines() stream_lines.scale(scale_factor) stream_lines.shift(shift_val) stream_lines.apply_complex_function(joukowsky_map) self.play( movers.scale, scale_factor, movers.shift, shift_val, ) self.wait() self.play( movers.apply_complex_function, joukowsky_map, ShowCreationThenFadeAround(self.func_label), run_time=2 ) self.add(self.get_stream_lines_animation(stream_lines)) self.wait(20) # Helpers def get_func_label(self): func_label = self.func_label = TexMobject("f(z) = z + 1 / z") func_label.add_background_rectangle() func_label.next_to(self.title, DOWN, MED_SMALL_BUFF) return func_label def get_warpable_grid(self): top_grid = NumberPlane() top_grid.prepare_for_nonlinear_transform() bottom_grid = top_grid.copy() tiny_buff = 0.0001 top_grid.next_to(ORIGIN, UP, buff=tiny_buff) bottom_grid.next_to(ORIGIN, DOWN, buff=tiny_buff) result = VGroup(top_grid, bottom_grid) result.add(*[ Line( ORIGIN, FRAME_WIDTH * RIGHT / 2, color=WHITE, path_arc=0, n_arc_anchors=100, ).next_to(ORIGIN, vect, buff=2) for vect in (LEFT, RIGHT) ]) # This line is a bit of a hack h_line = Line(LEFT, RIGHT, color=WHITE) h_line.set_points([LEFT, LEFT, RIGHT, RIGHT]) h_line.scale(2) result.add(h_line) return result def get_stream_lines(self): func = self.vector_field_func if self.production_quality_flow: delta_x = 0.5 delta_y = 0.1 else: delta_x = 1 # delta_y = 1 delta_y = 0.1 return StreamLines( func, start_points_generator_config={ "x_min": -8, "x_max": -7, "y_min": -4, "y_max": 4, "delta_x": delta_x, "delta_y": delta_y, "n_repeats": 1, "noise_factor": 0.1, }, stroke_width=2, virtual_time=15, ) def get_stream_lines_animation(self, stream_lines): if self.production_quality_flow: line_anim_class = ShowPassingFlashWithThinningStrokeWidth else: line_anim_class = ShowPassingFlash return AnimatedStreamLines( stream_lines, line_anim_class=line_anim_class, ) class OkayNotToUnderstand(Scene): def construct(self): words = TextMobject( "It's okay not to \\\\ understand this just yet." ) morty = Mortimer() morty.change("confused") words.next_to(morty, UP) self.add(morty, words) class ThatsKindOfInteresting(TeacherStudentsScene): def construct(self): self.student_says( "Cool!", target_mode="hooray", student_index=2, added_anims=[self.teacher.change, "happy"] ) self.change_student_modes("happy", "happy") self.wait(2) class ElectricField(CylinderModel, MovingCameraScene): def construct(self): self.add_plane() self.add_title() self.setup_warped_grid() self.show_uniform_field() self.show_moving_charges() self.show_field_lines() def setup_warped_grid(self): warped_grid = self.warped_grid = self.get_warpable_grid() warped_grid.save_state() func_label = self.get_func_label() unit_circle = self.unit_circle = Circle( radius=self.plane.unit_size, stroke_color=YELLOW, fill_color=BLACK, fill_opacity=1 ) self.add_foreground_mobjects(self.title, func_label, unit_circle) self.remove(self.plane) self.play( warped_grid.apply_complex_function, inverse_joukowsky_map, ) self.wait() def show_uniform_field(self): vector_field = self.vector_field = VectorField( lambda p: UP, colors=[BLUE_E, WHITE, RED] ) protons, electrons = groups = [ VGroup(*[method(radius=0.2) for x in range(20)]) for method in (get_proton, get_electron) ] for group in groups: group.arrange(RIGHT, buff=MED_SMALL_BUFF) random.shuffle(group.submobjects) protons.next_to(FRAME_HEIGHT * DOWN / 2, DOWN) electrons.next_to(FRAME_HEIGHT * UP / 2, UP) self.play( self.warped_grid.restore, FadeOut(self.unit_circle), FadeOut(self.title), FadeOut(self.func_label), LaggedStartMap(GrowArrow, vector_field) ) self.remove_foreground_mobjects(self.title, self.func_label) self.wait() for group, vect in (protons, UP), (electrons, DOWN): self.play(LaggedStartMap( ApplyMethod, group, lambda m: (m.shift, (FRAME_HEIGHT + 1) * vect), run_time=3, rate_func=rush_into )) def show_moving_charges(self): unit_circle = self.unit_circle protons = VGroup(*[ get_proton().move_to( rotate_vector(0.275 * n * RIGHT, angle) ) for n in range(4) for angle in np.arange( 0, TAU, TAU / (6 * n) if n > 0 else TAU ) ]) jiggling_protons = JigglingSubmobjects(protons) electrons = VGroup(*[ get_electron().move_to( proton.get_center() + proton.radius * rotate_vector(RIGHT, angle) ) for proton in protons for angle in [np.random.random() * TAU] ]) jiggling_electrons = JigglingSubmobjects(electrons) electrons.generate_target() for electron in electrons.target: y_part = electron.get_center()[1] if y_part > 0: electron.shift(2 * y_part * DOWN) # New vector field def new_electric_field(point): if get_norm(point) < 1: return ORIGIN vect = cylinder_flow_vector_field(point) return rotate_vector(vect, 90 * DEGREES) new_vector_field = VectorField( new_electric_field, colors=self.vector_field.colors ) warped_grid = self.warped_grid self.play(GrowFromCenter(unit_circle)) self.add(jiggling_protons, jiggling_electrons) self.add_foreground_mobjects( self.vector_field, unit_circle, protons, electrons ) self.play( LaggedStartMap(VFadeIn, protons), LaggedStartMap(VFadeIn, electrons), ) self.play( self.camera.frame.scale, 0.7, run_time=3 ) self.play( MoveToTarget(electrons), # More indication? warped_grid.apply_complex_function, inverse_joukowsky_map, Transform( self.vector_field, new_vector_field ), run_time=3 ) self.wait(5) def show_field_lines(self): h_lines = VGroup(*[ Line( 5 * LEFT, 5 * RIGHT, path_arc=0, n_arc_anchors=50, stroke_color=LIGHT_GREY, stroke_width=2, ).shift(y * UP) for y in np.arange(-3, 3.25, 0.25) if y != 0 ]) h_lines.apply_complex_function(inverse_joukowsky_map) for h_line in h_lines: h_line.save_state() voltage = DecimalNumber( 10, num_decimal_places=1, unit="\\, V", color=YELLOW, include_background_rectangle=True, ) vp_prop = 0.1 voltage_point = VectorizedPoint( h_lines[4].point_from_proportion(vp_prop) ) def get_voltage(dummy_arg): y = voltage_point.get_center()[1] return 10 - y voltage_update = voltage.add_updater( lambda d: d.set_value(get_voltage), ) voltage.add_updater( lambda d: d.next_to( voltage_point, UP, SMALL_BUFF ) ) self.play(ShowCreation( h_lines, run_time=2, lag_ratio=0 )) self.add(voltage_update) self.add_foreground_mobjects(voltage) self.play( UpdateFromAlphaFunc( voltage, lambda m, a: m.set_fill(opacity=a) ), h_lines[4].set_stroke, YELLOW, 4, ) for hl1, hl2 in zip(h_lines[4:], h_lines[5:]): self.play( voltage_point.move_to, hl2.point_from_proportion(vp_prop), hl1.restore, hl2.set_stroke, YELLOW, 3, run_time=0.5 ) self.wait(0.5) class AskQuestions(TeacherStudentsScene): def construct(self): div_tex = TexMobject("\\nabla \\cdot", vec_tex("v")) curl_tex = TexMobject("\\nabla \\times", vec_tex("v")) div_name = TextMobject("Divergence") curl_name = TextMobject("Curl") div = VGroup(div_name, div_tex) curl = VGroup(curl_name, curl_tex) for group in div, curl: group[1].set_color_by_tex(vec_tex("v"), YELLOW) group.arrange(DOWN) topics = VGroup(div, curl) topics.arrange(DOWN, buff=LARGE_BUFF) topics.move_to(self.hold_up_spot, DOWN) div.save_state() div.move_to(self.hold_up_spot, DOWN) screen = self.screen self.student_says( "What does fluid flow have \\\\ to do with electricity?", added_anims=[self.teacher.change, "happy"] ) self.wait() self.student_says( "And you mentioned \\\\ complex numbers?", student_index=0, ) self.wait(3) self.play( FadeInFromDown(div), self.teacher.change, "raise_right_hand", FadeOut(self.students[0].bubble), FadeOut(self.students[0].bubble.content), self.get_student_changes(*["pondering"] * 3) ) self.play( FadeInFromDown(curl), div.restore ) self.wait() self.look_at(self.screen) self.wait() self.change_all_student_modes("hooray", look_at_arg=screen) self.wait(3) topics.generate_target() topics.target.to_edge(LEFT, buff=LARGE_BUFF) arrow = TexMobject("\\leftrightarrow") arrow.scale(2) arrow.next_to(topics.target, RIGHT, buff=LARGE_BUFF) screen.next_to(arrow, RIGHT, LARGE_BUFF) complex_analysis = TextMobject("Complex analysis") complex_analysis.next_to(screen, UP) self.play( MoveToTarget(topics), self.get_student_changes( "confused", "sassy", "erm", look_at_arg=topics.target ), self.teacher.change, "pondering", screen ) self.play( Write(arrow), FadeInFromDown(complex_analysis) ) self.look_at(screen) self.wait(6) class ScopeMeiosis(PiCreatureScene): CONFIG = { "default_pi_creature_kwargs": { "flip_at_start": True, "color": GREY_BROWN, }, "default_pi_creature_start_corner": DR, } def construct(self): morty = self.pi_creature section_titles = VGroup(*list(map(TextMobject, [ "Background on div/curl", "Conformal maps", "Conformal map $\\Rightarrow" + "\\text{div}\\textbf{F} = " + "\\text{curl}\\textbf{F} = 0$", "Complex derivatives", ]))) sections = VGroup(*[ VGroup(title, self.get_lines(title, 3)) for title in section_titles ]) sections.arrange( DOWN, buff=MED_LARGE_BUFF, aligned_edge=LEFT ) sections.to_edge(UP) top_title = section_titles[0] lower_sections = sections[1:] self.add(sections) modes = [ "pondering", "pondering", "bump", "bump", "concerned_musician", "concerned_musician", ] for n, mode in zip(list(range(6)), modes): if n % 2 == 1: top_title = lines lines = self.get_lines(top_title, 4) else: lines = self.get_lines(top_title, 6) lower_sections.generate_target() lower_sections.target.next_to( lines, DOWN, MED_LARGE_BUFF, LEFT, ) self.play( ShowCreation(lines), MoveToTarget(lower_sections), morty.change, mode, lines, ) def get_lines(self, title, n_lines): lines = VGroup(*[ Line(3 * LEFT, 3 * RIGHT, color=LIGHT_GREY) for x in range(n_lines) ]) lines.arrange(DOWN, buff=MED_SMALL_BUFF) lines.next_to( title, DOWN, buff=MED_LARGE_BUFF, aligned_edge=LEFT ) lines[-1].pointwise_become_partial( lines[-1], 0, random.random() ) return lines class WhyAreYouTellingUsThis(TeacherStudentsScene): def construct(self): self.student_says( "Cool story bro...\\\\ how about the actual math?", target_mode="sassy", added_anims=[self.teacher.change, "guilty"] ) self.change_student_modes("angry", "sassy", "angry") self.wait(2) class TopicsAndConnections(Scene): CONFIG = { "random_seed": 1, } def construct(self): dots = VGroup(*[ Dot(8 * np.array([ random.random(), random.random(), 0 ])) for n in range(5) ]) topics = VGroup(*[ TextMobject(word).next_to( dot, RIGHT, SMALL_BUFF ) for dot, word in zip(dots, [ "Divergence/curl", "Fluid flow", "Electricity and magnetism", "Conformal maps", "Complex numbers" ]) ]) for topic in topics: topic.add_to_back( topic.copy().set_stroke(BLACK, 2) ) VGroup(dots, topics).center() for dot in dots: dot.save_state() dot.scale(3) dot.set_fill(opacity=0) connections = VGroup(*[ DashedLine(d1.get_center(), d2.get_center()) for d1, d2 in it.combinations(dots, 2) ]) connections.set_stroke(YELLOW, 2) full_rect = FullScreenFadeRectangle() self.play( LaggedStartMap( ApplyMethod, dots, lambda d: (d.restore,) ), LaggedStartMap(Write, topics), ) self.wait() self.play( LaggedStartMap(ShowCreation, connections), Animation(topics), Animation(dots), ) self.wait() self.play( FadeIn(full_rect), Animation(topics[0]), Animation(dots[0]), ) self.wait() class OnToTheLesson(Scene): def construct(self): words = TextMobject("On to the lesson!") words.scale(1.5) self.add(words) self.play(FadeInFromDown(words)) self.wait() class IntroduceVectorField(Scene): CONFIG = { "vector_field_config": { # "delta_x": 2, # "delta_y": 2, "delta_x": 0.5, "delta_y": 0.5, }, "stream_line_config": { "start_points_generator_config": { # "delta_x": 1, # "delta_y": 1, "delta_x": 0.25, "delta_y": 0.25, }, "virtual_time": 3, }, "stream_line_animation_config": { # "line_anim_class": ShowPassingFlash, "line_anim_class": ShowPassingFlashWithThinningStrokeWidth, } } def construct(self): self.add_plane() self.add_title() self.points_to_vectors() self.show_fluid_flow() self.show_gravitational_force() self.show_magnetic_force() self.show_fluid_flow() def add_plane(self): plane = self.plane = NumberPlane() plane.add_coordinates() plane.remove(plane.coordinate_labels[-1]) self.add(plane) def add_title(self): title = TextMobject("Vector field") title.scale(1.5) title.to_edge(UP, buff=MED_SMALL_BUFF) title.add_background_rectangle(opacity=1, buff=SMALL_BUFF) self.add_foreground_mobjects(title) def points_to_vectors(self): vector_field = self.vector_field = VectorField( four_swirls_function, **self.vector_field_config ) dots = VGroup() for vector in vector_field: dot = Dot(radius=0.05) dot.move_to(vector.get_start()) dot.target = vector dots.add(dot) self.play(LaggedStartMap(GrowFromCenter, dots)) self.wait() self.play(LaggedStartMap(MoveToTarget, dots, remover=True)) self.add(vector_field) self.wait() def show_fluid_flow(self): vector_field = self.vector_field stream_lines = StreamLines( vector_field.func, **self.stream_line_config ) stream_line_animation = AnimatedStreamLines( stream_lines, **self.stream_line_animation_config ) self.add(stream_line_animation) self.play( vector_field.set_fill, {"opacity": 0.5} ) self.wait(7) self.play( vector_field.set_fill, {"opacity": 1}, VFadeOut(stream_line_animation.mobject), ) self.remove(stream_line_animation) def show_gravitational_force(self): earth = self.earth = ImageMobject("earth") moon = self.moon = ImageMobject("moon", height=1) earth_center = 3 * RIGHT + 2 * UP moon_center = 3 * LEFT + DOWN earth.move_to(earth_center) moon.move_to(moon_center) gravity_func = get_force_field_func((earth_center, -6), (moon_center, -1)) gravity_field = VectorField( gravity_func, **self.vector_field_config ) self.add_foreground_mobjects(earth, moon) self.play( GrowFromCenter(earth), GrowFromCenter(moon), Transform(self.vector_field, gravity_field), run_time=2 ) self.vector_field.func = gravity_field.func self.wait() def show_magnetic_force(self): magnetic_func = get_force_field_func( (3 * LEFT, -1), (3 * RIGHT, +1) ) magnetic_field = VectorField( magnetic_func, **self.vector_field_config ) magnet = VGroup(*[ Rectangle( width=3.5, height=1, stroke_width=0, fill_opacity=1, fill_color=color ) for color in (BLUE, RED) ]) magnet.arrange(RIGHT, buff=0) for char, vect in ("S", LEFT), ("N", RIGHT): letter = TextMobject(char) edge = magnet.get_edge_center(vect) letter.next_to(edge, -vect, buff=MED_LARGE_BUFF) magnet.add(letter) self.add_foreground_mobjects(magnet) self.play( self.earth.scale, 0, self.moon.scale, 0, DrawBorderThenFill(magnet), Transform(self.vector_field, magnetic_field), run_time=2 ) self.vector_field.func = magnetic_field.func self.remove_foreground_mobjects(self.earth, self.moon) class QuickNoteOnDrawingThese(TeacherStudentsScene): def construct(self): self.teacher_says( "Quick note on \\\\ drawing vector fields", bubble_kwargs={"width": 5, "height": 3}, added_anims=[self.get_student_changes( "confused", "erm", "sassy" )] ) self.look_at(self.screen) self.wait(3) class ShorteningLongVectors(IntroduceVectorField): def construct(self): self.add_plane() self.add_title() self.contrast_adjusted_and_non_adjusted() def contrast_adjusted_and_non_adjusted(self): func = four_swirls_function unadjusted = VectorField( func, length_func=lambda n: n, colors=[WHITE], ) adjusted = VectorField(func) for v1, v2 in zip(adjusted, unadjusted): v1.save_state() v1.target = v2 self.add(adjusted) self.wait() self.play(LaggedStartMap( MoveToTarget, adjusted, run_time=3 )) self.wait() self.play(LaggedStartMap( ApplyMethod, adjusted, lambda m: (m.restore,), run_time=3 )) self.wait() class TimeDependentVectorField(ExternallyAnimatedScene): pass class ChangingElectricField(Scene): CONFIG = { "vector_field_config": {} } def construct(self): particles = self.get_particles() vector_field = self.get_vector_field() def update_vector_field(vector_field): new_field = self.get_vector_field() Transform(vector_field, new_field).update(1) vector_field.func = new_field.func def update_particles(particles, dt): func = vector_field.func for particle in particles: force = func(particle.get_center()) particle.velocity += force * dt particle.shift(particle.velocity * dt) self.add( Mobject.add_updater(vector_field, update_vector_field), Mobject.add_updater(particles, update_particles), ) self.wait(20) def get_particles(self): particles = self.particles = VGroup() for n in range(9): if n % 2 == 0: particle = get_proton(radius=0.2) particle.charge = +1 else: particle = get_electron(radius=0.2) particle.charge = -1 particle.velocity = np.random.normal(0, 0.1, 3) particles.add(particle) particle.shift(np.random.normal(0, 0.2, 3)) particles.arrange_in_grid(buff=LARGE_BUFF) return particles def get_vector_field(self): func = get_force_field_func(*list(zip( list(map(Mobject.get_center, self.particles)), [p.charge for p in self.particles] ))) self.vector_field = VectorField(func, **self.vector_field_config) return self.vector_field class InsertAirfoildTODO(TODOStub): CONFIG = {"message": "Insert airfoil flow animation"} class ThreeDVectorField(ExternallyAnimatedScene): pass class ThreeDVectorFieldEquation(Scene): def construct(self): vector = Matrix([ "yz", "xz", "xy", ]) vector.set_height(FRAME_HEIGHT - 1) self.add(vector) class GravityFluidFlow(IntroduceVectorField): def construct(self): self.vector_field = VectorField( lambda p: np.array(ORIGIN), **self.vector_field_config ) self.show_gravitational_force() self.show_fluid_flow() class TotallyToScale(Scene): def construct(self): words = TextMobject( "Totally drawn to scale. \\\\ Don't even worry about it." ) words.set_width(FRAME_WIDTH - 1) words.add_background_rectangle() self.add(words) self.wait() # TODO: Revisit this class FluidFlowAsHillGradient(CylinderModel, ThreeDScene): CONFIG = { "production_quality_flow": False, } def construct(self): def potential(point): x, y = point[:2] result = 2 - 0.01 * op.mul( ((x - 4)**2 + y**2), ((x + 4)**2 + y**2) ) return max(-10, result) vector_field_func = negative_gradient(potential) stream_lines = StreamLines( vector_field_func, virtual_time=3, color_lines_by_magnitude=False, start_points_generator_config={ "delta_x": 0.2, "delta_y": 0.2, } ) for line in stream_lines: line.points[:, 2] = np.apply_along_axis( potential, 1, line.points ) stream_lines_animation = self.get_stream_lines_animation( stream_lines ) plane = NumberPlane() self.add(plane) self.add(stream_lines_animation) self.wait(3) self.begin_ambient_camera_rotation(rate=0.1) self.move_camera( phi=70 * DEGREES, run_time=2 ) self.wait(5) class DefineDivergence(ChangingElectricField): CONFIG = { "vector_field_config": { "length_func": lambda norm: 0.3, "min_magnitude": 0, "max_magnitude": 1, }, "stream_line_config": { "start_points_generator_config": { "delta_x": 0.125, "delta_y": 0.125, }, "virtual_time": 5, "n_anchors_per_line": 10, "min_magnitude": 0, "max_magnitude": 1, "stroke_width": 2, }, "stream_line_animation_config": { "line_anim_class": ShowPassingFlash, }, "flow_time": 10, "random_seed": 7, } def construct(self): self.draw_vector_field() self.show_flow() self.point_out_sources_and_sinks() self.show_divergence_values() def draw_vector_field(self): particles = self.get_particles() random.shuffle(particles.submobjects) particles.remove(particles[0]) particles.arrange_in_grid( n_cols=4, buff=3 ) for particle in particles: particle.shift( np.random.normal(0, 0.75) * RIGHT, np.random.normal(0, 0.5) * UP, ) particle.shift_onto_screen(buff=2 * LARGE_BUFF) particle.charge *= 0.125 vector_field = self.get_vector_field() self.play( LaggedStartMap(GrowArrow, vector_field), LaggedStartMap(GrowFromCenter, particles), run_time=4 ) self.wait() self.play(LaggedStartMap(FadeOut, particles)) def show_flow(self): stream_lines = StreamLines( self.vector_field.func, **self.stream_line_config ) stream_line_animation = AnimatedStreamLines( stream_lines, **self.stream_line_animation_config ) self.add(stream_line_animation) self.wait(self.flow_time) def point_out_sources_and_sinks(self): particles = self.particles self.positive_points, self.negative_points = [ [ particle.get_center() for particle in particles if u * particle.charge > 0 ] for u in (+1, -1) ] pair_of_vector_circle_groups = VGroup() for point_set in self.positive_points, self.negative_points: vector_circle_groups = VGroup() for point in point_set: vector_circle_group = VGroup() for angle in np.linspace(0, TAU, 12, endpoint=False): step = 0.5 * rotate_vector(RIGHT, angle) vect = self.vector_field.get_vector(point + step) vect.set_color(WHITE) vect.set_stroke(width=2) vector_circle_group.add(vect) vector_circle_groups.add(vector_circle_group) pair_of_vector_circle_groups.add(vector_circle_groups) self.play( self.vector_field.set_fill, {"opacity": 0.5}, LaggedStartMap( LaggedStartMap, vector_circle_groups, lambda vcg: (GrowArrow, vcg), ), ) self.wait(4) self.play(FadeOut(vector_circle_groups)) self.play(self.vector_field.set_fill, {"opacity": 1}) self.positive_vector_circle_groups = pair_of_vector_circle_groups[0] self.negative_vector_circle_groups = pair_of_vector_circle_groups[1] self.wait() def show_divergence_values(self): positive_points = self.positive_points negative_points = self.negative_points div_func = divergence(self.vector_field.func) circle = Circle(color=WHITE, radius=0.2) circle.add(Dot(circle.get_center(), radius=0.02)) circle.move_to(positive_points[0]) div_tex = TexMobject( "\\text{div} \\, \\textbf{F}(x, y) = " ) div_tex.add_background_rectangle() div_tex_update = Mobject.add_updater( div_tex, lambda m: m.next_to(circle, UP, SMALL_BUFF) ) div_value = DecimalNumber( 0, num_decimal_places=1, include_background_rectangle=True, include_sign=True, ) div_value_update = ContinualChangingDecimal( div_value, lambda a: np.round(div_func(circle.get_center()), 1), position_update_func=lambda m: m.next_to(div_tex, RIGHT, SMALL_BUFF), include_sign=True, ) self.play( ShowCreation(circle), FadeIn(div_tex), FadeIn(div_value), ) self.add(div_tex_update) self.add(div_value_update) self.wait() for point in positive_points[1:-1]: self.play(circle.move_to, point) self.wait(1.5) for point in negative_points: self.play(circle.move_to, point) self.wait(2) self.wait(4) # self.remove(div_tex_update) # self.remove(div_value_update) # self.play( # ApplyMethod(circle.scale, 0, remover=True), # FadeOut(div_tex), # FadeOut(div_value), # ) class DefineDivergenceJustFlow(DefineDivergence): CONFIG = { "flow_time": 10, } def construct(self): self.force_skipping() self.draw_vector_field() self.revert_to_original_skipping_status() self.clear() self.show_flow() class DefineDivergenceSymbols(Scene): def construct(self): tex_mob = TexMobject( "\\text{div}", "\\textbf{F}", "(x, y)", "=", ) div, F, xy, eq = tex_mob output = DecimalNumber(0, include_sign=True) output.next_to(tex_mob, RIGHT) time_tracker = ValueTracker() always_shift(time_tracker, rate=1) self.add(time_tracker) output_animation = ContinualChangingDecimal( output, lambda a: 3 * np.cos(int(time_tracker.get_value())), ) F.set_color(BLUE) xy.set_color(YELLOW) F_brace = Brace(F, UP, buff=SMALL_BUFF) F_label = F_brace.get_text( "Vector field function", ) F_label.match_color(F) xy_brace = Brace(xy, DOWN, buff=SMALL_BUFF) xy_label = xy_brace.get_text("Some point") xy_label.match_color(xy) output_brace = Brace(output, UP, buff=SMALL_BUFF) output_label = output_brace.get_text( "Measure of how much \\\\ " "$(x, y)$ ``generates'' fluid" ) brace_label_pairs = [ (F_brace, F_label), (xy_brace, xy_label), (output_brace, output_label), ] self.add(tex_mob, output_animation) fade_anims = [] for brace, label in brace_label_pairs: self.play( GrowFromCenter(brace), FadeInFromDown(label), *fade_anims ) self.wait(2) fade_anims = list(map(FadeOut, [brace, label])) self.wait() class DivergenceAtSlowFastPoint(Scene): CONFIG = { "vector_field_config": { "length_func": lambda norm: 0.1 + 0.4 * norm / 4.0, "min_magnitude": 0, "max_magnitude": 3, }, "stream_lines_config": { "start_points_generator_config": { "delta_x": 0.125, "delta_y": 0.125, }, "virtual_time": 1, "min_magnitude": 0, "max_magnitude": 3, }, } def construct(self): def func(point): return 3 * sigmoid(point[0]) * RIGHT vector_field = self.vector_field = VectorField( func, **self.vector_field_config ) circle = Circle(color=WHITE) slow_words = TextMobject("Slow flow in") fast_words = TextMobject("Fast flow out") words = VGroup(slow_words, fast_words) for word, vect in zip(words, [LEFT, RIGHT]): word.add_background_rectangle() word.next_to(circle, vect) div_tex = TexMobject( "\\text{div}\\,\\textbf{F}(x, y) > 0" ) div_tex.add_background_rectangle() div_tex.next_to(circle, UP) self.add(vector_field) self.add_foreground_mobjects(circle, div_tex) self.begin_flow() self.wait(2) for word in words: self.add_foreground_mobjects(word) self.play(Write(word)) self.wait(8) def begin_flow(self): stream_lines = StreamLines( self.vector_field.func, **self.stream_lines_config ) stream_line_animation = AnimatedStreamLines(stream_lines) stream_line_animation.update(3) self.add(stream_line_animation) class DivergenceAsNewFunction(Scene): def construct(self): self.add_plane() self.show_vector_field_function() self.show_divergence_function() def add_plane(self): plane = self.plane = NumberPlane() plane.add_coordinates() self.add(plane) def show_vector_field_function(self): func = self.func unscaled_vector_field = VectorField( func, length_func=lambda norm: norm, colors=[BLUE_C, YELLOW, RED], delta_x=np.inf, delta_y=np.inf, ) in_dot = Dot(color=PINK) in_dot.move_to(3.75 * LEFT + 1.25 * UP) def get_input(): return in_dot.get_center() def get_out_vect(): return unscaled_vector_field.get_vector(get_input()) # Tex func_tex = TexMobject( "\\textbf{F}(", "+0.00", ",", "+0.00", ")", "=", ) dummy_in_x, dummy_in_y = func_tex.get_parts_by_tex("+0.00") func_tex.add_background_rectangle() rhs = DecimalMatrix( [[0], [0]], element_to_mobject_config={ "num_decimal_places": 2, "include_sign": True, }, include_background_rectangle=True ) rhs.next_to(func_tex, RIGHT) dummy_out_x, dummy_out_y = rhs.get_mob_matrix().flatten() VGroup(func_tex, rhs).to_corner(UL, buff=MED_SMALL_BUFF) VGroup( dummy_in_x, dummy_in_y, dummy_out_x, dummy_out_y, ).set_fill(BLACK, opacity=0) # Changing decimals in_x, in_y, out_x, out_y = [ DecimalNumber(0, include_sign=True) for x in range(4) ] VGroup(in_x, in_y).set_color(in_dot.get_color()) VGroup(out_x, out_y).set_color(get_out_vect().get_fill_color()) in_x_update = ContinualChangingDecimal( in_x, lambda a: get_input()[0], position_update_func=lambda m: m.move_to(dummy_in_x) ) in_y_update = ContinualChangingDecimal( in_y, lambda a: get_input()[1], position_update_func=lambda m: m.move_to(dummy_in_y) ) out_x_update = ContinualChangingDecimal( out_x, lambda a: func(get_input())[0], position_update_func=lambda m: m.move_to(dummy_out_x) ) out_y_update = ContinualChangingDecimal( out_y, lambda a: func(get_input())[1], position_update_func=lambda m: m.move_to(dummy_out_y) ) self.add(func_tex, rhs) # self.add(Mobject.add_updater( # rhs, lambda m: m.next_to(func_tex, RIGHT) # )) # Where those decimals actually change self.add(in_x_update, in_y_update) in_dot.save_state() in_dot.move_to(ORIGIN) self.play(in_dot.restore) self.wait() self.play(*[ ReplacementTransform( VGroup(mob.copy().fade(1)), VGroup(out_x, out_y), ) for mob in (in_x, in_y) ]) out_vect = get_out_vect() VGroup(out_x, out_y).match_style(out_vect) out_vect.save_state() out_vect.move_to(rhs) out_vect.set_fill(opacity=0) self.play(out_vect.restore) self.out_vect_update = Mobject.add_updater( out_vect, lambda ov: Transform(ov, get_out_vect()).update(1) ) self.add(self.out_vect_update) self.add(out_x_update, out_y_update) self.add(Mobject.add_updater( VGroup(out_x, out_y), lambda m: m.match_style(out_vect) )) self.wait() for vect in DOWN, 2 * RIGHT, UP: self.play( in_dot.shift, 3 * vect, run_time=3 ) self.wait() self.in_dot = in_dot self.out_vect = out_vect self.func_equation = VGroup(func_tex, rhs) self.out_x, self.out_y = out_x, out_y self.in_x, self.in_y = out_x, out_y self.in_x_update = in_x_update self.in_y_update = in_y_update self.out_x_update = out_x_update self.out_y_update = out_y_update def show_divergence_function(self): vector_field = VectorField(self.func) vector_field.remove(*[ v for v in vector_field if v.get_start()[0] < 0 and v.get_start()[1] > 2 ]) vector_field.set_fill(opacity=0.5) in_dot = self.in_dot def get_neighboring_points(step_sizes=[0.3], n_angles=12): point = in_dot.get_center() return list(it.chain(*[ [ point + step_size * step for step in compass_directions(n_angles) ] for step_size in step_sizes ])) def get_vector_ring(): return VGroup(*[ vector_field.get_vector(point) for point in get_neighboring_points() ]) def get_stream_lines(): return StreamLines( self.func, start_points_generator=get_neighboring_points, start_points_generator_config={ "step_sizes": np.arange(0.1, 0.5, 0.1) }, virtual_time=1, stroke_width=3, ) def show_flow(): stream_lines = get_stream_lines() random.shuffle(stream_lines.submobjects) self.play(LaggedStartMap( ShowCreationThenDestruction, stream_lines, remover=True )) vector_ring = get_vector_ring() vector_ring_update = Mobject.add_updater( vector_ring, lambda vr: Transform(vr, get_vector_ring()).update(1) ) func_tex, rhs = self.func_equation out_x, out_y = self.out_x, self.out_y out_x_update = self.out_x_update out_y_update = self.out_y_update div_tex = TexMobject("\\text{div}") div_tex.add_background_rectangle() div_tex.move_to(func_tex, LEFT) div_tex.shift(2 * SMALL_BUFF * RIGHT) self.remove(out_x_update, out_y_update) self.remove(self.out_vect_update) self.add(self.in_x_update, self.in_y_update) self.play( func_tex.next_to, div_tex, RIGHT, SMALL_BUFF, {"submobject_to_align": func_tex[1][0]}, Write(div_tex), FadeOut(self.out_vect), FadeOut(out_x), FadeOut(out_y), FadeOut(rhs), ) # This line is a dumb hack around a Scene bug self.add(*[ Mobject.add_updater( mob, lambda m: m.set_fill(None, 0) ) for mob in (out_x, out_y) ]) self.add_foreground_mobjects(div_tex) self.play( LaggedStartMap(GrowArrow, vector_field), LaggedStartMap(GrowArrow, vector_ring), ) self.add(vector_ring_update) self.wait() div_func = divergence(self.func) div_rhs = DecimalNumber( 0, include_sign=True, include_background_rectangle=True ) div_rhs_update = ContinualChangingDecimal( div_rhs, lambda a: div_func(in_dot.get_center()), position_update_func=lambda d: d.next_to(func_tex, RIGHT, SMALL_BUFF) ) self.play(FadeIn(div_rhs)) self.add(div_rhs_update) show_flow() for vect in 2 * RIGHT, 3 * DOWN, 2 * LEFT, 2 * LEFT: self.play(in_dot.shift, vect, run_time=3) show_flow() self.wait() def func(self, point): x, y = point[:2] return np.sin(x + y) * RIGHT + np.sin(y * x / 3) * UP class DivergenceZeroCondition(Scene): def construct(self): title = TextMobject( "For actual (incompressible) fluid flow:" ) title.to_edge(UP) equation = TexMobject( "\\text{div} \\, \\textbf{F} = 0 \\quad \\text{everywhere}" ) equation.next_to(title, DOWN) for mob in title, equation: mob.add_background_rectangle(buff=MED_SMALL_BUFF / 2) self.add_foreground_mobjects(mob) self.wait(1) class PureCylinderFlow(Scene): def construct(self): self.add_vector_field() self.begin_flow() self.add_circle() self.wait(5) def add_vector_field(self): vector_field = VectorField( cylinder_flow_vector_field, ) for vector in vector_field: if get_norm(vector.get_start()) < 1: vector_field.remove(vector) vector_field.set_fill(opacity=0.75) self.modify_vector_field(vector_field) self.add_foreground_mobjects(vector_field) def begin_flow(self): stream_lines = StreamLines( cylinder_flow_vector_field, colors=[BLUE_E, BLUE_D, BLUE_C], start_points_generator_config={ "delta_x": 0.125, "delta_y": 0.125, }, virtual_time=5, ) self.add(stream_lines) for stream_line in stream_lines: if get_norm(stream_line.points[0]) < 1: stream_lines.remove(stream_line) self.modify_flow(stream_lines) stream_line_animation = AnimatedStreamLines(stream_lines) stream_line_animation.update(3) self.add(stream_line_animation) def add_circle(self): circle = Circle( radius=1, stroke_color=YELLOW, fill_color=BLACK, fill_opacity=1, ) self.modify_flow(circle) self.add_foreground_mobjects(circle) def modify_flow(self, mobject): pass def modify_vector_field(self, vector_field): pass class PureAirfoilFlow(PureCylinderFlow): def modify_flow(self, mobject): vect = 0.1 * LEFT + 0.2 * UP mobject.scale(get_norm(vect - RIGHT)) mobject.shift(vect) mobject.apply_complex_function(joukowsky_map) return mobject def modify_vector_field(self, vector_field): def func(z): w = complex(-0.1, 0.2) n = abs(w - 1) return joukowsky_map(inverse_joukowsky_map(z) - w / n) def new_vector_field_func(point): z = R3_to_complex(point) return complex_to_R3(derivative(func)(z).conjugate()) vf = VectorField(new_vector_field_func, delta_y=0.33) Transform(vector_field, vf).update(1) vf.set_fill(opacity=0.5) class IntroduceCurl(IntroduceVectorField): CONFIG = { "stream_line_animation_config": { "line_anim_class": ShowPassingFlash, }, "stream_line_config": { "start_points_generator_config": { "delta_x": 0.125, "delta_y": 0.125, }, "virtual_time": 1, } } def construct(self): self.add_title() self.show_vector_field() self.begin_flow() self.show_rotation() def add_title(self): title = self.title = Title( "Curl", match_underline_width_to_text=True, scale_factor=1.5, ) title.add_background_rectangle() title.to_edge(UP, buff=MED_SMALL_BUFF) self.add_foreground_mobjects(title) def show_vector_field(self): vector_field = self.vector_field = VectorField( four_swirls_function, **self.vector_field_config ) vector_field.submobjects.sort( key=lambda v: v.get_length() ) self.play(LaggedStartMap(GrowArrow, vector_field)) self.wait() def begin_flow(self): stream_lines = StreamLines( self.vector_field.func, **self.stream_line_config ) stream_line_animation = AnimatedStreamLines( stream_lines, **self.stream_line_animation_config ) self.add(stream_line_animation) self.wait(3) def show_rotation(self): clockwise_arrows, counterclockwise_arrows = [ VGroup(*[ self.get_rotation_arrows(clockwise=cw).move_to(point) for point in points ]) for cw, points in [ (True, [2 * UP, 2 * DOWN]), (False, [4 * LEFT, 4 * RIGHT]), ] ] for group, u in (counterclockwise_arrows, +1), (clockwise_arrows, -1): for arrows in group: label = TexMobject( "\\text{curl} \\, \\textbf{F}", ">" if u > 0 else "<", "0" ) label.add_background_rectangle() label.next_to(arrows, DOWN) self.add_foreground_mobjects(label) always_rotate(arrows, rate=u * 30 * DEGREES) self.play( FadeIn(arrows), FadeIn(label) ) self.wait(2) for group in counterclockwise_arrows, clockwise_arrows: self.play(FocusOn(group[0])) self.play( UpdateFromAlphaFunc( group, lambda mob, alpha: mob.set_color( interpolate_color(WHITE, PINK, alpha) ).set_stroke( width=interpolate(5, 10, alpha) ), rate_func=there_and_back, run_time=2 ) ) self.wait() self.wait(6) # Helpers def get_rotation_arrows(self, clockwise=True, width=1): result = VGroup(*[ Arrow( *points, buff=2 * SMALL_BUFF, path_arc=90 * DEGREES ).set_stroke(width=5) for points in adjacent_pairs(compass_directions(4, RIGHT)) ]) if clockwise: result.flip() result.set_width(width) return result class ShearCurl(IntroduceCurl): def construct(self): self.show_vector_field() self.begin_flow() self.wait(2) self.comment_on_relevant_region() def show_vector_field(self): vector_field = self.vector_field = VectorField( self.func, **self.vector_field_config ) vector_field.submobjects.key=sort( key=lambda a: a.get_length() ) self.play(LaggedStartMap(GrowArrow, vector_field)) def comment_on_relevant_region(self): circle = Circle(color=WHITE, radius=0.75) circle.next_to(ORIGIN, UP, LARGE_BUFF) self.play(ShowCreation(circle)) slow_words, fast_words = words = [ TextMobject("Slow flow below"), TextMobject("Fast flow above") ] for word, vect in zip(words, [DOWN, UP]): word.add_background_rectangle(buff=SMALL_BUFF) word.next_to(circle, vect) self.add_foreground_mobjects(word) self.play(Write(word)) self.wait() twig = Rectangle( height=0.8 * 2 * circle.radius, width=SMALL_BUFF, stroke_width=0, fill_color=GREY_BROWN, fill_opacity=1, ) twig.add(Dot(twig.get_center())) twig.move_to(circle) always_rotate( twig, rate=-90 * DEGREES, ) self.play(FadeInFrom(twig, UP)) self.add(twig_rotation) self.wait(16) # Helpers def func(self, point): return 0.5 * point[1] * RIGHT class FromKAWrapper(TeacherStudentsScene): def construct(self): screen = self.screen self.play( self.teacher.change, "raise_right_hand", self.get_student_changes( "pondering", "confused", "hooray", ) ) self.look_at(screen) self.wait(2) self.change_student_modes("erm", "happy", "confused") self.wait(3) self.teacher_says( "Our focus is \\\\ the 2d version", bubble_kwargs={"width": 4, "height": 3}, added_anims=[self.get_student_changes( "happy", "hooray", "happy" )] ) self.wait() class ShowCurlAtVariousPoints(IntroduceCurl): CONFIG = { "func": four_swirls_function, "sample_points": [ 4 * RIGHT, 2 * UP, 4 * LEFT, 2 * DOWN, ORIGIN, 3 * RIGHT + 2 * UP, 3 * LEFT + 2 * UP, ], "vector_field_config": { "fill_opacity": 0.75 }, "stream_line_config": { "virtual_time": 5, "start_points_generator_config": { "delta_x": 0.25, "delta_y": 0.25, } } } def construct(self): self.add_plane() self.show_vector_field() self.begin_flow() self.show_curl_at_points() def add_plane(self): plane = NumberPlane() plane.add_coordinates() self.add(plane) self.plane = plane def show_curl_at_points(self): dot = Dot() circle = Circle(radius=0.25, color=WHITE) circle.move_to(dot) circle_update = Mobject.add_updater( circle, lambda m: m.move_to(dot) ) curl_tex = TexMobject( "\\text{curl} \\, \\textbf{F}(x, y) = " ) curl_tex.add_background_rectangle(buff=0.025) curl_tex_update = Mobject.add_updater( curl_tex, lambda m: m.next_to(circle, UP, SMALL_BUFF) ) curl_func = two_d_curl(self.func) curl_value = DecimalNumber( 0, include_sign=True, include_background_rectangle=True, ) curl_value_update = ContinualChangingDecimal( curl_value, lambda a: curl_func(dot.get_center()), position_update_func=lambda m: m.next_to( curl_tex, RIGHT, buff=0 ), include_background_rectangle=True, include_sign=True, ) points = self.sample_points self.add(dot, circle_update) self.play( dot.move_to, points[0], VFadeIn(dot), VFadeIn(circle), ) curl_tex_update.update(0) curl_value_update.update(0) self.play(Write(curl_tex), FadeIn(curl_value)) self.add(curl_tex_update, curl_value_update) self.wait() for point in points[1:]: self.play(dot.move_to, point, run_time=3) self.wait(2) self.wait(2) class IllustrationUseVennDiagram(Scene): def construct(self): title = Title("Divergence \\& Curl") title.to_edge(UP, buff=MED_SMALL_BUFF) useful_for = TextMobject("Useful for") useful_for.next_to(title, DOWN) useful_for.set_color(BLUE) fluid_flow = TextMobject("Fluid \\\\ flow") fluid_flow.next_to(ORIGIN, UL) ff_circle = Circle(color=YELLOW) ff_circle.surround(fluid_flow, stretch=True) fluid_flow.match_color(ff_circle) big_circle = Circle( fill_color=BLUE, fill_opacity=0.2, stroke_color=BLUE, ) big_circle.stretch_to_fit_width(9) big_circle.stretch_to_fit_height(6) big_circle.next_to(useful_for, DOWN, SMALL_BUFF) illustrated_by = TextMobject("Illustrated by") illustrated_by.next_to( big_circle.point_from_proportion(3. / 8), UL ) illustrated_by.match_color(ff_circle) illustrated_by_arrow = Arrow( illustrated_by.get_bottom(), ff_circle.get_left(), path_arc=90 * DEGREES, color=YELLOW, ) illustrated_by_arrow.pointwise_become_partial( illustrated_by_arrow, 0, 0.95 ) examples = VGroup( TextMobject("Electricity"), TextMobject("Magnetism"), TextMobject("Phase flow"), TextMobject("Stokes' theorem"), ) points = [ 2 * RIGHT + 0.5 * UP, 2 * RIGHT + 0.5 * DOWN, 2 * DOWN, 2 * LEFT + DOWN, ] for example, point in zip(examples, points): example.move_to(point) self.play(Write(title), run_time=1) self.play( Write(illustrated_by), ShowCreation(illustrated_by_arrow), run_time=1, ) self.play( ShowCreation(ff_circle), FadeIn(fluid_flow), ) self.wait() self.play( Write(useful_for), DrawBorderThenFill(big_circle), Animation(fluid_flow), Animation(ff_circle), ) self.play(LaggedStartMap( FadeIn, examples, run_time=3, )) self.wait() class MaxwellsEquations(Scene): CONFIG = { "faded_opacity": 0.3, } def construct(self): self.add_equations() self.circle_gauss_law() self.circle_magnetic_divergence() self.circle_curl_equations() def add_equations(self): title = Title("Maxwell's equations") title.to_edge(UP, buff=MED_SMALL_BUFF) tex_to_color_map = { "\\textbf{E}": BLUE, "\\textbf{B}": YELLOW, "\\rho": WHITE, } equations = self.equations = VGroup(*[ TexMobject( tex, tex_to_color_map=tex_to_color_map ) for tex in [ """ \\text{div} \\, \\textbf{E} = {\\rho \\over \\varepsilon_0} """, """\\text{div} \\, \\textbf{B} = 0""", """ \\text{curl} \\, \\textbf{E} = -{\\partial \\textbf{B} \\over \\partial t} """, """ \\text{curl} \\, \\textbf{B} = \\mu_0 \\left( \\textbf{J} + \\varepsilon_0 {\\partial \\textbf{E} \\over \\partial t} \\right) """, ] ]) equations.arrange( DOWN, aligned_edge=LEFT, buff=MED_LARGE_BUFF ) field_definitions = VGroup(*[ TexMobject(text, tex_to_color_map=tex_to_color_map) for text in [ "\\text{Electric field: } \\textbf{E}", "\\text{Magnetic field: } \\textbf{B}", ] ]) field_definitions.arrange( RIGHT, buff=MED_LARGE_BUFF ) field_definitions.next_to(title, DOWN, MED_LARGE_BUFF) equations.next_to(field_definitions, DOWN, MED_LARGE_BUFF) field_definitions.shift(MED_SMALL_BUFF * UP) self.add(title) self.add(field_definitions) self.play(LaggedStartMap( FadeIn, equations, run_time=3, lag_range=0.4 )) self.wait() def circle_gauss_law(self): equation = self.equations[0] rect = SurroundingRectangle(equation) rect.set_color(RED) rho = equation.get_part_by_tex("\\rho") sub_rect = SurroundingRectangle(rho) sub_rect.match_color(rect) rho_label = TextMobject("Charge density") rho_label.next_to(sub_rect, RIGHT) rho_label.match_color(sub_rect) gauss_law = TextMobject("Gauss's law") gauss_law.next_to(rect, RIGHT) self.play( ShowCreation(rect), Write(gauss_law, run_time=1), self.equations[1:].set_fill, {"opacity": self.faded_opacity} ) self.wait(2) self.play( ReplacementTransform(rect, sub_rect), FadeOut(gauss_law), FadeIn(rho_label), rho.match_color, sub_rect, ) self.wait() self.play( self.equations.to_edge, LEFT, MaintainPositionRelativeTo(rho_label, equation), MaintainPositionRelativeTo(sub_rect, equation), VFadeOut(rho_label), VFadeOut(sub_rect), ) self.wait() def circle_magnetic_divergence(self): equations = self.equations rect = SurroundingRectangle(equations[1]) self.play( equations[0].set_fill, {"opacity": self.faded_opacity}, equations[1].set_fill, {"opacity": 1.0}, ) self.play(ShowCreation(rect)) self.wait(3) self.play(FadeOut(rect)) def circle_curl_equations(self): equations = self.equations rect = SurroundingRectangle(equations[2:]) randy = Randolph(height=2) randy.flip() randy.next_to(rect, RIGHT, aligned_edge=DOWN) randy.look_at(rect) self.play( equations[1].set_fill, {"opacity": self.faded_opacity}, equations[2:].set_fill, {"opacity": 1.0}, ) self.play(ShowCreation(rect)) self.play( randy.change, "confused", VFadeIn(randy), ) self.play(Blink(randy)) self.play(randy.look_at, 2 * RIGHT) self.wait(3) self.play( FadeOut(rect), randy.change, "pondering", randy.look_at, rect, ) self.wait() self.play(Blink(randy)) self.wait() class ThatWeKnowOf(Scene): def construct(self): words = TextMobject("*That we know of!") self.add(words) class IllustrateGaussLaw(DefineDivergence, MovingCameraScene): CONFIG = { "flow_time": 10, "stream_line_config": { "start_points_generator_config": { "delta_x": 1.0 / 16, "delta_y": 1.0 / 16, "x_min": -2, "x_max": 2, "y_min": -1.5, "y_max": 1.5, }, "color_lines_by_magnitude": True, "colors": [BLUE_E, BLUE_D, BLUE_C], "stroke_width": 3, }, "stream_line_animation_config": { "line_anim_class": ShowPassingFlashWithThinningStrokeWidth, "line_anim_config": { "n_segments": 5, } }, "final_frame_width": 4, } def construct(self): particles = self.get_particles() vector_field = self.get_vector_field() self.add_foreground_mobjects(vector_field) self.add_foreground_mobjects(particles) self.zoom_in() self.show_flow() def get_particles(self): particles = VGroup( get_proton(radius=0.1), get_electron(radius=0.1), ) particles.arrange(RIGHT, buff=2.25) particles.shift(0.25 * UP) for particle, sign in zip(particles, [+1, -1]): particle.charge = sign self.particles = particles return particles def zoom_in(self): self.play( self.camera_frame.set_width, self.final_frame_width, run_time=2 ) class IllustrateGaussMagnetic(IllustrateGaussLaw): CONFIG = { "final_frame_width": 7, "stream_line_config": { "start_points_generator_config": { "delta_x": 1.0 / 16, "delta_y": 1.0 / 16, "x_min": -3.5, "x_max": 3.5, "y_min": -2, "y_max": 2, }, "color_lines_by_magnitude": True, "colors": [BLUE_E, BLUE_D, BLUE_C], "stroke_width": 3, }, "stream_line_animation_config": { "start_up_time": 0, }, "flow_time": 10, } def construct(self): self.add_wires() self.show_vector_field() self.zoom_in() self.show_flow() def add_wires(self): top, bottom = [ Circle( radius=0.275, stroke_color=WHITE, fill_color=BLACK, fill_opacity=1 ) for x in range(2) ] top.add(TexMobject("\\times").scale(0.5)) bottom.add(Dot().scale(0.5)) top.move_to(1 * UP) bottom.move_to(1 * DOWN) self.add_foreground_mobjects(top, bottom) def show_vector_field(self): vector_field = self.vector_field = VectorField( self.func, **self.vector_field_config ) vector_field.submobjects.sort( key=lambda a: -a1.get_length() ) self.play(LaggedStartMap(GrowArrow, vector_field)) self.add_foreground_mobjects( vector_field, *self.foreground_mobjects ) def func(self, point): x, y = point[:2] top_part = np.array([(y - 1.0), -x, 0]) bottom_part = np.array([-(y + 1.0), x, 0]) norm = get_norm return 1 * op.add( top_part / (norm(top_part) * norm(point - UP) + 0.1), bottom_part / (norm(bottom_part) * norm(point - DOWN) + 0.1), # top_part / (norm(top_part)**2 + 1), # bottom_part / (norm(bottom_part)**2 + 1), ) class IllustrateEMCurlEquations(ExternallyAnimatedScene): pass class RelevantInNonSpatialCircumstances(TeacherStudentsScene): def construct(self): self.teacher_says( """ $\\textbf{div}$ and $\\textbf{curl}$ are \\\\ even useful in some \\\\ non-spatial problems """, target_mode="hooray" ) self.change_student_modes( "sassy", "confused", "hesitant" ) self.wait(3) class ShowTwoPopulations(Scene): CONFIG = { "total_num_animals": 80, "start_num_foxes": 40, "start_num_rabbits": 20, "animal_height": 0.5, "final_wait_time": 30, "count_word_scale_val": 1, } def construct(self): self.introduce_animals() self.evolve_system() def introduce_animals(self): foxes = self.foxes = VGroup(*[ self.get_fox() for n in range(self.total_num_animals) ]) rabbits = self.rabbits = VGroup(*[ self.get_rabbit() for n in range(self.total_num_animals) ]) foxes[self.start_num_foxes:].set_fill(opacity=0) rabbits[self.start_num_rabbits:].set_fill(opacity=0) fox, rabbit = examples = VGroup(foxes[0], rabbits[0]) for mob in examples: mob.save_state() mob.set_height(3) examples.arrange(LEFT, buff=2) preditor, prey = words = VGroup( TextMobject("Predator"), TextMobject("Prey") ) for mob, word in zip(examples, words): word.scale(1.5) word.next_to(mob, UP) self.play( FadeInFromDown(mob), Write(word, run_time=1), ) self.play( LaggedStartMap( ApplyMethod, examples, lambda m: (m.restore,) ), LaggedStartMap(FadeOut, words), *[ LaggedStartMap( FadeIn, group[1:], run_time=4, lag_ratio=0.1, rate_func=lambda t: np.clip(smooth(2 * t), 0, 1) ) for group in [foxes, rabbits] ] ) def evolve_system(self): foxes = self.foxes rabbits = self.rabbits phase_point = VectorizedPoint( self.start_num_rabbits * RIGHT + self.start_num_foxes * UP ) self.add(move_along_vector_field( phase_point, preditor_prey_vector_field, )) def get_num_rabbits(): return phase_point.get_center()[0] def get_num_foxes(): return phase_point.get_center()[1] def get_updater(pop_size_getter): def update(animals): target_num = pop_size_getter() for n, animal in enumerate(animals): animal.set_fill( opacity=np.clip(target_num - n, 0, 1) ) target_int = int(np.ceil(target_num)) tail = animals.submobjects[target_int:] random.shuffle(tail) animals.submobjects[target_int:] = tail return update self.add(Mobject.add_updater( foxes, get_updater(get_num_foxes) )) self.add(Mobject.add_updater( rabbits, get_updater(get_num_rabbits) )) # Add counts for foxes and rabbits labels = self.get_pop_labels() num_foxes = Integer(10) num_foxes.scale(self.count_word_scale_val) num_foxes.next_to(labels[0], RIGHT) num_foxes.align_to(labels[0][0][1], DOWN) num_rabbits = Integer(10) num_rabbits.scale(self.count_word_scale_val) num_rabbits.next_to(labels[1], RIGHT) num_rabbits.align_to(labels[1][0][1], DOWN) num_foxes.add_updater(lambda d: d.set_value(get_num_foxes())) num_rabbits.add_updater(lambda d: d.set_value(get_num_rabbits())) self.add(num_foxes, num_rabbits) for count in num_foxes, num_rabbits: self.add(Mobject.add_updater( count, self.update_count_color, )) self.play( FadeIn(labels), *[ UpdateFromAlphaFunc(count, lambda m, a: m.set_fill(opacity=a)) for count in (num_foxes, num_rabbits) ] ) self.wait(self.final_wait_time) # Helpers def get_animal(self, name, color): result = SVGMobject( file_name=name, height=self.animal_height, fill_color=color, ) # for submob in result.family_members_with_points(): # if submob.is_subpath: # submob.is_subpath = False # submob.set_fill( # interpolate_color(color, BLACK, 0.8), # opacity=1 # ) x_shift, y_shift = [ (2 * random.random() - 1) * max_val for max_val in [ FRAME_WIDTH / 2 - 2, FRAME_HEIGHT / 2 - 2 ] ] result.shift(x_shift * RIGHT + y_shift * UP) return result def get_fox(self): return self.get_animal("fox", FOX_COLOR) def get_rabbit(self): # return self.get_animal("rabbit", WHITE) return self.get_animal("bunny", RABBIT_COLOR) def get_pop_labels(self): labels = VGroup( TextMobject("\\# Foxes: "), TextMobject("\\# Rabbits: "), ) for label in labels: label.scale(self.count_word_scale_val) labels.arrange(RIGHT, buff=2) labels.to_edge(UP) return labels def update_count_color(self, count): count.set_fill(interpolate_color( BLUE, RED, (count.number - 20) / 30.0 )) return count class PhaseSpaceOfPopulationModel(ShowTwoPopulations, PiCreatureScene, MovingCameraScene): CONFIG = { "origin": 5 * LEFT + 2.5 * DOWN, "vector_field_config": { "max_magnitude": 50, }, "pi_creatures_start_on_screen": False, "default_pi_creature_kwargs": { "height": 1.8 }, "flow_time": 10, } def setup(self): MovingCameraScene.setup(self) PiCreatureScene.setup(self) def construct(self): self.add_axes() self.add_example_point() self.write_differential_equations() self.add_vectors() self.show_phase_flow() def add_axes(self): axes = self.axes = Axes( x_min=0, x_max=55, x_axis_config={"unit_size": 0.15}, y_min=0, y_max=55, y_axis_config={"unit_size": 0.09}, number_line_config={ "tick_frequency": 10, }, ) axes.shift(self.origin) for axis in axes.x_axis, axes.y_axis: axis.add_numbers(*list(range(10, 60, 10))) axes_labels = self.axes_labels = VGroup(*[ VGroup( method().set_height(0.75), TextMobject("Population"), ).arrange(RIGHT, buff=MED_SMALL_BUFF) for method in (self.get_rabbit, self.get_fox) ]) for axis, label, vect in zip(axes, axes_labels, [RIGHT, UP]): label.next_to( axis, vect, submobject_to_align=label[0] ) self.add(axes, axes_labels) def add_example_point(self): axes = self.axes origin = self.origin x = self.start_num_rabbits y = self.start_num_foxes point = axes.coords_to_point(x, y) x_point = axes.coords_to_point(x, 0) y_point = axes.coords_to_point(0, y) v_line = DashedLine(x_point, point) h_line = DashedLine(y_point, point) v_line.set_color(FOX_COLOR) h_line.set_color(LIGHT_GREY) dot = Dot(point) coord_pair = TexMobject( "(10, 10)", substrings_to_isolate=["10"] ) pop_sizes = VGroup(Integer(10), Integer(10)) pop_sizes[0].set_color(LIGHT_GREY) pop_sizes[1].set_color(FOX_COLOR) tens = coord_pair.get_parts_by_tex("10") tens.fade(1) def get_pop_size_update(i): return ContinualChangingDecimal( pop_sizes[i], lambda a: int(np.round( axes.point_to_coords(dot.get_center())[i] )), position_update_func=lambda m: m.move_to(tens[i]) ) coord_pair.add_background_rectangle() coord_pair_update = Mobject.add_updater( coord_pair, lambda m: m.next_to(dot, UR, SMALL_BUFF) ) pop_sizes_updates = [get_pop_size_update(i) for i in (0, 1)] phase_space = TextMobject("``Phase space''") phase_space.set_color(YELLOW) phase_space.scale(1.5) phase_space.to_edge(UP) phase_space.shift(2 * RIGHT) self.play(ShowCreation(v_line)) self.play(ShowCreation(h_line)) dot.save_state() dot.move_to(origin) self.add(coord_pair_update) self.add(*pop_sizes_updates) self.play( dot.restore, VFadeIn(coord_pair), UpdateFromAlphaFunc(pop_sizes, lambda m, a: m.set_fill(opacity=a)), ) self.wait() self.play(Write(phase_space)) self.wait(2) self.play(FadeOut(VGroup(h_line, v_line, phase_space))) self.play(Rotating( dot, about_point=axes.coords_to_point(30, 30), rate_func=smooth, )) self.dot = dot self.coord_pair = coord_pair self.coord_pair_update = coord_pair_update self.pop_sizes = pop_sizes self.pop_sizes_updates = pop_sizes_updates def write_differential_equations(self): equations = self.get_equations() equations.shift(2 * DOWN) rect = SurroundingRectangle(equations, color=YELLOW) rect.set_fill(BLACK, 0.8) title = TextMobject("Differential equations") title.next_to(rect, UP) title.set_color(rect.get_stroke_color()) self.differential_equation_group = VGroup( rect, equations, title ) self.differential_equation_group.to_corner(UR) randy = self.pi_creature randy.next_to(rect, DL) self.play( Write(title, run_time=1), ShowCreation(rect) ) self.play( LaggedStartMap(FadeIn, equations), randy.change, "confused", equations, VFadeIn(randy), ) self.wait(3) def add_vectors(self): origin = self.axes.coords_to_point(0, 0) dot = self.dot randy = self.pi_creature def rescaled_field(point): x, y = self.axes.point_to_coords(point) result = preditor_prey_vector_field(np.array([x, y, 0])) return self.axes.coords_to_point(*result[:2]) - origin self.vector_field_config.update({ "x_min": origin[0] + 0.5, "x_max": self.axes.get_right()[0] + 1, "y_min": origin[1] + 0.5, "y_max": self.axes.get_top()[1], }) vector_field = VectorField( rescaled_field, **self.vector_field_config ) def get_dot_vector(): vector = vector_field.get_vector(dot.get_center()) vector.scale(1, about_point=vector.get_start()) return vector dot_vector = get_dot_vector() self.play( LaggedStartMap(GrowArrow, vector_field), randy.change, "thinking", dot, Animation(self.differential_equation_group) ) self.wait(3) self.play( Animation(dot), vector_field.set_fill, {"opacity": 0.2}, Animation(self.differential_equation_group), GrowArrow(dot_vector), randy.change, "pondering", ) self.wait() self.play( dot.move_to, dot_vector.get_end(), dot.align_to, dot, RIGHT, run_time=3, ) self.wait(2) self.play( dot.move_to, dot_vector.get_end(), run_time=3, ) self.wait(2) for x in range(6): new_dot_vector = get_dot_vector() fade_anims = [ FadeOut(dot_vector), FadeIn(new_dot_vector), Animation(dot), ] if x == 4: fade_anims += [ vector_field.set_fill, {"opacity": 0.5}, FadeOut(randy), FadeOut(self.differential_equation_group), ] self.play(*fade_anims) dot_vector = new_dot_vector self.play(dot.move_to, dot_vector.get_end()) dot_movement = move_along_vector_field( dot, lambda p: 0.3 * vector_field.func(p) ) self.add(dot_movement) self.play(FadeOut(dot_vector)) self.wait(10) self.play( vector_field.set_fill, {"opacity": 1.0}, VFadeOut(dot), VFadeOut(self.coord_pair), UpdateFromAlphaFunc(self.pop_sizes, lambda m, a: m.set_fill(opacity=1 - a)), ) self.remove( dot_movement, self.coord_pair_update, *self.pop_sizes_updates ) self.wait() self.vector_field = vector_field def show_phase_flow(self): vector_field = self.vector_field stream_lines = StreamLines( vector_field.func, start_points_generator_config={ "x_min": vector_field.x_min, "x_max": vector_field.x_max, "y_min": vector_field.y_min, "y_max": vector_field.y_max, "delta_x": 0.25, "delta_y": 0.25, }, min_magnitude=vector_field.min_magnitude, max_magnitude=vector_field.max_magnitude, virtual_time=4, ) stream_line_animation = AnimatedStreamLines( stream_lines, ) self.add(stream_line_animation) self.add_foreground_mobjects(vector_field) self.wait(self.flow_time) self.play( self.camera_frame.scale, 1.5, {"about_point": self.origin}, run_time=self.flow_time, ) self.wait(self.flow_time) # def get_equations(self): variables = ["X", "YY"] equations = TexMobject( """ {dX \\over dt} = X \\cdot (\\alpha - \\beta YY \\,) \\\\ \\quad \\\\ {dYY \\over dt} = YY \\cdot (\\delta X - \\gamma) """, substrings_to_isolate=variables ) animals = [self.get_rabbit(), self.get_fox().flip()] for char, animal in zip(variables, animals): for part in equations.get_parts_by_tex(char): animal_copy = animal.copy() animal_copy.set_height(0.5) animal_copy.move_to(part, DL) part.become(animal_copy) return equations class PhaseFlowWords(Scene): def construct(self): words = TextMobject("``Phase flow''") words.scale(2) self.play(Write(words)) self.wait() class PhaseFlowQuestions(Scene): def construct(self): questions = VGroup( TextMobject( "Which points does the flow \\\\" + "converge to? Diverge away from?", ), TextMobject("Where are there cycles?"), ) questions.arrange(DOWN, buff=LARGE_BUFF) questions.to_corner(UR) for question in questions: self.play(FadeInFromDown(question)) self.wait(2) class ToolsBeyondDivAndCurlForODEs(PiCreatureScene): CONFIG = { "default_pi_creature_kwargs": { "color": GREY_BROWN, }, "default_pi_creature_start_corner": DOWN, } def construct(self): morty = self.pi_creature div_curl = TextMobject("div \\\\", "curl") div_curl.set_color_by_tex("div", BLUE) div_curl.set_color_by_tex("curl", YELLOW) div_curl.next_to(morty.get_corner(UL), UP, MED_LARGE_BUFF) jacobian = TextMobject("Analyze the \\\\ Jacobian") jacobian.set_color(GREEN) jacobian.next_to(morty.get_corner(UR), UP, MED_LARGE_BUFF) flow_intuitions = TextMobject("Flow-based intuitions") flow_intuitions.next_to( VGroup(div_curl, jacobian), UP, buff=1.5 ) arrow1 = Arrow(div_curl.get_top(), flow_intuitions.get_bottom()) arrow2 = Arrow(flow_intuitions.get_bottom(), jacobian.get_top()) self.play( FadeInFromDown(div_curl), morty.change, "raise_left_hand", ) self.wait() self.play( FadeInFromDown(jacobian), morty.change, "raise_right_hand" ) self.wait() self.play( ReplacementTransform( flow_intuitions.copy().fade(1).move_to(div_curl), flow_intuitions, ), GrowArrow(arrow1), morty.change, "pondering" ) self.wait(0.5) self.play(GrowArrow(arrow2)) self.wait() class AskAboutComputation(TeacherStudentsScene): def construct(self): self.student_says( "Sure, but how do you \\\\" + "\\emph{compute} $\\textbf{div}$ and $\\textbf{curl}$?", target_mode="sassy", ) self.change_student_modes( "confused", "sassy", "angry", added_anims=[self.teacher.change, "guilty"] ) self.wait() self.teacher_says( "Are you familiar \\\\" + "with my work \\\\" + "at Khan Academy?", target_mode="speaking", bubble_kwargs={"width": 4, "height": 3} ) self.change_student_modes( * 3 * ["pondering"], look_at_arg=self.screen ) self.wait(5) class QuickWordsOnNotation(Scene): def construct(self): words = TextMobject("Quick words on notation:") words.scale(1.5) self.play(FadeInFromDown(words)) self.wait() class NablaNotation(PiCreatureScene, MovingCameraScene): CONFIG = { "default_pi_creature_kwargs": { "color": GREY_BROWN, }, "default_pi_creature_start_corner": DL, } def setup(self): MovingCameraScene.setup(self) PiCreatureScene.setup(self) def construct(self): self.show_notation() self.show_expansion() self.zoom_out() def show_notation(self): morty = self.pi_creature tex_to_color_map = { "\\text{div}": BLUE, "\\nabla \\cdot": BLUE, "\\text{curl}": YELLOW, "\\nabla \\times": YELLOW, } div_equation = TexMobject( "\\text{div} \\, \\textbf{F} = \\nabla \\cdot \\textbf{F}", tex_to_color_map=tex_to_color_map ) div_nabla = div_equation.get_part_by_tex("\\nabla") curl_equation = TexMobject( "\\text{curl} \\, \\textbf{F} = \\nabla \\times \\textbf{F}", tex_to_color_map=tex_to_color_map ) curl_nabla = curl_equation.get_part_by_tex("\\nabla") equations = VGroup(div_equation, curl_equation) equations.arrange(DOWN, buff=LARGE_BUFF) equations.next_to(morty, UP, 2) equations.to_edge(LEFT) self.play( FadeInFromDown(div_equation), morty.change, "raise_right_hand" ) self.wait() self.play(WiggleOutThenIn(div_nabla, scale_value=1.5)) self.wait() self.play( FadeInFromDown(curl_equation), morty.change, "raise_left_hand" ) self.wait() self.play(WiggleOutThenIn(curl_nabla, scale_value=1.5)) self.wait() self.equations = equations def show_expansion(self): equations = self.equations morty = self.pi_creature nabla_vector = Matrix([ ["\\partial \\over \\partial x"], ["\\partial \\over \\partial y"], ], v_buff=1.5) F_vector = Matrix([ ["\\textbf{F}_x"], ["\\textbf{F}_y"], ], v_buff=1.2) nabla_vector.match_height(F_vector) div_lhs, curl_lhs = lhs_groups = VGroup(*[ VGroup( nabla_vector.deepcopy(), TexMobject(tex).scale(1.5), F_vector.copy(), TexMobject("=") ) for tex in ("\\cdot", "\\times") ]) colors = [BLUE, YELLOW] for lhs, color in zip(lhs_groups, colors): lhs.arrange(RIGHT, buff=MED_SMALL_BUFF) VGroup(lhs[0].brackets, lhs[1]).set_color(color) div_lhs.to_edge(UP) curl_lhs.next_to(div_lhs, DOWN, buff=LARGE_BUFF) div_rhs = TexMobject( "{\\partial F_x \\over \\partial x} + " + "{\\partial F_y \\over \\partial y}" ) curl_rhs = TexMobject( "{\\partial F_y \\over \\partial x} - " + "{\\partial F_x \\over \\partial y}" ) rhs_groups = VGroup(div_rhs, curl_rhs) for rhs, lhs in zip(rhs_groups, lhs_groups): rhs.next_to(lhs, RIGHT) for rhs, tex, color in zip(rhs_groups, ["div", "curl"], colors): rhs.rect = SurroundingRectangle(rhs, color=color) rhs.label = TexMobject( "\\text{%s}" % tex, "\\, \\textbf{F}" ) rhs.label.set_color(color) rhs.label.next_to(rhs.rect, UP) for i in 0, 1: self.play( ReplacementTransform( equations[i][2].copy(), lhs_groups[i][0].brackets ), ReplacementTransform( equations[i][3].copy(), lhs_groups[i][2], ), morty.change, "pondering", *[ GrowFromPoint(mob, equations[i].get_right()) for mob in [ lhs_groups[i][0].get_entries(), lhs_groups[i][1], lhs_groups[i][3] ] ], run_time=2 ) self.wait() self.wait() for rhs in rhs_groups: self.play( Write(rhs), morty.change, 'confused' ) self.play( ShowCreation(rhs.rect), FadeInFromDown(rhs.label), ) self.wait() self.play(morty.change, "erm") self.wait(3) def zoom_out(self): screen_rect = self.camera_frame.copy() screen_rect.set_stroke(WHITE, 3) screen_rect.scale(1.01) words = TextMobject("Something deeper at play...") words.scale(1.3) words.next_to(screen_rect, UP) self.add(screen_rect) self.play( self.camera_frame.set_height, FRAME_HEIGHT + 3, Write(words, rate_func=squish_rate_func(smooth, 0.3, 1)), run_time=2, ) self.wait() class DivCurlDotCross(Scene): def construct(self): rects = VGroup(*[ ScreenRectangle(height=2.5) for n in range(4) ]) rects.arrange_in_grid(n_rows=2, buff=LARGE_BUFF) rects[2:].shift(MED_LARGE_BUFF * DOWN) titles = VGroup(*list(map(TextMobject, [ "Divergence", "Curl", "Dot product", "Cross product" ]))) for title, rect in zip(titles, rects): title.next_to(rect, UP) self.add(rects, titles) class ShowDotProduct(MovingCameraScene): CONFIG = { "prod_tex": "\\cdot" } def construct(self): plane = NumberPlane() v1 = Vector(RIGHT, color=BLUE) v2 = Vector(UP, color=YELLOW) dot_product = TexMobject( "\\vec{\\textbf{v}}", self.prod_tex, "\\vec{\\textbf{w}}", "=" ) dot_product.set_color_by_tex_to_color_map({ "textbf{v}": BLUE, "textbf{w}": YELLOW, }) dot_product.add_background_rectangle() dot_product.next_to(2.25 * UP, RIGHT) dot_product_value = DecimalNumber( 1.0, include_background_rectangle=True, ) dot_product_value.next_to(dot_product) dot_product_value_update = ContinualChangingDecimal( dot_product_value, lambda a: self.get_product(v1, v2), include_background_rectangle=True, ) self.camera_frame.set_height(4) self.camera_frame.move_to(DL, DL) self.add(plane) self.add(dot_product, dot_product_value_update) self.add_additional_continual_animations(v1, v2) self.add_foreground_mobjects(v1, v2) for n in range(5): self.play( Rotate(v1, 45 * DEGREES, about_point=ORIGIN), Rotate(v2, -45 * DEGREES, about_point=ORIGIN), run_time=3, rate_func=there_and_back ) self.wait(0.5) def get_product(self, v1, v2): return np.dot(v1.get_vector(), v2.get_vector()) def add_additional_continual_animations(self, v1, v2): pass class ShowCrossProduct(ShowDotProduct): CONFIG = { "prod_tex": "\\times" } def get_product(self, v1, v2): return get_norm( np.cross(v1.get_vector(), v2.get_vector()) ) def add_additional_continual_animations(self, v1, v2): square = Square( stroke_color=YELLOW, stroke_width=3, fill_color=YELLOW, fill_opacity=0.2, ) self.add(Mobject.add_updater( square, lambda s: s.set_points_as_corners([ ORIGIN, v1.get_end(), v1.get_end() + v2.get_end(), v2.get_end(), ]) )) class DivergenceTinyNudgesView(MovingCameraScene): CONFIG = { "scale_factor": 0.25, "point": ORIGIN, } def construct(self): self.add_vector_field() self.zoom_in() self.take_tiny_step() self.show_dot_product() self.show_circle_of_values() self.switch_to_curl_words() self.rotate_difference_vectors() def add_vector_field(self): plane = self.plane = NumberPlane() def func(p): x, y = p[:2] result = np.array([ np.sin(x + 0.1), np.cos(2 * y), 0 ]) result /= (get_norm(result)**0.5 + 1) return result vector_field = self.vector_field = VectorField( func, length_func=lambda n: 0.5 * sigmoid(n), # max_magnitude=1.0, ) self.add(plane) self.add(vector_field) def zoom_in(self): point = self.point vector_field = self.vector_field sf = self.scale_factor vector_field.vector_config.update({ "rectangular_stem_width": 0.02, "tip_length": 0.1, }) vector_field.length_func = lambda n: n vector = vector_field.get_vector(point) input_dot = Dot(point).scale(sf) input_words = TextMobject("$(x_0, y_0)$").scale(sf) input_words.next_to(input_dot, DL, SMALL_BUFF * sf) output_words = TextMobject("Output").scale(sf) output_words.add_background_rectangle() output_words.next_to(vector.get_top(), UP, sf * SMALL_BUFF) output_words.match_color(vector) self.play( self.camera_frame.scale, sf, self.camera_frame.move_to, point, FadeOut(vector_field), FadeIn(vector), run_time=2 ) self.add_foreground_mobjects(input_dot) self.play( FadeInFrom(input_dot, SMALL_BUFF * DL), Write(input_words), ) self.play( Indicate(vector), Write(output_words), ) self.wait() self.set_variables_as_attrs( point, vector, input_dot, input_words, output_words, ) def take_tiny_step(self): sf = self.scale_factor vector_field = self.vector_field point = self.point vector = self.vector output_words = self.output_words input_dot = self.input_dot nudge = 0.5 * RIGHT nudged_point = point + nudge new_vector = vector_field.get_vector(nudged_point) new_vector.set_color(YELLOW) new_dot = Dot(nudged_point).scale(sf) step_vector = Arrow( point, nudged_point, buff=0, color=TEAL, **vector_field.vector_config ) step_vector.set_stroke(BLACK, 0.5) new_output_words = TextMobject("New output").scale(sf) new_output_words.add_background_rectangle() new_output_words.next_to(new_vector.get_end(), UP, sf * SMALL_BUFF) new_output_words.match_color(new_vector) step_words = TextMobject("Step").scale(sf) step_words.next_to(step_vector, UP, buff=0) step_words.set_color(step_vector.get_fill_color()) step_words.add_background_rectangle() small_step_words = TextMobject("(think tiny step)").scale(sf) small_step_words.next_to( step_words, RIGHT, buff=sf * MED_SMALL_BUFF, ) small_step_words.add_background_rectangle() small_step_words.match_style(step_words) shifted_vector = vector.copy().shift(nudge) diff_vector = Arrow( shifted_vector.get_end(), new_vector.get_end(), buff=0, color=RED, **vector_field.vector_config ) diff_words = TextMobject("Difference").scale(sf) diff_words.add_background_rectangle() diff_words.next_to(diff_vector.get_start(), UR, buff=2 * sf * SMALL_BUFF) diff_words.match_color(diff_vector) diff_words.rotate( diff_vector.get_angle(), about_point=diff_vector.get_start() ) self.play( GrowArrow(step_vector), Write(step_words), ReplacementTransform(input_dot.copy(), new_dot) ) self.add_foreground_mobjects(new_dot) self.play(FadeIn(small_step_words)) self.play(FadeOut(small_step_words)) self.play( ReplacementTransform(vector.copy(), new_vector), ReplacementTransform(output_words.copy(), new_output_words), ) self.wait() self.play(ReplacementTransform( vector.copy(), shifted_vector, path_arc=-TAU / 4 )) self.wait() self.play( FadeOut(output_words), FadeOut(new_output_words), GrowArrow(diff_vector), Write(diff_words) ) self.wait() self.play( vector.scale, 0, {"about_point": vector.get_start()}, shifted_vector.scale, 0, {"about_point": shifted_vector.get_start()}, ReplacementTransform( new_vector, diff_vector.copy().shift(-vector.get_vector()), remover=True ), diff_vector.shift, -vector.get_vector(), MaintainPositionRelativeTo(diff_words, diff_vector), run_time=2 ) self.wait() self.set_variables_as_attrs( step_vector, step_words, diff_vector, diff_words, ) def show_dot_product(self): sf = self.scale_factor point = self.point step_vector = self.step_vector step_words = self.step_words diff_vector = self.diff_vector diff_words = self.diff_words vects = VGroup(step_vector, diff_vector) moving_step_vector = step_vector.copy() moving_diff_vector = diff_vector.copy() def update_moving_diff_vector(dv): step = moving_step_vector.get_vector() o1 = self.vector_field.get_vector(point).get_vector() o2 = self.vector_field.get_vector(point + step).get_vector() diff = o2 - o1 dv.put_start_and_end_on( moving_step_vector.get_end(), moving_step_vector.get_end() + diff, ) self.moving_diff_vector_update = Mobject.add_updater( moving_diff_vector, update_moving_diff_vector ) self.add(self.moving_diff_vector_update) div_text = self.get_operator_text("div") step_words_copy = step_words.copy() diff_words_copy = diff_words.copy() copies = VGroup(step_words_copy, diff_words_copy) substrings = ["Step", "Difference"] dot_product = TextMobject( "(Step) $\\cdot$ (Difference)", substrings_to_isolate=substrings, arg_separator="", ).scale(sf) group = VGroup(div_text, dot_product) group.arrange(RIGHT, buff=sf * MED_SMALL_BUFF) group.next_to( self.camera_frame.get_top(), DOWN, buff=sf * MED_SMALL_BUFF ) for substring, mob, vect in zip(substrings, copies, vects): part = dot_product.get_part_by_tex(substring) mob.generate_target() mob.target.rotate(-vect.get_angle()) mob.target.replace(part) # part.set_fill(opacity=0) part.match_color(mob) dot_product.add_background_rectangle() brace = Brace( dot_product.copy().scale(1 / sf, about_point=ORIGIN), DOWN, buff=SMALL_BUFF ).scale(sf, about_point=ORIGIN) dp_kwargs = { "include_sign": True, } dot_product_value = DecimalNumber(1.0, **dp_kwargs) dot_product_value.scale(sf) dot_product_value.next_to(brace, DOWN, sf * SMALL_BUFF) dot_product_value_update = ContinualChangingDecimal( dot_product_value, lambda a: np.dot( moving_step_vector.get_vector(), moving_diff_vector.get_vector(), ), **dp_kwargs ) self.play( Write(dot_product), LaggedStartMap(MoveToTarget, copies) ) self.remove(copies) self.play(FadeIn(div_text)) self.play(ShowPassingFlashAround( div_text[1:3], surrounding_rectangle_config={"buff": sf * SMALL_BUFF} )) self.add(BackgroundRectangle(dot_product_value)) self.play( GrowFromCenter(brace), Write(dot_product_value), ) self.add(dot_product_value_update) self.wait() self.set_variables_as_attrs( div_text, dot_product, moving_step_vector, moving_diff_vector, dot_product_value, dot_product_value_update, brace, ) def show_circle_of_values(self): point = self.point moving_step_vector = self.moving_step_vector moving_diff_vector = self.moving_diff_vector all_diff_vectors = VGroup() all_step_vectors = VGroup() # Loop around n_samples = 12 angle = TAU / n_samples self.add_foreground_mobjects(self.step_words) for n in range(n_samples): self.play( Rotating( moving_step_vector, radians=angle, about_point=point, run_time=15.0 / n_samples, rate_func=linear, ) ) step_vector_copy = moving_step_vector.copy() diff_vector_copy = moving_diff_vector.copy() diff_vector_copy.set_stroke(BLACK, 0.5) self.add(step_vector_copy, diff_vector_copy) all_step_vectors.add(step_vector_copy) all_diff_vectors.add(diff_vector_copy) self.remove( self.step_vector, self.diff_vector, self.moving_step_vector, self.moving_diff_vector, self.moving_diff_vector_update, self.dot_product_value_update ) self.remove_foreground_mobjects(self.step_words) self.play( FadeOut(self.brace), FadeOut(self.dot_product_value), FadeOut(self.step_words), FadeOut(self.diff_words), ) self.wait() for s in 0.6, -0.6: for step, diff in zip(all_step_vectors, all_diff_vectors): diff.generate_target() diff.target.put_start_and_end_on( step.get_end(), step.get_end() + s * step.get_vector() ) self.play( all_step_vectors.set_fill, {"opacity": 0.5}, LaggedStartMap( MoveToTarget, all_diff_vectors, run_time=3 ), ) self.wait() self.show_stream_lines(lambda p: s * (p - point)) self.wait() self.set_variables_as_attrs( all_step_vectors, all_diff_vectors, ) def switch_to_curl_words(self): sf = self.scale_factor div_text = self.div_text dot_product = self.dot_product curl_text = self.get_operator_text("curl") cross_product = TextMobject( "(Step) $\\times$ (Difference)", tex_to_color_map={ "Step": TEAL, "Difference": RED }, arg_separator="", ).scale(sf) cross_product.add_background_rectangle(opacity=1) group = VGroup(curl_text, cross_product) group.arrange(RIGHT, buff=sf * MED_SMALL_BUFF) group.next_to(self.camera_frame.get_top(), sf * DOWN) self.play( dot_product.shift, sf * DOWN, dot_product.fade, 1, remover=True ) self.play(FadeInFrom(cross_product, sf * DOWN)) self.play( div_text.shift, sf * DOWN, div_text.fade, 1, remover=True ) self.play(FadeInFrom(curl_text, sf * DOWN)) self.wait() def rotate_difference_vectors(self): point = self.point all_step_vectors = self.all_step_vectors all_diff_vectors = self.all_diff_vectors for s in 0.6, -0.6: for step, diff in zip(all_step_vectors, all_diff_vectors): diff.generate_target() diff.target.put_start_and_end_on( step.get_end(), step.get_end() + s * rotate_vector( step.get_vector(), 90 * DEGREES ) ) self.play( LaggedStartMap( MoveToTarget, all_diff_vectors, run_time=2 ), ) self.wait() self.show_stream_lines( lambda p: s * rotate_vector((p - point), 90 * DEGREES) ) self.wait() self.set_variables_as_attrs( all_step_vectors, all_diff_vectors, ) # Helpers def get_operator_text(self, operator): text = TextMobject( operator + "\\,", "$\\textbf{F}(x_0, y_0)\\,$", "corresponds to average of", arg_separator="" ).scale(self.scale_factor) text.set_color_by_tex(operator, YELLOW) text.add_background_rectangle() return text def show_stream_lines(self, func): point = self.point stream_lines = StreamLines( func, start_points_generator_config={ "x_min": point[0] - 2, "x_max": point[0] + 2, "y_min": point[1] - 1, "y_max": point[1] + 1, "delta_x": 0.025, "delta_y": 0.025, }, virtual_time=1, ) random.shuffle(stream_lines.submobjects) self.play(LaggedStartMap( ShowPassingFlash, stream_lines, run_time=4, )) class ZToHalfFlowNearWall(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(*[l for l in list(planes) + [plane.axes[0]] if np.abs(l.get_center()[0]) < 0.1]) plane.set_stroke(MAROON_B, width=2) horizontal_lines.set_stroke(BLUE, width=2) 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("(Idealized) 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)) def func(point): z = R3_to_complex(point) d_half = derivative(lambda z: z**2) return complex_to_R3(d_half(z).conjugate()) stream_lines = StreamLines( func, start_points_generator_config={ "x_min": 0.01, "y_min": 0.01, "delta_x": 0.125, "delta_y": 0.125, }, virtual_time=3, stroke_width=2, max_magnitude=10, ) stream_line_animation = AnimatedStreamLines(stream_lines) self.add(stream_line_animation) self.wait(7) class IncmpressibleAndIrrotational(Scene): def construct(self): div_0 = TextMobject("div$\\textbf{F} = 0$") curl_0 = TextMobject("curl$\\textbf{F}$ = 0") incompressible = TextMobject("Incompressible") irrotational = TextMobject("Irrotational") for text in [div_0, curl_0, incompressible, irrotational]: self.stylize_word_for_background(text) div_0.to_edge(UP) curl_0.next_to(div_0, DOWN, MED_LARGE_BUFF) for op, word in (div_0, incompressible), (curl_0, irrotational): op.generate_target() group = VGroup(op.target, word) group.arrange(RIGHT, buff=MED_LARGE_BUFF) group.move_to(op) self.play(FadeInFromDown(div_0)) self.play(FadeInFromDown(curl_0)) self.wait() self.play( MoveToTarget(div_0), FadeInFromDown(incompressible), ) self.wait() self.play( MoveToTarget(curl_0), FadeInFromDown(irrotational), ) self.wait() rect = SurroundingRectangle(VGroup(curl_0, irrotational)) question = TextMobject("Does this actually happen?") question.next_to(rect, DOWN) question.match_color(rect) self.stylize_word_for_background(question) self.play(ShowCreation(rect)) self.play(Write(question)) self.wait() def stylize_word_for_background(self, word): word.add_background_rectangle() class NoChargesOverlay(Scene): def construct(self): rect = FullScreenFadeRectangle() rect.set_fill(BLUE_D, 0.75) circle = Circle(radius=1.5, num_anchors=5000) circle.rotate(135 * DEGREES) rect.add_subpath(circle.points) words = TextMobject("No charges outside wire") words.scale(1.5) words.to_edge(UP) self.add(rect, words) # End message class BroughtToYouBy(PiCreatureScene): CONFIG = { "pi_creatures_start_on_screen": False, } def construct(self): self.brought_to_you_by() self.just_you_and_me() def brought_to_you_by(self): so_words = TextMobject("So", "...", arg_separator="") so_words.scale(2) btyb = TextMobject("Brought to you", "by") btyb.scale(1.5) btyb_line = Line(LEFT, RIGHT) btyb_line.next_to(btyb, RIGHT, SMALL_BUFF) btyb_line.align_to(btyb[0], DOWN) btyb_group = VGroup(btyb, btyb_line) btyb_group.center() you_word = TextMobject("\\emph{you}") you_word.set_color(YELLOW) you_word.scale(1.75) you_word.move_to(btyb_line) you_word.align_to(btyb, DOWN) only_word = TextMobject("(only)") only_word.scale(1.25) only_brace = Brace(only_word, DOWN, buff=SMALL_BUFF) only_group = VGroup(only_word, only_brace) only_group.next_to( VGroup(btyb[0][-1], btyb[1][0]), UP, SMALL_BUFF ) only_group.set_color(RED) full_group = VGroup(btyb_group, only_group, you_word) full_group.generate_target() full_group.target.scale(0.4) full_group.target.to_corner(UL) patreon_logo = PatreonLogo() patreon_logo.scale(0.4) patreon_logo.next_to(full_group.target, DOWN) self.play( Write(so_words[0]), LaggedStartMap( DrawBorderThenFill, so_words[1], run_time=5 ), ) self.play( so_words.shift, DOWN, so_words.fade, 1, remover=True ) self.play(FadeInFromDown(btyb_group)) self.wait() self.play(Write(you_word)) self.play( GrowFromCenter(only_brace), Write(only_word) ) self.wait() self.play(MoveToTarget( full_group, rate_func=running_start, )) self.play(LaggedStartMap( DrawBorderThenFill, patreon_logo )) self.wait() def just_you_and_me(self): randy, morty = self.pi_creatures for pi in self.pi_creatures: pi.change("pondering") math = TexMobject("\\sum_{n=1}^\\infty \\frac{1}{n^s}") math.scale(2) math.move_to(self.pi_creatures) spiral = Line(0.5 * RIGHT, 0.5 * RIGHT + 70 * UP) spiral.insert_n_curves(1000) from old_projects.zeta import zeta spiral.apply_complex_function(zeta) step = 0.1 spiral = VGroup(*[ VMobject().pointwise_become_partial( spiral, a, a + step ) for a in np.arange(0, 1, step) ]) spiral.set_color_by_gradient(BLUE, YELLOW, RED) spiral.scale(0.5) spiral.move_to(math) self.play(FadeInFromDown(randy)) self.play(FadeInFromDown(morty)) self.play( Write(math), randy.change, "hooray", morty.change, "hooray", ) self.look_at(math) self.play( ShowCreation(spiral, run_time=6, rate_func=linear), math.scale, 0.5, math.shift, 3 * UP, randy.change, "thinking", morty.change, "thinking", ) self.play(LaggedStartMap(FadeOut, spiral, run_time=3)) self.wait(3) # Helpers def create_pi_creatures(self): randy = Randolph(color=BLUE_C) randy.to_edge(DOWN).shift(4 * LEFT) morty = Mortimer() morty.to_edge(DOWN).shift(4 * RIGHT) return VGroup(randy, morty) class ThoughtsOnAds(Scene): def construct(self): title = Title( "Internet advertising", match_underline_width_to_text=True, underline_buff=SMALL_BUFF, ) line = NumberLine( color=LIGHT_GREY, x_min=0, x_max=12, numbers_with_elongated_ticks=[] ) line.move_to(DOWN) arrows = VGroup(Vector(2 * LEFT), Vector(2 * RIGHT)) arrows.arrange(RIGHT, buff=2) arrows.next_to(line, DOWN) misaligned = TextMobject("Misaligned") misaligned.next_to(arrows[0], DOWN) aligned = TextMobject("Well-aligned") aligned.next_to(arrows[1], DOWN) VGroup(arrows[0], misaligned).set_color(RED) VGroup(arrows[1], aligned).set_color(BLUE) left_text = TextMobject( "Any website presented \\\\", "as a click-maximizing \\\\ slideshow" ) left_text.scale(0.8) left_text.next_to(line, UP, buff=MED_LARGE_BUFF) left_text.to_edge(LEFT) viewer, brand, creator = vcb = VGroup( *list(map(TextMobject, ["viewer", "brand", "creator"])) ) brand.next_to(creator, LEFT, LARGE_BUFF) viewer.next_to(vcb[1:], UP, LARGE_BUFF) arrow_config = { "path_arc": 60 * DEGREES, "tip_length": 0.15, } vcb_arrows = VGroup(*[ VGroup( Arrow(p1, p2, **arrow_config), Arrow(p2, p1, **arrow_config), ) for p1, p2 in [ (creator.get_left(), brand.get_right()), (brand.get_top(), viewer.get_bottom()), (viewer.get_bottom(), creator.get_top()), ] ]) vcb_arrows.set_stroke(width=2) vcb_arrows.set_color(BLUE) vcb_group = VGroup(vcb, vcb_arrows) vcb_group.next_to(line, UP, buff=MED_LARGE_BUFF) vcb_group.to_edge(RIGHT) knob = RegularPolygon(n=3, start_angle=-90 * DEGREES) knob.set_height(0.25) knob.set_stroke(width=0) knob.set_fill(YELLOW, 1) knob.move_to(line.get_left(), DOWN) right_rect = Rectangle( width=3, height=0.25, stroke_color=WHITE, stroke_width=2, fill_color=BLUE, fill_opacity=0.5 ) right_rect.move_to(line, RIGHT) right_rect_label = Group( ImageMobject("3b1b_logo", height=1), TextMobject("(hopefully)").scale(0.8) ) right_rect_label.arrange(DOWN, buff=SMALL_BUFF) # TextMobject( # "Where I hope \\\\ I've been" # ) right_rect_label.next_to( right_rect, UP, SMALL_BUFF ) # right_rect_label.set_color(BLUE) self.add(title) self.play(ShowCreation(line)) self.play( Write(misaligned), Write(aligned), *list(map(GrowArrow, arrows)), run_time=1 ) self.play( FadeIn(left_text), FadeInFrom(knob, 2 * RIGHT) ) self.wait() self.play( LaggedStartMap(FadeInFromDown, vcb), LaggedStartMap(ShowCreation, vcb_arrows), ApplyMethod( knob.move_to, line.get_right(), DOWN, run_time=2 ) ) self.wait(2) self.play(vcb_group.shift, 2 * UP) self.play( DrawBorderThenFill(right_rect), FadeIn(right_rect_label), ) self.wait() class HoldUpPreviousPromo(PiCreatureScene): CONFIG = { "default_pi_creature_kwargs": { "color": GREY_BROWN, "flip_at_start": True, }, "default_pi_creature_start_corner": DR, } def construct(self): morty = self.pi_creature screen_rect = ScreenRectangle(height=5) screen_rect.to_corner(UL) self.play( FadeInFromDown(screen_rect), morty.change, "raise_right_hand", ) self.wait(5) class GoalWrapper(Scene): def construct(self): goal = TextMobject( "Goal: Teach/remind people \\\\ that they love math" ) goal.to_edge(UP) self.add(goal) screen_rect = ScreenRectangle(height=6) screen_rect.next_to(goal, DOWN) self.play(ShowCreation(screen_rect)) self.wait() class PeopleValueGraph(GraphScene): CONFIG = { "x_axis_label": "People reached", "y_axis_label": "Value per person", "x_min": 0, "x_max": 12, "x_axis_width": 11, "y_max": 8, "y_axis_height": 5, "graph_origin": 2 * DOWN + 5 * LEFT, "riemann_rectangles_config": { "dx": 0.01, "stroke_width": 0, "start_color": GREEN, "end_color": BLUE, } } def construct(self): self.setup_axes() self.tweak_labels() self.add_curve() self.comment_on_incentives() self.change_curve() def tweak_labels(self): self.add_foreground_mobjects(self.x_axis_label_mob) self.y_axis_label_mob.to_edge(LEFT) def add_curve(self): graph = self.graph = self.get_graph( lambda x: 7 * np.exp(-0.5 * x), ) self.play( ShowCreation(graph), rate_func=bezier([0, 0, 1, 1]), run_time=3 ) def comment_on_incentives(self): reach_arrow = Vector(5 * RIGHT) reach_arrow.next_to( self.x_axis, DOWN, buff=SMALL_BUFF, aligned_edge=RIGHT ) reach_words = TextMobject("Maximize reach?") reach_words.next_to(reach_arrow, DOWN, buff=SMALL_BUFF) reach_words.match_color(reach_arrow) area = self.area = self.get_riemann_rectangles( self.graph, **self.riemann_rectangles_config ) area_words = TextMobject("Maximize this area") area_words.set_color(BLUE) area_words.move_to(self.coords_to_point(4, 5)) area_arrow = Arrow( area_words.get_bottom(), self.coords_to_point(1.5, 2) ) self.play(GrowArrow(reach_arrow)) self.play(Write(reach_words)) self.wait() self.play( LaggedStartMap(DrawBorderThenFill, area), Animation(self.graph), Animation(self.axes), Write(area_words), GrowArrow(area_arrow), ) self.wait() self.area_label_group = VGroup(area_words, area_arrow) def change_curve(self): new_graph = self.get_graph( lambda x: interpolate( 7 * np.exp(-0.01 * x), 7 * np.exp(-3 * x), smooth(np.clip(x / 5, 0, 1)) ) ) new_area = self.get_riemann_rectangles( new_graph, **self.riemann_rectangles_config ) self.play( Transform(self.area, new_area), Transform(self.graph, new_graph), self.area_label_group[0].shift, RIGHT, Animation(self.area_label_group), Animation(self.axes), run_time=4, ) self.wait() class DivCurlEndScreen(PatreonEndScreen): CONFIG = { "specific_patrons": [ "Juan Benet", "Keith Smith", "Chloe Zhou", "Desmos ", "Burt Humburg", "CrypticSwarm", "Andrew Sachs", "Devin Scott", "Akash Kumar", "Felix Tripier", "Arthur Zey", "David Kedmey", "Ali Yahya", "Mayank M. Mehrotra", "Lukas Biewald", "Yana Chernobilsky", "Kaustuv DeBiswas", "Yu Jun", "Dave Nicponski", "Damion Kistler", "Jordan Scales", "Markus Persson", "Fela ", "Fred Ehrsam", "Randy C. Will", "Britt Selvitelle", "Jonathan Wilson", "Ryan Atallah", "Joseph John Cox", "Luc Ritchie", "Omar Zrien", "Sindre Reino Trosterud", "Jeff Straathof", "Matt Langford", "Matt Roveto", "Marek Cirkos", "Magister Mugit", "Stevie Metke", "Cooper Jones", "James Hughes", "John V Wertheim", "Chris Giddings", "Song Gao", "Alexander Feldman", "Richard Burgmann", "John Griffith", "Chris Connett", "Steven Tomlinson", "Jameel Syed", "Bong Choung", "Ignacio Freiberg", "Zhilong Yang", "Giovanni Filippi", "Eric Younge", "Prasant Jagannath", "James H. Park", "Norton Wang", "Kevin Le", "Tianyu Ge", "David MacCumber", "Oliver Steele", "Yaw Etse", "Dave B", "Waleed Hamied", "George Chiesa", "supershabam ", "Delton Ding", "Thomas Tarler", "1stViewMaths", "Jacob Magnuson", "Mark Govea", "Clark Gaebel", "Mathias Jansson", "David Clark", "Michael Gardner", "Mads Elvheim", "Awoo ", "Dr . David G. Stork", "Ted Suzman", "Linh Tran", "Andrew Busey", "John Haley", "Ankalagon ", "Eric Lavault", "Boris Veselinovich", "Julian Pulgarin", "Jeff Linse", "Robert Teed", "Jason Hise", "Bernd Sing", "Mustafa Mahdi", "Mathew Bramson", "Jerry Ling", "Sh\\`im\\'in Ku\\=ang", "Rish Kundalia", "Achille Brighton", "Ripta Pasay", ], }