Rank and null space: reachable outputs and hidden input changes

Use rank, column space, and null space to understand a linear map. Explore rank-nullity, unreachable targets, and families of solutions with a small matrix experiment.

By 12 min read

What you will learn

  • Identify a matrix's reachable outputs and input directions that produce zero.
  • Apply rank-nullity using the number of input coordinates.
  • Explain whether a target has no solution, one solution, or a family of solutions.
  • Interpret Jacobian null-space motion as a local velocity statement.

Before you start

Different inputs can produce the same output through a linear map. Rank counts the independent output directions. The null space contains input changes that leave the output unchanged.

These ideas help you answer two separate questions about Ax = b: can any input reach b, and if so, how many inputs do? A small map from three inputs to two outputs makes both questions concrete.

Count independent output directions

An m × n matrix A maps an n-component input x to an m-component output. Its column space, written Col(A), contains every output Ax that the map can produce. It is the span of A's columns.

The rank is the dimension of this column space. A basis gives a set of independent columns that spans the reachable outputs. Counting all nonzero columns can overstate the rank because some columns repeat information already present.

Consider a matrix with two rows and three columns:

A = [1, 0, 1; 0, 1, 1]

Its columns are c₁ = (1, 0), c₂ = (0, 1), and c₃ = (1, 1). The first two independently cover the output plane. The third equals c₁ + c₂, so it adds no new direction.

This matrix has rank 2. Its column space is all of ℝ². Interactive Linear Algebra defines rank through the column space, while matrix multiplication explains how input coordinates weight those columns.

Find inputs that produce zero

The null space, also called the kernel, contains every input z satisfying Az = 0. It lives in the input space ℝⁿ. Interactive Linear Algebra introduces column and null spaces as subspaces.

For our matrix, multiplying gives:

Az = (z₁ + z₃, z₂ + z₃)

To get zero, set z₁ = −z₃ and z₂ = −z₃. Choose z₃ = t. Then every null-space vector has the form z = t(−1, −1, 1).

The vector n₁ = (−1, −1, 1) is a basis for the null space. Check it directly: An₁ = (−1 + 1, −1 + 1) = (0, 0). The cancellation corresponds to the column relation −c₁ − c₂ + c₃ = 0.

The null space always contains zero. If zero is its only member, the null space has dimension zero and an empty basis. That is different from an equation Ax = b having no solution.

Account for every input coordinate

The dimension of Null(A) is its nullity. For a matrix with n columns, the rank-nullity theorem states:

rank(A) + nullity(A) = n

Our example has 2 + 1 = 3. The total uses the number of columns, which counts the input coordinates. Its two output rows do not set that total.

Row reduction gives another way to see the count. Pivot columns account for rank; nonpivot input variables are free. Their count gives nullity. The rank theorem connects these counts.

Nullity counts independent directions, not individual vectors. A one-dimensional null space already contains infinitely many real vectors. Also keep the spaces distinct: our column space lies in ℝ², while our null space is a line in ℝ³.

Build the full solution family

Suppose the target is b = (2, 1). One solution is x₀ = (2, 1, 0), because Ax₀ = (2, 1). We call x₀ a particular solution.

Adding any null-space vector preserves the output:

A(x₀ + z) = Ax₀ + Az = b + 0 = b

For this example, every solution is:

x = (2, 1, 0) + t(−1, −1, 1)
x = (2 − t, 1 − t, t)

At t = 0, use (2, 1, 0). At t = 1, use (1, 0, 1). Both produce (2, 1).

This recipe gives all solutions. If x and x₀ both reach b, then A(x − x₀) = b − b = 0. Their difference must belong to the null space.

A robot can use the same freedom in its joint rates. The kinematic redundancy lesson adds a third joint to a planar arm and tests how null-space motion changes the joints while preserving the instantaneous tip velocity.

The pseudoinverse lesson uses this same solution family to find the input with the smallest Euclidean norm.

The notation x₀ + Null(A) describes an affine solution set: a shifted null space. When b is nonzero, the solution set does not contain the origin and is not a vector subspace. Interactive Linear Algebra explains the geometry of these solution sets.

Change inputs without changing the output

The explorer starts with the 2 × 3 example. Move Null coefficient t₁ and watch the input readout change. The output point stays at b because each added direction belongs to Null(A).

Interactive experiment

Change the input while holding the output

Inspect the reachable output space and add null-space directions to a particular solution. The matrices use exact, small integer entries.

