# Library for dealing with bilinear forms, and related topics like
# angles of vectors, GramSchmidt, etc.

# Evaluate (v,w) with respect to the bilinear form given by the matrix A.
#FIXME: check parameters
SetHelp ("BilinearForm", "linear_algebra", "Evaluate (v,w) with respect to the bilinear form given by the matrix A")
function BilinearForm(v,A,w) = (v.'*A*w)@(1)

# Evaluate (v,w) with respect to the sesquilinear form given by the matrix A.
#FIXME: check parameters
SetHelp ("SesquilinearForm", "linear_algebra", "Evaluate (v,w) with respect to the sesquilinear form given by the matrix A")
function SesquilinearForm(v,A,w) = (v'*A*w)@(1)

# The angle of two vectors, given an inner product
#FIXME: default inner product!
#FIXME: check parameters
SetHelp ("VectorAngle", "linear_algebra", "The angle of two vectors, given an inner product")
function VectorAngle(v,w,B...) =
  (
        # if you don't give anything, assume standard inner product
	if IsNull(B) or IsNull(B@(1)) then P=HermitianProduct
	# If it's a matrix, then use the sesquilinear form defined by that matrix
	else if IsMatrix(B@(1)) then (P = SesquilinearFormFunction(u,B@(1),w))
	# If B is just some function, assume that it's an inner product
        else if elements(B) > 1 or not IsFunction(B@(1)) then (error("VectorAngle: argument B not a function or Matrix");bailout)
	else P = B@(1);
 	
	acos(P(v,w)/(P(v,v)*P(w,w)))
)

#FIXME: check parameters
SetHelp ("BilinearFormFunction", "linear_algebra", "Return a function that evaluates two vectors with respect to the bilinear form given by A")
function BilinearFormFunction(A)=(`(v,w)[A]=(v.'*A*w)@(1))

SetHelp ("SesquilinearFormFunction", "linear_algebra", "Return a function that evaluates two vectors with respect to the sesquilinear form given by A")
function SesquilinearFormFunction(A)=(`(v,w)[A]=(v'*A*w)@(1))

# Projection onto a vector space
# Projection of vector v onto subspace W given a sesquilinear form B
SetHelp ("Projection", "linear_algebra", "Projection of vector v onto subspace W given a sesquilinear form B (if not given use Hermitian product)")
function Projection(v,W,B...) =
  (
        # if you don't give anything, assume standard inner product
	if IsNull(B) or IsNull(B@(1)) then P=HermitianProduct
	# If it's a matrix, then use the sesquilinear form defined by that matrix
	else if IsMatrix(B@(1)) then (P = SesquilinearFormFunction(u,B@(1),w))
	# If B is just some function, assume that it's an inner product
        else if elements(B) > 1 or not IsFunction(B@(1)) then (error("Projection: argument B not a function or Matrix");bailout)
	else P = B@(1);

	sum c in ColumnsOf(W) do (
		(P(v,c)/P(c,c))*c
	)
  )

# Gram-Schmidt Orthogonalization
# Described, for instance, in Hoffman & Kunze, ``Linear Algebra'' p.~280
SetHelp ("GramSchmidt", "linear_algebra", "Apply the Gram-Schmidt process (to the columns) with respect to inner product given by B (if not given use Hermitian product)")
function GramSchmidt(v,B...) =
# Takes k column vectors v_1,...,v_k
# and returns a collection, orthonormal with respect to the inner product given by B(-,-): V x V -> R
# Note: if you plug in a bad function or a matrix that isn't symmetric and
# positive definite, then you'll get garbage as a result.
	(
        # if you don't give anything, assume standard inner product
	if IsNull(B) or IsNull(B@(1)) then P=HermitianProduct
	# If it's a matrix, then use the sesquilinear form defined by that matrix
	else if IsMatrix(B@(1)) then (function P(u,w)[B]=SesquilinearForm(u,B@(1),w))
	# If B is just some function, assume that it's an inner product
        else if elements(B) > 1 or not IsFunction(B@(1)) then (error("GramSchmidt: argument B not a function or Matrix");bailout)
	else P = B@(1);

#FIXME: this is not the most stable algorithm
	w=v@(,1);			# First vector
	w=w/sqrt(P(w,w));
	for i = 2 to columns(v) do (	# now iterate
		ww = v@(,i)-Projection(v@(,i),w,P);
		w = [w, ww / sqrt (P(ww,ww))]
	);
	w
	)
