Removed questions -- moved to email

Removed questions -- moved to email

/** 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;

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
    var   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 = new FTree(order=k);
    var   d = new 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);
    const hgT   : [hgDom] real = transpose(hg);

    // Quadrature coefficients
    const quadDom   = {0..k-1};
    const quad_x    : [quadDom] real = gl_getPoints(k);
    const quad_w    : [quadDom] real = gl_getWeights(k);

    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;

    proc ~Function() {
        delete s;
        delete d;
    }

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

        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.
     */
    proc init_quadrature(order: int) {
        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.
     */
    proc 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
     */
    proc copy() {
        return new 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
     */
    proc skeletonCopy() {
        // Omit: f, compressed, s, d
        return new 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
     */
    proc 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
     */
    proc 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.
     */
    proc 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.
     */
    proc 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
     */
    proc 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
     */
    proc 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.
     */
    proc 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)
     */
    proc 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.
     */
    proc 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.
     */
    proc diff() {
        proc 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))
     */
    proc 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).
     */
    proc gaxpy(alpha, other, beta) {
        // recursive "iteration" for gaxpy
        proc 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.
     */
    proc 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.
     */
    proc subtract(other) {
        return copy().gaxpy(1.0, other, -1.0);
    }

    /** Recursively multiply f1 and f2 put the result into this
     */
    proc 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
     */
    proc 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
     */
    proc 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
	        writef("   level ##   %#boxes=####  norm=%0.2er\n",
		       n, ncoeffs, 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
	        writef("   level ##   %#boxes=####  norm=%0.2er\n",
		       n, ncoeffs, truncate(sqrt(sum)));
        }

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

    /** Evaluate the analytic and numerical functions over the given interval
        and print the error.
     */
    proc evalNPT(npt) {
        for i in 0..npt {
	    // This truncation may lead to a loss of precision in the error calculation.
            // var (fval, Fval) = (truncate(f(i/npt:real)), truncate(this(i/npt:real)));
            var (fval, Fval) = (f(i/npt:real), this(i/npt:real));
            writef(" -- %.2dr:  F_numeric()=% .8dr  f_analytic()=% .8dr err=% .8dr%s\n",
		   i/npt:real, truncate(Fval), truncate(fval), truncate(Fval-fval),
		   if abs(Fval-fval) > thresh then "  > thresh" else "");
        }
    }
}

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

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

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

proc *(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;

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
    var   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 = new FTree(order=k);
    var   d = new 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);
    const hgT   : [hgDom] real = transpose(hg);

    // Quadrature coefficients
    const quadDom   = {0..k-1};
    const quad_x    : [quadDom] real = gl_getPoints(k);
    const quad_w    : [quadDom] real = gl_getWeights(k);

    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;

    proc ~Function() {
        delete s;
        delete d;
    }

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

        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.
     */
    proc init_quadrature(order: int) {
        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.
     */
    proc 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
     */
    proc copy() {
        return new 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
     */
    proc skeletonCopy() {
        // Omit: f, compressed, s, d
        return new 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
     */
    proc 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
     */
    proc 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.
     */
    proc 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.
     */
    proc 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
     */
    proc 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
     */
    proc 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.
     */
    proc 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)
     */
    proc 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.
     */
    proc 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.
     */
    proc diff() {
        proc 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))
     */
    proc 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).
     */
    proc gaxpy(alpha, other, beta) {
        // recursive "iteration" for gaxpy
        proc 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.
     */
    proc 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.
     */
    proc subtract(other) {
        return copy().gaxpy(1.0, other, -1.0);
    }

    /** Recursively multiply f1 and f2 put the result into this
     */
    proc 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
     */
    proc 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
     */
    proc 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
              // MPPF: Should we always require % for ### formats to avoid having
              // to escape all #'s?
	        writef("   level ##   %#boxes=####  norm=%0.2er\n",
		       n, ncoeffs, 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
	        writef("   level ##   %#boxes=####  norm=%0.2er\n",
		       n, ncoeffs, truncate(sqrt(sum)));
        }

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

    /** Evaluate the analytic and numerical functions over the given interval
        and print the error.
     */
    proc evalNPT(npt) {
        for i in 0..npt {
	    // This truncation may lead to a loss of precision in the error calculation.
            // var (fval, Fval) = (truncate(f(i/npt:real)), truncate(this(i/npt:real)));
            var (fval, Fval) = (f(i/npt:real), this(i/npt:real));
	    // HEY mppf: Why doesn't %.2r work below???:
            writef(" -- %.2dr:  F_numeric()=% .8dr  f_analytic()=% .8dr err=% .8dr%s\n",
		   i/npt:real, truncate(Fval), truncate(fval), truncate(Fval-fval),
		   if abs(Fval-fval) > thresh then "  > thresh" else "");
        }
    }
}

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

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

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

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


Fix substr.chpl.

Fix substr.chpl.

Revised the test to accommodate the new string implementation.

After renaming string.substring as string.this, he syntax <string-literal> '[]' does not
work, so I just stubbed out those tests.

c_string.this is not implemented, but c_string.substring() is, so I reverted those test to
call the .substring() method.

This test still fails, because of the open question regarding the semantics of
.this(i:integral) when the index falls outside of the given string (e.g. -1).

use checkType;

const s = "0123456789";

checkType(s[-1].type);
checkType(s[5].type);
checkType(s[6..9].type);

