changeset 508:3420e5f61a1b sized_matrix

Store size alongside matrix data, so we can now distinguish between different matrix sizes in which one dimension is zero
author Chris Cannam
date Wed, 20 Nov 2013 13:57:32 +0000
parents 38e7dce5fa37
children 051c4644d063
files src/may/matrix.yeti src/may/matrix/test/test_matrix.yeti src/may/stream/convolve.yeti src/may/stream/framer.yeti src/may/stream/manipulate.yeti src/may/stream/syntheticstream.yeti src/may/stream/test/test_manipulate.yeti src/may/test/test.yeti
diffstat 8 files changed, 242 insertions(+), 205 deletions(-) [+]
line wrap: on
line diff
--- a/src/may/matrix.yeti	Wed Nov 20 12:17:21 2013 +0000
+++ b/src/may/matrix.yeti	Wed Nov 20 13:57:32 2013 +0000
@@ -33,10 +33,9 @@
  * column-major and row-major storage; and use toSparse and toDense to
  * convert between sparse and dense storage.
  *
- * Note that the matrix representation does not take into account
- * different forms of zero-width or zero-height matrix. All matrices
- * of zero width or height are equal to each other, and all are equal
- * to the zero-sized matrix.
+ * Note that the matrix size is preserved even if at least one
+ * dimension is zero. That is, it is legal to have matrices of size
+ * 0x0, 0x4, 1x0 etc, and they are distinct from each other.
  */
 
 module may.matrix;
@@ -44,49 +43,28 @@
 vec = load may.vector;
 mm = load may.mathmisc;
 
