Added code to measure and print the timing of Kernel 1 in SSCA#2.
While on that, separated DEBUG_WEIGHT_GENERATOR and DEBUG_GRAPH_GENERATOR
for RMAT edge/graph construction.

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

module SSCA2_RMAT_graph_generator

{
  // +=========================================================================+
  // |             RMAT approximate power law graph generator                  |
  // |                                 and                                     |
  // |                           SSCA #2 Kernel 1                              |
  // |                                                                         |
  // |  Generate approximate power law graph                                   |
  // |                                                                         |
  // |  This procedure accepts a generic graph representation which must       |
  // |  provide only the capabilities to:                                      |
  // |    1. add a vertex to a neighbor list for some other vertex             |
  // |    2. assign an integer weight to an entry in an associated weight list |
  // |                                                                         |
  // |  The RMAT procedure implicitly assumes that vertices are integers       |
  // |  in the range [0, 2^SCALE].  So vertices are integers in this           |
  // |  procedure and edges are pairs of integers.                             |
  // |                                                                         |
  // |  This implementation first generates the RMAT graph as a list of        |
  // |  triples, using an array of edges and an associated array of weights.   |
  // |  These are transformed into the Chapel graph representation using       |
  // |  native Chapel syntax for associative and sparse domains and arrays.    |
  // |  That transformation is Kernel 1 of the SSCA #2 benchmark.              |
  // |                                                                         |
  // |  The code uses two auxiliary procedures for clarity and to help the     |
  // |  compiler use more abstract, higher level, code.                        |
  // +=========================================================================+

  use SSCA2_compilation_config_params, Time;

  var stopwatch : Timer;

  record directed_vertex_pair {
    var start = 1: int;
    var end   = 1: int;
  }

  proc +(l: directed_vertex_pair, r: directed_vertex_pair)
      return new directed_vertex_pair (l.start + r.start, l.end + r.end);

  // ============================
  // Quadrant selection algorithm
  // ============================

  proc assign_quadrant ( u: real, a: real, b: real, c: real, d : real,
			bit : int ) : directed_vertex_pair
    {
      // ---------------------------------------------------------------------
      // The heart of the RMAT random graph generator is a procedure that
      // assigns a single bit of the edge starting and ending vertex by
      // assigning the edge with specified probability to one of the four
      // quadrants of a 2 x 2 grid.
      //
      // Chapel is able to promote this conditional-based scalar procedure to
      // array operations where it is not able to promote conditional code
      // directly.
      //
      // Determine randomly in which quadrant of the grid a point lies,
      // based on the following picture:
      //
      //     +---------------------------+
      //     | u < a         | u < a+b   |
      //     |-------------+-------------|
      //     | u < a + b + c | otherwise |
      //     +---------------------------+
      //
      // The probability of the edge falling in the upper left quadrant is  a,
      // in the upper right quadrant, b, the lower left quadrant  c
      // and the lower right quadrant  d, where the probabilities are
      // normalized to sum to one.
      // ---------------------------------------------------------------------

      var start_inc = 0;
      var end_inc   = 0;
      var edge : directed_vertex_pair;

      if u <= a then
	{}
      else if u <= a + b then
	{ end_inc = 1;}
      else if u <= a + b + c then
	{ start_inc = 1;}
      else
	{ start_inc = 1; end_inc = 1;};

      edge.start = bit * start_inc;
      edge.end   = bit * end_inc;
      return ( edge );
    }

  // ====================================
  // Main RMAT Graph Generation Procedure
  // ====================================~

  proc Gen_RMAT_graph ( a : real,
		       b : real,
		       c : real,
		       d : real,
		       SCALE :int,
		       N_VERTICES : int,
		       n_raw_edges : int,
		       MAX_EDGE_WEIGHT :int,
		       G )

    { use Random;

      const vertex_range = 1..N_VERTICES,
	    edge_range   = 1..n_raw_edges,
	    rand_range   = 1..n_raw_edges + 1;

      // Random Numbers return in the range [0.0, 1.0)

      var Rand_Gen = if REPRODUCIBLE_PROBLEMS then
	               new RandomStream (seed = 0556707007)
		     else
		       new RandomStream ();

      var   Noisy_a     : [edge_range] real,
	    Noisy_b     : [edge_range] real,
            Noisy_c     : [edge_range] real,
            Noisy_d     : [edge_range] real,
            norm        : [edge_range] real,
	    Unif_Random : [edge_range] real,
	    Edges       = [edge_range] new directed_vertex_pair ();

       // ---------------------------------------------------------------
      // The RMAT algorithm is based on recursively sub-dividing a grid.
      // In this case, the grid is square of order 2^SCALE by 2^SCALE.
      // So exactly "SCALE" steps of recursion will be required and the
      // recursion can be implemented directly by iteration.
      // ---------------------------------------------------------------

      // -----------------------------------------------------------------
      // Note on random number generators -- the RMAT generator creates
      // 5*SCALE vectors of length 2**SCALE.  The dependence on powers
      // of two provides an opportunity to expose statistical correlations
      // in the pseudo-random numbers.  This certainly occurs with the
      // current Chapel Random module.  The "skips" in the random number
      // sequence dramatically change the results.  Without them, the
      // Chapel RMAT matrices are inconsistent with what is seen in
      // other implementations.
      // -----------------------------------------------------------------

      writeln ("Random graph generated by stride of 1 in one random stream",
	       " with skips" );

      var bit = 1 << SCALE;
      var   skip : real;

      for depth in 1..SCALE do {
	  bit >>= 1;

	  // randomize the coefficients, tweaking them by numbers in [-.05, .05)

	  skip = Rand_Gen.getNext ();
	  Rand_Gen.fillRandom (Unif_Random);
	  Noisy_a = a * (0.95 + 0.1 * Unif_Random);

	  skip = Rand_Gen.getNext ();
	  Rand_Gen.fillRandom (Unif_Random);
	  Noisy_b = b * (0.95 + 0.1 * Unif_Random);

	  skip = Rand_Gen.getNext ();
	  Rand_Gen.fillRandom (Unif_Random);
	  Noisy_c = c * (0.95 + 0.1 * Unif_Random);

	  skip = Rand_Gen.getNext ();
	  Rand_Gen.fillRandom (Unif_Random);
	  Noisy_d = d * (0.95 + 0.1 * Unif_Random);

	  norm     = 1.0 / (Noisy_a + Noisy_b + Noisy_c + Noisy_d);
	  Noisy_a *= norm;
	  Noisy_b *= norm;
	  Noisy_c *= norm;
	  Noisy_d *= norm;

	  skip = Rand_Gen.getNext ();
	  Rand_Gen.fillRandom (Unif_Random);

	  Edges += assign_quadrant ( Unif_Random, Noisy_a, Noisy_b,
				     Noisy_c, Noisy_d, bit );

	};

      // ---------------------------------------------------------------------
      // Assign weights to edges randomly, then randomly relabel the vertices
      // to avoid locality from the obvious imbalance that will arise when
      // one of the coefficients is clearly larger than the others
      // ---------------------------------------------------------------------

      var permutation : [vertex_range] int = vertex_range;
      var Edge_Weight : [edge_range] int;

      Rand_Gen.fillRandom ( Unif_Random  );
      Edge_Weight = floor (1 + Unif_Random * MAX_EDGE_WEIGHT) : int;

      Rand_Gen.fillRandom ( Unif_Random (vertex_range) );

      for v in vertex_range do
	{ var new_id : int;
	  new_id = floor (1 + Unif_Random (v) * N_VERTICES) : int;
	  permutation (v) <=> permutation (new_id);
	};

      var node_count : [vertex_range] int = 0;

      //      for e in edge_range do {
      //	Edges(e).start = permutation (Edges(e).start);
      //	Edges(e).end   = permutation (Edges(e).end  );
      //      }
      Edges.start = permutation (Edges.start);
      Edges.end   = permutation (Edges.end  );

      if DEBUG_WEIGHT_GENERATOR then {
	writeln ();
	for e in edge_range do
	  writeln ("edge (", e, ") : (", Edges(e), ", ",
		   Edge_Weight (e), ")" );
      }

      // --------------------------
      // Graph consistency checking
      // --------------------------

      assert ( Edges.start > 0,
	       "edge start vertices out of low end of range");

      assert ( Edges.end > 0,
	       "edge end vertices out of low end of range");

      assert ( Edges.start <= 2**SCALE,
	       "edge start vertices out of high end of range");

      assert ( Edges.end <= 2**SCALE,
	       "edge end vertices out of high end of range");

      assert ( Edge_Weight > 0,
	       "edge weightd out of low end of range");

      assert ( Edge_Weight <= MAX_EDGE_WEIGHT,
	       "edge weightd out of high end of range");

      writeln (); writeln ("Vertex Set in G:", G.vertices);

      // ----------------------------------------------------------
      // Kernel 1: assemble graph from list of triples.
      // Include only non-self incident edges.
      // In case of (start, end) duplicates, the first triple wins.
      // Alternatively, could skip the member(v) check (so the last triple
      // wins) or also use += instead of = (to sum duplicates' weights).
      // ----------------------------------------------------------

      if PRINT_TIMING_STATISTICS then stopwatch.start ();

      var collisions = 0, self_edges = 0;
      for e in edge_range do {
	var u = Edges (e).start;
	var v = Edges (e).end  ;

	if ( v != u ) then {
	  if G.Neighbors (u).member(v) then {
	    collisions += 1;
	  }
	  else {
	    G.Neighbors (u).add (v);
	    G.Row(u).Weight (v) = Edge_Weight (e);
	  }
	}
	else
	  self_edges += 1;
      }

      if PRINT_TIMING_STATISTICS then {
	stopwatch.stop ();
	writeln ( "Elapsed time for Kernel 1: ", stopwatch.elapsed (),
		  " seconds");
	stopwatch.clear ();
      }

      writeln ( "# of raw edges generated  ", n_raw_edges );
      writeln ( "# of duplicate edges      ", collisions );
      writeln ( "# of self edges           ", self_edges );
      writeln ( "# of edges in final graph ",
		+ reduce [v in G.vertices] G.n_Neighbors (v) );

      if DEBUG_GRAPH_GENERATOR then {
	writeln ();
	writeln ("tuples denote (edge, weight)");
	writeln ();
	for u in G.vertices do {
	  write ( "row ", u, ": [", G.n_Neighbors (u), "] " );
	  for (v,w) in (G .Neighbors (u), G.edge_weight (u) ) do
	    write ( (v, w) );
	  writeln ();
	}
      }

      var max_edges = max reduce [v in vertex_range] G.n_Neighbors (v);

      var edge_count : [0..max_edges] int = 0;

      for v in G.vertices do
	edge_count (G.n_Neighbors (v)) += 1;

      writeln ("histograph of node distributions by number of outgoing edges");
      writeln ( "# edges  # nodes");
      for count in 0..max_edges do
	writeln ( count, "  ", edge_count (count) );

      writeln ();

    }
}

