Quick actions

cmd+k|ctrl+k

Navigation

Languages

Lua Linear Algebra

Snippet info

Language

Lua

Visibility

public

Author

thosewhoknow

Created

2023-11-21T16:53:22.517278Z

Updated

2023-11-21T17:12:08.86692Z

local linalg = {}

-- ARITHMETIC FUNCTIONS
local abs = math.abs
local floor = math.floor
local sqrt = math.sqrt

-- TRIG FUNCTIONS
local cos = math.cos
local sin = math.sin

--[[ ROW CLASS ]]--
local _rowClass = {}
local _newRow = function(values)
	local instance = setmetatable({}, _rowClass)

	rawset(instance, "_values", values)

	return instance
end
_rowClass.__index = function(row, key)
	if type(key) == "number" then
		return row._values[key]
	elseif key == "Length" then
		return #row._values
	end

	error("Unrecognized key: " .. tostring(key))
end
_rowClass.__newindex = function()
	error("Rows are immutable", 2)
end
_rowClass.__tostring = function(row)
	local result = ""

	for i = 1, row.Length do
		result = result .. row[i]
		if i < row.Length then
			result = result .. " "
		end
	end

	return result
end
_rowClass.__unm = function (row)
	if row.Length == 1 then
		return -1 * row[1]
	end

	local resultArray = {}

	for i = 1, row.Length do
		resultArray[i] = -1 * row[i]
	end

	return _newRow(resultArray)
end
_rowClass.__add = function (left, right)
	if type(left) == "number" or type(right) == "number" then
		local row = type(left) == "number" and right or left

		if row.Length == 1 then
			local leftScalar = type(left) == "number" and left or left[1]
			local rightScalar = type(right) == "number" and right or right[1]
			return leftScalar + rightScalar
		end

		error("Addition of a scalar to a row is undefined", 2)
	end

	if left.Length ~= right.Length then
		error("Addition of rows of different lengths is undefined", 2)
	end

	if left.Length == 1 then
		return left[1] + right[1]
	end

	local resultArray = {}

	for i = 1, left.Length do
		resultArray[i] = left[i] + right[i]
	end

	return _newRow(resultArray)
end
_rowClass.__sub = function (left, right)
	if type(left) == "number" or type(right) == "number" then
		local row = type(left) == "number" and right or left

		if row.Length == 1 then
			local leftScalar = type(left) == "number" and left or left[1]
			local rightScalar = type(right) == "number" and right or right[1]
			return leftScalar - rightScalar
		end

		error("Subtraction of a scalar to a row is undefined", 2)
	end

	if left.Length ~= right.Length then
		error("Subtraction of rows of different lengths is undefined", 2)
	end

	if left.Length == 1 then
		return left[1] - right[1]
	end

	local resultArray = {}

	for i = 1, left.Length do
		resultArray[i] = left[i] - right[i]
	end

	return _newRow(resultArray)
end
_rowClass.__mul = function (left, right)
	if type(left) == "number" or type(right) == "number" then
		local row = type(left) == "number" and right or left
		local scalar = type(left) == "number" and left or right

		if row.Length == 1 then
			local leftScalar = type(left) == "number" and left or left[1]
			local rightScalar = type(right) == "number" and right or right[1]
			return leftScalar * rightScalar
		else
			local resultArray = {}

			for i = 1, row.Length do
				resultArray[i] = scalar * row[i]
			end

			return _newRow(resultArray)
		end
	elseif left.Length == 1 or right.Length == 1 then
		if left.Length == 1 and right.Length == 1 then
			return left[1] * right[1]
		elseif left.Length == 1 then
			return left[1] * right
		else
			return left * right[1]
		end
	end

	error("Multiplication of two rows is undefined", 2)
end
_rowClass.__div = function (left, right)
	if type(left) == "number" then
		error("Division of a scalar by a row is undefined", 2)
	elseif type(right) == "number" then
		if left.Length == 1 then
			return left[1] / right
		else
			local resultArray = {}

			for i = 1, left.Length do
				resultArray[i] = left[i] / right
			end

			return _newRow(resultArray)
		end
	elseif left.Length == 1 or right.Length == 1 then
		if left.Length == 1 and right.Length == 1 then
			return left[1] / right[1]
		elseif left.Length == 1 then
			error("Division of a scalar by a row is undefined", 2)
		else
			return left / right[1]
		end
	end

	error("Division of two rows is undefined", 2)
