Update spectralnorm to use iostyle.precision vs. iostyle.significant_digits

The field iostyle.significant_digits was removed in -r20978 in favor of
iostyle.precision. Update a constructor call.

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

/*
 * The Computer Language Benchmarks Game
 * http://shootout.alioth.debian.org/
 *
 * Original C contributed by Sebastien Loisel
 * Conversion to Chapel by Albert Sidelnik
 */

use Time;
config const timer : bool = false;
var t : Timer;
config const NUM = 500;

use barrierWF;
var b: BarrierWF;

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

proc eval_A_times_u(u : [] real, inRange : int, Au : [] real, outRange1, outRange2 : int)
{
	for i in {outRange1..outRange2-1} do {
		Au(i) = + reduce [j in 0..inRange-1] (u(j) * eval_A(i,j));
	}
}

proc eval_At_times_u(u : [] real, inRange : int, Au : [] real, outRange1, outRange2 : int)
{
	for i in {outRange1..outRange2-1} do
		Au(i) = + reduce [j in 0..inRange-1] (u(j) * eval_A(j,i));
}

proc eval_AtA_times_u(u,AtAu,v : [] real, inRange, range1, range2 : int)
{
           eval_A_times_u(u, inRange, v, range1, range2);
           b.barrier();
           eval_At_times_u(v, inRange, AtAu, range1, range2);
           b.barrier();
}

proc spectral_game(N : int) : real
{
	var tmp, u, v : [0..#N] real;
	var numThreads = here.numCores;
	var chunk = N / numThreads;

	u = 1.0;
        b = new BarrierWF(numThreads);

	coforall i in 0..#numThreads do {
		var r_begin = i * chunk;
		var r_end : int;
		if (i < (numThreads - 1)) then
			r_end = r_begin + chunk;
		else
			r_end = N;

		for 1..10 do {
			eval_AtA_times_u(u,v,tmp,N,r_begin,r_end);
			eval_AtA_times_u(v,u,tmp,N,r_begin,r_end);
		}
	}
	var vv = + reduce (v * v);
	var vBv = + reduce (u * v);

	return sqrt(vBv/vv);
}

proc main() {
	if timer then
		t.start();
	writeln(spectral_game(NUM), new iostyle(precision=10));
	if timer then {
		t.stop();
		writeln("Time elapsed : ", t.elapsed(), " seconds");
	}
}

/*
 * The Computer Language Benchmarks Game
 * http://shootout.alioth.debian.org/
 *
 * Original C contributed by Sebastien Loisel
 * Conversion to Chapel by Albert Sidelnik
 */

use Time;
config const timer : bool = false;
var t : Timer;
config const NUM = 500;

use barrierWF;
var b: BarrierWF;

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

proc eval_A_times_u(u : [] real, inRange : int, Au : [] real, outRange1, outRange2 : int)
{
	for i in {outRange1..outRange2-1} do {
		Au(i) = + reduce [j in 0..inRange-1] (u(j) * eval_A(i,j));
	}
}

proc eval_At_times_u(u : [] real, inRange : int, Au : [] real, outRange1, outRange2 : int)
{
	for i in {outRange1..outRange2-1} do
		Au(i) = + reduce [j in 0..inRange-1] (u(j) * eval_A(j,i));
}

proc eval_AtA_times_u(u,AtAu,v : [] real, inRange, range1, range2 : int)
{
           eval_A_times_u(u, inRange, v, range1, range2);
           b.barrier();
           eval_At_times_u(v, inRange, AtAu, range1, range2);
           b.barrier();
}

proc spectral_game(N : int) : real
{
	var tmp, u, v : [0..#N] real;
	var numThreads = here.numCores;
	var chunk = N / numThreads;

	u = 1.0;
        b = new BarrierWF(numThreads);

	coforall i in 0..#numThreads do {
		var r_begin = i * chunk;
		var r_end : int;
		if (i < (numThreads - 1)) then
			r_end = r_begin + chunk;
		else
			r_end = N;

		for 1..10 do {
			eval_AtA_times_u(u,v,tmp,N,r_begin,r_end);
			eval_AtA_times_u(v,u,tmp,N,r_begin,r_end);
		}
	}
	var vv = + reduce (v * v);
	var vBv = + reduce (u * v);

	return sqrt(vBv/vv);
}

proc main() {
	if timer then
		t.start();
	writeln(spectral_game(NUM), new iostyle(significant_digits=10));
	if timer then {
		t.stop();
		writeln("Time elapsed : ", t.elapsed(), " seconds");
	}
}


Spelling check, missing words, comment line size fixing.

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

//
// Reductions Primer
//
// This primer includes example reductions.
//
// An array is filled with random values and the locations of the
// maximum and minimum are found. The Euclidean norm of the Array's
// columns is computed using + reductions over slices of A.  Finally,
// an && reduction is used to compute whether all values in A are
// greater than 0.25 and the results of the computations are printed.
//

use Random; // For random number generation

config const seed = 31415; // Random generation seed
config const size = 10;    // The size of each side of the array

var A: [1..size, 1..size] real; // The 2D work array

//
// Fill A with random real values between 0 and 1.
//
fillRandom(A, seed);
writeln("A is: "); writeln(A);
writeln();

//
// Find the average value of the array, by summing over its elements
// and dividing by the number of elements it contains.
//
var eltAvg = (+ reduce A) / size**2;
writeln("The average element of A has the value ", eltAvg);
writeln();

//
// We can find the 1-norm of A by summing over the absolute value of the
// elements.
//
// The expression "abs(A)" creates a new matrix which contains in each of its
// elements the absolute value of the corresponding element in A.
// The "+ reduce" clause just sums these up.
//
var oneNorm = + reduce abs(A);
writeln("The 1-norm of A is ", oneNorm);
writeln();

//
// The Frobenius norm is the square root of the sum over all elements of their
// respective squares.
//
// The expression below can be broken down thus:
//  1) The Frobenius norm is the square root of sum quantity.
//  2) The quantity is the sum over all elements of a matrix.
//  3) The matrix is the promotion of A by "**2".
//     That is, a matrix each of whose elements the square of the corresponding
//       element in A.
//
var frobNorm = sqrt(+ reduce A**2);
writeln("The Frobenius norm of A is ", frobNorm);
writeln();

//
// Apply minloc and maxloc reductions. Capture the results into
// the maxVal, maxLoc, minVal, minLoc variables.
//
// maxloc and minloc define reductions which expect a tuple of arrays
// that can be iterated using zippered iteration
// (meaning that the iterator returns one value from each array in a 2-tuple).
//
// The reduce operator combines each successive element with a running result
// which is also a tuple.  The first element of the result tuple is the running
// maximum (or minimum); the second element is its index (i.e. location).
//
var (maxVal, maxLoc) = maxloc reduce zip(A, A.domain);
var (minVal, minLoc) = minloc reduce zip(A, A.domain);
writeln("The maximum value in A is: A", maxLoc, " = ", maxVal);
writeln("The minimum value in A is: A", minLoc, " = ", minVal);
writeln("The difference is: ", maxVal - minVal);
writeln();

//
// Compute Euclidean norms for each column using + reductions.
//
// Breaking down the statement below:
//  1) vecNorms is a 1-D array containing size elements (indexed by 1..size).
//  2) The elements of vecNorms are the square-root of some quantity.
//  3) The quantity is the sum over all of the elements of some vector.
//  4) The vector consists of the promotion of the j-th column of A by **2.
//     (That is, each element of that column vector is squared.)
//
var vecNorms = [j in 1..size] sqrt(+ reduce A(1..size, j)**2);
writeln("The Euclidean norm of each column is: ", vecNorms);
writeln();

//
// Use the && reduction to compute if all values in A are greater than 0.25.
//
// The parenthesized value is the promotion of A by "> 0.25".  This yields
// an array of the same size as A, containing boolean values that are true
// if the corresponding element in A exceeds 0.25 and false otherwise.
//
// The clause "&& reduce" applies the Boolean AND operator to each element in
// the array, and accumulates the result.
//
var onlyBigValues = && reduce (A > 0.25);
if onlyBigValues then
  writeln("The values in A are all greater than 0.25");
else
  writeln("Some values in A are less than 0.25");
writeln();

//
// Reductions Primer
//
// This primer includes example reductions.
//
// An array is filled with random values and the locations of the
// maximum and minimum are found. The Euclidean norm of the Array's
// columns is computed using + reductions over slices of A.  Finally,
// an && reduction is used to compute whether all values in A are
// greater than 0.25 and the results of the computations are printed.
//

use Random; // For random number generation

config const seed = 31415; // Random generation seed
config const size = 10;    // The size of each side of the array

var A: [1..size, 1..size] real; // The 2D work array

//
// Fill A with random real values between 0 and 1.
//
fillRandom(A, seed);
writeln("A is: "); writeln(A);
writeln();

//
// Find the average value of the array, but summing over its elements
// and dividing by the number of elements it contains.
//
var eltAvg = (+ reduce A) / size**2;
writeln("The average element of A has the value ", eltAvg);
writeln();

//
// We can find the 1-norm of A by summing over the absolute value of the elements.
//
// The expression "abs(A)" creates a new matrix which contains in each of its elements
// the absolute value of the corresponding element in A.
// The "+ reduce" clause just sums these up.
//
var oneNorm = + reduce abs(A);
writeln("The 1-norm of A is ", oneNorm);
writeln();

//
// The Frobenius norm is the square root of the sum over all elements of their
// respective squares.
//
// The expression below can be broken down thus:
//  1) The Frobenius norm is the square root of sum quantity.
//  2) The quantity the sum over all elements of a matrix.
//  3) The matrix is the promotion of A by "**2".
//     That is, a matrix each of whose elements the square of the corresponding
//       element in A.
//
var frobNorm = sqrt(+ reduce A**2);
writeln("The Frobenius norm of A is ", frobNorm);
writeln();

//
// Apply minloc and maxloc reductions. Capture the results into
// the maxVal, maxLoc, minVal, minLoc variables.
//
// maxloc and minloc define reductions which expect a tuple of arrays
// that can be iterated using zippered iteration
// (meaning that the iterator returns one value from each array in a 2-tuple).
//
// The reduce operator combines each successive element with a running result
// which is also a tuple.  The first element of the result tuple is the running
// maximum (or minimum); the second element is its index (i.e. location).
//
var (maxVal, maxLoc) = maxloc reduce zip(A, A.domain);
var (minVal, minLoc) = minloc reduce zip(A, A.domain);
writeln("The maximum value in A is: A", maxLoc, " = ", maxVal);
writeln("The minimum value in A is: A", minLoc, " = ", minVal);
writeln("The difference is: ", maxVal - minVal);
writeln();

