      program sbl1
c
c
c  Strict bounds on Linear Functionals of Bounded Variables Subject
c     to a One-Norm Misfit Criterion on Linear Data Constraints
c
c  P.B. Stark      Version 12/29/92
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  an l-1 norm error tolerance:
c
c
c  min pv.z   such that             bl<=z<=bu and 
c  max                    sum_i |A(i).z-dbar(i)|/dsv(i) <= chi*(1+tol) 
c
c  for a list of vectors pv.
c
c  Quadratic programming with heavy weights on the data constraints
c  is used to simulate linear programming with inequality constraints.
c  The use of quadratic programming thus necessitates the introduction
c  of copious slack variables, which subroutine  prep  handles.
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, CHECK, DOT, FORWRD, GETBND, GETMAP, GETVEC, 
c*        PREP, PREPV, SETDAT, SLACKS, and SPACE.
c
c*********************************************************************
c******************************WARNING********************************
c
c  The program contains two ``tuning constants:''   scale  and  tol.
c  They are set in the data statement to 1.d-2 .
c  These values have worked in a wide variety of test cases.
c  ``scale'' is the relative weight of the (bottom) row of the matrix  
c  aa sent to bvls.  It specifies the tradeoff between minimizing the 
c  difference between pv.z and its target value, and fitting the data.
c  tol specifies the relative tolerance with which the target value
c  of the misfit,  chi,  should be achieved.
c
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                                  central value   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     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=15000, nimax=500 )
c  nmax:   maximum allocated array storage.
c  nimax:  maximum integer array storage.
      common /c/ c(nmax)
      common /s/ scale, pmin, pmax
c  scale:  relative weight of constraints versus penalty functional.
c          bounds on pv.z
c  pmin:   a priori lower bound on pv.z
c  pmax:   a priori upper bound on pv.z

      common /datset/ dbar(50),dsv(50),chi,nd,lvls,nv
c  dbar:   data central values.
c  dsv:    data standard deviations.
c  chi:    maximum acceptable l1-norm of misfit.  Should be chosen
c          to provide a 1-alpha confidence region for the noise-free
c          data values.  Percentage points for the 1-norm of iid
c          Gaussian variables are tabulated in Parker and McNutt, JGR, 
c          v85, p4429, 1980.
c  nd:     number of data.
c  lvls:   number of elements in the model.
c  nv:     number penalty vectors for which to find extremal bounds.
c
      common /icons/ nz,nbnd,map,npv,naa,nbb,nbl,nbu,
     +  nw,nzz,nact,ntot
c
c  nz:    starting location of the current solution in /c/.
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/, scale /1.d-2/, tol/1.d-2/
c  tol:   the fitting tolerance for the data (a fraction of chi)
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
      iused=lvls+2*nd+4
      print '(/)'
      print *,'Real array space required ',ntot,' reserved ',nmax
      print *,'Integer space required ',iused,' reserved ',nimax
      if(ntot.gt.nmax.or.iused.gt.nimax) go to 100
      print '(/)'
c  Get a priori upper/lower bounds on model.
      call getbnd(lvls,c(nbl),c(nbu))
c  Set the bounds on the slack variables using the data tolerances.
      call slacks(lvls+2*nd+3, c(nbl),c(nbu))
c  Get the data mapping matrix.
      call getmap(c(map),nd,lvls)
c
c  Test for problem feasibility.
c  Set up the matrices and bounds without penalty vector.
      call prep(nd,lvls,nd+1,lvls+2*nd+3,c(map),c(naa),c(nbb))
