Reputation: 659
Consider an underdetermined linear system of equations Ax=b
.
I would like to find a set of vectors x_1, ..., x_n
such that they all solve Ax=b
and they are as different between each other as possible.
The second part is actually less important; I would be happy with an algorithm that returns a random solution of Ax=b
every time I call it.
I know that scipy.sparse.linalg.lsqr
and numpy.linalg.lstsq
return a sparse solution (in terms of least squares) of an underdetermined linear system Ax=b
, but I don't care about the properties of the solution; I just want any solution of Ax=b
, as long as I can generate a bunch of different solutions.
In fact, scipy.sparse.linalg.lsqr
and numpy.linalg.lstsq
should follow an iterative process that jumps from a solution to an other until they find a solution that seems to be the minimum in terms of least squares. Well, is there a python module that lets me jump between solutions without a particular objective, and returns them?
Upvotes: 4
Views: 2983
Reputation: 74222
For the underdetermined system A·x = b you can compute the null space of your coefficient matrix A. The null space, Z, is a set of basis vectors spanning a subspace of A such that A·Z = 0. In other words, the columns of Z are vectors that are orthogonal to all of the rows in A. This means that for any solution x' to A·x = b, then x' + Z·c must also be a solution for any arbitrary vector c.
So if you wanted to sample random solutions to A·x = b then you could do the following:
np.linalg.lstsq
, which finds a solution that minimizes the L2 norm of x'.For example:
import numpy as np
from scipy.linalg import qr
def qr_null(A, tol=None):
"""Computes the null space of A using a rank-revealing QR decomposition"""
Q, R, P = qr(A.T, mode='full', pivoting=True)
tol = np.finfo(R.dtype).eps if tol is None else tol
rnk = min(A.shape) - np.abs(np.diag(R))[::-1].searchsorted(tol)
return Q[:, rnk:].conj()
# An underdetermined system with nullity 2
A = np.array([[1, 4, 9, 6, 9, 2, 7],
[6, 3, 8, 5, 2, 7, 6],
[7, 4, 5, 7, 6, 3, 2],
[5, 2, 7, 4, 7, 5, 4],
[9, 3, 8, 6, 7, 3, 1]])
b = np.array([0, 4, 1, 3, 2])
# Find an initial solution using `np.linalg.lstsq`
x_lstsq = np.linalg.lstsq(A, b)[0]
# Compute the null space of `A`
Z = qr_null(A)
nullity = Z.shape[1]
# Sample some random solutions
for _ in range(5):
x_rand = x_lstsq + Z.dot(np.random.rand(nullity))
# If `x_rand` is a solution then `||A·x_rand - b||` should be very small
print(np.linalg.norm(A.dot(x_rand) - b))
Example output:
3.33066907388e-15
3.58036167305e-15
4.63775652864e-15
4.67877015036e-15
4.31132637123e-15
The space of possible c vectors is infinite - you'll have to make some choice as to how you want to sample these.
Upvotes: 4
Reputation: 6937
Here’s the code accompanying my comment. It uses the rank_nullspace.py
module from the Scipy Cookbook.
import numpy as np
from numpy.linalg import lstsq
from rank_nullspace import nullspace
# rank_nullspace from
# http://scipy-cookbook.readthedocs.io/items/RankNullspace.html
def randsol(A, b, num=1, check=False):
xLS, *_ = lstsq(A, b)
colsOfNullspace = nullspace(A)
nullrank = colsOfNullspace.shape[1]
if check:
assert(np.allclose(np.dot(A, xLS), b))
assert(np.allclose(np.dot(A, xLS + np.dot(colsOfNullspace,
np.random.randn(nullrank))),
b))
sols = xLS[:, np.newaxis] + np.dot(colsOfNullspace,
np.random.randn(nullrank, num))
return sols
A = np.random.randn(2, 10)
b = np.random.randn(2)
x = randsol(A, b, num=50, check=True)
assert(np.allclose(np.dot(A, x), b[:, np.newaxis]))
With a bunch of solutions in x
, you can pick ones that are “different” from each other, however you define “different”.
Upvotes: 2