diff --git a/.idea/misc.xml b/.idea/misc.xml
index a165cb3..cbb200f 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -1,6 +1,6 @@
-
+
\ No newline at end of file
diff --git a/.idea/project.iml b/.idea/project.iml
index 47baa8c..d34aa22 100644
--- a/.idea/project.iml
+++ b/.idea/project.iml
@@ -6,5 +6,14 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/in.txt b/in.txt
new file mode 100644
index 0000000..090b2e8
--- /dev/null
+++ b/in.txt
@@ -0,0 +1,5 @@
+4 4
+0 0 2 0
+0 1 5 1
+0 0 6 0
+0 0 12 0
\ No newline at end of file
diff --git a/out.txt b/out.txt
new file mode 100644
index 0000000..af5f51f
--- /dev/null
+++ b/out.txt
@@ -0,0 +1 @@
+Infinite solutions
\ No newline at end of file
diff --git a/src/solver/AugmentedMatrix.java b/src/solver/AugmentedMatrix.java
new file mode 100644
index 0000000..c5477d9
--- /dev/null
+++ b/src/solver/AugmentedMatrix.java
@@ -0,0 +1,152 @@
+package solver;
+
+import java.lang.reflect.Array;
+import java.text.DecimalFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.InputMismatchException;
+import java.util.Scanner;
+
+public class AugmentedMatrix {
+ private Row[] matrix;
+ private String decimalPattern = "#.####";
+
+ // We don't always need swapHistory, so method which uses this field must check whether it is initialized or not.
+ private ArrayList swapHistory;
+
+
+ AugmentedMatrix(double[][] matrix) {
+ setMatrix(matrix);
+ }
+
+ AugmentedMatrix() {}
+
+ public void readMatrix(Scanner scanner) throws InputMismatchException {
+ int n = scanner.nextInt();
+ int m = scanner.nextInt();
+ matrix = new Row[n];
+ for (int i = 0; i < n; i++) {
+ matrix[i] = new Row(m);
+ for (int j = 0; j < m; j++) {
+ this.set(i, j, scanner.nextDouble());
+ }
+ }
+ }
+
+ public void set(int i, int j, double value) {
+ matrix[i].set(j, value);
+ }
+
+ public double get(int i, int j) {
+ return matrix[i].get(j);
+ }
+
+
+ public String fancyPrint(String decimalPattern) {
+ if(decimalPattern == null) {
+ decimalPattern = this.decimalPattern;
+ }
+ StringBuilder sb = new StringBuilder();
+ double[] row;
+ for (int i = 0; i < matrix.length; i++) {
+ row = matrix[i].getRow();
+ sb.append("[");
+ for(int j =0; j < row.length; j++) {
+ sb.append(new DecimalFormat(decimalPattern).format(row[j]));
+ sb.append(", ");
+ }
+ sb.delete(sb.length()-2, sb.length());
+ sb.append("]");
+ sb.append("\n");
+ }
+ return sb.toString();
+ }
+
+ @Override
+ public String toString() {
+ return fancyPrint(null);
+ }
+
+ public void swapRows(int firstRowIndex, int secondRowIndex) {
+ Row temp = matrix[firstRowIndex];
+ matrix[firstRowIndex] = matrix[secondRowIndex];
+ matrix[secondRowIndex] = temp;
+ }
+
+ private void addSwapHistoryEntry(int prevIndex, int nextIndex) {
+ // Initialization check
+ if(swapHistory==null) {
+ swapHistory = new ArrayList<>();
+ }
+ swapHistory.add(new SwapInfo(prevIndex, nextIndex));
+ }
+
+ public void clearSwapHistory() {
+ swapHistory.clear();
+ }
+
+ public void swapColumns(int firstColumnIndex, int secondColumnIndex) {
+ swapColumns(firstColumnIndex, secondColumnIndex, true);
+ }
+
+ public void swapColumns(int firstColumnIndex, int secondColumnIndex, boolean writeLog) {
+ double coeff1, coeff2;
+ for(Row row : matrix) {
+ coeff1 = row.get(firstColumnIndex);
+ coeff2 = row.get(secondColumnIndex);
+ row.set(firstColumnIndex, coeff2);
+ row.set(secondColumnIndex, coeff1);
+ }
+ if(writeLog) {
+ addSwapHistoryEntry(firstColumnIndex, secondColumnIndex);
+ }
+ }
+
+ public ArrayList getSwapHistory() {
+ return swapHistory;
+ }
+
+ public double[][] getMatrixCopy() {
+ double[][] copy = new double[matrix.length][];
+ for(int i = 0; i < matrix.length; i++) {
+ copy[i] = matrix[i].getRow();
+ }
+ return copy;
+ }
+
+ public void setMatrix(double[][] matrix) {
+ this.matrix = new Row[matrix.length];
+ for(int i = 0; i < matrix.length; i++) {
+ this.matrix[i] = new Row(matrix[i]);
+ }
+ }
+
+ public Row getRow(int index) {
+ return this.matrix[index];
+ }
+
+ public Row getColumnCopy(int index) {
+ int rows = size()[0]; // Number of rows
+ Row col = new Row(rows);
+ for(int i = 0; i < rows; i++) {
+ col.set(i, matrix[i].get(index));
+ }
+ return col;
+ }
+
+
+
+ /**
+ * Get dimensions of Augmented matrix
+ * @return size()[0] - number of rows, size()[1] - number of columns
+ */
+ public int[] size() {
+ int[] size = new int[2];
+ size[0] = matrix.length;
+ // Consider that all rows have the same size, according to the class initialization
+ size[1] = matrix[0].size();
+ return size;
+ }
+
+
+}
diff --git a/src/solver/LinearEquationSolver.java b/src/solver/LinearEquationSolver.java
new file mode 100644
index 0000000..d2f5eda
--- /dev/null
+++ b/src/solver/LinearEquationSolver.java
@@ -0,0 +1,297 @@
+package solver;
+
+import java.text.DecimalFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+enum SystemType {
+ NO_SOLUTIONS,
+ SINGLE_SOLUTION,
+ INFINITE_NUMBER_OF_SOLUTIONS,
+ UNKNOWN_ERROR
+}
+
+public class LinearEquationSolver {
+ private AugmentedMatrix matrix;
+ private StringBuilder logs;
+ private String result;
+ private SystemType resultType;
+ private String decimalPattern = "#.#####";
+
+ LinearEquationSolver(AugmentedMatrix matrix) {
+ this.matrix = new AugmentedMatrix(matrix.getMatrixCopy());
+ this.logs = new StringBuilder();
+ }
+
+ /**
+ * It is step 1 of solving System of Linear Equations: transforming matrix to upper triangular form
+ */
+ private void transformToUpperTriangularForm() {
+ Row currentRow; double currentCoefficient;
+ Row nextRow; double nextCoefficient;
+ double multiplicator;
+ int n = matrix.size()[0]; // number of rows
+ int m = matrix.size()[1]; // number of cols
+ int rowNum, colNum;
+ // If there are no special cases, then rowNum = colNum (transforming to diagonal form)
+ for(rowNum = 0, colNum = 0; rowNum < n && colNum < m-1; rowNum++, colNum++) {
+ currentRow = matrix.getRow(rowNum);
+ currentCoefficient = currentRow.get(colNum);
+
+
+ // Search for the row with non-zero coefficent
+ int columnOffset = 0; boolean foundInColumn = false;
+ double coeff; int innerColNum = colNum;
+ while(currentCoefficient == 0 && innerColNum < m-1) {
+
+ // If a current coefficient is zero, then we should find row with non-zero coefficient below
+ if (currentCoefficient == 0) {
+ foundInColumn = false;
+ int nextRowNum;
+ for (nextRowNum = rowNum; nextRowNum < n; nextRowNum++) {
+ coeff = matrix.getRow(nextRowNum).get(innerColNum);
+ if (coeff != 0) {
+ // Found row with non-zero i-th coefficient
+ foundInColumn = true;
+ break;
+ }
+ }
+
+ if(foundInColumn) {
+ // We should swap rows
+ // These variables can be equal if previous column was filled with zeros,
+ // but on this column this row have non-zero coefficient
+ if(rowNum != nextRowNum) {
+ this.matrix.swapRows(rowNum, nextRowNum);
+ logMessage(String.format("R%d <-> R%d", rowNum, nextRowNum));
+ }
+ // If innerColNum != colNum -> we found non-zero coefficient in other column, we should swap columns
+ if(innerColNum != colNum) {
+ matrix.swapColumns(colNum, innerColNum);
+ logMessage(String.format("C%d <-> C%d", colNum, innerColNum));
+ }
+ // Update currentRow and currentCoefficient variables after all swaps
+ currentRow = matrix.getRow(rowNum); // rowNum after swap references to nextRowNum
+ currentCoefficient = currentRow.get(colNum);
+ break;
+ } else {
+ innerColNum++;
+ }
+
+ }
+
+ }
+
+ // If after all (full search of non-zero coefficient in whole matrix) we have zero-coefficient
+ // We should end up with matrix processing - matrix already transformed
+ if(currentCoefficient == 0) {
+ return;
+ }
+
+ // Make coefficient equal to one by dividing entire row by this coefficient
+ if(currentCoefficient != 1.0) {
+ currentRow.divide(currentCoefficient);
+ logMessage(String.format("R%d / %s -> R%d", rowNum, new DecimalFormat(decimalPattern).format(currentCoefficient), rowNum));
+ }
+
+ // Perform actions to zero coefficients in other rows
+ for(int j = rowNum + 1; j < n; j++) {
+ nextRow = matrix.getRow(j);
+ nextCoefficient = nextRow.get(colNum);
+ currentCoefficient = currentRow.get(colNum); // it must always be equal to one
+ // nextCoefficient should be zero
+ if(nextCoefficient != 0) {
+ multiplicator = nextCoefficient / currentCoefficient * (-1);
+ nextRow.add(currentRow.multiply(multiplicator, false));
+ logMessage(String.format("%s * R%d + R%d -> R%d", new DecimalFormat(decimalPattern).format(multiplicator), rowNum, j, j));
+ }
+ }
+ }
+ }
+
+ /**
+ * It is step 2 of solving System of Linear Equations: reduction of non-diagonal members of upper triangular form of matrix
+ */
+ private void reduceNonDiagonalCoefficients() {
+ // Start from the end
+ int rowNum, colNum, nextRowNum;
+ int n = matrix.size()[0];
+ int coefficientIndex = -1;
+ double currentCoefficient, nextCoefficient, multiplicator;
+ Row row; Row nextRow;
+ for(rowNum = n-1; rowNum >= 0; rowNum--) {
+
+ // Find first non-zero element
+ row = matrix.getRow(rowNum);
+ for(int i = 0; i < row.size()-1; i++) {
+ if(row.get(i) != 0) {
+ coefficientIndex = i;
+ break;
+ }
+ }
+
+
+ // If entire row filled with zeros - stop processing, go to the next iteration
+ if(coefficientIndex == -1) {
+ continue;
+ }
+
+ // Start subtracting this row from rows above
+ for(nextRowNum = rowNum - 1; nextRowNum >= 0; nextRowNum--) {
+ currentCoefficient = row.get(coefficientIndex);
+ nextRow = matrix.getRow(nextRowNum);
+ nextCoefficient = nextRow.get(coefficientIndex);
+ if (nextCoefficient != 0) {
+ multiplicator = nextCoefficient / currentCoefficient * (-1);
+ nextRow.add(row.multiply(multiplicator, false));
+ logMessage(String.format("%s * R%d + R%d -> R%d", new DecimalFormat(decimalPattern).format(multiplicator), rowNum, nextRowNum, nextRowNum));
+ }
+ }
+
+ }
+ }
+
+
+ /**
+ * It is step 3 of solving System of Linear Equations: checking for number of solutions
+ *
+ * In this method it is essential that matrix has a pseudo-diagonal form (after 1st and 2nd steps were completed)
+ *
+ * @return SystemType enum
+ */
+ private SystemType checkResult() {
+ // Checking for no solution
+ int rowNum, index;
+ int n = matrix.size()[0]; // number of rows
+ int m = matrix.size()[1]; // number of columns
+ Row currentRow;
+ for(rowNum = n-1; rowNum >=0; rowNum--) {
+ currentRow = matrix.getRow(rowNum);
+ if(currentRow.isZeroFilled()) {
+ continue; // Zero rows are insignificant for us
+ }
+
+ // Getting first non-zero element
+ index = currentRow.getIndexOfFirstNonZeroElement();
+
+ // If first non-zero element located in a last column - the system is inconsistent
+ if(index == m-1) {
+ return SystemType.NO_SOLUTIONS;
+ }
+
+ // Check for infinite number of solutions
+
+ // We look at last non-trivial row and get the matrix[i][j] first non-zero element, if j - is last column of
+ // non-augmented matrix - then we have single solution, if j - is not the last column of the non-augmented
+ // matrix - there is infinite number of solutions
+ // Example:
+ // 1 0 0 | 4 1 0 0 | 4
+ // 0 1 0 | 5 0 1 0 | 5
+ // 0 0 0 | 0 0 0 1 | 0
+ // 0 0 0 | 0 0 0 0 | 0
+ // INFINITE SOLUTIONS SINGLE SOLUTION
+ int cols = m -1; // number of columns of non-augmented matrix
+ if(index+1 != cols) {
+ return SystemType.INFINITE_NUMBER_OF_SOLUTIONS;
+ }
+
+ // All other ways were considered -> only one variant
+ // We have single solution
+ return SystemType.SINGLE_SOLUTION;
+ }
+
+ return SystemType.UNKNOWN_ERROR;
+ }
+
+ /**
+ * It is step 4 of solving System of Linear Equations: get original columns' order in matrix by looking at swap history
+ *
+ * Look at swap history and do reverse actions in desc order to get original column positions.
+ */
+ public void revokeColumnsSwap() {
+ int[] reversedSwapIndexes;
+ ArrayList swapHistory = matrix.getSwapHistory();
+ try {
+ for (int i = swapHistory.size() - 1; i >= 0; i--) {
+ reversedSwapIndexes = swapHistory.get(i).getReversedSwapInfo();
+ matrix.swapColumns(reversedSwapIndexes[0], reversedSwapIndexes[1], false);
+ logMessage(String.format("Reverse: C%d <-> C%d", reversedSwapIndexes[0], reversedSwapIndexes[1]));
+ }
+ matrix.clearSwapHistory();
+ } catch(NullPointerException e) {
+ // Ignore:
+ // It means that we haven't swapped any columns and swapHistory is not initialized yet
+ // (we do not initialize swapHistory to preserve memory, i know that in general this is possibly stupid =) )
+ return;
+ }
+ }
+
+ private void transformToDiagonalForm() {
+ transformToUpperTriangularForm();
+ reduceNonDiagonalCoefficients();
+ resultType = checkResult();
+ revokeColumnsSwap();
+ }
+
+ private void logMessage(String msg) {
+ // System.out.println(msg);
+ logs.append(msg);
+ logs.append("\n");
+ }
+
+ /**
+ * Get coefficients of single solution
+ *
+ * @return solution - array of coefficients
+ */
+ private double[] getSingleSolution() {
+ int n = matrix.size()[0]; // Number of rows
+ int m = matrix.size()[1]; // Number of columns of augmented matrix
+ double[] solution = new double[m-1];
+ int solutionIndex;
+ for(int rowNum = 0; rowNum < n; rowNum++) {
+ if(matrix.getRow(rowNum).isZeroFilled()) {
+ continue;
+ }
+
+ solutionIndex = matrix.getRow(rowNum).getIndexOfFirstNonZeroElement();
+ solution[solutionIndex] = matrix.getRow(rowNum).get(m-1);
+ }
+ return solution;
+ }
+
+ public String getLogs() {
+ return this.logs.toString();
+ }
+
+ public String getResult() {
+ return result;
+ }
+
+
+ public LinearEquationSolver solve() {
+ transformToDiagonalForm();
+ switch(resultType) {
+ case NO_SOLUTIONS:
+ result = "No solutions";
+ return this;
+ case INFINITE_NUMBER_OF_SOLUTIONS:
+ result = "Infinite solutions";
+ return this;
+ case SINGLE_SOLUTION:
+ double[] res = getSingleSolution();
+ StringBuilder sb = new StringBuilder();
+ for(double coefficient: res) {
+ sb.append(new DecimalFormat(decimalPattern).format(coefficient));
+ sb.append("\n");
+ }
+ sb.deleteCharAt(sb.length()-1);
+ result = sb.toString();
+ }
+ return this;
+ }
+
+ public AugmentedMatrix getMatrix() {
+ return matrix;
+ }
+}
diff --git a/src/solver/Main.java b/src/solver/Main.java
index 4404714..74b9ca1 100644
--- a/src/solver/Main.java
+++ b/src/solver/Main.java
@@ -1,7 +1,106 @@
package solver;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.InputMismatchException;
+import java.util.Scanner;
+
public class Main {
+
+ static String getHelp() {
+ String helpMsg = "This program is intended to solve Linear Equations with any amount of variables using Gauss-Jordan Elimination. \n\n" +
+ "Options: " +
+ "\n\t * -in [pathToFile] reads file with the first number - number of equations and variables, and augmented matrix of linear equation" +
+ "\n\t * -out [pathToFile] writes results to the output file " +
+ "\n\t * -h shows help information " +
+ "\n\nExample: \njava Solver -in in.txt -out out.txt" +
+ "\n\nExample of in.txt:\n" +
+ "3\n" +
+ "1 1 2 9\n" +
+ "2 4 -3 1\n" +
+ "3 6 -5 0";
+ return helpMsg;
+ }
+
+ /**
+ * Program should start with necessary options -in [pathToFileWithMatrix] and -out [pathToOutputFile]
+ * Example: > java Solver -in in.txt -out out.txt
+ * @param args
+ */
public static void main(String[] args) {
- System.out.print("Hello world!");
+ // Getting and handling console parameters
+ String inputFile="";
+ String outputFile="";
+ for(int i = 0; i < args.length; i++) {
+ try {
+ switch (args[i]) {
+ case "-in":
+ inputFile = args[i + 1];
+ break;
+ case "-out":
+ outputFile = args[i + 1];
+ break;
+ case "-h" :
+ System.out.println(getHelp());
+ return;
+
+ }
+ } catch (ArrayIndexOutOfBoundsException e) {
+ System.err.println("Invalid command line options, use '-h' option to see help");
+ return;
+ }
+ }
+
+ if(inputFile.isEmpty() || outputFile.isEmpty()) {
+ System.err.println("Invalid command line options, use '-h' option to see help");
+ return;
+ }
+
+ System.out.println("Input File: " + inputFile + "\nOutput File: " + outputFile);
+
+ // Working with input file
+ File file = new File(inputFile);
+ AugmentedMatrix matrix = new AugmentedMatrix();
+ try(Scanner fileScanner = new Scanner(file);) {
+ matrix.readMatrix(fileScanner);
+ } catch(FileNotFoundException e) {
+ System.out.println("File: " + file.getAbsolutePath() + " doesn't exist");
+ return;
+ } catch (InputMismatchException e) {
+ System.err.println("Invalid structure of input file: " + file.getAbsolutePath());
+ return;
+ }
+
+ if(matrix.size()[0] == 0 || matrix.size()[1] == 0) {
+ System.err.println("Invalid structure of input file: " + file.getAbsolutePath());
+ return;
+ }
+
+ // Input matrix output
+ System.out.println("Input matrix:");
+ System.out.println(matrix);
+
+
+ // Solving equation
+ System.out.println("Start solving linear equation.");
+ LinearEquationSolver solver = new LinearEquationSolver(matrix);
+ String res = solver.solve().getResult();
+ System.out.println("Rows manipulation:");
+ System.out.println(solver.getLogs());
+
+ System.out.println("Resulting matrix:");
+ System.out.println(solver.getMatrix());
+
+ System.out.println("The solution is: (" + res.replaceAll("\n", ", ") + ")");
+
+ // Output writing
+ try(FileWriter fileWriter = new FileWriter(outputFile)) {
+ fileWriter.write(res);
+ System.out.println("Saved to " + outputFile);
+ } catch(IOException e) {
+ System.err.println("Result cannot be written to the output file, please try other output file");
+ }
}
}
\ No newline at end of file
diff --git a/src/solver/Row.java b/src/solver/Row.java
new file mode 100644
index 0000000..6143ea2
--- /dev/null
+++ b/src/solver/Row.java
@@ -0,0 +1,106 @@
+package solver;
+
+import java.util.Arrays;
+
+public class Row {
+ private double[] row;
+
+ Row(double[] row) {
+ setRow(row);
+ }
+
+ Row(int m) {
+ row = new double[m];
+ }
+
+
+ Row divide(double v) {
+ if(v == 0){
+ throw new java.lang.ArithmeticException("Division by zero");
+ }
+ for(int i = 0; i < row.length; i++) {
+ row[i] = row[i] / v;
+ }
+ return this;
+ }
+
+ Row multiply(double v, boolean inplace) {
+ Row curRow;
+ if(inplace) {
+ curRow = this;
+ } else {
+ curRow = new Row(row.length);
+ }
+ for(int i = 0; i < row.length; i++) {
+ curRow.set(i, row[i] * v);
+ }
+ return curRow;
+ }
+
+ Row multiply(double v) {
+ return multiply(v, true);
+ }
+
+ Row subtract(Row row) {
+ for(int i = 0; i < this.row.length; i++) {
+ this.row[i] = this.row[i] - row.get(i);
+ }
+ return this;
+ }
+
+ Row add(Row row) {
+ return add(row, true);
+ }
+
+ Row add(Row nextRow, boolean inplace) {
+ Row curRow;
+ if(inplace) {
+ curRow = this;
+ } else {
+ curRow = new Row(row.length);
+ }
+ for(int i = 0; i < row.length; i++) {
+ curRow.set(i, curRow.get(i) + nextRow.get(i));
+ }
+ return curRow;
+ }
+
+ public double[] getRow() {
+ return Arrays.copyOf(row, row.length);
+ }
+
+ public void setRow(double[] row) {
+ this.row = Arrays.copyOf(row, row.length);
+ }
+
+ public void set(int j, double value) {
+ this.row[j] = value;
+ }
+
+ public double get(int j) {
+ return this.row[j];
+ }
+
+ public boolean isZeroFilled() {
+ for(int i = 0; i < row.length; i++) {
+ if( row[i] != 0) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public int getIndexOfFirstNonZeroElement() {
+ for(int i = 0; i < row.length; i++) {
+ if(row[i] != 0 ) {
+ return i;
+ }
+ }
+
+ return -1;
+ }
+
+ public int size() {
+ return this.row.length;
+ }
+}
diff --git a/src/solver/SwapInfo.java b/src/solver/SwapInfo.java
new file mode 100644
index 0000000..7c65dc2
--- /dev/null
+++ b/src/solver/SwapInfo.java
@@ -0,0 +1,19 @@
+package solver;
+
+public class SwapInfo {
+ private int prevIndex;
+ private int nextIndex;
+
+ SwapInfo(int prevIndex, int nextIndex) {
+ this.prevIndex = prevIndex;
+ this.nextIndex = nextIndex;
+ }
+
+ public int[] getSwapInfo() {
+ return new int[]{prevIndex, nextIndex};
+ }
+
+ public int[] getReversedSwapInfo() {
+ return new int[]{nextIndex, prevIndex};
+ }
+}