C
C Program MATMUL to compute product M3=M1*M2 of two matrices M1 and M2.
C Matrices M1 and M2 may be transposed before the multiplication.
C
C Version: 6.10
C Date: 2007, June 13
C
C Coded by Petr Bulant
C     Department of Geophysics, Charles University Prague,
C     Ke Karlovu 3, 121 16 Praha 2, Czech Republic,
C
C.......................................................................
C                                                    
C Description of data files:
C
C Input data read from the standard input device (*):
C     The data are read by the list directed input (free format) and
C     consist of a single string 'SEP':
C     'SEP'...String in apostrophes containing the name of the input
C             SEP parameter or history file with the input data.
C     No default, 'SEP' must be specified and cannot be blank.
C
C                                                     
C Input data file 'SEP':
C     File 'SEP' has the form of the SEP
C     parameter file.  The parameters, which do not differ from their
C     defaults, need not be specified in file 'SEP'.
C Filenames of the header files of the matrices:
C     MATIN1='string' .. Name of the header file of the input matrix M1.
C             No default, MATIN1 must be specified and cannot be blank.
C     MATIN2='string' .. Name of the header file of the input matrix M2.
C             Default: MATIN2=' ' ... no input matrix M2, matrix M1 will
C             be written on output. This option may be used to transpose
C             matrix M1, or to change the form of the matrix M1.
C     MATOUT='string' . Name of the header file of the output matrix M3.
C             No default, MATOUT must be specified and cannot be blank.
C     For general description of the files with matrices refer to file
C     forms.htm.
C Form of the output file with the matrix M3:
C     SYMMETRY='string' ... Symmetry of the matrix. For SYMMETRY='diag',
C             only the diagonal components of the matrix are calculated
C             and written to the output file, other components are not
C             calculated.  Simmilarly for other symmetries.
C             Possible values of SYMMETRY are:
C               SYMMETRY='diag' ... diagonal matrix
C               SYMMETRY='sym'  ... symmetric matrix
C               SYMMETRY='skew' ... antisymmetric matrix
C               SYMMETRY=' '    ... general matrix
C             Default:
C               For matrix M2 not specified (MATIN2=' '):
C                 SYMMETRY equal to symmetry of matrix M1
C               For both input matrices M1 and M2 diagonal:
C                 SYMMETRY='diag' (output matrix M3 will be diagonal)
C               In other cases:
C                 SYMMETRY=' ' (output matrix M3 will be general)
C     MATFORM='string' ... Form of the output file with the matrix.
C             Possible values of MATFORM are:
C               MATFORM='formatted'   ... formatted file
C               MATFORM='unformatted' ... unformatted file
C             Default: MATFORM='formatted'
C     SPARSE=integer ... Identifies whether the matrix should be sparse.
C             Possible values of SPARSE are:
C               SPARSE=1  ... sparse matrix
C               SPARSE=0  ... automatic selection of the sparseness:
C                 matrix with half or more zero elements will be sparse
C               SPARSE=-1 ... normal (not sparse) matrix
C             Default: SPARSE=0 (automatic selection of the sparseness)
C     For detailed description of the forms of the files with matrices
C     refer to file forms.htm.
C Switches identifying transposition of the input matrices:
C     MATT1=integer ... Identifies whether the matrix M1 is to be
C             transposed before the multiplication:
C             MATT1=0 ... no transposition
C             MATT1=1 ... matrix M1 is to be transposed
C             Default: MATT1=0 (no transposition of M1)
C     MATT2=integer ... Same as MATT1, but for matrix M2.
C             Default: MATT2=0 (no transposition of M2)
C Optional parameter specifying the form of the output formatted
C matrix data files:
C     NUMLIN=positive integer ... Number of reals or integers in one
C             line of the output file. See the description in file
C             mat.for.
C
C For detailed description of storage of matrices in the memory
C refer to file mat.for.
C
C-----------------------------------------------------------------------
C Subroutines and external functions required:
      EXTERNAL ERROR,RSEP1,RSEP3I,RSEP3T,RMATH,RMATD,WMATH,WMATD,
     *TMATR,SMATRE,SMATR,GSMAT,GSMATR,SGMATR,ISYM,NELMAT,MSHIFT,
     *MMILOC,MMIRVE,OMULS,OMULNS
      INTEGER ISYM,NELMAT
C     ERROR ... File error.for.
C     RSEP1,RSEP3I,RSEP3T,SSEP ...
C             File sep.for.
C     RMATH,RMATD,WMATH,WMATD  ...
C     TMATR,SMATRE,SMATR,GSMAT,GSMATR,SGMATR,ISYM,NELMAT,MSHIFT  ...
C             File forms.for.
C     MMILOC,MMIRVE,OMULS,OMULNS ... This file.
C Subroutines and external functions referred by above subroutines:
C     LENGTH ... File length.for.
C     WSEP3I,WSEP3T ... File sep.for.
C     RARRAI,RARRAY,WARRAI,WARRAY ...
C             File forms.for.
C     INDEXI ... File indexi.for.
C
C Common block /RAMC/:
      INCLUDE 'ram.inc'
C     ram.inc
C
      INTEGER IRAM(MRAM)
      EQUIVALENCE (IRAM,RAM)
C
      CHARACTER*80 FILSEP,FILE1,FILE2,FILE3,FILED1,FILED2,FILED3
      CHARACTER*80 SYM1,SYM2,SYM3,SYM3I,SYMC,FORM1,FORM2,FORM3
      INTEGER MATT1,MATT2,M1,M2,M2B,M3,NEL1,NEL2,NELC,OA,OA1,OAN,NA,
     *OB,OB1,OBN,NB,OC,NC,MC,NCTMP,NCCOL,OTMP,MTMP,
     *NSPAR1,NSPAR2,ISPAR3,NSPARC,ISYM1,ISYM2,ISYM3,ISYMC,
     *ISTORA,ISTORB,ISTORC,LU1,
     *I1,II,JJ,KK,II1,II2,II3,IIA,JJ1,JJ2,JJ3,JJA,IIKK,OCTMP1
      REAL AA,BB,CIK,RSPAR1,RSPAR2,RSPAR3
      CHARACTER*72 TXTERR
      PARAMETER (LU1=1)
C
C-----------------------------------------------------------------------
C
C     Reading a name of the file with the input data:
      WRITE(*,'(A)') '+MATMUL: Enter input filename: '
      FILSEP=' '
      READ(*,*) FILSEP
C
C     Reading all the data from the SEP file into the memory:
      IF (FILSEP.NE.' ') THEN
        CALL RSEP1(LU1,FILSEP)
      ELSE
C       MATMUL-01
        CALL ERROR('MATMUL-01: SEP file not given.')
      ENDIF
C
C     Reading the names of matrices header files:
      CALL RSEP3T('MATIN1',FILE1,' ')
      IF (FILE1.EQ.' ') THEN
C       MATMUL-02
        CALL ERROR('MATMUL-02: File MATIN1 not given.')
      ENDIF
      CALL RSEP3T('MATIN2',FILE2,' ')
      CALL RSEP3T('MATOUT',FILE3,' ')
      FILED3=' '
      IF (FILE3.EQ.' ') THEN
C       MATMUL-03
        CALL ERROR('MATMUL-03: File MATOUT not given.')
      ENDIF
C     Reading the header files of the input matrices:
      CALL RMATH(LU1,FILE1,FILED1,M1,M2,NSPAR1,SYM1,FORM1,NEL1)
      ISYM1=ISYM(SYM1)
      IF (FILE2.NE.' ') THEN
        CALL RMATH(LU1,FILE2,FILED2,M2B,M3,NSPAR2,SYM2,FORM2,NEL2)
        ISYM2=ISYM(SYM2)
      ENDIF