module SSCA2_RMAT_graph_generator

{
  // +=========================================================================+
  // |             RMAT approximate power law graph generator                  |
  // |                                 and                                     |
  // |                           SSCA #2 Kernel 1                              |
  // |                                                                         |
  // |  Generate approximate power law graph                                   |
  // |                                                                         |
  // |  This procedure accepts a generic graph representation which must       |
  // |  provide only the capabilities to:                                      |
  // |    1. add a vertex to a neighbor list for some other vertex             |
  // |    2. assign an integer weight to an entry in an associated weight list |
  // |                                                                         |
  // |  The RMAT procedure implicitly assumes that vertices are integers       |
  // |  in the range [0, 2^SCALE].  So vertices are integers in this           |
  // |  procedure and edges are pairs of integers.                             |
  // |                                                                         |
  // |  This implementation first generates the RMAT graph as a list of        |
  // |  triples, using an array of edges and an associated array of weights.   |
  // |  These are transformed into the Chapel graph representation using       |
  // |  native Chapel syntax for associative and sparse domains and arrays.    |
  // |  That transformation is Kernel 1 of the SSCA #2 benchmark.              |
  // |                                                                         |
  // |  The code uses two auxiliary procedures for clarity and to help the     |
  // |  compiler use more abstract, higher level, code.                        |
  // +=========================================================================+

  use SSCA2_compilation_config_params;

  record directed_vertex_pair {
    var start = 1: int;
    var end   = 1: int;
  }

  proc +(l: directed_vertex_pair, r: directed_vertex_pair)
      return new directed_vertex_pair (l.start + r.start, l.end + r.end);

  // ============================
  // Quadrant selection algorithm
  // ============================

  proc assign_quadrant ( u: real, a: real, b: real, c: real, d : real,
			bit : int ) : directed_vertex_pair
    {
      // ---------------------------------------------------------------------
      // The heart of the RMAT random graph generator is a procedure that
      // assigns a single bit of the edge starting and ending vertex by
      // assigning the edge with specified probability to one of the four
      // quadrants of a 2 x 2 grid.
      //
      // Chapel is able to promote this conditional-based scalar procedure to
      // array operations where it is not able to promote conditional code
      // directly.
      //
      // Determine randomly in which quadrant of the grid a point lies,
      // based on the following picture:
      //
      //     +---------------------------+
      //     | u < a         | u < a+b   |
      //     |-------------+-------------|
      //     | u < a + b + c | otherwise |
      //     +---------------------------+
      //
      // The probability of the edge falling in the upper left quadrant is  a,
      // in the upper right quadrant, b, the lower left quadrant  c
      // and the lower right quadrant  d, where the probabilities are
      // normalized to sum to one.
      // ---------------------------------------------------------------------

      var start_inc = 0;
      var end_inc   = 0;
      var edge : directed_vertex_pair;

      if u <= a then
	{}
      else if u <= a + b then
	{ end_inc = 1;}
      else if u <= a + b + c then
	{ start_inc = 1;}
      else
	{ start_inc = 1; end_inc = 1;};

      edge.start = bit * start_inc;
      edge.end   = bit * end_inc;
      return ( edge );
    }

  // ====================================
  // Main RMAT Graph Generation Procedure
  // ====================================~

  proc Gen_RMAT_graph ( a : real,
		       b : real,
		       c : real,
		       d : real,
		       SCALE :int,
		       N_VERTICES : int,
		       n_raw_edges : int,
		       MAX_EDGE_WEIGHT :int,
		       G )

    { use Random;

      const vertex_range = 1..N_VERTICES,
	    edge_range   = 1..n_raw_edges,
	    rand_range   = 1..n_raw_edges + 1;

      // Random Numbers return in the range [0.0, 1.0)

      var Rand_Gen = if REPRODUCIBLE_PROBLEMS then
	               new RandomStream (seed = 0556707007)
		     else
		       new RandomStream ();

      var   Noisy_a     : [edge_range] real,
	    Noisy_b     : [edge_range] real,
            Noisy_c     : [edge_range] real,
            Noisy_d     : [edge_range] real,
            norm        : [edge_range] real,
	    Unif_Random : [edge_range] real,
	    Edges       = [edge_range] new directed_vertex_pair ();

       // ---------------------------------------------------------------
      // The RMAT algorithm is based on recursively sub-dividing a grid.
      // In this case, the grid is square of order 2^SCALE by 2^SCALE.
      // So exactly "SCALE" steps of recursion will be required and the
      // recursion can be implemented directly by iteration.
      // ---------------------------------------------------------------

      // -----------------------------------------------------------------
      // Note on random number generators -- the RMAT generator creates
      // 5*SCALE vectors of length 2**SCALE.  The dependence on powers
      // of two provides an opportunity to expose statistical correlations
      // in the pseudo-random numbers.  This certainly occurs with the
      // current Chapel Random module.  The "skips" in the random number
      // sequence dramatically change the results.  Without them, the
      // Chapel RMAT matrices are inconsistent with what is seen in
      // other implementations.
      // -----------------------------------------------------------------

      writeln ("Random graph generated by stride of 1 in one random stream",
	       " with skips" );

      var bit = 1 << SCALE;
      var   skip : real;

      for depth in 1..SCALE do {
	  bit >>= 1;

	  // randomize the coefficients, tweaking them by numbers in [-.05, .05)

	  skip = Rand_Gen.getNext ();
	  Rand_Gen.fillRandom (Unif_Random);
	  Noisy_a = a * (0.95 + 0.1 * Unif_Random);

	  skip = Rand_Gen.getNext ();
	  Rand_Gen.fillRandom (Unif_Random);
	  Noisy_b = b * (0.95 + 0.1 * Unif_Random);

	  skip = Rand_Gen.getNext ();
	  Rand_Gen.fillRandom (Unif_Random);
	  Noisy_c = c * (0.95 + 0.1 * Unif_Random);

	  skip = Rand_Gen.getNext ();
	  Rand_Gen.fillRandom (Unif_Random);
	  Noisy_d = d * (0.95 + 0.1 * Unif_Random);

	  norm     = 1.0 / (Noisy_a + Noisy_b + Noisy_c + Noisy_d);
	  Noisy_a *= norm;
	  Noisy_b *= norm;
	  Noisy_c *= norm;
	  Noisy_d *= norm;

	  skip = Rand_Gen.getNext ();
	  Rand_Gen.fillRandom (Unif_Random);

	  Edges += assign_quadrant ( Unif_Random, Noisy_a, Noisy_b,
				     Noisy_c, Noisy_d, bit );

	};

      // ---------------------------------------------------------------------
      // Assign weights to edges randomly, then randomly relabel the vertices
      // to avoid locality from the obvious imbalance that will arise when
      // one of the coefficients is clearly larger than the others
      // ---------------------------------------------------------------------

      var permutation : [vertex_range] int = vertex_range;
      var Edge_Weight : [edge_range] int;

      Rand_Gen.fillRandom ( Unif_Random  );
      Edge_Weight = floor (1 + Unif_Random * MAX_EDGE_WEIGHT) : int;

      Rand_Gen.fillRandom ( Unif_Random (vertex_range) );

      for v in vertex_range do
	{ var new_id : int;
	  new_id = floor (1 + Unif_Random (v) * N_VERTICES) : int;
	  permutation (v) <=> permutation (new_id);
	};

      var node_count : [vertex_range] int = 0;

      //      for e in edge_range do {
      //	Edges(e).start = permutation (Edges(e).start);
      //	Edges(e).end   = permutation (Edges(e).end  );
      //      }
      Edges.start = permutation (Edges.start);
      Edges.end   = permutation (Edges.end  );

      if DEBUG_GRAPH_GENERATOR || DEBUG_WEIGHT_GENERATOR then {
	writeln ();
	for e in edge_range do
	  writeln ("edge (", e, ") : (", Edges(e), ", ",
		   Edge_Weight (e), ")" );
      }

      // --------------------------
      // Graph consistency checking
      // --------------------------

      assert ( Edges.start > 0,
	       "edge start vertices out of low end of range");

      assert ( Edges.end > 0,
	       "edge end vertices out of low end of range");

      assert ( Edges.start <= 2**SCALE,
	       "edge start vertices out of high end of range");

      assert ( Edges.end <= 2**SCALE,
	       "edge end vertices out of high end of range");

      assert ( Edge_Weight > 0,
	       "edge weightd out of low end of range");

      assert ( Edge_Weight <= MAX_EDGE_WEIGHT,
	       "edge weightd out of high end of range");

      writeln (); writeln ("Vertex Set in G:", G.vertices);

      // ----------------------------------------------------------
      // Kernel 1: assemble graph from list of triples.
      // Include only non-self incident edges.
      // In case of (start, end) duplicates, the first triple wins.
      // Alternatively, could skip the member(v) check (so the last triple
      // wins) or also use += instead of = (to sum duplicates' weights).
      // ----------------------------------------------------------

      var collisions = 0, self_edges = 0;
      for e in edge_range do {
	var u = Edges (e).start;
	var v = Edges (e).end  ;

	if ( v != u ) then {
	  if G.Neighbors (u).member(v) then {
	    collisions += 1;
	  }
	  else {
	    G.Neighbors (u).add (v);
	    G.Row(u).Weight (v) = Edge_Weight (e);
	  }
	}
	else
	  self_edges += 1;
      }

      writeln ( "# of raw edges generated  ", n_raw_edges );
      writeln ( "# of duplicate edges      ", collisions );
      writeln ( "# of self edges           ", self_edges );
      writeln ( "# of edges in final graph ",
		+ reduce [v in G.vertices] G.n_Neighbors (v) );

      if DEBUG_GRAPH_GENERATOR || DEBUG_WEIGHT_GENERATOR then {
	writeln ();
	writeln ("tuples denote (edge, weight)");
	writeln ();
	for u in G.vertices do {
	  write ( "row ", u, ": [", G.n_Neighbors (u), "] " );
	  for (v,w) in (G .Neighbors (u), G.edge_weight (u) ) do
	    write ( (v, w) );
	  writeln ();
	}
      }

      var max_edges = max reduce [v in vertex_range] G.n_Neighbors (v);

      var edge_count : [0..max_edges] int = 0;

      for v in G.vertices do
	edge_count (G.n_Neighbors (v)) += 1;

      writeln ("histograph of node distributions by number of outgoing edges");
      writeln ( "# edges  # nodes");
      for count in 0..max_edges do
	writeln ( count, "  ", edge_count (count) );

      writeln ();

    }
}


