Reorganized, simplified, and fixed a few function and operator
definitions in the internal Tuple module.  The operator overloads are
now much simpler.  I also fixed the unary operator implementations to
call the unary operators on the components always (this wasn't called
for unary +) and to return a new tuple that may differ in type.

git-svn-id: 88467cb1fb04b8a755be7e1ee1026be4190196ef@16712 3a8e244f-b0f2-452b-bcba-4c88e055c3ca

//
// tuple data implementation as a record
//
pragma "tuple" record _tuple {
  param size : int;
}

//
// syntactic support for tuples
//
pragma "inline" def _build_tuple(x...) {
  if x.size == 1 then
    return x(1);
  else
    return x;
}

pragma "inline" pragma "allow ref" def _build_tuple_always_allow_ref(x ...?size)
  return x;

pragma "inline" def _build_tuple(type t ...?size) type
  return t;

pragma "inline" def _build_tuple_always(x ...?size)
  return x;

//
// homogeneous tuple type syntax
//
def *(param p: int, type t) type {
  var oneTuple: _build_tuple(t);
  def _fill(param p: int, x: _tuple) {
    if x.size == p then
      return x;
    else if 2*x.size <= p then
      return _fill(p, ((...x), (...x)));
    else
      return _fill(p, ((...x), (..._fill(p-x.size, oneTuple))));
  }
  if p <= 0 then
    compilerError("tuple must have positive size");
  return _fill(p, oneTuple).type;
}

def *(p: int, type t) type {
  compilerError("tuple size must be static");
}

//
// standard tuple creation function
//
pragma "inline" def tuple(x ...?size)
  return x;

//
// isTuple and isHomogeneousTuple param functions
//
def isTuple(x: _tuple) param
  return true;

def isTuple(x) param
  return false;

def isHomogeneousTuple(x: _tuple) param
  return __primitive("is star tuple type", x);

//
// tuple assignment
//
pragma "inline" def =(x: _tuple, y: _tuple) where x.size == y.size {
  for param i in 1..x.size do
    x(i) = y(i);
  return x;
}

//
// homogeneous tuple accessor
//
def _tuple.this(i : integral) var {
  if !isHomogeneousTuple(this) then
    compilerError("invalid access of non-homogeneous tuple by runtime value");
  if boundsChecking then
    if i < 1 || i > size then
      halt("tuple access out of bounds: ", i);
  return __primitive("get svec member", this, i);
}

//
// tuple methods
//
def _tuple.writeThis(f: Writer) {
  f.write("(", this(1));
  for param i in 2..size do
    f.write(", ", this(i));
  f.write(")");
}

//
// tuple casts to complex(64) and complex(128)
//
pragma "inline" def _cast(type t, x: (?,?)) where t == complex(64) {
  var c: complex(64);
  c.re = x(1):real(32);
  c.im = x(2):real(32);
  return c;
}

pragma "inline" def _cast(type t, x: (?,?)) where t == complex(128) {
  var c: complex(128);
  c.re = x(1):real(64);
  c.im = x(2):real(64);
  return c;
}

//
// helper function to return a tuple of all of the components in a
// tuple except the first
//
pragma "inline" def chpl__tupleRest(t: _tuple) {
  def chpl__tupleRestHelper(first, rest...)
    return rest;
  return chpl__tupleRestHelper((...t));
}

//
// standard overloaded unary operators on tuples
//
pragma "inline" def +(a: _tuple) {
  if a.size == 1 then
    return tuple(+a(1));
  else
    return tuple(+a(1), (...+chpl__tupleRest(a)));
}

pragma "inline" def -(a: _tuple) {
  if a.size == 1 then
    return tuple(-a(1));
  else
    return tuple(-a(1), (...-chpl__tupleRest(a)));
}

pragma "inline" def ~(a: _tuple) {
  if a.size == 1 then
    return tuple(~a(1));
  else
    return tuple(~a(1), (...~chpl__tupleRest(a)));
}

pragma "inline" def !(a: _tuple) {
  if a.size == 1 then
    return tuple(!a(1));
  else
    return tuple(!a(1), (...!chpl__tupleRest(a)));
}

//
// standard overloaded binary operators on tuples
//
pragma "inline" def +(a: _tuple, b: _tuple) {
  if a.size != b.size then
    compilerError("tuple operands to + have different sizes");
  if a.size == 1 then
    return tuple(a(1)+b(1));
  else
    return tuple(a(1)+b(1), (...chpl__tupleRest(a)+chpl__tupleRest(b)));
}

pragma "inline" def -(a: _tuple, b: _tuple) {
  if a.size != b.size then
    compilerError("tuple operands to - have different sizes");
  if a.size == 1 then
    return tuple(a(1)-b(1));
  else
    return tuple(a(1)-b(1), (...chpl__tupleRest(a)-chpl__tupleRest(b)));
}

pragma "inline" def *(a: _tuple, b: _tuple) {
  if a.size != b.size then
    compilerError("tuple operands to * have different sizes");
  if a.size == 1 then
    return tuple(a(1)*b(1));
  else
    return tuple(a(1)*b(1), (...chpl__tupleRest(a)*chpl__tupleRest(b)));
}

pragma "inline" def /(a: _tuple, b: _tuple) {
  if a.size != b.size then
    compilerError("tuple operands to / have different sizes");
  if a.size == 1 then
    return tuple(a(1)/b(1));
  else
    return tuple(a(1)/b(1), (...chpl__tupleRest(a)/chpl__tupleRest(b)));
}

pragma "inline" def %(a: _tuple, b: _tuple) {
  if a.size != b.size then
    compilerError("tuple operands to % have different sizes");
  if a.size == 1 then
    return tuple(a(1)%b(1));
  else
    return tuple(a(1)%b(1), (...chpl__tupleRest(a)%chpl__tupleRest(b)));
}

pragma "inline" def **(a: _tuple, b: _tuple) {
  if a.size != b.size then
    compilerError("tuple operands to ** have different sizes");
  if a.size == 1 then
    return tuple(a(1)**b(1));
  else
    return tuple(a(1)**b(1), (...chpl__tupleRest(a)**chpl__tupleRest(b)));
}

pragma "inline" def &(a: _tuple, b: _tuple) {
  if a.size != b.size then
    compilerError("tuple operands to & have different sizes");
  if a.size == 1 then
    return tuple(a(1)&b(1));
  else
    return tuple(a(1)&b(1), (...chpl__tupleRest(a)&chpl__tupleRest(b)));
}

pragma "inline" def |(a: _tuple, b: _tuple) {
  if a.size != b.size then
    compilerError("tuple operands to | have different sizes");
  if a.size == 1 then
    return tuple(a(1)|b(1));
  else
    return tuple(a(1)|b(1), (...chpl__tupleRest(a)|chpl__tupleRest(b)));
}

pragma "inline" def ^(a: _tuple, b: _tuple) {
  if a.size != b.size then
    compilerError("tuple operands to ^ have different sizes");
  if a.size == 1 then
    return tuple(a(1)^b(1));
  else
    return tuple(a(1)^b(1), (...chpl__tupleRest(a)^chpl__tupleRest(b)));
}

pragma "inline" def <<(a: _tuple, b: _tuple) {
  if a.size != b.size then
    compilerError("tuple operands to << have different sizes");
  if a.size == 1 then
    return tuple(a(1)<<b(1));
  else
    return tuple(a(1)<<b(1), (...chpl__tupleRest(a)<<chpl__tupleRest(b)));
}

pragma "inline" def >>(a: _tuple, b: _tuple) {
  if a.size != b.size then
    compilerError("tuple operands to >> have different sizes");
  if a.size == 1 then
    return tuple(a(1)>>b(1));
  else
    return tuple(a(1)>>b(1), (...chpl__tupleRest(a)>>chpl__tupleRest(b)));
}

//
// standard overloaded relational operators on tuples
//
pragma "inline" def >(a: _tuple, b: _tuple) {
  if a.size != b.size then
    compilerError("tuple operands to > have different sizes");
  for param i in 1..a.size do
    if a(i) > b(i) then
      return true;
    else if a(i) < b(i) then
      return false;
  return false;
}