end
_rowClass.__mod = function (left, right)
	if type(left) == "number" then
		error("Modulo of a scalar by a row is undefined", 2)
	elseif type(right) == "number" then
		if left.Length == 1 then
			return left[1] % right
		else
			local resultArray = {}

			for i = 1, left.Length do
				resultArray[i] = left[i] % right
			end

			return _newRow(resultArray)
		end
	elseif left.Length == 1 or right.Length == 1 then
		if left.Length == 1 and right.Length == 1 then
			return left[1] % right[1]
		elseif left.Length == 1 then
			error("Modulo of a scalar by a row is undefined", 2)
		else
			return left % right[1]
		end
	end

	error("Modulo of two rows is undefined", 2)
end
_rowClass.__pow = function (left, right)
	if type(left) == "number" then
		if right.Length == 1 then
			return left ^ right[1]
		else
			error("Exponentiation of a scalar by a row is undefined", 2)
		end
	elseif type(right) == "number" then
		if left.Length == 1 then
			return left[1] ^ right
		else
			error("Exponentiation of a row by a scalar is undefined", 2)
		end
	elseif left.Length == 1 or right.Length == 1 then
		if left.Length == 1 and right.Length == 1 then
			return left[1] ^ right[1]
		elseif left.Length == 1 then
			error("Exponentiation of a scalar by a row is undefined", 2)
		else
			error("Exponentiation of a row by a scalar is undefined", 2)
		end
	end

	error("Exponentiation of two rows is undefined", 2)
end
_rowClass.__eq = function(left, right)
	if left.Length ~= right.Length then
		return false
	end

	for i = 1, left.Length do
		if left[i] ~= right[i] then
			return false
		end
	end

	return true
end

--[[ MATRIX CLASS ]]--
local _matrixClass = {}
local _newMatrix = function(rows)
	local rowInstances = {}
	for i = 1, #rows do
		rowInstances[i] = _newRow(rows[i])
	end

	local instance = setmetatable({}, _matrixClass)
	rawset(instance, "_rows", rowInstances)

	return instance