C     Reading the properties of the output file:
      IF (FILE2.EQ.' ') THEN
        SYM3I=SYM1
      ELSEIF ((SYM1.EQ.'diag').AND.(SYM2.EQ.'diag')) THEN
        SYM3I='diag'
      ELSE
        SYM3I=' '
      ENDIF
      CALL RSEP3T('SYMMETRY',SYM3,SYM3I)
      CALL LOWER(SYM3)
      ISYM3=ISYM(SYM3)
      CALL RSEP3T('MATFORM',FORM3,'formatted')
      CALL LOWER(FORM3)
      CALL RSEP3I('SPARSE',ISPAR3,0)
C     Reading the transposition switches:
      CALL RSEP3I('MATT1',MATT1,0)
      CALL RSEP3I('MATT2',MATT2,0)
C
C     Reading input matrix A:
      IF (NSPAR1.GE.0) THEN
        NA=2*NEL1
        OA=1
        OTMP=NA+1
        MTMP=MRAM-NA
      ELSE
        NA=NEL1
        OA=MRAM-NA+1
        OTMP=1
        MTMP=OA-1
      ENDIF
      IF (NA+1.GT.MRAM) THEN
C       MATMUL-04
        WRITE(TXTERR,'(A,I9,A)')
     *  'MATMUL-04: Array RAM too small,',NA+1-MRAM,' units missing.'
        CALL ERROR(TXTERR)
      ENDIF
      CALL RMATD(LU1,FILED1,NSPAR1,FORM1,NEL1,IRAM(OA),RAM(OA))
      ISTORA=0
C     Checking whether the non-sparse matrix is to be changed to the
C     sparse matrix, changing the matrix:
      IF (NSPAR1.LT.0) THEN
        RSPAR1=0.5
        CALL GSMAT(M1,M2,SYM1,NSPAR1,NEL1,RAM,IRAM,MRAM,
     *                  OA,NA,ISTORA,RSPAR1)
        IF (NSPAR1.GE.0) THEN
          OTMP=NA+1
          MTMP=MRAM-NA
        ENDIF
      ELSE
        RSPAR1=FLOAT(NSPAR1)/FLOAT(NSPAR1+NEL1)
      ENDIF
C     Transposing input matrix A:
      IF (MATT1.EQ.1) THEN
        CALL TMATR(M1,M2,SYM1,NSPAR1,NEL1,IRAM(OA),RAM(OA),
     *             IRAM(OTMP),RAM(OTMP),MTMP)
      ENDIF
C     Extending sparse matrix A:
      IF ((NSPAR1.GE.0).AND.((SYM1.EQ.'sym').OR.(SYM1.EQ.'skew'))) THEN
        CALL SMATRE
     *       (M1,M2,SYM1,NSPAR1,NEL1,IRAM(OA),RAM(OA),MRAM,NA,ISTORA)
        ISYM1=ISYM(SYM1)
      ENDIF
C     Reorganizing sparse matrix A in the memory:
      IF (NSPAR1.GE.0) THEN
        CALL SMATR(M1,M2,NSPAR1,NEL1,IRAM,RAM,MRAM,
     *             OA,NA,ISTORA)
      ENDIF
C
      IF (FILE2.EQ.' ') THEN
C       Matrix A is to be written as output matrix C:
        M3=M2
        IF (SYM3.EQ.SYM1) THEN
C         No change of matrix symmetry:
          OC=OA
          NC=NA
          NSPARC=NSPAR1
          NELC=NEL1
          SYMC=SYM1
          ISYMC=ISYM1
          ISTORC=ISTORA
C         Copying matrix A to the position of C:
          IF (NSPARC.GE.0) THEN
            DO 2, I1=1,M3+1
              IRAM(I1)=IRAM(OC-1+I1)-OC+1
   2        CONTINUE
            DO 3, I1=1,NC-M3-1-1,2
              IRAM(1+M3+I1)=IRAM(OC+M3+I1)
              RAM(1+M3+I1+1)=RAM(OC+M3+I1+1)
   3        CONTINUE
          ELSE
            DO 4, I1=1,NC
              RAM(I1)=RAM(OC-1+I1)
   4        CONTINUE
          ENDIF
          OC=1
        ELSE
C         Changing matrix symmetry:
          IF ((ISYM3.LE.3).AND.(M1.NE.M3)) THEN
C           MATMUL-05
            CALL ERROR('MATMUL-05: Wrong symmetry of output matrix.')
C           If the output matrix should have symmetry 'diag', 'sym' or
C           'skew', it must have the same number of rows and columns.
          ENDIF
          OC=1
          IF     (ISYM3.EQ.2) THEN
C           Symmetric matrix
            NC=M1*(M1+1)/2
          ELSEIF (ISYM3.EQ.3) THEN
C           Antisymmetric matrix
            NC=M1*(M1-1)/2
          ELSEIF (ISYM3.EQ.1) THEN
C           Diagonal matrix
            NC=M1
          ELSE
C           General matrix
            NC=M1*M3
          ENDIF
          MC=OA-1
          NSPARC=-1
          NELC=NC
          SYMC=SYM3
          ISYMC=ISYM3
          ISTORC=0
C
          NCTMP=0
          OCTMP1=0
          DO 15, KK=1,M3
C           Preparing sufficient number of the storage locations:
C           Number NCCOL of storage locations for column KK:
            IF     (ISYMC.EQ.1) THEN
              NCCOL=1
            ELSEIF (ISYMC.EQ.2) THEN
              NCCOL=KK
            ELSEIF (ISYMC.EQ.3) THEN
              NCCOL=KK-1
            ELSE
              NCCOL=M1
            ENDIF
            IF (NCTMP+NCCOL.GT.MC) THEN
C             Erasing unneeded columns 1 to KK-1 of A:
              IF (NSPAR1.LT.0) THEN
C               OA stays unchanged, MC is redefined:
                IF     (ISYM1.EQ.1) THEN
                  MC=OA+KK-1-1
                ELSEIF (ISYM1.EQ.2) THEN
                  MC=OA+(KK-1)*KK/2-1
                ELSEIF (ISYM1.EQ.3) THEN
                  MC=OA+(KK-2)*(KK-1)/2-1
                ELSE
                  MC=OA+M1*(KK-1)-1
                ENDIF
              ELSE
C               OA must be changed, addresses of the ends of the columns
C               must be moved:
                OAN=IRAM(OA+KK-1)-M2
                DO 12, I1=M2,0,-1
                  IF (I1.GE.KK) THEN
                    IRAM(OAN+I1)=IRAM(OA+I1)
                  ELSE
                    IRAM(OAN+I1)=IRAM(OA+KK-1)
                  ENDIF
  12            CONTINUE
                OA=OAN
                MC=OA-1
              ENDIF
              IF (NCTMP+NCCOL.GT.MC) THEN
C               Changing the written part of C to sparse matrix:
                CALL GSPART(M1,M3,ISYMC,NSPARC,NELC,RAM,IRAM,OA-1,
     *                      OC,NCTMP,ISTORC,KK-1,OCTMP1)
              ENDIF
              IF (NCTMP+NCCOL.GT.MC) THEN
C               MATMUL-06
                WRITE(TXTERR,'(A,I9,A)')
     *         'MATMUL-06: Array RAM too small,',NCTMP+NCCOL-MC,
     *         ' units missing.'
                CALL ERROR(TXTERR)
              ENDIF
            ENDIF
C           Initiating the values in column KK of C:
            DO 11, I1=NCTMP+1,NCTMP+NCCOL
              RAM(I1)=0.
  11        CONTINUE
C           Preparing loop over column KK of A:
            CALL MMILOC(M1,M2,ISYM1,NSPAR1,RAM,IRAM,OA,KK,II1,II2,II3)
