      program sbvq
c
c
c  Strict bounds on Linear Functionals of Bounded Variables Subject
c     to a Quadratic Misfit Criterion on Linear Data Constraints
c
c  P.B. Stark      Version 1/88
c
c  Finds bounds on linear functionals of a model required to satisfy
c  a priori upper and lower bounds and a set of linear equations within
c  a specified weighted sum-of-squares tolerance.
c
c  i.e.      min and max  p .x     j=1,...,nv
c                          j
c                               2       2
c      such that   || A.x - d ||  <= chi     (+- small tolerance chitol)
c                               2
c
c         and      bl(i) <= x(i) <= bu(i) , i=1,...,lvls
c
c  for a user provided list of vectors p .
c                                       j
c
c  x, bl, bu, and p  are  lvls-vectors,  A  is an  nd  by  lvls  matrix,
c                  j
c  d  is an  nd-vector.
c
c
c  Structured so that the user may easily replace many IO routines with
c  subroutines to generate the mapping matrix, a priori bounds, and
c  objective vectors.
c
c* Uses BVLS (Bounded-Variable Least-Squares) quadratic programming
c* routine by R.L. Parker and P.B. Stark, 1987.
c*
c*
c* Calls  BVLS, DOT, FITMIS, FORWRD, GETBND,
c* GETMAP, GETVEC, PREP1, PREP2, SETDAT, and SPACE.
c
c
c  Input required:
c
c     print mode integer (1-4)   gives increasing amounts of output.
c
c     data file name             file should contain pairs:
c                                  mean datum    standard deviation
c
c     number of data
c
c     number of levels in the
c       model
c
c     number of objective
c       vectors to find
c       bounds for
c
c     relative weighting of      1.d-4 seems to work consistently.
c       penalty functional       1.e-3 works in CRAY single precision.
c
c     expected chi-squared       set for desired confidence level for 
c                                  number of data used
c
c     tolerance for fitting      set to about .001 chi-squared
c       chi
c
c     initial step length for    make this big enough to be sure chi**2
c       sweeping objective         is violated in 2-3 steps
c       values
c
c     file name for a priori     file should have pairs:
c       upper and lower bounds     bl(i), bu(i)
c
c     file name for              file should contain the matrix,
c       data mapping matrix        row by row (natural order)
c
c     file name for objective    file should contain the vectors,
c       vectors                    one after another
c
c
      implicit double precision (a-h,o-z)
      parameter (nmax=50000, nimax=500, maxit=500)
c  nmax: maximum allocated array storage.
c  maxit: maximum number of iterations when hunting for bounds.
      common /c/ c(nmax)
      common /srch/ scale,chi20,chitol,del0,scz
c  scale: relative weight of constraints versus penalty functional.
c  chi20: expected chi**2 to achieve.
c  chitol: tolerance for fitting chi20.
c  del0: initial step size for the objective constraint.
c
      common /datset/ nd,lvls,dbar(100),dsv(100),nv,zne
c
c  nd: number of data.
c  lvls: number of elements in the model.
c  dbar: data means.
c  dsv: data standard deviations.
c  nv: number penalty vectors for which to find extremal bounds.
c  zne: objective value the model is constrained to achieve.
c
      common /icons/ nz0,nz,nbnd,map,npv,naa,nbb,nbl,nbu,
     +  nw,nzz,nact,ntot
c
c  nz0: starting location of best fitting model in /c/.
c  nz:  starting location of the current solution in /c/.
c  nd:  starting location of normalized data and target depth.
c  nbnd:starting location of the tabulated bounds.  First nv elements
c       of /c/ from nbnd are lower bounds; next nv elements are upper.
c  map: starting location of the data mapping matrix.
c  npv: starting location of the penalty vector.
c  naa: starting location of matrix aa for bvls.
c  nbb: starting location of vector bb for bvls.
c  nbl: starting location of lower bounds on the model.
c  nbu: starting location of upper bounds on the model.
c  nw:  starting location for bvls working array w.
c  nzz: starting location for bvls working array zz.
c  nact:starting location for bvls working array act.
c  ntot: total required storage for the above arrays.
      common /print/mode,id
c  mode: determines the amount of printed output.
c  id: disk fortran unit number.
      dimension istate (nimax)
c  istate: integer working array for bvls.
      data id/7/
c
c------------------------first executable statement--------------------
c
 100  print '(/a/a)','Enter print mode integer (1-4): ',
     +  'larger values yield more printing. Enter -1 to quit.'
      read *, mode
      if ( mode .lt. 1 ) stop
      call setdat
c  Partition the main common block.
      call space
      print '(/)'
      print *,'Real array space required ',ntot,' reserved ',nmax
      print *,'Integer space required ',lvls+1, ' reserved ',nimax
      if(ntot.gt.nmax.or.lvls+1.gt.nimax) go to 100
      print '(/)'
