from sympy import ZZ, Matrix from sympy.polys.matrices import DM, DomainMatrix from sympy.polys.matrices.ddm import DDM from sympy.polys.matrices.sdm import SDM import pytest zeros = lambda shape, K: DomainMatrix.zeros(shape, K).to_dense() eye = lambda n, K: DomainMatrix.eye(n, K).to_dense() # # DomainMatrix.nullspace can have a divided answer or can return an undivided # uncanonical answer. The uncanonical answer is not unique but we can make it # unique by making it primitive (remove gcd). The tests here all show the # primitive form. We test two things: # # A.nullspace().primitive()[1] == answer. # A.nullspace(divide_last=True) == _divide_last(answer). # # The nullspace as returned by DomainMatrix and related classes is the # transpose of the nullspace as returned by Matrix. Matrix returns a list of # of column vectors whereas DomainMatrix returns a matrix whose rows are the # nullspace vectors. # NULLSPACE_EXAMPLES = [ ( 'zz_1', DM([[ 1, 2, 3]], ZZ), DM([[-2, 1, 0], [-3, 0, 1]], ZZ), ), ( 'zz_2', zeros((0, 0), ZZ), zeros((0, 0), ZZ), ), ( 'zz_3', zeros((2, 0), ZZ), zeros((0, 0), ZZ), ), ( 'zz_4', zeros((0, 2), ZZ), eye(2, ZZ), ), ( 'zz_5', zeros((2, 2), ZZ), eye(2, ZZ), ), ( 'zz_6', DM([[1, 2], [3, 4]], ZZ), zeros((0, 2), ZZ), ), ( 'zz_7', DM([[1, 1], [1, 1]], ZZ), DM([[-1, 1]], ZZ), ), ( 'zz_8', DM([[1], [1]], ZZ), zeros((0, 1), ZZ), ), ( 'zz_9', DM([[1, 1]], ZZ), DM([[-1, 1]], ZZ), ), ( 'zz_10', DM([[0, 0, 0, 0, 0, 1, 0, 0, 0, 0], [1, 0, 0, 0, 0, 0, 1, 0, 0, 0], [0, 1, 0, 0, 0, 0, 0, 1, 0, 0], [0, 0, 0, 1, 0, 0, 0, 0, 1, 0], [0, 0, 0, 0, 1, 0, 0, 0, 0, 1]], ZZ), DM([[ 0, 0, 1, 0, 0, 0, 0, 0, 0, 0], [-1, 0, 0, 0, 0, 0, 1, 0, 0, 0], [ 0, -1, 0, 0, 0, 0, 0, 1, 0, 0], [ 0, 0, 0, -1, 0, 0, 0, 0, 1, 0], [ 0, 0, 0, 0, -1, 0, 0, 0, 0, 1]], ZZ), ), ] def _to_DM(A, ans): """Convert the answer to DomainMatrix.""" if isinstance(A, DomainMatrix): return A.to_dense() elif isinstance(A, DDM): return DomainMatrix(list(A), A.shape, A.domain).to_dense() elif isinstance(A, SDM): return DomainMatrix(dict(A), A.shape, A.domain).to_dense() else: assert False # pragma: no cover def _divide_last(null): """Normalize the nullspace by the rightmost non-zero entry.""" null = null.to_field() if null.is_zero_matrix: return null rows = [] for i in range(null.shape[0]): for j in reversed(range(null.shape[1])): if null[i, j]: rows.append(null[i, :] / null[i, j]) break else: assert False # pragma: no cover return DomainMatrix.vstack(*rows) def _check_primitive(null, null_ans): """Check that the primitive of the answer matches.""" null = _to_DM(null, null_ans) cont, null_prim = null.primitive() assert null_prim == null_ans def _check_divided(null, null_ans): """Check the divided answer.""" null = _to_DM(null, null_ans) null_ans_norm = _divide_last(null_ans) assert null == null_ans_norm @pytest.mark.parametrize('name, A, A_null', NULLSPACE_EXAMPLES) def test_Matrix_nullspace(name, A, A_null): A = A.to_Matrix() A_null_cols = A.nullspace() # We have to patch up the case where the nullspace is empty if A_null_cols: A_null_found = Matrix.hstack(*A_null_cols) else: A_null_found = Matrix.zeros(A.cols, 0) A_null_found = A_null_found.to_DM().to_field().to_dense() # The Matrix result is the transpose of DomainMatrix result. A_null_found = A_null_found.transpose() _check_divided(A_null_found, A_null) @pytest.mark.parametrize('name, A, A_null', NULLSPACE_EXAMPLES) def test_dm_dense_nullspace(name, A, A_null): A = A.to_field().to_dense() A_null_found = A.nullspace(divide_last=True) _check_divided(A_null_found, A_null) @pytest.mark.parametrize('name, A, A_null', NULLSPACE_EXAMPLES) def test_dm_sparse_nullspace(name, A, A_null): A = A.to_field().to_sparse() A_null_found = A.nullspace(divide_last=True) _check_divided(A_null_found, A_null) @pytest.mark.parametrize('name, A, A_null', NULLSPACE_EXAMPLES) def test_ddm_nullspace(name, A, A_null): A = A.to_field().to_ddm() A_null_found, _ = A.nullspace() _check_divided(A_null_found, A_null) @pytest.mark.parametrize('name, A, A_null', NULLSPACE_EXAMPLES) def test_sdm_nullspace(name, A, A_null): A = A.to_field().to_sdm() A_null_found, _ = A.nullspace() _check_divided(A_null_found, A_null) @pytest.mark.parametrize('name, A, A_null', NULLSPACE_EXAMPLES) def test_dm_dense_nullspace_fracfree(name, A, A_null): A = A.to_dense() A_null_found = A.nullspace() _check_primitive(A_null_found, A_null) @pytest.mark.parametrize('name, A, A_null', NULLSPACE_EXAMPLES) def test_dm_sparse_nullspace_fracfree(name, A, A_null): A = A.to_sparse() A_null_found = A.nullspace() _check_primitive(A_null_found, A_null)