c Set the bounds on the slacks for the objective vector.
      c(nbl+lvls+2*nd+1)=0.0
      c(nbl+lvls+2*nd+2)=0.0
      c(nbu+lvls+2*nd+1)=0.0
      c(nbu+lvls+2*nd+2)=0.0
      key=0
      call bvls(key,nd+1,lvls+2*nd+3,c(naa),c(nbb),c(nbl),c(nbu),
     + c(nz),c(nw),c(nact),c(nzz),istate,its)
      call check(c(nz),lvls,c(map),nd,viol)
      if (mode.ge.2) print *,' l-1 misfit 1st trial ', viol
      if (mode.ge.3) call forwrd(c(nz),lvls,c(map),nd)
      if (viol .ge. chi*(1.0+tol) ) then
        print '(/)'
        print *,'**********************************************'
        print *,' DATA AND A PRIORI CONSTRAINTS INCONSISTENT.'
        print *,' ONE-NORM MISFIT ', viol, ' ALLOWED ',chi
        print *,' Check data, mapping matrix, and bounds.'
        print *,'**********************************************'
        go to 100
      endif
c
c  Open the penalty vector file.
      call getvec
c  Set up the bvls matrices  aa  and  bb.
      call prep(nd,lvls,nd+3,lvls+2*nd+3,c(map),c(naa),c(nbb))
c  Future calls to bvls are warm starts.
      key=1
c
c------------Loop for minimization and maximization.----------------
c
      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  Load the (scaled) vector into  aa and the target value into  bb.
      call prepv(lvls,nd+3,lvls+2*nd+3,c(npv),c(nbl),c(nbu),c(naa),
     +  c(nbb),j)
c  Update the upper bounds on the slacks for the objective value.
      c(nbu+lvls+2*nd+1)=4.0*(pmax-pmin)
      c(nbu+lvls+2*nd+2)=4.0*(pmax-pmin)
c  Loop to achieve target value of chi---changes to  scale .
      do 300 k=1,20
        call scalobj(nd+3,lvls+2*nd+3,c(naa))
        call bvls(key,nd+3,lvls+2*nd+3,c(naa),c(nbb),c(nbl),c(nbu),
     +   c(nz),c(nw),c(nact),c(nzz),istate,its)
c  Find the l-1 misfit for this model.
        call check(c(nz),lvls,c(map),nd,viol)
c  If the misfit is less than  chi , need to test whether the bound
c  was really found.  The optimal value must be achieved on the 
c  boundary of the constraint set.  Thus if the misfit constraint is
c  not active, the a priori bounds must be, and the value of the
c  objective functional should be the a priori bound.  If not, adjust 
c  the value of  scale  and return to the bvls call.
c  If the misfit is greater than  chi , the value of  scale  is too
c  large  (since the problem is already known to be feasible).  Adjust
c  the value of   scale  and try again.
c
c  First calculate the achieved value of the objective functional.
        call dot(lvls,c(nz),c(npv),bnd)
        if ( (j .eq. 0 .and. bnd .eq. pmin)
     +    .or. (j .eq. 1 .and. bnd .eq. pmax) 
     +    .or. ( (viol .gt. chi*(1.0-tol)) .and. 
     +         (viol .lt. chi*(1.0+tol)) ) ) 
     +    then
            go to 400
          else if (viol .lt. chi*(1.0-tol) ) then
            scale=2.0*scale
          else 
            scale=scale/2.0
        endif
        if (mode .gt. 2) print *,' l-1 misfit ', viol,
     +    ' Changing  scale  to ', scale
 300  continue
      print *,'**********************************************'
      print *,' Target misfit not achieved. '
      print *,' Target: ', chi, ' Actual: ', viol
      print *,' Scale may need to be changed. '
      print *,'**********************************************'
      go to 100
 400  continue
c  Print as indicated and store the extremal bound.
      if (mode.gt.1) then
        print '(/)'
        print *,'vector # ',i,' bound ',bnd,
     +  ' one-norm misfit ', viol,
     +  ' a priori bounds on objective:', pmin, pmax,
     +   ' bvls iterations ', its
      endif
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.
 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)
      go to 100
      end
c*********************************************************************
      subroutine check(z, m, a, nr, viol )
c*********************************************************************
c  Calculates one-norm violation of the data constraints.
c* Calls no other routines.
      implicit double precision (a-h,o-z)
      dimension z(m), a(nr,m)
      common /datset/ dbar(50),dsv(50),chi,nd,lvls,nv
      viol=0.0
      do 10 i=1,nd
      tx=0.0
      do 5 j=1,lvls
      tx=tx+a(i,j)*z(j)