C           Loop over nonzero elements of column KK of A:
            DO 13, IIA=II1,II2,II3
C             Computing index II of row and value AA of element of A:
              CALL MMIRVE(M1,M2,ISYM1,NSPAR1,RAM,IRAM,OA,KK,IIA,II1,II3,
     *                                     II,AA)
              IF     ((ISYMC.EQ.1).AND.(II.EQ.KK)) THEN
                IIKK=OCTMP1+II
              ELSEIF ((ISYMC.EQ.2).AND.(II.LE.KK)) THEN
                IIKK=OCTMP1+(KK-1)*KK/2+II
              ELSEIF ((ISYMC.EQ.3).AND.(II.LT.KK)) THEN
                IIKK=OCTMP1+(KK-2)*(KK-1)/2+II
              ELSEIF (ISYMC.EQ.4)                  THEN
                IIKK=OCTMP1+(KK-1)*M1+II
              ELSE
                GOTO 13
              ENDIF
C             Storing the element:
              RAM(IIKK)=AA
  13        CONTINUE
            NCTMP=NCTMP+NCCOL
  15      CONTINUE
          IF (NSPARC.GE.0) THEN
C           Changing the remaining part of C to sparse matrix:
            CALL GSPART(M1,M3,ISYMC,NSPARC,NELC,RAM,IRAM,OA-1,
     *                  OC,NCTMP,ISTORC,M3,OCTMP1)
          ENDIF
        ENDIF
        GOTO 100
      ENDIF
C
C     Reading input matrix B:
      IF (((MATT2.EQ.0).AND.(M2B.NE.M2)).OR.
     *    ((MATT2.EQ.1).AND.(M3 .NE.M2))) THEN
C       MATMUL-07
        CALL ERROR('MATMUL-07: Mismatch in dimensions of the matrices.')
      ENDIF
      IF ((ISYM3.LE.3).AND.(((MATT2.EQ.0).AND.(M1.NE.M3)).OR.
     *                      ((MATT2.EQ.1).AND.(M2.NE.M3)))) THEN
C       MATMUL-08
        CALL ERROR('MATMUL-08: Wrong symmetry of output matrix.')
C       If the output matrix should have symmetry 'diag', 'sym' or
C       'skew', it must have the same number of rows and columns.
      ENDIF
      IF (NSPAR2.GE.0) THEN
        NB=2*NEL2
        OB=1
        OTMP=NB+1
        MTMP=MRAM-NA-NB
      ELSE
        NB=NEL2
        OB=MRAM-NA-NB+1
        OTMP=1
        MTMP=OB-1
      ENDIF
      IF (NB+1.GT.MRAM-NA) THEN
C       MATMUL-09
        WRITE(TXTERR,'(A,I9,A)')
     *  'MATMUL-09: Array RAM too small,',NB+1-MRAM+NA,' units missing.'
        CALL ERROR(TXTERR)
      ENDIF
      CALL RMATD(LU1,FILED2,NSPAR2,FORM2,NEL2,IRAM(OB),RAM(OB))
      ISTORB=0
C     Checking whether the non-sparse matrix is to be changed to the
C     sparse matrix, changing the matrix:
      IF (NSPAR2.LT.0) THEN
        RSPAR2=0.5
        CALL GSMAT(M2B,M3,SYM2,NSPAR2,NEL2,RAM,IRAM,OA-1,
     *                  OB,NB,ISTORB,RSPAR2)
        IF (NSPAR2.GE.0) THEN
          OTMP=NB+1
          MTMP=MRAM-NA-NB
        ENDIF
      ELSE
        RSPAR2=FLOAT(NSPAR2)/FLOAT(NSPAR2+NEL2)
      ENDIF
C     Transposing input matrix B:
      IF (MATT2.EQ.1) THEN
        CALL TMATR(M2B,M3,SYM2,NSPAR2,NEL2,IRAM(OB),RAM(OB),
     *             IRAM(OTMP),RAM(OTMP),MTMP)
      ENDIF
C     Extending sparse matrix B:
      IF ((NSPAR2.GE.0).AND.((SYM2.EQ.'sym').OR.(SYM2.EQ.'skew'))) THEN
        CALL SMATRE
     *       (M2,M3,SYM2,NSPAR2,NEL2,IRAM(OB),RAM(OB),OA-1,NB,ISTORB)
        ISYM2=ISYM(SYM2)
      ENDIF
C     Reorganizing sparse matrix B in the memory:
      IF (NSPAR2.GE.0) THEN
        CALL SMATR(M2,M3,NSPAR2,NEL2,IRAM,RAM,OA-1,
     *             OB,NB,ISTORB)
      ENDIF
C
C     Optimizations for the speed of calculation:
      IF (((1.-RSPAR1)*(1.-RSPAR2).LE.0.5).AND.
     *    (.NOT.((NSPAR1.LT.0).AND.(ISYM1.EQ.2.OR.ISYM1.EQ.3))).AND.
     *    (.NOT.((NSPAR2.LT.0).AND.(ISYM2.EQ.2.OR.ISYM2.EQ.3)))) THEN
C       Matrices are to be multiplied as sparse matrices:
C       Checking if there is enough of memory for multiplication:
C       Calculating OC for the case that B will be sparse:
        CALL OMULS(M1,ISYM3,M2,M3,ISYM2,NSPAR2,NEL2,IRAM,RAM,OA-1,
     *             OB,NB,ISTORB,OC)
        OAN=OA-OC+1
C       Shifting OC if A is to be changed to sparse:
        IF (NSPAR1.LT.0) THEN
          OC=OC + NA - (2*NINT((1.-RSPAR1)*FLOAT(NA))+M2+1)
        ENDIF
        IF (OC.GT.0) THEN
C         Matrices may be (and will be) changed to sparse matrices
C         and shifted to optimum position for multiplication:
C         Shifting B to the beginning of RAM:
          CALL MSHIFT(M2,M3,ISYM2,NSPAR2,NEL2,IRAM,RAM,MRAM,
     *                OB,NB,ISTORB,1)
          IF (NSPAR2.LT.0) THEN
C           Changing B to sparse, form "as in the memory":
            ISTORB=1
            CALL GSMATR(M2,M3,SYM2,NSPAR2,NEL2,RAM,IRAM,OA-1,
     *                  OB,NB,ISTORB)
          ENDIF
          IF (NSPAR1.LT.0) THEN
C           Shifting A behind B:
            CALL MSHIFT(M1,M2,ISYM1,NSPAR1,NEL1,IRAM,RAM,MRAM,
     *                  OA,NA,ISTORA,NB+1)
C           Changing A to sparse, form "as in the memory":
            ISTORA=1
            CALL GSMATR(M1,M2,SYM1,NSPAR1,NEL1,RAM,IRAM,MRAM,
     *                  OA,NA,ISTORA)
          ENDIF
C         Shifting A to the optimum position for multiplication:
          CALL MSHIFT(M1,M2,ISYM1,NSPAR1,NEL1,IRAM,RAM,MRAM,
     *                OA,NA,ISTORA,OAN)
C         Shifting B to the optimum position for multiplication:
          CALL MSHIFT(M2,M3,ISYM2,NSPAR2,NEL2,IRAM,RAM,MRAM,
     *                OB,NB,ISTORB,OAN-NB)
        ENDIF
      ELSE
C       Matrices are to be multiplied as non-sparse matrices:
C       Calculating OC for the case that B will be non-sparse:
        CALL OMULNS(M1,ISYM3,M2,M3,ISYM2,NSPAR2,NEL2,IRAM,RAM,OA-1,
     *              OB,NB,ISTORB,OC)
        OAN=OA-OC+1
C       Shifting OC if A is to be changed to non-sparse:
        IF (NSPAR1.GE.0) THEN
          OC=OC + NA - NELMAT(M1,M2,ISYM1)
        ENDIF
