Source code for manim.mobject.matrix

r"""Mobjects representing matrices.

Examples
--------

.. manim:: MatrixExamples
    :save_last_frame:

    class MatrixExamples(Scene):
        def construct(self):
            m0 = Matrix([["\\pi", 0], [-1, 1]])
            m1 = IntegerMatrix([[1.5, 0.], [12, -1.3]],
                left_bracket="(",
                right_bracket=")")
            m2 = DecimalMatrix(
                [[3.456, 2.122], [33.2244, 12.33]],
                element_to_mobject_config={"num_decimal_places": 2},
                left_bracket="\\{",
                right_bracket="\\}")
            m3 = MobjectMatrix(
                [[Circle().scale(0.3), Square().scale(0.3)],
                [MathTex("\\pi").scale(2), Star().scale(0.3)]],
                left_bracket="\\langle",
                right_bracket="\\rangle")
            g = Group(m0, m1, m2, m3).arrange_in_grid(buff=2)
            self.add(g)
"""

from __future__ import annotations

__all__ = [
    "Matrix",
    "DecimalMatrix",
    "IntegerMatrix",
    "MobjectMatrix",
    "matrix_to_tex_string",
    "matrix_to_mobject",
    "get_det_text",
]


import itertools as it
from typing import Iterable, Sequence

import numpy as np

from manim.mobject.mobject import Mobject
from manim.mobject.opengl.opengl_compatibility import ConvertToOpenGL
from manim.mobject.text.numbers import DecimalNumber, Integer
from manim.mobject.text.tex_mobject import MathTex, Tex

from ..constants import *
from ..mobject.types.vectorized_mobject import VGroup, VMobject

# TO DO : The following two functions are not used in this file.
#         Not sure if we should keep it or not.