Entries of A
Rowx1x2x3
y1101
y2011

Column-space basis

  • c1 = (1.00, 0.00)
  • c2 = (0.00, 1.00)

Null-space basis

  • n1 = (-1.00, -1.00, 1.00)

Output space: two coordinates

Column space and the requested outputThe column space is the whole output plane. Target b is (2.00, 1.00). The output Ax is (2.00, 1.00) and coincides with b. This diagram shows output coordinates, not the input or null space. Both axes run from −5 to 5.-404-44y1y2
Amber marks the column space: the whole output plane. The open circle is target b; a blue center marks a matching Ax. The plane or line continues beyond this window.
Matrix shape
2 × 3
Rank
2
Nullity
1
Rank + nullity
2 + 1 = 3
Target b
(2.00, 1.00)
Particular x₀
(2.00, 1.00, 0.00)
Input x
(2.00, 1.00, 0.00)
Output Ax
(2.00, 1.00)
Solution set
Infinitely many

Change the null coefficient: the input moves through solutions while Ax stays (2.00, 1.00).

The diagram stays in output space. Null directions live in the 3-dimensional input space and appear numerically above. The slider explores a bounded part of the full solution family.

The plot shows output space only. Amber marks the reachable plane, line, or origin. The open circle marks b, and a blue center marks the matching Ax when a solution exists.

Try these cases:

  • One output direction: the second column is twice the first. The rank is 1, the nullity is 1, and all outputs lie on y₂ = 2y₁.
  • Choose Target (1, 0) for that matrix. The target misses the line, so no input can reach it.
  • Full rank: the 2 × 2 map has nullity 0. Every target has one solution, and no null coefficient remains to vary.
  • Zero map: only b = 0 is reachable. For that target, both input coordinates are free and nullity is 2.

Adding a null vector cannot repair an unreachable target: it changes the output by zero. The slider range displays part of each solution family; the mathematical free parameters can take any real value.

Separate reachability from uniqueness

For an m × n matrix, rank cannot exceed either m or n. Full row rank, rank m, makes every b in ℝᵐ reachable. Full column rank, rank n, makes the null space trivial, so each reachable target has a unique input.

Our 2 × 3 matrix has full row rank. It reaches every two-component target, but its nullity is 1. Each target therefore has infinitely many inputs when the inputs are unrestricted real vectors.

For a tall example, take T = [1, 0; 0, 1; 1, 1], a 3 × 2 matrix. It maps (x₁, x₂) to (x₁, x₂, x₁ + x₂). Its rank is 2 and nullity is 0.

The target (2, 1, 3) has the unique input (2, 1). The target (2, 1, 4) has no solution because the third output must equal the sum of the first two. Full column rank establishes uniqueness for consistent systems; it does not make every output reachable.

For a square n × n matrix, rank n gives both properties and an inverse. The transpose and inverse lesson develops that connection. A nonzero determinant also certifies full rank for a square matrix; rectangular matrices still have rank and null spaces without an ordinary determinant.

Read a robot Jacobian locally

A robot's Jacobian J(q) maps joint velocities to a chosen task velocity at configuration q. For example, a planar endpoint-position task can use two output coordinates and three joint inputs. Modern Robotics illustrates this with a 2 × 3 Jacobian.

At a fixed configuration, suppose J(q)q̇₀ = v. Adding z with J(q)z = 0 gives another joint velocity q̇₀ + z with the same instantaneous task velocity. The input joints can move in a direction that the selected task does not observe at that instant.

This is a local velocity statement. J changes as the robot moves, so a null direction computed once need not remain null. A finite motion needs updated kinematics and checks for joint limits, collisions, and actuator limits; null-space membership alone establishes none of those conditions.

Task choice matters too. Preserving endpoint position velocity does not automatically preserve tool orientation. At a singular configuration, a Jacobian can lose rank relative to its maximum, removing an instantaneous task direction and increasing nullity.

The explorer uses synthetic linear maps to show this algebra. It does not model the geometry or safety of a physical robot.

Distinguish exact rank from numerical rank

The explorer's bounded integer matrices support exact independence checks. It uses small integer products and differences to find independent columns. Its rank claims apply to those inputs, with no adjustable floating-point matrix editor.

Measured data need a different numerical treatment. For instance, [1, 0; 0, ε] has exact rank 2 whenever ε is nonzero. A very small ε can still represent an output direction that is weak relative to the scale or uncertainty of the data.