c  Find best-fitting model for reference.
c
c  Get a priori upper/lower bounds on model, and data mapping matrix.
      call getbnd(lvls,c(nbl),c(nbu))
      call getmap(c(map),nd,lvls)
c  Open penalty vector file.
      call getvec
c  Form all scaled vectors and matrices for bvls.
      call prep1(nd,lvls,c(map),c(naa),c(nbb))
c  Solve.
      key=0
      call bvls(key,nd,lvls,c(naa),c(nbb),c(nbl),c(nbu),c(nz0),
     +  c(nw),c(nact),c(nzz),istate,its)
      chi2=fitmis(nd,lvls,c(map),c(nz0))
      print *,' minimum misfit ', chi2, ' bvls misfit ', c(nw)
      if (chi2.gt.chi20+chitol) then
         print *, ' *** infeasible problem *** '
         go to 100
      endif
C  Forward model, print best-fitting model if requested.
      if (mode.ge.3) call forwrd (c(nz0), lvls, c(map), nd)
      if (mode.eq.4) print '(/a/(i4,6x,g10.4))',
     +  '  i        m(i)', (l,c(nz0+l-1),l=1,lvls)
      key=1
c  Loop for minimization and maximization.
      do 600 j=0,1
c  Loop for target values of objective functional.
      do 500 i=1,nv
c  Get the next penalty vector.
      read (id,*),(c(npv+ii),ii=0,lvls-1)
c  Find the objective value achieved by the best-fitting model.
      call dot(lvls,c(npv),c(nz0),bnd0)
      print '(/)'
      print *,'vector # ',i,' best-fitting objective ',bnd0
c  Find max and min possible penalty values for a model satisfying
c  the a priori bounds.
      call dot(lvls,c(npv),c(nbl),bndlo)
      call dot(lvls,c(npv),c(nbu),bndhi)
c  Set new bvls parameters, etc.
      call prep2(nd,lvls,c(map),c(npv),c(naa),c(nbb))
c  Initialize the search direction.
      del=del0*(-1.0)**(j+1)
c  Set the target objective value; scale and load into bb.
      zne=bnd0+del
      c(nbb)=zne*scz
C  Outer loop with constant step size.
      do 300 it1=1,maxit
      call bvls(key,nd+1,lvls,c(naa),c(nbb),c(nbl),c(nbu),c(nz),
     + c(nw),c(nact),c(nzz),istate,its)
c  Find the misfit.
      chi2=fitmis(nd,lvls,c(map),c(nz))
c  Compute the extremal bound.
      call dot(lvls,c(nz),c(npv),bnd)
      if (mode.gt.1)
     +  print *,'target value ',zne,'vector # ',i,' achieved ',bnd,
     +   ' chi**2 ',chi2,' bvls iterations ', its, 
     +   ' bvls misfit ', c(nw)
      if ( abs(chi2-chi20) .le. chitol .or.
     +  (chi2 .lt. chi20 .and. (zne .le. bndlo .or.
     +     zne .ge. bndhi ))) then
c  Within the tolerance--save and print.
        go to 400
c  If chi2 < chi20 take another step.
      else if (chi2.lt.chi20) then
        zne=zne+del
        c(nbb)=zne*scz
c  If chi2 > chi20, [ zne, zne-del ] has a root: bisect.
      else 
        del=del/2.0
        zne=zne-del
        c(nbb)=zne*scz
c  Inner loop for bisection.
        do 200 it=1,maxit
          call bvls(key,nd+1,lvls,c(naa),c(nbb),c(nbl),c(nbu),
     +      c(nz),c(nw),c(nact),c(nzz),istate,its)
          chi2=fitmis(nd,lvls,c(map),c(nz))
c  Compute and print the extremal bound.
          call dot(lvls,c(nz),c(npv),bnd)
          if (mode.gt.1)
     +      print *,'target ',zne,'vector # ',i,' achieved ',bnd,
     +       ' chi**2 ',chi2, ' bvls iterations ', its, 
     +       ' bvls misfit ', c(nw)
          del=del/2.0
          if ( abs(chi2-chi20) .le. chitol  .or.
     +  (chi2 .lt. chi20 .and. (zne .le. bndlo .or.
     +     zne .gt. bndhi ))) then
c  Within the tolerance--save and print.
            go to 400
          else if (chi2.gt.chi20) then
c  [zne,zne-2*del] contains a root.
            zne=zne-del
            c(nbb)=zne*scz
          else
c  [zne,zne+2*del] contains a root.
            zne=zne+del
            c(nbb)=zne*scz
          end if
c  Iterate again.
 200    continue
        print '(/a)','Maximum number of steps exceeded.'
        go to 450
      end if
 300  continue
      print '(/a)','Maximum number of steps exceeded.'
      go to 450