//
// Compute Euclidean norms for each column using + reductions.
//
// Breaking down the statement below:
//  1) vecNorms is a 1-D array containing size elements (indexed by 1..size).
//  2) The elements of vecNorms are the square-root of some quantity.
//  3) The quantity is the sum over all of the elements of some vector.
//  4) The vector consists of the promotion of the j-th column of A by **2.
//     (That is, each element of that column vector is squared.)
//
var vecNorms = [j in 1..size] sqrt(+ reduce A(1..size, j)**2);
writeln("The Euclidean norm of each column is: ", vecNorms);
writeln();

//
// Use the && reduction to compute if all values in A are greater than 0.25.
//
// The parenthesized value is the promotion of A by "> 0.25".  This yields
// an array of the same size as A, contining boolean values that are true
// if the corresponding element in A exceeds 0.25 and false otherwise.
//
// The clause "&& reduce" applies the Boolean AND operator to each element in the
// array, and accumulates the result.
//
var onlyBigValues = && reduce (A > 0.25);
if onlyBigValues then
  writeln("The values in A are all greater than 0.25");
else
  writeln("Some values in A are less than 0.25");
writeln();


some cleanup

some cleanup

/*
  Package of Graph-Based Semi-Supervised Learning techniques based partially
  on the work by `Subramanya and Talukdar <http://graph-ssl.wdfiles.com/local--files/blog%3A_start/graph_ssl_acl12_tutorial_slides_final.pdf>`_

  Also, see notes in the /tex folder under main.

 */
module GBSSL {
  use LinearAlgebra;

  class AdjacencyMatrix {
    var vdom: domain(1),
        gdom: domain(2),
        edom: sparse subdomain(gdom),
        v: [vdom] real,
        e: [edom] real,
        directed: bool,
        A: [gdom] real,
        pcont: [vdom] real,
        pabdn: [vdom] real,
        pinj : [vdom] real,
        beta: real = 1.1;

    proc add(x: []) {
      if x.shape[1] != x.shape[2] {
      } else {
        vdom = {1..#x.shape[1]};
        gdom = {1..#x.shape[1], 1..#x.shape[1]};
        var xd = x.domain;
        ref Xd = A.reindex(xd);
        for ij in x.domain {
          if x[ij] > 0 {
            edom += ij+(1,1);
            Xd[ij] = x[ij];
          }
        }
      }
    }

    /*
      We need three probabilities as each vertex for the MAD algo.
     */
    proc calculateProbs() {
      for v in vdom {
        var ps = cellProbabilities(v);
        pcont[v] = ps[1];
        pinj[v] = ps[2];
        pabdn[v] = ps[3];
      }
    }

    /*
     Try to take advantage of function promotion by doing this one element at a time
     Will need some expert advice.
     */
     proc cellProbabilities(i:int) {
       // Need to remove the diagonal from the expressions m and l below
       var m: real = + reduce A[i,..];
       m = m - A[i,i];
       var l = + reduce xlogx(A[i,..]);
       l = l - xlogx(A[i,i]);
       var h = max(log(m) - l / m,0);
       var c = log(beta) / (log(beta + h));
       var d = (1- c)* sqrt(h);
       var z = max(c+d, 1);
       //writeln(" cell probabilities (%n, %n, %n)".format(c/z, d/z, 1-(c+d)/z));
       return (c/z, d/z, 1-c-d);
     }
  }

   proc xlogx(x: real) {
     if x > 0.0005 {
       return x * log(x);
     } else {
       return 0;
     }
   }
}

/*
  Package of Graph-Based Semi-Supervised Learning techniques based partially
  on the work by `Subramanya and Talukdar <http://graph-ssl.wdfiles.com/local--files/blog%3A_start/graph_ssl_acl12_tutorial_slides_final.pdf>`_

 */
module GBSSL {
  use LinearAlgebra;

  class AdjacencyMatrix {
    var vdom: domain(1),
        gdom: domain(2),
        edom: sparse subdomain(gdom),
        v: [vdom] real,
        e: [edom] real,
        directed: bool,
        A: [gdom] real,
        pcont: [vdom] real,
        pabdn: [vdom] real,
        pinj : [vdom] real,
        beta: real = 1.1;

    proc add(x: []) {
      if x.shape[1] != x.shape[2] {
      } else {
        vdom = {1..#x.shape[1]};
        gdom = {1..#x.shape[1], 1..#x.shape[1]};
        var xd = x.domain;
        ref Xd = A.reindex(xd);
        for ij in x.domain {
          if x[ij] > 0 {
            edom += ij+(1,1);
            Xd[ij] = x[ij];
          }
        }
      }
      //pContinue();
    }

    proc pContinue() {
    }

    proc calculateProbs() {
      for v in vdom {
        var ps = cellProbabilities(v);
        pcont[v] = ps[1];
        pinj[v] = ps[2];
        pabdn[v] = ps[3];
      }
    }

    /*
     Try to take advantage of function promotion by doing this one element at a time
     Will need some expert advice.
     */
     proc cellProbabilities(i:int) {
       // Need to remove the diagonal from the expressions m and l below
       var m: real = + reduce A[i,..];
       m = m - A[i,i];
       var l = + reduce xlogx(A[i,..]);
       l = l - xlogx(A[i,i]);
       var h = max(log(m) - l / m,0);
       var c = log(beta) / (log(beta + h));
       var d = (1- c)* sqrt(h);
       var z = max(c+d, 1);
       //writeln(" cell probabilities (%n, %n, %n)".format(c/z, d/z, 1-(c+d)/z));
       return (c/z, d/z, 1-c-d);
     }
  }

   proc xlogx(x: real) {
     if x > 0.0005 {
       return x * log(x);
     } else {
       return 0;
     }
   }
}


Serialized writeln() for deterministic output.

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

var fours = (0..36 by 4);
for i in fours do
  writeln(i);

var fours = (0..36 by 4);
[i in fours] writeln(i);


Trying to debug failure in norm2

Trying to debug failure in norm2

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

/** MRA -- MultiResolution Analysis Module

    Multiresolution representation of 1-d functions using a multiwavelet
    basis (similar to discontinuous spectral element with a hierarchal
    decomposition).

    Mad Chapel Author: James Dinan <dinan@cray.com>
                 Date: August, 2007

    This code is an adaptation of 1d Madness, originally written by Robert
    Harrison <harrisonrj@ornl.gov>, et al.
 */

use Math;
use Tensor;
use FTree;
use Quadrature;
use TwoScale;
use AnalyticFcn;
use StringConvert;  // FIXME: workaround for formatting reals

config const verbose = false;
config const debug   = false;

class Function {
    const k             = 5;    // use first k Legendre polynomials as the basis in each box
    const thresh        = 1e-5; // truncation threshold for small wavelet coefficients
    const f: AFcn       = nil;  // analytic f(x) to project into the numerical represntation
    const initial_level = 2;    // initial level of refinement
    const max_level     = 30;   // maximum level of refinement mostly as a sanity check
    var   autorefine    = true; // automatically refine during multiplication
    var   compressed    = false;// keep track of what basis we are in

    // Sum and Difference coefficients
    var   s = FTree(order=k);
    var   d = FTree(order=k);

    // FIXME: Ideally all of these matrices should be const as well but they
    //        can't be presently since they must be assigned in initialize()

    // Two-Scale relationship matrices
    const hgDom = [0..2*k-1, 0..2*k-1];
    const hg    : [hgDom] real = hg_getCoeffs(k); // FIXME: hg  =  hg_getCoeffs(k);
    var   hgT   : [hgDom] real;                   //        hgT => transpose(hg);

    // Quadrature coefficients
    const quadDom   = [0..k-1];
    var   quad_x    : [quadDom] real; // points
    var   quad_w    : [quadDom] real; // weights

    const quad_phiDom = [0..k-1, 0..k-1];
    var   quad_phi    : [quad_phiDom] real; // phi[point,i]
    var   quad_phiT   : [quad_phiDom] real; // phi[point,i] transpose
    var   quad_phiw   : [quad_phiDom] real; // phi[point,i]*weight[point]

    // blocks of the block tridiagonal derivative operator
    const dcDom = [0..k-1, 0..k-1];
    var   rm    : [dcDom] real;
    var   r0    : [dcDom] real;
    var   rp    : [dcDom] real;

    def initialize() {
        if debug then writeln("Creating Function: k=", k, " thresh=", thresh);

        if debug then writeln("  initializing two-scale relation coefficients");
        hg  = hg_getCoeffs(k);
        transposeCopy(hgT, hg);

        if debug then writeln("  initializing quadrature coefficients");
        init_quadrature(k);

        // blocks of the block tridiagonal derivative operator
        if debug then writeln("  initializing tridiagonal derivative operator");
        make_dc_periodic();

        // initial refinement of analytic function f(x)
        if f != nil {
            if debug then writeln("  performing initial refinement of f(x)");
            for l in 0..2**initial_level-1 do
                refine(initial_level, l);
        }

        if debug then writeln("done.");
    }

    /** Initialize the quadrature coefficient matricies.
     */
    def init_quadrature(order: int) {
        quad_x   = gl_getPoints(k);
        quad_w   = gl_getWeights(k);

        for i in quad_phiDom.dim(1) {
            var p = phi(quad_x[i], k);
            quad_phi [i, ..] = p;
            quad_phiw[i, ..] = quad_w[i] * p;
            quad_phiT[.., i] = p;
        }
    }

    /** Return the level-0 blocks rm, r0, rp of the central
        difference derivative operator with periodic boundary
        conditions on either side.
     */
    def make_dc_periodic() {
        var iphase = 1.0;
        for i in dcDom.dim(1) {
            var jphase = 1.0;

            for j in dcDom.dim(2) {
                var gammaij = sqrt((2*i+1) * (2*j+1));
                var Kij = if (i-j) > 0 && ((i-j) % 2) == 1 then
                        2.0 else 0.0;
                r0[i,j] = 0.5*(1.0 - iphase*jphase - 2.0*Kij)*gammaij;
                rm[i,j] = 0.5*jphase*gammaij;
                rp[i,j] =-0.5*iphase*gammaij;
                jphase = -jphase;
            }
            iphase = -iphase;
        }
    }

