//    Copyright 2011 Brad Block, Pawjaw, LLC. (brad@pawjaw.com)
//
//    This program is free software: you can redistribute it and/or modify
//    it under the terms of the GNU General Public License as published by
//    the Free Software Foundation, either version 3 of the License, or
//    (at your option) any later version.
//
//    This program is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//    GNU General Public License for more details.
//
//    You should have received a copy of the GNU General Public License
//    along with this program.  If not, see <http://www.gnu.org/licenses/>.
//
//    This program implements concepts described in the paper entitled
//    "Sparse Matrix Multiplication Package (SMMP)"
//    written by Randolph E. Bank and Craig C. Douglas.

package com.pawjaw.linalg;

import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.regex.Pattern;

public class Matrix {
    public int rows, cols, vals;
    public int[] pointr, rowind;
    public double[] value;

    private static Comparator matrix_sorter = new Comparator() {
        public int compare(Object a, Object b) {
            double r = ((double[])a)[0] - ((double[])b)[0];
            double c = ((double[])a)[1] - ((double[])b)[1];
            return c < 0 ? -1 : c > 0 ? 1 : r < 0 ? -1 : r > 0 ? 1 : 0;
        }
    };

    public Matrix() {}

    public Matrix(double[][] m) { // m = double[rows][columns]
        this(m, false);
    }

    public Matrix(DataInputStream dis) throws IOException {
        int i, I;
        rows = dis.readInt();
        cols = dis.readInt();
        vals = dis.readInt();
        pointr = new int[I = dis.readInt()];
        for(i = 0;i < I;i++)
            pointr[i] = dis.readInt();
        rowind = new int[I = dis.readInt()];
        for(i = 0;i < I;i++)
            rowind[i] = dis.readInt();
        value = new double[I = dis.readInt()];
        for(i = 0;i < I;i++)
            value[i] = dis.readDouble();
    }

    public Matrix(BufferedReader br) throws IOException {
        int row, col, maxrow = 0, maxcol = 0;
        String line;
        MatrixBuilder mb;
        Pattern splitter = Pattern.compile("\\s+");
        List<double[]> rows_cols_values = new ArrayList();
        String[] ss;
        while((line = br.readLine()) != null)
            if((ss = splitter.split(line.trim())).length == 3 && !ss[0].startsWith("#")) {
                rows_cols_values.add(new double[] { row = Integer.valueOf(ss[0]), col = Integer.valueOf(ss[1]), Double.valueOf(ss[2]) });
                if(maxrow < row)
                    maxrow = row;
                if(maxcol < col)
                    maxcol = col;
            }
        mb = new MatrixBuilder(maxrow+1, maxcol+1);
        for(double[] row_col_value : rows_cols_values)
            mb.set((int)row_col_value[0], (int)row_col_value[1], row_col_value[2]);
        mb.toMatrix(this);
    }

    public Matrix(double[][] m, boolean transpose) { // m = double[rows][columns]
        int i, I, j, J, n = 0, c = 0;
        vals = 0;
        if(transpose) {
            rows = m[0].length;
            cols = m.length;
            I = m[0].length;
            J = m.length;
            for(i = 0;i < I;i++)
                for(j = 0;j < J;j++)
                    if(m[j][i] != 0)
                        vals++;
        } else {
            rows = m.length;
            cols = m[0].length;
            I = m.length;
            J = m[0].length;
            for(i = 0;i < I;i++)
                for(j = 0;j < J;j++)
                    if(m[i][j] != 0)
                        vals++;
        }
        pointr = new int[cols + 1];
        rowind = new int[vals];
        value = new double[vals];
        pointr[0] = 0;
        if(transpose) {
            for(j = 0;j < J;j++)
                for(i = 0;i < I;i++) {
                    if(m[j][i] == 0)
                        continue;
                    while(j > c)
                        pointr[++c] = n;
                    rowind[n] = i;
                    value[n] = m[j][i];
                    n++;
                }
        } else {
            for(j = 0;j < J;j++)
                for(i = 0;i < I;i++) {
                    if(m[i][j] == 0)
                        continue;
                    while(j > c)
                        pointr[++c] = n;
                    rowind[n] = i;
                    value[n] = m[i][j];
                    n++;
                }
        }
        while(cols > c)
            pointr[++c] = n;
    }

