diff --git a/src/arcade/core/util/MatrixArray.java b/src/arcade/core/util/MatrixArray.java new file mode 100644 index 000000000..c764710c2 --- /dev/null +++ b/src/arcade/core/util/MatrixArray.java @@ -0,0 +1,166 @@ +package arcade.core.util; + +import java.util.ArrayList; +import java.util.Collections; +import arcade.core.util.Matrix.Value; + +/** + * Container class for array-based sparse matrix representation. + * + *
Class provides a subset of matrix operations needed for solving a system of linear equations + * using the successive over-relaxation method in {@link arcade.core.util.Solver}. + */ +public class MatrixArray { + /** Number of rows in the matrix. */ + int nRows; + + /** Number of columns in the matrix. */ + int nColumns; + + /** List of non-zero values in the matrix. */ + Value[] values; + + /** + * Constructs a sparse matrix from a dense two-dimensional array. + * + *
All non-zero elements of the input matrix are stored in the internal sparse
+ * representation. The input matrix must be rectangular; that is, every row must have the same
+ * number of columns.
+ *
+ * @param a the dense matrix to convert
+ * @throws IllegalArgumentException if the matrix is empty, contains empty rows, or is not
+ * rectangular
+ */
+ public MatrixArray(double[][] a) {
+ nRows = a.length;
+ if (nRows == 0) {
+ throw new IllegalArgumentException("MatrixArray ctor: input is empty");
+ }
+ nColumns = a[0].length;
+
+ if (nColumns == 0) {
+ throw new IllegalArgumentException("Matrix array ctor: empty columns");
+ }
+
+ ArrayList The entries are sorted by row and column index and copied into an internal array to
+ * support efficient sparse matrix operations such as matrix-vector multiplication.
+ *
+ * @param alValues the non-zero matrix entries
+ * @param rows the number of rows in the matrix
+ * @param columns the number of columns in the matrix
+ */
+ public MatrixArray(ArrayList The matrix is stored in sparse form, so only non-zero entries are processed during the
+ * multiplication.
+ *
+ * @param b the vector to multiply by
+ * @return the resulting vector {@code A * b}
+ * @throws IllegalArgumentException if the vector length is not compatible with the matrix
+ * dimensions
+ */
+ public double[] multiply(double[] b) {
+ if (b.length != nRows) {
+ throw new IllegalArgumentException("MatrixArray.multiply (by a vector): conformation");
+ }
+ double[] multiplied = new double[nRows];
+
+ // Iterate through all entries and multiply.
+ for (Value a : values) {
+ multiplied[a.i] += a.v * b[a.j];
+ }
+
+ return multiplied;
+ }
+}
diff --git a/src/arcade/core/util/Solver.java b/src/arcade/core/util/Solver.java
index 019aefff8..672b24344 100644
--- a/src/arcade/core/util/Solver.java
+++ b/src/arcade/core/util/Solver.java
@@ -1,7 +1,6 @@
package arcade.core.util;
import java.util.ArrayList;
-import java.util.logging.Logger;
import arcade.core.util.Matrix.Value;
import static arcade.core.util.Matrix.*;
@@ -19,8 +18,6 @@
*
*/
public class Solver {
- /** Logger for {@code Solver}. */
- private static final Logger LOGGER = Logger.getLogger(Solver.class.getName());
/** Error tolerance for Cash-Karp. */
private static final double ERROR = 1E-5;
@@ -38,13 +35,13 @@ public class Solver {
private static final double OMEGA = 1.4;
/** Maximum number of iterations. */
- private static final int MAX_ITERS = 10000;
+ private static final int MAX_ITERS = 20000;
/** Error tolerance for SOR. */
- private static final double TOLERANCE = 1E-8;
+ private static final double TOLERANCE = 1E-5;
/** Convergence delta for bisection method. */
- private static final double DELTA = 1E-5;
+ private static final double DELTA = 1E-6;
/** Matrix size threshold for dense representation. */
private static final int MATRIX_THRESHOLD = 100;
@@ -370,7 +367,6 @@ private static double[] denseSOR(
double[] r = subtract(vec, multiply(mat, xCurr));
error = normalize(r);
}
-
return xCurr;
}
@@ -396,6 +392,7 @@ private static double[] sparseSOR(
double[] c = forwardSubstitution(sparseA, vec);
ArrayList Root is found by repeatedly bisecting the interval and selecting the interval in which the
* function changes sign. If no root is found, the simulation will throw an ArithmeticException.
@@ -480,6 +478,38 @@ public static double bisection(Function func, double a, double b, int maxIters)
* @return the root of the function
*/
public static double bisection(Function func, double a, double b) {
- return bisection(func, a, b, MAX_ITERS);
+ return bisection(func, a, b, MAX_ITERS, DELTA);
+ }
+
+ /**
+ * Finds root using bisection method with default maximum iterations.
+ *
+ * Root is found by repeatedly bisecting the interval and selecting the interval in which the
+ * function changes sign. If no root is found, the simulation will throw an ArithmeticException.
+ *
+ * @param func the function
+ * @param a the lower bound on the interval
+ * @param b the upper bound on the interval
+ * @param delta the error tolerance
+ * @return the root of the function
+ */
+ public static double bisection(Function func, double a, double b, double delta) {
+ return bisection(func, a, b, MAX_ITERS, delta);
+ }
+
+ /**
+ * Finds root using bisection method with default tolerance.
+ *
+ * Root is found by repeatedly bisecting the interval and selecting the interval in which the
+ * function changes sign. If no root is found, the simulation will throw an ArithmeticException.
+ *
+ * @param func the function
+ * @param a the lower bound on the interval
+ * @param b the upper bound on the interval
+ * @param maxIters the maximum number of iterations
+ * @return the root of the function
+ */
+ public static double bisection(Function func, double a, double b, int maxIters) {
+ return bisection(func, a, b, maxIters, DELTA);
}
}
diff --git a/test/arcade/core/util/SolverTest.java b/test/arcade/core/util/SolverTest.java
index 81dcd7ef5..da07ea396 100644
--- a/test/arcade/core/util/SolverTest.java
+++ b/test/arcade/core/util/SolverTest.java
@@ -236,4 +236,21 @@ public void testBisection_quadraticFunctionAndSwappedInputs_returnsAnswer() {
assertEquals(1.41421, result, 0.0001);
}
+
+ @Test
+ public void testBisection_exceedsMaxIterations_returnsNaN() {
+ Function f = (x) -> x * x - 2;
+ double result = Solver.bisection(f, 2, 0, 2);
+
+ assertEquals(Double.NaN, result, 0.001);
+ }
+
+ @Test
+ public void testBisection_givenPrecision_returnsAnswersWithinPrecision() {
+ Function f = (x) -> x * x - 2;
+ double result = Solver.bisection(f, 2, 0, 1E-3);
+
+ assertEquals(1.41421, result, 1E-3);
+ assertNotEquals(1.41421, result, 1E-4);
+ }
}