Fixed a typo in copy()

Fixed a typo in copy()

git-svn-id: 88467cb1fb04b8a755be7e1ee1026be4190196ef@11548 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];
    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..i, ..] = p;            // FIXME: quad_phi [i, ..] = p;
            quad_phiw[i..i, ..] = quad_w[i] * p;//        quad_phiw[i, ..] = ...;
            quad_phiT[.., i..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);
    }

    /** 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", 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", 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) = (f(i/npt:real), 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..i, ..] = p;            // FIXME: quad_phi [i, ..] = p;
            quad_phiw[i..i, ..] = quad_w[i] * p;//        quad_phiw[i, ..] = ...;
            quad_phiT[.., i..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.copy(), 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);
    }

    /** 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", 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", 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) = (f(i/npt:real), 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);
}


Removed member method from BaseDist.  This was written to take an
index argument of any type.  It halted with the message 'membership
test not supported for this domain type'.  The generic nature of this
method, however, made it so that promotion would not work with the
member method, instead resulting in this error.

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

use List;

config const minElemsPerChunk: uint(64) = 1;
config const maxChunks: int = -1;

//
// Abstract distribution class
//
class BaseDist {
  var _distCnt$: sync int = 0; // distribution reference count and lock
  var _doms: list(BaseDom);    // arrays declared over this domain

  pragma "dont disable remote value forwarding"
  def destroyDist(dom: BaseDom = nil) {
    var cnt = _distCnt$ - 1;
    if cnt < 0 then
      halt("distribution reference count is negative!");
    if dom then
      on dom do
        _doms.remove(dom);
    _distCnt$ = cnt;
    return cnt;
  }

  def newArithmeticDom(param rank: int, type idxType, param stridable: bool) {
    compilerError("arithmetic domains not supported by this distribution");
  }

  def newAssociativeDom(type idxType) {
    compilerError("associative domains not supported by this distribution");
  }

  def newAssociativeDom(type idxType) where __primitive("isEnumType", idxType) {
    compilerError("enumerated domains not supported by this distribution");
  }

  def newOpaqueDom(type idxType) {
    compilerError("opaque domains not supported by this distribution");
  }

  def newSparseDom(param rank: int, type idxType, dom: domain) {
    compilerError("sparse domains not supported by this distribution");
  }

  def supportsPrivatization() param return false;
  def requiresPrivatization() param return false;

  def destroyDistributionDescriptor() { }
}

//
// Abstract domain classes
//
class BaseDom {
  var _domCnt$: sync int = 0; // domain reference count and lock
  var _arrs: list(BaseArr);   // arrays declared over this domain

  def getBaseDist(): BaseDist {
    return nil;
  }

  pragma "dont disable remote value forwarding"
  def destroyDom(arr: BaseArr = nil) {
    var cnt = _domCnt$ - 1;
    if cnt < 0 then
      halt("domain reference count is negative!");
    if arr then
      on arr do
        _arrs.remove(arr);
    _domCnt$ = cnt;
    if cnt == 0 {
      var dist = getBaseDist();
      if dist then on dist {
        var cnt = dist.destroyDist(this);
        if cnt == 0 then
          delete dist;
      }
    }
    return cnt;
  }

  // used for associative domains/arrays
  def _backupArrays() {
    for arr in _arrs do
      arr._backupArray();
  }

  def _removeArrayBackups() {
    for arr in _arrs do
      arr._removeArrayBackup();
  }

  def _preserveArrayElement(oldslot, newslot) {
    for arr in _arrs do
      arr._preserveArrayElement(oldslot, newslot);
  }

  def supportsPrivatization() param return false;
  def requiresPrivatization() param return false;

  // false for default distribution so that we don't increment the
  // default distribution's reference count and add domains to the
  // default distribution's list of domains
  def linksDistribution() param return true;
}

class BaseArithmeticDom : BaseDom {
  def clear() {
    halt("clear not implemented for this distribution");
  }

  def clearForIteratableAssign() {
    compilerError("Illegal assignment to an arithmetic domain");
  }

  def add(x) {
    compilerError("Cannot add indices to an arithmetic domain");
  }
}

class BaseSparseDom : BaseDom {
  def clear() {
    halt("clear not implemented for this distribution");
  }

  def clearForIteratableAssign() {
    clear();
  }
}

class BaseAssociativeDom : BaseDom {
  def clear() {
    halt("clear not implemented for this distribution");
  }

  def clearForIteratableAssign() {
    clear();
  }
}

class BaseOpaqueDom : BaseDom {
  def clear() {
    halt("clear not implemented for this distribution");
  }

  def clearForIteratableAssign() {
    clear();
  }
}

//
// Abstract array class
//
pragma "base array"
class BaseArr {
  var _arrCnt$: sync int = 0; // array reference count (and eventually lock)
  var _arrAlias: BaseArr;     // reference to base array if an alias

  def canCopyFromDevice param return false;
  def canCopyFromHost param return false;

  def getBaseDom(): BaseDom {
    return nil;
  }

  pragma "dont disable remote value forwarding"
  def destroyArr(): int {
    var cnt = _arrCnt$ - 1;
    if cnt < 0 then
      halt("array reference count is negative!");
    _arrCnt$ = cnt;
    if cnt == 0 {
      if _arrAlias {
        on _arrAlias {
          var cnt = _arrAlias.destroyArr();
          if cnt == 0 then
            delete _arrAlias;
        }
      } else {
        destroyData();
      }
      var dom = getBaseDom();
      on dom {
        var cnt = dom.destroyDom(this);
        if cnt == 0 then
          delete dom;
      }
    }
    return cnt;
  }

  def destroyData() { }

  def reallocate(d: domain) {
    halt("reallocating not supported for this array type");
  }

  // This method is unsatisfactory -- see bradc's commit entries of
  // 01/02/08 around 14:30 for details
  def _purge( ind: int) {
    halt("purging not supported for this array type");
  }

  def _resize( length: int, old_map) {
    halt("resizing not supported for this array type");
  }

  def sparseShiftArray(shiftrange, initrange) {
    halt("sparseGrowDomain not supported for non-sparse arrays");
  }

  // methods for associative arrays
  def _backupArray() {
    halt("_backupArray() not supported for non-associative arrays");
  }

  def _removeArrayBackup() {
    halt("_removeArrayBackup() not supported for non-associative arrays");
  }

  def _preserveArrayElement(oldslot, newslot) {
    halt("_preserveArrayElement() not supported for non-associative arrays");
  }

  def supportsAlignedFollower() param return false;

  def supportsPrivatization() param return false;
  def requiresPrivatization() param return false;
}

use List;

config const minElemsPerChunk: uint(64) = 1;
config const maxChunks: int = -1;

//
// Abstract distribution class
//
class BaseDist {
  var _distCnt$: sync int = 0; // distribution reference count and lock
  var _doms: list(BaseDom);    // arrays declared over this domain

  pragma "dont disable remote value forwarding"
  def destroyDist(dom: BaseDom = nil) {
    var cnt = _distCnt$ - 1;
    if cnt < 0 then
      halt("distribution reference count is negative!");
    if dom then
      on dom do
        _doms.remove(dom);
    _distCnt$ = cnt;
    return cnt;
  }

  def newArithmeticDom(param rank: int, type idxType, param stridable: bool) {
    compilerError("arithmetic domains not supported by this distribution");
  }

  def newAssociativeDom(type idxType) {
    compilerError("associative domains not supported by this distribution");
  }

  def newAssociativeDom(type idxType) where __primitive("isEnumType", idxType) {
    compilerError("enumerated domains not supported by this distribution");
  }

  def newOpaqueDom(type idxType) {
    compilerError("opaque domains not supported by this distribution");
  }

  def newSparseDom(param rank: int, type idxType, dom: domain) {
    compilerError("sparse domains not supported by this distribution");
  }

  def supportsPrivatization() param return false;
  def requiresPrivatization() param return false;

  def destroyDistributionDescriptor() { }
}

//
// Abstract domain classes
//
class BaseDom {
  var _domCnt$: sync int = 0; // domain reference count and lock
  var _arrs: list(BaseArr);   // arrays declared over this domain

  def getBaseDist(): BaseDist {
    return nil;
  }

  pragma "dont disable remote value forwarding"
  def destroyDom(arr: BaseArr = nil) {
    var cnt = _domCnt$ - 1;
    if cnt < 0 then
      halt("domain reference count is negative!");
    if arr then
      on arr do
        _arrs.remove(arr);
    _domCnt$ = cnt;
    if cnt == 0 {
      var dist = getBaseDist();
      if dist then on dist {
        var cnt = dist.destroyDist(this);
        if cnt == 0 then
          delete dist;
      }
    }
    return cnt;
  }

  def member(ind) : bool {
    halt("membership test not supported for this domain type");
    return false;
  }

  // used for associative domains/arrays
  def _backupArrays() {
    for arr in _arrs do
      arr._backupArray();
  }

  def _removeArrayBackups() {
    for arr in _arrs do
      arr._removeArrayBackup();
  }

  def _preserveArrayElement(oldslot, newslot) {
    for arr in _arrs do
      arr._preserveArrayElement(oldslot, newslot);
  }

  def supportsPrivatization() param return false;
  def requiresPrivatization() param return false;

  // false for default distribution so that we don't increment the
  // default distribution's reference count and add domains to the
  // default distribution's list of domains
  def linksDistribution() param return true;
}

class BaseArithmeticDom : BaseDom {
  def clear() {
    halt("clear not implemented for this distribution");
  }

  def clearForIteratableAssign() {
    compilerError("Illegal assignment to an arithmetic domain");
  }

  def add(x) {
    compilerError("Cannot add indices to an arithmetic domain");
  }
}

class BaseSparseDom : BaseDom {
  def clear() {
    halt("clear not implemented for this distribution");
  }

  def clearForIteratableAssign() {
    clear();
  }
}

class BaseAssociativeDom : BaseDom {
  def clear() {
    halt("clear not implemented for this distribution");
  }

  def clearForIteratableAssign() {
    clear();
  }
}

class BaseOpaqueDom : BaseDom {
  def clear() {
    halt("clear not implemented for this distribution");
  }

  def clearForIteratableAssign() {
    clear();
  }
}

//
// Abstract array class
//
pragma "base array"
class BaseArr {
  var _arrCnt$: sync int = 0; // array reference count (and eventually lock)
  var _arrAlias: BaseArr;     // reference to base array if an alias

  def canCopyFromDevice param return false;
  def canCopyFromHost param return false;

  def getBaseDom(): BaseDom {
    return nil;
  }

  pragma "dont disable remote value forwarding"
  def destroyArr(): int {
    var cnt = _arrCnt$ - 1;
    if cnt < 0 then
      halt("array reference count is negative!");
    _arrCnt$ = cnt;
    if cnt == 0 {
      if _arrAlias {
        on _arrAlias {
          var cnt = _arrAlias.destroyArr();
          if cnt == 0 then
            delete _arrAlias;
        }
      } else {
        destroyData();
      }
      var dom = getBaseDom();
      on dom {
        var cnt = dom.destroyDom(this);
        if cnt == 0 then
          delete dom;
      }
    }
    return cnt;
  }

  def destroyData() { }

  def reallocate(d: domain) {
    halt("reallocating not supported for this array type");
  }

  // This method is unsatisfactory -- see bradc's commit entries of
  // 01/02/08 around 14:30 for details
  def _purge( ind: int) {
    halt("purging not supported for this array type");
  }

  def _resize( length: int, old_map) {
    halt("resizing not supported for this array type");
  }

  def sparseShiftArray(shiftrange, initrange) {
    halt("sparseGrowDomain not supported for non-sparse arrays");
  }

  // methods for associative arrays
  def _backupArray() {
    halt("_backupArray() not supported for non-associative arrays");
  }

  def _removeArrayBackup() {
    halt("_removeArrayBackup() not supported for non-associative arrays");
  }

  def _preserveArrayElement(oldslot, newslot) {
    halt("_preserveArrayElement() not supported for non-associative arrays");
  }

  def supportsAlignedFollower() param return false;

  def supportsPrivatization() param return false;
  def requiresPrivatization() param return false;
}


I studied the update steps of the blockLU algorithm to determine which
blocks of the matrix need to be communicated to which other processors.
I rewrote some of the loops to better reflect this understanding.  (These
loops should not necessarily remain implemented like this - it was just
a learning tool.)

I added comments to explain the communication needs of the update.

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

/* This file contains a right-looking block-LU solver function named
 * rightBlockLU() along with its supporting functions and iterators,
 * and a solve routine that takes the LU factors to solve Ax=b.
 *
 * This rightBlockLU function is written for a single locale.  It uses an
 * iterator to define blocks of indices that make expressing the block
 * algorithm easier and less error-prone.  Array aliases are defined
 * with these block indices at each iteration, so that the submatrices
 * involved in the block factorization and in the subsequent updates
 * are easily referenced and modified.
 */