    /** Return a deep copy of this Function
     */
    def copy() {
        return Function(k=k, thresh=thresh, f=f, initial_level=initial_level,
                max_level=max_level, autorefine=autorefine, compressed=compressed,
                s=s.copy(), d=d.copy());
    }

    /** Return a copy of this function's skeleton
     */
    def skeletonCopy() {
        // Omit: f, compressed, s, d
        return Function(k=k, thresh=thresh, initial_level=initial_level,
                max_level=max_level, autorefine=autorefine);
    }

    /** s[n, l] = integral(phi[n][l](x) * f(x))
        for box (n,l) project f(x) using quadrature rule
        into scaling function basis
     */
    def project(n: int, l: int) {
        var s     : [quadDom] real;
        var h     = 0.5 ** n;
        var scale = sqrt(h);

        for mu in quad_phiDom.dim(1) {
            var x  = (l + quad_x[mu]) * h;
            var fx = f(x);
            for i in quad_phiDom.dim(2) do
                s[i] += scale * fx * quad_phiw[mu, i];
        }

        return s;
    }

    /** Refine numerical representation of f(x) to desired tolerance
     */
    def refine(n, l) {
        // project f(x) at next level
        var s0 = project(n+1, 2*l);
        var s1 = project(n+1, 2*l+1);
        var sc : [0..2*k-1] real;

        sc[0..k-1]   = s0;
        sc[k..2*k-1] = s1;

        // apply the two scale relationship to get difference coeff
        // in 1d this is O(k^2) flops
        var dc = sc*hgT;

        // check to see if within tolerance
        // normf() is Frobenius norm == 2-norm for vectors
        var nf = normf(dc[k..2*k-1]);
        if((nf < thresh) || (n >= (max_level-1))) {
            s[n+1, 2*l]   = s0;
            s[n+1, 2*l+1] = s1;
        }
        else {
            // these recursive calls on sub-trees can go in parallel
            // if the HashMap is syncronized
            refine(n+1, 2*l  );
            refine(n+1, 2*l+1);
        }
    }

    /** Evaluate numerical representation of f at x.  Performed in scaling
        function basis only.
     */
    def this(x) {
        if compressed then reconstruct();
        return evaluate(0, 0, x);
    }

    /** eval f(x) using adaptively refined numerical representation of f(x)
        answer should be within tolerance of the analytical f(x)

        Descend tree looking for box (n,l) with scaling function
        coefficients containing the point x.
     */
    def evaluate(in n=0, in l=0, in x): real {
        if s.has_coeffs(n, l) {
            var p = phi(x, k);
            return inner(s[n, l], p)*sqrt(2.0**n);

        } else {
            (n, l, x) = (n+1, 2*l, 2*x);
            if x >= 1 then (l, x) = (l+1, x-1);
            return evaluate(n, l, x);
        }
    }

    /** change from scaling function basis to multi-wavelet basis (s -> d)
        tree is filled out with s[0][0] and d
        n is level in tree
        l is box index
     */
    def compress(n=0, l=0) {
        if compressed then return;

        // sub-trees can be done in parallel
        if !s.has_coeffs(n+1,   2*l) then compress(n+1,   2*l);
        if !s.has_coeffs(n+1, 2*l+1) then compress(n+1, 2*l+1);

        var sc: [0..2*k-1] real;
        sc[0..k-1]   = s[n+1,   2*l];
        sc[k..2*k-1] = s[n+1, 2*l+1];

        // apply the two scale relationship to get difference coeff
        // in 1d this is O(k^2) flops (in 3d this is O(k^4) flops)
        var dc = sc*hgT;

        s[n, l] = dc[0..k-1];
        d[n, l] = dc[k..2*k-1];

        s.remove(n+1,   2*l);
        s.remove(n+1, 2*l+1);

        if n==0 then compressed = true;
    }

    /** change from multi-wavelet basis to scaling function basis (d -> s)
        tree just has s at leaves
        n is level in tree
        l is box index
     */
    def reconstruct(n=0, l=0) {
        if !compressed then return;

        if d.has_coeffs(n, l) {
            var dc: [0..2*k-1] real;
            dc[0..k-1]   = s[n, l];
            dc[k..2*k-1] = d[n, l];
            d.remove(n, l);
            s.remove(n, l);

            // apply the two scale relationship to get difference coeff
            // in 1d this is O(k^2) flops (in 3d this is O(k^4) flops)
            var sc = dc*hg;

            s[n+1, 2*l  ] = sc[0..k-1];
            s[n+1, 2*l+1] = sc[k..2*k-1];

            // sub-trees can be done in parallel
            reconstruct(n+1, 2*l);
            reconstruct(n+1, 2*l+1);
        }

        if n == 0 then compressed = false;
    }

    /** In s are scaling coefficients for box n,l ... apply twoscale to generate
        the corresponding coefficients on level n+1 and insert the results into
        the tree of scaling function coefficients.
     */
    def recur_down(n, l) {
        if debug then writeln(" + recur_down(", n, ", ", l, ")");

        var sc : [0..2*k-1] real;
        sc[0..k-1] = s[n, l];

        var new_sc = sc*hg;
        s[n+1, 2*l  ] = new_sc[0..k-1];
        s[n+1, 2*l+1] = new_sc[k..2*k-1];
    }

    /** If the scaling coefficeints in box n,l exist, return them.
        (allow here for zero boundary conditions for boxes just off
        the ends of the domain)

        Else recur up to the next level looking for a parent.  If a
        parent exists, use two scale to recur those coefficients down
        to make n,l.  Note that this modifies the tree in place and
        you should eventually call sclean to tidy up when finished.

        Else, return None (corresponding child boxes exist at a finer scale)
     */
    def get_coeffs(_n, _l) {
        if debug then writeln(" @ get_coeffs(", _n, ", ", _l, ")");
        var n = _n, l = _l;

        // Walk up the tree to find scaling coeffs
        while n >= 0 {
            if l < 0 || l >= 2**n {
                var coeffs : [0..k-1] real;
                return coeffs;
            }

            if s.has_coeffs(n, l) then break;

            n -= 1;
            l  = l/2;
        }

        // Didn't find coeffs, they must be below (_n, _l)
        if n < 0 then return None;

        // Recur down to (_n, _l)
        for i in n.._n-1 {
            l = _l/(2**(_n-i));
            recur_down(i, l);
        }

        return s[_n, _l];
    }

    /** Differentiation (also inner and mul) may leave scaling function
        coefficients below their original level.  Recur down to the
        locally first box with scaling function coefficients.
        Delete all children below there.
     */
    def sclean(n=0, l=0, in cleaning=false) {
        if cleaning { //then
            if debug then writeln(" + sclean deleting (", n, ", ", l, ")");
            s.remove(n, l);
        } else
            cleaning = s.has_coeffs(n, l);

        // Sub trees can run in parallel
        if n < max_level {
            if !cleaning || s.has_coeffs(n+1, 2*l) then
                sclean(n+1, 2*l, cleaning);
            if !cleaning || s.has_coeffs(n+1, 2*l+1) then
                sclean(n+1, 2*l+1, cleaning);
        }
    }

    /** Differentiate the function, which corresponds to application
        of a block triadiagonal matrix.  For an adaptively refined
        target function we may need to refine boxes down until three
        boxes exist in the same scale.
     */
    def diff() {
        def diffHelper(n = 0, l = 0, result) {
            if debug then writeln(" * diff(", n, ", ", l, ")");
            if !s.has_coeffs(n, l) {
                // Sub trees can run in parallel
                // Run down tree until we hit scaling function coefficients
                diffHelper(n+1, 2*l  , result);
                diffHelper(n+1, 2*l+1, result);
            } else {
                // These can also go in parallel since may involve
                // recurring up & down the tree.
                if debug then writeln(" * diff: coeffs at (", n, ", ", l, ")");
                var sm = get_coeffs(n, l-1);
                var sp = get_coeffs(n, l+1);
                var s0 = s[n, l];

                // We have s0, check if we found sm and sp at this level
                if !isNone(sm) && !isNone(sp) {
                    var r = rp*sm + r0*s0 + rm*sp;
                    result.s[n, l] = r * 2.0**n;
                } else {
                    recur_down(n, l);
                    // Sub trees can run in parallel
                    diffHelper(n+1, 2*l  , result);
                    diffHelper(n+1, 2*l+1, result);
                }
            }
        }

        if compressed then reconstruct();
        var result = skeletonCopy();

        diffHelper(0, 0, result);

        sclean();
        return result;
    }

    /** Return sqrt(integral(f(x)**2))
     */
    def norm2() {
        if compressed then reconstruct();
        writeln(s.idx_iter());
        writeln([i in s.idx_iter()] i);
        //return sqrt(+ reduce [i in s] normf(i)**2);
        return 1.0;
    }

    /** Perform GAXPY in the multi-wavelet (compressed) basis
        this = alpha*this + beta*other (other is not changed).
     */
    def gaxpy(alpha, other, beta) {
        // recursive "iteration" for gaxpy
        def gaxpy_iter(n=0, l=0) {
            if d.has_coeffs(n, l) || other.d.has_coeffs(n, l) {
                if d.has_coeffs(n, l) && other.d.has_coeffs(n, l) then
                    d[n, l] = d[n, l]*alpha + other.d[n, l]*beta;

                else if !d.has_coeffs(n, l) && other.d.has_coeffs(n, l) then
                    d[n, l] = other.d[n, l] * beta;

                else // d.has_coeffs(n, l) && !other.d.has_coeffs(n, l)
                    d[n, l] *= alpha;

                // calls on sub-trees can go in parallel
                gaxpy_iter(n+1, 2*l);
                gaxpy_iter(n+1, 2*l+1);
            }
        }
    }

    /*
    def gaxpy(alpha, other, beta) {
        if !compressed then compress();
        if !other.compressed then other.compress();

        s[0, 0] = s[0, 0]*alpha + other.s[0, 0]*beta; // Do scaling coeffs
        for (n, l) in d.idx_iter() {
            d[n, l] *= alpha;
        }
        for (n, l) in other.d.idx_iter() {
            d[n, l] += other.d[n, l]*beta;
        }

        // return this so operations can be chained
        return this;
    }
    */

    /** Add this function to another and return the result in a new
        function.  This and other are unchanged.
     */
    def add(other) {
        return copy().gaxpy(1.0, other, 1.0);
    }