C       Shifting B to the beginning of RAM:
        CALL MSHIFT(M2,M3,ISYM2,NSPAR2,NEL2,IRAM,RAM,MRAM,
     *              OB,NB,ISTORB,1)
        IF (NSPAR2.GE.0) THEN
C         Changing B to non-sparse:
          CALL SGMATR(M2,M3,SYM2,NSPAR2,NEL2,RAM,IRAM,OA-1,
     *                OB,NB,ISTORB)
        ENDIF
        IF (NSPAR1.GE.0) THEN
C         Shifting A behind B:
          CALL MSHIFT(M1,M2,ISYM1,NSPAR1,NEL1,IRAM,RAM,MRAM,
     *                OA,NA,ISTORA,NB+1)
C         Changing A to non-sparse:
          CALL SGMATR(M1,M2,SYM1,NSPAR1,NEL1,RAM,IRAM,MRAM,
     *                OA,NA,ISTORA)
        ENDIF
C       Shifting A to the optimum position for multiplication:
        IF (OC.LE.0) OAN=MRAM-NA+1
        CALL MSHIFT(M1,M2,ISYM1,NSPAR1,NEL1,IRAM,RAM,MRAM,
     *              OA,NA,ISTORA,OAN)
C       Shifting B to the optimum position for multiplication:
        CALL MSHIFT(M2,M3,ISYM2,NSPAR2,NEL2,IRAM,RAM,MRAM,
     *              OB,NB,ISTORB,OAN-NB)
      ENDIF
C
C     Multiplication:
      WRITE(*,'(A)') '+MATMUL: Multiplying...            '
C
*      IF ((SYM2.EQ.'diag').AND.
*     *    (((SYM1.EQ.'DIAG').OR.(SYM1.EQ.' ')).AND.(SYM1.EQ.SYM3)))THEN
*C       Matrix B is diagonal, A is diagonal or general,
*C       thus C will be A multiplied by the diagonal of B:
*        OC=OA
*        NC=NA
*        NSPARC=NSPAR1
*        NELC=NEL1
*        SYMC=SYM1
*        ISYMC=ISYM1
*        ISTORC=ISTORA
*C       Loop over diagonal of B:
*        DO 10, KK=1,M2
*          WRITE(*,'(2(A,1I9))') '+MATMUL: Multiplying...',KK,' /',M2
*C         Calculating the value BB of diagonal element of B:
*          IF (NSPAR2.GE.0) THEN
*            IF (IRAM(OB+KK).EQ.IRAM(OB+KK-1)) THEN
*              BB=0.
*            ELSE
*              BB=RAM(IRAM(OB+KK))
*            ENDIF
*          ELSE
*            BB=RAM(OB-1+KK)
*          ENDIF
*C         Preparing loop over column KK of A:
*          IF (NSPAR1.GE.0) THEN
*            II1=IRAM(OA+KK-1)+2
*            II2=IRAM(OA+KK)
*            II3=2
*          ELSE
*            IF (SYM1.EQ.'diag') THEN
*              II1=OA+KK-1
*              II2=II1
*              II3=1
*            ELSEIF (SYM1.EQ.'sym') THEN
*              II1=OA+(KK-1)*KK/2
*              II2=II1+KK-1
*              II3=1
*            ELSEIF (SYM1.EQ.'skew') THEN
*              II1=OA+(KK-1)*(KK-2)/2
*              II2=II1+KK-2
*              II3=1
*            ELSE
*              II1=OA+(KK-1)*M1
*              II2=II1+M1-1
*              II3=1
*            ENDIF
*          ENDIF
*C         Loop over column KK of A:
*          DO 8, II=II1,II2,II3
*C           Multiplying:
*            RAM(II)=RAM(II)*BB
*   8      CONTINUE
*  10    CONTINUE
*C       Shifting matrix C:
*        IF (NSPARC.GE.0) THEN
*          DO 12, I1=1,M3+1
*            IRAM(I1)=IRAM(OC-1+I1)-OC+1
*  12      CONTINUE
*          DO 13, I1=1,NC-M3-1-1,2
*            IRAM(1+M3+I1)=IRAM(OC+M3+I1)
*            RAM(1+M3+I1+1)=RAM(OC+M3+I1+1)
*  13      CONTINUE
*        ELSE
*          DO 14, I1=1,NC
*            RAM(I1)=RAM(OC-1+I1)
*  14      CONTINUE
*        ENDIF
*        OC=1
*      ELSEIF ((SYM1.EQ.'diag').AND.
*     *    (((SYM2.EQ.'DIAG').OR.(SYM2.EQ.' ')).AND.(SYM2.EQ.SYM3)))THEN
*C       Matrix A is diagonal, B is diagonal or general,
*C       thus C will be B multiplied by the diagonal of A:
*        OC=OB
*        NC=NB
*        NELC=NEL2
*        NSPARC=NSPAR2
*        SYMC=SYM2
*        ISYMC=ISYM2
*        ISTORC=ISTORB
*C       Loop over all columns of B:
*        DO 20, KK=1,M3
*          WRITE(*,'(2(A,1I9))') '+MATMUL: Multiplying...',KK,' /',M3
*C         Preparing loop over column KK of B:
*          IF (NSPAR2.GE.0) THEN
*            JJ1=IRAM(OB+KK-1)+2
*            JJ2=IRAM(OB+KK)
*            JJ3=2
*          ELSE
*            IF (SYM2.EQ.'sym') THEN
*              JJ1=OB+(KK-1)*KK/2
*              JJ2=JJ1+KK-1
*              JJ3=1
*            ELSEIF (SYM2.EQ.'skew') THEN
*              JJ1=OB+(KK-1)*(KK-2)/2
*              JJ2=JJ1+KK-2
*              JJ3=1
*            ELSE
*              JJ1=OB+(KK-1)*M2
*              JJ2=JJ1+M2-1
*              JJ3=1
*            ENDIF
*          ENDIF
*C         Loop over column KK of B:
*          DO 18, JJ=JJ1,JJ2,JJ3
*C           Calculating index II of row of B:
*            IF (NSPAR2.GE.0) THEN
*              II=IRAM(JJ-1)
*            ELSE
*              II=(JJ-JJ1+JJ3)/JJ3
*            ENDIF
*C           Calculating the value AA of diagonal element of A:
*            IF (NSPAR1.GE.0) THEN
*              IF (IRAM(OA+II).EQ.IRAM(OA+II-1)) THEN
*                AA=0.
*              ELSE
*                AA=RAM(IRAM(OA+II))
*              ENDIF
*            ELSE
*              AA=RAM(OA-1+II)
*            ENDIF
*C           Multiplying:
*            RAM(JJ)=RAM(JJ)*AA
*  18      CONTINUE
*  20    CONTINUE
*C       Shifting matrix C:
*        IF (NSPARC.GE.0) THEN
*          DO 22, I1=1,M3+1
*            IRAM(I1)=IRAM(OC+I1-1)-OC+1
*  22      CONTINUE
*          DO 23, I1=1,NC-M3-1-1,2
*            IRAM(1+M3+I1)=IRAM(OC+M3+I1)
*            RAM(1+M3+I1+1)=RAM(OC+M3+I1+1)
*  23      CONTINUE
*        ELSE
*          DO 24, I1=1,NC
*            RAM(I1)=RAM(OC+I1-1)
*  24      CONTINUE
*        ENDIF
*        OC=1
*      ELSEIF ((SYM1.EQ.' ').AND.(SYM2.EQ.' ').AND.
      IF ((NSPAR1.LT.0).AND.(NSPAR2.LT.0).AND.
ccc  *    ((M1.LT.OB).AND.(M1*M3.LT.OA-M2))) THEN
     *    (OC.GT.0)) THEN
