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:
- AA^+A = A
- A^+AA^+ = A^+
- (AA^+)^* = AA^+
- (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()