package com.numix.calculator; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.ejml.simple.SimpleEVD; import org.ejml.simple.SimpleMatrix; import org.ejml.simple.SimpleSVD; import org.javia.arity.SyntaxException; import com.numix.calculator.BaseModule.Mode; import com.numix.calculator.view.AdvancedDisplay; public class MatrixModule { Logic mLogic; MatrixModule(Logic logic) { mLogic = logic; } static SimpleMatrix addScalar(SimpleMatrix mat, double scalar) { SimpleMatrix temp = mat.copy(); int M = mat.numRows(); int N = mat.numCols(); for(int i = 0; i < M; i++) { for(int j = 0; j < N; j++) temp.set(i, j, mat.get(i, j) + scalar); } return temp; } double gatherScalar(String text) throws SyntaxException { if(!Character.isDigit(text.charAt(1))) throw new SyntaxException(); return Double.parseDouble(text.substring(1)); } private String calculate(String input) throws SyntaxException { // I never realized negative numbers could be so difficult. input = input.replace(Logic.MINUS, '-'); // All remaining instances of U+2212 will be on negative numbers. // They will be counted as whole tokens. // Instantiate matrices first. Matcher m = Pattern.compile("\\[\\[.+?\\]\\]").matcher(input); while(m.find()) { SimpleMatrix temp = parseMatrix(m.group()); input = input.replace(m.group(), printMatrix(temp)); } // Get percentage. input = input.replaceAll("(?<=\\d)%(?!\\d)", "\u00d70.01"); // Might as well get factorial too. m = Pattern.compile("(?<!\\.)([0-9]+)\\!").matcher(input); while(m.find()) { int temp = Integer.parseInt(m.group(1)); input = input.replace(m.group(), fact(temp)); } int open = 0; for(int i = 0; i < input.length(); i++) { if(input.charAt(i) == '(') open++; else if(input.charAt(i) == ')') open--; } if(open == 1) input = input.concat(")"); // Auto-balance if possible else if(open != 0) throw new SyntaxException(); // Unbalanced Pattern pat = Pattern.compile("\\(([^\\(\\)]+?)\\)"); while(input.contains("(")) { Matcher mch = pat.matcher(input); while(mch.find()) { input = input.replace(mch.group(), calculate(mch.group(1))); } } // Process transpositions. Matcher match = Pattern.compile("(\\[.+\\])\\^T").matcher(input); while(match.find()) { SimpleMatrix temp = parseMatrix(match.group(1)).transpose(); input = input.replace(match.group(), printMatrix(temp)); } // Process inverses match = Pattern.compile("(\\[.+\\])\uFEFF\\^-1").matcher(input); while(match.find()) { SimpleMatrix temp = parseMatrix(match.group(1)).pseudoInverse(); input = input.replace(match.group(), printMatrix(temp)); } // Handle functions. match = Pattern.compile("(\u221a|log|ln|asin|acos|atan|sind|cosd|tand|asind|acosd|atand|sin|cos|tan|det)(\u2212?\\d+(?:\\.\\d+)?|\\[\\[.+\\]\\])") .matcher(input); while(match.find()) { String res = applyFunc(match.group(1), match.group(2)); input = input.replace(match.group(), res); } // Functions might generate NaN. Return error if so. if(input.contains("NaN")) return mLogic.mErrorString; // Substitute e // input = input.replaceAll("(?<!\\d)e", "2.7182818284590452353"); input = input.replaceAll("(?<!\\d)(e)(?!\\d)", "2.7182818284590452353"); // Sub pi input = input.replace("\u03c0", "3.1415926535897932384626"); // Split into seperate arrays of operators and operands. // Operator 0 applies to operands 0 and 1, and so on String[] parts = input.split("\u00d7|\\+|(?<=\\d|\\])(?<=\\d|\\])-|\u00f7|\\^"); char[] ops = opSplit(input); // This never changes, so no need to keep calling it int N = ops.length; // If there are no ops, there's nothing to do // Since we've already made substitutions and parsed parentheses if(N == 0) return input; // Fill in the pieces. // Store everything as Object, and cast out later Object[] pieces = new Object[parts.length]; for(int i = 0; i < parts.length; i++) { if(parts[i].startsWith("[[")) pieces[i] = parseMatrix(parts[i]); else pieces[i] = Double.parseDouble(parts[i].replace('\u2212', '-')); } // Work on the operators in order of their precedence. // Go from right to left to make ^ chains right-associative. for(int i = N - 1; i >= 0; i--) { int[] landr = null; if(ops[i] == '^') { landr = lookAfield(pieces, i); int l = landr[0]; int r = landr[1]; Object res = applyPow(pieces[l], pieces[r]); pieces[l] = res;// This is arbitrary (I think) pieces[r] = null;// I could also have put the result in right // and null in left } } // Yes, I'm doing a complete loop over all operators several times. // Realistically, there will only be a few of them. // For the purposes of this app, it's no big deal. for(int i = 0; i < N; i++) { int[] landr = null; if(ops[i] == Logic.MUL || ops[i] == Logic.DIV) { landr = lookAfield(pieces, i); int l = landr[0]; int r = landr[1]; Object res = null; if(ops[i] == Logic.MUL) res = applyMult(pieces[l], pieces[r]); else res = applyDiv(pieces[l], pieces[r]); // else res = applyMod(pieces[l], pieces[r]); pieces[l] = res; pieces[r] = null; } } for(int i = 0; i < N; i++) { int[] landr = null; if(ops[i] == '+' || ops[i] == '-') { landr = lookAfield(pieces, i); int l = landr[0]; int r = landr[1]; Object res = null; if(ops[i] == '+') res = applyPlus(pieces[l], pieces[r]); else res = applySub(pieces[l], pieces[r]); pieces[l] = res; pieces[r] = null; } } for(Object piece : pieces) if(piece != null) { if(piece instanceof Double) return numToString((Double) piece); else if(piece instanceof SimpleMatrix) return printMatrix((SimpleMatrix) piece); else throw new SyntaxException(); // Neither matrix nor double // should never happen } throw new RuntimeException(); // Getting here should be impossible }// end main String evaluateMatrices(AdvancedDisplay display) throws SyntaxException { String text = display.getText(); text = mLogic.convertToDecimal(text); String result = ""; try { result = calculate(mLogic.localize(text)); result = result.replace('-', Logic.MINUS);// Back to fancy minus } catch(Exception e) { result = mLogic.mErrorString; } result = mLogic.relocalize(result); return mLogic.mBaseModule.updateTextToNewMode(result, Mode.DECIMAL, mLogic.mBaseModule.getMode()); } private static char[] opSplit(String str) { StringBuilder buffer = new StringBuilder(); char c, prev; prev = str.charAt(0); for(int i = 0; i < str.length(); i++) { c = str.charAt(i); if(c == '^' || c == Logic.MUL || c == Logic.DIV || c == '+') buffer.append(c); else if(c == '-' && (Character.isDigit(prev) || prev == ']') && (prev != 'e')) buffer.append(c); prev = c; } return buffer.toString().toCharArray(); } // Look for the nearest valid operand private static int[] lookAfield(Object[] field, int orig) { int left, right; // Start with the original position (of the operator) // Left operand is at the same index as its operator // But if it's null, look farther left int pos = orig; while(field[pos] == null) // pos--; left = pos; // Right operand is one greater than the operator index pos = orig + 1; while(field[pos] == null) // Look to the right if null pos++; right = pos;// Found it return new int[] { left, right };// Return the indices to allow later // sub-in of null } private static String fact(int n) { long m = n; for(int i = n - 1; i > 1; i--) m *= i; return Long.toString(m); } // The following have a lot of repeated boilerplate code. // Condensing it down would require language features/properties // that Java does not have. // In short, Java is not F#. private String applyFunc(String func, String arg) throws SyntaxException { arg = arg.replace(Logic.MINUS, '-'); double DEG = Math.PI / 180.0; if(func.equals("\u221a"))// sqrt { if(arg.startsWith("[[")) { SimpleMatrix matrix = parseMatrix(arg); int m = matrix.numRows(); int n = matrix.numCols(); if(m != n) throw new SyntaxException(); SimpleEVD<SimpleMatrix> decomp = new SimpleEVD<SimpleMatrix>(matrix.getMatrix()); double[] evals = new double[m]; for(int i1 = 0; i1 < m; i1++) { evals[i1] = Math.sqrt(decomp.getEigenvalue(i1).getMagnitude()); } SimpleMatrix D = SimpleMatrix.diag(evals); SimpleMatrix V = new SimpleMatrix(m, n); for(int k = 0; k < m; k++) { SimpleMatrix col = decomp.getEigenVector(k); for(int l = 0; l < n; l++) { V.set(k, l, col.get(l, 0)); } } SimpleMatrix temp = V.mult(D); temp = temp.mult(V.invert()); return printMatrix(temp); } else return numToString(Math.sqrt(Double.parseDouble(arg))); } else if(func.equals("sin")) { if(arg.startsWith("[[")) { SimpleMatrix m = parseMatrix(arg); for(int i = 0; i < m.numRows(); i++) for(int j = 0; j < m.numCols(); j++) m.set(i, j, Math.sin(m.get(i, j))); return printMatrix(m); } else return numToString(Math.sin(Double.parseDouble(arg))); } else if(func.equals("cos")) { if(arg.startsWith("[[")) { SimpleMatrix m = parseMatrix(arg); for(int i = 0; i < m.numRows(); i++) for(int j = 0; j < m.numCols(); j++) m.set(i, j, Math.cos(m.get(i, j))); return printMatrix(m); } else return numToString(Math.cos(Double.parseDouble(arg))); } else if(func.equals("tan")) { if(arg.startsWith("[[")) { SimpleMatrix m = parseMatrix(arg); for(int i = 0; i < m.numRows(); i++) for(int j = 0; j < m.numCols(); j++) m.set(i, j, Math.tan(m.get(i, j))); return printMatrix(m); } else return numToString(Math.tan(Double.parseDouble(arg))); } else if(func.equals("sind")) { if(arg.startsWith("[[")) { SimpleMatrix m = parseMatrix(arg); for(int i = 0; i < m.numRows(); i++) for(int j = 0; j < m.numCols(); j++) m.set(i, j, Math.sin(m.get(i, j) * DEG)); return printMatrix(m); } else return numToString(Math.sin(Double.parseDouble(arg) * DEG)); } else if(func.equals("cosd")) { if(arg.startsWith("[[")) { SimpleMatrix m = parseMatrix(arg); for(int i = 0; i < m.numRows(); i++) for(int j = 0; j < m.numCols(); j++) m.set(i, j, Math.cos(m.get(i, j) * DEG)); return printMatrix(m); } else return numToString(Math.cos(Double.parseDouble(arg) * DEG)); } else if(func.equals("tand")) { if(arg.startsWith("[[")) { SimpleMatrix m = parseMatrix(arg); for(int i = 0; i < m.numRows(); i++) for(int j = 0; j < m.numCols(); j++) m.set(i, j, Math.tan(m.get(i, j) * DEG)); return printMatrix(m); } else return numToString(Math.tan(Double.parseDouble(arg) * DEG)); } else if(func.equals("asind")) { if(arg.startsWith("[[")) { SimpleMatrix m = parseMatrix(arg); for(int i = 0; i < m.numRows(); i++) for(int j = 0; j < m.numCols(); j++) m.set(i, j, Math.asin(m.get(i, j) / DEG)); return printMatrix(m); } else return numToString(Math.asin(Double.parseDouble(arg)) / DEG); } else if(func.equals("acosd")) { if(arg.startsWith("[[")) { SimpleMatrix m = parseMatrix(arg); for(int i = 0; i < m.numRows(); i++) for(int j = 0; j < m.numCols(); j++) m.set(i, j, Math.acos(m.get(i, j)) / DEG); return printMatrix(m); } else return numToString(Math.acos(Double.parseDouble(arg)) / DEG); } else if(func.equals("atand")) { if(arg.startsWith("[[")) { SimpleMatrix m = parseMatrix(arg); for(int i = 0; i < m.numRows(); i++) for(int j = 0; j < m.numCols(); j++) m.set(i, j, Math.atan(m.get(i, j)) / DEG); return printMatrix(m); } else return numToString(Math.atan(Double.parseDouble(arg)) / DEG); } else if(func.equals("log")) { if(arg.startsWith("[[")) { SimpleMatrix m = parseMatrix(arg); for(int i = 0; i < m.numRows(); i++) for(int j = 0; j < m.numCols(); j++) m.set(i, j, Math.log10(m.get(i, j))); return printMatrix(m); } else return numToString(Math.log10(Double.parseDouble(arg))); } else if(func.equals("ln")) { if(arg.startsWith("[[")) { SimpleMatrix m = parseMatrix(arg); for(int i = 0; i < m.numRows(); i++) for(int j = 0; j < m.numCols(); j++) m.set(i, j, Math.log(m.get(i, j))); return printMatrix(m); } else return numToString(Math.log(Double.parseDouble(arg))); } else if(func.equals("asin")) { if(arg.startsWith("[[")) { SimpleMatrix m = parseMatrix(arg); for(int i = 0; i < m.numRows(); i++) for(int j = 0; j < m.numCols(); j++) m.set(i, j, Math.asin(m.get(i, j))); return printMatrix(m); } else return numToString(Math.asin(Double.parseDouble(arg))); } else if(func.equals("acos")) { if(arg.startsWith("[[")) { SimpleMatrix m = parseMatrix(arg); for(int i = 0; i < m.numRows(); i++) for(int j = 0; j < m.numCols(); j++) m.set(i, j, Math.acos(m.get(i, j))); return printMatrix(m); } else return numToString(Math.acos(Double.parseDouble(arg))); } else if(func.equals("atan")) { if(arg.startsWith("[[")) { SimpleMatrix m = parseMatrix(arg); for(int i = 0; i < m.numRows(); i++) for(int j = 0; j < m.numCols(); j++) m.set(i, j, Math.atan(m.get(i, j))); return printMatrix(m); } else return numToString(Math.atan(Double.parseDouble(arg))); } else if(func.equals("det")) { if(arg.startsWith("[[")) { SimpleMatrix m = parseMatrix(arg); if(m.numCols() != m.numRows()) throw new SyntaxException(); double d = m.determinant(); return numToString(d); } else return arg; // Determinant of a scalar is equivalent to det. of // 1x1 matrix, which is the matrix' one element } else throw new SyntaxException(); } private Object applyPow(Object l, Object r) throws SyntaxException { if(l instanceof SimpleMatrix && r instanceof SimpleMatrix) throw new SyntaxException(); else if(l instanceof SimpleMatrix) { SimpleMatrix a = (SimpleMatrix) l; int m = a.numRows(); int n = a.numCols(); if(m != n) throw new SyntaxException(); double b = (Double) r; if(b > Math.floor(b)) { SimpleSVD<SimpleMatrix> decomp = new SimpleSVD<SimpleMatrix>(a.getMatrix(), false); SimpleMatrix S = decomp.getW(); for(int i1 = 0; i1 < m; i1++) { for(int j = 0; j < n; j++) { double arg = S.get(i1, j); S.set(i1, j, Math.pow(arg, b)); } } SimpleMatrix matrix = decomp.getU().mult(S); matrix = matrix.mult(decomp.getV().transpose()); return matrix; } else { long equiv = Math.round(b); for(long e = 1; e < equiv; e++) a = a.mult(a); return a; } } else if(r instanceof SimpleMatrix) { SimpleMatrix a = (SimpleMatrix) r; int m = a.numRows(); int n = a.numCols(); if(m != n) throw new SyntaxException(); double b = (Double) l; if(b > Math.floor(b)) { SimpleSVD<SimpleMatrix> decomp = new SimpleSVD<SimpleMatrix>(a.getMatrix(), false); SimpleMatrix S = decomp.getW(); for(int i1 = 0; i1 < m; i1++) { for(int j = 0; j < n; j++) { double arg = S.get(i1, j); S.set(i1, j, Math.pow(arg, b)); } } SimpleMatrix matrix = decomp.getU().mult(S); matrix = matrix.mult(decomp.getV().transpose()); return matrix; } else { long equiv = Math.round(b); for(long e = 1; e < equiv; e++) a = a.mult(a); return a; } } else { double a = (Double) l; double b = (Double) r; return Math.pow(a, b); } } private Object applyMult(Object l, Object r) throws SyntaxException { if(l instanceof SimpleMatrix && r instanceof SimpleMatrix) { SimpleMatrix a = (SimpleMatrix) l; SimpleMatrix b = (SimpleMatrix) r; return a.mult(b); } else if(l instanceof SimpleMatrix) { SimpleMatrix a = (SimpleMatrix) l; double b = (Double) r; return a.scale(b); } else if(r instanceof SimpleMatrix) { SimpleMatrix a = (SimpleMatrix) r; double b = (Double) l; return a.scale(b); } else { double a = (Double) l; double b = (Double) r; return a * b; } } private Object applyDiv(Object l, Object r) throws SyntaxException { if(l instanceof SimpleMatrix && r instanceof SimpleMatrix) { SimpleMatrix a = (SimpleMatrix) l; SimpleMatrix b = (SimpleMatrix) r; return a.mult(b.pseudoInverse()); } else if(l instanceof SimpleMatrix) { SimpleMatrix a = (SimpleMatrix) l; double b = (Double) r; return a.scale(1.0 / b); } else if(r instanceof SimpleMatrix) { SimpleMatrix a = (SimpleMatrix) r; double b = (Double) l; return a.pseudoInverse().scale(b); } else { double a = (Double) l; double b = (Double) r; return a / b; } } private Object applyPlus(Object l, Object r) throws SyntaxException { if(l instanceof SimpleMatrix && r instanceof SimpleMatrix) { SimpleMatrix a = (SimpleMatrix) l; SimpleMatrix b = (SimpleMatrix) r; return a.plus(b); } else if(l instanceof SimpleMatrix) { SimpleMatrix a = (SimpleMatrix) l; double b = (Double) r; return addScalar(a, b); } else if(r instanceof SimpleMatrix) { SimpleMatrix a = (SimpleMatrix) r; double b = (Double) l; return addScalar(a, b); } else { double a = (Double) l; double b = (Double) r; return a + b; } } private Object applySub(Object l, Object r) throws SyntaxException { if(l instanceof SimpleMatrix && r instanceof SimpleMatrix) { SimpleMatrix a = (SimpleMatrix) l; SimpleMatrix b = (SimpleMatrix) r; return a.minus(b); } else if(l instanceof SimpleMatrix) { SimpleMatrix a = (SimpleMatrix) l; double b = (Double) r; return addScalar(a, -b); } else if(r instanceof SimpleMatrix) { SimpleMatrix a = (SimpleMatrix) r; double b = (Double) l; return addScalar(a, -b); } else { double a = (Double) l; double b = (Double) r; return a - b; } } // private Object applyMod(Object object, Object object2) throws // SyntaxException { // if(object instanceof Double && object2 instanceof Double) { // double arg1 = (Double) object; // double arg2 = (Double) object2; // return arg1 % arg2; // } // else throw new SyntaxException(); // } private SimpleMatrix parseMatrix(String text) throws SyntaxException { // Count rows & cols String interior = text.substring(2, text.length() - 2); String[] rows = interior.split("\\]\\["); SimpleMatrix temp = new SimpleMatrix(rows.length, rows[0].split(",").length); for(int i = 0; i < rows.length; i++) { String[] cols = rows[i].split(","); if(cols.length == 0) throw new SyntaxException(); for(int j = 0; j < cols.length; j++) { if(cols[j].isEmpty()) throw new SyntaxException(); temp.set(i, j, Double.parseDouble(calculate(cols[j]))); } } return temp; } private static String numToString(double arg) { // Cut off very small arguments if(Math.abs(arg) < 1.0E-10) return "0"; String temp = Double.toString(arg).replace('E', 'e'); if(temp.endsWith(".0")) temp = temp.substring(0, temp.length() - 2); return temp; } private static String printMatrix(SimpleMatrix mat) { StringBuilder buffer = new StringBuilder("["); int m = mat.numRows(); int n = mat.numCols(); for(int i = 0; i < m; i++) { buffer.append('['); for(int j = 0; j < n; j++) { buffer.append(numToString(mat.get(i, j))); if(j != n - 1) buffer.append(','); } buffer.append(']'); } buffer.append(']'); return buffer.toString(); } }