from manim_imports_ext import * def get_matrix_exponential(matrix, height=1.5, scalar_tex="t", **matrix_config): elem = matrix[0][0] if isinstance(elem, str): mat_class = Matrix elif isinstance(elem, int) or isinstance(elem, np.int64): mat_class = IntegerMatrix else: mat_class = DecimalMatrix matrix = mat_class(matrix, **matrix_config) base = OldTex("e") base.set_height(0.4 * height) matrix.set_height(0.6 * height) matrix.move_to(base.get_corner(UR), DL) result = VGroup(base, matrix) if scalar_tex: scalar = OldTex(scalar_tex) scalar.set_height(0.7 * base.get_height()) scalar.next_to(matrix, RIGHT, buff=SMALL_BUFF, aligned_edge=DOWN) result.add(scalar) return result def get_vector_field_and_stream_lines( func, coordinate_system, magnitude_range=(0.5, 4), vector_opacity=0.75, vector_thickness=0.03, color_by_magnitude=False, line_color=GREY_A, line_width=3, line_opacity=0.75, sample_freq=5, n_samples_per_line=10, arc_len=3, time_width=0.3, ): vector_field = VectorField( func, coordinate_system, magnitude_range=magnitude_range, vector_config={ "fill_opacity": vector_opacity, "thickness": vector_thickness, } ) stream_lines = StreamLines( func, coordinate_system, step_multiple=1.0 / sample_freq, n_samples_per_line=n_samples_per_line, arc_len=arc_len, magnitude_range=magnitude_range, color_by_magnitude=color_by_magnitude, stroke_color=line_color, stroke_width=line_width, stroke_opacity=line_opacity, ) animated_lines = AnimatedStreamLines( stream_lines, line_anim_config={ "time_width": time_width, }, ) return vector_field, animated_lines def mat_exp(matrix, N=100): curr = np.identity(len(matrix)) curr_sum = curr for n in range(1, N): curr = np.dot(curr, matrix) / n curr_sum += curr return curr_sum def get_1d_equation(r="r"): return OldTex( "{d \\over d{t}} x({t}) = {" + r + "} \\cdot x({t})", tex_to_color_map={ "{t}": GREY_B, "{" + r + "}": BLUE, "=": WHITE, } ) def get_2d_equation(matrix=[["a", "b"], ["c", "d"]]): deriv = OldTex("d \\over dt", tex_to_color_map={"t": GREY_B}) vect = Matrix( [["x(t)"], ["y(t)"]], bracket_h_buff=SMALL_BUFF, bracket_v_buff=SMALL_BUFF, element_to_mobject_config={ "tex_to_color_map": {"t": GREY_B}, "isolate": ["(", ")"] } ) deriv.match_height(vect) equals = OldTex("=") matrix_mob = Matrix(matrix, h_buff=0.8) matrix_mob.set_color(TEAL) matrix_mob.match_height(vect) equation = VGroup(deriv, vect, equals, matrix_mob, vect.deepcopy()) equation.arrange(RIGHT) return equation class VideoWrapper(Scene): title = "" def construct(self): self.add(FullScreenRectangle()) screen_rect = ScreenRectangle(height=6) screen_rect.set_stroke(BLUE_D, 1) screen_rect.set_fill(BLACK, 1) screen_rect.to_edge(DOWN) self.add(screen_rect) title = OldTexText(self.title, font_size=90) if title.get_width() > screen_rect.get_width(): title.set_width(screen_rect.get_width()) title.next_to(screen_rect, UP) self.play(Write(title)) self.wait() # Video scenes class ArnoldBookClip(ExternallyAnimatedScene): pass class ZoomInOnProblem(Scene): def construct(self): # Highlight problem image = ImageMobject("mat_exp_exercise.png") image.set_height(FRAME_HEIGHT) prob_rect = Rectangle(3.25, 0.35) prob_rect.move_to([-2.5, -2.5, 0]) prob_rect.set_stroke(BLUE, 2) examples_rect = Rectangle(2.0, 0.8) examples_rect.move_to([01.8, 2.8, 0.0]) examples_rect.set_stroke(YELLOW, 3) answer_rect = Rectangle(3.0, 2.0) answer_rect.move_to(examples_rect, UR) answer_rect.shift(0.1 * RIGHT) full_rect = FullScreenRectangle() full_rect.flip() full_rect.set_fill(BLACK, opacity=0.6) full_rect.append_vectorized_mobject(prob_rect) full_rect2 = full_rect.copy() full_rect3 = full_rect.copy() full_rect.append_vectorized_mobject(examples_rect.copy().scale(1e-6)) full_rect2.append_vectorized_mobject(examples_rect) full_rect3.append_vectorized_mobject(answer_rect) self.add(image) # Write problem problem = OldTexText( "Compute the {{matrix}} {{$e^{At}$}}\\\\if the {{matrix A}} has the form..." ) problem.to_corner(UL) problem.set_stroke(BLACK, 5, background=True) prob_arrow = Arrow(prob_rect, problem) prob_arrow.set_fill(BLUE) mat_underline = Underline(problem.get_part_by_tex("matrix")) mat_underline.set_color(YELLOW) self.play( ShowCreation(prob_rect), FadeIn(full_rect), FadeTransform(prob_rect.copy(), problem), GrowArrow(prob_arrow), ) self.wait() for part in ["e^{At}", "matrix A"]: self.play(FlashAround(problem.get_part_by_tex(part), run_time=2, time_width=4)) self.wait() self.play(ShowCreation(mat_underline)) self.wait() mat_underline.rotate(PI) self.play(Uncreate(mat_underline)) self.wait() # Show inputs examples_label = OldTexText("Various matrices to\\\\plug in for $A$", font_size=40) examples_label.next_to(examples_rect) lhs = OldTex("A = ", font_size=96) lhs.set_stroke(BLACK, 5, background=True) matrices = VGroup( IntegerMatrix([[1, 0], [0, 2]]), IntegerMatrix([[0, 1], [0, 0]]), IntegerMatrix([[0, 1], [-1, 0]]), IntegerMatrix([[0, 1, 0], [0, 0, 1], [0, 0, 0]]), ) eq = VGroup(lhs, matrices[-1]) eq.arrange(RIGHT) eq.next_to(examples_rect, DOWN, LARGE_BUFF, aligned_edge=LEFT) eq.shift_onto_screen() for matrix in matrices: matrix.move_to(matrices[-1], LEFT) matrix.set_stroke(BLACK, 5, background=True) self.play( Transform(full_rect, full_rect2) ) self.play( ShowCreation(examples_rect), FadeIn(examples_label), FadeIn(lhs), FadeIn(matrices[0]), ) self.wait() for m1, m2 in zip(matrices, matrices[1:]): self.play(FadeTransform(m1, m2)) self.wait() class LeadToPhysicsAndQM(Scene): def construct(self): de_words = OldTexText("Differential\\\\equations", font_size=60) de_words.set_x(-3).to_edge(UP) mat_exp = get_matrix_exponential([[3, 1, 4], [1, 5, 9], [2, 6, 5]]) mat_exp[1].set_color(TEAL) mat_exp.next_to(de_words, DOWN, buff=3) qm_words = OldTexText("Quantum\\\\mechanics", font_size=60) qm_words.set_x(3).to_edge(UP) physics_words = OldTexText("Physics", font_size=60) physics_words.move_to(qm_words) qm_exp = OldTex("e^{-i \\hat{H} t / \\hbar}") qm_exp.scale(2) qm_exp.refresh_bounding_box() qm_exp[0][0].set_height(mat_exp[0].get_height(), about_edge=UR) qm_exp[0][0].shift(SMALL_BUFF * DOWN) qm_exp.match_x(qm_words) qm_exp.align_to(mat_exp, DOWN) qm_exp[0][3:5].set_color(TEAL) de_arrow = Arrow(de_words, mat_exp) qm_arrow = Arrow(qm_words, qm_exp) top_arrow = Arrow(de_words, qm_words) self.add(de_words) self.play( GrowArrow(de_arrow), FadeIn(mat_exp, shift=DOWN), ) self.wait() self.play( GrowArrow(top_arrow), FadeIn(physics_words, RIGHT) ) self.wait() self.play( FadeOut(physics_words, UP), FadeIn(qm_words, UP), ) self.play( TransformFromCopy(de_arrow, qm_arrow), FadeTransform(mat_exp.copy(), qm_exp), ) self.wait() class LaterWrapper(VideoWrapper): title = "Later..." class PlanForThisVideo(TeacherStudentsScene): def construct(self): s0, s1, s2 = self.students self.play( PiCreatureSays(s0, OldTexText("But what\\\\is $e^{M}$?"), target_mode="raise_left_hand"), s1.change("erm", UL), s2.change("pondering", UL), ) self.wait() s0.bubble = None self.play( PiCreatureSays( s2, OldTexText("And who cares?"), target_mode="sassy", bubble_config={"direction": LEFT}, ), s1.change("hesitant", UL), self.teacher.change("guilty") ) self.wait(3) class IntroduceTheComputation(Scene): def construct(self): # Matrix in exponent base = OldTex("e") base.set_height(1.0) matrix = IntegerMatrix( [[3, 1, 4], [1, 5, 9], [2, 6, 5]], ) matrix.move_to(base.get_corner(UR), DL) matrix_exp = VGroup(base, matrix) matrix_exp.set_height(2) matrix_exp.to_corner(UL) matrix_exp.shift(3 * RIGHT) randy = Randolph() randy.set_height(2) randy.to_corner(DL) matrix.save_state() matrix.center() matrix.set_height(2.5) self.add(randy) self.play( randy.change("pondering", matrix), Write(matrix.get_brackets()), ShowIncreasingSubsets(matrix.get_entries()), ) self.play( matrix.animate.restore(), Write(base), randy.change("erm", base), ) self.play(Blink(randy)) # Question the repeated multiplication implication rhs = OldTex("= e \\cdot e \\dots e \\cdot e") rhs.set_height(0.75 * base.get_height()) rhs.next_to(matrix_exp, RIGHT) rhs.align_to(base, DOWN) brace = Brace(rhs[0][1:], DOWN) matrix_copy = matrix.copy() matrix_copy.scale(0.5) brace_label = VGroup( matrix.copy().scale(0.5), Text("times?") ) brace_label.arrange(RIGHT) brace_label.next_to(brace, DOWN, SMALL_BUFF) bubble = randy.get_bubble( OldTexText("I'm sorry,\\\\what?!").scale(0.75), height=2, width=3, bubble_type=SpeechBubble, ) self.play( TransformMatchingParts( base.copy(), rhs, path_arc=10 * DEGREES, lag_ratio=0.01, ), GrowFromCenter(brace), ReplacementTransform( matrix.copy(), brace_label[0], path_arc=30 * DEGREES, run_time=2, rate_func=squish_rate_func(smooth, 0.3, 1), ), Write( brace_label[1], run_time=2, rate_func=squish_rate_func(smooth, 0.5, 1), ), randy.change("angry", rhs), ShowCreation(bubble), Write(bubble.content, run_time=1), ) self.wait() false_equation = VGroup( matrix_exp, rhs, brace, brace_label ) # This is nonsense. morty = Mortimer() morty.refresh_triangulation() morty.match_height(randy) morty.to_corner(DR) morty.set_opacity(0) false_equation.generate_target() false_equation.target.scale(0.5) false_equation.target.next_to(morty, UL) fe_rect = SurroundingRectangle(false_equation.target) fe_rect.set_color(GREY_BROWN) cross = Cross(false_equation.target[1]) cross.insert_n_curves(1) cross.set_stroke(RED, width=[1, 5, 1]) nonsense = Text("This would be nonsense") nonsense.match_width(fe_rect) nonsense.next_to(fe_rect, UP) nonsense.set_color(RED) randy.bubble = bubble self.play( MoveToTarget(false_equation), RemovePiCreatureBubble(randy, target_mode="hesitant"), morty.animate.set_opacity(1).change("raise_right_hand"), ShowCreation(fe_rect), ) self.play( ShowCreation(cross), FadeIn(nonsense), ) self.play(Blink(morty)) self.wait() false_group = VGroup(false_equation, fe_rect, cross, nonsense) # Show Taylor series real_equation = OldTex( "e^x = x^0 + x^1 + \\frac{1}{2} x^2 + \\frac{1}{6} x^3 + \\cdots + \\frac{1}{n!} x^n + \\cdots", isolate=["x"] ) xs = real_equation.get_parts_by_tex("x") xs.set_color(YELLOW) real_equation.set_width(FRAME_WIDTH - 2.0) real_equation.to_edge(UP) real_rhs = real_equation[3:] real_label = Text("Real number", color=YELLOW, font_size=24) # real_label.next_to(xs[0], DOWN, buff=0.8) # real_label.to_edge(LEFT, buff=MED_SMALL_BUFF) # real_arrow = Arrow(real_label, xs[0], buff=0.1, fill_color=GREY_B, thickness=0.025) real_label.to_corner(UL, buff=MED_SMALL_BUFF) real_arrow = Arrow(real_label, real_equation[1], buff=0.1) taylor_brace = Brace(real_rhs, DOWN) taylor_label = taylor_brace.get_text("Taylor series") self.play( TransformFromCopy(base, real_equation[0]), FadeTransform(matrix.copy(), real_equation[1]), FadeIn(real_label, UR), GrowArrow(real_arrow), randy.change("thinking", real_label), morty.animate.look_at(real_label), ) self.wait() self.play( Write(real_equation[2], lag_ratio=0.2), FadeTransformPieces(xs[:1].copy(), xs[1:], path_arc=20 * DEGREES), LaggedStart(*( FadeIn(part) for part in real_equation[4:] if part not in xs )), randy.change("pondering", real_equation), morty.change("pondering", real_equation), ) self.add(real_equation) self.play(Blink(morty)) self.play( false_group.animate.scale(0.7).to_edge(DOWN), GrowFromCenter(taylor_brace), FadeIn(taylor_label, 0.5 * DOWN) ) self.wait() # Taylor series example ex_rhs = OldTex( """ {2}^0 + {2}^1 + { {2}^2 \\over 2} + { {2}^3 \\over 6} + { {2}^4 \\over 24} + { {2}^5 \\over 120} + { {2}^6 \\over 720} + { {2}^7 \\over 5040} + \\cdots """, tex_to_color_map={"{2}": YELLOW, "+": WHITE}, ) ex_rhs.next_to(real_equation[3:], DOWN, buff=0.75) ex_parts = VGroup(*( ex_rhs[i:j] for i, j in [ (0, 2), (3, 5), (6, 8), (9, 11), (12, 14), (15, 17), (18, 20), (21, 23), (24, 25), ] )) term_brace = Brace(ex_parts[0], DOWN) frac = OldTex("1", font_size=36) frac.next_to(term_brace, DOWN, SMALL_BUFF) rects = VGroup(*( Rectangle(height=2**n / math.factorial(n), width=1) for n in range(11) )) rects.arrange(RIGHT, buff=0, aligned_edge=DOWN) rects.set_fill(opacity=1) rects.set_submobject_colors_by_gradient(BLUE, GREEN) rects.set_stroke(WHITE, 1) rects.set_width(7) rects.to_edge(DOWN) self.play( ReplacementTransform(taylor_brace, term_brace), FadeTransform(real_equation[3:].copy(), ex_rhs), FadeOut(false_group, shift=DOWN), FadeOut(taylor_label, shift=DOWN), FadeIn(frac), ) term_values = VGroup() for n in range(11): rect = rects[n] fact = math.factorial(n) ex_part = ex_parts[min(n, len(ex_parts) - 1)] value = DecimalNumber(2**n / fact) value.set_color(GREY_A) max_width = 0.6 * rect.get_width() if value.get_width() > max_width: value.set_width(max_width) value.next_to(rects[n], UP, SMALL_BUFF) new_brace = Brace(ex_part, DOWN) if fact == 1: new_frac = OldTex(f"{2**n}", font_size=36) else: new_frac = OldTex(f"{2**n} / {fact}", font_size=36) new_frac.next_to(new_brace, DOWN, SMALL_BUFF) self.play( term_brace.animate.become(new_brace), FadeTransform(frac, new_frac), ) frac = new_frac rect.save_state() rect.stretch(0, 1, about_edge=DOWN) rect.set_opacity(0) value.set_value(0) self.play( Restore(rect), ChangeDecimalToValue(value, 2**n / math.factorial(n)), UpdateFromAlphaFunc(value, lambda m, a: m.next_to(rect, UP, SMALL_BUFF).set_opacity(a)), randy.animate.look_at(rect), morty.animate.look_at(rect), ) term_values.add(value) self.play(FadeOut(frac)) new_brace = Brace(ex_rhs, DOWN) sum_value = DecimalNumber(math.exp(2), num_decimal_places=4, font_size=36) sum_value.next_to(new_brace, DOWN) self.play( term_brace.animate.become(new_brace), randy.change("thinking", sum_value), morty.change("tease", sum_value), *(FadeTransform(dec.copy().set_opacity(0), sum_value) for dec in term_values) ) self.play(Blink(randy)) lhs = OldTex("e \\cdot e =") lhs.match_height(real_equation[0]) lhs.next_to(ex_rhs, LEFT) self.play(Write(lhs)) self.play(Blink(morty)) self.play(Blink(randy)) # Increment input twos = ex_rhs.get_parts_by_tex("{2}") threes = VGroup(*( OldTex("3").set_color(YELLOW).replace(two) for two in twos )) new_lhs = OldTex("e \\cdot e \\cdot e = ") new_lhs.match_height(lhs) new_lhs[0].space_out_submobjects(0.8) new_lhs[0][-1].shift(SMALL_BUFF * RIGHT) new_lhs.move_to(lhs, RIGHT) anims = [] unit_height = 0.7 * rects[0].get_height() for n, rect, value_mob in zip(it.count(0), rects, term_values): rect.generate_target() new_value = 3**n / math.factorial(n) rect.target.set_height(unit_height * new_value, stretch=True, about_edge=DOWN) value_mob.rect = rect anims += [ MoveToTarget(rect), ChangeDecimalToValue(value_mob, new_value), UpdateFromFunc(value_mob, lambda m: m.next_to(m.rect, UP, SMALL_BUFF)) ] self.play( FadeOut(twos, 0.5 * UP), FadeIn(threes, 0.5 * UP), ) twos.set_opacity(0) self.play( ChangeDecimalToValue(sum_value, math.exp(3)), *anims, ) self.play( FadeOut(lhs, 0.5 * UP), FadeIn(new_lhs, 0.5 * UP), ) self.wait() # Isolate polynomial real_lhs = VGroup(real_equation[:3], real_label, real_arrow) self.play( LaggedStartMap(FadeOut, VGroup( *new_lhs, *threes, *ex_rhs, term_brace, sum_value, *rects, *term_values, )), real_lhs.animate.set_opacity(0.2), randy.change("erm", real_equation), morty.change("thinking", real_equation), run_time=1, ) self.play(Blink(morty)) # Alternate inputs rhs_tex = "X^0 + X^1 + \\frac{1}{2} X^2 + \\frac{1}{6} X^3 + \\cdots + \\frac{1}{n!} X^n + \\cdots" pii_rhs = OldTex( rhs_tex.replace("X", "(\\pi i)"), tex_to_color_map={"(\\pi i)": BLUE}, ) pii_rhs.match_width(real_rhs) mat_tex = "\\left[ \\begin{array}{ccc} 3 & 1 & 4 \\\\ 1 & 5 & 9 \\\\ 2 & 6 & 5 \\end{array} \\right]" mat_rhs = OldTex( rhs_tex.replace("X", mat_tex), tex_to_color_map={mat_tex: TEAL}, ) mat_rhs.scale(0.5) pii_rhs.next_to(real_rhs, DOWN, buff=0.7) mat_rhs.next_to(pii_rhs, DOWN, buff=0.7) self.play(FlashAround(real_rhs)) self.wait() self.play( morty.change("raise_right_hand", pii_rhs), FadeTransformPieces(real_rhs.copy(), pii_rhs), ) self.play(Blink(randy)) self.play( FadeTransformPieces(real_rhs.copy(), mat_rhs), ) self.play( randy.change("maybe", mat_rhs), ) self.wait() why = Text("Why?", font_size=36) why.next_to(randy, UP, aligned_edge=LEFT) self.play( randy.change("confused", mat_rhs.get_corner(UL)), Write(why), ) self.play(Blink(randy)) reassurance = VGroup( Text("I know it looks complicated.", font_size=24), Text("Don't panic.", font_size=24), ) reassurance.arrange(DOWN) reassurance.next_to(morty, LEFT, aligned_edge=UP) reassurance.set_color(GREY_A) for words in reassurance: self.play(FadeIn(words)) self.play(Blink(morty)) # Describe exp to_right_group = VGroup(real_lhs, real_rhs, mat_rhs, pii_rhs) to_right_group.generate_target() to_right_group.target.to_edge(RIGHT, buff=SMALL_BUFF) to_right_group.target[2].to_edge(RIGHT, buff=SMALL_BUFF) self.play( MoveToTarget(to_right_group), FadeOut(why), FadeOut(reassurance), randy.change("pondering", mat_rhs), morty.change("tease"), ) pii_lhs = OldTex("\\text{exp}\\left(\\pi i \\right) = ")[0] pii_lhs.next_to(pii_rhs, LEFT) mat_lhs = OldTex("\\text{exp}\\left(" + mat_tex + "\\right) = ")[0] mat_lhs.match_height(mat_rhs) mat_lhs[:3].match_height(pii_lhs[:3]) mat_lhs[:3].next_to(mat_lhs[3:5], LEFT, SMALL_BUFF) mat_lhs.next_to(mat_rhs, LEFT) pii_lhs_pi_part = pii_lhs[4:6] pii_lhs_pi_part.set_color(BLUE) mat_lhs_mat_part = mat_lhs[5:18] mat_lhs_mat_part.set_color(TEAL) self.play( FadeIn(pii_lhs), randy.change("thinking", pii_lhs), randy.change("tease", pii_lhs), ) self.play(FadeIn(mat_lhs)) self.play(Blink(randy)) self.play( LaggedStart( FlashAround(pii_lhs[:3]), FlashAround(mat_lhs[:3]), lag_ratio=0.3, run_time=2 ), randy.change("raise_left_hand", pii_lhs), ) self.wait() # Transition to e^x notation crosses = VGroup(*( Cross(lhs[:3], stroke_width=[0, 3, 3, 3, 0]).scale(1.3) for lhs in [pii_lhs, mat_lhs] )) bases = VGroup() powers = VGroup() equals = VGroup() for part, lhs in (pii_lhs_pi_part, pii_lhs), (mat_lhs_mat_part, mat_lhs): power = part.copy() part.set_opacity(0) self.add(power) base = OldTex("e", font_size=60) equal = OldTex(":=") power.generate_target() if power.target.get_height() > 0.7: power.target.set_height(0.7) power.target.next_to(base, UR, buff=0.05) group = VGroup(base, power.target, equal) equal.next_to(group[:2], RIGHT, MED_SMALL_BUFF) equal.match_y(base) if lhs is mat_lhs: equal.shift(0.1 * UP) group.shift(lhs.get_right() - equal.get_right()) bases.add(base) powers.add(power) equals.add(equal) self.play( ShowCreation(crosses), randy.change("hesitant", crosses), ) self.play(Blink(randy)) self.play(real_lhs.animate.set_opacity(1)) self.play( FadeOut(pii_lhs), FadeOut(mat_lhs), FadeOut(crosses), *(MoveToTarget(power) for power in powers), *(TransformFromCopy(real_equation[0], base) for base in bases), Write(equals), randy.change("sassy", powers), ) self.wait() # Theorem vs. definition real_part = VGroup(real_lhs, real_rhs) pii_part = VGroup(bases[0], powers[0], equals[0], pii_rhs) mat_part = VGroup(bases[1], powers[1], equals[1], mat_rhs) def_parts = VGroup(pii_part, mat_part) self.play( FadeOut(randy, DOWN), FadeOut(morty, DOWN), real_part.animate.set_x(0).shift(DOWN), def_parts.animate.set_x(0).to_edge(DOWN), ) real_rect = SurroundingRectangle(real_part) real_rect.set_stroke(YELLOW, 2) theorem_label = Text("Theorem") theorem_label.next_to(real_rect, UP) def_rect = SurroundingRectangle(def_parts) def_rect.set_stroke(BLUE, 2) def_label = Text("Definition") def_label.next_to(def_rect, UP) self.play( ShowCreation(real_rect), FadeIn(theorem_label, 0.5 * UP), ) self.wait() self.play( ShowCreation(def_rect), FadeIn(def_label, 0.5 * UP), ) self.wait() # Abuse? Or the beauty of discovery... randy2 = Randolph() randy2.set_height(1.5) randy2.next_to(def_rect, UP, SMALL_BUFF, aligned_edge=LEFT) self.play( ApplyMethod(randy2.change, "angry", mat_rhs), UpdateFromAlphaFunc(randy2, lambda m, a: m.set_opacity(a)), ) self.play(Blink(randy2)) discovery_label = Text("Discovery") discovery_label.move_to(theorem_label, DOWN) invention_label = Text("Invention") invention_label.move_to(def_label, DOWN) self.play( FadeOut(theorem_label, UP), FadeIn(discovery_label, UP), randy2.change("hesitant", theorem_label), ) self.play( FadeOut(def_label, UP), FadeIn(invention_label, UP), ) self.wait(2) # Isolate matrix right hand side to_fade = VGroup( randy2, discovery_label, invention_label, real_rect, def_rect, real_arrow, real_label, real_part, pii_part, bases, powers, equals, ) self.play( mat_rhs.animate.set_width(FRAME_WIDTH - 1).center().to_edge(UP), LaggedStartMap(FadeOut, to_fade), FadeIn(VGroup(randy, morty), run_time=2, rate_func=squish_rate_func(smooth, 0.5, 1)) ) # Matrix powers mat = mat_rhs[4] mat_brace = Brace(VGroup(mat, mat_rhs[5][0]), DOWN, buff=SMALL_BUFF) matrix = np.array([[3, 1, 4], [1, 5, 9], [2, 6, 5]]) matrix_square = np.dot(matrix, matrix) result = IntegerMatrix(matrix_square, h_buff=1.3, v_buff=0.7) result.match_height(mat) square_eq = VGroup(mat.copy(), mat.copy(), OldTex("="), result) square_eq.arrange(RIGHT, buff=SMALL_BUFF) square_eq.next_to(mat_brace, DOWN) self.play(GrowFromCenter(mat_brace)) self.play( LaggedStart( TransformFromCopy(mat, square_eq[0], path_arc=45 * DEGREES), TransformFromCopy(mat, square_eq[1]), Write(square_eq[2]), Write(result.brackets), ), randy.change("pondering", square_eq), ) self.show_mat_mult(matrix, matrix, square_eq[0][2:11], square_eq[1][2:11], result.elements) # Show matrix cubed mat_brace.generate_target() mat_brace.target.next_to(mat_rhs[6], DOWN, SMALL_BUFF) mat_squared = result mat_cubed = IntegerMatrix( np.dot(matrix, matrix_square), h_buff=1.8, v_buff=0.7, element_alignment_corner=ORIGIN, ) mat_cubed.match_height(mat) cube_eq = VGroup( VGroup(mat.copy(), mat.copy(), mat.copy()).arrange(RIGHT, buff=SMALL_BUFF), OldTex("=").rotate(90 * DEGREES), VGroup(mat.copy(), mat_squared.deepcopy()).arrange(RIGHT, buff=SMALL_BUFF), OldTex("=").rotate(90 * DEGREES), mat_cubed ) cube_eq.arrange(DOWN) cube_eq.next_to(mat_brace.target, DOWN) self.play( MoveToTarget(mat_brace), ReplacementTransform(square_eq[0], cube_eq[0][1]), ReplacementTransform(square_eq[1], cube_eq[0][2]), ReplacementTransform(square_eq[2], cube_eq[1]), ReplacementTransform(square_eq[3], cube_eq[2][1]), randy.change("happy", cube_eq), ) self.play( LaggedStart( FadeIn(cube_eq[0][0]), FadeIn(cube_eq[2][0]), FadeIn(cube_eq[3]), FadeIn(cube_eq[4].brackets), ), randy.change("tease", cube_eq), ) self.show_mat_mult( matrix, matrix_square, cube_eq[2][0][2:11], cube_eq[2][1].get_entries(), cube_eq[4].get_entries(), 0.1, 0.1, ) self.play(Blink(morty)) # Scaling example_matrix = Matrix([ ["a", "b", "c"], ["d", "e", "f"], ["g", "h", "i"], ]) example_scaled_matrix = Matrix([ ["a / n!", "b / n!", "c / n!"], ["d / n!", "e / n!", "f / n!"], ["g / n!", "h / n!", "i / n!"], ]) factor = OldTex("1 \\over n!") factor.scale(1.5) factor.next_to(example_matrix, LEFT, MED_SMALL_BUFF) self.play( LaggedStartMap(FadeOut, VGroup(mat_brace, *cube_eq[:-1])), FadeIn(factor), FadeTransformPieces(cube_eq[-1], example_matrix), ) self.wait() self.play( TransformMatchingShapes( VGroup(*factor, *example_matrix), example_scaled_matrix, ), randy.change("pondering", example_scaled_matrix), ) self.wait() # Adding mat1 = np.array([[2, 7, 1], [8, 2, 8], [1, 8, 2]]) mat2 = np.array([[8, 4, 5], [9, 0, 4], [5, 2, 3]]) sum_eq = VGroup( IntegerMatrix(mat1), OldTex("+"), IntegerMatrix(mat2), OldTex("="), Matrix( np.array([ f"{m1} + {m2}" for m1, m2 in zip(mat1.flatten(), mat2.flatten()) ]).reshape((3, 3)), h_buff=1.8, ) ) sum_eq.set_height(1.5) sum_eq.arrange(RIGHT) sum_eq.center() self.play( FadeOut(example_scaled_matrix, UP), FadeIn(sum_eq[:-1], UP), FadeIn(sum_eq[-1].brackets, UP), morty.change("raise_right_hand", sum_eq), randy.change("thinking", sum_eq), ) last_rects = VGroup() for e1, e2, e3 in zip(sum_eq[0].elements, sum_eq[2].elements, sum_eq[4].elements): rects = VGroup(SurroundingRectangle(e1), SurroundingRectangle(e2)) self.add(e3, rects) self.play(FadeOut(last_rects), run_time=0.2) self.wait(0.1) last_rects = rects self.play(FadeOut(last_rects)) # Ask about infinity bubble = randy.get_bubble(OldTexText("But...going\\\\to $\\infty$?")) bubble.shift(SMALL_BUFF * RIGHT) self.play( Write(bubble), Write(bubble.content), FadeOut(sum_eq, UP), randy.change("sassy", mat_rhs), morty.change("guilty", randy.eyes), ) self.play(Blink(randy)) self.wait() self.play( FadeOut(bubble), bubble.content.animate.next_to(randy, RIGHT, aligned_edge=UP), randy.change("pondering", mat_rhs), morty.change("pondering", mat_rhs), ) # Replace matrix pi_mat_tex = "" pi_mat_tex = "\\left[ \\begin{array}{cc} 0 & -\\pi \\\\ \\pi & 0 \\end{array} \\right]" pi_mat_rhs = OldTex( rhs_tex.replace("X", pi_mat_tex), tex_to_color_map={pi_mat_tex: BLUE}, ) pi_mat_rhs.match_width(mat_rhs) pi_mat_rhs.move_to(mat_rhs) pi_mat = pi_mat_rhs.get_part_by_tex(pi_mat_tex).copy() pi_mat.scale(1.5) pi_mat.next_to(morty, UL) self.play( morty.change("raise_right_hand"), FadeIn(pi_mat, UP) ) self.play(Blink(morty)) self.play( FadeTransformPieces(mat_rhs, pi_mat_rhs), Transform( VGroup(pi_mat), pi_mat_rhs.get_parts_by_tex(pi_mat_tex), remover=True, ), morty.change("tease"), ) self.wait() # Show various partial sum values matrix = np.array([[0, -np.pi], [np.pi, 0]]) curr_matrix = np.identity(2) curr_sum = np.identity(2) curr_sum_mob = IntegerMatrix(curr_matrix) curr_sum_mob.set_height(1.5) mat_parts = pi_mat_rhs.get_parts_by_tex(pi_mat_tex) brace = Brace(mat_parts[0], DOWN) brace.stretch(1.1, 0, about_edge=LEFT) curr_sum_mob.next_to(brace, DOWN) curr_sum_mob.shift_onto_screen() self.play( GrowFromCenter(brace), FadeTransform(mat_parts[0].copy(), curr_sum_mob), randy.change("erm", curr_sum_mob), ) self.wait() last_n_label = VMobject() partial_sum_mobs = [curr_sum_mob] for n in range(1, 18): if n < 5: new_brace = Brace(mat_parts[:n + 1]) new_brace.set_width(new_brace.get_width() + 0.2, about_edge=LEFT) brace.generate_target() brace.target.become(new_brace) anims = [ MoveToTarget(brace), ] else: n_label = OldTex(f"n = {n}", font_size=24) n_label.next_to(brace.get_corner(DR), DL, SMALL_BUFF) anims = [ FadeIn(n_label), FadeOut(last_n_label), ] last_n_label = n_label curr_matrix = np.dot(curr_matrix, matrix) / n curr_sum += curr_matrix nd = min(n + 1, 4) if n < 2: h_buff = 1.3 else: sample = DecimalMatrix(curr_sum[0], num_decimal_places=nd) sample.replace(curr_sum_mob.get_entries()[0], 1) h_buff = 1.3 * sample.get_width() new_sum_mob = DecimalMatrix( curr_sum, element_alignment_corner=RIGHT, element_to_mobject_config={ "num_decimal_places": nd, "font_size": 36, }, h_buff=h_buff, ) new_sum_mob.match_height(curr_sum_mob) new_sum_mob.next_to(brace.target, DOWN) self.play( FadeOut(curr_sum_mob), FadeIn(new_sum_mob), randy.animate.look_at(new_sum_mob), *anims, run_time=(1 if n < 5 else 1 / 60) ) self.wait() curr_sum_mob = new_sum_mob partial_sum_mobs.append(new_sum_mob) self.play( FadeOut(last_n_label), randy.change("confused", curr_sum_mob), ) # Ask why why = Text("Why?") why.move_to(bubble.content, UL) epii = OldTex("e^{\\pi i} = -1") epii.next_to(morty, UL) later_text = Text("...but that comes later", font_size=24) later_text.set_color(GREY_A) later_text.next_to(epii, DOWN, aligned_edge=RIGHT) self.play( randy.change("maybe"), FadeIn(why, UP), FadeOut(bubble.content, UP), ) self.wait() self.play( morty.change("raise_right_hand"), FadeIn(epii, UP), ) self.play(Blink(morty)) self.play( Write(later_text, run_time=1), randy.change("hesitant", morty.eyes) ) # Show partial sums new_mat_rhs = OldTex( rhs_tex.replace("X", mat_tex), tex_to_color_map={mat_tex: TEAL}, isolate=["+"] ) new_mat_rhs.replace(mat_rhs) self.play( FadeOut(pi_mat_rhs), FadeIn(new_mat_rhs), FadeOut(new_sum_mob, DOWN), brace.animate.become(Brace(new_mat_rhs, DOWN)), LaggedStartMap( FadeOut, VGroup( why, epii, later_text, ), shift=DOWN, ), randy.change("pondering", new_mat_rhs), morty.change("pondering", new_mat_rhs), ) matrix = np.array([[3, 1, 4], [1, 5, 9], [2, 6, 5]]) partial_sum_mobs = VGroup() curr_matrix = np.identity(3) partial_sum = np.array(curr_matrix) for n in range(50): psm = DecimalMatrix( partial_sum, element_to_mobject_config={"num_decimal_places": 2}, element_alignment_corner=ORIGIN, h_buff=1.5 * DecimalNumber(partial_sum[0, 0]).get_width(), v_buff=1.0, ) psm.next_to(brace, DOWN, MED_LARGE_BUFF) partial_sum_mobs.add(psm) curr_matrix = np.dot(curr_matrix, matrix) / (n + 1) partial_sum += curr_matrix new_mat_rhs[2:].set_opacity(0.1) self.add(partial_sum_mobs[0]) self.wait(0.5) for n, k in zip(it.count(1), [5, 9, 13, 19, 21]): self.remove(partial_sum_mobs[n - 1]) self.add(partial_sum_mobs[n]) new_mat_rhs[:k].set_opacity(1) self.wait(0.5) brace.become(Brace(new_mat_rhs, DOWN)) n_label = VGroup(OldTex("n = "), Integer(n)) n_label[1].set_height(n_label[0].get_height() * 1.2) n_label.arrange(RIGHT, SMALL_BUFF) n_label.set_color(GREY_B) n_label.next_to(brace.get_corner(DR), DL, SMALL_BUFF) self.add(n_label) for n in range(6, 50): self.remove(partial_sum_mobs[n - 1]) self.add(partial_sum_mobs[n]) n_label[1].set_value(n) n_label[1].set_color(GREY_B) n_label[1].next_to(n_label[0], RIGHT, SMALL_BUFF) self.wait(0.1) self.play( randy.change("erm"), morty.change("tease"), # LaggedStartMap( # FadeOut, VGroup(brace, n_label, partial_sum_mobs[n]), # shift=DOWN, # ) ) self.play(Blink(morty)) self.play(Blink(randy)) self.wait() def show_mat_mult(self, m1, m2, m1_terms, m2_terms, rhs_terms, per_term=0.1, between_terms=0.35): dim = m1.shape[0] m1_color = m1_terms[0].get_fill_color() m2_color = m2_terms[0].get_fill_color() for n in range(dim * dim): i = n // dim j = n % dim row = m1_terms[dim * i:dim * i + dim] col = m2_terms[j::dim] row_rect = SurroundingRectangle(row, buff=0.05) col_rect = SurroundingRectangle(col, buff=0.05) row_rect.set_stroke(YELLOW, 2) col_rect.set_stroke(YELLOW, 2) right_elem = Integer(0, edge_to_fix=ORIGIN) right_elem.replace(rhs_terms[n], dim_to_match=1) right_elem.set_value(0) self.add(row_rect, col_rect, right_elem) for k in range(dim): self.wait(per_term) right_elem.increment_value(m1[i, k] * m2[k, j]) right_elem.scale(rhs_terms[0][0].get_height() / right_elem[-1].get_height()) row[k].set_color(YELLOW) col[k].set_color(YELLOW) self.remove(right_elem) self.add(rhs_terms[n]) self.wait(between_terms) m1_terms.set_color(m1_color) m2_terms.set_color(m2_color) self.remove(row_rect, col_rect) class ShowHigherMatrixPowers(IntroduceTheComputation): matrix = [[3, 1, 4], [1, 5, 9], [2, 6, 5]] per_term = 0.1 between_terms = 0.1 N_powers = 10 def construct(self): # Show many matrix powers def get_mat_mob(matrix): term = Integer(matrix[0, 0]) return IntegerMatrix( matrix, h_buff=max(0.8 + 0.3 * len(term), 1.0) ) N = self.N_powers matrix = np.matrix(self.matrix) matrix_powers = [np.identity(len(matrix)), matrix] for x in range(N): matrix_powers.append(np.dot(matrix, matrix_powers[-1])) mat_mobs = [get_mat_mob(mat) for mat in matrix_powers] for mob in mat_mobs: mob.set_height(1) mat_mobs[1].set_color(TEAL) equation = VGroup( mat_mobs[1].deepcopy(), Integer(2, font_size=18), OldTex("="), mat_mobs[1].deepcopy(), mat_mobs[1].deepcopy(), OldTex("="), mat_mobs[2].deepcopy() ) equation.arrange(RIGHT) equation.to_edge(LEFT) equation[1].set_y(equation[0].get_top()[1]) equation[0].set_x(equation[1].get_left()[0] - SMALL_BUFF, RIGHT) self.add(equation) exp = equation[1] m1, m2, eq, rhs = equation[-4:] self.remove(*rhs.get_entries()) for n in range(3, N): self.show_mat_mult( matrix_powers[1], matrix_powers[n - 2], m1.get_entries(), m2.get_entries(), rhs.get_entries(), between_terms=self.between_terms, per_term=self.per_term ) self.wait(0.5) rhs.generate_target() eq.generate_target() rhs.target.move_to(m2, LEFT) eq.target.next_to(rhs.target, RIGHT) new_rhs = mat_mobs[n].deepcopy() new_rhs.next_to(eq.target, RIGHT) new_exp = Integer(n) new_exp.replace(exp, dim_to_match=1) self.play( MoveToTarget(rhs, path_arc=PI / 2), MoveToTarget(eq), FadeOut(m2, DOWN), FadeIn(new_rhs.get_brackets()), FadeIn(new_exp, 0.5 * DOWN), FadeOut(exp, 0.5 * DOWN), ) m2, rhs = rhs, new_rhs exp = new_exp class Show90DegreePowers(ShowHigherMatrixPowers): matrix = [[0, -1], [1, 0]] per_term = 0.0 between_terms = 0.2 N_powers = 16 class WhyTortureMatrices(TeacherStudentsScene): def construct(self): self.play_student_changes( "maybe", "confused", "erm", look_at=self.screen, ) q_marks = VGroup() for student in self.students: marks = OldTex("???") marks.next_to(student, UP) q_marks.add(marks) self.play(FadeIn(q_marks, 0.25 * UP, lag_ratio=0.1, run_time=2)) self.wait(2) self.student_says( OldTexText("Why...would you\\\\ever want\\\\to do that?"), index=2, added_anims=[FadeOut(q_marks)], ) self.play( self.change_students("confused", "pondering", "raise_left_hand", look_at=self.screen), self.teacher.change("tease", self.screen) ) self.wait(2) self.play(self.students[0].change("erm")) self.wait(7) class DefinitionFirstVsLast(Scene): show_love_and_quantum = True def construct(self): # Setup objects top_title = Text("Textbook progression") low_title = Text("Discovery progression") top_prog = VGroup( OldTexText("Definition", color=BLUE), OldTexText("Theorem"), OldTexText("Proof"), OldTexText("Examples"), ) low_prog = VGroup( OldTexText("Specific\n\nproblem"), OldTexText("General\n\nproblems"), OldTexText("Helpful\n\nconstructs"), OldTexText("Definition", color=BLUE), ) progs = VGroup(top_prog, low_prog) for progression in progs: progression.arrange(RIGHT, buff=1.2) progs.arrange(DOWN, buff=3) progs.set_width(FRAME_WIDTH - 2) for progression in progs: arrows = VGroup() for m1, m2 in zip(progression[:-1], progression[1:]): arrows.add(Arrow(m1, m2)) progression.arrows = arrows top_dots = OldTex("\\dots", font_size=72) top_dots.next_to(top_prog.arrows[0], RIGHT) low_dots = top_dots.copy() low_dots.next_to(low_prog.arrows[-1], LEFT) top_rect = SurroundingRectangle(top_prog, buff=MED_SMALL_BUFF) top_rect.set_stroke(TEAL, 2) top_title.next_to(top_rect, UP) top_title.match_color(top_rect) low_rect = SurroundingRectangle(low_prog, buff=MED_SMALL_BUFF) low_rect.set_stroke(YELLOW, 2) low_title.next_to(low_rect, UP) low_title.match_color(low_rect) versus = Text("vs.") # Show progressions self.add(top_prog[0]) self.play( GrowArrow(top_prog.arrows[0]), FadeIn(top_dots, 0.2 * RIGHT, lag_ratio=0.1), ) self.wait() kw = {"path_arc": -90 * DEGREES} self.play( LaggedStart( TransformFromCopy(top_prog[0], low_prog[-1], **kw), TransformFromCopy(top_prog.arrows[0], low_prog.arrows[-1], **kw), TransformFromCopy(top_dots, low_dots, **kw), ), Write(versus) ) self.wait() self.play( ShowCreation(top_rect), FadeIn(top_title, 0.25 * UP) ) self.play( FadeOut(top_dots), FadeIn(top_prog[1]), ) for arrow, term in zip(top_prog.arrows[1:], top_prog[2:]): self.play( GrowArrow(arrow), FadeIn(term, shift=0.25 * RIGHT), ) self.wait() self.play( ShowCreation(low_rect), FadeIn(low_title, 0.25 * UP), versus.animate.move_to(midpoint(low_title.get_top(), top_rect.get_bottom())), ) self.wait() self.play(FadeIn(low_prog[0])) self.play( GrowArrow(low_prog.arrows[0]), FadeIn(low_prog[1], shift=0.25 * RIGHT), ) self.play( GrowArrow(low_prog.arrows[1]), FadeIn(low_prog[2], shift=0.25 * RIGHT), FadeOut(low_dots), ) self.wait() # Highlight specific example full_rect = FullScreenRectangle() full_rect.set_fill(BLACK, opacity=0.75) sp, gp, hc = low_prog[:3].copy() self.add(full_rect, sp) self.play(FadeIn(full_rect)) self.wait() # Go to general if not self.show_love_and_quantum: self.play(FadeIn(gp)) self.play(FlashAround(gp, color=BLUE, run_time=2)) self.wait() return # Love and quantum love = SVGMobject("hearts") love.set_height(1) love.set_fill(RED, 1) love.set_stroke(MAROON_B, 1) quantum = OldTex("|\\psi\\rangle") quantum.set_color(BLUE) quantum.match_height(love) group = VGroup(quantum, love) group.arrange(RIGHT, buff=MED_LARGE_BUFF) group.next_to(sp, UP, MED_LARGE_BUFF) love.save_state() love.match_x(sp) self.play(Write(love)) self.wait() self.play( Restore(love), FadeIn(quantum, 0.5 * LEFT) ) self.wait() self.play( love.animate.center().scale(1.5), FadeOut(quantum), FadeOut(sp), full_rect.animate.set_fill(opacity=1) ) self.wait() class DefinitionFirstVsLastGP(DefinitionFirstVsLast): show_love_and_quantum = False class RomeoAndJuliet(Scene): def construct(self): # Add Romeo and Juliet romeo, juliet = lovers = self.get_romeo_and_juliet() lovers.set_height(2) lovers.arrange(LEFT, buff=1) lovers.move_to(0.5 * DOWN) self.add(*lovers) self.make_romeo_and_juliet_dynamic(romeo, juliet) romeo.love_tracker.set_value(1.5) juliet.love_tracker.set_value(1.5) get_romeo_juilet_name_labels(lovers) for creature in lovers: self.play( creature.love_tracker.animate.set_value(2.5), Write(creature.name_label, run_time=1), ) self.wait() # Add their scales juliet_scale = self.get_love_scale(juliet, LEFT, "x", BLUE_B) romeo_scale = self.get_love_scale(romeo, RIGHT, "y", BLUE) scales = [juliet_scale, romeo_scale] scale_labels = VGroup( OldTexText("Juliet's love for Romeo", font_size=30), OldTexText("Romeo's love for Juliet", font_size=30), ) scale_arrows = VGroup() for scale, label in zip(scales, scale_labels): var = scale[2][0][0] label.next_to(var, UP, buff=0.7) arrow = Arrow(var, label, buff=0.1, thickness=0.025) scale_arrows.add(arrow) label.set_color(var.get_fill_color()) for lover, scale, arrow, label, final_love in zip(reversed(lovers), scales, scale_arrows, scale_labels, [1, -1]): self.add(scale) self.play(FlashAround(scale[2][0][0])) self.play( lover.love_tracker.animate.set_value(5), GrowArrow(arrow), FadeIn(label, 0.5 * UP), ) self.play(lover.love_tracker.animate.set_value(final_love), run_time=2) self.wait() # Juliet's rule frame = self.camera.frame equations = VGroup( OldTex("{dx \\over dt} {{=}} -{{y(t)}}"), OldTex("{dy \\over dt} {{=}} {{x(t)}}"), ) juliet_eq, romeo_eq = equations juliet_eq.next_to(scale_labels[0], UR) juliet_eq.shift(0.5 * UP) self.play( frame.animate.move_to(0.7 * UP), Write(equations[0]), ) self.wait() self.play(FlashAround(juliet_eq[0])) self.wait() y_rect = SurroundingRectangle(juliet_eq.get_parts_by_tex("y(t)"), buff=0.05) y_rect_copy = y_rect.copy() y_rect_copy.replace(romeo.scale_mob.dot, stretch=True) self.play(FadeIn(y_rect)) self.wait() self.play(TransformFromCopy(y_rect, y_rect_copy)) y_rect_copy.add_updater(lambda m: m.move_to(romeo.scale_mob.dot)) self.wait() self.play(romeo.love_tracker.animate.set_value(-3)) big_arrow = Arrow( juliet.scale_mob.number_line.get_bottom(), juliet.scale_mob.number_line.get_top(), ) big_arrow.set_color(GREEN) big_arrow.next_to(juliet.scale_mob.number_line, LEFT) self.play( FadeIn(big_arrow), ApplyMethod(juliet.love_tracker.set_value, 5, run_time=3, rate_func=linear), ) self.wait() self.play(romeo.love_tracker.animate.set_value(5)) self.play( big_arrow.animate.rotate(PI).set_color(RED), path_arc=PI, run_time=0.5, ) self.play(juliet.love_tracker.animate.set_value(-5), rate_func=linear, run_time=5) self.play(FadeOut(y_rect), FadeOut(y_rect_copy)) # Romeo's rule romeo_eq.next_to(scale_labels[1], UL) romeo_eq.shift(0.5 * UP) self.play( juliet_eq.animate.to_edge(LEFT), FadeOut(big_arrow), ) self.play(FadeIn(romeo_eq, UP)) self.wait() dy_rect = SurroundingRectangle(romeo_eq.get_part_by_tex("dy")) x_rect = SurroundingRectangle(romeo_eq.get_part_by_tex("x(t)"), buff=0.05) x_rect_copy = x_rect.copy() x_rect_copy.replace(juliet.scale_mob.dot, stretch=True) self.play(ShowCreation(dy_rect)) self.wait() self.play(TransformFromCopy(dy_rect, x_rect)) self.play(TransformFromCopy(x_rect, x_rect_copy)) self.wait() big_arrow.next_to(romeo.scale_mob.number_line, RIGHT) self.play(FadeIn(big_arrow), LaggedStartMap(FadeOut, VGroup(dy_rect, x_rect))) self.play(romeo.love_tracker.animate.set_value(-3), run_time=4, rate_func=linear) x_rect_copy.add_updater(lambda m: m.move_to(juliet.scale_mob.dot)) juliet.love_tracker.set_value(5) self.wait() self.play( big_arrow.animate.rotate(PI).set_color(GREEN), path_arc=PI, run_time=0.5, ) self.play(romeo.love_tracker.animate.set_value(5), rate_func=linear, run_time=5) self.play(FadeOut(x_rect_copy)) self.wait() # Show constant change left_arrow = Arrow(UP, DOWN) left_arrow.character = juliet left_arrow.get_rate = lambda: -romeo.love_tracker.get_value() right_arrow = Arrow(DOWN, UP) right_arrow.character = romeo right_arrow.get_rate = lambda: juliet.love_tracker.get_value() def update_arrow(arrow): nl = arrow.character.scale.number_line rate = arrow.get_rate() if rate == 0: rate = 1e-6 arrow.put_start_and_end_on(nl.n2p(0), nl.n2p(rate)) arrow.next_to(nl, np.sign(nl.get_center()[0]) * RIGHT) if rate > 0: arrow.set_color(GREEN) else: arrow.set_color(RED) left_arrow.add_updater(update_arrow) right_arrow.add_updater(update_arrow) self.play( VFadeIn(left_arrow), ApplyMethod(big_arrow.scale, 0, remover=True, run_time=3), ApplyMethod(juliet.love_tracker.set_value, 0, run_time=3), ) ps_point = Point(5 * UP) curr_time = self.time ps_point.add_updater(lambda m: m.move_to([ -5 * np.sin(0.5 * (self.time - curr_time)), 5 * np.cos(0.5 * (self.time - curr_time)), 0, ])) juliet.love_tracker.add_updater(lambda m: m.set_value(ps_point.get_location()[0])) romeo.love_tracker.add_updater(lambda m: m.set_value(ps_point.get_location()[1])) self.add(ps_point) self.add(right_arrow) self.play( equations.animate.arrange(RIGHT, buff=LARGE_BUFF).to_edge(UP, buff=0), run_time=2, ) # Just let this play out for a long time while other animations are played on top self.wait(5 * TAU) def get_romeo_and_juliet(self): romeo = PiCreature(color=BLUE_E, flip_at_start=True) juliet = PiCreature(color=BLUE_B) return VGroup(romeo, juliet) def make_romeo_and_juliet_dynamic(self, romeo, juliet): cutoff_values = [-5, -3, -1, 0, 1, 3, 5] modes = ["angry", "sassy", "hesitant", "plain", "happy", "hooray", "surprised"] self.make_character_dynamic(romeo, juliet, cutoff_values, modes) self.make_character_dynamic(juliet, romeo, cutoff_values, modes) def get_romeo_juilet_name_labels(self, lovers, font_size=36, spacing=1.2, buff=MED_SMALL_BUFF): name_labels = VGroup(*( Text(name, font_size=font_size) for name in ["Romeo", "Juliet"] )) for label, creature in zip(name_labels, lovers): label.next_to(creature, DOWN, buff) creature.name_label = label name_labels.space_out_submobjects(spacing) return name_labels def make_character_dynamic(self, pi_creature, lover, cutoff_values, modes): height = pi_creature.get_height() bottom = pi_creature.get_bottom() copies = [ pi_creature.deepcopy().change(mode).set_height(height).move_to(bottom, DOWN) for mode in modes ] pi_creature.love_tracker = ValueTracker() def update_func(pi): love = pi.love_tracker.get_value() if love < cutoff_values[0]: pi.become(copies[0]) elif love >= cutoff_values[-1]: pi.become(copies[-1]) else: i = 1 while cutoff_values[i] < love: i += 1 copy1 = copies[i - 1] copy2 = copies[i] alpha = inverse_interpolate(cutoff_values[i - 1], cutoff_values[i], love) s_alpha = squish_rate_func(smooth, 0.25, 0.75)(alpha) # if s_alpha > 0: copy1.align_data_and_family(copy2) pi.align_data_and_family(copy1) pi.align_data_and_family(copy2) fam = pi.family_members_with_points() f1 = copy1.family_members_with_points() f2 = copy2.family_members_with_points() for sm, sm1, sm2 in zip(fam, f1, f2): sm.interpolate(sm1, sm2, s_alpha) pi.look_at(lover.get_top()) if love < cutoff_values[1]: # Look away from the lover pi.look_at(2 * pi.eyes.get_center() - lover.eyes.get_center() + DOWN) return pi pi_creature.add_updater(update_func) def update_eyes(heart_eyes): love = pi_creature.love_tracker.get_value() l_alpha = np.clip( inverse_interpolate(cutoff_values[-1] - 0.5, cutoff_values[-1], love), 0, 1 ) pi_creature.eyes.set_opacity(1 - l_alpha) heart_eyes.set_opacity(l_alpha) # heart_eyes.move_to(pi_creature.eyes) heart_eyes.match_x(pi_creature.mouth) heart_eyes = self.get_heart_eyes(pi_creature) heart_eyes.add_updater(update_eyes) pi_creature.heart_eyes = heart_eyes self.add(heart_eyes) return pi_creature def get_heart_eyes(self, creature): hearts = VGroup() for eye in creature.eyes: heart = SVGMobject("hearts") heart.set_fill(RED) heart.match_width(eye) heart.move_to(eye) heart.scale(1.25) heart.set_stroke(BLACK, 1) hearts.add(heart) hearts.set_opacity(0) return hearts def get_love_scale(self, creature, direction, var_name, color): number_line = NumberLine((-5, 5)) number_line.rotate(90 * DEGREES) number_line.set_height(1.5 * creature.get_height()) number_line.next_to(creature, direction, buff=MED_LARGE_BUFF) number_line.add_numbers( range(-4, 6, 2), font_size=18, color=GREY_B, buff=0.1, direction=LEFT, ) dot = Dot(color=color) dot.add_updater(lambda m: m.move_to(number_line.n2p(creature.love_tracker.get_value()))) label = VGroup(OldTex(var_name, "=", font_size=36), DecimalNumber(font_size=24)) label.set_color(color) label[0].shift(label[1].get_left() + SMALL_BUFF * LEFT - label[0][1].get_right()) label.next_to(number_line, UP) label[1].add_updater(lambda m: m.set_value(creature.love_tracker.get_value()).set_color(color)) result = VGroup(number_line, dot, label) result.set_stroke(background=True) result.number_line = number_line result.dot = dot result.label = label creature.scale_mob = result return result class DiscussSystem(Scene): def construct(self): # Setup equations equations = VGroup( OldTex("{dx \\over dt} {{=}} -{{y(t)}}"), OldTex("{dy \\over dt} {{=}} {{x(t)}}"), ) equations.arrange(RIGHT, buff=LARGE_BUFF) equations.to_edge(UP, buff=1.5) eq_rect = SurroundingRectangle(equations, stroke_width=2, buff=0.25) sys_label = Text("System of differential equations") sys_label.next_to(eq_rect, UP) self.add(equations) self.play( FadeIn(sys_label, 0.5 * UP), ShowCreation(eq_rect), ) style = {"color": BLUE, "time_width": 3, "run_time": 2} self.play(LaggedStart( FlashAround(sys_label.get_part_by_text("differential"), **style), FlashAround(equations[0].get_part_by_tex("dx"), **style), FlashAround(equations[1].get_part_by_tex("dy"), **style), )) self.wait() # Ask for explicit solutions solutions = VGroup( OldTex("x(t) {{=}} (\\text{expression with } t)"), OldTex("y(t) {{=}} (\\text{expression with } t)"), ) for solution in solutions: solution.set_color_by_tex("expression", GREY_B) solutions.arrange(DOWN, buff=0.5) solutions.move_to(equations) solutions.set_x(3) self.play( sys_label.animate.match_width(eq_rect).to_edge(LEFT), VGroup(equations, eq_rect).animate.to_edge(LEFT), LaggedStartMap(FadeIn, solutions, shift=0.5 * UP, lag_ratio=0.3), ) self.wait() # Show a guess guess_rhss = VGroup( OldTex("\\cos(t)", color=GREY_B)[0], OldTex("\\sin(t)", color=GREY_B)[0], ) temp_rhss = VGroup() for rhs, solution in zip(guess_rhss, solutions): temp_rhss.add(solution[2]) rhs.move_to(solution[2], LEFT) bubble = ThoughtBubble(height=4, width=4) bubble.flip() bubble.set_fill(opacity=0) bubble[:3].rotate(30 * DEGREES, about_point=bubble[3].get_center() + 0.2 * RIGHT) bubble.shift(solutions.get_left() + 0.7 * LEFT - bubble[3].get_left()) self.remove(temp_rhss) self.play( ShowCreation(bubble), *( TransformMatchingShapes(temp_rhs.copy(), guess_rhs) for temp_rhs, guess_rhs in zip(temp_rhss, guess_rhss) ), ) self.wait() # Not enough! not_enough = Text("Not enough!", font_size=40) not_enough.next_to(bubble[3].get_corner(UR), DR) not_enough.set_color(RED) self.play(LaggedStartMap(FadeIn, not_enough, run_time=1, lag_ratio=0.1)) self.wait() self.remove(guess_rhss) self.play( LaggedStartMap(FadeOut, VGroup(*bubble, *not_enough)), *( TransformMatchingShapes(guess_rhs.copy(), temp_rhs) for temp_rhs, guess_rhs in zip(temp_rhss, guess_rhss) ), ) # Initial condition solutions.generate_target() initial_conditions = VGroup( OldTex("x(0) = x_0"), OldTex("y(0) = y_0"), ) full_requirement = VGroup(*solutions.target, *initial_conditions) full_requirement.arrange(DOWN, buff=0.25, aligned_edge=LEFT) full_requirement.scale(0.8) full_requirement.move_to(solutions) full_requirement.to_edge(UP) self.play( MoveToTarget(solutions), LaggedStartMap(FadeIn, initial_conditions, shift=0.1 * UP, lag_ratio=0.3), ) self.wait() ic_label = Text("Initial condition", font_size=30) ic_label.set_color(BLUE) ic_label.next_to(initial_conditions, RIGHT, buff=1.0) ic_arrows = VGroup(*( Arrow(ic_label.get_left(), eq.get_right(), buff=0.1, fill_color=BLUE, thickness=0.025) for eq in initial_conditions )) self.play( FadeIn(ic_label), LaggedStartMap(GrowArrow, ic_arrows, run_time=1) ) self.wait() class MoreGeneralSystem(Scene): def construct(self): kw = { "tex_to_color_map": { "x": RED, "y": GREEN, "z": BLUE, "{t}": GREY_B, } } equations = VGroup( OldTex("{dx \\over d{t} } = a\\cdot x({t}) + b\\cdot y({t}) + c\\cdot z({t})", **kw), OldTex("{dy \\over d{t} } = d\\cdot x({t}) + e\\cdot y({t}) + f\\cdot z({t})", **kw), OldTex("{dz \\over d{t} } = g\\cdot x({t}) + h\\cdot y({t}) + i\\cdot z({t})", **kw), ) equations.arrange(DOWN, buff=LARGE_BUFF) self.add(equations) self.play(LaggedStartMap(FadeIn, equations, shift=UP, lag_ratio=0.5, run_time=3)) self.wait() class HowExampleLeadsToMatrixExponents(Scene): def construct(self): # Screen self.add(FullScreenRectangle()) screen = ScreenRectangle() screen.set_height(3) screen.set_fill(BLACK, 1) screen.set_stroke(BLUE_B, 2) screen.to_edge(LEFT) self.add(screen) # Mat exp mat_exp = get_matrix_exponential( [["a", "b"], ["c", "d"]], height=2, h_buff=0.95, v_buff=0.75 ) mat_exp.set_x(FRAME_WIDTH / 4) def get_arrow(): return Arrow(screen, mat_exp[0]) arrow = get_arrow() self.play( GrowArrow(arrow), FadeIn(mat_exp, RIGHT) ) self.wait() # New screen screen2 = screen.copy() screen2.set_stroke(GREY_BROWN, 2) screen2.to_corner(DR) mat_exp.generate_target() mat_exp.target.to_edge(UP) mat_exp.target.match_x(screen2) double_arrow = VGroup( Arrow(mat_exp.target, screen2), Arrow(screen2, mat_exp.target), ) for mob in double_arrow: mob.scale(0.9, about_point=mob.get_end()) self.play( MoveToTarget(mat_exp), GrowFromCenter(double_arrow), arrow.animate.become(Arrow(screen, screen2)), FadeIn(screen2, DOWN), ) self.wait() class RomeoJulietVectorSpace(RomeoAndJuliet): def construct(self): # Set up Romeo and Juliet romeo, juliet = lovers = self.get_romeo_and_juliet() lovers.set_height(2.0) lovers.arrange(LEFT, buff=3) name_labels = self.get_romeo_juilet_name_labels(lovers, font_size=36, spacing=1.1) self.make_romeo_and_juliet_dynamic(*lovers) self.add(*lovers) self.add(*name_labels) # Scales juliet_scale = self.get_love_scale(juliet, LEFT, "x", BLUE_B) romeo_scale = self.get_love_scale(romeo, RIGHT, "y", BLUE) scales = [juliet_scale, romeo_scale] self.add(*scales) # Animate in psp_tracker = Point() def get_psp(): # Get phase space point return psp_tracker.get_location() juliet.love_tracker.add_updater(lambda m: m.set_value(get_psp()[0])) romeo.love_tracker.add_updater(lambda m: m.set_value(get_psp()[1])) self.add(romeo.love_tracker, juliet.love_tracker) psp_tracker.move_to([1, -3, 0]) self.play( Rotate(psp_tracker, 90 * DEGREES, about_point=ORIGIN, run_time=3, rate_func=linear) ) # Transition to axes axes = Axes( x_range=(-5, 5), y_range=(-5, 5), height=7, width=7, axis_config={ "include_tip": False, "numbers_to_exclude": [], } ) axes.set_x(-3) for axis in axes: axis.add_numbers(range(-4, 6, 2), color=GREY_B) axis.numbers[2].set_opacity(0) for pi in lovers: pi.clear_updaters() pi.generate_target() pi.target.set_height(0.75) pi.name_label.generate_target() pi.name_label.target.scale(0.5) group = VGroup(pi.target, pi.name_label.target) group.arrange(DOWN, buff=SMALL_BUFF) pi.target_group = group pi.scale_mob[2].clear_updaters() self.add(*pi.scale_mob) juliet.target_group.next_to(axes.x_axis.get_end(), RIGHT) romeo.target_group.next_to(axes.y_axis.get_corner(UR), RIGHT) romeo.target_group.shift_onto_screen(buff=MED_SMALL_BUFF) romeo.target.flip() juliet.target.flip() juliet.target.make_eye_contact(romeo.target) self.play(LaggedStart( juliet.scale_mob.number_line.animate.become(axes.x_axis), FadeOut(juliet.scale_mob.label), MoveToTarget(juliet), MoveToTarget(juliet.name_label), romeo.scale_mob.number_line.animate.become(axes.y_axis), FadeOut(romeo.scale_mob.label), MoveToTarget(romeo), MoveToTarget(romeo.name_label), run_time=3 )) self.add(*romeo.scale_mob[:2], *juliet.scale_mob[:2]) # Reset pi creatures self.remove(lovers) self.remove(romeo.heart_eyes) self.remove(juliet.heart_eyes) new_lovers = self.get_romeo_and_juliet() for new_pi, pi in zip(new_lovers, lovers): new_pi.flip() new_pi.replace(pi) new_pi.scale_mob = pi.scale_mob lovers = new_lovers romeo, juliet = new_lovers self.add(romeo, juliet) self.make_romeo_and_juliet_dynamic(romeo, juliet) juliet.love_tracker.add_updater(lambda m: m.set_value(get_psp()[0])) romeo.love_tracker.add_updater(lambda m: m.set_value(get_psp()[1])) self.add(romeo.love_tracker, juliet.love_tracker) # h_line and v_line ps_dot = Dot(color=BLUE) ps_dot.add_updater(lambda m: m.move_to(axes.c2p(*get_psp()[:2]))) v_line = Line().set_stroke(BLUE_D, 2) h_line = Line().set_stroke(BLUE_B, 2) v_line.add_updater(lambda m: m.put_start_and_end_on( axes.x_axis.n2p(get_psp()[0]), axes.c2p(*get_psp()[:2]), )) h_line.add_updater(lambda m: m.put_start_and_end_on( axes.y_axis.n2p(get_psp()[1]), axes.c2p(*get_psp()[:2]), )) x_dec = DecimalNumber(0, font_size=24) x_dec.next_to(h_line, UP, SMALL_BUFF) y_dec = DecimalNumber(0, font_size=24) y_dec.next_to(v_line, RIGHT, SMALL_BUFF) romeo.scale_mob.dot.clear_updaters() juliet.scale_mob.dot.clear_updaters() self.play( ShowCreation(h_line.copy().clear_updaters(), remover=True), ShowCreation(v_line.copy().clear_updaters(), remover=True), ReplacementTransform(romeo.scale_mob.dot, ps_dot), ReplacementTransform(juliet.scale_mob.dot, ps_dot), ChangeDecimalToValue(x_dec, get_psp()[0]), VFadeIn(x_dec), ChangeDecimalToValue(y_dec, get_psp()[1]), VFadeIn(y_dec), ) self.add(h_line, v_line, ps_dot) # Add coordinates equation = VGroup( Matrix([["x"], ["y"]], bracket_h_buff=SMALL_BUFF), OldTex("="), DecimalMatrix( np.reshape(get_psp()[:2], (2, 1)), element_to_mobject_config={ "num_decimal_places": 2, "font_size": 36, "include_sign": True, } ), ) equation[0].match_height(equation[2]) equation.arrange(RIGHT) equation.to_corner(UR) equation.shift(MED_SMALL_BUFF * LEFT) self.play( FadeIn(equation[:2]), FadeIn(equation[2].get_brackets()), TransformFromCopy(x_dec, equation[2].get_entries()[0]), TransformFromCopy(y_dec, equation[2].get_entries()[1]), ) equation[2].get_entries()[0].add_updater(lambda m: m.set_value(get_psp()[0])) equation[2].get_entries()[1].add_updater(lambda m: m.set_value(get_psp()[1])) self.play(FadeOut(x_dec), FadeOut(y_dec)) # Play around in state space self.play(psp_tracker.move_to, [3, -2, 0], path_arc=120 * DEGREES, run_time=3) self.wait() self.play(psp_tracker.move_to, [-5, -2, 0], path_arc=0 * DEGREES, run_time=3, rate_func=there_and_back) self.wait() self.play(psp_tracker.move_to, [3, 5, 0], path_arc=0 * DEGREES, run_time=3, rate_func=there_and_back) self.wait() self.play(psp_tracker.move_to, [5, 3, 0], path_arc=-120 * DEGREES, run_time=2) self.wait() # Arrow vs. dot arrow = Arrow(axes.get_origin(), ps_dot.get_center(), buff=0, fill_color=BLUE) arrow.set_stroke(BLACK, 2, background=True) arrow_outline = arrow.copy() arrow_outline.set_fill(opacity=0) arrow_outline.set_stroke(YELLOW, 1) self.play(LaggedStart( FadeIn(arrow), FadeOut(ps_dot), ShowPassingFlash(arrow_outline, run_time=1, time_width=0.5), lag_ratio=0.5, )) self.wait() self.play(LaggedStart( FadeIn(ps_dot), FadeOut(arrow), FlashAround(ps_dot, buff=0.05), )) self.wait() self.play(FlashAround(equation)) self.play(psp_tracker.move_to, [4, 3, 0], run_time=2) self.wait() # Function of time new_lhs = Matrix([["x(t)"], ["y(t)"]]) new_lhs.match_height(equation[0]) new_lhs.move_to(equation[0], RIGHT) self.play( FadeTransformPieces(equation[0], new_lhs), ) self.remove(equation[0]) self.add(new_lhs) equation.replace_submobject(0, new_lhs) # Initialize rotation curr_time = self.time curr_psp = get_psp() psp_tracker.add_updater(lambda m: m.move_to(np.dot( curr_psp, np.transpose(rotation_about_z(0.25 * (self.time - curr_time))), ))) self.wait(5) # Rate of change deriv_lhs = Matrix([["x'(t)"], ["y'(t)"]], bracket_h_buff=SMALL_BUFF) deriv_lhs.match_height(equation[0]) deriv_lhs.move_to(equation[0]) deriv_lhs.set_color(RED_B) deriv_label = Text("Rate of change", font_size=24) deriv_label.match_width(deriv_lhs) deriv_label.match_color(deriv_lhs) deriv_label.next_to(deriv_lhs, DOWN, SMALL_BUFF) self.play( FadeIn(deriv_lhs), Write(deriv_label, run_time=1), equation.animate.shift(2.0 * deriv_lhs.get_height() * DOWN) ) self.wait(5) deriv_vect = Arrow(fill_color=RED_B) deriv_vect.add_updater( lambda m: m.put_start_and_end_on( axes.get_origin(), axes.c2p(-0.5 * get_psp()[1], 0.5 * get_psp()[0]) ).shift( ps_dot.get_center() - axes.get_origin() ) ) pre_vect = Arrow(LEFT, RIGHT) pre_vect.replace(deriv_label, dim_to_match=0) pre_vect.set_fill(RED_B, 0) moving_vect = pre_vect.copy() deriv_vect.set_opacity(0) self.add(deriv_vect) self.play( UpdateFromAlphaFunc( moving_vect, lambda m, a: m.interpolate(pre_vect, deriv_vect, a).set_fill(opacity=a), remover=True ) ) deriv_vect.set_fill(opacity=1) self.add(deriv_vect, ps_dot) self.wait(8) # Show equation rhs = VGroup( OldTex("="), Matrix([["-y(t)"], ["x(t)"]], bracket_h_buff=SMALL_BUFF) ) rhs.match_height(deriv_lhs) rhs.arrange(RIGHT) rhs.next_to(deriv_lhs, RIGHT) self.play(FadeIn(rhs)) self.wait() for i in range(2): self.play(FlashAround( VGroup(deriv_lhs.get_entries()[i], rhs[1].get_entries()[i]), run_time=3, time_width=4, )) self.wait(2) # Write with a matrix deriv_lhs.generate_target() new_eq = VGroup( deriv_lhs.target, OldTex("="), IntegerMatrix([[0, -1], [1, 0]], bracket_v_buff=MED_LARGE_BUFF), Matrix([["x(t)"], ["y(t)"]], bracket_h_buff=SMALL_BUFF), ) new_eq[2].match_height(new_eq[0]) new_eq[3].match_height(new_eq[0]) new_eq.arrange(RIGHT) new_eq.to_corner(UR) self.play( MoveToTarget(deriv_lhs), MaintainPositionRelativeTo(deriv_label, deriv_lhs), ReplacementTransform(rhs[0], new_eq[1]), ReplacementTransform(rhs[1].get_brackets(), new_eq[3].get_brackets()), FadeIn(new_eq[2], scale=2), FadeTransform(rhs[1].get_entries()[1], new_eq[3].get_entries()[0]), FadeTransform(rhs[1].get_entries()[0], new_eq[3].get_entries()[1]), ) self.wait(3) row_rect = SurroundingRectangle(new_eq[2].get_entries()[:2], buff=SMALL_BUFF) col_rect = SurroundingRectangle(new_eq[3].get_entries(), buff=SMALL_BUFF) both_rects = VGroup(row_rect, col_rect) both_rects.set_stroke(YELLOW, 2) self.play(*map(ShowCreation, both_rects)) self.wait(3) self.play(row_rect.animate.move_to(new_eq[2].get_entries()[2:4])) self.wait(3) self.play(FadeOut(both_rects)) # Write general form general_form = OldTex( "{d \\over dt}", "\\vec{\\textbf{v} }", "(t)", "=", "\\textbf{M}", "\\vec{\\textbf{v} }", "(t)", ) general_form.set_color_by_tex("d \\over dt", RED_B) general_form.set_color_by_tex("\\textbf{v}", GREY_B) general_form.scale(1.2) general_form.next_to(new_eq, DOWN, LARGE_BUFF) general_form.shift(0.5 * RIGHT) gf_rect = SurroundingRectangle(general_form, buff=MED_SMALL_BUFF) gf_rect.set_stroke(YELLOW, 2) equation.clear_updaters() self.play( FadeIn(general_form), FadeOut(equation), ) self.wait() self.play(ShowCreation(gf_rect)) self.wait(4 * TAU) # Fade all else out self.play(FadeOut(VGroup(gf_rect, general_form, new_eq, deriv_lhs, deriv_label))) self.wait(4 * TAU) print(self.num_plays) class From2DTo1D(Scene): show_solution = False def construct(self): # (Setup vector equation) equation = get_2d_equation() equation.center() equation.to_edge(UP, buff=1.0) deriv, vect_sym, equals, matrix_mob, vect_sym2 = equation vect_sym.save_state() # (Setup plane) plane = NumberPlane( x_range=(-4, 4), y_range=(-2, 2), height=4, width=8, ) plane.to_edge(DOWN) point = Point(plane.c2p(2, 0.5)) vector = Arrow(plane.get_origin(), point.get_location(), buff=0) vector.set_color(YELLOW) # Show vector vect_sym.set_x(0) static_vect_sym = vect_sym.deepcopy() for entry in static_vect_sym.get_entries(): entry[1:].set_opacity(0) entry[:1].move_to(entry) static_vect_sym.get_brackets().space_out_submobjects(0.7) vector.save_state() vector.put_start_and_end_on( static_vect_sym.get_corner(DL), static_vect_sym.get_corner(UR), ) vector.set_opacity(0) self.add(plane, static_vect_sym) self.play(Restore(vector)) self.wait() # Changing with time matrix = np.array([[0.5, -3], [1, -0.5]]) def func(x, y): return 0.2 * np.dot([x, y], matrix.T) move_points_along_vector_field(point, func, plane) vector.add_updater(lambda m: m.put_start_and_end_on( plane.get_origin(), point.get_location(), )) deriv_vector = Vector(fill_color=RED, thickness=0.03) deriv_vector.add_updater( lambda m: m.put_start_and_end_on( plane.get_origin(), plane.c2p(*func(*plane.p2c(point.get_location()))), ).shift(vector.get_vector()) ) self.add(point) self.play(ReplacementTransform(static_vect_sym, vect_sym)) self.wait(3) # Show matrix equation deriv_underline = Underline(VGroup(deriv, vect_sym.saved_state)) deriv_underline.set_stroke(RED, 3) alt_line = deriv_underline.deepcopy() self.play( Restore(vect_sym), FadeIn(deriv), ) self.wait() self.play( ShowCreation(deriv_underline), ) self.play( VFadeIn(deriv_vector, rate_func=squish_rate_func(smooth, 0.8, 1.0)), UpdateFromAlphaFunc( alt_line, lambda m, a: m.put_start_and_end_on( interpolate(deriv_underline.get_start(), deriv_vector.get_start(), a), interpolate(deriv_underline.get_end(), deriv_vector.get_end(), a), ), remover=True, ), ) self.wait(4) self.play( LaggedStartMap(FadeIn, equation[2:4], shift=RIGHT, lag_ratio=0.3), TransformFromCopy( equation[1], equation[4], path_arc=-45 * DEGREES, run_time=2, rate_func=squish_rate_func(smooth, 0.3, 1.0) ) ) self.wait(8) # Highlight equation deriv_rect = SurroundingRectangle(equation[:2]) deriv_rect.set_stroke(RED, 2) rhs_rect = SurroundingRectangle(equation[-1]) rhs_rect.set_stroke(YELLOW, 2) self.play(ShowCreation(deriv_rect)) self.wait() self.play(ReplacementTransform(deriv_rect, rhs_rect, path_arc=-45 * DEGREES)) # Draw vector field vector_field = VectorField( func, plane, magnitude_range=(0, 1.2), opacity=0.5, vector_config={"thickness": 0.02} ) vector_field.sort(lambda p: get_norm(p - plane.get_origin())) self.add(vector_field, deriv_vector, vector) VGroup(vector, deriv_vector).set_stroke(BLACK, 5, background=True) self.play( FadeOut(rhs_rect), LaggedStartMap(GrowArrow, vector_field, lag_ratio=0) ) self.wait(14) # flow_lines = AnimatedStreamLines(StreamLines(func, plane, step_multiple=0.25)) # self.add(flow_lines) # self.wait(4) # self.play(VFadeOut(flow_lines)) # self.wait(10) # Show solution equation.add(deriv_underline) mat_exp = get_matrix_exponential( [["a", "b"], ["c", "d"]], h_buff=0.75, v_buff=0.75, ) mat_exp[1].set_color(TEAL) if self.show_solution: equation.generate_target() equation.target.to_edge(LEFT) implies = OldTex("\\Rightarrow") implies.next_to(equation.target, RIGHT) solution = VGroup( equation[1].copy(), OldTex("="), mat_exp, Matrix( [["x(0)"], ["y(0)"]], bracket_h_buff=SMALL_BUFF, bracket_v_buff=SMALL_BUFF, ) ) solution[2].match_height(solution[0]) solution[3].match_height(solution[0]) solution.arrange(RIGHT, buff=MED_SMALL_BUFF) solution.next_to(implies, RIGHT, MED_LARGE_BUFF) solution.align_to(equation[1], DOWN) solution_rect = SurroundingRectangle(solution, buff=MED_SMALL_BUFF) self.play( MoveToTarget(equation), ) self.play(LaggedStart( Write(implies), ShowCreation(solution_rect), TransformFromCopy(equation[4], solution[0], path_arc=30 * DEGREES), )) self.wait() self.play(LaggedStart( TransformFromCopy(equation[3], solution[2][1]), FadeIn(solution[2][0]), FadeIn(solution[2][2]), FadeIn(solution[1]), FadeIn(solution[3]), lag_ratio=0.1, )) self.wait(10) return else: # Show relation with matrix exp mat_exp.move_to(equation[0], DOWN) mat_exp.to_edge(RIGHT, buff=LARGE_BUFF) equation.generate_target() equation.target.to_edge(LEFT, buff=LARGE_BUFF) arrow1 = Arrow(equation.target.get_corner(UR), mat_exp.get_corner(UL), path_arc=-45 * DEGREES) arrow2 = Arrow(mat_exp.get_corner(DL), equation.target.get_corner(DR), path_arc=-45 * DEGREES) arrow1.shift(0.2 * RIGHT) self.play(MoveToTarget(equation)) self.play( FadeIn(mat_exp[0::2]), TransformFromCopy(equation[3], mat_exp[1]), Write(arrow1, run_time=1), ) self.wait(4) self.play(Write(arrow2, run_time=2)) self.wait(4) normal_exp = OldTex("e^{rt}")[0] normal_exp.set_height(1.0) normal_exp[1].set_color(BLUE) normal_exp.move_to(mat_exp) self.play( FadeTransformPieces(mat_exp, normal_exp), FadeOut(VGroup(arrow1, arrow2)) ) self.wait(2) # Transition to 1D max_x = 50 mult = 50 number_line = NumberLine((0, max_x), width=max_x) number_line.add_numbers() number_line.move_to(plane) number_line.to_edge(LEFT) nl = number_line nl2 = NumberLine((0, mult * max_x, mult), width=max_x) nl2.add_numbers() nl2.set_width(nl.get_width() * mult) nl2.shift(nl.n2p(0) - nl2.n2p(0)) nl2.set_opacity(0) nl.add(nl2) new_equation = OldTex( "{d \\over dt}", "x(t)", "=", "r \\cdot ", "x(t)", ) new_equation[0][3].set_color(GREY_B) new_equation[1][2].set_color(GREY_B) new_equation[4][2].set_color(GREY_B) new_equation[3][0].set_color(BLUE) new_equation.match_height(equation) new_equation.move_to(equation) self.remove(point) vector.clear_updaters() deriv_vector.clear_updaters() self.remove(vector_field) plane.add(vector_field) self.add(number_line, deriv_vector, vector) self.play( normal_exp.animate.scale(0.5).to_corner(UR), # Plane to number line vector.animate.put_start_and_end_on(nl.n2p(0), nl.n2p(1)), deriv_vector.animate.put_start_and_end_on(nl.n2p(1), nl.n2p(1.5)), plane.animate.shift(nl.n2p(0) - plane.get_origin()).set_opacity(0), FadeIn(number_line, rate_func=squish_rate_func(smooth, 0.5, 1)), # Equation TransformMatchingShapes(equation[0], new_equation[0]), Transform(equation[1].get_entries()[0], new_equation[1]), FadeTransform(equation[2], new_equation[2]), FadeTransform(equation[3], new_equation[3]), FadeTransform(equation[4].get_entries()[0], new_equation[4]), FadeOut(equation[1].get_brackets()), FadeOut(equation[1].get_entries()[1]), FadeOut(equation[4].get_brackets()), FadeOut(equation[4].get_entries()[1]), FadeOut(deriv_underline), run_time=2, ) vt = ValueTracker(1) vt.add_updater(lambda m, dt: m.increment_value(0.2 * dt * m.get_value())) vector.add_updater(lambda m: m.put_start_and_end_on(nl.n2p(0), nl.n2p(vt.get_value()))) deriv_vector.add_updater(lambda m: m.set_width(0.5 * vector.get_width()).move_to(vector.get_right(), LEFT)) self.add(vt) self.wait(11) self.play( number_line.animate.scale(0.3, about_point=nl.n2p(0)), ) self.wait(4) number_line.generate_target() number_line.target.scale(0.1, about_point=nl.n2p(0)), number_line.target[-1].set_opacity(1) self.play( MoveToTarget(number_line) ) self.wait(11) self.play(number_line.animate.scale(0.2, about_point=nl.n2p(0))) self.wait(10) class SchroedingersEquationIntro(Scene): def construct(self): # Show equation title = Text("Schrödinger equation", font_size=72) title.to_edge(UP) self.add(title) t2c = { "|\\psi \\rangle": BLUE, "{H}": GREY_A, "=": WHITE, "i\\hbar": WHITE, } original_equation = OldTex( "i\\hbar \\frac{\\partial}{\\partial t} |\\psi \\rangle = {H} |\\psi \\rangle", tex_to_color_map=t2c ) equation = OldTex( "\\frac{\\partial}{\\partial t} |\\psi \\rangle = \\frac{1}{i\\hbar} {H} |\\psi \\rangle", tex_to_color_map=t2c ) VGroup(original_equation, equation).scale(1.5) psis = original_equation.get_parts_by_tex("\\psi") state_label = OldTexText("State of a system \\\\ as a vector", font_size=36) state_label.next_to(psis, DOWN, buff=1.5) state_label.shift(0.5 * RIGHT) state_arrows = VGroup(*(Arrow(state_label, psi) for psi in psis)) state_label.match_color(psis[0]) state_arrows.match_color(psis[0]) psis.set_color(WHITE) randy = Randolph(height=2.0, color=BLUE_C) randy.to_corner(DL) randy.set_opacity(0) self.play(Write(original_equation, run_time=3)) self.wait() self.play( randy.animate.set_opacity(1).change("horrified", original_equation) ) self.play(Blink(randy)) self.play( randy.change("pondering", state_label), psis.animate.match_color(state_label), FadeIn(state_label, 0.25 * DOWN), *map(GrowArrow, state_arrows), ) self.wait() self.play(Blink(randy)) self.wait() self.play( ReplacementTransform(original_equation[1:4], equation[0:3]), Write(equation[3]), ReplacementTransform(original_equation[0], equation[4], path_arc=90 * DEGREES), ReplacementTransform(original_equation[4:], equation[5:]), state_arrows.animate.become( VGroup(*(Arrow(state_label, psi) for psi in equation.get_parts_by_tex("\\psi"))) ), randy.change("hesitant", equation) ) self.play(FlashAround(equation[0], time_width=2, run_time=2)) self.play(Blink(randy)) mat_rect = SurroundingRectangle(equation[3:6], buff=0.05, color=TEAL) mat_label = Text("A certain matrix", font_size=36) mat_label.next_to(mat_rect, UP) mat_label.match_color(mat_rect) self.play( ShowCreation(mat_rect), FadeIn(mat_label, 0.25 * UP), ) self.wait() self.play(randy.change("confused", equation)) self.play(Blink(randy)) self.wait() # Complicating factors psi_words = OldTexText( "Often this is a function.\\\\", "(But whatever functions are really\\\\just infinite-dimensional vectors)" ) psi_words[0].match_width(psi_words[1], about_edge=DOWN) psi_words[1].shift(0.1 * DOWN) psi_words.scale(0.75) psi_words.move_to(state_label, UP) psi_words[0].set_color(RED) psi_words[1].set_color(RED_D) mat_line = Line(LEFT, RIGHT) mat_line.set_stroke(RED, 5) mat_line.replace(mat_label.get_part_by_text("matrix"), dim_to_match=0) operator_word = Text("operator", font_size=36) operator_word.next_to(mat_label, UP, buff=SMALL_BUFF) operator_word.align_to(mat_line, LEFT) operator_word.set_color(RED) complex_valued = Text("Complex-valued", font_size=30) complex_valued.set_color(RED) complex_valued.next_to(equation, RIGHT) complex_valued.to_edge(RIGHT) cv_arrow = Arrow(complex_valued, equation, fill_color=RED) self.play(LaggedStart( FadeOut(state_label), FadeIn(psi_words), ShowCreation(mat_line), Write(operator_word, run_time=1), GrowArrow(cv_arrow), FadeIn(complex_valued), randy.change("horrified", equation), lag_ratio=0.5 )) self.play(Blink(randy)) self.wait() class SimpleDerivativeOfExp(TeacherStudentsScene): def construct(self): eq = OldTex( "{d \\over dt}", "e^{rt}", "=", "r", "e^{rt}", ) eq.set_color_by_tex("r", TEAL, substring=False) for part in eq.get_parts_by_tex("e^{rt}"): part[1].set_color(TEAL) s0, s1, s2 = self.students morty = self.teacher bubble = s2.get_bubble(eq) self.play( s2.change("pondering", eq), FadeIn(bubble, lag_ratio=0.2), ) self.play( LaggedStart( s0.change("hesitant", eq), s1.change("erm", eq), morty.change("tease"), ), Write(eq[:2]) ) self.wait() self.play( TransformFromCopy(*eq.get_parts_by_tex("e^{rt}"), path_arc=45 * DEGREES), Write(eq.get_part_by_tex("=")), *( pi.animate.look_at(eq[4]) for pi in self.pi_creatures ) ) self.play( FadeTransform(eq[4][1].copy(), eq[3][0], path_arc=90 * DEGREES) ) self.play( LaggedStart( s0.change("thinking"), s1.change("tease"), ) ) self.wait(2) rect = ScreenRectangle(height=3.5) rect.set_fill(BLACK, 1) rect.set_stroke(BLUE_B, 2) rect.to_corner(UR) self.play( morty.change("raise_right_hand", rect), self.change_students("pondering", "pondering", "pondering", look_at=rect), FadeIn(rect, UP) ) self.wait(10) class ETitleCard(Scene): def construct(self): title = OldTexText("A brief review of $e$\\\\and exponentials") title.scale(2) self.add(title) class GraphAndHistoryOfExponential(Scene): def construct(self): # Setup axes = Axes( x_range=(0, 23, 1), y_range=(0, 320, 10), height=160, width=13, axis_config={"include_tip": False} ) axes.y_axis.add(*(axes.y_axis.get_tick(x, size=0.05) for x in range(20))) axes.to_corner(DL) r = 0.25 exp_graph = axes.get_graph(lambda t: np.exp(r * t)) exp_graph.set_stroke([BLUE_E, BLUE, YELLOW]) graph_template = exp_graph.copy() graph_template.set_stroke(width=0) axes.add(graph_template) equation = self.get_equation() solution = OldTex("x({t}) = e^{r{t} }", tex_to_color_map={"{t}": GREY_B, "r": BLUE}) solution.next_to(equation, DOWN, MED_LARGE_BUFF) self.add(axes) self.add(axes.get_x_axis_label("t")) self.add(axes.get_y_axis_label("x")) self.add(equation) curr_time = self.time exp_graph.add_updater(lambda m: m.pointwise_become_partial( graph_template, 0, (self.time - curr_time) / 20, )) dot = Dot(color=BLUE_B, radius=0.04) dot.add_updater(lambda d: d.move_to(exp_graph.get_end())) vect = Arrow(DOWN, UP, fill_color=YELLOW, thickness=0.025) vect.add_updater(lambda v: v.put_start_and_end_on( axes.get_origin(), axes.y_axis.get_projection(exp_graph.get_end()), )) h_line = always_redraw(lambda: DashedLine( vect.get_end(), dot.get_left(), stroke_width=1, stroke_color=GREY_B, )) v_line = always_redraw(lambda: DashedLine( axes.x_axis.get_projection(dot.get_bottom()), dot.get_bottom(), stroke_width=1, stroke_color=GREY_B, )) def stretch_axes(factor): axes.generate_target(use_deepcopy=True) axes.target.stretch(factor, 1, about_point=axes.get_origin()), axes.target.x_axis.stretch(1 / factor, 1, about_point=axes.get_origin()), self.play(MoveToTarget(axes)) self.add(exp_graph, dot, vect, h_line, v_line) # equation.scale(2, about_edge=UP)### self.wait(2) # Highlight equation parts index = equation.index_of_part_by_tex("=") lhs = equation[:index] rhs = equation[index + 1:] for part in (rhs, lhs): self.play(FlashAround(part, time_width=3, run_time=2)) self.wait() # self.wait(4) stretch_axes(0.2) self.wait(6) stretch_axes(0.23) self.wait(4) exp_graph.clear_updaters() exp_graph.become(graph_template) exp_graph.set_stroke(width=3) self.add(exp_graph) self.play(FadeOut(vect), FadeOut(dot)) self.wait() # Write exponential growth words = Text("Exponential growth") words.next_to(axes.c2p(0, 0), UR, buff=0) original_words = words.deepcopy() original_words.set_opacity(0) def func(p): t, x = axes.p2c(p) angle = axes.angle_of_tangent(t, exp_graph) vect = rotate_vector(RIGHT, angle + PI / 2) graph_point = axes.input_to_graph_point(t, exp_graph) y = (axes.y_axis.get_projection(p) - axes.get_origin())[1] return graph_point + y * vect fill_tracker = ValueTracker(0) words.add_updater(lambda m: m.become(original_words).set_fill(opacity=fill_tracker.get_value()).apply_function(func)) self.add(words) self.play( ApplyMethod(original_words.next_to, axes.c2p(14, 0), UP, SMALL_BUFF, run_time=2), fill_tracker.animate.set_value(1), ) self.remove(original_words, fill_tracker) words.clear_updaters() self.wait() # Introduce 2.71828 solution_with_number = OldTex( "{d \\over dt}", "(2.71828...)^{", "r", "t", "}", "=", "r", "\\cdot", "(2.71828...)^{", "r", "t", "}", tex_to_color_map={ "2.71828...": TEAL, } ) solution_with_e = OldTex( "{d \\over dt}", "{e}^{", "r", "t", "}", "=", "r", "\\cdot", "{e}^{", "r", "t", "}", tex_to_color_map={ "{e}": TEAL, } ) for eq in solution_with_number, solution_with_e: eq.set_color_by_tex("r", BLUE, substring=False) eq.set_color_by_tex("t", GREY_B, substring=False) eq.next_to(equation, DOWN, buff=MED_LARGE_BUFF) lhs = solution_with_number[:6] lhs.save_state() lhs.match_x(equation) self.play(FadeIn(lhs[1:], DOWN)) self.wait() self.play(Write(lhs[0])) dot1 = Dot(radius=0.05, color=RED) dot2 = dot1.copy() for sec_dot in dot1, dot2: sec_dot.x_tracker = ValueTracker(15) sec_dot.add_updater(lambda d: d.move_to(axes.input_to_graph_point( d.x_tracker.get_value(), exp_graph ))) line = Line(LEFT, RIGHT, stroke_color=GREY_A, stroke_width=2) line.add_updater(lambda l: l.put_start_and_end_on( dot1.get_center(), dot2.get_center() ).set_length(10)) dot2.x_tracker.set_value(17) dot1.x_tracker.set_value(15) self.play( FadeOut(words), FadeIn(dot1), FadeIn(dot2), FadeIn(line), ) self.play( dot2.x_tracker.animate.set_value(15 + 1e-6), run_time=3 ) self.play( dot1.x_tracker.animate.set_value(18), dot2.x_tracker.animate.set_value(18 + 1e-6), run_time=3 ) self.play( FadeOut(dot1), FadeOut(dot2), FadeOut(line), ) self.wait() self.play(Restore(lhs)) self.play( FadeIn(solution_with_number[6]), TransformFromCopy(lhs[1:], solution_with_number[8:], path_arc=30 * DEGREES), ) self.play(TransformFromCopy( solution_with_number[12], solution_with_number[7], path_arc=-90 * DEGREES, )) self.wait() # Historical letters correspondants = Group() for name1, name2, letter, year in [("Leibniz", "Huygens", "b", "1690"), ("Euler", "Goldbach", "e", "1731")]: im1 = ImageMobject(name1, height=2.5) im2 = ImageMobject(name2, height=2.5) lines = VGroup(*(Line(LEFT, RIGHT) for x in range(11))) lines.set_width(1.6) lines.arrange(DOWN, buff=0.2) lines.set_stroke(GREY_A, 2) lines[-1].stretch(0.5, 0, about_edge=LEFT) eq = OldTex(letter, "= 2.71828\\dots") eq[0].scale(1.5, about_edge=RIGHT) eq[0].align_to(eq[1], DOWN) eq.set_color(TEAL) eq.match_width(lines) eq.move_to(lines[5]) lines.remove(*lines[4:7]) lines.add(eq) box = SurroundingRectangle(lines, buff=0.25) box.set_stroke(GREY_C, 2) note = VGroup(box, lines) note.match_height(im1) group = Group() arrow = Vector(0.5 * RIGHT, fill_color=GREY_A) group.add(im1, arrow, note, arrow.copy(), im2) group.arrange(RIGHT) note.align_to(im1, UP) group.next_to(solution_with_number, DOWN, buff=LARGE_BUFF) for im, name, index in [(im1, name1, 0), (im2, name2, 4)]: name_label = Text(name, font_size=24) name_label.set_color(GREY_A) name_label.next_to(im, DOWN) rect = SurroundingRectangle(im, buff=0, stroke_color=GREY_A, stroke_width=1) group.replace_submobject(index, Group(im, rect, name_label)) date = Text(year, font_size=24) date.next_to(box, UP) note.add(date) group.eq = eq group.eq.set_opacity(0) correspondants.add(group) b_group, e_group = correspondants self.play(LaggedStartMap(FadeIn, b_group, shift=0.5 * UP, lag_ratio=0.2, run_time=1)) b_group.eq.set_fill(opacity=1) self.play(Write(b_group.eq, run_time=1)) self.wait() self.play( LaggedStartMap(FadeOut, b_group, shift=0.5 * UP), LaggedStartMap(FadeIn, e_group, shift=0.5 * UP), ) e_group.eq.set_fill(opacity=1) self.play(Write(e_group.eq, run_time=1)) self.wait() # Euler's book e_usage = e_group.eq.copy() book = ImageMobject("Introductio_in_Analysin_infinitorum") book.match_width(e_group[0][0]) book.move_to(e_group[2][0]) date = Text("1748", font_size=24) date.next_to(book, UP, SMALL_BUFF) self.play( LaggedStartMap(FadeOut, e_group[2:], shift=0.5 * RIGHT), FadeIn(book), FadeIn(date), e_usage.animate.scale(1.4).next_to(book, RIGHT, aligned_edge=UP).shift(0.25 * DOWN) ) self.wait() pi_eq = OldTex("\\pi = 3.1415\\dots", fill_color=GREEN, font_size=36) func_eq = OldTex("f(x)", font_size=36) pi_eq.next_to(e_usage, DOWN, buff=0.5, aligned_edge=LEFT) func_eq.next_to(pi_eq, DOWN, buff=0.5, aligned_edge=LEFT) self.play(Write(pi_eq, run_time=1)) self.wait() self.play(Write(func_eq, run_time=1)) self.wait() # Final notation self.play(FadeTransformPieces(solution_with_number, solution_with_e)) self.wait() def get_equation(self, r="r"): return get_1d_equation(r).to_edge(UP) class BernoullisThoughts(Scene): def construct(self): im = ImageMobject("Jacob_Bernoulli", height=4) name = Text("Jacob Bernoulli") name.match_width(im) name.next_to(im, DOWN, SMALL_BUFF) jacob = Group(im, name) jacob.to_corner(DR) bubble = ThoughtBubble(height=3.5, width=5) bubble.pin_to(jacob) bubble.shift(0.25 * RIGHT + 0.75 * DOWN) dollars = Text("$$$") dollars.set_height(1) dollars.set_color(GREEN) dollars.move_to(bubble.get_bubble_center()) dollar = dollars[0].copy() continuous_money = VGroup(*( dollar.copy().shift(smooth(x) * RIGHT * 2.0 + np.exp(smooth(x) - 1) * UP) for x in np.linspace(0, 1, 300) )) continuous_money.set_fill(opacity=0.1) continuous_money.center() continuous_money.move_to(dollars) self.play(FadeIn(jacob, shift=RIGHT)) self.play( ShowCreation(bubble), Write(dollars) ) self.wait() self.add(continuous_money) self.play( FadeOut(dollars), Write(continuous_money, stroke_color=GREEN, stroke_width=1, run_time=3), ) self.play( FadeOut(continuous_money, lag_ratio=0.01), FadeIn(dollars) ) self.wait() limit = OldTex("\\left(1 + \\frac{r}{n}\\right)^{nt}") limit.move_to(bubble.get_bubble_center()) limit.scale(1.5) self.play( FadeOut(dollars), FadeIn(limit), ) self.wait() class CompoundInterestPopulationAndEpidemic(Scene): def construct(self): N = 16000 points = 2 * np.random.random((N, 3)) - 1 points[:, 2] = 0 points = points[[get_norm(p) < 1 for p in points]] points *= 2 * FRAME_WIDTH points = np.array(list(sorted( points, key=lambda p: get_norm(p) + 0.5 * random.random() ))) dollar = OldTex("\\$") dollar.set_fill(GREEN) person = SVGMobject("person") person.set_fill(GREY_B, 1) virus = SVGMobject("virus") virus.set_fill([RED, RED_D]) virus.remove(virus[1:]) virus[0].set_points(virus[0].get_subpaths()[0]) templates = [dollar, person, virus] mob_height = 0.25 for mob in templates: mob.set_stroke(BLACK, 1, background=True) mob.set_height(mob_height) dollars, people, viruses = groups = [ VGroup(*(mob.copy().move_to(point) for point in points)) for mob in templates ] dollars.set_submobjects(dollars[:20]) people.set_submobjects(people[:500]) start_time = self.time def get_n(): time = self.time - start_time return int(math.exp(0.75 * time)) def update_group(group): group.set_opacity(0) group[:get_n()].set_opacity(0.9) def update_height(group, alpha): for mob in group: mob.set_height(max(alpha * mob_height, 1e-4)) for group in groups: group.add_updater(update_group) frame = self.camera.frame frame.set_height(2) frame.add_updater(lambda m, dt: m.set_height(m.get_height() * (1 + 0.2 * dt))) self.add(frame) self.add(dollars) self.wait(3) self.play( UpdateFromAlphaFunc(people, update_height), UpdateFromAlphaFunc(dollars, update_height, rate_func=lambda t: smooth(1 - t), remover=True), ) self.wait(4) self.play( UpdateFromAlphaFunc(viruses, update_height), UpdateFromAlphaFunc(people, update_height, rate_func=lambda t: smooth(1 - t), remover=True), ) self.wait(4) class CovidPlot(ExternallyAnimatedScene): pass class Compare1DTo2DEquations(Scene): def construct(self): eq1d = get_1d_equation() eq2d = get_2d_equation() eq1d.match_height(eq2d) equations = VGroup(eq1d, eq2d) equations.arrange(DOWN, buff=2.0) equations.to_edge(LEFT, buff=1.0) solutions = VGroup( OldTex("e^{rt}", tex_to_color_map={"r": BLUE}), get_matrix_exponential( [["a", "b"], ["c", "d"]], h_buff=0.75, v_buff=0.5, bracket_h_buff=0.25, ) ) solutions[1][1].set_color(TEAL) solutions[0].scale(2) arrows = VGroup() for eq, sol in zip(equations, solutions): sol.next_to(eq[-1], RIGHT, index_of_submobject_to_align=0) sol.set_x(4) arrows.add(Arrow(eq, sol[0], buff=0.5)) sol0, sol1 = solutions sol0.save_state() sol0.center() self.add(sol0) self.wait() self.play( Restore(sol0), TransformFromCopy(Arrow(eq1d, sol0, fill_opacity=0), arrows[0]), FadeIn(eq1d), ) self.wait(2) self.play( TransformMatchingShapes(sol0.copy(), sol1, fade_transform_mismatches=True), ) self.wait() self.play( TransformFromCopy(*arrows), LaggedStart(*( FadeTransform(m1.copy(), m2) for m1, m2 in zip( [eq1d[:2], eq1d[2:5], eq1d[5], eq1d[6], eq1d[7:]], eq2d ) ), lag_ratio=0.05) ) self.wait() class EVideoWrapper(VideoWrapper): title = "Video on $e^x$" class ManyExponentialForms(ExternallyAnimatedScene): pass class EBaseMisconception(Scene): def construct(self): randy = Randolph() randy.flip() randy.to_corner(DR) self.add(randy) self.play( PiCreatureSays( randy, OldTexText("This function is\\\\about about $e$", tex_to_color_map={"$e$": BLUE}), target_mode="thinking", bubble_config={"height": 3, "width": 4}, ) ) self.play(Blink(randy)) self.wait(2) self.play(randy.change("tease")) self.play(Blink(randy)) cross = Cross(randy.bubble.content, stroke_width=[0, 5, 5, 5, 0]) for line in cross: line.insert_n_curves(10) cross.scale(1.25) self.play( ShowCreation(cross), randy.change("guilty") ) self.wait() class OneFinalPoint(TeacherStudentsScene): def construct(self): self.teacher_says( OldTexText("One final point\\\\about one-dimension"), bubble_config={"height": 3, "width": 4}, ) self.play_student_changes( "happy", "hesitant", "tease", added_anims=[self.teacher.animate.look_at(self.students[2])] ) self.wait(3) class ManySolutionsDependingOnInitialCondition(VideoWrapper): def construct(self): # Setup graphs and equations axes = Axes( x_range=(0, 12), y_range=(0, 10), width=12, height=6, ) axes.add(axes.get_x_axis_label("t")) axes.add(axes.get_y_axis_label("x")) axes.x_axis.add_numbers() equation = get_1d_equation("0.3").to_edge(UP) def get_graph(x0, r=0.3): return axes.get_graph(lambda t: np.exp(r * t) * x0) step = 0.05 graphs = VGroup(*( get_graph(x0) for x0 in np.arange(step, axes.y_range[1], step) )) graphs.set_submobject_colors_by_gradient(BLUE_E, BLUE, TEAL, YELLOW) graphs.set_stroke(width=1) graph = get_graph(1) graph.set_color(BLUE) solution = OldTex( "x({t}) = e^{0.3 {t} } \\cdot x_0", tex_to_color_map={"{t}": GREY_B, "0.3": BLUE, "=": WHITE}, ) solution.next_to(equation, DOWN, LARGE_BUFF) solution.shift(0.25 * RIGHT) solution[-1][1:].set_fill(MAROON_B) group = VGroup(solution, equation) group.set_stroke(BLACK, 5, background=True) group.shift(2 * LEFT) labels = VGroup( Text("Differential equation", font_size=30), Text("Solution", font_size=30), ) labels.set_stroke(BLACK, 5, background=True) arrows = VGroup() for label, eq in zip(labels, [equation, solution[:-1]]): label.next_to(eq, RIGHT, buff=1.5) label.align_to(labels[0], LEFT) arrows.add(Arrow(label, eq)) eq_label, sol_label = labels eq_arrow, sol_arrow = arrows # Show many possible representations alt_rhs = OldTex( "&= 2^{(0.4328\\dots) t }\\\\", "&= 3^{(0.2730\\dots) t }\\\\", "&= 4^{(0.2164\\dots) t }\\\\", "&= 5^{(0.1864\\dots) t }\\\\", ) alt_rhs.next_to(solution.get_part_by_tex("="), DOWN, buff=MED_LARGE_BUFF, aligned_edge=LEFT) alt_rhs.set_stroke(BLACK, 5, background=True) choice_words = Text("This is a choice!", color=YELLOW, font_size=24) choice_words.next_to(solution[:-1], RIGHT, MED_LARGE_BUFF) choice_words.set_stroke(BLACK, 5, background=True) self.add(axes) self.add(solution[:-1]) self.play(ShowCreation(graph, run_time=3)) self.wait() self.play(FlashAround(solution.get_part_by_tex("e"), time_width=2, buff=0.05, run_time=2)) self.wait() self.play(LaggedStartMap(FadeIn, alt_rhs, shift=0.5 * DOWN, lag_ratio=0.6, run_time=5)) self.wait() self.play( alt_rhs.animate.set_opacity(0.4), FlashAround(solution[4:-1]), Write(choice_words, run_time=1) ) self.wait() self.play( FadeTransform(solution[:3].copy(), equation[2:5]), # x(t) FadeTransform(solution[:3].copy(), equation[7:10]), # x(t) FadeTransform(solution[3].copy(), equation[5]), # = FadeIn(equation[:2]), FadeOut(choice_words), ) self.play(FadeTransform(solution[5].copy(), equation[6])) # r self.wait() self.play(alt_rhs.animate.set_opacity(1.0)) self.wait() self.play(LaggedStartMap(FadeOut, alt_rhs, shift=DOWN)) for label, arrow in zip(labels, arrows): self.play( FadeIn(label, 0.5 * RIGHT), GrowArrow(arrow), ) self.wait() # Just one solution one = Text("One", font_size=30) many = Text("out of many", font_size=30) one.move_to(sol_label.get_corner(UL), LEFT) sol_label.generate_target() group = VGroup( VGroup(one, sol_label.target).arrange(RIGHT, buff=MED_SMALL_BUFF, aligned_edge=DOWN), many ) group.arrange(DOWN, buff=0.15) group.move_to(sol_label, LEFT) dot = Dot().scale(2 / 3) dot.move_to(axes.c2p(0, 0)) self.add(graphs, graph, equation, solution[:-1], arrows, labels, one, many) self.play( MoveToTarget(sol_label, rate_func=squish_rate_func(smooth, 0, 0.5)), FadeIn(one), FadeIn(many), ShowIncreasingSubsets(graphs), dot.animate.move_to(axes.c2p(0, 10)), run_time=4, ) self.wait() sol_label = VGroup(one, sol_label, many) # Initial conditions ic_label = Text("Initial conditions") ic_label.rotate(90 * DEGREES) ic_label.next_to(axes.y_axis, LEFT) x0_tracker = ValueTracker(1) get_x0 = x0_tracker.get_value x0_label = VGroup( OldTex("x_0 = ", font_size=36), DecimalNumber(1, font_size=36), ) x0_label.set_color(MAROON_B) x0_label[1].match_height(x0_label[0]) x0_label[1].next_to(x0_label[0][0][2], RIGHT, SMALL_BUFF) x0_label.add_updater(lambda m: m[1].set_value(get_x0()).set_color(MAROON_B)) sv = x0_label.get_width() + MED_SMALL_BUFF x0_label.add_updater(lambda m: m.move_to(axes.c2p(0, get_x0()), LEFT).shift(sv * LEFT)) self.play( Write(ic_label), run_time=1, ) self.play( dot.animate.move_to(axes.c2p(0, 1)), graphs.animate.set_stroke(opacity=0.5), run_time=2, ) self.wait() graph.add_updater(lambda g: g.match_points(get_graph(get_x0()))) dot.add_updater(lambda d: d.move_to(axes.c2p(0, get_x0()))) rect = BackgroundRectangle(x0_label) rect.set_fill(BLACK, 1) self.add(x0_label, rect, ic_label) self.play( FadeOut(rect), FadeOut(ic_label), self.camera.frame.animate.shift(0.6 * LEFT) ) self.wait() self.play( x0_tracker.animate.set_value(0.1), ) self.wait() for x in [5, 9]: self.play( x0_tracker.animate.set_value(x), run_time=5 ) self.wait() self.play( x0_tracker.animate.set_value(1), run_time=3, ) self.wait() # Show general solution new_sol_label = Text("General solution", font_size=36) new_sol_label.move_to(sol_label, LEFT) self.play( TransformFromCopy(x0_label[0][0][:2], solution[-1]), sol_arrow.animate.become(Arrow(sol_label, solution)) ) self.play( FadeOut(sol_label), FadeIn(new_sol_label), ) self.wait() rhs = solution[4:].copy() rhs_rect = SurroundingRectangle(rhs, stroke_width=2) rhs.generate_target() index = equation.index_of_part(equation.get_parts_by_tex("x")[1]) rhs.target.move_to(equation[index], LEFT) rhs.target.shift(0.05 * UP) self.play(ShowCreation(rhs_rect)) self.play( MoveToTarget(rhs), MaintainPositionRelativeTo(rhs_rect, rhs), FadeOut(equation[index:]), FadeOut(eq_arrow), FadeOut(eq_label), ) self.play(FadeOut(rhs_rect)) self.wait() self.play(x0_tracker.animate.set_value(2.5), run_time=2) self.play(x0_tracker.animate.set_value(0.5), run_time=2) self.wait() self.play( FadeOut(rhs), FadeIn(equation[index:]), FadeIn(eq_arrow), FadeIn(eq_label), ) self.wait() # Emphasize solution vs action exp_rect = SurroundingRectangle(solution[4:7], buff=0.05, stroke_width=2) words1 = Text("Don't think of this\n\n as a solution", font_size=30) words2 = Text( "It's something which\n\nacts on an initial condition\n\nto give a solution", t2s={"acts": ITALIC}, t2c={"acts": YELLOW, "initial condition": MAROON_B}, font_size=30 ) for words in [words1, words2]: words.next_to(exp_rect, DOWN, MED_LARGE_BUFF) words.set_stroke(BLACK, 5, background=True) self.play( ShowCreation(exp_rect), Write(words1, run_time=1) ) self.wait(2) self.play( FadeOut(words1), FadeIn(words2), ) self.wait(2) class ExoticExponentsWithEBase(Scene): def construct(self): # Write exotic exponents exps = VGroup( OldTex("e", "^{it}"), get_matrix_exponential([[3, 1], [4, 1]]), OldTex("e", "^{((i + j + k) / \\sqrt{3})t}"), OldTex("e", "^{\\left(\\frac{\\partial}{\\partial x}\\right)t}"), ) for i in (0, 2, 3): exps[i][0].scale(1.25) exps[i][0].move_to(exps[i][1:].get_corner(DL), UR) exps[1].scale(exps[0][0].get_height() / exps[1][0].get_height()) exps.arrange(DOWN, buff=LARGE_BUFF, aligned_edge=LEFT) labels = VGroup( Text("Complex numbers", font_size=30), Text("Matrices", font_size=30), Text("Quaternions", font_size=30), Text("Operators", font_size=30), ) labels.set_submobject_colors_by_gradient(BLUE_B, BLUE_C) for label, exp in zip(labels, exps): label.move_to(exp, DL) label.align_to(labels[0], LEFT) labels.set_x(-2) exps.set_x(labels.get_right()[0] + 3) arrow = Arrow(labels.get_corner(UL), labels.get_corner(DL), buff=0) arrow.shift(0.5 * LEFT) arrow.set_color(TEAL) exotic_label = OldTexText("More\\\\exotic\\\\exponents", alignment="") exotic_label.next_to(arrow, LEFT) VGroup(arrow, exotic_label).set_opacity(0) e_mobs = VGroup() for label, exp in zip(labels, exps): e_mobs.add(exp[0]) self.play( FadeIn(label), FadeIn(exp[0]), GrowFromPoint(exp[1:], exp[0].get_center()), exotic_label.animate.set_opacity(1), arrow.animate.set_opacity(1), ) self.wait() self.play(LaggedStart(*( FlashAround(e, buff=0.05, time_width=3, run_time=2) for e in e_mobs ), lag_ratio=0.1)) self.wait() # Analytic number theory exception zeta = OldTex("\\sum_{n = 1}^\\infty \\frac{1}{n^s}") zeta.move_to(exps[0], LEFT) self.play( FadeOut(exps[0]), FadeIn(zeta), ) self.wait() self.play( FadeOut(zeta), FadeIn(exps[0]), ) self.wait() # Other bases n_mob_groups = VGroup() for n in ["2", "5", "\\pi", "1{,}729"]: n_mobs = VGroup() for e_mob in e_mobs: n_mob = OldTex(n) n_mob.match_height(e_mob) n_mob.scale(1.2) n_mob.move_to(e_mob, UR) n_mob.shift(0.025 * DL) n_mobs.add(n_mob) n_mob_groups.add(n_mobs) for color, group in zip([YELLOW, GREEN_B, RED, MAROON_B], n_mob_groups): group.set_color(color) last_group = e_mobs for group in [*n_mob_groups, e_mobs]: self.play( LaggedStartMap(FadeOut, last_group, lag_ratio=0.2), LaggedStartMap(FadeIn, group, lag_ratio=0.2), run_time=1.5, ) self.wait() last_group = group # Show which equations are solved. equations = VGroup( OldTex("\\frac{dz}{dt}(t) = i \\cdot z(t)"), get_2d_equation([["3", "1"], ["4", "1"]]), OldTex("\\frac{dq}{dt}(t) = \\frac{i + j + k}{ \\sqrt{3} } \\cdot q(t)"), OldTex("{\\partial \\over \\partial t}f(x, t) = {\\partial \\over \\partial x}f(x, t)"), ) for eq, exp in zip(equations, exps): eq.match_height(exp) eq.move_to(exp, DL) eq.align_to(equations[0], LEFT) equations.to_edge(RIGHT) self.play( FadeOut(arrow, 3 * LEFT), FadeOut(exotic_label, 3 * LEFT), VGroup(labels, exps).animate.to_edge(LEFT), LaggedStartMap(FadeIn, equations, run_time=3, lag_ratio=0.5) ) self.wait() self.play( VGroup( labels[0], labels[2:], exps[0], exps[2:], equations[0], equations[2:] ).animate.set_opacity(0.3), equations[1].animate.scale(1.5, about_edge=RIGHT), exps[1].animate.scale(1.5), labels[1].animate.scale(1.5, about_edge=LEFT), ) self.wait() class TryToDefineExp(TeacherStudentsScene): CONFIG = { "background_color": BLACK, } def construct(self): words = OldTexText( "Try to define {{$e^{M}$}} to\\\\make sure this is true!", ) words[1][1].set_color(TEAL) self.play( PiCreatureSays(self.teacher, words, target_mode="surprised"), self.change_students("pondering", "thinking", "pondering"), ) for pi in self.students: for eye in pi.eyes: eye.refresh_bounding_box() self.look_at(4 * UP + 2 * RIGHT) self.wait(6) class SolutionsToMatrixEquation(From2DTo1D): show_solution = True class SolutionToRomeoJuliet(Scene): def construct(self): mat_exp = get_matrix_exponential( [["0", "-1"], ["1", "0"]], h_buff=1.0, ) solution = VGroup( Matrix( [["x(t)"], ["y(t)"]], bracket_h_buff=SMALL_BUFF, bracket_v_buff=SMALL_BUFF, ), OldTex("="), mat_exp, Matrix( [["x(0)"], ["y(0)"]], bracket_h_buff=SMALL_BUFF, bracket_v_buff=SMALL_BUFF, ) ) solution[2].match_height(solution[0]) solution[3].match_height(solution[0]) solution.arrange(RIGHT) self.add(solution) self.wait() rect = SurroundingRectangle(mat_exp, buff=0.1) rect_copy = rect.copy() self.play(ShowCreation(rect)) self.wait() self.play(rect.animate.become(SurroundingRectangle(solution[-1], color=BLUE))) self.wait() rot_question = Text("Rotation?") rot_question.next_to(rect_copy, DOWN) rot_question.set_color(YELLOW) self.play( rect.animate.become(rect_copy), Write(rot_question) ) self.wait() class RotMatrixStill(Scene): def construct(self): mat = IntegerMatrix([[0, -1], [1, 0]]) br = BackgroundRectangle(mat, buff=SMALL_BUFF) br.set_fill(BLACK, 1) self.add(br, mat) class ExpRotMatrixComputation(Scene): def construct(self): # Plug Mt into series mat_exp = get_matrix_exponential([[0, -1], [1, 0]]) mat_exp.to_corner(UL) mat_exp[1].set_color(TEAL) equation = OldTex( "e^X", ":=", "X^0 + X^1 + \\frac{1}{2} X^2 + \\frac{1}{6} X^3 + \\cdots + \\frac{1}{n!} X^n + \\cdots", isolate=["X", "+"], ) equation.set_width(FRAME_WIDTH - 1) equation.to_edge(UP) self.add(equation) rhs_tex = "{t}^0 X^0 + {t}^1 X^1 + \\frac{1}{2} {t}^2 X^2 + \\frac{1}{6} {t}^3 X^3 + \\cdots + \\frac{1}{n!} {t}^n X^n + \\cdots" mat_tex = "\\left[ \\begin{array}{cc} 0 & -1 \\\\ 1 & 0 \\end{array} \\right]" mat_rhs = OldTex( rhs_tex.replace("X", mat_tex), tex_to_color_map={mat_tex: TEAL}, isolate=["{t}", "+"], ) mat_rhs.scale(0.5) mat_equals = OldTex("=") mat_exp.match_height(mat_rhs) mat_equation = VGroup(mat_exp, mat_equals, mat_rhs) mat_equation.arrange(RIGHT) mat_exp.align_to(mat_rhs, DOWN) mat_equation.set_width(FRAME_WIDTH - 1) mat_equation.next_to(equation, DOWN, LARGE_BUFF) self.wait() self.play(LaggedStart( FadeTransform(equation[:2].copy(), mat_equation[0]), FadeTransform(equation[2].copy(), mat_equation[1]), *( FadeTransform(equation[i].copy(), mat_equation[2][j]) for i, j in zip( # Christ... [3, 4, 3, 4, 5, 6, 7, 6, 7, 8, 9, 10, 11, 10, 11, 12, 13, 14, 15, 14, 15, 16, 17, 18, 19, 20, 21, 20, 21, 22, 23], it.count() ) ), run_time=2, lag_ratio=0.02 )) self.wait() # Show first few powers new_eq = mat_equation.copy() new_eq.shift(1.5 * DOWN) kw = {"bracket_h_buff": 0.25} m0 = IntegerMatrix([[1, 0], [0, 1]], **kw) m1 = IntegerMatrix([[0, -1], [1, 0]], **kw) m2 = IntegerMatrix([[-1, 0], [0, -1]], **kw) m3 = IntegerMatrix([[0, 1], [-1, 0]], **kw) mat_powers = VGroup(m0, m1, m2, m3) mat_powers.set_submobject_colors_by_gradient(BLUE, GREEN, RED) indices = [2, 7, 13, 19] for mat_power, index in zip(mat_powers, indices): mat_power.replace(new_eq[2][index], dim_to_match=0) mat_power.source = mat_equation[2][index:index + 2] new_eq[2][index: index + 2].set_opacity(0) self.play(LaggedStart( TransformFromCopy(mat_equation[0], new_eq[0]), TransformFromCopy(mat_equation[1], new_eq[1]), *( TransformFromCopy(mat_equation[2][i], new_eq[2][i]) for i in range(21) if i not in indices and i - 1 not in indices ), lag_ratio=0.01 )) rect = SurroundingRectangle(mat_powers[0].source, buff=0.05) rect.set_stroke(width=0) for mat_power in mat_powers: self.play(rect.animate.become(SurroundingRectangle(mat_power.source, buff=0.05)), run_time=0.5) self.play(FadeTransform(mat_power.source.copy(), mat_power)) self.wait(0.5) self.wait() self.play(rect.animate.move_to(mat_equation[2][27:29])) # Show cycling pattern rows = VGroup() for i in range(2): row = VGroup() for j in range(4): mat_power_copy = mat_powers[j].deepcopy() power = str(4 * (i + 1) + j) coef = OldTex("+\\frac{1}{" + power + "!} t^{" + power + "}") coef.match_height(mat_equation[2][10]) coef.next_to(mat_power_copy, LEFT, SMALL_BUFF) row.add(coef, mat_power_copy) row.shift(1.1 * (i + 1) * DOWN) rows.add(row) dots = OldTex("+ \\cdots", font_size=24) dots.next_to(rows, DOWN, aligned_edge=LEFT) for row in rows: for coef, mp in zip(row[0::2], row[1::2]): new_rect = SurroundingRectangle(mp, buff=0.05) self.add(coef, mp, new_rect) self.wait(0.5) self.remove(new_rect) self.play(Write(dots)) self.wait() # Setup for new rhs lhs = mat_equation[:2] self.play( lhs.animate.scale(2).to_corner(UL).shift(DOWN), FadeOut(VGroup(mat_equation[2], rect, equation)) ) # Show massive rhs rhs = Matrix( [ [ "1 - \\frac{t^2}{2!} + \\frac{t^4}{4!} - \\frac{t^6}{6!} + \\cdots", "-t + \\frac{t^3}{3!} - \\frac{t^5}{5!} + \\frac{t^7}{7!} - \\cdots", ], [ "t - \\frac{t^3}{3!} + \\frac{t^5}{5!} - \\frac{t^7}{7!} + \\cdots", "1 - \\frac{t^2}{2!} + \\frac{t^4}{4!} - \\frac{t^6}{6!} + \\cdots", ], ], h_buff=6, v_buff=2, ) rhs.set_width(10) rhs.next_to(lhs, RIGHT) power_entry_rects = VGroup(*(VGroup() for x in range(4))) for group in [mat_powers, rows[0][1::2], rows[1][1::2]]: for mat_power in group: for i, entry in enumerate(mat_power.get_entries()): rect = SurroundingRectangle(entry, buff=0.05) rect.set_stroke(YELLOW, 2) power_entry_rects[i].add(rect) self.play(Write(rhs.get_brackets())) last_per = power_entry_rects[0].copy() last_per.set_opacity(0) last_rect = VMobject() for entry, per in zip(rhs.get_entries(), power_entry_rects): rect = VGroup(SurroundingRectangle(entry, stroke_width=1)) self.play( ReplacementTransform(last_per, per), FadeIn(entry), FadeIn(rect), FadeOut(last_rect), ) self.wait() last_per = per last_rect = rect self.play(FadeOut(last_per), FadeOut(last_rect)) self.wait() # Show collapse to trig functions low_terms = VGroup(new_eq[2][:21], mat_powers, rows, dots) new_lhs = lhs.copy() new_lhs.next_to(rhs, DOWN, LARGE_BUFF) new_lhs.align_to(lhs, LEFT) final_result = Matrix( [["\\cos(t)", "-\\sin(t)"], ["\\sin(t)", "\\cos(t)"]], h_buff=2.0 ) final_result.next_to(new_lhs, RIGHT) anims = [] colors = [BLUE_B, BLUE_D, BLUE_D, BLUE_B] for color, entry1, entry2 in zip(colors, rhs.get_entries(), final_result.get_entries()): anims.append(entry1.animate.set_color(color)) entry2.set_color(color) self.play( *anims, FadeOut(low_terms, shift=2 * DOWN), ReplacementTransform(new_eq[:2], new_lhs), TransformFromCopy(rhs.get_brackets(), final_result.get_brackets()), ) self.wait() last_rects = VGroup() for entry1, entry2 in zip(rhs.get_entries(), final_result.get_entries()): rect1 = SurroundingRectangle(entry1, stroke_width=1) rect2 = SurroundingRectangle(entry2, stroke_width=1) self.play( FadeIn(rect1), FadeIn(rect2), FadeIn(entry2), FadeOut(last_rects), ) last_rects = VGroup(rect1, rect2) self.play(FadeOut(last_rects)) self.wait() # Ask question question = OldTexText("What transformation\\\\is this?") question.next_to(final_result, RIGHT, buff=1.5) question.shift_onto_screen() arrow = Arrow(question, final_result, buff=SMALL_BUFF) self.play( FadeIn(question, shift=0.5 * RIGHT), GrowArrow(arrow) ) self.wait() # Highlight result high_eq = VGroup(lhs, rhs) low_eq = VGroup(new_lhs, final_result) self.play(LaggedStart( FadeOut(high_eq, UP), FadeOut(VGroup(question, arrow)), low_eq.animate.center().to_edge(UP), lag_ratio=0.05 )) self.wait() class ThatsHorrifying(TeacherStudentsScene): def construct(self): self.student_says( OldTexText("You want us to\\\\do what?"), index=2, target_mode="pleading", added_anims=[LaggedStart( self.students[0].change("tired"), self.students[1].change("horrified"), self.teacher.change("guilty"), lag_ratio=0.5 )] ) for pi in self.pi_creatures: for eye in pi.eyes: # Why? eye.refresh_bounding_box() self.look_at(self.screen) self.wait(2) self.teacher_says( "Just wait", bubble_config={"height": 3, "width": 3.5}, target_mode="tease" ) self.play_student_changes( "pondering", "thinking", "hesitant", look_at=self.screen, ) self.wait(3) class LinearAlgebraWrapper(VideoWrapper): title = "Matrices as linear transformations" class HowBasisVectorMultiplicationPullsOutColumns(Scene): def construct(self): # Setup plane = NumberPlane() plane.scale(2.5) plane.shift(1.5 * DOWN) b_plane = plane.copy() b_plane.set_color(GREY_B) plane.add_coordinate_labels() self.add(b_plane, plane) matrix = Matrix( [["a", "b"], ["c", "d"]], h_buff=0.8, ) matrix.to_corner(UL) matrix.to_edge(LEFT, buff=MED_SMALL_BUFF) matrix.add_to_back(BackgroundRectangle(matrix)) self.add(matrix) basis_vectors = VGroup( Arrow(plane.get_origin(), plane.c2p(1, 0), buff=0, fill_color=GREEN), Arrow(plane.get_origin(), plane.c2p(0, 1), buff=0, fill_color=RED), ) bhb = 0.2 basis_labels = VGroup( Matrix([["1"], ["0"]], bracket_h_buff=bhb), Matrix([["0"], ["1"]], bracket_h_buff=bhb), ) for vector, label, direction in zip(basis_vectors, basis_labels, [UR, RIGHT]): label.scale(0.7) label.match_color(vector) label.add_to_back(BackgroundRectangle(label)) label.next_to(vector.get_end(), direction) # Show products basis_label_copies = basis_labels.deepcopy() rhss = VGroup( Matrix([["a"], ["c"]], bracket_h_buff=bhb), Matrix([["b"], ["d"]], bracket_h_buff=bhb), ) colors = [GREEN, RED] def show_basis_product(index, matrix): basis_label_copies[index].match_height(matrix) basis_label_copies[index].next_to(matrix, RIGHT, SMALL_BUFF), equals = OldTex("=") equals.next_to(basis_label_copies[index], RIGHT, SMALL_BUFF) rhss[index].next_to(equals, RIGHT, SMALL_BUFF) rhss[index].set_color(colors[index]) rhs_br = BackgroundRectangle(rhss[index]) self.play( FadeIn(basis_labels[index], RIGHT), GrowArrow(basis_vectors[index]), FadeIn(basis_label_copies[index]), FadeIn(equals), FadeIn(rhs_br), FadeIn(rhss[index].get_brackets()), ) rect_kw = {"stroke_width": 2, "buff": 0.1} row_rects = [ SurroundingRectangle(row, **rect_kw) for row in matrix.get_rows() ] col_rect = SurroundingRectangle(basis_label_copies[index].get_entries(), **rect_kw) col_rect.set_stroke(opacity=0) last_row_rect = VMobject() for e1, e2, row_rect in zip(matrix.get_columns()[index], rhss[index].get_entries(), row_rects): self.play( col_rect.animate.set_stroke(opacity=1), FadeIn(row_rect), FadeOut(last_row_rect), e1.animate.set_color(colors[index]), FadeIn(e2), ) last_row_rect = row_rect self.play(FadeOut(last_row_rect), FadeOut(col_rect)) rhss[index].add_to_back(rhs_br) rhss[index].add(equals) low_matrix = matrix.deepcopy() show_basis_product(0, matrix) self.wait() self.play(low_matrix.animate.shift(2.5 * DOWN)) show_basis_product(1, low_matrix) self.wait() class ColumnsToBasisVectors(ExternallyAnimatedScene): pass class ReadColumnsOfRotationMatrix(Scene): show_exponent = False def construct(self): # Setup plane = NumberPlane(faded_line_ratio=0) plane.scale(3.0) plane.shift(1.5 * DOWN) b_plane = plane.copy() b_plane.set_color(GREY_B) b_plane.set_stroke(opacity=0.5) angle = 50 * DEGREES plane2 = plane.copy().apply_matrix([ [math.cos(angle), 0], [math.sin(angle), 1], ], about_point=plane.get_origin()) plane3 = plane.copy().apply_matrix([ [math.cos(angle), -math.sin(angle)], [math.sin(angle), math.cos(angle)], ], about_point=plane.get_origin()) coords = plane.deepcopy().add_coordinate_labels((-2, -1, 1, 2), (-1, 1)) self.add(b_plane, plane, coords) equation = VGroup( get_matrix_exponential([[0, -1], [1, 0]]), OldTex("="), Matrix( [["\\cos(t)", "-\\sin(t)"], ["\\sin(t)", "\\cos(t)"]], h_buff=2.0 ) ) exp, eq, matrix = equation exp[1].set_color(TEAL) exp[1].add_background_rectangle() matrix.add_background_rectangle() equation.arrange(RIGHT) equation.set_width(6) equation.to_corner(UL) if not self.show_exponent: equation.remove(*equation[:2]) equation.set_height(1.5) equation.to_corner(UL) self.add(equation) basis_vectors = VGroup( Arrow(plane.get_origin(), plane.c2p(1, 0), buff=0, fill_color=GREEN), Arrow(plane.get_origin(), plane.c2p(0, 1), buff=0, fill_color=RED), ) basis_shadows = basis_vectors.copy() basis_shadows.set_fill(opacity=0.5) self.add(basis_shadows, basis_vectors) self.play(FlashAround(matrix.get_columns()[0], color=GREEN)) self.wait() # Show action on basis vectors rot_b0, rot_b1 = rot_basis_vectors = basis_vectors.copy() for rot_b in rot_basis_vectors: rot_b.rotate(angle, about_point=plane.get_origin()) rbl0, rbl1 = rot_basis_labels = VGroup( Matrix([["\\cos(t)"], ["\\sin(t)"]]), Matrix([["-\\sin(t)"], ["\\cos(t)"]]), ) for label, color, rot_b, direction in zip(rot_basis_labels, [GREEN, RED], rot_basis_vectors, [UR, LEFT]): label.set_color(color) label.scale(0.7) label.next_to(rot_b.get_end(), direction, SMALL_BUFF) label.add_background_rectangle() arcs = VGroup( Arc(0, angle, arc_center=plane.get_origin(), radius=0.5), Arc(PI / 2, angle, arc_center=plane.get_origin(), radius=0.5), ) arcs.set_stroke(WHITE, 2) arc_label = OldTexText("$t$", " ") arc_label.next_to(arcs[0], RIGHT, SMALL_BUFF) arc_label.shift(SMALL_BUFF * UP) h_line = DashedLine(plane.get_origin(), plane.c2p(math.cos(angle), 0)) v_line = DashedLine(plane.c2p(math.cos(angle), 0), plane.c2p(math.cos(angle), math.sin(angle))) cos_label = matrix.get_entries()[0].copy().next_to(h_line, DOWN, SMALL_BUFF) sin_label = matrix.get_entries()[2].copy().next_to(v_line, RIGHT, SMALL_BUFF) self.play( TransformFromCopy(matrix[0], rbl0[0]), # Background rectangles TransformFromCopy(matrix.get_brackets(), rbl0.get_brackets()), TransformFromCopy(matrix.get_columns()[0], rbl0.get_entries()), Transform(plane, plane2, path_arc=angle), Transform(basis_vectors[0], rot_b0, path_arc=angle), Animation(matrix.get_columns()[1]), run_time=2, ) rects = VGroup(*( SurroundingRectangle(entry, color=WHITE, stroke_width=2, buff=0.05) for entry in rbl0.get_entries() )) self.play( ShowCreation(h_line), FadeIn(rects[0]), FadeIn(cos_label), ) self.play( ShowCreation(v_line), FadeOut(rects[0]), FadeIn(rects[1]), FadeIn(sin_label), ) self.play(FadeOut(rects[1])) self.wait() self.play(FlashAround(matrix.get_columns()[1], color=RED)) self.play( TransformFromCopy(matrix[0], rbl1[0]), # Background rectangles TransformFromCopy(matrix.get_brackets(), rbl1.get_brackets()), TransformFromCopy(matrix.get_columns()[1], rbl1.get_entries()), Transform(plane, plane3, path_arc=angle), Transform(basis_vectors[1], rot_b1, path_arc=angle), Animation(matrix.get_columns()[0]), run_time=2, ) self.wait() self.play( FadeIn(arc_label), *map(ShowCreation, arcs), ) self.wait() class ReadColumnsOfRotationMatrixWithExp(ReadColumnsOfRotationMatrix): show_exponent = True class AnalyzeRomeoAndJulietSpace(RomeoJulietVectorSpace): def construct(self): # Add axes axes = self.get_romeo_juliet_axes() ps_dot = axes.ps_dot ps_dot.set_opacity(0) ps_dot.move_to(axes.c2p(4, 3)) ps_arrow = self.get_ps_arrow(axes, add_shadow=True) ps_arrow.update() self.add(ps_arrow) # Add equation matrix = [["0", "-1"], ["1", "0"]] equation = get_2d_equation(matrix) equation.to_corner(UR) implies = OldTex("\\Downarrow", font_size=72) implies.next_to(equation, DOWN, buff=0.3) initial_condition = Matrix([["x_0"], ["y_0"]], bracket_h_buff=0.1) initial_condition.match_height(equation) initial_condition.set_color(BLUE_B) ic_source = initial_condition.copy() ic_source.scale(0.5) ic_source.next_to(ps_arrow.get_end(), RIGHT, SMALL_BUFF) mat_exp = get_matrix_exponential(matrix, h_buff=0.8) solution = VGroup(equation[1].copy(), OldTex("="), mat_exp, initial_condition) solution[2][1].set_color(TEAL) solution.arrange(RIGHT) solution.scale(0.8) solution.next_to(implies, DOWN, buff=0.3) rot_matrix = Matrix( [["\\cos(t)", "-\\sin(t)"], ["\\sin(t)", "\\cos(t)"]], bracket_h_buff=0.2, h_buff=2.0 ) for entry in rot_matrix.get_entries(): entry[0][-2].set_color(GREY_B) rot_matrix.match_height(solution[0]) solution2 = solution.deepcopy() solution2.replace_submobject(2, rot_matrix) solution2.arrange(RIGHT) solution2.next_to(solution, DOWN, buff=0.7) rot_matrix_brace = Brace(rot_matrix, DOWN) rot_label = OldTexText("Rotate by angle $t$") rot_label.next_to(rot_matrix_brace, DOWN, SMALL_BUFF) self.add(equation) self.play(FlashAround(equation, run_time=2)) self.wait() self.play( Write(implies), TransformFromCopy(equation[1], solution[0], path_arc=-20 * DEGREES), TransformFromCopy(equation[2], solution[1]), ) self.play( Write(mat_exp[0]), TransformFromCopy(equation[3], mat_exp[1]), ) self.play(Write(mat_exp[2])) self.wait() self.play( FadeIn(ic_source), FlashAround(ic_source), ) self.play(TransformFromCopy(ic_source, initial_condition)) self.wait() self.play( *( TransformFromCopy(solution[i], solution2[i]) for i in [0, 1, 3] ), FadeTransform(mat_exp.copy(), rot_matrix) ) self.play( GrowFromCenter(rot_matrix_brace), FadeIn(rot_label), ) self.wait() # Rotate vector start_angle = ps_arrow.get_angle() t_tracker = ValueTracker(0) get_t = t_tracker.get_value def get_arc(): curr_time = get_t() arc = ParametricCurve( lambda s: axes.c2p(*tuple((1 + 0.2 * s) * np.array([ math.cos(start_angle + s * curr_time), math.sin(start_angle + s * curr_time), ]))), t_range=(0, 1, 0.01) ) arc.set_stroke(WHITE, 2) return arc arc = always_redraw(get_arc) t_label = VGroup(OldTex("t = ", font_size=30), DecimalNumber(font_size=24)) t_label.arrange(RIGHT, buff=SMALL_BUFF) def update_t_label(label): point = arc.pfp(0.5) label[1].set_value(get_t()) label.set_stroke(BLACK, 5, background=True) label.set_opacity(min(2 * get_t(), 1)) if get_t() < 3.65: target_point = point + 0.75 * (point - axes.get_origin()) label.shift(target_point - label[1].get_center()) t_label.add_updater(update_t_label) curr_xy = np.array(axes.p2c(ps_dot.get_center())) def update_ps_dot(dot): rot_M = np.array(rotation_matrix_transpose(get_t(), OUT))[:2, :2] new_xy = np.dot(curr_xy, rot_M) dot.move_to(axes.c2p(*new_xy)) ps_dot.add_updater(update_ps_dot) t_tracker.add_updater(lambda m, dt: m.increment_value(0.5 * dt)) self.add(arc, t_label, t_tracker, ps_dot) self.play(VFadeIn(t_label)) self.wait(16 * PI - 1) def get_romeo_juliet_axes(self, height=5): # Largely copied from RomeoJulietVectorSpace romeo, juliet = lovers = self.get_romeo_and_juliet() axes = Axes( x_range=(-5, 5), y_range=(-5, 5), height=height, width=height, axis_config={ "include_tip": False, "numbers_to_exclude": [], } ) axes.to_edge(LEFT) for axis in axes: axis.add_numbers(range(-4, 6, 2), color=GREY_A, font_size=12) axis.numbers[2].set_opacity(0) lovers.set_height(0.5) lovers.flip() juliet.next_to(axes.x_axis.get_end(), RIGHT) romeo.next_to(axes.y_axis.get_end(), UP) ps_dot = Dot(radius=0.04, fill_color=BLUE) ps_dot.move_to(axes.c2p(3, 1)) def get_xy(): # Get phase space point return axes.p2c(ps_dot.get_center()) self.make_romeo_and_juliet_dynamic(romeo, juliet) juliet.love_tracker.add_updater(lambda m: m.set_value(get_xy()[0])) romeo.love_tracker.add_updater(lambda m: m.set_value(get_xy()[1])) self.add(romeo.love_tracker, juliet.love_tracker) name_labels = self.get_romeo_juilet_name_labels(lovers, font_size=12, spacing=1.0, buff=0.05) axes.lovers = lovers axes.name_labels = name_labels axes.ps_dot = ps_dot axes.add(lovers, name_labels, ps_dot) self.add(axes) self.add(*lovers) self.add(*(pi.heart_eyes for pi in lovers)) return axes def get_ps_arrow(self, axes, color=BLUE, add_shadow=False): arrow = Arrow( LEFT, RIGHT, fill_color=color, thickness=0.04 ) arrow.add_updater(lambda m: m.put_start_and_end_on( axes.get_origin(), axes.ps_dot.get_center(), )) if add_shadow: ps_arrow_shadow = arrow.copy() ps_arrow_shadow.clear_updaters() ps_arrow_shadow.set_fill(BLUE, 0.5) self.add(ps_arrow_shadow) return arrow class GeometricReasoningForRomeoJuliet(AnalyzeRomeoAndJulietSpace): def construct(self): # Setup axes = self.get_romeo_juliet_axes() ps_dot = axes.ps_dot ps_dot.set_opacity(0) ps_dot.move_to(axes.c2p(4, 3)) ps_arrow = self.get_ps_arrow(axes) self.add(ps_arrow) matrix = [["0", "-1"], ["1", "0"]] equation = get_2d_equation(matrix) ddt, xy1, eq, mat, xy2 = equation equation.to_corner(UR) self.add(equation) # Show 90 degree rotation mat_brace = Brace(mat, DOWN, buff=SMALL_BUFF) mat_label = mat_brace.get_text("90-degree rotation matrix", font_size=30) mat_label.set_color(TEAL) self.play( GrowFromCenter(mat_brace), Write(mat_label, run_time=1) ) self.wait() # Spiral around to various points curve = VMobject() curve.set_points_smoothly( [ axes.c2p(x, y) for x, y in [(4, 3), (0, 2), (0, -1), (-3, -4), (-3, 2), (3, 1)] ], true_smooth=True ) self.play(MoveAlongPath(ps_dot, curve, run_time=5)) self.wait() # Rate of change deriv_vect = ps_arrow.copy() deriv_vect.clear_updaters() deriv_vect.set_color(RED) deriv_vect.shift(ps_arrow.get_vector()) deriv_rect = SurroundingRectangle(VGroup(ddt, xy1)) deriv_rect.set_stroke(RED, 2) d_line = DashedLine(ps_arrow.get_start(), ps_arrow.get_end()) d_line.shift(ps_arrow.get_vector()) d_line.set_stroke(WHITE, 1) arc = Arc( start_angle=ps_arrow.get_angle(), angle=90 * DEGREES, arc_center=ps_arrow.get_end(), radius=0.25 ) elbow = VMobject() elbow.set_points_as_corners([RIGHT, UR, UP]) elbow.scale(0.25, about_point=ORIGIN) elbow.rotate(ps_arrow.get_angle(), about_point=ORIGIN) elbow.shift(ps_arrow.get_end()) VGroup(elbow, arc).set_stroke(WHITE, 2) self.play( TransformFromCopy(ps_arrow, deriv_vect, path_arc=30 * DEGREES), ShowCreation(deriv_rect), ) self.add(d_line, deriv_vect) self.play( ShowCreation(arc), Rotate(deriv_vect, 90 * DEGREES, about_point=ps_arrow.get_end()), run_time=2, rate_func=rush_into, ) self.remove(arc) self.add(elbow) self.wait() # Show rotation ps_arrow.clear_updaters() ps_dot.add_updater(lambda m: m.move_to(ps_arrow.get_end())) rot_group = VGroup(ps_arrow, d_line, elbow, deriv_vect) circle = Circle(radius=ps_arrow.get_length()) circle.set_stroke(GREY, 1) circle.move_to(axes.get_origin()) self.play( Rotate( rot_group, angle=TAU - ps_arrow.get_angle(), about_point=axes.get_origin(), run_time=6, rate_func=linear, ), FadeIn(circle), ) self.wait() # Show matching length self.play( deriv_vect.animate.put_start_and_end_on(ps_arrow.get_start(), ps_arrow.get_end()).shift(0.2 * UP), run_time=3, rate_func=there_and_back_with_pause, ) self.wait() # Show one radian of arc arc = Arc( 0, 1, radius=circle.radius, arc_center=axes.get_origin(), ) arc.set_stroke(YELLOW, 3) sector = Sector( start_angle=0, angle=1, outer_radius=circle.radius, ) sector.shift(axes.get_origin()) sector.set_stroke(width=0) sector.set_fill(GREY_D, 1) t_label = VGroup(OldTex("t = ", font_size=30), DecimalNumber(0, font_size=20)) t_label.arrange(RIGHT, buff=SMALL_BUFF) t_label.next_to(arc.pfp(0.5), UR, SMALL_BUFF) self.play( Rotate(rot_group, 1, about_point=axes.get_origin()), ShowCreation(arc), ChangeDecimalToValue(t_label[1], 1.0), VFadeIn(t_label), run_time=2, rate_func=linear, ) self.add(sector, axes, ps_arrow, arc) self.play(FadeIn(sector)) self.wait() radius = Line(axes.get_origin(), arc.get_start()) radius.set_stroke(RED, 5) self.play(ShowCreation(radius)) self.play(Rotate(radius, -PI / 2, about_point=radius.get_end())) radius.rotate(PI) self.play(radius.animate.match_points(arc)) self.play(FadeOut(radius)) self.wait() # One radian per unit time self.play( t_label.animate.shift(UP).scale(2), ) t_label[1].data["font_size"] *= 2 for x in range(5): VGroup(sector, arc).rotate(1, about_point=axes.get_origin()) self.play( Rotate(rot_group, 1, about_point=axes.get_origin()), ChangeDecimalToValue(t_label[1], t_label[1].get_value() + 1), run_time=2, rate_func=linear, ) self.play( FadeOut(VGroup(sector, arc)), Rotate(rot_group, TAU - 6, about_point=axes.get_origin()), ChangeDecimalToValue(t_label[1], TAU), run_time=2 * (TAU - 6), rate_func=linear, ) self.wait(2) t_label[1].set_value(0) self.play( Rotate(rot_group, PI, about_point=axes.get_origin()), ChangeDecimalToValue(t_label[1], PI), run_time=2 * PI, rate_func=linear, ) self.wait(2) class Show90DegreeRotation(Scene): def construct(self): plane = NumberPlane() plane.scale(2.5) back_plane = plane.deepcopy() back_plane.set_stroke(color=GREY, opacity=0.5) back_plane.add_coordinate_labels() vects = VGroup( Arrow(plane.get_origin(), plane.c2p(1, 0), fill_color=GREEN, buff=0, thickness=0.075), Arrow(plane.get_origin(), plane.c2p(0, 1), fill_color=RED, buff=0, thickness=0.075), ) plane.add(*vects) plane.set_stroke(background=True) self.add(back_plane, plane) self.wait() self.play(Rotate(plane, 90 * DEGREES, run_time=3)) self.wait() class Show90DegreeRotationColumnByColumn(Scene): def construct(self): plane = NumberPlane() plane.scale(2.5) back_plane = plane.deepcopy() back_plane.set_stroke(color=GREY, opacity=0.5) back_plane.add_coordinate_labels() plane2 = plane.copy() plane3 = plane.copy() plane2.apply_matrix([[0, 0], [1, 1]]) plane3.apply_matrix([[0, -1], [1, 0]]) vects = VGroup( Arrow(plane.get_origin(), plane.c2p(1, 0), fill_color=GREEN, buff=0, thickness=0.075), Arrow(plane.get_origin(), plane.c2p(0, 1), fill_color=RED, buff=0, thickness=0.075), ) self.add(back_plane, plane, *vects) self.wait() self.play( Transform(plane, plane2, path_arc=90 * DEGREES), Rotate(vects[0], 90 * DEGREES, about_point=plane.get_origin()), run_time=2 ) self.wait() self.play( Transform(plane, plane3, path_arc=90 * DEGREES), Rotate(vects[1], 90 * DEGREES, about_point=plane.get_origin()), run_time=2 ) self.wait() arc = Arc(5 * DEGREES, 80 * DEGREES, buff=0.1, radius=1.5) arc.set_stroke(width=3) arc.add_tip(width=0.15, length=0.15) arc2 = arc.copy().rotate(PI, about_point=ORIGIN) arcs = VGroup(arc, arc2) arcs.set_color(GREY_B) self.play(*map(ShowCreation, arcs)) self.wait() class DistanceOverTimeEquation(Scene): def construct(self): equation = OldTex( "{\\text{Distance}", " \\over", " \\text{Time} }", "=", "\\text{Radius}", ) # equation[:3].set_color(RED_B) equation.add(SurroundingRectangle(equation[:3], color=RED, stroke_width=1)) equation[4].set_color(BLUE) self.play(FadeIn(equation, UP)) self.wait() class ExplicitSolution(Scene): def construct(self): kw = {"tex_to_color_map": { "x_0": BLUE_D, "y_0": BLUE_B, "{t}": GREY_B, }} solutions = VGroup( OldTex("x({t}) = \\cos(t) x_0 - \\sin(t) y_0", **kw), OldTex("y({t}) = \\sin(t) x_0 + \\cos(t) y_0", **kw), ) solutions.arrange(DOWN, buff=MED_LARGE_BUFF) for solution in solutions: self.play(Write(solution)) self.wait() class TwoDifferetViewsWrapper(Scene): def construct(self): self.add(FullScreenRectangle()) screens = VGroup(*(ScreenRectangle() for x in range(2))) screens.arrange(RIGHT) screens.set_width(FRAME_WIDTH - 1) screens.set_fill(BLACK, 1) screens[0].set_stroke(BLUE, 2) screens[1].set_stroke(GREY_BROWN, 2) self.add(screens) titles = VGroup( Text("Geometric"), Text("Analytic"), ) for title, screen in zip(titles, screens): title.next_to(screen, UP) title.align_to(titles[0], UP) self.play(Write(title)) self.wait() self.wait() class EulersFormulaWrapper(VideoWrapper): title = "Video on $e^{it}$" class ImaginaryExponent(ExternallyAnimatedScene): pass class ComplexEquation(Scene): def construct(self): # Equation equation = OldTex( """ \\frac{d}{d{t} } \\Big[ x(t) + {i}y(t)\\Big] = {i} \\cdot \\Big[ x(t) + {i}y(t) \\Big] """, tex_to_color_map={ "x": BLUE_B, "y": BLUE_D, "{t}": GREY_B, "=": WHITE, "\\cdot": WHITE, "{i}": RED, } ) equation.move_to(2 * UP) braces = VGroup(*( Brace(equation[i:j], UP, buff=SMALL_BUFF) for i, j in [(2, 8), (11, 17)] )) braces.set_fill(GREY_A, 1) for brace in braces: brace.zt = OldTex("z(t)", tex_to_color_map={"z": BLUE, "t": GREY_B}) brace.zt.next_to(brace, UP, SMALL_BUFF) self.add(equation) self.play( *(GrowFromCenter(b) for b in braces), *(FadeIn(b.zt, 0.25 * UP) for b in braces) ) self.wait() # Show romeo and juilet x_part = equation.get_part_by_tex("x") y_part = equation.get_part_by_tex("y") juliet_label = OldTexText("Juliet's love", font_size=30) juliet_label.next_to(x_part, DOWN, LARGE_BUFF) romeo_label = OldTexText("Romeo's love", font_size=30) romeo_label.next_to(y_part, DOWN, LARGE_BUFF) juliet_label.align_to(romeo_label, DOWN) VGroup(juliet_label, romeo_label).space_out_submobjects(1.5) juliet_arrow = Arrow(x_part, juliet_label, buff=0.1, fill_color=BLUE_B) romeo_arrow = Arrow(y_part, romeo_label, buff=0.1, fill_color=BLUE_D) self.play( GrowArrow(juliet_arrow), FadeIn(juliet_label), ) self.play( GrowArrow(romeo_arrow), FadeIn(romeo_label), ) self.wait() # Describe i i_part = equation.get_parts_by_tex("{i}")[1] i_label = OldTexText("90-degree rotation", font_size=30) i_label.set_color(RED) i_label.next_to(romeo_label, RIGHT, MED_LARGE_BUFF, aligned_edge=DOWN) i_arrow = Arrow(i_part, i_label, fill_color=RED) self.play( GrowArrow(i_arrow), FadeIn(i_label, DR), ) self.wait() all_labels = VGroup(romeo_label, juliet_label, i_label) # Show solution solution = OldTex( "z(t) = e^{it} z_0", tex_to_color_map={ "i": RED, "t": GREY_B, "z": BLUE, "=": WHITE, } ) solution.scale(1.5) solution.next_to(all_labels, DOWN, LARGE_BUFF) solution[8:].set_color(BLUE_D) self.play( TransformFromCopy(braces[0].zt, solution[:4]), Write(solution[4]), ) self.play( Write(solution[5]), TransformFromCopy(equation[9], solution[6]), Write(solution[7]), ) self.play( FadeIn(solution[8:], 0.1 * DOWN) ) self.wait() class JulietChidingRomeo(Scene): def construct(self): juliet = PiCreature(color=BLUE_B) romeo = PiCreature(color=BLUE_D) romeo.flip() pis = VGroup(juliet, romeo) pis.set_height(2) pis.arrange(RIGHT, buff=LARGE_BUFF) pis.to_edge(DOWN) romeo.make_eye_contact(juliet) self.add(pis) self.play( PiCreatureSays( juliet, OldTexText("It just seems like \\\\ your feelings aren't \\\\ real"), bubble_config={"height": 2.5, "width": 3.5}, target_mode="sassy", ), romeo.change("guilty"), ) for x in range(2): self.play(Blink(juliet)) self.play(Blink(romeo)) self.wait() class General90DegreeRotationExponents(Scene): def construct(self): # Setup exps = VGroup( get_matrix_exponential([["0", "-1"], ["1", "0"]]), OldTex("e^{it}", tex_to_color_map={"i": RED}), OldTex( "e^{(ai + bj + ck)t}", tex_to_color_map={ "i": RED, "j": GREEN, "k": BLUE, } ), OldTex( "e^{i \\sigma_x t}, \\quad ", "e^{i \\sigma_y t}, \\quad ", "e^{i \\sigma_z t}", tex_to_color_map={ "\\sigma_x": RED, "\\sigma_y": GREEN, "\\sigma_z": BLUE, } ), ) exps[0][1].set_color(TEAL) exps[0].scale(0.5) exps.arrange(DOWN, buff=1.5, aligned_edge=LEFT) labels = VGroup( OldTexText("$90^\\circ$ rotation matrix"), OldTexText("Imaginary numbers"), OldTexText("Quaternions"), OldTexText("Pauli matrices"), ) for label, exp in zip(labels, exps): label.scale(0.75) label.next_to(exp, LEFT, LARGE_BUFF, aligned_edge=DOWN) label.align_to(labels[0], LEFT) VGroup(exps, labels).to_edge(LEFT) quat_note = OldTex("(a^2 + b^2 + c^2 = 1)", font_size=24) quat_note.next_to(exps[2], DOWN, aligned_edge=LEFT) exps[2].add(quat_note) for exp, label in zip(exps, labels): self.play( FadeIn(exp), FadeIn(label), ) self.wait() class RotationIn3dPlane(Scene): def construct(self): # Axes and frame axes = ThreeDAxes() axes.set_stroke(width=2) axes.set_flat_stroke(False) frame = self.camera.frame frame.set_height(2.0 * FRAME_HEIGHT) frame.reorient(-40, 70) frame.add_updater(lambda f, dt: f.increment_theta(0.03 * dt)) self.add(axes, frame) # Plane plane = Surface() plane.replace(VGroup(axes.x_axis, axes.y_axis), stretch=True) plane.set_color(GREY_C, opacity=0.75) plane.set_gloss(1) grid = NumberPlane((-6, 6), (-5, 5), faded_line_ratio=0) radius = 3 p_vect = Vector(radius * RIGHT, fill_color=BLUE) v_vect = p_vect.copy() arc = Arc(0, PI / 2, radius=radius / 4) circle = Circle(radius=radius) circle.set_stroke(WHITE, 2) randy = Randolph(mode="pondering") randy.set_gloss(0.7) rot_group = Group(plane, grid, p_vect, v_vect, arc, circle, randy) normal = OUT for angle, axis in [(30 * DEGREES, RIGHT), (20 * DEGREES, UP)]: rot_group.rotate(angle, axis) normal = rotate_vector(normal, angle, axis) self.play( ShowCreation(plane), FadeIn(randy), ) self.play(GrowArrow(p_vect)) self.wait(2) self.play( Rotate(v_vect, PI / 2, axis=normal, about_point=axes.get_origin()), Rotate(randy, PI / 2, axis=normal, about_point=axes.get_origin()), UpdateFromAlphaFunc(v_vect, lambda m, a: m.set_fill(interpolate_color(BLUE, RED, a))), ShowCreation(arc), run_time=2, ) self.wait(3) self.play( v_vect.animate.shift(p_vect.get_end() - v_vect.get_start()), FadeOut(arc), FadeOut(randy), ) rot_group = VGroup(grid, p_vect, v_vect) rot_group.set_stroke(background=True) self.add(circle, rot_group) self.play( FadeIn(circle), Rotate( rot_group, 2 * TAU, axis=normal, about_point=axes.get_origin(), run_time=4 * TAU, rate_func=linear, ), VFadeIn(grid), ) class StoryForAnotherTime(TeacherStudentsScene): def construct(self): self.teacher_says( OldTexText("The full story\\\\takes more time."), bubble_config={"height": 3, "width": 3.5}, added_anims=[self.change_students("confused", "erm", "hesitant", look_at=self.screen)] ) self.wait(5) class SchrodingerSum(Scene): def construct(self): # Add title equation_label = VGroup( OldTexText("Schrödinger equation:"), OldTex( "{i} \\hbar \\frac{\\partial}{\\partial t} |\\psi(t)\\rangle = \\hat{H} |\\psi(t)\\rangle", tex_to_color_map={ "|\\psi(t)\\rangle": BLUE, "{i}": WHITE, "\\frac{\\partial}{\\partial t}": GREY_A, }, font_size=36 ) ) equation_label.arrange(RIGHT, buff=MED_LARGE_BUFF) equation_label.set_width(FRAME_WIDTH - 1) equation_label.to_edge(UP) i_part = equation_label[1].get_part_by_tex("{i}") self.add(equation_label) # Axes x_range = np.array([-5, 5, 1]) y_range = np.array([-1, 1, 0.5]) axes = ThreeDAxes( x_range=x_range, y_range=y_range, z_range=y_range, height=1.25, depth=1.25, width=4, axis_config={"include_tip": False, "tick_size": 0.05}, ) # plane = ComplexPlane(y_range, y_range) # plane.rotate(PI / 2, DOWN) # plane.match_depth(axes) # axes.add(plane) # axes.y_axis.set_opacity(0) # axes.z_axis.set_opacity(0) lil_axes = VGroup(*(axes.deepcopy() for x in range(3))) lil_axes.arrange(DOWN, buff=MED_LARGE_BUFF) lil_axes.to_corner(DL) brace = Brace(lil_axes, RIGHT, buff=MED_LARGE_BUFF) axes.scale(2) axes.next_to(brace, RIGHT) for ax in [axes, *lil_axes]: ax.rotate(-30 * DEGREES, UP) ax.rotate(10 * DEGREES, RIGHT) ax.set_flat_stroke(False) # Graphs def func0(x, t): magnitude = np.exp(-x * x / 2) phase = np.exp(complex(0, 0.5 * t)) return magnitude * phase def func1(x, t): magnitude = np.exp(-x * x / 2) * 2 * x magnitude *= 1 / math.sqrt(2) phase = np.exp(complex(0, 1.5 * t)) return magnitude * phase def func2(x, t): magnitude = -1 * np.exp(-x * x / 2) * (2 - 4 * x * x) magnitude *= 1 / math.sqrt(8) phase = np.exp(complex(0, 2.5 * t)) return magnitude * phase def comb_func(x, t): return (func0(x, t) + func1(x, t) + func2(x, t)) / 3 def to_xyz(func, x): z = func(x, get_t()) return (x, z.real, z.imag) t_tracker = ValueTracker() t_tracker.add_updater(lambda m, dt: m.increment_value(dt)) self.add(t_tracker) get_t = t_tracker.get_value def get_graph(axes, func, color): fade_tracker = ValueTracker(1) result = VGroup() graph = always_redraw(lambda: ParametricCurve( lambda x: axes.c2p(*to_xyz(func, x)), t_range=x_range[:2], color=color, stroke_opacity=fade_tracker.get_value(), flat_stroke=False, )) result.add(graph) for x in np.linspace(0, 1, 100): line = Line(stroke_color=color, stroke_width=2) line.x = x line.axes = axes line.graph = graph line.fade_tracker = fade_tracker line.add_updater(lambda m: m.set_points_as_corners([ m.axes.x_axis.pfp(m.x), m.graph.pfp(m.x), ]).set_opacity(0.5 * m.fade_tracker.get_value())) result.add(line) result.fade_tracker = fade_tracker return result graph0, graph1, graph2, comb_graph = graphs = [ get_graph(axes, func, color) for axes, func, color in zip( [*lil_axes, axes], [func0, func1, func2, comb_func], [BLUE, TEAL, GREEN, YELLOW] ) ] lil_axes[1].save_state() lil_axes[1].scale(2) lil_axes[1].move_to(DOWN) self.add(lil_axes[1], graph1) self.wait(10) self.add(*graphs) self.play( FadeIn(lil_axes[::2]), FadeIn(axes), Restore(lil_axes[1]), GrowFromCenter(brace), *( UpdateFromAlphaFunc(g.fade_tracker, lambda m, a: m.set_value(a)) for g in (*graphs[::2], comb_graph) ) ) self.wait(15) self.play( FlashAround(i_part, color=RED), i_part.animate.set_color(RED) ) self.wait(30) class BasicVectorFieldIdea(Scene): matrix = [[-1.5, -1], [3, 0.5]] def construct(self): # Equation v_tex = "\\vec{\\textbf{v} }(t)" equation = OldTex( "{d \\over dt}", v_tex, "=", "M", v_tex, tex_to_color_map={ "M": GREY_A, v_tex: YELLOW, } ) equation.set_height(1.5) equation.to_corner(UL, buff=0.25) background_rect = SurroundingRectangle(equation, buff=SMALL_BUFF) background_rect.set_fill(BLACK, opacity=0.9).set_stroke(WHITE, 2) # Plane and field matrix = np.array(self.matrix) def func(x, y): return 0.15 * np.dot(matrix.T, [x, y]) plane = NumberPlane() vector_field = VectorField( func, plane, magnitude_range=(0, 2), vector_config={"thickness": 0.025} ) dots = VGroup(*( Dot(v.get_start(), radius=0.02, fill_color=YELLOW) for v in vector_field )) # Velocity and position vel_rect = SurroundingRectangle(equation[:2], stroke_color=RED) pos_rect = SurroundingRectangle(equation[4], stroke_color=YELLOW) pos_rect.match_height(vel_rect, stretch=True) pos_rect.match_y(vel_rect) vel_words = Text("Velocity", color=RED) vel_words.next_to(vel_rect, DOWN) vel_words.shift_onto_screen(buff=0.2) pos_words = Text("Position", color=YELLOW) pos_words.next_to(pos_rect, DOWN) for word in vel_words, pos_words: word.add_background_rectangle() self.add(plane, background_rect, equation) self.play( ShowCreation(vel_rect), FadeIn(vel_words, 0.5 * DOWN), ) self.wait() self.play( ShowCreation(pos_rect), FadeIn(pos_words, 0.5 * DOWN), ) self.wait() term_labels = VGroup(vel_rect, vel_words, pos_rect, pos_words) foreground = [background_rect, equation, term_labels] self.add(dots, *foreground) self.play(LaggedStartMap(GrowFromCenter, dots)) self.add(dots, vector_field, *foreground) self.play(LaggedStartMap(GrowArrow, vector_field, lag_ratio=0.01)) self.wait() self.play( FadeOut(dots), FadeOut(term_labels), vector_field.animate.set_opacity(0.25) ) # Show Mv being attached to v index = 326 lil_vect = vector_field[index] coords = plane.p2c(lil_vect.get_start()) dot = Dot(color=YELLOW, radius=0.05) dot.move_to(plane.c2p(*coords)) dot_label = OldTex("\\vec{\\textbf{v}}", color=YELLOW) dot_label.next_to(dot, UR, SMALL_BUFF) vector = Arrow(plane.get_origin(), dot.get_center(), buff=0) vector.set_fill(YELLOW, opacity=1) vector.set_stroke(BLACK, 0.5) Mv = Arrow(plane.get_origin(), plane.c2p(*3 * func(*coords)), buff=0) Mv.set_fill(RED, 1) Mv_label = OldTex("M", "\\vec{\\textbf{v}}") Mv_label[0].set_color(GREY_B) Mv_label.next_to(Mv.get_end(), DOWN) attached_Mv = lil_vect.copy().set_opacity(1) self.play( GrowFromPoint(dot, equation[-1].get_center()), FadeTransform(equation[-1][0].copy(), dot_label), ) self.wait() self.play( FadeIn(vector), ReplacementTransform(vector.copy().set_opacity(0), Mv, path_arc=-45 * DEGREES), FadeTransform(equation[3:5].copy(), Mv_label), ) self.wait() self.play( TransformFromCopy(Mv, attached_Mv) ) self.remove(attached_Mv) lil_vect.set_opacity(1) self.wait() for x in range(100): index += 1 lil_vect = vector_field[index] coords = plane.p2c(lil_vect.get_start()) vector.put_start_and_end_on(plane.get_origin(), plane.c2p(*coords)) Mv.put_start_and_end_on(plane.get_origin(), plane.c2p(*3 * func(*coords))) Mv_label.next_to(Mv.get_end(), DOWN) dot.move_to(vector.get_end()) dot_label.next_to(dot, UR, SMALL_BUFF) lil_vect.set_opacity(1) if x < 20: self.wait(0.25) else: self.wait(0.1) self.play( LaggedStartMap(FadeOut, VGroup(Mv_label, dot_label, Mv, vector, dot)), vector_field.animate.set_opacity(1) ) self.wait() # Show example initial condition evolving def get_flow_lines(step_multiple, arc_len): return StreamLines( func, plane, step_multiple=step_multiple, magnitude_range=(0, 2), color_by_magnitude=False, stroke_color=GREY_A, stroke_width=2, stroke_opacity=1, arc_len=arc_len, ) dot = Dot() vect = Vector() vect.set_color(RED) def update_vect(vect): coords = plane.p2c(dot.get_center()) end = plane.c2p(*func(*coords)) vect.put_start_and_end_on(plane.get_origin(), end) vect.shift(dot.get_center() - plane.get_origin()) vect.add_updater(update_vect) flow_line = get_flow_lines(4, 20)[10] flow_line.insert_n_curves(100) flow_line.set_stroke(width=3) dot.move_to(flow_line.get_start()) self.add(flow_line, vect, dot) self.play( MoveAlongPath(dot, flow_line, run_time=10, rate_func=linear), ShowCreation(flow_line, run_time=10, rate_func=linear), VFadeIn(dot), VFadeIn(vect), ) self.play(LaggedStartMap(FadeOut, VGroup(flow_line, dot, vect))) self.wait() # Show exponential solution solution = OldTex( v_tex, "=", "e^{M t}", "\\vec{\\textbf{v} }(0)" ) solution[0].set_color(YELLOW) solution[2][1].set_color(GREY_B) solution.match_width(equation) solution.next_to(equation, DOWN, MED_LARGE_BUFF) new_br = SurroundingRectangle(VGroup(equation, solution), buff=MED_SMALL_BUFF) new_br.match_style(background_rect) self.play( Transform(background_rect, new_br), FadeIn(solution, 0.5 * DOWN) ) self.wait() foreground = VGroup(background_rect, equation, solution) self.play(FadeOut(foreground)) # Show flow of all initial conditions initial_points = np.array([ plane.c2p(x, y) for x in np.arange(-16, 16, 0.5) for y in np.arange(-6, 6, 0.5) ]) dots = DotCloud(initial_points) dots.set_radius(0.06) dots.set_color(WHITE) initial_points = np.array(dots.get_points()) self.add(dots) self.play(vector_field.animate.set_opacity(0.75)) time_tracker = ValueTracker() def update_dots(dots): time = time_tracker.get_value() transformation = np.identity(3) transformation[:2, :2] = mat_exp(0.15 * matrix * time) dots.set_points(np.dot(initial_points, transformation)) streaks = Group() def update_streaks(streaks): dc = dots.copy() dc.clear_updaters() dc.set_opacity(0.25) dc.set_radius(0.01) streaks.add(dc) dots.add_updater(update_dots) streaks.add_updater(update_streaks) self.add(plane, vector_field, streaks, dots) self.play(time_tracker.animate.set_value(3), run_time=6, rate_func=linear) # Flow # animated_flow = AnimatedStreamLines(get_flow_lines(0.25, 3)) class DefineVectorFieldWithHyperbolicFlow(BasicVectorFieldIdea): matrix = np.array([[0.0, 1.0], [1.0, 0.0]]) / 0.45 class MoreShakesperianRomeoJuliet(RomeoAndJuliet): def construct(self): # Add plane/vector field plane = NumberPlane((-5, 5), (-5, 5), faded_line_ratio=0) plane.set_height(5) plane.to_corner(DL) self.add(plane) def func0(x, y): return (x, y) def func1(x, y): return (-y, x) def func2(x, y): return (y, x) vector_fields = VGroup(*( VectorField( func, plane, step_multiple=1, magnitude_range=(0, 8), vector_config={"thickness": 0.025}, length_func=lambda norm: 0.9 * sigmoid(norm) ) for func in [func0, func1, func2] )) # Put differential equation above it equations = VGroup( get_2d_equation([["0", "-1"], ["+1", "0"]]), get_2d_equation([["0", "+1"], ["+1", "0"]]), ) equations.to_corner(UL) m1 = equations[0][3] m2 = equations[1][3] self.add(equations[0]) vector_fields[0].set_opacity(0) vf = vector_fields[0] self.play(Transform(vf, vector_fields[1])) self.wait() # Add Romeo and Juliet romeo, juliet = lovers = self.get_romeo_and_juliet() lovers.set_height(2) lovers.arrange(LEFT, buff=0.5) lovers.to_corner(DR, buff=1.5) self.make_romeo_and_juliet_dynamic(romeo, juliet) scales = VGroup( self.get_love_scale(romeo, RIGHT, "y", BLUE_D), self.get_love_scale(juliet, LEFT, "x", BLUE_B), ) x0, y0 = (5, 5) ps_point = Dot(color=BLUE_B) ps_point.move_to(plane.c2p(x0, y0)) romeo.love_tracker.add_updater(lambda m: m.set_value(plane.p2c(ps_point.get_center())[0])) juliet.love_tracker.add_updater(lambda m: m.set_value(plane.p2c(ps_point.get_center())[1])) self.add(*lovers, scales, self.get_romeo_juilet_name_labels(lovers)) self.add(*(pi.love_tracker for pi in lovers)) self.add(*(pi.heart_eyes for pi in lovers)) self.add(ps_point) self.wait() # Transition to alternate field self.play( Transform(vf, vector_fields[2]), FadeOut(m1, UP), FadeIn(m2, UP), ) self.wait() last_rect = VMobject() for row in m2.get_rows(): rect = SurroundingRectangle(row) self.play(FadeIn(rect), FadeOut(last_rect)) self.wait() last_rect = rect self.play(FadeOut(last_rect)) self.play(ShowIncreasingSubsets(vf, run_time=6, rate_func=linear)) # Show flow def get_flow_line(x0, y0): line = ParametricCurve( lambda t: plane.c2p( math.cosh(t) * x0 + math.sinh(t) * y0, math.sinh(t) * x0 + math.cosh(t) * y0, ), t_range=(0, 4), ) line.set_stroke(WHITE, 3) return line def move_along_line(line, run_time=8): self.add(line, ps_point) self.play( MoveAlongPath(ps_point, line.copy()), ShowCreation(line), rate_func=linear, run_time=run_time, ) line1 = get_flow_line(4, -3) # line2 = get_flow_line(-3, 4) line3 = get_flow_line(-4, 3) # move_along_line(line1) # self.wait() ps_point.move_to(line1.get_start()) self.remove(line1) low_vects = VGroup() mid_vects = VGroup() high_vects = VGroup() for vector in vf: x, y = plane.p2c(vector.get_start()) if x + y < -1e-6: low_vects.add(vector) elif -1e-6 < x + y < 1e-6: mid_vects.add(vector) else: high_vects.add(vector) low_vects.set_opacity(0.1) mid_vects.set_opacity(0.5) self.wait() move_along_line(line1) self.wait() self.remove(line1) ps_point.move_to(line3) low_vects.set_opacity(1) high_vects.set_opacity(0.1) move_along_line(line3) self.wait() class NotAllThatRomanticLabel(Scene): def construct(self): text = OldTexText("Not all that\n\n romantic!") arrow = Vector(LEFT) arrow.next_to(text, LEFT) text.set_color(RED) arrow.set_color(RED) self.add(text) self.play(ShowCreation(arrow)) self.wait() class TransitionWrapper(Scene): def construct(self): self.add(FullScreenRectangle()) screens = VGroup(*(ScreenRectangle() for x in range(2))) screens.set_width(0.4 * FRAME_WIDTH) screens[0].to_edge(LEFT) screens[1].to_edge(RIGHT) screens.set_fill(BLACK, 1) screens[0].set_stroke(BLUE, 2) screens[1].set_stroke(GREY_BROWN, 2) self.add(screens) arrow = Arrow(*screens) exp = OldTex("e^{Mt}") exp.next_to(arrow, UP) titles = VGroup( OldTexText("Time: $0$"), OldTexText("Time: $t$"), ) for screen, title in zip(screens, titles): title.next_to(screen, UP) screen.add(title) vf_words = OldTexText("Vector field defined by $\\vec{\\textbf{v} } \\rightarrow M\\vec{\\textbf{v} }$") vf_words.match_width(screens[0]) vf_words.next_to(screens[0], DOWN) self.add(vf_words) self.play(ShowCreation(arrow)) self.play(Write(exp)) self.wait() class DerivativeOfExpMt(Scene): def construct(self): # For all tex v0_tex = "\\vec{\\textbf{v} }_0" kw = { "tex_to_color_map": { "M": GREY_B, "{t}": YELLOW, v0_tex: BLUE, "=": WHITE, "\\cdots": WHITE, "+": WHITE, "\\left(": WHITE, "\\right)": WHITE, } } # Show claim solution = OldTex("\\vec{\\textbf{v} }({t}) = e^{M {t} } " + v0_tex, **kw) equation = OldTex("{d \\over dt} \\vec{\\textbf{v} }({t}) = M \\vec{\\textbf{v} }({t})", **kw) arrow = Vector(1.5 * RIGHT) top_line = VGroup(solution, arrow, equation) top_line.arrange(RIGHT, buff=MED_LARGE_BUFF) top_line.set_width(FRAME_WIDTH - 1) solves = Text("Solves(?)", font_size=24) solves.next_to(arrow, UP, SMALL_BUFF) self.add(solution) self.play( GrowArrow(arrow), FadeIn(equation, RIGHT), FadeIn(solves, lag_ratio=0.1) ) self.wait() arrow.add(solves) # Try it... # Show calculations... tex_expressions = [ """ e^{ {t}M } v_0 = \\left( {t}^0 M^0 + {t}^1 M^1 + { {t}^2 \\over 2} M^2 + { {t}^3 \\over 6} M^3 + \\cdots + { {t}^n \\over n!} M^n + \\cdots \\right) v_0 """, """ e^{ {t}M } v_0 = {t}^0 M^0 v_0 + {t}^1 M^1 v_0 + { {t}^2 \\over 2} M^2 v_0 + { {t}^3 \\over 6} M^3 v_0 + \\cdots + { {t}^n \\over n!} M^n v_0 + \\cdots """, """ {d \\over dt} e^{ {t}M } v_0 = 0 + 1 \\cdot {t}^0 M^1 v_0 + {2 {t}^1 \\over 2} M^2 v_0 + {3 {t}^2 \\over 6} M^3 v_0 + \\cdots + {n {t}^{n - 1} \\over n!} M^n v_0 + \\cdots """, """ {d \\over dt} e^{ {t}M } v_0 = 0 + 1 \\cdot {t}^0 M^1 v_0 + {t}^1 M^2 v_0 + { {t}^2 \\over 2} M^3 v_0 + \\cdots + { {t}^{n - 1} \\over (n - 1)!} M^n v_0 + \\cdots """, """ {d \\over dt} e^{ {t}M } v_0 = M \\left( {t}^0 M^0 v_0 + {t}^1 M^1 v_0 + { {t}^2 \\over 2} M^2 v_0 + \\cdots + { {t}^{n - 1} \\over (n - 1)!} M^{n - 1} v_0 + \\cdots \\right) """, """ {d \\over dt} e^{ {t}M } v_0 = M \\left( e^{ {t}M } v_0 \\right) """ ] lines = VGroup(*( OldTex(tex.replace("v_0", v0_tex), **kw) for tex in tex_expressions )) lines.set_width(FRAME_WIDTH - 1) max_height = 1.0 for line in lines: line.set_width(FRAME_WIDTH - 2) if line.get_height() > max_height: line.set_height(max_height) line.center() def match_lines(l1, l2): eq_centers = [eq.get_part_by_tex("=").get_center() for eq in (l1, l2)] l1.shift(eq_centers[1] - eq_centers[0]) # Line 0 self.play(top_line.animate.set_width(6).to_edge(UP)) lines[0].set_y(1) self.play(TransformMatchingTex(solution[4:].copy(), lines[0]), run_time=2) self.wait() # 0 -> 1 match_lines(lines[1], lines[0]) lines[1].set_y(-1) self.play( TransformMatchingTex( VGroup(*( sm for sm in lines[0][5:] if sm.get_tex() not in ["\\left(", "\\right)"] )).copy(), lines[1][5:] ), TransformFromCopy(lines[0][:5], lines[1][:5]) ) self.play( lines[1].animate.set_y(1), FadeOut(lines[0], UP) ) self.wait() # 1 -> 2 -> 3 match_lines(lines[2], lines[1]) match_lines(lines[3], lines[2]) lines[2:4].set_y(-1) self.play(FadeIn(lines[2], DOWN)) l1_indices, l2_indices, l3_indices = [ [ lines[i].index_of_part(part) for part in it.chain(lines[i].get_parts_by_tex("="), lines[i].get_parts_by_tex("+")) ] for i in (1, 2, 3) ] last_rects = VMobject() for l1i, l1j, l2i, l2j in zip(l1_indices, l1_indices[1:], l2_indices, l2_indices[1:]): if l1i is l1_indices[4]: continue r1 = SurroundingRectangle(lines[1][l1i + 1:l1j]) r2 = SurroundingRectangle(lines[2][l2i + 1:l2j]) rects = VGroup(r1, r2) self.play(FadeIn(rects), FadeOut(last_rects)) self.wait(0.5) last_rects = rects self.play(FadeOut(last_rects)) self.play( lines[2].animate.set_y(1), FadeOut(lines[1]), FadeIn(lines[3]), ) self.wait() last_rects = VMobject() for l2i, l2j, l3i, l3j in zip(l2_indices, l2_indices[1:], l3_indices, l3_indices[1:]): # Such terrible style...please no on look if l2i in [l2_indices[0], l2_indices[1], l2_indices[4]]: continue r2 = SurroundingRectangle(lines[2][l2i + 1:l2j]) r3 = SurroundingRectangle(lines[3][l3i + 1:l3j]) rects = VGroup(r2, r3) self.play(FadeIn(rects), FadeOut(last_rects)) self.wait(0.5) last_rects = rects self.play(FadeOut(last_rects)) self.wait() # 3 -> 4 match_lines(lines[4], lines[3]) lines[4].set_y(-1) self.play( FadeIn(lines[1], DOWN), FadeOut(lines[2], DOWN) ) self.wait() self.play(LaggedStart(*( FlashUnder(sm, time_width=2, run_time=1) for sm in lines[3].get_parts_by_tex("M")[1:] ), lag_ratio=0.2)) self.wait() self.play( TransformMatchingTex(lines[3][:5], lines[4][:5]), FadeTransform(lines[3][5:], lines[4][7:34]), FadeIn(lines[4].get_part_by_tex("\\left(")), FadeIn(lines[4].get_part_by_tex("\\right)")), TransformFromCopy( lines[3].get_parts_by_tex("M")[1:], VGroup(lines[4][5]), path_arc=-45 * DEGREES, ) ) self.wait() # 4 -> 5 match_lines(lines[5], lines[4]) lines[5].set_y(-3) self.play( TransformFromCopy(lines[4][:7], lines[5][:7]), TransformFromCopy(lines[4][34], lines[5][11]), FadeTransform(lines[4][7:34].copy(), lines[5][7:11], stretch=False), ) self.wait() class TryIt(Scene): def construct(self): morty = Mortimer() morty.to_corner(DR) self.add(morty) self.play(PiCreatureSays(morty, "Try it!", target_mode="surprised", run_time=1)) self.play(Blink(morty)) self.wait(2) self.remove(morty.bubble, morty.bubble.content) self.play(PiCreatureSays(morty, "Brace yourself now...", target_mode="hesitant")) morty.look(ORIGIN) self.wait(2) class TracePropertyAndComputation(TeacherStudentsScene): def construct(self): trace_eq = OldTex( "\\text{Det}\\left(e^{Mt}\\right) = e^{\\text{Tr}(M) t}", tex_to_color_map={ "\\text{Det}": GREEN_D, "\\text{Tr}": RED_B, } ) trace_eq.move_to(self.hold_up_spot, DOWN) self.play( self.teacher.change("raise_right_hand", trace_eq), FadeIn(trace_eq, 0.5 * UP), ) self.play_student_changes("pondering", "confused", "pondering", look_at=trace_eq) self.wait(2) text = OldTexText("Diagonalization $\\rightarrow$ Easier computation") text.move_to(self.hold_up_spot, DOWN) text.shift_onto_screen() self.play( trace_eq.animate.shift(UP), FadeIn(text, 0.5 * UP), self.change_students("erm", "tease", "maybe"), ) for pi in self.pi_creatures: # Why? pi.eyes[0].refresh_bounding_box() pi.eyes[1].refresh_bounding_box() self.look_at(text) self.wait(3) topics = VGroup(trace_eq, text) exp_deriv = OldTex("e", "{d \\over dx}") exp_deriv[0].scale(2) exp_deriv[1].move_to(exp_deriv[0].get_corner(UR), DL) exp_deriv.move_to(self.hold_up_spot, DOWN) self.play( FadeIn(exp_deriv, scale=2), topics.animate.scale(0.5).to_corner(UL), self.teacher.change("tease", exp_deriv), ) self.play_student_changes("confused", "sassy", "angry") self.wait(6) class EndScreen(PatreonEndScreen): pass # GENERIC flow scenes class ExponentialPhaseFlow(Scene): CONFIG = { "field_config": { "color_by_magnitude": False, "magnitude_range": (0.5, 5), "arc_len": 5, }, "plane_config": { "x_range": [-4, 4], "y_range": [-2, 2], "height": 8, "width": 16, }, "matrix": [ [1, 0], [0, 1], ], "label_height": 3, "run_time": 30, "slow_factor": 0.25, } def construct(self): mr = np.array(self.field_config["magnitude_range"]) self.field_config["magnitude_range"] = self.slow_factor * mr plane = NumberPlane(**self.plane_config) plane.add_coordinate_labels() vector_field, animated_lines = get_vector_field_and_stream_lines( self.func, plane, **self.field_config, ) box = Square() box.replace(Line(plane.c2p(-1, -1), plane.c2p(1, 1)), stretch=True) box.set_stroke(GREY_A, 1) box.set_fill(BLUE_E, 0.8) move_points_along_vector_field(box, self.func, plane) basis_vectors = VGroup( Vector(RIGHT, fill_color=GREEN), Vector(UP, fill_color=RED), ) basis_vectors[0].add_updater(lambda m: m.put_start_and_end_on( plane.get_origin(), box.pfp(7 / 8) )) basis_vectors[1].add_updater(lambda m: m.put_start_and_end_on( plane.get_origin(), box.pfp(1 / 8) )) self.add(plane) self.add(vector_field) self.add(animated_lines) self.add(box) self.add(*basis_vectors) self.wait(self.run_time) def func(self, x, y): return self.slow_factor * np.dot([x, y], np.transpose(self.matrix)) def get_label(self): exponential = get_matrix_exponential(self.matrix) changing_t = DecimalNumber(0, color=YELLOW) changing_t.match_height(exponential[2]) changing_t.move_to(exponential[2], DL) exponential.replace_submobject(2, changing_t) equals = OldTex("=") rhs = DecimalMatrix( np.zeros((2, 2)), element_to_mobject_config={"num_decimal_places": 3}, h_buff=1.8, ) rhs.match_height(exponential) equation = VGroup( exponential, equals, rhs, ) equation.arrange(RIGHT) equation.to_corner(UL) class ExponentialEvaluationWithTime(Scene): flow_scene_class = ExponentialPhaseFlow def construct(self): flow_scene_attrs = merge_dicts_recursively( ExponentialPhaseFlow.CONFIG, self.flow_scene_class.CONFIG, ) matrix = np.array(flow_scene_attrs["matrix"]) slow_factor = flow_scene_attrs["slow_factor"] def get_t(): return slow_factor * self.time exponential = get_matrix_exponential(matrix) dot = OldTex("\\cdot") dot.move_to(exponential[2], LEFT) changing_t = DecimalNumber(0) changing_t.match_height(exponential[2]) changing_t.next_to(dot, RIGHT, SMALL_BUFF) changing_t.align_to(exponential[1], DOWN) changing_t.add_updater(lambda m: m.set_value(get_t()).set_color(YELLOW)) lhs = VGroup(*exponential[:2], dot, changing_t) equals = OldTex("=") rhs = DecimalMatrix( np.zeros((2, 2)), element_to_mobject_config={"num_decimal_places": 2}, element_alignment_corner=ORIGIN, h_buff=2.0, ) for mob in rhs.get_entries(): mob.edge_to_fix = ORIGIN rhs.match_height(lhs) def update_rhs(rhs): result = mat_exp(matrix * get_t()) for mob, value in zip(rhs.get_entries(), result.flatten()): mob.set_value(value) return rhs rhs.add_updater(update_rhs) equation = VGroup(lhs, equals, rhs) equation.arrange(RIGHT) equation.center() self.add(equation) self.wait(flow_scene_attrs["run_time"]) self.embed() class CircularPhaseFlow(ExponentialPhaseFlow): CONFIG = { "field_config": { "magnitude_range": (0.5, 8), }, "matrix": [ [0, -1], [1, 0], ] } class CircularFlowEvaluation(ExponentialEvaluationWithTime): flow_scene_class = CircularPhaseFlow class EllipticalPhaseFlow(ExponentialPhaseFlow): CONFIG = { "field_config": { "magnitude_range": (0.5, 8), }, "matrix": [ [0.5, -3], [1, -0.5], ] } class EllipticalFlowEvaluation(ExponentialEvaluationWithTime): flow_scene_class = EllipticalPhaseFlow class HyperbolicPhaseFlow(ExponentialPhaseFlow): CONFIG = { "field_config": { "sample_freq": 8, }, "matrix": [ [1, 0], [0, -1], ] } class HyperbolicFlowEvaluation(ExponentialEvaluationWithTime): flow_scene_class = HyperbolicPhaseFlow class ShearPhaseFlow(ExponentialPhaseFlow): CONFIG = { "field_config": { "sample_freq": 2, "magnitude_range": (0.5, 8), }, "plane_config": { "x_range": [-8, 8], "y_range": [-4, 4], }, "matrix": [ [1, 1], [0, 1], ], "slow_factor": 0.1, } class ShearFlowEvaluation(ExponentialEvaluationWithTime): flow_scene_class = ShearPhaseFlow class HyperbolicTrigFlow(ExponentialPhaseFlow): CONFIG = { "field_config": { "sample_freq": 2, "magnitude_range": (0.5, 7), }, "plane_config": { "x_range": [-8, 8], "y_range": [-4, 4], }, "matrix": [ [0, 1], [1, 0], ], "slow_factor": 0.1, } class HyperbolicTrigFlowEvaluation(ExponentialEvaluationWithTime): flow_scene_class = HyperbolicTrigFlow class DampedRotationPhaseFlow(ExponentialPhaseFlow): CONFIG = { "matrix": [ [-1, -1], [1, 0], ], } class DampedRotationFlowEvaluation(ExponentialEvaluationWithTime): flow_scene_class = DampedRotationPhaseFlow class FrameForFlow(Scene): def construct(self): self.add(FullScreenRectangle(fill_color=GREY_D)) screen_rect = ScreenRectangle() screen_rect.set_height(5.5) screen_rect.set_stroke(WHITE, 3) screen_rect.set_fill(BLACK, 1) screen_rect.to_edge(DOWN) self.add(screen_rect) class ThumbnailBackdrop(DampedRotationPhaseFlow): CONFIG = { "run_time": 10, } def construct(self): super().construct() for mob in self.mobjects: if isinstance(mob, Square) or isinstance(mob, Arrow): self.remove(mob) if isinstance(mob, NumberPlane): self.remove(mob.coordinate_labels) class Thumbnail(Scene): def construct(self): im = ImageMobject("ExpMatThumbnailBackdrop") im.set_height(FRAME_HEIGHT) im.set_opacity(0.7) self.add(im) # rect = FullScreenFadeRectangle() # rect.set_fill(opacity=0.3) # self.add(rect) exp = get_matrix_exponential([[-1, -1], [1, 0]], scalar_tex="") exp.set_height(5) exp.set_stroke(BLACK, 50, opacity=0.5, background=True) fuzz = VGroup() N = 100 for w in np.linspace(150, 0, N): ec = exp.copy() ec.set_stroke(BLUE_E, width=w, opacity=(1 / N)) ec.set_fill(opacity=0) fuzz.add(ec) self.add(fuzz, exp) self.embed() # Older class LetsSumUp(TeacherStudentsScene): def construct(self): self.teacher_says( "Let's review", added_anims=[self.change_students("thinking", "pondering", "thinking")] ) self.wait(3) class PrerequisitesWrapper(Scene): def construct(self): self.add(FullScreenRectangle()) title = Text("Helpful background knowledge") title.to_edge(UP) self.add(title) screens = VGroup(*(ScreenRectangle() for x in range(2))) screens.arrange(RIGHT, buff=LARGE_BUFF) screens.set_width(FRAME_WIDTH - 1) screens.move_to(DOWN) screens.set_fill(BLACK, 1) screens.set_stroke(WHITE, 2) topics = VGroup( OldTexText("Basics of $e^x$"), OldTexText("How matrices act\\\\as transformations"), ) for topic, screen in zip(topics, screens): topic.next_to(screen, UP) topic.set_color(WHITE) for topic, screen in zip(topics, screens): sc = screen.copy() sc.set_fill(opacity=0) sc.set_stroke(width=3) self.play( FadeIn(topic, 0.5 * UP), FadeIn(screen), VShowPassingFlash(sc, time_width=1.0, run_time=1.5), ) self.wait(2) class SchroedingersComplicatingFactors(TeacherStudentsScene): def construct(self): pass class OldComputationCode(Scene): def construct(self): # Taylor series example ex_rhs = OldTex( """ {2}^0 + {2}^1 + { {2}^2 \\over 2} + { {2}^3 \\over 6} + { {2}^4 \\over 24} + { {2}^5 \\over 120} + { {2}^6 \\over 720} + { {2}^7 \\over 5040} + \\cdots """, tex_to_color_map={"{2}": YELLOW, "+": WHITE}, ) ex_rhs.next_to(real_equation[3:], DOWN, buff=0.75) ex_parts = VGroup(*( ex_rhs[i:j] for i, j in [ (0, 2), (3, 5), (6, 8), (9, 11), (12, 14), (15, 17), (18, 20), (21, 23), (24, 25), ] )) term_brace = Brace(ex_parts[0], DOWN) frac = OldTex("1", font_size=36) frac.next_to(term_brace, DOWN, SMALL_BUFF) rects = VGroup(*( Rectangle(height=2**n / math.factorial(n), width=1) for n in range(11) )) rects.arrange(RIGHT, buff=0, aligned_edge=DOWN) rects.set_fill(opacity=1) rects.set_submobject_colors_by_gradient(BLUE, GREEN) rects.set_stroke(WHITE, 1) rects.set_width(7) rects.to_edge(DOWN) self.play( ReplacementTransform(taylor_brace, term_brace), FadeTransform(real_equation[3:].copy(), ex_rhs), FadeOut(false_group, shift=DOWN), FadeOut(taylor_label, shift=DOWN), FadeIn(frac), ) term_values = VGroup() for n in range(11): rect = rects[n] fact = math.factorial(n) ex_part = ex_parts[min(n, len(ex_parts) - 1)] value = DecimalNumber(2**n / fact) value.set_color(GREY_A) max_width = 0.6 * rect.get_width() if value.get_width() > max_width: value.set_width(max_width) value.next_to(rects[n], UP, SMALL_BUFF) new_brace = Brace(ex_part, DOWN) if fact == 1: new_frac = OldTex(f"{2**n}", font_size=36) else: new_frac = OldTex(f"{2**n} / {fact}", font_size=36) new_frac.next_to(new_brace, DOWN, SMALL_BUFF) self.play( term_brace.animate.become(new_brace), FadeTransform(frac, new_frac), ) frac = new_frac rect.save_state() rect.stretch(0, 1, about_edge=DOWN) rect.set_opacity(0) value.set_value(0) self.play( Restore(rect), ChangeDecimalToValue(value, 2**n / math.factorial(n)), UpdateFromAlphaFunc(value, lambda m, a: m.next_to(rect, UP, SMALL_BUFF).set_opacity(a)), randy.animate.look_at(rect), morty.animate.look_at(rect), ) term_values.add(value) self.play(FadeOut(frac)) new_brace = Brace(ex_rhs, DOWN) sum_value = DecimalNumber(math.exp(2), num_decimal_places=4, font_size=36) sum_value.next_to(new_brace, DOWN) self.play( term_brace.animate.become(new_brace), randy.change("thinking", sum_value), morty.change("tease", sum_value), *(FadeTransform(dec.copy().set_opacity(0), sum_value) for dec in term_values) ) self.play(Blink(randy)) lhs = OldTex("e \\cdot e =") lhs.match_height(real_equation[0]) lhs.next_to(ex_rhs, LEFT) self.play(Write(lhs)) self.play(Blink(morty)) self.play(Blink(randy)) # Increment input twos = ex_rhs.get_parts_by_tex("{2}") threes = VGroup(*( OldTex("3").set_color(YELLOW).replace(two) for two in twos )) new_lhs = OldTex("e \\cdot e \\cdot e = ") new_lhs.match_height(lhs) new_lhs[0].space_out_submobjects(0.8) new_lhs[0][-1].shift(SMALL_BUFF * RIGHT) new_lhs.move_to(lhs, RIGHT) anims = [] unit_height = 0.7 * rects[0].get_height() for n, rect, value_mob in zip(it.count(0), rects, term_values): rect.generate_target() new_value = 3**n / math.factorial(n) rect.target.set_height(unit_height * new_value, stretch=True, about_edge=DOWN) value_mob.rect = rect anims += [ MoveToTarget(rect), ChangeDecimalToValue(value_mob, new_value), UpdateFromFunc(value_mob, lambda m: m.next_to(m.rect, UP, SMALL_BUFF)) ] self.play( FadeOut(twos, 0.5 * UP), FadeIn(threes, 0.5 * UP), ) twos.set_opacity(0) self.play( ChangeDecimalToValue(sum_value, math.exp(3)), *anims, ) self.play( FadeOut(lhs, 0.5 * UP), FadeIn(new_lhs, 0.5 * UP), ) self.wait() class PreviewVisualizationWrapper(Scene): def construct(self): background = FullScreenFadeRectangle(fill_color=GREY_E, fill_opacity=1) self.add(background) screen = ScreenRectangle(height=6) screen.set_fill(BLACK, 1) screen.set_stroke(GREY_A, 3) screen.to_edge(DOWN) self.add(screen) titles = VGroup( Text("How to think about matrix exponentiation"), Text( "How to visualize matrix exponentiation", t2s={"visualize": ITALIC}, ), Text("What problems matrix exponentiation solves?"), ) for title in titles: title.next_to(screen, UP) title.get_parts_by_text("matrix exponentiation").set_color(TEAL) self.play(FadeIn(titles[0], 0.5 * UP)) self.wait(2) self.play(*( FadeTransform( titles[0].get_parts_by_text(w1), titles[1].get_parts_by_text(w2), ) for w1, w2 in [ ("How to", "How to"), ("think about", "visualize"), ("matrix exponentiation", "matrix exponentiation"), ] )) self.wait(2) self.play( *( FadeTransform( titles[1].get_parts_by_text(w1), titles[2].get_parts_by_text(w2), ) for w1, w2 in [ ("How to visualize", "What problems"), ("matrix exponentiation", "matrix exponentiation"), ] ), FadeIn(titles[2].get_parts_by_text("solves?")) ) self.wait(2)