// rightBlockLU: Computes block-LU with pivoting.  The arguments are as follows:
//   A: the input array to be factored.  It must be 2D, square, and
//        its dimensions must be defined using identical ranges
//
//   blk: the blocking factor, which must be positive and less than or
//        equal to A's size.
//
//   piv: a vector used to store and output the permutations performed
//        by pivoting operations

def rightBlockLU(A: [?D], blk, piv: [D.dim(1)]) where (D.rank == 2) {

  // Need to modify routine to factor rectangular system:  [A | b].
  // Also, don't pivot rows of L or return pivot vector.

  // Test that the domain of A is square with the same index set for
  // each dimension.
  if (D.dim(1) != D.dim(2)) then
    halt("blockLU requires square matrix with same dimensions");

  // Test that 0 < blk <= n, where n = length of one dimension of A.
  if (blk <= 0) || (blk > D.dim(1).length) then
    halt(blk," is an invalid block size passed to blockLU");

  [i in D.dim(1)] piv(i) = i;    // initialize the pivot vector

  // Main loop of block LU uses an iterator to compute three sets of
  // index ranges -- those that are unfactored, divided into those
  // currently being factored and those that remain.

  for (UnfactoredInds, CurrentPanelInds, TrailingInds)
        in generateBlockLURanges(D, blk) {

    // Create array aliases for various submatrices of current block
    // iteration as follows:
    //
    //     |Factored    |   |Factored   |   |Factored   |
    //     |  _ _ _ _ _ |   |    _ _ _ _|   |    _ _ _ _|
    // A = | |Unfactored| = |   |A1 | A2| = |   |A11 A12|
    //     | |          |   |   |   |   |   |   |A21 A22|
    //
    // where A1 = |A11|
    //            |A21| is the current rectangular block that
    //                  is factored in this iteration
    //
    //       A2 = |A12|
    //            |A22| is updated after the LU factorization
    //                  of A1

    var A1  => A[UnfactoredInds, CurrentPanelInds],
        A2  => A[UnfactoredInds, TrailingInds],
        A11 => A[CurrentPanelInds, CurrentPanelInds],
        A21 => A[TrailingInds, CurrentPanelInds],
        A12 => A[CurrentPanelInds, TrailingInds],
        A22 => A[TrailingInds, TrailingInds];

    // First compute the LU factorization of A1...
    //
    // We loop over the rows of A11, but we eliminate the
    // the full rectangular A1 block.

    // This is the panel factorization.  Need to add alternative
    // factorization options.  Also, need to add pipelining of
    // panel factorization.  Instead of CurrentPanelInds, have
    // CurrentPanelInds and NextPanelInds for a lookahead of one
    // (or is that two? need to check).
    for k in CurrentPanelInds {

      // Find pivot for kth column:
      //   identify largest magnitude element in kth column
      //   (pivot), from the diagonal element downward
      //   through all rows of A1

      // Was not able to replace k..k with k here.  Why?
      const pivotRow = computePivotRow(A1[k.., k..k]),
            pivot = A1[pivotRow, k];

      // If kth row is not row of pivot, swap rows:
      if (pivotRow != k) {

        // store row permutation in piv array
        piv(k) <=> piv(pivotRow);

        // swap values in rows k and pivotRow of the full
        // matrix A
        //** swap only in panel and save remainder of swaps for
        // for update later?
        A[k, ..] <=> A[pivotRow, ..];
      }

      // If pivot is zero, halt.  Matrix is singular and
      // cannot be factored.
      if (pivot == 0) then halt("zero pivot encountered");

      // Compute the k-th elimination step:
      //   store multipliers...
      A1[k+1.., k] /= pivot;

      //   ..and subtract scaled kth row from remaining
      //   unfactored rows of A1

      forall (i,j) in [UnfactoredInds(k+1..), CurrentPanelInds(k+1..)] do
        A1(i,j) -= A1(i,k) * A1(k,j);
    }

    // ... and now update A2.
    // First update A12.

    // Add loop to update NextPanel first so that lookahead factorization
    // could occur.  Maybe use sync vars to do this?

    // Communication of A1 to other processors occurs before this
    // step.  There are options for communication that need to be reflected
    // here or in distribution.
    // A1 (nb x nb) is broadcast across processors in process row.
    forall columnblk in blkIter(TrailingInds,blk) {
      var Ublk => A12[CurrentPanelInds,columnblk];
      forall j in columnblk do
        for k in CurrentPanelInds do
          forall i in CurrentPanelInds(k+1..) do
            Ublk(i,j) -= A1(i,k) * Ublk(k,j);
    }

    // And then update A22 -= A21*A12.
    //
    // All process columns and rows are involved in this step.
    // Blocks of L (nb x nb blocks of A21) are broadcast across
    //   processors in process row.
    // Blocks of U (nb x nb blocks of A21) are broadcast across
    //   processors in process column.
    // Computation occurs on processor that contains Ablk (nb x nb).
    for (rowblk, columnblk) in blkIter2D(TrailingInds,blk) {
      var Lblk => A21[rowblk,CurrentPanelInds];
      var Ublk => A12[CurrentPanelInds,columnblk];
      var Ablk => A22[rowblk,columnblk];
      for (i,j) in [rowblk, columnblk] do
        for k in CurrentPanelInds do
          Ablk(i,j) -= Lblk(i,k) * Ublk(k,j);
      }
    }

}

// The generateBlockLURanges iterator returns a 3-tuple of
// the ranges to be used in each iteration of block LU:
//   (UnfactoredInds, CurrentPanelInds, TrailingInds).
// By defining these ranges, the blockLU code is cleaner. It
// eliminates the need to test for the last iteration where a full-sized
// block may not be possible and only the factorization step (and not
// the update step) is required.
//
// The range UnfactoredInds is equivalent to the range
// CurrentPanelInds concatenated with the range TrailingInds.
//
// The range CurrentPanelInds has the length of blksize, except
// for the end case when the last block may not be of full size.
// This iterator takes care of this end case so that testing for
//  it is not necessary in the blockLU code itself.
//
// The range TrailingInds will be an empty range for the
// the last block iteration.  When it is empty, the loops in
// the blockLU code will not execute, so testing for this case is
// not necessary.

def generateBlockLURanges(D:domain(2), blksize) {
  const end = D.dim(1).high;

  for i in D.dim(1) by blksize {
    const hi = min(i + blksize-1, end);
    yield (i..end, i..hi, hi+1..end);
  }
}

def blkIter(indRange, blksize) {
  const end = indRange.high;

  for i in indRange by blksize {
    const hi = min(i+blksize-1, end);
    yield i..hi;
  }
}

def blkIter2D(indRange, blksize) {
  const end = indRange.high;

  for i in indRange by blksize {
    const rowhi = min(i+blksize-1, end);
    for j in indRange by blksize {
      const colhi = min(j+blksize-1, end);
      yield (i..rowhi, j..colhi);
    }
  }
}

// The MMIterator generates indices for a matrix multiplication loop.
// This iterator can be tuned for loop nesting order or loop unrolling,
// isolating these optimizations from the blockLU code (or any other
// code in a complete linear algebra library that uses matrix-matrix
// multiplication).

def MMIterator(D1, D2) {
  for j in D2.dim(2) do
    for (k1, k2) in (D1.dim(2), D2.dim(1)) do
      for i in D1.dim(1) do
        yield (i,j,k1,k2);
}

// This function computes the pivot row for an LU pivoting operation
// by using Chapel's maxloc reduction on the absolute values in the
// matrix, dropping the maximum value on the floor using the
// underscore operator, and returning the row of the resulting index.

def computePivotRow(A:[?D]) {
   const (_, ind) = maxloc reduce (abs(A), D);
   return ind(1);
}

def LUSolve (A: [?ADom], x: [?xDom], b: [?bDom], piv) {

   var n = piv.numElements;
   var AD1 = ADom.dim(1);

   x = b(piv);
   for j in 1..n-1 {
     for (i1, i2) in (xDom(j+1..n), AD1(j+1..n)) {
        x(i1) -= x(j)*A(i2,j);
     }
   }

   for j in (2..n) by -1 {
     x(j) /= A(j,j);
     for (i1, i2) in (xDom(1..j-1), AD1(1..j-1)) {
       x(i1) -= x(j)*A(i2, j);
     }
   }

   x(1) /= A(1,1);

}

/* This file contains a right-looking block-LU solver function named
 * rightBlockLU() along with its supporting functions and iterators,
 * and a solve routine that takes the LU factors to solve Ax=b.
 *
 * This rightBlockLU function is written for a single locale.  It uses an
 * iterator to define blocks of indices that make expressing the block
 * algorithm easier and less error-prone.  Array aliases are defined
 * with these block indices at each iteration, so that the submatrices
 * involved in the block factorization and in the subsequent updates
 * are easily referenced and modified.
 */

// rightBlockLU: Computes block-LU with pivoting.  The arguments are as follows:
//   A: the input array to be factored.  It must be 2D, square, and
//        its dimensions must be defined using identical ranges
//
//   blk: the blocking factor, which must be positive and less than or
//        equal to A's size.
//
//   piv: a vector used to store and output the permutations performed
//        by pivoting operations

def rightBlockLU(A: [?D], blk, piv: [D.dim(1)]) where (D.rank == 2) {

  // Need to modify routine to factor rectangular system:  [A | b].
  // Also, don't pivot rows of L or return pivot vector.

  // Test that the domain of A is square with the same index set for
  // each dimension.
  if (D.dim(1) != D.dim(2)) then
    halt("blockLU requires square matrix with same dimensions");

  // Test that 0 < blk <= n, where n = length of one dimension of A.
  if (blk <= 0) || (blk > D.dim(1).length) then
    halt(blk," is an invalid block size passed to blockLU");

  [i in D.dim(1)] piv(i) = i;    // initialize the pivot vector

  // Main loop of block LU uses an iterator to compute three sets of
  // index ranges -- those that are unfactored, divided into those
  // currently being factored and those that remain.

  for (UnfactoredInds, CurrentBlockInds, TrailingBlockInds)
        in generateBlockLURanges(D, blk) {

    // Create array aliases for various submatrices of current block
    // iteration as follows:
    //
    //     |Factored    |   |Factored   |   |Factored   |
    //     |  _ _ _ _ _ |   |    _ _ _ _|   |    _ _ _ _|
    // A = | |Unfactored| = |   |A1 | A2| = |   |A11 A12|
    //     | |          |   |   |   |   |   |   |A21 A22|
    //
    // where A1 = |A11|
    //            |A21| is the current rectangular block that
    //                  is factored in this iteration
    //
    //       A2 = |A12|
    //            |A22| is updated after the LU factorization
    //                  of A1

    var A1  => A[UnfactoredInds, CurrentBlockInds],
        A2  => A[UnfactoredInds, TrailingBlockInds],
        A11 => A[CurrentBlockInds, CurrentBlockInds],
        A21 => A[TrailingBlockInds, CurrentBlockInds],
        A12 => A[CurrentBlockInds, TrailingBlockInds],
        A22 => A[TrailingBlockInds, TrailingBlockInds];

    // First compute the LU factorization of A1...
    //
    // We loop over the rows of A11, but we eliminate the
    // the full rectangular A1 block.

    // This is the panel factorization.  Need to add alternative
    // factorization options.  Also, need to add pipelining of
    // panel factorization.  Instead of CurrentBlockInds, have
    // CurrentPanelInds and NextPanelInds for a lookahead of one
    // (or is that two? need to check).
    for k in CurrentBlockInds {

      // Find pivot for kth column:
      //   identify largest magnitude element in kth column
      //   (pivot), from the diagonal element downward
      //   through all rows of A1

      // Was not able to replace k..k with k here.  Why?
      const pivotRow = computePivotRow(A1[k.., k..k]),
            pivot = A1[pivotRow, k];

      // If kth row is not row of pivot, swap rows:
      if (pivotRow != k) {

        // store row permutation in piv array
        piv(k) <=> piv(pivotRow);

        // swap values in rows k and pivotRow of the full
        // matrix A
        A[k, ..] <=> A[pivotRow, ..];
      }

      // If pivot is zero, halt.  Matrix is singular and
      // cannot be factored.
      if (pivot == 0) then halt("zero pivot encountered");

      // Compute the k-th elimination step:
      //   store multipliers...
      A1[k+1.., k] /= pivot;

      //   ..and subtract scaled kth row from remaining
      //   unfactored rows of A1

      forall (i,j) in [UnfactoredInds(k+1..), CurrentBlockInds(k+1..)] do
        A1(i,j) -= A1(i,k) * A1(k,j);
    }

    // ... and now update A2.
    // First update A12.

    // Add loop to update NextPanel first so that lookahead factorization
    // could occur.  Maybe use sync vars to do this?

    // Communication of A1 to other processors occurs before this
    // step.  There are options for communication that need to be reflected
    // here or in distribution.
    forall j in TrailingBlockInds do
      for k in CurrentBlockInds do
        forall i in CurrentBlockInds(k+1..) do
          A12(i,j) -= A1(i,k) * A12(k,j);

    // And then update A22 -= A21*A12.
    //
    // MMIterator is used to generate the indices for this
    // matrix-matrix multiplication step.  MMIterator can
    // be optimized (e.g., for the loop nesting order and loop
    // unrolling) separately from this blockLU routine.

    for (i,j,k1,k2) in MMIterator(A21.domain, A12.domain) do
      A22(i,j) -= A21(i,k1) * A12(k2,j);
  }
}

// The generateBlockLURanges iterator returns a 3-tuple of
// the ranges to be used in each iteration of block LU:
//   (UnfactoredInds, CurrentBlockInds, TrailingBlockInds).
// By defining these ranges, the blockLU code is cleaner. It
// eliminates the need to test for the last iteration where a full-sized
// block may not be possible and only the factorization step (and not
// the update step) is required.
//
// The range UnfactoredInds is equivalent to the range
// CurrentBlockInds concatenated with the range TrailingBlockInds.
//
// The range CurrentBlockInds has the length of blksize, except
// for the end case when the last block may not be of full size.
// This iterator takes care of this end case so that testing for
//  it is not necessary in the blockLU code itself.
//
// The range TrailingBlockInds will be an empty range for the
// the last block iteration.  When it is empty, the loops in
// the blockLU code will not execute, so testing for this case is
// not necessary.

def generateBlockLURanges(D:domain(2), blksize) {
  const end = D.dim(1).high;

  for i in D.dim(1) by blksize {
    const hi = min(i + blksize-1, end);
    yield (i..end, i..hi, hi+1..end);
  }
}

// The MMIterator generates indices for a matrix multiplication loop.
// This iterator can be tuned for loop nesting order or loop unrolling,
// isolating these optimizations from the blockLU code (or any other
// code in a complete linear algebra library that uses matrix-matrix
// multiplication).

def MMIterator(D1, D2) {
  for j in D2.dim(2) do
    for (k1, k2) in (D1.dim(2), D2.dim(1)) do
      for i in D1.dim(1) do
        yield (i,j,k1,k2);
}

// This function computes the pivot row for an LU pivoting operation
// by using Chapel's maxloc reduction on the absolute values in the
// matrix, dropping the maximum value on the floor using the
// underscore operator, and returning the row of the resulting index.

def computePivotRow(A:[?D]) {
   const (_, ind) = maxloc reduce (abs(A), D);
   return ind(1);
}

def LUSolve (A: [?ADom], x: [?xDom], b: [?bDom], piv) {

   var n = piv.numElements;
   var AD1 = ADom.dim(1);

   x = b(piv);
   for j in 1..n-1 {
     for (i1, i2) in (xDom(j+1..n), AD1(j+1..n)) {
        x(i1) -= x(j)*A(i2,j);
     }
   }

   for j in (2..n) by -1 {
     x(j) /= A(j,j);
     for (i1, i2) in (xDom(1..j-1), AD1(1..j-1)) {
       x(i1) -= x(j)*A(i2, j);
     }
   }

   x(1) /= A(1,1);

}


Adds close calls for bits tests.

Adds close calls for bits tests.

When trying to understand why this example leaks so
much memory, I realized that sometimes we're not
even calling channel.close(). It's not the source of
most of the leaks (most leaks are coming from the
channel structure not being freed even though it is
closed).

Anyway, this adds the close calls for the last example.

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

/*
 * File I/O Primer
 */

/*
   First example: Textual Array I/O
   Second and Third examples: Binary I/O
   Fourth example: reading UTF-8 lines and printing them out.
   Fifth example: error handling.
   Sixth example: object-at-a-time writing.
   Seventh example: binary I/O with bits at a time
*/

config var n = 9,            // the problem size for example 1
           filename = "Arr.dat";  // the filename for writing/reading the array
config const num = 1*1024*1024;
config const example = 0;
config const testfile = "test.bin";

use IO;

/* Example 1
 * This is a simple example of using file I/O in Chapel.
 *
 * It initializes an array and writes its size and data to a file.  It
 * then opens the file, uses the size in the file to declare a new
 * domain and array, and reads in the array data.
 */
if example == 0 || example == 1 {
  const ADom = {1..n, 1..n};  // Create a domain of the specified problem size

  // Create and initialize an array of the specified size
  var A: [ADom] real = [(i,j) in ADom] i + j/10.0;

  // Write the problem size and array out to the specified filename
  writeSquareArray(n, A, filename);

  // Read an array in from the specified filename, storing in a new variable, B
  var B = readArray(filename);

  // Print out B as a debugging step
  writeln("B is:\n", B);

  //
  // verify that A and B contain the same values and print success or failure
  //
  const numErrors = + reduce [i in ADom] (A(i) != B(i));

  if (numErrors > 0) {
    writeln("FAILURE");
  } else {
    writeln("SUCCESS");
  }
}

//
// this procedure writes a square array out to a file
//
proc writeSquareArray(n, X, filename) {
  // Create and open an output file with the specified filename in write mode
  var outfile = open(filename, iomode.cw);
  var writer = outfile.writer();

  // Write the problem size in each dimension to the file
  writer.writeln(n, " ", n);

  // write out the array itself
  writer.write(X);

  // close the file
  writer.close();
  outfile.close();
}

//
// This procedure reads a new array out of a file and returns it
//
proc readArray(filename) {
   // Open an input file with the specified filename in read mode
  var infile = open(filename, iomode.r);
  var reader = infile.reader();

  // Read the number of rows and columns in the array in from the file
  var m = reader.read(int),
      n = reader.read(int);

  // Declare an array of the specified dimensions
  var X: [1..m, 1..n] real;

  //
  // Read in the array elements one by one (eventually, you should be
  // able to read in the array wholesale, but this isn't currently
  // supported.
  //
  for i in 1..m do
    for j in 1..n do
      reader.read(X(i,j));

  // Close the file
  reader.close();
  infile.close();

  // Return the array
  return X;
}

/*
   In Examples 2 and 3, we will write numbers 0..n-1 to a file in binary,
   and then we'll open it up, and read the numbers in reverse,
   just to show how to 'seek' in a file.

   We show two versions of this example; a simple version, and
   a slightly more complicated version that has some performance hints.
*/

if example == 0 || example == 2 {
  writeln("Running Example 2");

  // Here comes the simple version!

  // First, open up a test file. Chapel's I/O interface allows
  // us to open regular files, temporary files, memory, or file descriptors;
  var f = open(testfile, iomode.cwr);

  /* Since the typical 'file position' design leads to race conditions
   * all over, the Chapel I/O design separates a file from a channel.
   * A channel is a buffer to a particular spot in a file. Channels
   * can have a start and and end, so that if you're doing parallel I/O
   * to different parts of a file with different channels, you can
   * partition the file to be assured that they do not interfere.
   */

  {
    var w = f.writer(kind=ionative); // get a binary writing channel for the start of the file.

    for i in 0..#num { // for each i in [0,n)
      var tmp:uint(64) = i:uint(64);
      w.write(tmp); // writing a uint(64) will write 8 bytes.
    }

    // Now w goes out of scope, it will flush anything that is buffered.
    // Channels are reference-counted, so it should be easy, but if you want to make sure,
    // you can close the channel.
    w.close();
  }

  // OK, now we have written our data file. Now read it backwards.
  // Note -- this could be a forall loop to do I/O in parallel!
  for i in 0..#num by -1 {
    var r = f.reader(kind=ionative, start=8*i, end=8*i+8);
    var tmp:uint(64);
    r.read(tmp);
    assert(tmp == i:uint(64));
    r.close();
  }

  // Now close the file. Like channels, files are reference-counted,
  // and so should close when they go out of scope, but we close it
  // here anyway to be sure.
  f.close();

  // Finally, we can remove the test file.
  unlink(testfile);
}

if example == 0 || example == 3 {
  writeln("Running Example 3");

  /* Here comes a faster version of example 2, using some hints.
     This time we're not going to use a temporary file, because
     we want to open it twice for performance reasons.
     If you want to measure the performance difference, try:
       time ./fielIOv2 --example=2
       time ./fielIOv2 --example=3
   */

  // First, open up a file and write to it.
  {
    var f = open(testfile, iomode.cwr);
    /* When we create the writer, suppling locking=false will do unlocked I/O.
       That's fine as long as the channel is not shared between tasks.
      */
    var w = f.writer(kind=ionative, locking=false);

    for i in 0..#num {
      var tmp:uint(64) = i:uint(64);
      w.write(tmp);
    }

    w.close();
    f.close();
  }

  /* Now that we've created the file, when we open it for
     read access and hint 'random access' and 'keep data cached/assume data is cached',
     we can optimize better (using mmap, if you like details).
   */
  {
    var f = open(testfile, iomode.r,
                 hints=IOHINT_RANDOM|IOHINT_CACHED|IOHINT_PARALLEL);

    // Note -- this could be a forall loop to do I/O in parallel!
    forall i in 0..#num by -1 {
      /* When we create the reader, suppling locking=false will do unlocked I/O.
         That's fine as long as the channel is not shared between tasks;
         here it's just used as a local variable, so we are O.K.
        */
      var r = f.reader(kind=ionative, locking=false, start=8*i, end=8*i+8);
      var tmp:uint(64);
      r.read(tmp);
      assert(tmp == i:uint(64));
      r.close();
    }

    f.close();
  }

  // Finally, we can remove the test file.
  unlink(testfile);
}

/*
   In Example 4, we show that it's easy to read the lines in a file,
   including UTF-8 characters.
*/
if example == 0 || example == 4 {
  writeln("Running Example 4");

  var f = open(testfile, iomode.cwr);
  var w = f.writer();

  w.writeln("Hello");
  w.writeln("This");
  w.writeln(" is ");
  w.writeln(" a test ");
  // We only write the UTF-8 characters if unicode is supported,
  // and that depends on the current unix locale environment
  // (e.g. setting the environment variable LC_ALL=C will disable unicode support).
  // Note that since UTF-8 strings are C strings, this should work
  // even in a C locale. We don't do it all the time for testing sanity reasons.
  if unicodeSupported() then w.writeln(" of UTF-8 Euro Sign: €");

  // flush buffers, close the channel.
  w.close();

  var r = f.reader();
  var line:string;
  while( r.readline(line) ) {
    write("Read line: ", line);
  }
  r.close();

  // Or, if we just want all the lines in the file,
  // we can use file.lines, and we don't even have to make a reader:
  for line in f.lines() {
    write("Read line: ", line);
  }

  f.close();
  unlink(testfile);
}

/*
   In Example 5, we show that error handling is possible
   with the new I/O routines. Maybe one day this strategy will
   be replaced by exceptions, but until then... Chapel programs
   can still respond to errors.

*/
if example == 0 || example == 5 {
  writeln("Running Example 5");

  // Error handling.
  var err:syserr;
  // Who knows, maybe 1st unlink succeeds.
  unlink(testfile, error=err);

  // File does not exist by now, for sure.
  unlink(testfile, error=err);
  assert(err == ENOENT);

  // What happens if we try to open a non-existant file?
  var f = open(testfile, iomode.r, error=err);
  assert(err == ENOENT);

  /* Note that if an error= argument is not supplied to an
     I/O function, it will call ioerror, which will
     in turn halt with an error message.
   */
}

/*
   In Example 6, we demonstrate that output from multiple tasks
   can use the same channel, and each write() call will be
   completed entirely before another one is allowed to begin.
*/
if example == 0 || example == 6 {
  writeln("Running Example 6");

  forall i in 1..4 {
    writeln("This should be a chunk: {", "\n a", "\n b", "\n}");
  }

  record MyThing {
    proc writeThis(w:Writer) {
      w.writeln("This should be a chunk: {");
      w.writeln(" a");
      w.writeln(" b");
      w.writeln("}");
    }
  }

  forall i in 1..4 {
    var t:MyThing;
    write(t);
  }
}

/*
   In Example 7, we demonstrate bit-level I/O.
 */
if example == 0 || example == 7 {
  writeln("Running Example 7");

  var f = open(testfile, iomode.cwr);

  {
    var w = f.writer(kind=ionative);

    // Write 011 0110 011110000
    w.writebits(0b011, 3);
    w.writebits(0b0110, 4);
    w.writebits(0b011110000, 9);
    w.close();
  }

  // Try reading it back the way we wrote it.
  {
    var r = f.reader(kind=ionative);
    var tmp:uint(64);

    r.readbits(tmp, 3);
    assert(tmp == 0b011);

    r.readbits(tmp, 4);
    assert(tmp == 0b0110);

    r.readbits(tmp, 9);
    assert(tmp == 0b011110000);

    r.close();
  }

  // Try reading it back all as one big chunk.
  // Read 01101100 11110000
  {
    var r = f.reader(kind=ionative);
    var tmp:uint(8);

    r.read(tmp);
    assert(tmp == 0b01101100);

    r.read(tmp);
    assert(tmp == 0b11110000);

    r.close();
  }

  f.close();
}

/*
 * File I/O Primer
 */

/*
   First example: Textual Array I/O
   Second and Third examples: Binary I/O
   Fourth example: reading UTF-8 lines and printing them out.
   Fifth example: error handling.
   Sixth example: object-at-a-time writing.
   Seventh example: binary I/O with bits at a time
*/

config var n = 9,            // the problem size for example 1
           filename = "Arr.dat";  // the filename for writing/reading the array
config const num = 1*1024*1024;
config const example = 0;
config const testfile = "test.bin";

use IO;

/* Example 1
 * This is a simple example of using file I/O in Chapel.
 *
 * It initializes an array and writes its size and data to a file.  It
 * then opens the file, uses the size in the file to declare a new
 * domain and array, and reads in the array data.
 */
if example == 0 || example == 1 {
  const ADom = {1..n, 1..n};  // Create a domain of the specified problem size

  // Create and initialize an array of the specified size
  var A: [ADom] real = [(i,j) in ADom] i + j/10.0;

  // Write the problem size and array out to the specified filename
  writeSquareArray(n, A, filename);

  // Read an array in from the specified filename, storing in a new variable, B
  var B = readArray(filename);

  // Print out B as a debugging step
  writeln("B is:\n", B);

  //
  // verify that A and B contain the same values and print success or failure
  //
  const numErrors = + reduce [i in ADom] (A(i) != B(i));

  if (numErrors > 0) {
    writeln("FAILURE");
  } else {
    writeln("SUCCESS");
  }
}

//
// this procedure writes a square array out to a file
//
proc writeSquareArray(n, X, filename) {
  // Create and open an output file with the specified filename in write mode
  var outfile = open(filename, iomode.cw);
  var writer = outfile.writer();

  // Write the problem size in each dimension to the file
  writer.writeln(n, " ", n);

  // write out the array itself
  writer.write(X);

  // close the file
  writer.close();
  outfile.close();
}

//
// This procedure reads a new array out of a file and returns it
//
proc readArray(filename) {
   // Open an input file with the specified filename in read mode
  var infile = open(filename, iomode.r);
  var reader = infile.reader();

  // Read the number of rows and columns in the array in from the file
  var m = reader.read(int),
      n = reader.read(int);

  // Declare an array of the specified dimensions
  var X: [1..m, 1..n] real;

  //
  // Read in the array elements one by one (eventually, you should be
  // able to read in the array wholesale, but this isn't currently
  // supported.
  //
  for i in 1..m do
    for j in 1..n do
      reader.read(X(i,j));

  // Close the file
  reader.close();
  infile.close();

  // Return the array
  return X;
}

/*
   In Examples 2 and 3, we will write numbers 0..n-1 to a file in binary,
   and then we'll open it up, and read the numbers in reverse,
   just to show how to 'seek' in a file.

   We show two versions of this example; a simple version, and
   a slightly more complicated version that has some performance hints.
*/

if example == 0 || example == 2 {
  writeln("Running Example 2");

  // Here comes the simple version!

  // First, open up a test file. Chapel's I/O interface allows
  // us to open regular files, temporary files, memory, or file descriptors;
  var f = open(testfile, iomode.cwr);

  /* Since the typical 'file position' design leads to race conditions
   * all over, the Chapel I/O design separates a file from a channel.
   * A channel is a buffer to a particular spot in a file. Channels
   * can have a start and and end, so that if you're doing parallel I/O
   * to different parts of a file with different channels, you can
   * partition the file to be assured that they do not interfere.
   */

  {
    var w = f.writer(kind=ionative); // get a binary writing channel for the start of the file.

    for i in 0..#num { // for each i in [0,n)
      var tmp:uint(64) = i:uint(64);
      w.write(tmp); // writing a uint(64) will write 8 bytes.
    }

    // Now w goes out of scope, it will flush anything that is buffered.
    // Channels are reference-counted, so it should be easy, but if you want to make sure,
    // you can close the channel.
    w.close();
  }

  // OK, now we have written our data file. Now read it backwards.
  // Note -- this could be a forall loop to do I/O in parallel!
  for i in 0..#num by -1 {
    var r = f.reader(kind=ionative, start=8*i, end=8*i+8);
    var tmp:uint(64);
    r.read(tmp);
    assert(tmp == i:uint(64));
    r.close();
  }

  // Now close the file. Like channels, files are reference-counted,
  // and so should close when they go out of scope, but we close it
  // here anyway to be sure.
  f.close();

  // Finally, we can remove the test file.
  unlink(testfile);
}

if example == 0 || example == 3 {
  writeln("Running Example 3");

  /* Here comes a faster version of example 2, using some hints.
     This time we're not going to use a temporary file, because
     we want to open it twice for performance reasons.
     If you want to measure the performance difference, try:
       time ./fielIOv2 --example=2
       time ./fielIOv2 --example=3
   */

  // First, open up a file and write to it.
  {
    var f = open(testfile, iomode.cwr);
    /* When we create the writer, suppling locking=false will do unlocked I/O.
       That's fine as long as the channel is not shared between tasks.
      */
    var w = f.writer(kind=ionative, locking=false);

    for i in 0..#num {
      var tmp:uint(64) = i:uint(64);
      w.write(tmp);
    }

    w.close();
    f.close();
  }

  /* Now that we've created the file, when we open it for
     read access and hint 'random access' and 'keep data cached/assume data is cached',
     we can optimize better (using mmap, if you like details).
   */
  {
    var f = open(testfile, iomode.r,
                 hints=IOHINT_RANDOM|IOHINT_CACHED|IOHINT_PARALLEL);

    // Note -- this could be a forall loop to do I/O in parallel!
    forall i in 0..#num by -1 {
      /* When we create the reader, suppling locking=false will do unlocked I/O.
         That's fine as long as the channel is not shared between tasks;
         here it's just used as a local variable, so we are O.K.
        */
      var r = f.reader(kind=ionative, locking=false, start=8*i, end=8*i+8);
      var tmp:uint(64);
      r.read(tmp);
      assert(tmp == i:uint(64));
      r.close();
    }

    f.close();
  }

  // Finally, we can remove the test file.
  unlink(testfile);
}

/*
   In Example 4, we show that it's easy to read the lines in a file,
   including UTF-8 characters.
*/
if example == 0 || example == 4 {
  writeln("Running Example 4");

  var f = open(testfile, iomode.cwr);
  var w = f.writer();

  w.writeln("Hello");
  w.writeln("This");
  w.writeln(" is ");
  w.writeln(" a test ");
  // We only write the UTF-8 characters if unicode is supported,
  // and that depends on the current unix locale environment
  // (e.g. setting the environment variable LC_ALL=C will disable unicode support).
  // Note that since UTF-8 strings are C strings, this should work
  // even in a C locale. We don't do it all the time for testing sanity reasons.
  if unicodeSupported() then w.writeln(" of UTF-8 Euro Sign: €");

  // flush buffers, close the channel.
  w.close();

  var r = f.reader();
  var line:string;
  while( r.readline(line) ) {
    write("Read line: ", line);
  }
  r.close();

  // Or, if we just want all the lines in the file,
  // we can use file.lines, and we don't even have to make a reader:
  for line in f.lines() {
    write("Read line: ", line);
  }

  f.close();
  unlink(testfile);
}

/*
   In Example 5, we show that error handling is possible
   with the new I/O routines. Maybe one day this strategy will
   be replaced by exceptions, but until then... Chapel programs
   can still respond to errors.

*/
if example == 0 || example == 5 {
  writeln("Running Example 5");

  // Error handling.
  var err:syserr;
  // Who knows, maybe 1st unlink succeeds.
  unlink(testfile, error=err);

  // File does not exist by now, for sure.
  unlink(testfile, error=err);
  assert(err == ENOENT);

  // What happens if we try to open a non-existant file?
  var f = open(testfile, iomode.r, error=err);
  assert(err == ENOENT);

  /* Note that if an error= argument is not supplied to an
     I/O function, it will call ioerror, which will
     in turn halt with an error message.
   */
}

/*
   In Example 6, we demonstrate that output from multiple tasks
   can use the same channel, and each write() call will be
   completed entirely before another one is allowed to begin.
*/
if example == 0 || example == 6 {
  writeln("Running Example 6");

  forall i in 1..4 {
    writeln("This should be a chunk: {", "\n a", "\n b", "\n}");
  }

  record MyThing {
    proc writeThis(w:Writer) {
      w.writeln("This should be a chunk: {");
      w.writeln(" a");
      w.writeln(" b");
      w.writeln("}");
    }
  }

  forall i in 1..4 {
    var t:MyThing;
    write(t);
  }
}

/*
   In Example 7, we demonstrate bit-level I/O.
 */
if example == 0 || example == 7 {
  writeln("Running Example 7");

  var f = open(testfile, iomode.cwr);

  {
    var w = f.writer(kind=ionative);

    // Write 011 0110 011110000
    w.writebits(0b011, 3);
    w.writebits(0b0110, 4);
    w.writebits(0b011110000, 9);
    w.close();
  }

  // Try reading it back the way we wrote it.
  {
    var r = f.reader(kind=ionative);
    var tmp:uint(64);

    r.readbits(tmp, 3);
    assert(tmp == 0b011);

    r.readbits(tmp, 4);
    assert(tmp == 0b0110);

    r.readbits(tmp, 9);
    assert(tmp == 0b011110000);
  }

  // Try reading it back all as one big chunk.
  // Read 01101100 11110000
  {
    var r = f.reader(kind=ionative);
    var tmp:uint(8);

    r.read(tmp);
    assert(tmp == 0b01101100);

    r.read(tmp);
    assert(tmp == 0b11110000);
  }
}


Removed a race condition that can cause deadlock.

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

var a = 0;
var b = 0;
var c = 0;
var d = 0;

fun jam_records1 () {
  while (0==a) {;}   // wait on thread last statement in cobegin
  b = 1;
}

fun jam_records2 () {
  while (0==b) {;}   // wait on thread 1
  c = 1;
}

fun jam_records3 () {
  while (0==c) {;}   // wait on thread 2
  d = 1;
}

cobegin {
  jam_records3();
  jam_records2();
  jam_records1();
  a = 1;
}

write ("d is ");
writeln (d);

var a = 0;
var b = 0;
var c = 0;
var d = 0;

fun jam_records1 () {
  while (0==a) {   // wait on thread last statement in cobegin
    b = 1;
  }
}

fun jam_records2 () {
  while (0==b) {   // wait on thread 1
    c = 1;
  }
}

fun jam_records3 () {
  while (0==c) {   // wait on thread 2
    d = 1;
  }
}

cobegin {
  jam_records3();
  jam_records2();
  jam_records1();
  a = 1;
}

write ("d is ");
writeln (d);