Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added three functions for linalg: SVD, Least Squares and PseudoInverse #258

Closed
wants to merge 1 commit into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 104 additions & 1 deletion mathics/builtin/linalg.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"""

import sympy
from mpmath import mp

from mathics.builtin.base import Builtin
from mathics.core.convert import from_sympy
Expand All @@ -27,6 +28,22 @@ def to_sympy_matrix(data, **kwargs):
except (TypeError, AssertionError, ValueError):
return None

def to_mpmath_matrix(data, **kwargs):
def mpmath_matrix_data(m):
if not m.has_form('List', None):
return None
if not all(leaf.has_form('List', None) for leaf in m.leaves):
return None
return [[str(item) for item in row.leaves] for row in m.leaves]


if not isinstance(data, list):
data = mpmath_matrix_data(data)
try:
return mp.matrix(data)
except (TypeError, AssertionError, ValueError):
return None


class Det(Builtin):
u"""
Expand Down Expand Up @@ -83,7 +100,93 @@ def apply(self, m, evaluation):
if matrix.det() == 0:
return evaluation.message('Inverse', 'sing', m)
inv = matrix.inv()
return from_sympy(inv)
return from_sympy(inv)


class SingularValueDecomposition(Builtin):
"""
<dl>
<dt>'SingularValueDecomposition[$m$]'
<dd>Calculate the singular value decomposition for matrix $m$.
Returns $u, s, w$ such that $m=u s v$, $u'u=1$, $v'v=1$, $s$ is diagonal.
</dl>

>> SingularValueDecomposition[{{1, 2}, {2, 3}, {3, 4}}]
= {{-11 / 6, -1 / 3, 7 / 6}, {4 / 3, 1 / 3, -2 / 3}}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test is failing (because we get inexact answers). It's nice to have the tests pass, and unfortunately in the current system there's no way to mark a test as xfail. I'd suggest adding a Line before this test explaining why the results are inexact, and then testing the inexact results.

Let me know if that's unclear.


>> PseudoInverse[{{1, 2, 0}, {2, 3, 0}, {3, 4, 1}}]
= {{-3, 2, 0}, {2, -1, 0}, {1, -2, 1}}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one too.

"""


def apply(self, m, evaluation):
'SingularValueDecomposition[m_]'

matrix = to_mpmath_matrix(m)
U, S, V = mp.svd(matrix)
S = mp.diag(S)
print(U, S, V)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't forget to remove this.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh, thanks!

U_list = Expression('List', *U.tolist())
S_list = Expression('List', *S.tolist())
V_list = Expression('List', *V.tolist())
return Expression('List', *[U_list, S_list, V_list])


class PseudoInverse(Builtin):
"""
<dl>
<dt>'PseudoInverse[$m$]'
<dd>computes the Moore-Penrose pseudoinverse of the matrix $m$.
If $m$ is invertible, the pseudoinverse equals the inverse.
</dl>

>> PseudoInverse[{{1, 2}, {2, 3}, {3, 4}}]
= {{-11 / 6, -1 / 3, 7 / 6}, {4 / 3, 1 / 3, -2 / 3}}

>> PseudoInverse[{{1, 2, 0}, {2, 3, 0}, {3, 4, 1}}]
= {{-3, 2, 0}, {2, -1, 0}, {1, -2, 1}}
"""

def apply(self, m, evaluation):
'PseudoInverse[m_]'

matrix = to_sympy_matrix(m)
pinv = matrix.pinv()
return from_sympy(pinv)


class LeastSquares(Builtin):
"""
<dl>
<dt>'LeastSquares[$m$, $b$]'
<dd>Compute the least squares solution to $m x = b$.
Finds an x that solves for b optimally.
</dl>

>> LeastSquares[{{1, 2}, {2, 3}, {5, 6}}, {1, 5, 3}]
= {{-28 / 13}, {31 / 13}}

>> Simplify[LeastSquares[{{1, 2}, {2, 3}, {5, 6}}, {1, x, 3}]]
= {{12 / 13 - 8 x / 13}, {-4 / 13 + 7 x / 13}}

"""

messages = {
'underdetermined': "Solving for underdetermined system not implemented"
}

def apply(self, m, b, evaluation):
'LeastSquares[m_, b_]'

matrix = to_sympy_matrix(m)
b_vector = to_sympy_matrix([el.to_sympy() for el in b.leaves])

try:
solution = matrix.solve_least_squares(b_vector) # default method = Cholesky
except NotImplementedError as e:
return evaluation.message('LeastSquares', 'underdetermined')

return from_sympy(solution)


class LinearSolve(Builtin):
Expand Down