PINV

Overview

The PINV function computes the Moore-Penrose pseudoinverse of a matrix, a generalized form of the matrix inverse that exists for any rectangular matrix, including singular and non-square matrices. The pseudoinverse is widely used in solving overdetermined or underdetermined systems of linear equations and computing least-squares solutions.

Unlike the standard matrix inverse (which only exists for square, non-singular matrices), the Moore-Penrose pseudoinverse is uniquely defined for all matrices and satisfies four defining conditions originally established by E. H. Moore in 1920 and Roger Penrose in 1955. For a matrix A with pseudoinverse A^+, these conditions are:

  1. AA^+A = A
  2. A^+AA^+ = A^+
  3. (AA^+)^* = AA^+
  4. (A^+A)^* = A^+A

This implementation uses the SciPy scipy.linalg.pinv function, which computes the pseudoinverse via singular value decomposition (SVD). Given the SVD of a matrix A = U \Sigma V^*, where U and V are unitary matrices and \Sigma is a diagonal matrix of singular values, the pseudoinverse is computed as:

A^+ = V \Sigma^+ U^*

where \Sigma^+ is formed by taking the reciprocal of each non-zero singular value in \Sigma and transposing the result. Singular values below a threshold (determined by atol and rtol parameters) are treated as zero to improve numerical stability.

The pseudoinverse is essential for solving least-squares problems: for a system Ax = b that may not have an exact solution, x = A^+b provides the solution that minimizes \|Ax - b\|_2. When multiple solutions exist, it yields the minimum-norm solution. For more mathematical background, see the Wikipedia article on the Moore-Penrose inverse. The source code is available in the SciPy GitHub repository.

This example function is provided as-is without any representation of accuracy.

Excel Usage

=PINV(matrix, atol, rtol, return_rank, check_finite)
  • matrix (list[list], required): Matrix to pseudo-invert. Scalars are treated as 1x1 matrices.
  • atol (float, optional, default: 0): Absolute threshold for small singular values to be treated as zero.
  • rtol (float, optional, default: null): Relative threshold for small singular values. If None, uses max(M, N) * eps.
  • return_rank (bool, optional, default: false): If True, return the effective rank instead of the pseudoinverse.
  • check_finite (bool, optional, default: true): If True, verify the input contains only finite numbers.

Returns (list[list]): 2D pseudoinverse matrix, or error message string.

Examples

Example 1: Pseudoinverse of a 2x2 invertible matrix

Inputs:

matrix
1 2
3 4

Excel formula:

=PINV({1,2;3,4})

Expected output:

Result
-2 1
1.5 -0.5

Example 2: Pseudoinverse of a 3x2 rectangular matrix

Inputs:

matrix
1 2
3 4
5 6

Excel formula:

=PINV({1,2;3,4;5,6})

Expected output:

Result
-1.333333 -0.333333 0.666667
1.083333 0.333333 -0.416667

Example 3: Effective rank of a 2x2 matrix

Inputs:

matrix return_rank
1 2 true
3 4

Excel formula:

=PINV({1,2;3,4}, TRUE)

Expected output:

Result
2

Example 4: Pseudoinverse with custom atol and rtol thresholds

Inputs:

matrix atol rtol
1 2 0.00001 0.00001
3 4

Excel formula:

=PINV({1,2;3,4}, 0.00001, 0.00001)

Expected output:

Result
-2 1
1.5 -0.5

Python Code

import numpy as np
from scipy.linalg import pinv as scipy_pinv

def pinv(matrix, atol=0, rtol=None, return_rank=False, check_finite=True):
    """
    Compute the Moore-Penrose pseudoinverse of a matrix using singular value decomposition (SVD).

    See: https://docs.scipy.org/doc/scipy/reference/generated/scipy.linalg.pinv.html

    This example function is provided as-is without any representation of accuracy.

    Args:
        matrix (list[list]): Matrix to pseudo-invert. Scalars are treated as 1x1 matrices.
        atol (float, optional): Absolute threshold for small singular values to be treated as zero. Default is 0.
        rtol (float, optional): Relative threshold for small singular values. If None, uses max(M, N) * eps. Default is None.
        return_rank (bool, optional): If True, return the effective rank instead of the pseudoinverse. Default is False.
        check_finite (bool, optional): If True, verify the input contains only finite numbers. Default is True.

    Returns:
        list[list]: 2D pseudoinverse matrix, or error message string.
    """
    # Helper function to normalize scalar/single-element input to 2D list
    def to2d(x):
        return [[x]] if not isinstance(x, list) else x

    # Normalize scalar input into a 2D list for consistent handling
    matrix = to2d(matrix)
    if not isinstance(matrix[0], list):
        # Handle case where input was a 1D list
        return [["Invalid input: matrix must be a 2D list or scalar numeric value."]]

    # Convert scalar values that came from Excel single-element arrays
    for i, row in enumerate(matrix):
        for j, val in enumerate(row):
            if isinstance(val, (int, float, bool)):
                matrix[i][j] = float(val)
            elif isinstance(val, str):
                try:
                    matrix[i][j] = float(val)
                except Exception:
                    return [["Invalid input: matrix must contain only numeric values."]]

    # Ensure the matrix is a 2D list with consistent row lengths
    if not matrix or not all(isinstance(row, list) for row in matrix):
        return [["Invalid input: matrix must be a 2D list with at least one row."]]
    if any(len(row) == 0 for row in matrix):
        return [["Invalid input: matrix rows must contain at least one value."]]
    row_length = len(matrix[0])
    if any(len(row) != row_length for row in matrix):
        return [["Invalid input: all rows in matrix must have the same length."]]

    # Validate optional parameters
    try:
        atol_value = float(atol)
    except Exception:
        return [["Invalid input: atol must be a numeric value."]]

    if rtol is not None:
        try:
            rtol_value = float(rtol)
        except Exception:
            return [["Invalid input: rtol must be a numeric value or None."]]
    else:
        rtol_value = None

    if not isinstance(return_rank, bool):
        return [["Invalid input: return_rank must be a boolean."]]
    if not isinstance(check_finite, bool):
        return [["Invalid input: check_finite must be a boolean."]]

    # Convert the matrix elements to floats, ensuring numeric values
    numeric_rows = []
    for row in matrix:
        numeric_row = []
        for value in row:
            try:
                numeric_row.append(float(value))
            except Exception:
                return [["Invalid input: matrix must contain only numeric values."]]
        numeric_rows.append(numeric_row)

    arr = np.array(numeric_rows, dtype=float)

    # Compute the pseudoinverse and optionally the rank
    try:
        if return_rank:
            _, rank = scipy_pinv(
                arr,
                atol=atol_value,
                rtol=rtol_value,
                return_rank=True,
                check_finite=check_finite,
            )
            if not np.isfinite(rank):
                return [["scipy.linalg.pinv error: effective rank is not finite."]]
            return [[float(rank)]]

        result = scipy_pinv(
            arr,
            atol=atol_value,
            rtol=rtol_value,
            return_rank=False,
            check_finite=check_finite,
        )
    except Exception as exc:
        return [[f"scipy.linalg.pinv error: {exc}"]]

    # Ensure the result does not contain non-finite values before returning to Excel
    if not np.all(np.isfinite(result)):
        return [["scipy.linalg.pinv error: result contains non-finite values."]]

    return result.tolist()

Online Calculator