pragma "inline" def >=(a: _tuple, b: _tuple) {
  if a.size != b.size then
    compilerError("tuple operands to >= have different sizes");
  for param i in 1..a.size do
    if a(i) > b(i) then
      return true;
    else if a(i) < b(i) then
      return false;
  return true;
}

pragma "inline" def <(a: _tuple, b: _tuple) {
  if a.size != b.size then
    compilerError("tuple operands to < have different sizes");
  for param i in 1..a.size do
    if a(i) < b(i) then
      return true;
    else if a(i) > b(i) then
      return false;
  return false;
}

pragma "inline" def <=(a: _tuple, b: _tuple) {
  if a.size != b.size then
    compilerError("tuple operands to <= have different sizes");
  for param i in 1..a.size do
    if a(i) < b(i) then
      return true;
    else if a(i) > b(i) then
      return false;
  return true;
}

pragma "inline" def ==(a: _tuple, b: _tuple) {
  if a.size != b.size then
    return false;
  for param i in 1..a.size do
    if a(i) != b(i) then
      return false;
  return true;
}

pragma "inline" def !=(a: _tuple, b: _tuple) {
  if a.size != b.size then
    return true;
  for param i in 1..a.size do
    if a(i) != b(i) then
      return true;
  return false;
}

//
// square tuples
//
record _square_tuple {
  param size: int;
  var tuple;

  pragma "expand tuples with values"
  def these_help(param dim: int) {
    if dim == size - 1 {
      for i in tuple(dim) do
        for j in tuple(size) do
          yield _build_tuple_always_allow_ref(i, j);
    } else {
      for i in tuple(dim) do
        for j in these_help(dim+1) do
          yield _build_tuple_always_allow_ref(i, (...j));
    }
  }

  def these() {
    if size == 1 {
      for i in tuple(1) do
        yield i;
    } else {
      for i in these_help(1) do
        yield i;
    }
  }

  pragma "inline iterator"
  pragma "expand tuples with values"
  def lead_help(param dim: int) {
    if dim == size - 1 {
      for i in _toLeader(tuple(dim)) do
        for j in _toLeader(tuple(size)) do
          yield _build_tuple_always_allow_ref(i, j);
    } else {
      for i in _toLeader(tuple(dim)) do
        for j in lead_help(dim+1) do
          yield _build_tuple_always_allow_ref(i, (...j));
    }
  }

  def these(param tag: iterator) where tag == iterator.leader {
    if size == 1 {
      for i in _toLeader(tuple(1)) do
        yield i;
    } else {
      for i in lead_help(1) do
        yield i;
    }
  }

  pragma "expand tuples with values"
  def follow_help(param dim: int, follower) {
    if dim == size - 1 {
      for i in _toFollower(tuple(dim), follower(dim)) do
        for j in _toFollower(tuple(size), follower(size)) do
          yield _build_tuple_always_allow_ref(i, j);
    } else {
      for i in _toFollower(tuple(dim), follower(dim)) do
        for j in follow_help(dim+1) do
          yield _build_tuple_always_allow_ref(i, (...j));
    }
  }

  def these(param tag: iterator, follower) where tag == iterator.follower {
    if size == 1 {
      for i in _toFollower(tuple(1), follower) do
        yield i;
    } else {
      for i in follow_help(1, follower) do
        yield i;
    }
  }
}

//
// syntactic support for square tuples
//
def chpl__buildDomainExpr(x ...?size) where size > 1
  return new _square_tuple(size, x);

def *(param p: int, type t) type {
  var oneTuple: _build_tuple(t);
  def _fill(param p: int, x: _tuple) {
    if x.size == p then
      return x;
    else if 2*x.size <= p then
      return _fill(p, ((...x), (...x)));
    else
      return _fill(p, ((...x), (..._fill(p-x.size, oneTuple))));
  }
  if p <= 0 then
    compilerError("tuple must have positive size");
  return _fill(p, oneTuple).type;
}

def *(p: int, type t) type {
  compilerError("tuple size must be static");
}

def _isStarTupleType(x: _tuple) param
  return __primitive("is star tuple type", x);

pragma "tuple" record _tuple {
  param size : int;

  def this(i : integral) var {
    if !_isStarTupleType(this) then
      compilerError("cannot index into non-homogeneous tuples with non-parameter value");
    if boundsChecking then
      if i < 1 || i > size then
        halt("tuple indexing out-of-bounds error");
    return __primitive("get svec member", this, i);
  }
}

pragma "inline" def =(x: _tuple, y: _tuple) where x.size == y.size {
  for param i in 1..x.size do
    x(i) = y(i);
  return x;
}

def ==( a: _tuple, b: _tuple): bool {
  if (a.size != b.size) then
    return false;
  else {
    for param i in 1..a.size do
      if (a(i) != b(i)) then
        return false;
    return true;
  }
}

def !=( a: _tuple, b: _tuple): bool {
  if (a.size != b.size) then
    return true;
  else {
    for param i in 1..a.size do
      if (a(i) != b(i)) then
        return true;
    return false;
  }
}

def _tuple.writeThis(f: Writer) {
  f.write("(", this(1));
  for param i in 2..size do
    f.write(", ", this(i));
  f.write(")");
}

pragma "inline" def _cast(type t, x: _tuple) where (t == complex(64)) && (x.size == 2) {
  var c: complex(64);
  c.re = x(1):real(32);
  c.im = x(2):real(32);
  return c;
}

pragma "inline" def _cast(type t, x: _tuple) where (t == complex(128)) && (x.size == 2) {
  var c: complex(128);
  c.re = x(1):real(64);
  c.im = x(2):real(64);
  return c;
}

pragma "inline" def +(a: _tuple) {
  return a;
}

pragma "inline" def -(a: _tuple) {
  for param i in 1..a.size do
    a(i) = -a(i);
  return a;
}

pragma "inline" def +(a: _tuple, b: _tuple) {
  pragma "inline" def help(param i: int) {
    if i == a.size {
      var result = a(i) + b(i);
      var onetuple: 1 * result.type;
      onetuple(1) = result;
      return onetuple;
    } else {
      var rest = help(i+1);
      return (a(i) + b(i), (...rest));
    }
  }

  if (a.size != b.size) then
    compilerError("Tuples must have the same size");

  return help(1);
}

pragma "inline" def -(a: _tuple, b: _tuple) {
  pragma "inline" def help(param i: int) {
    if i == a.size {
      var result = a(i) - b(i);
      var onetuple: 1 * result.type;
      onetuple(1) = result;
      return onetuple;
    } else {
      var rest = help(i+1);
      return (a(i) - b(i), (...rest));
    }
  }

  if (a.size != b.size) then
    compilerError("Tuples must have the same size");

  return help(1);
}

pragma "inline" def *(a: _tuple, b: _tuple) {
  pragma "inline" def help(param i: int) {
    if i == a.size {
      var result = a(i) * b(i);
      var onetuple: 1 * result.type;
      onetuple(1) = result;
      return onetuple;
    } else {
      var rest = help(i+1);
      return (a(i) * b(i), (...rest));
    }
  }

  if (a.size != b.size) then
    compilerError("Tuples must have the same size");

  return help(1);
}

pragma "inline" def /(a: _tuple, b: _tuple) {
  pragma "inline" def help(param i: int) {
    if i == a.size {
      var result = a(i) / b(i);
      var onetuple: 1 * result.type;
      onetuple(1) = result;
      return onetuple;
    } else {
      var rest = help(i+1);
      return (a(i) / b(i), (...rest));
    }
  }

  if (a.size != b.size) then
    compilerError("Tuples must have the same size");

  return help(1);
}

pragma "inline" def %(a: _tuple, b: _tuple) {
  pragma "inline" def help(param i: int) {
    if i == a.size {
      var result = a(i) % b(i);
      var onetuple: 1 * result.type;
      onetuple(1) = result;
      return onetuple;
    } else {
      var rest = help(i+1);
      return (a(i) % b(i), (...rest));
    }
  }

  if (a.size != b.size) then
    compilerError("Tuples must have the same size");

  return help(1);
}