C       Multiplication for non-sparse matrices:
        OC=1
        NC=M1*M3
        NSPARC=-1
        NELC=NC
        SYMC=SYM3
        ISYMC=ISYM3
        ISTORC=0
        IIKK=0
        II1=1
        II2=M1
        OA1=OA-1
        OB1=OB-1
C       Loop over columns of C:
        DO 27, KK=1,M3
          WRITE(*,'(2(A,1I9))') '+MATMUL: Multiplying...',KK,' /',M3
C         Loop over lines of column KK of matrix C:
          IF     (ISYMC.EQ.1) THEN
            II1=KK
            II2=KK
          ELSEIF (ISYMC.EQ.2) THEN
            II2=KK
          ELSEIF (ISYMC.EQ.3) THEN
            II2=KK-1
          ENDIF
          DO 26, II=II1,II2
            IIKK=IIKK+1
            CIK=0.
            DO 25, JJ=1,M2
C             Element of the matrix A:
              IF     (ISYM1.EQ.1) THEN
                IF (II.EQ.JJ) THEN
                  AA=RAM(OA1+JJ)
                ELSE
                  GOTO 25
                ENDIF
              ELSEIF (ISYM1.EQ.2) THEN
                IF (II.LE.JJ) THEN
                  AA=RAM(OA1+(JJ-1)*JJ/2+II)
                ELSE
                  AA=RAM(OA1+(II-1)*II/2+JJ)
                ENDIF
              ELSEIF (ISYM1.EQ.3) THEN
                IF (II.EQ.JJ) THEN
                  GOTO 25
                ELSEIF (II.LT.JJ) THEN
                  AA= RAM(OA1+(JJ-1)*(JJ-2)/2+II)
                ELSE
                  AA=-RAM(OA1+(II-1)*(II-2)/2+JJ)
                ENDIF
              ELSE
                AA=RAM(OA1+(JJ-1)*M1+II)
              ENDIF
C             Element of the matrix B:
              IF     (ISYM2.EQ.1) THEN
                IF (JJ.EQ.KK) THEN
                  BB=RAM(OB1+KK)
                ELSE
                  GOTO 25
                ENDIF
              ELSEIF (ISYM2.EQ.2) THEN
                IF (JJ.LE.KK) THEN
                  BB=RAM(OB1+(KK-1)*KK/2+JJ)
                ELSE
                  BB=RAM(OB1+(JJ-1)*JJ/2+KK)
                ENDIF
              ELSEIF (ISYM2.EQ.3) THEN
                IF (JJ.EQ.KK) THEN
                  GOTO 25
                ELSEIF (JJ.LT.KK) THEN
                  BB= RAM(OB1+(KK-1)*(KK-2)/2+JJ)
                ELSE
                  BB=-RAM(OB1+(JJ-1)*(JJ-2)/2+KK)
                ENDIF
              ELSE
                BB=RAM(OB1+(KK-1)*M2+JJ)
              ENDIF
              CIK=CIK+AA*BB
  25        CONTINUE
            RAM(IIKK)=CIK
  26      CONTINUE
  27    CONTINUE
      ELSE
C       Multiplication for the case that at least one matrix is sparse,
C       or OC is negative (no space for whole matrix C):
        OC=1
        IF (SYM3.EQ.'sym') THEN
C         Symmetric matrix
          NC=M1*(M1+1)/2
        ELSEIF (SYM3.EQ.'skew') THEN
C         Antisymmetric matrix
          NC=M1*(M1-1)/2
        ELSEIF (SYM3.EQ.'diag') THEN
C         Diagonal matrix
          NC=M1
        ELSE
C         General matrix
          NC=M1*M3
        ENDIF
        MC=OB-1
        NSPARC=-1
        NELC=NC
        SYMC=SYM3
        ISYMC=ISYM3
        ISTORC=0
C
        NCTMP=0
        OCTMP1=0
        DO 50, KK=1,M3
          WRITE(*,'(2(A,1I9))') '+MATMUL: Multiplying...',KK,' /',M3
C         Preparing sufficient number of the storage locations:
C         Number NCCOL of storage locations for column KK:
          IF     (ISYMC.EQ.1) THEN
            NCCOL=1
          ELSEIF (ISYMC.EQ.2) THEN
            NCCOL=KK
          ELSEIF (ISYMC.EQ.3) THEN
            NCCOL=KK-1
          ELSE
            NCCOL=M1
          ENDIF
          IF (NCTMP+NCCOL.GT.MC) THEN
C           Erasing unneeded columns 1 to KK-1 of B:
            IF (NSPAR2.LT.0) THEN
C             OB stays unchanged, MC is redefined:
              IF     (ISYM2.EQ.1) THEN
                MC=OB+KK-1-1
              ELSEIF (ISYM2.EQ.2) THEN
                MC=OB+(KK-1)*KK/2-1
              ELSEIF (ISYM2.EQ.3) THEN
                MC=OB+(KK-2)*(KK-1)/2-1
              ELSE
                MC=OB+M2*(KK-1)-1
              ENDIF
            ELSE
C             OB must be changed, addresses of the ends of the columns
C             must be moved:
              OBN=IRAM(OB+KK-1)-M3
              DO 28, I1=M3,0,-1
                IF (I1.GE.KK) THEN
                  IRAM(OBN+I1)=IRAM(OB+I1)
                ELSE
                  IRAM(OBN+I1)=IRAM(OB+KK-1)
                ENDIF
  28          CONTINUE
              OB=OBN
              MC=OB-1
            ENDIF
            IF (NCTMP+NCCOL.GT.MC) THEN
C             Changing the calculated part of C to sparse matrix:
              CALL GSPART(M1,M3,ISYMC,NSPARC,NELC,RAM,IRAM,OB-1,
     *                    OC,NCTMP,ISTORC,KK-1,OCTMP1)
            ENDIF
            IF (NCTMP+NCCOL.GT.MC) THEN
C             MATMUL-10
              WRITE(TXTERR,'(A,I9,A)')
     *       'MATMUL-10: Array RAM too small,',NCTMP+NCCOL-MC,
     *       ' units missing.'
              CALL ERROR(TXTERR)
            ENDIF
          ENDIF
C         Initiating the values in column KK of C:
          DO 29, I1=NCTMP+1,NCTMP+NCCOL
            RAM(I1)=0.
  29      CONTINUE
C         Preparing loop over column KK of B:
          CALL MMILOC(M2,M3,ISYM2,NSPAR2,RAM,IRAM,OB,KK,JJ1,JJ2,JJ3)
C         Loop over nonzero elements of column KK of B:
          DO 40, JJA=JJ1,JJ2,JJ3
C           Computing index JJ of row and value BB of the element of B:
            CALL MMIRVE(M2,M3,ISYM2,NSPAR2,RAM,IRAM,OB,KK,JJA,JJ1,JJ3,
     *                                     JJ,BB)
C           Preparing loop over column JJ of A:
            CALL MMILOC(M1,M2,ISYM1,NSPAR1,RAM,IRAM,OA,JJ,II1,II2,II3)
C           Loop over nonzero elements of column JJ of A:
            DO 30, IIA=II1,II2,II3
C             Computing index II of row and value AA of element of A:
              CALL MMIRVE(M1,M2,ISYM1,NSPAR1,RAM,IRAM,OA,JJ,IIA,II1,II3,
     *                                     II,AA)
              IF     ((ISYMC.EQ.1).AND.(II.EQ.KK)) THEN
                IIKK=OCTMP1+II
              ELSEIF ((ISYMC.EQ.2).AND.(II.LE.KK)) THEN
                IIKK=OCTMP1+(KK-1)*KK/2+II
              ELSEIF ((ISYMC.EQ.3).AND.(II.LT.KK)) THEN
                IIKK=OCTMP1+(KK-2)*(KK-1)/2+II
              ELSEIF (ISYMC.EQ.4)                  THEN
                IIKK=OCTMP1+(KK-1)*M1+II
              ELSE
                GOTO 30
              ENDIF