Removed numLocs which was being used as a substitute for numLocales. The
madness tree isn't currently being distributed across the locale set though.

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

/** FTree: The tree data structure used to store the numerical representation
    of a function.

    For 1d Madness, an FTree is a binary tree, for 2d it would
    be a quad tree, 3d an octtree, and so on.  The FTree used here uses level
    and index hashing to store the tree rather than storing connectivity
    information.  So, it is important that you do a has_coeffs() before
    indexing into the tree to be sure you don't create coeffs when you don't
    mean to.
 */

// FIXME: The Coeff record works around arrays of arrays.  Ideally, nodes
//        should be a mapping from indices onto :[coeffDom] real.
record Coeff {
    var dom  : domain(1);
    var data : [dom] real;
}

// The empty vector, returned when there are no Coeffs.
var None: [0..-1] real;
const rootNode = (0,0);
const emptyNode = (-1,-1);

// Check if we have None.
def isNone(x) {
    return x.numElements == 0;
}

class LocTree {
    const coeffDom : domain(1);
    var locIndices : domain(2*int);      // Indexed by 2-tuples of integers
    var locNodes   : [locIndices] Coeff; // Associative Mapping: (:int, :int)
                                         // => Coeff

    var zeroes     : [coeffDom] real;    // Return zeroes from this() when
                                         // reading something that has not yet
                                         // been set.

    /** Access an element in the associative domain.  If the element
        doesn't exist it will be created.

        We have to be really careful about creating extra boxes when we don't
        mean to.  The algorithms tend to be really sensitive to the structure
        of the tree.  Scaling coefficients are used to mark the leaves in the
        sum tree, so if you accidentally create scaling coeffs somewhere in the
        middle of the tree you will short circuit a whole subtree!

     */
    def this(lvl: int, idx: int) var {
        if !locIndices.member((lvl, idx)) {
            if setter {
              locIndices += ((lvl, idx));
              locNodes[(lvl, idx)] = new Coeff(coeffDom);
            } else {
              // This is a getter so it shouldn't be modifying what
              // we return, should be safe to return the zero vector.
              // FIXME: Zeroes should really be a const, but can'ti
              //        return const from a var fcn.
              return zeroes;
            }
        }

        return locNodes[(lvl, idx)].data;
    }

    /** Access an element in the associative domain.  If it doesn't exist,
        return None.
     */
    def peek(lvl: int, idx: int) var {
        if has_coeffs(lvl, idx) then
            return this(lvl, idx);
        else
            return None;
    }

    /** Unordered iterator over all coefficients
     */
    def these() {
        for n in locNodes do
            yield n.data;
    }

    /** Unordered iterator over all boxes in a particular level.
        This is not a particularly fast way to do this, but for many of the
        Madness levelwise iterators that do all levels from 0..max_level this
        will be much faster when the data is not all the way down at max_level.
        A sparse array or an array of associative arrays may be more conducive
        to this type of iteration vs. the associative domain used here.
     */
    def lvl_iter(lvl: int) {
        // AGS - Why are we doing indices.member(i)?
        for i in locIndices do
            if i(1) == lvl && locIndices.member(i) then yield locNodes[i].data;
    }

    /** Check if there are coefficients in box (lvl, idx)
     */
    def has_coeffs(lvl: int, idx: int) {
        return locIndices.member((lvl, idx));
    }

    /** Remove an element from the associative domain.  If the element
        does not exist, it is ignored.
     */
    def remove(lvl: int, idx: int) {
        if locIndices.member((lvl, idx)) then locIndices.remove((lvl, idx));
    }

    def idx_iter(lvl: int) {
        for idx in locIndices do
            if idx(1) == lvl then yield idx;
    }

    def idx_iter() {
        for idx in locIndices do
            yield idx;
    }
}

class FTree {
    const order    : int;
    const coeffDom = [0..order-1];

    //var nodes    : domain(1) distributed(Block) = [Locales.domain] LocTree;
    var nodes    : [Locales.domain] LocTree;

    def initialize() {
        if order == 0 then
            halt("FTree must be initialized with an order > 0");

        [loc in nodes.domain] nodes[loc] = new LocTree(coeffDom);
    }

    def mapNodeToLoc(lvl, idx) {
        return (lvl+idx)%numLocales;
    }

    def this((lvl, idx)) var {
        const n => nodes[mapNodeToLoc(lvl, idx)];
        return n[lvl, idx];
    }

    def this(loc) {
        for data in nodes[loc] do
            yield data;
    }

    def these() {
        for n in nodes do
            for data in n do
                yield data;
    }

    def lvl_iter(lvl) {
        for n in nodes do
            for data in n.lvl_iter(lvl) do
                yield data;
    }

    def peek((lvl, idx)) var {
        const n => nodes[mapNodeToLoc(lvl, idx)];
        return n.peek(lvl, idx);
    }

    def has_coeffs((lvl, idx)) {
        const n => nodes[mapNodeToLoc(lvl, idx)];
        return n.has_coeffs(lvl, idx);
    }

    def remove((lvl, idx)) {
        const n => nodes[mapNodeToLoc(lvl, idx)];
        n.remove(lvl, idx);
    }

    /** Return a copy of this FTree
     */
    def copy() {
        var t = new FTree(order);
        t.nodes = nodes;
        return t;
    }

    def idx_iter(lvl) {
        for n in nodes do
            for idx in n.idx_iter(lvl) do
                yield idx;
    }

    def idx_iter() {
        for n in nodes do
            for idx in n.idx_iter() do
                yield idx;
    }

    def path_upwards((lvl0, idx0), (lvl1, idx1)) {
        var l = idx0;
        for n in lvl1..lvl0 by -1 {
           yield ((n,l));
           l /= 2;
        }
    }

    def path_downwards((lvl0, idx0), (lvl1, idx1)) {
        for n in (lvl0..lvl1-1) {
          yield (n, idx1/(2**(lvl1-n)));
        }
    }

    def on_boundary((lvl, idx)) {
        return ((idx < 0 || idx >= 2**lvl));
    }

    def get_children((lvl, idx)) {
        return ((lvl+1, 2*idx), (lvl+1, 2*idx+1));
    }

    def get_neighbors((lvl, idx)) {
        return ((lvl, idx-1), (lvl, idx+1));
    }
}

def main() {
    /*
    var f = new FTree(2);

    for (i, j) in [0..2, 0..2] do f[(i, j)] = (i, j);

    for (i, j) in [1..0, 1..0] do f.remove((i, j));

    for (i, j) in [0..1, 0..1] do f[(i, j)] = (-(i:real), -(j:real));

    for (i, j) in [2..1, 2..1] do f.remove((i, j));

    for (i, j) in [1..2, 1..2] do f[(i, j)] = (-(i:real), -(j:real));

    for (i, j) in [0..2, 0..2] do
        writeln("(",i,", ",j,") = ", f.peek((i, j)));

    for i in 0..2 do
        for n in f.lvl_iter(i) do
            writeln(i, ": ", n);
    */

    var f = new FTree(2);

    for (i, j) in [1..3, 2..4] do f[(i, j)] = (i, j);

    for loc in Locales.domain {
        writeln("\n\ntree on loc ", loc, " = ");
        for data in f[loc] do
            writeln(data);
    }

    writeln("\n\nf.has_coeffs((4, 5)) = ", f.has_coeffs((4, 5)));
    writeln("f.peek((4, 5)) = ", f.peek((4, 5)));
    writeln("f[(4, 5)] = ", f[(4, 5)]);
    writeln("f.remove((4, 5))");
    f.remove((4, 5));

    writeln("\n\nf.has_coeffs((1, 2)) = ", f.has_coeffs((1, 2)));
    writeln("f.peek((1, 2)) = ", f.peek((1, 2)));
    writeln("f[(1, 2)] = ", f[(1, 2)]);
    writeln("f.remove((1, 2))");
    f.remove((1, 2));

    writeln("\n\nf.remove((3, 4))");
    f.remove((3, 4));

    for loc in Locales.domain {
        writeln("\n\ntree on loc ", loc, " = ");
        for data in f[loc] do
            writeln(data);
    }

    writeln("\n\nentire tree = ");
    for data in f do
        writeln(data);

    writeln("\n\nentire tree = ");
    writeln(f);

    var f1 = f.copy();

    writeln("\n\nf.remove((3, 2))");
    f.remove((3, 2));

    writeln("\n\nentire tree = ");
    writeln(f);

    writeln("\n\ntree copy = ");
    writeln(f1);

    for lvl in 1..3 {
        writeln("\n\ndata on lvl ", lvl, " = ");
        for data in f.lvl_iter(lvl) do
            writeln(data);
    }

    for lvl in 1..3 {
        writeln("\n\nindices on lvl ", lvl, " = ");
            for idx in f.idx_iter(lvl) do
                writeln(idx);
    }

    writeln("\n\nall tree indices = ");
    for (n, l) in f.idx_iter() do
        writeln(n,l);
}

