Posted
: April 18, 2019
Status
:
Completed
Tags
:
Gram-Schmidt
Coding
Fortran
Categories
:
Code-Snippets
Instead of re-inventing the wheel, I googled the code for the Gram-Schmidt process. The first result is a note from MIT, on how to code the G-S process in MATLAB.
The code is pretty straight forward.
A
stores n
column vectors. Q
stores the normalized orthogonal vectors, and R
is the $n\times n$ matrix such that $A=Q\cdot R$.
for j=1:n
v=A(:,j);
for i=1:j-1
R(i,j)=Q(:,i)'*A(:,j); % change A(:,j) to v for more accuracy
v=v-R(i,j)*Q(:,i);
end
R(j,j)=norm(v);
Q(:,j)=v/R(j,j);
end
But when I coded it in Fortran, weird things start to happen.
do j=1,7
vec=Amat(:,j)
do i=1,j-1
Rmat(i,j) = dot_product(Qmat(:,i),vec(:))
vec(:)=vec(:)-Rmat(i,j)*Qmat(:,i) ! subtract the projection
end do ! now vec the perpendicular to all Q(:,i)
Rmat(j,j) = cdsqrt(dot_product(vec(:),vec(:)))
Qmat(:,j)= vec(:)/Rmat(j,j) ! normalize
end do
The above code does not give the expected result.
Moreover, when I check the rank of the Amat
, it gives 3. But after orthogonalization, the rank of Qmat
is 7!
call zgesvd("N", "N", dm, 7, Amat, dm, sing, Umat, dm, vt, 7, Awork, 25*dm, Arwork, inf)
write(*,*) "************************Rank****************************"
rank = 0
do i=1,7
if (sing(i).ge. 0.05d0) rank = rank +1
end do
write(*,*) inf
This is when I started to question everything. Is it that the zgesvd
not for calculating the rank? Or is it that the process is not as such? I checked and checked everything again and again, making unit tests, giving it dummy input and everything seems fine. But the inconsistency keeps arising.
I finally realized that the problem arises when the input are “realistic”, i.e., with limited precisions. When the inputs are “nice” such as
\[\begin{pmatrix} 1+\imath \\ 2\\ 3 \\0\end{pmatrix} \cdots\]the results are correct anyway. But once you apply the above code to vectors such as
\[\scriptsize\begin{pmatrix} \vdots \\ -5.958120542697545\times10^{-2}+3.458921825623937\times10^{-2}\imath\\ -0.140304539565186-6.689129223560432E-003\imath \\ -0.108080108742333+4.045132235649551E-002\imath \\ -3.146446494299783\times10^{-2}-5.166562823556472\times10^{-3}\imath \\ \vdots \end{pmatrix} \cdots\]even if the two vectors are practically linearly dependent, the error in calculation will result in a tiny non-zero vector, which will later be normalized regardless of it’s magnitude. To avoid this, check abs(dot_product(vec(:),vec(:))
to remove such problem.
do j=1,7
vec(:)=Amat(:,j)
if (j.gt.1) then
do i=1,j-1
vec(:)=vec(:)-dot_product(Qmat(:,i),vec(:))*Qmat(:,i) ! subtract the projection
end do ! now vec the perpendicular to all Q(:,i)
end if
if (abs(dot_product(vec(:),vec(:))).le.0.01) then
Qmat(:,j)=vec(:)
else
Qmat(:,j)= vec(:)/sqrt(dot_product(vec(:),vec(:))) ! normalize
end if
end do
Now the code gives the correct result.
See this thread.
I used to read the different coding style between industry-grade code and academic-project code. I have always taken pride in my code and considered the industrial way of coding ugly and ungraceful. I actually considered myself quite aware of errors by machine precisions, and have applied quite many “academic” ways to cope with errors anyway. Besides, I have never really met any problem that specifically requires me to address such error.
I fell into the pitfall mainly because
In hindsight, I should have realized the code from note from MIT is purely academic-project grade code. You can see the example in that code. The example actually applies the code to
\[A=\begin{pmatrix}4 & -2\\3 & 1\end{pmatrix}.\]I wouldn’t say that my version of G-S is industrial grade, but this is surely a lesson: actual programs’ behaviors are much more complicated and a pure “theoretical” way of doing it might not work. Do not ever forget the error even if someone might have done the dirty work for you.