    public Matrix toTranspose() {
        int i = 0, I, c, r, j;
        int[] _pointr, _rowind;
        double[] _value;
        Matrix m = new Matrix();
        m.rows = cols;
        m.cols = rows;
        m.vals = vals;
        _pointr = m.pointr = new int[rows + 1];
        _rowind = m.rowind = new int[vals];
        _value = m.value = new double[vals];
        while(i < vals)
            _pointr[rowind[i++]]++;
        _pointr[rows] = vals - _pointr[rows - 1];
        for(r = rows - 1;r > 0;r--)
            _pointr[r] = _pointr[r+1] - _pointr[r-1];
        _pointr[0] = 0;
        i = 0;
        for(c = 0;c < cols;c++) {
            I = pointr[c+1];
            while(i < I) {
                j = _pointr[(int)rowind[i]+1]++;
                _rowind[j] = c;
                _value[j] = value[i];
                i++;
            }
        }
        return m;
    }

    public double[][] toArray() {
        int j, p, pe;
        double[][] m = new double[rows][cols];
        for(j = 0;j < cols;j++) {
            pe = pointr[j+1];
            for(p = pointr[j];p < pe;p++)
                m[rowind[p]][j] = value[p];
        }
        return m;
    }

    public void write(DataOutputStream dos) throws IOException {
        int i, I;
        dos.writeInt(rows);
        dos.writeInt(cols);
        dos.writeInt(vals);
        dos.writeInt(I = pointr.length);
        for(i = 0;i < I;i++)
            dos.writeInt(pointr[i]);
        dos.writeInt(I = rowind.length);
        for(i = 0;i < I;i++)
            dos.writeInt(rowind[i]);
        dos.writeInt(I = value.length);
        for(i = 0;i < I;i++)
            dos.writeDouble(value[i]);
    }

    public void write(PrintWriter pw) throws IOException {
        int j, p, pe;
        for(j = 0;j < cols;j++) {
            pe = pointr[j+1];
            for(p = pointr[j];p < pe;p++)
                pw.printf("%d %d %s\n", rowind[p], j, value[p] == (int)value[p] ? Integer.toString((int)value[p]) : Double.toString(value[p]));
        }
    }

    public static void write(double[][] matrix_mn, PrintWriter pw) throws IOException {
        int m, M = matrix_mn.length, n, N = matrix_mn[0].length;
        for(m = 0;m < M;m++) {
            for(n = 0;n < N;n++) {
                if(n > 0)
                    pw.write(" ");
                pw.write(Double.toString(matrix_mn[m][n]));
            }
            pw.println();
        }
    }

    public static double[][] read(BufferedReader br) throws IOException {
        int n, N, m = 0;
        String line;
        Pattern splitter = Pattern.compile("\\s+");
        List<double[]> rows = new ArrayList();
        String[] ss;
        double[] row;
        double[][] matrix_mn;
        while((line = br.readLine()) != null)
            if((N = (ss = splitter.split(line.trim())).length) > 0 && !ss[0].startsWith("#")) {
                rows.add(row = new double[N]);
                for(n = 0;n < N;n++)
                    row[n] = Double.valueOf(ss[n]);
            }
        matrix_mn = new double[rows.size()][];
        for(double[] _row : rows)
            matrix_mn[m++] = _row;
        return matrix_mn;
    }

    public String toSummary() {
        StringBuilder sb = new StringBuilder();
        sb.append(rows);
        sb.append(" rows x ");
        sb.append(cols);
        sb.append(" columns, ");
        sb.append(vals);
        sb.append(" values, ");
        sb.append(pointr.length);
        sb.append(" pointr length, ");
        sb.append(rowind.length);
        sb.append(" rowind length, ");
        sb.append(value.length);
        sb.append(" value length, ");
        sb.append(String.format("%.2f", (double)vals * 100.0 / (double)(rows * cols)));
        sb.append("% density");
        return sb.toString();
    }