/** FTree: The tree data structure used to store the numerical representation
    of a function.

    For 1d Madness, an FTree is a binary tree, for 2d it would
    be a quad tree, 3d an octtree, and so on.  The FTree used here uses level
    and index hashing to store the tree rather than storing connectivity
    information.  So, it is important that you do a has_coeffs() before
    indexing into the tree to be sure you don't create coeffs when you don't
    mean to.
 */
config const numLocs = 3;

// FIXME: The Coeff record works around arrays of arrays.  Ideally, nodes
//        should be a mapping from indices onto :[coeffDom] real.
record Coeff {
    var dom  : domain(1);
    var data : [dom] real;
}

// The empty vector, returned when there are no Coeffs.
var None: [0..-1] real;
const rootNode = (0,0);
const emptyNode = (-1,-1);

// Check if we have None.
def isNone(x) {
    return x.numElements == 0;
}

class LocTree {
    const coeffDom : domain(1);
    var locIndices : domain(2*int);      // Indexed by 2-tuples of integers
    var locNodes   : [locIndices] Coeff; // Associative Mapping: (:int, :int)
                                         // => Coeff

    var zeroes     : [coeffDom] real;    // Return zeroes from this() when
                                         // reading something that has not yet
                                         // been set.

    /** Access an element in the associative domain.  If the element
        doesn't exist it will be created.

        We have to be really careful about creating extra boxes when we don't
        mean to.  The algorithms tend to be really sensitive to the structure
        of the tree.  Scaling coefficients are used to mark the leaves in the
        sum tree, so if you accidentally create scaling coeffs somewhere in the
        middle of the tree you will short circuit a whole subtree!

     */
    def this(lvl: int, idx: int) var {
        if !locIndices.member((lvl, idx)) {
            if setter {
              locIndices += ((lvl, idx));
              locNodes[(lvl, idx)] = new Coeff(coeffDom);
            } else {
              // This is a getter so it shouldn't be modifying what
              // we return, should be safe to return the zero vector.
              // FIXME: Zeroes should really be a const, but can'ti
              //        return const from a var fcn.
              return zeroes;
            }
        }

        return locNodes[(lvl, idx)].data;
    }

    /** Access an element in the associative domain.  If it doesn't exist,
        return None.
     */
    def peek(lvl: int, idx: int) var {
        if has_coeffs(lvl, idx) then
            return this(lvl, idx);
        else
            return None;
    }

    /** Unordered iterator over all coefficients
     */
    def these() {
        for n in locNodes do
            yield n.data;
    }

    /** Unordered iterator over all boxes in a particular level.
        This is not a particularly fast way to do this, but for many of the
        Madness levelwise iterators that do all levels from 0..max_level this
        will be much faster when the data is not all the way down at max_level.
        A sparse array or an array of associative arrays may be more conducive
        to this type of iteration vs. the associative domain used here.
     */
    def lvl_iter(lvl: int) {
        // AGS - Why are we doing indices.member(i)?
        for i in locIndices do
            if i(1) == lvl && locIndices.member(i) then yield locNodes[i].data;
    }

    /** Check if there are coefficients in box (lvl, idx)
     */
    def has_coeffs(lvl: int, idx: int) {
        return locIndices.member((lvl, idx));
    }

    /** Remove an element from the associative domain.  If the element
        does not exist, it is ignored.
     */
    def remove(lvl: int, idx: int) {
        if locIndices.member((lvl, idx)) then locIndices.remove((lvl, idx));
    }

    def idx_iter(lvl: int) {
        for idx in locIndices do
            if idx(1) == lvl then yield idx;
    }

    def idx_iter() {
        for idx in locIndices do
            yield idx;
    }
}

class FTree {
    const order    : int;
    const coeffDom = [0..order-1];

    const locDom = [0..numLocs);
    var nodes    : [locDom] LocTree;

    def initialize() {
        if order == 0 then
            halt("FTree must be initialized with an order > 0");

        [loc in locDom] nodes[loc] = new LocTree(coeffDom);
    }

    def mapNodeToLoc(lvl, idx) {
        return (lvl+idx)%numLocs;
    }

    def this((lvl, idx)) var {
        const n => nodes[mapNodeToLoc(lvl, idx)];
        return n[lvl, idx];
    }

    def this(loc) {
        for data in nodes[loc] do
            yield data;
    }

    def these() {
        for n in nodes do
            for data in n do
                yield data;
    }

    def lvl_iter(lvl) {
        for n in nodes do
            for data in n.lvl_iter(lvl) do
                yield data;
    }

    def peek((lvl, idx)) var {
        const n => nodes[mapNodeToLoc(lvl, idx)];
        return n.peek(lvl, idx);
    }

    def has_coeffs((lvl, idx)) {
        const n => nodes[mapNodeToLoc(lvl, idx)];
        return n.has_coeffs(lvl, idx);
    }

    def remove((lvl, idx)) {
        const n => nodes[mapNodeToLoc(lvl, idx)];
        n.remove(lvl, idx);
    }

    /** Return a copy of this FTree
     */
    def copy() {
        var t = new FTree(order);
        t.nodes = nodes;
        return t;
    }

    def idx_iter(lvl) {
        for n in nodes do
            for idx in n.idx_iter(lvl) do
                yield idx;
    }

    def idx_iter() {
        for n in nodes do
            for idx in n.idx_iter() do
                yield idx;
    }

    def path_upwards((lvl0, idx0), (lvl1, idx1)) {
        var l = idx0;
        for n in lvl1..lvl0 by -1 {
           yield ((n,l));
           l /= 2;
        }
    }

    def path_downwards((lvl0, idx0), (lvl1, idx1)) {
        for n in (lvl0..lvl1-1) {
          yield (n, idx1/(2**(lvl1-n)));
        }
    }

    def on_boundary((lvl, idx)) {
        return ((idx < 0 || idx >= 2**lvl));
    }

    def get_children((lvl, idx)) {
        return ((lvl+1, 2*idx), (lvl+1, 2*idx+1));
    }

    def get_neighbors((lvl, idx)) {
        return ((lvl, idx-1), (lvl, idx+1));
    }
}

def main() {
    /*
    var f = new FTree(2);

    for (i, j) in [0..2, 0..2] do f[(i, j)] = (i, j);

    for (i, j) in [1..0, 1..0] do f.remove((i, j));

    for (i, j) in [0..1, 0..1] do f[(i, j)] = (-(i:real), -(j:real));

    for (i, j) in [2..1, 2..1] do f.remove((i, j));

    for (i, j) in [1..2, 1..2] do f[(i, j)] = (-(i:real), -(j:real));

    for (i, j) in [0..2, 0..2] do
        writeln("(",i,", ",j,") = ", f.peek((i, j)));

    for i in 0..2 do
        for n in f.lvl_iter(i) do
            writeln(i, ": ", n);
    */

    var f = new FTree(2);

    for (i, j) in [1..3, 2..4] do f[(i, j)] = (i, j);

    for loc in 0..numLocs-1 {
        writeln("\n\ntree on loc ", loc, " = ");
        for data in f[loc] do
            writeln(data);
    }

    writeln("\n\nf.has_coeffs((4, 5)) = ", f.has_coeffs((4, 5)));
    writeln("f.peek((4, 5)) = ", f.peek((4, 5)));
    writeln("f[(4, 5)] = ", f[(4, 5)]);
    writeln("f.remove((4, 5))");
    f.remove((4, 5));

    writeln("\n\nf.has_coeffs((1, 2)) = ", f.has_coeffs((1, 2)));
    writeln("f.peek((1, 2)) = ", f.peek((1, 2)));
    writeln("f[(1, 2)] = ", f[(1, 2)]);
    writeln("f.remove((1, 2))");
    f.remove((1, 2));

    writeln("\n\nf.remove((3, 4))");
    f.remove((3, 4));

    for loc in 0..numLocs-1 {
        writeln("\n\ntree on loc ", loc, " = ");
        for data in f[loc] do
            writeln(data);
    }

    writeln("\n\nentire tree = ");
    for data in f do
        writeln(data);

    writeln("\n\nentire tree = ");
    writeln(f);

    var f1 = f.copy();

    writeln("\n\nf.remove((3, 2))");
    f.remove((3, 2));

    writeln("\n\nentire tree = ");
    writeln(f);

    writeln("\n\ntree copy = ");
    writeln(f1);

    for lvl in 1..3 {
        writeln("\n\ndata on lvl ", lvl, " = ");
        for data in f.lvl_iter(lvl) do
            writeln(data);
    }

    for lvl in 1..3 {
        writeln("\n\nindices on lvl ", lvl, " = ");
            for idx in f.idx_iter(lvl) do
                writeln(idx);
    }

    writeln("\n\nall tree indices = ");
    for (n, l) in f.idx_iter() do
        writeln(n,l);
}