pragma "inline" def **(a: _tuple, b: _tuple) {
  pragma "inline" def help(param i: int) {
    if i == a.size {
      var result = a(i) ** b(i);
      var onetuple: 1 * result.type;
      onetuple(1) = result;
      return onetuple;
    } else {
      var rest = help(i+1);
      return (a(i) ** b(i), (...rest));
    }
  }

  if (a.size != b.size) then
    compilerError("Tuples must have the same size");

  return help(1);
}

pragma "inline" def ~(a: _tuple) {
  for param i in 1..a.size do
    a(i) = ~a(i);
  return a;
}

pragma "inline" def &(a: _tuple, b: _tuple) {
  pragma "inline" def help(param i: int) {
    if i == a.size {
      var result = a(i) & b(i);
      var onetuple: 1 * result.type;
      onetuple(1) = result;
      return onetuple;
    } else {
      var rest = help(i+1);
      return (a(i) & b(i), (...rest));
    }
  }

  if (a.size != b.size) then
    compilerError("Tuples must have the same size");

  return help(1);
}

pragma "inline" def |(a: _tuple, b: _tuple) {
  pragma "inline" def help(param i: int) {
    if i == a.size {
      var result = a(i) | b(i);
      var onetuple: 1 * result.type;
      onetuple(1) = result;
      return onetuple;
    } else {
      var rest = help(i+1);
      return (a(i) | b(i), (...rest));
    }
  }

  if (a.size != b.size) then
    compilerError("Tuples must have the same size");

  return help(1);
}

pragma "inline" def ^(a: _tuple, b: _tuple) {
  pragma "inline" def help(param i: int) {
    if i == a.size {
      var result = a(i) ^ b(i);
      var onetuple: 1 * result.type;
      onetuple(1) = result;
      return onetuple;
    } else {
      var rest = help(i+1);
      return (a(i) ^ b(i), (...rest));
    }
  }

  if (a.size != b.size) then
    compilerError("Tuples must have the same size");

  return help(1);
}

pragma "inline" def <<(a: _tuple, b: _tuple) {
  pragma "inline" def help(param i: int) {
    if i == a.size {
      var result = a(i) << b(i);
      var onetuple: 1 * result.type;
      onetuple(1) = result;
      return onetuple;
    } else {
      var rest = help(i+1);
      return (a(i) << b(i), (...rest));
    }
  }

  if (a.size != b.size) then
    compilerError("Tuples must have the same size");

  return help(1);
}

pragma "inline" def >>(a: _tuple, b: _tuple) {
  pragma "inline" def help(param i: int) {
    if i == a.size {
      var result = a(i) >> b(i);
      var onetuple: 1 * result.type;
      onetuple(1) = result;
      return onetuple;
    } else {
      var rest = help(i+1);
      return (a(i) >> b(i), (...rest));
    }
  }
  if (a.size != b.size) then
    compilerError("Tuples must have the same size");

  return help(1);
}

pragma "inline" def !(a: _tuple) {
  var b: a.size * bool;
  for param i in 1..a.size do
    b(i) = !a(i);
  return b;
}

pragma "inline" def >(a: _tuple, b: _tuple) {
  if (a.size != b.size) then
    compilerError("Tuples must have the same size");

  for param i in 1..a.size do
    if a(i) > b(i) then
      return true;
    else if a(i) < b(i) then
      return false;
  return false;
}

pragma "inline" def >=(a: _tuple, b: _tuple) {
  if (a.size != b.size) then
    compilerError("Tuples must have the same size");

  for param i in 1..a.size do
    if a(i) > b(i) then
      return true;
    else if a(i) < b(i) then
      return false;
  return true;
}

pragma "inline" def <(a: _tuple, b: _tuple) {
  if (a.size != b.size) then
    compilerError("Tuples must have the same size");

  for param i in 1..a.size do
    if a(i) < b(i) then
      return true;
    else if a(i) > b(i) then
      return false;
  return false;
}

pragma "inline" def <=(a: _tuple, b: _tuple) {
  if (a.size != b.size) then
    compilerError("Tuples must have the same size");

  for param i in 1..a.size do
    if a(i) < b(i) then
      return true;
    else if a(i) > b(i) then
      return false;
  return true;
}

record _square_tuple {
  param size: int;
  var tuple;

  pragma "expand tuples with values"
  def these_help(param dim: int) {
    if dim == size - 1 {
      for i in tuple(dim) do
        for j in tuple(size) do
          yield _build_tuple_always_allow_ref(i, j);
    } else {
      for i in tuple(dim) do
        for j in these_help(dim+1) do
          yield _build_tuple_always_allow_ref(i, (...j));
    }
  }

  def these() {
    if size == 1 {
      for i in tuple(1) do
        yield i;
    } else {
      for i in these_help(1) do
        yield i;
    }
  }

  pragma "inline iterator"
  pragma "expand tuples with values"
  def lead_help(param dim: int) {
    if dim == size - 1 {
      for i in _toLeader(tuple(dim)) do
        for j in _toLeader(tuple(size)) do
          yield _build_tuple_always_allow_ref(i, j);
    } else {
      for i in _toLeader(tuple(dim)) do
        for j in lead_help(dim+1) do
          yield _build_tuple_always_allow_ref(i, (...j));
    }
  }

  def these(param tag: iterator) where tag == iterator.leader {
    if size == 1 {
      for i in _toLeader(tuple(1)) do
        yield i;
    } else {
      for i in lead_help(1) do
        yield i;
    }
  }

  pragma "expand tuples with values"
  def follow_help(param dim: int, follower) {
    if dim == size - 1 {
      for i in _toFollower(tuple(dim), follower(dim)) do
        for j in _toFollower(tuple(size), follower(size)) do
          yield _build_tuple_always_allow_ref(i, j);
    } else {
      for i in _toFollower(tuple(dim), follower(dim)) do
        for j in follow_help(dim+1) do
          yield _build_tuple_always_allow_ref(i, (...j));
    }
  }

  def these(param tag: iterator, follower) where tag == iterator.follower {
    if size == 1 {
      for i in _toFollower(tuple(1), follower) do
        yield i;
    } else {
      for i in follow_help(1, follower) do
        yield i;
    }
  }
}

def chpl__buildDomainExpr(x ...?size) where size > 1
  return new _square_tuple(size, x);

pragma "inline" def _build_tuple(x ...?size) {
  if size == 1 then
    return x(1);
  else
    return x;
}

pragma "inline" pragma "allow ref" def _build_tuple_always_allow_ref(x ...?size)
  return x;

pragma "inline" def _build_tuple(type t ...?size) type
  return t;

pragma "inline" def _build_tuple_always(x ...?size)
  return x;

pragma "inline" def tuple(x ...?size)
  return x;


Here's where this exercise is ending today.  I
replaced a bunch of explicit complex manipulation
with multiplications by 1.0i following Steve's
nice suggestion.  Haven't replaced those weirder
complex expressions yet -- still trying to intuit
some sense from them.

git-svn-id: 88467cb1fb04b8a755be7e1ee1026be4190196ef@7786 3a8e244f-b0f2-452b-bcba-4c88e055c3ca

// BLC: For certain inputs, not having a complex/real
// function can result in numerical instability.

// BLC: clean up complex arithmetic

// BLC: insert problem size computation based on memory size?

use BitOps;
use Random;
use Time;

// problem size configs
config const logN = 5;

param radix = 4;

// pseudo-random input configs
config const deterministic = false,
             seed = 314159265.0,
             arand = 1220703125.0;

// verification configs
config const epsilon = 2.0 ** -51.0,
             threshold = 16.0;

// boolean configs
config const printTiming = true;

// BLC: control printing out of error value to avoid portability issues

