test_nullspace.py 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. from sympy import ZZ, Matrix
  2. from sympy.polys.matrices import DM, DomainMatrix
  3. from sympy.polys.matrices.ddm import DDM
  4. from sympy.polys.matrices.sdm import SDM
  5. import pytest
  6. zeros = lambda shape, K: DomainMatrix.zeros(shape, K).to_dense()
  7. eye = lambda n, K: DomainMatrix.eye(n, K).to_dense()
  8. #
  9. # DomainMatrix.nullspace can have a divided answer or can return an undivided
  10. # uncanonical answer. The uncanonical answer is not unique but we can make it
  11. # unique by making it primitive (remove gcd). The tests here all show the
  12. # primitive form. We test two things:
  13. #
  14. # A.nullspace().primitive()[1] == answer.
  15. # A.nullspace(divide_last=True) == _divide_last(answer).
  16. #
  17. # The nullspace as returned by DomainMatrix and related classes is the
  18. # transpose of the nullspace as returned by Matrix. Matrix returns a list of
  19. # of column vectors whereas DomainMatrix returns a matrix whose rows are the
  20. # nullspace vectors.
  21. #
  22. NULLSPACE_EXAMPLES = [
  23. (
  24. 'zz_1',
  25. DM([[ 1, 2, 3]], ZZ),
  26. DM([[-2, 1, 0],
  27. [-3, 0, 1]], ZZ),
  28. ),
  29. (
  30. 'zz_2',
  31. zeros((0, 0), ZZ),
  32. zeros((0, 0), ZZ),
  33. ),
  34. (
  35. 'zz_3',
  36. zeros((2, 0), ZZ),
  37. zeros((0, 0), ZZ),
  38. ),
  39. (
  40. 'zz_4',
  41. zeros((0, 2), ZZ),
  42. eye(2, ZZ),
  43. ),
  44. (
  45. 'zz_5',
  46. zeros((2, 2), ZZ),
  47. eye(2, ZZ),
  48. ),
  49. (
  50. 'zz_6',
  51. DM([[1, 2],
  52. [3, 4]], ZZ),
  53. zeros((0, 2), ZZ),
  54. ),
  55. (
  56. 'zz_7',
  57. DM([[1, 1],
  58. [1, 1]], ZZ),
  59. DM([[-1, 1]], ZZ),
  60. ),
  61. (
  62. 'zz_8',
  63. DM([[1],
  64. [1]], ZZ),
  65. zeros((0, 1), ZZ),
  66. ),
  67. (
  68. 'zz_9',
  69. DM([[1, 1]], ZZ),
  70. DM([[-1, 1]], ZZ),
  71. ),
  72. (
  73. 'zz_10',
  74. DM([[0, 0, 0, 0, 0, 1, 0, 0, 0, 0],
  75. [1, 0, 0, 0, 0, 0, 1, 0, 0, 0],
  76. [0, 1, 0, 0, 0, 0, 0, 1, 0, 0],
  77. [0, 0, 0, 1, 0, 0, 0, 0, 1, 0],
  78. [0, 0, 0, 0, 1, 0, 0, 0, 0, 1]], ZZ),
  79. DM([[ 0, 0, 1, 0, 0, 0, 0, 0, 0, 0],
  80. [-1, 0, 0, 0, 0, 0, 1, 0, 0, 0],
  81. [ 0, -1, 0, 0, 0, 0, 0, 1, 0, 0],
  82. [ 0, 0, 0, -1, 0, 0, 0, 0, 1, 0],
  83. [ 0, 0, 0, 0, -1, 0, 0, 0, 0, 1]], ZZ),
  84. ),
  85. ]
  86. def _to_DM(A, ans):
  87. """Convert the answer to DomainMatrix."""
  88. if isinstance(A, DomainMatrix):
  89. return A.to_dense()
  90. elif isinstance(A, DDM):
  91. return DomainMatrix(list(A), A.shape, A.domain).to_dense()
  92. elif isinstance(A, SDM):
  93. return DomainMatrix(dict(A), A.shape, A.domain).to_dense()
  94. else:
  95. assert False # pragma: no cover
  96. def _divide_last(null):
  97. """Normalize the nullspace by the rightmost non-zero entry."""
  98. null = null.to_field()
  99. if null.is_zero_matrix:
  100. return null
  101. rows = []
  102. for i in range(null.shape[0]):
  103. for j in reversed(range(null.shape[1])):
  104. if null[i, j]:
  105. rows.append(null[i, :] / null[i, j])
  106. break
  107. else:
  108. assert False # pragma: no cover
  109. return DomainMatrix.vstack(*rows)
  110. def _check_primitive(null, null_ans):
  111. """Check that the primitive of the answer matches."""
  112. null = _to_DM(null, null_ans)
  113. cont, null_prim = null.primitive()
  114. assert null_prim == null_ans
  115. def _check_divided(null, null_ans):
  116. """Check the divided answer."""
  117. null = _to_DM(null, null_ans)
  118. null_ans_norm = _divide_last(null_ans)
  119. assert null == null_ans_norm
  120. @pytest.mark.parametrize('name, A, A_null', NULLSPACE_EXAMPLES)
  121. def test_Matrix_nullspace(name, A, A_null):
  122. A = A.to_Matrix()
  123. A_null_cols = A.nullspace()
  124. # We have to patch up the case where the nullspace is empty
  125. if A_null_cols:
  126. A_null_found = Matrix.hstack(*A_null_cols)
  127. else:
  128. A_null_found = Matrix.zeros(A.cols, 0)
  129. A_null_found = A_null_found.to_DM().to_field().to_dense()
  130. # The Matrix result is the transpose of DomainMatrix result.
  131. A_null_found = A_null_found.transpose()
  132. _check_divided(A_null_found, A_null)
  133. @pytest.mark.parametrize('name, A, A_null', NULLSPACE_EXAMPLES)
  134. def test_dm_dense_nullspace(name, A, A_null):
  135. A = A.to_field().to_dense()
  136. A_null_found = A.nullspace(divide_last=True)
  137. _check_divided(A_null_found, A_null)
  138. @pytest.mark.parametrize('name, A, A_null', NULLSPACE_EXAMPLES)
  139. def test_dm_sparse_nullspace(name, A, A_null):
  140. A = A.to_field().to_sparse()
  141. A_null_found = A.nullspace(divide_last=True)
  142. _check_divided(A_null_found, A_null)
  143. @pytest.mark.parametrize('name, A, A_null', NULLSPACE_EXAMPLES)
  144. def test_ddm_nullspace(name, A, A_null):
  145. A = A.to_field().to_ddm()
  146. A_null_found, _ = A.nullspace()
  147. _check_divided(A_null_found, A_null)
  148. @pytest.mark.parametrize('name, A, A_null', NULLSPACE_EXAMPLES)
  149. def test_sdm_nullspace(name, A, A_null):
  150. A = A.to_field().to_sdm()
  151. A_null_found, _ = A.nullspace()
  152. _check_divided(A_null_found, A_null)
  153. @pytest.mark.parametrize('name, A, A_null', NULLSPACE_EXAMPLES)
  154. def test_dm_dense_nullspace_fracfree(name, A, A_null):
  155. A = A.to_dense()
  156. A_null_found = A.nullspace()
  157. _check_primitive(A_null_found, A_null)
  158. @pytest.mark.parametrize('name, A, A_null', NULLSPACE_EXAMPLES)
  159. def test_dm_sparse_nullspace_fracfree(name, A, A_null):
  160. A = A.to_sparse()
  161. A_null_found = A.nullspace()
  162. _check_primitive(A_null_found, A_null)