Fix to last night's regressions.  My fault.

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

//
// Arrays and domains
//

// this is the class that all array classes are derived from
pragma "abase"
class _abase {
  def reallocate(d: _domain) {
    halt("reallocation not implemented for this array");
  }
}

// this is a wrapper class for all arrays
pragma "array"
class _array {
  type _array_type;
  type elt_type;
  param rank : int;
  var _value : _array_type;
  var dom : _domain;

  def this(d : _domain)
    return _value.slice(d._value);

  def =this(d : _domain, val: elt_type) {
    for i in d do
      this(i) = val;
  }

  def this(i...?k)
    return _value((...i));

  def =this(i...?k, val: elt_type) {
    _value((...i)) = val;
  }

  def getHeadCursor()
    return _value.getHeadCursor();

  def getNextCursor(c)
    return _value.getNextCursor(c);

  def getValue(c)
    return _value.getValue(c);

  def isValidCursor?(c)
    return _value.isValidCursor?(c);

  iterator this() : elt_type {
    forall x in _value
      yield x;
  }

  def numElements return dom.numIndices;

  def view(d: _domain)
    return _value.view(d._value);
}

def =(a: _array, b: _array) {
  a._value.assign(b._value);
  return a;
}

def =(a: _array, b: _domain) {
  a._value.assign(b._value);
  return a;
}

def =(a: _array, b: seq) {
  a._value.assign(b);
  return a;
}

def =(a: _array, b: _aseq) {
  a._value.assign(b);
  return a;
}

def =(a: _array, b: a.elt_type) {
  a._value.assign(b);
  return a;
}

def _copy(a: _array) {
  var b : [a.dom] a.elt_type;
  b = a;
  return b;
}

def _init(a: _array) {
  var b : [a.dom] a.elt_type;
  return b;
}

def fwrite(f : file, a: _array) {
  fwrite(f, a._value);
}

pragma "domain"
class _domain {
  type _domain_type;
  type _index_type;
  param rank : int;
  var _value : _domain_type;
  var _arrs: seq(_abase);

  def getHeadCursor()
    return _value.getHeadCursor();

  def getNextCursor(c)
    return _value.getNextCursor(c);

  def getValue(c)
    return _value.getValue(c);

  def isValidCursor?(c)
    return _value.isValidCursor?(c);

  iterator this() : _index_type {
    forall x in _value
      yield x;
  }

  def this(dim : int)
    return _value(dim);

  def _build_array(type elt_type) {
    var x = _value._build_array(elt_type);
    _arrs #= x;
    return _array(x.type, elt_type, rank, x, this);
  }

  def _build_sparse_domain() {
    var x = _value._build_sparse_domain();
    return _domain(x.type, _index_type, rank, x);
  }

  def _exclusive_upper
    return _domain(_domain_type, _index_type, rank, _value._exclusive_upper);

  def add(i) {
    _value.add(i);
  }

  def remove(i) {
    _value.remove(i);
  }

  def numIndices return _value.numIndices;

  def member?(i) {
    return _value.member?(i);
  }

  def expand(i...?k) {
    var x = _value.expand((...i));
    return _domain(x.type, _index_type, rank, x);
  }

  def exterior(i...?k) {
    var x = _value.exterior((...i));
    return _domain(x.type, _index_type, rank, x);
  }

  def interior(i...?k) {
    var x = _value.interior((...i));
    return _domain(x.type, _index_type, rank, x);
  }

  def translate(i...?k) {
    var x = _value.translate((...i));
    return _domain(x.type, _index_type, rank, x);
  }
}

def =(a: _domain, b: _domain) {
  for e in a._arrs do
    e.reallocate(b);
  a._value = b._value;
  return a;
}

def fwrite(f : file, a: _domain) {
  fwrite(f, a._value);
}

def by(a: _domain, b) {
  var x = a._value by b;
  return _domain(x.type, a._index_type, a.rank, x);
}

////////////////////////////////////////////////////

def _build_domain(x)
  return x;

def _build_domain(ranges : _aseq ...?rank) {
  type t = ranges(1).elt_type;
  var x = _adomain(rank, t, ranges);
  return _domain(x.type, x.getValue(x.getHeadCursor()).type, rank, x);
}

def _build_domain_exclusive_upper(x...?rank)
  return _build_domain((...x))._exclusive_upper;

def _build_domain_type(param rank : int, type dimensional_index_type = int) {
  var x = _adomain(rank, dimensional_index_type);
  return _domain(x.type, x.getValue(x.getHeadCursor()).type, rank, x);
}

def _build_domain_type(type ind_type) {
  var x = _idomain(ind_type);
  return _domain(x.type, ind_type, 1, x);
}

def _build_sparse_domain_type(dom)
  return dom._build_sparse_domain();

def _build_array_type(dom, type elt_type)
  return dom._build_array(elt_type);

def _build_index_type(param i: int) where i > 1 {
  var x : i*int;
  return x;
}

def _build_index_type(param i: int) where i == 1 {
  var x : int;
  return x;
}

def _build_index_type(dom) {
  var x: dom._index_type;
  return x;
}

///////////////

def _aseq._translate(i : int) {
  return _low+i.._high+i by _stride;
}

def _aseq._interior(i : int) {
  var x = _low.._high by _stride;
  if (i < 0) {
    x = _low.._low-1-i by _stride;
  } else if (i > 0) {
    x = _high+1-i.._high by _stride;
  }
  return x;
}

def _aseq._exterior(i : int) {
  var x = _low.._high by _stride;
  if (i < 0) {
    x = _low+i.._low-1 by _stride;
  } else if (i > 0) {
    x = _high+1.._high+i by _stride;
  }
  return x;
}

def _aseq._expand(i : int) {
  return _low-i.._high+i by _stride;
}

class _adomain {
  param rank : int;
  type dim_type;
  var ranges : rank*_aseq(dim_type);

  def _exclusive_upper {
    var x = _adomain(rank, dim_type);
    for param i in 1..rank {
      if ranges(i)._stride != 1 then
        halt("syntax [domain-specification) requires a stride of one");
      x.ranges(i) = ranges(i)._low..ranges(i)._high-1;
    }
    return x;
  }

  def getHeadCursor() {
    var c : rank*dim_type;
    for param i in 1..rank do
      c(i) = ranges(i).getHeadCursor();
    return c;
  }

  def getNextCursor(c) {
    for param i in 1..rank {
      c(rank-i+1) = ranges(rank-i+1).getNextCursor(c(rank-i+1));
      if ranges(rank-i+1).isValidCursor?(c(rank-i+1)) then
        return c;
      else
        c(rank-i+1) = ranges(rank-i+1).getHeadCursor();
    }
    c(1) = ranges(1)._high+1;
    return c;
  }

  def getValue(c) {
    if rank == 1 then
      return c(1);
    else
      return c;
  }

  def isValidCursor?(c) {
    return c(1) != ranges(1)._high+1;
  }

  def this(dim : int)
    return ranges(dim);

  def numIndices {
    var sum = 1;
    for param i in 1..rank do
      sum *= ranges(i).length;
    return sum;
    // WANT: return * reduce (this(1..rank).length);
  }

  def _build_array(type elt_type)
    return _aarray(elt_type, rank, dim_type, dom=this);

  def _build_sparse_domain()
    return _sdomain(rank, dim_type, adomain=this);

  def translate(dim : rank*int) {
    var x = _adomain(rank, int);
    for i in 1..rank do
      x.ranges(i) = this(i)._translate(dim(i));
    return x;
  }

  def translate(dim : int ...?numDims) {
    return translate(dim);
  }

  def interior(dim : rank*int) {
    var x = _adomain(rank, int);
    for i in 1..rank do {
      if ((dim(i) > 0) && (this(i)._high+1-dim(i) < this(i)._low) ||
          (dim(i) < 0) && (this(i)._low-1-dim(i) > this(i)._high)) {
        halt("***Error: Argument to 'interior' function out of range in dimension ", i, "***");
      }
      x.ranges(i) = this(i)._interior(dim(i));
    }
    return x;
  }

  def interior(dim : int ...?numDims) {
    return interior(dim);
  }

  def exterior(dim : rank*int) {
    var x = _adomain(rank, int);
    for i in 1..rank do
      x.ranges(i) = this(i)._exterior(dim(i));
    return x;
  }

  def exterior(dim : int ...?numDims) {
    return exterior(dim);
  }

  def expand(dim : rank*int) {
    var x = _adomain(rank, int);
    for i in 1..rank do {
      x.ranges(i) = ranges(i)._expand(dim(i));
      if (x.ranges(i)._low > x.ranges(i)._high) {
        halt("***Error: Degenerate dimension created in dimension ", i, "***");
      }
    }
    return x;
  }

  def expand(dim : int ...?numDims) {
    var x = _adomain(rank, int);
    if (rank == numDims) {
      -- NOTE: would probably like to get rid of this assignment
      -- since domain assignment is/will eventually be nontrivial
      -- in cost;  yet returning expand(dim) currently causes
      -- problems as captured in trivial/shannon/condReturn3.chpl
      x = expand(dim);
    } else if (numDims == 1) {
      for i in 1..rank do
        x.ranges(i) = ranges(i)._expand(dim(1));
    } else {
      halt("***Error: Rank mismatch between domain and expand() arguments (", rank, " != ", numDims, ")***");
    }
    return x;
  }
}

