Example 8.5.1 - Gram-Schmidt Orthonormalization

This notebook defines two functions, GramSchmidt and GramSchmidtVerbose, which perform the process of Gram-Schmidt orthonomalization outlined in example 8.5.1.  When you perform the first evaluation anywhere in this notebook, you will get a Mathematica dialog window to evaluate the cells containing the code and define the functions.  If you did not get this window or selected "no" in the window, you may define the functions by clicking on the collapsed cell below this paragraph (the bracket is small on the right edge of the notebook) and evaluating it.

GramSchmidt[vectors_]:=
Block[ {orthonormals,k,tempvector,j},
If[!MatrixQ[vectors], Print["Not all vectors have the same length."];Return[] ];
If[ !Union[ Map[NumericQ,Flatten[vectors]]]=={True},
Print[ "Not all the vector components are numeric"];Return[] ];
orthonormals={};
Do[
Which[ 
Length[orthonormals]==0 && Union[vectors[[k]]]≠{0}, AppendTo[orthonormals,vectors[[k]]/Simplify[Norm[vectors[[k]] ] ]] ,
Length[orthonormals]==0 && Union[vectors[[k]] ]=={0},True,
Union[ tempvector=FullSimplify[vectors[[k]]-Sum[ vectors[[k]].orthonormals[[j]] orthonormals[[j]],{j,1,Length[orthonormals]}]]]≠{0},AppendTo[orthonormals , tempvector/Simplify[Norm[tempvector]]],True,True],{k,1,Length[vectors]}];
Return[orthonormals]
];

GramSchmidtVerbose[vectors_]:=
Block[ {orthonormals,k,tempvector,j},
If[!MatrixQ[vectors], Print["Not all vectors have the same length."];Return[] ];
If[ !Union[ Map[NumericQ,Flatten[vectors]]]=={True},
Print[ "Not all the vector components are numeric"];Return[] ];
orthonormals={};
Do[
Which[ 
Length[orthonormals]==0 && Union[vectors[[k]]]≠{0}, AppendTo[orthonormals,vectors[[k]]/Simplify[Norm[vectors[[k]] ] ]] ;Print[ "The current orthonormal set is empty, adding the normaliztion of ", vectors[[k]], ", i.e.  ", vectors[[k]]/Simplify[Norm[vectors[[k]] ] ], " to the list."],
Length[orthonormals]==0 && Union[vectors[[k]] ]=={0},Print["The current orthonormal set is empty, but the zero vector cannot be normalized/added."],
Union[ tempvector=FullSimplify[vectors[[k]]-Sum[ vectors[[k]].orthonormals[[j]] orthonormals[[j]],{j,1,Length[orthonormals]}]]]≠{0},AppendTo[orthonormals , tempvector/Simplify[Norm[tempvector]]];
Print["The vector ",vectors[[k]], " is independent of the vectors in the current orthonormal set so taking away its projections onto those vectors and normalizing, adding ",tempvector/Simplify[Norm[tempvector]], " to the set."],True,Print["The vector ", vectors[[k]], "is dependent on the vectors currently in the orthonormal list, so no new vector is being added."]]
If[k<Length[vectors],Print[ "The current orthonormal set is ",orthonormals], Print["The final orthonormal set is"]],{k,1,Length[vectors]}];
Return[orthonormals]
];

Both commands take in a set of vectors and construct a new set of vectors from those which are of length one, pairwise orthogonal, and have the same span.  The input is a set of vectors, all of which must have the same length (i.e. the first vector cannot be 2-dimensional anf the second 3-dimensional).  In addition, each vector must be composed of numbers rather than variables (although vectors with complex numbers are allowed).  No assmuptions are made about linear dependence or independence.  The specific procedure used is as follows:

Given a set of vectors V, build an orthonormal set B from them by:

1) If the first vector of V is the zero vector, throw it out and start over.
2) If the first vector of V is nonzero, divide it by its length (i.e. normalize it) and add it to the set B.  Proceed to step 3.
3) For the next vector (OverVector[(Subscript[v, i])]) in V and B={(OverVector[(Subscript[b, 1])]), (OverVector[(Subscript[b, 2])]), .... (OverVector[(Subscript[b, j])])}, form the new vector (OverVector[b])= (OverVector[(Subscript[v, i])])-(∑_(k=1)^j)(Subscript[proj, (OverVector[(Subscript[b, k])])])((OverVector[(Subscript[v, i])])). If V has no more vectors, stop and return the set B as your orthnormal set.
4) If the vector (OverVector[b])=(OverVector[0]), add nothing to B (as (OverVector[(Subscript[v, i])]) was dependent on previous vectors), and go back to step 3 starting with  (OverVector[(Subscript[v, i+1])]).  
5) If the vector (OverVector[b])≠(OverVector[0]), normalize the vector (OverVector[b]) and add the normaized vector to the set B as  (OverVector[(Subscript[b, j+1])]) and return to step 3.

Some notes about GramScmidt and GramSchmidtVerbose:

1) The only difference between GramSchmidt and GramSchmidtVerbose is that the "verbose" version prints out information about each step of the process so you can follow along.
2) Both commands check to make sure the vectors contain only numbers and have the same dimension.
3) The result of the process is dependent on the order of the vectors (although you will always get an orthonormal set, the exact set you get can change if you switch vectors around).
4) If the vectors have exact numbers in their components, all of the arithmetic will be done exactly.  So for either larger sets of vectors or high-dimension vectors, expect lots of large square roots because of the normalizations.
5) If you give either command a set of zero vectors, an empty set will be returned.

Below are some examples of the commands:

GramSchmidt[ {{1,0,1}, {2,1,4}, {3,1,6}}]

GramSchmidt[ { {2,1,4}, {3,1,5},{1,0,1}}]

GramSchmidtVerbose[ {{1,2,1},{2,1,3},{3,3,4}}]

GramSchmidt[ {{0,0,0},{3,2,1},{1,4,6},{6,7,8},{2,1,2}}]

GramSchmidt[ {{1,0,I}, {2+I,3,1},{I,-I, 1+I}}]