-typedef opaque matrix_t =
-    DenseRows array<vec.vector_t> | // array of rows
-    DenseCols array<vec.vector_t> | // array of columns
-    SparseCSR {
-        values is vec.vector_t,
-        indices is array<number>, // column index of each value
-        pointers is array<number>, // offset of first value in each row
-        extent is number // max possible index + 1, i.e. number of columns
+typedef opaque matrix_t = {
+    size is { rows is number, columns is number },
+    data is
+        DenseRows array<vec.vector_t> | // array of rows
+        DenseCols array<vec.vector_t> | // array of columns
+        SparseCSR {
+            values is vec.vector_t,
+            indices is array<number>, // column index of each value
+            pointers is array<number>, // offset of first value in each row
+            extent is number // max possible index + 1, i.e. number of columns
         } |
-    SparseCSC {
-        values is vec.vector_t,
-        indices is array<number>, // row index of each value
-        pointers is array<number>, // offset of first value in each column
-        extent is number // max pointers index + 1, i.e. number of rows
-        };
+        SparseCSC {
+            values is vec.vector_t,
+            indices is array<number>, // row index of each value
+            pointers is array<number>, // offset of first value in each column
+            extent is number // max pointers index + 1, i.e. number of rows
+        }
+};
 
-();
-
-width m = 
-    case m of
-    DenseRows r:
-        if not empty? r then vec.length r[0] else 0 fi;
-    DenseCols c:
-        length c;
-    SparseCSR { values, indices, pointers, extent }:
-        extent;
-    SparseCSC { values, indices, pointers, extent }:
-        (length pointers) - 1;
-    esac;
-
-height m =
-    case m of
-    DenseRows r:
-        length r;
-    DenseCols c:
-        if not empty? c then vec.length c[0] else 0 fi;
-    SparseCSR { values, indices, pointers, extent }:
-        (length pointers) - 1;
-    SparseCSC { values, indices, pointers, extent }:
-        extent;
-    esac;
-
-size m = { rows = height m, columns = width m };
+size m = m.size;
+width m = m.size.columns;
+height m = m.size.rows;
 
 nonZeroValues m =
    (nz d =
@@ -94,7 +72,7 @@
            (map do v:
                 sum (map do n: if n == 0 then 0 else 1 fi done (vec.list v))
                 done d);
-    case m of 
+    case m.data of 
     DenseRows d: nz d;
     DenseCols d: nz d;
     SparseCSR d: vec.length d.values;
@@ -142,7 +120,7 @@
     vec.vector dslice);
 
 at' m row col =
-    case m of
+    case m.data of
     DenseRows rows: r = rows[row]; vec.at r col;
     DenseCols cols: c = cols[col]; vec.at c row;
     SparseCSR data: fromSlice row col data;
@@ -152,14 +130,14 @@
 //!!! better as getXx or just xx?
 
 getColumn j m =
-    case m of
+    case m.data of
     DenseCols cols: cols[j];
     SparseCSC data: filledSlice j data;
     _: vec.fromList (map do i: at' m i j done [0..height m - 1]);
     esac;
 
 getRow i m =
-    case m of
+    case m.data of
     DenseRows rows: rows[i];
     SparseCSR data: filledSlice i data; 
     _: vec.fromList (map do j: at' m i j done [0..width m - 1]);
@@ -178,7 +156,7 @@
     map do i: getColumn i m done [0 .. (width m) - 1];
 
 isRowMajor? m =
-    case m of
+    case m.data of
     DenseRows _: true;
     DenseCols _: false;
     SparseCSR _: true;
@@ -186,7 +164,7 @@
     esac;
 
 isSparse? m =
-    case m of
+    case m.data of
     DenseRows _: false;
     DenseCols _: false;
     SparseCSR _: true;
@@ -203,36 +181,41 @@
     else RowMajor ()
     fi;
 
+flippedSize { rows, columns } = { rows = columns, columns = rows };
+
 newColumnMajorStorage { rows, columns } = 
-    if rows < 1 then array []
-    else array (map \(vec.zeros rows) [1..columns])
-    fi;
+    array (map \(vec.zeros rows) [1..columns]);
 
-zeroMatrix { rows, columns } = 
-    DenseCols (newColumnMajorStorage { rows, columns });
+zeroMatrix size = 
+    {
+        size,
+        data = DenseCols (newColumnMajorStorage size)
+    };
 
-zeroMatrixWithTypeOf m { rows, columns } = 
-    if isRowMajor? m then
-        DenseRows (newColumnMajorStorage { rows = columns, columns = rows });
-    else
-        DenseCols (newColumnMajorStorage { rows, columns });
-    fi;
+zeroMatrixWithTypeOf m size = 
+    {
+        size,
+        data =
+            if isRowMajor? m then
+                DenseRows (newColumnMajorStorage (flippedSize size));
+            else
+                DenseCols (newColumnMajorStorage size);
+            fi
+    };
 
 zeroSizeMatrix () = zeroMatrix { rows = 0, columns = 0 };
 
-empty?' m = (width m == 0 or height m == 0);
-
 generate f { rows, columns } =
-    if rows < 1 or columns < 1 then zeroSizeMatrix ()
-    else
-        m = array (map \(new double[rows]) [1..columns]);
-        for [0..columns-1] do col:
-            for [0..rows-1] do row:
-                m[col][row] := f row col;
-            done;
+   (m = array (map \(new double[rows]) [1..columns]);
+    for [0..columns-1] do col:
+        for [0..rows-1] do row:
+            m[col][row] := f row col;
         done;
-        DenseCols (array (map vec.vector m))
-    fi;
+    done;
+    {
+        size = { rows, columns },
+        data = DenseCols (array (map vec.vector m))
+    });
 
 swapij =
     map do { i, j, v }: { i = j, j = i, v } done;
@@ -248,7 +231,7 @@
                     (slice indices start end)
                     (vec.list (vec.slice values start end))
                 done [0..length pointers - 2]);
-    case m of
+    case m.data of
     SparseCSC d: swapij (enumerate d);
     SparseCSR d: enumerate d;
      _: [];
@@ -263,7 +246,7 @@
                     [0..vec.length vv - 1]
                     (vec.list vv);
                 done [0..length d - 1]);
-    case m of
+    case m.data of
     DenseCols c: swapij (enumerate c);
     DenseRows r: enumerate r;
      _: [];
@@ -303,11 +286,14 @@
             esac;
         fi;
     fillPointers 0 0 (map (.maj) ordered);
-    tagger {
-        values = vec.fromList (map (.v) ordered),
-        indices = array (map (.min) ordered),
-        pointers,
-        extent = minorSize,
+    {
+        size,
+        data = tagger {
+            values = vec.fromList (map (.v) ordered),
+            indices = array (map (.min) ordered),
+            pointers,
+            extent = minorSize,
+        }
     });
 
 // Make a sparse matrix from entries that may contain out-of-range
@@ -330,24 +316,32 @@
     fi;
 
 toDense m =
-    if not (isSparse? m) then m
-    elif isRowMajor? m then
-        DenseRows (array (map do row: getRow row m done [0..height m - 1]));
-    else
-        DenseCols (array (map do col: getColumn col m done [0..width m - 1]));
-    fi;
+    {
+        size = m.size,
+        data = 
+            if not (isSparse? m) then m.data
+            elif isRowMajor? m then
+                DenseRows (array (map do row: getRow row m done [0..height m - 1]));
+            else
+                DenseCols (array (map do col: getColumn col m done [0..width m - 1]));
+            fi
+    };
 
 constMatrix n = generate do row col: n done;
 randomMatrix = generate do row col: Math#random() done;
 identityMatrix = constMatrix 1;
 
 transposed m =
-    case m of
-    DenseRows d: DenseCols d;
-    DenseCols d: DenseRows d;
-    SparseCSR d: SparseCSC d;
-    SparseCSC d: SparseCSR d;
-    esac;
+    {
+        size = flippedSize m.size,
+        data = 
+            case m.data of
+            DenseRows d: DenseCols d;
+            DenseCols d: DenseRows d;
+            SparseCSR d: SparseCSC d;
+            SparseCSC d: SparseCSR d;
+            esac
+    };
 
 flipped m =
     if isSparse? m then
@@ -357,8 +351,7 @@
             generate do row col: at' m row col done (size m);
         else
             transposed
-               (generate do row col: at' m col row done
-                { rows = (width m), columns = (height m) });
+               (generate do row col: at' m col row done (flippedSize (size m)));
         fi
     fi;
 
@@ -376,21 +369,19 @@
         vecComparator d1.values d2.values and
         d1.indices == d2.indices and
         d1.pointers == d2.pointers;
-    case m1 of
+    case m1.data of
     DenseRows d1:
-        case m2 of DenseRows d2: compareVecLists d1 d2; _: false; esac;
+        case m2.data of DenseRows d2: compareVecLists d1 d2; _: false; esac;
     DenseCols d1:
-        case m2 of DenseCols d2: compareVecLists d1 d2; _: false; esac;
+        case m2.data of DenseCols d2: compareVecLists d1 d2; _: false; esac;
     SparseCSR d1:
-        case m2 of SparseCSR d2: compareSparse d1 d2; _: false; esac;
+        case m2.data of SparseCSR d2: compareSparse d1 d2; _: false; esac;
     SparseCSC d1:
-        case m2 of SparseCSC d2: compareSparse d1 d2; _: false; esac;
+        case m2.data of SparseCSC d2: compareSparse d1 d2; _: false; esac;
     esac);
 
 equal' comparator vecComparator m1 m2 =
-    if empty?' m1 and empty?' m2 then
-        true
-    elif size m1 != size m2 then 
+    if size m1 != size m2 then 
         false
     elif isRowMajor? m1 != isRowMajor? m2 then
         equal' comparator vecComparator (flipped m1) m2;
@@ -415,25 +406,56 @@
 equal =
     equal' (==) vec.equal;
 
-newMatrix type data = //!!! NB does not copy data
-   (tagger = case type of RowMajor (): DenseRows; ColumnMajor (): DenseCols esac;
-    if empty? data or vec.empty? (head data)
-    then zeroSizeMatrix ()
-    else tagger (array data)
-    fi);
+newMatrixOfSize size type rowscols = //!!! NB does not copy data
+    if type == RowMajor () then
+        {
+            size,
+            data = DenseRows (array rowscols)
+        }
+    else
+        {
+            size,
+            data = DenseCols (array rowscols)
+        }
+    fi;
+
+newMatrix type rowscols = //!!! NB does not copy data
+    if type == RowMajor () then 
+        {
+            size = { 
+                rows = length rowscols, 
+                columns = 
+                    if empty? rowscols then 0
+                    else vec.length (head rowscols) 
+                    fi,
+            },
+            data = DenseRows (array rowscols)
+        }
+    else
+        {
+            size = { 
+                columns = length rowscols, 
+                rows = 
+                    if empty? rowscols then 0
+                    else vec.length (head rowscols) 
+                    fi,
+            },
+            data = DenseCols (array rowscols)
+        }
+    fi;
 
 newRowVector data = //!!! NB does not copy data
-    DenseRows (array [data]);
+    newMatrix (RowMajor ()) (array [data]);
 
 newColumnVector data = //!!! NB does not copy data
-    DenseCols (array [data]);
+    newMatrix (ColumnMajor ()) (array [data]);
 
 denseLinearOp op m1 m2 =
     if isRowMajor? m1 then
-        newMatrix (typeOf m1) 
+        newMatrixOfSize m1.size (typeOf m1) 
            (map2 do c1 c2: op c1 c2 done (asRows m1) (asRows m2));
     else
-        newMatrix (typeOf m1) 
+        newMatrixOfSize m1.size (typeOf m1) 
            (map2 do c1 c2: op c1 c2 done (asColumns m1) (asColumns m2));
     fi;
 
@@ -480,9 +502,9 @@
         makeSparse (typeOf m) (size m)
            (map do { i, j, v }: { i, j, v = factor * v } done (enumerate m))
     elif isRowMajor? m then
-        newMatrix (typeOf m) (map (vec.scaled factor) (asRows m));
+        newMatrixOfSize m.size (typeOf m) (map (vec.scaled factor) (asRows m));
     else
-        newMatrix (typeOf m) (map (vec.scaled factor) (asColumns m));
+        newMatrixOfSize m.size (typeOf m) (map (vec.scaled factor) (asColumns m));
     fi;
 
 abs' m =
@@ -490,9 +512,9 @@
         makeSparse (typeOf m) (size m)
            (map do { i, j, v }: { i, j, v = abs v } done (enumerate m))
     elif isRowMajor? m then
-        newMatrix (typeOf m) (map vec.abs (asRows m));
+        newMatrixOfSize m.size (typeOf m) (map vec.abs (asRows m));
     else
-        newMatrix (typeOf m) (map vec.abs (asColumns m));
+        newMatrixOfSize m.size (typeOf m) (map vec.abs (asColumns m));
     fi;
 
 negative m =
@@ -500,9 +522,9 @@
         makeSparse (typeOf m) (size m)
            (map do { i, j, v }: { i, j, v = (-v) } done (enumerate m))
     elif isRowMajor? m then
-        newMatrix (typeOf m) (map vec.negative (asRows m));
+        newMatrixOfSize m.size (typeOf m) (map vec.negative (asRows m));
     else
-        newMatrix (typeOf m) (map vec.negative (asColumns m));
+        newMatrixOfSize m.size (typeOf m) (map vec.negative (asColumns m));
     fi;
 
 //!!! doc: filter by predicate, always returns sparse matrix
@@ -518,11 +540,12 @@
     all f (map (.v) (enumerate m));
 
 sparseProductLeft size m1 m2 =
-   ({ values, indices, pointers } = case m1 of
-         SparseCSR d: d;
-         SparseCSC d: d;
-         _: failWith "sparseProductLeft called for non-sparse m1";
-         esac;
+   ({ values, indices, pointers } = 
+        case m1.data of
+        SparseCSR d: d;
+        SparseCSC d: d;
+        _: failWith "sparseProductLeft called for non-sparse m1";
+        esac;
     rows = isRowMajor? m1;
     data = array (map \(new double[size.rows]) [1..size.columns]);
     for [0..size.columns - 1] do j':
@@ -535,14 +558,15 @@
             data[j'][i] := data[j'][i] + (vec.at values ix) * (vec.at c j);
         done;
     done;
-    DenseCols (array (map vec.vector (list data))));
+    newMatrixOfSize size (ColumnMajor ()) (array (map vec.vector (list data))));
 
 sparseProductRight size m1 m2 =
-   ({ values, indices, pointers } = case m2 of
-         SparseCSR d: d;
-         SparseCSC d: d;
-         _: failWith "sparseProductLeft called for non-sparse m1";
-         esac;
+   ({ values, indices, pointers } = 
+        case m2.data of
+        SparseCSR d: d;
+        SparseCSC d: d;
+        _: failWith "sparseProductLeft called for non-sparse m1";
+        esac;
     rows = isRowMajor? m2;
     data = array (map \(new double[size.columns]) [1..size.rows]);
     for [0..size.rows - 1] do i':
@@ -555,12 +579,13 @@
             data[i'][j] := data[i'][j] + (vec.at values ix) * (vec.at r i);
         done;
     done;
-    DenseRows (array (map vec.vector (list data))));
+    newMatrixOfSize size (RowMajor ()) (array (map vec.vector (list data))));
 
 sparseProduct size m1 m2 =
-    case m2 of
+    case m2.data of
     SparseCSC d:
-       ({ values, indices, pointers } = case m1 of
+       ({ values, indices, pointers } =
+            case m1.data of
             SparseCSR d1: d1;
             SparseCSC d1: d1;
             _: failWith "sparseProduct called for non-sparse matrices";
@@ -605,7 +630,7 @@
             data[j][i] := vec.sum (vec.multiply row (getColumn j m2));
         done;
     done;
-    DenseCols (array (map vec.vector (list data))));
+    newMatrixOfSize size (ColumnMajor ()) (array (map vec.vector (list data))));
 
 product m1 m2 =
     if (size m1).columns != (size m2).rows
@@ -672,7 +697,7 @@
         failWith "Matrix dimensions incompatible for concat (found \(map do m: counter (size m) done mm) not all of which are \(n))";
     fi);
 