    /** Subtract this function from another and return the result in a new
        function.  This and other are unchanged.
     */
    def subtract(other) {
        return copy().gaxpy(1.0, other, -1.0);
    }

    /** Recursively multiply f1 and f2 put the result into this
     */
    def multiply(f1, f2, n=0, l=0) {
        if f1.s.has_coeffs(n, l) && f2.s.has_coeffs(n, l) {
            if autorefine && n+1 <= max_level {
                // if autorefine is set we are multiplying two polynomials
                // of order k-1 the result could be up to order 2(k-1) so
                // refine one more level to reduce the error and try to
                // keep it within the threshold

                // refine both one more level
                f1.recur_down(n, l);
                f2.recur_down(n, l);

                // scale factor for this level = sqrt((2^d)^(n+1))
                var scale_factor = sqrt(2.0**(n+1));

                // multiply f1.s[n+1][2*l] and f2.s[n+1][2*l]
                var f = f1.s[n+1, 2*l] * quad_phiT;
                var g = f2.s[n+1, 2*l] * quad_phiT;
                f *= g; // Note: Element-wise multiplication
                s[n+1, 2*l] = (f * quad_phiw) * scale_factor;

                // multiply f1.s[n+1][2*l+1] and f2.s[n+1][2*l+1]
                f  =  f1.s[n+1, 2*l+1] * quad_phiT;
                g  =  f2.s[n+1, 2*l+1] * quad_phiT;
                f *= g;
                s[n+1, 2*l+1] = (f * quad_phiw) * scale_factor;

            } else {
                // if autorefine is not set or we are at the max_level
                // live with what you get
                var f = f1.s[n, l] * quad_phiT;
                var g = f2.s[n, l] * quad_phiT;
                f *= g;

                // scale factor for this level = sqrt((2^d)^(n+1))
                s[n, l] = (f * quad_phiw) * sqrt(2.0**(n));
            }

        } else {
            if f1.s.has_coeffs[n, l] && !f2.s.has_coeffs[n, l] then
                // refine this box down to next level in f1
                f1.recur_down(n, l);
            else if !f1.s.has_coeffs[n, l] && f2.s.has_coeffs[n, l] then
                // refine this box down to next level in f2
                f2.recur_down(n, l);

            // calls on sub-trees can go in parallel
            multiply(f1, f2, n+1, 2*l);
            multiply(f1, f2, n+1, 2*l+1);
        }
    }

    /** Function Multiplication:
        For multiply, both operands need to be in the scaling function basis
        so possibly call reconstruct on one or both of them first
     */
    def multiply(other) {
        if compressed then reconstruct();
        if other.compressed then other.reconstruct();

        // Store the result in a new Function
        var result = skeletonCopy();
        result.multiply(this, other);

        sclean();
        other.sclean();

        return result;
    }

    /** Mostly for debugging, print summary of coefficients,
        optionally printing the norm of each block
     */
    def summarize() {
        writeln("\n-----------------------------------------------------");
        writeln("k=", k, " thresh=", thresh, " compressed=", compressed);
        writeln("sum coefficients:");
        for n in 0..max_level {
            var sum = 0.0, ncoeffs = 0;
            for box in s.lvl_iter(n) {
                sum     += normf(box)**2;
                ncoeffs += 1;
            }
            if ncoeffs != 0 then
                writeln("   level ", toString("%2d", n), "   #boxes=",
                        toString("%4d", ncoeffs), "  norm=", toString("%0.2e", truncate(sqrt(sum))));
        }

        writeln("difference coefficients:");
        for n in 0..max_level {
            var sum = 0.0, ncoeffs = 0;
            for box in d.lvl_iter(n) {
                sum     += normf(box)**2;
                ncoeffs += 1;
            }
            if ncoeffs != 0 then
                writeln("   level ", toString("%2d", n), "   #boxes=",
                        toString("%4d", ncoeffs), "  norm=", toString("%0.2e", truncate(sqrt(sum))));
        }

        writeln("-----------------------------------------------------\n");
    }

    /** Evaluate the analytic and numerical functions over the given interval
        and print the error.
     */
    def evalNPT(npt) {
        for i in 0..npt {
            var (fval, Fval) = (truncate(f(i/npt:real)), truncate(this(i/npt:real)));
            //writeln(" -- ", toString("%0.2f", i/npt:real), ":  F_numeric()=", toString("% 0.5e", Fval),
            //        "  f_analytic()=", toString("% 0.5e", fval), " err=", toString("% 0.5e", Fval-fval),
            //        if abs(Fval-fval) > thresh then "  > thresh" else "");
            writeln(" -- ", toString("%0.2f", i/npt:real), ":  F_numeric()=", toString("% 0.8f", Fval),
                    "  f_analytic()=", toString("% 0.8f", fval), " err=", toString("% 0.1e", Fval-fval),
                    if abs(Fval-fval) > thresh then "  > thresh" else "");
        }
    }
}

/*************************************************************************/
/* Standard operations on Function objects:                              */
/*************************************************************************/

def +(F: Function, G: Function): Function {
    return F.add(G);
}

def -(F: Function, G: Function): Function {
    return F.subtract(G);
}

def *(F: Function, G: Function): Function {
    return F.multiply(G);
}

/** MRA -- MultiResolution Analysis Module

    Multiresolution representation of 1-d functions using a multiwavelet
    basis (similar to discontinuous spectral element with a hierarchal
    decomposition).

    Mad Chapel Author: James Dinan <dinan@cray.com>
                 Date: August, 2007

    This code is an adaptation of 1d Madness, originally written by Robert
    Harrison <harrisonrj@ornl.gov>, et al.
 */

use Math;
use Tensor;
use FTree;
use Quadrature;
use TwoScale;
use AnalyticFcn;
use StringConvert;  // FIXME: workaround for formatting reals

config const verbose = false;
config const debug   = false;

class Function {
    const k             = 5;    // use first k Legendre polynomials as the basis in each box
    const thresh        = 1e-5; // truncation threshold for small wavelet coefficients
    const f: AFcn       = nil;  // analytic f(x) to project into the numerical represntation
    const initial_level = 2;    // initial level of refinement
    const max_level     = 30;   // maximum level of refinement mostly as a sanity check
    var   autorefine    = true; // automatically refine during multiplication
    var   compressed    = false;// keep track of what basis we are in

    // Sum and Difference coefficients
    var   s = FTree(order=k);
    var   d = FTree(order=k);

    // FIXME: Ideally all of these matrices should be const as well but they
    //        can't be presently since they must be assigned in initialize()

    // Two-Scale relationship matrices
    const hgDom = [0..2*k-1, 0..2*k-1];
    var   hg    : [hgDom] real; // FIXME: hg  =  hg_getCoeffs(k);
    var   hgT   : [hgDom] real; //        hgT => transpose(hg);

    // Quadrature coefficients
    const quadDom   = [0..k-1];
    var   quad_x    : [quadDom] real; // points
    var   quad_w    : [quadDom] real; // weights

    const quad_phiDom = [0..k-1, 0..k-1];
    var   quad_phi    : [quad_phiDom] real; // phi[point,i]
    var   quad_phiT   : [quad_phiDom] real; // phi[point,i] transpose
    var   quad_phiw   : [quad_phiDom] real; // phi[point,i]*weight[point]

    // blocks of the block tridiagonal derivative operator
    const dcDom = [0..k-1, 0..k-1];
    var   rm    : [dcDom] real;
    var   r0    : [dcDom] real;
    var   rp    : [dcDom] real;

    def initialize() {
        if debug then writeln("Creating Function: k=", k, " thresh=", thresh);

        if debug then writeln("  initializing two-scale relation coefficients");
        hg  = hg_getCoeffs(k);
        transposeCopy(hgT, hg);

        if debug then writeln("  initializing quadrature coefficients");
        init_quadrature(k);

        // blocks of the block tridiagonal derivative operator
        if debug then writeln("  initializing tridiagonal derivative operator");
        make_dc_periodic();

        // initial refinement of analytic function f(x)
        if f != nil {
            if debug then writeln("  performing initial refinement of f(x)");
            for l in 0..2**initial_level-1 do
                refine(initial_level, l);
        }

        if debug then writeln("done.");
    }

    /** Initialize the quadrature coefficient matricies.
     */
    def init_quadrature(order: int) {
        quad_x   = gl_getPoints(k);
        quad_w   = gl_getWeights(k);

        for i in quad_phiDom.dim(1) {
            var p = phi(quad_x[i], k);
            quad_phi [i, ..] = p;
            quad_phiw[i, ..] = quad_w[i] * p;
            quad_phiT[.., i] = p;
        }
    }

    /** Return the level-0 blocks rm, r0, rp of the central
        difference derivative operator with periodic boundary
        conditions on either side.
     */
    def make_dc_periodic() {
        var iphase = 1.0;
        for i in dcDom.dim(1) {
            var jphase = 1.0;

            for j in dcDom.dim(2) {
                var gammaij = sqrt((2*i+1) * (2*j+1));
                var Kij = if (i-j) > 0 && ((i-j) % 2) == 1 then
                        2.0 else 0.0;
                r0[i,j] = 0.5*(1.0 - iphase*jphase - 2.0*Kij)*gammaij;
                rm[i,j] = 0.5*jphase*gammaij;
                rp[i,j] =-0.5*iphase*gammaij;
                jphase = -jphase;
            }
            iphase = -iphase;
        }
    }

    /** Return a deep copy of this Function
     */
    def copy() {
        return Function(k=k, thresh=thresh, f=f, initial_level=initial_level,
                max_level=max_level, autorefine=autorefine, compressed=compressed,
                s=s.copy(), d=d.copy());
    }

    /** Return a copy of this function's skeleton
     */
    def skeletonCopy() {
        // Omit: f, compressed, s, d
        return Function(k=k, thresh=thresh, initial_level=initial_level,
                max_level=max_level, autorefine=autorefine);
    }

    /** s[n, l] = integral(phi[n][l](x) * f(x))
        for box (n,l) project f(x) using quadrature rule
        into scaling function basis
     */
    def project(n: int, l: int) {
        var s     : [quadDom] real;
        var h     = 0.5 ** n;
        var scale = sqrt(h);

        for mu in quad_phiDom.dim(1) {
            var x  = (l + quad_x[mu]) * h;
            var fx = f(x);
            for i in quad_phiDom.dim(2) do
                s[i] += scale * fx * quad_phiw[mu, i];
        }

        return s;
    }