c  Save and print the answer for this target.
 400  print '(/a/)','Bound found:'
      print *,'target ',zne,'vector # ',i,' achieved ',bnd,
     + ' chi**2 ',chi2, ' bvls iterations ', its, 
     + ' bvls misfit ', c(nw)
c  Save the bound.
      c(nbnd+j*nv+i-1)=bnd
c  Forward model, print model for appropriate mode.
      if (mode.ge.3) call forwrd(c(nz),lvls,c(map),nd)
      if (mode.ge.4) print '(/a/(i4,6x,g10.4))',
     +  '  i        m(i)', (l,c(nz+l-1),l=1,lvls)
c  Next target.
 450  continue
 500  continue
c  Start the maximization with the first penalty vector.
      rewind (unit=id)
 600  continue
c  Close the penalty vector file.
      close (unit=id)
c  Print the results.
      print '(/a/a/(i4,8x,g10.4))','Extremal Bounds',
     +  ' vector #     min ', (j,c(nbnd+j-1),j=1,nv)
      print '(/a/(i4,8x,g10.4))',' vector #     max ',
     +  (j,c(nbnd+nv+j-1),j=1,nv)
      stop
      end
c**********************************************************************
      subroutine dot(n,a,b,d)
c**********************************************************************
c  Computes double precision dot product of n-vectors a and b.
c* Calls no other routines.
      implicit double precision (a-h,o-z)
      dimension a(n),b(n)
      d=0.0
      do 10 i=1,n
 10   d=d+a(i)*b(i)
      return
      end
c**********************************************************************
      double precision function fitmis(m,n,a,x)
cs    real function fitmis(m,n,a,x)
c**********************************************************************
c                       2
c  Computes ||a.x-dbar||     .
c                       dsv
c
      implicit double precision (a-h, o-z)
      dimension a(m,n), x(n)
      common /datset/ nd,lvls,dbar(100),dsv(100),nv,zne
      fitmis=0.0
      do 20 i=1,nd
      p=0.0
      do 10 j=1,lvls
10    p=p+a(i,j)*x(j)
20    fitmis=fitmis+((p-dbar(i))/dsv(i))**2
      return
      end
c*********************************************************************
      subroutine forwrd(zp,m,a,nr)
c*********************************************************************
c  Calculates forward predictions from ZP using matrix A, and
c  displays the predictions between TXLO and TXHI at each p in PL.
c* Calls no other routines.
      implicit double precision (a-h,o-z)
      dimension zp(m), a(nr,m)
      common /datset/ nd,lvls,dbar(100),dsv(100),nv,zne
      print '(/)'
      print *,'  i         sigma         prediction       mean  '
      do 10 i=1,nd
      tx=0.0
      do 5 j=1,m
      tx=tx+a(i,j)*zp(j)
5     continue
10    print '(i4,2x,3(g15.7,3x))',i,dsv(i),tx,dbar(i)
      return
      end
c**********************************************************************
      subroutine getbnd(m,bl,bu)
c***********************************************************************
c  Fetches upper and lower bounds.
c
c* Calls no other routines.
      implicit double precision (a-h,o-z)
      common /datset/ nd,lvls,dbar(100),dsv(100),nv,zne
      common /print/mode,id
      dimension bl(m),bu(m)
      character*64 name
      print *,'Enter file name for upper and lower bounds on the',
     + 'model elements. Lower bound for each element should',
     + ' precede upper bound:  bl(i), bu(i).'
      read '(a64)', name
      open (unit=id, file=name)
      read (id,*) (bl(j),bu(j),j=1,m)
      close (unit=id)
      return
      end
c**********************************************************************
      subroutine getmap (a,m,n)
c**********************************************************************
c  Fetches the data mapping matrix.
c
c* Calls no other routines.
      implicit double precision (a-h,o-z)
      common /datset/ nd,lvls,dbar(100),dsv(100),nv,zne
      common/print/mode,id
      dimension a(m,n)
      character*64 name
      print '(/a/a)','Enter file name for data mapping matrix.',
     + 'File should be in row-by-row format.'
      read '(a64)', name
      open (unit=id, file=name)
      read (id,*) ((a(i,j),j=1,n),i=1,m)
      close (unit=id)
      return
      end
c**********************************************************************
      subroutine getvec
c***********************************************************************
c  Opens the file containing the penalty vectors.
c
c* Calls no other routines.
      implicit double precision (a-h,o-z)
      common /print/mode,id
      character*64 name
      print *,'Enter file name for penalty vectors.'
      read '(a64)', name
      open (unit=id, file=name)
      return
      end
c*********************************************************************
      subroutine prep1( m, n, a, aa, bb )