5     continue
      viol=viol+abs(tx-dbar(i))/dsv(i)
10    continue
      return
      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*********************************************************************
      subroutine forwrd(z,m,a,nr)
c*********************************************************************
c  Calculates forward predictions from Z using matrix A, and
c  displays the predictions, dbar and dsv.
c* Calls no other routines.
      implicit double precision (a-h,o-z)
      dimension z(m), a(nr,m)
      common /datset/ dbar(50),dsv(50),chi,nd,lvls,nv
      print '(/)'
      print *,'  i    prediction         datum            std. dev.  '
      do 10 i=1,nd
      tx=0.0
      do 5 j=1,m
      tx=tx+a(i,j)*z(j)
5     continue
10    print '(i4,2x,3(g15.7,3x))',i,tx,dbar(i),dsv(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/ dbar(50),dsv(50),chi,nd,lvls,nv
      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/ dbar(50),dsv(50),chi,nd,lvls,nv
      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 *,''
      print *,'Enter file name for penalty vectors.'
      read '(a64)', name
      open (unit=id, file=name)
      return
      end
c*********************************************************************
      subroutine prep(m, n, mm, nn, a, aa, bb)
c*********************************************************************
c  This subroutine builds the greater part of the  bvls  arrays aa, bb.
c
C  The matrix  aa   and the ``data'' vector  bb  look like 
c
c  [ A  I  -I  0  0  0]                         [ dbar ]
c  [ 0  1   1  1  0  0]                         [ chi  ]
c  [pv  0   0  0  1 -1]                         [ p    ]
c  [ 0  0   0  0  1  1]                         [ 0    ]
c
c  Each of the first nd rows of A and dbar is multiplied by the 
c  smallest standard deviation and divided by its individual standard 
c  deviation.  The identity matrices are multiplied by the smallest 
c  standard deviation.
c  This routine builds the first nd+1 rows of  aa  and  bb , and the 
c  last 0 in  bb; the last two rows of  aa  and the next-to-last 
c  element of  bb  are built by prepv.
c  The last row of aa is modified by scalobj.
c
c* Calls no other routines.
c
      implicit double precision (a-h,o-z)
      dimension a(m,n), aa(mm,nn), bb(mm)
      common /datset/ dbar(50),dsv(50),chi,nd,lvls,nv
c
c  Initialize aa .
      do 10 i=1, mm
      do 10 j=1,nn
 10   aa(i,j)=0.0
c  Find the scaling.
      rmin=dsv(1)
      do 20 i=2,nd
 20   rmin=min(rmin,dsv(i))
      do 40 i=1,nd
         bb(i)=dbar(i)*rmin/dsv(i)
         aa(i,i+lvls)=rmin
         aa(i,i+lvls+nd)=-rmin
         aa(nd+1,i+lvls)=1.0
         aa(nd+1,i+lvls+nd)=1.0
         do 30 j=1,lvls
 30         aa(i,j)=a(i,j)*rmin/dsv(i)
 40   continue
      aa(nd+1,lvls+2*nd+1)=1.0
      bb(nd+1)=chi
      bb(nd+3)=0.0
      return
      end
c*********************************************************************
      subroutine prepv(n, m, nn, pv, bl, bu, aa, bb, key)
c*********************************************************************
c
c  Constructs the bottom two rows of  aa  using  scale  and the penalty
c  vector  pv.
c  Loads the scaled target value into the bottom of bb.
c  Finds a ``reasonable'' target value by computing a priori bounds
c  on pv.z given the upper and lower bounds on z.
c
c* Calls no other routines.
      implicit double precision (a-h, o-z)
      common /s/ scale, pmin, pmax
      common /datset/ dbar(50),dsv(50),chi,nd,lvls,nv
      dimension pv(n), bl(nn), bu(nn), aa(m,nn), bb(m)
c
c  Find a priori limits on pv.z given that bl<=z<=bu.
c
      pmax=0.0
      pmin=0.0
      pnorm=0.0
      do 10 i=1,lvls
      pnorm=pnorm+pv(i)**2
      if (pv(i) .ge. 0.0 ) then
        pmax=pmax+bu(i)*pv(i)
        pmin=pmin+bl(i)*pv(i)
      else
        pmax=pmax+bl(i)*pv(i)
        pmin=pmin+bu(i)*pv(i)
      endif
 10   continue
c  Scale and enter the elements of pv into aa and the target into bb.
      sc=1.0/sqrt( pnorm )
      if (key .eq. 0) then
        bb(nd+2)=(pmin-3.0*(pmax-pmin))*sc
      else
        bb(nd+2)=(pmax+3.0*(pmax-pmin))*sc
      endif
      do 20 i=1,lvls
 20      aa (nd+2, i)=pv(i)*sc
c  Set up slacks for objective value.
      aa (nd+2, lvls+2*nd+2)=1.d0
      aa (nd+2, lvls+2*nd+3)=-1.d0
      return
      end
c*********************************************************************
      subroutine scalobj(m,n,aa)
c*********************************************************************
c  Sets up the elements of  aa  to minimize the deviation from the
c  objective value.
c
c* Calls no other routines.
c
      implicit double precision (a-h, o-z)
      common /s/ scale, pmin, pmax
      common /datset/ dbar(50),dsv(50),chi,nd,lvls,nv
      dimension aa(m,n)
      aa (nd+3, lvls+2*nd+2)=scale
      aa (nd+3, lvls+2*nd+3)=scale
      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()  data values and their standard deviations.
c  nv is the number of penalty vectors to be bounded.
c  chi is the acceptable weighted one-norm misfit.
c
c* Calls no other routines.
      implicit double precision (a-h,o-z)
      common /datset/ dbar(50),dsv(50),chi,nd,lvls,nv
      common/print/mode,id
      common /s/ scale, pmin, pmax
      character*64 name
      print '(/''Input data file name.''/
     + ''Maximum of 50 data in the form:''/
     + ''   datum        standard deviation '')'
      read '(a64)', name
      print *,'Enter # data, # of model elements (<500), ',
     +  ' # of penalty vectors to bound,', 
     +  ' and allowable one-norm misfit.'
      read *,nd,lvls,nv,chi
      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,
     + ''datum '',4x,''standard dev.'')'
      do 200 i=1,nd
  200 print '(i4,7x,2g12.6)',i,dbar(i),dsv(i)
      print *,'Maximum misfit ', chi
      return
      end
c*********************************************************************
      subroutine slacks(m,bl,bu)
c*********************************************************************
c  Sets up the upper and lower bounds on all but the last 2 slack 
c  variables to impose the data constraints.
C  The last two slacks are set up in prepv.
c* Calls no other routines.
      implicit double precision (a-h, o-z)
      common /datset/ dbar(50),dsv(50),chi,nd,lvls,nv
      dimension bl(m), bu(m)
c  The lower bounds on all the slacks are zero, the upper bounds are
c  chi.
      do 10 i=1, nd
         bl(lvls+i)=0.0
         bl(lvls+nd+i)=0.0
         bu(lvls+i)=chi
 10      bu(lvls+nd+i)=chi
      bl(lvls+2*nd+1)=0.0
      bu(lvls+2*nd+1)=chi
      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/ dbar(50),dsv(50),chi,nd,lvls,nv
      common /icons/ nz,nbnd,map,npv,naa,nbb,nbl,nbu,nw,
     +  nzz,nact,ntot
      nz=1
      nbnd=nz+lvls+2*nd+3 
      map=nbnd+2*nv                    
      npv=map+lvls*nd
      naa=npv+lvls                     
      nbb=naa+(lvls+2*nd+3)*(nd+3)           
      nbl=nbb+nd+3                  
      nbu=nbl+lvls+2*nd+3
      nw=nbu+lvls+2*nd+3
      nzz=nw+lvls+2*nd+3
      nact=nzz+nd+3                 
      ntot=nzz+(nd+3)*(nd+5)     
      return
      end