    /** Refine numerical representation of f(x) to desired tolerance
     */
    def refine(n, l) {
        // project f(x) at next level
        var s0 = project(n+1, 2*l);
        var s1 = project(n+1, 2*l+1);
        var sc : [0..2*k-1] real;

        sc[0..k-1]   = s0;
        sc[k..2*k-1] = s1;

        // apply the two scale relationship to get difference coeff
        // in 1d this is O(k^2) flops
        var dc = sc*hgT;

        // check to see if within tolerance
        // normf() is Frobenius norm == 2-norm for vectors
        var nf = normf(dc[k..2*k-1]);
        if((nf < thresh) || (n >= (max_level-1))) {
            s[n+1, 2*l]   = s0;
            s[n+1, 2*l+1] = s1;
        }
        else {
            // these recursive calls on sub-trees can go in parallel
            // if the HashMap is syncronized
            refine(n+1, 2*l  );
            refine(n+1, 2*l+1);
        }
    }

    /** Evaluate numerical representation of f at x.  Performed in scaling
        function basis only.
     */
    def this(x) {
        if compressed then reconstruct();
        return evaluate(0, 0, x);
    }

    /** eval f(x) using adaptively refined numerical representation of f(x)
        answer should be within tolerance of the analytical f(x)

        Descend tree looking for box (n,l) with scaling function
        coefficients containing the point x.
     */
    def evaluate(in n=0, in l=0, in x): real {
        if s.has_coeffs(n, l) {
            var p = phi(x, k);
            return inner(s[n, l], p)*sqrt(2.0**n);

        } else {
            (n, l, x) = (n+1, 2*l, 2*x);
            if x >= 1 then (l, x) = (l+1, x-1);
            return evaluate(n, l, x);
        }
    }

    /** change from scaling function basis to multi-wavelet basis (s -> d)
        tree is filled out with s[0][0] and d
        n is level in tree
        l is box index
     */
    def compress(n=0, l=0) {
        if compressed then return;

        // sub-trees can be done in parallel
        if !s.has_coeffs(n+1,   2*l) then compress(n+1,   2*l);
        if !s.has_coeffs(n+1, 2*l+1) then compress(n+1, 2*l+1);

        var sc: [0..2*k-1] real;
        sc[0..k-1]   = s[n+1,   2*l];
        sc[k..2*k-1] = s[n+1, 2*l+1];

        // apply the two scale relationship to get difference coeff
        // in 1d this is O(k^2) flops (in 3d this is O(k^4) flops)
        var dc = sc*hgT;

        s[n, l] = dc[0..k-1];
        d[n, l] = dc[k..2*k-1];

        s.remove(n+1,   2*l);
        s.remove(n+1, 2*l+1);

        if n==0 then compressed = true;
    }

    /** change from multi-wavelet basis to scaling function basis (d -> s)
        tree just has s at leaves
        n is level in tree
        l is box index
     */
    def reconstruct(n=0, l=0) {
        if !compressed then return;

        if d.has_coeffs(n, l) {
            var dc: [0..2*k-1] real;
            dc[0..k-1]   = s[n, l];
            dc[k..2*k-1] = d[n, l];
            d.remove(n, l);
            s.remove(n, l);

            // apply the two scale relationship to get difference coeff
            // in 1d this is O(k^2) flops (in 3d this is O(k^4) flops)
            var sc = dc*hg;

            s[n+1, 2*l  ] = sc[0..k-1];
            s[n+1, 2*l+1] = sc[k..2*k-1];

            // sub-trees can be done in parallel
            reconstruct(n+1, 2*l);
            reconstruct(n+1, 2*l+1);
        }

        if n == 0 then compressed = false;
    }

    /** In s are scaling coefficients for box n,l ... apply twoscale to generate
        the corresponding coefficients on level n+1 and insert the results into
        the tree of scaling function coefficients.
     */
    def recur_down(n, l) {
        if debug then writeln(" + recur_down(", n, ", ", l, ")");

        var sc : [0..2*k-1] real;
        sc[0..k-1] = s[n, l];

        var new_sc = sc*hg;
        s[n+1, 2*l  ] = new_sc[0..k-1];
        s[n+1, 2*l+1] = new_sc[k..2*k-1];
    }

    /** If the scaling coefficeints in box n,l exist, return them.
        (allow here for zero boundary conditions for boxes just off
        the ends of the domain)

        Else recur up to the next level looking for a parent.  If a
        parent exists, use two scale to recur those coefficients down
        to make n,l.  Note that this modifies the tree in place and
        you should eventually call sclean to tidy up when finished.

        Else, return None (corresponding child boxes exist at a finer scale)
     */
    def get_coeffs(_n, _l) {
        if debug then writeln(" @ get_coeffs(", _n, ", ", _l, ")");
        var n = _n, l = _l;

        // Walk up the tree to find scaling coeffs
        while n >= 0 {
            if l < 0 || l >= 2**n {
                var coeffs : [0..k-1] real;
                return coeffs;
            }

            if s.has_coeffs(n, l) then break;

            n -= 1;
            l  = l/2;
        }

        // Didn't find coeffs, they must be below (_n, _l)
        if n < 0 then return None;

        // Recur down to (_n, _l)
        for i in n.._n-1 {
            l = _l/(2**(_n-i));
            recur_down(i, l);
        }

        return s[_n, _l];
    }

    /** Differentiation (also inner and mul) may leave scaling function
        coefficients below their original level.  Recur down to the
        locally first box with scaling function coefficients.
        Delete all children below there.
     */
    def sclean(n=0, l=0, in cleaning=false) {
        if cleaning { //then
            if debug then writeln(" + sclean deleting (", n, ", ", l, ")");
            s.remove(n, l);
        } else
            cleaning = s.has_coeffs(n, l);

        // Sub trees can run in parallel
        if n < max_level {
            if !cleaning || s.has_coeffs(n+1, 2*l) then
                sclean(n+1, 2*l, cleaning);
            if !cleaning || s.has_coeffs(n+1, 2*l+1) then
                sclean(n+1, 2*l+1, cleaning);
        }
    }

    /** Differentiate the function, which corresponds to application
        of a block triadiagonal matrix.  For an adaptively refined
        target function we may need to refine boxes down until three
        boxes exist in the same scale.
     */
    def diff() {
        def diffHelper(n = 0, l = 0, result) {
            if debug then writeln(" * diff(", n, ", ", l, ")");
            if !s.has_coeffs(n, l) {
                // Sub trees can run in parallel
                // Run down tree until we hit scaling function coefficients
                diffHelper(n+1, 2*l  , result);
                diffHelper(n+1, 2*l+1, result);
            } else {
                // These can also go in parallel since may involve
                // recurring up & down the tree.
                if debug then writeln(" * diff: coeffs at (", n, ", ", l, ")");
                var sm = get_coeffs(n, l-1);
                var sp = get_coeffs(n, l+1);
                var s0 = s[n, l];

                // We have s0, check if we found sm and sp at this level
                if !isNone(sm) && !isNone(sp) {
                    var r = rp*sm + r0*s0 + rm*sp;
                    result.s[n, l] = r * 2.0**n;
                } else {
                    recur_down(n, l);
                    // Sub trees can run in parallel
                    diffHelper(n+1, 2*l  , result);
                    diffHelper(n+1, 2*l+1, result);
                }
            }
        }

        if compressed then reconstruct();
        var result = skeletonCopy();

        diffHelper(0, 0, result);

        sclean();
        return result;
    }

    /** Return sqrt(integral(f(x)**2))
     */
    def norm2() {
        if compressed then reconstruct();
        //return sqrt(+ reduce [i in s] normf(i)**2);
        return 1.0;
    }

    /** Perform GAXPY in the multi-wavelet (compressed) basis
        this = alpha*this + beta*other (other is not changed).
     */
    def gaxpy(alpha, other, beta) {
        // recursive "iteration" for gaxpy
        def gaxpy_iter(n=0, l=0) {
            if d.has_coeffs(n, l) || other.d.has_coeffs(n, l) {
                if d.has_coeffs(n, l) && other.d.has_coeffs(n, l) then
                    d[n, l] = d[n, l]*alpha + other.d[n, l]*beta;

                else if !d.has_coeffs(n, l) && other.d.has_coeffs(n, l) then
                    d[n, l] = other.d[n, l] * beta;

                else /* d.has_coeffs(n, l) && !other.d.has_coeffs(n, l) */
                    d[n, l] *= alpha;

                // calls on sub-trees can go in parallel
                gaxpy_iter(n+1, 2*l);
                gaxpy_iter(n+1, 2*l+1);
            }
        }

        if !compressed then compress();
        if !other.compressed then other.compress();

        s[0, 0] = s[0, 0]*alpha + other.s[0, 0]*beta; // Do scaling coeffs
        gaxpy_iter();                                 // Do multi-wavelet coeffs

        // return this so operations can be chained
        return this;
    }

    /** Add this function to another and return the result in a new
        function.  This and other are unchanged.
     */
    def add(other) {
        return copy().gaxpy(1.0, other, 1.0);
    }

    /** Subtract this function from another and return the result in a new
        function.  This and other are unchanged.
     */
    def subtract(other) {
        return copy().gaxpy(1.0, other, -1.0);
    }

    /** Recursively multiply f1 and f2 put the result into this
     */
    def multiply(f1, f2, n=0, l=0) {
        if f1.s.has_coeffs(n, l) && f2.s.has_coeffs(n, l) {
            if autorefine && n+1 <= max_level {
                // if autorefine is set we are multiplying two polynomials
                // of order k-1 the result could be up to order 2(k-1) so
                // refine one more level to reduce the error and try to
                // keep it within the threshold

                // refine both one more level
                f1.recur_down(n, l);
                f2.recur_down(n, l);

                // scale factor for this level = sqrt((2^d)^(n+1))
                var scale_factor = sqrt(2.0**(n+1));

                // multiply f1.s[n+1][2*l] and f2.s[n+1][2*l]
                var f = f1.s[n+1, 2*l] * quad_phiT;
                var g = f2.s[n+1, 2*l] * quad_phiT;
                f *= g; // Note: Element-wise multiplication
                s[n+1, 2*l] = (f * quad_phiw) * scale_factor;

                // multiply f1.s[n+1][2*l+1] and f2.s[n+1][2*l+1]
                f  =  f1.s[n+1, 2*l+1] * quad_phiT;
                g  =  f2.s[n+1, 2*l+1] * quad_phiT;
                f *= g;
                s[n+1, 2*l+1] = (f * quad_phiw) * scale_factor;

            } else {
                // if autorefine is not set or we are at the max_level
                // live with what you get
                var f = f1.s[n, l] * quad_phiT;
                var g = f2.s[n, l] * quad_phiT;
                f *= g;

                // scale factor for this level = sqrt((2^d)^(n+1))
                s[n, l] = (f * quad_phiw) * sqrt(2.0**(n));
            }

        } else {
            if f1.s.has_coeffs[n, l] && !f2.s.has_coeffs[n, l] then
                // refine this box down to next level in f1
                f1.recur_down(n, l);
            else if !f1.s.has_coeffs[n, l] && f2.s.has_coeffs[n, l] then
                // refine this box down to next level in f2
                f2.recur_down(n, l);

            // calls on sub-trees can go in parallel
            multiply(f1, f2, n+1, 2*l);
            multiply(f1, f2, n+1, 2*l+1);
        }
    }