Numerical libraries commonly estimate rank from singular values using a tolerance. NumPy documents its SVD-based rank method and threshold. A threshold can classify a very small nonzero singular value as effectively zero.

Report the method, tolerance, and coordinate scaling when a numerical rank decision matters. Exact algebraic rank and an application's effective rank answer related questions with different assumptions.

Check a basis with exact Python arithmetic

This standard-library example uses Fraction for exact row reduction of integer inputs. It finds pivot columns and constructs a null-space basis by setting each free variable to one in turn. The parametric-form guide explains the free-variable construction.

from fractions import Fraction

def rref(matrix):
    if not matrix or not matrix[0]:
        raise ValueError("Use a nonempty matrix")
    width = len(matrix[0])
    if any(len(row) != width for row in matrix):
        raise ValueError("Rows must have equal length")
    rows = [[Fraction(value) for value in row] for row in matrix]
    pivots = []
    pivot_row = 0
    for column in range(width):
        found = next((i for i in range(pivot_row, len(rows))
                      if rows[i][column] != 0), None)
        if found is None:
            continue
        rows[pivot_row], rows[found] = rows[found], rows[pivot_row]
        divisor = rows[pivot_row][column]
        rows[pivot_row] = [value / divisor for value in rows[pivot_row]]
        for i in range(len(rows)):
            if i != pivot_row:
                factor = rows[i][column]
                rows[i] = [value - factor * pivot
                           for value, pivot in zip(rows[i], rows[pivot_row])]
        pivots.append(column)
        pivot_row += 1
        if pivot_row == len(rows):
            break
    return rows, pivots

def matvec(matrix, vector):
    return tuple(sum(a * x for a, x in zip(row, vector)) for row in matrix)

def display(vector):
    return tuple(float(value) for value in vector)

A = ((1, 0, 1), (0, 1, 1))
reduced, pivots = rref(A)
free = [j for j in range(len(A[0])) if j not in pivots]
basis = []
for j in free:
    z = [Fraction(0)] * len(A[0])
    z[j] = Fraction(1)
    for i, pivot in enumerate(pivots):
        z[pivot] = -reduced[i][j]
    basis.append(tuple(z))

print(f"Rank: {len(pivots)}; nullity: {len(free)}")
print("Null basis:", [display(z) for z in basis])
print("A n1:", display(matvec(A, basis[0])))
x0 = (2, 1, 0)
for t in [-1, 0, 1]:
    x = tuple(value + t * z for value, z in zip(x0, basis[0]))
    assert matvec(A, x) == (2, 1)
    print(f"t={t}: x={display(x)}, Ax={display(matvec(A, x))}")

Expected output:

Rank: 2; nullity: 1
Null basis: [(-1.0, -1.0, 1.0)]
A n1: (0.0, 0.0)
t=-1: x=(3.0, 2.0, -1.0), Ax=(2.0, 1.0)
t=0: x=(2.0, 1.0, 0.0), Ax=(2.0, 1.0)
t=1: x=(1.0, 0.0, 1.0), Ax=(2.0, 1.0)

The computation stays rational through each assertion. Only the final display converts these values to floating-point numbers. This checks exact examples; it does not replace a numerical rank method for noisy measurements.

Try it yourself

Exercise 1. Let B = [1, 2, 0; 2, 4, 0]. Find its rank and nullity. Give a null-space basis, then decide whether targets (3, 6) and (3, 5) are reachable.

Show solution: count one independent column

The second column is twice the first, and the third is zero. Rank is 1, so nullity is 3 − 1 = 2. A null-space basis is (−2, 1, 0) and (0, 0, 1).

Every output obeys y₂ = 2y₁. Target (3, 6) is reachable, for example with x = (3, 0, 0). Target (3, 5) is unreachable because 5 differs from 2 × 3.

Exercise 2. For the initial 2 × 3 matrix and target b = (2, 1), verify that x = (0, −1, 2) is a solution. Express its difference from x₀ = (2, 1, 0) using the null-space basis. Is the solution set itself a vector subspace?

Show solution: subtract a particular solution

Ax = (0 + 2, −1 + 2) = (2, 1). The difference is (−2, −2, 2) = 2(−1, −1, 1), which lies in Null(A).

The solution set is an affine line. It excludes zero because A0 = 0 differs from b, so it is not a vector subspace. Shifting it by −x₀ recovers the null space.

Sources and further study