///// These tests no longer work after .substring() was renamed as [] ('this').
// Chapel does not have explicit operator invocation syntax, so
// <string-literal> '[]' results in a syntax error.
//checkType("0123456789"[-1].type);
//checkType("0123456789"[5].type);
//checkType("0123456789"[6..9].type);

const cs = s.c_str();

// The substring method is still implemented for c_string objects.
// The interface has changed, so now c_string.substring() returns a c_string_copy.
checkType(c_string_copy, cs.substring(-1).type);
checkType(c_string_copy, cs.substring(5).type);
checkType(c_string_copy, cs.substring(6..9).type);

use checkType;

const s = "0123456789";

checkType(s[-1].type);
checkType(s[5].type);
checkType(s[6..9].type);

checkType("0123456789"[-1].type);
checkType("0123456789"[5].type);
checkType("0123456789"[6..9].type);

const cs = s.c_str();

checkType(c_string, cs[-1].type);
checkType(c_string, cs[5].type);
checkType(c_string, cs[6..9].type);


refactored namedmatrix inits

refactored namedmatrix inits

use Cdo,
    LinearAlgebra,
    LinearAlgebra.Sparse,
    NumSuch,
    Random;

config param batchsize = 10000;

/*
 A matrix of data with named rows and columns.
 */
class NamedMatrix {
  var D: domain(2),
      SD = CSRDomain(D),
      X: [SD] real,  // the actual data
      rows: BiMap = new BiMap(),
      cols: BiMap = new BiMap();

   proc init() {
     super.init();  //NAMEDMATRIX DOESNT EXPLICITLY INHERIT ANYTHING SO IS THIS NECESSARY??
   }

   proc init(X) {
     this.D = {X.domain.dim(1), X.domain.dim(2)};
     super.init();
     this.loadX(X);
   }

   proc init(X, names: [] string) {
     this.init(X);
     try! this.setRowNames(names);
     try! this.setColNames(names);
   }

   proc init(X, rownames: [] string, colnames: [] string) {
     this.init(X);
     try! this.setRowNames(rownames);
     try! this.setColNames(colnames);
   }

   proc init(N: NamedMatrix) {
     this.init(N.X);
     this.rows = N.rows;
     this.cols = N.cols;
   }
}

/*
Loads the data from X into the internal array, also called X.  We call them all X to keep it clear.

:arg real[]: Array representing the matrix
 */
proc NamedMatrix.loadX(X:[], shape: 2*int =(-1,-1)) {
  if shape(1) > 0 && shape(2) > 0 {
    this.D = {1..shape(1), 1..shape(2)};
  }
  for (i,j) in X.domain {
    this.SD += (i,j);
    this.X(i,j) = X(i,j);
  }
}

/*
Sets the row names for the matrix X
 */
proc NamedMatrix.setRowNames(rn:[]): string throws {
  if rn.size != X.domain.dim(1).size then throw new Error();
  for i in 1..rn.size {
    this.rows.add(rn[i]);
  }
  return this.rows;
}

/*
Sets the column names for the matrix X
*/
proc NamedMatrix.setColNames(cn:[]): string throws {
  if cn.size != X.domain.dim(2).size then throw new Error();
  for i in 1..cn.size {
    this.cols.add(cn[i]);
  }
  return this.cols;
}

/*
 Gets the value of the (i,j) entry of the matrix X in the NamedMatrix
 */
proc NamedMatrix.get(i: int, j: int) {
   return this.X(i,j);
 }

 /*
 Get the values using the named rows and columns
  */
proc NamedMatrix.get(f: string, t: string) {
    return this.X(rows.get(f), cols.get(t));
 }

/*
 Set the value of this.X(i,j) by index row
 */
proc NamedMatrix.set(i: int, j: int, w: real) {
    if this.SD.member((i,j)) {
      this.X(i,j) = w;
    } else {
      this.SD += (i,j);
      this.X(i,j) = w;
    }
    return w;
}

/*
Set the values using the row and column names
*/
proc NamedMatrix.set(f: string, t: string, w: real) {
  return this.set(rows.get(f), cols.get(t), w);
}

/*
Update the value in X(i,j) with `w`
 */
proc NamedMatrix.update(i: int, j: int, w: real) {
  const x = this.get(i,j);
  return this.set(i,j, x + w);
}

/*
Update the value in X(i,j) with `w` using the row column names
 */
proc NamedMatrix.update(f: string, t: string, w: real) {
  const x = this.get(rows.get(f),cols.get(t));
  return this.set(rows.get(f),cols.get(t), x + w);
}

/*
Find the number of non-zeroes in the matrix
 */
proc NamedMatrix.nnz() {
  var i: int = 0;
  for ij in this.X.domain {
    i += 1;
  }
  return i;
}

/*
Calculates the sparsity of the matrix: Number of entries / frame size
 */
proc NamedMatrix.sparsity() {
  const d = this.X.shape[1]:real * this.X.shape[2]: real;
  return this.nnz():real / d;
}

/*
 Multiplies the current NamedMatrix `X` against the argument `Y`, but first it aligns
 the names of `X.cols` with `Y.rows`.  Returns an appropriately named NamedMatrix
 :arg NamedMatrix Y:
 */
proc NamedMatrix.alignAndMultiply(Y: NamedMatrix) {
    var rcOverlap: domain(string) = this.cols.keys & Y.rows.keys;

    var xSD: sparse subdomain(this.D) dmapped CS(),
        ySD: sparse subdomain(Y.D) dmapped CS(),
        xX: [xSD] real,  // the actual data
        yX: [ySD] real,  // the actual data
        xrows: BiMap = new BiMap(),
        yrows: BiMap = new BiMap(),
        xcols: BiMap = new BiMap(),
        ycols: BiMap = new BiMap();

    for (left_row, right_col) in zip(this.D.dim(2), Y.D.dim(1)) {
      for rc in rcOverlap {
        const j = this.cols.get(rc);
        if this.SD.member(left_row, j) {
          xSD += (left_row, j);
          xX[left_row, j] = this.X[left_row, j];
        }
        if Y.SD.member(j, right_col) {
          ySD += (j, right_col);
          yX[j, right_col] = Y.X[j, right_col];
        }
      }
    }
    const z = xX.dot(yX);
    var n = new NamedMatrix(X=z);
    n.rows = this.rows;
    n.cols = Y.cols;
    return n;
}

/*
 Creates a NamedMatrix from a table in Postgres.  Does not optimize for square matrices.  This assumption
 is that the matrix is sparse.

 :arg string edgeTable: The SQL table holding the values of the matrix.
 :arg string fromField: The table column representing rows, e.g. `i`.
 :arg string toField: The table column representing columns, e.g. 'j'.
 :arg string wField: `default=NONE` the table column containing the values of cell `(i,j)``
 :arg boolean square: Whether the matrix should be built to have the same rows and columns
 */

 proc NamedMatrixFromPG(con: Connection
   , edgeTable: string
   , fromField: string, toField: string, wField: string = "NONE"
   , square=false) {
  if square {
    return NamedMatrixFromPGSquare(con: Connection
      , edgeTable, fromField, toField, wField);
  } else {
    return NamedMatrixFromPGRectangular(con: Connection
      , edgeTable, fromField, toField, wField);
  }
}

proc NamedMatrixFromPGRectangular(con: Connection
  , edgeTable: string
  , fromField: string, toField: string, wField: string = "NONE") {

  var q = """
  SELECT ftr, t
  FROM (
    SELECT distinct(%s) AS ftr, 'r' AS t FROM %s
    UNION ALL
    SELECT distinct(%s) AS ftr, 'c' AS t FROM %s
  ) AS a
  GROUP BY ftr, t
  ORDER BY ftr, t ;
  """;

  var rows: BiMap = new BiMap(),
      cols: BiMap = new BiMap();

  var cursor = con.cursor();
  cursor.query(q, (fromField, edgeTable, toField, edgeTable));

  for row in cursor {
    if row['t'] == 'r' {
      rows.add(row['ftr']);
    } else if row['t'] == 'c' {
      cols.add(row['ftr']);
    }
  }

  var D: domain(2) = {1..rows.size(), 1..cols.size()},
      SD = CSRDomain(D),
      X: [SD] real;  // the actual data

  var r = """
  SELECT %s, %s
  FROM %s
  ORDER BY %s, %s ;
  """;
  var cursor2 = con.cursor();
  cursor2.query(r, (fromField, toField, edgeTable, fromField, toField));
  const size = cursor2.rowcount(): int;
  var count = 0: int,
      dom = {1..size},
      indices: [dom] (int, int),
      values: [dom] real;

  // This guy is causing problems.  Exterminiate with extreme prejudice
  //forall row in cursor2 {
  forall row in cursor2 with (+ reduce count) {
    count += 1;
    indices[count]=(
       rows.get(row[fromField])
      ,cols.get(row[toField])
      );

    if wField == "NONE" {
      values[count] = 1;
    } else {
      values[count] = row[wField]: real;
    }
  }

  SD.bulkAdd(indices);
  forall (ij, a) in zip(indices, values) {
    X(ij) = a;
  }

  const nm = new NamedMatrix(X=X);
  nm.rows = rows;
  nm.cols = cols;
  return nm;
}

/*
 Build a square version of the matrix.  Still directed, but with the same number of rows/cols
 */
proc NamedMatrixFromPGSquare ( con: Connection
    , edgeTable: string
    , fromField: string, toField: string, wField: string = "NONE") {

    var q = """
    SELECT ftr
    FROM (
      SELECT distinct(%s) AS ftr FROM %s
      UNION ALL
      SELECT distinct(%s) AS ftr FROM %s
    ) AS a
    GROUP BY ftr ORDER BY ftr;
    """;

    var cursor = con.cursor();
    cursor.query(q, (fromField, edgeTable, toField, edgeTable));
    var rows: BiMap = new BiMap();

    forall row in cursor {
    //for row in cursor {
      rows.add(row['ftr']);
    }

    var D: domain(2) = {1..rows.size(), 1..rows.size()},
        SD = CSRDomain(D),
        X: [SD] real;  // the actual data

    var r = """
    SELECT %s, %s
    FROM %s
    ORDER BY %s, %s ;
    """;
    var cursor2 = con.cursor();
    cursor2.query(r, (fromField, toField, edgeTable, fromField, toField));
    var dom1: domain(1) = {1..0},
        dom2: domain(1) = {1..0},
        indices: [dom1] (int, int),
        values: [dom2] real;
    //forall row in cursor2 {
    for row in cursor2 {
      indices.push_back((
         rows.get(row[fromField])
        ,rows.get(row[toField])
        ));

      if wField == "NONE" {
        values.push_back(1);
      } else {
        values.push_back(row[wField]: real);
      }
    }

    SD.bulkAdd(indices);
    forall (ij, a) in zip(indices, values) {
      X(ij) = a;
    }

    const nm = new NamedMatrix(X=X);
    nm.rows = rows;
    nm.cols = rows;
    return nm;
}

proc NamedMatrixFromPG_(con: Connection
  , edgeTable: string
  , fromField: string, toField: string, wField: string = "NONE") {

  var q = """
  SELECT ftr, t, row_number() OVER(PARTITION BY t ORDER BY ftr ) AS ftr_id
  FROM (
    SELECT distinct(%s) AS ftr, 'r' AS t FROM %s
    UNION ALL
    SELECT distinct(%s) AS ftr, 'c' AS t FROM %s
  ) AS a
  GROUP BY ftr, t
  ORDER BY ftr_id, t ;
  """;

  var rows: BiMap = new BiMap(),
      cols: BiMap = new BiMap();

  var cursor = con.cursor();
  cursor.query(q, (fromField, edgeTable, toField, edgeTable));
  for row in cursor {
    if row['t'] == 'r' {
      rows.add(row['ftr'], row['ftr_id']:int);
    } else if row['t'] == 'c' {
      cols.add(row['ftr'], row['ftr_id']:int);
    }
  }

  var D: domain(2) = {1..rows.size(), 1..cols.size()},
      SD = CSRDomain(D),
      X: [SD] real;  // the actual data

  var r = """
  SELECT %s, %s
  FROM %s
  ORDER BY %s, %s ;
  """;
  var cursor2 = con.cursor();
  cursor2.query(r, (fromField, toField, edgeTable, fromField, toField));
  var dom1: domain(1) = {1..0},
      dom2: domain(1) = {1..0},
      indices: [dom1] (int, int),
      values: [dom2] real;

  //forall row in cursor2 {
  for row in cursor2 {
//    writeln("row: ", row[fromField], " -> ", row[toField]);
    indices.push_back((
       rows.get(row[fromField])
      ,cols.get(row[toField])
      ));

    if wField == "NONE" {
      values.push_back(1);
    } else {
      values.push_back(row[wField]: real);
    }
  }
  SD.bulkAdd(indices);
  for (ij, a) in zip(indices, values) {
    X(ij) = a;
  }

  const nm = new NamedMatrix(X=X);
  nm.rows = rows;
  nm.cols = cols;
  return nm;
}

/*

  :arg con: A CDO Connection to Postgres
  :arg edgeTable: The table in PG of edges
  :arg fromField: The field of edgeTable containing the id of the head vertex
  :arg toField: the field of edgeTable containing the id of the tail vertex
  :arg wField: The field of edgeTable containing the weight of the edge
  :arg n: number of distinct vertices. In practice, this may be gives and the number of names
  :arg weights: Boolean on whether to use the weights in the table or a 1 (indicator)
 */
proc wFromPG(con: Connection, edgeTable: string
    , fromField: string, toField: string, wField: string, weights=false) {
  var q = "SELECT %s, %s FROM %s ORDER BY 1, 2;";
  var source_size_q = """
                      SELECT count(*) AS n FROM (SELECT distinct(s.source_cui) FROM r.cui_confabulation s) AS sources;
                      """;

  var target_size_q = """
                      SELECT count(*) AS n FROM (SELECT distinct(s.exhibited_cui) FROM r.cui_confabulation s) AS exhibited;
                      """;
  var cursor2 = con.cursor();
  var cursor3 = con.cursor();
  cursor2.query(source_size_q);
  cursor3.query(target_size_q);
  const row2 = cursor2.fetchone();
  const row3 = cursor3.fetchone();
  const source_size = row2['n']: int;
  const exhibit_size = row3['n']: int;

  var cursor = con.cursor();
  cursor.query(q,(fromField, toField, edgeTable));
  const size = cursor.rowcount(): int;
  var D: domain(2) = {1..source_size, 1..exhibit_size};
  var SD: sparse subdomain(D) dmapped CS();
  var X: [SD] real;
  var dom: domain(1) = {1..size};
  var indices: [dom] (int, int);
  var values: [dom] real;
  forall (row, i) in zip(cursor, dom) {
    indices[i] = (row[fromField]: int,row[toField]: int);
    values[i] = 1: real;
  }
  SD.bulkAdd(indices);
  forall (ij, a) in zip(indices, values) {
    if weights {
      X(ij) = a;
    } else {
      X(ij) = 1;
    }
  }
  return X;
}

//SERIAL EXTRACTION FUNCTION FOR PERFORMANCE COMPARISONS
proc wFromPG_(con: Connection, edgeTable: string
    , fromField: string, toField: string, wField: string, n: int, weights=true) {
  var q = "SELECT %s, %s, %s FROM %s ORDER BY 1, 2;";
  var cursor = con.cursor();
  cursor.query(q,(fromField, toField, wField, edgeTable));
  const D: domain(2) = {1..n, 1..n};
  var SD: sparse subdomain(D) dmapped CS();
  var W: [SD] real;

  for row in cursor {
    SD += (row[fromField]: int, row[toField]:int);
    if weights {
      W[row[fromField]:int, row[toField]:int] = row[wField]: real;
    } else {
      W[row[fromField]:int, row[toField]:int] = 1;
    }
  }
   return W;
}

/*

 :arg con: A connection to a Postgres database containing a table with <ftr_id>, <vertex_name> pairs
 :arg nameTable: The name of the Postgres table containing the pairs
 :arg nameField: The name of the field in the nameTable containing the names
 :arg idField: The name of the field in the nameTable containing the feature ids

 :returns: An array of strings in order of feature id
 */
proc vNamesFromPG(con: Connection, nameTable: string
  , nameField: string, idField: string ) {

  var cursor = con.cursor();
  var q1 = "SELECT max(%s) AS n FROM %s";
  cursor.query(q1, (idField, nameTable));
  var n:int= cursor.fetchone()['n']: int;
  var vertexNames: [1..n] string;

  var q2 = "SELECT %s, %s FROM %s ORDER BY 1";
  cursor.query(q2, (idField, nameField, nameTable));
  for row in cursor {
      vertexNames[row[idField]:int ] = row[nameField];
  }
  return vertexNames;
}

/*
 Build a random sparse matrix.  Good for testing;
 */
proc generateRandomSparseMatrix(size: int, sparsity: real) {
  const D: domain(2) = {1..size, 1..size};
  var SD: sparse subdomain(D) dmapped CS();
  var R: [SD] real;
  var da: domain(1) = {1..size};
  var array: [da] int = 1..size;
  var dom: domain(1) = {1..0};
  var indices: [dom] 2*int;
  var N = floor(size*(1-sparsity)): int;
  forall i in array {
    forall j in array {
      indices.push_back((i,j));
    }
  }
  shuffle(indices);
  var sparseids = indices[1..N];
  SD.bulkAdd(sparseids);
  forall (i,j) in sparseids {
    R(i,j) = 1;
  }
  //ndices = zip(array1, array2)
  return R;
}

// BATCH PERSISTENCE
 proc persistSparseMatrix(con: Connection, aTable: string
   , fromField: string, toField: string, weightField: string
   , X:[?D] real) {
   const q = "INSERT INTO %s (%s, %s, %s) VALUES (%s, %s, %s);";
   var cur = con.cursor();
   var count: int = 0;
   var dom: domain(1, int, false) = {1..0};
   var ts: [dom] (string, string, string, string, int, int, real);
   for ij in X.domain {
     ts.push_back((aTable, fromField, toField, weightField, ij(1), ij(2), X(ij)));
     count += 1;
     if count >= batchsize {
       cur.execute(q, ts);
       count = 0;
       var reset: [dom] (string, string, string, string, int, int, real);
       ts = reset;
     }
   }
   cur.execute(q,ts);
 }

//SERIAL PERSISTANCE FUNCTION FOR PERFORMANCE COMPARISONS
proc persistSparseMatrix_(con: Connection, aTable: string
  , fromField: string, toField: string, weightField: string
  , X:[?D] real) {
  const q = "INSERT INTO %s (%s, %s, %s) VALUES (%s, %s, %s);";
  var cur = con.cursor();
  for ij in X.domain {
    const d: domain(1) = {1..0};
    var t: [d] (string, string, string, string, int, int, real);
    t.push_back((aTable, fromField, toField, weightField, ij(1), ij(2), X(ij)));
    cur.execute(q, t);
  }
}

// PARALLEL PERSISTENCE FUNCTION FOR PERFORMANCE COMPARISONS
proc persistSparseMatrix_P(con: Connection, aTable: string
  , fromField: string, toField: string, weightField: string
  , X:[?D] real) {
  const q = "INSERT INTO %s (%s, %s, %s) VALUES (%s, %s, %s);";
  var cur = con.cursor();
  forall ij in X.domain {
    const d: domain(1) = {1..0};
    var t: [d] (string, string, string, string, int, int, real);
    t.push_back((aTable, fromField, toField, weightField, ij(1), ij(2), X(ij)));
    cur.execute(q, t);
  }
}

proc sparsity(X) {
  const d = X.shape[1]:real * X.shape[2]: real;
  var i: real = 0.0;
  for ij in X.domain {
    i += 1.0;
  }
  return i / d;
}

use Cdo,
    LinearAlgebra,
    LinearAlgebra.Sparse,
    NumSuch,
    Random;

config param batchsize = 10000;

/*
 A matrix of data with named rows and columns.
 */
class NamedMatrix {
  var D: domain(2),
      SD = CSRDomain(D),
      X: [SD] real,  // the actual data
      rows: BiMap = new BiMap(),
      cols: BiMap = new BiMap();

   proc init() {
     super.init();
   }

   proc init(X) {
     this.D = {X.domain.dim(1), X.domain.dim(2)};
     super.init();
     this.loadX(X);
   }
}

/*
Loads the data from X into the internal array, also called X.  We call them all X to keep it clear.

:arg real[]: Array representing the matrix
 */
proc NamedMatrix.loadX(X:[], shape: 2*int =(-1,-1)) {
  if shape(1) > 0 && shape(2) > 0 {
    this.D = {1..shape(1), 1..shape(2)};
  }
  for (i,j) in X.domain {
    this.SD += (i,j);
    this.X(i,j) = X(i,j);
  }
}

/*
Sets the row names for the matrix X
 */
proc NamedMatrix.setRowNames(rn:[]): string throws {
  if rn.size != X.domain.dim(1).size then throw new Error();
  for i in 1..rn.size {
    this.rows.add(rn[i]);
  }
  return this.rows;
}

/*
Sets the column names for the matrix X
*/
proc NamedMatrix.setColNames(cn:[]): string throws {
  if cn.size != X.domain.dim(2).size then throw new Error();
  for i in 1..cn.size {
    this.cols.add(cn[i]);
  }
  return this.cols;
}

/*
 Gets the value of the (i,j) entry of the matrix X in the NamedMatrix
 */
proc NamedMatrix.get(i: int, j: int) {
   return this.X(i,j);
 }

 /*
 Get the values using the named rows and columns
  */
proc NamedMatrix.get(f: string, t: string) {
    return this.X(rows.get(f), cols.get(t));
 }

