From 7d85c949f42a25a58711c883d0cbb959b4dfb45d Mon Sep 17 00:00:00 2001 From: ys_teng <58208381+YeeShin504@users.noreply.github.com> Date: Thu, 26 Feb 2026 13:45:20 +0800 Subject: [PATCH 1/7] Add tutorial tests and reorganise test directory --- docs/tutorials/tut03.ipynb | 134 ------------ docs/tutorials/tut07.ipynb | 4 +- src/ma1522/symbolic.py | 3 + tests/chapters/test_c1_linear_systems.py | 190 ++++++++++++++++++ tests/chapters/test_c2_matrix_algebra.py | 96 +++++++++ .../test_c3_euclidean_vector_spaces.py | 99 +++++++++ .../test_c4_subspaces_assoc_matrix.py | 13 ++ .../chapters/test_c5_orthogonality_and_lss.py | 86 ++++++++ tests/chapters/test_c6_eigen_analysis.py | 69 +++++++ .../chapters/test_c7_linear_transformation.py | 15 ++ tests/tutorials/test_tut01.py | 156 ++++++++++++++ tests/tutorials/test_tut02.py | 86 ++++++++ tests/tutorials/test_tut03.py | 99 +++++++++ tests/tutorials/test_tut04.py | 52 +++++ tests/tutorials/test_tut05.py | 100 +++++++++ tests/tutorials/test_tut06.py | 108 ++++++++++ tests/tutorials/test_tut07.py | 134 ++++++++++++ tests/tutorials/test_tut08.py | 100 +++++++++ tests/tutorials/test_tut09.py | 83 ++++++++ tests/tutorials/test_tut10.py | 118 +++++++++++ tests/tutorials/test_tut11.py | 98 +++++++++ 21 files changed, 1707 insertions(+), 136 deletions(-) create mode 100644 tests/chapters/test_c1_linear_systems.py create mode 100644 tests/chapters/test_c2_matrix_algebra.py create mode 100644 tests/chapters/test_c3_euclidean_vector_spaces.py create mode 100644 tests/chapters/test_c4_subspaces_assoc_matrix.py create mode 100644 tests/chapters/test_c5_orthogonality_and_lss.py create mode 100644 tests/chapters/test_c6_eigen_analysis.py create mode 100644 tests/chapters/test_c7_linear_transformation.py create mode 100644 tests/tutorials/test_tut01.py create mode 100644 tests/tutorials/test_tut02.py create mode 100644 tests/tutorials/test_tut03.py create mode 100644 tests/tutorials/test_tut04.py create mode 100644 tests/tutorials/test_tut05.py create mode 100644 tests/tutorials/test_tut06.py create mode 100644 tests/tutorials/test_tut07.py create mode 100644 tests/tutorials/test_tut08.py create mode 100644 tests/tutorials/test_tut09.py create mode 100644 tests/tutorials/test_tut10.py create mode 100644 tests/tutorials/test_tut11.py diff --git a/docs/tutorials/tut03.ipynb b/docs/tutorials/tut03.ipynb index f2e2425..4764c31 100644 --- a/docs/tutorials/tut03.ipynb +++ b/docs/tutorials/tut03.ipynb @@ -1528,140 +1528,6 @@ "# Therefore, det has only been scaled by a factor of 1 + x**3" ] }, - { - "cell_type": "code", - "execution_count": 23, - "id": "b8678a5d", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Modified matrix for column 1:\n" - ] - }, - { - "data": { - "text/latex": [ - "$\\displaystyle \\left[\\begin{array}{ccc}1 & 5 & 3\\\\2 & 2 & -2\\\\0 & 1 & 3\\end{array}\\right]$" - ], - "text/plain": [ - "Matrix([\n", - "[1, 5, 3]\n", - "[2, 2, -2]\n", - "[0, 1, 3]\n", - "])" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/latex": [ - "$\\displaystyle \\text{Determinant for column }1: -2$" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Modified matrix for column 2:\n" - ] - }, - { - "data": { - "text/latex": [ - "$\\displaystyle \\left[\\begin{array}{ccc}1 & 1 & 3\\\\0 & 2 & -2\\\\0 & 0 & 3\\end{array}\\right]$" - ], - "text/plain": [ - "Matrix([\n", - "[1, 1, 3]\n", - "[0, 2, -2]\n", - "[0, 0, 3]\n", - "])" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/latex": [ - "$\\displaystyle \\text{Determinant for column }2: \\frac{3}{4}$" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Modified matrix for column 3:\n" - ] - }, - { - "data": { - "text/latex": [ - "$\\displaystyle \\left[\\begin{array}{ccc}1 & 5 & 1\\\\0 & 2 & 2\\\\0 & 1 & 0\\end{array}\\right]$" - ], - "text/plain": [ - "Matrix([\n", - "[1, 5, 1]\n", - "[0, 2, 2]\n", - "[0, 1, 0]\n", - "])" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/latex": [ - "$\\displaystyle \\text{Determinant for column }3: - \\frac{1}{4}$" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/latex": [ - "$\\displaystyle \\left[\\begin{array}{c}-2\\\\\\frac{3}{4}\\\\- \\frac{1}{4}\\end{array}\\right]$" - ], - "text/plain": [ - "Matrix([\n", - "[ -2]\n", - "[ 3/4]\n", - "[-1/4]\n", - "])" - ] - }, - "execution_count": 23, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "Matrix.from_str(\"1 5 3; 0 2 -2; 0 1 3\").cramer_solve(Matrix.from_str(\"1; 2; 0\"))\n", - "\n" - ] - }, { "cell_type": "markdown", "id": "344bf798", diff --git a/docs/tutorials/tut07.ipynb b/docs/tutorials/tut07.ipynb index cb5f102..8727d60 100644 --- a/docs/tutorials/tut07.ipynb +++ b/docs/tutorials/tut07.ipynb @@ -111,7 +111,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": null, "id": "db1d293b", "metadata": {}, "outputs": [ @@ -135,7 +135,7 @@ } ], "source": [ - "sol = mat.solve(aug)\n", + "sol = mat.solve(aug)[0]\n", "sol" ] }, diff --git a/src/ma1522/symbolic.py b/src/ma1522/symbolic.py index c70f03f..39604b2 100644 --- a/src/ma1522/symbolic.py +++ b/src/ma1522/symbolic.py @@ -3478,6 +3478,9 @@ def diagonalize( - `P` ([`Matrix`][...]): The matrix of eigenvectors. - `D` ([`Matrix`][...]): The diagonal matrix of eigenvalues. + Raises: + sympy.matrices.matrixbase.MatrixError: If the matrix is not diagonalizable. + Examples: >>> mat = Matrix([[1, 2], [3, 4]]) >>> mat.diagonalize() diff --git a/tests/chapters/test_c1_linear_systems.py b/tests/chapters/test_c1_linear_systems.py new file mode 100644 index 0000000..e52fb55 --- /dev/null +++ b/tests/chapters/test_c1_linear_systems.py @@ -0,0 +1,190 @@ +"""Include the following methods in the tests +- aug_line +- rm_aug_line +- row_join +- col_join +- scale_row +- swap_row +- reduce_row +- get_pivot_row +- get_pivot_pos +- get_pivot_elements +- ref +- find_all_cases +- evaluate_cases +- rref +- solve +""" + +import pytest +from sympy import S + +from ma1522 import Matrix + + +class TestAugLine: + """Tests for Matrix.aug_line() method + + Covers: + - Adding augmentation lines at different positions + - Multiple augmentation lines + - Edge cases (invalid positions) + """ + + def test_single_aug_line(self): + mat = Matrix([[1, 2], [3, 4]]) + mat2 = mat.aug_line(1) + assert hasattr(mat2, "_aug_pos") + assert mat2._aug_pos == set([1]) + + def test_multiple_aug_lines(self): + """Test adding multiple augmentation lines""" + mat = Matrix([[1, 2], [3, 4]]) + mat = mat.aug_line(0).aug_line(1) + assert mat._aug_pos == set([0, 1]) + + def test_invalid_position(self): + """Test invalid augmentation position raises error""" + mat = Matrix([[1, 2], [3, 4]]) + with pytest.raises(IndexError): + mat.aug_line(3) + + +class TestRmAugLine: + """Tests for Matrix.rm_aug_line() method + + Covers: + - Removing existing augmentation lines + - Attempting to remove non-existent lines + """ + + def test_remove_existing_line(self): + """Test removing an existing augmentation line""" + mat = Matrix([[1, 2], [3, 4]], aug_pos={0, 1}) + mat = mat.rm_aug_line(0) + assert mat._aug_pos == set([1]) + + def test_remove_nonexistent_line(self): + """Test removing a non-existent line has no effect""" + mat = Matrix([[1, 2], [3, 4]], aug_pos={0}) + mat = mat.rm_aug_line(1) + assert mat._aug_pos == set([0]) + + +class TestJoinOperations: + """Tests for basic join operations + + Covers: + - row_join() + - col_join() + """ + + # TODO + + +class TestRowOperations: + """Tests for basic row operations + + Covers: + - scale_row() + - swap_row() + - reduce_row() + """ + + def test_scale_row(self): + """Test scaling a row by a scalar""" + mat = Matrix([[1, 2], [3, 4]]) + mat.scale_row(0, S(2)) + assert mat == Matrix([[2, 4], [3, 4]]) + + def test_swap_row(self): + """Test swapping two rows""" + mat = Matrix([[1, 2], [3, 4]]) + mat.swap_row(0, 1) + assert mat == Matrix([[3, 4], [1, 2]]) + + def test_reduce_row(self): + """Test row reduction operation""" + mat = Matrix([[1, 2], [3, 4]]) + mat.reduce_row(1, 3.0, 0) + assert mat == Matrix([[1, 2], [0, -2]]) + + +class TestPivotOperations: + """Tests for pivot-related operations + + Covers: + - get_pivot_row() + - get_pivot_pos() + - get_pivot_elements() + """ + + @pytest.mark.parametrize( + "matrix, col, start_row, expected", + [ + (Matrix([[1, 2], [0, 1]]), 0, 0, 0), # First row is pivot + (Matrix([[0, 2], [1, 1]]), 0, 0, 1), # Second row is pivot + (Matrix([[0, 2], [0, 1]]), 0, 0, None), # No pivot + ], + ) + def test_get_pivot_row(self, matrix, col, start_row, expected): + """Test finding pivot row in a column""" + assert matrix.get_pivot_row(col, start_row) == expected + + def test_get_pivot_pos(self): + """Test finding all pivot positions""" + mat = Matrix([[1, 2, 3], [0, 1, 2], [0, 0, 1]]) + assert mat.get_pivot_pos() == [(0, 0), (1, 1), (2, 2)] + + def test_get_pivot_elements(self): + """Test getting pivot elements""" + mat = Matrix([[1, 2, 3], [0, 1, 2], [0, 0, 1]]) + assert mat.get_pivot_elements() == [1, 1, 1] + + +class TestReductions: + """Tests for matrix reduction methods + + Covers: + - ref() + - rref() + """ + + def test_ref(self): + """Test row echelon form""" + mat = Matrix([[1, 2], [3, 4]]) + plu = mat.ref() + assert plu.U[1, 0] == 0 # Verify lower triangular is zero + assert plu.U == Matrix([[1, 2], [0, -2]]) # Verify exact REF result + + def test_rref(self): + """Test reduced row echelon form""" + mat = Matrix([[1, 2], [3, 4]]) + rref_mat, pivots = mat.rref() + assert rref_mat[1, 0] == 0 # Below diagonal is zero + assert rref_mat[0, 1] == 0 # Above pivot is zero + assert rref_mat == Matrix([[1, 0], [0, 1]]) # Verify exact RREF result + assert pivots == (0, 1) # Verify pivot columns + + +class TestEvaluateCases: + """Tests for rank of matrix with unknowns + Covers: + - find_all_cases + - evaluate_cases + - solve + """ + + # TODO + def test_evaluate_cases(self): + """Test evaluating special cases""" + mat = Matrix([[1, 2], [3, 4]]) + rhs = Matrix([[1], [2]]) + # Just verify the method runs without error + mat.evaluate_cases(rhs) + + def test_solve(self): + A = Matrix([[1, 2], [3, 4]]) + b = Matrix([[1], [1]]) + x = A.solve(b)[0] + assert (A @ x - b) == Matrix([0, 0]) diff --git a/tests/chapters/test_c2_matrix_algebra.py b/tests/chapters/test_c2_matrix_algebra.py new file mode 100644 index 0000000..ab40390 --- /dev/null +++ b/tests/chapters/test_c2_matrix_algebra.py @@ -0,0 +1,96 @@ +"""Include the following methods in the tests +- inverse +- elem +- adj +- column_constraints +""" + +import pytest +import sympy as sym + +from ma1522 import Matrix + + +class TestMatrixInverse: + """Tests for Matrix inverse operations + + Covers: + - Left/right inverses + - Edge cases (singular matrices) + - Special cases + """ + + def test_left_inverse(self): + """Test left inverse for full column rank matrix""" + mat = Matrix([[1, 0], [0, 1], [1, 1]]) + inv = mat.inverse(option="left", verbosity=2) + assert inv is not None + assert (inv @ mat) == Matrix.eye(2) + + def test_right_inverse(self): + """Test right inverse for full row rank matrix""" + mat = Matrix([[1, 0, 1], [0, 1, 1]]) + inv = mat.inverse(verbosity=2) + assert inv is not None + assert (mat @ inv) == Matrix.eye(2) + + def test_square_inverse(self): + """Test inverse for square matrix""" + mat = Matrix([[1, 2], [3, 4]]) + inv = mat.inverse(option="both", verbosity=2) + assert (inv @ mat) == Matrix.eye(2) + assert (mat @ inv) == Matrix.eye(2) + + def test_singular_matrix(self): + """Test inverse fails for singular matrix""" + mat = Matrix([[1, 2], [2, 4]]) # Determinant = 0 + with pytest.raises(ValueError): + mat.inverse() + + def test_1x1_matrix(self): + """Test special case for 1x1 matrix""" + mat = Matrix([[5]]) + inv = mat.inverse() + assert (inv @ mat) == Matrix.eye(1) + assert (mat @ inv) == Matrix.eye(1) + assert inv == Matrix([[sym.Rational(1, 5)]]) + + +class TestAdjointOperations: + """Tests for adjoint/adjugate operations""" + + def test_adjoint(self): + """Test classical adjoint calculation""" + mat = Matrix([[1, 2], [3, 4]]) + adj = mat.adj() + assert adj == Matrix([[4, -2], [-3, 1]]) + + def test_adjoint_property(self): + """Test A * adj(A) = det(A)*I""" + mat = Matrix([[1, 2], [3, 4]]) + assert (mat @ mat.adj()) == Matrix.diag(mat.det(), mat.det()) + + +class TestElem: + """Tests for Matrix.elem() method""" + + def test_elem(self): + """Test elem() returns identity matrix of correct size""" + mat = Matrix([[1, 2], [3, 4]]) + identity_matrix = mat.elem() + assert identity_matrix == Matrix.eye(2) + assert identity_matrix.shape == (2, 2) + + +class TestColumnConstraints: + """Tests for column constraint calculations""" + + def test_column_constraints(self): + """Test column constraints for non-invertible matrix""" + x1, x2 = Matrix.create_unk_matrix(2, 1, "x") + mat = Matrix([[1, 2], [2, 4]]) + constraints = mat.column_constraints() + half_x2 = sym.Mul(x2, sym.Rational(1, 2)) + x1_minus_half = sym.Add(x1, sym.Mul(half_x2, -1)) # Equivalent to x1 - half_x2 + expected_rref = Matrix([[1, 2, half_x2], [0, 0, x1_minus_half]], aug_pos=1) + assert constraints == expected_rref diff --git a/tests/chapters/test_c3_euclidean_vector_spaces.py b/tests/chapters/test_c3_euclidean_vector_spaces.py new file mode 100644 index 0000000..37daa6e --- /dev/null +++ b/tests/chapters/test_c3_euclidean_vector_spaces.py @@ -0,0 +1,99 @@ +"""Include the following methods in the tests +- normalized +- is_linearly_independent +- simplify_basis +- extend_basis +- intersect_subspace +- is_same_subspace +- coords_relative +- transition_matrix +""" + +import sympy as sym + +from ma1522 import Matrix + + +class TestEuclideanVectorSpaces: + """Tests for euclidean vector space operations + + Covers: + - Vector normalization + - Linear independence + - Same subspace + - Relative coordinates + - Transition matrix + """ + + def test_normalized(self): + mat = Matrix([[1, 0], [1, 4]]) + normalized_mat = mat.normalized() + assert ( + normalized_mat - Matrix([[1 / sym.sqrt(2), 0], [1 / sym.sqrt(2), 1]]) # type: ignore + == Matrix.zeros(2, 2) + ) + + def test_is_linearly_independent(self): + mat = Matrix([[1, 2], [3, 4]]) + assert mat.is_linearly_independent() is True + mat = Matrix([[1, 2], [2, 4]]) + assert mat.is_linearly_independent() is False + + def test_is_same_subspace(self): + mat1 = Matrix([[1, 0], [0, 1]]) + mat2 = Matrix([[1, 2], [3, 4]]) + assert mat1.is_same_subspace(mat2) is True + + def test_transition_matrix(self): + mat1 = Matrix([[1, 0], [0, 1]]) + mat2 = Matrix([[2, 0], [0, 2]]) + transition = mat1.transition_matrix(to=mat2, verbosity=2) + assert transition == Matrix.from_str("1/2 0; 0 1/2") + + +class TestSubspaceOperations: + """Tests for subspace operations + + Covers: + - Simplify column/row space operations + - Basis extension + - Subspace intersections + """ + + def test_simplify_basis(self): + """Test simplify basis""" + mat = Matrix([[1, 2, 2, 5], [3, 4, 6, 13], [0, 0, 0, 0]]) + col_basis = mat.simplify_basis(colspace=True) + row_basis = mat.simplify_basis(colspace=False) + assert col_basis == Matrix([[1, 0], [0, 1], [0, 0]]) + assert row_basis == Matrix([[1, 0, 2, 3], [0, 1, 0, 1]]) + + def test_extend_basis(self): + """Test extending to a basis""" + mat = Matrix([[1, 1], [1, 1]]) + extended = mat.extend_basis() + assert extended.cols == 2 # Original space dimension + assert extended.rank() == 2 # Now full rank + + def test_subspace_intersection(self): + """Test subspace intersection""" + A = Matrix([[1, 0], [0, 1]]) + B = Matrix([[1, 1], [1, 1]]) + intersection = A.intersect_subspace(B) + assert intersection.cols == 1 # One common direction + + +class TestCoordinatesBasis: + def test_transition_matrix(self): + """Test transition matrix between bases""" + A = Matrix([[1, 0], [0, 1]]) + B = Matrix([[1, 1], [1, -1]]) + P = A.transition_matrix(B) + assert (P @ B) == A + + def test_coordinate_transformation(self): + """Test coordinate transformation""" + A = Matrix([[1, 0], [0, 1]]) + v = Matrix([[1], [1]]) + coords = v.coords_relative(A) + assert (A @ coords) == v diff --git a/tests/chapters/test_c4_subspaces_assoc_matrix.py b/tests/chapters/test_c4_subspaces_assoc_matrix.py new file mode 100644 index 0000000..46e2406 --- /dev/null +++ b/tests/chapters/test_c4_subspaces_assoc_matrix.py @@ -0,0 +1,13 @@ +"""Include the following methods in the tests +- nullspace +""" + +from ma1522 import Matrix + + +class TestSubspacesAssocMatrix: + def test_nullspace(self): + mat = Matrix([[1, 2], [3, 6]]) + nullspace = Matrix.from_list(mat.nullspace()) + assert nullspace.cols == 1 # One free variable + assert (mat @ nullspace).norm() == 0 # Av = 0 diff --git a/tests/chapters/test_c5_orthogonality_and_lss.py b/tests/chapters/test_c5_orthogonality_and_lss.py new file mode 100644 index 0000000..35be1d7 --- /dev/null +++ b/tests/chapters/test_c5_orthogonality_and_lss.py @@ -0,0 +1,86 @@ +"""Include the following methods in the tests +- orthogonal_complement +- is_vec_orthogonal +- is_mat_orthogonal +- orthogonal_decomposition +- proj_comp +- norm_comp +- gram_schmidt +- QRdecomposition +- solve_least_squares +- create_vander +- apply_vander +""" + +import sympy as sym + +from ma1522 import Matrix, VecDecomp + + +class TestChapter5: + def test_orthogonal_complement(self): + mat = Matrix([[1, 0], [0, 1], [0, 0]]) + ortho_comp = mat.orthogonal_complement() + assert ortho_comp.cols == 1 + assert (mat.T @ ortho_comp).is_zero_matrix + + mat = Matrix([[1, 0], [0, 1]]) + orth_comp = mat.orthogonal_complement() + assert orth_comp == Matrix([]) + + def test_is_vec_orthogonal(self): + mat = Matrix([[1, 0], [0, 1]]) + assert mat.is_vec_orthogonal() is True + mat = Matrix([[2, 0], [0, 3]]) + assert mat.is_vec_orthogonal() is True + mat = Matrix([[1, 1], [1, 1]]) + assert mat.is_vec_orthogonal() is False + + def test_is_mat_orthogonal(self): + mat = Matrix([[1, 0], [0, 1]]) + assert mat.is_mat_orthogonal() is True + mat = Matrix([[2, 0], [0, 3]]) + assert mat.is_mat_orthogonal() is False + mat = Matrix([[1, 1], [1, 0]]) + assert mat.is_mat_orthogonal() is False + + def test_orthogonal_decomposition(self): + """Test vector decomposition into orthogonal components""" + A = Matrix([[1, 0], [1, 1]]) + b = Matrix([[-1], [2]]) + decomp = b.orthogonal_decomposition(A) + assert isinstance(decomp, VecDecomp) + assert (decomp.proj + decomp.norm) == b + + def test_proj_comp(self): + to = Matrix([[1, 0], [0, 1]]) + vec = Matrix([[1], [1]]) + proj = vec.proj_comp(to) + assert proj == vec + + def test_norm_comp(self): + to = Matrix([[1, 0], [0, 1]]) + vec = Matrix([[1], [1]]) + norm = vec.norm_comp(to) + assert norm == Matrix([[0], [0]]) + + def test_gram_schmidt(self): + mat = Matrix([[1, 1], [1, 0]]) + ortho_mat = mat.gram_schmidt(factor=True) + assert ortho_mat.full.is_vec_orthogonal() is True # type: ignore + assert ortho_mat.full.select_cols(0).dot(ortho_mat.full.select_cols(1)) == 0 # type: ignore + + def test_QRdecomposition(self): + mat = Matrix([[1, 1], [1, 0]]) + q, r = mat.QRdecomposition() + # Verify Q is orthogonal + assert (q.T @ q) == Matrix.eye(2) + # Verify R is upper triangular + assert r[1, 0] == 0 + + def test_solve_least_squares(self): + """Test least squares solution""" + A = Matrix([[0, 1], [1, 1], [2, 1]]) + b = Matrix([[6], [0], [0]]) + x = A.solve_least_squares(b) + assert (A @ x - b).norm() == sym.sqrt(6) # Minimized error diff --git a/tests/chapters/test_c6_eigen_analysis.py b/tests/chapters/test_c6_eigen_analysis.py new file mode 100644 index 0000000..e650975 --- /dev/null +++ b/tests/chapters/test_c6_eigen_analysis.py @@ -0,0 +1,69 @@ +"""Include the following methods in the tests +- cpoly +- is_diagonalizable +- eigenvects_associated +- diagonalize +- is_orthogonally_diagonalizable +- orthogonally_diagonalize +- is_stochastic +- equilibrium_vectors +- singular_value_decomposition +- fast_svd +""" + +import sympy as sym + +from ma1522 import Matrix + + +class TestChapter6: + def test_cpoly(self): + mat = Matrix([[1, 2], [3, 4]]) + poly = mat.cpoly() + poly = sym.Poly(poly) + assert poly.all_coeffs() == [1, -5, -2] + + def test_cpoly_2(self): + res = Matrix.diag(-1, 4, sym.Rational(1, 2)).cpoly() + roots = set() + for expr in res.args: # type: ignore + roots.add(sym.solve(expr)[0]) + assert roots == set((4, sym.Rational(1, 2), -1)) + + def test_is_diagonalizable(self): + mat = Matrix([[1, 1], [0, 1]]) + assert mat.is_diagonalizable() is False + mat = Matrix([[1, 0], [0, 2]]) + assert mat.is_diagonalizable() is True + + def test_diagonalize(self): + mat = Matrix([[1, 2], [0, 3]]) + pdp = mat.diagonalize() + assert (pdp.P @ pdp.D @ pdp.P.inv() - mat).norm() < 1e-10 + + def test_is_orthogonally_diagonalizable(self): + mat = Matrix([[1, 2], [2, 1]]) + assert mat.is_orthogonally_diagonalizable() is True + mat = Matrix([[1, 2], [3, 4]]) + assert mat.is_orthogonally_diagonalizable() is False + + def test_orthogonally_diagonalize(self): + mat = Matrix([[1, 2], [2, 1]]) + pdp = mat.orthogonally_diagonalize(verbosity=0) + assert (pdp.P @ pdp.D @ pdp.P.T - mat).norm() < 1e-10 + + def test_equilibrium_vectors(self): + mat = Matrix([[0.8, 0.3], [0.2, 0.7]]) + eq_vects = mat.equilibrium_vectors() + assert mat @ eq_vects == eq_vects + + def test_fast_svd(self): + mat = Matrix([[1, 2], [3, 4]]) + svd = mat.fast_svd(option="np", identify=False) + U, S, V = svd.U, svd.S, svd.V + assert (Matrix(U) @ Matrix(S) @ Matrix(V).T - mat).norm() < 1e-10 + + def test_singular_value_decomposition(self): + mat = Matrix([[1, 2], [3, 4]]) + svd = mat.singular_value_decomposition(verbosity=2) + assert (svd.U @ svd.S @ svd.V.T - mat).norm() < 1e-9 diff --git a/tests/chapters/test_c7_linear_transformation.py b/tests/chapters/test_c7_linear_transformation.py new file mode 100644 index 0000000..04452c4 --- /dev/null +++ b/tests/chapters/test_c7_linear_transformation.py @@ -0,0 +1,15 @@ +"""Include the following methods in the tests +- standard_matrix +""" + +from ma1522 import Matrix + + +class TestLinearTransformations: + def test_standard_matrix(self): + """Test standard matrix representation""" + standard_matrix = Matrix.create_rand_matrix(3, 3) + input_vectors = Matrix.create_rand_matrix(3, 3) + output_vectors = standard_matrix @ input_vectors + sol = Matrix.standard_matrix(input_vectors, output_vectors)[0] + assert sol == standard_matrix diff --git a/tests/tutorials/test_tut01.py b/tests/tutorials/test_tut01.py new file mode 100644 index 0000000..f86b2c0 --- /dev/null +++ b/tests/tutorials/test_tut01.py @@ -0,0 +1,156 @@ +import pytest + +import sympy as sym + +from ma1522 import Matrix + + +class TestTutorial01: + def test_question_3a(self): + mat = Matrix([[3, 2, -4], [2, 3, 3], [5, -3, 1]]) + + aug = Matrix([3, 15, 14]) + + aug_mat = mat.row_join(aug) + + # Intermediate: REF should produce an upper-triangular echelon form + plu = aug_mat.ref() + assert plu.U.is_echelon + + # Intermediate: full RREF should be the identity augmented with the solution + assert plu.U.rref(pivots=False) == Matrix( # type: ignore + [[1, 0, 0, 3], [0, 1, 0, 1], [0, 0, 1, 2]], aug_pos=2 + ) + # Solution column only (using the RREF namedtuple's .rref attribute) + rref = plu.U.rref() + assert rref.rref[:, -1] == Matrix([3, 1, 2]) # type: ignore + + def test_question_3b(self): + mat = Matrix([[1, 1, -1, -2], [2, 1, -1, 1], [-1, 1, -3, 1]]) + aug = Matrix([0, -2, 4]) + aug_mat = mat.row_join(aug) + + # Intermediate: RREF shows the parametric structure — x4 is the free variable + rref = aug_mat.rref(pivots=False) + assert rref == Matrix( + [ + [1, 0, 0, 3, -2], + [0, 1, 0, sym.Rational(-19, 2), 2], + [0, 0, 1, sym.Rational(-9, 2), 0], + ], + aug_pos=3, + ) + + # Solution expressed in terms of the free parameter + sol = mat.solve(aug)[0] + unk = sol[-1] + assert sol == Matrix([-3 * unk - 2, 19 * unk / 2 + 2, 9 * unk / 2, unk]) # type: ignore + + def test_question_3c(self): + aug_mat = Matrix([[1, -4, 2, -2], [1, 2, -2, -3], [1, -1, 0, 4]], aug_pos=2) + + # Intermediate: RREF has a pivot in the augmented column → inconsistent system + rref = aug_mat.rref(pivots=False) + # Last row must be all-zero on the coefficient side with a non-zero RHS entry + assert rref[-1, :-1] == Matrix([[0, 0, 0]]) + assert rref[-1, -1] != 0 + + mat = aug_mat.select_cols(0, 1, 2) + aug = aug_mat.select_cols(3) + + with pytest.raises(ValueError): + mat.solve(aug)[0] + + def test_question_4(self): + # TODO: Implement better evaluate all cases + a, b = sym.symbols("a b") # Define symbolic variables + mat = Matrix([[a, 0, b, 2], [a, a, 4, 4], [0, a, 2, b]], aug_pos=2) + + # Intermediate: REF always produces an upper-triangular form + plu = mat.ref() + assert plu.U.is_echelon + + # Case 1: b != 2, a = 0 → no solution (pivot in augmented column) + assert plu.U.subs({a: 0}).rref(pivots=False) == Matrix( + [[0, 0, 1, 0], [0, 0, 0, 1], [0, 0, 0, 0]], aug_pos=2 + ) + # Case 2: b != 2, a != 0 → unique solution + assert plu.U.rref(pivots=False) == Matrix( + [[1, 0, 0, (2 - b) / a], [0, 1, 0, (b - 2) / a], [0, 0, 1, 1]], aug_pos=2 + ) + # Case 3: b = 2, a != 0 → infinitely many solutions, 1 free variable (x3) + assert plu.U.subs({b: 2}).rref(pivots=False) == Matrix( + [[1, 0, 2 / a, 2 / a], [0, 1, 2 / a, 2 / a], [0, 0, 0, 0]], aug_pos=2 + ) + # Case 4: b = 2, a = 0 → infinitely many solutions, 2 free variables (x1, x2) + assert plu.U.subs({a: 0, b: 2}).rref(pivots=False) == Matrix( + [[0, 0, 1, 1], [0, 0, 0, 0], [0, 0, 0, 0]], aug_pos=2 + ) + + def test_question_6(self): + x, y, z = sym.symbols("x y z") + + # Intermediate: substituting u=x², v=y², w=z² linearises the system; + # RREF gives a unique solution u=4, v=0, w=1 + mat = Matrix([[1, -1, 2, 6], [2, 2, -5, 3], [2, 5, 1, 9]], aug_pos=2) + assert mat.rref(pivots=False) == Matrix( + [[1, 0, 0, 4], [0, 1, 0, 0], [0, 0, 1, 1]], aug_pos=2 + ) + + # Four real (x,y,z) solutions come from x=±2, y=0, z=±1 + vec = Matrix([x**2, y**2, z**2]) + coef_mat = Matrix([[1, -1, 2], [2, 2, -5], [2, 5, 1]]) + aug = Matrix([6, 3, 9]) + sols = sym.solve(coef_mat @ vec - aug) + assert len(sols) == 4 + + # Each solution must satisfy x²=4, y²=0, z²=1 + for sol in sols: + assert sol[x] ** 2 == 4 + assert sol[y] ** 2 == 0 + assert sol[z] ** 2 == 1 + + def test_question_7(self): + aug_mat = Matrix( + [ + [1, 0, 1, 0, 0, 0, 0, 800], + [1, -1, 0, 1, 0, 0, 0, 200], + [0, 1, 0, 0, -1, 0, 0, 500], + [0, 0, 1, 0, 0, 1, 0, 750], + [0, 0, 0, -1, 0, -1, 1, -600], + [0, 0, 0, 0, 1, 0, -1, -50], + ], + aug_pos=6, + ) + + # Intermediate (7a): RREF has 5 pivots among 7 coefficient columns + # → 2 free variables, so the system is underdetermined + rref = aug_mat.rref(pivots=False) + assert rref.shape == (6, 8) + # 5 coefficient columns become identity columns; x6 and x7 are free + pivot_cols = [ + c + for c in range(7) + if any( + rref[r, c] == 1 and all(rref[rr, c] == 0 for rr in range(6) if rr != r) + for r in range(6) + ) + ] + assert len(pivot_cols) == 5 + + mat = aug_mat.select_cols(*range(7)) + aug = aug_mat.select_cols(7) + + sol = mat.solve(aug)[0] + + # Intermediate: the general solution has exactly 2 free symbols (x6 and x7) + assert len(sol.free_symbols) == 2 + + y = sol[5] + z = sol[6] + + # (7b) Substitute x6=50, x7=100 + assert sol.subs({y: 50, z: 100}) == Matrix([100, 550, 700, 650, 50, 50, 100]) # type: ignore + + # (7c) Set x6=-50 so that x1=0; x7 remains free + assert sol.subs({y: -50}) == Matrix([0, z + 450, 800, z + 650, z - 50, -50, z]) # type: ignore diff --git a/tests/tutorials/test_tut02.py b/tests/tutorials/test_tut02.py new file mode 100644 index 0000000..536ba5c --- /dev/null +++ b/tests/tutorials/test_tut02.py @@ -0,0 +1,86 @@ +import pytest +import sympy as sym + +from ma1522 import Matrix + + +class TestTutorial02: + def test_question_2a(self): + A = Matrix.from_str("1 1 0 1; 0 1 1 0; 0 0 1 1", col_sep=" ", row_sep=";") + assert A.shape == (3, 4) + + X = A.inverse("right", matrices=2) + # Particular solution must satisfy A @ X_part == I_3 + assert (A @ X.part_sol).applyfunc(sym.simplify) == Matrix.eye(3) + assert X.part_sol.shape == (4, 3) + + def test_question_2b(self): + B = Matrix.from_str("1 0 1; 1 1 0; 0 1 1; 0 0 1", col_sep=" ", row_sep=";") + assert B.shape == (4, 3) + + Y = B.inverse("left", matrices=2) + # Particular solution must satisfy Y_part @ B == I_3 + assert (Y.part_sol @ B).applyfunc(sym.simplify) == Matrix.eye(3) + assert Y.part_sol.shape == (3, 4) + + def test_question_3a(self): + A = Matrix.from_str("5 -2 6 0; -2 1 3 1") + result = ( + A.copy() + .reduce_row(1, -2 / 5, 0) + .scale_row(0, 1 / 5) + .scale_row(1, 5) + .reduce_row(0, -2 / 5, 1) + ) + # Result must equal the RREF of A + expected_rref = Matrix([[1, 0, 12, 2], [0, 1, 27, 5]]) + assert result.applyfunc(sym.simplify) == expected_rref + + def test_question_3b(self): + A = Matrix.from_str("-1 3 -4; 2 4 1; -4 2 -9") + result = ( + A.copy() + .reduce_row(1, -2, 0) + .reduce_row(2, 4, 0) + .reduce_row(2, -1, 1) + .scale_row(0, -1) + .scale_row(1, sym.Rational(1, 10)) + .reduce_row(0, -3, 1) + ) + expected_rref = A.rref(pivots=False) + assert result.applyfunc(sym.simplify) == expected_rref + + def test_question_3c(self): + A = Matrix.from_str("1 -1 0; 2 -2 1; 1 2 3") + result = ( + A.copy() + .reduce_row(1, 2, 0) + .reduce_row(2, 1, 0) + .swap_row(1, 2) + .scale_row(1, sym.Rational(1, 3)) + .reduce_row(1, 1, 2) + .reduce_row(0, -1, 1) + ) + expected_rref = A.rref(pivots=False) + assert result.applyfunc(sym.simplify) == expected_rref + + def test_question_4a(self): + mat = Matrix.from_str("-1 3; 3 -2") + inv = mat.inverse() + assert mat.det() != 0 + assert (inv @ mat).applyfunc(sym.simplify) == Matrix.eye(2) + + def test_question_4b(self): + mat = Matrix.from_str("-1 3 -4; 2 4 1; -4 2 -9") + # Singular matrix → det == 0 → inverse raises ValueError + assert mat.det() == 0 + with pytest.raises(ValueError): + mat.inverse() + + def test_question_5(self): + a, b, c = sym.symbols("a b c") + mat = Matrix([[1, 1, 1], [a, b, c], [a**2, b**2, c**2]]) + + det = mat.det() + expected = (b - a) * (c - a) * (c - b) + assert sym.simplify(det - expected) == 0 diff --git a/tests/tutorials/test_tut03.py b/tests/tutorials/test_tut03.py new file mode 100644 index 0000000..5741afa --- /dev/null +++ b/tests/tutorials/test_tut03.py @@ -0,0 +1,99 @@ +import sympy as sym + +from ma1522 import Matrix + + +class TestTutorial03: + def test_question_1(self): + E_1 = Matrix.eye(4).scale_row(1, sym.Rational(1, 2)) + E_2 = Matrix.eye(4).reduce_row(0, 1, 1) + E_3 = Matrix.eye(4).swap_row(1, 3) + E_4 = Matrix.eye(4).reduce_row(2, -3, 0) + + A = E_4 @ E_3 @ E_2 @ E_1 + A_inv = E_1**-1 @ E_2**-1 @ E_3**-1 @ E_4**-1 + assert (A @ A_inv).applyfunc(sym.simplify) == Matrix.eye(4) + assert (A_inv @ A).applyfunc(sym.simplify) == Matrix.eye(4) + for E in [E_1, E_2, E_3, E_4]: + assert E.det() != 0 + + def test_question_2a(self): + A = Matrix.from_str("2 -1 2; -6 0 -2; 8 -1 5") + b = Matrix.from_str("1; 0; 4") + + plu = A.ref() + assert plu.U.is_echelon + assert plu.P == Matrix.eye(3) + assert (plu.L @ plu.U).applyfunc(sym.simplify) == A + + # Solve the unique system A @ x = b + x = A.solve(b)[0] + assert (A @ x).applyfunc(sym.simplify) == b + + def test_question_2b(self): + A = Matrix.from_str("2 -4 4 -2; 6 -9 7 -3; -1 -4 8 0") + b = Matrix.from_str("0; 0; 5") + + plu = A.ref() + assert plu.U.is_echelon + assert (plu.P.T @ plu.L @ plu.U).applyfunc(sym.simplify) == A + + # Solve underdetermined system A @ x = b (multiple solutions) + sol = A.solve(b)[0] + assert len(sol.free_symbols) > 0 + part = sol.subs({v: 0 for v in sol.free_symbols}) + assert (A @ part).applyfunc(sym.simplify) == b + + def test_question_3a(self): + A = Matrix.from_str("2 -6 6; -4 5 -7; 3 5 -1; -6 4 -8; 8 -3 9") + plu = A.ref() + + assert plu.U.is_echelon + assert (plu.P.T @ plu.L @ plu.U).applyfunc(sym.simplify) == A + + def test_question_4(self): + x = sym.Symbol("x") + A = Matrix([[-x, 1, 0], [0, -x, 1], [2, -5, 4 - x]]) + + det = A.det().factor() + expected = -((x - 1) ** 2) * (x - 2) + assert sym.simplify(det - expected) == 0 + + def test_question_5(self): + a, b, c, p, q, r, u, v, w, x = sym.symbols("a b c p q r u v w x") + + lhs_mat = Matrix( + [ + [a + p * x, b + q * x, c + r * x], + [p + u * x, q + v * x, r + w * x], + [u + a * x, v + b * x, w + c * x], + ] + ) + rhs_mat = Matrix([[a, b, c], [p, q, r], [u, v, w]]) + + ratio = sym.simplify(lhs_mat.det() / rhs_mat.det()) + assert sym.simplify(ratio - (1 + x**3)) == 0 + + def test_question_7(self): + mat = Matrix.from_str("1 5 3; 0 2 -2; 0 1 3") + rhs = Matrix.from_str("1; 2; 0") + + sol = mat.cramer_solve(rhs) + # Verify solution satisfies the system + assert (mat @ sol).applyfunc(sym.simplify) == rhs + assert sol == Matrix( + [sym.Rational(-2, 1), sym.Rational(3, 4), sym.Rational(-1, 4)] + ) + + def test_question_8(self): + A = Matrix.from_str("1 -1 2; 0 2 1; 3 0 6") + + adj = A.adj() + det = A.det() + + assert det != 0 + + A_inv = adj / det + assert (A @ A_inv).applyfunc(sym.simplify) == Matrix.eye(3) + assert (A_inv @ A).applyfunc(sym.simplify) == Matrix.eye(3) + assert (adj / det).applyfunc(sym.simplify) == A.inverse() diff --git a/tests/tutorials/test_tut04.py b/tests/tutorials/test_tut04.py new file mode 100644 index 0000000..9203db6 --- /dev/null +++ b/tests/tutorials/test_tut04.py @@ -0,0 +1,52 @@ +import pytest + +from ma1522 import Matrix + + +class TestTutorial04: + def test_question_2a(self): + U = Matrix.from_str("2 1 0 3; 3 -1 5 2; -1 0 2 1").T + actual_contraints = U.column_constraints()[3, 3] + vec = Matrix.create_unk_matrix(4, 1, "x") + expected = vec[0, 0] + 7 * vec[1, 0] + 2 * vec[2, 0] - 3 * vec[3, 0] + assert actual_contraints == expected + + def test_question_2b(self): + U = Matrix.from_str("2 1 0 3; 3 -1 5 2; -1 0 2 1").T + E = U.extend_basis() + assert E.shape == (4, 4) + assert E.rank() == 4 + + def test_question_3a(self): + V = Matrix([[1, -1, -1]]).nullspace() + S = Matrix.from_str("1 1 0; 5 2 3").T + assert S.is_same_subspace(Matrix.from_list(V)) + + def test_question_3b(self): + S = Matrix.from_str("1 1 0; 5 2 3").T + T = S.row_join(Matrix.from_str("0 0 1").T) + T.rm_aug_line() + assert T.is_same_subspace() + + def test_question_4(self): + S_i = Matrix.from_str("1 0 0 1; 0 1 0 0; 1 1 1 1; 1 1 1 0").T + assert S_i.is_same_subspace() + + S_ii = Matrix.from_str("1 2 1 0; 1 1 -1 0; 0 0 0 1").T + assert not S_ii.is_same_subspace() + + S_iii = Matrix.from_str("6 4 -2 4; 2 0 0 1; 3 2 -1 2; 5 6 -3 2; 0 4 -2 -1").T + assert not S_iii.is_same_subspace() + + S_iv = Matrix.from_str("1 1 0 0; 1 2 -1 1; 0 0 1 1; 2 1 2 1; 1 2 3 4").T + assert S_iv.is_same_subspace() + + def test_question_5a(self): + U = Matrix.from_str("2 -2 0; -1 1 -1; 0 0 9").T + V = Matrix.from_str("1 -1 -5; 0 1 1").T + assert not U.is_same_subspace(V) + + def test_question_5b(self): + U = Matrix.from_str("1 6 4; 2 4 -1; -1 2 5").T + V = Matrix.from_str("1 -2 -5; 0 8 9").T + assert U.is_same_subspace(V) diff --git a/tests/tutorials/test_tut05.py b/tests/tutorials/test_tut05.py new file mode 100644 index 0000000..4745ce2 --- /dev/null +++ b/tests/tutorials/test_tut05.py @@ -0,0 +1,100 @@ +import sympy as sym + +from ma1522 import Matrix + + +class TestTutorial05: + def test_question_1a(self): + S = Matrix.from_str("2 -1 0; 0 3 2; 2 4 3; 3 6 6").T + assert not S.is_linearly_independent() + assert S.rank() == 3 + + def test_question_1b(self): + S = Matrix.from_str("1 1 0; 3 4 2").T + assert S.is_linearly_independent() + assert S.rank() == 2 + + def test_question_1c(self): + # Contains the zero vector → always linearly dependent + S = Matrix.from_str("1 1 0; 3 4 2; 0 0 0").T + assert not S.is_linearly_independent() + + def test_question_1d(self): + S = Matrix.from_str("1 0 0; 0 1 1; 1 2 -1").T + assert S.is_linearly_independent() + assert S.rank() == 3 + + def test_question_2d(self): + a, b, c = sym.symbols("a b c") + u, v, w = sym.symbols("u v w") + v1, v2, v3 = u, u + v, u + v + w + sols = sym.solve(a * v1 + b * v2 + c * v3, (a, b, c)) + assert sols == {a: 0, b: 0, c: 0} + + def test_question_2e(self): + a, b, c, d = sym.symbols("a b c d") + u, v, w = sym.symbols("u v w") + v1, v2, v3, v4 = u + v, v + w, u + w, u + v + w + sols = sym.solve(a * v1 + b * v2 + c * v3 + d * v4, (a, b, c, d)) + assert len(sols) == 1 + half = sym.Rational(1, 2) + assert sols[0] == (-half * d, -half * d, -half * d, d) + + def test_question_3a(self): + a, b, c, d = sym.symbols("a b c d") + V = Matrix([a + b, a + c, c + d, b + d]) + vecs = V.sep_unk() + + expected_a = Matrix([1, 1, 0, 0]) + expected_b = Matrix([1, 0, 0, 1]) + expected_c = Matrix([0, 1, 1, 0]) + expected_d = Matrix([0, 0, 1, 1]) + + assert vecs[a] == expected_a + assert vecs[b] == expected_b + assert vecs[c] == expected_c + assert vecs[d] == expected_d + + V_mat = Matrix.from_list([*vecs.values()]) + basis = V_mat.get_linearly_independent_vectors() + assert basis.shape == (4, 3) + assert basis.rank() == 3 + + def test_question_3b(self): + V = Matrix.from_str("1 0 -1; -1 2 3; 0 3 0; 1 -1 1").T + basis = V.get_linearly_independent_vectors() + assert basis.shape == (3, 3) + assert basis.rank() == 3 + + def test_question_3c(self): + mat = Matrix.from_str("1 0 1 1 -1; 0 1 1 2 1; 1 1 2 1 -2") + null = mat.nullspace() + assert len(null) == mat.cols - mat.rank() + for v in null: + assert (mat @ v).applyfunc(sym.simplify) == Matrix.zeros(mat.rows, 1) + + def test_question_4(self): + a = sym.Symbol("a", real=True) + U = Matrix([[a, 1, -1], [-1, a, 1], [1, -1, a]]).T + singular_vals = sym.solve(U.det(), a) + assert len(singular_vals) == 1 + assert singular_vals[0] == 0 + + def test_question_5b(self): + U = Matrix.from_str("1 1 1 1; 1 2 2 1").T + V = Matrix.from_str("1 0 1 0; 1 0 2 -1").T + uv_basis = U.row_join(V).get_linearly_independent_vectors() + assert uv_basis.shape == (4, 3) + assert uv_basis.rank() == 3 + + def test_question_5e(self): + U = Matrix.from_str("1 1 1 1; 1 2 2 1").T + V = Matrix.from_str("1 0 1 0; 1 0 2 -1").T + intersection = U.intersect_subspace(V) + assert intersection.shape == (4, 1) + for j in range(intersection.cols): + col = intersection.select_cols(j) + aug_U = U.row_join(col) + aug_V = V.row_join(col) + assert aug_U.rank() == U.rank() + assert aug_V.rank() == V.rank() diff --git a/tests/tutorials/test_tut06.py b/tests/tutorials/test_tut06.py new file mode 100644 index 0000000..677b6eb --- /dev/null +++ b/tests/tutorials/test_tut06.py @@ -0,0 +1,108 @@ +import pytest +import sympy as sym + +from ma1522 import Matrix + + +class TestTutorial06: + def test_question_1a(self): + S = Matrix.from_str("1 2 -1; 0 2 1; 0 -1 3").T + basis = S.get_linearly_independent_vectors() + assert basis.shape == (3, 3) + assert S.rank() == 3 + + def test_question_1b(self): + S = Matrix.from_str("1 2 -1; 0 2 1; 0 -1 3").T + w = Matrix.from_str("1; 1; 1") + coords = w.coords_relative(S) + # Check: S @ coords == w + assert (S @ coords).applyfunc(sym.simplify) == w + assert coords.applyfunc(sym.simplify) == Matrix( + [ + sym.Integer(1), + sym.Rational(-1, 7), + sym.Rational(5, 7), + ] + ) + + def test_question_1c(self): + S = Matrix.from_str("1 2 -1; 0 2 1; 0 -1 3").T + T = Matrix.from_str("1 5 4; -1 3 7; 2 2 4").T + P_T_to_S = T.transition_matrix(S) + assert (S @ P_T_to_S).applyfunc(sym.simplify) == T + + def test_question_1d(self): + S = Matrix.from_str("1 2 -1; 0 2 1; 0 -1 3").T + T = Matrix.from_str("1 5 4; -1 3 7; 2 2 4").T + P_T_to_S = T.transition_matrix(S) + P_S_to_T = S.transition_matrix(T) + assert P_S_to_T.applyfunc(sym.simplify) == P_T_to_S.inverse() + + def test_question_1e(self): + T = Matrix.from_str("1 5 4; -1 3 7; 2 2 4").T + w = Matrix.from_str("1; 1; 1") + w_T = w.coords_relative(T) + assert (T @ w_T).applyfunc(sym.simplify) == w + + def test_question_3a(self): + A = Matrix.from_str("1 -1 1; 1 1 -1; -1 -1 1") + b = Matrix.from_str("2; 1; 0") + # b is NOT in the column space of A + with pytest.raises(ValueError): + b.coords_relative(A) + + def test_question_3b(self): + A = Matrix.from_str("1 9 1; -1 3 1; 1 1 1") + b = Matrix.from_str("5 1 -1") + scalars = b.T.coords_relative(A.T) + assert scalars == Matrix([1, -3, 1]) + assert (A.T @ scalars).applyfunc(sym.simplify) == b.T + + def test_question_3c(self): + A = Matrix.from_str("1 2 0 1; 0 1 2 1; 1 2 1 3; 0 1 2 2") + assert A.rank() == 4 + assert A.is_same_subspace() + assert A.T.is_same_subspace() + + def test_question_4a(self): + A = Matrix.from_str("1 2 5 3; 1 -4 -1 -9; -1 0 -3 1; 2 1 7 0; 0 1 1 2") + r = A.rank() + n = A.nullity() + assert r + n == A.cols + rowspace = A.get_linearly_independent_vectors(colspace=False) + assert rowspace.rows == r + colspace = A.get_linearly_independent_vectors(colspace=True) + assert colspace.cols == r + nullvecs = A.nullspace() + assert len(nullvecs) == n + + def test_question_4b(self): + A = Matrix.from_str("1 3 7; 2 1 8; 3 -5 -1; 2 -2 2; 1 1 5") + rowspace = A.simplify_basis(colspace=False) + assert rowspace.shape == (3, 3) + assert rowspace.rank() == 3 + colspace = A.simplify_basis(colspace=True) + assert colspace.shape == (5, 3) + assert colspace.rank() == 3 + with pytest.warns(UserWarning): # trivial null space + nullspace = A.nullspace(verbosity=2) + assert len(nullspace) == 0 + + def test_question_5a(self): + W = Matrix.from_str("1 -2 0 0 3; 2 -5 -3 -2 6; 0 5 15 10 0; 2 1 15 8 6").T + basis = W.get_linearly_independent_vectors(colspace=True) + assert basis.shape == (5, 3) + assert basis.rank() == 3 + + def test_question_5c(self): + W = Matrix.from_str("1 -2 0 0 3; 2 -5 -3 -2 6; 0 5 15 10 0; 2 1 15 8 6").T + extended = W.extend_basis() + assert extended.shape == (5, 5) + assert extended.rank() == 5 + + def test_question_6(self): + V = Matrix.from_str("1 0 1 3; 2 -1 0 1; -1 3 5 12; 0 1 2 5; 3 -1 1 4").T + S_prime = V.get_linearly_independent_vectors(colspace=True) + assert S_prime.is_same_subspace(V) + assert S_prime.shape == (4, 2) + assert S_prime.rank() == 2 diff --git a/tests/tutorials/test_tut07.py b/tests/tutorials/test_tut07.py new file mode 100644 index 0000000..bf0e851 --- /dev/null +++ b/tests/tutorials/test_tut07.py @@ -0,0 +1,134 @@ +import pytest +import sympy as sym + +from ma1522 import Matrix +from ma1522.custom_types import ScalarFactor + + +class TestTutorial07: + def test_question_1b(self): + mat = Matrix.from_str("1 3 -2 0; 2 6 -5 -2; 0 0 5 10") + aug = Matrix.zeros(rows=3, cols=1) + + sol = mat.solve(aug)[0] + assert len(sol.free_symbols) == 2 + + x = Matrix([-3, 1, 0, 0]) + z = Matrix([-4, 0, -2, 1]) + + sol_dict = sol.sep_unk() + assert x in sol_dict.values() + assert z in sol_dict.values() + + def test_question_1c(self): + A = Matrix.from_str("1 3 -2 0; 2 6 -5 -2; 0 0 5 10").T + v = Matrix.create_unk_matrix(4, 1, "x") + sol = sym.solve(A.T @ v, v) + v_sub = v.subs(sol) + assert len(v_sub.free_symbols) == 2 + + W_perp = A.orthogonal_complement() + assert W_perp.cols == A.rows - A.rank() + + # Every column of W_perp must be orthogonal to every column of A + for j in range(W_perp.cols): + v = W_perp.select_cols(j) + assert (A.T @ v).applyfunc(sym.simplify) == Matrix.zeros(A.cols, 1) + + def test_question_3a(self): + v1 = Matrix.from_str("1; 2; -1") + v2 = Matrix.from_str("1; 0; 1") + assert v1.dot(v1) == 6 + assert v1.dot(v2) == 0 + assert v2.dot(v1) == 0 + assert v2.dot(v2) == 2 + + def test_question_3b(self): + v1 = Matrix.from_str("1; 2; -1") + v2 = Matrix.from_str("1; 0; 1") + V = v1.row_join(v2) + gram = V.T @ V + assert gram == Matrix([[6, 0], [0, 2]]) + + def test_question_4a(self): + S = Matrix.from_str("1 1 1 1 1; 1 2 -1 -2 0; 1 -1 1 -1 0").T + assert S.is_linearly_independent(verbosity=2) + + def test_question_4b(self): + S = Matrix.from_str("1 1 1 1 1; 1 2 -1 -2 0; 1 -1 1 -1 0").T + # All pairs are orthogonal + assert S.is_vec_orthogonal() + + def test_question_4c(self): + S = Matrix.from_str("1 1 1 1 1; 1 2 -1 -2 0; 1 -1 1 -1 0").T + W_perp = S.orthogonal_complement() + assert W_perp.shape == (5, 2) + assert W_perp.rank() == 2 + for j in range(W_perp.cols): + v = W_perp.select_cols(j) + assert (S.T @ v).applyfunc(sym.simplify) == Matrix.zeros(3, 1) + + def test_question_4d(self): + S = Matrix.from_str("1 1 1 1 1; 1 2 -1 -2 0; 1 -1 1 -1 0").T + T_norm = S.normalized(factor=True) + assert T_norm.full.shape == S.shape + assert T_norm.full.is_vec_orthogonal() + assert T_norm.diag.is_diagonal() + assert T_norm.order == "FD" + + def test_question_4ef(self): + S = Matrix.from_str("1 1 1 1 1; 1 2 -1 -2 0; 1 -1 1 -1 0").T + v = Matrix.from_str("2; 0; 1; 1; -1") + W = S + v_proj = v.proj_comp(W, verbosity=1) + + # v_proj must be in W (check consistency) + aug = W.row_join(v_proj) + assert aug.rank() == W.rank() + + # v - v_proj must be orthogonal to W + W_perp = S.orthogonal_complement() + v_norm = v - v_proj + aug2 = W_perp.row_join(v_norm) + assert aug2.rank() == W_perp.rank() + + def test_question_5a(self): + S = Matrix.from_str("1 2 2 -1; 1 1 -1 1; -1 1 -1 -1; -2 1 1 2").T + assert S.is_vec_orthogonal() + assert S.rank() == 4 + + def test_question_5b(self): + S = Matrix.from_str("1 2 2 -1; 1 1 -1 1; -1 1 -1 -1; -2 1 1 2").T + with pytest.warns(UserWarning): # trivial orthogonal complement + w = S.orthogonal_complement(verbosity=1) + assert len(w) == 0 + + def test_question_5d(self): + S = Matrix.from_str("1 2 2 -1; 1 1 -1 1; -1 1 -1 -1; -2 1 1 2").T + v = Matrix.from_str("0; 1; 2; 3") + v_S = v.coords_relative(S) + assert (S @ v_S).applyfunc(sym.simplify) == v + expected = Matrix( + [ + sym.Rational(3, 10), + sym.Rational(1, 2), + -1, + sym.Rational(9, 10), + ] + ) + assert v_S.applyfunc(sym.simplify) == expected + + def test_question_5e(self): + S = Matrix.from_str("1 2 2 -1; 1 1 -1 1; -1 1 -1 -1; -2 1 1 2").T + T = S.copy().normalized(factor=True) + assert isinstance(T, ScalarFactor) + assert T.full.is_vec_orthogonal() + T_eval = T.eval() + assert isinstance(T_eval, Matrix) + assert T_eval.shape == S.shape + assert T_eval.is_mat_orthogonal() + + w_S = Matrix.from_str("1; 2; 1; 1") + w = S @ w_S + w_T = w.coords_relative(T_eval) + assert (T_eval @ w_T).applyfunc(sym.simplify) == w diff --git a/tests/tutorials/test_tut08.py b/tests/tutorials/test_tut08.py new file mode 100644 index 0000000..1aca96b --- /dev/null +++ b/tests/tutorials/test_tut08.py @@ -0,0 +1,100 @@ +import gen +import pytest +import sympy as sym + +from ma1522 import Matrix +from ma1522.custom_types import ScalarFactor + + +class TestTutorial08: + def test_question_1a(self): + U = Matrix.from_str("1 1 1 1; 1 -1 1 0; 1 1 -1 -1; 1 2 0 1").T + Q_SF = U.gram_schmidt(factor=True, verbosity=1) + assert isinstance(Q_SF, ScalarFactor) + Q = U.gram_schmidt(factor=False, verbosity=1) + assert isinstance(Q, Matrix) + assert (Q - Q_SF.eval()).applyfunc(sym.simplify) == Matrix.zeros(*Q.shape) + + assert (Q.T @ Q).applyfunc(lambda x: x.simplify()) == Matrix.eye(4) + assert Q.cols == 4 + + def test_question_1b(self): + U = Matrix.from_str("1 2 2 1; 1 2 1 0; 1 0 1 0; 1 0 2 1").T + with pytest.warns(UserWarning): # linearly dependent set + Q = U.gram_schmidt(factor=False, verbosity=1) + assert not Q.is_mat_orthogonal() + + def test_question_2a(self): + A = Matrix.from_str("0 1 1 0; 1 -1 1 -1; 1 0 1 0; 1 1 1 1") + b = Matrix.from_str("6; 3; -1; 1") + # System is inconsistent: b is NOT in col(A) + assert not b.is_subspace_of(A) + + def test_question_2b(self): + A = Matrix.from_str("0 1 1 0; 1 -1 1 -1; 1 0 1 0; 1 1 1 1") + b = Matrix.from_str("6; 3; -1; 1") + sol = A.solve_least_squares(b) + part, gen = sol.sep_part_gen() + assert part == Matrix([-6, -1, 7, 0]) + assert len(gen.free_symbols) == 1 + free = gen.free_symbols.pop() + assert gen == free * Matrix([-1, -1, 1, 1]) + + def test_question_2c(self): + A = Matrix.from_str("0 1 1 0; 1 -1 1 -1; 1 0 1 0; 1 1 1 1") + b = Matrix.from_str("6; 3; -1; 1") + sol = A.solve_least_squares(b) + proj = A @ sol + + assert proj == Matrix([6, 2, 1, 0]) + + def test_question_3(self): + A = Matrix.from_str("1 0.01; 1 0.012; 1 0.015; 1 0.02") + b = Matrix.from_str("2.75E-4; 3.31E-4; 3.92E-4; 4.95E-4") + + A.simplify(tolerance=1e-10) + b.simplify(tolerance=1e-10) + + assert not b.is_subspace_of(A, verbosity=2) + + sol = A.solve_least_squares(b) + assert len(sol.free_symbols) == 0 + assert A.T @ A @ sol == A.T @ b + + def test_question_4(self): + x = Matrix.from_str("4 4.5 5 5.5 6 6.5 7 8 8.5").T + y = Matrix.from_str( + "0.8651 0.4828 2.590 -4.389 -7.858 3.103 7.456 0.0965 4.326" + ).T + x.simplify(tolerance=1e-10) + y.simplify(tolerance=1e-10) + + N = Matrix.create_vander(num_rows=x.rows, num_cols=5) + N = N.apply_vander(x) + sol = N.solve_least_squares(y, verbosity=2) + assert len(sol.free_symbols) == 0 + assert N.T @ N @ sol == N.T @ y + + def test_question_5a(self): + A = Matrix.from_str("1 1 0; 1 1 0; 1 1 1; 0 1 1") + QR = A.QRdecomposition() + Q, R = QR.Q, QR.R + + gram = (Q.T @ Q).applyfunc(lambda x: x.simplify()) + assert gram == Matrix.eye(Q.cols) + assert R.is_upper + assert R.shape == (A.cols, A.cols) + assert (Q @ R).applyfunc(sym.simplify) == A + + def test_question_5b(self): + A = Matrix.from_str("1 1 0; 1 1 0; 1 1 1; 0 1 1") + b = Matrix.from_str("1; 1; 0; 0") + QR = A.QRdecomposition() + Q, R = QR.Q, QR.R + + sol_qr = (R.inverse() @ Q.T @ b).applyfunc(sym.simplify) + sol_lsq = A.solve_least_squares(b).sep_part_gen()[0].applyfunc(sym.simplify) + assert sol_qr == sol_lsq + assert (A.T @ A @ sol_qr).applyfunc(sym.simplify) == (A.T @ b).applyfunc( + sym.simplify + ) diff --git a/tests/tutorials/test_tut09.py b/tests/tutorials/test_tut09.py new file mode 100644 index 0000000..3e79046 --- /dev/null +++ b/tests/tutorials/test_tut09.py @@ -0,0 +1,83 @@ +from numpy import char +import pytest +import sympy as sym +from sympy.matrices.matrixbase import MatrixError + +from ma1522 import Matrix + + +class TestTutorial09: + def test_question_1a(self): + mat = Matrix.from_str("1 2 0; 0 1 1; 1 0 -2") + aug = Matrix.from_str("300; 300; 300") + assert not aug.is_subspace_of(mat) + + def test_question_1b(self): + mat = Matrix.from_str("1 2 0; 0 1 1; 1 0 -2") + aug = Matrix.from_str("300; 300; 300") + sol = mat.solve_least_squares(aug) + part, gen = sol.sep_part_gen() + assert part == Matrix([200, 100, 0]) + assert len(gen.free_symbols) == 1 + free = gen.free_symbols.pop() + assert gen == free * Matrix([2, -1, 1]) + + def test_question_2b(self): + A = Matrix.from_str("1 1 0; 1 1 0; 1 1 1; 0 1 1") + qr = A.QRdecomposition(full=True) + Q, R = qr.Q, qr.R + assert Q.shape == (4, 4) + gram = (Q.T @ Q).applyfunc(lambda x: x.simplify()) + assert gram == Matrix.eye(4) + Q_thin = Q.select_cols(0, 1, 2) + R_top = R.select_rows(0, 1, 2) + assert (Q_thin @ R_top).applyfunc(sym.simplify) == A + + def test_question_3a(self): + X = Matrix.from_str("1 1 2; 1 2 1; 2 1 1") + p_X = X**3 - 4 * X**2 - X + 4 * Matrix.eye(3) + assert p_X.applyfunc(sym.simplify) == Matrix.zeros(3, 3) + + def test_question_3b(self): + X = Matrix.from_str("1 1 2; 1 2 1; 2 1 1") + # X is symmetric — all eigenvalues must be real + char_poly = X.cpoly() + assert isinstance(char_poly, sym.Mul) + + x = sym.Symbol("x", real=True) + expected = (x - 4) * (x + 1) * (x - 1) + assert sym.simplify(char_poly - expected) == 0 + + def test_question_4a(self): + A = Matrix.from_str("1 -3 3; 3 -5 3; 6 -6 4") + assert A.is_diagonalizable() + PDP = A.diagonalize(verbosity=1) + assert (PDP.P.inv() @ A @ PDP.P).applyfunc(sym.simplify) == PDP.D + + def test_question_4b(self): + A = Matrix.from_str("9 8 6 3; 0 -1 3 -4; 0 0 2 0; 0 0 0 3") + PDP = A.diagonalize(reals_only=True) + assert (PDP.P.inv() @ A @ PDP.P).applyfunc(sym.simplify) == PDP.D + # Diagonal entries are the eigenvalues + eigenvals = set(PDP.D.diagonal()) + assert eigenvals == {9, -1, 2, 3} + + def test_question_4c(self): + A = Matrix.from_str("1 0 0; 1 1 0; 0 1 1") + # Not diagonalizable over reals → raises MatrixError + with pytest.raises(MatrixError): + A.diagonalize(reals_only=True) + + def test_question_4d(self): + A = Matrix.from_str("0 0 1 0; 0 0 0 1; 1 0 0 0; 0 1 0 0") + PDP = A.diagonalize(reals_only=True) + assert (PDP.P.inv() @ A @ PDP.P).applyfunc(sym.simplify) == PDP.D + + def test_question_4e(self): + A = Matrix.from_str("-1 1 1; 1 1 -1; -4 2 3") + # Has complex eigenvalues → not diagonalizable over reals + assert not A.is_diagonalizable(reals_only=True) + # but diagonlizable over complex numbers + assert A.is_diagonalizable(reals_only=False) + PDP = A.diagonalize(reals_only=False) + assert (PDP.P.inv() @ A @ PDP.P).applyfunc(sym.simplify) == PDP.D diff --git a/tests/tutorials/test_tut10.py b/tests/tutorials/test_tut10.py new file mode 100644 index 0000000..852807f --- /dev/null +++ b/tests/tutorials/test_tut10.py @@ -0,0 +1,118 @@ +import sympy as sym + +from ma1522 import Matrix, SVD + + +class TestTutorial10: + def test_question_1a(self): + A = Matrix.from_str("0.4 0.1 0.5; 0.2 0.6 0.2; 0.4 0.3 0.3") + A.simplify(rational=True) + assert A.is_stochastic() + + def test_question_1b(self): + A = Matrix.from_str("0.4 0.1 0.5; 0.2 0.6 0.2; 0.4 0.3 0.3") + A.simplify(rational=True) + assert A.is_stochastic(verbosity=1) + + x_0 = Matrix.from_str("100; 0; 0") + PDP = A.diagonalize(reals_only=True) + + x_3 = PDP.P @ PDP.D**3 @ PDP.P.inv() @ x_0 + + print(x_3) + assert x_3.applyfunc(sym.simplify) == Matrix( + [35, sym.Rational(156, 5), sym.Rational(169, 5)] + ) + + def test_question_1d(self): + A = Matrix.from_str("0.4 0.1 0.5; 0.2 0.6 0.2; 0.4 0.3 0.3") + A.simplify(rational=True) + x_0 = Matrix.from_str("100; 0; 0") + PDP = A.diagonalize(reals_only=True) + + D_inf = Matrix.diag(0, 0, 1) + diff = PDP.D**100 - D_inf + assert sum(abs(diff.evalf(10))) < 1e-10 + + steady = (PDP.P @ D_inf @ PDP.P.inv() @ x_0).applyfunc(sym.simplify) + state = sym.Rational(100, 3) + assert steady == Matrix([state, state, state]) + + def test_question_1e(self): + A = Matrix.from_str("0.4 0.1 0.5; 0.2 0.6 0.2; 0.4 0.3 0.3") + A.simplify(rational=True) + PDP = A.diagonalize(reals_only=True) + D_inf = Matrix.diag( + *[ + 1 if abs(d - 1) < 1e-9 else 0 + for d in [float(v) for v in PDP.D.diagonal()] + ] + ) + alpha, beta, gamma = sym.symbols("alpha beta gamma") + x = Matrix([alpha, beta, gamma]) + result = (PDP.P @ D_inf @ PDP.P.inv() @ x).applyfunc(sym.simplify) + steady = (alpha + beta + gamma) / 3 + assert (result - Matrix([steady, steady, steady])).applyfunc( + sym.simplify + ) == Matrix.zeros(3, 1) + + def test_question_2(self): + A = Matrix.from_str("1 0 3; 0 4 0; 0 0 4") + PDP = A.diagonalize(reals_only=True) + B = PDP.P @ sym.sqrt(PDP.D) @ PDP.P.inv() + assert (B**2).applyfunc(sym.simplify) == A + + def test_question_3a(self): + A = Matrix.from_str("3 1; 1 3") + PDPT = A.orthogonally_diagonalize() + P, D = PDPT.P, PDPT.D + assert (P.T @ P).applyfunc(sym.simplify) == Matrix.eye(2) + assert (P.T @ A @ P).applyfunc(sym.simplify) == D + assert set(D.diagonal()) == {2, 4} + + def test_question_3b(self): + A = Matrix.from_str("2 2 -2; 2 -1 4; -2 4 -1") + PDPT = A.orthogonally_diagonalize() + P, D = PDPT.P, PDPT.D + assert (P.T @ P).applyfunc(sym.simplify) == Matrix.eye(3) + assert (P.T @ A @ P).applyfunc(sym.simplify) == D + + def test_question_4(self): + A = Matrix.from_str("1 -2 0 0; -2 1 0 0; 0 0 1 -2; 0 0 -2 1") + PDPT = A.orthogonally_diagonalize(verbosity=1) + P, D = PDPT.P, PDPT.D + assert (P.T @ P).applyfunc(sym.simplify) == Matrix.eye(4) + assert (P.T @ A @ P).applyfunc(sym.simplify) == D + + def test_question_5a(self): + A = Matrix.from_str("3 2; 2 3; 2 -2") + svd = A.singular_value_decomposition(verbosity=1) + # Reconstruct A from SVD + reconst = svd.eval() + reconst.simplify() + assert reconst == A + # U has orthonormal columns + assert (svd.U.T @ svd.U).applyfunc(sym.simplify) == Matrix.eye(svd.U.cols) + # V is orthogonal + assert (svd.V.T @ svd.V).applyfunc(sym.simplify) == Matrix.eye(svd.V.cols) + + def test_question_5b(self): + A = Matrix.from_str("3 2; 2 3; 2 -2") + svd = A.singular_value_decomposition() + svdT = SVD(U=svd.V, S=svd.S.T, V=svd.U) + AT_reconst = (svdT.eval()).applyfunc(sym.simplify) + assert AT_reconst == A.T + + def test_question_5c(self): + A = Matrix.from_str("1 0 1; 0 1 1; 1 1 2") + # A is symmetric and rank-deficient (rank 2) + assert A.rank() == 2 + svd = A.singular_value_decomposition() + reconst = (svd.eval()).applyfunc(sym.simplify) + assert reconst == A + + def test_question_6(self): + A = Matrix.from_str("-18 13 -4 4; 2 19 -4 12; -14 11 -12 8; -2 21 4 8") + svd = A.singular_value_decomposition(verbosity=1) + reconst = (svd.eval()).applyfunc(sym.simplify) + assert reconst == A diff --git a/tests/tutorials/test_tut11.py b/tests/tutorials/test_tut11.py new file mode 100644 index 0000000..b383c00 --- /dev/null +++ b/tests/tutorials/test_tut11.py @@ -0,0 +1,98 @@ +import sympy as sym +import pytest + +from ma1522 import Matrix + + +class TestTutorial11: + def test_question_1a(self): + in_vec = Matrix.from_str("x; y") + out_vec = Matrix.from_str("x+y; y-x") + mat = in_vec.standard_matrix(out_vec, matrices=1) + assert len(mat) == 1 + assert mat[0] == Matrix.from_str("1 1; -1 1") + assert len(mat[0].columnspace()) == 2 + assert mat[0].nullspace() == [] + + def test_question_1b(self): + in_vec = Matrix.from_str("x; y") + out_vec = Matrix.from_str("2**x; 0") + with pytest.raises(ValueError): + in_vec.standard_matrix(out_vec, matrices=1) + + def test_question_1c(self): + in_vec = Matrix.from_str("x; y") + out_vec = Matrix.from_str("x+y; 0; 0") + mat = in_vec.standard_matrix(out_vec, matrices=1) + assert len(mat) == 1 + assert mat[0].shape == (3, 2) + assert len(mat[0].columnspace()) == 1 + assert len(mat[0].nullspace()) == 1 + + def test_question_1d(self): + in_vec = Matrix.from_str("x; y; z") + out_vec = Matrix.from_str("1; y-x; y-z") + with pytest.raises(ValueError): + in_vec.standard_matrix(out_vec, matrices=1) + + def test_question_1e(self): + in_vec = Matrix.create_unk_matrix(5, 1, "x", is_real=True) + out_vec = Matrix.from_str("x3 + 2*x4 - x5", col_sep="?", is_real=True) + result = in_vec.standard_matrix(out_vec, matrices=1) + assert len(result) == 1 + assert result[0].shape == (1, 5) + assert result[0] == Matrix.from_str("0 0 1 2 -1") + + def test_question_1f(self): + x = Matrix.create_unk_matrix(3, 1) + with pytest.raises(ValueError): + x.standard_matrix(Matrix([x.dot(x)]), matrices=1) + + def test_question_2a(self): + F_in = Matrix.from_str("x1; x2; x3") + F_out = Matrix.from_str("x1 - 2*x2; x1 + x2 - 3*x3; 5*x2 - x3", col_sep="?") + A_F = F_in.standard_matrix(F_out, matrices=1)[0] + assert A_F == Matrix.from_str("1 -2 0; 1 1 -3; 0 5 -1") + + G_in = Matrix.from_str("x1; x2; x3") + G_out = Matrix.from_str("x3 - x1; x2 + 5*x1; x1 + x2 + x3", col_sep="?") + B_G = G_in.standard_matrix(G_out, matrices=1)[0] + assert B_G == Matrix.from_str("-1 0 1; 5 1 0; 1 1 1") + + def test_question_2c(self): + A_F = Matrix.from_str("1 -2 0; 1 1 -3; 0 5 -1") + B_G = Matrix.from_str("-1 0 1; 5 1 0; 1 1 1") + comp = A_F @ B_G + expected = Matrix.from_str("-11 -2 1; 1 -2 -2; 24 4 -1") + assert comp.shape == (3, 3) + assert comp == expected + + def test_question_2d(self): + B_G = Matrix.from_str("-1 0 1; 5 1 0; 1 1 1") + H = B_G.inverse() + assert (H @ B_G).applyfunc(sym.simplify) == Matrix.eye(3) + + def test_question_3a(self): + T_in = Matrix.eye(3) + T_out = Matrix.from_str("1 3 0 1; 2 2 -1 4; 0 4 1 6").T + mat = T_in.standard_matrix(T_out, matrices=1) + assert len(mat) == 1 + assert mat[0].shape == (4, 3) + assert mat[0] == T_out + + def test_question_3b(self): + # Overdetermined but consistent → still a unique solution + T_in = Matrix.from_str("1 -1; 1 1; 2 0").T + T_out = Matrix.from_str("2 0; 0 2; 2 2").T + mat = T_in.standard_matrix(T_out, matrices=1) + assert len(mat) == 1 + assert (mat[0] @ T_in).applyfunc(sym.simplify) == T_out + + def test_question_3c(self): + T_in = Matrix.from_str("1 -1 0; 0 1 -1; -1 0 1").T + T_out = Matrix.from_str("-1 1 0") + mat = T_in.standard_matrix(T_out, matrices=1) + assert len(mat[0].free_symbols) == 1 + free = mat[0].free_symbols.pop() + expected = Matrix([[free, free + 1, free]]) + assert mat[0] == expected From 7436bd0bb69d30484145dbb3d917d642ebdcf634 Mon Sep 17 00:00:00 2001 From: ys_teng <58208381+YeeShin504@users.noreply.github.com> Date: Sun, 19 Apr 2026 21:37:15 +0800 Subject: [PATCH 2/7] Update organisation and coverage of test cases --- docs/tutorials/tut01.ipynb | 1053 +++++++++++++++++++++- tests/chapters/test_c1_linear_systems.py | 31 +- tests/test_c1_linear_systems.py | 209 ----- tests/test_c2_matrix_algebra.py | 96 -- tests/test_c3_euclidean_vector_spaces.py | 99 -- tests/test_c4_subspaces_assoc_matrix.py | 13 - tests/test_c5_orthogonality_and_lss.py | 86 -- tests/test_c6_eigen_analysis.py | 69 -- tests/test_c7_linear_transformation.py | 15 - tests/test_tut01.py | 86 -- tests/test_verbosity.py | 71 ++ tests/tutorials/test_tut01.py | 34 +- tests/tutorials/test_tut02.py | 14 + tests/tutorials/test_tut03.py | 6 + tests/tutorials/test_tut04.py | 16 +- tests/tutorials/test_tut06.py | 12 + 16 files changed, 1222 insertions(+), 688 deletions(-) delete mode 100644 tests/test_c1_linear_systems.py delete mode 100644 tests/test_c2_matrix_algebra.py delete mode 100644 tests/test_c3_euclidean_vector_spaces.py delete mode 100644 tests/test_c4_subspaces_assoc_matrix.py delete mode 100644 tests/test_c5_orthogonality_and_lss.py delete mode 100644 tests/test_c6_eigen_analysis.py delete mode 100644 tests/test_c7_linear_transformation.py delete mode 100644 tests/test_tut01.py create mode 100644 tests/test_verbosity.py diff --git a/docs/tutorials/tut01.ipynb b/docs/tutorials/tut01.ipynb index 4ab32b2..dcbd117 100644 --- a/docs/tutorials/tut01.ipynb +++ b/docs/tutorials/tut01.ipynb @@ -42,6 +42,7 @@ "outputs": [ { "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAALcAAABLCAYAAAAyPhb9AAAACXBIWXMAAA7EAAAOxAGVKw4bAAAJ/ElEQVR4Ae2dTZLVNhSFX3cxTjWkKvM0O+BnB50dhGIHsINQjJpZCnYA2UFgB8AKCOwAFpBB05UNkPMZ60X+6362ryzZuapyy5Zt6ejoWLrWk28fnZ+f39ntdh+19YU3z549e9B3wtO2y4Da/Ju2o9JrKIyfhfG0Dyf4b0QnXmifi+PwJT7wfWegMAae9+D5RWm/kh6L+6XU7mLuYcuTymRAen3VRqY0kjribl836ViZM0w8rm8+Uczxc6W/q9OyRTW2JzWAe4ovtD1R+qdsoBIWrHphcj5WHNojYWndrGu+Q9mLayHuubvoRqaoMlQAsYQK7bTPU/RW8QNtb0ZmaXa5yuYhY3Ri2KqC9hnWPpKmLfvDV8OyjF4rsywPrvjMroVjSyaV1yM2VawaFuq8g2ieGpc1NjuEvH/ouFk46cUvtSGCTQXV7bfMFcquBWtx00sgFrYqiOT9fkjLFJ+p3M/Cc9Iqn4fvROn07JsIqgvmSKMdMlQsuxaszRKEcjMmUkSHXvxlnJ5hH2x3hGfoYWuLPgNEsyIfqp4N89As5wMzUvnZtWAq7na9VUF6y8oc0H7nzbZ9fcpjlT80X08vt9P5LLapdZ1VD8yR3B1Jp1rCtbgWkohbFUEwVOa+NkTzl7biQo0TcyTMoBSHcQwg1Ye6XCouZko3pxZSiRtBVz2hKodZwoxE1tmSAZHwIsmvsPyAtYXAtF9RD6rwZNNCEnHHKlHlEA927mvFN+v9+JIs+8LB0P1F8ZC5sigu4cDmf69tjO1PhxE6EWYnijNHYhKFdVEtmIpb4IfsV8wSzBS2bHPdgWjhRAi3FO/nvMO5XLGw0AHcnVK+7sUcYcanNHNkJ0zVwxfVazEtmIpbFagWYKlCxfTQEanVrrBhJt1WvO+xtV9NAyouRhxt3Nccg/++8Lfn6+lsTut0RqklTZbsWrAWN73PO5FIHId79QHTQ9mCcNHYiKDdyAg+62zOHFJUH3jtcKv0r6Qr3j/Ic8oZeW92LViLuy2anYhFOCfaeNlpi34kX9MvV9n0bvRsNHbbNj1T2lZeKmOS4J0tR8iuBVNxSyCvtCGUWDyIqoS1G2+FAyzY2+3Qtgvb51d1XPNPXQm0Bw/1B8WLPcAqK7sWTMUNk6pU7xDJuZxBuG7nLH/JslXXxhqaJcuOy8qtheMYjO87A1tiwMW9pdb0ujQYcHE36PCDLTHg4t5Sa3pdGgy4uBt0+MGWGHBxb6k1vS4NBlzcDTr8YEsMuLi31JpelwYDLu4GHX6wJQZc3FtqzRXURb9aVsuiY6hKS/KBdoqf308FPCyaYTXgBceqQPb1G8IAtvDTNAuKOC7CYZBwXBvWgF8YES9rWe5qv2+h3Hulw33QQ1jYNWkt+1WkmYq7Jr9Ixzc1oY0vwpXGisXsDoOuaqBwrmT8NbY/hJWOjA6NTmMocA2Bh4D183y88rvy6HsQdGp6MBW3YPQ6vhFwVuLxNDfcPkyHPelOMDwSFsQcvgYKa6Cf6lxIm5T5AjcVi78WZrVmXPt8fd8xPSJ+PumaRdaXH0eFWuyeKZNSHd8wDNI77HuIulEs6r1EHmvHvwRHjTKse256wiId30jIYGuMHErDLCHE68+/pxT2d+34c9BpKm41wNBwUw1TOk/vU0QQFkaZIhwGTSFkzfiFHROLF8kftWGfY3Oba8NU3ALZCQKNsKlAmEHpXLNkQo0HYd/XBqF8jb2asHb8IhpR/6l6VOahYrSBKWv+tVZycQs4L5LFOL4RiQi66iW0j1lSqsMgQeuGDeBvuNNQffgqH5MR09D0aynrF8pGawg0gItxfNMApwPhY4aEHgSHQfQoqwprxx+RzZQgLijoxc0C4v6hzi3EJpkLKHZVMY5vhIcX3b4pqmCWYKoUG9aOH2JVB6Zhh/65GJdYdDA/kREBcf9T7f0X14fTI1WA4b7j+Ebppk/mSISQigliQeDIok0uXzt+SOAHnj7+b3FSbVOZi+zPCH+HexG3aRBAeschxzcXpoWNy+xSlwdfdfGdEE4IP+h8Pyrv79rxwyjuHvrsakZNc/5NXygFnJ6ZF8gSHd90ZmuElxGGniSrwyCVf0hYC36m9wj0xjyQcWBpBltY37PTfvj3JkPTyPH9o/ZNxa2Si3V8IxKzO4kZ1TKti0vHL3x0aoTw7sJLOi+K2NmVqzqOtbFQLfxoxgPAaP6z0toPgpLnBVNxC2DfkDMPoeHdwsfQZz78GUK8MquS8QvbQT2vrkPw+577ygrPPGluc8/E47c7A2YMuLjNqPSMSmPAxV1aizgeMwZc3GZUekalMeDiLq1FHI8ZAy5uMyo9o9IYcHGX1iKOx4wBF7cZlZ5RaQy4uEtrEcdjxoC5uPULFAunGkFpSZyuNArxA2egxYDpz+913os5XWnVZfahHsJTZRJ+GmZBFcfFOe2pOxDWcgw5vtGpPGEstvp6Fq4F3s2ApxB3WNZKD57U6YoZC8pI5CLmYp321PgOdXxjSc21ec3ExkNqsY67gzOFuBdzutKpzbyEYp3eUC0JiFVz1eIk7V/n+GYeEyPvnoqtrsfI0g6/3NzmPrzo4q6k90BAbFWoGy0cemzIgLhlZG/wbZh9lVWKntsa4yL5iWyWwq7Wac8iJNkW8lCcN8xA2+x3uyTiFuhFnK5YkxHnpzqc6Xi1TnviupS2L24xq8IHC8ngpRD3idAu4nQlBSsinuESYa/SaU8KTizzFL/MQF0qZrIhaTAXt0Av5nQlBTPCj+1dvb1rn28sV+W0JwUnxnky7df5HtS4jCo7c3EPgOQpPVOlcLyS5IlVvowY77URHxoe6L7BaSidC1/L8z3gTW28AHmYyID4w1xNbo4EeKbiFng+EMYRz5CX/DHCCxgPimvhDZV7bR66v/plVXFb7DjtwUxhw0OVhwkMiFfMEX6pTtK59UEyFbcKwAfIRU9Bt0jrEU7PpdmSKk9Iwug9dJomQNz4swlfyYdS6FQY0Unn63gzk8Va3LhP6ANHr1f6V+eYHPhbaZsea3HaI/jlBvHa63lA6V+FGt4P+np+TA2Px1x8wLWV05X4OoFO5nQlLsdgn4cSs2ofhL1Upz2x45s93kJ2xmLDVE1irh6dn58zLDAk49tvtj2kPBh+Qu+NOYKZwmR9u0dUcllBGBlh4h6EuhSzcEr4wpAOTgTB+wFttnd8o/3ZQeV803Y0JqOx2HQ9L5bwS10IvM98UPqL6mjiH91fvbQqProxMY/B25QpZJuv8Bos0PCEsPcOnYZFzMpK+OIHb1Ze1jePxabrk2vE2iyx5szzcwYmM+Dinkyd31g6Ay7u0lvI8U1mwMU9mTq/sXQGXNylt5Djm8xAPFvCv0trZ8TaimLf0Ntg/fj/xYC0+Vk1Ph2qNeK+aupu9rz3UMGe7gwYMMB6+8HwL4vxyyCCO0iJAAAAAElFTkSuQmCC", "text/latex": [ "$\\displaystyle \\left[\\begin{array}{ccc|c}3 & 2 & -4 & 3\\\\2 & 3 & 3 & 15\\\\5 & -3 & 1 & 14\\end{array}\\right]$" ], @@ -89,6 +90,7 @@ }, { "data": { + "image/png": "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", "text/latex": [ "$\\displaystyle \\left[\\begin{array}{ccc|c}3 & 2 & -4 & 3\\\\0 & \\frac{5}{3} & \\frac{17}{3} & 13\\\\5 & -3 & 1 & 14\\end{array}\\right]$" ], @@ -125,6 +127,7 @@ }, { "data": { + "image/png": "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", "text/latex": [ "$\\displaystyle \\left[\\begin{array}{ccc|c}3 & 2 & -4 & 3\\\\0 & \\frac{5}{3} & \\frac{17}{3} & 13\\\\0 & - \\frac{19}{3} & \\frac{23}{3} & 9\\end{array}\\right]$" ], @@ -161,6 +164,7 @@ }, { "data": { + "image/png": "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", "text/latex": [ "$\\displaystyle \\left[\\begin{array}{ccc|c}3 & 2 & -4 & 3\\\\0 & \\frac{5}{3} & \\frac{17}{3} & 13\\\\0 & 0 & \\frac{146}{5} & \\frac{292}{5}\\end{array}\\right]$" ], @@ -237,6 +241,7 @@ "outputs": [ { "data": { + "image/png": "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", "text/latex": [ "$\\displaystyle \\left[\\begin{array}{cccc|c}1 & 1 & -1 & -2 & 0\\\\2 & 1 & -1 & 1 & -2\\\\-1 & 1 & -3 & 1 & 4\\end{array}\\right]$" ], @@ -294,12 +299,13 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 7, "id": "3b879141", "metadata": {}, "outputs": [ { "data": { + "image/png": "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", "text/latex": [ "$\\displaystyle \\left[\\begin{array}{c}- 3 z - 2\\\\\\frac{19 z}{2} + 2\\\\\\frac{9 z}{2}\\\\z\\end{array}\\right]$" ], @@ -450,12 +456,13 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 8, "id": "8e0bfdfd", "metadata": {}, "outputs": [ { "data": { + "image/png": "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", "text/latex": [ "$\\displaystyle \\left[\\begin{array}{ccc|c}a & 0 & b & 2\\\\a & a & 4 & 4\\\\0 & a & 2 & b\\end{array}\\right]$" ], @@ -467,7 +474,7 @@ "])" ] }, - "execution_count": 11, + "execution_count": 8, "metadata": {}, "output_type": "execute_result" } @@ -482,9 +489,1045 @@ "mat" ] }, + { + "cell_type": "markdown", + "id": "cddf03a9", + "metadata": {}, + "source": [ + "### New method in v1.2 using `evaluate_cases()`\n", + "\n", + "In v1.2, a new method `evaluate_cases(rhs, verbosity)` is introduced to check through all possible combinations at the same time using RREF. If `rhs` is not provided, it is treated as a homogenous system." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d13cbaea", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/latex": [ + "$\\displaystyle \\left( \\left[\\begin{array}{ccc}a & 0 & b\\\\a & a & 4\\\\0 & a & 2\\end{array}\\right], \\ \\left[\\begin{array}{c}2\\\\4\\\\b\\end{array}\\right]\\right)$" + ], + "text/plain": [ + "⎛⎡a 0 b⎤ ⎡2⎤⎞\n", + "⎜⎢ ⎥ ⎢ ⎥⎟\n", + "⎜⎢a a 4⎥, ⎢4⎥⎟\n", + "⎜⎢ ⎥ ⎢ ⎥⎟\n", + "⎝⎣0 a 2⎦ ⎣b⎦⎠" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Require separation of A and b as it is a non-homogenous system\n", + "A = mat.select_cols(0, 1, 2)\n", + "b = mat.select_cols(3)\n", + "\n", + "A, b" + ] + }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 10, + "id": "4ed95803", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Branching on pivot at (0, 0) due to possibility of zero value with free symbols {a}\n", + "Branch 1 on pivot (0, 0) = 0 with conditions:\n" + ] + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAADkAAAAVCAYAAAD8dkbIAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAC2klEQVRYCdWY4VFUQQyA5YYCQDs4O0CtQOgA7EDtQMZ/98/BDpQKGOkArACkA+lA5jo4v+/5dmdfbnnvMXMzvsvMkmySzUs22eweO6vV6lmExWKxB++1fOjrKJ/aHB/n+HTAuIO+j/7NIgOlT/B+tvzbKJ/ivA3M4M6grxgmKcNOmUmEx0h+MPahl1nrPxP4cta68Af8kmEwaxlTB/4VaAk+cS7ETL6Bd4/ClAL8hU83+HTK+Ap9yjBblmgN7mBauhlikFkwBYJAPuDHHvgy+QNtApx/S7whPOkgcd6SMzMRbmAcEnDn7EWlNJ96kIc4+pCcLXA6j8oHYXdQIyiwe3NYNgI/ZCOwdI7he14GAb3fKF2DP/YpIx+Tped9NpIsZlLD1nwV+LA7ZyOwCaRG4Nmw2w061eq4Sc0dDO6DFMCj/rC49k03Pq1t7OcgccCPv2NUD3TroNeLwaVy0Yj0qI7MOh32enrlwg3Ai4oNK8tm5XXYwC4Tg7P8xCfMH3vhqOPi7+ASzG7ufqWgRrO+LzPlktpZTPKUKbPWAey74fswz8GfwV9mMiUYtwyzVCsBRE2WOxvQ6ro5XsAbBWynzaj5k3hlRZXfNzj9Mp7LmRII33ypEZzLKwGZRh0xGMvb9Z3g5W0ItKuzEVIm176LL5apT9O3ya8myMLC2muhkEnGnTuC19xjGmfUHAomnjS1B9SalGfaxCwr1ny1+azLshhkZU2TKRd0dtWg4B0wLHPhCF7chH+S9i9yz/SKYYceBPQ8/w/g3ESgrSgr6H2Pgc55fso96evDw2wp2NUuGGbS60NetSvDz4CeO+xGpI3Jsh7CrPkNM2SjEVuKTQVBD8LoIHUQawYaocaLOnmOHX9FjIb2u6lfjF5XKo4q13LBNtIxyLXXwhYG5Zm16jLEIL3UbQ75oGfNLSDwOzWlTn/o/GfAOFCcg/xh6l10wdzAJw2tz55bfV/z+S+Vvf1nz3ryxgAAAABJRU5ErkJggg==", + "text/latex": [ + "$\\displaystyle \\left\\{ a : 0\\right\\}$" + ], + "text/plain": [ + "{a: 0}" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/latex": [ + "$\\displaystyle R_1 \\leftrightarrow R_2$" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAIwAAABLCAYAAACiLW8yAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAHv0lEQVR4Ae2c7Y0bNxCGZUO/A8MGUoDcwSWuIJcO8lFB4g7in3d/nQ7sVJDYHTgdHHwdnAsIEOeQBpL3WWiEFcXVraQZUlqRgG6XH7vvzPDdIXfJuUdXV1cXs9nso3659P76+vr7XEUri7OAbP6ffo/iEIbvLNw71S5yLZBp3qv4Vec07qdP/Uw7PwsLvM5o+a3KvqO8T5g3YlAjSMZa51QkDrxN9VUZRRuESdvtnReAsfRv3eS5fq9LkbEmdt9gkoOh/qWOL/vlpc6j8B97KyBBmQ/d6PhKP4a5V/p90Hl2XPTEr4md0eOdyp5myksVheC7EkYd9rOs8UTH92YVnd/rnPwbK4s41sRO9ZEsv6RlJfOR+K6EkVF4o7rNGOdGZZdS5EmmzquoJvZKB+nIUMRDwq94isb3JsylLPQ5YyWbTFMflWpi93X6UZ22MXHsNwg+D8V3I8xI7xEyptfE7ne+5GAoCh16+3jpeQl8N8JIeCPDNlccNSTVxO76TZ210Mm9juZN0/4MzZfC9yTMGIM8G9MoqE00Nq/QNYeiIviehMnNXazvzQPwXSYi1cSeiSi8HdYciorhuxFGRrOhKDfsWFmIu66MzVDEp4QQ3R56uoRbFH/+kEA71v+p9iiQJvMw1EelWtjo+0Idx4eyfrpQZrEs/6QjHzAjUlF8b8JgNFsW6BvnK2VuZTTzQv06r/Mq2NIJom48CCr/h3IdQ1f7S+M/9uot7iPhmfR91rFbqFqWMRz9oN9P5KNSTewBndDbhuKBJqHFIfjeHgYL4E1YbHyhI5Ncjt8on/sCrCrXVBO7U0R6Mvm1YZmv23g+1tZYVwtP0fjuhJHA97JKrRXaatjGBOlfRfdS+K5DkgndjtO1QCPMdPs2RLNGmBCzTvemjTDT7dsQzRphQsw63Zs2wky3b0M0a4QJMet0b9oIM92+DdGsESbErNO9aSPMdPs2RDP3pQGk1OdxW7E+m0A26cxiH+tGX+vHAiwBfMWS8BYCsy0UnQzkVe66hufuYSTgWQaySW/28xKDzEaqje0OKgtLwoUshDp3kZY6dttJVPZR55eewK6EkXAtkG02Y+PUB89OGnEvPPraoqf6Am/DYixezy25EkZS1Qwmq4nddUjvaS7qYQSOF7kTPsNiPyEH20fxQC7JmzAIntuQbftdXd1jYoGa2CZKNySpg3iySyaIwTbQIdyUSHvLNt/7yuTCDLuTFl3W9vbm6vYuq4mdCA1p2YpqsdVsHvtd+VWsedLeJav7411zieFxpnq3ia+nhzEyDLEc2d2Yzs16qSZ2J4Y6Bd3oINz/W+XZYce21Hc6dxsSdL9RSZgmi705jbruoUaehHkIi/pnYxoFtYnGtuGW7ajdQ7M8cr7a4xykW+62THb5l3OuW0M9CZObu5gi5gEmGci2VJL5Sy4yAs8TTVazc3cUSdhXzJxmaKhaa79LBsJ8sbzAjrtcv2q7fJrIY6A0WZlNftP6g/I1sXuC42HW3o4kl3mdm1670FNh8mnjqY4Q2Ct9aTeCMP8uM3a0un2OGGyRudA8zJpBM+0OKaqJjdzonRLDnvBIvVc2E0kY+p7raLgznRNMl+uT1XUjTv6yNhDGMzFu8lk6Td2XRwnOeB6VamKbTisPKl3xqsRj8fU1Uu8OWxgXOiECM53kQqJt04Xu+rF/XAkjYc85kI1X5/7DAoH/WNpkbH/s1U4YeBDw+EjHEsHqpzJXws73knD7RXiTcwxk4xX6N3UWi454F16nS/37D5YiIA3zlzTdpgWH5N0JIyPhftfWNQ4RcJdrjwB7NXfYRe5D2y5JeuhtRl3vOiSNQmyNTtoCjTAn3X3lhW+EKW/zk0ZshDnp7isvfCNMeZufNGIjzEl3X3nhG2HK2/ykERthTrr7ygvfCFPe5ieN2Ahz0t1XXnj3pQFU0KfqcwxkW0h1WylmEZIVYvdAMt1zI8ne3dqVKjrcyKUCdw8jYc8ukE06Q5YigWQbbFGB8IsF0bkSRoKfayBbsUCyHGF6ZeyJCQ2icyWMhGW1NrecfqPySxEK1xmVamKzFbNIINmQ8WRbZCCF7u7zJgxC53Z32U40U6rTzPlPTWw6qUgg2RabsYd3mwxbLh1fNR/fdHvLkd7D9vZuv9mOtTWxEVX4Q/tgGCKoz3ldqjwTD0x4EJ2nhzEysIFqKEUNSTWxs7qKJJBloZ+9OWXbeRQKC7saXmgQnSdhxuheND4nEag0Nnts3QPJEp0sa0N9eBCdJ2FycxdTyDzAlAPZTFeGoLBAshXI+kmxIDo3wshINhTlhh0rs8nvuroH5mpip6JLlohAshQmzeNh1t6OJId5Hd5Q3ZIbYZYSIfQiI515mDWlMu0OKaqJ3cmtTiIGKCKQ7CG7YPOUGDYRd7W5N2EYt/uxOabo5APZRBYmneGBZGbQzHHlvSULHj0kiM6VMBL0LAPZpDdPOA9LeCBZhigUFQuimw8IcEjxOQayFQskG+iYYkF07oTR08bk96wC2aRz0X+xmpJmaXObs6TVrnnXIclVsnazo7RAI8xRdsvxCtUIc7x9c5SSNcIcZbccr1CNMMfbN0cpWf8tiQ1AqZAsnhWZfafALV/HAurvOyEvhtAhDF8Ih16DV18Ph27QyidnAdvAn1Xsf9NuG6d4G6c3AAAAAElFTkSuQmCC", + "text/latex": [ + "$\\displaystyle \\left[\\begin{array}{ccc|c}0 & 0 & 4 & 4\\\\0 & 0 & b & 2\\\\0 & 0 & 2 & b\\end{array}\\right]$" + ], + "text/plain": [ + "Matrix([\n", + "[0, 0, 4 | 4]\n", + "[0, 0, b | 2]\n", + "[0, 0, 2 | b]\n", + "])" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "\n" + ] + }, + { + "data": { + "text/latex": [ + "$\\displaystyle \\left(\\frac{1}{4}\\right) R_1 \\rightarrow R_1$" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAIwAAABLCAYAAACiLW8yAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAHiElEQVR4Ae2c343cNhDGz8E9B4YDpIB1B+e4gtgdxOkgcQfx4/ktcDqwXYFjd+B0cPB1cC4gQAwjDSTfT9AIWi6l0+0OSf0hAZ2WFMVvZvhpSImcu3d5eXlxdnb2SUcsfXj58uWz2IVals4Csvl/Ou6lQxhuWbg3urqL1UCm896FP/Sbyv30uZ+pvzdhgVcRLZ+q7CfK+4R5LQZVgkSstaUiceBNqK/KKDogTFjv6LwAjKX/qJGHOl7lImNJbAwmfIb49zoe6fdXynKm1Ph9D+OilwRmPvS7zh9oUOf7On3S+amOpB5M7RfBbnV8Kz2/6PhBR3QOoPIkKSf+N54aSPBf1d59nRuy0LZ+85SRf00+VSqM/VX4z3Q8l37vUuk41C42zoXvShgpxBvVdUSxK5U9kVJ4m1SpJHYqnWbXrjdhnkhD3HKYbCjieqpUEjuVTrNr140wE73HgxQWKImdQp85t+lGGClpZBh7M0g1JJXEnnP/usvmSZgpwn03pVKiOiWxE6mUv1lPwsTmLqaReQC+y6RIJbFT6DPbNt0Io3mEDUWxYcfKbPLrapCS2K6KLKAxN8K0uv6l8y6it3kYrqdKJbFT6TS7dr0JwydxvnSG6ZEKrnueILzukS+J7SH/ItpwJYwIwcLVF52bhSosoN8MRz/r+IV8qlQSO9DJJtfmVYPLybNJ8d3XkmQOvAmLjY91ZpLL+UflY1+Adck1FcOWfng4kn2cfK8y5mwfdT5YAW5qOv7Jhe9OGAnO5Jc1leypMDZLE8WSdM+C7zokFbNWBc5mgUqYbKZeB1AlzDr6MZsWlTDZTL0OoEqYdfRjNi0qYbKZeh1AlTDr6MdsWlTCZDP1OoAqYdbRj9m0qITJZup1ALkvDWAWfabeXCCbdGaR1VbMWYAlgC9bEt5OYC9aQHYMsKnshcpd1/DcPYwEJJjsSmeEJV4bJViAQ6GkqTA2sUHEILPgmHLfz4ENW9sS6vy8PViEhSgEENpi6MF9xxS4EkbCbTKQLTD8hfIfg7LUWTz63oKv+oIHlYVgW0V3kcGVMJKIFdOYC7xS+eoD2XpPc1YPg2113AifYbGfkINIVDfv7k0YBGfsDJPt5XV1jwFISWwTpRmS1EE82TkTxPg8ghsS6WjZzo++M7gxwu6gRpNNsgutJHagJKRlK+pvbTmbx94p38WaB/Vdsmp/aC8Mw+OZrse8/lHYnh7GyDD2dLkxPdC2JHYjijoF3egg3P8b5Znwsy2VnXduQ4Lam5SEabLYm9Ok+26r5EmY27C4bvtNp9T1rpMa24ZbtqM2D0175ne3x9lbqZH2mOzyL+cgrlvyJExs7mKCmgdYcyAb85dYZASeJzVZzc7NWSThX6swpxkaqvbq3yUDYb5tb7DzXe7v6rZPE3kMFCYrs8lveP2kfEnsnuB4mL23I8llXoe3xCxJmHzaeKAzBPZK31tDEObfNmNnu3bMGYPtIjeah9kzaKTeKUUlsZEbvUNi2BOeUu/OZiIJQ99DnQ33TL93HF2l4378bbdBGM/EuMln6TA1Xx4lOON5qlQS23TqPKh0xasSj8XX15R6N9jCuNCPxzqHk1xINDZdaO6f+seVMBJ2y4FsvDr3HxYI/Gdrk6n9cVQ9YeBBwOMjHUsE3aEyV8KeHyXh+E14k80FsklnXqHfqrNYdMS78DqdPIBNOCSWIiAN85cwXYcFp+TdCSMj4X731jVOEfAu984Au5s73EXuU+u2JD21mUn3uw5JkxBrpUVboBJm0d2XX/hKmPw2XzRiJcyiuy+/8JUw+W2+aMRKmEV3X37hK2Hy23zRiJUwi+6+/MJXwuS3+aIRK2EW3X35hXdfGkAFfareYiDbTqrbSjGLkKwQuweSqc2DJHs3a1e60OCmXCpw9zASdnOBbNIZsmQJJDtgiwqEny2IzpUwEnyrgWzZAslihOmVsScmaRCdK2EkLKu1seX0K5WvOZCNrZhZAsmEE016WJGBlHR3nzdhEDq2u8t2oplSjWbOf0pi00lZAslGbMYe3jEZRm6dful8etXxmmI4E6/bku3tva3ena6XxEZQ4Q/tg2GI4HrM63LJM/HAJA+i8/QwRgY2UA2lKaQaunesvCR2VC6RBLLsdNibU7SeR6GwsKvhJQ2i8yTMFN2zxucEAuXGZo+teyBZoJNlbahPHkTnSZjY3MUUMg+w5kA205UhKFkgWQey/yNbEJ0bYWQkG4piw46V2eR3X90TcyWxQ9ElS4pAshAmzONh9t6OJId5Hd5Q3ZIbYVqJEHoXkc48zJ5SkXqnFJXEbuRWJxEDlCKQ7Da7YPOQGDYRd7W5N2EYt/uxOabo6gPZRBYmnckDycygkXPnvSULHj1JEJ0rYSToJgPZpDdPOA9L8kCyCFEoyhZEdz4gwCnFWwxkyxZINtAx2YLo3Amjp43J76YC2aRz1n+xGpKmtbnNWcLLrnnXIclVstrYLC1QCTPLbpmvUJUw8+2bWUpWCTPLbpmvUJUw8+2bWUrWf0tiA1AoJItnWWbfIXDNl7GA+vtGyLshdAjDF8Kh1+Du6+FQA7V8dRawDfxRxf4HT0rj9l8Y99sAAAAASUVORK5CYII=", + "text/latex": [ + "$\\displaystyle \\left[\\begin{array}{ccc|c}0 & 0 & 1 & 1\\\\0 & 0 & b & 2\\\\0 & 0 & 2 & b\\end{array}\\right]$" + ], + "text/plain": [ + "Matrix([\n", + "[0, 0, 1 | 1]\n", + "[0, 0, b | 2]\n", + "[0, 0, 2 | b]\n", + "])" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "\n" + ] + }, + { + "data": { + "text/latex": [ + "$\\displaystyle R_2 - \\left(b\\right)R_1 \\rightarrow R_2$" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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", + "text/latex": [ + "$\\displaystyle \\left[\\begin{array}{ccc|c}0 & 0 & 1 & 1\\\\0 & 0 & 0 & 2 - b\\\\0 & 0 & 2 & b\\end{array}\\right]$" + ], + "text/plain": [ + "Matrix([\n", + "[0, 0, 1 | 1]\n", + "[0, 0, 0 | 2 - b]\n", + "[0, 0, 2 | b]\n", + "])" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "\n" + ] + }, + { + "data": { + "text/latex": [ + "$\\displaystyle R_3 - \\left(2\\right)R_1 \\rightarrow R_3$" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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", + "text/latex": [ + "$\\displaystyle \\left[\\begin{array}{ccc|c}0 & 0 & 1 & 1\\\\0 & 0 & 0 & 2 - b\\\\0 & 0 & 0 & b - 2\\end{array}\\right]$" + ], + "text/plain": [ + "Matrix([\n", + "[0, 0, 1 | 1]\n", + "[0, 0, 0 | 2 - b]\n", + "[0, 0, 0 | b - 2]\n", + "])" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "\n", + "Branching on pivot at (1, 3) due to possibility of zero value with free symbols {b}\n", + "Branch 1 on pivot (1, 3) = 0 with conditions:\n" + ] + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAG0AAAAVCAYAAABMiWD6AAAACXBIWXMAAA7EAAAOxAGVKw4bAAAEgUlEQVRoBe2Y7VEcMQyGF4YCDtIBdJBABRwdQDoAOkgm//iXIR0AFRDoAFIBHx1AB4HrgLyPsXZ2dd6PA+9wZNCMz7Ysy5IlS9pbeHp6KjwcHByMhFsHr/GlX/+YD3sDuvNVnfBZ7Vbje3/aokeI6JtwfyL+2q9/zIe/gWgojHWo8YUaj6iEhepL0+K2Vs7UljWelFRvPJAsh1GEv+rX1FBmygNfKqZ4cSnoTXR50JwzBgHx5hV9j8zDecyFv00dKPyF8BP1O7buX9qGFu5FME8Gu5FMV5IJxX5pjMJ4H8pnAfRV2xIzHGGwdBBlPlK/H9sXnYexbjQfq08B64TKErzRyoV5GEiRPckxUn9u8miMQzE/MlzGnsvBs4cCIsZ+lbn0wQnRiZfeC+baaNKAkJAKG1fCj6UwYS0LiJd5+mAvTYJyxl1Cbs7EOXtFj3k3Gko+qHmwfGYX7ddfMg/hURc3ZGrAOG3pp5cTLs2qXfQGnjkXR2FAqNoWnnzTCaK7E9Gl+lqY8Bu13keBFb/vFXMcgBKb6hkgv59qXobmgE38iKavTmUx4diEnCU+qajiSIvCvzQuqtHTxBTFKAwoCqwwILdQzXVecqQhBIRvQPVtYAZplEebO89sO8DWolxcHLIda44D7qqdaQyuEWbUaYqP9tu5VlF6Gh6G3UVYK40WhfsqbDLBR+FIlhjLwhNMGLc9eWgCaB8G4HOCqikHfMrBRDwszG5GGYvYIy+fQY0Q6V6jE3d6Lj5NkYqXTr4r5VjSBE8i3NHvaN6UiKFh87H6KqBwZwixDdrf9nKMjD6Vy2zdPA8vzAHkM0Kjl20kfKdjJPb1kkn7eCA4fFPYLLTG+rLoTtT/UP9zESQDtWs1XhGCpoBXWDNopMXY2ctk8bYLTMljuOqLT8ncF4fjed3s9VGpZgfpx+fMinocpgswFveMfc4XodYAL7PC4ARcFbTGJdG8cTAk+2sKg8sE8EVYD/bScp3LGd445v25zih10H0R6tbU2xnc4SqtJIoD4aClOCJ0B1mC0SqEVC8kxibwnh3CCsQwV5s6tIlRTzzxfj1BG/5J0HkTW9PYXp+hZu1L3SIvHJJ/LsozZmWYohc/7ndDvS88ME4qJWwIP6nKsZRi7HFsUKt5veYcggDmiVvCteY2rXOxj2q87M5iRDRUcoQEHCLwVg8PLnRTLUDEParvxdf2VXp44xxWcuMsv8XP5+/Kluehnc1ejVt10jpODW8+eXzBNxauqRipGbOX0Z7FC/9OkAx5qiTnUzVeGuU+OC+EUHUQHcbHo8mffYGL4Aw8jsKDnlBhF1xobHxHGtNmfR274olu/FGMU1DqdxpMdAVnqfXVifSC4chnHkp9/IKf+3/5qRDx6sH+5fYC5JxLbl4/Xjyr0XKKkZWXdJmyic9pWQ98A2bkiv/GYE3354029fXdtHHe8DIWYS3Xd9s8qYdeNUf0RiMhkxMIM+8N9iR3UyJ/b7oEeaMjUnTV6oVaToNShCRKylG+hXr9YSq6D8h4A9EGfDdjiykb/ANVTL+xL2Y6twAAAABJRU5ErkJggg==", + "text/latex": [ + "$\\displaystyle \\left\\{ a : 0, \\ b : 2\\right\\}$" + ], + "text/plain": [ + "{a: 0, b: 2}" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Completed branch with conditions:\n" + ] + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAG0AAAAVCAYAAABMiWD6AAAACXBIWXMAAA7EAAAOxAGVKw4bAAAEgUlEQVRoBe2Y7VEcMQyGF4YCDtIBdJBABRwdQDoAOkgm//iXIR0AFRDoAFIBHx1AB4HrgLyPsXZ2dd6PA+9wZNCMz7Ysy5IlS9pbeHp6KjwcHByMhFsHr/GlX/+YD3sDuvNVnfBZ7Vbje3/aokeI6JtwfyL+2q9/zIe/gWgojHWo8YUaj6iEhepL0+K2Vs7UljWelFRvPJAsh1GEv+rX1FBmygNfKqZ4cSnoTXR50JwzBgHx5hV9j8zDecyFv00dKPyF8BP1O7buX9qGFu5FME8Gu5FMV5IJxX5pjMJ4H8pnAfRV2xIzHGGwdBBlPlK/H9sXnYexbjQfq08B64TKErzRyoV5GEiRPckxUn9u8miMQzE/MlzGnsvBs4cCIsZ+lbn0wQnRiZfeC+baaNKAkJAKG1fCj6UwYS0LiJd5+mAvTYJyxl1Cbs7EOXtFj3k3Gko+qHmwfGYX7ddfMg/hURc3ZGrAOG3pp5cTLs2qXfQGnjkXR2FAqNoWnnzTCaK7E9Gl+lqY8Bu13keBFb/vFXMcgBKb6hkgv59qXobmgE38iKavTmUx4diEnCU+qajiSIvCvzQuqtHTxBTFKAwoCqwwILdQzXVecqQhBIRvQPVtYAZplEebO89sO8DWolxcHLIda44D7qqdaQyuEWbUaYqP9tu5VlF6Gh6G3UVYK40WhfsqbDLBR+FIlhjLwhNMGLc9eWgCaB8G4HOCqikHfMrBRDwszG5GGYvYIy+fQY0Q6V6jE3d6Lj5NkYqXTr4r5VjSBE8i3NHvaN6UiKFh87H6KqBwZwixDdrf9nKMjD6Vy2zdPA8vzAHkM0Kjl20kfKdjJPb1kkn7eCA4fFPYLLTG+rLoTtT/UP9zESQDtWs1XhGCpoBXWDNopMXY2ctk8bYLTMljuOqLT8ncF4fjed3s9VGpZgfpx+fMinocpgswFveMfc4XodYAL7PC4ARcFbTGJdG8cTAk+2sKg8sE8EVYD/bScp3LGd445v25zih10H0R6tbU2xnc4SqtJIoD4aClOCJ0B1mC0SqEVC8kxibwnh3CCsQwV5s6tIlRTzzxfj1BG/5J0HkTW9PYXp+hZu1L3SIvHJJ/LsozZmWYohc/7ndDvS88ME4qJWwIP6nKsZRi7HFsUKt5veYcggDmiVvCteY2rXOxj2q87M5iRDRUcoQEHCLwVg8PLnRTLUDEParvxdf2VXp44xxWcuMsv8XP5+/Kluehnc1ejVt10jpODW8+eXzBNxauqRipGbOX0Z7FC/9OkAx5qiTnUzVeGuU+OC+EUHUQHcbHo8mffYGL4Aw8jsKDnlBhF1xobHxHGtNmfR274olu/FGMU1DqdxpMdAVnqfXVifSC4chnHkp9/IKf+3/5qRDx6sH+5fYC5JxLbl4/Xjyr0XKKkZWXdJmyic9pWQ98A2bkiv/GYE3354029fXdtHHe8DIWYS3Xd9s8qYdeNUf0RiMhkxMIM+8N9iR3UyJ/b7oEeaMjUnTV6oVaToNShCRKylG+hXr9YSq6D8h4A9EGfDdjiykb/ANVTL+xL2Y6twAAAABJRU5ErkJggg==", + "text/latex": [ + "$\\displaystyle \\left\\{ a : 0, \\ b : 2\\right\\}$" + ], + "text/plain": [ + "{a: 0, b: 2}" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAIwAAABLCAYAAACiLW8yAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAFQElEQVR4Ae1d0W0UMRQMKN8oAokCQgcEKiB0QOgAKIHP5A+FDgIVINIBdBAlHYQCkECIBmAm2odWi/e4hHnPe/aztPGud+PxjCe277xPuXV4ePhwa2vrHEcpnR4dHR2UbmSZnwLQ/BeOW34I8zUD9xJ3d0tPsE3boxtvcc6Hx+nL+CLPu1DguMDyKcqesXxsmBM4KA1SUKunInjg3ZQvylj0l2Gmz934GgDm0m+o5AGO4ygz1sSmYMDnFP8Rxx7Of7AsMnnjj0cYCS80mOuhN8hPWSHyHWTnyJ/icB3BUH8V7IHje/D8juMRjuIaAOUuKRL/tpIBGv4S9e0gvzIL68Y5/8p4fcJrr1QZ+wfwD3C8Ar8PXhzn6qXGUfhSw4AQP1FdFIidoWwfpDjaeKWa2F6cFlev2jD7YMhheZpsKuJ9r1QT24vT4uqVGWbN0eOuhwI1sT34LLlOmWFA0syw6pOB15RUE3vJ/Stvm9Iw6zTu3joPOT1TE9uJUny1SsOU1i7GyEYAfi/jkWpie/BZbJ0yw2AdYVNRadqxMlv8SgWpiS0lsgGVyQwzcP2MfLfA20YY3vdKNbG9OC2uXrVh+JU4v+mcpj0UXIxGgul9xXVNbEX7N6IOqWFgCG5cfUd+tVFFBXDO6eg5jhe89ko1sSecbHFto+rktvulK758LwlycDThZuNj5FzkMn+C69I3wLglTdWwwY8jHJN9OfkRZVyzfUL+1w7w1ZPCH1H4csOg4Vz8ck8lPFXG5tZEtQTuIfjSKamaWgkcpkAaJkzqNoDSMG30YxiLNEyY1G0ApWHa6McwFmmYMKnbAErDtNGPYSzSMGFStwGUhmmjH8NYpGHCpG4DSL41QFnwNXWXgWw1eUfpLh9hIBqDyc6Qv8bBeO3XOLgBt0tSnqlXbGoaxV1qGDS610C2arwHs4ThSw2DxnPHtPQaQ+uBbDV50zNh+GrD8F2Q0gvZ9i6vvStCkurUKzZ1DOMuMwymI3vRe5URXN5C6xWbQkdzlxkGbTcz8AWqubSOqeZ+d1V5r9jUJJS70jCrOtTu2fumdh2Z94pNjWXclYYprV3MEPZX0GIgW03e1DcUn4a5M/Sq5cPl9TLMpTYVlaYdK7PF7/Uq/8fTvWJTliDu960LaJifw4Xldu8mec1gsl6x2U/e3L+aGZRTEuusGUzWK3ao7lLDYHjsMpCtJm+6JRJ/m4DiVC2YDDx6xWYXhnCXGwZu5+K3x0C2arzplijdpVMSG56pbQXSMG33r5xdGkYuadsVpmHa7l85uzSMXNK2K0zDtN2/cnZpGLmkbVeYhmm7f+Xs0jBySduuMA3Tdv/K2cm3BthCfE2dgWzB/4kuSnf5CAOzZCBbcADfYJYQ3aWGgVnCAqoo0jj1ik0NIrlLDYO2hwVUjc0ynPeKTfph3NWGCQuoKhimV2xKEcZdZhgMi/aid6Ev/xRZ9MCfAsVJr9jULpq7zDBou5mBLxLNpXVMNfe7q8p7xaYmodyVhlnVoXZPFlBlFV4j7xWbEsm4Kw0TGlA1MUqv2JQhlLvMMJhLbSoqTTtWloFsE6f/72W07jLDDMS9A6pW6dsrNjUJ4642TK/BZDV50zBh+FLDYHjMQDZ2HxK04DTs/p/oBqww3T02H0MCqihUIfWKTSlCuMsNMyzCMpCt4GbPoijdpVOSpyBZ9zIUSMMsox82phVpmI3pqmU0NA2zjH7YmFakYTamq5bR0PGnpEustKetOkUZX87J1IkC6O9LUN2do0vDcH9n7mOwy97PXGOyfBEK2Av8xcb8Bl7UtGLfQ6l3AAAAAElFTkSuQmCC", + "text/latex": [ + "$\\displaystyle \\left[\\begin{array}{ccc|c}0 & 0 & 1 & 1\\\\0 & 0 & 0 & 0\\\\0 & 0 & 0 & 0\\end{array}\\right]$" + ], + "text/plain": [ + "Matrix([\n", + "[0, 0, 1 | 1]\n", + "[0, 0, 0 | 0]\n", + "[0, 0, 0 | 0]\n", + "])" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "\n", + "Branch 2 on pivot (1, 3) ≠ 0 with conditions:\n" + ] + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAADkAAAAVCAYAAAD8dkbIAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAC2klEQVRYCdWY4VFUQQyA5YYCQDs4O0CtQOgA7EDtQMZ/98/BDpQKGOkArACkA+lA5jo4v+/5dmdfbnnvMXMzvsvMkmySzUs22eweO6vV6lmExWKxB++1fOjrKJ/aHB/n+HTAuIO+j/7NIgOlT/B+tvzbKJ/ivA3M4M6grxgmKcNOmUmEx0h+MPahl1nrPxP4cta68Af8kmEwaxlTB/4VaAk+cS7ETL6Bd4/ClAL8hU83+HTK+Ap9yjBblmgN7mBauhlikFkwBYJAPuDHHvgy+QNtApx/S7whPOkgcd6SMzMRbmAcEnDn7EWlNJ96kIc4+pCcLXA6j8oHYXdQIyiwe3NYNgI/ZCOwdI7he14GAb3fKF2DP/YpIx+Tped9NpIsZlLD1nwV+LA7ZyOwCaRG4Nmw2w061eq4Sc0dDO6DFMCj/rC49k03Pq1t7OcgccCPv2NUD3TroNeLwaVy0Yj0qI7MOh32enrlwg3Ai4oNK8tm5XXYwC4Tg7P8xCfMH3vhqOPi7+ASzG7ufqWgRrO+LzPlktpZTPKUKbPWAey74fswz8GfwV9mMiUYtwyzVCsBRE2WOxvQ6ro5XsAbBWynzaj5k3hlRZXfNzj9Mp7LmRII33ypEZzLKwGZRh0xGMvb9Z3g5W0ItKuzEVIm176LL5apT9O3ya8myMLC2muhkEnGnTuC19xjGmfUHAomnjS1B9SalGfaxCwr1ny1+azLshhkZU2TKRd0dtWg4B0wLHPhCF7chH+S9i9yz/SKYYceBPQ8/w/g3ESgrSgr6H2Pgc55fso96evDw2wp2NUuGGbS60NetSvDz4CeO+xGpI3Jsh7CrPkNM2SjEVuKTQVBD8LoIHUQawYaocaLOnmOHX9FjIb2u6lfjF5XKo4q13LBNtIxyLXXwhYG5Zm16jLEIL3UbQ75oGfNLSDwOzWlTn/o/GfAOFCcg/xh6l10wdzAJw2tz55bfV/z+S+Vvf1nz3ryxgAAAABJRU5ErkJggg==", + "text/latex": [ + "$\\displaystyle \\left\\{ a : 0\\right\\}$" + ], + "text/plain": [ + "{a: 0}" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/latex": [ + "$\\displaystyle \\left(\\frac{1}{2 - b}\\right) R_2 \\rightarrow R_2$" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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", + "text/latex": [ + "$\\displaystyle \\left[\\begin{array}{ccc|c}0 & 0 & 1 & 1\\\\0 & 0 & 0 & 1\\\\0 & 0 & 0 & b - 2\\end{array}\\right]$" + ], + "text/plain": [ + "Matrix([\n", + "[0, 0, 1 | 1]\n", + "[0, 0, 0 | 1]\n", + "[0, 0, 0 | b - 2]\n", + "])" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "\n" + ] + }, + { + "data": { + "text/latex": [ + "$\\displaystyle R_1 - \\left(1\\right)R_2 \\rightarrow R_1$" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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", + "text/latex": [ + "$\\displaystyle \\left[\\begin{array}{ccc|c}0 & 0 & 1 & 0\\\\0 & 0 & 0 & 1\\\\0 & 0 & 0 & b - 2\\end{array}\\right]$" + ], + "text/plain": [ + "Matrix([\n", + "[0, 0, 1 | 0]\n", + "[0, 0, 0 | 1]\n", + "[0, 0, 0 | b - 2]\n", + "])" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "\n" + ] + }, + { + "data": { + "text/latex": [ + "$\\displaystyle R_3 - \\left(-2\\right)R_2 \\rightarrow R_3$" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAIwAAABLCAYAAACiLW8yAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAGHElEQVR4Ae1d0W0UMRC9oHyjCCQKODpIoAKSDggdAB3AZ/KHoANIBSHpIHQQJR2EApCIIhqA946daDHey95lZnxnz0gbr72OZ974re3dtc8bBwcH25PJ5AJHTk4PDw/3cxcizc4D8PlvHBt2GoZLht4rXJ3mctCmzd6FTzhn5r5870fivAkPfMyg3EPaS6b3CfMZDAqCZLzVUhI48CXFizQm/UeYNN/ScSgQlv5EIU9xfPQiY0nddBj0s4s/wbGD8xumeYkH9n4Lo4ILRnM89AHhKQtEuIXgAuEeDtMWDOUX0d1hPALOaxzPcGTHAEg3Ey/sDzQRwOg3KG8L4YwsLBvnvMsY/8y4lRTWfQP9+zjeAt+xFcahcj2xqxIGgPhEdZkBdo60XQBja2MlJXVbYRpbrht2bcLsAiGb5VSkK+J1Kymp2wrT2HLdsKsRZmTr8WisBxbJV1L3InZa5PXGrkYYOEPIMO/JwKpLKqnbggeLlOmKXZMwY0A+HpPJKE9J3UaQRherhl2TMLmxiyCSu4DvZSykpG4LPIuU6YpdjTDoS6UrynU7kiaD30UccmfekrrvNM44gzd2NcJ0fvmGcJrxkbQwvG4lJXVbYRpbrht2bcLwlTjfdKayg4TL3t2QXteIl9StYf99ynDDrkoYEIIfrq4Rzj5U0QM4Z3f0Csdrxq2kpO4EkwwwpVVNLutHPbFv6ps/YWvCj43PEXKQy/AF4pcIraWYbuDjXU6Rl5MnSOOY7Qzhf1+AZzl1/7hgVycMnMPBL7+puEth3Xw9X0y8sKt2ScW8FYrdPBCEcXN1HYqCMHXUoxuKIIybq+tQFISpox7dUARh3Fxdh6IgTB316IYiCOPm6joUBWHqqEc3FEEYN1fXoUj90wDdgtfUTS5kK4lb6AgbtnHO71omC+nUCQODiywmo8Ma1s0ZAS4L6VS7JFRYqwvZiuHubhS3hXSqhIHxbguq6KhEWtWduME2qk0YzgXJTUqWuby8biWt6rbyZ7ZcNcKgO5KJ3llFXaLJLLRWdc9ztNU1NcLAQCEDJ1ANyRhSDf3vvPRWdc/zick1TcKMMVDmu47Jq52nVd2qftQkTG7sIsZKC1DjQraSuMW/biEJ87DTJuFSyjGOkK4o1+1Imgx+l9Ix9E+t6h7yh0H6EymThPnVRSSUa8uEbguqMsa1qjvjCvWkH1KiZpfEMt0WVAmAXtiq7p4L7E9VCYOuocmFbCVxZygig3sZN2ayLJ+k/i0JprgsqBqA3KruCUjrspBOnTAwnIPfFheyFcPNmwd+d1lIp9olDdz1kVyRB4IwFVWmB5QgjIeXK9IRhKmoMj2gBGE8vFyRjiBMRZXpASUI4+HlinQEYSqqTA8oQRgPL1ekIwhTUWV6QFH/NECj8Zo6FrI57kQHf3O+kXyt56+Ychc8E1FvYWAsF7KdI3yPgxuPvsfBX5KcmiDoFdqwbq5L4kaenKDGeUFmokoYGF1sQVeruhNmbCN+lqSpRlUJA8taXUxWEveMELhhuC6Lsj4tDIyl0blJ0TKXV0ARmLa0qlv8OOuSQBxOszCTTa2SYSgHXneJySywVnUnzuYNw/0c3nXpzxEeI367YWuSf6moZpckZJjH8DGkWgZIq7pnvupuGI5f+GDxBXE+bHBvB/58PdPURJMwY4yS+aZj8mrnqVm3dPXc02F2w3Yhz283CtFwqCZhcmMXsVFagFjIJh7RDTl+yW0vxBZd9UZRI4wwGwbmuh1Jk8Gvqrta1d1zIluYf56O4BNpdc57+e59qkaYzpJWF5OVxE3XT3GkxJBJ4f8QqaunpQNtwsjr6dQgLv/INZlpvvvEW9UtPrttvdG6sEXnpmZve62v5LtXqEoYGBcL2brq6FUan1ashY/O/a0TefN87epDVfemaml/C2Nr0tyObMBcEjdJeQSC8KMjWxc+TpvsAqdOGBh6A4NjIRuc4CWdz2XMYqpWtUsytTQKXwkPBGFWohrWx4ggzPrU1UpYGoRZiWpYHyOCMOtTVythaf8p6Qqj7dSoU6S5jL5TxREv4wHU9xU0T4e0kzB8Qzj0GHz79nCogEivzgMygT8L7A8jFb+44+iZvwAAAABJRU5ErkJggg==", + "text/latex": [ + "$\\displaystyle \\left[\\begin{array}{ccc|c}0 & 0 & 1 & 0\\\\0 & 0 & 0 & 1\\\\0 & 0 & 0 & b\\end{array}\\right]$" + ], + "text/plain": [ + "Matrix([\n", + "[0, 0, 1 | 0]\n", + "[0, 0, 0 | 1]\n", + "[0, 0, 0 | b]\n", + "])" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "\n" + ] + }, + { + "data": { + "text/latex": [ + "$\\displaystyle R_3 - \\left(b\\right)R_2 \\rightarrow R_3$" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAIwAAABLCAYAAACiLW8yAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAFXklEQVR4Ae1dUY7URhQcIr4jFKQcYHMDICcAbgC5AeQIfO7+IXID4ASIvQHcALE32BwgUhDiAqRq5SdZpj14Ju/VY7tfS962256uruqyu8f227lxenp6Z7fbfcTSSudnZ2ePWzuqLE4BaP4Vy404hPWagXuJvSetI9imm7Mdf2GdB8/T3/ONWh9CgRcNlg9R9ojlc8O8hIPKIA21RiqCB14t+aKMRd8YZnnc0dsAMJf+i0p+w/JCZcZMbAoGfA7xb7HcxfpnlqmSgvv8CuPCC43mfOg58nNWiPwWso/IH2IJvYKh/hTsieNr8PyE5R6W5hwA5WFJxf0nTwZo9FPUdwv5lVlYN9Z5lnH7JbejUjL2Z+A/xvIn+L2J4rhWr5K7q2FAiN+oLhrEPqDsAYjxahOVMrGjOG2tV8bd2zAPwJCX5WWyoYj7o1ImdhSnrfXKuLsZZuPV45etChxyXCb2Ie2MOFbN3c0wEMPMsO+bQdSQlIkd4YND6pRy9zTMFpK3txwUdEwmdhClzdW6cfc0TGvuYozsLOB9mYiUiR3B55A6pdzdDIOx1Iai1rBjZTb5PUSQ7x6bif3dxgUfoObuZphJl/fITxoa2RWG+6NSJnYUp631yrh7G4a3xHmnc5nuouBidjYs93tsZ2J7tP//1CHj7moYGIIPrj4hv3pQRQWwzuHoDyxPuB2VMrEXnGyCaVfVxW7/TSX3m/7N3/FqwoeNvyPnJJf5fWxfII9Oadjgx7OcyW5OvkUZ52zvkH/zBPjqSN8/Eu7uhoE4nPzymYo8JWPz9nxaUnF3HZLS1CpgmQJlGJnUfQCVYfroRxmLMoxM6j6AyjB99KOMRRlGJnUfQGWYPvpRxqIMI5O6D6AyTB/9KGNRhpFJ3QeQ+6MByoLb1EMGsmXyNjuiDXewzudaIYF07oZBg1OCySjYwNh8I0ASSOc6JKHDRg1kS+M9nSiyQDpXw6DxsoAqCrVIo2IvZIjd9DYM3wVpvZRs7/Jyf1QaFTtKz2a9bobBcGQvejeBpsKQt9BGxd4ndNQ+N8OggWYGvkC1lraYau2z+8pHxd6nScg+T8NsaaC977rlWO9jRsV21dHTMK25izXWrgA9BrJl8jZ9ZTkN8/OEZvlR4JhH2FDUGnaszCa/R2GsfWhU7DU9Asp/tTppmC/ThuW275hcFlDVaNyo2A0p3Iv+sRo9hyTWKQuoMgKzfFTsmQTxq66GwdAwZCBbJu+GRWxyb/PGxiHHF7k/S0JTJAFVK5RHxd7BtJJAOnfDoOGc/I4YyJbGmycPdJcE0rkOSStnfRV3pEAZpqPOVFApwyhU7gijDNNRZyqolGEUKneEUYbpqDMVVMowCpU7wijDdNSZCiplGIXKHWGUYTrqTAUV90cDbDRuU1cgm/iX6FS6u19hYBYGsn1A/gwLf3j0GRb+J8kTkopMo2JTUxV3V8Og0WkBXaNiT2aR6e5qGDR+1GCyTN70jAzf2zCjBpNl8qZhZPhuhsGQYC96k8BaCnkLbFRsiqzm7mYYtN3MwBeJ1tIWU619dl/5qNjURMrd0zD7OtT22fumtq3MR8Wmxm7cPQ2TGdA1KjbNIOXuZhiMpTYUtYYdK6tANnaxY1Lr7maYSYNRg8kyeVN6Gb63YUYNJsvkTcPI8F0Ng8tjBbKx+5CgBYfh8F+im7Bkukc8fBw1mCyTN30jwXc3zDQJq0A2dqEwqXR3HZKE+hRUkgJlmCThrytsGea69lxSu8swScJfV9gyzHXtuaR2z78lXWKmvWzGOcok/0ZiCVzbOQqgvy+BfLKGTsPw+c7a1+CQZz9rjanyH0IBe4G/2Zj/AGwGurAKP3p/AAAAAElFTkSuQmCC", + "text/latex": [ + "$\\displaystyle \\left[\\begin{array}{ccc|c}0 & 0 & 1 & 0\\\\0 & 0 & 0 & 1\\\\0 & 0 & 0 & 0\\end{array}\\right]$" + ], + "text/plain": [ + "Matrix([\n", + "[0, 0, 1 | 0]\n", + "[0, 0, 0 | 1]\n", + "[0, 0, 0 | 0]\n", + "])" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "\n", + "Completed branch with conditions:\n" + ] + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAADkAAAAVCAYAAAD8dkbIAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAC2klEQVRYCdWY4VFUQQyA5YYCQDs4O0CtQOgA7EDtQMZ/98/BDpQKGOkArACkA+lA5jo4v+/5dmdfbnnvMXMzvsvMkmySzUs22eweO6vV6lmExWKxB++1fOjrKJ/aHB/n+HTAuIO+j/7NIgOlT/B+tvzbKJ/ivA3M4M6grxgmKcNOmUmEx0h+MPahl1nrPxP4cta68Af8kmEwaxlTB/4VaAk+cS7ETL6Bd4/ClAL8hU83+HTK+Ap9yjBblmgN7mBauhlikFkwBYJAPuDHHvgy+QNtApx/S7whPOkgcd6SMzMRbmAcEnDn7EWlNJ96kIc4+pCcLXA6j8oHYXdQIyiwe3NYNgI/ZCOwdI7he14GAb3fKF2DP/YpIx+Tped9NpIsZlLD1nwV+LA7ZyOwCaRG4Nmw2w061eq4Sc0dDO6DFMCj/rC49k03Pq1t7OcgccCPv2NUD3TroNeLwaVy0Yj0qI7MOh32enrlwg3Ai4oNK8tm5XXYwC4Tg7P8xCfMH3vhqOPi7+ASzG7ufqWgRrO+LzPlktpZTPKUKbPWAey74fswz8GfwV9mMiUYtwyzVCsBRE2WOxvQ6ro5XsAbBWynzaj5k3hlRZXfNzj9Mp7LmRII33ypEZzLKwGZRh0xGMvb9Z3g5W0ItKuzEVIm176LL5apT9O3ya8myMLC2muhkEnGnTuC19xjGmfUHAomnjS1B9SalGfaxCwr1ny1+azLshhkZU2TKRd0dtWg4B0wLHPhCF7chH+S9i9yz/SKYYceBPQ8/w/g3ESgrSgr6H2Pgc55fso96evDw2wp2NUuGGbS60NetSvDz4CeO+xGpI3Jsh7CrPkNM2SjEVuKTQVBD8LoIHUQawYaocaLOnmOHX9FjIb2u6lfjF5XKo4q13LBNtIxyLXXwhYG5Zm16jLEIL3UbQ75oGfNLSDwOzWlTn/o/GfAOFCcg/xh6l10wdzAJw2tz55bfV/z+S+Vvf1nz3ryxgAAAABJRU5ErkJggg==", + "text/latex": [ + "$\\displaystyle \\left\\{ a : 0\\right\\}$" + ], + "text/plain": [ + "{a: 0}" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAIwAAABLCAYAAACiLW8yAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAFXklEQVR4Ae1dUY7URhQcIr4jFKQcYHMDICcAbgC5AeQIfO7+IXID4ASIvQHcALE32BwgUhDiAqRq5SdZpj14Ju/VY7tfS962256uruqyu8f227lxenp6Z7fbfcTSSudnZ2ePWzuqLE4BaP4Vy404hPWagXuJvSetI9imm7Mdf2GdB8/T3/ONWh9CgRcNlg9R9ojlc8O8hIPKIA21RiqCB14t+aKMRd8YZnnc0dsAMJf+i0p+w/JCZcZMbAoGfA7xb7HcxfpnlqmSgvv8CuPCC43mfOg58nNWiPwWso/IH2IJvYKh/hTsieNr8PyE5R6W5hwA5WFJxf0nTwZo9FPUdwv5lVlYN9Z5lnH7JbejUjL2Z+A/xvIn+L2J4rhWr5K7q2FAiN+oLhrEPqDsAYjxahOVMrGjOG2tV8bd2zAPwJCX5WWyoYj7o1ImdhSnrfXKuLsZZuPV45etChxyXCb2Ie2MOFbN3c0wEMPMsO+bQdSQlIkd4YND6pRy9zTMFpK3txwUdEwmdhClzdW6cfc0TGvuYozsLOB9mYiUiR3B55A6pdzdDIOx1Iai1rBjZTb5PUSQ7x6bif3dxgUfoObuZphJl/fITxoa2RWG+6NSJnYUp631yrh7G4a3xHmnc5nuouBidjYs93tsZ2J7tP//1CHj7moYGIIPrj4hv3pQRQWwzuHoDyxPuB2VMrEXnGyCaVfVxW7/TSX3m/7N3/FqwoeNvyPnJJf5fWxfII9Oadjgx7OcyW5OvkUZ52zvkH/zBPjqSN8/Eu7uhoE4nPzymYo8JWPz9nxaUnF3HZLS1CpgmQJlGJnUfQCVYfroRxmLMoxM6j6AyjB99KOMRRlGJnUfQGWYPvpRxqIMI5O6D6AyTB/9KGNRhpFJ3QeQ+6MByoLb1EMGsmXyNjuiDXewzudaIYF07oZBg1OCySjYwNh8I0ASSOc6JKHDRg1kS+M9nSiyQDpXw6DxsoAqCrVIo2IvZIjd9DYM3wVpvZRs7/Jyf1QaFTtKz2a9bobBcGQvejeBpsKQt9BGxd4ndNQ+N8OggWYGvkC1lraYau2z+8pHxd6nScg+T8NsaaC977rlWO9jRsV21dHTMK25izXWrgA9BrJl8jZ9ZTkN8/OEZvlR4JhH2FDUGnaszCa/R2GsfWhU7DU9Asp/tTppmC/ThuW275hcFlDVaNyo2A0p3Iv+sRo9hyTWKQuoMgKzfFTsmQTxq66GwdAwZCBbJu+GRWxyb/PGxiHHF7k/S0JTJAFVK5RHxd7BtJJAOnfDoOGc/I4YyJbGmycPdJcE0rkOSStnfRV3pEAZpqPOVFApwyhU7gijDNNRZyqolGEUKneEUYbpqDMVVMowCpU7wijDdNSZCiplGIXKHWGUYTrqTAUV90cDbDRuU1cgm/iX6FS6u19hYBYGsn1A/gwLf3j0GRb+J8kTkopMo2JTUxV3V8Og0WkBXaNiT2aR6e5qGDR+1GCyTN70jAzf2zCjBpNl8qZhZPhuhsGQYC96k8BaCnkLbFRsiqzm7mYYtN3MwBeJ1tIWU619dl/5qNjURMrd0zD7OtT22fumtq3MR8Wmxm7cPQ2TGdA1KjbNIOXuZhiMpTYUtYYdK6tANnaxY1Lr7maYSYNRg8kyeVN6Gb63YUYNJsvkTcPI8F0Ng8tjBbKx+5CgBYfh8F+im7Bkukc8fBw1mCyTN30jwXc3zDQJq0A2dqEwqXR3HZKE+hRUkgJlmCThrytsGea69lxSu8swScJfV9gyzHXtuaR2z78lXWKmvWzGOcok/0ZiCVzbOQqgvy+BfLKGTsPw+c7a1+CQZz9rjanyH0IBe4G/2Zj/AGwGurAKP3p/AAAAAElFTkSuQmCC", + "text/latex": [ + "$\\displaystyle \\left[\\begin{array}{ccc|c}0 & 0 & 1 & 0\\\\0 & 0 & 0 & 1\\\\0 & 0 & 0 & 0\\end{array}\\right]$" + ], + "text/plain": [ + "Matrix([\n", + "[0, 0, 1 | 0]\n", + "[0, 0, 0 | 1]\n", + "[0, 0, 0 | 0]\n", + "])" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "\n", + "Branch 2 on pivot (0, 0) ≠ 0 with conditions:\n" + ] + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAABIAAAAVCAYAAABLy77vAAAACXBIWXMAAA7EAAAOxAGVKw4bAAABMklEQVQ4EeWT4W3CMBCFSSagjJARKtgEugIbgPiV/EOMUDEBgg1KN0CMwAYgNki/F9muOTcIqPjFSced352fj/g5q+u6Y62qqi5YXzj51sWC+I7vwQ7CYsvjhXKaJoRvh+9cFK7N8gX5F67DgmXxRBSHVNb4G/k5dJlEREBn4siX7EQDCodrJG7jnqi/GcwShcK9yQsR6Upbbyv6bkfyXrTuhG/ETRUUPvDPuKEl34B32SO5NJaVZSmCBa44pdgouale+aFP0y9x7ZvnAFLrHJeKRaSGW2xGkz98Y5WdKPYvRg5LXkBuGhPFmrpf6gXoiYSLsUS+8ZZ4ipv+QxTz/F7/BfrA4mkTJYptGU4SCR9aPXaiRLGWyOkseQEXOtImGgvCFNdbWrEWucfHpKoHXDXZDyiXZyQzrNEgAAAAAElFTkSuQmCC", + "text/latex": [ + "$\\displaystyle \\left\\{ \\right\\}$" + ], + "text/plain": [ + "{}" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/latex": [ + "$\\displaystyle \\left(\\frac{1}{a}\\right) R_1 \\rightarrow R_1$" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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", + "text/latex": [ + "$\\displaystyle \\left[\\begin{array}{ccc|c}1 & 0 & \\frac{b}{a} & \\frac{2}{a}\\\\a & a & 4 & 4\\\\0 & a & 2 & b\\end{array}\\right]$" + ], + "text/plain": [ + "Matrix([\n", + "[1, 0, b/a | 2/a]\n", + "[a, a, 4 | 4]\n", + "[0, a, 2 | b]\n", + "])" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "\n" + ] + }, + { + "data": { + "text/latex": [ + "$\\displaystyle R_2 - \\left(a\\right)R_1 \\rightarrow R_2$" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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", + "text/latex": [ + "$\\displaystyle \\left[\\begin{array}{ccc|c}1 & 0 & \\frac{b}{a} & \\frac{2}{a}\\\\0 & a & 4 - b & 2\\\\0 & a & 2 & b\\end{array}\\right]$" + ], + "text/plain": [ + "Matrix([\n", + "[1, 0, b/a | 2/a]\n", + "[0, a, 4 - b | 2]\n", + "[0, a, 2 | b]\n", + "])" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "\n", + "Branching on pivot at (1, 1) due to possibility of zero value with free symbols {a}\n", + "Branch 1 on pivot (1, 1) ≠ 0 with conditions:\n" + ] + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAABIAAAAVCAYAAABLy77vAAAACXBIWXMAAA7EAAAOxAGVKw4bAAABMklEQVQ4EeWT4W3CMBCFSSagjJARKtgEugIbgPiV/EOMUDEBgg1KN0CMwAYgNki/F9muOTcIqPjFSced352fj/g5q+u6Y62qqi5YXzj51sWC+I7vwQ7CYsvjhXKaJoRvh+9cFK7N8gX5F67DgmXxRBSHVNb4G/k5dJlEREBn4siX7EQDCodrJG7jnqi/GcwShcK9yQsR6Upbbyv6bkfyXrTuhG/ETRUUPvDPuKEl34B32SO5NJaVZSmCBa44pdgouale+aFP0y9x7ZvnAFLrHJeKRaSGW2xGkz98Y5WdKPYvRg5LXkBuGhPFmrpf6gXoiYSLsUS+8ZZ4ipv+QxTz/F7/BfrA4mkTJYptGU4SCR9aPXaiRLGWyOkseQEXOtImGgvCFNdbWrEWucfHpKoHXDXZDyiXZyQzrNEgAAAAAElFTkSuQmCC", + "text/latex": [ + "$\\displaystyle \\left\\{ \\right\\}$" + ], + "text/plain": [ + "{}" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/latex": [ + "$\\displaystyle \\left(\\frac{1}{a}\\right) R_2 \\rightarrow R_2$" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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", + "text/latex": [ + "$\\displaystyle \\left[\\begin{array}{ccc|c}1 & 0 & \\frac{b}{a} & \\frac{2}{a}\\\\0 & 1 & - \\frac{b}{a} + \\frac{4}{a} & \\frac{2}{a}\\\\0 & a & 2 & b\\end{array}\\right]$" + ], + "text/plain": [ + "Matrix([\n", + "[1, 0, b/a | 2/a]\n", + "[0, 1, -b/a + 4/a | 2/a]\n", + "[0, a, 2 | b]\n", + "])" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "\n" + ] + }, + { + "data": { + "text/latex": [ + "$\\displaystyle R_3 - \\left(a\\right)R_2 \\rightarrow R_3$" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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", + "text/latex": [ + "$\\displaystyle \\left[\\begin{array}{ccc|c}1 & 0 & \\frac{b}{a} & \\frac{2}{a}\\\\0 & 1 & - \\frac{b}{a} + \\frac{4}{a} & \\frac{2}{a}\\\\0 & 0 & b - 2 & b - 2\\end{array}\\right]$" + ], + "text/plain": [ + "Matrix([\n", + "[1, 0, b/a | 2/a]\n", + "[0, 1, -b/a + 4/a | 2/a]\n", + "[0, 0, b - 2 | b - 2]\n", + "])" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "\n", + "Branching on pivot at (2, 2) due to possibility of zero value with free symbols {b}\n", + "Branch 1 on pivot (2, 2) = 0 with conditions:\n" + ] + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAADcAAAAVCAYAAADiv3Z7AAAACXBIWXMAAA7EAAAOxAGVKw4bAAAC8klEQVRYCdWX7VEbMRBADeMCgHQAHSS4gkAHIR0kdJBM/vlfhnQAVJDgDiAVJNABdBBCB857NzqNTqc73zEwMTsja7W72tVqP3TeWC6Xkxzm8/kWtH3p4Fc5f13WnG2Xs7xm3IDf5efazAkIfYL2M9B/5/x1WgeHdOoE/JJhUCJspJGD+Q7OBWMb/CFKgYSN8ozoPeu9lP/UOPqNyuegt7LpGvpNyRb0S+gPzEc1f1ojYZ4x3yHQcExeoB0yX7N81ohiQ8dOmQ+1LYCfMF1LY5RKRacNToRWWkZON2KOe0vPCTpynBrAIaPopZs9g2CUcxg4CFpLNzfI4EAh7dxir1FD0LS7Bd3IroRRzqHNNCmm7UpL4wR0os9O7nRRe15zRaGE6I3adu2ogjX6nfWiWvX8IHML+4q5kW6lLcjEppDxLYkJ/GJTyWQneeS8EfO6BSiUp3JT4oz1N+YPjAvw3jQJe5Wx6z0K0FHbrjtorucPhJ2UGJ0LB3wP8zQVSPC63t4iW11AmMUbXSrZU6FBzuflTc4bsbaRLNDhpZbA7LEe41mmLLxRu5PzEeuuZmG9mZJ5ZI3oK0YvFPb1yqdM9nrh1mBXuk7gyd9G7pz5C/PXTYkiDN8uH0kPWwIj13Ac2Tqav0obnoKGjY/o2WGOb16PXp0ySPqxqNISxIjUhX7esdlNuRP1TTac7tg/msyZTLE95trOBHzXkSuDpqyNzrKpzlM5lwjahaqOlNBSNH6cosAIW6PH4HmqpnsehaPTc8yY8waiE/cFpTNofn7Fs0wLQl0kC3afUbdhC/wHys66NtR0ZLyIvwwzZGVTQcbIqN+nI29wB9C6mkrD6THO2fYtVj+YPaxPwErHkJsg540adet6CPh5p4PWWw715eb01nqwcx6Q3TH3W5pWENg/+F/EGNk+s3nN9cm+OF7uXOuVf0EeWSpmV4TcudYrHyXXGCGNdaz1ddX4J+75EbSQbb9+pw36KEbuv0A4q++zZ26d9R8LYAw6Lc5P8QAAAABJRU5ErkJggg==", + "text/latex": [ + "$\\displaystyle \\left\\{ b : 2\\right\\}$" + ], + "text/plain": [ + "{b: 2}" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Completed branch with conditions:\n" + ] + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAADcAAAAVCAYAAADiv3Z7AAAACXBIWXMAAA7EAAAOxAGVKw4bAAAC8klEQVRYCdWX7VEbMRBADeMCgHQAHSS4gkAHIR0kdJBM/vlfhnQAVJDgDiAVJNABdBBCB857NzqNTqc73zEwMTsja7W72tVqP3TeWC6Xkxzm8/kWtH3p4Fc5f13WnG2Xs7xm3IDf5efazAkIfYL2M9B/5/x1WgeHdOoE/JJhUCJspJGD+Q7OBWMb/CFKgYSN8ozoPeu9lP/UOPqNyuegt7LpGvpNyRb0S+gPzEc1f1ojYZ4x3yHQcExeoB0yX7N81ohiQ8dOmQ+1LYCfMF1LY5RKRacNToRWWkZON2KOe0vPCTpynBrAIaPopZs9g2CUcxg4CFpLNzfI4EAh7dxir1FD0LS7Bd3IroRRzqHNNCmm7UpL4wR0os9O7nRRe15zRaGE6I3adu2ogjX6nfWiWvX8IHML+4q5kW6lLcjEppDxLYkJ/GJTyWQneeS8EfO6BSiUp3JT4oz1N+YPjAvw3jQJe5Wx6z0K0FHbrjtorucPhJ2UGJ0LB3wP8zQVSPC63t4iW11AmMUbXSrZU6FBzuflTc4bsbaRLNDhpZbA7LEe41mmLLxRu5PzEeuuZmG9mZJ5ZI3oK0YvFPb1yqdM9nrh1mBXuk7gyd9G7pz5C/PXTYkiDN8uH0kPWwIj13Ac2Tqav0obnoKGjY/o2WGOb16PXp0ySPqxqNISxIjUhX7esdlNuRP1TTac7tg/msyZTLE95trOBHzXkSuDpqyNzrKpzlM5lwjahaqOlNBSNH6cosAIW6PH4HmqpnsehaPTc8yY8waiE/cFpTNofn7Fs0wLQl0kC3afUbdhC/wHys66NtR0ZLyIvwwzZGVTQcbIqN+nI29wB9C6mkrD6THO2fYtVj+YPaxPwErHkJsg540adet6CPh5p4PWWw715eb01nqwcx6Q3TH3W5pWENg/+F/EGNk+s3nN9cm+OF7uXOuVf0EeWSpmV4TcudYrHyXXGCGNdaz1ddX4J+75EbSQbb9+pw36KEbuv0A4q++zZ26d9R8LYAw6Lc5P8QAAAABJRU5ErkJggg==", + "text/latex": [ + "$\\displaystyle \\left\\{ b : 2\\right\\}$" + ], + "text/plain": [ + "{b: 2}" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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", + "text/latex": [ + "$\\displaystyle \\left[\\begin{array}{ccc|c}1 & 0 & \\frac{2}{a} & \\frac{2}{a}\\\\0 & 1 & \\frac{2}{a} & \\frac{2}{a}\\\\0 & 0 & 0 & 0\\end{array}\\right]$" + ], + "text/plain": [ + "Matrix([\n", + "[1, 0, 2/a | 2/a]\n", + "[0, 1, 2/a | 2/a]\n", + "[0, 0, 0 | 0]\n", + "])" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "\n", + "Branch 2 on pivot (2, 2) ≠ 0 with conditions:\n" + ] + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAABIAAAAVCAYAAABLy77vAAAACXBIWXMAAA7EAAAOxAGVKw4bAAABMklEQVQ4EeWT4W3CMBCFSSagjJARKtgEugIbgPiV/EOMUDEBgg1KN0CMwAYgNki/F9muOTcIqPjFSced352fj/g5q+u6Y62qqi5YXzj51sWC+I7vwQ7CYsvjhXKaJoRvh+9cFK7N8gX5F67DgmXxRBSHVNb4G/k5dJlEREBn4siX7EQDCodrJG7jnqi/GcwShcK9yQsR6Upbbyv6bkfyXrTuhG/ETRUUPvDPuKEl34B32SO5NJaVZSmCBa44pdgouale+aFP0y9x7ZvnAFLrHJeKRaSGW2xGkz98Y5WdKPYvRg5LXkBuGhPFmrpf6gXoiYSLsUS+8ZZ4ipv+QxTz/F7/BfrA4mkTJYptGU4SCR9aPXaiRLGWyOkseQEXOtImGgvCFNdbWrEWucfHpKoHXDXZDyiXZyQzrNEgAAAAAElFTkSuQmCC", + "text/latex": [ + "$\\displaystyle \\left\\{ \\right\\}$" + ], + "text/plain": [ + "{}" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/latex": [ + "$\\displaystyle \\left(\\frac{1}{b - 2}\\right) R_3 \\rightarrow R_3$" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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", + "text/latex": [ + "$\\displaystyle \\left[\\begin{array}{ccc|c}1 & 0 & \\frac{b}{a} & \\frac{2}{a}\\\\0 & 1 & - \\frac{b}{a} + \\frac{4}{a} & \\frac{2}{a}\\\\0 & 0 & 1 & 1\\end{array}\\right]$" + ], + "text/plain": [ + "Matrix([\n", + "[1, 0, b/a | 2/a]\n", + "[0, 1, -b/a + 4/a | 2/a]\n", + "[0, 0, 1 | 1]\n", + "])" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "\n" + ] + }, + { + "data": { + "text/latex": [ + "$\\displaystyle R_1 - \\left(\\frac{b}{a}\\right)R_3 \\rightarrow R_1$" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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", + "text/latex": [ + "$\\displaystyle \\left[\\begin{array}{ccc|c}1 & 0 & 0 & - \\frac{b}{a} + \\frac{2}{a}\\\\0 & 1 & - \\frac{b}{a} + \\frac{4}{a} & \\frac{2}{a}\\\\0 & 0 & 1 & 1\\end{array}\\right]$" + ], + "text/plain": [ + "Matrix([\n", + "[1, 0, 0 | -b/a + 2/a]\n", + "[0, 1, -b/a + 4/a | 2/a]\n", + "[0, 0, 1 | 1]\n", + "])" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "\n" + ] + }, + { + "data": { + "text/latex": [ + "$\\displaystyle R_2 - \\left(\\frac{4 - b}{a}\\right)R_3 \\rightarrow R_2$" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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", + "text/latex": [ + "$\\displaystyle \\left[\\begin{array}{ccc|c}1 & 0 & 0 & - \\frac{b}{a} + \\frac{2}{a}\\\\0 & 1 & 0 & \\frac{b}{a} - \\frac{2}{a}\\\\0 & 0 & 1 & 1\\end{array}\\right]$" + ], + "text/plain": [ + "Matrix([\n", + "[1, 0, 0 | -b/a + 2/a]\n", + "[0, 1, 0 | b/a - 2/a]\n", + "[0, 0, 1 | 1]\n", + "])" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "\n", + "Completed branch with conditions:\n" + ] + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAABIAAAAVCAYAAABLy77vAAAACXBIWXMAAA7EAAAOxAGVKw4bAAABMklEQVQ4EeWT4W3CMBCFSSagjJARKtgEugIbgPiV/EOMUDEBgg1KN0CMwAYgNki/F9muOTcIqPjFSced352fj/g5q+u6Y62qqi5YXzj51sWC+I7vwQ7CYsvjhXKaJoRvh+9cFK7N8gX5F67DgmXxRBSHVNb4G/k5dJlEREBn4siX7EQDCodrJG7jnqi/GcwShcK9yQsR6Upbbyv6bkfyXrTuhG/ETRUUPvDPuKEl34B32SO5NJaVZSmCBa44pdgouale+aFP0y9x7ZvnAFLrHJeKRaSGW2xGkz98Y5WdKPYvRg5LXkBuGhPFmrpf6gXoiYSLsUS+8ZZ4ipv+QxTz/F7/BfrA4mkTJYptGU4SCR9aPXaiRLGWyOkseQEXOtImGgvCFNdbWrEWucfHpKoHXDXZDyiXZyQzrNEgAAAAAElFTkSuQmCC", + "text/latex": [ + "$\\displaystyle \\left\\{ \\right\\}$" + ], + "text/plain": [ + "{}" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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", + "text/latex": [ + "$\\displaystyle \\left[\\begin{array}{ccc|c}1 & 0 & 0 & - \\frac{b}{a} + \\frac{2}{a}\\\\0 & 1 & 0 & \\frac{b}{a} - \\frac{2}{a}\\\\0 & 0 & 1 & 1\\end{array}\\right]$" + ], + "text/plain": [ + "Matrix([\n", + "[1, 0, 0 | -b/a + 2/a]\n", + "[0, 1, 0 | b/a - 2/a]\n", + "[0, 0, 1 | 1]\n", + "])" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "\n", + "Summary of merged cases for non-homogeneous system:\n", + "Case 1: assume {a: 0}, excluding [{b: 2}]\n", + "No solution\n" + ] + }, + { + "data": { + "text/latex": [ + "$\\text{RREF}\\left\\{\\text{rref} = \\left[\\begin{array}{ccc|c}0 & 0 & 1 & 0\\\\0 & 0 & 0 & 1\\\\0 & 0 & 0 & 0\\end{array}\\right], \\quad\\text{pivots} = (2, 3)\\right\\}$" + ], + "text/plain": [ + "RREF(rref=Matrix([\n", + "[0, 0, 1 | 0]\n", + "[0, 0, 0 | 1]\n", + "[0, 0, 0 | 0]\n", + "]), pivots=(2, 3))" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "\n", + "Case 2: assume {}, excluding [{a: 0}, {b: 2}]\n", + "Unique solution\n" + ] + }, + { + "data": { + "text/latex": [ + "$\\text{RREF}\\left\\{\\text{rref} = \\left[\\begin{array}{ccc|c}1 & 0 & 0 & - \\frac{b}{a} + \\frac{2}{a}\\\\0 & 1 & 0 & \\frac{b}{a} - \\frac{2}{a}\\\\0 & 0 & 1 & 1\\end{array}\\right], \\quad\\text{pivots} = (0, 1, 2)\\right\\}$" + ], + "text/plain": [ + "RREF(rref=Matrix([\n", + "[1, 0, 0 | -b/a + 2/a]\n", + "[0, 1, 0 | b/a - 2/a]\n", + "[0, 0, 1 | 1]\n", + "]), pivots=(0, 1, 2))" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "\n", + "Case 3: assume {b: 2}, excluding [{a: 0}]\n", + "Solution with 1 free parameter(s)\n" + ] + }, + { + "data": { + "text/latex": [ + "$\\text{RREF}\\left\\{\\text{rref} = \\left[\\begin{array}{ccc|c}1 & 0 & \\frac{2}{a} & \\frac{2}{a}\\\\0 & 1 & \\frac{2}{a} & \\frac{2}{a}\\\\0 & 0 & 0 & 0\\end{array}\\right], \\quad\\text{pivots} = (0, 1)\\right\\}$" + ], + "text/plain": [ + "RREF(rref=Matrix([\n", + "[1, 0, 2/a | 2/a]\n", + "[0, 1, 2/a | 2/a]\n", + "[0, 0, 0 | 0]\n", + "]), pivots=(0, 1))" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "\n", + "Case 4: assume {a: 0, b: 2}, excluding []\n", + "Solution with 2 free parameter(s)\n" + ] + }, + { + "data": { + "text/latex": [ + "$\\text{RREF}\\left\\{\\text{rref} = \\left[\\begin{array}{ccc|c}0 & 0 & 1 & 1\\\\0 & 0 & 0 & 0\\\\0 & 0 & 0 & 0\\end{array}\\right], \\quad\\text{pivots} = (2,)\\right\\}$" + ], + "text/plain": [ + "RREF(rref=Matrix([\n", + "[0, 0, 1 | 1]\n", + "[0, 0, 0 | 0]\n", + "[0, 0, 0 | 0]\n", + "]), pivots=(2,))" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "\n" + ] + }, + { + "data": { + "image/png": "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", + "text/latex": [ + "$\\displaystyle \\left[ \\text{RREFCase}\\left\\{\\text{conditions} = \\left\\{ a : 0\\right\\},\\quad \\text{excluded} = \\left[ \\left\\{ b : 2\\right\\}\\right],\\quad \\text{rref} = \\left[\\begin{array}{ccc|c}0 & 0 & 1 & 0\\\\0 & 0 & 0 & 1\\\\0 & 0 & 0 & 0\\end{array}\\right],\\quad \\text{pivots} = (2, 3),\\quad \\text{free\\_params} = 2,\\quad \\text{is\\_consistent} = \\text{False}\\right\\}, \\ \\text{RREFCase}\\left\\{\\text{conditions} = \\{\\},\\quad \\text{excluded} = \\left[ \\left\\{ a : 0\\right\\}, \\ \\left\\{ b : 2\\right\\}\\right],\\quad \\text{rref} = \\left[\\begin{array}{ccc|c}1 & 0 & 0 & - \\frac{b}{a} + \\frac{2}{a}\\\\0 & 1 & 0 & \\frac{b}{a} - \\frac{2}{a}\\\\0 & 0 & 1 & 1\\end{array}\\right],\\quad \\text{pivots} = (0, 1, 2),\\quad \\text{free\\_params} = 0,\\quad \\text{is\\_consistent} = \\text{True}\\right\\}, \\ \\text{RREFCase}\\left\\{\\text{conditions} = \\left\\{ b : 2\\right\\},\\quad \\text{excluded} = \\left[ \\left\\{ a : 0\\right\\}\\right],\\quad \\text{rref} = \\left[\\begin{array}{ccc|c}1 & 0 & \\frac{2}{a} & \\frac{2}{a}\\\\0 & 1 & \\frac{2}{a} & \\frac{2}{a}\\\\0 & 0 & 0 & 0\\end{array}\\right],\\quad \\text{pivots} = (0, 1),\\quad \\text{free\\_params} = 1,\\quad \\text{is\\_consistent} = \\text{True}\\right\\}, \\ \\text{RREFCase}\\left\\{\\text{conditions} = \\left\\{ a : 0, \\ b : 2\\right\\},\\quad \\text{excluded} = [\\,],\\quad \\text{rref} = \\left[\\begin{array}{ccc|c}0 & 0 & 1 & 1\\\\0 & 0 & 0 & 0\\\\0 & 0 & 0 & 0\\end{array}\\right],\\quad \\text{pivots} = (2,),\\quad \\text{free\\_params} = 2,\\quad \\text{is\\_consistent} = \\text{True}\\right\\}\\right]$" + ], + "text/plain": [ + "⎡RREFCase(conditions={a: 0}, excluded=[{b: 2}], rref=Matrix([, RREFCase(condit ↪\n", + "⎢ [0, 0, 1 | 0] ↪\n", + "⎢ [0, 0, 0 | 1] ↪\n", + "⎢ [0, 0, 0 | 0] ↪\n", + "⎣ ]), pivots=(2, 3), free_params=2, is_consistent=False) ]), pivots= ↪\n", + "\n", + "↪ ions={}, excluded=[{a: 0}, {b: 2}], rref=Matrix([, RREFCase(conditions={b: 2 ↪\n", + "↪ [1, 0, 0 | -b/a + 2/a] [1, ↪\n", + "↪ [0, 1, 0 | b/a - 2/a] [0, ↪\n", + "↪ [0, 0, 1 | 1] [0, ↪\n", + "↪ (0, 1, 2), free_params=0, is_consistent=True) ]), pivots=(0, 1), fre ↪\n", + "\n", + "↪ }, excluded=[{a: 0}], rref=Matrix([, RREFCase(conditions={a: 0, b: 2}, exclu ↪\n", + "↪ 0, 2/a | 2/a] [0, 0, 1 | 1] ↪\n", + "↪ 1, 2/a | 2/a] [0, 0, 0 | 0] ↪\n", + "↪ 0, 0 | 0] [0, 0, 0 | 0] ↪\n", + "↪ e_params=1, is_consistent=True) ]), pivots=(2,), free_params=2, is_ ↪\n", + "\n", + "↪ ded=[], rref=Matrix([⎤\n", + "↪ ⎥\n", + "↪ ⎥\n", + "↪ ⎥\n", + "↪ consistent=True) ⎦" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "A.evaluate_cases(rhs=b, verbosity=2)" + ] + }, + { + "cell_type": "markdown", + "id": "d46b52a8", + "metadata": {}, + "source": [ + "### Alternative Solution\n", + "\n", + "This is an alternative method that that gives you more flexibility to do your own substitution." + ] + }, + { + "cell_type": "code", + "execution_count": null, "id": "efcb9ba0", "metadata": {}, "outputs": [ @@ -562,7 +1605,7 @@ } ], "source": [ - "plu = mat.ref() # Reduced Row Echelon Form using PLU decomposition" + "plu = mat.ref() # Row Echelon Form using PLU decomposition" ] }, { diff --git a/tests/chapters/test_c1_linear_systems.py b/tests/chapters/test_c1_linear_systems.py index e52fb55..ddce142 100644 --- a/tests/chapters/test_c1_linear_systems.py +++ b/tests/chapters/test_c1_linear_systems.py @@ -17,6 +17,7 @@ """ import pytest +import sympy as sym from sympy import S from ma1522 import Matrix @@ -175,13 +176,31 @@ class TestEvaluateCases: - solve """ - # TODO def test_evaluate_cases(self): - """Test evaluating special cases""" - mat = Matrix([[1, 2], [3, 4]]) - rhs = Matrix([[1], [2]]) - # Just verify the method runs without error - mat.evaluate_cases(rhs) + """Test the symbolic case split produces all expected cases.""" + a, b = sym.symbols("a b") + mat = Matrix([[a, 0, b, 2], [a, a, 4, 4], [0, a, 2, b]], aug_pos=2) + rhs = mat.select_cols(3) + mat = mat.select_cols(*range(3)) + + cases = mat.evaluate_cases(rhs, verbosity=0) + + assert len(cases) == 4 + assert [c.conditions for c in cases] == [ + {a: 0}, + {}, + {b: 2}, + {a: 0, b: 2}, + ] + assert [c.excluded for c in cases] == [ + [{b: 2}], + [{a: 0}, {b: 2}], + [{a: 0}], + [], + ] + assert [c.is_consistent for c in cases] == [False, True, True, True] + assert [c.free_params for c in cases] == [2, 0, 1, 2] + assert [c.pivots for c in cases] == [(2, 3), (0, 1, 2), (0, 1), (2,)] def test_solve(self): A = Matrix([[1, 2], [3, 4]]) diff --git a/tests/test_c1_linear_systems.py b/tests/test_c1_linear_systems.py deleted file mode 100644 index ddce142..0000000 --- a/tests/test_c1_linear_systems.py +++ /dev/null @@ -1,209 +0,0 @@ -"""Include the following methods in the tests -- aug_line -- rm_aug_line -- row_join -- col_join -- scale_row -- swap_row -- reduce_row -- get_pivot_row -- get_pivot_pos -- get_pivot_elements -- ref -- find_all_cases -- evaluate_cases -- rref -- solve -""" - -import pytest -import sympy as sym -from sympy import S - -from ma1522 import Matrix - - -class TestAugLine: - """Tests for Matrix.aug_line() method - - Covers: - - Adding augmentation lines at different positions - - Multiple augmentation lines - - Edge cases (invalid positions) - """ - - def test_single_aug_line(self): - mat = Matrix([[1, 2], [3, 4]]) - mat2 = mat.aug_line(1) - assert hasattr(mat2, "_aug_pos") - assert mat2._aug_pos == set([1]) - - def test_multiple_aug_lines(self): - """Test adding multiple augmentation lines""" - mat = Matrix([[1, 2], [3, 4]]) - mat = mat.aug_line(0).aug_line(1) - assert mat._aug_pos == set([0, 1]) - - def test_invalid_position(self): - """Test invalid augmentation position raises error""" - mat = Matrix([[1, 2], [3, 4]]) - with pytest.raises(IndexError): - mat.aug_line(3) - - -class TestRmAugLine: - """Tests for Matrix.rm_aug_line() method - - Covers: - - Removing existing augmentation lines - - Attempting to remove non-existent lines - """ - - def test_remove_existing_line(self): - """Test removing an existing augmentation line""" - mat = Matrix([[1, 2], [3, 4]], aug_pos={0, 1}) - mat = mat.rm_aug_line(0) - assert mat._aug_pos == set([1]) - - def test_remove_nonexistent_line(self): - """Test removing a non-existent line has no effect""" - mat = Matrix([[1, 2], [3, 4]], aug_pos={0}) - mat = mat.rm_aug_line(1) - assert mat._aug_pos == set([0]) - - -class TestJoinOperations: - """Tests for basic join operations - - Covers: - - row_join() - - col_join() - """ - - # TODO - - -class TestRowOperations: - """Tests for basic row operations - - Covers: - - scale_row() - - swap_row() - - reduce_row() - """ - - def test_scale_row(self): - """Test scaling a row by a scalar""" - mat = Matrix([[1, 2], [3, 4]]) - mat.scale_row(0, S(2)) - assert mat == Matrix([[2, 4], [3, 4]]) - - def test_swap_row(self): - """Test swapping two rows""" - mat = Matrix([[1, 2], [3, 4]]) - mat.swap_row(0, 1) - assert mat == Matrix([[3, 4], [1, 2]]) - - def test_reduce_row(self): - """Test row reduction operation""" - mat = Matrix([[1, 2], [3, 4]]) - mat.reduce_row(1, 3.0, 0) - assert mat == Matrix([[1, 2], [0, -2]]) - - -class TestPivotOperations: - """Tests for pivot-related operations - - Covers: - - get_pivot_row() - - get_pivot_pos() - - get_pivot_elements() - """ - - @pytest.mark.parametrize( - "matrix, col, start_row, expected", - [ - (Matrix([[1, 2], [0, 1]]), 0, 0, 0), # First row is pivot - (Matrix([[0, 2], [1, 1]]), 0, 0, 1), # Second row is pivot - (Matrix([[0, 2], [0, 1]]), 0, 0, None), # No pivot - ], - ) - def test_get_pivot_row(self, matrix, col, start_row, expected): - """Test finding pivot row in a column""" - assert matrix.get_pivot_row(col, start_row) == expected - - def test_get_pivot_pos(self): - """Test finding all pivot positions""" - mat = Matrix([[1, 2, 3], [0, 1, 2], [0, 0, 1]]) - assert mat.get_pivot_pos() == [(0, 0), (1, 1), (2, 2)] - - def test_get_pivot_elements(self): - """Test getting pivot elements""" - mat = Matrix([[1, 2, 3], [0, 1, 2], [0, 0, 1]]) - assert mat.get_pivot_elements() == [1, 1, 1] - - -class TestReductions: - """Tests for matrix reduction methods - - Covers: - - ref() - - rref() - """ - - def test_ref(self): - """Test row echelon form""" - mat = Matrix([[1, 2], [3, 4]]) - plu = mat.ref() - assert plu.U[1, 0] == 0 # Verify lower triangular is zero - assert plu.U == Matrix([[1, 2], [0, -2]]) # Verify exact REF result - - def test_rref(self): - """Test reduced row echelon form""" - mat = Matrix([[1, 2], [3, 4]]) - rref_mat, pivots = mat.rref() - assert rref_mat[1, 0] == 0 # Below diagonal is zero - assert rref_mat[0, 1] == 0 # Above pivot is zero - assert rref_mat == Matrix([[1, 0], [0, 1]]) # Verify exact RREF result - assert pivots == (0, 1) # Verify pivot columns - - -class TestEvaluateCases: - """Tests for rank of matrix with unknowns - Covers: - - find_all_cases - - evaluate_cases - - solve - """ - - def test_evaluate_cases(self): - """Test the symbolic case split produces all expected cases.""" - a, b = sym.symbols("a b") - mat = Matrix([[a, 0, b, 2], [a, a, 4, 4], [0, a, 2, b]], aug_pos=2) - rhs = mat.select_cols(3) - mat = mat.select_cols(*range(3)) - - cases = mat.evaluate_cases(rhs, verbosity=0) - - assert len(cases) == 4 - assert [c.conditions for c in cases] == [ - {a: 0}, - {}, - {b: 2}, - {a: 0, b: 2}, - ] - assert [c.excluded for c in cases] == [ - [{b: 2}], - [{a: 0}, {b: 2}], - [{a: 0}], - [], - ] - assert [c.is_consistent for c in cases] == [False, True, True, True] - assert [c.free_params for c in cases] == [2, 0, 1, 2] - assert [c.pivots for c in cases] == [(2, 3), (0, 1, 2), (0, 1), (2,)] - - def test_solve(self): - A = Matrix([[1, 2], [3, 4]]) - b = Matrix([[1], [1]]) - x = A.solve(b)[0] - assert (A @ x - b) == Matrix([0, 0]) diff --git a/tests/test_c2_matrix_algebra.py b/tests/test_c2_matrix_algebra.py deleted file mode 100644 index ab40390..0000000 --- a/tests/test_c2_matrix_algebra.py +++ /dev/null @@ -1,96 +0,0 @@ -"""Include the following methods in the tests -- inverse -- elem -- adj -- column_constraints -""" - -import pytest -import sympy as sym - -from ma1522 import Matrix - - -class TestMatrixInverse: - """Tests for Matrix inverse operations - - Covers: - - Left/right inverses - - Edge cases (singular matrices) - - Special cases - """ - - def test_left_inverse(self): - """Test left inverse for full column rank matrix""" - mat = Matrix([[1, 0], [0, 1], [1, 1]]) - inv = mat.inverse(option="left", verbosity=2) - assert inv is not None - assert (inv @ mat) == Matrix.eye(2) - - def test_right_inverse(self): - """Test right inverse for full row rank matrix""" - mat = Matrix([[1, 0, 1], [0, 1, 1]]) - inv = mat.inverse(verbosity=2) - assert inv is not None - assert (mat @ inv) == Matrix.eye(2) - - def test_square_inverse(self): - """Test inverse for square matrix""" - mat = Matrix([[1, 2], [3, 4]]) - inv = mat.inverse(option="both", verbosity=2) - assert (inv @ mat) == Matrix.eye(2) - assert (mat @ inv) == Matrix.eye(2) - - def test_singular_matrix(self): - """Test inverse fails for singular matrix""" - mat = Matrix([[1, 2], [2, 4]]) # Determinant = 0 - with pytest.raises(ValueError): - mat.inverse() - - def test_1x1_matrix(self): - """Test special case for 1x1 matrix""" - mat = Matrix([[5]]) - inv = mat.inverse() - assert (inv @ mat) == Matrix.eye(1) - assert (mat @ inv) == Matrix.eye(1) - assert inv == Matrix([[sym.Rational(1, 5)]]) - - -class TestAdjointOperations: - """Tests for adjoint/adjugate operations""" - - def test_adjoint(self): - """Test classical adjoint calculation""" - mat = Matrix([[1, 2], [3, 4]]) - adj = mat.adj() - assert adj == Matrix([[4, -2], [-3, 1]]) - - def test_adjoint_property(self): - """Test A * adj(A) = det(A)*I""" - mat = Matrix([[1, 2], [3, 4]]) - assert (mat @ mat.adj()) == Matrix.diag(mat.det(), mat.det()) - - -class TestElem: - """Tests for Matrix.elem() method""" - - def test_elem(self): - """Test elem() returns identity matrix of correct size""" - mat = Matrix([[1, 2], [3, 4]]) - identity_matrix = mat.elem() - assert identity_matrix == Matrix.eye(2) - assert identity_matrix.shape == (2, 2) - - -class TestColumnConstraints: - """Tests for column constraint calculations""" - - def test_column_constraints(self): - """Test column constraints for non-invertible matrix""" - x1, x2 = Matrix.create_unk_matrix(2, 1, "x") - mat = Matrix([[1, 2], [2, 4]]) - constraints = mat.column_constraints() - half_x2 = sym.Mul(x2, sym.Rational(1, 2)) - x1_minus_half = sym.Add(x1, sym.Mul(half_x2, -1)) # Equivalent to x1 - half_x2 - expected_rref = Matrix([[1, 2, half_x2], [0, 0, x1_minus_half]], aug_pos=1) - assert constraints == expected_rref diff --git a/tests/test_c3_euclidean_vector_spaces.py b/tests/test_c3_euclidean_vector_spaces.py deleted file mode 100644 index 37daa6e..0000000 --- a/tests/test_c3_euclidean_vector_spaces.py +++ /dev/null @@ -1,99 +0,0 @@ -"""Include the following methods in the tests -- normalized -- is_linearly_independent -- simplify_basis -- extend_basis -- intersect_subspace -- is_same_subspace -- coords_relative -- transition_matrix -""" - -import sympy as sym - -from ma1522 import Matrix - - -class TestEuclideanVectorSpaces: - """Tests for euclidean vector space operations - - Covers: - - Vector normalization - - Linear independence - - Same subspace - - Relative coordinates - - Transition matrix - """ - - def test_normalized(self): - mat = Matrix([[1, 0], [1, 4]]) - normalized_mat = mat.normalized() - assert ( - normalized_mat - Matrix([[1 / sym.sqrt(2), 0], [1 / sym.sqrt(2), 1]]) # type: ignore - == Matrix.zeros(2, 2) - ) - - def test_is_linearly_independent(self): - mat = Matrix([[1, 2], [3, 4]]) - assert mat.is_linearly_independent() is True - mat = Matrix([[1, 2], [2, 4]]) - assert mat.is_linearly_independent() is False - - def test_is_same_subspace(self): - mat1 = Matrix([[1, 0], [0, 1]]) - mat2 = Matrix([[1, 2], [3, 4]]) - assert mat1.is_same_subspace(mat2) is True - - def test_transition_matrix(self): - mat1 = Matrix([[1, 0], [0, 1]]) - mat2 = Matrix([[2, 0], [0, 2]]) - transition = mat1.transition_matrix(to=mat2, verbosity=2) - assert transition == Matrix.from_str("1/2 0; 0 1/2") - - -class TestSubspaceOperations: - """Tests for subspace operations - - Covers: - - Simplify column/row space operations - - Basis extension - - Subspace intersections - """ - - def test_simplify_basis(self): - """Test simplify basis""" - mat = Matrix([[1, 2, 2, 5], [3, 4, 6, 13], [0, 0, 0, 0]]) - col_basis = mat.simplify_basis(colspace=True) - row_basis = mat.simplify_basis(colspace=False) - assert col_basis == Matrix([[1, 0], [0, 1], [0, 0]]) - assert row_basis == Matrix([[1, 0, 2, 3], [0, 1, 0, 1]]) - - def test_extend_basis(self): - """Test extending to a basis""" - mat = Matrix([[1, 1], [1, 1]]) - extended = mat.extend_basis() - assert extended.cols == 2 # Original space dimension - assert extended.rank() == 2 # Now full rank - - def test_subspace_intersection(self): - """Test subspace intersection""" - A = Matrix([[1, 0], [0, 1]]) - B = Matrix([[1, 1], [1, 1]]) - intersection = A.intersect_subspace(B) - assert intersection.cols == 1 # One common direction - - -class TestCoordinatesBasis: - def test_transition_matrix(self): - """Test transition matrix between bases""" - A = Matrix([[1, 0], [0, 1]]) - B = Matrix([[1, 1], [1, -1]]) - P = A.transition_matrix(B) - assert (P @ B) == A - - def test_coordinate_transformation(self): - """Test coordinate transformation""" - A = Matrix([[1, 0], [0, 1]]) - v = Matrix([[1], [1]]) - coords = v.coords_relative(A) - assert (A @ coords) == v diff --git a/tests/test_c4_subspaces_assoc_matrix.py b/tests/test_c4_subspaces_assoc_matrix.py deleted file mode 100644 index 46e2406..0000000 --- a/tests/test_c4_subspaces_assoc_matrix.py +++ /dev/null @@ -1,13 +0,0 @@ -"""Include the following methods in the tests -- nullspace -""" - -from ma1522 import Matrix - - -class TestSubspacesAssocMatrix: - def test_nullspace(self): - mat = Matrix([[1, 2], [3, 6]]) - nullspace = Matrix.from_list(mat.nullspace()) - assert nullspace.cols == 1 # One free variable - assert (mat @ nullspace).norm() == 0 # Av = 0 diff --git a/tests/test_c5_orthogonality_and_lss.py b/tests/test_c5_orthogonality_and_lss.py deleted file mode 100644 index 35be1d7..0000000 --- a/tests/test_c5_orthogonality_and_lss.py +++ /dev/null @@ -1,86 +0,0 @@ -"""Include the following methods in the tests -- orthogonal_complement -- is_vec_orthogonal -- is_mat_orthogonal -- orthogonal_decomposition -- proj_comp -- norm_comp -- gram_schmidt -- QRdecomposition -- solve_least_squares -- create_vander -- apply_vander -""" - -import sympy as sym - -from ma1522 import Matrix, VecDecomp - - -class TestChapter5: - def test_orthogonal_complement(self): - mat = Matrix([[1, 0], [0, 1], [0, 0]]) - ortho_comp = mat.orthogonal_complement() - assert ortho_comp.cols == 1 - assert (mat.T @ ortho_comp).is_zero_matrix - - mat = Matrix([[1, 0], [0, 1]]) - orth_comp = mat.orthogonal_complement() - assert orth_comp == Matrix([]) - - def test_is_vec_orthogonal(self): - mat = Matrix([[1, 0], [0, 1]]) - assert mat.is_vec_orthogonal() is True - mat = Matrix([[2, 0], [0, 3]]) - assert mat.is_vec_orthogonal() is True - mat = Matrix([[1, 1], [1, 1]]) - assert mat.is_vec_orthogonal() is False - - def test_is_mat_orthogonal(self): - mat = Matrix([[1, 0], [0, 1]]) - assert mat.is_mat_orthogonal() is True - mat = Matrix([[2, 0], [0, 3]]) - assert mat.is_mat_orthogonal() is False - mat = Matrix([[1, 1], [1, 0]]) - assert mat.is_mat_orthogonal() is False - - def test_orthogonal_decomposition(self): - """Test vector decomposition into orthogonal components""" - A = Matrix([[1, 0], [1, 1]]) - b = Matrix([[-1], [2]]) - decomp = b.orthogonal_decomposition(A) - assert isinstance(decomp, VecDecomp) - assert (decomp.proj + decomp.norm) == b - - def test_proj_comp(self): - to = Matrix([[1, 0], [0, 1]]) - vec = Matrix([[1], [1]]) - proj = vec.proj_comp(to) - assert proj == vec - - def test_norm_comp(self): - to = Matrix([[1, 0], [0, 1]]) - vec = Matrix([[1], [1]]) - norm = vec.norm_comp(to) - assert norm == Matrix([[0], [0]]) - - def test_gram_schmidt(self): - mat = Matrix([[1, 1], [1, 0]]) - ortho_mat = mat.gram_schmidt(factor=True) - assert ortho_mat.full.is_vec_orthogonal() is True # type: ignore - assert ortho_mat.full.select_cols(0).dot(ortho_mat.full.select_cols(1)) == 0 # type: ignore - - def test_QRdecomposition(self): - mat = Matrix([[1, 1], [1, 0]]) - q, r = mat.QRdecomposition() - # Verify Q is orthogonal - assert (q.T @ q) == Matrix.eye(2) - # Verify R is upper triangular - assert r[1, 0] == 0 - - def test_solve_least_squares(self): - """Test least squares solution""" - A = Matrix([[0, 1], [1, 1], [2, 1]]) - b = Matrix([[6], [0], [0]]) - x = A.solve_least_squares(b) - assert (A @ x - b).norm() == sym.sqrt(6) # Minimized error diff --git a/tests/test_c6_eigen_analysis.py b/tests/test_c6_eigen_analysis.py deleted file mode 100644 index e650975..0000000 --- a/tests/test_c6_eigen_analysis.py +++ /dev/null @@ -1,69 +0,0 @@ -"""Include the following methods in the tests -- cpoly -- is_diagonalizable -- eigenvects_associated -- diagonalize -- is_orthogonally_diagonalizable -- orthogonally_diagonalize -- is_stochastic -- equilibrium_vectors -- singular_value_decomposition -- fast_svd -""" - -import sympy as sym - -from ma1522 import Matrix - - -class TestChapter6: - def test_cpoly(self): - mat = Matrix([[1, 2], [3, 4]]) - poly = mat.cpoly() - poly = sym.Poly(poly) - assert poly.all_coeffs() == [1, -5, -2] - - def test_cpoly_2(self): - res = Matrix.diag(-1, 4, sym.Rational(1, 2)).cpoly() - roots = set() - for expr in res.args: # type: ignore - roots.add(sym.solve(expr)[0]) - assert roots == set((4, sym.Rational(1, 2), -1)) - - def test_is_diagonalizable(self): - mat = Matrix([[1, 1], [0, 1]]) - assert mat.is_diagonalizable() is False - mat = Matrix([[1, 0], [0, 2]]) - assert mat.is_diagonalizable() is True - - def test_diagonalize(self): - mat = Matrix([[1, 2], [0, 3]]) - pdp = mat.diagonalize() - assert (pdp.P @ pdp.D @ pdp.P.inv() - mat).norm() < 1e-10 - - def test_is_orthogonally_diagonalizable(self): - mat = Matrix([[1, 2], [2, 1]]) - assert mat.is_orthogonally_diagonalizable() is True - mat = Matrix([[1, 2], [3, 4]]) - assert mat.is_orthogonally_diagonalizable() is False - - def test_orthogonally_diagonalize(self): - mat = Matrix([[1, 2], [2, 1]]) - pdp = mat.orthogonally_diagonalize(verbosity=0) - assert (pdp.P @ pdp.D @ pdp.P.T - mat).norm() < 1e-10 - - def test_equilibrium_vectors(self): - mat = Matrix([[0.8, 0.3], [0.2, 0.7]]) - eq_vects = mat.equilibrium_vectors() - assert mat @ eq_vects == eq_vects - - def test_fast_svd(self): - mat = Matrix([[1, 2], [3, 4]]) - svd = mat.fast_svd(option="np", identify=False) - U, S, V = svd.U, svd.S, svd.V - assert (Matrix(U) @ Matrix(S) @ Matrix(V).T - mat).norm() < 1e-10 - - def test_singular_value_decomposition(self): - mat = Matrix([[1, 2], [3, 4]]) - svd = mat.singular_value_decomposition(verbosity=2) - assert (svd.U @ svd.S @ svd.V.T - mat).norm() < 1e-9 diff --git a/tests/test_c7_linear_transformation.py b/tests/test_c7_linear_transformation.py deleted file mode 100644 index 04452c4..0000000 --- a/tests/test_c7_linear_transformation.py +++ /dev/null @@ -1,15 +0,0 @@ -"""Include the following methods in the tests -- standard_matrix -""" - -from ma1522 import Matrix - - -class TestLinearTransformations: - def test_standard_matrix(self): - """Test standard matrix representation""" - standard_matrix = Matrix.create_rand_matrix(3, 3) - input_vectors = Matrix.create_rand_matrix(3, 3) - output_vectors = standard_matrix @ input_vectors - sol = Matrix.standard_matrix(input_vectors, output_vectors)[0] - assert sol == standard_matrix diff --git a/tests/test_tut01.py b/tests/test_tut01.py deleted file mode 100644 index 556b73f..0000000 --- a/tests/test_tut01.py +++ /dev/null @@ -1,86 +0,0 @@ -import pytest - -import sympy as sym - -from ma1522 import Matrix - - -class TestTutorial01: - def test_question_3a(self): - mat = Matrix([[3, 2, -4], [2, 3, 3], [5, -3, 1]]) - - aug = Matrix([3, 15, 14]) - - aug_mat = mat.row_join(aug) - plu = aug_mat.ref() - assert plu.U.is_echelon - - rref = plu.U.rref() - assert rref.rref[:, -1] == Matrix([3, 1, 2]) # type: ignore - - def test_question_3b(self): - mat = Matrix([[1, 1, -1, -2], [2, 1, -1, 1], [-1, 1, -3, 1]]) - aug = Matrix([0, -2, 4]) - sol = mat.solve(aug)[0] - unk = sol[-1] - assert sol == Matrix([-3 * unk - 2, 19 * unk / 2 + 2, 9 * unk / 2, unk]) # type: ignore - - def test_question_3c(self): - aug_mat = Matrix([[1, -4, 2, -2], [1, 2, -2, -3], [1, -1, 0, 4]], aug_pos=2) - mat = aug_mat.select_cols(0, 1, 2) # Remove the augmented column - aug = aug_mat.select_cols(3) # Select the augmented column - - with pytest.raises(ValueError): - mat.solve(aug)[0] - - def test_question_4(self): - # TODO: Implement better evaluate all cases - a, b = sym.symbols("a b") # Define symbolic variables - mat = Matrix([[a, 0, b, 2], [a, a, 4, 4], [0, a, 2, b]], aug_pos=2) - plu = mat.ref() - assert plu.U.is_echelon - # Case 1: b != 2, a = 0 - assert plu.U.subs({a: 0}).rref(pivots=False) == Matrix( - [[0, 0, 1, 0], [0, 0, 0, 1], [0, 0, 0, 0]], aug_pos=2 - ) - # Case 2: b != 2, a != 0 - assert plu.U.rref(pivots=False) == Matrix( - [[1, 0, 0, (2 - b) / a], [0, 1, 0, (b - 2) / a], [0, 0, 1, 1]], aug_pos=2 - ) - # Case 3: b = 2, a != 0 - assert plu.U.subs({b: 2}).rref(pivots=False) == Matrix( - [[1, 0, 2 / a, 2 / a], [0, 1, 2 / a, 2 / a], [0, 0, 0, 0]], aug_pos=2 - ) - # Case 4: b = 2, a = 0 - assert plu.U.subs({a: 0, b: 2}).rref(pivots=False) == Matrix( - [[0, 0, 1, 1], [0, 0, 0, 0], [0, 0, 0, 0]], aug_pos=2 - ) - - def test_question_6(self): - x, y, z = sym.symbols("x y z") - vec = Matrix([x**2, y**2, z**2]) - mat = Matrix([[1, -1, 2], [2, 2, -5], [2, 5, 1]]) - aug = Matrix([6, 3, 9]) - assert len(sym.solve(mat @ vec - aug)) == 4 - - def test_question_7(self): - aug_mat = Matrix( - [ - [1, 0, 1, 0, 0, 0, 0, 800], - [1, -1, 0, 1, 0, 0, 0, 200], - [0, 1, 0, 0, -1, 0, 0, 500], - [0, 0, 1, 0, 0, 1, 0, 750], - [0, 0, 0, -1, 0, -1, 1, -600], - [0, 0, 0, 0, 1, 0, -1, -50], - ], - aug_pos=6, - ) - - mat = aug_mat.select_cols(*range(7)) - aug = aug_mat.select_cols(7) - - sol = mat.solve(aug)[0] - y = sol[5] - z = sol[6] - assert sol.subs({y: 50, z: 100}) == Matrix([100, 550, 700, 650, 50, 50, 100]) # type: ignore - assert sol.subs({y: -50}) == Matrix([0, z + 450, 800, z + 650, z - 50, -50, z]) # type: ignore diff --git a/tests/test_verbosity.py b/tests/test_verbosity.py new file mode 100644 index 0000000..0626706 --- /dev/null +++ b/tests/test_verbosity.py @@ -0,0 +1,71 @@ +import pytest +import sympy as sym +from ma1522 import Matrix + +class TestVerbosityCoverage: + """Tests designed to hit printing/verbosity lines in symbolic.py for coverage.""" + + def test_ref_verbosity(self): + A = Matrix.from_str("1 2; 3 4") + # REF verbosity + A.ref(verbosity=1) + A.ref(verbosity=2) + + def test_rref_standard(self): + A = Matrix.from_str("1 2; 3 4") + # RREF does not take verbosity, but evaluate_cases does + A.rref() + + def test_solve_verbosity(self): + A = Matrix.from_str("1 2; 3 4") + b = Matrix.from_str("5; 6") + A.solve(b, verbosity=1) + A.solve(b, verbosity=2) + + def test_inverse_verbosity(self): + A = Matrix.from_str("1 2; 3 4") + A.inverse(verbosity=1) + A.inverse(verbosity=2) + + def test_ref_plu_verbosity(self): + A = Matrix.from_str("1 2; 3 4") + A.ref(verbosity=1) + + def test_det_standard(self): + A = Matrix.from_str("1 2; 3 4") + A.det() + + def test_diagonalize_verbosity(self): + A = Matrix.from_str("1 0; 0 1") + A.diagonalize(verbosity=1) + A.is_diagonalizable(verbosity=1) + + def test_svd_verbosity(self): + A = Matrix.from_str("1 2; 3 4") + A.singular_value_decomposition(verbosity=1) + + def test_gram_schmidt_verbosity(self): + A = Matrix.from_str("1 0; 0 1").T + A.gram_schmidt(verbosity=1) + + def test_orthogonality_verbosity(self): + A = Matrix.from_str("1 0; 0 1").T + A.is_vec_orthogonal(verbosity=1) + A.is_mat_orthogonal(verbosity=1) + + def test_subspace_verbosity(self): + A = Matrix.from_str("1 0; 0 1").T + B = Matrix.from_str("1 0; 0 1").T + A.is_subspace_of(B, verbosity=1) + A.is_subspace_of(B, verbosity=2) + A.is_same_subspace(B, verbosity=1) + + def test_evaluate_cases_verbosity(self): + a = sym.Symbol("a") + A = Matrix([[a, 1], [1, 1]]) + rhs = Matrix([1, 1]) + A.evaluate_cases(rhs=rhs, verbosity=1) + + def test_stochastic_verbosity(self): + A = Matrix([[sym.S.Half, sym.S.Half], [sym.S.Half, sym.S.Half]]) + A.is_stochastic(verbosity=1) diff --git a/tests/tutorials/test_tut01.py b/tests/tutorials/test_tut01.py index f86b2c0..8f84549 100644 --- a/tests/tutorials/test_tut01.py +++ b/tests/tutorials/test_tut01.py @@ -62,10 +62,28 @@ def test_question_3c(self): mat.solve(aug)[0] def test_question_4(self): - # TODO: Implement better evaluate all cases - a, b = sym.symbols("a b") # Define symbolic variables + a, b = sym.symbols("a b") mat = Matrix([[a, 0, b, 2], [a, a, 4, 4], [0, a, 2, b]], aug_pos=2) + # Explicitly test evaluate_cases as shown in notebook + A = mat.select_cols(0, 1, 2) + rhs = mat.select_cols(3) + cases = A.evaluate_cases(rhs=rhs, verbosity=0) + + assert len(cases) == 4 + # Case 1: b != 2, a = 0 + assert cases[0].conditions == {a: 0} + assert cases[0].is_consistent is False + # Case 2: b != 2, a != 0 + assert cases[1].conditions == {} + assert cases[1].is_consistent is True + # Case 3: b = 2, a != 0 + assert cases[2].conditions == {b: 2} + assert cases[2].is_consistent is True + # Case 4: b = 2, a = 0 + assert cases[3].conditions == {a: 0, b: 2} + assert cases[3].is_consistent is True + # Intermediate: REF always produces an upper-triangular form plu = mat.ref() assert plu.U.is_echelon @@ -123,10 +141,19 @@ def test_question_7(self): aug_pos=6, ) + # Explicitly assert the matrix from notebook + assert aug_mat.shape == (6, 8) + # Intermediate (7a): RREF has 5 pivots among 7 coefficient columns # → 2 free variables, so the system is underdetermined rref = aug_mat.rref(pivots=False) assert rref.shape == (6, 8) + + # Assert specific RREF values from notebook + assert rref[0, -1] == 50 + assert rref[1, -1] == 450 + assert rref[5, -1] == 0 + # 5 coefficient columns become identity columns; x6 and x7 are free pivot_cols = [ c @@ -154,3 +181,6 @@ def test_question_7(self): # (7c) Set x6=-50 so that x1=0; x7 remains free assert sol.subs({y: -50}) == Matrix([0, z + 450, 800, z + 650, z - 50, -50, z]) # type: ignore + + # Coverage: Run once with verbosity to hit print lines + mat.solve(aug, verbosity=1) diff --git a/tests/tutorials/test_tut02.py b/tests/tutorials/test_tut02.py index 536ba5c..85d7ac2 100644 --- a/tests/tutorials/test_tut02.py +++ b/tests/tutorials/test_tut02.py @@ -10,6 +10,10 @@ def test_question_2a(self): assert A.shape == (3, 4) X = A.inverse("right", matrices=2) + # Explicitly assert the particular solution matrix from notebook + expected_part = Matrix([[1, -1, 1], [0, 1, -1], [0, 0, 1], [0, 0, 0]]) + assert X.part_sol == expected_part + # Particular solution must satisfy A @ X_part == I_3 assert (A @ X.part_sol).applyfunc(sym.simplify) == Matrix.eye(3) assert X.part_sol.shape == (4, 3) @@ -19,6 +23,16 @@ def test_question_2b(self): assert B.shape == (4, 3) Y = B.inverse("left", matrices=2) + # Explicitly assert the particular solution matrix from notebook + expected_part = Matrix( + [ + [sym.Rational(1, 2), sym.Rational(1, 2), sym.Rational(-1, 2), 0], + [sym.Rational(-1, 2), sym.Rational(1, 2), sym.Rational(1, 2), 0], + [sym.Rational(1, 2), sym.Rational(-1, 2), sym.Rational(1, 2), 0], + ] + ) + assert Y.part_sol == expected_part + # Particular solution must satisfy Y_part @ B == I_3 assert (Y.part_sol @ B).applyfunc(sym.simplify) == Matrix.eye(3) assert Y.part_sol.shape == (3, 4) diff --git a/tests/tutorials/test_tut03.py b/tests/tutorials/test_tut03.py index 5741afa..53e424a 100644 --- a/tests/tutorials/test_tut03.py +++ b/tests/tutorials/test_tut03.py @@ -24,6 +24,9 @@ def test_question_2a(self): plu = A.ref() assert plu.U.is_echelon assert plu.P == Matrix.eye(3) + # Explicitly assert L and U from notebook + assert plu.L == Matrix([[1, 0, 0], [-3, 1, 0], [4, -1, 1]]) + assert plu.U == Matrix([[2, -1, 2], [0, -3, 4], [0, 0, 1]]) assert (plu.L @ plu.U).applyfunc(sym.simplify) == A # Solve the unique system A @ x = b @@ -36,6 +39,9 @@ def test_question_2b(self): plu = A.ref() assert plu.U.is_echelon + # Explicitly assert L and U from notebook + assert plu.L == Matrix([[1, 0, 0], [3, 1, 0], [sym.Rational(-1, 2), -2, 1]]) + assert plu.U == Matrix([[2, -4, 4, -2], [0, 3, -5, 3], [0, 0, 0, 5]]) assert (plu.P.T @ plu.L @ plu.U).applyfunc(sym.simplify) == A # Solve underdetermined system A @ x = b (multiple solutions) diff --git a/tests/tutorials/test_tut04.py b/tests/tutorials/test_tut04.py index 9203db6..925e6a5 100644 --- a/tests/tutorials/test_tut04.py +++ b/tests/tutorials/test_tut04.py @@ -6,10 +6,22 @@ class TestTutorial04: def test_question_2a(self): U = Matrix.from_str("2 1 0 3; 3 -1 5 2; -1 0 2 1").T - actual_contraints = U.column_constraints()[3, 3] + actual_constraints = U.column_constraints() vec = Matrix.create_unk_matrix(4, 1, "x") + # Explicitly assert the constraint from notebook expected = vec[0, 0] + 7 * vec[1, 0] + 2 * vec[2, 0] - 3 * vec[3, 0] - assert actual_contraints == expected + assert actual_constraints[3, 3] == expected + + # Check vectors from (a) + rhs = Matrix.from_str("2 3 -7 3; 0 0 0 0; 1 1 1 1; -4 6 -13 4").T + # (i) 2 + 7(3) + 2(-7) - 3(3) = 2 + 21 - 14 - 9 = 0 -> Consistent + assert rhs.select_cols(0).is_subspace_of(U) + # (ii) 0 -> Consistent + assert rhs.select_cols(1).is_subspace_of(U) + # (iii) 1 + 7 + 2 - 3 = 7 != 0 -> Inconsistent + assert not rhs.select_cols(2).is_subspace_of(U) + # (iv) -4 + 7(6) + 2(-13) - 3(4) = -4 + 42 - 26 - 12 = 0 -> Consistent + assert rhs.select_cols(3).is_subspace_of(U) def test_question_2b(self): U = Matrix.from_str("2 1 0 3; 3 -1 5 2; -1 0 2 1").T diff --git a/tests/tutorials/test_tut06.py b/tests/tutorials/test_tut06.py index 677b6eb..b876035 100644 --- a/tests/tutorials/test_tut06.py +++ b/tests/tutorials/test_tut06.py @@ -29,6 +29,9 @@ def test_question_1c(self): S = Matrix.from_str("1 2 -1; 0 2 1; 0 -1 3").T T = Matrix.from_str("1 5 4; -1 3 7; 2 2 4").T P_T_to_S = T.transition_matrix(S) + # Explicitly assert transition matrix from notebook + expected = Matrix([[1, -1, 2], [2, 3, 0], [1, 1, 2]]) + assert P_T_to_S == expected assert (S @ P_T_to_S).applyfunc(sym.simplify) == T def test_question_1d(self): @@ -36,6 +39,15 @@ def test_question_1d(self): T = Matrix.from_str("1 5 4; -1 3 7; 2 2 4").T P_T_to_S = T.transition_matrix(S) P_S_to_T = S.transition_matrix(T) + # Explicitly assert transition matrix from notebook + expected_inv = Matrix( + [ + [sym.Rational(3, 4), sym.Rational(1, 2), sym.Rational(-3, 4)], + [sym.Rational(-1, 2), 0, sym.Rational(1, 2)], + [sym.Rational(-1, 8), sym.Rational(-1, 4), sym.Rational(5, 8)], + ] + ) + assert P_S_to_T == expected_inv assert P_S_to_T.applyfunc(sym.simplify) == P_T_to_S.inverse() def test_question_1e(self): From 193beb1740c34e7c72ba89b73e593cc216990b6e Mon Sep 17 00:00:00 2001 From: ys_teng <58208381+YeeShin504@users.noreply.github.com> Date: Sun, 19 Apr 2026 22:04:01 +0800 Subject: [PATCH 3/7] Improve test coverage --- src/ma1522/__init__.py | 3 ++- src/ma1522/symbolic.py | 11 ++++++++--- tests/test_verbosity.py | 4 ++++ tests/test_warnings.py | 0 4 files changed, 14 insertions(+), 4 deletions(-) create mode 100644 tests/test_warnings.py diff --git a/src/ma1522/__init__.py b/src/ma1522/__init__.py index dd5796c..a543b77 100644 --- a/src/ma1522/__init__.py +++ b/src/ma1522/__init__.py @@ -14,7 +14,7 @@ NumSVD, ) -from .symbolic import Matrix +from .symbolic import Matrix, sympy_commands __all__ = [ "display", @@ -30,4 +30,5 @@ "SVD", "NumSVD", "Matrix", + "sympy_commands", ] diff --git a/src/ma1522/symbolic.py b/src/ma1522/symbolic.py index d6dcfe4..288c9c6 100644 --- a/src/ma1522/symbolic.py +++ b/src/ma1522/symbolic.py @@ -119,7 +119,7 @@ def sympy_commands(): >>> A.reduce_row(idx_1: int, scalar: float, idx_2: int, verbosity=0) # Row reduction >>> A.get_pivot_row(col_idx: int, row_start_idx: int, follow_GE=False) # Find pivot row >>> A.ref(verbosity=2, max_tries=2, follow_GE=False, matrices=2) # Row echelon form (REF) - >>> A.evaluate_cases(rhs: Matrix) # Display solution cases for symbolic systems + >>> A.evaluate_cases(rhs: Matrix, verbosity=0) # Display solution cases for symbolic systems >>> A.column_constraints(use_id=False, use_ref=False) # RREF of [A | b], constraints for b >>> A.extend_basis(span_subspace=A.elem()) # Augment basis to span subspace >>> A.transition_matrix(to: Matrix) # Transition matrix between bases @@ -907,8 +907,11 @@ def identify( Examples: >>> import math + >>> import pytest >>> mat = Matrix([[math.sqrt(4), math.e], [1/math.sqrt(2), 0.0]]) - >>> mat.identify() == Matrix([[2, sym.E], [sym.sqrt(2) / 2, 0]]) + >>> with pytest.warns(RuntimeWarning, match="Non-zero Identification Error"): + ... result = mat.identify() + >>> result == Matrix([[2, sym.E], [sym.sqrt(2) / 2, 0]]) True See Also: @@ -2512,7 +2515,9 @@ def adjoint(self) -> Matrix: Examples: >>> mat = Matrix([[1, 2], [3, 4]]) - >>> mat.adjoint() + >>> import pytest + >>> with pytest.warns(DeprecationWarning, match="The classical adjoint"): + ... mat.adjoint() Matrix([ [ 4, -2], [-3, 1]]) diff --git a/tests/test_verbosity.py b/tests/test_verbosity.py index 0626706..f5ed6cb 100644 --- a/tests/test_verbosity.py +++ b/tests/test_verbosity.py @@ -4,6 +4,10 @@ class TestVerbosityCoverage: """Tests designed to hit printing/verbosity lines in symbolic.py for coverage.""" + + def test_command(self): + from ma1522.symbolic import sympy_commands + sympy_commands() def test_ref_verbosity(self): A = Matrix.from_str("1 2; 3 4") diff --git a/tests/test_warnings.py b/tests/test_warnings.py new file mode 100644 index 0000000..e69de29 From 9495c19fa006efe029a2fbaa7f51a5afbe319e47 Mon Sep 17 00:00:00 2001 From: ys_teng <58208381+YeeShin504@users.noreply.github.com> Date: Sun, 19 Apr 2026 23:12:26 +0800 Subject: [PATCH 4/7] Move sympy_commands to utils --- src/ma1522/__init__.py | 4 +- src/ma1522/custom_types.py | 12 +++-- src/ma1522/symbolic.py | 106 ++----------------------------------- src/ma1522/utils.py | 102 +++++++++++++++++++++++++++++++++++ 4 files changed, 117 insertions(+), 107 deletions(-) diff --git a/src/ma1522/__init__.py b/src/ma1522/__init__.py index a543b77..bb6b2d9 100644 --- a/src/ma1522/__init__.py +++ b/src/ma1522/__init__.py @@ -1,4 +1,4 @@ -from .utils import display +from .utils import display, sympy_commands from .custom_types import ( Shape, @@ -14,7 +14,7 @@ NumSVD, ) -from .symbolic import Matrix, sympy_commands +from .symbolic import Matrix __all__ = [ "display", diff --git a/src/ma1522/custom_types.py b/src/ma1522/custom_types.py index da43162..78c7899 100644 --- a/src/ma1522/custom_types.py +++ b/src/ma1522/custom_types.py @@ -5,10 +5,10 @@ from typing import TYPE_CHECKING, NamedTuple import sympy as sym +from sympy.matrices.exceptions import NonInvertibleMatrixError from sympy.printing.latex import LatexPrinter from .utils import _gen_latex_repr, _textify -# from ma1522 import utils if TYPE_CHECKING: from typing import Literal @@ -407,10 +407,14 @@ class PDP(Printable): def _latex(self, printer=None) -> str: try: P_inv = self.P.inv() # inv exists and is unique + # Normalize inverse to Matrix subclass so custom latex uses array formatting. + P_inv_fmt = self.P.__class__(P_inv, aug_pos=set()) return ( - self.P._latex(printer) + self.D._latex(printer) + P_inv._latex(printer) - ) # type: ignore - except Exception: + self.P._latex(printer) + + self.D._latex(printer) + + P_inv_fmt._latex(printer) + ) + except NonInvertibleMatrixError: return ( self.P._latex(printer) + self.D._latex(printer) diff --git a/src/ma1522/symbolic.py b/src/ma1522/symbolic.py index 288c9c6..e1f65ab 100644 --- a/src/ma1522/symbolic.py +++ b/src/ma1522/symbolic.py @@ -43,106 +43,6 @@ ######## -def sympy_commands(): - commands = """ - # Note: zero-indexing, see https://docs.sympy.org/latest/modules/matrices/matrices.html - import sympy as sym - - # Variables - >>> a, b = sym.symbols("a b") # symbols - >>> I_3 = eye(3) # identity matrix - >>> zeros = sym.zeros(2, cols=2) # zero matrix - >>> A = Matrix([[...]] ) # user-defined matrix - >>> B = Matrix.vstack(A, I_3, ...) # repeated application of col_join - >>> C = sym.nsimplify(A, tolerance=0.001, rational=True) # convert to fraction - >>> D = C.evalf() # convert to decimal - - # Matrix Operations - >>> A @ B # matrix multiplication - >>> A + B # element wise addition - >>> A - B # element wise subtraction - >>> A.col_del(col) # delete column - >>> A.col_insert(pos, B) # insert matrix B at pos in A, column wise - >>> A.col_join(B) # insert matrix B below matrix A - >>> A.dot(B) # dot product of A and B - >>> A.exp() # exponential - >>> A.flat() # flatten to row vector - >>> A.pow(exp) # power - >>> A.reshape(rows, cols) # reshape - >>> A.rot90(k=1) # rotate 90deg, k times - >>> A.row_del(row) # delete row - >>> A.row_insert(pos, B) # insert matrix B at pos in A, row wise - >>> A.row_join(B) # insert matrix B on RHS of matrix A - >>> A.vec() # stack to column vector - >>> A.xreplace(dict) # replace sym (key) with value - - # Symbolic Methods - >>> A.T # transpose - >>> A.inv() # inverse - >>> A.adj() # adjoint (as per MA1522 definition) - >>> A.cofactor(i, j) # (i, j) cofactor - >>> A.cofactor_matrix() # cofactor matrix - >>> A.columnspace(simplify=False) # list of vectors that span column space of A - >>> A.conjugate() # conjugate - >>> A.copy() # copy matrix - >>> A.det(method='bareiss', iszerofunc=None) # determinant, use domain-ge/laplace as method - >>> A.diag() # diagonal - >>> A.echelon_form() # REF - >>> A.eigenvals(rational=True) # eigenvalues - >>> A.eigenvects() # eigenvectors - >>> A.elementary_row_op(op='n->kn', row=None, k=None, row1=None, row2=None) # ERO, "n->kn"/"n<->m"/"n->n+km" - >>> A.is_nilpotent() # check if nilpotent - >>> A.is_symmetric() # check if symmetric - >>> A.minor(i, j) # (i, j) minor (WRONG DEFINITION, uses determinant) - >>> A.nullspace() # nullspace - >>> A.rank(iszerofunc=, simplify=False) # rank - >>> A.rowspace(simplify=False) # list of vectors that span row space of A - >>> A.rref() # returns [rref, list_of_pivot_cols] - >>> A.LUdecomposition(iszerofunc=, simpfunc=None, rankcheck=False) # LU decomposition - >>> A.lower_triangular_solve(rhs) # solve Ax = rhs, A lower triangular - >>> A.upper_triangular_solve(rhs) # solve Ax = rhs, A upper triangular - - # Custom Commands (verbosity >= 1 returns ERO (idx + 1), >= 2 returns matrix at each step) - >>> is_zero(expr, symbolic: bool = True) - >>> Matrix.from_latex(expr, row_join=True, norm=False, aug_pos=None) # Parse LaTeX matrix/vector to Matrix - >>> Matrix.from_str(matrix_str, row_sep=';', col_sep=' ', aug_pos=None, is_real=True) # Parse string to Matrix - >>> Matrix.from_list(vectors, row_join=True, aug_pos=None) # Create Matrix from list of vectors - >>> Matrix.create_unk_matrix(num_rows: int, num_cols: int, symbol: str, is_real: bool) # Matrix with symbolic entries - >>> Matrix.create_rand_matrix(num_rows: int, num_cols: int) # Matrix with random entries - >>> A.simplify(rational=True, tolerance=1e-4, simplify=True, expand=True, collect_sym=None) # Simplify entries - >>> A.identify(tolerance: float) # Identify symbolic/numeric constants - >>> A.elem() # Identity matrix with same number of rows as A - >>> A.select_rows(*idx) # New matrix with selected rows - >>> A.select_cols(*idx) # New matrix with selected columns - >>> A.scale_row(idx: int, scalar: float, verbosity=0) # Scale row - >>> A.swap_row(idx_1: int, idx_2: int, verbosity=0) # Swap rows - >>> A.reduce_row(idx_1: int, scalar: float, idx_2: int, verbosity=0) # Row reduction - >>> A.get_pivot_row(col_idx: int, row_start_idx: int, follow_GE=False) # Find pivot row - >>> A.ref(verbosity=2, max_tries=2, follow_GE=False, matrices=2) # Row echelon form (REF) - >>> A.evaluate_cases(rhs: Matrix, verbosity=0) # Display solution cases for symbolic systems - >>> A.column_constraints(use_id=False, use_ref=False) # RREF of [A | b], constraints for b - >>> A.extend_basis(span_subspace=A.elem()) # Augment basis to span subspace - >>> A.transition_matrix(to: Matrix) # Transition matrix between bases - >>> A.intersect_subspace(other: Matrix, verbosity=1) # Basis for intersection of subspaces - >>> A.is_same_subspace(other: Matrix, verbosity=1) # Check if subspaces are equal - >>> A.inverse(option: str, verbosity=0) # Inverse (left/right/both) - >>> A.orthogonal_complement(verbosity=0) # Null(A^T) - >>> A.is_vec_orthogonal(verbosity=1) # Check if columns are orthogonal - >>> A.normalized(factor=False) # Normalize columns - >>> A.scalar_factor(column=True) # Factor out common divisors - >>> A.gram_schmidt(factor=True, verbosity=1) # Orthonormalize columns - >>> A.QRdecomposition(full=False) # QR decomposition - >>> A.solve_least_squares(rhs: Matrix, verbosity=1) # Least squares solution - >>> A.cpoly(force_factor=True) # Characteristic polynomial - >>> A.is_diagonalizable(reals_only=True, verbosity=1) # Diagonalizability - >>> A.diagonalize(reals_only=True, verbosity=0) # Diagonalization - >>> A.is_orthogonally_diagonalizable # Check if symmetric - >>> A.orthogonally_diagonalize(reals_only=True, factor=True, verbosity=1) # Orthogonal diagonalization - >>> A.equilibrium_vectors() # Probability vectors Ax = x - >>> A.fast_svd(option='np', identify=True, tolerance=None) # Fast SVD (numeric) - >>> A.singular_value_decomposition(verbosity=0) # Full SVD (A = U @ S @ V.T) - """ - print(commands) sym.init_printing(use_unicode=True) @@ -255,7 +155,11 @@ def __eq__(self, other) -> bool: # Override def _latex(self, printer=None) -> str: - raw = printer._print(sym.Matrix(self)) # type: ignore + if printer is None: + raw = sym.latex(sym.Matrix(self)) + else: + raw = printer._print(sym.Matrix(self)) # type: ignore + if not hasattr(self, "_aug_pos"): # Matrices produced by parent methods may not have _aug_pos return raw diff --git a/src/ma1522/utils.py b/src/ma1522/utils.py index 5461c44..831c3c3 100644 --- a/src/ma1522/utils.py +++ b/src/ma1522/utils.py @@ -12,6 +12,108 @@ from ma1522.custom_types import Printable +def sympy_commands(): + commands = """ + # Note: zero-indexing, see https://docs.sympy.org/latest/modules/matrices/matrices.html + import sympy as sym + + # Variables + >>> a, b = sym.symbols("a b") # symbols + >>> I_3 = eye(3) # identity matrix + >>> zeros = sym.zeros(2, cols=2) # zero matrix + >>> A = Matrix([[...]] ) # user-defined matrix + >>> B = Matrix.vstack(A, I_3, ...) # repeated application of col_join + >>> C = sym.nsimplify(A, tolerance=0.001, rational=True) # convert to fraction + >>> D = C.evalf() # convert to decimal + + # Matrix Operations + >>> A @ B # matrix multiplication + >>> A + B # element wise addition + >>> A - B # element wise subtraction + >>> A.col_del(col) # delete column + >>> A.col_insert(pos, B) # insert matrix B at pos in A, column wise + >>> A.col_join(B) # insert matrix B below matrix A + >>> A.dot(B) # dot product of A and B + >>> A.exp() # exponential + >>> A.flat() # flatten to row vector + >>> A.pow(exp) # power + >>> A.reshape(rows, cols) # reshape + >>> A.rot90(k=1) # rotate 90deg, k times + >>> A.row_del(row) # delete row + >>> A.row_insert(pos, B) # insert matrix B at pos in A, row wise + >>> A.row_join(B) # insert matrix B on RHS of matrix A + >>> A.vec() # stack to column vector + >>> A.xreplace(dict) # replace sym (key) with value + + # Symbolic Methods + >>> A.T # transpose + >>> A.inv() # inverse + >>> A.adj() # adjoint (as per MA1522 definition) + >>> A.cofactor(i, j) # (i, j) cofactor + >>> A.cofactor_matrix() # cofactor matrix + >>> A.columnspace(simplify=False) # list of vectors that span column space of A + >>> A.conjugate() # conjugate + >>> A.copy() # copy matrix + >>> A.det(method='bareiss', iszerofunc=None) # determinant, use domain-ge/laplace as method + >>> A.diag() # diagonal + >>> A.echelon_form() # REF + >>> A.eigenvals(rational=True) # eigenvalues + >>> A.eigenvects() # eigenvectors + >>> A.elementary_row_op(op='n->kn', row=None, k=None, row1=None, row2=None) # ERO, "n->kn"/"n<->m"/"n->n+km" + >>> A.is_nilpotent() # check if nilpotent + >>> A.is_symmetric() # check if symmetric + >>> A.minor(i, j) # (i, j) minor (WRONG DEFINITION, uses determinant) + >>> A.nullspace() # nullspace + >>> A.rank(iszerofunc=, simplify=False) # rank + >>> A.rowspace(simplify=False) # list of vectors that span row space of A + >>> A.rref() # returns [rref, list_of_pivot_cols] + >>> A.LUdecomposition(iszerofunc=, simpfunc=None, rankcheck=False) # LU decomposition + >>> A.lower_triangular_solve(rhs) # solve Ax = rhs, A lower triangular + >>> A.upper_triangular_solve(rhs) # solve Ax = rhs, A upper triangular + + # Custom Commands (verbosity >= 1 returns ERO (idx + 1), >= 2 returns matrix at each step) + >>> is_zero(expr, symbolic: bool = True) + >>> Matrix.from_latex(expr, row_join=True, norm=False, aug_pos=None) # Parse LaTeX matrix/vector to Matrix + >>> Matrix.from_str(matrix_str, row_sep=';', col_sep=' ', aug_pos=None, is_real=True) # Parse string to Matrix + >>> Matrix.from_list(vectors, row_join=True, aug_pos=None) # Create Matrix from list of vectors + >>> Matrix.create_unk_matrix(num_rows: int, num_cols: int, symbol: str, is_real: bool) # Matrix with symbolic entries + >>> Matrix.create_rand_matrix(num_rows: int, num_cols: int) # Matrix with random entries + >>> A.simplify(rational=True, tolerance=1e-4, simplify=True, expand=True, collect_sym=None) # Simplify entries + >>> A.identify(tolerance: float) # Identify symbolic/numeric constants + >>> A.elem() # Identity matrix with same number of rows as A + >>> A.select_rows(*idx) # New matrix with selected rows + >>> A.select_cols(*idx) # New matrix with selected columns + >>> A.scale_row(idx: int, scalar: float, verbosity=0) # Scale row + >>> A.swap_row(idx_1: int, idx_2: int, verbosity=0) # Swap rows + >>> A.reduce_row(idx_1: int, scalar: float, idx_2: int, verbosity=0) # Row reduction + >>> A.get_pivot_row(col_idx: int, row_start_idx: int, follow_GE=False) # Find pivot row + >>> A.ref(verbosity=2, max_tries=2, follow_GE=False, matrices=2) # Row echelon form (REF) + >>> A.evaluate_cases(rhs: Matrix, verbosity=0) # Display solution cases for symbolic systems + >>> A.column_constraints(use_id=False, use_ref=False) # RREF of [A | b], constraints for b + >>> A.extend_basis(span_subspace=A.elem()) # Augment basis to span subspace + >>> A.transition_matrix(to: Matrix) # Transition matrix between bases + >>> A.intersect_subspace(other: Matrix, verbosity=1) # Basis for intersection of subspaces + >>> A.is_same_subspace(other: Matrix, verbosity=1) # Check if subspaces are equal + >>> A.inverse(option: str, verbosity=0) # Inverse (left/right/both) + >>> A.orthogonal_complement(verbosity=0) # Null(A^T) + >>> A.is_vec_orthogonal(verbosity=1) # Check if columns are orthogonal + >>> A.normalized(factor=False) # Normalize columns + >>> A.scalar_factor(column=True) # Factor out common divisors + >>> A.gram_schmidt(factor=True, verbosity=1) # Orthonormalize columns + >>> A.QRdecomposition(full=False) # QR decomposition + >>> A.solve_least_squares(rhs: Matrix, verbosity=1) # Least squares solution + >>> A.cpoly(force_factor=True) # Characteristic polynomial + >>> A.is_diagonalizable(reals_only=True, verbosity=1) # Diagonalizability + >>> A.diagonalize(reals_only=True, verbosity=0) # Diagonalization + >>> A.is_orthogonally_diagonalizable # Check if symmetric + >>> A.orthogonally_diagonalize(reals_only=True, factor=True, verbosity=1) # Orthogonal diagonalization + >>> A.equilibrium_vectors() # Probability vectors Ax = x + >>> A.fast_svd(option='np', identify=True, tolerance=None) # Fast SVD (numeric) + >>> A.singular_value_decomposition(verbosity=0) # Full SVD (A = U @ S @ V.T) + """ + print(commands) + + def _powerset(args: Iterable) -> list: """Generates the powerset of an iterable. From f887b2aee7d8c3100633e0c3c25494c7c4d3d51e Mon Sep 17 00:00:00 2001 From: ys_teng <58208381+YeeShin504@users.noreply.github.com> Date: Sun, 19 Apr 2026 23:34:10 +0800 Subject: [PATCH 5/7] Improve test coverage for utils and custom types --- tests/test_custom_types.py | 142 ++++++++++++++++++++++--------------- tests/test_utils.py | 76 ++++++++++++++++++++ tests/test_verbosity.py | 3 +- tests/test_warnings.py | 0 4 files changed, 163 insertions(+), 58 deletions(-) delete mode 100644 tests/test_warnings.py diff --git a/tests/test_custom_types.py b/tests/test_custom_types.py index 1dc9615..5016376 100644 --- a/tests/test_custom_types.py +++ b/tests/test_custom_types.py @@ -11,6 +11,7 @@ PDP, SVD, NumSVD, + RREFCase, ) @@ -23,72 +24,101 @@ def test_shape_enum(self): assert Shape.STRICT_LOWER.value == "STRICT_LOWER" assert Shape.SYMMETRIC.value == "SYMMETRIC" - def test_partgen(self): - part_sol = Matrix([[1, 0], [0, 1]]) - gen_sol = Matrix([[0, 1], [1, 0]]) - pg = PartGen(part_sol, gen_sol) - assert pg.part_sol == part_sol - assert pg.gen_sol == gen_sol + def test_printable_base(self): + mat = Matrix([[1, 2], [3, 4]]) + pivots = (0, 1) + rref = RREF(mat, pivots) + + # Iteration + fields = list(rref) + assert fields[0] == mat + assert fields[1] == pivots + + # Get/Set + assert rref[0] == mat + new_mat = Matrix.eye(2) + rref[0] = new_mat + assert rref.rref == new_mat + + # Eval / Evalf + assert rref.eval() == new_mat + assert isinstance(rref.evalf(), Matrix) + + def test_partgen_methods(self): + part = Matrix([[1], [0]]) + gen = Matrix([[0], [1]]) + pg = PartGen(part, gen) + assert pg.eval() == Matrix([[1], [1]]) + assert "\\left(" in pg._latex() - def test_scalarfactor(self): - diag = Matrix([[1, 0], [0, 1]]) + def test_scalarfactor_methods(self): + diag = Matrix([[2, 0], [0, 2]]) full = Matrix([[1, 2], [3, 4]]) - sf = ScalarFactor(diag, full, "FD") - assert sf.diag == diag - assert sf.full == full - assert sf.order == "FD" + sf = ScalarFactor(diag, full, "DF") + assert sf.eval() == Matrix([[2, 4], [6, 8]]) + assert sf._latex().startswith(diag._latex()) + + sf_fd = ScalarFactor(diag, full, "FD") + assert sf_fd.eval() == Matrix([[2, 4], [6, 8]]) + assert sf_fd._latex().endswith(diag._latex()) - def test_plu(self): - P = Matrix([[1, 0], [0, 1]]) - L = Matrix([[1, 0], [0, 1]]) - U = Matrix([[1, 0], [0, 1]]) + def test_plu_methods(self): + P, L, U = Matrix.eye(2), Matrix([[1, 0], [2, 1]]), Matrix([[3, 4], [0, 5]]) plu = PLU(P, L, U) - assert plu.P == P - assert plu.L == L - assert plu.U == U - - def test_rref(self): - rref_mat = Matrix([[1, 0], [0, 1]]) - pivots = (0, 1) - rref = RREF(rref_mat, pivots) - assert rref.rref == rref_mat - assert rref.pivots == pivots + assert plu.eval() == L @ U + assert plu._latex().count("array") == 6 - def test_vecdecomp(self): - proj = Matrix([[1], [0]]) - norm = Matrix([[0], [1]]) + def test_vecdecomp_methods(self): + proj, norm = Matrix([[1], [0]]), Matrix([[0], [1]]) vd = VecDecomp(proj, norm) - assert vd.proj == proj - assert vd.norm == norm + assert vd.eval() == Matrix([[1], [1]]) - def test_qr(self): - Q = Matrix([[1, 0], [0, 1]]) - R = Matrix([[1, 0], [0, 1]]) + def test_qr_methods(self): + Q, R = Matrix.eye(2), Matrix([[1, 2], [0, 3]]) qr = QR(Q, R) - assert qr.Q == Q - assert qr.R == R + assert qr.eval() == R + assert qr._latex().count("array") == 4 - def test_pdp(self): - P = Matrix([[1, 0], [0, 1]]) - D = Matrix([[1, 0], [0, 1]]) + def test_pdp_methods(self): + # Regular case + P = Matrix([[1, 1], [0, 1]]) + D = Matrix([[2, 0], [0, 3]]) pdp = PDP(P, D) - assert pdp.P == P - assert pdp.D == D + assert pdp.eval() == P @ D @ P.inv() + assert pdp._latex().count("array") == 6 + + # Singular case (exception fallback) + P_sing = Matrix([[1, 1], [1, 1]]) + pdp_sing = PDP(P_sing, D) + assert "P inverse does not exist" in pdp_sing._latex() - def test_svd(self): - U = Matrix([[1, 0], [0, 1]]) - S = Matrix([[1, 0], [0, 1]]) - V = Matrix([[1, 0], [0, 1]]) + def test_svd_methods(self): + U, S, V = Matrix.eye(2), Matrix([[2, 0], [0, 1]]), Matrix.eye(2) svd = SVD(U, S, V) - assert svd.U == U - assert svd.S == S - assert svd.V == V + assert svd.eval() == S + assert svd._latex().count("array") == 6 + + def test_numsvd_methods(self): + U = np.eye(2) + S = np.diag([2, 1]) + V = np.eye(2) + nsvd = NumSVD(U, S, V) + assert np.allclose(nsvd.eval(), S) + assert "NumSVD" in repr(nsvd) + + def test_rref_case_methods(self): + rc = RREFCase( + conditions={"a": 0}, + excluded=[{"b": 0}], + rref=Matrix.eye(2), + pivots=(0, 1), + free_params=0, + is_consistent=True + ) + assert rc.eval() == Matrix.eye(2) + assert "RREFCase" in rc._latex() - def test_numsvd(self): - U = np.array([[1, 0], [0, 1]]) - S = np.array([[1, 0], [0, 1]]) - V = np.array([[1, 0], [0, 1]]) - numsvd = NumSVD(U, S, V) - assert numsvd.U.all() == U.all() - assert numsvd.S.all() == S.all() - assert numsvd.V.all() == V.all() + def test_repr_latex(self): + rref = RREF(Matrix.eye(2), (0, 1)) + assert rref._repr_latex_().startswith("$") + assert rref._repr_latex_().endswith("$") diff --git a/tests/test_utils.py b/tests/test_utils.py index 7a8be36..717c0b2 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -19,3 +19,79 @@ def test_is_zero(self): n, d = sym.fraction(1) assert _is_zero(d) is False + + def test_textify(self): + from ma1522.utils import _textify + assert _textify("hello_world") == r"\text{hello\_world}" + assert _textify("Simple") == r"\text{Simple}" + + def test_latex_wrapping(self): + from ma1522.utils import _wrap_latex, _unwrap_latex + assert _wrap_latex("x") == "$x$" + assert _unwrap_latex("$x$") == "x" + assert _unwrap_latex("$$x$$") == "x" + assert _unwrap_latex(None) == "" + + def test_ipython_detection(self): + from ma1522.utils import _is_IPython + from unittest.mock import patch, MagicMock + + # Test failure (default/standard python) + with patch("IPython.core.getipython.get_ipython", side_effect=NameError): + assert _is_IPython() is False + + # Test success (IPython shell) + with patch("IPython.core.getipython.get_ipython") as mock_get: + mock_shell = MagicMock() + mock_shell.__class__.__name__ = "ZMQInteractiveShell" + mock_get.return_value = mock_shell + assert _is_IPython() is True + + # Test "Other type" (False) + with patch("IPython.core.getipython.get_ipython") as mock_get: + mock_shell = MagicMock() + mock_shell.__class__.__name__ = "SomeOtherShell" + mock_get.return_value = mock_shell + assert _is_IPython() is False + + def test_display_ipython(self): + from ma1522.utils import display + from unittest.mock import patch, MagicMock + + # Mock IPython success + with patch("IPython.core.getipython.get_ipython") as mock_get: + mock_shell = MagicMock() + mock_shell.__class__.__name__ = "ZMQInteractiveShell" + mock_get.return_value = mock_shell + + with patch("IPython.display.display") as mock_disp: + display({"a": 1}, 5, opt="dict") + assert mock_disp.called + + display("math expression", opt="math") + assert mock_disp.called + + display("standard") + assert mock_disp.called + + def test_gen_latex_repr_dict_special(self): + from ma1522.utils import _gen_latex_repr_dict + + class Special: + def _repr_latex_(self): + return "$special$" + def __repr__(self): + return "$special$" + + obj = {"key": Special()} + res = _gen_latex_repr_dict(obj) + assert "special" in res + + def test_standardise_symbol(self): + from ma1522.utils import _standardise_symbol + import sympy as sym + symbols = {sym.Symbol("x1"), sym.Symbol("y2")} + std = _standardise_symbol(symbols) + std_names = [str(s) for s in std] + assert "x_1" in std_names + assert "y_2" in std_names diff --git a/tests/test_verbosity.py b/tests/test_verbosity.py index f5ed6cb..e6798eb 100644 --- a/tests/test_verbosity.py +++ b/tests/test_verbosity.py @@ -1,12 +1,11 @@ import pytest import sympy as sym -from ma1522 import Matrix +from ma1522 import Matrix, sympy_commands class TestVerbosityCoverage: """Tests designed to hit printing/verbosity lines in symbolic.py for coverage.""" def test_command(self): - from ma1522.symbolic import sympy_commands sympy_commands() def test_ref_verbosity(self): diff --git a/tests/test_warnings.py b/tests/test_warnings.py deleted file mode 100644 index e69de29..0000000 From b6970fd04146205406a7f823c543536e77e5ef73 Mon Sep 17 00:00:00 2001 From: ys_teng <58208381+YeeShin504@users.noreply.github.com> Date: Mon, 20 Apr 2026 00:26:53 +0800 Subject: [PATCH 6/7] Fix formatting issues --- mkdocs.yml | 9 +++++---- pyproject.toml | 9 +++++++++ src/ma1522/symbolic.py | 25 ++++++++++--------------- src/ma1522/utils.py | 17 ----------------- 4 files changed, 24 insertions(+), 36 deletions(-) diff --git a/mkdocs.yml b/mkdocs.yml index 3cd09a8..864b5e5 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -4,7 +4,7 @@ site_author: Yee Shin Teng site_url: https://yeeshin504.github.io/linear-algebra/ repo_url: https://github.com/YeeShin504/linear-algebra repo_name: YeeShin504/linear-algebra -watch: [mkdocs.yml, README.md] +watch: [ mkdocs.yml, README.md ] copyright: Copyright © 2025 Yee Shin Teng nav: @@ -77,7 +77,7 @@ plugins: - mkdocs-jupyter: ignore_h1_titles: True # live notebooks to be processed using jupyterlite plugin - ignore: ["live/*.ipynb"] + ignore: [ "live/*.ipynb" ] execute: true - jupyterlite: notebook_patterns: @@ -90,7 +90,7 @@ plugins: default_handler: python_xref handlers: python_xref: - paths: [src] + paths: [ src ] inventories: - https://docs.python.org/3/objects.inv - https://docs.sympy.org/latest/objects.inv @@ -142,8 +142,9 @@ markdown_extensions: custom_fences: - name: mermaid class: mermaid - format: !!python/name:pymdownx.superfences.fence_code_format + format: + !!python/name:pymdownx.superfences.fence_code_format - toc: permalink: true diff --git a/pyproject.toml b/pyproject.toml index d25079a..05af81d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,6 +36,7 @@ dev = [ "build>=1.2.2.post1", "twine>=6.2.0", "pytest-cov>=7.0.0", + "ma1522-linear-algebra", ] docs = [ @@ -52,6 +53,14 @@ docs = [ [tool.uv] default-groups = ["dev", "docs"] +[tool.uv.workspace] +members = [ + ".", +] + +[tool.uv.sources] +ma1522-linear-algebra = { workspace = true, editable = true } + [tool.setuptools] license-files = [] diff --git a/src/ma1522/symbolic.py b/src/ma1522/symbolic.py index e1f65ab..4692ba7 100644 --- a/src/ma1522/symbolic.py +++ b/src/ma1522/symbolic.py @@ -38,13 +38,6 @@ from sympy.core.symbol import Symbol from sympy.core.mul import Mul -######## -# MISC # -######## - - - - sym.init_printing(use_unicode=True) np.set_printoptions(formatter={"float": lambda x: f"{x:10.7g}"}) @@ -2094,18 +2087,19 @@ def rref_cases( and returns one [`RREFCase`][(p).RREFCase] per distinct branch. Algorithm: + 1. Work column-by-column to find the leftmost pivot in each active row. 2. If the candidate pivot entry has free symbols that can equal zero, create two branches: - - **Zero branch**: substitute the zero-making values and retry the - same column (a different row may now become the pivot). - - **Non-zero branch**: treat the entry as a non-zero (possibly - symbolic) scalar, normalise the pivot row to 1, and eliminate - the pivot column in all other rows (full RREF). + - **Zero branch**: substitute the zero-making values and retry the + same column (a different row may now become the pivot). + - **Non-zero branch**: treat the entry as a non-zero (possibly + symbolic) scalar, normalise the pivot row to 1, and eliminate + the pivot column in all other rows (full RREF). 3. Recursion terminates when all columns (or rows) have been processed. Args: - rhs ([`Matrix`][...], optional): Right-hand side of the system ``Ax = rhs``, + rhs (Matrix, optional): Right-hand side of the system ``Ax = rhs``, appended as an augmented column block. When provided, each [`RREFCase`][(p).RREFCase] reports consistency in `RREFCase.is_consistent`. @@ -2114,10 +2108,11 @@ def rref_cases( Returns: (list[RREFCase]): One entry per distinct case. Each [`RREFCase`][(p).RREFCase] contains: + - ``conditions`` — the symbol substitutions that define the case. - ``excluded`` — zero-conditions from *other* cases (i.e. what - is **not** assumed here), excluding redundant alternatives for - symbols already fixed by ``conditions``. + is **not** assumed here), excluding redundant alternatives for + symbols already fixed by ``conditions``. - ``rref`` — the RREF matrix (augmented if *rhs* was given). - ``pivots`` — pivot column indices. - ``free_params`` — number of free parameters. diff --git a/src/ma1522/utils.py b/src/ma1522/utils.py index 831c3c3..2c6269a 100644 --- a/src/ma1522/utils.py +++ b/src/ma1522/utils.py @@ -167,23 +167,6 @@ def _gen_latex_repr(obj: Printable, printer: LatexPrinter | None = None) -> str: A LaTeX string representation of the object. """ - # def text(txt: str) -> str: - # return "\\text{" + txt + "}" - - # list_repr = [] - # for k, v in dataclasses.asdict(obj).items(): - # k_repr = text(k) - # if hasattr(v, "_latex"): - # # used for overriding printing behaviour in sympy objects - # v_repr = v._latex(printer) - # elif hasattr(v, "_repr_latex_") and _unwrap_latex(v.__repr__()) != v.__repr__(): - # # used for objects that support IPython printing in latex - # v_repr = _unwrap_latex(v.__repr__()) - # else: - # v_repr = text(v.__repr__()) - # list_repr.append(k_repr + " = " + v_repr) - - # merged = ", \\quad".join(list_repr) return _textify(type(obj).__name__) + _gen_latex_repr_dict( dataclasses.asdict(obj), printer=printer ) From f385e21d293577036295f6c864012d7851bf5861 Mon Sep 17 00:00:00 2001 From: ys_teng <58208381+YeeShin504@users.noreply.github.com> Date: Mon, 20 Apr 2026 00:38:25 +0800 Subject: [PATCH 7/7] Updated README on quick reference sheet --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 34bf295..6d74dfe 100644 --- a/README.md +++ b/README.md @@ -108,6 +108,12 @@ To use the offline documentation: 2. Extract the ZIP to a folder 3. Open `index.html` in your browser +A quick reference guide is also available as a function in the library: +```python +from ma1522 import sympy_commands +sympy_commands() +``` + #### Building from Source If the offline ZIP is not available in the releases, you can build it yourself: