############################################################################
#
#	File:     lu.icn
#
#	Subject:  Procedures for LU manipulation
#
#	Author:   Ralph E. Griswold
#
#	Date:     August 14, 1996
#
############################################################################
#
#   This file is in the public domain.
#
############################################################################
#
#  lu_decomp(M, I) performs LU decomposition on the square matrix M
#  using the vector I.  Both M and I are modified in the process.  The
#  value returned is +1 or -1 depending on whether the number of row
#  interchanges is even or odd.  lu_decomp() is used in combination with
#  lu_back_sub() to solve linear equations or invert matrices.
#
#  lu_decomp() fails if the matrix is singular.
#
#  lu_back_sub(M, I, B) solves the set of linear equations M x X = B.  M
#  is the matrix as modified by lu_decomp().  I is the index vector
#  produced by lu_decomp().  B is the right-hand side vector and return
#  with the solution vector. M and I are not modified by lu_back_sub()
#  and can be used in successive calls of lu_back_sub() with different
#  Bs.
#
############################################################################
#
#  Acknowledgement:  These procedures are based on algorithms given in
#  "Numerical Recipes; The Art of Scientific Computing"; William H. Press,
#  Brian P. Flannery, Saul A. Teukolsky. and William T. Vetterling;
#  Cambridge University Press, 1986.
#
############################################################################

procedure lu_decomp(M, I)
   local small, d, n, vv, i, largest, j, sum, k, pivot_val, imax

   initial small := 1.0e-20

   d := 1.0

   n := *M
   if n ~= *M[1] then stop("*** non-square matrix")
   if n ~= *I then stop("*** index vector incorrect length")

   vv := list(n, 0.0)			# scaling vector

   every i := 1 to n do {
      largest := 0.0
      every j := 1 to n do
         largest <:= abs(M[i][j])
      if largest = 0.0 then fail		# matrix is singular
      vv[i] := 1.0 / largest
      }

   every j := 1 to n do {			# Crout's method
      if j > 1 then {
         every i := 1 to j - 1 do {
            sum := M[i][j]
            if i > 1 then {
               every k := 1 to i - 1 do
                   sum -:= M[i][k] * M[k][j]
               M[i][j] := sum
               }
            }
         }

      largest := 0.0				# search for largest pivot
      every i := j to n do {
         sum := M[i][j]
         if j > 1 then {
            every k := 1 to j - 1 do
               sum -:= M[i][k] * M[k][j]
            M[i][j] := sum
            }
         pivot_val := vv[i] * abs(sum)
         if pivot_val > largest then {
            largest := pivot_val
            imax := i
            }
         }

      if j ~= imax then {			# interchange rows?
         every k := 1 to n do {
            pivot_val := M[imax][k]
            M[imax][k] := M[j][k]
            M[j][k] := pivot_val
            }
         d := -d				# change parity
         vv[imax] := vv[j]			# and scale factor
         }
      I[j] := imax
      if j ~= n then {				# divide by the pivot element
         if M[j][j] = 0.0 then M[j][j] := small	# small value is better than
         pivot_val := 1.0 / M[j][j]		# zero for some applications
         every i := j + 1 to n do
            M[i][j] *:= pivot_val
         }
      }

      if M[n][n] = 0.0 then M[n][n] := small

      return d

end

procedure lu_back_sub(M, I, B)
   local n, ii, i, ip, sum, j

   n := *M
   if n ~= *M[1] then stop("*** matrix not square")
   if n ~= *I then stop("*** index vector wrong length")
   if n ~= *B then stop("*** output vector wrong length")

   ii := 0

   every i := 1 to n do {
      ip := I[i] | stop("failed in line ", &line)
      sum := B[ip] | stop("failed in line ", &line)
      B[ip] := B[i] | stop("failed in line ", &line)
      if ii ~= 0 then
         every j := ii to i - 1 do
            sum -:= M[i][j] * B[j] | stop("failed in line ", &line)
      else if sum ~= 0.0 then ii := i
      B[i] := sum | stop("failed in line ", &line)
      }
   every i := n to 1 by -1 do {
      sum := B[i] | stop("failed in line ", &line)
      if i < n then {
         every j := i + 1 to n do
            sum -:= M[i][j] * B[j] | stop("failed in line ", &line)
         }
      B[i] := sum / M[i][i] | stop("failed in line ", &line)
      }

   return

end
