% csc148, Spring 1996 % Solution program for Assignment 3 % --------------------------------------------------------------------------- % "factor.t" % % This file contains code for symbolically factoring a matrix as % outlined in the Assignment 4 handout. % % A matrix is stored as an array of rows. Each row is represented % by a list of row items. Each row item represents a non-zero value % in the matrix. % % Note: Both a first and last pointer is kept for each row. This % is because the procedure Cholesky always adds to the end of a row. % Having a last pointer takes advantage of this fact. type RowItem : record col : int next : ^RowItem end record type Row : record first, last : ^RowItem end record % The length on an output line. const lineLength : int := 80 % setValue(M, row, col) % - Adds an item (row,col) to matrix M. Duplicates are NOT added. procedure setValue (var M : array 1 .. * of Row, row, col : int) const n := upper (M) assert 1 <= row and row <= n and 1 <= col and col <= n % Add the row item at column 'col'. var RI : ^RowItem new RI assert RI not= nil RI -> col := col RI -> next := nil const first := M (row).first const last := M (row).last % Special cases: if first = nil then % First element ever. M (row).first := RI M (row).last := RI elsif col < first -> col then % New first element M (row).first := RI RI -> next := first elsif col > last -> col then % New last element last -> next := RI M (row).last := RI else % General case. % Look for insertion point var scan : ^RowItem := first loop exit when scan -> next -> col >= col scan := scan -> next end loop % Only insert if not already present. if scan -> next -> col not= col then RI -> next := scan -> next scan -> next := RI else free RI end if end if end setValue % obtainRow(M, row) % - This function returns a row of the matrix M. function obtainRow (M : array 1 .. * of Row, row : int) : Row const n := upper (M) assert 1 <= row and row <= n result M (row) end obtainRow % obtainRowItem(R, col) % - This function returns the row item at column 'col' if it % exists. Otherwise nil is returned. function obtainRowItem (R : Row, col : int) : ^RowItem if R.first = nil or col > R.last -> col then result nil end if % Look for requested row item. % Special case of looking for last item. if R.last not= nil and R.last -> col = col then result R.last end if % General case of searching from the beginning of the list. var scan : ^RowItem := R.first loop exit when scan = nil or scan -> col > col if scan -> col = col then result scan end if scan := scan -> next end loop % Requested item wasn't in the row. result nil end obtainRowItem % value(M, i, j) % - This function returns the (boolean) value of M(i,j). function value (M : array 1 .. * of Row, i, j : int) : boolean const n := upper (M) assert 1 <= i and i <= n and 1 <= j and j <= n % Value is true iff it is present result obtainRowItem (obtainRow (M, i), j) not= nil end value % initMatrix(M) % - This procedure initializes a matrix to all zero. It is % assumed that any allocated storage has already been freed. procedure initMatrix (var M : array 1 .. * of Row) const n := upper (M) for row : 1 .. n M (row).first := nil M (row).last := nil end for end initMatrix % freeRowItems(R) % - This procedure frees any storage used by a list of row items. % R is set to nil. procedure freeRowItems (var R : ^RowItem) loop exit when R = nil var tmp : ^RowItem := R R := R -> next free tmp end loop end freeRowItems % freeMatrix(M) % - This procedure frees any storage used by a matrix. It % is assumed that M has already been initialized. procedure freeMatrix (var M : array 1 .. * of Row) const n := upper (M) for row : 1 .. n freeRowItems (M (row).first) M (row).last := nil end for end freeMatrix % copyMatrix(M1, M2) % - This procedures copies a matrix M1 into M2. This procedure % initializes M2 before copying. procedure copyMatrix (M1 : array 1 .. * of Row, var M2 : array 1 .. * of Row) const n := upper (M1) assert n = upper (M2) initMatrix (M2) for row : 1 .. n var scan : ^RowItem := obtainRow (M1, row).first loop exit when scan = nil setValue (M2, row, scan -> col) scan := scan -> next end loop end for end copyMatrix % permuteSymmetrixMatrix(M1, M2, p) % - This procedure permutes a matrix M1 according to a permutation 'p' % and stores the result in M2. M2 is initialized before the permutation. % M1 is assumed to be symmetric and only the lower left triangle of M2 % is actually stored. procedure permuteSymmetricMatrix (M1 : array 1 .. * of Row, var M2 : array 1 .. * of Row, p : array 1 .. * of int) const n := upper (p) assert n = upper (M1) and n = upper (M2) initMatrix (M2) for row : 1 .. n const sourceRow := p (row) var scan : ^RowItem := obtainRow (M1, sourceRow).first loop exit when scan = nil const sourceColumn := scan -> col % Invert the permutation for col : 1 .. n if sourceColumn = p (col) then if (col <= row) then setValue (M2, row, col) else setValue (M2, col, row) end if exit end if end for scan := scan -> next end loop end for end permuteSymmetricMatrix % transposeMatrix(M1, M2) % - This procedure transposes a matrix M1 and stores the result in M2. % M2 is initialized before the transposition occurs. procedure transposeMatrix (M1 : array 1 .. * of Row, var M2 : array 1 .. * of Row) const n := upper (M1) assert n = upper (M2) initMatrix (M2) for row : 1 .. n var scan : ^RowItem := obtainRow (M1, row).first loop exit when scan = nil setValue (M2, scan -> col, row) scan := scan -> next end loop end for end transposeMatrix % removeUpperTriangle(M) % - This procedure frees the memory used by the upper triangular % part of the matrix. It is assumed that the matrix has a non-zero % diaganol. procedure removeUpperTriangle (M : array 1 .. * of Row) const n := upper (M) for i : 1 .. n var diagElement : ^RowItem := obtainRowItem (obtainRow (M, i), i) assert diagElement not= nil freeRowItems (diagElement -> next) end for end removeUpperTriangle % putRow(M, row) % - This procedure outputs the sparsity pattern of row 'row' of M. % No newlines are outputted. procedure putRow (M : array 1 .. * of Row, row : int) const n := upper (M) assert 1 <= row and row <= n var numPrinted := 0 var scan : ^RowItem := obtainRow (M, row).first loop exit when scan = nil put repeat (" ", scan -> col - numPrinted - 1) .. put "*" .. numPrinted := scan -> col scan := scan -> next end loop put repeat (" ", n - numPrinted) .. end putRow % putSymmetricRow(M, row) % - This procedure outputs the sparsity pattern of row 'row' of M. % No newlines are outputted. M is assumed to be symmetric so only % the lower left triangle is accessed. procedure putSymmetricRow (M : array 1 .. * of Row, row : int) const n := upper (M) assert 1 <= row and row <= n var numPrinted := 0 % First print columns <= row. var scan : ^RowItem := obtainRow (M, row).first loop exit when scan = nil or scan -> col > row put repeat (" ", scan -> col - numPrinted - 1) .. put "*" .. numPrinted := scan -> col scan := scan -> next end loop put repeat (" ", row - numPrinted) .. % Now print remaining columns for col : row + 1 .. n if value (M, col, row) then put "*" .. else put " " .. end if end for end putSymmetricRow % putMatrix(M) % - This procedure outputs the sparsity pattern of M. procedure putMatrix (M : array 1 .. * of Row) const n := upper (M) for i : 1 .. n putRow (M, i) put "" end for end putMatrix % putOutputMatrices(M, L, Lt) % - This procedure outputs a matrix M, its factor L and L transpose % as per the example in Figure 2 of the handout. M is assumed to be % symmetric and only the lower left triangle is accessed. procedure putOutputMatrices (M, L, Lt : array 1 .. * of Row) const indentAmount := 6 const firstSeparationAmount := 8 const secondSeparationAmount := 4 const n := upper (M) assert n = upper (L) and n = upper (Lt) for i : 1 .. n put repeat (" ", indentAmount) .. putSymmetricRow (M, i) put repeat (" ", firstSeparationAmount) .. putRow (L, i) put repeat (" ", secondSeparationAmount) .. putRow (Lt, i) put "" end for end putOutputMatrices % putBanner(message) % - This procedure outputs a message centered on the line and % surrounded by dashes. Two spaces are placed before and after the % message. The output line is two less than maximum. This procedure % uses the global constant 'lineLength'. procedure putBanner (message : string) const messageLength := length (message) const dashLength := (lineLength - messageLength - 6) div 2 put repeat ("-", dashLength) .. put " ", message, " " .. put repeat ("-", dashLength) end putBanner % putPermutation(P) % - This procedure outputs a permutation P. procedure putPermutation (P : array 1 .. * of int) const n := upper (P) for i : 1 .. upper (P) put " ", P (i) .. end for put "" end putPermutation % doIntersect(rowA, rowB) : boolean % - This function returns true iff rowA and rowB both % have items in some column. It is basically a merge (see % mergesort, p. 304 in your notes). function doIntersect (rowA, rowB : Row) : boolean var scanA : ^RowItem := rowA.first var scanB : ^RowItem := rowB.first loop exit when scanA = nil or scanB = nil if scanA -> col = scanB -> col then result true elsif scanA -> col < scanB -> col then scanA := scanA -> next else % scanA->col > scanB->col then scanB := scanB -> next end if end loop result false end doIntersect % symbolicCholeskyRow(M, L) % - This procedure symbolically factors a matrix M and % stores the lower triangular factor in L. This version % is row-oriented in that it processes a row at a time. The % handout version of Cholesky processes a column at a time. % % The matrix L is initialized before the factoring begins. procedure symbolicCholeskyRow (M : array 1 .. * of Row, var L : array 1 .. * of Row) const n := upper (M) assert n = upper (L) initMatrix (L) for i : 1 .. n var scan : ^RowItem := obtainRow (M, i).first for j : 1 .. i - 1 if scan not= nil and scan -> col = j then setValue (L, i, j) scan := scan -> next elsif doIntersect (L (i), L (j)) then setValue (L, i, j) end if end for setValue (L, i, i) end for end symbolicCholeskyRow % Column-wise version. procedure symbolicCholeskyCol (M : array 1 .. * of Row, var L : array 1 .. * of Row) const n := upper (M) assert n = upper (L) initMatrix (L) for j : 1 .. n for i : j + 1 .. n if value (M, i, j) or doIntersect (L (i), L (j)) then setValue (L, i, j) end if end for setValue (L, j, j) end for end symbolicCholeskyCol % getRow(M, row) % - This procedure inputs row 'row' of M according to the format % specified by the handout. M is assumed to be initialized. procedure getRow (var M : array 1 .. * of Row, row : int) const n := upper (M) assert 1 <= row and row <= n var line : string assert not eof get line : * assert length (line) = n for col : 1 .. n if line (col) = "*" then setValue (M, row, col) elsif line (col) not= " " then assert false % Can't happen end if end for end getRow % testMatrix(M, choleskyCandidate, error) % - This procedure tests a sparsity pattern to see if it is a % valid candidate for factoring. See the handout for details. M is % assumed to be initialized. procedure testMatrix (M : array 1 .. * of Row, var choleskyCandidate : boolean, var reasonForRejection : string) const n := upper (M) for i : 1 .. n % Diaganol elements must be present if not value (M, i, i) then reasonForRejection := "Pattern has a zero on the diaganol." choleskyCandidate := false return end if % Matrix must be symmetric for j : 1 .. i - 1 if value (M, i, j) not= value (M, j, i) then reasonForRejection := "Pattern is not symmetric." choleskyCandidate := false return end if end for end for reasonForRejection := "" choleskyCandidate := true end testMatrix % testSymbolicFactoring % - This procedure tests the symbolic factorization procedure % and is based on the code-structure at the bottom of page 4 of % the handout. procedure testSymbolicFactoring var patternNumber : int := 0 loop exit when eof patternNumber += 1 var n, p : int var canFactorMatrix : boolean var reasonWhyNot : string get n var S : array 1 .. n of Row initMatrix (S) % Read in sparsity pattern for row : 1 .. n getRow (S, row) end for putBanner ("sparsity pattern " + intstr (patternNumber) + " start") % Test if pattern can be factored. testMatrix (S, canFactorMatrix, reasonWhyNot) if not canFactorMatrix then put "Pattern cannot be factored." put reasonWhyNot else % We don't need upper triangular part of the matrix % after testing symmetry. removeUpperTriangle (S) end if % Read in number of permutations. get p for : 1 .. p var permutation : array 1 .. n of int for i : 1 .. n get permutation (i) % Check validity of permutation assert 1 <= i and i <= n for j : 1 .. i - 1 assert permutation (i) not= permutation (j) end for end for if canFactorMatrix then var Scopy, L, Lt : array 1 .. n of Row permuteSymmetricMatrix (S, Scopy, permutation) symbolicCholeskyRow (Scopy, L) transposeMatrix (L, Lt) putPermutation (permutation) putOutputMatrices (Scopy, L, Lt) put "" % Free allocated memory freeMatrix (Scopy) freeMatrix (L) freeMatrix (Lt) end if end for freeMatrix (S) putBanner ("sparsity pattern " + intstr (patternNumber) + " end") end loop end testSymbolicFactoring %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % Main program testSymbolicFactoring