    public Matrix multiply(Matrix m) {
        int i, k, K, nonzerorows = 0, nonzerocols = 0, istart, length, j, J, jj, JJ, jbk, jcj;
        Matrix out = new Matrix();
        int[] counter = new int[rows], ia = m.pointr, ja = m.rowind, ib = pointr, jb = rowind, jc, ic, index;
        double[] a = m.value, b = value, c, temp;
        double[][] jcicc;
        for(i = 0;i < vals;i++)
            if(counter[k = rowind[i]] != 1) {
                nonzerorows++;
                counter[k] = 1;
            }
        for(i = 0;i < m.cols;i++)
            if(m.pointr[i] != m.pointr[i+1])
                nonzerocols++;
        out.rows = rows;
        out.cols = m.cols;
        out.vals = nonzerorows * nonzerocols;
        ic = out.pointr = new int[out.cols + 1];
        jcicc = new double[out.vals][3];
        index = new int[Math.max(Math.max(rows, cols), m.cols)];
        temp = new double[index.length];
        for(i = 0;i < m.cols;i++) {
            istart = -2;
            length = 0;
            JJ = ia[i+1];
            for(jj = ia[i];jj < JJ;jj++) {
                j = ja[jj];
                K = ib[j+1];
                for(k = ib[j];k < K;k++) {
                    jbk = jb[k];
                    if(index[jbk] == 0) {
                        index[jbk] = istart + 1;
                        istart = jbk;
                        length++;
                    }
                }
            }
            ic[i+1] = ic[i] + length;
            J = ic[i+1] - 1;
            for(j = ic[i];j <= J;j++) {
                jcicc[jj = J - (j - ic[i])][0] = istart;
                istart = index[istart] - 1;
                index[(int)jcicc[jj][0]] = 0;
            }
        }
        for(i = 0;i < m.cols;i++) {
            JJ = ia[i+1];
            for(jj = ia[i];jj < JJ;jj++) {
                j = ja[jj];
                K = ib[j+1];
                for(k = ib[j];k < K;k++)
                    temp[jb[k]] += a[jj] * b[k];
            }
            J = ic[i+1];
            for(j = ic[i];j < J;j++) {
                jcj = (int)jcicc[j][0];
                jcicc[j][1] = i - out.cols;
                jcicc[j][2] = temp[jcj];
                temp[jcj] = 0;
            }
        }
        Arrays.sort(jcicc, matrix_sorter);
        for(i = 0;i < out.vals && jcicc[i][1] < 0;i++);
        out.vals = i;
        jc = out.rowind = new int[out.vals];
        c = out.value = new double[out.vals];
        for(i = 0;i < out.vals;i++) {
            jc[i] = (int)jcicc[i][0];
            c[i] = jcicc[i][2];
        }
        return out;
    }

    public int[] identifyNonZeroColumns() {
        int i, n = 0;
        int[] nzc;
        for(i = 0;i < cols;i++)
            if(pointr[i] != pointr[i+1])
                n++;
        nzc = new int[n];
        n = 0;
        for(i = 0;i < cols;i++)
            if(pointr[i] != pointr[i+1])
                nzc[n++] = i;
        return nzc;
    }

    public Matrix getNonZeroColumns() {
        return getColumns(identifyNonZeroColumns());
    }

    public Matrix allocate() {
        Matrix m = new Matrix();
        m.cols = cols;
        m.rows = rows;
        m.vals = vals;
        m.value = new double[value.length];
        m.pointr = new int[pointr.length];
        m.rowind = new int[rowind.length];
        return m;
    }

    public Matrix[] holdOutFromEachColumn(double hold_out_percent) {
        int j, p, pe, t, T, r, lr, ap = 0, bp = 0;
        List<Integer> rs = new ArrayList(), rsa = new ArrayList(), rsb = new ArrayList();
        Matrix a = allocate(), b = allocate();
        for(j = 0;j < cols;j++) {
            rs.clear();
            rsa.clear();
            rsb.clear();
            pe = pointr[j+1];
            for(p = pointr[j];p < pe;p++) {
                r = rowind[p];
                T = (int)Math.ceil(value[p]);
                for(t = 0;t < T;t++)
                    rs.add(r);
            }
            Collections.shuffle(rs);
            T = (int)Math.ceil(hold_out_percent * (double)rs.size());
            t = 0;
            for(int _r : rs)
                if(t++ < T)
                    rsa.add(_r);
                else
                    rsb.add(_r);
            Collections.sort(rsa);
            Collections.sort(rsb);
            lr = -1;
            for(int _r : rsa)
                if(lr != _r) {
                    a.value[ap] = 1;
                    a.rowind[ap++] = lr = _r;
                } else
                    a.value[ap-1] += 1;
            a.pointr[j+1] = ap;
            lr = -1;
            for(int _r : rsb)
                if(lr != _r) {
                    b.value[bp] = 1;
                    b.rowind[bp++] = lr = _r;
                } else
                    b.value[bp-1] += 1;
            b.pointr[j+1] = bp;
        }
        a.vals = (a.value = Arrays.copyOfRange(a.value, 0, ap)).length;
        a.rowind = Arrays.copyOfRange(a.rowind, 0, ap);
        b.vals = (b.value = Arrays.copyOfRange(b.value, 0, bp)).length;
        b.rowind = Arrays.copyOfRange(b.rowind, 0, bp);
        return new Matrix[] { a, b };
    }

    public double[][] columnOuterProduct(double norm) {
        int a, b;
        double[][] m = new double[cols][cols];
        for(a = 0;a < cols;a++) {
            m[a][a] = columnInnerProduct(a, a, norm);
            for(b = a+1;b < cols;b++)
                m[a][b] = m[b][a] = columnInnerProduct(a, b, norm);
        }
        return m;
    }

    public double columnInnerProduct(int columna, int columnb, double norm) {
        boolean usenorm = norm != 0;
        int pa = pointr[columna], pb = pointr[columnb], pea = pointr[columna+1], peb = pointr[columnb+1], ra, rb;
        double innerproduct = 0, anorm = 0, bnorm = 0;
        while(pa < pea && pb < peb)
            if((ra = rowind[pa]) == (rb = rowind[pb])) {
                innerproduct += value[pa] * value[pb];
                if(usenorm) {
                    anorm += Math.pow(value[pa], norm);
                    bnorm += Math.pow(value[pb], norm);
                }
                pa++;
                pb++;
            } else if(ra < rb)
                pa++;
            else
                pb++;
        if(anorm != 0 && bnorm != 0)
            return innerproduct / Math.pow(anorm, 1.0 / norm) / Math.pow(bnorm, 1.0 / norm);
        return innerproduct;
    }

    public double columnInnerProductSum(int[] columns, double norm) {
        int i, j, N = columns.length, p, pe;
        double innerproductsum = 0;
        for(i = 0;i < N;i++) {
            innerproductsum += columnInnerProduct(i, i, norm);
            for(j = i+1;j < N;j++)
                innerproductsum += 2.0 * columnInnerProduct(i, j, norm);
        }
        return innerproductsum;
    }

    public double jensenShannonDivergence(int columna, int columnb) {
        int pa = pointr[columna], pb = pointr[columnb], pea = pointr[columna+1], peb = pointr[columnb+1], ra, rb;
        double pw, qw, jsd = 0;
        while(pa < pea && pb < peb)
            if((ra = rowind[pa]) == (rb = rowind[pb])) {
                pw = value[pa++];
                qw = value[pb++];
                jsd += pw * Math.log(pw) + qw * Math.log(qw) - (pw + qw) * Math.log((pw + qw) / 2.0) - pw - qw;
            } else if(ra < rb)
                pa++;
            else
                pb++;
        return 1.0 + jsd / 2.0;
    }

    public Matrix applyTfIdf() {
        int i, j, p, pe;
        double[] lens = new double[(int)cols], idf = new double[rows];
        for(j = 0;j < cols;j++) {
            pe = pointr[j+1];
            for(p = pointr[j];p < pe;p++)
                if(value[p] != 0) {
                    idf[rowind[p]] += 1.0;
                    lens[j] += value[p];
                }
        }
        for(i = 0;i < rows;i++)
            if(idf[i] > 0)
                idf[i] = Math.log((double)cols / idf[i]);
        for(j = 0;j < cols;j++)
            if(lens[j] != 0) {
                pe = pointr[j+1];
                for(p = pointr[j];p < pe;p++)
                    value[p] = value[p] / lens[j] * idf[rowind[p]];
            }
        return this;
    }

    public static int[] range(int first, int increment, int steps) {
        int i;
        int[] range = new int[steps];
        for(i = 0;i < steps;i++)
            range[i] = first + increment * i;
        return range;
    }

    public Matrix getColumns(int[] columns) {
        int i = 0, k = 0, p, pe;
        Matrix out = new Matrix();
        int[] ic = out.pointr = new int[columns.length + 1], jc = out.rowind = new int[vals];
        double[] c = out.value = new double[vals];
        out.rows = rows;
        out.cols = columns.length;
        for(int j : columns) {
            pe = pointr[j+1];
            for(p = pointr[j];p < pe;p++) {
                jc[k] = rowind[p];
                c[k] = value[p];
                k++;
            }
            ic[++i] = k;
        }
        out.vals = k;
        out.rowind = Arrays.copyOf(jc, k);
        out.value = Arrays.copyOf(c, k);
        return out;
    }

    public Matrix toCopy() {
        Matrix out = new Matrix();
        out.rows = rows;
        out.cols = cols;
        out.vals = vals;
        out.rowind = Arrays.copyOf(rowind, rowind.length);
        out.pointr = Arrays.copyOf(pointr, pointr.length);
        out.value = Arrays.copyOf(value, value.length);
        return out;
    }

    public String toString() {
        int j, p, pe;
        StringBuilder sb = new StringBuilder();
        for(j = 0;j < cols;j++) {
            pe = pointr[j+1];
            for(p = pointr[j];p < pe;p++) {
                sb.append("(");
                sb.append(rowind[p]);
                sb.append(",");
                sb.append(j);
                sb.append(") -> ");
                sb.append(String.format("% .20f\t", value[p]));
                sb.append("\n");
            }
        }
        return sb.toString();
    }

    public Matrix normalizeColumns(double norm) {
        int j, p, pe;
        double normsum;
        if(norm != 0)
            for(j = 0;j < cols;j++) {
                pe = pointr[j+1];
                normsum = 0;
                for(p = pointr[j];p < pe;p++)
                    normsum += Math.pow(value[p], norm);
                if(normsum != 0) {
                    normsum = Math.pow(normsum, 1.0 / norm);
                    for(p = pointr[j];p < pe;p++)
                        value[p] /= normsum;
                }
            }
        return this;
    }

    public static double[][] getLowerTriangular(double[][] m) {
        int i, I = m.length, j;
        double[][] t = new double[I][I];
        for(i = 0;i < I;i++)
            for(j = i;j < I;j++)
                t[j][i] = m[j][i];
        return t;
    }

    public static Matrix combineColumns(List<Matrix> matrices) {
        int j = 0, k, l = 0, p, pe = 0;
        Matrix result = new Matrix();
        for(Matrix m : matrices) {
            result.cols += m.cols;
            if(result.rows < m.rows)
                result.rows = m.rows;
            result.vals += m.vals;
        }
        result.rowind = new int[result.vals];
        result.value = new double[result.vals];
        result.pointr = new int[result.cols+1];
        for(Matrix m : matrices)
            for(k = 0;k < m.cols;k++) {
                pe = m.pointr[k+1];
                for(p = m.pointr[k];p < pe;p++) {
                    result.rowind[l] = m.rowind[p];
                    result.value[l] = m.value[p];
                    l++;
                }
                result.pointr[++j] = l;
            }
        return result;
    }

    public Matrix selectRowsByNIdf(double min_nidf, double max_nidf) {
        int p, pe, newvals = 0, j, J = cols, a = 0, b;
        double normn = ((double)J + 0.5), normd = Math.log((double)J + 1.0), nidf;
        Matrix out = new Matrix();
        double[] idf = new double[rows];
        pe = vals;
        for(p = 0;p < pe;p++)
            if(value[p] != 0)
                idf[rowind[p]] += 1.0;
        pe = rows;
        for(p = 0;p < pe;p++)
            idf[p] = (idf[p] > 0 && (nidf = Math.log(normn / idf[p]) / normd) >= min_nidf && nidf <= max_nidf) ? 1 : 0;
        pe = vals;
        for(p = 0;p < pe;p++)
            if(idf[rowind[p]] > 0)
                newvals++;
        out.cols = J;
        out.rows = rows;
        out.vals = newvals;
        out.pointr = new int[J+1];
        out.value = new double[newvals];
        out.rowind = new int[newvals];
        for(j = 0;j < J;j++) {
            b = 0;
            pe = pointr[j+1];
            for(p = pointr[j];p < pe;p++)
                if(idf[rowind[p]] > 0) {
                    out.value[a] = value[p];
                    out.rowind[a] = rowind[p];
                    a++;
                    b++;
                }
            out.pointr[j+1] = out.pointr[j] + b;
        }
        return out;
    }

    public static int[] toArray(Collection<Integer> is) {
        int k = 0;
        int[] _is = new int[is.size()];
        for(int i : is)
            _is[k++] = i;
        return _is;
    }

    public static double[] toArray(Collection<Double> ds) {
        int k = 0;
        double[] _ds = new double[ds.size()];
        for(double d : ds)
            _ds[k++] = d;
        return _ds;
    }

    public static double[] asDoubles(int[] is) {
        int k = 0;
        double[] ds = new double[is.length];
        for(int i : is)
            ds[k++] = i;
        return ds;
    }

    public static List<Integer> toList(int[] is) {
        List<Integer> _is = new ArrayList();
        for(int i : is)
            _is.add(i);
        return _is;
    }

    public static List<Double> toList(double[] ds) {
        List<Double> _ds = new ArrayList();
        for(double d : ds)
            _ds.add(d);
        return _ds;
    }

    public double[] toColumnMajor() {
        int j, p, pe, jrows;
        double[] m = new double[cols * rows];
        for(j = 0;j < cols;j++) {
            pe = pointr[j+1];
            jrows = j * rows;
            for(p = pointr[j];p < pe;p++)
                m[jrows + rowind[p]] = value[p];
        }
        return m;
    }

    public static double[] toColumnMajor(double[][] a) {
        int i, j, jrows, rows = a.length, cols = a[0].length;
        double[] m = new double[rows * cols];
        for(j = 0;j < cols;j++) {
            jrows = j * rows;
            for(i = 0;i < rows;i++)
                m[jrows + i] = a[i][j];
        }
        return m;
    }

    public static double[] getLowerTriangularColumnMajor(double[][] a) {
        int i, j, jrows, rows = a.length, cols = a[0].length;
        double[] m = new double[rows * cols];
        for(j = 0;j < cols;j++) {
            jrows = j * rows;
            for(i = j;i < rows;i++)
                m[jrows + i] = a[i][j];
        }
        return m;
    }

    public static void main(String[] args) throws Exception {
        BufferedReader br = new BufferedReader(new FileReader("/tmp/w.oct"));
        Matrix a = new Matrix(br);
        br.close();
        System.out.println(a.toSummary());
        System.out.println(a);
        PrintWriter pw = new PrintWriter(new FileWriter("/tmp/densemat"));
        Matrix.write(a.toArray(), pw);
        pw.close();
        br = new BufferedReader(new FileReader("/tmp/densemat"));
        double[][] b = Matrix.read(br);
        br.close();
        System.out.println();
        System.out.println(new Matrix(b));

//        BufferedReader br = new BufferedReader(new FileReader("/tmp/20ngtrain.mat"));
//        Matrix a = new Matrix(br);
//        br.close();
//        br = new BufferedReader(new FileReader("/tmp/20ngtest.mat"));
//        Matrix b = new Matrix(br);
//        br.close();
//        System.out.println(a.toSummary());
//        System.out.println(b.toSummary());
//        br = new BufferedReader(new FileReader("/tmp/20ngdict"));
//        int stemid = 0;
//        String line;
//        Map<Integer, String> stem_stemid = new HashMap();
//        while((line = br.readLine()) != null)
//            stem_stemid.put(stemid++, line);
//        br.close();
//        int k, K = 80, S = 200;
//        // 8.03 6.97
//        // 8.10 7.09
//        CVHDP tm = new CVHDP(a, b, K, 4, S * K, true, S);
////        CGLDA tm = new CGLDA(a, b, K, CGLDA.DEFAULT_ALPHASUM / (double)K, CGLDA.DEFAULT_BETA, 1, 1, S);
//        System.out.println(tm.avglls);
////        System.out.println("expectation: " + tm.real_expectation_time_elapsed);
////        System.out.println("maximization: " + tm.real_maximization_time_elapsed);
////        System.out.println("bookkeeping: " + tm.real_bookkeeping_time_elapsed);
//        for(k = 0;k < K;k++) {
//            int dk = tm.dk_k[k];
//            System.out.println(tm.E_n_k[dk]);
////            System.out.println(tm.n_k[dk]);
//            SortedPairs pairs = new SortedPairs(tm.phi_kw[dk].length);
//            int w = 0;
//            for(double phi : tm.phi_kw[dk])
//                pairs.add(new double[] { phi, w++ });
//            int i = 0;
//            for(double[] pair : pairs.sortDescending())
//                if(i++ == 20)
//                    break;
//                else
//                    System.out.print(stem_stemid.get((int)pair[1]) + ", ");
//            System.out.println();
//            System.out.println();
//        }
    }
}