end
_matrixClass.__index = function(mat, key)
	if type(key) == "number" then
		return rawget(mat, "_rows")[key]
		--return mat._rows[key]
	elseif key == "Shape" then
		return { #rawget(mat, "_rows"), rawget(mat, "_rows")[1].Length }
	elseif key == "T" then
		return linalg.matrix.transpose(mat)
	elseif key == "Unit" then
		if mat.Shape[2] == 1 then
			return linalg.vector.unit(mat)
		elseif mat.Shape[1] == mat.Shape[2] then
			return linalg.matrix.identity(mat.Shape[1])
		else
			error("Unit of a non-square matrix is undefined", 2)
		end
	elseif linalg.matrix[key] then
		if type(linalg.matrix[key]) == "function" then
			return function (...) return linalg.matrix[key](mat, ...) end
		end
	elseif linalg.vector[key] and mat.Shape[2] == 1 then
		if type(linalg.vector[key]) == "function" then
			return function (...) return linalg.vector[key](mat, ...) end
		end
	end

	error("Unrecognized key: " .. tostring(key))
end
_matrixClass.__newindex = function()
	error("Matrices are immutable", 2)
end
_matrixClass.__tostring = function(mat)
	local result = ""

	for i = 1, mat.Shape[1] do
		result = result .. tostring(mat[i])
		if i < mat.Shape[1] then
			result = result .. "\n"
		end
	end

	return result
end
_matrixClass.__unm = function(mat)
	local resultRows = {}

	for i = 1, mat.Shape[1] do
		resultRows[i] = {}
		for j = 1, mat.Shape[2] do
			resultRows[i][j] = -1 * mat[i][j]
		end
	end

	return _newMatrix(resultRows)
end
_matrixClass.__add = function(left, right)
	if left.Shape[1] ~= right.Shape[1] or left.Shape[2] ~= right.Shape[2] then
		error(
			"Cannot add matrices of sizes (" ..
				left.Shape[1] .. " x " .. left.Shape[2] .. ") and (" .. right.Shape[1] .. " x " .. right.Shape[2] .. ")",
			2
		)
	end

	local resultRows = {}

	for i = 1, left.Shape[1] do
		resultRows[i] = {}
		for j = 1, left.Shape[2] do
			resultRows[i][j] = left[i][j] + right[i][j]
		end
	end

	return _newMatrix(resultRows)
end
_matrixClass.__sub = function(left, right)
	if left.Shape[1] ~= right.Shape[1] or left.Shape[2] ~= right.Shape[2] then
		error(
			"Cannot subtract matrices of sizes (" ..
				left.Shape[1] .. " x " .. left.Shape[2] .. ") and (" .. right.Shape[1] .. " x " .. right.Shape[2] .. ")",
			2
		)
	end

	local resultRows = {}

	for i = 1, left.Shape[1] do
		resultRows[i] = {}
		for j = 1, left.Shape[2] do
			resultRows[i][j] = left[i][j] - right[i][j]
		end
	end

	return _newMatrix(resultRows)
end
_matrixClass.__mul = function(left, right)
	local resultRows = {}

	if type(left) == "number" or type(right) == "number" then
		local mat = type(left) == "number" and right or left
		local scalar = type(left) == "number" and left or right

		for i = 1, mat.Shape[1] do
			resultRows[i] = {}
			for j = 1, mat.Shape[2] do
				resultRows[i][j] = scalar * mat[i][j]
			end
		end
	else
		if left.Shape[2] ~= right.Shape[1] then
			error(
				"Cannot multiply matrices of sizes (" ..
					left.Shape[1] .. " x " .. left.Shape[2] .. ") and (" .. right.Shape[1] .. " x " .. right.Shape[2] .. ")",
				2
			)
		end

		for i = 1, left.Shape[1] do
			resultRows[i] = {}
			for j = 1, right.Shape[2] do
				resultRows[i][j] = 0
				for k = 1, left.Shape[2] do
					resultRows[i][j] = resultRows[i][j] + (left[i][k] * right[k][j])
				end
			end
		end
	end

	return _newMatrix(resultRows)
end
_matrixClass.__div = function(mat, scalar)
	if type(mat) == "number" then
		error("Cannot divide a scalar by a matrix", 2)
	elseif type(scalar) ~= "number" then
		error("Cannot divide a matrix by a matrix", 2)
	end

	local resultRows = {}

	for i = 1, mat.Shape[1] do
		resultRows[i] = {}
		for j = 1, mat.Shape[2] do
			resultRows[i][j] = mat[i][j] / scalar
		end
	end

	return _newMatrix(resultRows)
end
_matrixClass.__mod = function(mat, scalar)
	if type(mat) == "number" then
		error("Cannot compute modulus of a scalar by a matrix", 2)
	elseif type(scalar) ~= "number" then
		error("Cannot compute modulus of a matrix by a matrix", 2)
	end

	local resultRows = {}

	for i = 1, mat.Shape[1] do
		resultRows[i] = {}
		for j = 1, mat.Shape[2] do
			resultRows[i][j] = mat[i][j] % scalar
		end
	end

	return _newMatrix(resultRows)
end
_matrixClass.__pow = function(base, power)
	if type(base) ~= "number" and type(power) == "number" then
		if base.Shape[1] ~= base.Shape[2] then
			error("Cannot exponentiate a non-square matrix", 2)
		end
		if floor(power) ~= power or power < 1 then
			error("Cannot exponentiate a matrix by a non-positive non-integer", 2)
		end

		-- Runs in O(n^2 log_2(n)) time
		-- See https://www.hackerearth.com/practice/notes/matrix-exponentiation-1/
		local resultMatrix = linalg.matrix.identity(base.Shape[1])
		local curMat = base

		while power > 0 do
			if power % 2 == 1 then
				resultMatrix = resultMatrix * curMat
			end

			curMat = curMat * curMat
			power = floor(power / 2)
		end

		return resultMatrix
	elseif type(base) == "number" and type(power) ~= "number" then
		error("Cannot raise an arbitrary number to a matrix power", 2)
	else
		error("Cannot exponentiate a matrix by a matrix", 2)
		--TODO (You should be able to do exactly this)
	end
end
_matrixClass.__eq = function(left, right)
	if left.Shape[1] ~= right.Shape[1] or left.Shape[2] ~= right.Shape[2] then
		return false
	end

	for i = 1, left.Shape[1] do
		for j = 1, left.Shape[2] do
			if left[i][j] ~= right[i][j] then
				return false
			end
		end
	end

	return true
end

-- MATRIX FUNCTIONS
linalg.matrix = {}

--[[**
	Creates a new matrix

	@param [t:array<array<number>>] rows A (m x n) array of numbers to fill the matrix with

	@returns [t:(m x n) matrix] The new matrix
**--]]
linalg.matrix.new = function(rows)
	return _newMatrix(rows)
end

linalg.matrix.fromColumns = function(columnVectors)
	local resultRows = {}
	local n = columnVectors[1]["Shape"] and columnVectors[1].Shape[1] or #columnVectors[1]

	for i = 1, #columnVectors do
		for j = 1, n do
			if not resultRows[j] then
				resultRows[j] = {}
			end

			resultRows[j][i] = columnVectors[i][j] * 1 --*1 to ensure scalar functionality
		end
	end

	return _newMatrix(resultRows)
end

--[[**
	Creates an identity matrix of size (n x n)

	@param [t:number] n The size of the matrix

	@returns [t:(n x n) matrix] The identity matrix
**--]]
linalg.matrix.identity = function(n)
	local rows = {}

	for i = 1, n do
		rows[i] = {}
		for j = 1, n do
			rows[i][j] = i == j and 1 or 0
		end
	end

	return _newMatrix(rows)
end

--[[**
	Creates a matrix of all zeros of size (m x n)

	@param [t:number] m
	@param [t:number] n

	@returns [t:(m x n) matrix] The zeros matrix
**--]]
linalg.matrix.zeros = function(m, n)
	local rows = {}

	for i = 1, m do
		rows[i] = {}
		for j = 1, n do
			rows[i][j] = 0
		end
	end

	return _newMatrix(rows)
end

--[[**
	Creates a matrix of all zeros of the same size as the provided matrix

	@param [t:(m x n) matrix] mat The matrix to copy the size of

	@returns [t:(m x n) matrix] The zeros matrix
**--]]
linalg.matrix.zerosLike = function(mat)
	return linalg.matrix.zeros(mat.Shape[1], mat.Shape[2])
end

--[[**
	Creates a diagonal matrix with the values provided as the diagonal entries

	@param [t:array<number>] values An n-length array whose entries will be set as the diagonal entries

	@returns [t:(n x n) matrix] The resulting diagonal matrix
**--]]
linalg.matrix.diagonal = function(values)
	local resultRows = {}

	for i = 1, #values do
		resultRows[i] = {}
		for j = 1, #values do
			resultRows[i][j] = i == j and values[i] or 0
		end
	end

	return _newMatrix(resultRows)
end

--[[**
	Determines whether a matrix is diagonal
	Does not exclusively refer to square matrices
	Refers strictly to whether all non-zero values are on the main diagonal (i.e., a_{ij} = 0 for all i, j where i ~= j)

	@param [t:(m x n) matrix] mat The matrix to check

	@returns [t:boolean] True if the matrix is diagonal, false otherwise
**--]]
linalg.matrix.isDiagonal = function(mat)
	for i = 1, mat.Shape[1] do
		for j = 1, mat.Shape[2] do
			if i ~= j and mat[i][j] ~= 0 then
				return false
			end
		end
	end

	return true
end

--[[**
	Determines whether a matrix is upper triangular
	Note that any non-square matrix will return false

	@param [t:(m x n) matrix] mat The matrix to check

	@returns [t:boolean] True if the matrix is upper triangular, false otherwise
**--]]
linalg.matrix.isUpperTriangular = function(mat)
	if mat.Shape[1] ~= mat.Shape[2] then
		return false
	end

	for i = 1, mat.Shape[1] do
		for j = 1, mat.Shape[2] do
			if i > j and mat[i][j] ~= 0 then
				return false
			end
		end
	end

	return true
end

--[[**
	Determines whether a matrix is lower triangular
	Note that any non-square matrix will return false

	@param [t:(m x n) matrix] mat The matrix to check

	@returns [t:boolean] True if the matrix is lower triangular, false otherwise
**--]]
linalg.matrix.isLowerTriangular = function(mat)
	if mat.Shape[1] ~= mat.Shape[2] then
		return false
	end

	for i = 1, mat.Shape[1] do
		for j = 1, mat.Shape[2] do
			if i < j and mat[i][j] ~= 0 then
				return false
			end
		end
	end

	return true
end

--[[**
	Creates a new matrix that is the transpose of the provided matrix

	@param [t: (m x n) matrix] mat The matrix to create the transpose of

	@returns [t: (n x m) matrix] The transpose of mat
**--]]
linalg.matrix.transpose = function(mat)
	local resultRows = {}

	for i = 1, mat.Shape[1] do
		for j = 1, mat.Shape[2] do
			if not resultRows[j] then
				resultRows[j] = {}
			end

			resultRows[j][i] = mat[i][j]
		end
	end

	return _newMatrix(resultRows)
end

--[[**
	Solves for e^mat
	Defined as: e^A = \sum_{k=0}^{\infinity} \frac{1}{k!} A^k
	Implemented in a naive way to approximate by using iterations
	Runtime of O(n^3)

	@param [t:(n x n) matrix] mat The matrix to use as the exponent
	@param [t:number] numIterations The number of iterations to take the sum of the taylor series to

	@returns The matrix exponential approximation
**--]]
linalg.matrix.expm = function(mat, numIterations)
	if mat.Shape[1] ~= mat.Shape[2] then
		error("Cannot find the exponential of a non-square matrix", 2)
	end

	if not numIterations then
		numIterations = 128
	end

	local resultMatrix = linalg.matrix.identity(mat.Shape[1])
	local curMat = resultMatrix
	local factorial = 1

	for i = 1, numIterations do
		curMat = curMat * mat
		factorial = factorial * i

		resultMatrix = resultMatrix + ((1 / factorial) * curMat)
	end

	return resultMatrix
end

-- VECTOR FUNCTIONS
linalg.vector = {}

--[[**
	Creates a new column vector

	@param [t:array<number>] values The values to have for the column vector

	@returns [t:(n x 1) matrix] The new column vector
**--]]
linalg.vector.new = function(values)
	local rows = {}

	for i = 1, #values do
		rows[i] = {values[i]}
	end

	return _newMatrix(rows)
end

--[[**
	Creates the standard basis vector i for R^n
	That is, creates a vector of length n with all zeros except at index i which will have value 1

	@param [t:number] i The index of e
	@param [t:number] n The dimensionality of the vector

	@returns [t:(n x 1) matrix] The standard basis vector e_i in R^n
**--]]
linalg.vector.e = function(i, n)
	local rows = {}

	for j = 1, n do
		rows[j] = i == j and {1} or {0}
	end

	return _newMatrix(rows)
end

linalg.vector.norm = {}

--[[**
	The L1 norm of a vector
	sum_i{|v_i|}

	@param [t:(n x 1) matrix] v The vector

	@returns [t:number] The resulting value
**--]]
linalg.vector.norm.l1 = function(v)
	local sum = 0

	for i = 1, v.Shape[1] do
		sum = sum + abs(v[i][1])
	end

	return sum
end

--[[**
	The L2 norm of a vector
	sqrt(sum_i{(v_i)^2})

	@param [t:(n x 1) matrix] v The vector

	@returns [t:number] The resulting value
**--]]
linalg.vector.norm.l2 = function(v)
	local sum = 0

	for i = 1, v.Shape[1] do
		sum = sum + v[i]^2
	end

	return sqrt(sum)
end

--[[**
	The L-infinity norm of a vector
	max{v}

	@param [t:(n x 1) matrix] v The vector

	@returns [t:number] The resulting value
**--]]
linalg.vector.norm.linf = function(v)
	local maxComponentValue = 0

	for i = 1, v.Shape[1] do
		local componentValue = abs(v[i][1])
		if componentValue > maxComponentValue then
			maxComponentValue = componentValue
		end
	end

	return maxComponentValue
end

-- Inner product functions
linalg.vector.ip = {}

--[[**
	Computes the standard dot product of two vectors
	Defined as \sum_{i=0}^{n-1} v1[i] * v2[i]

	@param [t:(n x 1) matrix] v1 The first vector
	@param [t:(n x 1) matrix] v2 The second vector

	@returns [t:number] The result
**--]]
linalg.vector.ip.dot = function(v1, v2)
	if v1.Shape[1] ~= v2.Shape[1] then
		error("Cannot compute the dot product for vectors of unequal dimensions", 2)
	end
	if v1.Shape[2] ~= 1 or v2.Shape[2] ~= 1 then
		error("Cannot compute the dot product for non column vectors", 2)
	end

	local result = 0

	for i = 1, v1.Shape[1] do
		result = result + (v1[i] * v2[i])
	end

	return result
end

--[[**
	Projects vector v onto vector space u
	Defined as \sum_{i=0}^{m-1} <v, u[i]>/|u[i]|^2 * u[i]

	@param [t:(n x 1) matrix] v The vector to project onto u
	@param [t:array<(n x 1) matrix>] u The vector space to project v onto (can also be just one vector)
	@param [t:function([(n x 1) matrix], [(n x 1) matrix])?] innerProductFunc The inner product function to use; Defaults to the dot product
	@param [t:function([(n x 1) matrix])?] normFunc The norm function to use; Defaults to the L2 norm

	@returns The vector projection of v onto u
**--]]
linalg.vector.project = function (v, u, innerProductFunc, normFunc)
	if #u == 0 then -- u must be a single vector
		u = { u }
	end

	if u[1].Shape[1] ~= v.Shape[1] then
		error("Cannot project vectors of different dimensions", 2)
	end
	if u[1].Shape[2] ~= 1 or v.Shape[2] ~= 1 then
		error("Expected two vectors, not matrices", 2)
	end

	if not innerProductFunc then
		innerProductFunc = linalg.vector.ip.dot
	end
	if not normFunc then
		normFunc = linalg.vector.norm.l2
	end

	local result = linalg.matrix.zerosLike(v)

	for i = 1, #u do
		result = result + (innerProductFunc(v, u[i]) / normFunc(u[i])^2) * u[i]
	end

	return result
end

--[[**
	Gets the unit vector with the same direction as the provided vectr

	@param [t:(n x 1) matrix] v The vector with the appropriate direction
	@param [t:function?] normFunc The function to use as the norm; Defaults to the L2 norm

	@returns [t:(n x 1) matrix] The unit vector
**--]]
linalg.vector.unit = function(v, normFunc)
	if not normFunc then normFunc = linalg.vector.norm.l2 end
	return v / normFunc(v)
end

--[[**
	Creates a matrix that rotates a vector about an arbitrary vector
	Only works for 3 dimensions

	@param [t:(n x 1) matrix] v The vector to rotate about (should be a unit vector)
	@param [t:number] theta The angle to rotate by (in radians)

	@returns [t:nxn matrix] The resulting linear operator
**--]]
linalg.vector.createArbitraryAxisRotationMatrix = function(v, theta)
	if v.Shape[1] ~= 3 then
		error("Cannot create rotation matrix about arbitrary unit vector other than in R^3", 2)
	end

	local costheta = cos(theta)
	local sintheta = sin(theta)
	local versine = 1 - costheta
	local u = v.Unit

	return _newMatrix({
		{versine * u[1] ^ 2 + costheta, (versine * u[1] * u[2]) - (sintheta * u[3]), (versine * u[1] * u[3]) + (sintheta * u[2])},
		{(versine * u[1] * u[2]) + (sintheta * u[3]), versine * u[2] ^ 2 + costheta, (versine * u[2] * u[3]) - (sintheta * u[1])},
		{(versine * u[1] * u[3]) - (sintheta * u[2]), (versine * u[2] * u[3]) + (sintheta * u[1]), versine * u[3] ^ 2 + costheta}
	})
end

--[[ GENERAL FUNCTIONS ]]--
--[[**
	Creates an orthonormal basis for a dim-dimensional inner product space

	@param [t:array<(n x 1) matrix>] u The list of matrices to add to the basis (will be converted to unit vectors) (can be a single vector instead of an array)
	@param [t:number?] epsilon The minimum norm value for a vector to count to be added to the basis; Defaults to 0.01
	@param [t:function([(n x 1) matrix], [(n x 1) matrix])?] innerProductFunc The inner product function to use; Defaults to the dot product
	@param [t:function([(n x 1) matrix])?] normFunc The norm function to use; Defaults to the L2 norm

	@returns [t:array<(n x 1) matrix>] An orthonormal basis that includes the unit vectors of the original u
**--]]
linalg.gramSchmidt = function (u, epsilon, dim, innerProductFunc, normFunc)
	if #u == 0 then -- u must be just a single vector
		u = { u }
	end
	if not epsilon then
		epsilon = 0.01
	end
	if not dim then
		dim = u[1].Shape[1]
	end
	if not innerProductFunc then
		innerProductFunc = linalg.vector.ip.dot
	end
	if not normFunc then
		normFunc = linalg.vector.norm.l2
	end

	-- Normalize all elements in u
	local newU = {}
	for i = 1, #u do
		newU[i] = u[i].Unit
	end
	u = newU

	for i = 1, dim do
		local eiRows = {}
		for j = 1, dim do
			eiRows[j] = { i == j and 1 or 0 }
		end
		table.insert(u, _newMatrix(eiRows))
	end

	local basisVectors = { u[1].Unit }

	for i = 1, #u do
		local potentialBasisVector = u[i] - linalg.vector.project(u[i], basisVectors, innerProductFunc, normFunc)
		if normFunc(potentialBasisVector) >= epsilon then
			table.insert(basisVectors, potentialBasisVector.Unit)
		end

		if #basisVectors == dim then
			break
		end
	end

	return basisVectors
end
return linalg
INFO