def main() {
  // compute problem size
  const N = 1 << logN;

  // twiddle domain and arrays
  const TwiddleDom = [0..N/4);
  var Twiddles: [TwiddleDom] complex;

  computeTwiddles(Twiddles);
  bitReverseShuffle(Twiddles);

  // problem domain and arrays
  const ProblemDom = [0..N);
  var Z, z: [ProblemDom] complex;

  // generate pseudo-random input
  if deterministic then
    fillRandom(z, seed=seed, arand=arand);
  else
    fillRandom(z);

  // conjugate input, storing result to work array
  Z = conjg(z);

  // TIMED SECTION
  var startTime = getCurrentTime();

  bitReverseShuffle(Z);
  dfft(Z, Twiddles);

  var execTime = getCurrentTime() - startTime;

  verifyResults(z, Z, execTime, Twiddles);
}

def computeTwiddles(W) {
  const n = W.numElements;
  const delta = 2.0 * atan(1.0) / n;

  W(0) = 1.0;
  W(n/2) = let x = cos(delta * n/2)
            in (x, x):complex;
  forall i in [1..n/2) {
    const x = cos(delta*i);
    const y = sin(delta*i);
    W(i)     = (x, y):complex;
    W(n - i) = (y, x):complex;
  }
}

def bitReverseShuffle(W: [?WD]) {
  const n = WD.numIndices;
  const reverse = lg(n);
  var V: [WD] W.elt_type;  // BLC: rename elt_type to remove underscore?
  // BLC: would like to use:
  //  var Perm: [i in WD] int = bitReverse(i:uint(64), numBits = reverse):int;
  var Perm: [WD] index(WD);
  [i in WD] Perm(i) = bitReverse(i, numBits = reverse);
  // BLC: would like to use:
  // V(Perm) = W;
  [i in WD] V(Perm(i)) = W(i);
  W = V;
}

// reverses numBits low-order bits of val
def bitReverse(val: ?valType, numBits = 64) {
  param mask: uint(64) = 0x0102040810204080;
  const valReverse64 = bitMatMultOr(mask, bitMatMultOr(val:uint(64), mask));
  const valReverse = bitRotLeft(valReverse64, numBits);
  return valReverse: valType;
}

def dfft(Z, W) {
  cft1st(Z, W);

  var span = radix;
  var lasti = 2;
  for i in radix..logN/2 by 2 {
    cftmd1(span, Z, W);
    span *= radix;
    lasti = i;
  }
  for i in [lasti+2..logN) by 2 {
    cftmd2(span, Z, W);
    span *= radix;
  }

  const n = Z.numElements;
  if (radix*span == n) {
    forall j in [0..span) {
      butterfly(1.0, 1.0, 1.0, Z[j..j+3*span by span]);
    }
  } else {
    forall j in [0..span) {
      var a = Z(j);
      var b = Z(j+span);
      Z(j) = a + b;
      Z(j+span) = a - b;
    }
  }
}

def verifyResults(z, Z, execTime, Twiddles) {
  const N = Z.numElements;

  // BLC: This line wants /(complex,real) to be implemented directly:
  Z = conjg(Z) / N;

  bitReverseShuffle(Z);
  dfft(Z, Twiddles);

  // BLC: need to check this against written spec
  var maxerr = max reduce sqrt((z.real - Z.real)**2 + (z.imag - Z.imag)**2);

  maxerr = maxerr / logN / epsilon;

  write(if (maxerr < threshold) then "SUCCESS" else "FAILURE");
  writeln(", error = ", maxerr);
  writeln();
  writeln("N      = ", N);
  if (printTiming) {
    writeln("Time   = ", execTime);
    const gflop = 5.0 * N * logN / 1000000000.0;
    writeln("GFlops = ", gflop / execTime);
  }
}

def cft1st(A, W) {
  const n = A.domain(1).length;
  var x0 = A(0) + A(1);
  var x1 = A(0) - A(1);
  var x2 = A(2) + A(3);
  var x3rot = (A(2) - A(3))*1.0i;

  const wk1r = W(1).real;

  A(0) = x0 + x2;
  A(2) = x0 - x2;
  A(1) = x1 + x3rot;
  A(3) = x1 - x3rot;

  x0 = A(4) + A(5);
  x1 = A(4) - A(5);
  x2 = A(6) + A(7);
  var x3 = A(6) - A(7);  // BLC: try to eliminate this
  x3rot = (A(6) - A(7))*1.0i;
  A(4) = x0 + x2;
  A(6) = (x2.imag - x0.imag, x0.real - x2.real):complex;
  x0 = x1 + x3rot;
  A(5) = wk1r * (x0.real - x0.imag, x0.real + x0.imag):complex;
  x0 = (x3.imag + x1.real, x3.real - x1.imag):complex;
  A(7) = wk1r * (x0.imag - x0.real, x0.imag + x0.real):complex;

  // BLC: would like to use an indefinite arithmetic array here
  // BLC: would also like to use () on both indices and zipping
  //      together of ranges
  forall j,k1 in [8..n) by 8, 1..(n-8)/8 {
    var wk2 = W(k1);
    var wk1 = W(2*k1);
    var wk3 = (wk1.real - 2* wk2.imag * wk1.imag,
               2 * wk2.imag * wk1.real - wk1.imag):complex;

    butterfly(wk1, wk2, wk3, A[j..j+3]);

    wk1 = W(2*k1+1);
    wk3 = (wk1.real - 2*wk2.real * wk1.imag,
           2*wk2.real * wk1.real - wk1.imag):complex;
    wk2 = wk2*1.0i;
    butterfly(wk1, wk2, wk3, A[j+4..j+7]);
  }
}

def cftmd0(span, A, W) {
  var wk1r = W(1).real;
  const m = radix*span;

  forall j in [0..span) {
    butterfly(1.0, 1.0, 1.0, A[j..j+3*span by span]);
  }

  forall j in [m..m+span) {
    butterfly((wk1r, wk1r):complex, 1.0i, (-wk1r, wk1r):complex,
              A[j..j+3*span by span]);
  }
}

def cftmd1(span, A, W) {
  const m = radix*span;
  const m2 = 2*m;
  const n = A.domain(1).length;

  cftmd0(span, A, W);
  forall k,k1 in [m2..n) by m2, 1..(n-m2)/m2 {
    var wk2 = W[k1];
    var wk1 = W[2*k1];
    var wk3 = (wk1.real - 2 * wk2.imag * wk1.imag,
               2 * wk2.imag * wk1.real - wk1.imag):complex;
    for j in [k..k+span) {
      butterfly(wk1, wk2, wk3, A[j..j+3*span by span]);
    }

    wk1 = W[2*k1+1];
    wk3 = (wk1.real - 2 * wk2.real * wk1.imag,
           2 * wk2.real * wk1.real - wk1.imag):complex;

    for j in [k+m..k+m+span) {
      butterfly(wk1, wk2*1.0i, wk3, A[j..j+3*span by span]);
    }
  }
}

def cftmd2(span, A, W) {
  var m = radix*span;
  var m2 = 2*m;
  const n = A.domain(1).length;

  cftmd0(span, A, W);
  if (m2 >= n) return;
  if (m2 >= n / 8) {
    cftmd21(span, A, W);
    return;
  }

  forall j in [0..span) {
    forall k,k1 in [m2..n) by m2, 1..(n-m2)/m2 {
      var wk2 = W[k1];
      var wk1 = W[k1 + k1];
      var wk3 = (wk1.real - 2*wk2.imag * wk1.imag,
                 2 * wk2.imag * wk1.real - wk1.imag):complex;
      butterfly(wk1, wk2, wk3, A[j+k..j+k+3*span by span]);
    }

    forall k,k1 in [m2..n) by m2, 1..(n-m2)/m2 {
      var wk2 = W[k1];
      var wk1 = W[2*k1 + 1];
      var wk3 = (wk1.real - 2*wk2.real * wk1.imag,
                 2*wk2.real * wk1.real - wk1.imag):complex;
      wk2 = wk2*1.0i;

      butterfly(wk1, wk2, wk3, A[j+k+m..j+k+m+3*span by span]);
    }
  }
}

