c======================================================================= c=== RBio/RBwrite ====================================================== c======================================================================= c RBio: a MATLAB toolbox for reading and writing sparse matrices in c Rutherford/Boeing format. c Copyright (c) 2006, Timothy A. Davis, Univ. Florida. Version 1.0. c----------------------------------------------------------------------- c RBkind: determine the type of a MATLAB matrix c----------------------------------------------------------------------- c c input: a zero-based MATLAB sparse matrix c c nrow number of rows of A c ncol number of columns of A c Ap size ncol+1, column pointers c Ai size nnz, row indices (nnz = Ap (ncol+1)) c Ax size nnz, real values c Az size nnz, imaginary values (not accessed if A is real) c cmplex 1 if A is complex, 0 otherwise c c output: c mkind: r: 0 (real), p: 1 (pattern), c: 2 (complex), c i: 3 (integer) c skind: r: -1 (rectangular), u: 0 (unsymmetric), s: 1 symmetric, c h: 2 (Hermitian), z: 3 (skew symmetric) c c workspace: c munch size ncol+1, not defined on input or output c c Note that the MATLAB matrix is zero-based (Ap and Ai). 1 must be c added whenever they are used (see "1+" in the code below). c c See also SuiteSparse/CHOLMOD/MatrixOps/cholmod_symmetry.c, which c also determines if the diagonal is positive. c----------------------------------------------------------------------- subroutine RBkind (nrow, ncol, Ap, Ai, Ax, Az, $ cmplex, mkind, skind, mtype, nnz, munch, kmin, kmax) integer $ nrow, ncol, Ap (ncol+1), Ai (*), cmplex, mkind, skind, $ munch (ncol+1), p, i, j, pt, nnz, k, kmin, kmax double precision Ax (*), Az (*), x_r, x_i, xtr, xti logical is_p, is_s, is_h, is_z, is_int character mtype*3 c ---------------------------------------------------------------- c determine numeric type (I*A, R*A, P*A, C*A) c ---------------------------------------------------------------- c pattern: if real and all entries are 1. c integer: if real and all entries are integers. c complex: if cmplex is 1. c real: otherwise. nnz = 1+ (Ap (ncol+1) - 1) kmin = 0 kmax = 0 if (cmplex .eq. 1) then c complex matrix (C*A) mtype (1:1) = 'c' mkind = 2 else c select P** format if all entries are equal to 1 c select I** format if all entries are integer and c between -99,999,999 and +999,999,999 is_p = .true. is_int = .true. k = dint (Ax (1)) kmin = k kmax = k do 10 p = 1, nnz if (Ax (p) .ne. 1) then is_p = .false. endif k = dint (Ax (p)) kmin = min (kmin, k) kmax = max (kmax, k) if (k .ne. Ax (p)) then is_int = .false. endif if (k .le. -99999999 .or. k .ge. 999999999) then c use real format for really big integers is_int = .false. endif if (.not. is_int .and. .not. is_p) then goto 20 endif 10 continue 20 continue if (is_p) then c pattern-only matrix (P*A) mtype (1:1) = 'p' mkind = 1 elseif (is_int) then c integer matrix (I*A) mtype (1:1) = 'i' mkind = 3 else c real matrix (R*A) mtype (1:1) = 'r' mkind = 0 endif endif c only assembled matrices are handled mtype (3:3) = 'a' c ---------------------------------------------------------------- c determine symmetry (*RA, *UA, *SA, *HA, *ZA) c ---------------------------------------------------------------- c Note that A must have sorted columns for this method to work. c This is not checked, since all MATLAB matrices "should" have c sorted columns. Use spcheck(A) to check for this, if needed. if (nrow .ne. ncol) then c rectangular matrix (*RA), no need to check values or pattern mtype (2:2) = 'r' skind = -1 return endif c if complex, the matrix is Hermitian until proven otherwise is_h = (cmplex .eq. 1) c the matrix is symmetric until proven otherwise is_s = .true. c a non-pattern matrix is skew symmetric until proven otherwise is_z = (mkind .ne. 1) c if this method returns early, the matrix is unsymmetric mtype (2:2) = 'u' skind = 0 c initialize the munch pointers do 30 j = 1, ncol munch (j) = 1+ (Ap (j)) 30 continue do 50 j = 1, ncol c consider all entries not yet munched in column j do 40 p = munch (j), 1+ (Ap (j+1)-1) i = 1+ (Ai (p)) if (i .lt. j) then c entry A(i,j) is unmatched, matrix is unsymmetric return endif c get the A(j,i) entry, if it exists pt = munch (i) c munch the A(j,i) entry munch (i) = pt + 1 if (pt .ge. 1+ (Ap (i+1))) then c entry A(j,i) doesn't exist, matrix unsymmetric return endif if (1+ (Ai (pt)) .ne. j) then c entry A(j,i) doesn't exist, matrix unsymmetric return endif c A(j,i) exists; check its value with A(i,j) if (cmplex .eq. 1) then c get A(i,j) x_r = Ax (p) x_i = Az (p) c get A(j,i) xtr = Ax (pt) xti = Az (pt) if (x_r .ne. xtr .or. x_i .ne. xti) then c the matrix cannot be *SA is_s = .false. endif if (x_r .ne. -xtr .or. x_i .ne. -xti) then c the matrix cannot be *ZA is_z = .false. endif if (x_r .ne. xtr .or. x_i .ne. -xti) then c the matrix cannot be *HA is_h = .false. endif else c get A(i,j) x_r = Ax (p) c get A(j,i) xtr = Ax (pt) if (x_r .ne. xtr) then c the matrix cannot be *SA is_s = .false. endif if (x_r .ne. -xtr) then c the matrix cannot be *ZA is_z = .false. endif endif if (.not. (is_s .or. is_z .or. is_h)) then c matrix is unsymmetric; terminate the test return endif 40 continue 50 continue c ---------------------------------------------------------------- c return the symmetry c ---------------------------------------------------------------- if (is_h) then c Hermitian matrix (*HA) mtype (2:2) = 'h' skind = 2 elseif (is_s) then c symmetric matrix (*SA) mtype (2:2) = 's' skind = 1 elseif (is_z) then c skew symmetric matrix (*ZA) mtype (2:2) = 'z' skind = 3 endif return end c----------------------------------------------------------------------- c RBformat: determine the format required for an array of values c----------------------------------------------------------------------- c c This function ensures that a sufficiently wide format is used that c can accurately represent the data. It also ensures that when printed, c the numerical values all have at least one blank space between them. c This makes it trivial for a program written in C (say) to read in a c matrix generated by RBwrite. c ww, valfmt, valn, and is_int must be defined on input. They c are modified on output. c----------------------------------------------------------------------- subroutine RBformat (nnz, x, ww, valfmt, valn, is_int, $ kmin, kmax) integer $ nnz, i, ww, k, nf (18), valn, nd (9), kmin, kmax double precision x (nnz), e, a, b logical is_int character*20 f (18), d (9), valfmt character*80 s c ---------------------------------------------------------------- c define all possible formats c ---------------------------------------------------------------- f (1) = '(8E9.1) ' f (2) = '(8E10.2) ' f (3) = '(7E11.3) ' f (4) = '(6E12.4) ' f (5) = '(6E13.5) ' f (6) = '(5E14.6) ' f (7) = '(5E15.7) ' f (8) = '(5E16.8) ' f (9) = '(4E17.9) ' f (10) = '(4E18.10) ' f (11) = '(4E19.11) ' f (12) = '(4E20.12) ' f (13) = '(3E21.13) ' f (14) = '(3E22.14) ' f (15) = '(3E23.15) ' f (16) = '(3E24.16) ' f (17) = '(3E25.17) ' f (18) = '(3E26.18) ' nf (1) = 8 nf (2) = 8 nf (3) = 7 nf (4) = 6 nf (5) = 6 nf (6) = 5 nf (7) = 5 nf (8) = 5 nf (9) = 4 nf (10) = 4 nf (11) = 4 nf (12) = 4 nf (13) = 3 nf (14) = 3 nf (15) = 3 nf (16) = 3 nf (17) = 3 nf (18) = 3 if (is_int) then c ------------------------------------------------------------ c use an integer format c ------------------------------------------------------------ call RBiformat (kmin, kmax, valfmt, valn, ww) else c ------------------------------------------------------------ c determine if the matrix has huge values or NaN's c ------------------------------------------------------------ do 10 i = 1, nnz a = abs (x (i)) if (a .ne. 0) then if (a .ne. a .or. a < 1e-90 .or. a > 1e90) then ww = 18 valfmt = '(2E30.18E3) ' valn = 2 return endif endif 10 continue c ------------------------------------------------------------ c find the required precision for a real or complex matrix c ------------------------------------------------------------ do 20 i = 1, nnz a = x (i) do 30 k = ww,18 c write the value to a string, read back in, and check write (unit = s, fmt = f(k)) a read (unit = s, fmt = f(k)) b if (a .eq. b) then ww = max (ww, k) goto 40 endif 30 continue 40 continue ww = max (ww, k) 20 continue c valn is the number of entries per line valfmt = f (ww) valn = nf (ww) endif return end c----------------------------------------------------------------------- c RBwrite: write portions of the matrix to the file c----------------------------------------------------------------------- c c task 0: just count the total number of entries in the matrix c task 1: do task 0, and also construct w and cp c task 2: write the row indices c task 3: write the numerical values c c Note that the MATLAB arrays A and Z are zero-based. "1+" is added c to each use of Ap, Ai, Zp, and Zi. c----------------------------------------------------------------------- subroutine RBwrite (task, nrow, ncol, skind, cmplex, doZ, Ap, $ Ai, Ax, Az, Zp, Zi, mkind, $ indfmt, indn, valfmt, valn, nnz, w, cp) integer $ task, nrow, ncol, Ap (*), Ai (*), Zp (*), Zi (*), $ cp (*), w (*), nnz, znz, ibuf (80), j, i, nbuf, pa, pz, $ cmplex, paend, pzend, ia, iz, skind, indn, valn, p, mkind logical doZ double precision xbuf (80), xr, xi, Ax (*), Az (*) character valfmt*20, indfmt*20 c ---------------------------------------------------------------- c determine number of entries in Z c ---------------------------------------------------------------- if (doZ) then znz = 1+ (Zp (ncol+1) - 1) else znz = 0 endif c clear the nonzero counts nnz = 0 do 10 j = 1, ncol w (j) = 0 10 continue c start with an empty buffer nbuf = 0 if (znz .eq. 0) then c ------------------------------------------------------------ c no Z present c ------------------------------------------------------------ do 30 j = 1, ncol do 20 pa = 1+ (Ap (j)), 1+ (Ap (j+1) - 1) i = 1+ (Ai (pa)) xr = Ax (pa) if (cmplex .eq. 1) then xi = Az (pa) endif if (skind .le. 0 .or. i .ge. j) then c consider the (i,j) entry with value (xr,xi) nnz = nnz + 1 if (task .eq. 1) then c only determining nonzero counts w (j) = w (j) + 1 elseif (task .eq. 2) then c printing the row indices call RBiprint (indfmt, ibuf, nbuf, i, indn) elseif (task .eq. 3) then c printing the numerical values call RBxprint (valfmt, xbuf, nbuf, xr, $ valn, mkind) if (cmplex .eq. 1) then call RBxprint (valfmt, xbuf, nbuf, xi, $ valn, mkind) endif endif endif 20 continue 30 continue else c ------------------------------------------------------------ c symmetric, unsymmetric or rectangular matrix, with Z present c ------------------------------------------------------------ do 40 j = 1, ncol c find the set union of A (:,j) and Z (:,j) pa = 1+ (Ap (j)) pz = 1+ (Zp (j)) paend = 1+ (Ap (j+1) - 1) pzend = 1+ (Zp (j+1) - 1) c while entries appear in A or Z 70 continue c get the next entry from A(:,j) if (pa .le. paend) then ia = 1+ (Ai (pa)) else ia = 1+ nrow endif c get the next entry from Z(:,j) if (pz .le. pzend) then iz = 1+ (Zi (pz)) else iz = 1+ nrow endif c exit loop if neither entry is present if (ia .gt. nrow .and. iz .gt. nrow) goto 80 if (ia .lt. iz) then c get A (i,j) i = ia xr = Ax (pa) if (cmplex .eq. 1) then xi = Az (pa) endif pa = pa + 1 else if (iz .lt. ia) then c get Z (i,j) i = iz xr = 0 xi = 0 pz = pz + 1 else c get A (i,j), and delete its matched Z(i,j) i = ia xr = Ax (pa) if (cmplex .eq. 1) then xi = Az (pa) endif pa = pa + 1 pz = pz + 1 endif if (skind .le. 0 .or. i .ge. j) then c consider the (i,j) entry with value (xr,xi) nnz = nnz + 1 if (task .eq. 1) then c only determining nonzero counts w (j) = w (j) + 1 elseif (task .eq. 2) then c printing the row indices call RBiprint (indfmt, ibuf, nbuf, i, indn) elseif (task .eq. 3) then c printing the numerical values call RBxprint (valfmt, xbuf, nbuf, xr, $ valn, mkind) if (cmplex .eq. 1) then call RBxprint (valfmt, xbuf, nbuf, xi, $ valn, mkind) endif endif endif goto 70 c end of while loop 80 continue 40 continue endif c ---------------------------------------------------------------- c determine the new column pointers, or finish printing c ---------------------------------------------------------------- if (task .eq. 1) then cp (1) = 1 do 100 j = 2, ncol+1 cp (j) = cp (j-1) + w (j-1) 100 continue else if (task .eq. 2) then call RBiflush (indfmt, ibuf, nbuf) elseif (task .eq. 3) then call RBxflush (valfmt, xbuf, nbuf, mkind) endif return end c----------------------------------------------------------------------- c RBiprint: print a single integer to the file, flush buffer if needed c----------------------------------------------------------------------- subroutine RBiprint (indfmt, ibuf, nbuf, i, indn) character indfmt*20 integer $ ibuf (80), nbuf, i, indn if (nbuf .ge. indn) then call RBiflush (indfmt, ibuf, nbuf) nbuf = 0 endif nbuf = nbuf + 1 ibuf (nbuf) = i return end c----------------------------------------------------------------------- c RBiflush: flush the integer buffer to the file c----------------------------------------------------------------------- subroutine RBiflush (indfmt, ibuf, nbuf) character indfmt*20 integer $ ibuf (*), nbuf, k write (unit = 7, fmt = indfmt, err = 999) (ibuf (k), k = 1,nbuf) return 999 call mexErrMsgTxt ('error writing ints') return end c----------------------------------------------------------------------- c RBxprint: print a single real to the file, flush the buffer if needed c----------------------------------------------------------------------- subroutine RBxprint (valfmt, xbuf, nbuf, x, valn, mkind) character valfmt*20 integer $ nbuf, valn, mkind double precision xbuf (80), x if (nbuf .ge. valn) then call RBxflush (valfmt, xbuf, nbuf, mkind) nbuf = 0 endif nbuf = nbuf + 1 xbuf (nbuf) = x return end c----------------------------------------------------------------------- c RBxflush: flush the real buffer to the file c----------------------------------------------------------------------- subroutine RBxflush (valfmt, xbuf, nbuf, mkind) character valfmt*20 integer $ nbuf, k, ibuf (80), mkind double precision xbuf (80) if (mkind .eq. 3) then c convert to integer first; valfmt is (10I8), for example do 10 k = 1,nbuf ibuf (k) = dint (xbuf (k)) 10 continue write (unit = 7, fmt = valfmt, err = 999) $ (ibuf (k), k = 1,nbuf) else write (unit = 7, fmt = valfmt, err = 999) $ (xbuf (k), k = 1,nbuf) endif return 999 call mexErrMsgTxt ('error writing numerical values') return end c----------------------------------------------------------------------- c RBiformat: determine format for printing an integer c----------------------------------------------------------------------- subroutine RBiformat (kmin, kmax, indfmt, indn, ww) integer $ n, indn, kmin, kmax, ww character*20 indfmt if (kmin .ge. 0. and. kmax .le. 9) then indfmt = '(40I2) ' ww = 2 indn = 40 elseif (kmin .ge. -9 .and. kmax .le. 99) then indfmt = '(26I3) ' ww = 3 indn = 26 elseif (kmin .ge. -99 .and. kmax .le. 999) then indfmt = '(20I4) ' ww = 4 indn = 20 elseif (kmin .ge. -999 .and. kmax .le. 9999) then indfmt = '(16I5) ' ww = 5 indn = 16 elseif (kmin .ge. -9999 .and. kmax .le. 99999) then indfmt = '(13I6) ' ww = 6 indn = 13 elseif (kmin .ge. -99999 .and. kmax .le. 999999) then indfmt = '(11I7) ' ww = 7 indn = 11 elseif (kmin .ge. -999999 .and. kmax .le. 9999999) then indfmt = '(10I8) ' ww = 8 indn = 10 elseif (kmin .ge. -9999999 .and. kmax .le. 99999999) then indfmt = '(8I9) ' ww = 9 indn = 8 elseif (kmin .ge. -99999999 .and. kmax .le. 999999999) then indfmt = '(8I10) ' ww = 10 indn = 8 else indfmt = '(5I15) ' ww = 15 indn = 5 endif return end c----------------------------------------------------------------------- c RBcards: determine number of cards required c----------------------------------------------------------------------- subroutine RBcards (nitems, nperline, ncards) integer $ nitems, nperline, ncards if (nitems .eq. 0) then ncards = 0 else ncards = ((nitems-1) / nperline) + 1 endif return end