    /** Function Multiplication:
        For multiply, both operands need to be in the scaling function basis
        so possibly call reconstruct on one or both of them first
     */
    def multiply(other) {
        if compressed then reconstruct();
        if other.compressed then other.reconstruct();

        // Store the result in a new Function
        var result = skeletonCopy();
        result.multiply(this, other);

        sclean();
        other.sclean();

        return result;
    }

    /** Mostly for debugging, print summary of coefficients,
        optionally printing the norm of each block
     */
    def summarize() {
        writeln("\n-----------------------------------------------------");
        writeln("k=", k, " thresh=", thresh, " compressed=", compressed);
        writeln("sum coefficients:");
        for n in 0..max_level {
            var sum = 0.0, ncoeffs = 0;
            for box in s.lvl_iter(n) {
                sum     += normf(box)**2;
                ncoeffs += 1;
            }
            if ncoeffs != 0 then
                writeln("   level ", toString("%2d", n), "   #boxes=",
                        toString("%4d", ncoeffs), "  norm=", toString("%0.2e", truncate(sqrt(sum))));
        }

        writeln("difference coefficients:");
        for n in 0..max_level {
            var sum = 0.0, ncoeffs = 0;
            for box in d.lvl_iter(n) {
                sum     += normf(box)**2;
                ncoeffs += 1;
            }
            if ncoeffs != 0 then
                writeln("   level ", toString("%2d", n), "   #boxes=",
                        toString("%4d", ncoeffs), "  norm=", toString("%0.2e", truncate(sqrt(sum))));
        }

        writeln("-----------------------------------------------------\n");
    }

    /** Evaluate the analytic and numerical functions over the given interval
        and print the error.
     */
    def evalNPT(npt) {
        for i in 0..npt {
            var (fval, Fval) = (truncate(f(i/npt:real)), truncate(this(i/npt:real)));
            //writeln(" -- ", toString("%0.2f", i/npt:real), ":  F_numeric()=", toString("% 0.5e", Fval),
            //        "  f_analytic()=", toString("% 0.5e", fval), " err=", toString("% 0.5e", Fval-fval),
            //        if abs(Fval-fval) > thresh then "  > thresh" else "");
            writeln(" -- ", toString("%0.2f", i/npt:real), ":  F_numeric()=", toString("% 0.8f", Fval),
                    "  f_analytic()=", toString("% 0.8f", fval), " err=", toString("% 0.1e", Fval-fval),
                    if abs(Fval-fval) > thresh then "  > thresh" else "");
        }
    }
}

/*************************************************************************/
/* Standard operations on Function objects:                              */
/*************************************************************************/

def +(F: Function, G: Function): Function {
    return F.add(G);
}

def -(F: Function, G: Function): Function {
    return F.subtract(G);
}

def *(F: Function, G: Function): Function {
    return F.multiply(G);
}


Lowering number of trials for timeout purposes. Lowering iters to avoid timeouts

config const iters = 200;
config const trials = 500;
config const n = 20;

var D: domain(real);

for i in 1..trials {
  coforall t in 1..n {
    const b = if t & 1 then -1 else 1;
    var r = 1..iters by b;
    for j in r do
      if !D.member((t+j):real) then
        D += (t+j);
  }
  for j in 2..n+iters do assert(D.member(j));
  D.clear();
}

config const iters = 400;
config const trials = 1000;
config const n = 20;

var D: domain(real);

for i in 1..trials {
  coforall t in 1..n {
    const b = if t & 1 then -1 else 1;
    var r = 1..iters by b;
    for j in r do
      if !D.member((t+j):real) then
        D += (t+j);
  }
  for j in 2..n+iters do assert(D.member(j));
  D.clear();
}


Small fix of oversight in my update of this duplicated test.

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

fun f() { return 4; }
var a = => f();
writeln(a());

fun g() {
  fun f() { return 1; }
  return a();
}

fun h() {
  fun f() { return 2; }
  a = => f();
  writeln(g());
}

fun i() {
  fun f() { return 3; }
  a = => f();
  writeln(g());
}

h();
i();

fun f() { return 4; }
var a;
writeln(a());

fun g() {
  fun f() { return 1; }
  return a();
}

fun h() {
  fun f() { return 2; }
  a = => f();
  writeln(g());
}

fun i() {
  fun f() { return 3; }
  a = => f();
  writeln(g());
}

h();
i();


I noticed the leftover/unintentional "eureka" comment in the debug
output and decided to replace it with a reduction.

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

config const n = 10;
config param printOutput = true;
config const printTiming = false;
config param doCommDiag = false;
config param debug = false;

use CyclicDist;
use Time;

const Dcyclic: domain(1) dmapped Cyclic(0) = 0..#numLocales;
const D = [1..n];

var A: [Dcyclic] [D] int(64);

forall a in A do
  a = here.id;

if printOutput then writeln(A);

var st = getCurrentTime();
coforall loc in Locales do on loc {
  const l = (here.id+1)%numLocales;
  var myA: [D] int(64);
  var Aalias => A[l];
  if doCommDiag then startCommDiagnostics();
  myA = Aalias;
  if doCommDiag then stopCommDiagnostics();
  if (debug) {
    writeln(here, ": copying from locale ", l);
    writeln(here, ": ", + reduce [a in myA] a != l, " errors");
  }
  if doCommDiag {
    var Diagnostics = getCommDiagnostics();
    writeln(loc, " : (gets, puts, forks, fast forks, non-blocking forks)");
    for (lid, diagnostics) in (1..,Diagnostics) do
      writeln(loc, " ", lid, ": ", diagnostics);
  }
  if printOutput then writeln(loc, ":\n", myA);
 }
var dt = getCurrentTime()-st;

if printTiming then writeln("Copy to local (", numLocales, " locales): ", dt);

config const n = 10;
config param printOutput = true;
config const printTiming = false;
config param doCommDiag = false;
config param debug = false;

use CyclicDist;
use Time;

const Dcyclic: domain(1) dmapped Cyclic(0) = 0..#numLocales;
const D = [1..n];

var A: [Dcyclic] [D] int(64);

forall a in A do
  a = here.id;

if printOutput then writeln(A);

var st = getCurrentTime();
coforall loc in Locales do on loc {
  const l = (here.id+1)%numLocales;
  var myA: [D] int(64);
  var Aalias => A[l];
  if doCommDiag then startCommDiagnostics();
  myA = Aalias;
  if doCommDiag then stopCommDiagnostics();
  if (debug) {
    writeln(here, ": copying from locale ", l);
    writeln(myA);

    var numErrors = 0;
    for a in myA do // I need a eureka
      if a != l {
        writeln("ERROR: ", a, "!=", l);
        numErrors += 1;
      }
    writeln(here, ": ", numErrors, " errors");
  }
  if doCommDiag {
    var Diagnostics = getCommDiagnostics();
    writeln(loc, " : (gets, puts, forks, fast forks, non-blocking forks)");
    for (lid, diagnostics) in (1..,Diagnostics) do
      writeln(loc, " ", lid, ": ", diagnostics);
  }
  if printOutput then writeln(loc, ":\n", myA);
 }
var dt = getCurrentTime()-st;

if printTiming then writeln("Copy to local (", numLocales, " locales): ", dt);


Added data structure for coarse-fine clusters of cells.

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

use level_base_defs;

//===> AMRHierarchy class ===>
//===========================>
class AMRHierarchy {

  const max_level_number: int;
  const level_numbers = [1..max_level_number];

  var level_list: [level_numbers] BaseLevel;
  var levels: domain(BaseLevel);

  var coarse_interfaces: [levels] LevelInterface;
  var fine_interfaces:   [levels] LevelInterface;

  var boundary_ghosts: [levels] LevelBoundaryDomain;

}
//<=== AMRHierarchy class <===
//<===========================

//===> AMRHierarchy.partitionBoundaryGhosts method ===>
//====================================================>
//------------------------------------------------------------------
// Sets up boundary_ghosts for all grids in the hierarchy.
//------------------------------------------------------------------
def partitionBoundaryGhosts() {

  var boundary_domain: domain(dimension);
  var range_tuple:     dimension*range(stridable=true);

  for level in levels {

    boundary_ghosts(level) = new LevelBoundaryDomain(level = level);

    coforall grid in level.grids {

      //===> Set subdomains by orientation ===>
      for orientation in dimensions {

	//==== Set up off-dimensions ====
	for d in [1..orientation-1] do
	  range_tuple(d) = level.cells.dim(d);

	for d in [orientation+1 .. dimension] do
	  range_tuple(d) = level.ext_cells.dim(d);

	//==== Low end ====
	range_tuple(orientation) = level.ext_cells.low(orientation)
                                   .. level.cells.low(orientation) - 2
                                   by 2;

	boundary_domain = range_tuple;

	boundary_ghosts(level)(grid).low(d) = intersectDomains(low_ghost_cells(d), boundary_domain);

	//==== High end ====
	range_tuple(orientation) = level.cells.high(orientation) + 2
                                   .. level.ext_cells.high(orientation)
                                   by 2;

	boundary_domain = range_tuple;

	boundary_ghosts(level)(grid).high(d) = intersectDomains(high_ghost_cells(d), boundary_domain);
      }
      //<=== Set subdomains by orientation <===

    } // end coforall grid in level.grids

  } // end for level in levels

}
//<=== AMRHierarchy.partitionBoundaryGhosts method <===
//<====================================================

//===> LevelInterface class ===>
//=============================>
class LevelInterface {

