209 lines
5.3 KiB
Python
209 lines
5.3 KiB
Python
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)
|