/*
 Set the value of this.X(i,j) by index row
 */
proc NamedMatrix.set(i: int, j: int, w: real) {
    if this.SD.member((i,j)) {
      this.X(i,j) = w;
    } else {
      this.SD += (i,j);
      this.X(i,j) = w;
    }
    return w;
}

/*
Set the values using the row and column names
*/
proc NamedMatrix.set(f: string, t: string, w: real) {
  return this.set(rows.get(f), cols.get(t), w);
}

/*
Update the value in X(i,j) with `w`
 */
proc NamedMatrix.update(i: int, j: int, w: real) {
  const x = this.get(i,j);
  return this.set(i,j, x + w);
}

/*
Update the value in X(i,j) with `w` using the row column names
 */
proc NamedMatrix.update(f: string, t: string, w: real) {
  const x = this.get(rows.get(f),cols.get(t));
  return this.set(rows.get(f),cols.get(t), x + w);
}

/*
Find the number of non-zeroes in the matrix
 */
proc NamedMatrix.nnz() {
  var i: int = 0;
  for ij in this.X.domain {
    i += 1;
  }
  return i;
}

/*
Calculates the sparsity of the matrix: Number of entries / frame size
 */
proc NamedMatrix.sparsity() {
  const d = this.X.shape[1]:real * this.X.shape[2]: real;
  return this.nnz():real / d;
}

/*
 Multiplies the current NamedMatrix `X` against the argument `Y`, but first it aligns
 the names of `X.cols` with `Y.rows`.  Returns an appropriately named NamedMatrix
 :arg NamedMatrix Y:
 */
proc NamedMatrix.alignAndMultiply(Y: NamedMatrix) {
    var rcOverlap: domain(string) = this.cols.keys & Y.rows.keys;

    var xSD: sparse subdomain(this.D) dmapped CS(),
        ySD: sparse subdomain(Y.D) dmapped CS(),
        xX: [xSD] real,  // the actual data
        yX: [ySD] real,  // the actual data
        xrows: BiMap = new BiMap(),
        yrows: BiMap = new BiMap(),
        xcols: BiMap = new BiMap(),
        ycols: BiMap = new BiMap();

    for (left_row, right_col) in zip(this.D.dim(2), Y.D.dim(1)) {
      for rc in rcOverlap {
        const j = this.cols.get(rc);
        if this.SD.member(left_row, j) {
          xSD += (left_row, j);
          xX[left_row, j] = this.X[left_row, j];
        }
        if Y.SD.member(j, right_col) {
          ySD += (j, right_col);
          yX[j, right_col] = Y.X[j, right_col];
        }
      }
    }
    const z = xX.dot(yX);
    var n = new NamedMatrix(X=z);
    n.rows = this.rows;
    n.cols = Y.cols;
    return n;
}

/*
 Creates a NamedMatrix from a table in Postgres.  Does not optimize for square matrices.  This assumption
 is that the matrix is sparse.

 :arg string edgeTable: The SQL table holding the values of the matrix.
 :arg string fromField: The table column representing rows, e.g. `i`.
 :arg string toField: The table column representing columns, e.g. 'j'.
 :arg string wField: `default=NONE` the table column containing the values of cell `(i,j)``
 :arg boolean square: Whether the matrix should be built to have the same rows and columns
 */

 proc NamedMatrixFromPG(con: Connection
   , edgeTable: string
   , fromField: string, toField: string, wField: string = "NONE"
   , square=false) {
  if square {
    return NamedMatrixFromPGSquare(con: Connection
      , edgeTable, fromField, toField, wField);
  } else {
    return NamedMatrixFromPGRectangular(con: Connection
      , edgeTable, fromField, toField, wField);
  }
}

proc NamedMatrixFromPGRectangular(con: Connection
  , edgeTable: string
  , fromField: string, toField: string, wField: string = "NONE") {

  var q = """
  SELECT ftr, t
  FROM (
    SELECT distinct(%s) AS ftr, 'r' AS t FROM %s
    UNION ALL
    SELECT distinct(%s) AS ftr, 'c' AS t FROM %s
  ) AS a
  GROUP BY ftr, t
  ORDER BY ftr, t ;
  """;

  var rows: BiMap = new BiMap(),
      cols: BiMap = new BiMap();

  var cursor = con.cursor();
  cursor.query(q, (fromField, edgeTable, toField, edgeTable));

  for row in cursor {
    if row['t'] == 'r' {
      rows.add(row['ftr']);
    } else if row['t'] == 'c' {
      cols.add(row['ftr']);
    }
  }

  var D: domain(2) = {1..rows.size(), 1..cols.size()},
      SD = CSRDomain(D),
      X: [SD] real;  // the actual data

  var r = """
  SELECT %s, %s
  FROM %s
  ORDER BY %s, %s ;
  """;
  var cursor2 = con.cursor();
  cursor2.query(r, (fromField, toField, edgeTable, fromField, toField));
  const size = cursor2.rowcount(): int;
  var count = 0: int,
      dom = {1..size},
      indices: [dom] (int, int),
      values: [dom] real;

  // This guy is causing problems.  Exterminiate with extreme prejudice
  //forall row in cursor2 {
  forall row in cursor2 with (+ reduce count) {
    count += 1;
    indices[count]=(
       rows.get(row[fromField])
      ,cols.get(row[toField])
      );

    if wField == "NONE" {
      values[count] = 1;
    } else {
      values[count] = row[wField]: real;
    }
  }

  SD.bulkAdd(indices);
  forall (ij, a) in zip(indices, values) {
    X(ij) = a;
  }

  const nm = new NamedMatrix(X=X);
  nm.rows = rows;
  nm.cols = cols;
  return nm;
}

