dumpEntry use iter

dumpEntry use iter

// BUG: declaring use IO, Memory here causes chpl compiler segv

module Search {

  use Logging, Memory, LockFreeHash, GenHashKey32, Partitions;

  config const sync_writers = false;
  config const entry_size: uint(32) = 1024*1024;
  config const max_doc_node_size: uint(32) =  32 * 1024;

  // TODO: should be using a doc index that maps to a doc id?
  type DocId = uint(64);

  /**
    Document Ids are contained in a linked list, where each node is double the length of the previous node up to max_doc_node_size.
    The linked list HEAD contains the most recently indexed items with the older document ids further down in the list.
    In each node, document ids are added from right to left in the array.
  */
  class DocumentIdNode {
    // controls the size of this document list
    var listSize: int = 1;

    var next: DocumentIdNode;

    // list of documents
    var documents: [0..listSize-1] DocId;

    // number of documents in this node's list
    var documentCount: atomic int;

    // Gets the document id index to use to add a new document id.  documentCount should be incremented after using this index.
    proc documentIdIndex() {
      return documents.size - documentCount.read() - 1;
    }

    proc nextDocumentIdNodeSize() {
      if (documents.size >= max_doc_node_size) {
        return documents.size;
      } else {
        return documents.size * 2;
      }
    }
  }

  // for scoring and compactness purposes, consider making this use more contiguous memory,
  //  or an associative array(s) that use the score or docid as index.
  class Entry {
    var word: string;

    var score: real; // TODO: may not be needed if we use docCount as frequency

    // total number of documents in all the documentId nodes
    var documentCount: atomic int;

    var documentIdNode: DocumentIdNode;
  }

  class PartitionIndex {
    var entryCount: atomic uint(32);
    var entryIndex = new LockFreeHash(entry_size);
    var entries: [1..entry_size] Entry;
    var writerLock: atomicflag;

    inline proc lockIndexWriter() {
      // debug("attempting to get lock");
      if (sync_writers) then while writerLock.testAndSet() do chpl_task_yield();
      // debug("have lock");
    }

    inline proc unlockIndexWriter() {
      // writeln("releasing lock");
      if (sync_writers) then writerLock.clear();
      // writeln("released lock");
    }

    proc PartitionIndex() {
      entryCount.add(1); // start at the first index of entries
    }
  }

  var Indices: [0..Partitions.size-1] PartitionIndex;

  proc initIndices() {
    for i in 0..Partitions.size-1 {
      on Partitions[i] {
        info("index [", i, "] is mapped to partition ", i);

        // allocate the partition index on the partition locale
        Indices[i] = new PartitionIndex();
      }
    }
  }

  proc entryForWord(word: string): Entry {
    var partition = partitionForWord(word);
    var partitionIndex = Indices[partition];
    var entry: Entry;
    on partitionIndex {
      entry = entryForWordOnPartition(word, partitionIndex);
    }
    return entry;
  }

  proc entryForWordOnPartition(word: string, partitionIndex: PartitionIndex): Entry {
    var entryIndex = entryIndexForWord(word, partitionIndex);
    if (entryIndex > 0) {
      return partitionIndex.entries[entryIndex];
    }
    return nil;
  }

  proc indexWord(word: string, docid: DocId) {
    var partition = partitionForWord(word);
    var partitionIndex = Indices[partition];
    on partitionIndex {
      partitionIndex.lockIndexWriter();

      var entry = entryForWordOnPartition(word, partitionIndex);
      if (entry != nil) {
        debug("adding ", word, " to existing entries on partition ", partition);
        var docNode = entry.documentIdNode;
        var docCount = docNode.documentCount.read();
        if (docCount < docNode.documents.size) {
          docNode.documents[docNode.documentIdIndex()] = docid;
          docNode.documentCount.add(1);
        } else {
          docNode = new DocumentIdNode(docNode.nextDocumentIdNodeSize(), docNode);
          debug("adding new document id node of size ", docNode.documents.size);
          docNode.documents[docNode.documentIdIndex()] = docid;
          docNode.documentCount.write(1);
          entry.documentIdNode = docNode;
        }
        entry.documentCount.add(1);
      } else {
        debug("adding new entry ", word , " on partition ", partition);

        var entriesCount = partitionIndex.entryCount.read();
        if (entriesCount < partitionIndex.entries.size) {
          entry = new Entry();
          entry.word = word;
          entry.score = 0;

          var docNode = new DocumentIdNode();
          docNode.documents[docNode.documentIdIndex()] = docid;
          docNode.documentCount.write(1);
          entry.documentIdNode = docNode;
          entry.documentCount.write(1);

          var entryIndex: uint(32) = partitionIndex.entryCount.fetchAdd(1);
          partitionIndex.entries[entryIndex] = entry;
          var success = partitionIndex.entryIndex.setItem(word, entryIndex);
          if (!success) {
            error("indexWord: failed to index ", word);
            exit(0);
            // how do we accumuate per-partition indexing errors for a final response?
          }
        }
      }

      partitionIndex.unlockIndexWriter();
    }
  }

  proc indexContainsWord(word: string, partitionIndex: PartitionIndex): bool {
    return entryIndexForWord(word, partitionIndex) != 0;
  }

  proc entryIndexForWord(word: string, partitionIndex: PartitionIndex): uint(32) {
    return partitionIndex.entryIndex.getItem(word);
  }

  iter documentIdsForWord(word: string) {
    var entry = entryForWord(word);
    if (entry != nil) {
      on entry {
        var node = entry.documentIdNode;
        while (node != nil) {
          var startIdx = node.documents.size - node.documentCount.read();
          for i in startIdx..node.documents.size-1 {
            var docId = node.documents[i];
            if (docId > 0) {
              yield docId;
            }
          }
          node = node.next;
        }
      }
    }
  }