  const coarse_level: BaseLevel;
  const fine_level:   BaseLevel;

  const coarse_grids = coarse_level.grids;
  const fine_grids   = fine_level.grids;

  const ref_ratio: dimension*int;

  var f2c_coarse_cells: [coarse_grids] [fine_grids]
                        subdomain(coarse_level.cells);
  var f2c_fine_cells:   [coarse_grids] [fine_grids]
                        subdomain(fine_level.cells);

/*   var c2f_coarse_cells: [coarse_grids] [fine_grids] */
/*                         sparse subdomain(coarse_level.cells); */
/*   var c2f_fine_cells:   [coarse_grids] [fine_grids] */
/*                         sparse subdomain(fine_level.cells); */

  var c2f_clusters: [coarse_grids] [fine_grids] ClusteredCFCells;

  class CFCluster {
    var coarse_cells: sparse subdomain(coarse_level.cells);
    var fine_cells: [coarse_cells] sparse subdomain(fine_level.cells);
  }

}
//<=== LevelInterface class <===
//<=============================

//===> LevelInterface.refineCells method ===>
//==========================================>
//-----------------------------------------------------------------
// Refines a domain of cells using the LevelInterface's ref_ratio.
//-----------------------------------------------------------------
def LevelInterface.refineCells(coarse_cells: subdomain(coarse_level.cells)) {

  //=== Index bounds for refined domain ====
  var fine_cells_low  = coarse_cells.low;
  var fine_cells_high = coarse_cells.high;

  for d in dimensions {
    //==== Move cell centers to vertices ====
    fine_cells_low(d)  -= 1;
    fine_cells_high(d) += 1;

    //==== Refine ====
    fine_cells_low(d)  *= ref_ratio;
    fine_cells_high(d) *= ref_ratio;

    //==== Move vertices back to cell centers ====
    fine_cells_low(d)  += 1;
    fine_cells_high(d) -= 1;
  }

  //==== Set and return new domain ====
  var range_tuple: dimension*range(stridable=true);

  for d in dimensions do
    range_tuple(d) = fine_cells_low(d) .. fine_cells_high(d) by 2;

  var fine_cells: subdomain(fine_level.cells) = range_tuple;
  return fine_cells;

}
//<=== LevelInterface.refineCells method <===
//<==========================================

//===> LevelInterface.refineCell method ===>
//=========================================>
//---------------------------------------------------------------------
// Refine a single coarse cell rather than a domain.  No safety check.
//---------------------------------------------------------------------
def LevelInterface.refineCell(coarse_cell: dimension*int) {

  var fine_cells_low = coarse_cell;
  var fine_cells_high = coarse_cell;

  for d in dimensions {
    //==== Move cell centers to vertices ====
    fine_cells_low(d)  -= 1;
    fine_cells_high(d) += 1;

    //==== Refine ====
    fine_cells_low(d)  *= ref_ratio(d);
    fine_cells_high(d) *= ref_ratio(d);

    //==== Move vertices back to cell centers ====
    fine_cells_low(d)  += 1;
    fine_cells_high(d) -= 1;
  }

  //==== Set and return new domain ====
  var range_tuple: dimension*range(stridable=true);

  for d in dimensions do
    range_tuple(d) = fine_cells_low(d) .. fine_cells_high(d) by 2;

  var fine_cells: subdomain(fine_level.cells) = range_tuple;
  return fine_cells;

}
//<=== LevelInterface.refineCell method <===
//<=========================================

//===> LevelInterface.coarsenCells method ===>
//===========================================>
//------------------------------------------------------------------
// Coarsens a domain of cells using the LevelInterface's ref_ratio.
//------------------------------------------------------------------
def LevelInterface.coarsenCells(fine_cells: subdomain(fine_level.cells)) {

  //=== Index bounds for coarsened domain ====
  var low_coarse  = fine_cells.low;
  var high_coarse = fine_cells.high;

  for d in dimensions do {
    //==== Move cell centers to vertices ====
    low_coarse(d)  -= 1;
    high_coarse(d) += 1;

    //==== Make sure coarsening is valid ====
    assert(low_coarse(d)  % ref_ratio(d) == 0);
    assert(high_coarse(d) % ref_ratio(d) == 0);

    //==== Coarsen ====
    low_coarse(d)  /= ref_ratio(d);
    high_coarse(d) /= ref_ratio(d);

    //==== Move vertices back to cell centers ====
    low_coarse(d)  -= 1;
    high_coarse(d) += 1;
  }

  //==== Set and return new domain ====
  var range_tuple: dimension*range(stridable=true);

  for d in dimensions do
    range_tuple(d) = low_coarse(d) .. coarse(d) by 2;

  var coarse_cells: subdomain(coarse_level.cells) = range_tuple;
  return coarse_cells;

}
//<=== LevelInterface.coarsen method <===
//<======================================

//===> LevelInterface.coarsenCell method ===>
//==========================================>
//--------------------------------------------------
// Coarsen a single fine cell rather than a domain.
//--------------------------------------------------
def LevelInterface.coarsenCell(fine_cell: dimension*int) {

  var coarse_cell: dimension*int;

  //-------------------------------------------------------------
  // Dividing by ref_ratio (and throwing away remainder) maps to
  // lower vertex of the coarse cell.  Adding 1 shifts index to
  // the cell center.
  //-------------------------------------------------------------
  for d in dimensions do
    coarse_cell(d) = fine_cell(d) / ref_ratio(d) + 1;

  return coarse_cell;

}
//<=== LevelInterface.coarsenCell method <===
//<==========================================

//===> LevelInterface.createC2FClusters ===>
//=========================================>
//-----------------------------------------------------------------
// Identifies cells (as associative domains) that will be involved
// in coarse-to-fine interpolation to fill fine ghost cells on the
// way down an AMR cycle.
//-----------------------------------------------------------------
def partitionC2FCells() {

  for coarse_grid in coarse_grids {

    for fine_grid in fine_grids {

      //==== Calculate coarse-fine intersection ====
      var unclustered_fine_cells: sparse subdomain(fine_grid.ext_cells);

      //===> Populate fine_cell_list ===>
      var fine_intersection = intersectDomains(fine_grid.ext_cells,
		                               refineCells(coarse_grid.cells));
      if fine_intersection.numIndices > 0 {

	//==== Add fine ghost cells in the intersection ====
	for cell in fine_grid.ghost_cells do
	  if fine_intersection.member(cell) then
	    unclustered_fine_cells.add(cell);

	//==== Remove any fine_level.shared_ghosts ====
	for sib in fine_grids {
	  for cell in fine_level.shared_ghosts(fine_grid)(sib) do
	    if unclustered_fine_cells.member(cell) then
	      unclustered_fine_cells.remove(cell);
	}

      }
      //<=== Populate fine_cell_list <===

      //==== Create coarse-fine cluster ===
      var cluster = new CFCluster();
      for fine_cell in unclustered_fine_cells {
	var coarse_cell = coarsen(fine_cell);

	//==== Chapel should filter out duplicates... ====
	cluster.coarse_cells.add(coarse_cell);

	cluster.fine_cells(coarse_cell).add(fine_cell);
      }

      //==== Place reference in c2f cluster array ====
      c2f_clusters(coarse_grid)(fine_grid) = cluster;

    } // end for fine_grid in fine_grids

  } // end for coarse_grid in coarse_grids

}
//<=== LevelInterface.createC2FClusters <===
//<=========================================

//===> LevelInterface.partitionF2CCells method ===>
//================================================>
//---------------------------------------------------------------------
// Identifies cells (as arithmetic domains) that will be involved in
// fine-to-coarse averaging on the way up an AMR cycle.
//---------------------------------------------------------------------
def LevelInterface.partitionF2CCells() {

  for coarse_grid in coarse_grids {
    for fine_grid in fine_grids {

      f2c_coarse_cells(coarse_grid)(fine_grid)
          = intersectDomains(coarse_grid.cells,
                             coarsenCells(fine_grid.cells));

      f2c_fine_cells(coarse_grid)(fine_grid)
          = refineCells( f2c_coarse_cells(coarse_grid)(fine_grid) );

    }
  }

}
//<=== LevelInterface.partitionF2CCells method <===
//<================================================

//===> LevelInterface.C2FInterpolator method ===>
//==============================================>
//---------------------------------------------------------------------
// Interpolates data from q_coarse into the appropriate ghost cells of
// q_fine, using a minmod-type method for limiting.  (Same method as
// in the Chombo spec, but a bit more descriptive.)
//---------------------------------------------------------------------
def LevelInterface.C2FInterpolator(
  q_coarse: LevelArray,
  q_fine:   LevelArray,)
{

  for coarse_grid in coarse_grids {
    for fine_grid in fine_grids {

      var cluster = c2f_clusters(coarse_grid)(fine_grid);
      var q_coarse_grid = q_coarse(coarse_grid);

      //==== Compute differentials on the coarse grid's locale ====
      var coarse_values = q_coarse_grid(cluster.coarse_cells);
      var coarse_diffs  = minmodDiffs(q_coarse_grid, cluster.coarse_cells);

      // Send coarse_values and coarse_diffs to the fine grid's locale

      //==== Evaluate the interpolant on refined image of each coarse cell ====
      forall coarse_cell in cluster.coarse_cells {
	for fine_cell in cluster.fine_cells(coarse_cell) {

	  var fine_displacement: real;
	  var fine_value = coarse_values(coarse_cell);

	  for d in dimensions {
	    //==== Move to coarse indices ====
	    fine_displacement = fine_cell(d):real / ref_ratio(d):real;

	    //==== Compute displacement ====
	    fine_displacement -= coarse_cell(d):real;

	    //==== Rescale: One cell occupies 2 indices ====
	    fine_displacement /= 2.0;

	    //==== Modify fine_value ====
	    fine_value += fine_displacement * coarse_diffs(coarse_cell)(d);
	  }

	  //==== Store fine_value ====
	  q_fine(fine_grid)(fine_cell) = fine_value;

	}
      }

    } // end for fine_grid in fine_grids
  } // end for coarse_grid in coarse_grids

}
//<=== LevelInterface.C2FInterpolator method <===
//<==============================================

// Next up: create minmodDiffs method

//===> LevelInterface.minmodDiffs ===>
//===================================>
//----------------------------------------------------
//
//
def LevelInterface.minmodDiffs(
  q:     GridArray,
  cells: subdomain(coarse_level.cells))
{

  var differentials: [cells] [dimensions] real;

  forall cell in cells {

    //===> Maximum allowed differential ===>
    var max_rise = 0.0,
      max_drop = 0.0;

    //==== First compute the max and min values in the neighborhood ====
    for nbr in neighborhood(cell,1) {
      max_rise = max( max_rise, sol.value(nbr) );
      max_drop = min( max_drop, sol.value(nbr) );
    }

    //==== Now adjust accordingly to get the diffs ====
    max_rise = max_rise - sol.value(cell);
    max_drop = sol.value(cell) - max_drop;
    var max_allowed_diff = min(max_rise, max_drop);
    //<=== Maximum allowed differential <===

    //==== Estimate differentials with centered differences ====
    for d in dimensions {
      var shift: dimension*int;
      shift(d) = 2;
      differentials(cell)(d) = (  q.value(cell + shift)
		                - q.value(cell - shift)
		               ) / 2.0;
    }

    //==== Maximum of centered differentials ====
    var max_centered_diff = 0.0;
    for d in dimensions do
      max_centered_diff = max(max_centered_diff,
                              abs(differentials(cell)(d)));

    //==== Compute limiter ====
    var limiter =
      if max_centered_diff > max_allowed_diff then
        max_allowed_diff / max_centered_diff
      else
	1;

    //==== Limit the estimated differentials ====
    differentials(cell) *= limiter;

  }

}
//<=== LevelInterface.minmodDiffs <===
//<===================================

use level_base_defs;

//===> AMRHierarchy class ===>
//===========================>
class AMRHierarchy {

  const max_level_number: int;
  const level_numbers = [1..max_level_number];

  var level_list: [level_numbers] BaseLevel;
  var levels: domain(BaseLevel);

  var coarse_interfaces: [levels] LevelInterface;
  var fine_interfaces:   [levels] LevelInterface;

  var boundary_ghosts: [levels] LevelBoundaryDomain;

}
//<=== AMRHierarchy class <===
//<===========================

//===> AMRHierarchy.partitionBoundaryGhosts method ===>
//====================================================>
//------------------------------------------------------------------
// Sets up boundary_ghosts for all grids in the hierarchy.
//------------------------------------------------------------------
def partitionBoundaryGhosts() {

  var boundary_domain: domain(dimension);
  var range_tuple:     dimension*range(stridable=true);

  for level in levels {

    boundary_ghosts(level) = new LevelBoundaryDomain(level = level);

    coforall grid in level.grids {

      //===> Set subdomains by orientation ===>
      for orientation in dimensions {

	//==== Set up off-dimensions ====
	for d in [1..orientation-1] do
	  range_tuple(d) = level.cells.dim(d);

	for d in [orientation+1 .. dimension] do
	  range_tuple(d) = level.ext_cells.dim(d);

	//==== Low end ====
	range_tuple(orientation) = level.ext_cells.low(orientation)
                                   .. level.cells.low(orientation) - 2
                                   by 2;

	boundary_domain = range_tuple;

	boundary_ghosts(level)(grid).low(d) = intersectDomains(low_ghost_cells(d), boundary_domain);

	//==== High end ====
	range_tuple(orientation) = level.cells.high(orientation) + 2
                                   .. level.ext_cells.high(orientation)
                                   by 2;

	boundary_domain = range_tuple;

	boundary_ghosts(level)(grid).high(d) = intersectDomains(high_ghost_cells(d), boundary_domain);
      }
      //<=== Set subdomains by orientation <===

    } // end coforall grid in level.grids

  } // end for level in levels

}
//<=== AMRHierarchy.partitionBoundaryGhosts method <===
//<====================================================

//===> LevelInterface class ===>
//=============================>
class LevelInterface {

  const coarse_level: BaseLevel;
  const fine_level:   BaseLevel;

  const ref_ratio: dimension*int;

  var f2c_interp_cells: [coarse_level.grids]
                        [fine_level.grids]
                        subdomain(coarse_level.cells);

  var c2f_interp_cells: [fine_level.grids]
                        [coarse_level.grids]
                        sparse subdomain(fine_level.cells);

}
//<=== LevelInterface class <===
//<=============================

//===> LevelInterface.refineCells method ===>
//==========================================>
//-----------------------------------------------------------------
// Refines a domain of cells using the LevelInterface's ref_ratio.
//-----------------------------------------------------------------
def LevelInterface.refineCells(coarse_cells: subdomain(coarse_level.cells)) {

  //=== Index bounds for refined domain ====
  var low_fine  = coarse_cells.low;
  var high_fine = coarsecells.high;

  for d in dimensions {
    //==== Move cell centers to vertices ====
    low_fine(d)  -= 1;
    high_fine(d) += 1;

    //==== Refine ====
    low_fine(d)  *= ref_ratio;
    high_fine(d) *= ref_ratio;

    //==== Move vertices back to cell centers ====
    low_fine(d)  += 1;
    high_fine(d) -= 1;
  }

  //==== Set and return new domain ====
  var range_tuple: dimension*range(stridable=true);

  for d in dimensions do
    range_tuple(d) = low_fine(d) .. high_fine(d) by 2;

  var fine_cells: subdomain(fine_level.cells) = range_tuple;
  return fine_cells;

}
//<=== LevelInterface.refineCells method <===
//<==========================================

//===> LevelInterface.coarsenCells method ===>
//===========================================>
//------------------------------------------------------------------
// Coarsens a domain of cells using the LevelInterface's ref_ratio.
//------------------------------------------------------------------
def LevelInterface.coarsenCells(fine_cells: subdomain(fine_level.cells)) {

  //=== Index bounds for coarsened domain ====
  var low_coarse  = fine_cells.low;
  var high_coarse = fine_cells.high;

  for d in dimensions do {
    //==== Move cell centers to vertices ====
    low_coarse(d)  -= 1;
    high_coarse(d) += 1;

    //==== Make sure coarsening is valid ====
    assert(low_coarse(d)  % ref_ratio(d) == 0);
    assert(high_coarse(d) % ref_ratio(d) == 0);

    //==== Coarsen ====
    low_coarse(d)  /= ref_ratio(d);
    high_coarse(d) /= ref_ratio(d);

    //==== Move vertices back to cell centers ====
    low_coarse(d)  -= 1;
    high_coarse(d) += 1;
  }

  //==== Set and return new domain ====
  var range_tuple: dimension*range(stridable=true);

  for d in dimensions do
    range_tuple(d) = low_coarse(d) .. coarse(d) by 2;

  var coarse_cells: subdomain(coarse_level.cells) = range_tuple;
  return coarse_cells;

}
//<=== LevelInterface.coarsen method <===
//<======================================

//===> LevelInterface.partitionC2FInterpCells ===>
//===============================================>
//----------------------------------------------------------------
// Identifies fine ghost cells on which function values should be
// interpolated from the coarse grid.
//----------------------------------------------------------------
def partitionC2FInterpCells() {

  for fine_grid in fine_level.grids {

    //==== Nicknamed pointer, for convenience ====
    var interp_cells => c2f_interp_cells(fine_grid);

    for coarse_grid in coarse_level.grids {

      //==== Calculate coarse-fine intersection ====
      var cf_intersection = intersectDomains(fine_grid.ext_cells,
		                             refineCells(coarse_grid.cells));

      //===> Populate interp_cells(coarse_grid) ===>
      if cf_intersection.numIndices > 0 {

	//==== Add all ghost cells in coarse-fine intersection to the temp list ====
	for cell in fine_grid.ghost_cells do
	  if cf_intersection.member(cell) then
	    interp_cells(coarse_grid).add(cell);

	//==== Remove any fine_level.shared_ghosts from the temp list ====
	for sib in fine_level.grids {
	  for cell in fine_level.shared_ghosts(fine_grid)(sib) do
	    if interp_cells(coarse_grid).member(cell) then
	      interp_cells(coarse_grid).remove(cell);
	}

      }
      //<=== Populate interp_cells(coarse_grid) <===

    } // end for coarse_grid in coarse_level.grids

  } // end for fine_grid in fine_level.grids

}
//<=== LevelInterface.partitionC2FInterpCells method <===
//<======================================================

//===> LevelInterface.partitionF2CInterpCells method ===>
//======================================================>
//---------------------------------------------------------------------
// Identifies coarse cells which are shadowed by fine cells, and thus
// should have their values overwritten by fine grid values on the way
// back up an AMR cycle.
//---------------------------------------------------------------------
def LevelInterface.partitionF2CInterpCells() {

  for coarse_grid in coarse_level.grids {
    for fine_grid in fine_level.grids {

      f2c_interp_cells(coarse_grid)(fine_grid)
          = intersectDomains(coarse_grid.cells,
                             coarsenCells(fine_grid.cells));

    }
  }

}
//<=== LevelInterface.partitionF2CInterpCells method <===
//<======================================================

//===> LevelInterface.C2FInterpolator method ===>
//==============================================>
//---------------------------------------------------------------------
// Interpolates data from q_coarse into the appropriate ghost cells of
// q_fine, using a minmod-type method for limiting.  (Same method as
// in the Chombo spec, but a bit more descriptive.)
//---------------------------------------------------------------------
def LevelInterface.C2FInterpolator(
  q_coarse: LevelArray,
  q_fine:   LevelArray,)
{

  coforall fine_grid in fine_level.grids {
    for coarse_grid in coarse_level.grids {

      var c2f_preimage = c2f_coarse_cells(fine_grid)(coarse_grid);

      // Compute differentials on the coarse grid's locale
      var coarse_values = q_coarse(coarse_grid).value(c2f_preimage);
      var coarse_diffs  = minmodDiffs(q_coarse, c2f_preimage);

      // Send to the fine grid's locale

      // Evaluate the interpolant on refined image of each coarse cell
      forall coarse_cell in c2f_preimage {
	for fine_cell in refineCell(coarse_cell) {
	  var fine_disp: real;
	  var fine_value = coarse_values(coarse_cell);

	  for d in dimensions {
	    fine_disp = fine_cell(d) / ref_ratio(d);
	    fine_disp -= coarse_cell(d);
	    fine_disp *= 0.5;

	    fine_value += fine_disp * coarse_diffs(coarse_cell)(d);
	  }

	  q_fine(fine_grid)(fine_cell) = fine_value;
	}
      }

    }
  }

}
//<=== LevelInterface.C2FInterpolator method <===
//<==============================================

// Next up: change c2f storage; create minmodDiffs method; refineCell method