/*
 Build a square version of the matrix.  Still directed, but with the same number of rows/cols
 */
proc NamedMatrixFromPGSquare ( con: Connection
    , edgeTable: string
    , fromField: string, toField: string, wField: string = "NONE") {

    var q = """
    SELECT ftr
    FROM (
      SELECT distinct(%s) AS ftr FROM %s
      UNION ALL
      SELECT distinct(%s) AS ftr FROM %s
    ) AS a
    GROUP BY ftr ORDER BY ftr;
    """;

    var cursor = con.cursor();
    cursor.query(q, (fromField, edgeTable, toField, edgeTable));
    var rows: BiMap = new BiMap();

    forall row in cursor {
    //for row in cursor {
      rows.add(row['ftr']);
    }

    var D: domain(2) = {1..rows.size(), 1..rows.size()},
        SD = CSRDomain(D),
        X: [SD] real;  // the actual data

    var r = """
    SELECT %s, %s
    FROM %s
    ORDER BY %s, %s ;
    """;
    var cursor2 = con.cursor();
    cursor2.query(r, (fromField, toField, edgeTable, fromField, toField));
    var dom1: domain(1) = {1..0},
        dom2: domain(1) = {1..0},
        indices: [dom1] (int, int),
        values: [dom2] real;
    //forall row in cursor2 {
    for row in cursor2 {
      indices.push_back((
         rows.get(row[fromField])
        ,rows.get(row[toField])
        ));

      if wField == "NONE" {
        values.push_back(1);
      } else {
        values.push_back(row[wField]: real);
      }
    }

    SD.bulkAdd(indices);
    forall (ij, a) in zip(indices, values) {
      X(ij) = a;
    }

    const nm = new NamedMatrix(X=X);
    nm.rows = rows;
    nm.cols = rows;
    return nm;
}

proc NamedMatrixFromPG_(con: Connection
  , edgeTable: string
  , fromField: string, toField: string, wField: string = "NONE") {

  var q = """
  SELECT ftr, t, row_number() OVER(PARTITION BY t ORDER BY ftr ) AS ftr_id
  FROM (
    SELECT distinct(%s) AS ftr, 'r' AS t FROM %s
    UNION ALL
    SELECT distinct(%s) AS ftr, 'c' AS t FROM %s
  ) AS a
  GROUP BY ftr, t
  ORDER BY ftr_id, t ;
  """;

  var rows: BiMap = new BiMap(),
      cols: BiMap = new BiMap();

  var cursor = con.cursor();
  cursor.query(q, (fromField, edgeTable, toField, edgeTable));
  for row in cursor {
    if row['t'] == 'r' {
      rows.add(row['ftr'], row['ftr_id']:int);
    } else if row['t'] == 'c' {
      cols.add(row['ftr'], row['ftr_id']:int);
    }
  }

  var D: domain(2) = {1..rows.size(), 1..cols.size()},
      SD = CSRDomain(D),
      X: [SD] real;  // the actual data

  var r = """
  SELECT %s, %s
  FROM %s
  ORDER BY %s, %s ;
  """;
  var cursor2 = con.cursor();
  cursor2.query(r, (fromField, toField, edgeTable, fromField, toField));
  var dom1: domain(1) = {1..0},
      dom2: domain(1) = {1..0},
      indices: [dom1] (int, int),
      values: [dom2] real;

  //forall row in cursor2 {
  for row in cursor2 {
//    writeln("row: ", row[fromField], " -> ", row[toField]);
    indices.push_back((
       rows.get(row[fromField])
      ,cols.get(row[toField])
      ));

    if wField == "NONE" {
      values.push_back(1);
    } else {
      values.push_back(row[wField]: real);
    }
  }
  SD.bulkAdd(indices);
  for (ij, a) in zip(indices, values) {
    X(ij) = a;
  }

  const nm = new NamedMatrix(X=X);
  nm.rows = rows;
  nm.cols = cols;
  return nm;
}

/*

  :arg con: A CDO Connection to Postgres
  :arg edgeTable: The table in PG of edges
  :arg fromField: The field of edgeTable containing the id of the head vertex
  :arg toField: the field of edgeTable containing the id of the tail vertex
  :arg wField: The field of edgeTable containing the weight of the edge
  :arg n: number of distinct vertices. In practice, this may be gives and the number of names
  :arg weights: Boolean on whether to use the weights in the table or a 1 (indicator)
 */
proc wFromPG(con: Connection, edgeTable: string
    , fromField: string, toField: string, wField: string, weights=false) {
  var q = "SELECT %s, %s FROM %s ORDER BY 1, 2;";
  var source_size_q = """
                      SELECT count(*) AS n FROM (SELECT distinct(s.source_cui) FROM r.cui_confabulation s) AS sources;
                      """;

  var target_size_q = """
                      SELECT count(*) AS n FROM (SELECT distinct(s.exhibited_cui) FROM r.cui_confabulation s) AS exhibited;
                      """;
  var cursor2 = con.cursor();
  var cursor3 = con.cursor();
  cursor2.query(source_size_q);
  cursor3.query(target_size_q);
  const row2 = cursor2.fetchone();
  const row3 = cursor3.fetchone();
  const source_size = row2['n']: int;
  const exhibit_size = row3['n']: int;

  var cursor = con.cursor();
  cursor.query(q,(fromField, toField, edgeTable));
  const size = cursor.rowcount(): int;
  var D: domain(2) = {1..source_size, 1..exhibit_size};
  var SD: sparse subdomain(D) dmapped CS();
  var X: [SD] real;
  var dom: domain(1) = {1..size};
  var indices: [dom] (int, int);
  var values: [dom] real;
  forall (row, i) in zip(cursor, dom) {
    indices[i] = (row[fromField]: int,row[toField]: int);
    values[i] = 1: real;
  }
  SD.bulkAdd(indices);
  forall (ij, a) in zip(indices, values) {
    if weights {
      X(ij) = a;
    } else {
      X(ij) = 1;
    }
  }
  return X;
}