  proc dumpEntry(entry: Entry) {
    on entry {
      info("word: ", entry.word, " score: ", entry.score);
      var count = 0;
      for docId in documentIdsForWord(entry.word) {
        writeln("\t", docId);
        count += 1;
      }
      if (count != entry.documentCount.read()) {
        error("ERROR: documentCount != count", count, entry.documentCount.read());
      }
    }
  }

  proc dumpPartition(partition: int) {
    var partitionIndex = Indices[partition];
    on partitionIndex {
      info("entries on partition (", partition, ") locale (", here.id, ") ", partitionIndex.entries);

      var word: string;
      for i in 0..partitionIndex.entryCount.read()-1 {
        var entry = partitionIndex.entries[i];
        info("word: ", entry.word);
        dumpPostingTableForWord(entry.word);
      }
    }
  }

  proc dumpPostingTableForWord(word: string) {
    var entry = entryForWord(word);
    if (entry != nil) {
      dumpEntry(entry);
    } else {
      error("word (", word, ") is not in the index");
    }
  }
}

// BUG: declaring use IO, Memory here causes chpl compiler segv

module Search {

  use Logging, Memory, LockFreeHash, GenHashKey32, Partitions;

  config const sync_writers = false;
  config const entry_size: uint(32) = 1024*1024;
  config const max_doc_node_size: uint(32) =  32 * 1024;

  // TODO: should be using a doc index that maps to a doc id?
  type DocId = uint(64);

  /**
    Document Ids are contained in a linked list, where each node is double the length of the previous node up to max_doc_node_size.
    The linked list HEAD contains the most recently indexed items with the older document ids further down in the list.
    In each node, document ids are added from right to left in the array.
  */
  class DocumentIdNode {
    // controls the size of this document list
    var listSize: int = 1;

    var next: DocumentIdNode;

    // list of documents
    var documents: [0..listSize-1] DocId;

    // number of documents in this node's list
    var documentCount: atomic int;

    // Gets the document id index to use to add a new document id.  documentCount should be incremented after using this index.
    proc documentIdIndex() {
      return documents.size - documentCount.read() - 1;
    }

    proc nextDocumentIdNodeSize() {
      if (documents.size >= max_doc_node_size) {
        return documents.size;
      } else {
        return documents.size * 2;
      }
    }
  }

  // for scoring and compactness purposes, consider making this use more contiguous memory,
  //  or an associative array(s) that use the score or docid as index.
  class Entry {
    var word: string;

    var score: real; // TODO: may not be needed if we use docCount as frequency

    // total number of documents in all the documentId nodes
    var documentCount: atomic int;

    var documentIdNode: DocumentIdNode;
  }

  class PartitionIndex {
    var entryCount: atomic uint(32);
    var entryIndex = new LockFreeHash(entry_size);
    var entries: [1..entry_size] Entry;
    var writerLock: atomicflag;

    inline proc lockIndexWriter() {
      // debug("attempting to get lock");
      if (sync_writers) then while writerLock.testAndSet() do chpl_task_yield();
      // debug("have lock");
    }

    inline proc unlockIndexWriter() {
      // writeln("releasing lock");
      if (sync_writers) then writerLock.clear();
      // writeln("released lock");
    }

    proc PartitionIndex() {
      entryCount.add(1); // start at the first index of entries
    }
  }

  var Indices: [0..Partitions.size-1] PartitionIndex;

  proc initIndices() {
    for i in 0..Partitions.size-1 {
      on Partitions[i] {
        info("index [", i, "] is mapped to partition ", i);

        // allocate the partition index on the partition locale
        Indices[i] = new PartitionIndex();
      }
    }
  }

  proc entryForWord(word: string): Entry {
    var partition = partitionForWord(word);
    var partitionIndex = Indices[partition];
    var entry: Entry;
    on partitionIndex {
      entry = entryForWordOnPartition(word, partitionIndex);
    }
    return entry;
  }

  proc entryForWordOnPartition(word: string, partitionIndex: PartitionIndex): Entry {
    var entryIndex = entryIndexForWord(word, partitionIndex);
    if (entryIndex > 0) {
      return partitionIndex.entries[entryIndex];
    }
    return nil;
  }

  proc indexWord(word: string, docid: DocId) {
    var partition = partitionForWord(word);
    var partitionIndex = Indices[partition];
    on partitionIndex {
      partitionIndex.lockIndexWriter();

      var entry = entryForWordOnPartition(word, partitionIndex);
      if (entry != nil) {
        debug("adding ", word, " to existing entries on partition ", partition);
        var docNode = entry.documentIdNode;
        var docCount = docNode.documentCount.read();
        if (docCount < docNode.documents.size) {
          docNode.documents[docNode.documentIdIndex()] = docid;
          docNode.documentCount.add(1);
        } else {
          docNode = new DocumentIdNode(docNode.nextDocumentIdNodeSize(), docNode);
          debug("adding new document id node of size ", docNode.documents.size);
          docNode.documents[docNode.documentIdIndex()] = docid;
          docNode.documentCount.write(1);
          entry.documentIdNode = docNode;
        }
        entry.documentCount.add(1);
      } else {
        debug("adding new entry ", word , " on partition ", partition);

        var entriesCount = partitionIndex.entryCount.read();
        if (entriesCount < partitionIndex.entries.size) {
          entry = new Entry();
          entry.word = word;
          entry.score = 0;

          var docNode = new DocumentIdNode();
          docNode.documents[docNode.documentIdIndex()] = docid;
          docNode.documentCount.write(1);
          entry.documentIdNode = docNode;
          entry.documentCount.write(1);

          var entryIndex: uint(32) = partitionIndex.entryCount.fetchAdd(1);
          partitionIndex.entries[entryIndex] = entry;
          var success = partitionIndex.entryIndex.setItem(word, entryIndex);
          if (!success) {
            error("indexWord: failed to index ", word);
            exit(0);
            // how do we accumuate per-partition indexing errors for a final response?
          }
        }
      }

      partitionIndex.unlockIndexWriter();
    }
  }

  proc indexContainsWord(word: string, partitionIndex: PartitionIndex): bool {
    return entryIndexForWord(word, partitionIndex) != 0;
  }