C             Multiplying:
              RAM(IIKK)=RAM(IIKK) + AA*BB
  30        CONTINUE
  40      CONTINUE
          NCTMP=NCTMP+NCCOL
  50    CONTINUE
        IF (NSPARC.GE.0) THEN
C         Changing the remaining part of C to sparse matrix:
          CALL GSPART(M1,M3,ISYMC,NSPARC,NELC,RAM,IRAM,OB-1,
     *                OC,NCTMP,ISTORC,M3,OCTMP1)
        ENDIF
      ENDIF
C
 100  CONTINUE
C
      IF ((ISPAR3.EQ.1).AND.(NSPARC.LT.0)) THEN
C       Changing non-sparse matrix to sparse matrix:
        CALL GSMATR(M1,M3,SYMC,NSPARC,NELC,RAM,IRAM,MRAM,OC,NC,ISTORC)
      ELSEIF ((ISPAR3.EQ.-1).AND.(NSPARC.GE.0)) THEN
C       Changing sparse matrix to non-sparse matrix:
        CALL SGMATR(M1,M3,SYMC,NSPARC,NELC,RAM,IRAM,MRAM,OC,NC,ISTORC)
      ELSEIF (ISPAR3.EQ.0) THEN
C       Automatic selection of the sparseness:
        IF (NSPARC.LT.0) THEN
C         Matrix is non-sparse, it will be changed to sparse if its
C         sparseness is 0.5 or more:
          RSPAR3=0.5
          CALL GSMAT(M1,M3,SYMC,NSPARC,NELC,RAM,IRAM,MRAM,
     *                  OC,NC,ISTORC,RSPAR3)
        ELSE
C         Matrix is sparse.
          RSPAR3=FLOAT(NSPARC)/FLOAT(NSPARC+NELC)
          IF (RSPAR3.LT.0.5) THEN
C           Matrix is sparse, sparseness is less than 0.5, thus changing
C           the sparse matrix to non-sparse matrix:
            CALL SGMATR(M1,M3,SYMC,NSPARC,NELC,RAM,IRAM,MRAM,
     *                  OC,NC,ISTORC)
          ENDIF
        ENDIF
      ENDIF
C
      IF ((NSPARC.GE.0).AND.(ISTORC.EQ.1)) THEN
C       Reorganizing sparse matrix in the memory:
        CALL SMATR(M1,M3,NSPARC,NELC,IRAM,RAM,MRAM,
     *             OC,NC,ISTORC)
      ENDIF
C
C
C     Writing output matrix C:
      CALL WMATH(LU1,FILE3,FILED3,M1,M3,NSPARC,SYM3,FORM3)
      CALL WMATD(LU1,FILED3,NSPARC,FORM3,NELC,IRAM(OC),RAM(OC))
C
      WRITE(*,'(A)') '+MATMUL: Done.                 '
C
      STOP
      END
C
C=======================================================================
C
C     
C
      SUBROUTINE MMILOC(M1,M2,ISYM,NSPARS,ARRAY,IARRAY,OARRAY,ICOLUM,
     *                 IELEM1,IELEM2,IELEM3)
      INTEGER M1,M2,ISYM,NSPARS,OARRAY,ICOLUM,IELEM1,IELEM2,IELEM3
      REAL     ARRAY(*)
      INTEGER IARRAY(*)
C
C Subroutine designed to Initiate Loop Over Column ICOLUM of a matrix.
C
C Input:
C     M1 ...  Number of rows of the matrix.
C     M2 ...  Number of columns of the matrix.
C     ISYM... Index of the symmetry of the matrix.
C     NSPARS . Sparseness of the matrix.
C     ARRAY,IARRAY ... Array with indices and values of the matrix.
C             Sparse matrix must be stored in the form "as in memory".
C     OARRAY..Address of the first storage location of the matrix.
C     ICOLUM..Number of the column under considertation.
C     For detailed description of storage of matrices in the memory
C     refer to file mat.for.
C
C Output:
C     IELEM1..Address of the value of the first element of column ICOLUM
C             in array ARRAY.
C     IELEM2..Address of the value of the last element of column ICOLUM
C             in array ARRAY.
C     IELEM3..Step between two consecutive values of the matrix elements
C             in array ARRAY.
C
C Coded by Petr Bulant
C     E-mail: bulant@seis.karlov.mff.cuni.cz
C
C-----------------------------------------------------------------------
C.......................................................................
C
C     Preparing loop over column ICOLUM of the matrix:
      IF (NSPARS.GE.0) THEN
        IELEM1=IARRAY(OARRAY+ICOLUM-1)+2
        IELEM2=IARRAY(OARRAY+ICOLUM)
        IELEM3=2
      ELSE
        IF     (ISYM.EQ.1) THEN
          IELEM1=OARRAY+ICOLUM-1
          IELEM2=IELEM1
          IELEM3=1
        ELSEIF (ISYM.EQ.2) THEN
          IELEM1=OARRAY+(ICOLUM-1)*ICOLUM/2
          IELEM2=IELEM1+M1-1
          IELEM3=1
        ELSEIF (ISYM.EQ.3) THEN
          IELEM1=OARRAY+(ICOLUM-1)*(ICOLUM-2)/2
          IELEM2=IELEM1+M1-1
          IELEM3=1
        ELSE
          IELEM1=OARRAY+(ICOLUM-1)*M1
          IELEM2=IELEM1+M1-1
          IELEM3=1
        ENDIF
      ENDIF
      RETURN
      END
C
C=======================================================================
C
C     
C
      SUBROUTINE MMIRVE(M1,M2,ISYM,NSPARS,ARRAY,IARRAY,OARRAY,ICOLUM,
     *                 IELEMA,IELEM1,IELEM3,
     *                                     IROW,VELEM)
      INTEGER M1,M2,ISYM,NSPARS,OARRAY,ICOLUM,IELEMA,IELEM1,IELEM3,IROW
      REAL     ARRAY(*),VELEM
      INTEGER IARRAY(*)
C
C Subroutine designed to calculate Index of Row and Value of Element
C of a matrix.
C
C Input:
C     M1 ...  Number of rows of the matrix.
C     M2 ...  Number of columns of the matrix.
C     ISYM... Index of the symmetry of the matrix.
C     NSPARS. Sparseness of the matrix.
C     ARRAY,IARRAY ... Array with indices and values of the matrix.
C             Sparse matrix must be stored in the form "as in memory".
C     OARRAY..Address of the first storage location of the matrix.
C     ICOLUM..Number of the column under considertation.
C     IELEMA..Address of the value of the current element of column
C             ICOLUM in array ARRAY.
C     IELEM1..Address of the value of the first element of column ICOLUM
C             in array ARRAY.
C     IELEM3..Step between two consecutive values of the matrix elements
C             in array ARRAY.
C     For detailed description of storage of matrices in the memory
C     refer to file mat.for.
C
C Output:
C     IROW .. Number of the row corresponding to the matrix element
C             under considertation.
C     VELEM...Value of the considered matrix element.
C
C Coded by Petr Bulant
C     E-mail: bulant@seis.karlov.mff.cuni.cz
C
C-----------------------------------------------------------------------
C.......................................................................
C
C     Calculating index IROW of row of the matrix:
      IF (NSPARS.GE.0) THEN
        IROW=IARRAY(IELEMA-1)
      ELSE
        IF (ISYM.EQ.1) THEN
          IROW=ICOLUM
        ELSE
          IROW=(IELEMA-IELEM1+IELEM3)/IELEM3
        ENDIF
      ENDIF
