# Computing the Nullspace

In :
import numpy as np
import numpy.linalg as la

In :
n = 5
np.random.seed(25)
A = np.random.randn(n, n)

# Decrease the rank
A = A + 5 * A
A = 3 * A -2 * A

In :
from m_echelon import m_echelon

In :
M, U = m_echelon(A.T)

In :
la.norm(
M.dot(A.T) - U)

Out:
1.2599818366099757e-15

In :
U.round(3)

Out:
array([[  1.027,   0.676,  -0.232,   1.202,  -0.135],
[  0.   ,  -3.498,  -1.468,   1.749,  -7.342],
[  0.   ,   0.   ,  -2.14 ,   0.   , -10.699],
[  0.   ,   0.   ,   0.   ,   0.   ,  -0.   ],
[  0.   ,   0.   ,   0.   ,   0.   ,  -0.   ]])


Now define NUT as vectors spanning the nullspace of $N(U^T)$.

In :
NUT = np.eye(n)[:, 3:]
NUT

Out:
array([[ 0.,  0.],
[ 0.,  0.],
[ 0.,  0.],
[ 1.,  0.],
[ 0.,  1.]])


Check that it's actually a nullspace:

In :
U.T.dot(NUT)

Out:
array([[  0.000e+00,   0.000e+00],
[  0.000e+00,   0.000e+00],
[  0.000e+00,   0.000e+00],
[  3.644e-16,   6.163e-33],
[ -1.776e-15,  -2.174e-16]])


Now define NA as some vectors spanning $N(A)$:

In :
NA = M.T.dot(NUT)


And check:

In :
A.dot(NA)

Out:
array([[  1.110e-16,  -1.110e-16],
[  4.441e-16,  -4.441e-16],
[ -2.220e-16,  -5.551e-17],
[  1.110e-16,   1.110e-16],
[ -8.882e-16,  -6.661e-16]])

In []: