Table length reduction

From: D. C. Sessions <dc.sessions@vlsi.com>
Date: Mon May 08 2000 - 10:29:44 PDT

OK, I said I'd have a go at a program to intelligently
reduce the size of IBIS tables so that we can have high
resolution where needed and still keep below the 100-point
limit. Here is is, attached. The language will look
very, very strange to most of you; it's Icon, an academic
language that's sort of a cross between C and Prolog.
You can get documentation and a copy for most any platform at
http://www.cs.arizona.edu/icon/

The program as written is pretty crude. It takes JUST the
table data and thins it out using a cubic interpolation.
Command-line syntax is

decimate <pts> <file>

Where <pts> is the maximum number of points in the output,
and <file> is the source. Output is to standard output, and
if no input file is specified it defaults to standard input.
It expects each line to have the same number of fields (and
will barf if they don't match). For unjustifiable reasons it
does NOT accept scientific notation in; it does accept 'munp'
suffixes. The output is scientific. If I'd been more awake
I'd have done both in scientific and left formatting up to a
perl or awk script.

That said, I've experimented it several data sets and it seems
to produce reasonable results. Feedback, improvements, or a
complete rewrite to C welcome.

-- 
D. C. Sessions
dc.sessions@vlsi.com

# Data row: an independent variable and a set of dependent variables
record t_row( x, yvals )

# Get a sequence of words from a line of text,
# where the cset 'delim' separates words
procedure get_word( delim, line )
  local left, right, word
  left := right := 1
  while right <= *line do {
    left := many( delim, line, right ) | right
    right := many( &ascii -- delim, line, left ) | left
    suspend word := line[ left:right ]
    }
  end

# Convert a word to a numerical value.
# The numerical portion consists of the usual
# numerical prefix optionally followed by scale
# of the munp type. Maybe if I get ambitious I'll
# add a scientific notation option.
procedure get_numeric( word )
  local base
  base := get_word( ~( &digits ++ '.-' ), word )
  return case word[ *base+1 ] of {
    "m" : base / 1E3
    "u" : base / 1E6
    "n" : base / 1E9
    "p" : base / 1E12
    default : base
    } | base
  end

# Translate a table into a list of structures
procedure get_fn_set( source )
  local line, lineval, fn
  fn := []
  while line := read( source ) do {
    lineval := []
    every put( lineval, get_numeric( get_word( ' \t', line ) ) )
    put( fn, t_row( lineval[ 1 ], copy( lineval[ 2 : *lineval+1 ] ) ) )
    }
  return fn
  end

# Extract a single function from a list of structures
# In other words, select a single column from the dependent
# variable collection.
procedure get_single_fn( fn_set, column )
  local row, result
  result := []
  every row := !fn_set do
    put( result, t_row( row.x, row.yvals[ column ] ) )
  return result
  end

# Interpolate a value from at 'new_x' based on
# the values of the list of rows 'neighbors'
procedure interpolate( new_x, neighbors )
  local pick, flick, product, result
  result := 0
  every pick := !neighbors do {
    product := pick.yvals
    every ( flick := !neighbors ) ~=== pick do
      product *:= ( new_x - flick.x )/( pick.x - flick.x )
    result +:= product
    }
  return result
  end

# Get an interval of 2n+1 centered at 'ctr' if
# possible but bounded by 1 and 'mx'
procedure left_bound( ctr, n, mx )
  return if ctr < n + 1
    then 1
    else if ctr > mx - n
      then mx - ( n + n )
      else ctr - n
  end

# Select a list of 2* 'n' neighbors for 'row' from list 'fn'
# and return the selected list
procedure pick_window( row, n, fn )
  local lend, rend
  lend := left_bound( row, n, *fn )
  rend := lend + n + n
  return fn[ lend:row ] ||| fn[ row+1:rend+1 ]
  end

# Compare the interpolated value at a row to
# the actual value. Return the relative error
procedure epsilon( row, n, fn, scale )
  local ival
  ival := interpolate( fn[ row ].x, pick_window( row, n, fn ) )
  return abs( ( ival - fn[ row ].yvals )/scale )
  end

# Build a table of error terms from a set of functions
# using an interpolation of order ( 2n-1 )
procedure err_tab( n, fn_set )
  local cn, j, row, result, scale, column
  result := table( 0 )
  every insert( result, !fn_set, 0 )
  every cn := 1 to *(fn_set[1].yvals) do {
    column := get_single_fn( fn_set, cn )
    scale := 0
    every row := !column do scale := scale < abs( row.yvals )
    every j := 1 to *column do
      result[ fn_set[ j ] ] := result[ fn_set[ j ] ] < epsilon( j, 2, column, scale )
    }
  return result
  end

# Decide which table elements to keep and which to cull
# Start by culling the elements which have the least
# error values, but keep their neighbors for this pass
# Return an edited function list
procedure cull( n, fn_set )
  local pick, j, l, r, etab, index_tab, cull_list, cull_tab, fn_size
  fn_size := *fn_set
  etab := err_tab( n, fn_set )
  index_tab := table( 1 )
  cull_tab := []
  every j := 1 to fn_size do {
    insert( index_tab, fn_set[ j ], j )
    put( cull_tab, 0 ) # Start with the decision unknown
    }
  cull_tab[ 1 ] := 1 # keep the first and last elements
  cull_tab[ fn_size ] := 1
                                # sort to a record list by values
  cull_list := sort( etab, 2 )[1:(fn_size/(n+n+1))] # Limit the number of culls
  every pick := index_tab[ (!cull_list)[1] ] do
    if 1 > cull_tab[ pick ] then {
      cull_tab[ pick ] := -1 # Ditch it
      l := left_bound( pick, n, *fn_set )
      r := l + n + n
      every cull_tab[ pick ~= ( l to r ) ] := 1 # Keep the neighbors
      }
  result := []
  every pick := 1 to *fn_set do
    if cull_tab[ pick ] >= 0
      then put( result, fn_set[ pick ] )
# else {
# writes( "Culled " )
# write_fn_line( fn_set[ pick ] )
# }
  return result
  end

# Round off a number 'x' to 'n' digits precision
procedure roundoff( n, x )
  base := log( 10 )
  if x = 0
    then return 0
    else {
      scale := integer( log( abs( x ), 10 ) )
      fudge := integer( exp( ( n - scale )*base ) )
# writes( "(" || n - scale || " " || fudge || ")" )
      return integer( x * fudge ) / real( fudge )
      }
  end

# Write out a line of a function
procedure write_fn_line( fn_line )
  local cwidth
  cwidth := 12
  writes( right( roundoff( 3, fn_line.x ), cwidth, " " ) )
  every writes( right( roundoff( 3, !fn_line.yvals ), cwidth, " " ) )
  write( )
  end

procedure main( argv )
  limit := pop( argv )
  source := open( pop( argv, src ) ) | &input
   fn_set := get_fn_set( source )
   foo := fn_set
   while *foo > limit do foo := cull( 2, foo )
   every bar := !foo do write_fn_line( bar )
  end
Received on Mon May 8 10:32:13 2000

This archive was generated by hypermail 2.1.8 : Fri Jun 03 2011 - 09:52:30 PDT