  proc entryIndexForWord(word: string, partitionIndex: PartitionIndex): uint(32) {
    return partitionIndex.entryIndex.getItem(word);
  }

  iter documentIdsForWord(word: string) {
    var entry = entryForWord(word);
    if (entry != nil) {
      on entry {
        var node = entry.documentIdNode;
        while (node != nil) {
          var startIdx = node.documents.size - node.documentCount.read();
          for i in startIdx..node.documents.size-1 {
            var docId = node.documents[i];
            if (docId > 0) {
              yield docId;
            }
          }
          node = node.next;
        }
      }
    }
  }

  proc dumpEntry(entry: Entry) {
    on entry {
      info("word: ", entry.word, " score: ", entry.score);
      var node = entry.documentIdNode;
      var count = 0;
      while (node != nil) {
        var startIdx = node.documents.size - node.documentCount.read();
        for i in startIdx..node.documents.size-1 {
          var docId = node.documents[i];
          if (docId > 0) {
            writeln("\t", docId);
            count += 1;
          }
        }
        node = node.next;
      }
      if (count != entry.documentCount.read()) {
        error("ERROR: documentCount != count", count, entry.documentCount.read());
      }
    }
  }

  proc dumpPartition(partition: int) {
    var partitionIndex = Indices[partition];
    on partitionIndex {
      info("entries on partition (", partition, ") locale (", here.id, ") ", partitionIndex.entries);

      var word: string;
      for i in 0..partitionIndex.entryCount.read()-1 {
        var entry = partitionIndex.entries[i];
        info("word: ", entry.word);
        dumpPostingTableForWord(entry.word);
      }
    }
  }

  proc dumpPostingTableForWord(word: string) {
    var entry = entryForWord(word);
    if (entry != nil) {
      dumpEntry(entry);
    } else {
      error("word (", word, ") is not in the index");
    }
  }
}


Switching versions of spectralnorm to promote to the release, since
Brad's has better comments and order for arguments.

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

/* The Computer Language Benchmarks Game
   http://benchmarksgame.alioth.debian.org/

   Contributed by Lydia Duncan & Albert Sidelnik, based on Sebastien
   Loisel's C version
*/

config const n = 500;

// Note:
// Matrix A is never actually stored, its contents are computed on the fly
// through calls to the method A.  This is part of the benchmark's
// required implementation.  For ease of reference, it will be referred to as
// simply "A" when used.  Similarly, At is A transposed.