def by(dom : _adomain, dim : dom.rank*int) {
  var x = _adomain(dom.rank, dom.dim_type);
  for i in 1..dom.rank do
    x.ranges(i) = dom.ranges(i) by dim(i);
  return x;
}

def by(dom : _adomain, dim : int) {
  var x = _adomain(dom.rank, dom.dim_type);
  for i in 1..dom.rank do
    x.ranges(i) = dom.ranges(i) by dim;
  return x;
}

class _aarray: _abase {
  type elt_type;
  param rank : int;
  type dim_type;

  var dom : _adomain(rank, dim_type);
  var off: rank*dim_type;
  var blk: rank*dim_type;
  var str: rank*int;
  var orig: rank*dim_type;
  var size : dim_type;
  var data : _ddata(elt_type);
  var noinit: bool = false;

  def getHeadCursor()
    return dom.getHeadCursor();

  def getNextCursor(c)
    return dom.getNextCursor(c);

  def getValue(c)
    return this(dom.getValue(c));

  def isValidCursor?(c)
    return dom.isValidCursor?(c);

  def initialize() {
    if noinit == true then return;
    for param dim in 1..rank {
      off(dim) = dom(dim)._low;
      str(dim) = dom(dim)._stride;
      orig(dim) = 0:dim_type;
    }
    blk(rank) = 1:dim_type;
    for dim in 1..rank-1 by -1 do
      blk(dim) = blk(dim+1) * dom(dim+1).length;
    size = blk(1) * dom(1).length;
    data = _ddata(elt_type, size:int); // ahh!!! can't cast to int here
    data.init();
  }

  def this(d : _adomain) {
    var a : [d] elt_type;
    for i in d do
      a(i) = this(i);
    return a;
  }

  def =this(d : _adomain, v) {
    for i in d do
      this(i) = v;
  }

  def this(ind : rank*dim_type) var {
    if boundsChecking
      for param i in 1..rank do
        if !_in(dom(i), ind(i)) then
          halt("array index out of bounds: ", ind);
    var sum : dim_type;
    for param i in 1..rank do
      sum = sum + (ind(i) - off(i)) * blk(i) / str(i):dim_type - orig(i);
//    write(" [", ind, " = ", sum, "] ");
    return data(sum:int); // !!ahh
  }

  def this(ind : dim_type ...rank) var
    return this(ind);

  def this(ind: _aseq(dim_type) ...rank) var {
    var d = [(...ind)];
    return slice(d._value);
  }

  def view(d: _adomain) {
    if rank != d.rank then
      halt("array rank change not supported");
    for param i in 1..rank do
      if d(i).length != dom(i).length then
        halt("extent in dimension ", i, " does not match actual");
    var alias = _aarray(elt_type, rank, dim_type, d, noinit=true);
    alias.data = data;
    alias.size = size;
    for param i in 1..rank {
      alias.off(i) = d(i)._low;
      alias.blk(i) = blk(i) * (dom(i)._stride / str(i));
      alias.str(i) = d(i)._stride;
      alias.orig(i) = orig(i) + (off(i) - dom(i)._low) * blk(i);
    }
    return _array(alias.type, elt_type, rank, alias, _domain(d.type, d.getValue(d.getHeadCursor()).type, rank, d));
  }

  def slice(d: _adomain) {
    if rank != d.rank then
      halt("array rank change not supported");
    for param i in 1..rank {
      if !_in(dom(i), d(i)) then
        halt("array slice out of bounds in dimension ", i, ": ", d);
      if d(i)._stride % dom(i)._stride != 0 then
        halt("stride of array slice is not multiple of stride in dimension ", i);
    }
    var alias = _aarray(elt_type, rank, dim_type, d, noinit=true);
    alias.data = data;
    alias.size = size;
    for param i in 1..rank {
      alias.off(i) = off(i);
      alias.blk(i) = blk(i);
      alias.str(i) = str(i);
      alias.orig(i) = orig(i);
    }
    return _array(alias.type, elt_type, rank, alias, _domain(d.type, d.getValue(d.getHeadCursor()).type, rank, d));
  }

  def reallocate(d: _domain) {
    if (d.rank == rank) {
      var new = _aarray(elt_type, rank, dim_type, d._value);
      for i in _intersect(d._value, dom) do
        new(i) = this(i);
      dom = new.dom;
      off = new.off;
      blk = new.blk;
      str = new.str;
      orig = new.orig;
      size = new.size;
      data = new.data;
    }
  }

  def assign(y : _aarray) {
    for i,e in dom,y do
      this(i) = e;
  }

  def assign(y : _adomain) {
    for i,e in dom,y do
      this(i) = e;
  }

  def assign(y : seq) {
    for i,e in dom,y do
      this(i) = e;
  }

  def assign(y: _aseq) {
    for i,e in dom,y do
      this(i) = e;
  }

  def assign(y: elt_type) {
    for i in dom do
      this(i) = y;
  }
}

def fwrite(f : file, x : _adomain) {
  fwrite(f, "[", x(1));
  for i in 2..x.rank do
    fwrite(f, ", ", x(i));
  fwrite(f, "]");
}

def fwrite(f : file, x : _aarray) {
  var i : x.rank*x.dim_type;
  for dim in 1..x.rank do
    i(dim) = x.dom(dim)._low;
  label next while true {
    fwrite(f, x(i));
    if i(x.rank) <= (x.dom(x.rank)._high - x.dom(x.rank)._stride:x.dim_type) {
      fwrite(f, " ");
      i(x.rank) += x.dom(x.rank)._stride:x.dim_type;
    } else {
      for dim in 1..x.rank-1 by -1 {
        if i(dim) <= (x.dom(dim)._high - x.dom(dim)._stride:x.dim_type) {
          i(dim) += x.dom(dim)._stride:x.dim_type;
          for dim2 in dim+1..x.rank {
            fwrite(f, "\n");
            i(dim2) = x.dom(dim2)._low;
          }
          continue next;
        }
      }
      break;
    }
  }
}

def _intersect(a: _aseq, b: _aseq) {
  var g, x: int;
  (g, x) = _extended_euclid(a._stride, b._stride);
  if abs(a._low - b._low) % g != 0 then
    return 1..0:a.elt_type;
  var low = max(a._low, b._low);
  var high = min(a._high, b._high);
  var stride = a._stride * b._stride / g;
  var alignment = (a._low + (b._low - a._low) * x * a._stride / g):a.elt_type;
  if alignment == 0 then
    alignment = stride:a.elt_type;
  low = low + low % alignment;
  return low..high by stride;
}

def _intersect(a: _adomain, b: _adomain) {
  var c = _adomain(a.rank, a.dim_type);
  for param i in 1..a.rank do
    c.ranges(i) = _intersect(a(i), b(i));
  return c;
}

// Extended-Euclid (Knuth Volume 2 --- Section 4.5.2)
// given two non-negative integers u and v
// returns (gcd(u, v), x) where x is set such that u*x + v*y = gcd(u, v)
def _extended_euclid(u: int, v: int) {
  var u1 = 1;
  var u2 = 0;
  var u3 = u;
  var v1 = 0;
  var v2 = 1;
  var v3 = v;
  while v3 != 0 {
    var q = u3 / v3;
    var t1 = u1 - v1 * q;
    var t2 = u2 - v2 * q;
    var t3 = u3 - v3 * q;
    u1 = v1;
    u2 = v2;
    u3 = v3;
    v1 = t1;
    v2 = t2;
    v3 = t3;
  }
  return (u3, u1);
}

-- Arithmetic sequence

def _build_aseq(low: int, high: int) return _aseq(int, low, high);
def _build_aseq(low: uint, high: uint) return _aseq(uint, low, high);
def _build_aseq(low: int(64), high: int(64)) return _aseq(int(64), low, high);
def _build_aseq(low: uint(64), high: uint(64)) return _aseq(uint(64), low, high);

def _build_aseq(low, high) {
  compilerError("arithmetic sequence bounds are not of integral type");
}

record _aseq {
  type elt_type;
  var _low : elt_type;
  var _high : elt_type;
  var _stride : int = 1;

  def initialize() {
    if _low > _high {
      _low = 1:elt_type;
      _high = 0:elt_type;
      _stride = 1;
    }
  }

  iterator this() : elt_type {
    forall x in this
      yield x;
  }

  def getHeadCursor()
    if _stride > 0 then
      return _low;
    else
      return _high;

  def getNextCursor(c)
    return c + _stride:elt_type;

  def getValue(c)
    return c;

  def isValidCursor?(c)
    return _low <= c && c <= _high;

  def length
    return
      (if _stride > 0
        then (_high - _low + _stride:elt_type) / _stride:elt_type
        else (_low - _high + _stride:elt_type) / _stride:elt_type);
}

def by(s : _aseq, i : int) {
  if i == 0 then
    halt("illegal stride of 0");
  if s._low == 1 && s._high == 0 then
    return _aseq(s.elt_type, s._low, s._high, s._stride);
  var as = _aseq(s.elt_type, s._low, s._high, s._stride * i);
  if as._stride < 0 then
    as._low = as._low + (as._high - as._low) % (-as._stride):as.elt_type;
  else
    as._high = as._high - (as._high - as._low) % (as._stride):as.elt_type;
  return as;
}

def _in(s : _aseq, i : s.elt_type)
  return i >= s._low && i <= s._high &&
    (i - s._low) % abs(s._stride):s.elt_type == 0;

// really slow --- REWRITE
def _in(s1: _aseq, s2: _aseq) {
  for i in s2 do
    if !_in(s1, i) then
      return false;
  return true;
}

def fwrite(f : file, s : _aseq) {
  fwrite(f, s._low, "..", s._high);
  if (s._stride != 1) then
    fwrite(f, " by ", s._stride);
}