C     Calculating the value VELEM of element of the matrix:
      IF (NSPARS.GE.0) THEN
        VELEM=ARRAY(IELEMA)
      ELSE
        IF     (ISYM.EQ.2) THEN
C         'sym'
          IF (IROW.LE.ICOLUM) THEN
            VELEM=ARRAY(IELEMA)
          ELSE
            VELEM=ARRAY(OARRAY-1+(IROW-1)*IROW/2+ICOLUM)
          ENDIF
        ELSEIF (ISYM.EQ.3) THEN
C         'skew'
          IF (IROW.LT.ICOLUM) THEN
            VELEM=ARRAY(IELEMA)
          ELSEIF (IROW.EQ.ICOLUM) THEN
            VELEM=0.
          ELSE
            VELEM=-ARRAY(OARRAY-1+(IROW-1)*(IROW-2)/2+ICOLUM)
          ENDIF
        ELSE
C         'diag' or ' '
          VELEM=ARRAY(IELEMA)
        ENDIF
      ENDIF
      RETURN
      END
C
C=======================================================================
C
C     
C
      SUBROUTINE OMULS(M1C,ISYMC,M1,M2,ISYM,NSPAR,NELEM,
     *                 IARRAY,ARRAY,MARRAY,OARRAY,NARRAY,ISTOR,OC)
      INTEGER M1C,ISYMC,M1,M2,ISYM,NSPAR,NELEM,MARRAY,OARRAY,NARRAY,
     *        ISTOR,OC
      REAL     ARRAY(*)
      INTEGER IARRAY(*)
C
C Subroutine to estimate optimum origin OC where to write matrix C,
C assuming that matrix B given to OMUL will be stored as sparse matrix,
C and will be stored so that it will terminate at the same position
C where it terminates now.
C OC is estimated as minimum over KK=1,M2 of End(KK-1)-M2-AllKKC,
C where End(KK-1) is the address of end of row KK-1 of matrix B if
C matrix B were stored as sparse matrix in the form "as in the memory",
C and AllKKC is the number of elements of columns 1 to KK of matrix C.
C
C Input:
C     M1C ... Number of rows of matrix C.
C     ISYMC.. Index of symmetry of matrix C.
C     M1...   Number of rows of matrix B.
C     M2...   Number of columns of matrix B.
C     ISYM .. Index of symmetry of matrix B.
C     NSPAR...Sparseness of matrix B.
C     NELEM...Number of elements of matrix B.
C     IARRAY..Components of matrix B.
C     ARRAY...Components of matrix B.
C     MARRAY..Dimension of array ARRAY.
C     OARRAY..First storage location of matrix B.
C     NARRAY..Number of storage locations for matrix B.
C     ISTOR...Form of matrix B.
C     For detailed description of the parameters describing the matrices
C     refer to file forms.htm.
C     Detailed description of storage of matrices in the memory
C     is given above.
C
C Output:
C     OC  ... Origin where to write matrix C.
C
C Coded by Petr Bulant
C     E-mail: bulant@seis.karlov.mff.cuni.cz
C
C-----------------------------------------------------------------------
C
C Subroutines and external functions required:
      EXTERNAL ERROR,NELMAT
      INTEGER NELMAT
C
C     Local storage location:
      INTEGER II,KK,JJ,NN,IB,II1,II2,OTMP
C
C.......................................................................
C
      IF (NSPAR.GE.0.AND.ISTOR.EQ.0) THEN
C       MATMUL-11
        CALL ERROR('MATMUL-11: Wrong ISTOR.')
C       This option is not yet coded, sparse matrix must be in the form
C       "as in the memory".
      ENDIF
C
      IF (NSPAR.LT.0) THEN
C       Calculating addresses of ends of columns of B if B were stored
C       as sparse matrix ending at the same position where it ends now:
        OTMP=OARRAY-M2-1
        IF (OTMP.LE.0) THEN
C         There is no space for ends of rows, thus OC is put to -1:
          OC=-1
          RETURN
        ENDIF
        IB=OARRAY+NARRAY-1
        JJ=IB
        IARRAY(OTMP+M2)=JJ
        II1=1
        II2=M1
C       Loop over columns of B:
        DO 12, KK=M2,1,-1
C         Loop over lines of B:
          IF     (ISYM.EQ.1) THEN
            II1=KK
            II2=KK
          ELSEIF (ISYM.EQ.2) THEN
            II2=KK
          ELSEIF (ISYM.EQ.3) THEN
            II2=KK-1
          ENDIF
C         Number NN of nonzero elements of column KK:
          NN=0
          DO 10, II=II1,II2
            IF (ARRAY(IB).NE.0.) NN=NN+1
            IB=IB-1
  10      CONTINUE
          JJ=JJ-2*NN
          IARRAY(OTMP+KK-1)=JJ
  12    CONTINUE
      ELSE
        OTMP=OARRAY
      ENDIF
      OC=OTMP
      DO 20, KK=1,M2
        OC=MIN0(OC,IARRAY(OTMP+KK-1)-M2-NELMAT(M1C,KK,ISYMC))
  20  CONTINUE
      RETURN
      END
C
C=======================================================================
C
C     
C
      SUBROUTINE OMULNS(M1C,ISYMC,M1,M2,ISYM,NSPAR,NELEM,
     *                  IARRAY,ARRAY,MARRAY,OARRAY,NARRAY,ISTOR,OC)
      INTEGER M1C,ISYMC,M1,M2,ISYM,NSPAR,NELEM,MARRAY,OARRAY,NARRAY,
     *        ISTOR,OC
      REAL     ARRAY(*)
      INTEGER IARRAY(*)
C
C Subroutine to estimate optimum origin OC where to write matrix C,
C assuming that matrix B given to OMUL will be stored as non-sparse
C matrix, and will be stored so that it will terminate at the same
C position where it terminates now.
C OC is estimated as minimum over KK=1,M2 of End(KK-1)-AllKKC+1,
C where End(KK-1) is the address of end of row KK-1 of matrix B if
C matrix B were stored as non-sparse matrix,
C and AllKKC is the number of elements of columns 1 to KK of matrix C.
C
C Input:
C     M1C ... Number of rows of matrix C.
C     ISYMC.. Index of symmetry of matrix C.
C     M1...   Number of rows of matrix B.
C     M2...   Number of columns of matrix B.
C     ISYM .. Index of symmetry of matrix B.
C     NSPAR...Sparseness of matrix B.
C     NELEM...Number of elements of matrix B.
C     IARRAY..Components of matrix B.
C     ARRAY...Components of matrix B.
C     MARRAY..Dimension of array ARRAY.
C     OARRAY..First storage location of matrix B.
C     NARRAY..Number of storage locations for matrix B.
C     ISTOR...Form of matrix B.
C     For detailed description of the parameters describing the matrices
C     refer to file forms.htm.
C     Detailed description of storage of matrices in the memory
C     is given above.
C
C Output:
C     OC  ... Origin where to write matrix C.
C
C Coded by Petr Bulant
C     E-mail: bulant@seis.karlov.mff.cuni.cz
C
C-----------------------------------------------------------------------
C
C Subroutines and external functions required:
      EXTERNAL NELMAT
      INTEGER NELMAT
C
C     Local storage location:
      INTEGER KK,OTMP
C
C.......................................................................
C
      IF (NSPAR.LT.0) THEN
        OTMP=OARRAY
      ELSE
        OTMP=OARRAY+NARRAY-NELMAT(M1,M2,ISYM)
      ENDIF
      OC=OTMP
      DO 20, KK=1,M2
        OC=MIN0(OC,OTMP+NELMAT(M1,KK-1,ISYM)-NELMAT(M1C,KK,ISYMC))
  20  CONTINUE
      RETURN
      END