def cftmd21(span, A, W) {
  const n = A.domain(1).length;
  var m = radix*span;
  var m2 = 2*m;

  for k,k1 in [m2..n) by m2, 1..(n-m2)/m2 {
    var wk2 = W[k1];
    var wk1 = W[2*k1];
    var wk3 = (wk1.real - 2*wk2.imag * wk1.imag,
               2* wk2.imag * wk1.real - wk1.imag):complex;

    forall j in [k..k+span) {
      butterfly(wk1, wk2, wk3, A[j..j+3*span by span]);
    }

    wk1 = W[2*k1 + 1];
    wk3 = (wk1.real - 2*wk2.real * wk1.imag,
           2*wk2.real * wk1.real - wk1.imag):complex;
    wk2 = wk2*1.0i;

    forall j in [k+m..k+m+span) {
      butterfly(wk1, wk2, wk3, A[j..j+3*span by span]);
    }
  }
}

def butterfly(wk1, wk2, wk3, inout A:[1..radix]) {
  var x0 = A[1] + A[2];
  var x1 = A[1] - A[2];
  var x2 = A[3] + A[4];
  var x3rot = (A[3] - A[4])*1.0i;

  A[1] = x0 + x2;
  x0 -= x2;
  A[3] = wk2 * x0;
  x0 = x1 + x3rot;
  A[2] = wk1 * x0;
  x0 = x1 - x3rot;
  A[4] = wk3 * x0;
}

// BLC: For certain inputs, not having a complex/real
// function can result in numerical instability.

// BLC: clean up complex arithmetic

// BLC: insert problem size computation based on memory size?

use BitOps;
use Random;
use Time;

// problem size configs
config const logN = 5;

param radix = 4;

// pseudo-random input configs
config const deterministic = false,
             seed = 314159265.0,
             arand = 1220703125.0;

// verification configs
config const epsilon = 2.0 ** -51.0,
             threshold = 16.0;

// boolean configs
config const printTiming = true;

// BLC: control printing out of error value to avoid portability issues

def main() {
  // compute problem size
  const N = 1 << logN;

  // twiddle domain and arrays
  const TwiddleDom = [0..N/4);
  var Twiddles: [TwiddleDom] complex;

  computeTwiddles(Twiddles);
  bitReverseShuffle(Twiddles);

  // problem domain and arrays
  const ProblemDom = [0..N);
  var Z, z: [ProblemDom] complex;

  // generate pseudo-random input
  if deterministic then
    fillRandom(z, seed=seed, arand=arand);
  else
    fillRandom(z);

  // conjugate input, storing result to work array
  Z = conjg(z);

  // TIMED SECTION
  var startTime = getCurrentTime();

  bitReverseShuffle(Z);
  dfft(Z, Twiddles);

  var execTime = getCurrentTime() - startTime;

  verifyResults(z, Z, execTime, Twiddles);
}

def computeTwiddles(W) {
  const n = W.numElements;
  const delta = 2.0 * atan(1.0) / n;

  W(0) = 1.0;
  W(n/2) = let x = cos(delta * n/2)
            in (x, x):complex;
  forall i in [1..n/2) {
    const x = cos(delta*i);
    const y = sin(delta*i);
    W(i)     = (x, y):complex;
    W(n - i) = (y, x):complex;
  }
}

def bitReverseShuffle(W: [?WD]) {
  const n = WD.numIndices;
  const reverse = lg(n);
  var V: [WD] W.elt_type;  // BLC: rename elt_type to remove underscore?
  // BLC: would like to use:
  //  var Perm: [i in WD] int = bitReverse(i:uint(64), numBits = reverse):int;
  var Perm: [WD] index(WD);
  [i in WD] Perm(i) = bitReverse(i, numBits = reverse);
  // BLC: would like to use:
  // V(Perm) = W;
  [i in WD] V(Perm(i)) = W(i);
  W = V;
}

// reverses numBits low-order bits of val
def bitReverse(val: ?valType, numBits = 64) {
  param mask: uint(64) = 0x0102040810204080;
  const valReverse64 = bitMatMultOr(mask, bitMatMultOr(val:uint(64), mask));
  const valReverse = bitRotLeft(valReverse64, numBits);
  return valReverse: valType;
}

def dfft(Z, W) {
  cft1st(Z, W);

  var span = radix;
  var lasti = 2;
  for i in radix..logN/2 by 2 {
    cftmd1(span, Z, W);
    span *= radix;
    lasti = i;
  }
  for i in [lasti+2..logN) by 2 {
    cftmd2(span, Z, W);
    span *= radix;
  }

  const n = Z.numElements;
  if (radix*span == n) {
    forall j in [0..span) {
      butterfly(1.0, 1.0, 1.0, Z[j..j+3*span by span]);
    }
  } else {
    forall j in [0..span) {
      var a = Z(j);
      var b = Z(j+span);
      Z(j) = a + b;
      Z(j+span) = a - b;
    }
  }
}

def verifyResults(z, Z, execTime, Twiddles) {
  const N = Z.numElements;

  // BLC: This line wants /(complex,real) to be implemented directly:
  Z = conjg(Z) / N;

  bitReverseShuffle(Z);
  dfft(Z, Twiddles);

  // BLC: need to check this against written spec
  var maxerr = max reduce sqrt((z.real - Z.real)**2 + (z.imag - Z.imag)**2);

  maxerr = maxerr / logN / epsilon;

  write(if (maxerr < threshold) then "SUCCESS" else "FAILURE");
  writeln(", error = ", maxerr);
  writeln();
  writeln("N      = ", N);
  if (printTiming) {
    writeln("Time   = ", execTime);
    const gflop = 5.0 * N * logN / 1000000000.0;
    writeln("GFlops = ", gflop / execTime);
  }
}

def cft1st(A, W) {
  const n = A.domain(1).length;
  var x0 = A(0) + A(1);
  var x1 = A(0) - A(1);
  var x2 = A(2) + A(3);
  var x3 = A(2) - A(3);

  const wk1r = W(1).real;

  A(0) = x0 + x2;
  A(2) = x0 - x2;
  A(1) = (x1.real - x3.imag, x1.imag + x3.real):complex;
  A(3) = (x1.real + x3.imag, x1.imag - x3.real):complex;

  x0 = A(4) + A(5);
  x1 = A(4) - A(5);
  x2 = A(6) + A(7);
  x3 = A(6) - A(7);
  A(4) = x0 + x2;
  A(6) = (x2.imag - x0.imag, x0.real - x2.real):complex;
  x0 = (x1.real - x3.imag, x1.imag + x3.real):complex;
  A(5) = wk1r * (x0.real - x0.imag, x0.real + x0.imag):complex;
  x0 = (x3.imag + x1.real, x3.real - x1.imag):complex;
  A(7) = wk1r * (x0.imag - x0.real, x0.imag + x0.real):complex;

  // BLC: would like to use an indefinite arithmetic array here
  // BLC: would also like to use () on both indices and zipping
  //      together of ranges
  forall j,k1 in [8..n) by 8, 1..(n-8)/8 {
    var wk2 = W(k1);
    var wk1 = W(2*k1);
    var wk3 = (wk1.real - 2* wk2.imag * wk1.imag,
               2 * wk2.imag * wk1.real - wk1.imag):complex;

    butterfly(wk1, wk2, wk3, A[j..j+3]);

    wk1 = W(2*k1+1);
    wk3 = (wk1.real - 2*wk2.real * wk1.imag,
           2*wk2.real * wk1.real - wk1.imag):complex;
    wk2 = (-wk2.imag, wk2.real): complex;
    butterfly(wk1, wk2, wk3, A[j+4..j+7]);
  }
}

def cftmd0(span, A, W) {
  var wk1r = W(1).real;
  const m = radix*span;

  forall j in [0..span) {
    butterfly(1.0, 1.0, 1.0, A[j..j+3*span by span]);
  }

  forall j in [m..m+span) {
    butterfly((wk1r, wk1r):complex, 1.0i, (-wk1r, wk1r):complex,
              A[j..j+3*span by span]);
  }
}