[docs]def matrix_to_tex_string(matrix): matrix = np.array(matrix).astype("str") if matrix.ndim == 1: matrix = matrix.reshape((matrix.size, 1)) n_rows, n_cols = matrix.shape prefix = "\\left[ \\begin{array}{%s}" % ("c" * n_cols) suffix = "\\end{array} \\right]" rows = [" & ".join(row) for row in matrix] return prefix + " \\\\ ".join(rows) + suffix
[docs]def matrix_to_mobject(matrix): return MathTex(matrix_to_tex_string(matrix))
[docs]class Matrix(VMobject, metaclass=ConvertToOpenGL): """A mobject that displays a matrix on the screen. Parameters ---------- matrix A numpy 2d array or list of lists. v_buff Vertical distance between elements, by default 0.8. h_buff Horizontal distance between elements, by default 1.3. bracket_h_buff Distance of the brackets from the matrix, by default ``MED_SMALL_BUFF``. bracket_v_buff Height of the brackets, by default ``MED_SMALL_BUFF``. add_background_rectangles_to_entries ``True`` if should add backgraound rectangles to entries, by default ``False``. include_background_rectangle ``True`` if should include background rectangle, by default ``False``. element_to_mobject The mobject class used to construct the elements, by default :class:`~.MathTex`. element_to_mobject_config Additional arguments to be passed to the constructor in ``element_to_mobject``, by default ``{}``. element_alignment_corner The corner to which elements are aligned, by default ``DR``. left_bracket The left bracket type, by default ``"["``. right_bracket The right bracket type, by default ``"]"``. stretch_brackets ``True`` if should stretch the brackets to fit the height of matrix contents, by default ``True``. bracket_config Additional arguments to be passed to :class:`~.MathTex` when constructing the brackets. Examples -------- The first example shows a variety of uses of this module while the second example exlpains the use of the options `add_background_rectangles_to_entries` and `include_background_rectangle`. .. manim:: MatrixExamples :save_last_frame: class MatrixExamples(Scene): def construct(self): m0 = Matrix([[2, "\\pi"], [-1, 1]]) m1 = Matrix([[2, 0, 4], [-1, 1, 5]], v_buff=1.3, h_buff=0.8, bracket_h_buff=SMALL_BUFF, bracket_v_buff=SMALL_BUFF, left_bracket="\\{", right_bracket="\\}") m1.add(SurroundingRectangle(m1.get_columns()[1])) m2 = Matrix([[2, 1], [-1, 3]], element_alignment_corner=UL, left_bracket="(", right_bracket=")") m3 = Matrix([[2, 1], [-1, 3]], left_bracket="\\\\langle", right_bracket="\\\\rangle") m4 = Matrix([[2, 1], [-1, 3]], ).set_column_colors(RED, GREEN) m5 = Matrix([[2, 1], [-1, 3]], ).set_row_colors(RED, GREEN) g = Group( m0,m1,m2,m3,m4,m5 ).arrange_in_grid(buff=2) self.add(g) .. manim:: BackgroundRectanglesExample :save_last_frame: class BackgroundRectanglesExample(Scene): def construct(self): background= Rectangle().scale(3.2) background.set_fill(opacity=.5) background.set_color([TEAL, RED, YELLOW]) self.add(background) m0 = Matrix([[12, -30], [-1, 15]], add_background_rectangles_to_entries=True) m1 = Matrix([[2, 0], [-1, 1]], include_background_rectangle=True) m2 = Matrix([[12, -30], [-1, 15]]) g = Group(m0, m1, m2).arrange(buff=2) self.add(g) """ def __init__( self, matrix: Iterable, v_buff: float = 0.8, h_buff: float = 1.3, bracket_h_buff: float = MED_SMALL_BUFF, bracket_v_buff: float = MED_SMALL_BUFF, add_background_rectangles_to_entries: bool = False, include_background_rectangle: bool = False, element_to_mobject: type[MathTex] = MathTex, element_to_mobject_config: dict = {}, element_alignment_corner: Sequence[float] = DR, left_bracket: str = "[", right_bracket: str = "]", stretch_brackets: bool = True, bracket_config: dict = {}, **kwargs, ): self.v_buff = v_buff self.h_buff = h_buff self.bracket_h_buff = bracket_h_buff self.bracket_v_buff = bracket_v_buff self.add_background_rectangles_to_entries = add_background_rectangles_to_entries self.include_background_rectangle = include_background_rectangle self.element_to_mobject = element_to_mobject self.element_to_mobject_config = element_to_mobject_config self.element_alignment_corner = element_alignment_corner self.left_bracket = left_bracket self.right_bracket = right_bracket self.stretch_brackets = stretch_brackets super().__init__(**kwargs) mob_matrix = self._matrix_to_mob_matrix(matrix) self._organize_mob_matrix(mob_matrix) self.elements = VGroup(*it.chain(*mob_matrix)) self.add(self.elements) self._add_brackets(self.left_bracket, self.right_bracket, **bracket_config) self.center() 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 _matrix_to_mob_matrix(self, matrix): return [ [ self.element_to_mobject(item, **self.element_to_mobject_config) for item in row ] for row in matrix ] def _organize_mob_matrix(self, matrix): for i, row in enumerate(matrix): for j, _ in enumerate(row): mob = matrix[i][j] mob.move_to( i * self.v_buff * DOWN + j * self.h_buff * RIGHT, self.element_alignment_corner, ) return self
[docs] def _add_brackets(self, left: str = "[", right: str = "]", **kwargs): """Adds the brackets to the Matrix mobject. See Latex document for various bracket types. Parameters ---------- left the left bracket, by default "[" right the right bracket, by default "]" Returns ------- :class:`Matrix` The current matrix object (self). """ # Height per row of LaTeX array with default settings BRACKET_HEIGHT = 0.5977 n = int((self.height) / BRACKET_HEIGHT) + 1 empty_tex_array = "".join( [ r"\begin{array}{c}", *n * [r"\quad \\"], r"\end{array}", ] ) tex_left = "".join( [ r"\left" + left, empty_tex_array, r"\right.", ] ) tex_right = "".join( [ r"\left.", empty_tex_array, r"\right" + right, ] ) l_bracket = MathTex(tex_left, **kwargs) r_bracket = MathTex(tex_right, **kwargs) bracket_pair = VGroup(l_bracket, r_bracket) if self.stretch_brackets: bracket_pair.stretch_to_fit_height(self.height + 2 * self.bracket_v_buff) l_bracket.next_to(self, LEFT, self.bracket_h_buff) r_bracket.next_to(self, RIGHT, self.bracket_h_buff) self.brackets = bracket_pair self.add(l_bracket, r_bracket) return self
[docs] def get_columns(self): """Return columns of the matrix as VGroups. Returns -------- List[:class:`~.VGroup`] Each VGroup contains a column of the matrix. Examples -------- .. manim:: GetColumnsExample :save_last_frame: class GetColumnsExample(Scene): def construct(self): m0 = Matrix([["\\pi", 3], [1, 5]]) m0.add(SurroundingRectangle(m0.get_columns()[1])) self.add(m0) """ return VGroup( *( VGroup(*(row[i] for row in self.mob_matrix)) for i in range(len(self.mob_matrix[0])) ) )
[docs] def set_column_colors(self, *colors: str): """Set individual colors for each columns of the matrix. Parameters ---------- colors The list of colors; each color specified corresponds to a column. Returns ------- :class:`Matrix` The current matrix object (self). Examples -------- .. manim:: SetColumnColorsExample :save_last_frame: class SetColumnColorsExample(Scene): def construct(self): m0 = Matrix([["\\pi", 1], [-1, 3]], ).set_column_colors([RED,BLUE], GREEN) self.add(m0) """ columns = self.get_columns() for color, column in zip(colors, columns): column.set_color(color) return self
[docs] def get_rows(self): """Return rows of the matrix as VGroups. Returns -------- List[:class:`~.VGroup`] Each VGroup contains a row of the matrix. Examples -------- .. manim:: GetRowsExample :save_last_frame: class GetRowsExample(Scene): def construct(self): m0 = Matrix([["\\pi", 3], [1, 5]]) m0.add(SurroundingRectangle(m0.get_rows()[1])) self.add(m0) """ return VGroup(*(VGroup(*row) for row in self.mob_matrix))
[docs] def set_row_colors(self, *colors: str): """Set individual colors for each row of the matrix. Parameters ---------- colors The list of colors; each color specified corresponds to a row. Returns ------- :class:`Matrix` The current matrix object (self). Examples -------- .. manim:: SetRowColorsExample :save_last_frame: class SetRowColorsExample(Scene): def construct(self): m0 = Matrix([["\\pi", 1], [-1, 3]], ).set_row_colors([RED,BLUE], GREEN) self.add(m0) """ rows = self.get_rows() for color, row in zip(colors, rows): row.set_color(color) return self
[docs] def add_background_to_entries(self): """Add a black background rectangle to the matrix, see above for an example. Returns ------- :class:`Matrix` The current matrix object (self). """ for mob in self.get_entries(): mob.add_background_rectangle() return self
[docs] def get_mob_matrix(self): """Return the underlying mob matrix mobjects. Returns -------- List[:class:`~.VGroup`] Each VGroup contains a row of the matrix. """ return self.mob_matrix
[docs] def get_entries(self): """Return the individual entries of the matrix. Returns -------- :class:`~.VGroup` VGroup containing entries of the matrix. Examples -------- .. manim:: GetEntriesExample :save_last_frame: class GetEntriesExample(Scene): def construct(self): m0 = Matrix([[2, 3], [1, 5]]) ent = m0.get_entries() colors = [BLUE, GREEN, YELLOW, RED] for k in range(len(colors)): ent[k].set_color(colors[k]) self.add(m0) """ return self.elements
[docs] def get_brackets(self): """Return the bracket mobjects. Returns -------- List[:class:`~.VGroup`] Each VGroup contains a bracket Examples -------- .. manim:: GetBracketsExample :save_last_frame: class GetBracketsExample(Scene): def construct(self): m0 = Matrix([["\\pi", 3], [1, 5]]) bra = m0.get_brackets() colors = [BLUE, GREEN] for k in range(len(colors)): bra[k].set_color(colors[k]) self.add(m0) """ return self.brackets
[docs]class DecimalMatrix(Matrix): """A mobject that displays a matrix with decimal entries on the screen. Examples -------- .. manim:: DecimalMatrixExample :save_last_frame: class DecimalMatrixExample(Scene): def construct(self): m0 = DecimalMatrix( [[3.456, 2.122], [33.2244, 12]], element_to_mobject_config={"num_decimal_places": 2}, left_bracket="\\{", right_bracket="\\}") self.add(m0) """ def __init__( self, matrix: Iterable, element_to_mobject: Mobject = DecimalNumber, element_to_mobject_config: dict[str, Mobject] = {"num_decimal_places": 1}, **kwargs, ): """ Will round/truncate the decimal places as per the provided config. Parameters ---------- matrix A numpy 2d array or list of lists element_to_mobject Mobject to use, by default DecimalNumber element_to_mobject_config Config for the desired mobject, by default {"num_decimal_places": 1} """ super().__init__( matrix, element_to_mobject=element_to_mobject, element_to_mobject_config=element_to_mobject_config, **kwargs, )
[docs]class IntegerMatrix(Matrix): """A mobject that displays a matrix with integer entries on the screen. Examples -------- .. manim:: IntegerMatrixExample :save_last_frame: class IntegerMatrixExample(Scene): def construct(self): m0 = IntegerMatrix( [[3.7, 2], [42.2, 12]], left_bracket="(", right_bracket=")") self.add(m0) """ def __init__( self, matrix: Iterable, element_to_mobject: Mobject = Integer, **kwargs ): """ Will round if there are decimal entries in the matrix. Parameters ---------- matrix A numpy 2d array or list of lists element_to_mobject Mobject to use, by default Integer """ super().__init__(matrix, element_to_mobject=element_to_mobject, **kwargs)
[docs]class MobjectMatrix(Matrix): """A mobject that displays a matrix of mobject entries on the screen. Examples -------- .. manim:: MobjectMatrixExample :save_last_frame: class MobjectMatrixExample(Scene): def construct(self): a = Circle().scale(0.3) b = Square().scale(0.3) c = MathTex("\\pi").scale(2) d = Star().scale(0.3) m0 = MobjectMatrix([[a, b], [c, d]]) self.add(m0) """ def __init__(self, matrix, element_to_mobject=lambda m: m, **kwargs): super().__init__(matrix, element_to_mobject=element_to_mobject, **kwargs)
[docs]def get_det_text( matrix: Matrix, determinant: int | str | None = None, background_rect: bool = False, initial_scale_factor: float = 2, ): r"""Helper function to create determinant. Parameters ---------- matrix The matrix whose determinant is to be created determinant The value of the determinant of the matrix background_rect The background rectangle initial_scale_factor The scale of the text `det` w.r.t the matrix Returns -------- :class:`~.VGroup` A VGroup containing the determinant Examples -------- .. manim:: DeterminantOfAMatrix :save_last_frame: class DeterminantOfAMatrix(Scene): def construct(self): matrix = Matrix([ [2, 0], [-1, 1] ]) # scaling down the `det` string det = get_det_text(matrix, determinant=3, initial_scale_factor=1) # must add the matrix self.add(matrix) self.add(det) """ parens = MathTex("(", ")") parens.scale(initial_scale_factor) parens.stretch_to_fit_height(matrix.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 = Tex("det") det.scale(initial_scale_factor) det.next_to(l_paren, LEFT, buff=0.1) if background_rect: det.add_background_rectangle() det_text = VGroup(det, l_paren, r_paren) if determinant is not None: eq = MathTex("=") eq.next_to(r_paren, RIGHT, buff=0.1) result = MathTex(str(determinant)) result.next_to(eq, RIGHT, buff=0.2) det_text.add(eq, result) return det_text