C
C=======================================================================
C
C     
C
      SUBROUTINE GSPART(M1,M2,ISYM,NSPAR,NELEM,ARRAY,IARRAY,MARRAY,
     *                  OARRAY,NARRAY,ISTOR,ICOL,OTMP)
      INTEGER M1,M2,ISYM,NSPAR,NELEM,OARRAY,NARRAY,MARRAY,ISTOR,
     *        ICOL,OTMP
      REAL     ARRAY(*)
      INTEGER IARRAY(*)
C
C Subroutine designed to consecutively change parts of non-sparse matrix
C to the sparse matrix. For the first invocation, it is assumed on input
C that non-sparse part of the matrix for columns 1 to ICOL is stored
C in ARRAY, and this part is changed to sparse matrix, form "as in the
C memory" and written to ARRAY and IARRAY together with M2+1 addresses
C of ends of columns. For second and other invocations, it is assumed
C that IARRAY and ARRAY contain M2+1 addresses of ends of columns of the
C sparse part of the matrix, followed by the sparse part of the matrix
C (columns 1 to ICOL0), and finally by non-sparse part of the matrix
C (columns ICOL0+1 to ICOL). The non-sparse part is converted to the
C sparse and written after the already stored sparse part, and the
C addressses of columns ICOL0+1 to M2 are updated.
C
C Input:
C     M1...   Number of rows of the full matrix.
C     M2...   Number of columns of the full matrix.
C     ISYM ...Index of symmetry of the matrix.
C     ARRAY...Components of the given matrix.
C     IARRAY..Components of the given matrix.
C     MARRAY..Dimension of array ARRAY.
C     OARRAY..Address of the first storage location in array ARRAY
C             used for the matrix.
C     NARRAY..Number of storage locations for the matrix.
C     ICOL ...Index of the last calculated column of the matrix.
C
C Output:
C     NSPAR...Sparseness of the M1*ICOL matrix.
C     NELEM...Number of elements of the M1*ICOL matrix.
C     NARRAY..New number of storage locations for the M1*ICOL matrix.
C     IARRAY..Components of the M1*ICOL matrix.
C     ARRAY...Components of the M1*ICOL matrix.
C     ISTOR...Form of the matrix.
C             ISTOR=1 ... form "as in the memory"
C     OTMP ...Fictitious position in ARRAY just before the first storage
C             location of the matrix if the matrix would stay non-sparse
C             and would terminate at the same location where it
C             terminates after its conversion to the sparse matrix.
C
C Coded by Petr Bulant
C     E-mail: bulant@seis.karlov.mff.cuni.cz
C
C-----------------------------------------------------------------------
C
C Subroutines and external functions required:
      EXTERNAL ERROR,NSPMAT,NELMAT
      INTEGER NSPMAT,NELMAT
C
C     Local storage location:
      INTEGER I1,I2,I3,I4,I20,I11,I12,J1,OAR,NAR,NSPA,NELE,ICOL0
      CHARACTER*72 TXTERR
      SAVE ICOL0
      DATA ICOL0/0/
C
C.......................................................................
C
C     Beginning OAR and number of elements NAR of the non-sparse part
C     of ARRAY:
      IF (ICOL0.EQ.0) THEN
        OAR=OARRAY
        NAR=NARRAY
      ELSE
        OAR=IARRAY(OARRAY+M2)+1
        NAR=NARRAY-(OAR-OARRAY)
      ENDIF
C     Number NSPA of zero and NELE of nonzero elements:
      NSPA=NSPMAT(NAR,ARRAY(OAR))
      NELE=NAR-NSPA
      IF (((ICOL0.EQ.0).AND.(OAR+M2+1+2*NELE.GT.MARRAY)).OR.
     *    ((ICOL0.NE.0).AND.(OAR+     2*NELE.GT.MARRAY))) THEN
C       MATMUL-12
        IF (ICOL0.EQ.0) THEN
          I1=OAR+M2+1+2*NELE - MARRAY
        ELSE
          I1=OAR+     2*NELE - MARRAY
        ENDIF
        WRITE(TXTERR,'(A,I9,A)')
     *  'MATMUL-12: Array RAM too small,',I1,' units missing.'
        CALL ERROR(TXTERR)
      ENDIF
C     Moving the nonzero elements of the matrix to the end of ARRAY,
C     storing the corresponding matrix indices of the elements:
      J1=MARRAY
      I3=OARRAY-1+NARRAY
      DO 20, I2=ICOL,ICOL0+1,-1
        IF     (ISYM.EQ.1) THEN
          I11=I2
          I12=I2
        ELSEIF (ISYM.EQ.2) THEN
          I11=I2
          I12=1
        ELSEIF (ISYM.EQ.3) THEN
          I11=I2-1
          I12=1
        ELSE
          I11=M1
          I12=1
        ENDIF
        DO 18, I1=I11,I12,-1
          IF (ARRAY(I3).NE.0.) THEN
            IF (J1-1.LT.I3) THEN
C             MATMUL-13
              WRITE(TXTERR,'(A,I9,A)')
     *        'MATMUL-13: Array RAM too small,',I3-J1+1,
     *        ' units missing.'
              CALL ERROR(TXTERR)
            ENDIF
            ARRAY(J1)=ARRAY(I3)
            IARRAY(J1-1)=(I2-1)*M1+I1
            J1=J1-2
          ENDIF
          I3=I3-1
  18    CONTINUE
  20  CONTINUE
      IF (I3.NE.OAR-1) THEN
C       MATMUL-14
        WRITE(TXTERR,'(A,I9,A)')
     *  'MATMUL-14: Array RAM too small,',OAR-1-I3,' units missing.'
        CALL ERROR(TXTERR)
      ENDIF
C     J1 now points just before the index of the first nonzero element
C
      ISTOR=1
      IF (ICOL0.EQ.0) OAR=OARRAY+M2+1
C     Rearranging the matrix to the form 'as in the memory':
C     Moving the indices and the values to the new positions:
      DO 42, I3=1,NELE
        ARRAY(OAR-1+2*I3)=ARRAY(J1+2*I3)
        IARRAY(OAR-1+2*I3-1)=IARRAY(J1+2*I3-1)
  42  CONTINUE
C     Calculating end addresses of the columns,
C     changing matrix indices of the elements to the row numbers:
      I20=M2+1
      DO 44, I3=NELE,1,-1
        I1=MOD(IARRAY(OAR-1+2*I3-1),M1)
        IF (I1.EQ.0) I1=M1
        I2=(IARRAY(OAR-1+2*I3-1)-1)/M1+1
        IARRAY(OAR-1+2*I3-1)=I1
        IF (I2.NE.I20) THEN
          DO 43, I4=I20-1,I2,-1
            IARRAY(OARRAY+I4)=OAR-1+2*I3
  43      CONTINUE
          I20=I2
        ENDIF
  44  CONTINUE
      DO 45, I4=I20-1,ICOL0,-1
        IARRAY(OARRAY+I4)=OAR-1
  45  CONTINUE
C
      ICOL0=ICOL
      NARRAY=IARRAY(OARRAY+M2)-OARRAY+1
      NELEM=(NARRAY-M2-1)/2
      I1=NELMAT(M1,ICOL,ISYM)
      NSPAR=I1-NELEM
      OTMP=IARRAY(OARRAY+M2)-I1
C
      RETURN
      END
C
C=======================================================================
C
      INCLUDE 'error.for'
C     error.for
      INCLUDE 'sep.for'
C     sep.for
      INCLUDE 'forms.for'
C     forms.for
      INCLUDE 'length.for'
C     length.for
      INCLUDE 'mat.for'
C     mat.for
      INCLUDE 'indexi.for'
C     indexi.for
C
C=======================================================================
C