pragma "inline" def string.substring(s: _aseq)
  if s._stride != 1 then
    return __primitive("string_strided_select", this, s._low, s._high, s._stride);
  else
    return __primitive("string_select", this, s._low, s._high);

//
// Arrays and domains
//

// this is the class that all array classes are derived from
pragma "abase"
class _abase {
  def reallocate(d: _domain) {
    halt("reallocation not implemented for this array");
  }
}

// this is a wrapper class for all arrays
pragma "array"
class _array {
  type _array_type;
  type elt_type;
  param rank : int;
  var _value : _array_type;
  var dom : _domain;

  def this(d : _domain)
    return _value.slice(d._value);

  def =this(d : _domain, val: elt_type) {
    for i in d do
      this(i) = val;
  }

  def this(i...?k)
    return _value((...i));

  def =this(i...?k, val: elt_type) {
    _value((...i)) = val;
  }

  def getHeadCursor()
    return _value.getHeadCursor();

  def getNextCursor(c)
    return _value.getNextCursor(c);

  def getValue(c)
    return _value.getValue(c);

  def isValidCursor?(c)
    return _value.isValidCursor?(c);

  iterator this() : elt_type {
    forall x in _value
      yield x;
  }

  def numElements return dom.numIndices;

  def view(d: _domain)
    return _value.view(d._value);
}

def =(a: _array, b: _array) {
  a._value.assign(b._value);
  return a;
}

def =(a: _array, b: _domain) {
  a._value.assign(b._value);
  return a;
}

def =(a: _array, b: seq) {
  a._value.assign(b);
  return a;
}

def =(a: _array, b: _aseq) {
  a._value.assign(b);
  return a;
}

def =(a: _array, b: a.elt_type) {
  a._value.assign(b);
  return a;
}

def _copy(a: _array) {
  var b : [a.dom] a.elt_type;
  b = a;
  return b;
}

def _init(a: _array) {
  var b : [a.dom] a.elt_type;
  return b;
}

def fwrite(f : file, a: _array) {
  fwrite(f, a._value);
}

pragma "domain"
class _domain {
  type _domain_type;
  type _index_type;
  param rank : int;
  var _value : _domain_type;
  var _arrs: seq(_abase);

  def getHeadCursor()
    return _value.getHeadCursor();

  def getNextCursor(c)
    return _value.getNextCursor(c);

  def getValue(c)
    return _value.getValue(c);

  def isValidCursor?(c)
    return _value.isValidCursor?(c);

  iterator this() : _index_type {
    forall x in _value
      yield x;
  }

  def this(dim : int)
    return _value(dim);

  def _build_array(type elt_type) {
    var x = _value._build_array(elt_type);
    _arrs #= x;
    return _array(x.type, elt_type, rank, x, this);
  }

  def _build_sparse_domain() {
    var x = _value._build_sparse_domain();
    return _domain(x.type, _index_type, rank, x);
  }

  def _exclusive_upper
    return _domain(_domain_type, _index_type, rank, _value._exclusive_upper);

  def add(i) {
    _value.add(i);
  }

  def remove(i) {
    _value.remove(i);
  }

  def numIndices return _value.numIndices;

  def member?(i) {
    return _value.member?(i);
  }

  def expand(i...?k) {
    var x = _value.expand((...i));
    return _domain(x.type, _index_type, rank, x);
  }

  def exterior(i...?k) {
    var x = _value.exterior((...i));
    return _domain(x.type, _index_type, rank, x);
  }

  def interior(i...?k) {
    var x = _value.interior((...i));
    return _domain(x.type, _index_type, rank, x);
  }

  def translate(i...?k) {
    var x = _value.translate((...i));
    return _domain(x.type, _index_type, rank, x);
  }
}

def =(a: _domain, b: _domain) {
  for e in a._arrs do
    e.reallocate(b);
  a._value = b._value;
  return a;
}

def fwrite(f : file, a: _domain) {
  fwrite(f, a._value);
}

def by(a: _domain, b) {
  var x = a._value by b;
  return _domain(x.type, a._index_type, a.rank, x);
}

////////////////////////////////////////////////////

def _build_domain(x)
  return x;

def _build_domain(ranges : _aseq ...?rank) {
  type t = ranges(1).elt_type;
  var x = _adomain(rank, t, ranges);
  return _domain(x.type, x.getValue(x.getHeadCursor()).type, rank, x);
}

def _build_domain_exclusive_upper(x...?rank)
  return _build_domain((...x))._exclusive_upper;

def _build_domain_type(param rank : int, type dimensional_index_type = int) {
  var x = _adomain(rank, dimensional_index_type);
  return _domain(x.type, x.getValue(x.getHeadCursor()).type, rank, x);
}

def _build_domain_type(type ind_type) {
  var x = _idomain(ind_type);
  return _domain(x.type, ind_type, 1, x);
}

def _build_sparse_domain_type(dom)
  return dom._build_sparse_domain();

def _build_array_type(dom, type elt_type)
  return dom._build_array(elt_type);

def _build_index_type(param i: int) where i > 1 {
  var x : i*int;
  return x;
}

def _build_index_type(param i: int) where i == 1 {
  var x : int;
  return x;
}

def _build_index_type(dom) {
  var x: dom._index_type;
  return x;
}

///////////////

def _aseq._translate(i : int) {
  return _low+i.._high+i by _stride;
}

def _aseq._interior(i : int) {
  var x = _low.._high by _stride;
  if (i < 0) {
    x = _low.._low-1-i by _stride;
  } else if (i > 0) {
    x = _high+1-i.._high by _stride;
  }
  return x;
}

def _aseq._exterior(i : int) {
  var x = _low.._high by _stride;
  if (i < 0) {
    x = _low+i.._low-1 by _stride;
  } else if (i > 0) {
    x = _high+1.._high+i by _stride;
  }
  return x;
}

def _aseq._expand(i : int) {
  return _low-i.._high+i by _stride;
}

class _adomain {
  param rank : int;
  type dim_type;
  var ranges : rank*_aseq(dim_type);

  def _exclusive_upper {
    var x = _adomain(rank, dim_type);
    for param i in 1..rank {
      if ranges(i)._stride != 1 then
        halt("syntax [domain-specification) requires a stride of one");
      x.ranges(i) = ranges(i)._low..ranges(i)._high-1;
    }
    return x;
  }

  def getHeadCursor() {
    var c : rank*dim_type;
    for param i in 1..rank do
      c(i) = ranges(i).getHeadCursor();
    return c;
  }

  def getNextCursor(c) {
    for param i in 1..rank {
      c(rank-i+1) = ranges(rank-i+1).getNextCursor(c(rank-i+1));
      if ranges(rank-i+1).isValidCursor?(c(rank-i+1)) then
        return c;
      else
        c(rank-i+1) = ranges(rank-i+1).getHeadCursor();
    }
    c(1) = ranges(1)._high+1;
    return c;
  }

  def getValue(c) {
    if rank == 1 then
      return c(1);
    else
      return c;
  }

  def isValidCursor?(c) {
    return c(1) != ranges(1)._high+1;
  }

  def this(dim : int)
    return ranges(dim);

  def numIndices {
    var sum = 1;
    for param i in 1..rank do
      sum *= ranges(i).length;
    return sum;
    // WANT: return * reduce (this(1..rank).length);
  }

  def _build_array(type elt_type)
    return _aarray(elt_type, rank, dim_type, dom=this);

  def _build_sparse_domain()
    return _sdomain(rank, dim_type, adomain=this);

  def translate(dim : rank*int) {
    var x = _adomain(rank, int);
    for i in 1..rank do
      x.ranges(i) = this(i)._translate(dim(i));
    return x;
  }

  def translate(dim : int ...?numDims) {
    return translate(dim);
  }

  def interior(dim : rank*int) {
    var x = _adomain(rank, int);
    for i in 1..rank do {
      if ((dim(i) > 0) && (this(i)._high+1-dim(i) < this(i)._low) ||
          (dim(i) < 0) && (this(i)._low-1-dim(i) > this(i)._high)) {
        halt("***Error: Argument to 'interior' function out of range in dimension ", i, "***");
      }
      x.ranges(i) = this(i)._interior(dim(i));
    }
    return x;
  }

  def interior(dim : int ...?numDims) {
    return interior(dim);
  }

  def exterior(dim : rank*int) {
    var x = _adomain(rank, int);
    for i in 1..rank do
      x.ranges(i) = this(i)._exterior(dim(i));
    return x;
  }

  def exterior(dim : int ...?numDims) {
    return exterior(dim);
  }

  def expand(dim : rank*int) {
    var x = _adomain(rank, int);
    for i in 1..rank do {
      x.ranges(i) = ranges(i)._expand(dim(i));
      if (x.ranges(i)._low > x.ranges(i)._high) {
        halt("***Error: Degenerate dimension created in dimension ", i, "***");
      }
    }
    return x;
  }

  def expand(dim : int ...?numDims) {
    var x = _adomain(rank, int);
    if (rank == numDims) {
      -- NOTE: would probably like to get rid of this assignment
      -- since domain assignment is/will eventually be nontrivial
      -- in cost;  yet returning expand(dim) currently causes
      -- problems as captured in trivial/shannon/condReturn3.chpl
      x = expand(dim);
    } else if (numDims == 1) {
      for i in 1..rank do
        x.ranges(i) = ranges(i)._expand(dim(1));
    } else {
      halt("***Error: Rank mismatch between domain and expand() arguments (", rank, " != ", numDims, ")***");
    }
    return x;
  }
}

def by(dom : _adomain, dim : dom.rank*int) {
  var x = _adomain(dom.rank, dom.dim_type);
  for i in 1..dom.rank do
    x.ranges(i) = dom.ranges(i) by dim(i);
  return x;
}