-concatHorizontal' mm = //!!! doc: storage order is taken from first matrix in sequence; concat is obviously not lazy (unlike std module)
+concatHorizontal mm = //!!! doc: storage order is taken from first matrix in sequence; concat is obviously not lazy (unlike std module)
     case mm of
     [m]: m;
     first::rest:
@@ -681,16 +706,20 @@
             sparseConcat (Horizontal ()) first mm
         else
             row = isRowMajor? first;
-            // horizontal, row-major: against grain with rows
-            // horizontal, col-major: with grain with cols
-            if row then concatAgainstGrain DenseRows getRow (.rows) mm;
-            else concatWithGrain DenseCols getColumn (.columns) mm;
-            fi;
+            {
+                size = { rows = height first, columns = sum (map width mm) },
+                data =
+                    // horizontal, row-major: against grain with rows
+                    // horizontal, col-major: with grain with cols
+                    if row then concatAgainstGrain DenseRows getRow (.rows) mm;
+                    else concatWithGrain DenseCols getColumn (.columns) mm;
+                    fi
+            };
         fi);
      _: zeroSizeMatrix ();
     esac;
 
-concatVertical' mm = //!!! doc: storage order is taken from first matrix in sequence; concat is obviously not lazy (unlike std module)
+concatVertical mm = //!!! doc: storage order is taken from first matrix in sequence; concat is obviously not lazy (unlike std module)
     case mm of
     [m]: m;
     first::rest:
@@ -699,23 +728,19 @@
             sparseConcat (Vertical ()) first mm
         else
             row = isRowMajor? first;
-            // vertical, row-major: with grain with rows
-            // vertical, col-major: against grain with cols
-            if row then concatWithGrain DenseRows getRow (.rows) mm;
-            else concatAgainstGrain DenseCols getColumn (.columns) mm;
-            fi;
+            {
+                size = { rows = sum (map height mm), columns = width first },
+                data = 
+                    // vertical, row-major: with grain with rows
+                    // vertical, col-major: against grain with cols
+                    if row then concatWithGrain DenseRows getRow (.rows) mm;
+                    else concatAgainstGrain DenseCols getColumn (.columns) mm;
+                    fi,
+            };
         fi);
      _: zeroSizeMatrix ();
     esac;
 
-//!!! doc this filter -- zero-size elts are ignored
-concatHorizontal mm =
-    concatHorizontal' (filter do mat: not (empty?' mat) done mm);
-
-//!!! doc this filter -- zero-size elts are ignored
-concatVertical mm =
-    concatVertical' (filter do mat: not (empty?' mat) done mm);
-
 //!!! next two v. clumsy
 
 //!!! doc note: argument order chosen for consistency with std module slice
@@ -728,9 +753,13 @@
         elif end > height m then rowSlice m start (height m)
         else
             if isRowMajor? m then
-                DenseRows (array (map ((flip getRow) m) [start .. end - 1]))
+                newMatrixOfSize { rows = end - start, columns = width m }
+                   (RowMajor ())
+                   (array (map ((flip getRow) m) [start .. end - 1]))
             else 
-                DenseCols (array (map do v: vec.slice v start end done (asColumns m)))
+                newMatrixOfSize { rows = end - start, columns = width m }
+                   (ColumnMajor ())
+                   (array (map do v: vec.slice v start end done (asColumns m)))
             fi;
         fi;
     fi;
@@ -745,9 +774,13 @@
         elif end > width m then columnSlice m start (width m)
         else
             if not isRowMajor? m then
-                DenseCols (array (map ((flip getColumn) m) [start .. end - 1]))
+                newMatrixOfSize { rows = height m, columns = end - start }
+                   (ColumnMajor ())
+                   (array (map ((flip getColumn) m) [start .. end - 1]))
             else 
-                DenseRows (array (map do v: vec.slice v start end done (asRows m)))
+                newMatrixOfSize { rows = height m, columns = end - start }
+                   (RowMajor ())
+                   (array (map do v: vec.slice v start end done (asRows m)))
             fi;
         fi;
     fi;
@@ -786,24 +819,24 @@
     fi);
 
 minValue m =
-    if empty?' m then 0
+    if width m == 0 or height m == 0 then 0
     else 
         minv ll = fold min (head ll) (tail ll);
         minv (map (.v) (enumerate m));
     fi;
 
 maxValue m =
-    if empty?' m then 0
+    if width m == 0 or height m == 0 then 0
     else 
         maxv ll = fold max (head ll) (tail ll);
         maxv (map (.v) (enumerate m));
     fi;
 
 transformRows rf m =
-    newMatrix (RowMajor ()) (map rf (asRows m));
+    newMatrixOfSize m.size (RowMajor ()) (map rf (asRows m));
 
 transformColumns cf m =
-    newMatrix (ColumnMajor ()) (map cf (asColumns m));
+    newMatrixOfSize m.size (ColumnMajor ()) (map cf (asColumns m));
 
 format m =
     strJoin "\n"
@@ -846,8 +879,6 @@
     randomMatrix,
     zeroMatrix,
     identityMatrix,
-    zeroSizeMatrix,
-    empty? = empty?',
     equal, //!!! if empty is empty?, why is equal not equal? ?
     equalUnder,
     transposed,
@@ -903,8 +934,6 @@
     randomMatrix is { rows is number, columns is number } -> matrix_t,
     zeroMatrix is { rows is number, columns is number } -> matrix_t, 
     identityMatrix is { rows is number, columns is number } -> matrix_t, 
-    zeroSizeMatrix is () -> matrix_t,
-    empty? is matrix_t -> boolean,
     equal is matrix_t -> matrix_t -> boolean,
     equalUnder is (number -> number -> boolean) -> matrix_t -> matrix_t -> boolean,
     transposed is matrix_t -> matrix_t,
--- a/src/may/matrix/test/test_matrix.yeti	Wed Nov 20 12:17:21 2013 +0000
+++ b/src/may/matrix/test/test_matrix.yeti	Wed Nov 20 13:57:32 2013 +0000
@@ -25,8 +25,9 @@
 ),
 
 "constMatrixEmpty2-\(name)": \(
-    compare (mat.size (constMatrix 2 { rows = 0, columns = 4 })) { columns = 0, rows = 0 } and
-        compare (mat.size (constMatrix 2 { rows = 4, columns = 0 })) { columns = 0, rows = 0 }
+    compare (mat.size (constMatrix 2 { rows = 0, columns = 4 })) { columns = 4, rows = 0 } and
+        compare (mat.size (constMatrix 2 { rows = 4, columns = 0 })) { columns = 0, rows = 4 } and
+        compare (vec.list (mat.getColumn 2 (constMatrix 2 { rows = 0, columns = 4 }))) []
 ),
 
 "constMatrix-\(name)": \(
@@ -71,10 +72,15 @@
 ),
 
 "generateEmpty-\(name)": \(
-    m = generate do row col: 0 done { rows = 0, columns = 0 };
+    m = generate do row col: 6 done { rows = 0, columns = 0 };
     compare (mat.size m) { columns = 0, rows = 0 }
 ),
 
+"generateEmpty2-\(name)": \(
+    m = generate do row col: 6 done { rows = 4, columns = 0 };
+    compare (mat.size m) { columns = 0, rows = 4 }
+),
+
 "generate-\(name)": \(
     m = generate do row col: row * 10 + col done { rows = 2, columns = 3 };
     compare (vec.list (mat.getRow 0 m)) [0,1,2] and
@@ -123,8 +129,8 @@
 
 "transposedEmpty-\(name)": \(
     compare (mat.size (mat.transposed (constMatrix 2 { rows = 0, columns = 0 }))) { columns = 0, rows = 0 } and
-        compare (mat.size (mat.transposed (constMatrix 2 { rows = 0, columns = 4 }))) { columns = 0, rows = 0 } and
-        compare (mat.size (mat.transposed (constMatrix 2 { rows = 4, columns = 0 }))) { columns = 0, rows = 0 }
+        compare (mat.size (mat.transposed (constMatrix 2 { rows = 0, columns = 4 }))) { columns = 0, rows = 4 } and
+        compare (mat.size (mat.transposed (constMatrix 2 { rows = 4, columns = 0 }))) { columns = 4, rows = 0 }
 ),
 
 "transposedSize-\(name)": \(
@@ -162,7 +168,7 @@
 
 "flipped-empty-\(name)": \(
     m = constMatrix 2 { rows = 0, columns = 4 };
-    compareMatrices (mat.flipped m) (mat.flipped (constMatrix 0 { rows = 0, columns = 0 }));
+    compareMatrices (mat.flipped m) (mat.flipped (constMatrix 0 { rows = 0, columns = 4 }));
 ),
 
 "toRowMajor-\(name)": \(
@@ -199,7 +205,7 @@
           (constMatrix (-1.5) { rows = 3, columns = 4 }) and
        compareMatrices
           (mat.scaled 0.5 (constMatrix 2 { rows = 0, columns = 2 }))
-          (constMatrix 5 { rows = 0, columns = 0 })
+          (constMatrix 5 { rows = 0, columns = 2 })
 ),
 
 "transformRows-\(name)": \(
@@ -370,7 +376,7 @@
            (newMatrix (RowMajor ()) [[1]]) and
         compareMatrices
            (mat.resizedTo { rows = 2, columns = 3 }
-               (mat.zeroSizeMatrix ()))
+               (mat.zeroMatrix { rows = 0, columns = 0 }))
            (newMatrix (RowMajor ()) [[0,0,0],[0,0,0]]) and
         mat.isSparse?
            (mat.resizedTo { rows = 1, columns = 1 }
@@ -379,19 +385,18 @@
 
 "zeroSizeMatrix-\(name)": \(
     compareMatrices
-       (mat.zeroSizeMatrix ())
+       (mat.zeroMatrix { rows = 0, columns = 0 })
        (newMatrix (ColumnMajor ()) []) and
         compareMatrices
-           (mat.zeroSizeMatrix ())
+           (mat.zeroMatrix { rows = 0, columns = 1 })
            (newMatrix (ColumnMajor ()) [[]]) and
+        (not mat.equal (newMatrix (ColumnMajor ()) [[]])
+                       (newMatrix (RowMajor ()) [[]])) and
         compareMatrices
-           (newMatrix (ColumnMajor ()) [[]])
-           (newMatrix (RowMajor ()) [[]]) and
+           (mat.zeroMatrix { rows = 0, columns = 0 })
+           (mat.newSparseMatrix (ColumnMajor ()) { rows = 0, columns = 0 } []) and
         compareMatrices
-           (mat.zeroSizeMatrix ())
-           (mat.newSparseMatrix (ColumnMajor ()) { rows = 0, columns = 1 } []) and
-        compareMatrices
-           (mat.zeroSizeMatrix ())
+           (mat.zeroMatrix { rows = 1, columns = 0 })
            (mat.newSparseMatrix (ColumnMajor ()) { rows = 1, columns = 0 } [])
 ),
 
@@ -421,14 +426,14 @@
     compareMatrices
        (mat.concatHorizontal 
           [(newMatrix (ColumnMajor ()) [[]]),
-           (newMatrix (RowMajor ()) [[]]),
-           (mat.zeroSizeMatrix ())])
-       (mat.zeroSizeMatrix ()) and
+           (newMatrix (ColumnMajor ()) [[],[]]),
+           (mat.zeroMatrix { rows = 0, columns = 6 })])
+       (mat.zeroMatrix { rows = 0, columns = 9 }) and
        compareMatrices
           (mat.concatHorizontal 
-             [(newMatrix (ColumnMajor ()) [[]]),
+             [(newMatrix (RowMajor ()) [[]]),
               (newMatrix (RowMajor ()) [[1,2]]),
-              (mat.zeroSizeMatrix ())])
+              (mat.zeroMatrix { rows = 1, columns = 0 })])
           (newMatrix (RowMajor ()) [[1,2]])
 ),
 
--- a/src/may/stream/convolve.yeti	Wed Nov 20 12:17:21 2013 +0000
+++ b/src/may/stream/convolve.yeti	Wed Nov 20 13:57:32 2013 +0000
@@ -74,7 +74,7 @@
     s with 
     {
         get finished? () =
-            s.finished? and (mat.empty? history),
+            s.finished? and mat.width history == 0,
         get available () = 
             case s.available of
             Known n: Known (n + mat.width history);
--- a/src/may/stream/framer.yeti	Wed Nov 20 12:17:21 2013 +0000
+++ b/src/may/stream/framer.yeti	Wed Nov 20 13:57:32 2013 +0000
@@ -120,14 +120,14 @@
 
 streamContiguous rate framesize frames =
    (var remaining = frames;
-    var buffered = mat.zeroSizeMatrix ();
     var position = 0;
     channels = mat.height (head frames); // so we don't need to keep a head ptr
+    var buffered = mat.zeroMatrix { rows = channels, columns = 0 };
     {
         get position () = position,
         get channels () = channels,
         get sampleRate () = rate,
-        get finished? () = empty? remaining and mat.empty? buffered,
+        get finished? () = empty? remaining and mat.width buffered == 0,
         get available () = 
             // Don't take length of frames -- we don't want to lose laziness.
             // If the caller cares that much, they can measure frames themselves
@@ -170,21 +170,22 @@
     first::rest:
         mat.concatHorizontal (ola rest first []);
      _: 
-        mat.zeroSizeMatrix ();
+        mat.zeroMatrix { rows = 0, columns = 0 };
     esac);
 
 streamOverlapping rate { framesize, hop, window } frames =
    (var remaining = frames;
-    var buffered = mat.zeroSizeMatrix ();
     var position = 0;
 
     factor = hop / (framesize/2);
     w = vec.scaled factor (window framesize);
     channels = mat.height (head frames); // so we don't need to keep a head ptr
 
+    var buffered = mat.zeroMatrix { rows = channels, columns = 0 };
+
     syncd = synchronized remaining;
 
-    finished' () = syncd \(empty? remaining and mat.empty? buffered);
+    finished' () = syncd \(empty? remaining and mat.width buffered == 0);
 
     read' count = 
        (framesFor samples acc =
--- a/src/may/stream/manipulate.yeti	Wed Nov 20 12:17:21 2013 +0000
+++ b/src/may/stream/manipulate.yeti	Wed Nov 20 13:57:32 2013 +0000
@@ -125,9 +125,7 @@
     {
         read count = 
            (m = s.read count;
-            if mat.empty? m then m
-            else mat.difference (mat.zeroMatrix (mat.size m)) m
-            fi)
+            mat.difference (mat.zeroMatrix (mat.size m)) m)
     };
 
 //!!! poor name, confusion with mixed, but consistent with channels.yeti
@@ -149,13 +147,14 @@
        (sz = { rows = max (mat.height m1) (mat.height m2),
                columns = min (mat.width m1) (mat.width m2) };
         if sz.columns == 0 then
-            mat.zeroSizeMatrix ()
+            mat.zeroMatrix sz
         else
             mat.sum (mat.resizedTo sz m1) (mat.resizedTo sz m2);
         fi);
+    channels = head (sortBy (>) (map (.channels) streams));
     {
         get position () = head (sort (map (.position) streams)),
-        get channels () = head (sortBy (>) (map (.channels) streams)),
+        get channels () = channels,
         get sampleRate () = (head streams).sampleRate, //!!! document this
         get available () =
             fold do dur s: minDurationOf dur s.available done (Infinite ()) streams,
@@ -174,7 +173,7 @@
                 _: acc;
                esac;
             case readTo (None ()) streams count of
-            None (): mat.zeroSizeMatrix ();
+            None (): mat.zeroMatrix { rows = channels, columns = 0 };
             Some m: m;
             esac),
         close () = for streams do s: s.close() done,
@@ -215,7 +214,7 @@
     if s.available == Infinite () then s
     else
         var pos = 0;
-        var cache = mat.zeroSizeMatrix ();
+        var cache = mat.zeroMatrix { rows = s.channels, columns = 0 };
         chunks = array [];
         cachedPartsFor count =
            (start = pos % (mat.width cache);
@@ -269,7 +268,8 @@
         var lowtide = 0;
         var hightide = 0;
         var adcount = 0;
-        var cache = mat.toRowMajor (mat.zeroSizeMatrix ());
+        var cache = mat.toRowMajor
+           (mat.zeroMatrix { rows = s.channels, columns = 0 });
         syncd = synchronized pos;
         advance i n =
            (wasLow = (pos[i] == lowtide);
@@ -305,7 +305,8 @@
                 read count = syncd
                   \(ready = hightide - pos[instance];
                     if s.finished? and ready <= 0 
-                    then mat.toRowMajor (mat.zeroSizeMatrix ())
+                    then mat.toRowMajor
+                       (mat.zeroMatrix { rows = s.channels, columns = 0 })
                     else
                         if count > ready then
                             more = s.read (count - ready);
--- a/src/may/stream/syntheticstream.yeti	Wed Nov 20 12:17:21 2013 +0000
+++ b/src/may/stream/syntheticstream.yeti	Wed Nov 20 13:57:32 2013 +0000
@@ -106,7 +106,7 @@
         get sampleRate () = rate,
         get available () = Known 0,
         get finished? () = true,
-        read count = mat.zeroSizeMatrix (),
+        read count = mat.zeroMatrix { rows = channels, columns = 0 },
         close = \(),
     };
 
--- a/src/may/stream/test/test_manipulate.yeti	Wed Nov 20 12:17:21 2013 +0000
+++ b/src/may/stream/test/test_manipulate.yeti	Wed Nov 20 13:57:32 2013 +0000
@@ -620,7 +620,7 @@
         compare s2.finished? false and
         compare s2.available (Known 1) and // when one is known, so is the other
 
-        compare (map vec.list (mat.asRows (s1.read 3))) [] and
+        compare (map vec.list (mat.asRows (s1.read 3))) [[]] and
 
         compare (map vec.list (mat.asRows (s2.read 1))) [[3]] and
         compare s1.position 3 and
--- a/src/may/test/test.yeti	Wed Nov 20 12:17:21 2013 +0000
+++ b/src/may/test/test.yeti	Wed Nov 20 13:57:32 2013 +0000
@@ -55,7 +55,8 @@
 
 failedTests testHash =
     select (!= "")
-       (mapHash do name f:
+       (map do name:
+            f = testHash[name];
             try
                 if f () then "" else
                     println "Test \(name) failed";
@@ -65,7 +66,7 @@
                 println "Test \(name) threw exception: \(e)";
                 name;
             yrt;
-        done testHash);
+        done (sort (keys testHash)));
         
 runTests group testHash =
    (start = System#currentTimeMillis();