def cftmd1(span, A, W) {
  const m = radix*span;
  const m2 = 2*m;
  const n = A.domain(1).length;

  cftmd0(span, A, W);
  forall k,k1 in [m2..n) by m2, 1..(n-m2)/m2 {
    var wk2 = W[k1];
    var wk1 = W[2*k1];
    var wk3 = (wk1.real - 2 * wk2.imag * wk1.imag,
               2 * wk2.imag * wk1.real - wk1.imag):complex;
    for j in [k..k+span) {
      butterfly(wk1, wk2, wk3, A[j..j+3*span by span]);
    }

    wk1 = W[2*k1+1];
    wk3 = (wk1.real - 2 * wk2.real * wk1.imag,
           2 * wk2.real * wk1.real - wk1.imag):complex;

    for j in [k+m..k+m+span) {
      butterfly(wk1, (-wk2.imag, wk2.real):complex, wk3, A[j..j+3*span by span]);
    }
  }
}

def cftmd2(span, A, W) {
  var m = radix*span;
  var m2 = 2*m;
  const n = A.domain(1).length;

  cftmd0(span, A, W);
  if (m2 >= n) return;
  if (m2 >= n / 8) {
    cftmd21(span, A, W);
    return;
  }

  forall j in [0..span) {
    forall k,k1 in [m2..n) by m2, 1..(n-m2)/m2 {
      var wk2 = W[k1];
      var wk1 = W[k1 + k1];
      var wk3 = (wk1.real - 2*wk2.imag * wk1.imag,
                 2 * wk2.imag * wk1.real - wk1.imag): complex;
      butterfly(wk1, wk2, wk3, A[j+k..j+k+3*span by span]);
    }

    forall k,k1 in [m2..n) by m2, 1..(n-m2)/m2 {
      var wk2 = W[k1];
      var wk1 = W[2*k1 + 1];
      var wk3 = (wk1.real - 2*wk2.real * wk1.imag,
                 2*wk2.real * wk1.real - wk1.imag): complex;
      wk2 = (-wk2.imag, wk2.real): complex;

      butterfly(wk1, wk2, wk3, A[j+k+m..j+k+m+3*span by span]);
    }
  }
}

def cftmd21(span, A, W) {
  const n = A.domain(1).length;
  var m = radix*span;
  var m2 = 2*m;

  for k,k1 in [m2..n) by m2, 1..(n-m2)/m2 {
    var wk2 = W[k1];
    var wk1 = W[2*k1];
    var wk3 = (wk1.real - 2*wk2.imag * wk1.imag,
               2* wk2.imag * wk1.real - wk1.imag): complex;

    forall j in [k..k+span) {
      butterfly(wk1, wk2, wk3, A[j..j+3*span by span]);
    }

    wk1 = W[2*k1 + 1];
    wk3 = (wk1.real - 2*wk2.real * wk1.imag,
           2*wk2.real * wk1.real - wk1.imag): complex;
    wk2 = (-wk2.imag, wk2.real): complex;

    forall j in [k+m..k+m+span) {
      butterfly(wk1, wk2, wk3, A[j..j+3*span by span]);
    }
  }
}

def butterfly(wk1: complex, wk2: complex, wk3: complex,
              inout A:[1..radix] complex) {
  var x0 = A[1] + A[2];
  var x1 = A[1] - A[2];
  var x2 = A[3] + A[4];
  var x3 = A[3] - A[4];

  A[1] = x0 + x2;
  x0 -= x2;
  A[3] = wk2 * x0;
  x0 = (x1.real - x3.imag, x1.imag + x3.real):complex;
  A[2] = wk1 * x0;
  x0 = (x1.real + x3.imag, x1.imag - x3.real):complex;
  A[4] = wk3 * x0;
}


Moved complex interpolations out into helper
functions.

git-svn-id: 88467cb1fb04b8a755be7e1ee1026be4190196ef@8007 3a8e244f-b0f2-452b-bcba-4c88e055c3ca

// TODO: clean up complex arithmetic
use BitOps;
use Random;
use Time;

use HPCCProblemSize;

param radix = 4;

param numVectors = 2;
type elemType = complex(128);

config const n = computeProblemSize(elemType, numVectors, returnLog2 = true);
const m = 2**n;

config const epsilon = 2.0 ** -51.0,
             threshold = 16.0;

config const useRandomSeed = true,
             seed = if useRandomSeed then SeedGenerator.clockMS else 314159265;

config const printParams = true,
             printArrays = false,
             printStats = true;

def main() {
  printConfiguration();

  const TwiddleDom = [0..m/4);
  var Twiddles: [TwiddleDom] elemType;

  const ProblemDom = [0..m);
  var Z, z: [ProblemDom] elemType;

  initVectors(Twiddles, z);

  const startTime = getCurrentTime();

  Z = conjg(z);
  bitReverseShuffle(Z);
  dfft(Z, Twiddles);

  const execTime = getCurrentTime() - startTime;

  const validAnswer = verifyResults(z, Z, Twiddles);
  printResults(validAnswer, execTime);
}

def dfft(Z, Twiddles) {
  cft1st(Z, Twiddles);

  var span = radix;

  for i in [radix..n) by 2 {
    if (i <= n/2) then
      cftmd1(span, Z, Twiddles);
    else
      cftmd2(span, Z, Twiddles);
    span *= radix;
  }

  if (radix*span == Z.numElements) then
    forall j in [0..span) do
      butterfly(1.0, 1.0, 1.0, Z[j..j+3*span by span]);
  else
    forall j in [0..span) {
      const a = Z(j),
            b = Z(j+span);
      Z(j)      = a + b;
      Z(j+span) = a - b;
    }
}

def printConfiguration() {
  if (printParams) then printProblemSize(elemType, numVectors, m);
}

def initVectors(Twiddles, z) {
  computeTwiddles(Twiddles);
  bitReverseShuffle(Twiddles);

  fillRandom(z, seed);

  if (printArrays) {
    writeln("After initialization, Twiddles is: ", Twiddles, "\n");
    writeln("z is: ", z, "\n");
  }
}

def computeTwiddles(Twiddles) {
  const numTwdls = Twiddles.numElements,
        delta = 2.0 * atan(1.0) / numTwdls;

  Twiddles(0) = 1.0;
  Twiddles(numTwdls/2) = let x = cos(delta * numTwdls/2)
                          in (x, x):complex;
  forall i in [1..numTwdls/2) {
    const x = cos(delta*i),
          y = sin(delta*i);
    Twiddles(i)            = (x, y):complex;
    Twiddles(numTwdls - i) = (y, x):complex;
  }
}

def bitReverseShuffle(Vect: [?Dom]) {
  const numBits = log2(Vect.numElements),
        Perm: [i in Dom] index(Dom) = bitReverse(i, revBits = numBits),
        Temp = Vect(Perm);
  Vect = Temp;
}

def bitReverse(val: ?valType, revBits = 64) {
  param mask = 0x0102040810204080;
  const valReverse64 = bitMatMultOr(mask, bitMatMultOr(val:uint(64), mask)),
        valReverse = bitRotLeft(valReverse64, revBits);
  return valReverse: valType;
}

def verifyResults(z, Z, Twiddles) {
  if (printArrays) then writeln("After FFT, Z is: ", Z, "\n");

  Z = conjg(Z) / m;
  bitReverseShuffle(Z);
  dfft(Z, Twiddles);

  if (printArrays) then writeln("After inverse FFT, Z is: ", Z, "\n");

  var maxerr = max reduce sqrt((z.re - Z.re)**2 + (z.im - Z.im)**2);
  maxerr /= (epsilon * n);
  if (printStats) then writeln("error = ", maxerr);

  return (maxerr < threshold);
}

def printResults(successful, execTime) {
  writeln("Validation: ", if successful then "SUCCESS" else "FAILURE");
  if (printStats) {
    writeln("Execution time = ", execTime);
    writeln("Performance (Gflop/s) = ", 5.0 * (m * n) / execTime / 1.0e-9);
  }
}

