diff --git a/active_projects/eola2/cramer.py b/active_projects/eola2/cramer.py new file mode 100644 index 00000000..5a1939df --- /dev/null +++ b/active_projects/eola2/cramer.py @@ -0,0 +1,1892 @@ +from big_ol_pile_of_manim_imports import * + +X_COLOR = GREEN +Y_COLOR = RED +Z_COLOR = BLUE +OUTPUT_COLOR = YELLOW +INPUT_COLOR = MAROON_B + + +def get_cramer_matrix(matrix, output_vect, index=0): + """ + The inputs matrix and output_vect should be Matrix mobjects + """ + new_matrix = np.array(matrix.mob_matrix) + new_matrix[:, index] = output_vect.mob_matrix[:, 0] + # Create a new Matrix mobject with copies of these entries + result = Matrix(new_matrix, element_to_mobject=lambda m: m.copy()) + result.match_height(matrix) + return result + + +class LinearSystem(VGroup): + CONFIG = { + "matrix_config": { + "element_to_mobject": Integer, + }, + "dimensions": 3, + "min_int": -9, + "max_int": 10, + "height": 4, + } + + def __init__(self, matrix=None, output_vect=None, **kwargs): + VGroup.__init__(self, **kwargs) + if matrix is None: + dim = self.dimensions + matrix = np.random.randint( + self.min_int, + self.max_int, + size=(dim, dim) + ) + else: + dim = len(matrix) + self.matrix_mobject = Matrix(matrix, **self.matrix_config) + self.equals = TexMobject("=") + self.equals.scale(1.5) + + colors = [X_COLOR, Y_COLOR, Z_COLOR][:dim] + chars = ["x", "y", "z"][:dim] + self.input_vect_mob = Matrix(np.array(chars)) + self.input_vect_mob.elements.set_color_by_gradient(*colors) + + if output_vect is None: + output_vect = np.random.randint( + self.min_int, self.max_int, size=(dim, 1)) + self.output_vect_mob = IntegerMatrix(output_vect) + self.output_vect_mob.elements.set_color(OUTPUT_COLOR) + + for mob in self.matrix_mobject, self.input_vect_mob, self.output_vect_mob: + mob.scale_to_fit_height(self.height) + + self.add( + self.matrix_mobject, + self.input_vect_mob, + self.equals, + self.output_vect_mob, + ) + self.arrange_submobjects(RIGHT, buff=SMALL_BUFF) + + +# Scenes + + +class LeaveItToComputers(TeacherStudentsScene): + CONFIG = { + "random_seed": 1, + } + + def construct(self): + system = LinearSystem(height=3) + system.next_to(self.pi_creatures, UP) + system.generate_target() + system.target.scale(0.5) + system.target.to_corner(UL) + + cramer_groups = VGroup() + for i in range(3): + numer_matrix = get_cramer_matrix( + system.matrix_mobject, system.output_vect_mob, + index=i + ) + numer = VGroup( + get_det_text(numer_matrix, initial_scale_factor=4), + numer_matrix + ) + numer.to_corner(UP) + denom_matrix_mobject = system.matrix_mobject.copy() + denom = VGroup( + get_det_text(denom_matrix_mobject, initial_scale_factor=4), + denom_matrix_mobject, + ) + rhs = VGroup(numer, Line(LEFT, RIGHT).match_width(numer), denom) + rhs.arrange_submobjects(DOWN) + rhs.scale_to_fit_height(2.25) + rhs.move_to(self.hold_up_spot, DOWN) + equals = TexMobject("=").next_to(rhs, LEFT) + variable = system.input_vect_mob.elements[i].copy() + variable.next_to(equals, LEFT) + cramer_group = VGroup(variable, equals, rhs) + cramer_group.variable = variable + cramer_group.equals = equals + cramer_group.rhs = rhs + cramer_groups.add(cramer_group) + + self.play( + Write(system), + self.teacher.change, "raise_right_hand", + self.get_student_changes("pondering", "thinking", "hooray") + ) + self.wait(2) + self.play( + PiCreatureSays( + self.teacher, "Let the computer \\\\ handle it", + target_mode="shruggie", + ), + MoveToTarget(system, path_arc=90 * DEGREES), + self.get_student_changes(*["confused"] * 3) + ) + self.wait(3) + + cg = cramer_groups[0] + scale_factor = 1.5 + cg.scale(scale_factor, about_edge=DOWN) + numer, line, denom = cg.rhs + x_copy = system.input_vect_mob.elements[0].copy() + self.play( + RemovePiCreatureBubble( + self.teacher, target_mode="raise_right_hand"), + ShowCreation(line), + ReplacementTransform( + system.matrix_mobject.copy(), + denom[1], + ), + Write(denom[0]), + FadeIn(cg.equals), + ReplacementTransform(x_copy, cg.variable), + ) + denom_mover = denom.deepcopy() + denom_mover.target = numer.deepcopy() + column1 = VGroup(*denom_mover.target[1].mob_matrix[:, 0]) + column1.set_fill(opacity=0) + self.play(MoveToTarget(denom_mover)) + self.look_at(system) + self.play( + ReplacementTransform( + system.output_vect_mob.elements.copy(), + VGroup(*numer[1].mob_matrix[:, 0]), + path_arc=180 * DEGREES + ), + self.teacher.change, "happy", + ) + self.remove(denom_mover) + self.add(cg) + self.change_all_student_modes("sassy") + self.wait(2) + self.play( + cramer_groups[0].scale, 1 / scale_factor, + cramer_groups[0].next_to, cramer_groups[1], LEFT, MED_LARGE_BUFF, + FadeIn(cramer_groups[1]), + FadeOut(system), + self.get_student_changes(*3 * ["horrified"], look_at_arg=UP), + ) + self.wait() + self.play( + FadeIn(cramer_groups[2]), + cramer_groups[:2].next_to, cramer_groups[2], LEFT, MED_LARGE_BUFF, + self.get_student_changes(*3 * ["horrified"], look_at_arg=UP), + ) + self.wait() + + brace = Brace(cramer_groups, UP) + rule_text = brace.get_text("``Cramer's rule''") + + self.play( + GrowFromCenter(brace), + Write(rule_text), + self.get_student_changes( + "pondering", "erm", "maybe", + look_at_arg=brace, + ) + ) + self.wait(3) + + +class PrerequisiteKnowledge(TeacherStudentsScene): + CONFIG = { + "camera_config": {"background_alpha": 255} + } + + def construct(self): + self.remove(*self.pi_creatures) + randy = self.students[1] + self.add(randy) + + title = TextMobject("Prerequisites") + title.to_edge(UP) + + h_line = Line(LEFT, RIGHT).scale(5) + h_line.next_to(title, DOWN) + + images = Group(*[ + ImageMobject("eola%d_thumbnail" % d) + for d in [5, 6, 7] + ]) + images.arrange_submobjects(RIGHT, buff=LARGE_BUFF) + images.next_to(h_line, DOWN, MED_LARGE_BUFF) + for image in images: + rect = SurroundingRectangle(image, color=BLUE) + image.rect = rect + + self.add(title) + self.play( + ShowCreation(h_line), + randy.change, "erm" + ) + self.wait() + for image in images: + self.play( + FadeInFromDown(image), + FadeInFromDown(image.rect), + randy.change, "pondering" + ) + self.wait() + + +class NotTheMostComputationallyEfficient(Scene): + CONFIG = { + "words": "Not the most \\\\ computationally \\\\ efficient", + "word_height": 4, + } + + def construct(self): + big_rect = FullScreenFadeRectangle(opacity=0.5) + self.add(big_rect) + + words = TextMobject(self.words) + words.set_color(RED) + words.set_stroke(WHITE, 1) + words.scale_to_fit_height(self.word_height) + self.play(Write(words)) + self.wait() + + +class SetupSimpleSystemOfEquations(LinearTransformationScene): + CONFIG = { + "matrix": [[3, 2], [-1, 2]], + "output_vect": [-4, -2], + "quit_before_final_transformation": False, + "array_scale_factor": 0.75 + } + + def construct(self): + self.remove_grid() + self.introduce_system() + self.from_system_to_matrix() + self.show_geometry() + + def remove_grid(self): + self.clear() + + def introduce_system(self): + system = self.system = self.get_system(self.matrix, self.output_vect) + big_dim = 7 # Big system size + big_matrix = np.random.randint(-9, 10, size=(big_dim, big_dim)) + big_output_vect = np.random.randint(-9, 10, size=big_dim) + big_matrix[:2, :2] = self.matrix + big_output_vect[:2] = self.output_vect + big_system = self.get_system(big_matrix, big_output_vect) + + unknown_circles = VGroup(*[ + Circle(color=YELLOW).replace(term).scale(1.5) + for term in system.unknowns + ]) + unknown_circles.set_stroke(YELLOW, 2) + for circle in unknown_circles: + circle.save_state() + circle.scale(5) + circle.fade(1) + row_rects = VGroup(*map(SurroundingRectangle, system)) + row_rects.set_stroke(BLUE, 2) + + self.add(system) + self.wait() + self.play(LaggedStart( + ApplyMethod, unknown_circles, + lambda m: (m.restore,), + lag_ratio=0.7 + )) + self.play(FadeOut(unknown_circles)) + self.play(LaggedStart(ShowCreation, row_rects, + run_time=1, lag_ratio=0.8)) + self.play(FadeOut(row_rects)) + self.wait() + self.remove(system) + self.play(ReplacementTransform(system.copy(), big_system)) + self.wait(2) + # Oh yeah, super readable line... + self.play(*[ + ReplacementTransform(big_system[i][:5], system[i][:5]) + for i in range(2) + ] + [ + ReplacementTransform(big_system[i][-2:], system[i][-2:]) + for i in range(2) + ] + [ + FadeOut(big_system[i][start:end]) + for i in range(big_dim) + for start in [5 if i < 2 else 0] + for end in [-2 if i < 2 else len(big_system[i])] + ]) + self.remove(big_system, system) + self.add(system) + + def from_system_to_matrix(self): + system_in_lines = self.system + matrix_system = self.matrix_system = LinearSystem( + self.matrix, self.output_vect, height=2 + ) + matrix_system.center() + + corner_rect = self.corner_rect = SurroundingRectangle( + matrix_system, buff=MED_SMALL_BUFF + ) + corner_rect.set_stroke(width=0) + corner_rect.set_fill(BLACK, opacity=0.8) + corner_rect.scale_to_fit_height(2) + corner_rect.to_corner(UL, buff=0) + + self.play(system_in_lines.to_edge, UP) + system_in_lines_copy = system_in_lines.deepcopy() + self.play( + ReplacementTransform( + system_in_lines_copy.matrix_elements, + matrix_system.matrix_mobject.elements, + ), + ReplacementTransform( + system_in_lines_copy.output_vect_elements, + matrix_system.output_vect_mob.elements, + ), + ReplacementTransform( + system_in_lines_copy.unknowns[:2], + matrix_system.input_vect_mob.elements, + ), + Write(matrix_system.matrix_mobject.brackets), + Write(matrix_system.output_vect_mob.brackets), + Write(matrix_system.input_vect_mob.brackets), + Write(matrix_system.equals) + ) + self.wait() + self.play( + Write(self.background_plane), + Write(self.plane), + FadeOut(system_in_lines), + FadeIn(corner_rect), + matrix_system.scale_to_fit_height, corner_rect.get_height() - MED_LARGE_BUFF, + matrix_system.move_to, corner_rect, + ) + self.play(*map(GrowArrow, self.basis_vectors)) + + self.add_foreground_mobject(corner_rect) + self.add_foreground_mobject(matrix_system) + + def show_geometry(self): + system = self.matrix_system + matrix_mobject = system.matrix_mobject + columns = VGroup(*[ + VGroup(*matrix_mobject.mob_matrix[:, i]) + for i in 0, 1 + ]) + + matrix = np.array(self.matrix) + first_half_matrix = np.identity(matrix.shape[0]) + first_half_matrix[:, 0] = matrix[:, 0] + second_half_matrix = np.dot( + matrix, + np.linalg.inv(first_half_matrix), + ) + + scale_factor = self.array_scale_factor + + column_mobs = VGroup() + for i in 0, 1: + column_mob = IntegerMatrix(matrix[:, i]) + column_mob.elements.set_color([X_COLOR, Y_COLOR][i]) + column_mob.scale(scale_factor) + column_mob.next_to( + self.plane.coords_to_point(*matrix[:, i]), RIGHT) + column_mob.add_to_back(BackgroundRectangle(column_mob)) + column_mobs.add(column_mob) + + output_vect_mob = self.get_vector(self.output_vect, color=OUTPUT_COLOR) + output_vect_label = system.output_vect_mob.deepcopy() + output_vect_label.add_to_back(BackgroundRectangle(output_vect_label)) + output_vect_label.generate_target() + output_vect_label.target.scale(scale_factor) + output_vect_label.target.next_to( + output_vect_mob.get_end(), LEFT, SMALL_BUFF) + + input_vect = np.dot(np.linalg.inv(self.matrix), self.output_vect) + input_vect_mob = self.get_vector(input_vect, color=INPUT_COLOR) + q_marks = TexMobject("????") + q_marks.set_color_by_gradient(INPUT_COLOR, OUTPUT_COLOR) + q_marks.next_to(input_vect_mob.get_end(), DOWN, SMALL_BUFF) + q_marks_rect = SurroundingRectangle(q_marks, color=WHITE) + + # Show output vector + self.play( + GrowArrow(output_vect_mob), + MoveToTarget(output_vect_label), + ) + self.add_foreground_mobjects(output_vect_mob, output_vect_label) + self.wait() + + # Show columns + for column, color in zip(columns, [X_COLOR, Y_COLOR]): + rect = SurroundingRectangle(column, color=WHITE) + self.play( + column.set_color, color, + system.input_vect_mob.elements.set_color, WHITE, + ShowPassingFlash(rect), + ) + matrices = [first_half_matrix, second_half_matrix] + for column, column_mob, m in zip(columns, column_mobs, matrices): + column_mob.save_state() + column_mob[0].scale(0).move_to(matrix_mobject) + Transform(column_mob.elements, column).update(1) + Transform(column_mob.brackets, matrix_mobject.brackets).update(1) + self.add_foreground_mobject(column_mob) + self.apply_matrix(m, added_anims=[ + ApplyMethod(column_mob.restore, path_arc=90 * DEGREES) + ]) + self.wait() + + # Do inverse transformation to reveal input + self.remove_foreground_mobjects(column_mobs) + self.apply_inverse(self.matrix, run_time=1, added_anims=[ + ReplacementTransform(output_vect_mob.copy(), input_vect_mob), + ReplacementTransform(output_vect_label.elements.copy(), q_marks), + FadeOut(column_mobs) + ]) + self.play(ShowPassingFlash(q_marks_rect)) + self.wait(2) + if not self.quit_before_final_transformation: + self.apply_matrix(self.matrix, added_anims=[ + FadeOut(q_marks), + ReplacementTransform(input_vect_mob, output_vect_mob), + FadeIn(column_mobs), + ]) + self.wait() + + self.q_marks = q_marks + self.input_vect_mob = input_vect_mob + self.output_vect_mob = output_vect_mob + self.output_vect_label = output_vect_label + self.column_mobs = column_mobs + + # Helpers + + def get_system(self, matrix, output_vect): + if len(matrix) <= 3: + chars = "xyzwv" + else: + chars = ["x_%d" % d for d in range(len(matrix))] + colors = [ + color + for i, color in zip( + range(len(matrix)), + it.cycle([X_COLOR, Y_COLOR, Z_COLOR, YELLOW, MAROON_B, TEAL]) + ) + ] + system = VGroup() + system.matrix_elements = VGroup() + system.output_vect_elements = VGroup() + system.unknowns = VGroup() + for row, num in zip(matrix, output_vect): + args = [] + for i in range(len(row)): + if i + 1 == len(row): + sign = "=" + elif row[i + 1] < 0: + sign = "-" + else: + sign = "+" + args += [str(abs(row[i])), chars[i], sign] + args.append(str(num)) + line = TexMobject(*args) + line.set_color_by_tex_to_color_map(dict([ + (char, color) + for char, color in zip(chars, colors) + ])) + system.add(line) + system.matrix_elements.add(*line[0:-1:3]) + system.unknowns.add(*line[1:-1:3]) + system.output_vect_elements.add(line[-1]) + + system.output_vect_elements.set_color(OUTPUT_COLOR) + system.arrange_submobjects( + DOWN, + buff=0.75, + index_of_submobject_to_align=-2 + ) + return system + + +class ShowZeroDeterminantCase(LinearTransformationScene): + CONFIG = { + "show_basis_vectors": True, + "matrix": [[3, -2.0], [1, -2.0 / 3]], + "tex_scale_factor": 1.25, + "det_eq_symbol": "=", + } + + def construct(self): + self.add_equation() + self.show_det_zero() + + def add_equation(self): + equation = self.equation = TexMobject( + "A", "\\vec{\\textbf{x}}", "=", "\\vec{\\textbf{v}}" + ) + equation.scale(self.tex_scale_factor) + equation.set_color_by_tex("{x}", INPUT_COLOR) + equation.set_color_by_tex("{v}", OUTPUT_COLOR) + equation.add_background_rectangle() + equation.to_corner(UL) + self.add(equation) + self.add_foreground_mobject(equation) + + def show_det_zero(self): + matrix = self.matrix + + # vect_in_span = [2, 2.0 / 3] + vect_in_span = [2, 2.0 / 3] + vect_off_span = [1, 2] + vect_in_span_mob = self.get_vector(vect_in_span, color=OUTPUT_COLOR) + vect_off_span_mob = self.get_vector(vect_off_span, color=OUTPUT_COLOR) + + for vect_mob in vect_in_span_mob, vect_off_span_mob: + circle = Circle(color=WHITE, radius=0.15, stroke_width=2) + circle.move_to(vect_mob.get_end()) + vect_mob.circle = circle + + vect_off_span_words = TextMobject("No input lands here") + vect_off_span_words.next_to(vect_off_span_mob.circle, UP) + vect_off_span_words.add_background_rectangle() + + vect_in_span_words = TextMobject("Many inputs lands here") + vect_in_span_words.next_to(vect_in_span_mob.circle, DR) + vect_in_span_words.shift_onto_screen() + vect_in_span_words.add_background_rectangle() + + moving_group = VGroup(self.plane, self.basis_vectors) + moving_group.save_state() + + solution = np.dot(np.linalg.pinv(matrix), vect_in_span) + import sympy + null_space_basis = np.array(sympy.Matrix(matrix).nullspace()) + null_space_basis = null_space_basis.flatten().astype(float) + solution_vectors = VGroup(*[ + self.get_vector( + solution + x * null_space_basis, + rectangular_stem_width=0.025, + tip_length=0.2, + ) + for x in np.linspace(-4, 4, 20) + ]) + solution_vectors.set_color_by_gradient(YELLOW, MAROON_B) + + self.apply_matrix(matrix, path_arc=0) + self.wait() + self.show_det_equation() + self.wait() + + # Mention zero determinants + self.play(GrowArrow(vect_off_span_mob)) + self.play( + ShowCreation(vect_off_span_mob.circle), + Write(vect_off_span_words), + ) + self.wait() + self.play( + FadeOut(vect_off_span_mob.circle), + ReplacementTransform(vect_off_span_mob, vect_in_span_mob), + ReplacementTransform(vect_off_span_words, vect_in_span_words), + ReplacementTransform(vect_off_span_mob.circle, + vect_in_span_mob.circle), + ) + self.wait(2) + self.play( + FadeOut(vect_in_span_words), + FadeOut(vect_in_span_mob.circle), + ApplyMethod(moving_group.restore, run_time=2), + ReplacementTransform( + VGroup(vect_in_span_mob), + solution_vectors, + run_time=2, + ), + ) + self.wait() + + # Helpers + + def show_det_equation(self): + equation = self.equation + det_equation = TexMobject( + "\\det(", "A", ")", self.det_eq_symbol, "0" + ) + det_equation.scale(self.tex_scale_factor) + det_equation.next_to( + equation, DOWN, buff=MED_LARGE_BUFF, aligned_edge=LEFT) + det_rect = BackgroundRectangle(det_equation) + self.play( + FadeIn(det_rect), + Write(det_equation[0]), + ReplacementTransform( + equation.get_part_by_tex("A").copy(), + det_equation.get_part_by_tex("A").copy(), + ), + Write(det_equation[2:]), + ) + self.add_foreground_mobject(det_rect, det_equation) + + +class NonZeroDeterminantCase(ShowZeroDeterminantCase, SetupSimpleSystemOfEquations): + CONFIG = { + "det_eq_symbol": "\\neq" + } + + def construct(self): + self.add_equation() + self.show_det_equation() + + output_vect = self.output_vect + matrix = self.matrix + input_vect = np.dot(np.linalg.inv(matrix), output_vect) + + input_vect_mob = self.get_vector(input_vect, color=INPUT_COLOR) + output_vect_mob = self.get_vector(output_vect, color=OUTPUT_COLOR) + + input_vect_label = TextMobject("Input") + input_vect_label.next_to(input_vect_mob.get_end(), DOWN, SMALL_BUFF) + input_vect_label.match_color(input_vect_mob) + output_vect_label = TextMobject("Output") + output_vect_label.next_to(output_vect_mob.get_end(), DOWN, SMALL_BUFF) + output_vect_label.match_color(output_vect_mob) + for label in input_vect_label, output_vect_label: + label.scale(1.25, about_edge=UP) + label.add_background_rectangle() + + self.apply_matrix(matrix) + self.wait() + self.apply_inverse(matrix) + self.wait() + self.play( + GrowArrow(input_vect_mob), + Write(input_vect_label), + run_time=1 + ) + self.wait() + self.remove(input_vect_mob, input_vect_label) + self.apply_matrix(matrix, added_anims=[ + ReplacementTransform(input_vect_mob.copy(), output_vect_mob), + ReplacementTransform(input_vect_label.copy(), output_vect_label), + ], run_time=2) + self.wait() + self.remove(output_vect_mob, output_vect_label) + self.apply_inverse(matrix, added_anims=[ + ReplacementTransform(output_vect_mob.copy(), input_vect_mob), + ReplacementTransform(output_vect_label.copy(), input_vect_label), + ], run_time=2) + self.wait() + + +class ThinkOfPuzzleAsLinearCombination(SetupSimpleSystemOfEquations): + CONFIG = { + "output_vect": [-4, -2], + } + + def construct(self): + self.force_skipping() + super(ThinkOfPuzzleAsLinearCombination, self).construct() + self.revert_to_original_skipping_status() + + self.rearrange_equation_as_linear_combination() + self.show_linear_combination_of_vectors() + + def rearrange_equation_as_linear_combination(self): + system = self.matrix_system + corner_rect = self.corner_rect + matrix, input_vect, equals, output_vect = system + + columns = VGroup(*[ + VGroup(*matrix.mob_matrix[:, i].flatten()) + for i in 0, 1 + ]) + column_arrays = VGroup(*[ + MobjectMatrix(matrix.deepcopy().mob_matrix[:, i]) + for i in 0, 1 + ]) + for column_array in column_arrays: + column_array.match_height(output_vect) + x, y = input_vect.elements + movers = VGroup(x, y, equals, output_vect) + for mover in movers: + mover.generate_target() + plus = TexMobject("+") + + new_system = VGroup( + x.target, column_arrays[0], plus, + y.target, column_arrays[1], + equals.target, + output_vect.target + ) + new_system.arrange_submobjects(RIGHT, buff=SMALL_BUFF) + new_system.move_to(matrix, LEFT) + + corner_rect.generate_target() + corner_rect.target.stretch_to_fit_width( + new_system.get_width() + MED_LARGE_BUFF, + about_edge=LEFT + ) + + self.remove_foreground_mobjects(corner_rect, system) + self.play( + MoveToTarget(corner_rect), + FadeOut(input_vect.brackets), + ReplacementTransform(matrix.brackets, column_arrays[0].brackets), + ReplacementTransform(matrix.brackets.copy(), + column_arrays[1].brackets), + ReplacementTransform(columns[0], column_arrays[0].elements), + ReplacementTransform(columns[1], column_arrays[1].elements), + Write(plus, rate_func=squish_rate_func(smooth, 0.5, 1)), + *[ + MoveToTarget(mover, replace_mobject_with_target_in_scene=True) + for mover in movers + ], + path_arc=90 * DEGREES, + run_time=2 + ) + self.add_foreground_mobject(corner_rect, new_system) + self.wait() + + def show_linear_combination_of_vectors(self): + basis_vectors = self.basis_vectors + input_vect = np.dot(np.linalg.inv(self.matrix), self.output_vect) + origin = self.plane.coords_to_point(0, 0) + for basis, scalar in zip(basis_vectors, input_vect): + basis.ghost = basis.copy() + basis.ghost.set_color(average_color(basis.get_color(), BLACK)) + self.add_foreground_mobjects(basis.ghost, basis) + basis.generate_target() + basis_coords = np.array( + self.plane.point_to_coords(basis.get_end())) + new_coords = scalar * basis_coords + basis.target.put_start_and_end_on( + origin, self.plane.coords_to_point(*new_coords), + ) + + dashed_lines = VGroup(*[DashedLine(LEFT, RIGHT) for x in range(2)]) + + def update_dashed_lines(lines): + for i in 0, 1: + lines[i].put_start_and_end_on( + basis_vectors[i].get_start(), + basis_vectors[i].get_end(), + ) + lines[i].shift(basis_vectors[1 - i].get_end() - origin) + return lines + update_dashed_lines(dashed_lines) + self.play(LaggedStart(ShowCreation, dashed_lines, lag_ratio=0.7)) + for basis in basis_vectors: + self.play( + MoveToTarget(basis, run_time=2), + UpdateFromFunc(dashed_lines, update_dashed_lines) + ) + self.wait() + + +class WrongButHelpful(TeacherStudentsScene): + def construct(self): + self.teacher_says("What's next is wrong, \\\\ but helpful") + self.change_student_modes("sassy", "sad", "angry") + self.wait(3) + + +class LookAtDotProducts(SetupSimpleSystemOfEquations): + CONFIG = { + "quit_before_final_transformation": True, + "equation_scale_factor": 0.7, + } + + def construct(self): + self.force_skipping() + super(LookAtDotProducts, self).construct() + self.revert_to_original_skipping_status() + + self.remove_corner_system() + self.show_dot_products() + + def remove_corner_system(self): + to_remove = [self.corner_rect, self.matrix_system] + self.remove_foreground_mobjects(*to_remove) + self.remove(*to_remove) + + q_marks = self.q_marks + input_vect_mob = self.input_vect_mob + + equations = self.equations = VGroup() + for i in 0, 1: + basis = [0, 0] + basis[i] = 1 + equation = VGroup( + Matrix(["x", "y"]), + TexMobject("\\cdot"), + IntegerMatrix(basis), + TexMobject("="), + TexMobject(["x", "y"][i]), + ) + for part in equation: + if isinstance(part, Matrix): + part.scale(self.array_scale_factor) + equation[2].elements.set_color([X_COLOR, Y_COLOR][i]) + equation.arrange_submobjects(RIGHT, buff=SMALL_BUFF) + equation.scale(self.equation_scale_factor) + equations.add(equation) + equations.arrange_submobjects(DOWN, buff=MED_LARGE_BUFF) + equations.to_corner(UL) + corner_rect = self.corner_rect = BackgroundRectangle( + equations, opacity=0.8) + self.resize_corner_rect_to_mobjet(corner_rect, equations) + corner_rect.save_state() + self.resize_corner_rect_to_mobjet(corner_rect, equations[0]) + + xy_vect_mob = Matrix(["x", "y"], include_background_rectangle=True) + xy_vect_mob.scale(self.array_scale_factor) + xy_vect_mob.next_to(input_vect_mob.get_end(), DOWN, SMALL_BUFF) + q_marks.add_background_rectangle() + q_marks.next_to(xy_vect_mob, RIGHT) + + origin = self.plane.coords_to_point(0, 0) + input_vect_end = input_vect_mob.get_end() + x_point = (input_vect_end - origin)[0] * RIGHT + origin + y_point = (input_vect_end - origin)[1] * UP + origin + v_dashed_line = DashedLine(input_vect_end, x_point) + h_dashed_line = DashedLine(input_vect_end, y_point) + + h_brace = Brace(Line(x_point, origin), UP, buff=SMALL_BUFF) + v_brace = Brace(Line(y_point, origin), RIGHT, buff=SMALL_BUFF) + + self.add(xy_vect_mob) + self.wait() + self.play( + FadeIn(corner_rect), + FadeIn(equations[0][:-1]), + ShowCreation(v_dashed_line), + GrowFromCenter(h_brace), + ) + self.play( + ReplacementTransform(h_brace.copy(), equations[0][-1]) + ) + self.wait() + self.play( + corner_rect.restore, + Animation(equations[0]), + FadeIn(equations[1]), + ) + self.wait() + self.play( + ReplacementTransform(equations[1][-1].copy(), v_brace), + ShowCreation(h_dashed_line), + GrowFromCenter(v_brace) + ) + self.wait() + + self.to_fade = VGroup( + h_dashed_line, v_dashed_line, + h_brace, v_brace, + xy_vect_mob, q_marks, + ) + + def show_dot_products(self): + moving_equations = self.equations.copy() + transformed_equations = VGroup() + implications = VGroup() + transformed_input_rects = VGroup() + transformed_basis_rects = VGroup() + for equation in moving_equations: + equation.generate_target() + xy_vect, dot, basis, equals, coord = equation.target + T1, lp1, rp1 = TexMobject("T", "(", ")") + lp1.scale(1, about_edge=LEFT) + rp1.scale(1, about_edge=LEFT) + for paren in lp1, rp1: + paren.stretch_to_fit_height(equation.get_height()) + T2, lp2, rp2 = T1.copy(), lp1.copy(), rp1.copy() + transformed_equation = VGroup( + T1, lp1, xy_vect, rp1, dot, + T2, lp2, basis, rp2, equals, + coord + ) + transformed_equation.arrange_submobjects(RIGHT, buff=SMALL_BUFF) + # transformed_equation.scale(self.equation_scale_factor) + + implies = TexMobject("\\Rightarrow").scale(1.2) + implies.next_to(equation, RIGHT) + implies.set_color(BLUE) + + transformed_equation.next_to(implies, RIGHT) + implications.add(implies) + + transformed_input_rects.add(SurroundingRectangle( + transformed_equation[:4], + color=OUTPUT_COLOR + )) + transformed_basis_rects.add(SurroundingRectangle( + transformed_equation[5:5 + 4], + color=basis.elements.get_color() + )) + + for mob in [implies]: + mob.add(TexMobject("?").next_to(mob, UP, SMALL_BUFF)) + + transformed_equation.parts_to_write = VGroup( + T1, lp1, rp1, T2, lp2, rp2 + ) + + transformed_equations.add(transformed_equation) + + corner_rect = self.corner_rect + corner_rect.generate_target() + group = VGroup(self.equations, transformed_equations) + self.resize_corner_rect_to_mobjet(corner_rect.target, group) + + for array in [self.output_vect_label] + list(self.column_mobs): + array.rect = SurroundingRectangle(array) + array.rect.match_color(array.elements) + + self.play( + MoveToTarget(corner_rect), + Animation(self.equations), + FadeOut(self.to_fade), + LaggedStart(Write, implications), + ) + self.remove(self.input_vect_mob) + self.apply_matrix(self.matrix, added_anims=[ + Animation(VGroup(corner_rect, self.equations, implications)), + MoveToTarget(moving_equations[0]), + LaggedStart(FadeIn, transformed_equations[0].parts_to_write), + FadeIn(self.column_mobs), + ReplacementTransform( + self.input_vect_mob.copy(), self.output_vect_mob) + ]) + self.play( + MoveToTarget(moving_equations[1]), + LaggedStart(FadeIn, transformed_equations[1].parts_to_write), + path_arc=-30 * DEGREES, + run_time=2 + ) + self.wait() + + # Show rectangles + self.play( + LaggedStart(ShowCreation, transformed_input_rects, lag_ratio=0.8), + ShowCreation(self.output_vect_label.rect), + ) + for tbr, column_mob in zip(transformed_basis_rects, self.column_mobs): + self.play( + ShowCreation(tbr), + ShowCreation(column_mob.rect), + ) + self.wait() + self.play(FadeOut(VGroup( + transformed_input_rects, + transformed_basis_rects, + self.output_vect_label.rect, + *[cm.rect for cm in self.column_mobs] + ))) + + # These computations assume plane is centered at ORIGIN + output_vect = self.output_vect_mob.get_end() + c1 = self.basis_vectors[0].get_end() + c2 = self.basis_vectors[1].get_end() + x_point = c1 * np.dot(output_vect, c1) / (np.linalg.norm(c1)**2) + y_point = c2 * np.dot(output_vect, c2) / (np.linalg.norm(c2)**2) + + dashed_line_to_x = DashedLine(self.output_vect_mob.get_end(), x_point) + dashed_line_to_y = DashedLine(self.output_vect_mob.get_end(), y_point) + + self.play(ShowCreation(dashed_line_to_x)) + self.play(ShowCreation(dashed_line_to_y)) + self.wait() + + # Helpers + + def resize_corner_rect_to_mobjet(self, rect, mobject): + rect.stretch_to_fit_width( + mobject.get_width() + MED_LARGE_BUFF + SMALL_BUFF) + rect.stretch_to_fit_height( + mobject.get_height() + MED_LARGE_BUFF + SMALL_BUFF) + rect.to_corner(UL, buff=0) + return rect + + +class NotAtAllTrue(NotTheMostComputationallyEfficient): + CONFIG = { + "words": "Not at all \\\\ True", + "word_height": 4, + } + + +class ShowDotProductChanging(LinearTransformationScene): + CONFIG = { + "matrix": [[2, -5.0 / 3], [-5.0 / 3, 2]], + "v": [1, 2], + "w": [2, 1], + "v_label": "v", + "w_label": "w", + "v_color": YELLOW, + "w_color": MAROON_B, + "rhs1": "> 0", + "rhs2": "< 0", + "foreground_plane_kwargs": { + "x_radius": 2 * FRAME_WIDTH, + "y_radius": 2 * FRAME_HEIGHT, + }, + "equation_scale_factor": 1.5, + } + + def construct(self): + v_mob = self.add_vector(self.v, self.v_color, animate=False) + w_mob = self.add_vector(self.w, self.w_color, animate=False) + kwargs = { + "transformation_name": "T", + "at_tip": True, + "animate": False, + } + v_label = self.add_transformable_label(v_mob, self.v_label, **kwargs) + w_label = self.add_transformable_label(w_mob, self.w_label, **kwargs) + + start_equation = self.get_equation(v_label, w_label, self.rhs1) + start_equation.to_corner(UR) + self.play( + Write(start_equation[0::2]), + ReplacementTransform(v_label.copy(), start_equation[1]), + ReplacementTransform(w_label.copy(), start_equation[3]), + ) + self.wait() + self.add_foreground_mobject(start_equation) + self.apply_matrix(self.matrix) + self.wait() + + end_equation = self.get_equation(v_label, w_label, self.rhs2) + end_equation.next_to(start_equation, DOWN, aligned_edge=RIGHT) + self.play( + FadeIn(end_equation[0]), + ReplacementTransform( + start_equation[2::2].copy(), + end_equation[2::2], + ), + ReplacementTransform(v_label.copy(), end_equation[1]), + ReplacementTransform(w_label.copy(), end_equation[3]), + ) + self.wait(2) + + def get_equation(self, v_label, w_label, rhs): + equation = VGroup( + v_label.copy(), + TexMobject("\\cdot"), + w_label.copy(), + TexMobject(rhs), + ) + equation.arrange_submobjects(RIGHT, buff=SMALL_BUFF) + equation.add_to_back(BackgroundRectangle(equation)) + equation.scale(self.equation_scale_factor) + return equation + + +class ShowDotProductChangingAwayFromZero(ShowDotProductChanging): + CONFIG = { + "matrix": [[2, 2], [0, -1]], + "v": [1, 0], + "w": [0, 1], + "v_label": "x", + "w_label": "y", + "v_color": X_COLOR, + "w_color": Y_COLOR, + "rhs1": "= 0", + "rhs2": "\\ne 0", + } + + +class OrthonormalWords(Scene): + def construct(self): + v_tex = "\\vec{\\textbf{v}}" + w_tex = "\\vec{\\textbf{w}}" + top_words = TexMobject( + "\\text{If }", + "T(", v_tex, ")", "\\cdot", + "T(", w_tex, ")", "=", + v_tex, "\\cdot", w_tex, + "\\text{ for all }", v_tex, "\\text{ and }", w_tex, + ) + top_words.set_color_by_tex_to_color_map({ + v_tex: YELLOW, + w_tex: MAROON_B, + }) + bottom_words = TextMobject( + "$T$", "is", "``Orthonormal''" + ) + bottom_words.set_color_by_tex("Orthonormal", BLUE) + words = VGroup(top_words, bottom_words) + words.arrange_submobjects(DOWN, buff=MED_LARGE_BUFF) + for word in words: + word.add_background_rectangle() + words.to_edge(UP) + + self.play(Write(words)) + self.wait() + + +class ShowSomeOrthonormalTransformations(LinearTransformationScene): + CONFIG = { + "random_seed": 1, + "n_angles": 5 + } + + def construct(self): + for x in range(self.n_angles): + angle = TAU * np.random.random() - TAU / 2 + matrix = rotation_matrix(angle, OUT)[:2, :2] + self.apply_matrix(matrix) + + +class SolvingASystemWithOrthonormalMatrix(LinearTransformationScene): + CONFIG = { + "array_scale_factor": 0.6, + } + + def construct(self): + # Setup system + angle = TAU / 12 + matrix = np.array([ + [np.cos(angle), -np.sin(angle)], + [np.sin(angle), np.cos(angle)] + ]) + output_vect = [1, 2] + symbolic_matrix = [ + ["\\cos(30^\\circ)", "-\\sin(30^\\circ)"], + ["\\sin(30^\\circ)", "\\cos(30^\\circ)"], + ] + system = LinearSystem( + matrix=symbolic_matrix, + output_vect=output_vect, + matrix_config={ + "h_buff": 2.5, + "element_to_mobject": TexMobject, + }, + height=1.25, + ) + system.to_corner(UL) + system.matrix_mobject.set_color_columns(X_COLOR, Y_COLOR) + system.input_vect_mob.elements.set_color(WHITE) + system_rect = BackgroundRectangle(system, buff=MED_SMALL_BUFF) + + matrix_brace = Brace(system.matrix_mobject, DOWN, buff=SMALL_BUFF) + orthonomal_label = TextMobject("Orthonormal") + orthonomal_label.set_color(WHITE) + orthonomal_label.next_to(matrix_brace, DOWN, SMALL_BUFF) + orthonomal_label.add_background_rectangle() + + # Input and output vectors + output_vect_mob = self.get_vector(output_vect, color=OUTPUT_COLOR) + output_vect_label = system.output_vect_mob.copy() + output_vect_label.add_background_rectangle() + output_vect_label.scale(self.array_scale_factor) + output_vect_label.next_to( + output_vect_mob.get_end(), RIGHT, buff=SMALL_BUFF) + + input_vect = np.dot(np.linalg.inv(matrix), output_vect) + input_vect_mob = self.get_vector(input_vect, color=INPUT_COLOR) + input_vect_label = TextMobject("Mystery input vector") + input_vect_label.add_background_rectangle() + input_vect_label.next_to(input_vect_mob.get_end(), RIGHT, SMALL_BUFF) + input_vect_label.match_color(input_vect_mob) + + # Column arrays + column_mobs = VGroup() + for i, vect in zip(range(2), [DR, DL]): + elements = system.matrix_mobject.deepcopy().mob_matrix[:, i] + column_mob = MobjectMatrix(elements) + column_mob.add_background_rectangle() + column_mob.scale(self.array_scale_factor) + column_mob.next_to( + self.get_vector(matrix[:, i]).get_end(), vect, + buff=SMALL_BUFF + ) + column_mobs.add(column_mob) + column_mobs[1].shift(SMALL_BUFF * UP) + + # Dot product lines + x_point = self.plane.coords_to_point(input_vect[0], 0) + y_point = self.plane.coords_to_point(0, input_vect[1]) + input_dashed_lines = VGroup( + DashedLine(input_vect_mob.get_end(), x_point), + DashedLine(input_vect_mob.get_end(), y_point), + ) + output_dashed_lines = input_dashed_lines.copy() + output_dashed_lines.apply_matrix(matrix) + + self.add_foreground_mobjects(system_rect, system) + self.add_foreground_mobjects(matrix_brace, orthonomal_label) + self.add_foreground_mobjects(output_vect_mob, output_vect_label) + self.plane.set_stroke(width=2) + + self.apply_matrix(matrix) + self.wait() + self.play(LaggedStart(ShowCreation, output_dashed_lines)) + self.play(*self.get_column_animations(system.matrix_mobject, column_mobs)) + self.wait() + self.remove(*output_dashed_lines) + self.apply_inverse(matrix, added_anims=[ + FadeOut(column_mobs), + ReplacementTransform(output_vect_mob.copy(), input_vect_mob), + ReplacementTransform( + output_dashed_lines.copy(), input_dashed_lines), + FadeIn(input_vect_label), + ]) + self.wait() + self.remove(input_dashed_lines, input_vect_mob) + self.apply_matrix(matrix, added_anims=[ + FadeOut(input_vect_label), + ReplacementTransform(input_vect_mob.copy(), output_vect_mob), + ReplacementTransform(input_dashed_lines.copy(), + output_dashed_lines), + FadeIn(column_mobs), + ]) + + # Write dot product equations + equations = VGroup() + for i in 0, 1: + moving_output_vect_label = output_vect_label.copy() + moving_column_mob = column_mobs[i].copy() + moving_var = system.input_vect_mob.elements[i].copy() + equation = VGroup( + moving_var.generate_target(), + TexMobject("="), + moving_output_vect_label.generate_target(), + TexMobject("\\cdot"), + moving_column_mob.generate_target() + ) + equation.movers = VGroup( + moving_var, moving_output_vect_label, moving_column_mob) + equation.to_write = equation[1::2] + equation[2].match_height(equation[4]) + equation.arrange_submobjects(RIGHT, buff=SMALL_BUFF) + equation.background_rectangle = BackgroundRectangle(equation) + equation.add_to_back(equation.background_rectangle) + equations.add(equation) + equations.arrange_submobjects(DOWN, buff=MED_LARGE_BUFF) + equations.to_corner(UR) + + for i, equation in enumerate(equations): + self.play( + FadeIn(equation.background_rectangle), + Write(equation.to_write), + LaggedStart(MoveToTarget, equation.movers, + path_arc=60 * DEGREES), + ) + self.wait() + + def get_column_animations(self, matrix_mobject, column_mobs): + def get_kwargs(i): + return { + "rate_func": squish_rate_func(smooth, 0.4 * i, 0.6 + 0.4 * i), + "run_time": 2, + } + return list(it.chain(*[ + [ + FadeIn(cm[0], **get_kwargs(i)), + ReplacementTransform( + matrix_mobject.brackets.copy(), + cm.brackets, + **get_kwargs(i) + ), + ReplacementTransform( + VGroup(*matrix_mobject.mob_matrix[:, i]).copy(), + cm.elements, + **get_kwargs(i) + ), + ] + for i, cm in enumerate(column_mobs) + ])) + + +class TransitionToParallelogramIdea(TeacherStudentsScene): + def construct(self): + teacher_words = TextMobject( + "Now ask about \\\\ other geometric \\\\ views of", + "$x$", "and", "$y$" + ) + teacher_words.set_color_by_tex_to_color_map({ + "$x$": X_COLOR, + "$y$": Y_COLOR, + }) + + self.student_says( + "But that's a super \\\\ specific case", + target_mode="sassy", + added_anims=[self.teacher.change, "guilty"] + ) + self.change_student_modes("confused", "sassy", "angry") + self.wait() + self.teacher_says( + teacher_words, + added_anims=[self.get_student_changes(*["pondering"] * 3)] + ) + self.wait() + + +class TransformingAreasYCoord(LinearTransformationScene): + CONFIG = { + # Determines whether x-coordinate or y-coordinate is computed + "index": 1, + "matrix": [[2, -1], [0, 1]], + "input_vect": [3, 2], + "array_scale_factor": 0.7, + } + + def construct(self): + self.init_matrix() + self.init_plane() + self.show_coord_parallelogram() + self.transform_space() + self.solve_for_coord() + + def init_matrix(self): + self.matrix = np.array(self.matrix) + + def init_plane(self): + self.plane.set_stroke(width=2) + + def show_coord_parallelogram(self): + index = self.index + non_index = (index + 1) % 2 + + input_vect = self.input_vect + input_vect_mob = self.get_vector(input_vect, color=INPUT_COLOR) + input_vect_label = Matrix(["x", "y"]) + input_vect_label.add_background_rectangle() + input_vect_label.scale(self.array_scale_factor) + self.set_input_vect_label_position(input_vect_mob, input_vect_label) + + mystery_words = TextMobject("Mystery input vector") + mystery_words.next_to(input_vect_label, RIGHT) + mystery_words.add_background_rectangle() + + # Add basis vector labels + basis_labels = self.basis_labels = VGroup() + basis_vectors = self.basis_vectors + chars = ["\\imath", "\\jmath"] + directions = ["right", "left"] + for basis, char, direction in zip(basis_vectors, chars, directions): + label = self.get_vector_label( + basis, "\\mathbf{\\hat{%s}}" % char, + direction=direction + ) + self.basis_labels.add(label) + + ip = self.get_input_parallelogram(input_vect_mob) + area_arrow_direction = 1.5 * DOWN + RIGHT if self.index == 0 else DR + area_arrow = Vector( + area_arrow_direction, color=WHITE, + rectangular_stem_width=0.025, + tip_length=0.2, + ) + area_arrow.shift(ip.get_center() - area_arrow.get_end() + SMALL_BUFF * DL) + area_words = TexMobject( + "\\text{Area}", "=", "1", "\\times", + ["x", "y"][index] + ) + area_words.next_to( + area_arrow.get_start(), UL, SMALL_BUFF, + submobject_to_align=area_words[0] + ) + area_words.set_color_by_tex_to_color_map({ + "Area": YELLOW, + "x": X_COLOR, + "y": Y_COLOR, + }) + area_words.rect = BackgroundRectangle(area_words) + + origin = self.plane.coords_to_point(0, 0) + unit_brace = Brace( + Line(origin, basis_vectors[non_index].get_end()), + [DOWN, LEFT][non_index], + buff=SMALL_BUFF + ) + one = unit_brace.get_tex("1") + one.add_background_rectangle() + coord_brace = self.get_parallelogram_braces(ip)[index] + + self.add(input_vect_mob, input_vect_label) + self.add(basis_labels) + + self.play( + FadeIn(ip), + Animation(VGroup(basis_vectors, input_vect_mob)) + ) + self.play( + ShowCreationThenDestruction(SurroundingRectangle(basis_labels[non_index])), + GrowArrow(self.basis_vectors[non_index].copy(), remover=True) + ) + self.play( + ShowCreationThenDestruction(SurroundingRectangle(input_vect_label)), + GrowArrow(input_vect_mob.copy(), remover=True), + ) + self.wait() + self.play( + FadeIn(area_words.rect), + Write(area_words[:2]), + GrowArrow(area_arrow), + ) + self.wait() + self.play( + FadeOut(basis_labels), + GrowFromCenter(unit_brace), + FadeIn(one), + FadeIn(area_words[2]) + ) + self.wait() + self.play( + GrowFromCenter(coord_brace), + FadeIn(coord_brace.label), + FadeIn(area_words[3:]), + ) + self.wait() + self.play( + area_words.rect.stretch_to_fit_width, + area_words[:3].get_width() + SMALL_BUFF, {"about_edge": LEFT}, + FadeOut(area_words[2:4]), + area_words[4].shift, + (area_words[2].get_left()[0] - area_words[4].get_left()[0]) * RIGHT, + Animation(area_words[:2]), + ) + area_words.remove(*area_words[2:4]) + self.wait() + + # Run with me + morty = Mortimer(height=2).flip() + morty.to_corner(DL) + randy = Randolph(height=2, color=BLUE_C).flip() + randy.move_to(4 * RIGHT) + randy.to_edge(DOWN) + self.play(FadeIn(randy)) + self.play(randy.change, "confused", ip) + self.play(Blink(randy), FadeIn(morty)) + self.play( + PiCreatureSays(morty, "Run with \\\\ me here...", look_at_arg=randy.eyes), + randy.look_at, morty.eyes, + ) + self.play(Blink(morty)) + self.play(FadeOut(VGroup(morty, morty.bubble, morty.bubble.content, randy))) + + # Signed area + signed = TextMobject("Signed") + signed.match_color(area_words[0]) + signed.next_to(area_words, LEFT) + + brace_group = VGroup(coord_brace, coord_brace.label) + + def update_brace_group(brace_group): + new_brace = self.get_parallelogram_braces(ip)[index] + new_group = VGroup(new_brace, new_brace.label) + Transform(brace_group, new_group).update(1) + + area_words.add_to_back(signed) + self.play( + area_words.rect.stretch_to_fit_width, area_words.get_width(), + {"about_edge": RIGHT}, + Write(signed), + Animation(area_words), + ) + self.play( + UpdateFromFunc( + ip, lambda m: Transform(m, self.get_input_parallelogram(input_vect_mob)).update(1) + ), + input_vect_mob.rotate, np.pi, {"about_point": origin}, + Animation(self.basis_vectors), + UpdateFromFunc(brace_group, update_brace_group), + UpdateFromFunc( + input_vect_label, + lambda ivl: self.set_input_vect_label_position(input_vect_mob, ivl) + ), + MaintainPositionRelativeTo(area_arrow, ip), + MaintainPositionRelativeTo(area_words.rect, area_arrow), + MaintainPositionRelativeTo(area_words, area_arrow), + run_time=9, + rate_func=there_and_back_with_pause, + ) + + # Fade out unneeded bits + self.play(LaggedStart(FadeOut, VGroup( + unit_brace, one, coord_brace, coord_brace.label, + ))) + + self.input_parallelogram = ip + self.area_words = area_words + self.area_arrow = area_arrow + self.input_vect_mob = input_vect_mob + self.input_vect_label = input_vect_label + + def transform_space(self): + matrix = self.matrix + ip = self.input_parallelogram + area_words = self.area_words + area_arrow = self.area_arrow + input_vect_mob = self.input_vect_mob + input_vect_label = self.input_vect_label + basis_vectors = self.basis_vectors + index = self.index + non_index = (index + 1) % 2 + + apply_words = TextMobject("Apply") + apply_words.add_background_rectangle() + matrix_mobject = IntegerMatrix(self.matrix) + matrix_mobject.set_color_columns(X_COLOR, Y_COLOR) + matrix_mobject.add_background_rectangle() + matrix_mobject.next_to(apply_words, RIGHT) + matrix_brace = Brace(matrix_mobject, DOWN, buff=SMALL_BUFF) + matrix_label = matrix_brace.get_tex("A") + matrix_label.add_background_rectangle() + apply_group = VGroup(apply_words, matrix_mobject, matrix_brace, matrix_label) + apply_group.to_corner(UL, buff=MED_SMALL_BUFF) + + area_scale_words = TextMobject("All areas get scaled by", "$\\det(A)$") + area_scale_words.scale(1.5) + area_scale_words.move_to(2 * DOWN) + area_scale_words.add_background_rectangle() + + blobs = VGroup( + Circle(radius=0.5).move_to(2 * LEFT + UP), + Square(side_length=1).rotate(TAU / 12).move_to(2 * UP + 0.5 * RIGHT), + TexMobject("\\pi").scale(3).move_to(3 * RIGHT) + ) + blobs.set_stroke(YELLOW, 3) + blobs.set_fill(YELLOW, 0.3) + + # Initial transform + self.add_transformable_mobject(ip) + self.add(self.basis_vectors) + self.add_vector(input_vect_mob, animate=False) + self.play( + Write(apply_words), FadeIn(matrix_mobject), + GrowFromCenter(matrix_brace), Write(matrix_label), + ) + self.add_foreground_mobjects(apply_group) + self.play(*map(FadeOut, [ + area_words.rect, area_words, area_arrow, input_vect_label, + ])) + self.apply_matrix(matrix) + self.wait(2) + self.apply_inverse(matrix, run_time=0) + + # Show many areas + self.play( + LaggedStart(DrawBorderThenFill, blobs), + Write(area_scale_words) + ) + self.add_transformable_mobject(blobs) + self.add_foreground_mobject(area_scale_words) + self.apply_matrix(matrix) + + # Ask about parallelogram + ip_copy = ip.copy() + ip_copy.set_stroke(BLACK, 4) + ip_copy.set_fill(BLACK, 0) + + q_marks = TexMobject("???") + q_marks.scale(1.5) + q_marks.rect = BackgroundRectangle(q_marks) + q_marks_group = VGroup(q_marks, q_marks.rect) + q_marks_group.rotate(input_vect_mob.get_angle()) + q_marks_group.move_to(ip) + + column_mobs = VGroup() + for i, vect in zip(range(2), [DOWN, LEFT]): + column = matrix_mobject.deepcopy().mob_matrix[:, i] + column_mob = MobjectMatrix(column) + column_mob.scale(self.array_scale_factor) + column_mob.next_to(basis_vectors[i].get_end(), vect) + column_mob.add_background_rectangle() + column_mobs.add(column_mob) + column_mob = column_mobs[non_index] + + transformed_input_vect_label = VGroup(input_vect_label.copy()) + transformed_input_vect_label.add_to_back(matrix_label.copy()) + transformed_input_vect_label.arrange_submobjects(RIGHT, buff=SMALL_BUFF) + transformed_input_vect_label.next_to(input_vect_mob.get_end(), UP) + + self.play( + ShowPassingFlash(ip_copy), + FadeIn(q_marks.rect), + Animation(ip), + Animation(basis_vectors), + Animation(input_vect_mob), + Write(q_marks), + LaggedStart(FadeOut, blobs), + ) + self.transformable_mobjects.remove(blobs) + self.play( + FadeIn(column_mob.background_rectangle), + ReplacementTransform( + matrix_mobject.brackets.copy(), column_mob.brackets + ), + ReplacementTransform( + VGroup(*matrix_mobject.mob_matrix[:, non_index]).copy(), + column_mob.elements + ), + ) + self.wait() + self.play( + ReplacementTransform(matrix_label.copy(), transformed_input_vect_label[0]), + FadeIn(transformed_input_vect_label[1]) + ) + self.wait(2) + + # Back to input state + self.remove(q_marks.rect) + self.apply_inverse(matrix, added_anims=[ + FadeOut(q_marks), + FadeOut(transformed_input_vect_label), + FadeOut(column_mob), + FadeIn(area_words.rect), + FadeIn(area_words), + FadeIn(area_arrow), + ]) + self.wait(2) + + # Show how parallelogram scales by det(A) + self.apply_matrix(matrix, added_anims=[ + UpdateFromFunc( + area_arrow, + lambda a: a.put_start_and_end_on( + area_arrow.get_start(), ip.get_center() + SMALL_BUFF * DL + ) + ), + Animation(area_words.rect), + Animation(area_words), + ]) + + det_A = area_scale_words.get_part_by_tex("det").copy() + det_A.generate_target() + det_A.target.scale(1.0 / 1.5) + det_A.target.next_to( + area_words[1:3], RIGHT, SMALL_BUFF, + aligned_edge=DOWN, + submobject_to_align=det_A.target[0] + ) + coord = area_words[-1] + coord.generate_target() + coord.target.next_to(det_A.target, RIGHT, SMALL_BUFF) + + self.play( + area_words.rect.match_width, VGroup(area_words, coord.target), + {"stretch": True, "about_edge": LEFT}, + Animation(area_words), + MoveToTarget(det_A), + MoveToTarget(coord), + ) + self.wait(2) + area_words.submobjects.insert(-1, det_A) + + self.area_scale_words = area_scale_words + self.apply_group = apply_group + + def solve_for_coord(self): + apply_group = self.apply_group + area_words = self.area_words.copy() + index = self.index + non_index = (index + 1) % 2 + + # Setup rearrangement + signed, area, equals, det, coord = area_words + for part in area_words: + part.add_background_rectangle() + part.generate_target() + apply_word, matrix_mobject, matrix_brace, matrix_label = apply_group + + h_line = Line(LEFT, RIGHT).match_width(det) + frac = VGroup(area.target, h_line, det.target) + frac.arrange_submobjects(DOWN) + coord_equation = VGroup(coord.target, equals.target, frac) + equals.target.next_to(coord.target, RIGHT) + frac.next_to(equals.target, RIGHT, submobject_to_align=h_line) + coord_equation.next_to(ORIGIN, DOWN, buff=1.2) + coord_equation.to_edge(LEFT) + + output_vect = np.dot(self.matrix, self.input_vect) + new_matrix = np.array(self.matrix) + new_matrix[:, self.index] = output_vect + + # Setup rhs + frac_matrix_height = 1.5 + matrix_mobject_copy = matrix_mobject.copy() + matrix_mobject_copy.scale_to_fit_height(frac_matrix_height) + denom_det_text = get_det_text(matrix_mobject_copy) + top_matrix_mobject = IntegerMatrix(new_matrix) + top_matrix_mobject.scale_to_fit_height(frac_matrix_height) + top_matrix_mobject.set_color_columns(X_COLOR, Y_COLOR) + VGroup(*top_matrix_mobject.mob_matrix[:, self.index]).set_color(MAROON_B) + top_matrix_mobject.add_background_rectangle() + num_det_text = get_det_text(top_matrix_mobject) + rhs_h_line = Line(LEFT, RIGHT) + rhs_h_line.match_width(num_det_text) + rhs = VGroup( + VGroup(num_det_text, top_matrix_mobject), + rhs_h_line, + VGroup(matrix_mobject_copy, denom_det_text) + ) + rhs.arrange_submobjects(DOWN, buff=SMALL_BUFF) + rhs_equals = TexMobject("=") + rhs_equals.next_to(h_line, RIGHT) + rhs.next_to(rhs_equals, submobject_to_align=rhs_h_line) + + # Setup linear system + output_vect_label = IntegerMatrix(output_vect) + output_vect_label.elements.match_color(self.input_vect_mob) + output_vect_label.scale(self.array_scale_factor) + output_vect_label.add_background_rectangle() + self.set_input_vect_label_position(self.input_vect_mob, output_vect_label) + + matrix_mobject.generate_target() + system_input = Matrix(["x", "y"]) + system_input.add_background_rectangle() + system_output = output_vect_label.copy() + system_output.generate_target() + system_eq = TexMobject("=") + for array in system_input, system_output.target: + array.match_height(matrix_mobject.target) + system = VGroup( + matrix_mobject.target, + system_input, system_eq, system_output.target + ) + system.arrange_submobjects(RIGHT, buff=SMALL_BUFF) + system.to_corner(UL) + + # Rearrange + self.play( + FadeOut(self.area_scale_words), + ShowCreation(h_line), + *map(MoveToTarget, area_words[1:]), + run_time=3 + ) + self.wait() + self.play( + ReplacementTransform(matrix_mobject.copy(), matrix_mobject_copy), + Write(rhs_equals), + Write(denom_det_text), + ShowCreation(rhs_h_line) + ) + self.wait(2) + + # Show output coord + self.play( + FadeIn(output_vect_label.background_rectangle), + ReplacementTransform( + self.input_vect_mob.copy(), + output_vect_label.elements, + ), + Write(output_vect_label.brackets), + ) + self.wait() + self.play( + FadeOut(apply_word), + MoveToTarget(matrix_mobject), + MaintainPositionRelativeTo(matrix_brace, matrix_mobject), + MaintainPositionRelativeTo(matrix_label, matrix_mobject), + ) + self.play( + FadeIn(system_input), + FadeIn(system_eq), + MoveToTarget(system_output), + area_words.rect.stretch_to_fit_width, self.area_words[1:].get_width(), + {"about_edge": RIGHT}, + Animation(self.area_words[1:]), + FadeOut(self.area_words[0]), + ) + self.wait() + replaced_column = VGroup(*top_matrix_mobject.mob_matrix[:, self.index]) + replaced_column.set_fill(opacity=0) + self.play( + ReplacementTransform(matrix_mobject_copy.copy(), top_matrix_mobject), + ReplacementTransform(denom_det_text.copy(), num_det_text), + ) + self.wiggle_vector(self.basis_vectors[non_index]) + self.wait() + self.wiggle_vector(self.input_vect_mob) + self.remove(replaced_column) + self.play( + Transform( + output_vect_label.elements.copy(), + replaced_column.copy().set_fill(opacity=1), + remover=True + ) + ) + replaced_column.set_fill(opacity=1) + self.wait() + + # Circle equation + equation = VGroup( + area_words[1:], h_line, + rhs_equals, rhs, + ) + equation_rect = SurroundingRectangle(equation) + equation_rect.set_stroke(YELLOW, 3) + equation_rect.set_fill(BLACK, 0.9) + + self.play( + DrawBorderThenFill(equation_rect), + Animation(equation) + ) + self.wait() + + # Helpers + + def get_input_parallelogram(self, input_vect_mob): + input_vect = np.array(self.plane.point_to_coords(input_vect_mob.get_end())) + matrix = self.matrix + dim = matrix.shape[0] + # Transofmration from unit square to input parallelogram + square_to_ip = self.square_to_ip = np.identity(dim) + square_to_ip[:, self.index] = np.array(input_vect) + + ip = self.get_unit_square() + ip.apply_matrix(self.square_to_ip) + if input_vect[self.index] < 0: + ip.set_color(GOLD) + + return ip + + def get_parallelogram_braces(self, input_parallelogram): + braces = VGroup(*[ + Brace( + input_parallelogram, vect, + buff=SMALL_BUFF, + min_num_quads=3, + max_num_quads=3, + ) + for vect in DOWN, RIGHT + ]) + for brace, tex, color in zip(braces, "xy", [X_COLOR, Y_COLOR]): + brace.label = brace.get_tex(tex, buff=SMALL_BUFF) + brace.label.add_background_rectangle() + brace.label.set_color(color) + return braces + + def set_input_vect_label_position(self, input_vect_mob, input_vect_label): + direction = np.sign(input_vect_mob.get_vector()) + input_vect_label.next_to(input_vect_mob.get_end(), direction, buff=SMALL_BUFF) + return input_vect_label + + def wiggle_vector(self, vector_mob): + self.play( + Rotate( + vector_mob, 15 * DEGREES, + about_point=ORIGIN, + rate_func=wiggle, + ) + ) + + +class TransformingAreasXCoord(TransformingAreasYCoord): + CONFIG = { + "index": 0, + } + + +class WriteCramersRule(Scene): + def construct(self): + words = TextMobject("``Cramer's Rule''") + words.scale_to_fit_width(FRAME_WIDTH - LARGE_BUFF) + self.play(Write(words)) + self.wait() diff --git a/mobject/matrix.py b/mobject/matrix.py index e763032a..2c1fe1dd 100644 --- a/mobject/matrix.py +++ b/mobject/matrix.py @@ -2,8 +2,10 @@ from __future__ import absolute_import import numpy as np -from mobject.mobject import Mobject +from mobject.numbers import DecimalNumber +from mobject.numbers import Integer from mobject.svg.tex_mobject import TexMobject +from mobject.svg.tex_mobject import TextMobject from mobject.types.vectorized_mobject import VGroup from mobject.types.vectorized_mobject import VMobject from mobject.shape_matchers import BackgroundRectangle @@ -38,7 +40,7 @@ def vector_coordinate_label(vector_mob, integer_labels=True, vect = np.round(vect).astype(int) vect = vect[:n_dim] vect = vect.reshape((n_dim, 1)) - label = Matrix(vect, add_background_rectangles=True) + label = Matrix(vect, add_background_rectangles_to_entries=True) label.scale(VECTOR_LABEL_SCALE_FACTOR) shift_dir = np.array(vector_mob.get_end()) @@ -55,9 +57,13 @@ def vector_coordinate_label(vector_mob, integer_labels=True, class Matrix(VMobject): CONFIG = { - "v_buff": 0.5, - "h_buff": 1, - "add_background_rectangles": False + "v_buff": 0.8, + "h_buff": 1.3, + "add_background_rectangles_to_entries": False, + "include_background_rectangle": False, + "element_to_mobject": TexMobject, + "element_to_mobject_config": {}, + "element_alignment_corner": DR, } def __init__(self, matrix, **kwargs): @@ -69,34 +75,34 @@ class Matrix(VMobject): matrix = np.array(matrix) if matrix.ndim == 1: matrix = matrix.reshape((matrix.size, 1)) - if not isinstance(matrix[0][0], Mobject): - matrix = matrix.astype("string") - matrix = self.string_matrix_to_mob_matrix(matrix) - self.organize_mob_matrix(matrix) - self.add(*matrix.flatten()) + mob_matrix = self.matrix_to_mob_matrix(matrix) + self.organize_mob_matrix(mob_matrix) + self.elements = VGroup(*mob_matrix.flatten()) + self.add(self.elements) self.add_brackets() self.center() - self.mob_matrix = matrix - if self.add_background_rectangles: - for mob in matrix.flatten(): + self.mob_matrix = mob_matrix + if self.add_background_rectangles_to_entries: + for mob in self.elements: mob.add_background_rectangle() + if self.include_background_rectangle: + self.add_background_rectangle() - def string_matrix_to_mob_matrix(self, matrix): - return np.array([ - map(TexMobject, row) - for row in matrix - ]).reshape(matrix.shape) + def matrix_to_mob_matrix(self, matrix): + return np.vectorize( + lambda e: self.element_to_mobject( + e, **self.element_to_mobject_config + ) + )(matrix) def organize_mob_matrix(self, matrix): for i, row in enumerate(matrix): for j, elem in enumerate(row): mob = matrix[i][j] - if i == 0 and j == 0: - continue - elif i == 0: - mob.next_to(matrix[i][j - 1], RIGHT, self.h_buff) - else: - mob.next_to(matrix[i - 1][j], DOWN, self.v_buff) + mob.move_to( + i * self.v_buff * DOWN + j * self.h_buff * RIGHT, + self.element_alignment_corner + ) return self def add_brackets(self): @@ -110,6 +116,10 @@ class Matrix(VMobject): self.brackets = VGroup(l_bracket, r_bracket) return self + def add_background_rectangle(self): + self.background_rectangle = BackgroundRectangle(self) + self.add_to_back(self.background_rectangle) + def set_color_columns(self, *colors): for i, color in enumerate(colors): VGroup(*self.mob_matrix[:, i]).set_color(color) @@ -128,3 +138,42 @@ class Matrix(VMobject): def get_brackets(self): return self.brackets + + +class DecimalMatrix(Matrix): + CONFIG = { + "element_to_mobject": DecimalNumber, + "element_to_mobject_config": {"num_decimal_points": 1} + } + + +class IntegerMatrix(Matrix): + CONFIG = { + "element_to_mobject": Integer, + } + + +class MobjectMatrix(Matrix): + CONFIG = { + "element_to_mobject": lambda m: m, + } + + +def get_det_text(matrix, determinant=None, background_rect=True, initial_scale_factor=2): + parens = TexMobject(["(", ")"]) + parens.scale(initial_scale_factor) + parens.stretch_to_fit_height(matrix.get_height()) + l_paren, r_paren = parens.split() + l_paren.next_to(matrix, LEFT, buff=0.1) + r_paren.next_to(matrix, RIGHT, buff=0.1) + det = TextMobject("det").next_to(l_paren, LEFT, buff=0.1) + if background_rect: + det.add_background_rectangle() + det_text = VMobject(det, l_paren, r_paren) + if determinant is not None: + eq = TexMobject("=") + eq.next_to(r_paren, RIGHT, buff=0.1) + result = TexMobject(str(determinant)) + result.next_to(eq, RIGHT, buff=0.2) + det_text.add(eq, result) + return det_text diff --git a/old_projects/eola/chapter3.py b/old_projects/eola/chapter3.py index 4b7d56ed..876f3f56 100644 --- a/old_projects/eola/chapter3.py +++ b/old_projects/eola/chapter3.py @@ -887,7 +887,7 @@ class MatrixVectorMultiplication(LinearTransformationScene): ) concrete_matrix = Matrix( copy.deepcopy(abstract_matrix), - add_background_rectangles = True + add_background_rectangles_to_entries = True ) concrete_matrix.to_edge(UP) if self.abstract: diff --git a/old_projects/eola/chapter4.py b/old_projects/eola/chapter4.py index 833f84c9..b8f70c24 100644 --- a/old_projects/eola/chapter4.py +++ b/old_projects/eola/chapter4.py @@ -106,7 +106,7 @@ class FollowLinearCombination(LinearTransformationScene): direction = "right", color = Y_COLOR ) vect = self.add_vector(vect_coords) - vect_array = Matrix(["x", "y"], add_background_rectangles = True) + vect_array = Matrix(["x", "y"], add_background_rectangles_to_entries = True) v_equals = TexMobject(["\\vec{\\textbf{v}}", "="]) v_equals.split()[0].set_color(YELLOW) v_equals.next_to(vect_array, LEFT) diff --git a/old_projects/eola/chapter5.py b/old_projects/eola/chapter5.py index 4f00962a..d96d9cfa 100644 --- a/old_projects/eola/chapter5.py +++ b/old_projects/eola/chapter5.py @@ -1,24 +1,6 @@ from big_ol_pile_of_manim_imports import * from eola.chapter3 import MatrixVectorMultiplicationAbstract -def get_det_text(matrix, determinant = None, background_rect = True): - parens = TexMobject(["(", ")"]) - parens.scale(2) - parens.stretch_to_fit_height(matrix.get_height()) - l_paren, r_paren = parens.split() - l_paren.next_to(matrix, LEFT, buff = 0.1) - r_paren.next_to(matrix, RIGHT, buff = 0.1) - det = TextMobject("det").next_to(l_paren, LEFT, buff = 0.1) - if background_rect: - det.add_background_rectangle() - det_text = VMobject(det, l_paren, r_paren) - if determinant is not None: - eq = TexMobject("=") - eq.next_to(r_paren, RIGHT, buff = 0.1) - result = TexMobject(str(determinant)) - result.next_to(eq, RIGHT, buff = 0.2) - det_text.add(eq, result) - return det_text class Blob(Circle): CONFIG = { diff --git a/scene/vector_space_scene.py b/scene/vector_space_scene.py index cd42b2e5..a07f2138 100644 --- a/scene/vector_space_scene.py +++ b/scene/vector_space_scene.py @@ -10,6 +10,7 @@ from animation.creation import Write from animation.transform import ApplyFunction from animation.transform import ApplyPointwiseFunction from animation.creation import FadeOut +from animation.creation import GrowArrow from animation.transform import Transform from mobject.mobject import Mobject from mobject.svg.tex_mobject import TexMobject @@ -20,7 +21,7 @@ from scene.scene import Scene from mobject.geometry import Arrow from mobject.geometry import Dot from mobject.geometry import Line -from mobject.geometry import Square +from mobject.geometry import Rectangle from mobject.geometry import Vector from mobject.coordinate_systems import Axes from mobject.coordinate_systems import NumberPlane @@ -37,6 +38,12 @@ Y_COLOR = RED_C Z_COLOR = BLUE_D +# TODO: Much of this scene type seems dependent on the coordinate system chosen. +# That is, being centered at the origin with grid units corresponding to the +# arbitrary space units. Change it! +# +# Also, methods I would have thought of as getters, like coords_to_vector, are +# actually doing a lot of animating. class VectorScene(Scene): CONFIG = { "basis_vector_stroke_width": 6 @@ -65,11 +72,19 @@ class VectorScene(Scene): self.add(axes) self.freeze_background() + def get_vector(self, numerical_vector, **kwargs): + return Arrow( + self.plane.coords_to_point(0, 0), + self.plane.coords_to_point(*numerical_vector[:2]), + buff=0, + **kwargs + ) + def add_vector(self, vector, color=YELLOW, animate=True, **kwargs): if not isinstance(vector, Arrow): vector = Vector(vector, color=color, **kwargs) if animate: - self.play(ShowCreation(vector)) + self.play(GrowArrow(vector)) self.add(vector) return vector @@ -106,6 +121,7 @@ class VectorScene(Scene): ]) def get_vector_label(self, vector, label, + at_tip=False, direction="left", rotate=False, color=None, @@ -120,15 +136,20 @@ class VectorScene(Scene): label.scale(label_scale_factor) label.add_background_rectangle() - angle = vector.get_angle() - if not rotate: - label.rotate(-angle, about_point=ORIGIN) - if direction is "left": - label.shift(-label.get_bottom() + 0.1 * UP) + if at_tip: + vect = vector.get_vector() + vect /= np.linalg.norm(vect) + label.next_to(vector.get_end(), vect, buff=SMALL_BUFF) else: - label.shift(-label.get_top() + 0.1 * DOWN) - label.rotate(angle, about_point=ORIGIN) - label.shift((vector.get_end() - vector.get_start()) / 2) + angle = vector.get_angle() + if not rotate: + label.rotate(-angle, about_point=ORIGIN) + if direction is "left": + label.shift(-label.get_bottom() + 0.1 * UP) + else: + label.shift(-label.get_top() + 0.1 * DOWN) + label.rotate(angle, about_point=ORIGIN) + label.shift((vector.get_end() - vector.get_start()) / 2) return label def label_vector(self, vector, label, animate=True, **kwargs): @@ -281,10 +302,10 @@ class LinearTransformationScene(VectorScene): } def setup(self): + # The has_already_setup attr is to not break all the old Scenes if hasattr(self, "has_already_setup"): return self.has_already_setup = True - # ^This is to not break all the old Scenes self.background_mobjects = [] self.foreground_mobjects = [] self.transformable_mobjects = [] @@ -311,6 +332,7 @@ class LinearTransformationScene(VectorScene): ) self.moving_vectors += list(self.basis_vectors) self.i_hat, self.j_hat = self.basis_vectors + self.add(self.basis_vectors) def add_special_mobjects(self, mob_list, *mobs_to_add): for mobject in mobs_to_add: @@ -321,6 +343,7 @@ class LinearTransformationScene(VectorScene): def add_background_mobject(self, *mobjects): self.add_special_mobjects(self.background_mobjects, *mobjects) + # TODO, this conflicts with Scene.add_fore def add_foreground_mobject(self, *mobjects): self.add_special_mobjects(self.foreground_mobjects, *mobjects) @@ -331,15 +354,26 @@ class LinearTransformationScene(VectorScene): mobject.target = target_mobject self.add_special_mobjects(self.moving_mobjects, mobject) - def add_unit_square(self, color=YELLOW, opacity=0.3, animate=False): - square = Square(color=color, side_length=1) - square.shift(-square.get_corner(DOWN + LEFT)) + def get_unit_square(self, color=YELLOW, opacity=0.3, stroke_width=3): + square = Rectangle( + color=color, + width=self.plane.get_x_unit_size(), + height=self.plane.get_y_unit_size(), + stroke_color=color, + stroke_width=stroke_width, + fill_color=color, + fill_opacity=opacity + ) + square.move_to(self.plane.coords_to_point(0, 0), DL) + return square + + def add_unit_square(self, animate=False, **kwargs): + square = self.get_unit_square(**kwargs) if animate: - added_anims = map(Animation, self.moving_vectors) - self.play(ShowCreation(square), *added_anims) - self.play(square.set_fill, color, opacity, *added_anims) - else: - square.set_fill(color, opacity) + self.play( + DrawBorderThenFill(square), + Animation(Group(*self.moving_vectors)) + ) self.add_transformable_mobject(square) self.bring_to_front(*self.moving_vectors) self.square = square @@ -357,12 +391,19 @@ class LinearTransformationScene(VectorScene): self.add_foreground_mobject(coords) return coords - def add_transformable_label(self, vector, label, new_label=None, **kwargs): + def add_transformable_label( + self, vector, label, + transformation_name="L", + new_label=None, + **kwargs): label_mob = self.label_vector(vector, label, **kwargs) if new_label: label_mob.target_text = new_label else: - label_mob.target_text = "L(%s)" % label_mob.get_tex_string() + label_mob.target_text = "%s(%s)" % ( + transformation_name, + label_mob.get_tex_string() + ) label_mob.vector = vector label_mob.kwargs = kwargs if "animate" in label_mob.kwargs: @@ -427,6 +468,9 @@ class LinearTransformationScene(VectorScene): def apply_matrix(self, matrix, **kwargs): self.apply_transposed_matrix(np.array(matrix).T, **kwargs) + def apply_inverse(self, matrix, **kwargs): + self.apply_matrix(np.linalg.inv(matrix), **kwargs) + def apply_transposed_matrix(self, transposed_matrix, **kwargs): func = self.get_transposed_matrix_transformation(transposed_matrix) if "path_arc" not in kwargs: