# SymPy Matrices and Linear Algebra This document covers SymPy's matrix operations, linear algebra capabilities, and solving systems of linear equations. ## Matrix Creation ### Basic Matrix Construction ```python from sympy import Matrix, eye, zeros, ones, diag # From list of rows M = Matrix([[1, 2], [3, 4]]) M = Matrix([ [1, 2, 3], [4, 5, 6] ]) # Column vector v = Matrix([1, 2, 3]) # Row vector v = Matrix([[1, 2, 3]]) ``` ### Special Matrices ```python # Identity matrix I = eye(3) # 3x3 identity # [[1, 0, 0], # [0, 1, 0], # [0, 0, 1]] # Zero matrix Z = zeros(2, 3) # 2 rows, 3 columns of zeros # Ones matrix O = ones(3, 2) # 3 rows, 2 columns of ones # Diagonal matrix D = diag(1, 2, 3) # [[1, 0, 0], # [0, 2, 0], # [0, 0, 3]] # Block diagonal from sympy import BlockDiagMatrix A = Matrix([[1, 2], [3, 4]]) B = Matrix([[5, 6], [7, 8]]) BD = BlockDiagMatrix(A, B) ``` ## Matrix Properties and Access ### Shape and Dimensions ```python M = Matrix([[1, 2, 3], [4, 5, 6]]) M.shape # (2, 3) - returns tuple (rows, cols) M.rows # 2 M.cols # 3 ``` ### Accessing Elements ```python M = Matrix([[1, 2, 3], [4, 5, 6]]) # Single element M[0, 0] # 1 (zero-indexed) M[1, 2] # 6 # Row access M[0, :] # Matrix([[1, 2, 3]]) M.row(0) # Same as above # Column access M[:, 1] # Matrix([[2], [5]]) M.col(1) # Same as above # Slicing M[0:2, 0:2] # Top-left 2x2 submatrix ``` ### Modification ```python M = Matrix([[1, 2], [3, 4]]) # Insert row M = M.row_insert(1, Matrix([[5, 6]])) # [[1, 2], # [5, 6], # [3, 4]] # Insert column M = M.col_insert(1, Matrix([7, 8])) # Delete row M = M.row_del(0) # Delete column M = M.col_del(1) ``` ## Basic Matrix Operations ### Arithmetic Operations ```python from sympy import Matrix A = Matrix([[1, 2], [3, 4]]) B = Matrix([[5, 6], [7, 8]]) # Addition C = A + B # Subtraction C = A - B # Scalar multiplication C = 2 * A # Matrix multiplication C = A * B # Element-wise multiplication (Hadamard product) C = A.multiply_elementwise(B) # Power C = A**2 # Same as A * A C = A**3 # A * A * A ``` ### Transpose and Conjugate ```python M = Matrix([[1, 2], [3, 4]]) # Transpose M.T # [[1, 3], # [2, 4]] # Conjugate (for complex matrices) M.conjugate() # Conjugate transpose (Hermitian transpose) M.H # Same as M.conjugate().T ``` ### Inverse ```python M = Matrix([[1, 2], [3, 4]]) # Inverse M_inv = M**-1 M_inv = M.inv() # Verify M * M_inv # Returns identity matrix # Check if invertible M.is_invertible() # True or False ``` ## Advanced Linear Algebra ### Determinant ```python M = Matrix([[1, 2], [3, 4]]) M.det() # -2 # For symbolic matrices from sympy import symbols a, b, c, d = symbols('a b c d') M = Matrix([[a, b], [c, d]]) M.det() # a*d - b*c ``` ### Trace ```python M = Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) M.trace() # 1 + 5 + 9 = 15 ``` ### Row Echelon Form ```python M = Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) # Reduced Row Echelon Form rref_M, pivot_cols = M.rref() # rref_M is the RREF matrix # pivot_cols is tuple of pivot column indices ``` ### Rank ```python M = Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) M.rank() # 2 (this matrix is rank-deficient) ``` ### Nullspace and Column Space ```python M = Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) # Nullspace (kernel) null = M.nullspace() # Returns list of basis vectors for nullspace # Column space col = M.columnspace() # Returns list of basis vectors for column space # Row space row = M.rowspace() # Returns list of basis vectors for row space ``` ### Orthogonalization ```python # Gram-Schmidt orthogonalization vectors = [Matrix([1, 2, 3]), Matrix([4, 5, 6])] ortho = Matrix.orthogonalize(*vectors) # With normalization ortho_norm = Matrix.orthogonalize(*vectors, normalize=True) ``` ## Eigenvalues and Eigenvectors ### Computing Eigenvalues ```python M = Matrix([[1, 2], [2, 1]]) # Eigenvalues with multiplicities eigenvals = M.eigenvals() # Returns dict: {eigenvalue: multiplicity} # Example: {3: 1, -1: 1} # Just the eigenvalues as a list eigs = list(M.eigenvals().keys()) ``` ### Computing Eigenvectors ```python M = Matrix([[1, 2], [2, 1]]) # Eigenvectors with eigenvalues eigenvects = M.eigenvects() # Returns list of tuples: (eigenvalue, multiplicity, [eigenvectors]) # Example: [(3, 1, [Matrix([1, 1])]), (-1, 1, [Matrix([1, -1])])] # Access individual eigenvectors for eigenval, multiplicity, eigenvecs in M.eigenvects(): print(f"Eigenvalue: {eigenval}") print(f"Eigenvectors: {eigenvecs}") ``` ### Diagonalization ```python M = Matrix([[1, 2], [2, 1]]) # Check if diagonalizable M.is_diagonalizable() # True or False # Diagonalize (M = P*D*P^-1) P, D = M.diagonalize() # P: matrix of eigenvectors # D: diagonal matrix of eigenvalues # Verify P * D * P**-1 == M # True ``` ### Characteristic Polynomial ```python from sympy import symbols lam = symbols('lambda') M = Matrix([[1, 2], [2, 1]]) charpoly = M.charpoly(lam) # Returns characteristic polynomial ``` ### Jordan Normal Form ```python M = Matrix([[2, 1, 0], [0, 2, 1], [0, 0, 2]]) # Jordan form (for non-diagonalizable matrices) P, J = M.jordan_form() # J is the Jordan normal form # P is the transformation matrix ``` ## Matrix Decompositions ### LU Decomposition ```python M = Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 10]]) # LU decomposition L, U, perm = M.LUdecomposition() # L: lower triangular # U: upper triangular # perm: permutation indices ``` ### QR Decomposition ```python M = Matrix([[1, 2], [3, 4], [5, 6]]) # QR decomposition Q, R = M.QRdecomposition() # Q: orthogonal matrix # R: upper triangular matrix ``` ### Cholesky Decomposition ```python # For positive definite symmetric matrices M = Matrix([[4, 2], [2, 3]]) L = M.cholesky() # L is lower triangular such that M = L*L.T ``` ### Singular Value Decomposition (SVD) ```python M = Matrix([[1, 2], [3, 4], [5, 6]]) # SVD (note: may require numerical evaluation) U, S, V = M.singular_value_decomposition() # M = U * S * V ``` ## Solving Linear Systems ### Using Matrix Equations ```python # Solve Ax = b A = Matrix([[1, 2], [3, 4]]) b = Matrix([5, 6]) # Solution x = A.solve(b) # or A**-1 * b # Least squares (for overdetermined systems) x = A.solve_least_squares(b) ``` ### Using linsolve ```python from sympy import linsolve, symbols x, y = symbols('x y') # Method 1: List of equations eqs = [x + y - 5, 2*x - y - 1] sol = linsolve(eqs, [x, y]) # {(2, 3)} # Method 2: Augmented matrix M = Matrix([[1, 1, 5], [2, -1, 1]]) sol = linsolve(M, [x, y]) # Method 3: A*x = b form A = Matrix([[1, 1], [2, -1]]) b = Matrix([5, 1]) sol = linsolve((A, b), [x, y]) ``` ### Underdetermined and Overdetermined Systems ```python # Underdetermined (infinite solutions) A = Matrix([[1, 2, 3]]) b = Matrix([6]) sol = A.solve(b) # Returns parametric solution # Overdetermined (least squares) A = Matrix([[1, 2], [3, 4], [5, 6]]) b = Matrix([1, 2, 3]) sol = A.solve_least_squares(b) ``` ## Symbolic Matrices ### Working with Symbolic Entries ```python from sympy import symbols, Matrix a, b, c, d = symbols('a b c d') M = Matrix([[a, b], [c, d]]) # All operations work symbolically M.det() # a*d - b*c M.inv() # Matrix([[d/(a*d - b*c), -b/(a*d - b*c)], ...]) M.eigenvals() # Symbolic eigenvalues ``` ### Matrix Functions ```python from sympy import exp, sin, cos, Matrix M = Matrix([[0, 1], [-1, 0]]) # Matrix exponential exp(M) # Trigonometric functions sin(M) cos(M) ``` ## Mutable vs Immutable Matrices ```python from sympy import Matrix, ImmutableMatrix # Mutable (default) M = Matrix([[1, 2], [3, 4]]) M[0, 0] = 5 # Allowed # Immutable (for use as dictionary keys, etc.) I = ImmutableMatrix([[1, 2], [3, 4]]) # I[0, 0] = 5 # Error: ImmutableMatrix cannot be modified ``` ## Sparse Matrices For large matrices with many zero entries: ```python from sympy import SparseMatrix # Create sparse matrix S = SparseMatrix(1000, 1000, {(0, 0): 1, (100, 100): 2}) # Only stores non-zero elements # Convert dense to sparse M = Matrix([[1, 0, 0], [0, 2, 0]]) S = SparseMatrix(M) ``` ## Common Linear Algebra Patterns ### Pattern 1: Solving Ax = b for Multiple b Vectors ```python A = Matrix([[1, 2], [3, 4]]) A_inv = A.inv() b1 = Matrix([5, 6]) b2 = Matrix([7, 8]) x1 = A_inv * b1 x2 = A_inv * b2 ``` ### Pattern 2: Change of Basis ```python # Given vectors in old basis, convert to new basis old_basis = [Matrix([1, 0]), Matrix([0, 1])] new_basis = [Matrix([1, 1]), Matrix([1, -1])] # Change of basis matrix P = Matrix.hstack(*new_basis) P_inv = P.inv() # Convert vector v from old to new basis v = Matrix([3, 4]) v_new = P_inv * v ``` ### Pattern 3: Matrix Condition Number ```python # Estimate condition number (ratio of largest to smallest singular value) M = Matrix([[1, 2], [3, 4]]) eigenvals = M.eigenvals() cond = max(eigenvals.keys()) / min(eigenvals.keys()) ``` ### Pattern 4: Projection Matrices ```python # Project onto column space of A A = Matrix([[1, 0], [0, 1], [1, 1]]) P = A * (A.T * A).inv() * A.T # P is projection matrix onto column space of A ``` ## Important Notes 1. **Zero-testing:** SymPy's symbolic zero-testing can affect accuracy. For numerical work, consider using `evalf()` or numerical libraries. 2. **Performance:** For large numerical matrices, consider converting to NumPy using `lambdify` or using numerical linear algebra libraries directly. 3. **Symbolic computation:** Matrix operations with symbolic entries can be computationally expensive for large matrices. 4. **Assumptions:** Use symbol assumptions (e.g., `real=True`, `positive=True`) to help SymPy simplify matrix expressions correctly.