proc main() {
  var tmp, u, v: [0..#n] real;  // Declare three vectors

  u = 1.0;                      // Initialize u to 1.0

  for 1..10 {                   // For 10 iterations...
    multiplyAtAv(u, tmp, v);    // ...compute v = A*u*At
    multiplyAtAv(v, tmp, u);    // ...and     u = A*v*At
  }

  writef("%.9dr\n", sqrt(+ reduce (u*v) / + reduce (v*v)));
}

//
// Multiply vector v by matrix A and then by matrix A transposed
//
proc multiplyAtAv(v, tmp, AtAv) {
  multiplyAv(v, tmp);
  multiplyAtv(tmp, AtAv);
}

//
// Multiply vector v by matrix A
//
proc multiplyAv(v: [?Dv], Av: [?DAv]) {
  forall i in DAv do
    // The use of a serial statement here and in multiplyAtv is to avoid
    // creating additional tasks for the reduction.  Traversing the outer
    // loop in parallel is more efficient than dividing the smaller
    // computations up between parallel tasks.
    serial do
      Av[i] = + reduce [j in Dv] (A[i,j] * v[j]);
}

//
// Multiply vector V by matrix A transposed
//
proc multiplyAtv(v: [?Dv], Atv: [?DAtv]) {
  forall i in DAtv do
    serial do
      Atv[i] = + reduce [j in Dv] (A[j,i] * v[j]);
}

//
// return element i,j of infinite matrix A
//
inline proc A(i, j) {
  return 1.0 / ((((i + j) * (i + j + 1)) / 2) + i + 1);
}

/* The Computer Language Benchmarks Game
   http://shootout.alioth.debian.org/

   Original C contributed by Sebastien Loisel
   Conversion to Chapel by Albert Sidelnik
   Updated by Lydia Duncan
*/

config const n = 500;

// Return: 1.0 / (i + j) * (i + j +1) / 2 + i + 1;
inline proc eval_A(i, j) : real {
  //
  // 1.0 / (i + j) * (i + j +1) / 2 + i + 1;
  // n * (n+1) is an even number. Therefore, just (>> 1) for (/2)
  //
  const d = (((i + j) * (i + j + 1)) >> 1) + i + 1;
  return 1.0 / d;
}

//
// Traverses the array serially, performing the computation (eval_A)*U(k) on
// each pair i, k where k is a number from 0 to inRange - 1.  This method
// is generalized so that it can perform both versions of the call.
//
proc sum (i, param flip : bool, U : [] real) {
  var result = 0.0;
  for k in U.domain do
    result += U(k) * ( if flip then eval_A(k, i) else eval_A(i, k) );
  return result;
}

//
// Fills the array Au with the result of calling sum with each index i from 0
// to inRange - 1, inRange, a boolean indicating not to flip the order for the
// inner call and the array U
//
proc eval_A_times_u(U : [] real, Au : [] real) {
  forall i in Au.domain do
    Au(i) = sum(i, false, U);
}

//
// Fills the array Au with the result of calling sum with each index i from 0
// to inRange - 1, inRange, a boolean indicating to flip the order for the
// inner call and the array U
//
proc eval_At_times_u(U : [] real, Au : [] real) {
  forall i in Au.domain do
    Au(i) = sum(i, true, U);
}

proc eval_AtA_times_u(u, AtAu, v : [] real) {
     eval_A_times_u(u, v);
     eval_At_times_u(v, AtAu);
}

proc spectral_game(N) : real {
  var tmp, U, V : [0..#N] real;

  U = 1.0;

  for 1..10 {
    eval_AtA_times_u(U,V,tmp);
    eval_AtA_times_u(V,U,tmp);
  }

  const vv = + reduce [v in V] (v * v);
  const vBv = + reduce [(u,v) in zip(U,V)] (u * v);

  return sqrt(vBv/vv);
}

proc main() {
  writeln(spectral_game(n), new iostyle(precision=10));
}


Fixing a dropped # pointed out by Michael

//
// This test is pulled from intern ii02 in the codes written in the
// Summer of 2011 to measure increase in productivity using Chapel
// versus C/MPI.It is a pared down version of the Locally Self-consistent
// Multiple Scattering (LSMS) application.
//

use Time;

config const debug = false;
config const reportFrequency = 1025;
config const perfTest = false;
config const displayLIZ = false;

config const span = 5;

const span_x = span,
	  span_y = span,
	  span_z = span,
	  nAtoms = span_x * span_y * span_z,
	  spanT  = (span, span, span);

config
const atomSpacing = 5.000, //spacing in X, Y, and Z
	  lizRadius   = 7.110, //radius of LIZ sphere
	  nIterations = 1024;

const reductionFactor = 1000.0;

param nExtent = 13;
type AtomMatrix = nExtent*real;
//type AtomMatrix = [0..#nExtent] real;

const GridDom  = {0..#span_x, 0..#span_y, 0..#span_z};

proc main() {
	var t: Timer;

	writeln("[[ LSMS ]]");
	writeln("Problem size = [", span_x, ", ", span_y, ", ", span_z, "]");

	//calculate Local Interaction Zones
	var lizDoms: [GridDom] sparse subdomain(GridDom);
	for (a, liz) in zip(GridDom, lizDoms) {
		for ac in GridDom do if a != ac {
			//compute dist between 2 points
			var diff: 3*int;
			for param d in 1..3 do diff[d] = circularDistance(a[d], ac[d], spanT[d]);
			var dist = atomSpacing * sqrt(diff[1]**2 + diff[2]**2 + diff[3]**2);
			if dist <= lizRadius {
				liz += ac;
			}
		}
	}
	if displayLIZ then writeln("LIZ counts: ", [liz in lizDoms] liz.numIndices);

	//initialize atoms
	var atoms: [GridDom] AtomMatrix;
	for (i, atom) in zip(GridDom, atoms) {
		for param e in 1..nExtent do atom[e] = GridDom.indexOrder(i);
	}

	t.start();

	for itr in 0..#nIterations {
		if itr % reportFrequency == 0 && reportFrequency <= nIterations {
			writef("step ####...\n", itr);
		}

		//each atom adds up extent values from neighbors (members of its liz sphere)
		var totals: [GridDom] AtomMatrix;
		for (liz, total, i) in zip(lizDoms, totals, GridDom) {
			for ac in liz {
				total += atoms[ac];
			}
		}
		for a in GridDom {
			for param e in 1..nExtent {
				atoms[a][e] += totals[a][e] / reductionFactor;
			}
		}

		if itr == 0 || itr == nIterations-1 {
			checkExpected(itr, atoms);
		}
	}

	t.stop();

	writeln("Success!");

	if perfTest then writeln("Chapel serial time = ", t.elapsed(), " s");
}

proc checkExpected(itr: int, atoms: [GridDom] AtomMatrix) {
	if span != 5 || nIterations != 1024 {
		writeln("Expected value check disabled...");
		return;
	}
	const first = GridDom.low,
		  last  = GridDom.high;

	var ExpectedDom: sparse subdomain(GridDom);
	ExpectedDom += first; ExpectedDom += last;

	var expectedValues: [ExpectedDom] real;
	if itr == 0 {
		expectedValues[first] = 0.775;
		expectedValues[last]  = 125.5;
	} else {
		expectedValues[first] = 5320018409.9;
		expectedValues[last]  = 5324998555.3;
	}

	for i in ExpectedDom {
		var tolerance = max(expectedValues[i] * 1e-9, 0.1);
		var pass = true;
		for param e in 1..nExtent {
			if abs(atoms[i][e] - expectedValues[i]) > tolerance then pass = false;
		}
                if debug { writef("itr %4n @ %t: %12.9r ~=~ %12.9r\n",
                                  itr, i, atoms[i][1], expectedValues[i]); }
		if !pass {
			writeln("Computed values do not match expected values: itr = ", itr,
					" @ ", i, ": ", atoms[i][1], " != ", expectedValues[i]);
			exit(0);
		}
	}
}

proc circularDistance(a, b, size: int) {
	if a < b {
		return min(a+size-b, b-a);
	} else {
		return min(a-b, b-a+size);
	}
}

//
// This test is pulled from intern ii02 in the codes written in the
// Summer of 2011 to measure increase in productivity using Chapel
// versus C/MPI.It is a pared down version of the Locally Self-consistent
// Multiple Scattering (LSMS) application.
//

use Time;

config const debug = false;
config const reportFrequency = 1025;
config const perfTest = false;
config const displayLIZ = false;

config const span = 5;

const span_x = span,
	  span_y = span,
	  span_z = span,
	  nAtoms = span_x * span_y * span_z,
	  spanT  = (span, span, span);

config
const atomSpacing = 5.000, //spacing in X, Y, and Z
	  lizRadius   = 7.110, //radius of LIZ sphere
	  nIterations = 1024;

const reductionFactor = 1000.0;

param nExtent = 13;
type AtomMatrix = nExtent*real;
//type AtomMatrix = [0..#nExtent] real;

const GridDom  = {0..#span_x, 0..#span_y, 0..#span_z};

proc main() {
	var t: Timer;

	writeln("[[ LSMS ]]");
	writeln("Problem size = [", span_x, ", ", span_y, ", ", span_z, "]");

	//calculate Local Interaction Zones
	var lizDoms: [GridDom] sparse subdomain(GridDom);
	for (a, liz) in zip(GridDom, lizDoms) {
		for ac in GridDom do if a != ac {
			//compute dist between 2 points
			var diff: 3*int;
			for param d in 1..3 do diff[d] = circularDistance(a[d], ac[d], spanT[d]);
			var dist = atomSpacing * sqrt(diff[1]**2 + diff[2]**2 + diff[3]**2);
			if dist <= lizRadius {
				liz += ac;
			}
		}
	}
	if displayLIZ then writeln("LIZ counts: ", [liz in lizDoms] liz.numIndices);

	//initialize atoms
	var atoms: [GridDom] AtomMatrix;
	for (i, atom) in zip(GridDom, atoms) {
		for param e in 1..nExtent do atom[e] = GridDom.indexOrder(i);
	}

	t.start();

	for itr in 0..#nIterations {
		if itr % reportFrequency == 0 && reportFrequency <= nIterations {
			writef("step  ###...\n", itr);
		}

		//each atom adds up extent values from neighbors (members of its liz sphere)
		var totals: [GridDom] AtomMatrix;
		for (liz, total, i) in zip(lizDoms, totals, GridDom) {
			for ac in liz {
				total += atoms[ac];
			}
		}
		for a in GridDom {
			for param e in 1..nExtent {
				atoms[a][e] += totals[a][e] / reductionFactor;
			}
		}

		if itr == 0 || itr == nIterations-1 {
			checkExpected(itr, atoms);
		}
	}

	t.stop();

	writeln("Success!");

	if perfTest then writeln("Chapel serial time = ", t.elapsed(), " s");
}

proc checkExpected(itr: int, atoms: [GridDom] AtomMatrix) {
	if span != 5 || nIterations != 1024 {
		writeln("Expected value check disabled...");
		return;
	}
	const first = GridDom.low,
		  last  = GridDom.high;

	var ExpectedDom: sparse subdomain(GridDom);
	ExpectedDom += first; ExpectedDom += last;

	var expectedValues: [ExpectedDom] real;
	if itr == 0 {
		expectedValues[first] = 0.775;
		expectedValues[last]  = 125.5;
	} else {
		expectedValues[first] = 5320018409.9;
		expectedValues[last]  = 5324998555.3;
	}

	for i in ExpectedDom {
		var tolerance = max(expectedValues[i] * 1e-9, 0.1);
		var pass = true;
		for param e in 1..nExtent {
			if abs(atoms[i][e] - expectedValues[i]) > tolerance then pass = false;
		}
                if debug { writef("itr %4n @ %t: %12.9r ~=~ %12.9r\n",
                                  itr, i, atoms[i][1], expectedValues[i]); }
		if !pass {
			writeln("Computed values do not match expected values: itr = ", itr,
					" @ ", i, ": ", atoms[i][1], " != ", expectedValues[i]);
			exit(0);
		}
	}
}

proc circularDistance(a, b, size: int) {
	if a < b {
		return min(a+size-b, b-a);
	} else {
		return min(a-b, b-a+size);
	}
}


Assert (implicitly) that chpl_taskID_t is a 64-bit integral type.

[Reviewed by Brad.]

This restricts us to only casting chpl_taskID_t values to/from 64-bit
ints and uints.  The former matches the usage in DefaultRectangular, and
the latter matches that in ChapelIO.  This gets rid of one use of
_isPrimitiveType(), helping us toward removing it altogether.

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

// ChapelTaskTable.chpl
//
pragma "no use ChapelStandard"
module ChapelTaskTable {

  use ChapelBase;
  use ChapelIO;
  use ChapelArray;
  use DefaultRectangular;
  use DefaultAssociative;

  /*
     This code keeps track of tasks, their state, and where they
     spawned from.

     The task information is used to generate a task report (optionally
     displayed when the user Ctrl+C's out of the program).

     The type chpl_taskID_t is a primitive type defined in the include
     files for each tasking layer.

  */

  // Define a few operations over chpl_taskID_t
  inline proc =(ref a:chpl_taskID_t, b:chpl_taskID_t) {
    __primitive("=", a, b);
  }
  inline proc ==(a: chpl_taskID_t, b: chpl_taskID_t)
    return __primitive("==", a, b);
  inline proc _cast(type t, x: chpl_taskID_t) where t == int(64)
                                                    || t == uint(64)
    return __primitive("cast", t, x);

  enum taskState { pending, active, suspended };

  //
  // This represents a currently running task.  The state, lineno, and
  // filename members should be obvious; tl_info is information belonging
  // to the tasking layer.
  //
  // If we included the parent in this, the print method could topologically
  // sort the tasks and indent to show the parent/child relationships, which
  // might be very nice.
  //
  record chpldev_Task {
    var state     : taskState;
    var lineno    : uint(32);
    var filename  : c_string;
    var tl_info   : uint(64);
  }

  class chpldev_taskTable_t {
    var dom : domain(chpl_taskID_t, parSafe=false);
    var map : [dom] chpldev_Task;
  }

  pragma "private"
  var chpldev_taskTable : chpldev_taskTable_t;

  //----------------------------------------------------------------------{
  //- Code to initialize the task table on each locale.
  //-
  proc chpldev_taskTable_init() {
    // Doing this in parallel should be safe as long as this module is
    // initialized late (after DefaultRectangular and most other
    // internal modules are already initialized)
    coforall loc in Locales ref(chpldev_taskTable) do on loc {
      chpldev_taskTable = new chpldev_taskTable_t();
    }
  }

  extern var taskreport: int(32);
  if taskreport then chpldev_taskTable_init();

  //-
  //----------------------------------------------------------------------}

  ////////////////////////////////////////////////////////////////////////{
  //
  // Exported task table code.
  //
  // In general, tasks may have been created before the task table was
  // initialized.
  // In that case, operations may be attempted on the task table using
  // taskIDs that are unknown to it.  That is why we check
  // for membership on all operations.
  //
  ////////////////////////////////////////////////////////////////////////}

  export proc chpldev_taskTable_add(taskID   : chpl_taskID_t,
                                    lineno   : uint(32),
                                    filename : c_string,
                                    tl_info  : uint(64))
  {
    if (chpldev_taskTable == nil) then return;

    if (!chpldev_taskTable.dom.member(taskID)) then
      // This must be serial, because if add() results in parallelism
      // (due to _resize), it may lead to deadlock (due to reentry
      // into the runtime tasking layer for task table operations).
      serial true do
        chpldev_taskTable.dom.add(taskID);

    chpldev_taskTable.map[taskID] = new chpldev_Task(taskState.pending,
                                                 lineno, filename, tl_info);
  }

  export proc chpldev_taskTable_remove(taskID : chpl_taskID_t)
  {
    if (chpldev_taskTable == nil ||
        !chpldev_taskTable.dom.member(taskID)) then return;

    // This must be serial, because if remove() results in parallelism
    // (due to _resize), it may lead to deadlock (due to reentry into
    // the runtime tasking layer for task table operations).
    serial true do
      chpldev_taskTable.dom.remove(taskID);
  }

  export proc chpldev_taskTable_set_active(taskID : chpl_taskID_t)
  {
    if (chpldev_taskTable == nil ||
        !chpldev_taskTable.dom.member(taskID)) then return;

    chpldev_taskTable.map[taskID].state = taskState.active;
  }

  export proc chpldev_taskTable_set_suspended(taskID : chpl_taskID_t)
  {
    if (chpldev_taskTable == nil ||
        !chpldev_taskTable.dom.member(taskID)) then return;

    chpldev_taskTable.map[taskID].state = taskState.suspended;
  }

  export proc chpldev_taskTable_get_tl_info(taskID : chpl_taskID_t)
  {
    if (chpldev_taskTable == nil ||
        !chpldev_taskTable.dom.member(taskID)) then return 0:uint(64);

    return chpldev_taskTable.map[taskID].tl_info;
  }

  export proc chpldev_taskTable_print()
  {
    if (chpldev_taskTable == nil) then return;

    for taskID in chpldev_taskTable.dom {
      stderr.writeln("- ", chpldev_taskTable.map[taskID].filename,
                     ":",  chpldev_taskTable.map[taskID].lineno,
                     " is ", chpldev_taskTable.map[taskID].state);
    }
  }

}

// ChapelTaskTable.chpl
//
pragma "no use ChapelStandard"
module ChapelTaskTable {

  use ChapelBase;
  use ChapelIO;
  use ChapelArray;
  use DefaultRectangular;
  use DefaultAssociative;

  /*
     This code keeps track of tasks, their state, and where they
     spawned from.

     The task information is used to generate a task report (optionally
     displayed when the user Ctrl+C's out of the program).

     The type chpl_taskID_t is a primitive type defined in the include
     files for each tasking layer.

  */

  // Define a few operations over chpl_taskID_t
  inline proc =(ref a:chpl_taskID_t, b:chpl_taskID_t) {
    __primitive("=", a, b);
  }
  inline proc ==(a: chpl_taskID_t, b: chpl_taskID_t)
    return __primitive("==", a, b);
  inline proc _cast(type t, x: chpl_taskID_t) where _isPrimitiveType(t)
    return __primitive("cast", t, x);

  enum taskState { pending, active, suspended };

  //
  // This represents a currently running task.  The state, lineno, and
  // filename members should be obvious; tl_info is information belonging
  // to the tasking layer.
  //
  // If we included the parent in this, the print method could topologically
  // sort the tasks and indent to show the parent/child relationships, which
  // might be very nice.
  //
  record chpldev_Task {
    var state     : taskState;
    var lineno    : uint(32);
    var filename  : c_string;
    var tl_info   : uint(64);
  }

  class chpldev_taskTable_t {
    var dom : domain(chpl_taskID_t, parSafe=false);
    var map : [dom] chpldev_Task;
  }

  pragma "private"
  var chpldev_taskTable : chpldev_taskTable_t;

  //----------------------------------------------------------------------{
  //- Code to initialize the task table on each locale.
  //-
  proc chpldev_taskTable_init() {
    // Doing this in parallel should be safe as long as this module is
    // initialized late (after DefaultRectangular and most other
    // internal modules are already initialized)
    coforall loc in Locales ref(chpldev_taskTable) do on loc {
      chpldev_taskTable = new chpldev_taskTable_t();
    }
  }

  extern var taskreport: int(32);
  if taskreport then chpldev_taskTable_init();

  //-
  //----------------------------------------------------------------------}

  ////////////////////////////////////////////////////////////////////////{
  //
  // Exported task table code.
  //
  // In general, tasks may have been created before the task table was
  // initialized.
  // In that case, operations may be attempted on the task table using
  // taskIDs that are unknown to it.  That is why we check
  // for membership on all operations.
  //
  ////////////////////////////////////////////////////////////////////////}

  export proc chpldev_taskTable_add(taskID   : chpl_taskID_t,
                                    lineno   : uint(32),
                                    filename : c_string,
                                    tl_info  : uint(64))
  {
    if (chpldev_taskTable == nil) then return;

    if (!chpldev_taskTable.dom.member(taskID)) then
      // This must be serial, because if add() results in parallelism
      // (due to _resize), it may lead to deadlock (due to reentry
      // into the runtime tasking layer for task table operations).
      serial true do
        chpldev_taskTable.dom.add(taskID);

    chpldev_taskTable.map[taskID] = new chpldev_Task(taskState.pending,
                                                 lineno, filename, tl_info);
  }

  export proc chpldev_taskTable_remove(taskID : chpl_taskID_t)
  {
    if (chpldev_taskTable == nil ||
        !chpldev_taskTable.dom.member(taskID)) then return;

    // This must be serial, because if remove() results in parallelism
    // (due to _resize), it may lead to deadlock (due to reentry into
    // the runtime tasking layer for task table operations).
    serial true do
      chpldev_taskTable.dom.remove(taskID);
  }

  export proc chpldev_taskTable_set_active(taskID : chpl_taskID_t)
  {
    if (chpldev_taskTable == nil ||
        !chpldev_taskTable.dom.member(taskID)) then return;

    chpldev_taskTable.map[taskID].state = taskState.active;
  }

  export proc chpldev_taskTable_set_suspended(taskID : chpl_taskID_t)
  {
    if (chpldev_taskTable == nil ||
        !chpldev_taskTable.dom.member(taskID)) then return;

    chpldev_taskTable.map[taskID].state = taskState.suspended;
  }

  export proc chpldev_taskTable_get_tl_info(taskID : chpl_taskID_t)
  {
    if (chpldev_taskTable == nil ||
        !chpldev_taskTable.dom.member(taskID)) then return 0:uint(64);

    return chpldev_taskTable.map[taskID].tl_info;
  }

  export proc chpldev_taskTable_print()
  {
    if (chpldev_taskTable == nil) then return;

    for taskID in chpldev_taskTable.dom {
      stderr.writeln("- ", chpldev_taskTable.map[taskID].filename,
                     ":",  chpldev_taskTable.map[taskID].lineno,
                     " is ", chpldev_taskTable.map[taskID].state);
    }
  }

}


Fixed a bug in domain assignment where a domain could lose track of
the arrays that it was tracking.  This fixes the cross-platform
difficulties and valgrind errors with release/examples/genericStack2.

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

class SingleLocaleDistribution {
  def buildDomain(param rank: int, type dim_type) {
    return SingleLocaleArithmeticDomain(rank=rank, dim_type=dim_type);
  }
  def buildDomain(type ind_type) {
    return SingleLocaleAssociativeDomain(ind_type=ind_type);
  }
}

var Block = SingleLocaleDistribution();

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

  def getIndices() return ranges;

  def setIndices(x: rank*_aseq(dim_type)) {
    ranges = x;
  }

  def buildExclusiveUpperDomain() {
    var x = SingleLocaleArithmeticDomain(rank=rank, dim_type=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:dim_type;
    for param i in 1..rank do
      sum *= ranges(i).length;
    return sum;
    // WANT: return * reduce (this(1..rank).length);
  }

  def low {
    if rank == 1 {
      return ranges(1)._low;
    } else {
      var result: rank*dim_type;
      for param i in 1..rank do
        result(i) = ranges(i)._low;
      return result;
    }
  }

  def high {
    if rank == 1 {
      return ranges(1)._high;
    } else {
      var result: rank*dim_type;
      for param i in 1..rank do
        result(i) = ranges(i)._high;
      return result;
    }
  }

  def buildArray(type eltType) {
    return SingleLocaleArithmeticArray(eltType, rank, dim_type, dom=this);
  }

  def buildSparseDomain()
    return SingleLocaleSparseDomain(rank=rank, dim_type=dim_type, adomain=this);

  def buildSubdomain()
    return SingleLocaleArithmeticDomain(rank=rank, dim_type=dim_type);

  def buildEmptyDomain()
    return SingleLocaleArithmeticDomain(rank=rank, dim_type=dim_type);

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

  def interior(dim: rank*int) {
    var x = SingleLocaleArithmeticDomain(rank=rank, dim_type=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 exterior(dim: rank*int) {
    var x = SingleLocaleArithmeticDomain(rank=rank, dim_type=int);
    for i in 1..rank do
      x.ranges(i) = this(i)._exterior(dim(i));
    return x;
  }

  def expand(dim: rank*int) {
    var x = SingleLocaleArithmeticDomain(rank=rank, dim_type=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) {
    var x = SingleLocaleArithmeticDomain(rank=rank, dim_type=int);
    for i in 1..rank do
      x.ranges(i) = ranges(i)._expand(dim);
    return x;
  }

  // this should eventually...
  //  ...be in the distribtion's implementation of the blocked domain
  // ...yield a subBlock of the domain per thread per locale
  iterator subBlocks {
    yield this;
  }

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

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

class SingleLocaleArithmeticArray: BaseArray {
  type eltType;
  param rank : int;
  type dim_type;

  var dom : SingleLocaleArithmeticDomain(rank=rank, dim_type=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(eltType);
  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(eltType, size:int); // ahh!!! can't cast to int here
    data.init();
  }

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

  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);
    return data(sum:int); // !!ahh
  }

  def view(d: SingleLocaleArithmeticDomain) {
    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 = SingleLocaleArithmeticArray(eltType, 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 alias;
  }

  def checkSlice(d: SingleLocaleArithmeticDomain) {
    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);
    }
  }

  def slice(d: SingleLocaleArithmeticDomain) {
    checkSlice(d);
    var alias = SingleLocaleArithmeticArray(eltType, 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 alias;
  }

  def reallocate(d: _domain) {
    if (d._value.type == dom.type) {
      var new = SingleLocaleArithmeticArray(eltType, rank, dim_type, d._value);
      for i in _intersect(d._value, dom) do
        new(i) = this(i);
      off = new.off;
      blk = new.blk;
      str = new.str;
      orig = new.orig;
      size = new.size;
      data = new.data;
    } else {
      halt("illegal reallocation");
    }
  }
}

def SingleLocaleArithmeticDomain.writeThis(f: Writer) {
  f.write("[", this(1));
  for i in 2..rank do
    f.write(", ", this(i));
  f.write("]");
}

def SingleLocaleArithmeticArray.writeThis(f: Writer) {
  var i : rank*dim_type;
  for dim in 1..rank do
    i(dim) = dom(dim)._low;
  label next while true {
    f.write(this(i));
    if i(rank) <= (dom(rank)._high - dom(rank)._stride:dim_type) {
      f.write(" ");
      i(rank) += dom(rank)._stride:dim_type;
    } else {
      for dim in 1..rank-1 by -1 {
        if i(dim) <= (dom(dim)._high - dom(dim)._stride:dim_type) {
          i(dim) += dom(dim)._stride:dim_type;
          for dim2 in dim+1..rank {
            f.writeln();
            i(dim2) = dom(dim2)._low;
          }
          continue next;
        }
      }
      break;
    }
  }
}

def _intersect(a: _aseq, b: _aseq) {
  var g, x: int;
  (g, x) = _extended_euclid(a._stride, b._stride);
  var gg = g:a.eltType;
  var xx = x:a.eltType;
  var as = (a._stride):a.eltType;
  if abs(a._low - b._low) % gg != 0 then
    return 1..0:a.eltType;
  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) * xx * as / gg;
  if alignment == 0 then
    alignment = stride:a.eltType;
  low = low + low % alignment;
  return low..high by stride;
}

def _intersect(a: SingleLocaleArithmeticDomain, b: SingleLocaleArithmeticDomain) {
  var c = SingleLocaleArithmeticDomain(rank=a.rank, dim_type=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);
}

class SingleLocaleDistribution {
  def buildDomain(param rank: int, type dim_type) {
    return SingleLocaleArithmeticDomain(rank=rank, dim_type=dim_type);
  }
  def buildDomain(type ind_type) {
    return SingleLocaleAssociativeDomain(ind_type=ind_type);
  }
}

var Block = SingleLocaleDistribution();

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

  def getIndices() return ranges;

  def setIndices(x: rank*_aseq(dim_type)) {
    ranges = x;
  }

  def buildExclusiveUpperDomain() {
    var x = SingleLocaleArithmeticDomain(rank=rank, dim_type=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:dim_type;
    for param i in 1..rank do
      sum *= ranges(i).length;
    return sum;
    // WANT: return * reduce (this(1..rank).length);
  }

  def low {
    if rank == 1 {
      return ranges(1)._low;
    } else {
      var result: rank*dim_type;
      for param i in 1..rank do
        result(i) = ranges(i)._low;
      return result;
    }
  }

  def high {
    if rank == 1 {
      return ranges(1)._high;
    } else {
      var result: rank*dim_type;
      for param i in 1..rank do
        result(i) = ranges(i)._high;
      return result;
    }
  }

  def buildArray(type eltType) {
    return SingleLocaleArithmeticArray(eltType, rank, dim_type, dom=this);
  }

  def buildSparseDomain()
    return SingleLocaleSparseDomain(rank=rank, dim_type=dim_type, adomain=this);

  def buildSubdomain()
    return SingleLocaleArithmeticDomain(rank=rank, dim_type=dim_type);

  def buildEmptyDomain()
    return SingleLocaleArithmeticDomain(rank=rank, dim_type=dim_type);

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

  def interior(dim: rank*int) {
    var x = SingleLocaleArithmeticDomain(rank=rank, dim_type=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 exterior(dim: rank*int) {
    var x = SingleLocaleArithmeticDomain(rank=rank, dim_type=int);
    for i in 1..rank do
      x.ranges(i) = this(i)._exterior(dim(i));
    return x;
  }

  def expand(dim: rank*int) {
    var x = SingleLocaleArithmeticDomain(rank=rank, dim_type=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) {
    var x = SingleLocaleArithmeticDomain(rank=rank, dim_type=int);
    for i in 1..rank do
      x.ranges(i) = ranges(i)._expand(dim);
    return x;
  }

  // this should eventually...
  //  ...be in the distribtion's implementation of the blocked domain
  // ...yield a subBlock of the domain per thread per locale
  iterator subBlocks {
    yield this;
  }

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

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

class SingleLocaleArithmeticArray: BaseArray {
  type eltType;
  param rank : int;
  type dim_type;

  var dom : SingleLocaleArithmeticDomain(rank=rank, dim_type=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(eltType);
  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(eltType, size:int); // ahh!!! can't cast to int here
    data.init();
  }

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

  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);
    return data(sum:int); // !!ahh
  }

  def view(d: SingleLocaleArithmeticDomain) {
    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 = SingleLocaleArithmeticArray(eltType, 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 alias;
  }

  def checkSlice(d: SingleLocaleArithmeticDomain) {
    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);
    }
  }

  def slice(d: SingleLocaleArithmeticDomain) {
    checkSlice(d);
    var alias = SingleLocaleArithmeticArray(eltType, 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 alias;
  }

  def reallocate(d: _domain) {
    if (d._value.type == dom.type) {
      var new = SingleLocaleArithmeticArray(eltType, 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;
    } else {
      halt("illegal reallocation");
    }
  }
}

def SingleLocaleArithmeticDomain.writeThis(f: Writer) {
  f.write("[", this(1));
  for i in 2..rank do
    f.write(", ", this(i));
  f.write("]");
}

def SingleLocaleArithmeticArray.writeThis(f: Writer) {
  var i : rank*dim_type;
  for dim in 1..rank do
    i(dim) = dom(dim)._low;
  label next while true {
    f.write(this(i));
    if i(rank) <= (dom(rank)._high - dom(rank)._stride:dim_type) {
      f.write(" ");
      i(rank) += dom(rank)._stride:dim_type;
    } else {
      for dim in 1..rank-1 by -1 {
        if i(dim) <= (dom(dim)._high - dom(dim)._stride:dim_type) {
          i(dim) += dom(dim)._stride:dim_type;
          for dim2 in dim+1..rank {
            f.writeln();
            i(dim2) = dom(dim2)._low;
          }
          continue next;
        }
      }
      break;
    }
  }
}

def _intersect(a: _aseq, b: _aseq) {
  var g, x: int;
  (g, x) = _extended_euclid(a._stride, b._stride);
  var gg = g:a.eltType;
  var xx = x:a.eltType;
  var as = (a._stride):a.eltType;
  if abs(a._low - b._low) % gg != 0 then
    return 1..0:a.eltType;
  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) * xx * as / gg;
  if alignment == 0 then
    alignment = stride:a.eltType;
  low = low + low % alignment;
  return low..high by stride;
}

def _intersect(a: SingleLocaleArithmeticDomain, b: SingleLocaleArithmeticDomain) {
  var c = SingleLocaleArithmeticDomain(rank=a.rank, dim_type=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);
}