Minor fixes in hplExample2 - fixed an assertion, minor changes in how ranges in
LUFactorize function are being defined (an upper bound is now explicitly stated
rather than having it be inferred from a domain slice).

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

// Like hplExample1 but without using the Panel2D structure (or counted
// ranges).

use Random;

// calculate C = C - A * B.
def dgemm(
    p : int,    // number of rows in A
    q : int,    // number of cols in A, number of rows in B
    r : int,    // number of cols in B
    A : [1..p, 1..q] ?t,
    B : [1..q, 1..r] t,
    C : [1..p, 1..r] t)
{
    // Calculate (i,j) using a dot product of a row of A and a column of B.
    for i in 1..p {
        for j in 1..r {
            for k in 1..q {
                C[i,j] -= A[i, k] * B[k, j];
            }
        }
    }
}

// do unblocked-LU decomposition within the specified panel, update the
// pivot vector accordingly
def panelSolve(
    A : [] ?t,
    panel : domain(2),
    piv : [] int)
{
    var pnlRows = panel.dim(1);
    var pnlCols = panel.dim(2);

    // Ideally some type of assertion to ensure panel is embedded in A's
    // domain
    assert(piv.domain.dim(1) == A.domain.dim(1));

    // iterate through the columns
    for k in pnlCols {
        var col = panel[k.., k..k];

        // The pivot is the element with the largest absolute value.
        var (pivot, pivotRow) =
            maxloc reduce(abs(A(col)), col.dim(1));

        // Swap the current row with the pivot row
        piv[k] <=> piv[pivotRow];
        A[k, ..] <=> A[pivotRow, ..];

        if(pivot == 0) then halt("Matrix can not be factorized");

        // divide all values below and in the same col as the pivot by
        // the pivot
        if k+1 <= pnlRows.high {
            A(col)[k+1.., k..k] /= pivot;
        }

        // update all other values below the pivot
        if k+1 <= pnlRows.high && k+1 <= pnlCols.high {
            forall (i,j) in panel[k+1.., k+1..] {
                A[i,j] -= A[i,k] * A[k,j];
            }
        }
    }
}

// Update the block row (tr for top-right) portion of the matrix in a blocked
// LU decomposition.  Each step of the LU decomposition will solve a block
// (tl for top-left) portion of a matrix. This function solves the rows to the
// right of the block.
def updateBlockRow(A : [] ?t, tl : domain(2), tr : domain(2))
{
    var tlRows = tl.dim(1);
    var tlCols = tl.dim(2);
    var trRows = tr.dim(1);
    var trCols = tr.dim(2);

    assert(tlCols == trRows);

    for i in trRows {
        forall j in trCols {
            for k in tlRows.low..i-1 {
                A[i, j] -= A[i, k] * A[k,j];
            }
        }
    }
}

// blocked LU factorization with pivoting for matrix augmented with vector of
// RHS values.
def LUFactorize(n : int, A : [1..n, 1..n+1] real, piv : [1..n] int) {
    const ARows = A.domain.dim(1);
    const ACols = A.domain.dim(2);

    param blkSize = 3;
    piv = 1..n;

    /*
       The following diagram illustrates how we partition the matrix.  The
       program defines a number of ranges (specified in the legend) to span
       various portions of the array.

       We use these ranges to find subdomains of the array (labeled in the
       picture as tl, tr, bl, and br) there's an additional domain (not shown)
       l that is the union of tl and bl.

                (point blk, blk)
       +-------//------------------+ -----------   LEGEND:
       |......//...................|          |      1: blockRange
       |.....//....................|          |      2: trailingRows
       |....+-----+----------------| ------.  |      3: unfactoredRows
       |....|     |                |  |    |  |      4: trailingCols
       |....| tl  |      tr        |  1    |         5: unfactoredCols
       |....|     |                |  |    |  7      6: ARows
       |....+-----+----------------| ---             7: ACols
       |....|     |                |  |    3  |
       |....|     |                |          |
       |....| bl  |      br        |  2    |  |
       |....|     |                |       |  |
       |....|     |                |  |    |  |
       +----+-----+----------------+ ------------

       |    |    |                 |
       |    |-1 -|------ 4 --------|
       |    |                      |
       |    `--------- 5 ----------|
       |                           |
       |------------ 6 ------------|
       |                           |
    */

    for blk in 1..n by blkSize {
        const crntBlkSize = min(blkSize, n-blk+1);

        const blockRange     = blk..blk+crntBlkSize-1;
        const trailingRows   = (blk+crntBlkSize..ARows.high);
        const unfactoredRows = (blk..ARows.high);
        const trailingCols   = (blk+crntBlkSize..ACols.high);
        const unfactoredCols = (blk..ACols.high);

        var tl = [blockRange, blockRange];
        var tr = [blockRange, trailingCols];
        var bl = [trailingRows, blockRange];
        var br = [trailingRows, trailingCols];
        var l  = [unfactoredRows, blockRange];

        // Now that we've sliced and diced A properly do the blocked-LU
        // computation:
        panelSolve(A, l, piv);
        updateBlockRow(A, tl, tr);

        // update trailing submatrix (if there)
        if(br.numIndices > 0) {
            dgemm(trailingRows.length, blockRange.length, trailingCols.length,
                  A(bl), A(tr), A(br));
          }
      }
}

// --------------------------------------------------------------------------
//   TESTING SYSTEM:
// --------------------------------------------------------------------------
def matrixMult(
    const m : int,
    const p : int,
    const n : int,
    const A : [1..m, 1..p],
    const B : [1..p, 1..n],
    C : [1..m, 1..n])
{
    C = 0;

    forall (i,j,k) in [C.domain.dim(1), C.domain.dim(2), 1..p] {
        C[i,j] += A[i,k] * B[k,j];
    }
}

// given a matrix in A in form: [L, U] multiply the L and U parts into
// a resulting matrix C.
def selfMult(n : int, A : [1..n,1..n] real, C : [1..n,1..n] real) {
    C = 0;

    forall (i,j) in C.domain {
        if(i <= j) {
            for k in 1..i-1 {
                C[i,j] += A[i,k] * A[k,j];
            }
            C[i,j] += A[i,j];
        }
        else if(j <= i){
            for k in 1..j {
                C[i,j] += A[i,k] * A[k,j];
            }
        }
    }
}

// given a matrix and a permuatations vector apply the permutations
// QUESTION: I'm intending vectorIn to be passed by value in this instance
// (since I modify it in the function but I don't want the result sent out).
// is this function doing the trick?
def permuteMatrix(matrix : [?dmn], in vector) {
    //var pdmn : sparse subdomain(dmn);
    var pdmn =
        [1..vector.domain.dim(1).length, 1..vector.domain.dim(1).length];
    var p : [pdmn] int;
    //p.IRV = 0;

    // construct permutation matrix
    forall i in vector {
        //pdmn.add((i, vector[i]));
        p[i, vector[i]] = 1;
    }

    // multiply permutation matrix times the matrix
    var permuted = matrix;

    matrixMult(
        dmn.dim(1).length,
        dmn.dim(1).length,
        dmn.dim(2).length,
        p, matrix, permuted);

    matrix = permuted;
}

def permuteBack(matrix : [?dmn], in vector) {
    // this just does a bubblesort of the permutation vector swapping the
    // rows of the matrix alongside. Yes the performance here will be horrible,
    // not to mention all the data being shuffled around, but this function
    // is in the test system so it's excusable.

    const low  = vector.domain.dim(1).low;
    const high = vector.domain.dim(1).high;

    for i in vector.domain {
        for j in low..high-1 {
            if vector[j+1] < vector[j] {
                vector[j] <=> vector[j+1];
                matrix[j, ..] <=> matrix[j+1, ..];
            }
        }
    }
}

def test_permuteMatrix(rprt = true) : bool {
    // test one of the testing functions: permuteMatrix (is this getting a
    // little too meta here?)

    param n = 10;
    var rand = new RandomStream();

    // this n by n array is filled so each element is assigned to its
    // row number. This test will permute these elements, keeping a pivot
    // vector alongside.
    var A : [1..n, 1..n] int = [(i,j) in [1..n, 1..n]] i;
    var piv : [1..n] int = [i in 1..n] i;
    var AOrig = A;

    // do 100 random swaps of A
    for i in 1..100 {
        var randRow1 = (rand.getNext() * 10000):int % n + 1;
        var randRow2 = (rand.getNext() * 10000):int % n + 1;

        A[randRow1, ..] <=> A[randRow2, ..];
        piv[randRow1] <=> piv[randRow2];
    }

    // permute original version of A
    permuteMatrix(AOrig, piv);

    // calculate and report results. Calculate by comparing the original
    // version of A which we just permuted to swapped version.
    if rprt then write("test_permuteMatrix: ");
    if & reduce(AOrig == A) then {
        if rprt then writeln("PASSED");
        return true;
    } else {
        if rprt then writeln("FAILED");
        return false;
    }
}

def test_panelSolve(rprt = true) : bool {
    var rand = new RandomStream();

    var piv : [1..8] int = [i in 1..8] i;
    var A : [1..8, 1..9] real =
        [(i,j) in [1..8, 1..9]] (rand.getNext() * 10000):int % 100 + 1;
    var AOrig = A;

    var AOrig2 = A;

    // grab a panel and solve it
    param offset = 3;
    param blkSize = 3;
    var pnl = A.domain[offset.., offset..offset+blkSize];

    panelSolve(A, pnl, piv);

    // to test multiply the L and U parts of the top portion of the panel
    // (just the block) together and check that the result matches the
    // block on the original matrix when permuted.
    var C : [1..blkSize, 1..blkSize] real;
    var blockD =
        [offset..offset+blkSize-1, offset..offset+blkSize-1];
    selfMult(blkSize, A(blockD), C);
    permuteMatrix(AOrig, piv);

    // calculate and report results.
    if rprt then write("test_panelSolve: ");
    // NOTE: The reduce statement requires this heavy parenthesization,
    // if I just do "if(& reduce AOrig(blockD) == C) {" it'll cause a compiler
    // error.
    if(& reduce ((AOrig(blockD)) - C <= .001)) {
        if rprt then writeln("PASSED");
        return true;
    } else {
        if rprt then writeln("FAILED");
        return false;
    }
}