//SERIAL EXTRACTION FUNCTION FOR PERFORMANCE COMPARISONS
proc wFromPG_(con: Connection, edgeTable: string
    , fromField: string, toField: string, wField: string, n: int, weights=true) {
  var q = "SELECT %s, %s, %s FROM %s ORDER BY 1, 2;";
  var cursor = con.cursor();
  cursor.query(q,(fromField, toField, wField, edgeTable));
  const D: domain(2) = {1..n, 1..n};
  var SD: sparse subdomain(D) dmapped CS();
  var W: [SD] real;

  for row in cursor {
    SD += (row[fromField]: int, row[toField]:int);
    if weights {
      W[row[fromField]:int, row[toField]:int] = row[wField]: real;
    } else {
      W[row[fromField]:int, row[toField]:int] = 1;
    }
  }
   return W;
}

/*

 :arg con: A connection to a Postgres database containing a table with <ftr_id>, <vertex_name> pairs
 :arg nameTable: The name of the Postgres table containing the pairs
 :arg nameField: The name of the field in the nameTable containing the names
 :arg idField: The name of the field in the nameTable containing the feature ids

 :returns: An array of strings in order of feature id
 */
proc vNamesFromPG(con: Connection, nameTable: string
  , nameField: string, idField: string ) {

  var cursor = con.cursor();
  var q1 = "SELECT max(%s) AS n FROM %s";
  cursor.query(q1, (idField, nameTable));
  var n:int= cursor.fetchone()['n']: int;
  var vertexNames: [1..n] string;

  var q2 = "SELECT %s, %s FROM %s ORDER BY 1";
  cursor.query(q2, (idField, nameField, nameTable));
  for row in cursor {
      vertexNames[row[idField]:int ] = row[nameField];
  }
  return vertexNames;
}

/*
 Build a random sparse matrix.  Good for testing;
 */
proc generateRandomSparseMatrix(size: int, sparsity: real) {
  const D: domain(2) = {1..size, 1..size};
  var SD: sparse subdomain(D) dmapped CS();
  var R: [SD] real;
  var da: domain(1) = {1..size};
  var array: [da] int = 1..size;
  var dom: domain(1) = {1..0};
  var indices: [dom] 2*int;
  var N = floor(size*(1-sparsity)): int;
  forall i in array {
    forall j in array {
      indices.push_back((i,j));
    }
  }
  shuffle(indices);
  var sparseids = indices[1..N];
  SD.bulkAdd(sparseids);
  forall (i,j) in sparseids {
    R(i,j) = 1;
  }
  //ndices = zip(array1, array2)
  return R;
}

// BATCH PERSISTENCE
 proc persistSparseMatrix(con: Connection, aTable: string
   , fromField: string, toField: string, weightField: string
   , X:[?D] real) {
   const q = "INSERT INTO %s (%s, %s, %s) VALUES (%s, %s, %s);";
   var cur = con.cursor();
   var count: int = 0;
   var dom: domain(1, int, false) = {1..0};
   var ts: [dom] (string, string, string, string, int, int, real);
   for ij in X.domain {
     ts.push_back((aTable, fromField, toField, weightField, ij(1), ij(2), X(ij)));
     count += 1;
     if count >= batchsize {
       cur.execute(q, ts);
       count = 0;
       var reset: [dom] (string, string, string, string, int, int, real);
       ts = reset;
     }
   }
   cur.execute(q,ts);
 }

//SERIAL PERSISTANCE FUNCTION FOR PERFORMANCE COMPARISONS
proc persistSparseMatrix_(con: Connection, aTable: string
  , fromField: string, toField: string, weightField: string
  , X:[?D] real) {
  const q = "INSERT INTO %s (%s, %s, %s) VALUES (%s, %s, %s);";
  var cur = con.cursor();
  for ij in X.domain {
    const d: domain(1) = {1..0};
    var t: [d] (string, string, string, string, int, int, real);
    t.push_back((aTable, fromField, toField, weightField, ij(1), ij(2), X(ij)));
    cur.execute(q, t);
  }
}

// PARALLEL PERSISTENCE FUNCTION FOR PERFORMANCE COMPARISONS
proc persistSparseMatrix_P(con: Connection, aTable: string
  , fromField: string, toField: string, weightField: string
  , X:[?D] real) {
  const q = "INSERT INTO %s (%s, %s, %s) VALUES (%s, %s, %s);";
  var cur = con.cursor();
  forall ij in X.domain {
    const d: domain(1) = {1..0};
    var t: [d] (string, string, string, string, int, int, real);
    t.push_back((aTable, fromField, toField, weightField, ij(1), ij(2), X(ij)));
    cur.execute(q, t);
  }
}

proc sparsity(X) {
  const d = X.shape[1]:real * X.shape[2]: real;
  var i: real = 0.0;
  for ij in X.domain {
    i += 1.0;
  }
  return i / d;
}