def by(dom : _adomain, dim : int) {
  var x = _adomain(dom.rank, dom.dim_type);
  for i in 1..dom.rank do
    x.ranges(i) = dom.ranges(i) by dim;
  return x;
}

class _aarray: _abase {
  type elt_type;
  param rank : int;
  type dim_type;

  var dom : _adomain(rank, dim_type);
  var off: rank*dim_type;
  var blk: rank*dim_type;
  var str: rank*int;
  var orig: rank*dim_type;
  var size : dim_type;
  var data : _ddata(elt_type);
  var noinit: bool = false;

  def getHeadCursor()
    return dom.getHeadCursor();

  def getNextCursor(c)
    return dom.getNextCursor(c);

  def getValue(c)
    return this(dom.getValue(c));

  def isValidCursor?(c)
    return dom.isValidCursor?(c);

  def initialize() {
    if noinit == true then return;
    for param dim in 1..rank {
      off(dim) = dom(dim)._low;
      str(dim) = dom(dim)._stride;
      orig(dim) = 0:dim_type;
    }
    blk(rank) = 1:dim_type;
    for dim in 1..rank-1 by -1 do
      blk(dim) = blk(dim+1) * dom(dim+1).length;
    size = blk(1) * dom(1).length;
    data = _ddata(elt_type, size:int); // ahh!!! can't cast to int here
    data.init();
  }

  def this(d : _adomain) {
    var a : [d] elt_type;
    for i in d do
      a(i) = this(i);
    return a;
  }

  def =this(d : _adomain, v) {
    for i in d do
      this(i) = v;
  }

  def this(ind : rank*dim_type) var {
    if boundsChecking
      for param i in 1..rank do
        if !_in(dom(i), ind(i)) then
          halt("array index out of bounds: ", ind);
    var sum : dim_type;
    for param i in 1..rank do
      sum = sum + (ind(i) - off(i)) * blk(i) / str(i):dim_type - orig(i);
//    write(" [", ind, " = ", sum, "] ");
    return data(sum:int); // !!ahh
  }

  def this(ind : dim_type ...rank) var
    return this(ind);

  def this(ind: _aseq(dim_type) ...rank) var {
    var d = [(...ind)];
    return slice(d._value);
  }

  def view(d: _adomain) {
    if rank != d.rank then
      halt("array rank change not supported");
    for param i in 1..rank do
      if d(i).length != dom(i).length then
        halt("extent in dimension ", i, " does not match actual");
    var alias = _aarray(elt_type, rank, dim_type, d, noinit=true);
    alias.data = data;
    alias.size = size;
    for param i in 1..rank {
      alias.off(i) = d(i)._low;
      alias.blk(i) = blk(i) * (dom(i)._stride / str(i));
      alias.str(i) = d(i)._stride;
      alias.orig(i) = orig(i) + (off(i) - dom(i)._low) * blk(i);
    }
    return _array(alias.type, elt_type, rank, alias, _domain(d.type, d.getValue(d.getHeadCursor()).type, rank, d));
  }

  def slice(d: _adomain) {
    if rank != d.rank then
      halt("array rank change not supported");
    for param i in 1..rank {
      if !_in(dom(i), d(i)) then
        halt("array slice out of bounds in dimension ", i, ": ", d);
      if d(i)._stride % dom(i)._stride != 0 then
        halt("stride of array slice is not multiple of stride in dimension ", i);
    }
    var alias = _aarray(elt_type, rank, dim_type, d, noinit=true);
    alias.data = data;
    alias.size = size;
    for param i in 1..rank {
      alias.off(i) = off(i);
      alias.blk(i) = blk(i);
      alias.str(i) = str(i);
      alias.orig(i) = orig(i);
    }
    return _array(alias.type, elt_type, rank, alias, _domain(d.type, d.getValue(d.getHeadCursor()).type, rank, d));
  }

  def reallocate(d: _domain) {
    if (d.rank == rank) {
      var new = _aarray(elt_type, rank, dim_type, d._value);
      for i in _intersect(d._value, dom) do
        new(i) = this(i);
      dom = new.dom;
      off = new.off;
      blk = new.blk;
      str = new.str;
      orig = new.orig;
      size = new.size;
      data = new.data;
    }
  }

  def assign(y : _aarray) {
    for i,e in dom,y do
      this(i) = e;
  }

  def assign(y : _adomain) {
    for i,e in dom,y do
      this(i) = e;
  }

  def assign(y : seq) {
    for i,e in dom,y do
      this(i) = e;
  }

  def assign(y: _aseq) {
    for i,e in dom,y do
      this(i) = e;
  }

  def assign(y: elt_type) {
    for i in dom do
      this(i) = y;
  }
}

def fwrite(f : file, x : _adomain) {
  fwrite(f, "[", x(1));
  for i in 2..x.rank do
    fwrite(f, ", ", x(i));
  fwrite(f, "]");
}

def fwrite(f : file, x : _aarray) {
  var i : x.rank*x.dim_type;
  for dim in 1..x.rank do
    i(dim) = x.dom(dim)._low;
  label next while true {
    fwrite(f, x(i));
    if i(x.rank) <= (x.dom(x.rank)._high - x.dom(x.rank)._stride:x.dim_type) {
      fwrite(f, " ");
      i(x.rank) += x.dom(x.rank)._stride:x.dim_type;
    } else {
      for dim in 1..x.rank-1 by -1 {
        if i(dim) <= (x.dom(dim)._high - x.dom(dim)._stride:x.dim_type) {
          i(dim) += x.dom(dim)._stride:x.dim_type;
          for dim2 in dim+1..x.rank {
            fwrite(f, "\n");
            i(dim2) = x.dom(dim2)._low;
          }
          continue next;
        }
      }
      break;
    }
  }
}

def _intersect(a: _aseq, b: _aseq) {
  var g, x: int;
  (g, x) = _extended_euclid(a._stride, b._stride);
  if abs(a._low - b._low) % g != 0 then
    return 1..0:a.elt_type;
  var low = max(a._low, b._low);
  var high = min(a._high, b._high);
  var stride = a._stride * b._stride / g;
  var alignment = (a._low + (b._low - a._low) * x * a._stride / g):a.elt_type;
  if alignment == 0 then
    alignment = stride:a.elt_type;
  low = low + low % alignment;
  return low..high by stride;
}

def _intersect(a: _adomain, b: _adomain) {
  var c = _adomain(a.rank, a.dim_type);
  for param i in 1..a.rank do
    c.ranges(i) = _intersect(a(i), b(i));
  return c;
}

// Extended-Euclid (Knuth Volume 2 --- Section 4.5.2)
// given two non-negative integers u and v
// returns (gcd(u, v), x) where x is set such that u*x + v*y = gcd(u, v)
def _extended_euclid(u: int, v: int) {
  var u1 = 1;
  var u2 = 0;
  var u3 = u;
  var v1 = 0;
  var v2 = 1;
  var v3 = v;
  while v3 != 0 {
    var q = u3 / v3;
    var t1 = u1 - v1 * q;
    var t2 = u2 - v2 * q;
    var t3 = u3 - v3 * q;
    u1 = v1;
    u2 = v2;
    u3 = v3;
    v1 = t1;
    v2 = t2;
    v3 = t3;
  }
  return (u3, u1);
}

-- Arithmetic sequence

def _build_aseq(low: int, high: int) return _aseq(int, low, high);
def _build_aseq(low: uint, high: uint) return _aseq(uint, low, high);
def _build_aseq(low: int(64), high: int(64)) return _aseq(int(64), low, high);
def _build_aseq(low: uint(64), high: uint(64)) return _aseq(uint(64), low, high);

def _build_aseq(low, high) {
  compilerError("arithmetic sequence bounds are not of integral type");
}

record _aseq {
  type elt_type;
  var _low : elt_type;
  var _high : elt_type;
  var _stride : int = 1;

  def initialize() {
    if _low > _high {
      _low = 1;
      _high = 0;
      _stride = 1;
    }
  }

  iterator this() : elt_type {
    forall x in this
      yield x;
  }

  def getHeadCursor()
    if _stride > 0 then
      return _low;
    else
      return _high;

  def getNextCursor(c)
    return c + _stride:elt_type;

  def getValue(c)
    return c;

  def isValidCursor?(c)
    return _low <= c && c <= _high;

  def length
    return
      (if _stride > 0
        then (_high - _low + _stride:elt_type) / _stride:elt_type
        else (_low - _high + _stride:elt_type) / _stride:elt_type);
}

def by(s : _aseq, i : int) {
  if i == 0 then
    halt("illegal stride of 0");
  if s._low == 1 && s._high == 0 then
    return _aseq(s.elt_type, s._low, s._high, s._stride);
  var as = _aseq(s.elt_type, s._low, s._high, s._stride * i);
  if as._stride < 0 then
    as._low = as._low + (as._high - as._low) % (-as._stride):as.elt_type;
  else
    as._high = as._high - (as._high - as._low) % (as._stride):as.elt_type;
  return as;
}

def _in(s : _aseq, i : s.elt_type)
  return i >= s._low && i <= s._high &&
    (i - s._low) % abs(s._stride):s.elt_type == 0;

// really slow --- REWRITE
def _in(s1: _aseq, s2: _aseq) {
  for i in s2 do
    if !_in(s1, i) then
      return false;
  return true;
}

def fwrite(f : file, s : _aseq) {
  fwrite(f, s._low, "..", s._high);
  if (s._stride != 1) then
    fwrite(f, " by ", s._stride);
}

pragma "inline" def string.substring(s: _aseq)
  if s._stride != 1 then
    return __primitive("string_strided_select", this, s._low, s._high, s._stride);
  else
    return __primitive("string_select", this, s._low, s._high);