def test_updateBlockRow(rprt = true) : bool {
    var rand = new RandomStream();

    // construct a matrix A = [X | Y], where X is an already LU-factorized
    // submatrix and Y is the block row we wish to update and test
    var randomOffset : int = (rand.getNext() * 100):int;
    var randomHeight : int = (rand.getNext() * 100):int + 1;
    var randomWidth  : int = (rand.getNext() * 100):int + randomHeight;
    var A : [randomOffset..randomOffset+randomHeight-1,
             randomOffset..randomOffset+randomWidth-1] real;
    rand.fillRandom(A);
    var OrigA = A;

    // capture X and Y
    var X = [randomOffset..randomOffset+randomHeight-1,
             randomOffset..randomOffset+randomHeight-1];
    var Y = [randomOffset..randomOffset+randomHeight-1,
             randomOffset+randomHeight..(randomWidth-randomHeight)];

    // apply the update block row function
    updateBlockRow(A, X, Y);

    // do a matrix multiply of L and U to obtain the original values in
    // the block row
    var recalc : [randomOffset..randomOffset+randomHeight-1,
                  randomOffset+randomHeight..(randomWidth-randomHeight)] real;
    forall (i,j) in recalc.domain {
        for k in randomOffset..i-1 {
            recalc[i,j] += A[i,k] * A[k,j];
        }
        recalc[i,j] += A[i,j];
    }

    // calculate and report results
    if rprt then write("test_updateBlockRow: ");
    if(& reduce (abs(recalc - (OrigA(recalc.domain))) <= 0.1)) {
        if rprt then writeln("PASSED");
        return true;
    }
    else {
        if rprt then writeln("FAILED");
        return false;
    }
}

def test_LUFactorize(rprt = true) : bool {
    var rand = new RandomStream();

    var randomN : int = (rand.getNext() * 10):int + 1;
    var A : [1..randomN, 1..randomN+1] real;
    fillRandom(A);
    var origA = A;

    var piv : [1..randomN] int;

    LUFactorize(randomN, A, piv);

    var C : [1..randomN, 1..randomN] real;
    selfMult(randomN, A[1..randomN,1..randomN], C);
    permuteBack(C, piv);

    if rprt then write("test_LUFactorize: ");
    if(& reduce (abs(C - origA(1..randomN, 1..randomN)) <= 0.01)) {
        if rprt then writeln("PASSED");
        return true;
    } else {
        if rprt then writeln("FAILED");
        return false;
    }
}

def main() {
    var result = true;
    write("test_permuteMatrix: ");
    for i in 1..100 do
        result &= test_permuteMatrix(rprt=false);
    if result then writeln("PASSED");
    else writeln("FAILED");

    var numPassed = 0;
    write("test_panelSolve: ");
    for i in 1..1000 do
        numPassed += test_panelSolve(rprt=false);
    writeln(numPassed, " PASSED, ", 1000-numPassed, " FAILED");

    numPassed = 0;
    write("test_updateBlockRow: ");
    for i in 1..100 do
        numPassed += test_updateBlockRow(rprt=false);
    writeln(numPassed, " PASSED, ", 100-numPassed, " FAILED");

    write("test_LUFactorize: ");
    for i in 1..1000 do
        numPassed += test_LUFactorize(rprt=false);
    writeln(numPassed, " PASSED, ", 1000-numPassed, " FAILED");
}

// Sample output:
// test_permuteMatrix: PASSED
// test_panelSolve: 938 PASSED, 62 FAILED
// test_updateBlockRow: 100 PASSED, 0 FAILED
// test_LUFactorize: 813 PASSED, 187 FAILED

// Like hplExample1 but without using the Panel2D structure (or counted
// ranges).

use Random;

// calculate C = C - A * B.
def dgemm(
    p : int,    // number of rows in A
    q : int,    // number of cols in A, number of rows in B
    r : int,    // number of cols in B
    A : [1..p, 1..q] ?t,
    B : [1..q, 1..r] t,
    C : [1..p, 1..r] t)
{
    // Calculate (i,j) using a dot product of a row of A and a column of B.
    for i in 1..p {
        for j in 1..r {
            for k in 1..q {
                C[i,j] -= A[i, k] * B[k, j];
            }
        }
    }
}

// do unblocked-LU decomposition within the specified panel, update the
// pivot vector accordingly
def panelSolve(
    A : [] ?t,
    panel : domain(2),
    piv : [] int)
{
    var pnlRows = panel.dim(1);
    var pnlCols = panel.dim(2);

    // Ideally some type of assertion to ensure panel is embedded in A's
    // domain
    assert(piv.domain.dim(1) == A.domain.dim(1));

    // iterate through the columns
    for k in pnlCols {
        var col = panel[k.., k..k];

        // The pivot is the element with the largest absolute value.
        var (pivot, pivotRow) =
            maxloc reduce(abs(A(col)), col.dim(1));

        // Swap the current row with the pivot row
        piv[k] <=> piv[pivotRow];
        A[k, ..] <=> A[pivotRow, ..];

        if(pivot == 0) then halt("Matrix can not be factorized");

        // divide all values below and in the same col as the pivot by
        // the pivot
        if k+1 <= pnlRows.high {
            A(col)[k+1.., k..k] /= pivot;
        }

        // update all other values below the pivot
        if k+1 <= pnlRows.high && k+1 <= pnlCols.high {
            forall (i,j) in panel[k+1.., k+1..] {
                A[i,j] -= A[i,k] * A[k,j];
            }
        }
    }
}

// Update the block row (tr for top-right) portion of the matrix in a blocked
// LU decomposition.  Each step of the LU decomposition will solve a block
// (tl for top-left) portion of a matrix. This function solves the rows to the
// right of the block.
def updateBlockRow(A : [] ?t, tl : domain(2), tr : domain(2))
{
    var tlRows = tl.dim(1);
    var tlCols = tl.dim(2);
    var trRows = tr.dim(1);
    var trCols = tr.dim(2);

    assert(tlRows == trRows);

    for i in trRows {
        forall j in trCols {
            for k in tlRows.low..i-1 {
                A[i, j] -= A[i, k] * A[k,j];
            }
        }
    }
}

// blocked LU factorization with pivoting for matrix augmented with vector of
// RHS values.
def LUFactorize(n : int, A : [1..n, 1..n+1] real, piv : [1..n] int) {
    const ARows = A.domain.dim(1);
    const ACols = A.domain.dim(2);

    param blkSize = 3;
    piv = 1..n;

    /*
       The following diagram illustrates how we partition the matrix.  The
       program defines a number of ranges (specified in the legend) to span
       various portions of the array.

       We use these ranges to find subdomains of the array (labeled in the
       picture as tl, tr, bl, and br) there's an additional domain (not shown)
       l that is the union of tl and bl.

                (point blk, blk)
       +-------//------------------+ -----------   LEGEND:
       |......//...................|          |      1: blockRange
       |.....//....................|          |      2: trailingRows
       |....+-----+----------------| ------.  |      3: unfactoredRows
       |....|     |                |  |    |  |      4: trailingCols
       |....| tl  |      tr        |  1    |         5: unfactoredCols
       |....|     |                |  |    |  7      6: ARows
       |....+-----+----------------| ---             7: ACols
       |....|     |                |  |    3  |
       |....|     |                |          |
       |....| bl  |      br        |  2    |  |
       |....|     |                |       |  |
       |....|     |                |  |    |  |
       +----+-----+----------------+ ------------

       |    |    |                 |
       |    |-1 -|------ 4 --------|
       |    |                      |
       |    `--------- 5 ----------|
       |                           |
       |------------ 6 ------------|
       |                           |
    */

    for blk in 1..n by blkSize {
        const crntBlkSize = min(blkSize, n-blk+1);

        const blockRange     = blk..blk+crntBlkSize-1;
        const trailingRows   = (blk+crntBlkSize..)(ARows);
        const unfactoredRows = (blk..)(ARows);
        const trailingCols   = (blk+crntBlkSize..)(ACols);
        const unfactoredCols = (blk..)(ACols);

        var tl = [blockRange, blockRange];
        var tr = [blockRange, trailingCols];
        var bl = [trailingRows, blockRange];
        var br = [trailingRows, trailingCols];
        var l  = [unfactoredRows, blockRange];

        // Now that we've sliced and diced A properly do the blocked-LU
        // computation:
        panelSolve(A, l, piv);
        updateBlockRow(A, tl, tr);

        // update trailing submatrix (if there)
        if(br.numIndices > 0) {
            dgemm(trailingRows.length, blockRange.length, trailingCols.length,
                  A(bl), A(tr), A(br));
          }
      }
}

// --------------------------------------------------------------------------
//   TESTING SYSTEM:
// --------------------------------------------------------------------------
def matrixMult(
    const m : int,
    const p : int,
    const n : int,
    const A : [1..m, 1..p],
    const B : [1..p, 1..n],
    C : [1..m, 1..n])
{
    C = 0;

    forall (i,j,k) in [C.domain.dim(1), C.domain.dim(2), 1..p] {
        C[i,j] += A[i,k] * B[k,j];
    }
}

// given a matrix in A in form: [L, U] multiply the L and U parts into
// a resulting matrix C.
def selfMult(n : int, A : [1..n,1..n] real, C : [1..n,1..n] real) {
    C = 0;

    forall (i,j) in C.domain {
        if(i <= j) {
            for k in 1..i-1 {
                C[i,j] += A[i,k] * A[k,j];
            }
            C[i,j] += A[i,j];
        }
        else if(j <= i){
            for k in 1..j {
                C[i,j] += A[i,k] * A[k,j];
            }
        }
    }
}

// given a matrix and a permuatations vector apply the permutations
// QUESTION: I'm intending vectorIn to be passed by value in this instance
// (since I modify it in the function but I don't want the result sent out).
// is this function doing the trick?
def permuteMatrix(matrix : [?dmn], in vector) {
    //var pdmn : sparse subdomain(dmn);
    var pdmn =
        [1..vector.domain.dim(1).length, 1..vector.domain.dim(1).length];
    var p : [pdmn] int;
    //p.IRV = 0;

    // construct permutation matrix
    forall i in vector {
        //pdmn.add((i, vector[i]));
        p[i, vector[i]] = 1;
    }

    // multiply permutation matrix times the matrix
    var permuted = matrix;

    matrixMult(
        dmn.dim(1).length,
        dmn.dim(1).length,
        dmn.dim(2).length,
        p, matrix, permuted);

    matrix = permuted;
}

def permuteBack(matrix : [?dmn], in vector) {
    // this just does a bubblesort of the permutation vector swapping the
    // rows of the matrix alongside. Yes the performance here will be horrible,
    // not to mention all the data being shuffled around, but this function
    // is in the test system so it's excusable.

    const low  = vector.domain.dim(1).low;
    const high = vector.domain.dim(1).high;

    for i in vector.domain {
        for j in low..high-1 {
            if vector[j+1] < vector[j] {
                vector[j] <=> vector[j+1];
                matrix[j, ..] <=> matrix[j+1, ..];
            }
        }
    }
}