def butterfly(wk1, wk2, wk3, inout A:[1..radix]) {
  var x0 = A(1) + A(2),
      x1 = A(1) - A(2),
      x2 = A(3) + A(4),
      x3rot = (A(3) - A(4))*1.0i;

  A(1) = x0 + x2;
  x0 -= x2;
  A(3) = wk2 * x0;
  x0 = x1 + x3rot;
  A(2) = wk1 * x0;
  x0 = x1 - x3rot;
  A(4) = wk3 * x0;
}

def cft1st(A, Twiddles) {
  var x0 = A(0) + A(1),
      x1 = A(0) - A(1),
      x2 = A(2) + A(3),
      x3rot = (A(2) - A(3))*1.0i;
  const wk1r = Twiddles(1).re;

  A(0) = x0 + x2;
  A(2) = x0 - x2;
  A(1) = x1 + x3rot;
  A(3) = x1 - x3rot;

  x0 = A(4) + A(5);
  x1 = A(4) - A(5);
  x2 = A(6) + A(7);
  const x3 = A(6) - A(7);  // BLC: try to eliminate this
  x3rot = (A(6) - A(7))*1.0i;
  A(4) = x0 + x2;
  A(6) = (x2.im - x0.im, x0.re - x2.re):complex;
  x0 = x1 + x3rot;
  A(5) = wk1r * (x0.re - x0.im, x0.re + x0.im):complex;
  x0 = (x3.im + x1.re, x3.re - x1.im):complex;
  A(7) = wk1r * (x0.im - x0.re, x0.im + x0.re):complex;

  forall (j,k1) in ([8..A.numElements) by 8, 1..) {
    var wk2 = Twiddles(k1),
        wk1 = Twiddles(2*k1),
        wk3 = (wk1.re - 2* wk2.im * wk1.im,
               2 * wk2.im * wk1.re - wk1.im):complex;

    butterfly(wk1, wk2, wk3, A[j..j+3]);

    wk1 = Twiddles(2*k1+1);
    wk3 = (wk1.re - 2*wk2.re * wk1.im,
           2*wk2.re * wk1.re - wk1.im):complex;
    wk2 = wk2*1.0i;
    butterfly(wk1, wk2, wk3, A[j+4..j+7]);
  }
}

def cftmd0(span, A, Twiddles) {
  const wk1r = Twiddles(1).re,
        m = radix*span;

  forall j in [0..span) do
    butterfly(1.0, 1.0, 1.0, A[j..j+3*span by span]);

  forall j in [m..m+span) do
    butterfly((wk1r, wk1r):complex, 1.0i, (-wk1r, wk1r):complex,
              A[j..j+3*span by span]);
}

def cftmd1(span, A, Twiddles) {
  const m = radix*span,
        m2 = 2*m;

  cftmd0(span, A, Twiddles);
  forall (k,k1) in ([m2..A.numElements) by m2, 1..) {
    var wk2 = Twiddles(k1),
        wk1 = Twiddles(2*k1),
        wk3 = interp1(wk1, wk2);
    //(wk1.re - 2 * wk2.im * wk1.im,
    // 2 * wk2.im * wk1.re - wk1.im):complex;
    for j in [k..k+span) do
      butterfly(wk1, wk2, wk3, A[j..j+3*span by span]);

    wk1 = Twiddles(2*k1+1);
    wk3 = interp2(wk1, wk2);
    //(wk1.re - 2 * wk2.re * wk1.im,
    // 2 * wk2.re * wk1.re - wk1.im):complex;

    for j in [k+m..k+m+span) do
      butterfly(wk1, wk2*1.0i, wk3, A[j..j+3*span by span]);
  }
}

def cftmd2(span, A, Twiddles) {
  const m = radix*span,
        m2 = 2*m,
        numElems = A.numElements;

  cftmd0(span, A, Twiddles);
  if (m2 >= numElems) return;
  if (m2 >= numElems / 8) {
    cftmd21(span, A, Twiddles);
    return;
  }

  forall j in [0..span) {
    forall (k,k1) in ([m2..numElems) by m2, 1..) {
      const wk2 = Twiddles(k1),
            wk1 = Twiddles(k1 + k1),
            wk3 = interp1(wk1, wk2);
      //            wk3 = (wk1.re - 2*wk2.im * wk1.im,
      //                   2 * wk2.im * wk1.re - wk1.im):complex;
      butterfly(wk1, wk2, wk3, A[j+k..j+k+3*span by span]);
    }

    forall (k,k1) in ([m2..numElems) by m2, 1..) {
      const wk2 = Twiddles(k1),
            wk1 = Twiddles(2*k1 + 1),
            wk3 = interp2(wk1, wk2);
      //(wk1.re - 2*wk2.re * wk1.im,
      //                   2*wk2.re * wk1.re - wk1.im):complex;
      wk2 = wk2*1.0i;

      butterfly(wk1, wk2, wk3, A[j+k+m..j+k+m+3*span by span]);
    }
  }
}

def cftmd21(span, A, Twiddles) {
  const m = radix*span,
        m2 = 2*m;

  for (k,k1) in ([m2..A.numElements) by m2, 1..) {
    var wk2 = Twiddles(k1),
        wk1 = Twiddles(2*k1),
        wk3 = interp1(wk1, wk2);
    //        wk3 = (wk1.re - 2*wk2.im * wk1.im,
    //               2*wk2.im * wk1.re - wk1.im):complex;

    forall j in [k..k+span) do
      butterfly(wk1, wk2, wk3, A[j..j+3*span by span]);

    wk1 = Twiddles(2*k1 + 1);
    wk3 = interp2(wk1, wk2);
    //    wk3 = (wk1.re - 2*wk2.re * wk1.im,
    //           2*wk2.re * wk1.re - wk1.im):complex;
    wk2 = wk2*1.0i;

    forall j in [k+m..k+m+span) do
      butterfly(wk1, wk2, wk3, A[j..j+3*span by span]);
  }
}

def interp1(a, b)
  return (a.re - 2*b.im*a.im,
          2*b.im*a.re - a.im): complex;

def interp2(a, b)
  return (a.re - 2*b.re*a.im,
          2*b.re*a.re - a.im): complex;

// TODO: clean up complex arithmetic
use BitOps;
use Random;
use Time;

use HPCCProblemSize;

param radix = 4;

param numVectors = 2;
type elemType = complex(128);

config const n = computeProblemSize(elemType, numVectors, returnLog2 = true);
const m = 2**n;

config const epsilon = 2.0 ** -51.0,
             threshold = 16.0;

config const useRandomSeed = true,
             seed = if useRandomSeed then SeedGenerator.clockMS else 314159265;

config const printParams = true,
             printArrays = false,
             printStats = true;

def main() {
  printConfiguration();

  const TwiddleDom = [0..m/4);
  var Twiddles: [TwiddleDom] elemType;

  const ProblemDom = [0..m);
  var Z, z: [ProblemDom] elemType;

  initVectors(Twiddles, z);

  const startTime = getCurrentTime();

  Z = conjg(z);
  bitReverseShuffle(Z);
  dfft(Z, Twiddles);

  const execTime = getCurrentTime() - startTime;

  const validAnswer = verifyResults(z, Z, Twiddles);
  printResults(validAnswer, execTime);
}

def dfft(Z, Twiddles) {
  cft1st(Z, Twiddles);

  var span = radix;

  for i in [radix..n) by 2 {
    if (i <= n/2) then
      cftmd1(span, Z, Twiddles);
    else
      cftmd2(span, Z, Twiddles);
    span *= radix;
  }

  if (radix*span == Z.numElements) then
    forall j in [0..span) do
      butterfly(1.0, 1.0, 1.0, Z[j..j+3*span by span]);
  else
    forall j in [0..span) {
      const a = Z(j),
            b = Z(j+span);
      Z(j)      = a + b;
      Z(j+span) = a - b;
    }
}