c*********************************************************************
c  Makes normalized matrices and vectors for bvls for
c  best-fitting model problem.
C  Normalizes the data and rows of the mapping by their relative errors.
c
c* Calls no other routines.
      implicit double precision (a-h,o-z)
      common /datset/ nd,lvls,dbar(100),dsv(100),nv,zne
      common /srch/ scale,chi20,chitol,del0,scz
      dimension a(m,n),aa(m,n),bb(m)
c
c  Find the normalization for the data constraints.
      rmin=dsv(1)
      do 10 i=2,m
 10   rmin=min(rmin,dsv(i))
c  Enter the mapping a, properly scaled, into rows 1 to m of aa.
      do 50 i=1, m
c  Build bb from the scaled data.
      bb(i)=rmin*dbar(i)/dsv(i)
      do 50 j=1, n
50    aa(i,  j)= a(i,j)*rmin/dsv(i)
      return
      end
c*********************************************************************
      subroutine prep2(m, n, a, pv, aa, bb )
c*********************************************************************
c  Makes normalized matrix aa and vector bb for bvls from data mapping 
c  matrix a, penalty vector pv, data vectors dbar and dsv, and the
c  scale factor scale.
c  bb(1) is left undefined; scz is returned (in common) to allow 
c  bb(1) to be constructed later in the program.
c
c* Calls no other routines.
      implicit double precision (a-h,o-z)
      common /datset/ nd,lvls,dbar(100),dsv(100),nv,zne
      common /srch/ scale,chi20,chitol,del0,scz
      dimension a(m,n),pv(n),aa(m+1,n),bb(m+1)
c
c  Find the normalization for the data constraints.
      rmin=dsv(1)
      do 10 i=2,m
 10   rmin=min(rmin,dsv(i))
c  Multiply by the smallest error so the rows do not get too big.
      sc= scale*rmin
c  Find the norm of the penalty vector.
      scz=0.0
      do 20 i=1,n
 20   scz=scz+pv(i)**2
      scz=1.e0/sqrt(scz)
c  Enter the scaled objective functional in row 1 of aa.
      do 40 i=1,n
40    aa(1,i)=scz*pv(i)
c
c  Rows 2 to m+1 of aa get a, scaled.
      do 50 i=1, m
c  Elements 2 to m+1 of bb are the scaled data; element 1 will get the
c    scaled target depth later.
      bb(i+1)=sc*dbar(i)/dsv(i)
      do 50 j=1, n
50    aa(i+1,  j)= a(i,j)*sc/dsv(i)
      return
      end
c*********************************************************************
      subroutine setdat
c*********************************************************************
c  Reads data, upper and lower bounds on the model, 
c       and defines the variables in /datset/.
c
c  nd  is the number data.
c  lvls  is the number of elements in the model
c  xl() is an array of values at which data have been measured.
c  dbar(),dsv()  mean, standard deviation of measured data values.
c  nv is the number of penalty vectors to be bounded.
c
c* Calls no other routines.
      implicit double precision (a-h,o-z)
      common /datset/ nd,lvls,dbar(100),dsv(100),nv,zne
      common/print/mode,id
      common /srch/ scale,chi20,chitol,del0,scz
      character*64 name
      print '(/''Input data file name.''/
     + ''Maximum of 100 data in the form:''/
     + ''   mean    standard deviation '')'
      read '(a64)', name
      print *,'Enter # data, # of model elements (<500), ',
     +  '# of penalty vectors to bound, penalty weight, ',
     +  'chi**2, chi tolerance, and step length'
      read *,nd,lvls,nv,scale,chi20,chitol,del0
      open(unit=id,file=name)
      do 100 i=1,nd
 100  read (id,*) dbar(i),dsv(i)
      close (unit=id)
c  Print the data.
      print '(/,''Data to be inverted:'',/,2x,''i'',5x,
     + ''mean'',8x,''sigma'')'
      do 200 i=1,nd
  200 print '(i4,2x,2g12.6)',i,dbar(i),dsv(i)
      return
      end
c*********************************************************************
      subroutine space
c*********************************************************************
c  Allocates space for storage of data mapping matrix, bounds, 
c  solutions, and objective functional.
c* Calls no other routines.
      implicit double precision (a-h,o-z)
      common /datset/ nd,lvls,dbar(100),dsv(100),nv,zne
      common /icons/ nz0,nz,nbnd,map,npv,naa,nbb,nbl,nbu,nw,
     +  nzz,nact,ntot
      nz0=1
      nz=nz0+lvls                      
      nbnd=nz+lvls                     
      map=nbnd+2*nv                    
      npv=map+lvls*nd
      naa=npv+lvls                     
      nbb=naa+lvls*(nd+1)           
      nbl=nbb+nd+1                  
      nbu=nbl+lvls
      nw=nbu+lvls
      nzz=nw+lvls                      
      nact=nzz+nd+1                 
      ntot=nzz+(nd+1)*(nd+3)     
      return
      end