def test_permuteMatrix(rprt = true) : bool {
    // test one of the testing functions: permuteMatrix (is this getting a
    // little too meta here?)

    param n = 10;
    var rand = new RandomStream();

    // this n by n array is filled so each element is assigned to its
    // row number. This test will permute these elements, keeping a pivot
    // vector alongside.
    var A : [1..n, 1..n] int = [(i,j) in [1..n, 1..n]] i;
    var piv : [1..n] int = [i in 1..n] i;
    var AOrig = A;

    // do 100 random swaps of A
    for i in 1..100 {
        var randRow1 = (rand.getNext() * 10000):int % n + 1;
        var randRow2 = (rand.getNext() * 10000):int % n + 1;

        A[randRow1, ..] <=> A[randRow2, ..];
        piv[randRow1] <=> piv[randRow2];
    }

    // permute original version of A
    permuteMatrix(AOrig, piv);

    // calculate and report results. Calculate by comparing the original
    // version of A which we just permuted to swapped version.
    if rprt then write("test_permuteMatrix: ");
    if & reduce(AOrig == A) then {
        if rprt then writeln("PASSED");
        return true;
    } else {
        if rprt then writeln("FAILED");
        return false;
    }
}

def test_panelSolve(rprt = true) : bool {
    var rand = new RandomStream();

    var piv : [1..8] int = [i in 1..8] i;
    var A : [1..8, 1..9] real =
        [(i,j) in [1..8, 1..9]] (rand.getNext() * 10000):int % 100 + 1;
    var AOrig = A;

    var AOrig2 = A;

    // grab a panel and solve it
    param offset = 3;
    param blkSize = 3;
    var pnl = A.domain[offset.., offset..offset+blkSize];

    panelSolve(A, pnl, piv);

    // to test multiply the L and U parts of the top portion of the panel
    // (just the block) together and check that the result matches the
    // block on the original matrix when permuted.
    var C : [1..blkSize, 1..blkSize] real;
    var blockD =
        [offset..offset+blkSize-1, offset..offset+blkSize-1];
    selfMult(blkSize, A(blockD), C);
    permuteMatrix(AOrig, piv);

    // calculate and report results.
    if rprt then write("test_panelSolve: ");
    // NOTE: The reduce statement requires this heavy parenthesization,
    // if I just do "if(& reduce AOrig(blockD) == C) {" it'll cause a compiler
    // error.
    if(& reduce ((AOrig(blockD)) - C <= .001)) {
        if rprt then writeln("PASSED");
        return true;
    } else {
        if rprt then writeln("FAILED");
        return false;
    }
}

def test_updateBlockRow(rprt = true) : bool {
    var rand = new RandomStream();

    // construct a matrix A = [X | Y], where X is an already LU-factorized
    // submatrix and Y is the block row we wish to update and test
    var randomOffset : int = (rand.getNext() * 100):int;
    var randomHeight : int = (rand.getNext() * 100):int + 1;
    var randomWidth  : int = (rand.getNext() * 100):int + randomHeight;
    var A : [randomOffset..randomOffset+randomHeight-1,
             randomOffset..randomOffset+randomWidth-1] real;
    rand.fillRandom(A);
    var OrigA = A;

    // capture X and Y
    var X = [randomOffset..randomOffset+randomHeight-1,
             randomOffset..randomOffset+randomHeight-1];
    var Y = [randomOffset..randomOffset+randomHeight-1,
             randomOffset+randomHeight..(randomWidth-randomHeight)];

    // apply the update block row function
    updateBlockRow(A, X, Y);

    // do a matrix multiply of L and U to obtain the original values in
    // the block row
    var recalc : [randomOffset..randomOffset+randomHeight-1,
                  randomOffset+randomHeight..(randomWidth-randomHeight)] real;
    forall (i,j) in recalc.domain {
        for k in randomOffset..i-1 {
            recalc[i,j] += A[i,k] * A[k,j];
        }
        recalc[i,j] += A[i,j];
    }

    // calculate and report results
    if rprt then write("test_updateBlockRow: ");
    if(& reduce (abs(recalc - (OrigA(recalc.domain))) <= 0.1)) {
        if rprt then writeln("PASSED");
        return true;
    }
    else {
        if rprt then writeln("FAILED");
        return false;
    }
}

def test_LUFactorize(rprt = true) : bool {
    var rand = new RandomStream();

    var randomN : int = (rand.getNext() * 10):int + 1;
    var A : [1..randomN, 1..randomN+1] real;
    fillRandom(A);
    var origA = A;

    var piv : [1..randomN] int;

    LUFactorize(randomN, A, piv);

    var C : [1..randomN, 1..randomN] real;
    selfMult(randomN, A[1..randomN,1..randomN], C);
    permuteBack(C, piv);

    if rprt then write("test_LUFactorize: ");
    if(& reduce (abs(C - origA(1..randomN, 1..randomN)) <= 0.01)) {
        if rprt then writeln("PASSED");
        return true;
    } else {
        if rprt then writeln("FAILED");
        return false;
    }
}

def main() {
    var result = true;
    write("test_permuteMatrix: ");
    for i in 1..100 do
        result &= test_permuteMatrix(rprt=false);
    if result then writeln("PASSED");
    else writeln("FAILED");

    var numPassed = 0;
    write("test_panelSolve: ");
    for i in 1..1000 do
        numPassed += test_panelSolve(rprt=false);
    writeln(numPassed, " PASSED, ", 1000-numPassed, " FAILED");

    numPassed = 0;
    write("test_updateBlockRow: ");
    for i in 1..100 do
        numPassed += test_updateBlockRow(rprt=false);
    writeln(numPassed, " PASSED, ", 100-numPassed, " FAILED");

    write("test_LUFactorize: ");
    for i in 1..1000 do
        numPassed += test_LUFactorize(rprt=false);
    writeln(numPassed, " PASSED, ", 1000-numPassed, " FAILED");
}

// Sample output:
// test_permuteMatrix: PASSED
// test_panelSolve: 938 PASSED, 62 FAILED
// test_updateBlockRow: 100 PASSED, 0 FAILED
// test_LUFactorize: 813 PASSED, 187 FAILED


added amax/min and args for regular and named matrices

use 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() {
     this.initDone(); // NAMEDMATRIX DOESNT EXPLICITLY INHERIT ANYTHING SO IS THIS NECESSARY??
   }

   proc init(X) {
     this.D = {X.domain.dim(1), X.domain.dim(2)};
     this.initDone();
     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(rownames: [] string, colnames: [] string) {
     this.D = {1..rownames.size, 1..colnames.size};
     this.initDone();
     try! this.setRowNames(rownames);
     try! this.setColNames(colnames);
   }

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

   proc init(X, rows: BiMap, cols: BiMap) {
     this.init(X);
     this.rows = rows;
     this.cols = cols;
   }
}

/*
Returns the number of rows in the matrix frame
 */
proc NamedMatrix.nrows() {
  return X.domain.dim(1).size;
}

/*
Returns the number of columns in the matrix frame
 */
proc NamedMatrix.ncols() {
  return X.domain.dim(2).size;
}

/*
 Finds the max along the row indicated by row number
 */

proc NamedMatrix.rowMax(i: int) {
  return aMax(axis = 1, this.X)[i];
}

proc NamedMatrix.rowMax() {
  return aMax(axis = 1, this.X);
}

proc NamedMatrix.colMax(i: int) {
  return aMax(axis = 2, this.X)[i];

}

proc NamedMatrix.colMax() {
  return aMax(axis = 2, this.X);
}

proc NamedMatrix.rowArgMax(i) {
  return argMax(1, this.X)[i];
}

proc NamedMatrix.rowArgMax() {
  return argMax(1, this.X);
}

proc NamedMatrix.colArgMax(i) {
  return argMax(2, this.X)[i];
}

proc NamedMatrix.colArgMax() {
  return argMax(2, this.X);
}

proc NamedMatrix.rowMin(i: int) {
  return aMin(axis = 1, this.X)[i];
}

proc NamedMatrix.rowMin() {
  return aMin(axis = 1, this.X);
}

proc NamedMatrix.colMin(i: int) {
  return aMin(axis = 2, this.X)[i];
}

proc NamedMatrix.colMin() {
  return aMin(axis = 2, this.X);
}

proc NamedMatrix.rowArgMin(i) {
  return argMin(1, this.X)[i];
}

proc NamedMatrix.rowArgMin() {
  return argMin(1, this.X);
}

proc NamedMatrix.colArgMin(i) {
  return argMin(2, this.X)[i];
}

proc NamedMatrix.colArgMin() {
  return argMin(2, this.X);
}

//proc NamedMatrix.argRowMax()

/*
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 (X.domain.dim(1).size > 0) && (rn.size != X.domain.dim(1).size) {
  //if (1 > 0) && (rn.size != X.domain.dim(1).size) {
  if rn.size != X.domain.dim(1).size {
    const err = new DimensionMatchError(expected = X.domain.dim(1).size, actual=rn.size);
    throw err;
    return "";
  } else {
    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.ndot(N: NamedMatrix) {
  var C: NamedMatrix = new NamedMatrix(X = dot(this.X,N.X), this.rows, N.cols);
  return C;
}

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;
}

proc aMax(axis = 0, X:[]) {
  var is: domain(1) = 1..max(X.domain.dim(1).size,X.domain.dim(2).size);
  var sis: sparse subdomain(is);
  var maxima: [sis] real;
  if axis == 0 {
    var wDom: domain(1) = 1..X.domain.size;
    var ws = reshape(X, wDom);
    sis += 1;
    maxima[1] = (max reduce ws);
  }
  if axis == 2 {
    is = X.domain.dim(1);
    for i in is {
      var wDom: domain(1);
      var ws: [wDom] real;
      for a in transpose(X).domain.dimIter(2, i) {
        sis += i;
        ws.push_back(X(a,i));
      }
      if sis.member(i) {
        maxima[i] = max reduce ws;
      }
    }
  } else if axis == 1 {
    is = X.domain.dim(2);
    for i in is {
      var wDom: domain(1);
      var ws: [wDom] real;
      for a in X.domain.dimIter(2, i) {
        sis += i;
        ws.push_back(X(i,a));
      }
      if sis.member(i) {
        maxima[i] = max reduce ws;
      }
    }
  }
  return maxima;
}

proc aMin(axis = 0, X:[]) {
  var is: domain(1) = 1..max(X.domain.dim(1).size,X.domain.dim(2).size);
  var sis: sparse subdomain(is);
  var minima: [sis] real;
  if axis == 0 {
    var wDom: domain(1) = 1..X.domain.size;
    var ws = reshape(X, wDom);
    sis += 1;
    minima[1] = (min reduce ws);
  }
  if axis == 2 {
    is = X.domain.dim(1);
    for i in is {
      var wDom: domain(1);
      var ws: [wDom] real;
      for a in transpose(X).domain.dimIter(2, i) {
        sis += i;
        ws.push_back(X(a,i));
      }
      if sis.member(i) {
        minima[i] = min reduce ws;
      }
    }
  } else if axis == 1 {
    is = X.domain.dim(2);
    for i in is {
      var wDom: domain(1);
      var ws: [wDom] real;
      for a in X.domain.dimIter(2, i) {
        sis += i;
        ws.push_back(X(i,a));
      }
      if sis.member(i) {
        minima[i] = min reduce ws;
      }
    }
  }
  return minima;
}

proc argMin(axis: int, X:[]) {
  var is: domain(1) = 1..max(X.domain.dim(1).size,X.domain.dim(2).size);
  var sis: sparse subdomain(is);
  var minima: [sis] 2*int;
  if axis == 0 {
    sis += 1;
    minima[1] = miniLoc_(axis == 0, id = 1, X);
  } else if axis == 2 {
    for i in is {
      var idm = miniLoc_(1,i,X);
      if idm != (0,0) {
        sis += i;
        minima[i] = idm;
      }
    }
  } else if axis == 1 {
    for i in is {
      var idm = miniLoc_(2,i,X);
      if idm != (0,0) {
        sis += i;
        minima[i] = idm;
      }
    }
  }
  return minima;
}

proc argMax(axis: int, X:[]) {
  var is: domain(1) = 1..max(X.domain.dim(1).size,X.domain.dim(2).size);
  var sis: sparse subdomain(is);
  var maxima: [sis] 2*int;
  if axis == 0 {
    sis += 1;
    maxima[1] = maxiLoc_(axis == 0, id = 1, X);
  } else if axis == 2 {
    for i in is {
      var idm = maxiLoc_(1,i,X);
      if idm != (0,0) {
        sis += i;
        maxima[i] = idm;
      }
    }
  } else if axis == 1 {
    for i in is {
      var idm = maxiLoc_(2,i,X);
      if idm != (0,0) {
        sis += i;
        maxima[i] = idm;
      }
    }
  }
  return maxima;
}

// HELPER FUNCTIONS SHOULD NOT BE EXPOSED TO USER
proc miniLoc_(axis:int, id, X:[]) {
  var idm: (int,int),
      minimum: real = 1000000;
  if axis == 0 {
    for (i,j) in X.domain {
      if X(i,j) < minimum {
        minimum = X(i,j);
        idm = (i,j);
      }
    }
  }
  if axis == 1 {
    for a in transpose(X).domain.dimIter(2, id) {
      if X(a,id) < minimum {
        minimum = X(a,id);
        idm = (a,id);
      }
    }
  }
  if axis == 2 {
    for a in X.domain.dimIter(2, id) {
      if X(id,a) < minimum {
        minimum = X(id,a);
        idm = (id,a);
      }
    }
  }
  return idm;
}

proc maxiLoc_(axis:int, id, X:[]) {
  var idm: (int,int),
      maximum: real;
  if axis == 0 {
    for (i,j) in X.domain {
      if X(i,j) > maximum {
        maximum = X(i,j);
        idm = (i,j);
      }
    }
  }
  if axis == 1 {
    for a in transpose(X).domain.dimIter(2, id) {
      if X(a,id) > maximum {
        maximum = X(a,id);
        idm = (a,id);
      }
    }
  }
  if axis == 2 {
    for a in X.domain.dimIter(2, id) {
      if X(id,a) > maximum {
        maximum = X(id,a);
        idm = (id,a);
      }
    }
  }
  return idm;
}

proc tropic(A:[],B:[]) { // UNDER CONSTRUCTION
  writeln("Calculating Tropic Product");
  writeln("A is");
  writeln(A);
  writeln("With domain");
  writeln(A.domain);
  var dom: domain(2) = {A.domain.dim(1),B.domain.dim(2)};
  var sps = CSRDomain(dom);
  var BT = transpose(B);
  writeln("BT is");
  writeln(BT);
  writeln("With domain");
  writeln(BT.domain);
  writeln("Tropic will have entries in");
  writeln(dom);
  var T: [sps] real;
  for (i,j) in dom {
    var mini = 100000000: real;
    var wdom: domain(1);
    var wids: [wdom] int;
    for w in A.domain.dimIter(2,i) {
      wids.push_back(w);
      writeln("Witness in A from %n to %n".format(i,w));
    }
    writeln(wids);
    for w in BT.domain.dimIter(2,j) {
      wids.push_back(w);
      writeln("Witness in B from %n to %n".format(w,j));
    }
    writeln(wids);
    var wDom: domain(1);
    var witness: [wDom] real;
    for w in wids {
      if A(i,w) + BT(j,w) > 0 {
        witness.push_back(A(i,w) + BT(j,w));
      }
    }
    writeln("Witnesses are");
    writeln(witness);
    mini = min reduce witness;
    writeln("Minimum among witnesses is");
    writeln(mini);
    if mini < 1000000000 {
      T(i,j) = mini;
    }
  }
  return T;
}

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

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;
}

class NumSuchError : Error {
  proc init() {
    super.init();
    this.initDone();
  }
  proc message() {
    return "Generic NumSuch Error";
  }
}

class DimensionMatchError : NumSuchError {
  var expected: int,
      actual: int;

  proc init(expected: int, actual:int) {
    super.init();
    this.initDone();
    this.expected = expected;
    this.actual = actual;
  }

  proc message() {
    return "Error matching dimensions.  Expected: " + this.expected + " Actual: " + this.actual;
  }
}

use 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() {
     this.initDone(); // NAMEDMATRIX DOESNT EXPLICITLY INHERIT ANYTHING SO IS THIS NECESSARY??
   }

   proc init(X) {
     this.D = {X.domain.dim(1), X.domain.dim(2)};
     this.initDone();
     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(rownames: [] string, colnames: [] string) {
     this.D = {1..rownames.size, 1..colnames.size};
     this.initDone();
     try! this.setRowNames(rownames);
     try! this.setColNames(colnames);
   }

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

   proc init(X, rows: BiMap, cols: BiMap) {
     this.init(X);
     this.rows = rows;
     this.cols = cols;
   }
}

/*
Returns the number of rows in the matrix frame
 */
proc NamedMatrix.nrows() {
  return X.domain.dim(1).size;
}

/*
Returns the number of columns in the matrix frame
 */
proc NamedMatrix.ncols() {
  return X.domain.dim(2).size;
}

/*
 Finds the max along the row indicated by row number
 */
proc NamedMatrix.rowMax(i: int) {
  var x:[1..0] real;
  for a in this.X.domain.dimIter(2,i) {
    x.push_back(this.get(i,a));
  }
  if x.size < 1 {
    return NAN;
  } else {
    return max reduce x;
  }
}

/*
 Returns the max of each row
 */
proc NamedMatrix.rowMax() {
  var mxs: [1..this.nrows()] real;
  forall i in 1..this.nrows() {
    mxs[i] = this.rowMax(i);
  }
  return mxs;
}

