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?
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.
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”.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With