def printConfiguration() {
  if (printParams) then printProblemSize(elemType, numVectors, m);
}

def initVectors(Twiddles, z) {
  computeTwiddles(Twiddles);
  bitReverseShuffle(Twiddles);

  fillRandom(z, seed);

  if (printArrays) {
    writeln("After initialization, Twiddles is: ", Twiddles, "\n");
    writeln("z is: ", z, "\n");
  }
}

def computeTwiddles(Twiddles) {
  const numTwdls = Twiddles.numElements,
        delta = 2.0 * atan(1.0) / numTwdls;

  Twiddles(0) = 1.0;
  Twiddles(numTwdls/2) = let x = cos(delta * numTwdls/2)
                          in (x, x):complex;
  forall i in [1..numTwdls/2) {
    const x = cos(delta*i),
          y = sin(delta*i);
    Twiddles(i)            = (x, y):complex;
    Twiddles(numTwdls - i) = (y, x):complex;
  }
}

def bitReverseShuffle(Vect: [?Dom]) {
  const numBits = log2(Vect.numElements),
        Perm: [i in Dom] index(Dom) = bitReverse(i, revBits = numBits),
        Temp = Vect(Perm);
  Vect = Temp;
}

def bitReverse(val: ?valType, revBits = 64) {
  param mask = 0x0102040810204080;
  const valReverse64 = bitMatMultOr(mask, bitMatMultOr(val:uint(64), mask)),
        valReverse = bitRotLeft(valReverse64, revBits);
  return valReverse: valType;
}

def verifyResults(z, Z, Twiddles) {
  if (printArrays) then writeln("After FFT, Z is: ", Z, "\n");

  Z = conjg(Z) / m;
  bitReverseShuffle(Z);
  dfft(Z, Twiddles);

  if (printArrays) then writeln("After inverse FFT, Z is: ", Z, "\n");

  var maxerr = max reduce sqrt((z.re - Z.re)**2 + (z.im - Z.im)**2);
  maxerr /= (epsilon * n);
  if (printStats) then writeln("error = ", maxerr);

  return (maxerr < threshold);
}

def printResults(successful, execTime) {
  writeln("Validation: ", if successful then "SUCCESS" else "FAILURE");
  if (printStats) {
    writeln("Execution time = ", execTime);
    writeln("Performance (Gflop/s) = ", 5.0 * (m * n) / execTime / 1.0e-9);
  }
}

def butterfly(wk1, wk2, wk3, inout A:[1..radix]) {
  var x0 = A(1) + A(2),
      x1 = A(1) - A(2),
      x2 = A(3) + A(4),
      x3rot = (A(3) - A(4))*1.0i;

  A(1) = x0 + x2;
  x0 -= x2;
  A(3) = wk2 * x0;
  x0 = x1 + x3rot;
  A(2) = wk1 * x0;
  x0 = x1 - x3rot;
  A(4) = wk3 * x0;
}

def cft1st(A, Twiddles) {
  var x0 = A(0) + A(1),
      x1 = A(0) - A(1),
      x2 = A(2) + A(3),
      x3rot = (A(2) - A(3))*1.0i;
  const wk1r = Twiddles(1).re;

  A(0) = x0 + x2;
  A(2) = x0 - x2;
  A(1) = x1 + x3rot;
  A(3) = x1 - x3rot;

  x0 = A(4) + A(5);
  x1 = A(4) - A(5);
  x2 = A(6) + A(7);
  const x3 = A(6) - A(7);  // BLC: try to eliminate this
  x3rot = (A(6) - A(7))*1.0i;
  A(4) = x0 + x2;
  A(6) = (x2.im - x0.im, x0.re - x2.re):complex;
  x0 = x1 + x3rot;
  A(5) = wk1r * (x0.re - x0.im, x0.re + x0.im):complex;
  x0 = (x3.im + x1.re, x3.re - x1.im):complex;
  A(7) = wk1r * (x0.im - x0.re, x0.im + x0.re):complex;

  forall (j,k1) in ([8..A.numElements) by 8, 1..) {
    var wk2 = Twiddles(k1),
        wk1 = Twiddles(2*k1),
        wk3 = (wk1.re - 2* wk2.im * wk1.im,
               2 * wk2.im * wk1.re - wk1.im):complex;

    butterfly(wk1, wk2, wk3, A[j..j+3]);

    wk1 = Twiddles(2*k1+1);
    wk3 = (wk1.re - 2*wk2.re * wk1.im,
           2*wk2.re * wk1.re - wk1.im):complex;
    wk2 = wk2*1.0i;
    butterfly(wk1, wk2, wk3, A[j+4..j+7]);
  }
}

def cftmd0(span, A, Twiddles) {
  const wk1r = Twiddles(1).re,
        m = radix*span;

  forall j in [0..span) do
    butterfly(1.0, 1.0, 1.0, A[j..j+3*span by span]);

  forall j in [m..m+span) do
    butterfly((wk1r, wk1r):complex, 1.0i, (-wk1r, wk1r):complex,
              A[j..j+3*span by span]);
}

def cftmd1(span, A, Twiddles) {
  const m = radix*span,
        m2 = 2*m;

  cftmd0(span, A, Twiddles);
  forall (k,k1) in ([m2..A.numElements) by m2, 1..) {
    var wk2 = Twiddles(k1),
        wk1 = Twiddles(2*k1),
        wk3 = (wk1.re - 2 * wk2.im * wk1.im,
               2 * wk2.im * wk1.re - wk1.im):complex;
    for j in [k..k+span) do
      butterfly(wk1, wk2, wk3, A[j..j+3*span by span]);

    wk1 = Twiddles(2*k1+1);
    wk3 = (wk1.re - 2 * wk2.re * wk1.im,
           2 * wk2.re * wk1.re - wk1.im):complex;

    for j in [k+m..k+m+span) do
      butterfly(wk1, wk2*1.0i, wk3, A[j..j+3*span by span]);
  }
}

def cftmd2(span, A, Twiddles) {
  const m = radix*span,
        m2 = 2*m,
        numElems = A.numElements;

  cftmd0(span, A, Twiddles);
  if (m2 >= numElems) return;
  if (m2 >= numElems / 8) {
    cftmd21(span, A, Twiddles);
    return;
  }

  forall j in [0..span) {
    forall (k,k1) in ([m2..numElems) by m2, 1..) {
      const wk2 = Twiddles(k1),
            wk1 = Twiddles(k1 + k1),
            wk3 = (wk1.re - 2*wk2.im * wk1.im,
                   2 * wk2.im * wk1.re - wk1.im):complex;
      butterfly(wk1, wk2, wk3, A[j+k..j+k+3*span by span]);
    }

    forall (k,k1) in ([m2..numElems) by m2, 1..) {
      const wk2 = Twiddles(k1),
            wk1 = Twiddles(2*k1 + 1),
            wk3 = (wk1.re - 2*wk2.re * wk1.im,
                   2*wk2.re * wk1.re - wk1.im):complex;
      wk2 = wk2*1.0i;

      butterfly(wk1, wk2, wk3, A[j+k+m..j+k+m+3*span by span]);
    }
  }
}

def cftmd21(span, A, Twiddles) {
  const m = radix*span,
        m2 = 2*m;

  for (k,k1) in ([m2..A.numElements) by m2, 1..) {
    var wk2 = Twiddles(k1),
        wk1 = Twiddles(2*k1),
        wk3 = (wk1.re - 2*wk2.im * wk1.im,
               2*wk2.im * wk1.re - wk1.im):complex;

    forall j in [k..k+span) do
      butterfly(wk1, wk2, wk3, A[j..j+3*span by span]);

    wk1 = Twiddles(2*k1 + 1);
    wk3 = (wk1.re - 2*wk2.re * wk1.im,
           2*wk2.re * wk1.re - wk1.im):complex;
    wk2 = wk2*1.0i;

    forall j in [k+m..k+m+span) do
      butterfly(wk1, wk2, wk3, A[j..j+3*span by span]);
  }
}