/*
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 (X.domain.dim(1).size > 0) && (rn.size != X.domain.dim(1).size) {
  //if (1 > 0) && (rn.size != X.domain.dim(1).size) {
  if rn.size != X.domain.dim(1).size {
    const err = new DimensionMatchError(expected = X.domain.dim(1).size, actual=rn.size);
    throw err;
    return "";
  } else {
    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.ndot(N: NamedMatrix) {
  var C: NamedMatrix = new NamedMatrix(X = dot(this.X,N.X), this.rows, N.cols);
  return C;
}

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;
}

proc maxima(axis:int, id:int , X:[]) {
  var idm: (int,int),
      maximum: real = 0;
  if axis == 0 {
    for (i,j) in X.domain {
      if X(i,j) > maximum {
        maximum = X(i,j);
        idm = (i,j);
      }
    }
  }
  if axis == 1 {
    for a in transpose(X).domain.dimIter(2, id) {
      if X(a,id) > maximum {
        maximum = X(a,id);
        idm = (a,id);
      }
    }
  }
  if axis == 2 {
    for a in X.domain.dimIter(2, id) {
      if X(id,a) > maximum {
        maximum = X(id,a);
        idm = (id,a);
      }
    }
  }
  return maximum;
}

proc maxiLoc(axis:int, id:int , X:[]) {
  var idm: (int,int),
      maximum: real = 0;
  if axis == 0 {
    for (i,j) in X.domain {
      if X(i,j) > maximum {
        maximum = X(i,j);
        idm = (i,j);
      }
    }
  }
  if axis == 1 {
    for a in transpose(X).domain.dimIter(2, id) {
      if X(a,id) > maximum {
        maximum = X(a,id);
        idm = (a,id);
      }
    }
  }
  if axis == 2 {
    for a in X.domain.dimIter(2, id) {
      if X(id,a) > maximum {
        maximum = X(id,a);
        idm = (id,a);
      }
    }
  }
  return idm;
}

proc minima(axis:int, id:int , X:[]) {
  var idm: (int,int),
      minimum: real = 1000000;
  if axis == 0 {
    for (i,j) in X.domain {
      if X(i,j) < minimum {
        minimum = X(i,j);
        idm = (i,j);
      }
    }
  }
  if axis == 1 {
    for a in transpose(X).domain.dimIter(2, id) {
      if X(a,id) < minimum {
        minimum = X(a,id);
        idm = (a,id);
      }
    }
  }
  if axis == 2 {
    for a in X.domain.dimIter(2, id) {
      if X(id,a) < minimum {
        minimum = X(id,a);
        idm = (id,a);
      }
    }
  }
  return minimum;
}

proc miniLoc(axis:int, id:int , X:[]) {
  var idm: (int,int),
      minimum: real = 1000000;
  if axis == 0 {
    for (i,j) in X.domain {
      if X(i,j) < minimum {
        minimum = X(i,j);
        idm = (i,j);
      }
    }
  }
  if axis == 1 {
    for a in transpose(X).domain.dimIter(2, id) {
      if X(a,id) < minimum {
        minimum = X(a,id);
        idm = (a,id);
      }
    }
  }
  if axis == 2 {
    for a in X.domain.dimIter(2, id) {
      if X(id,a) < minimum {
        minimum = X(id,a);
        idm = (id,a);
      }
    }
  }
  return idm;
}

proc rowMax_(id: int, X) {
  return maxima(axis = 2, id, X);
}

proc rowMax_(X) {
  return maxima(axis = 0, id = 0, X);
}

proc colMax_(id: int, X) {
  return maxima(axis = 1, id, X);
}

proc colMax_(X) {
  return maxima(axis = 0, id = 0, X);
}

proc argRowMax_(id: int, X) {
  return maxiLoc(axis = 2, id, X);
}

proc argRowMax_(X) {
  return maxiLoc(axis = 0, id = 0, X);
}

proc argColMax_(id: int, X) {
  return maxiLoc(axis = 1, id, X);
}

proc argColMax_(X) {
  return maxiLoc(axis = 0, id = 0, X);
}

proc tropic(A:[],B:[]) { // UNDER CONSTRUCTION
  writeln("Calculating Tropic Product");
  writeln("A is");
  writeln(A);
  writeln("With domain");
  writeln(A.domain);
  var dom: domain(2) = {A.domain.dim(1),B.domain.dim(2)};
  var sps = CSRDomain(dom);
  var BT = transpose(B);
  writeln("BT is");
  writeln(BT);
  writeln("With domain");
  writeln(BT.domain);
  writeln("Tropic will have entries in");
  writeln(dom);
  var T: [sps] real;
  for (i,j) in dom {
    var mini = 100000000: real;
    var wdom: domain(1);
    var wids: [wdom] int;
    for w in A.domain.dimIter(2,i) {
      wids.push_back(w);
      writeln("Witness in A from %n to %n".format(i,w));
    }
    writeln(wids);
    for w in BT.domain.dimIter(2,j) {
      wids.push_back(w);
      writeln("Witness in B from %n to %n".format(w,j));
    }
    writeln(wids);
    var wDom: domain(1);
    var witness: [wDom] real;
    for w in wids {
      if A(i,w) + BT(j,w) > 0 {
        witness.push_back(A(i,w) + BT(j,w));
      }
    }
    writeln("Witnesses are");
    writeln(witness);
    mini = min reduce witness;
    writeln("Minimum among witnesses is");
    writeln(mini);
    if mini < 1000000000 {
      T(i,j) = mini;
    }
  }
  return T;
}

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

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;
}

class NumSuchError : Error {
  proc init() {
    super.init();
    this.initDone();
  }
  proc message() {
    return "Generic NumSuch Error";
  }
}

class DimensionMatchError : NumSuchError {
  var expected: int,
      actual: int;

  proc init(expected: int, actual:int) {
    super.init();
    this.initDone();
    this.expected = expected;
    this.actual = actual;
  }

  proc message() {
    return "Error matching dimensions.  Expected: " + this.expected + " Actual: " + this.actual;
  }
}


Duplicated an optimization in Block that was in Block1D but didn't
transfer trivially in my initial draft.  In this optimization, we do a
local access in the unaligned array follower if a member check
suggests that the data is actually local.  This used to be done by
determining the local array class by indexing into the array of local
array classes with here.id.  Since this array is no longer necessarily
one-dimensional, we can't do that.  (Also, note that that optimization
would fail in Block1D if the target locales array was not Locales.)
In Block, the optimization uses a local array class reference myLocArr
that points to a local array class if there is one.  Thus we may miss
local accesses if a locale appears in the target locales array more
than once.  The program will still be correct however.

This optimization is very important to stream where verification
really suffered since it uses the unaligned array follower even though
the arrays are aligned.  The compiler missed this because verification
is in a function.  The performance difference was about two orders of
magnitude off.

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

//
// Block Distribution
//
//      Block       BlockDom     BlockArr
//
//   LocBlock    LocBlockDom  LocBlockArr
//

//
// TODO List
//
// 1. Make multi-dimensional
// 2. Support strided domains of locales
// 3. Support strided Block domains
// 4. Support slices
//
// Limitations
//
// 1. Changes to Block.tasksPerLocale are not made in privatized
//    copies of this distribution.
//

config param debugBlockDist = false; // internal development flag (debugging)

////////////////////////////////////////////////////////////////////////////////
// Block Distribution Class
//
class Block : BaseDist {
  param rank: int = 1;
  type idxType = int(64);

  const boundingBox: domain(rank, idxType);
  const targetLocDom: domain(rank);
  const targetLocs: [targetLocDom] locale;
  const locDist: [targetLocDom] LocBlock(rank, idxType);

  var tasksPerLocale: int; // tasks per locale for forall iteration

  def Block(param rank: int,
            type idxType = int(64),
            bbox: domain(rank, idxType),
            targetLocales: [] locale = Locales,
            tasksPerLocale = 0) {
    boundingBox = bbox;
    if rank == 1 {
      targetLocDom = [0..#targetLocales.numElements]; // 0-based for simplicity
      targetLocs = targetLocales;
    } else if targetLocales.rank == 1 then {
      const factors = _factor(rank, targetLocales.numElements);
      var ranges: rank*range;
      for param i in 1..rank do
        ranges(i) = 0..factors(i)-1;
      targetLocDom = [(...ranges)];
      for (loc1, loc2) in (targetLocs, targetLocales) do
        loc1 = loc2;
      if debugBlockDist {
        writeln(targetLocDom);
        writeln(targetLocs);
      }
    } else {
      if targetLocales.rank != rank then
        compilerError("locales array rank must be one or match distribution rank");
      halt("case 2 not handled yet");
    }

    coforall locid in targetLocDom do
      on targetLocs(locid) do
        locDist(locid) = new LocBlock(rank, idxType, locid, this);

    if tasksPerLocale == 0 then
      this.tasksPerLocale = min reduce targetLocs.numCores;
    else
      this.tasksPerLocale = tasksPerLocale;

    if debugBlockDist then
      for loc in locDist do writeln(loc);
  }

  // copy constructor for privatization
  def Block(param rank: int, type idxType, other: Block(rank, idxType)) {
    boundingBox = other.boundingBox;
    targetLocDom = other.targetLocDom;
    targetLocs = other.targetLocs;
    locDist = other.locDist;
    tasksPerLocale = other.tasksPerLocale;
  }
}

//
// create a new arithmetic domain over this distribution
//
def Block.newArithmeticDom(param rank: int, type idxType, param stridable: bool) {
  if idxType != this.idxType then
    compilerError("Block domain index type does not match distribution's");
  if rank != this.rank then
    compilerError("Block domain rank does not match distribution's");

  var dom = new BlockDom(rank=rank, idxType=idxType, dist=this, stridable=stridable);
  dom.setup();
  return dom;
}

//
// output distribution
//
def Block.writeThis(x:Writer) {
  x.writeln("Block");
  x.writeln("-------");
  x.writeln("distributes: ", boundingBox);
  x.writeln("across locales: ", targetLocs);
  x.writeln("indexed via: ", targetLocDom);
  x.writeln("resulting in: ");
  for locid in targetLocDom do
    x.writeln("  [", locid, "] locale ", locid, " owns chunk: ", locDist(locid).myChunk);
}

//
// convert an index into a locale value
//
def Block.ind2loc(ind: rank*idxType) {
  return targetLocs(ind2locInd(ind));
}

//
// compute what chunk of inds is owned by a given locale -- assumes
// it's being called on the locale in question
//
def Block.getChunk(inds, locid) {
  // use domain slicing to get the intersection between what the
  // locale owns and the domain's index set
  //
  // TODO: Should this be able to be written as myChunk[inds] ???
  //
  // TODO: Does using David's detupling trick work here?
  //
  return locDist(locid).myChunk((...inds.getIndices()));
}

//
// determine which locale owns a particular index
//
// TODO: I jotted down a note during the code review asking whether
// targetLocs.numElements and boundingbox.numIndices should be
// captured locally, or captured in the default dom/array implementation
// or inlined.  Not sure what that point was anymore, though.  Maybe
// someone else can help me remember it (since it was probably someone
// else's suggestion).
//
def Block.ind2locInd(ind: idxType) where rank == 1 {
  const ind0 = ind - boundingBox.low;
  const locInd = (ind0 * targetLocs.numElements:idxType) / boundingBox.numIndices;
  return max(0, min(locInd:int, (targetLocDom.dim(1).length-1):int));
}

def Block.ind2locInd(ind: rank*idxType) {
  var locInd: rank*int;
  for param i in 1..rank {
    const ind0 = ind(i) - boundingBox.low(i);
    const dimLocInd = (ind0 * targetLocDom.dim(i).length:idxType) / boundingBox.dim(i).length;
    locInd(i) = max(0, min(dimLocInd:int, (targetLocDom.dim(i).length-1):int));
  }
  return locInd;
}

////////////////////////////////////////////////////////////////////////////////
// Block Local Distribution Class
//
class LocBlock {
  param rank: int;
  type idxType;

  //
  // This stores the piece of the global bounding box owned by
  // the locale.  Note that my original guess that we'd want
  // to use lclIdxType here is wrong since we're talking about
  // the section of the global index space owned by the locale.
  //
  const myChunk: domain(rank, idxType);

  //
  // Constructor computes what chunk of index(1) is owned by the
  // current locale
  //
  def LocBlock(param rank: int,
                 type idxType,
                 locid,   // the locale index from the target domain
                 dist: Block(rank, idxType)) { // reference to glob dist
    if rank == 1 {
      const lo = dist.boundingBox.low;
      const hi = dist.boundingBox.high;
      const numelems = hi - lo + 1;
      const numlocs = dist.targetLocDom.numIndices;
      const (blo, bhi) = _computeBlock(min(idxType), numelems, lo,
                                       max(idxType), numlocs, locid);
      myChunk = [blo..bhi];
    } else {
      var tuple: rank*range(idxType);
      for param i in 1..rank {
        const lo = dist.boundingBox.low(i);
        const hi = dist.boundingBox.high(i);
        const numelems = hi - lo + 1;
        const numlocs = dist.targetLocDom.dim(i).length;
        const (blo, bhi) = _computeBlock(min(idxType), numelems, lo,
                                         max(idxType), numlocs, locid(i));
        tuple(i) = blo..bhi;
      }
      myChunk = [(...tuple)];
    }
  }
}

////////////////////////////////////////////////////////////////////////////////
// Block Domain Class
//
class BlockDom: BaseArithmeticDom {
  param rank: int;
  type idxType;
  param stridable: bool;

  //
  // LEFT LINK: a pointer to the parent distribution
  //
  const dist: Block(rank, idxType);

  //
  // DOWN LINK: an array of local domain class descriptors -- set up in
  // setup() below
  //
  var locDoms: [dist.targetLocDom] LocBlockDom(rank, idxType, stridable);

  //
  // a domain describing the complete domain
  //
  const whole: domain(rank, idxType);

  var pid: int = -1; // privatized object id
}

def BlockDom.dim(d: int) return whole.dim(d);

def BlockDom.these() {
  for i in whole do
    yield i;
}

def BlockDom.these(param tag: iterator) where tag == iterator.leader {
  const precomputedNumTasks = dist.tasksPerLocale;
  const precomputedWholeLow = whole.low;
  coforall locDom in locDoms do on locDom {
    const locBlock = locDom.myBlock - precomputedWholeLow,
      numTasks = precomputedNumTasks;
    if (numTasks == 1) {
      yield locBlock;
    } else {
      coforall taskid in 0..#numTasks {
        if rank == 1 {
          const (lo,hi) = _computeBlock(locBlock.low, locBlock.numIndices,
                                        locBlock.low, locBlock.high,
                                        numTasks, taskid);
          yield [lo..hi];
        } else {
          var tuple: rank*range(idxType);
          const (lo,hi) = _computeBlock(locBlock.low(1), locBlock.dim(1).length,
                                        locBlock.low(1), locBlock.high(1),
                                        numTasks, taskid);
          tuple(1) = lo..hi;
          for param i in 2..rank do
            tuple(i) = locBlock.low(i)..locBlock.high(i);
          yield [(...tuple)];
        }
      }
    }
  }
}

//
// TODO: Abstract the addition of low into a function?
// Note relationship between this operation and the
// order/position functions -- any chance for creating similar
// support? (esp. given how frequent this seems likely to be?)
//
// TODO: Is there some clever way to invoke the leader/follower
// iterator on the local blocks in here such that the per-core
// parallelism is expressed at that level?  Seems like a nice
// natural composition and might help with my fears about how
// stencil communication will be done on a per-locale basis.
//
def BlockDom.these(param tag: iterator, follower) where tag == iterator.follower {
  const followThis = follower + whole.low;

  for i in followThis {
    yield i;
  }
}

//
// output domain
//
def BlockDom.writeThis(x:Writer) {
  x.write(whole);
}

//
// how to allocate a new array over this domain
//
def BlockDom.buildArray(type eltType) {
  var arr = new BlockArr(eltType, rank, idxType, stridable, this);
  arr.setup();
  return arr;
}

def BlockDom.numIndices return whole.numIndices;
def BlockDom.low return whole.low;
def BlockDom.high return whole.high;

//
// INTERFACE NOTES: Could we make setIndices() for an arithmetic
// domain take a domain rather than something else?
//
def BlockDom.setIndices(x: domain) {
  halt("this is never called");
  if x.rank != rank then
    compilerError("rank mismatch in domain assignment");
  if x._value.idxType != idxType then
    compilerError("index type mismatch in domain assignment");
  whole = x;
  setup();
}

def BlockDom.setIndices(x) {
  if x.size != rank then
    compilerError("rank mismatch in domain assignment");
  if x(1).eltType != idxType then
    compilerError("index type mismatch in domain assignment");
  //
  // TODO: This seems weird:
  //
  whole.setIndices(x);
  setup();
}

def BlockDom.getIndices() {
  return whole;
}

def BlockDom.getDist(): Block(idxType) {
  return dist;
}

def BlockDom.setup() {
  coforall localeIdx in dist.targetLocDom do
    on dist.targetLocs(localeIdx) do
      if (locDoms(localeIdx) == nil) then
        locDoms(localeIdx) = new LocBlockDom(rank, idxType, stridable, this,
                                               dist.getChunk(whole, localeIdx));
  else
    locDoms(localeIdx).myBlock = dist.getChunk(whole, localeIdx);
  if debugBlockDist then
    for loc in dist.targetLocDom do writeln(loc, " owns ", locDoms(loc));

}

def BlockDom.supportsPrivatization() param return true;

def BlockDom.privatize() {
  var privateDist = new Block(rank, idxType, dist);
  var c = new BlockDom(rank=rank, idxType=idxType, stridable=stridable, dist=privateDist);
  c.locDoms = locDoms;
  c.whole = whole;
  return c;
}

def BlockDom.reprivatize(other) {
  locDoms = other.locDoms;
  whole = other.whole;
}

////////////////////////////////////////////////////////////////////////////////
// Block Local Domain Class
//
class LocBlockDom {
  param rank: int;
  type idxType;
  param stridable: bool;

  //
  // UP LINK: a reference to the parent global domain class
  //
  const wholeDom: BlockDom(rank, idxType, stridable);

  //
  // a local domain describing the indices owned by this locale
  //
  // NOTE: I used to use a local index type for this, but that would
  // require a glbIdxType offset in order to get from the global
  // indices back to the local index type.
  //
  var myBlock: domain(rank, idxType);
}

//
// output local domain piece
//
def LocBlockDom.writeThis(x:Writer) {
  x.write(myBlock);
}

//
// queries for this locale's number of indices, low, and high bounds
//
// TODO: I believe these are only used by the random number generator
// in stream -- will they always be required once that is rewritten?
//
def LocBlockDom.numIndices {
  return myBlock.numIndices;
}

def LocBlockDom.low {
  return myBlock.low;
}

def LocBlockDom.high {
  return myBlock.high;
}

////////////////////////////////////////////////////////////////////////////////
// Block Array Class
//
class BlockArr: BaseArr {
  type eltType;
  param rank: int;
  type idxType;
  param stridable: bool;

  //
  // LEFT LINK: the global domain descriptor for this array
  //
  var dom: BlockDom(rank, idxType, stridable);

  //
  // DOWN LINK: an array of local array classes
  //
  var locArr: [dom.dist.targetLocDom] LocBlockArr(eltType, rank, idxType, stridable);

  //
  // optimized reference to a local LocBlockArr instance (or nil)
  //
  var myLocArr: LocBlockArr(eltType, rank, idxType, stridable);

  var pid: int = -1; // privatized object id
}

def BlockArr.setup() {
  coforall localeIdx in dom.dist.targetLocDom {
    on dom.dist.targetLocs(localeIdx) {
      locArr(localeIdx) = new LocBlockArr(eltType, rank, idxType, stridable, dom.locDoms(localeIdx));
      if this.locale == here then
        myLocArr = locArr(localeIdx);
    }
  }
}

def BlockArr.supportsPrivatization() param return true;

def BlockArr.privatize() {
  var dompid = dom.pid;
  var thisdom = dom;
  var privdom = __primitive("chpl_getPrivatizedClass", thisdom, dompid);
  var c = new BlockArr(eltType, rank, idxType, stridable, privdom);
  c.locArr = locArr;
  for localeIdx in dom.dist.targetLocDom do
    if c.locArr(localeIdx).locale == here then
      c.myLocArr = c.locArr(localeIdx);
  return c;
}

//
// the global accessor for the array
//
// TODO: Do we need a global bounds check here or in ind2locind?
//
def BlockArr.this(i: idxType) var where rank == 1 {
  if myLocArr then local {
    if myLocArr.locDom.myBlock.member(i) then
      return myLocArr.this(i);
  }
  return locArr(dom.dist.ind2locInd(i))(i);
}

def BlockArr.this(i: rank*idxType) var {
//   const myLocArr = locArr(here.id);
//   local {
//     if myLocArr.locDom.myBlock.member(i) then
//       return myLocArr.this(i);
//   }
  if rank == 1 {
    return this(i(1));
  } else {
    return locArr(dom.dist.ind2locInd(i))(i);
  }
}

def BlockArr.these() var {
  for i in dom do
    yield this(i);
}

//
// TODO: Rewrite this to reuse more of the global domain iterator
// logic?  (e.g., can we forward the forall to the global domain
// somehow?
//
def BlockArr.these(param tag: iterator) where tag == iterator.leader {
  const precomputedNumTasks = dom.dist.tasksPerLocale;
  const precomputedWholeLow = dom.whole.low;
  coforall locDom in dom.locDoms do on locDom {
    const locBlock = locDom.myBlock - precomputedWholeLow,
      numTasks = precomputedNumTasks;
    if (numTasks == 1) {
      yield locBlock;
    } else {
      coforall taskid in 0..#numTasks {
        if rank == 1 {
          const (lo,hi) = _computeBlock(locBlock.low, locBlock.numIndices,
                                        locBlock.low, locBlock.high,
                                        numTasks, taskid);
          yield [lo..hi];
        } else {
          var tuple: rank*range(idxType);
          const (lo,hi) = _computeBlock(locBlock.low(1), locBlock.dim(1).length,
                                        locBlock.low(1), locBlock.high(1),
                                        numTasks, taskid);
          if hi >= lo {
            tuple(1) = lo..hi;
            for param i in 2..rank do
              tuple(i) = locBlock.low(i)..locBlock.high(i);
            yield [(...tuple)];
          }
        }
      }
    }
  }
}

def BlockArr.supportsAlignedFollower() param return true;

def BlockArr.these(param tag: iterator, follower, param aligned: bool = false) var where tag == iterator.follower {
  const followThis = follower + dom.low;

  //
  // TODO: The following is a buggy hack that will only work when we're
  // distributing across the entire Locales array.  I still think the
  // locArr/locDoms arrays should be associative over locale values.
  //
  const myLocArr = locArr(dom.dist.ind2locInd(followThis.low));
  if aligned {
    local {
      for i in followThis {
        yield myLocArr.this(i);
      }
    }
  } else {
    //
    // we don't own all the elements we're following
    //
    def accessHelper(i) var {
//      if myLocArr.locale == here {
//	local {
//          if myLocArr.locDom.myBlock.member(i) then
//            return myLocArr.this(i);
//        }
//      }
      return this(i);
    }
    for i in followThis {
      yield accessHelper(i);
    }
  }
}

//
// output array
//
def BlockArr.writeThis(f: Writer) {
  if dom.numIndices == 0 then return;
  var i : rank*idxType;
  for dim in 1..rank do
    i(dim) = dom.dim(dim)._low;
  label next while true {
    f.write(this(i));
    if i(rank) <= (dom.dim(rank)._high - dom.dim(rank)._stride:idxType) {
      f.write(" ");
      i(rank) += dom.dim(rank)._stride:idxType;
    } else {
      for dim in 1..rank-1 by -1 {
        if i(dim) <= (dom.dim(dim)._high - dom.dim(dim)._stride:idxType) {
          i(dim) += dom.dim(dim)._stride:idxType;
          for dim2 in dim+1..rank {
            f.writeln();
            i(dim2) = dom.dim(dim2)._low;
          }
          continue next;
        }
      }
      break;
    }
  }
}

def BlockArr.numElements return dom.numIndices;

////////////////////////////////////////////////////////////////////////////////
// Block Local Array Class
//
class LocBlockArr {
  type eltType;
  param rank: int;
  type idxType;
  param stridable: bool;

  //
  // LEFT LINK: a reference to the local domain class for this array and locale
  //
  const locDom: LocBlockDom(rank, idxType, stridable);

  // STATE:

  //
  // the block of local array data
  //
  var myElems: [locDom.myBlock] eltType;
}

//
// the accessor for the local array -- assumes the index is local
//
def LocBlockArr.this(i) var {
  return myElems(i);
}

//
// output local array piece
//
def LocBlockArr.writeThis(x: Writer) {
  // note on this fails; see writeThisUsingOn.chpl
  x.write(myElems);
}

def LocBlockArr.numElements return myElems.numElements;

//
// helper function for blocking index ranges
//
def _computeBlock(waylo, numelems, lo, wayhi, numblocks, blocknum) {
  def procToData(x, lo)
    return lo + (x:lo.type) + (x:real != x:int:real):lo.type;

  const blo =
    if blocknum == 0 then waylo
      else procToData((numelems:real * blocknum) / numblocks, lo);
  const bhi =
    if blocknum == numblocks - 1 then wayhi
      else procToData((numelems:real * (blocknum+1)) / numblocks, lo) - 1;

  return (blo, bhi);
}

//
// naive routine for dividing numLocales into rank factors
//
def _factor(param rank: int, value) {
  var factors: rank*int;
  for param i in 1..rank do
    factors(i) = 1;
  if value >= 1 {
    var iv = value;
    var factor = 1;
    while iv > 1 {
      for i in 2..iv {
        if iv % i == 0 {
          var j = 1;
          for i in 2..rank {
            if factors(i) < factors(j) then
              j = i;
          }
          factors(j) *= i;
          iv = iv / i;
          break;
        }
      }
    }
  }
  for i in 1..rank do
    for j in i+1..rank do
      if factors(i) < factors(j) then
        factors(i) <=> factors(j);
  return factors;
}

//
// Block Distribution
//
//      Block       BlockDom     BlockArr
//
//   LocBlock    LocBlockDom  LocBlockArr
//

//
// TODO List
//
// 1. Make multi-dimensional
// 2. Support strided domains of locales
// 3. Support strided Block domains
// 4. Support slices
//
// Limitations
//
// 1. Changes to Block.tasksPerLocale are not made in privatized
//    copies of this distribution.
//

config param debugBlockDist = false; // internal development flag (debugging)

////////////////////////////////////////////////////////////////////////////////
// Block Distribution Class
//
class Block : BaseDist {
  param rank: int = 1;
  type idxType = int(64);

  const boundingBox: domain(rank, idxType);
  const targetLocDom: domain(rank);
  const targetLocs: [targetLocDom] locale;
  const locDist: [targetLocDom] LocBlock(rank, idxType);

  var tasksPerLocale: int; // tasks per locale for forall iteration

  def Block(param rank: int,
            type idxType = int(64),
            bbox: domain(rank, idxType),
            targetLocales: [] locale = Locales,
            tasksPerLocale = 0) {
    boundingBox = bbox;
    if rank == 1 {
      targetLocDom = [0..#targetLocales.numElements]; // 0-based for simplicity
      targetLocs = targetLocales;
    } else if targetLocales.rank == 1 then {
      const factors = _factor(rank, targetLocales.numElements);
      var ranges: rank*range;
      for param i in 1..rank do
        ranges(i) = 0..factors(i)-1;
      targetLocDom = [(...ranges)];
      for (loc1, loc2) in (targetLocs, targetLocales) do
        loc1 = loc2;
      if debugBlockDist {
        writeln(targetLocDom);
        writeln(targetLocs);
      }
    } else {
      if targetLocales.rank != rank then
        compilerError("locales array rank must be one or match distribution rank");
      halt("case 2 not handled yet");
    }

    coforall locid in targetLocDom do
      on targetLocs(locid) do
        locDist(locid) = new LocBlock(rank, idxType, locid, this);

    if tasksPerLocale == 0 then
      this.tasksPerLocale = min reduce targetLocs.numCores;
    else
      this.tasksPerLocale = tasksPerLocale;

    if debugBlockDist then
      for loc in locDist do writeln(loc);
  }

  // copy constructor for privatization
  def Block(param rank: int, type idxType, other: Block(rank, idxType)) {
    boundingBox = other.boundingBox;
    targetLocDom = other.targetLocDom;
    targetLocs = other.targetLocs;
    locDist = other.locDist;
    tasksPerLocale = other.tasksPerLocale;
  }
}

//
// create a new arithmetic domain over this distribution
//
def Block.newArithmeticDom(param rank: int, type idxType, param stridable: bool) {
  if idxType != this.idxType then
    compilerError("Block domain index type does not match distribution's");
  if rank != this.rank then
    compilerError("Block domain rank does not match distribution's");

  var dom = new BlockDom(rank=rank, idxType=idxType, dist=this, stridable=stridable);
  dom.setup();
  return dom;
}

//
// output distribution
//
def Block.writeThis(x:Writer) {
  x.writeln("Block");
  x.writeln("-------");
  x.writeln("distributes: ", boundingBox);
  x.writeln("across locales: ", targetLocs);
  x.writeln("indexed via: ", targetLocDom);
  x.writeln("resulting in: ");
  for locid in targetLocDom do
    x.writeln("  [", locid, "] locale ", locid, " owns chunk: ", locDist(locid).myChunk);
}

//
// convert an index into a locale value
//
def Block.ind2loc(ind: rank*idxType) {
  return targetLocs(ind2locInd(ind));
}

//
// compute what chunk of inds is owned by a given locale -- assumes
// it's being called on the locale in question
//
def Block.getChunk(inds, locid) {
  // use domain slicing to get the intersection between what the
  // locale owns and the domain's index set
  //
  // TODO: Should this be able to be written as myChunk[inds] ???
  //
  // TODO: Does using David's detupling trick work here?
  //
  return locDist(locid).myChunk((...inds.getIndices()));
}

//
// determine which locale owns a particular index
//
// TODO: I jotted down a note during the code review asking whether
// targetLocs.numElements and boundingbox.numIndices should be
// captured locally, or captured in the default dom/array implementation
// or inlined.  Not sure what that point was anymore, though.  Maybe
// someone else can help me remember it (since it was probably someone
// else's suggestion).
//
def Block.ind2locInd(ind: idxType) where rank == 1 {
  const ind0 = ind - boundingBox.low;
  const locInd = (ind0 * targetLocs.numElements:idxType) / boundingBox.numIndices;
  return max(0, min(locInd:int, (targetLocDom.dim(1).length-1):int));
}

def Block.ind2locInd(ind: rank*idxType) {
  var locInd: rank*int;
  for param i in 1..rank {
    const ind0 = ind(i) - boundingBox.low(i);
    const dimLocInd = (ind0 * targetLocDom.dim(i).length:idxType) / boundingBox.dim(i).length;
    locInd(i) = max(0, min(dimLocInd:int, (targetLocDom.dim(i).length-1):int));
  }
  return locInd;
}

////////////////////////////////////////////////////////////////////////////////
// Block Local Distribution Class
//
class LocBlock {
  param rank: int;
  type idxType;

  //
  // This stores the piece of the global bounding box owned by
  // the locale.  Note that my original guess that we'd want
  // to use lclIdxType here is wrong since we're talking about
  // the section of the global index space owned by the locale.
  //
  const myChunk: domain(rank, idxType);

  //
  // Constructor computes what chunk of index(1) is owned by the
  // current locale
  //
  def LocBlock(param rank: int,
                 type idxType,
                 locid,   // the locale index from the target domain
                 dist: Block(rank, idxType)) { // reference to glob dist
    if rank == 1 {
      const lo = dist.boundingBox.low;
      const hi = dist.boundingBox.high;
      const numelems = hi - lo + 1;
      const numlocs = dist.targetLocDom.numIndices;
      const (blo, bhi) = _computeBlock(min(idxType), numelems, lo,
                                       max(idxType), numlocs, locid);
      myChunk = [blo..bhi];
    } else {
      var tuple: rank*range(idxType);
      for param i in 1..rank {
        const lo = dist.boundingBox.low(i);
        const hi = dist.boundingBox.high(i);
        const numelems = hi - lo + 1;
        const numlocs = dist.targetLocDom.dim(i).length;
        const (blo, bhi) = _computeBlock(min(idxType), numelems, lo,
                                         max(idxType), numlocs, locid(i));
        tuple(i) = blo..bhi;
      }
      myChunk = [(...tuple)];
    }
  }
}

////////////////////////////////////////////////////////////////////////////////
// Block Domain Class
//
class BlockDom: BaseArithmeticDom {
  param rank: int;
  type idxType;
  param stridable: bool;

  //
  // LEFT LINK: a pointer to the parent distribution
  //
  const dist: Block(rank, idxType);

  //
  // DOWN LINK: an array of local domain class descriptors -- set up in
  // setup() below
  //
  var locDoms: [dist.targetLocDom] LocBlockDom(rank, idxType, stridable);

  //
  // a domain describing the complete domain
  //
  const whole: domain(rank, idxType);

  var pid: int = -1; // privatized object id
}

def BlockDom.dim(d: int) return whole.dim(d);

def BlockDom.these() {
  for i in whole do
    yield i;
}

def BlockDom.these(param tag: iterator) where tag == iterator.leader {
  const precomputedNumTasks = dist.tasksPerLocale;
  const precomputedWholeLow = whole.low;
  coforall locDom in locDoms do on locDom {
    const locBlock = locDom.myBlock - precomputedWholeLow,
      numTasks = precomputedNumTasks;
    if (numTasks == 1) {
      yield locBlock;
    } else {
      coforall taskid in 0..#numTasks {
        if rank == 1 {
          const (lo,hi) = _computeBlock(locBlock.low, locBlock.numIndices,
                                        locBlock.low, locBlock.high,
                                        numTasks, taskid);
          yield [lo..hi];
        } else {
          var tuple: rank*range(idxType);
          const (lo,hi) = _computeBlock(locBlock.low(1), locBlock.dim(1).length,
                                        locBlock.low(1), locBlock.high(1),
                                        numTasks, taskid);
          tuple(1) = lo..hi;
          for param i in 2..rank do
            tuple(i) = locBlock.low(i)..locBlock.high(i);
          yield [(...tuple)];
        }
      }
    }
  }
}

//
// TODO: Abstract the addition of low into a function?
// Note relationship between this operation and the
// order/position functions -- any chance for creating similar
// support? (esp. given how frequent this seems likely to be?)
//
// TODO: Is there some clever way to invoke the leader/follower
// iterator on the local blocks in here such that the per-core
// parallelism is expressed at that level?  Seems like a nice
// natural composition and might help with my fears about how
// stencil communication will be done on a per-locale basis.
//
def BlockDom.these(param tag: iterator, follower) where tag == iterator.follower {
  const followThis = follower + whole.low;

  for i in followThis {
    yield i;
  }
}

//
// output domain
//
def BlockDom.writeThis(x:Writer) {
  x.write(whole);
}

//
// how to allocate a new array over this domain
//
def BlockDom.buildArray(type eltType) {
  var arr = new BlockArr(eltType, rank, idxType, stridable, this);
  arr.setup();
  return arr;
}

def BlockDom.numIndices return whole.numIndices;
def BlockDom.low return whole.low;
def BlockDom.high return whole.high;

//
// INTERFACE NOTES: Could we make setIndices() for an arithmetic
// domain take a domain rather than something else?
//
def BlockDom.setIndices(x: domain) {
  halt("this is never called");
  if x.rank != rank then
    compilerError("rank mismatch in domain assignment");
  if x._value.idxType != idxType then
    compilerError("index type mismatch in domain assignment");
  whole = x;
  setup();
}

def BlockDom.setIndices(x) {
  if x.size != rank then
    compilerError("rank mismatch in domain assignment");
  if x(1).eltType != idxType then
    compilerError("index type mismatch in domain assignment");
  //
  // TODO: This seems weird:
  //
  whole.setIndices(x);
  setup();
}

def BlockDom.getIndices() {
  return whole;
}

def BlockDom.getDist(): Block(idxType) {
  return dist;
}

def BlockDom.setup() {
  coforall localeIdx in dist.targetLocDom do
    on dist.targetLocs(localeIdx) do
      if (locDoms(localeIdx) == nil) then
        locDoms(localeIdx) = new LocBlockDom(rank, idxType, stridable, this,
                                               dist.getChunk(whole, localeIdx));
  else
    locDoms(localeIdx).myBlock = dist.getChunk(whole, localeIdx);
  if debugBlockDist then
    for loc in dist.targetLocDom do writeln(loc, " owns ", locDoms(loc));

}

def BlockDom.supportsPrivatization() param return true;

def BlockDom.privatize() {
  var privateDist = new Block(rank, idxType, dist);
  var c = new BlockDom(rank=rank, idxType=idxType, stridable=stridable, dist=privateDist);
  c.locDoms = locDoms;
  c.whole = whole;
  return c;
}

def BlockDom.reprivatize(other) {
  locDoms = other.locDoms;
  whole = other.whole;
}

////////////////////////////////////////////////////////////////////////////////
// Block Local Domain Class
//
class LocBlockDom {
  param rank: int;
  type idxType;
  param stridable: bool;

  //
  // UP LINK: a reference to the parent global domain class
  //
  const wholeDom: BlockDom(rank, idxType, stridable);

  //
  // a local domain describing the indices owned by this locale
  //
  // NOTE: I used to use a local index type for this, but that would
  // require a glbIdxType offset in order to get from the global
  // indices back to the local index type.
  //
  var myBlock: domain(rank, idxType);
}

//
// output local domain piece
//
def LocBlockDom.writeThis(x:Writer) {
  x.write(myBlock);
}

//
// queries for this locale's number of indices, low, and high bounds
//
// TODO: I believe these are only used by the random number generator
// in stream -- will they always be required once that is rewritten?
//
def LocBlockDom.numIndices {
  return myBlock.numIndices;
}

def LocBlockDom.low {
  return myBlock.low;
}

def LocBlockDom.high {
  return myBlock.high;
}

////////////////////////////////////////////////////////////////////////////////
// Block Array Class
//
class BlockArr: BaseArr {
  type eltType;
  param rank: int;
  type idxType;
  param stridable: bool;

  //
  // LEFT LINK: the global domain descriptor for this array
  //
  var dom: BlockDom(rank, idxType, stridable);

  //
  // DOWN LINK: an array of local array classes
  //
  var locArr: [dom.dist.targetLocDom] LocBlockArr(eltType, rank, idxType, stridable);

  var pid: int = -1; // privatized object id
}

def BlockArr.setup() {
  coforall localeIdx in dom.dist.targetLocDom do
    on dom.dist.targetLocs(localeIdx) do
      locArr(localeIdx) = new LocBlockArr(eltType, rank, idxType, stridable, dom.locDoms(localeIdx));
}

def BlockArr.supportsPrivatization() param return true;

def BlockArr.privatize() {
  var dompid = dom.pid;
  var thisdom = dom;
  var privdom = __primitive("chpl_getPrivatizedClass", thisdom, dompid);
  var c = new BlockArr(eltType, rank, idxType, stridable, privdom);
  c.locArr = locArr;
  return c;
}

//
// the global accessor for the array
//
// TODO: Do we need a global bounds check here or in ind2locind?
//
def BlockArr.this(i: idxType) var where rank == 1 {
  return locArr(dom.dist.ind2locInd(i))(i);
}

def BlockArr.this(i: rank*idxType) var {
//   const myLocArr = locArr(here.id);
//   local {
//     if myLocArr.locDom.myBlock.member(i) then
//       return myLocArr.this(i);
//   }
  if rank == 1 {
    return this(i(1));
  } else {
    return locArr(dom.dist.ind2locInd(i))(i);
  }
}

def BlockArr.these() var {
  for i in dom do
    yield this(i);
}

//
// TODO: Rewrite this to reuse more of the global domain iterator
// logic?  (e.g., can we forward the forall to the global domain
// somehow?
//
def BlockArr.these(param tag: iterator) where tag == iterator.leader {
  const precomputedNumTasks = dom.dist.tasksPerLocale;
  const precomputedWholeLow = dom.whole.low;
  coforall locDom in dom.locDoms do on locDom {
    const locBlock = locDom.myBlock - precomputedWholeLow,
      numTasks = precomputedNumTasks;
    if (numTasks == 1) {
      yield locBlock;
    } else {
      coforall taskid in 0..#numTasks {
        if rank == 1 {
          const (lo,hi) = _computeBlock(locBlock.low, locBlock.numIndices,
                                        locBlock.low, locBlock.high,
                                        numTasks, taskid);
          yield [lo..hi];
        } else {
          var tuple: rank*range(idxType);
          const (lo,hi) = _computeBlock(locBlock.low(1), locBlock.dim(1).length,
                                        locBlock.low(1), locBlock.high(1),
                                        numTasks, taskid);
          if hi >= lo {
            tuple(1) = lo..hi;
            for param i in 2..rank do
              tuple(i) = locBlock.low(i)..locBlock.high(i);
            yield [(...tuple)];
          }
        }
      }
    }
  }
}

def BlockArr.supportsAlignedFollower() param return true;

def BlockArr.these(param tag: iterator, follower, param aligned: bool = false) var where tag == iterator.follower {
  const followThis = follower + dom.low;

  //
  // TODO: The following is a buggy hack that will only work when we're
  // distributing across the entire Locales array.  I still think the
  // locArr/locDoms arrays should be associative over locale values.
  //
  const myLocArr = locArr(dom.dist.ind2locInd(followThis.low));
  if aligned {
    local {
      for i in followThis {
        yield myLocArr.this(i);
      }
    }
  } else {
    //
    // we don't own all the elements we're following
    //
    def accessHelper(i) var {
      //      local {
      //        if myLocArr.locDom.myBlock.member(i) then
      //          return myLocArr.this(i);
      //      }
      return this(i);
    }
    for i in followThis {
      yield accessHelper(i);
    }
  }
}

//
// output array
//
def BlockArr.writeThis(f: Writer) {
  if dom.numIndices == 0 then return;
  var i : rank*idxType;
  for dim in 1..rank do
    i(dim) = dom.dim(dim)._low;
  label next while true {
    f.write(this(i));
    if i(rank) <= (dom.dim(rank)._high - dom.dim(rank)._stride:idxType) {
      f.write(" ");
      i(rank) += dom.dim(rank)._stride:idxType;
    } else {
      for dim in 1..rank-1 by -1 {
        if i(dim) <= (dom.dim(dim)._high - dom.dim(dim)._stride:idxType) {
          i(dim) += dom.dim(dim)._stride:idxType;
          for dim2 in dim+1..rank {
            f.writeln();
            i(dim2) = dom.dim(dim2)._low;
          }
          continue next;
        }
      }
      break;
    }
  }
  /*
  var first = true;
  for loc in dom.dist.targetLocDom {
    // note on loc fails; see writeThisUsingOn.chpl
    if (locArr(loc).numElements >= 1) {
      if (first) {
        first = false;
      } else {
        x.write(" ");
      }
      x.write(locArr(loc));
    }
    //    }
    stdout.flush();
  }
  */
}

def BlockArr.numElements return dom.numIndices;

////////////////////////////////////////////////////////////////////////////////
// Block Local Array Class
//
class LocBlockArr {
  type eltType;
  param rank: int;
  type idxType;
  param stridable: bool;

  //
  // LEFT LINK: a reference to the local domain class for this array and locale
  //
  const locDom: LocBlockDom(rank, idxType, stridable);

  // STATE:

  //
  // the block of local array data
  //
  var myElems: [locDom.myBlock] eltType;
}

//
// the accessor for the local array -- assumes the index is local
//
def LocBlockArr.this(i) var {
  return myElems(i);
}

//
// output local array piece
//
def LocBlockArr.writeThis(x: Writer) {
  // note on this fails; see writeThisUsingOn.chpl
  x.write(myElems);
}

def LocBlockArr.numElements return myElems.numElements;

//
// helper function for blocking index ranges
//
def _computeBlock(waylo, numelems, lo, wayhi, numblocks, blocknum) {
  def procToData(x, lo)
    return lo + (x:lo.type) + (x:real != x:int:real):lo.type;

  const blo =
    if blocknum == 0 then waylo
      else procToData((numelems:real * blocknum) / numblocks, lo);
  const bhi =
    if blocknum == numblocks - 1 then wayhi
      else procToData((numelems:real * (blocknum+1)) / numblocks, lo) - 1;

  return (blo, bhi);
}

//
// naive routine for dividing numLocales into rank factors
//
def _factor(param rank: int, value) {
  var factors: rank*int;
  for param i in 1..rank do
    factors(i) = 1;
  if value >= 1 {
    var iv = value;
    var factor = 1;
    while iv > 1 {
      for i in 2..iv {
        if iv % i == 0 {
          var j = 1;
          for i in 2..rank {
            if factors(i) < factors(j) then
              j = i;
          }
          factors(j) *= i;
          iv = iv / i;
          break;
        }
      }
    }
  }
  for i in 1..rank do
    for j in i+1..rank do
      if factors(i) < factors(j) then
        factors(i) <=> factors(j);
  return factors;
}


Added some clarifying comments to Brad's version of spectralnorm.

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

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

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

config const n = 500;

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

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

  u = 1.0;                      // Initialize u to 1.0

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

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

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

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

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

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

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

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

config const n = 500;

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

  u = 1.0;                      // Initialize u to 1.0

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

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

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

//
// Multiply vector v by matrix A
//
proc multiplyAv(v: [?Dv], Av: [?DAv]) {
  forall i in DAv do
    serial do
      Av[i] = + reduce [j in Dv] (A[i,j] * v[j]);
}

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

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